ES6面试总结(持续更新~)

4,609 阅读13分钟

本篇文章面试官问到es6时,你可以按照套路一一回答面试官的问题,给面试官你基础还不错的印象。

请问你熟悉哪些ES6新特性?

  • let和const
  • 变量的解构赋值
  • 字符串、数组、函数、对象、运算符拓展
  • Symbol
  • Proxy
  • Reflect
  • Promise对象、async函数、Genertor函数语法
  • Iterator和for...of循环
  • class的基本语法
  • class继承
  • Module的语法
  • Moule的语法
  • Moudle的加载实现

- 区别篇

1. 请问let,var,const区别?

块级作用域方面: letconst有块级作用域,var没有块级作用域,但有函数作用域

变量提升方面var支持变量提升,constlet不支持,且let会出现暂时性死区的问题

声明赋值方面var可以重复声明,constlet不能重复声明,且const一旦初始化数据就不能赋值否则报错。

2. 箭头函数和普通函数的区别?

  • 箭头函数没有自己的this对象,它的this是它执行上下文的this
  • 不可以当作构造函数,也就是说不能new,否则会抛出一个错误
  • 没有argument
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数。

3. 介绍下Set、Map、WeakSet和WeakMap的区别?

- Set

它类似于数组,成员的值是唯一的,没有重复的值,set本身是一个构造函数,用于生成Set数据结构,主要的方法有adddeletehas,可以遍历,应用场景数组去重。

- weakSet

它与Set类似,也是不重复的值的集合。

它与Set的区别是:

  • WeakSet的成员只能是对象,而不能是其他类型的值。

image-20211001162400224.png

  • 没有size属性,不能遍历

image-20211001162645485.png

  • WeakSet中对象都是弱引用,垃圾回收机制考虑WeakSet对该对象的引用,也就是说,如果其他对象都不在引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑对象还存在WeakSet之中。
  • 应用场景:WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在WeakSet里面的引用也会消失,这样不容易造成内存泄漏

- Map

本质是键值对的集合(hash结构),键可以是对象,优化了Object。主要的方法为getsethasdelete,可以遍历。可以干各种类型转换。

- WeakMap

它与Map类似,它和Map的区别:

  • 键名只接受对象

image-20211001162906850.png

  • 没有size属性,不能遍历

image-20211001163033239.png

  • 是弱引用,不被垃圾回收机制回收

面试官:如何理解垃圾回收机制?(填坑)谈工作原理即可

工作原理:垃圾回收机制依赖引用计数,如果一个值不为0,垃圾回收机制就不会释放这块内存。结束使用该值,就会释放该值内存。如果忘记取消引用,就会导致内存无法释放,进而可能引起内存泄漏

4. async await对比promise的优缺点?

async/await优点

  • 它做到真正的串行的同步写法,代码阅读更简单
  • 对于条件语句和其他流程比较友好,可以直接写到判断条件
function a() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(111)
      }, 1111)
    })
  };
async function f() {
    try {
      if ( await a() === 111) {
        console.log('yes, it is!') // 会打印
      }
    } catch (err) {
      // ...
    }
  }

  • 处理复杂流程时,在代码清晰度方面有优势

async/await缺点

  • 无法处理promise返回对象的reject对象,要借助try...catch...
  • 用await可能会导致性能问题,因为await阻塞代码,之后的代码也许不依赖于前者,但仍然需要等待前者的完成,导致代码失去并发性。
  • try...catch...内部的变量无法传递给下一个try...catchPromise和then/catch内部定义的变量

promise的一些问题

  • 一旦执行,就无法中途取消,链式调用多个then不能随便跳出来
  • 错误无法外部捕捉,只能在内部预判处理,如果内部进行预判处理,如果不设置回调函数,Promise内部抛出的错误,不能反应到外部
  • Promise内部如何执行,检测很难,当处于pending状态时,无法得知目前进展到哪一个阶段

5. 简述一下Promise,async&await两者者的区别?

  • promise通过链式调用,直接在then中返回一个promise来进行成功之后的回调函数,用catch来做错误处理
  • async是Generator函数的语法糖,async/await则将其变成同步的写法,即可以用try-catch捕获,简洁可读性更高写法更优雅

6. CommonJS和ES6模块的区别

  • CommonJS模块输出是一个值的拷贝(浅拷贝),ES6模块输出是值的引用
  • CommonJS是运行时加载,ES6模块是编译时输出接口
  • CommonJS模块的require()是同步加载模块,ES6模块的import命令是异步加载,有一个独立依赖的解析模块阶段

CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值

ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

补充点:可能面试官会问你还见过哪些规范

就可以说一下AMD和CMD

AMD规范的模块化:用 require.config()指定引用路径等,用define()定义模块,用require()加载模块。

CMD规范的模块化:用define()定义模块, seajs.use 引用模块。

- 字符、数组、对象、函数、运算符拓展篇

1. 你是如何理解数组对象内容解构?

你可以举如下几个例子:

  • 数组结构
let [a1,a2,a3] = [1,2,3] //a1 = 1; a2 = 2; a3 = 3;
  • 对象解构
let {name,age} = {name:'meteor', age:8} // name = 'meteor' age = 8
  • 复杂解构
let [{age}] = [{age:8,name:'xx'},'江西',[1,2,3]] //age = 8 注意对象解构
  • 默认赋值
let {age = 5} = {age:8,name;'xx'} //age = 8 如果没有age字段age = 5
  • 常用函数给默认参数
//以前
function(){var a = a || 5}
//现在
function(a = 5){}

2. 请问字符串增添哪些方法?

  • 反引号(可以拼接一些变量)
let name = 'kk';
let age = 20;
let str = `${name}${age}岁了`
console.log(str); //kk20岁了
  • includes方法
//判断字符串是否包含某个字符串
let str = '23123S';
str.includes('3S')//true
  • endsWith、startsWith方法
//判断字符串是否以某一个字符串开始或结束
var a = '1AB42342D'
console.log(a.startsWith('1A')) //true
console.log(a.endsWith('2d')) //false

3. 数组新增哪些方法,并指明它的用法?

  1. Array.from()将类数组转化数组(对象,Set,Map)

  2. Array.of()用于将一组值,转换为数组

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
  1. Array.fill()填充数组
  2. Array.find()findIndex()
[1, 4, -5, 10].find((n) => n < 0)
// -5
[1, 4, 5, 10].find((n) => n < 0) //undefined
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 15;
}) // -1
  1. entries()keys()values()遍历数组
  2. includes()表示某个数组是否包含给定的值
[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true
  1. flat(n)n默认为1,表示拉平次数
//数组扁平化流氓解法
[1, [2, [3]]].flat(Infinity)
  1. flatMap()只能展开一层数组
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]

4. 请问对象的拓展哪些属性?

  • super关键字

this关键字总是指向函数所在的当前对象,而super指向当前对象的原型对象

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
  • 遍历方式
  1. fo...in
  2. Object.keys()
  3. Reflect.ownKeys(obj)

返回一个数组

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
  • 新增方法
  1. Object.is方法判断两个值是否同一个值

  2. Object.assign方法用于对象的合并,复制到目标对象,且是浅拷贝

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

5. 请问函数有哪些拓展呢?

  • rest参数(...变量名)
