文章目录
- 原生JS
-
- (1)什么是`JavaScript`?
- (2) 简单介绍一下`JS`基本类型
- (3) 字符串如何转换成布尔类型?
- (4) 数值类型是如何转换布尔类型
- (5) 字符串是如何转换成数值的?
- (6) 都了解过哪些运算符?
- (7) `||`和`??`运算符区别是什么?
- (8) 给变量赋值的方式有哪些?
- (9) 变量的命名有什么规范?
- (10) 你了解过预解析吗?
- (11) let、var、const区别?
- (12) 谈谈你对命名空间和作用域的理解?
- (13) 简单谈一下作用域链?
- (14) 都有哪些作用域?
- (15) 你是怎么理解if-else的?
- (16) continue和break的区别?
- (17) 如何跳出forEach()循环?
- (18) 数组中常见的函数
- (19)观察代码,筛选符合条件的数据
- (20) 观察代码,按照需求转换合理的数据
- (21) 数组中数据剔重
- (22) Array.of()和new Array()
- (23) 数组如何深拷贝?
- (24) NaN == NaN的结果?
- (25)null和undefined的区别?
- (26) 请说明常见的字符串的操作函数
- (27) 请说明Math中常用的操作函数
- (28) 请说明Date中常用的操作函数
- (29)请简述bind、call、apply三个函数的区别
- (30)如何获取数组中的最大值
- (31)如何判断一个变量的类型?
- (32)简述函数形式参数和实际参数区别
- (33) 常见函数声明方式
- (34) 简述一下函数的预解析
- (35) 观察下面的代码说明输出结果以及为什么
- (36) 观察下面的代码,说明输出结果以及为什么
- (37) 简述什么是匿名函数
- (38) 说说你对函数递归的理解
- (39) 什么是自执行函数
- (40) 什么是闭包?项目中如何使用?
- (41) 什么是回调函数?
- (42) 说说对全局污染的理解
- (43)简单说说你是怎么理解BOM
- (44) 你是怎么解决浏览器兼容性问题
- (45) 获取页面标签节点有几种方式
- (46)你是怎么理解DOM
- (47) 如何操作标签节点
- (48) 如何操作标签属性
- (49) 如何操作标签内容
- (50) 如何操作标签样式
- (51) 什么是面向对象
- (52)你是怎么理解原生JS面向对象
- (53)原生JS中怎么实现继承关系
- (54)原生JS中怎么实现继承关系
- (55) 什么是Ajax
- (56) 异步请求底层实现有哪些
- (57) 原生JS Ajax实现步骤
- (58) 什么是跨域?如何解决跨域?
- JS高级
-
- (1) 什么是Promise
- (2) Promise有几种状态
- (3) Promise有哪些执行方法
- (4) 如何同时执行多个异步任务
- (5) aysnc和await
- (6) 回流reflow、重绘repaint
- (7) new一个对象的过程
- (8) 常驻内存、内存泄漏、内存溢出
- (9) 防抖、节流
- (10) 函数柯里化
- (11) 常见排序算法
- (12) JS实现冒泡排序
- (13) 什么是JS事件循环
- (14) 宏任务、微任务,简述执行顺序
- (15) 简述JS垃圾回收机制
- (16) eval的作用
- (17) 本地存储有哪些方式,说说它们的区别
- (18) 栈内存和堆内存
- (19) 栈和队列
- (20) 数组和链表
- (22) 写一个单例模式
- ES6
- Git 基础
- Git高级
- Node.js
- vue2.x
-
- (1) 什么是Vue
- (2) Vue的两个核心
- (3) 简述常见的指令及其含义
- (4) v-if和v-show的区别
- (5) v-for中key的作用
- (6) Vue实例 el 选项的作用
- (7) Vue实例 data选项的作用
- (8) Vue实例 methods选项的作用
- (9) Vue实例 watch选项的作用
- (10) Vue实例 computed选项的作用
- (11) Vue实例 filters选项的作用
- (12) Vue实例 components选项的作用
- (13) Vue实例 name选项的作用
- (14) Vue实例 常见生命周期
- (15) created()、mounted()区别
- (16) 组件页面加载的时候触发哪些生命周期钩子
- (17) 监听器和普通函数的区别
- (18) 计算属性和普通函数的区别
- (19) 如何监听对象的属性
- (20) 如何让监听器开始时立即调用
- (21) 计算属性名称可以和data中的数据重名吗
- (22) 简述父子组件传值的方式
- (23) 什么是插槽,插槽的作用
- (24) 什么是边界传值/操作
- (25) 什么是代理注入传值
- (26) 如何理解Vue单向数据流
- (27) Vue组件的data为什么必须是函数
- (28) 怎么实现组件的缓存
- (29) 组件缓存对应的生命周期有哪些
- (30) Vue如何给标签/组件绑定事件
- (31) 常见的事件修饰符及其作用
- (32) 常见的按键修饰符及其作用
- (33) 常见的系统修饰符及其作用
- (34) 什么是混入mixin,有什么作用
- (35) 如何设置通用样式,如何设置组件样式
- (36) 什么是路由
- (37) 项目中vue-router版本对应关系
- (38) Vue项目中怎么配置路由
- (39) 编程式导航常用的方法
- (40) vue-router如何实现页面跳转
- (41) 如何实现导航高亮
- (42)路由重定向和别名的区别
- (43) 路由路径是如何匹配的,出现冲突如何解决
- (44) 什么是命名路由,有什么作用
- (45) 什么是命名视图,有什么作用
- (46) 路由如何传递查询字符串参数
- (47) 路由如何传递动态路径参数
- (48)` $route`、`$router`的区别
- (49) 如何实现路由嵌套
- (50) 什么是路由元数据
- (51) 如何监听页面滚动行为
- (52) 什么是导航守卫
- (53) 常见的导航守卫
- (54) 导航守卫、拦截器的区别
- (55) Vuex中的5个核心属性
- (56) Vuex解决了哪些问题
- (57) 简述Vuex中数据传递流程
- (58) mutations和actions区别
- (59) 辅助函数mapState/mapMutations/mapActions/mapGetters
- (60) Vuex模块化开发的构建
- (61) Vuex模块中namespaced的作用
- (62) 什么是axios
- (63) jQuery、axios的区别
- (64) axios的请求配置
- (65) axios的响应数据
- (66) axios拦截器
- (67) 如何取消请求
- (68) 用过的视图组件库有哪些
- (69) 项目中如何添加element-ui
- vue高级
-
- (1) 什么是MVVM
- (2) MVVM和MVC的区别
- (3) 什么是SPA,优缺点是什么
- (4) 什么是虚拟DOM
- (5) 虚拟DOM如何提高优化效率
- (6) v-model的底层实现原理
- (7) 数据双向绑定底层实现原理
- (8) nextTick()底层实现原理
- (9) Object.defineProperty()和Proxy()区别
- (10) 简述自定义组件的封装过程
- (11) 为什么需要避免v-if和v-for一起使用
- (12) vue-loader的作用
- (13) 如何触发Vue的强制更新
- (14) 数组哪些操作可以触发页面更新,哪些不可以,有什么解决方案
- (15) 父子组件嵌套时各自生命周期执行顺序
- (16) 父组件如何操作子组件DOM数据
- (17) 什么是组件递归调用
- (18) Vue页面渲染如何保留模板中的注释
- (19) Vue2.0兼容IE那个版本
- (20) Vue为什么首页加载缓慢
- (21) 如何做首屏加载优化
- (22) Vuex中strict属性的作用
- (23) 为什么mutations中不能做异步操作
- (24) 什么是SSR,优缺点是什么
- (25) Vue2、Vue3区别
- (26) 项目中如何管理导入依赖,说说自动导入的优缺点
- (27) 数组列表渲染,如果只更新单个数据,数组是否会重新渲染
- (28) 简单阐述一些Vue页面加载过程
- (29) 说明一下你理解的diff算法
- (30) 你都用过哪些代码检查工具
- <font color = Orange>## ***···以上来自博主老师的总结,我只是知识的搬运工***
原生JS
(1)什么是JavaScript
?
JavaScript是一个弱类型的、支持面向过程和面向对象编程的,主要工作在浏览器一侧给网页提供动态功能的、解释型的编程语言;
在发展过程中JavaScript也可以基于Node环境开发服务端应用,是很多前后端框架的底层实现技术!
(2) 简单介绍一下JS
基本类型
原生JS中基本类型包括
字符串String
数值类型Number、
布尔类型Boolean
空值类型Null、
未声明类型Undefined
ES6中提供了新的唯一值类型Symbol
拓展
面试官可能会追问:
Object
算不算基本类型? | 还有其他的吗?原生JS中面向对象编程的理念是一切皆对象,有些资料里面也会把Object当成基本类型对待
(3) 字符串如何转换成布尔类型?
字符串可以和
布尔类型
进行转换,空字符串
转换结果是false
,非空字符串
转换结果是true
在项目中一般对字符串的有效值判断直接放在if
条件中进行隐式
判断
const response = getListJson()
// 获取接口字符串数据
if(response.data) {
// data存储的字符串格式的json数据
…有效数据
} else {
… 无效数据,提示错误
}
(4) 数值类型是如何转换布尔类型
数值可以转换成布尔类型,0的转换结果是false,非0转换结果true
项目中大部分场景中主要做非0判断,可以直接将数值放在if条件中进行隐式判断,或者可以和逻辑运算符结合起来进行默认值赋值处理
const price = goodsPrice || 1
一部分场景中0也属于有效数据,可以借助??运算符进行赋值和判断处理
const price = goodsPrice ?? 1
(5) 字符串是如何转换成数值的?
其他问法:parseInt('111', 2)
转换结果是什么?
数值可以是字符串形式的,所以字符串类型的数字可以转换成数值
parseInt()
可以将字符串数字转换成整数
parseFloat()
可以将字符串数字转换成浮点数
如果字符串包含字母和数字,数字开头的话转换结果截取数字部分;如果字母开头转换结果NaN
parseInt()
转换的时候可以指定进制单位,如parseInt('111', 2)
当成2进制转换结果是7
(6) 都了解过哪些运算符?
原生JS常用的运算符
赋值运算符
、算术运算符
、比较运算符
、逻辑运算符
、三元/三目运算符
一些算法结构中为了提高性能会用到位运算符
(7) ||
和??
运算符区别是什么?
常规声明变量的时候如果没有赋值就会默认存储undefined
变量默认值赋值一般出现在函数或者数据处理流程中,使用||和??进行默认值赋值
||
赋值时会将空字符串和0
这样的数据当成无效数据,赋值指定默认数据
??
赋值时只会判断null和undefined
当成无效数据,赋值指定默认数据
(8) 给变量赋值的方式有哪些?
其他问法:你项目中是怎么给变量做初始化处理的?10%概率
变量的声明和赋值方式有不同的形式
- 标准方式可以直接声明变量并且赋值数据,
let a = 12; let a = 10, b = 12;
- 也可以先声明变量
(let a;)
,需要的时候赋值数据(a=11)
,赋值数据之前变量中存储的是undefined
- 如果需要多个相同数据的变量,可以进行连续赋值,
let a = b = c = 1
(9) 变量的命名有什么规范?
其他问法:你们项目组变量是什么命名的? 5%概率
变量语法规则是
字母
、数字
、下划线
、$符号
组成,数字
不能开头
我们项目中要求变量尽量做到使用英文单词做到见名知意,使用小驼峰命名法进行命名处理
(10) 你了解过预解析吗?
其他问法:你知道什么是变量提升吗? 70%概率
原生JS中使用var声明的变量,存在预解析处理操作
声明的变量在当前作用域中会将变量的声明部分提前到最前面,赋值部分保留在原位置
,也被称为变量声明提升
主要作用就是为了保障当前作用域中使用这个变量不会报错,提高代码的容错性
(11) let、var、const区别?
其他问法:原生JS中var声明变量和ES6中let、const声明变量有什么区别?80%概率
var
声明变量是原生JS中的声明方式,主要区分声明全局变量
和局部变量
,存在变量预解析
let
和const
都是ES6中提供的用于声明变量和常量的方式,是对var
的补充
let
声明的变量存在块级作用域、不能重复声明,没有变量预解析
const
主要用于声明常量
(12) 谈谈你对命名空间和作用域的理解?
其他问法:命名空间和作用域的区别?80%概率
命名空间是包含了当前作用域范围中的所有
变量
、函数
、类型
等数据的空间!
(所有数据)
作用域是某个变量、某个函数或者某个数据可以被访问的范围!
(单个数据)
(13) 简单谈一下作用域链?
其他问法:一个变量是怎么被访问到的?50%概率
每个变量可以被访问的范围是它的作用域,原生JS中
不同的作用域之间形成作用域链
变量的访问顺序是按照作用域链的过程被访问,首先读取当前作用域中的变量,如果没有该变量的声明就会继续访问上一级作用域,如果所有作用域中都没有访问到该变量就会报错提示变量没有定义!
(14) 都有哪些作用域?
其他问法:什么是作用域链 ?50%概率
每个变量可以被访问的范围是它的作用域,原生JS中不同的作用域之间形成作用域链
从大到小依次是环境作用域、系统作用域、全局作用域、局部作用域、嵌套/[闭包]作用域
(15) 你是怎么理解if-else的?
其他问法:你项目中如果出现了大量条件分支,你是怎么使用if-else的? 10%概率
项目中如果出现的分支并不是很多,可以直接使用
if-else
的多分支结构进行判断处理即可,我自己的项目中一般要求6个分支以内使用多分支结构
如果分支数量比较庞大,为了提高代码的可读性,同时降低代码的重复率可以借助数组或者对象和if双分支结构进行语法优化
const role = “会员” // 游客、管理员
if(role === “会员”) {…}
else if(role === “管理员”){…}
else if(role === “游客”) {…}
else {…}
const level = {“青铜1”: […], “青铜2”: […], …, “王者22星”: […]}
const info = level[“王者11星”]
if(info){
…
} else {
…
}
(16) continue和break的区别?
continue
和break
都可以出现在循环结构中
continue
终止本次循环直接开始下一次循环
break
直接跳出循环
break还可以出现在switch-case中用于结束一个分支
(17) 如何跳出forEach()循环?
其他问法:forEach()
循环结构可以使用break
吗? 10%概率
break
只能跳出循环结构
forEach()是一个函数无法使用break完成跳出循环的操作
forEach()`函数参数是一个闭包函数,也不能使用return跳出循环
forEach()满足跳出循环的条件时抛出异常,外部代码中使用
try-catch
捕获异常的方式跳出循环
let jobs = [“需求工程师”, “开发工程师”,…]// 1、无法跳出
jobs.forEach(item => {
…
break; 无法跳出循环,报错~break无法使用在switch和循环结构之外的其他地方
return; 无法跳出循环,只会结束本次循环闭包函数直接开始下一次循环,不会报错
})
// 2、跳出
try{
jobs.forEach(item => {
…
if(满足条件) {
throw new Error(“自定义错误”)
}
})
} catch(e) {
捕获错误,代码继续向下执行
}
(18) 数组中常见的函数
其他问法:请说出至少5个数组的操作函数,65%概率
push() / pop()
unshift() / shift()
forEach() / map() / filter()
indexOf()/lastIndexOf()
find()/findLast()
findIndex()/findLastIndex()
include()
startsWith()/endsWith()
match()/search()
reverse() / sort()
…
(19)观察代码,筛选符合条件的数据
const accounts = [“admin”, “manager”, “tom123”, “jerry”, “shuke_123”]
// 需求:保留账号长度在6~18位的账号
//考察:filter()
accounts = accounts.filter(item => { return item.length >= 6 && item.length <= 18 })
(20) 观察代码,按照需求转换合理的数据
const accounts = [“admin”, “manager”, “tom123”, “jerry”, “shuke_123”]
// 需求:将上述账号统一加上部门前缀-dept
// 考察:map()
accounts = accounts.map(item => { return dept${item} })
(21) 数组中数据剔重
其他问法:请将数组中重复的数据剔重 ;65%概率(笔试题)
const accounts = [“admin”, “manager”, “admin”, “jerry”, “jerry”]
// 需求:将上述代码中的重复数据剔除
// 考察:代码逻辑思考能力、新技术点掌握能力
let newAccounts = new Set(accounts)
// ES6 Set类型
accounts = Array.from(newAccounts)
// ES6 Array.from()类型转换
xxxxxxxxxx // 使用普通for循环,可以|(除非要求使用底层代码实现)否则不推荐
function uniqueArr(arr) {
let newArr = []
for(var i = 0; i < arr.length ; i++) {
if(!newArr.includes(item)) {
newArr.push(item)
}
}
return newArr
}
accounts = uniqueArr(accounts)
(22) Array.of()和new Array()
其他问法:原生JS创建数组的对象语法有什么问题?如何处理? 20%概率
原生JS中可以使用字面量创建数组,可以使用new对象语法创建数组
new Array()
创建数组时如果数组中只包含一个整数数据,会创建一个包含多个空数据的数组
ES6中提供了Array.of()封装了对象语法创建数组的方式,优化了原生JS数组对象创建的问题
let arr1 = [2] // 结果:[2]
let arr2 = new Array(2)
// 结果:[undefined, undefined]
let arr3 = Array.of(2)
// 结果:[2]
(23) 数组如何深拷贝?
其他问法:深浅拷贝的区别?70%概率
数据的基本复用方式主要有三种,引用赋值、浅拷贝和深拷贝
浅拷贝用于复制目标数据,但是如果目标数据是数组或者对象,存储的数据或者属性中包含内存地址就会直接复制内存地址,所以复制前后如果操作内部包含地址的数据,被复制和复制的数据都会受到影响
深拷贝可以完整的复制目标数据,包括内部数据如果包含内存地址就会找到内存地址存储的数据进行复制;所以复制前后操作任何数据,被复制和复制的数据不会互相影响
(24) NaN == NaN的结果?
其他问法:请描述NaN是什么类型?70%概率
false
NaN语义是 Not A Number,表示不是一个数字,属于一种特殊的数值类型!每个NaN都是唯一的!
(25)null和undefined的区别?
其他问法:null == undefined
的结果是什么?为什么?70%概率
null和undefined在语法上都可以用于描述无效数据,底层表示数据的二进制开头数值一致,所以
null==undefined返回结果true
,项目中经常用于变量中存储有效数据的判断
undefined一般声明普通变量并且不赋值时的默认数据,使用Number()转换结果是NaN,转换布尔类型是false
null一般是用于获取对象数据时没有数据会得到null,使用Number()转换结果是0,转换布尔类型是false
(26) 请说明常见的字符串的操作函数
其他问法:请写出至少5个字符串的操作函数及其含义 60%概率
大小写转换函数 toLowerCase()、toUpperCase()
索引查询函数 indexOf()、lastIndexOf()
字符串截取 substr(start, length)、substring(start, end)、slice(start, end)
字符串拆分 split(ch)
字符串搜索替换 match()、search()、replace()、replaceAll()
字符串判断 startsWith()、endsWith()、includes()
剔除空格 trim() 、trimLeft()/trimStart()、trimRight()/trimEnd()
字符补全 padStart()、padEnd()
其他函数 charAt()、charCodeAt()、codePointAt()…
(27) 请说明Math中常用的操作函数
其他问法:Math.ceil()、Math.floor()、Math.round()、Math.trunc()
之间的区别
Math.ceil() 数据向上取整
Math.floor() 数据向下取整
Math.round() 四舍五入
Math.trunc() 整数截断
Math.random() 随机数
Math.max()/min() 获取最大/最小值
Math.pow() 指数函数
Math.log() 对数函数
Math.sin()/cos()… 三角函数
(28) 请说明Date中常用的操作函数
其他问法:如何获取两个时间点之间的差值?如何计算距离目标某一天的时间?
new Date() 获取当前系统时间
new Date(time) 指定时间创建一个日期对象
getFullYear() 获取年份
getMonth() 获取0~11月份
getDate() 获取一个月中的第几天()
getDay() 获取一周中的第几天()
getHours() 获取小时
getMinutes() 获取分钟
getSeconds() 获取秒钟
getMilliseconds() 获取毫秒
getTime() 获取1970年0点到现在毫秒数
setFullYear() 设置年份
…
(29)请简述bind、call、apply三个函数的区别
其他问法:call、apply
有什么差异?项目中有没有使用过? 概率80%
bind、call、apply都是用于辅助执行目标函数的辅助函数,用于在执行目标函数过程中修改函数内this指向
bind()给目标函数绑定一个新的this指向并且返回函数声明,不会立即执行函数;如React中绑定无参事件函数
call()函数给目标函数绑定一个新的this指向,接受数据序列作为函数参数,并且立即执行函数
apply()函数给目标函数绑定一个新的this指向,接受数组作为函数参数,并且立即执行函数
function show(…args){
console.log(this)
console.log(args)
}
let obj = {}
// 使用bind,不会立即执行函数
show.bind(obj, “vue”)
// 使用bind并立即执行函数
show.bind(obj, “vue”)()
// 使用call,立即执行函数
show.call(obj, “vue”, “vue3”)
// 使用apply,立即执行函数
show.apply(obj, [‘html’, “css”])
(30)如何获取数组中的最大值
给定一个数组
const arr = [1,2,4,4,4,6,65,56,3,234,23,2,45,45,5,656,76,78]
需求:获取数组中的最大值、最小值
1. 使用普通for循环
function getMax(arr){
// 声明一个变量,存储最大数据
let max = 0
for(var i = 0; i < arr.length; i++) {
if(max < arr[i]) {
max = arr[i]
}
}
return max
}
2. 借助辅助函数
let max = Math.max.apply(null, arr)
let max = Math.max.call(null, …arr)
3 直接查询最大值
let max = Math.max(…arr)
(31)如何判断一个变量的类型?
其他问法:请描述typeof(dat)
和typeof dat
的区别?
项目查询变量的数据类型,一般使用typeof类型判断函数进行获取和判断
typeof的语法格式,包含函数形式和表达式形式两种格式
- typeof() 函数形式在获取类型时使用较多
- typeof dat表达式格式在条件判断时使用较多
(32)简述函数形式参数和实际参数区别
其他问法:形参、实参
形式参数简称形参,一般在声明函数的括号中包含,用来表示当前函数执行时需要的数据!
- 声明位置:函数声明的括号中
- 数据格式:描述了变量,没有具体数据
- 作用:描述函数执行需要哪些数据
实际参数简称实参,一般调用执行函数式传递给函数的具体数据
- 声明位置:调用执行函数时,函数括号中的具体数据
- 数据格式:包含数据类型和值的具体数据
- 作用:传递给函数代码运算的数据
(33) 常见函数声明方式
我自己项目中常用的函数声明方式主要有两种
使用function关键字函标准语法声明
- function show() {…}
使用表达式变量赋值语法声明函数- let show = function() {…}
注意:关于Function
关键字
有些情况下面试官可能会追问,你是否了解过Function
对象,用它操作过函数没有?
Function对象是原生JS中所有类型基础类型,原生JS中所有的函数或者类型都是直接或者间接从Function对象衍生出来的
所以可以使用Function对象声明函数,操作方式过于底层,开发中没有必要使用!
// 使用Function声明函数
let show = new Function( 'console.log("hello function!")' ) // 调用执行函数 show()
(34) 简述一下函数的预解析
函数的主要的声明方式有两种,有不同的解析效果
使用function标准语法声明的函数,包含函数预解析功能,在函数所在的作用域中可以任意调用执行
使用function表达式的方式声明的函数,包含变量预解析功能,函数声明前不能调用执行函数
hello() // 可以调用执行成功,包含函数预解析功能 hello(存储函数)
function hello(){
…
}
world() // 调用失败,报错; 包含变量预解析功能 world(undefined)
var world = function() {
…
}
(35) 观察下面的代码说明输出结果以及为什么
let name = “tom”
function changeName(n) {
n = “jerry”
console.log(n) // 1
console.log(name) // 2
}
changeName(name)
console.log(n) // 3
console.log(name) // 4
1位置输出 jerry;
2位置输出 tom
3位置语法上无法访问变量n,n是局部变量所以会报错
4位置无法执行;4位置变量name中存储的数据是tom
(36) 观察下面的代码,说明输出结果以及为什么
let techs = [“html”, “css”]
function changeTechs(t){
t.push(“vue”)
console.log(t) // 1
console.log(techs) // 2
}
changeTechs(techs)
console.log(t) // 3
console.log(techs) // 4
(37) 简述什么是匿名函数
匿名函数就是没有名称的函数
匿名函数在项目中主要有两种操作方式
第一种形式就是将一个函数当成另一个函数的参数进行使用;
jQuery: $(“button”).click(function() {
// 用户单击的时候执行的操作
})
第二种形式就是将一个匿名函数赋值给变量进行调用,函数的一种声明方式
let show = function() {…}
(38) 说说你对函数递归的理解
函数递归,是函数一种调用方式,某个函数的内部满足条件的情况下调用自己
函数递归导致正在执行的函数常驻内存,递归执行的函数一定要有结束条件、递归的次数需要进行限制一般要求不超过20次(需要根据函数内实际操作的数据确定)
面试官:你项目中用过递归吗?或者你用递归的依据是什么?
项目中使用递归的地方比较多的,如之前开发的文件内容管理模块
,对文件的遍历方式使用的就是递归方式;
项目中使用递归主要是看业务的执行过程是否重复,某个业务流程执行过程中出现类似循环执行的重复方式,如果业务数据量又不是非常庞大的情况下使用递归能提高代码的可读性
(39) 什么是自执行函数
自执行函数,就是函数在声明完成的同时就会立即执行的函数!
自执行函数的特点是声明之后立即执行、只能在创建的同时执行一次无法再次调用执行
自执行函数通常都是给网页初始化网页特效或者准备初始数据!
;(function(){…}()) 使用较多
;(function(){…})() 使用较多
;~function() {…} ()
;!function() {…} ()
…
(40) 什么是闭包?项目中如何使用?
函数的闭包,就是在函数的内部代码中,声明了内部函数!内部函数使用了外部函数的变量数据!
function outer() {
// 局部作用域
let name = “tom”
function inner() {
// 闭包: 嵌套作用域
let msg = "闭包函数"
console.log(name, "使用外部函数的数据")
}}
闭包的特点:
- 闭包函数执行过程中,延长局部作用域中变量的作用域链,扩大局部变量作用域
- 闭包函数执行时,内部函数如果被外部变量引用,导致外部函数常驻内存,注意内存使用问题
闭包使用场景
- 结合自执行函数,完成页面效果的初始化,并通过闭包解决变量/函数的全局污染问题
- 可以用于封装自定义JS功能插件,闭包可以解决插件和其他代码之间的变量污染问题
(41) 什么是回调函数?
回调函数,描述的是一种函数的异步执行情况,执行过程中回头调用声明的函数
面试官:什么是回头调用声明的函数?为什么说是异步执行情况?
通常函数的同步执行,按照调用步骤进行执行,先执行第一个函数,然后执行第二个函数,再次执行第三个函数,每个函数执行完成前后续的函数都处于等待状态
异步执行先执行第一个函数,可以让第二个函数作为回调函数等待执行,第三个函数执行完成后回调第二个函数完成整体步骤,一般回调函数都出现在异步执行流程中,作为函数的参数进行传递执行
(42) 说说对全局污染的理解
全局污染,描述了在一个模块中,出现了重名的变量或者函数,导致变量或者函数的数据被覆盖或者修改的情况,数据被影响的形式称为全局污染;
面试官:为什么会出现重名变量?开发人员难道不注意吗?
前端开发过程中,JS代码根据功能形式会分布在大量文件中,原生JS使用var声明变量是允许重复声明的,所以在某个文件中声明一个变量无法完整的检查其他的所有相关文件是否声明过这个变量,一旦变量名称重复后续的某个功能可能受到影响,这样的问题排查起来比较麻烦
面试官:你是怎么解决全局污染问题的?
对于一些通用功能,尽量使用let关键字声明变量,保障当前模块中不会重复声明该变量
在同时关联多个js文件时,开发的功能尽量使用闭包的方式进行封装,将变量通过局部作用域进行隔离解决全局污染问题
(43)简单说说你是怎么理解BOM
其他问法:都用过哪些BOM对象
BOM是浏览器对象模型,主要用于描述代码中通过BOM对象和浏览器进行交互
BOM中主要包含window窗口对象、history访问历史、location访问信息、navigator浏览器版本信息、screen计算机屏幕信息、document网页文档信息
项目中我用的比较多的window、history、location用的比较多一些
面试官:简单说说history你都用过那些功能?
原生JS封装功能的时候,使用history实现过历史记录访问
面试官:简单说说location你都用过那些功能?
原生JS封装功能的时候,使用location做过href页面跳转、reload()页面刷新这些操作
面试官:简单说说window你都用过那些功能?
window是一个环境对象,表示了浏览器窗口,如默认弹窗alert()、声明的全局变量或者普通函数都是默认挂载到window对象上的,浏览器相关的各种事件都可以直接操作,也使用window实现过滚动条相关动态效果
(44) 你是怎么解决浏览器兼容性问题
原生JS里面有很多对象的操作都存在兼容性问题
一般处理的时候直接使用了公共的Hack进行CSS兼容性问题处理,使用公共封装的JS Hack解决通用JS兼容性问题
我们公司自己处理的自研项目中自己封装了JS navigator模块进行浏览器版本判断,低版本直接提示浏览器版本过低请升级的信息,对于低版本浏览器基本不做兼容性处理!
(45) 获取页面标签节点有几种方式
原生JS中获取页面标签,可以使用内建函数直接操作
document.getElementById() 通过id获取单个标签节点
document.getElementsByName() 通过name属性获取多个节点
document.getElementsByClassName() 通过class属性获取多个节点
document.getElementsByTagName() 通过标签名称获取多个节点
document.querySelector()根据css选择器获取单个节点
document.querySelectorAll() 根据css选择器获取多个节点
我自己在项目中封装使用的时候,用querySelectorAll()
比较多一些
面试官:还有其他获取节点的方式吗?
前面说的这些都是通过document直接获取节点,也可以结合DOM提供的函数获取对应的节点
ele.children 获取子节点
ele.parent 获取父节点
ele.previousElement / ele.perviousSiblingElement 获取上一个兄弟节点
ele.nextElement / ele.nextSiblinElement 获取下一个兄弟节点
document.forms获取页面中的表单节点 等等
(46)你是怎么理解DOM
DOM描述的是文档模型对象
原生JS可以将网页数据读取加载到内存中形成DOM树结构,通过树结构完成节点的遍历查询
DOM模型中封装很多函数,可以用于标签节点、标签属性、标签样式、标签内容增删改查
(47) 如何操作标签节点
创建节点:
document.createElement("div")
删除节点:div.remove()
替换节点:parent.replace(old, new)
追加节点:div.appendChild(child)、div.insertBefore(child, old)
原生JS操作标签节点,可以对节点进行增删改查处理,createElement
创建节点、appendChild
或者insertBefore
可以添加节点、remove
可以删除节点、replace
可以替换节点
(48) 如何操作标签属性
原生JS提供了标签函数可以直接操作属性
div.setAttribute(key, value)
设置属性或者修改属性
div.getAttribute(key)
获取属性数据
div.removeAttribute(key)
删除属性
还可以直接通过标签节点对象对属性进行访问和设置,如div.id = "title"
(49) 如何操作标签内容
原生JS提供了内容的处理属性
div.innerText
可以获取或者设置文本内容
div.innerHTML
可以获取或者设置富文本内容
div.textContent
也可以获取或者设置文本内容,等价于innerText
(50) 如何操作标签样式
原生JS中操作标签的样式,可以区分设置样式和获取样式
设置样式div.style
.样式名称 = 样式值,可以单独设置一条样式
div.style.cssText= "width:100px; height:200px;
" 可以设置多条样式
获取样式getComputedStyle(div)
.样式名称 获取指定样式
兼容性语法div.currentStyle.
样式名称 获取指定样式
(51) 什么是面向对象
面向对象是一种编程思想,开发一个功能的时候分析拆分解决时参与的对象和对象的属性和方法,通过多个对象之间的方法调用完成功能处理,封装好的对象就可以在其他流程中实现复用,提高代码的复用性!
(52)你是怎么理解原生JS面向对象
原生JS主要编程方式还是函数式编程,面向对象中通过构造函数的方式模拟面向对象编程
我自己在项目中使用面向对象的方式封装功能时,用的比较多的是ES6 class语法
// 声明构造函数
function Person(name, age) {
this.name = name
this.age = age
}
// 创建对象
let tom = new Person("汤姆", 18)
(53)原生JS中怎么实现继承关系
原生JS面向对象是通过函数的方式模拟出来的,继承关系主要通过原型对象进行实现,继承方式有这么几种
第一种是经典继承
,通过构造函数的原型对象继承,也称为原型继承
第二种是冒充继承
,通过call()
辅助函数完成继承
第三种是组合继承
,结合了原型继承和冒充继承完成
第四种是寄生组合继承
,优化了组合继承方式完成的继承(最优)
(54)原生JS中怎么实现继承关系
原生JS中所有的对象都是直接或者间接继承自Object对象,底层就是通过原型链进行关联的;
原型和原型链就是原生JS中通过继承关系统一管理数据的底层实现方式
面试官:什么是原型?
原生JS中所有的构造函数或者类型都会有自己的原型对象,使用prototype表示,描述当前类型属于什么对象类型!
面试官:什么是原型链?
原生JS中所有的对象都是被构造出来的,可以通过 对象的原型链__proto__ 查询当前对象是由那个原型对象构造的!
通过原型链可以让当前对象使用原型链上的所有对象的公共属性和方法,实现的数据的统一管理和复用
(55) 什么是Ajax
Ajax是一种异步请求基础,实现页面的局部数据刷新功能
默认底层实现主要是通过JavaScript XMLHttpRequest对象,结合XML数据或者JSON数据完成异步数据交互的技术
(56) 异步请求底层实现有哪些
异步请求的底层实现方式主要有两种
- 第一种是通过JS XHR对象实现的,也是一种主流的实现方式
- 第二种是使用浏览器API fetch模块实现的,目前各大主流浏览器较新的版本中都开始支持,也是以后的趋势
(57) 原生JS Ajax实现步骤
xxxxxxxxxx
1. 创建异步对象
2. let _http = new XMLHttpRequest()
3. 2. 连接服务器
4. _http.open("GET", "http://www.example.com/api")
5. 3. 发送请求
6. _http.send()
7. 4. 处理响应数据
8. _http.onreadystatechange = function() {
9. if(_http.status === 200 && _http