1. 说说 JS 的数据类型
- 基本数据类型
- number
- string
- boolean
- null
- undefined
- symbol
- 用来给对象唯一的属性名(对象属性名的类型只能是:string,symbol)
- bigint
- 当 number 类型计算数据大于 Number.MAX_SAFE_INTEGER 或 小于 Number.MIN_SAFE_INTEGER 数时进行计算,值不正确的(精度存在问题)
- 使用 bigint 就没有问题
- 引用数据类型
- object
- function
- array
2. 如何判断 JS 数据类型
- typeof
大部分类型都能检查出来,但是对于 null\object\array 无法区分
- A instanceof B
简单理解:检查 A 是否是 B 的实例
真正理解:检查 A 的某一个__proto__是否和 B.prototype 指向同一个对象
主要用来检测引用数据类型
- Object.prototype.toString.call(xxx).slice(8, -1)
完美解决方案,可以检测所有数据类型,开发中使用 toString 方法去封装工具函数去检测类型
- Array.isArray
检测是否是数组类型
- A === B
检测 A 和 B 的值和类型都要相等
3. 说说常见的数组方法
- 更新数组的方法
- push 给数组最后添加一个元素
- pop 删除数组最后一个元素
- unshift 给数组最前面添加一个元素
- shift 删除数组最前面一个元素
- splice 删除/新增指定下标元素
- sort 排序
- reverse 反转
- 遍历元素的方法
- forEach 遍历
- map 返回一个新数组,新数组长度和原数组一致,但内部的值往往会发生变化。(长度不变,值变)
- 在 React 中更新数据中某个值 或 遍历展示数据 用 map。
- filter 返回一个新数组,新数组长度往往比原数组更少,但内部的值和原数组一致。(长度变,值不变)
- 批量删除,往往使用 filter 方法得到需要删除的元素,在用 map 方法得到其 id。
- reduce 常用于统计、累加和求和等功能。
- 购物车计算总价。
- find 查找某个元素,找到返回这个元素,找不到返回 undefined。
- findIndex 查找某个元素的下标,找到返回这个元素下标,找不到返回 -1。
- every 所有返回 true 整体才返回 true,只要有一个返回 false,整体就返回 false。
- some 只要有一个返回 true,整体就返回 true,只有全部返回 false,整体才返回 false。
- 其它方法
- slice 截取数组中某些元素
- concat 拼接数组
- join 将数组内部元素以某种方式拼接成字符串
- includes 判断是否包含某个元素,包含返回 true,不包含返回 false
- indexOf 判断是否包含某个元素,包含返回其下标,不包含返回-1
至少能说个 7-8,并且相对复杂的方法
4. 作用域和作用域链
- 作用域
- 概念:一个变量可以合法使用的范围/区域
- 作用:隔离变量, 避免了变量重名冲突的问题(也就是允许了不同作用域中可以有同名的变量)
- 分类:
- 全局作用域
- 函数作用域
- 块级作用域 => 通过 ES6 的 let 或 const 变量定义
- 作用域链
- 概念:多个嵌套的作用域形成的由内向外的结构
- 作用:用于查找变量
- 具体:先在自身作用域查找,再由内向外,一层层作用域查找,找到返回值,最终来到全局作用域找,找不到就会报错(xxx is not defined)
5. 说说你对闭包的理解
- 什么是闭包?
一个包含引用外部函数局部变量的“closure”对象,存在内部函数中
- 产生原因(条件):
- 函数嵌套
- 内部函数引用外部函数的局部变量
- 调用外部函数
- 作用(优点):
- 延长局部变量生命周期(存活时间)
- 让函数外部操作函数内部数据
- 缺点:
- 可能导致内存泄漏
- 解决方案:不用的时候,将内部函数变成垃圾对象(null),垃圾回收机制会回收内部函数,因为闭包存在内部函数中,所以闭包也被回收了
- 闭包生命周期
- 产生: 调用外部函数时产生(当内部函数定义执行完成)
- 死亡: 内部函数变成垃圾对象
- 应用
- React 高阶组件,高阶函数等
- 实际开发很少直接使用闭包。研究 Vue 响应式原理,在数据劫持阶段,内部以闭包形式存了 dep 对象,存在属性 get/set 方法中
6. 谈谈 this 指向
- 普通函数(普通函数的 this 指向看函数调用方式)
- 直接调用 fn() -> window 严格模式下 -> undefined
- 对象调用方法 obj.fn() -> 调用方法的对象 obj
- 通过 call/apply 方法调用函数 fn.call(obj) -> 方法的第一个参数 obj
- 通过 new 调用函数 new fn() -> 产生的实例对象
- 特殊函数
- 箭头函数:指向外层函数的 this
- 回调函数(1. 你定义的函数 2. 你没有调用 3. 最终函数执行了)
- DOM 事件回调函数(普通函数):指向绑定事件的 DOM 元素
- 定时器回调函数(普通函数):window
- 生命周期函数(普通函数):组件实例对象
7. 说说原型与原型链
- 原型
- 我们说的原型,指的是两个原型属性:
__proto__和 prototype - prototype 叫做显示原型属性
__proto__叫做隐式原型属性- 每个函数都有一个显式原型属性,它的值是一个对象,我们叫做原型对象。
- 这个原型对象上默认会有一个 constructor 方法,指向函数本身,有一个
__proto__属性,指向 Object 的原型对象 - 每个实例都有一个隐式原型属性,它的值指向其对应构造函数的原型对象。
特殊情况:
Function.prototype === Function.__proto__他们指向同一个对象Object.prototype.__proto__ === null这里是原型链的尽头
- 原型链
- 概念:从对象的
__proto__开始, 连接的所有对象, 这个结构叫做原型链,也可称为“隐式原型链” - 作用:用来查找对象的属性
- 规则:在查找对象属性或调用对象方法时,会先在对象自身上查找, 找不到就会沿着原型链查找,找到就返回属性的值,最终来到
Object.prototype.__proto__,找不到返回 undefined - 应用:利用原型链可以实现继承
- class&extend 继承
- Vue 的全局事件总线:Vue.prototype.$bus = this
- 汇总所有 api 接口函数到一个对象上:Vue.prototype.$api = xxx
- this.$api.trademark.getTrademarkList()
8. 说说 JS 的垃圾回收机制
在 JS 中对象的释放(回收)是靠浏览器中的垃圾回收器来回收处理的
1. 垃圾回收器
- 浏览器中有个专门的线程, 它每隔很短的时间就会运行一次
- 主要工作:判断一个对象是否是垃圾对象,如果是,清除其内存数据,并标记内存是空闲状态
2. 垃圾回收策略
- 机制 1:引用计数法
这其实是早先的一种垃圾回收算法,它把 对象是否不再需要 简化定义为 对象有没有其他对象引用到它,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收,目前很少使用这种算法了
它的策略是跟踪记录每个变量值被使用的次数
- 如果对象被引用一次,引用次数+1,如果变量的值被其他的值覆盖了,则引用次数-1
- 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间。
- 机制 2:标记清除法
目前大多数浏览器的实现方案,分为 标记 和 清除 两个阶段,
- 标记阶段即为所有活动对象做上标记
- 清除阶段则把没有标记(也就是非活动对象)销毁
具体过程如下:
- 垃圾回收器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为 0
- 然后从各个根对象开始遍历,把不是垃圾的节点改成 1
- 清理所有标记为 0 的垃圾,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改为 0,等待下一轮垃圾回收
缺点:内存碎片化,内存分配速度慢
3. 优化:标记整理算法
标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存
4. V8 引擎还继续优化:分代式垃圾回收
V8 中将堆内存分为新生代和老生代两区域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收
- 新生代的对象为存活时间较短的和体积较小对象通常只支持 1 ~ 8M 的容量,
- 而老生代的对象为存活时间较长或体积较大对象,简单来说就是经历过多次新生代垃圾回收后还存活下来的对象(存活时间长)
- 新生代区域垃圾回收:
- 将内存一分为 2(等分),一块称为使用区,一块称为空闲区,一开始对象都在使用区存放着
- 开始垃圾回收,将使用区活动对象(有被引用对象)复制到空闲区
- 全部执行完,将使用区清空,将空闲区变成使用区,使用区变成空闲区,等待下次垃圾回收
当满足两个条件其中之一时,对象会从新生代区晋升到老生代区:
- 经历过多次新生代垃圾回收后还存活下来的对象
- 容量超过使用区 25%
- 老生代区域垃圾回收:标记-整理-清除算法