常见javascript面试题(上)

140 阅读8分钟

1. 说说 JS 的数据类型

  1. 基本数据类型
  • number
  • string
  • boolean
  • null
  • undefined
  • symbol
    • 用来给对象唯一的属性名(对象属性名的类型只能是:string,symbol)
  • bigint
    • 当 number 类型计算数据大于 Number.MAX_SAFE_INTEGER 或 小于 Number.MIN_SAFE_INTEGER 数时进行计算,值不正确的(精度存在问题)
    • 使用 bigint 就没有问题
  1. 引用数据类型
  • object
  • function
  • array

2. 如何判断 JS 数据类型

  1. typeof

大部分类型都能检查出来,但是对于 null\object\array 无法区分

  1. A instanceof B

简单理解:检查 A 是否是 B 的实例 真正理解:检查 A 的某一个__proto__是否和 B.prototype 指向同一个对象

主要用来检测引用数据类型

  1. Object.prototype.toString.call(xxx).slice(8, -1)

完美解决方案,可以检测所有数据类型,开发中使用 toString 方法去封装工具函数去检测类型

  1. Array.isArray

检测是否是数组类型

  1. A === B

检测 A 和 B 的值和类型都要相等

3. 说说常见的数组方法

  1. 更新数组的方法
  • push 给数组最后添加一个元素
  • pop 删除数组最后一个元素
  • unshift 给数组最前面添加一个元素
  • shift 删除数组最前面一个元素
  • splice 删除/新增指定下标元素
  • sort 排序
  • reverse 反转
  1. 遍历元素的方法
  • forEach 遍历
  • map 返回一个新数组,新数组长度和原数组一致,但内部的值往往会发生变化。(长度不变,值变)
    • 在 React 中更新数据中某个值 或 遍历展示数据 用 map。
  • filter 返回一个新数组,新数组长度往往比原数组更少,但内部的值和原数组一致。(长度变,值不变)
    • 批量删除,往往使用 filter 方法得到需要删除的元素,在用 map 方法得到其 id。
  • reduce 常用于统计、累加和求和等功能。
    • 购物车计算总价。
  • find 查找某个元素,找到返回这个元素,找不到返回 undefined。
  • findIndex 查找某个元素的下标,找到返回这个元素下标,找不到返回 -1。
  • every 所有返回 true 整体才返回 true,只要有一个返回 false,整体就返回 false。
  • some 只要有一个返回 true,整体就返回 true,只有全部返回 false,整体才返回 false。
  1. 其它方法
  • slice 截取数组中某些元素
  • concat 拼接数组
  • join 将数组内部元素以某种方式拼接成字符串
  • includes 判断是否包含某个元素,包含返回 true,不包含返回 false
  • indexOf 判断是否包含某个元素,包含返回其下标,不包含返回-1

至少能说个 7-8,并且相对复杂的方法

4. 作用域和作用域链

  1. 作用域
  • 概念:一个变量可以合法使用的范围/区域
  • 作用:隔离变量, 避免了变量重名冲突的问题(也就是允许了不同作用域中可以有同名的变量)
  • 分类:
    • 全局作用域
    • 函数作用域
    • 块级作用域 => 通过 ES6 的 let 或 const 变量定义
  1. 作用域链
  • 概念:多个嵌套的作用域形成的由内向外的结构
  • 作用:用于查找变量
  • 具体:先在自身作用域查找,再由内向外,一层层作用域查找,找到返回值,最终来到全局作用域找,找不到就会报错(xxx is not defined)

5. 说说你对闭包的理解

  1. 什么是闭包?