// arguments变量的写法
function sortNumbers() {
  return Array.from(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
  • name属性
function foo() {}
foo.name // "foo"
  • 箭头函数(常问普通函数的区别)
  • 严格模式

只要函数参数使用了默认值解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

6. 请问你见过的运算符拓展有哪些?

指数运算符(**)

2 ** 2 // 4
2 ** 3 // 8

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512

看更多拓展可以参考链接运算符的扩展 - ECMAScript 6入门 (ruanyifeng.com)

- class继承篇

1. 请聊一下class继承

  • class可以通过extends关键字实现继承
  • Object.getPrototypeOf方法可以用来从子类上获取父类。
  • super这个关键字,,既可以当作函数使用,也可以当作对象使用。
  • 子类的__ proto __ 属性,表示构造函数继承,总是指向父类
  • 子类的prototype属性的__ proto __ 属性,表示方法的继承,总是指向父类的prototype属性
  • Mixin模式实现了继承多个类,参照以下写法
class DistributedEdit extends mix(Loggable, Serializable) {
  // ...
}

- promise篇(高频)

1. 请简述下promise的优缺点?

缺点

  1. 无法取消Promise,一旦新建就会立即执行,无法中途取消
  2. 如果不设置回调函数,promise内部抛出的错误就无法反应到外部
  3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

优点

  1. 解决回调地狱问题
  2. 代码扁平可读,.then方法链式调用
  3. 更好的进行错误捕获

2. 请聊一下promise.then() .catch() .finally()?

  • 首先三者多是微任务
  • .then.catch.finally都会返回一个新的Promise
  • .then方法能接受两个参数,第一个是处理成功的函数,第二个处理失败的函数
  • catch不管连接到哪一层,都能捕获上传未捕捉的错误。
  • .finally的回调函数不接受任何参数,也就是没法知道最终状态是resolved还是rejected

3. 请聊下promise.all() promise.race()和promise.any()

promise.all

  • 同时处理多个promise对象,包装到一个新的Promise实例(常会被问到),如果传递的参数不是promise实例,他会调用promise.resolve方法,将参数转换实例

举个例子,p1,p2,p3都是promise对象

const p = Promise.all([p1, p2, p3]);

其中p状态由p1,p2,p3,分成两种情况

  1. 只有p1,p2,p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值成一个数组,传递给p的回调函数
  2. 只要p1,p2,p3之中有一个被rejected,p的状态就变成rejected的实例返回值,会传递给p的回调函数

promise.race

  • 同时处理多个promise实例,包装成一个新的Promise实例,如果传递的参数不是promise实例,他会调用promise.resolve方法,将参数转换实例

还是以一个例子说明

const p = Promise.race([p1, p2, p3]);

p只有一种情况,只要p1,p2,p3任意一个的状态发生改变,p的状态也随之发生改变。

promise.any

  • 也是同时处理promise实例,包装成一个新的promise实例。

  • 它的状态变化跟promise.race类似,只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

  • promise.race()不同点,就是promise.any()不会因为某个promise变成rejected状态而结束,必须要等到所有的参数promise变成rejected状态才结束

4. promise手写系列

参考链接:

可能是目前最易理解的手写promise - 掘金 (juejin.cn)

手写Promise核心原理,再也不怕面试官问我Promise原理 - 掘金 (juejin.cn)

- 新增属性篇

1. 请聊一下ES6新增的数据类型symbol?

其最大的特点是:唯一值,独一无二的。

应用场景如下:

  • 作为对象属性名(key)
  • 使用symbol来代替常量

2. 请聊一下Proxy和Reflect?

回答步骤:

  • 什么是proxy?

在目标对象之前架设一层拦截器,必须通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤修改。说上以下三点可以做的操作:

  1. get(target,propKey,receiver):拦截对象属性读取

  2. set(target,popKey,value,receiver):拦截对象属性的设置

  3. has(target,propKey),拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值

  4. deleteProperty(target,propKey)拦截delete proxy[propKey]的操作,返回布尔值

​ ......

  • 什么是Reflect

它是一种于proxy对象一样,也是ES6新增的api,它的设计目的如下几点:

  1. 将Object对象的一些明显属于语言内部的方法
  2. 修改某些object方法的返回结果,让其变得更合理
  3. 让object操作都变成操作成为函数行为
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true
  1. reflect对象的方法与proxy对象一一对应,只要proxy对象,就能在proxy对象可以方便调用对应的reflect方法,完成默认行为,作为修改的基础,有了reflect对象更简单易懂
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1

// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1

3. 请聊下Genertor函数

直接开讲:

  • 在语法上,首先可以把它理解成,Genertor函数是一个状态机,封装了多个内部状态
  • 形式上,Generator函数是一个普通函数,两个特征。一是,function关键字与函数名之间有一个信号;二是,函数体内部状态(yield语句在英语里就是“产出”)

应用场景:

  • 异步操作的同步化表达(异步操作的后续操作可以放在yield语句下面)
  • 控制流管理
  • 部署Iteratior接口
  • 作为数据结构(可以看做一个数组结构,因为Genertor函数可以返回一系列的值)

4. 谈一下Iterator遍历器和for...of...

主要聊以下几个方面:

  • 什么是Iterator(遍历器)?

是一种机制,它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据只要部署iterator接口,就可以完成遍历操作。

  • 遍历器的作用
  1. 为各种数据结构,提供一个统一的、简便的访问接口
  2. 使得数据结构的成员能够按某种次序排列
  3. ES6创造一种新的遍历命令for...of循环,Iterator接口主要提供for..of消费

注意:Object.prototype上没有没有实现[Symbol.iterator ](),所以不能for...of遍历

  • 调用iterator接口的场合
  1. 结构赋值
  2. 拓展运算符(...)
  3. yield* [1,2,3,4]
  4. Promise.all(),Promise.race()
  5. Array.from()
  6. Map(),Set(),WeakMap(), WeakSet()
  • 最后聊一下for...of
  1. 循环内部调用的是数据结构Symbol.iterator方法

  2. for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。

ps: 参考链接

Proxy - ECMAScript 6入门 (ruanyifeng.com)