一个包含引用外部函数局部变量的“closure”对象,存在内部函数中

  1. 产生原因(条件):
  • 函数嵌套
  • 内部函数引用外部函数的局部变量
  • 调用外部函数
  1. 作用(优点):
  • 延长局部变量生命周期(存活时间)
  • 让函数外部操作函数内部数据
  1. 缺点:
  • 可能导致内存泄漏
  • 解决方案:不用的时候,将内部函数变成垃圾对象(null),垃圾回收机制会回收内部函数,因为闭包存在内部函数中,所以闭包也被回收了
  1. 闭包生命周期
  • 产生: 调用外部函数时产生(当内部函数定义执行完成)
  • 死亡: 内部函数变成垃圾对象
  1. 应用
  • React 高阶组件,高阶函数等
  • 实际开发很少直接使用闭包。研究 Vue 响应式原理,在数据劫持阶段,内部以闭包形式存了 dep 对象,存在属性 get/set 方法中

6. 谈谈 this 指向

  1. 普通函数(普通函数的 this 指向看函数调用方式)
  • 直接调用 fn() -> window 严格模式下 -> undefined
  • 对象调用方法 obj.fn() -> 调用方法的对象 obj
  • 通过 call/apply 方法调用函数 fn.call(obj) -> 方法的第一个参数 obj
  • 通过 new 调用函数 new fn() -> 产生的实例对象
  1. 特殊函数
  • 箭头函数:指向外层函数的 this
  • 回调函数(1. 你定义的函数 2. 你没有调用 3. 最终函数执行了)
    • DOM 事件回调函数(普通函数):指向绑定事件的 DOM 元素
    • 定时器回调函数(普通函数):window
    • 生命周期函数(普通函数):组件实例对象

7. 说说原型与原型链

  1. 原型
  • 我们说的原型,指的是两个原型属性:__proto__ 和 prototype
  • prototype 叫做显示原型属性
  • __proto__ 叫做隐式原型属性
  • 每个函数都有一个显式原型属性,它的值是一个对象,我们叫做原型对象。
  • 这个原型对象上默认会有一个 constructor 方法,指向函数本身,有一个 __proto__ 属性,指向 Object 的原型对象
  • 每个实例都有一个隐式原型属性,它的值指向其对应构造函数的原型对象。

特殊情况:

  • Function.prototype === Function.__proto__ 他们指向同一个对象
  • Object.prototype.__proto__ === null 这里是原型链的尽头
  1. 原型链
  • 概念:从对象的 __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,如果变量的值被其他的值覆盖了,则引用次数-1
  • 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间。
  1. 机制 2:标记清除法

目前大多数浏览器的实现方案,分为 标记 和 清除 两个阶段,

  • 标记阶段即为所有活动对象做上标记
  • 清除阶段则把没有标记(也就是非活动对象)销毁

具体过程如下:

  • 垃圾回收器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为 0
  • 然后从各个根对象开始遍历,把不是垃圾的节点改成 1
  • 清理所有标记为 0 的垃圾,销毁并回收它们所占用的内存空间
  • 最后,把所有内存中对象标记修改为 0,等待下一轮垃圾回收

缺点:内存碎片化,内存分配速度慢

3. 优化:标记整理算法

标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存

4. V8 引擎还继续优化:分代式垃圾回收

V8 中将堆内存分为新生代和老生代两区域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收

  • 新生代的对象为存活时间较短的和体积较小对象通常只支持 1 ~ 8M 的容量,
  • 而老生代的对象为存活时间较长或体积较大对象,简单来说就是经历过多次新生代垃圾回收后还存活下来的对象(存活时间长)
  1. 新生代区域垃圾回收:
  • 将内存一分为 2(等分),一块称为使用区,一块称为空闲区,一开始对象都在使用区存放着
  • 开始垃圾回收,将使用区活动对象(有被引用对象)复制到空闲区
  • 全部执行完,将使用区清空,将空闲区变成使用区,使用区变成空闲区,等待下次垃圾回收

当满足两个条件其中之一时,对象会从新生代区晋升到老生代区:

  • 经历过多次新生代垃圾回收后还存活下来的对象
  • 容量超过使用区 25%
  1. 老生代区域垃圾回收:标记-整理-清除算法

更多内容:juejin.cn/post/698158…