本片文章如题主要讲解ES6相关面试题,每道面试题都是从网上找的参考资料整理而来,侵删。部分未添加原文链接(苦笑,删除了浏览记录,找不到了)
这一系列纯粹是为了个人面试整理的,为了圆一下二流学生的大厂梦!
系列文章
- 大厂前端面试题总结(CSS篇)
- 大厂前端面试题总结(Web安全篇)
- 大厂前端面试题总结(浏览器篇)
- 大厂前端面试题总结(性能优化篇)
- 大厂前端面试题总结(JS手写篇)
- 大厂前端面试题总结(JS理论篇)
- 大厂前端面试题总结(Vue篇)
ES6
一、let、const、var的区别
二、箭头函数和普通函数的区别
三、变量的结构赋值
四、promise、async await、generator的区别
五、ES6的继承和ES5相比有什么不同
六、JS模块化(commonjs/AMD/CMD/ES6)
扩展
描述以下ES6中的“…”扩展运算符
如何理解JS中的Iterable
一、let、const、var的区别
var
定义的变量,作用域是整个封闭函数,是全域的;let
定义的变量,作用域是在块级或者字块中;- 变量提升:不论通过
var
声明的变量处于当前作用域的第几行,都会提升到作用域的最顶部;而let
声明的变量不会在顶部初始化,凡是在let
声明之前使用该变量都会报错(引用错误ReferenceError); - 只要块级作用域内存在
let
、它所声明的变量就会绑定在这个区域; let
不允许在相同作用域内重复声明(同时使用var
和let
、两个let
会报错);const
用来专门声明一个常量,它和let一样作用于块级作用域,没有变量提升,重复声明会报错,不同的是const声明的变量不可改变,声明时必须初始化(赋值)
二、箭头函数和普通函数的区别
- 箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种只包含一个表达式,
{...}
和return
都省略掉了。还有一种可以包含多条语句,此时不能省略{...}
和return
。 - 箭头函数是匿名函数,不能作为构造函数,不能使用
new
; - 箭头函数不绑定
arguments
,取而代之用rest参数...
解决function A(a) { console.log(arguments) } A(1,2,3) // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ] let B = (b) => console.log(argument) B(1,2,3) // Uncaught ReferenceError: arguments is not defined let C = (...c) => console.log(c) C(1,2,3) // (4) [1, 2, 3]
- 箭头函数不绑定
this
,会捕获其所在的上下文的this
值,作为自己的this
值;var obj = { a: 10, b: () => { console.log(this.a); // undefined console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} }, c: function() { console.log(this.a); // 10 console.log(this); // {a: 10, b: ƒ, c: ƒ} } } var obj = { a: 10, b: function(){ console.log(this.a); //10 }, c: function() { return ()=>{ console.log(this.a); //10 } } }
- 箭头函数通过
call()
或apply()
方法调用一个函数时,只传入了一个参数,对this
并没有影响。即箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向,如call()、bind()、apply();普通函数的this指向调用它的那个对象let obj2 = { a: 10, b: function(n) { let f = (n) => n + this.a; return f(n); }, c: function(n) { let f = (n) => n + this.a; let m = { a: 20 }; return f.call(m,n); } }; console.log(obj2.b(1)); // 11 console.log(obj2.c(1)); // 11
- 箭头函数没有原型属性
var a = ()=>{ return 1; } function b(){ return 2; } console.log(a.prototype); // undefined console.log(b.prototype); // {constructor: ƒ}
- 箭头函数不能当做generator函数,不能使用
yield
关键字
三、变量的解构赋值
- 交换变量的值
let x = 1; let y = 2; [x, y] = [y, x];
- 从函数返回多个值
- 函数参数的定义:解构赋值可以方便的将一组参数与变量名对应起来
// 参数是一组有次序的值 function f([x, y, z]) { ... } f([1, 2, 3]); // 参数是一组无次序的值 function f({x, y, z}) { ... } f({z: 3, y: 2, x: 1})
- 提取JSON数据
let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number); // 42, "OK", [867, 5309]
- 遍历Map结构
任何部署了Iterator
接口的对象,都可以用for...of
循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键值就非常方便:
如果只想获取键名,或者只想获取键值,可以写成下面这样:const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); } // first is hello // second is world
// 获取键名 for (let [key] of map) { // ... } // 获取键值 for (let [,value] of map) { // ... }
扩展
一、数组的解构
const details=['laver','laverlist.com',null];
const[name,website,category='php']=details;
console.log(name,website,category)//laver laverlist.com null
let a=10;
let b=20;
// a和b的值进行交换
[a,b]=[b,a];
console.log(a,b)
注意:ES6内部使用严格相等运算符===
,判断一个位置是否有值。所以只有当一个数组成员严格等于undefined
,默认值才会生效。
// let [x=1]=[undefined];
// console.log(x); //1
let [x=1]=[null]
console.log(x); //null
上面代码中,如果一个数组成员时null
,默认值就不会生效,因为null不严格等于undefined
二、对象的解构
const tom = {
name: 'jonse',
age: 25,
family: {
mother: 'a',
father: 'b',
brother: 'c'
}
};
const {name, age} = tom;
console.log(name, age);
const father = 'dad';
const {father: f, mother, brother} = tom.family;
console.log(father);
var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null
上面代码中,属性x等于null
,因为null
与undefined
不严格相等,所以是个有效的赋值,导致默认值3不会生效。
四、promise、async await、generator的区别
async/await
时javascript编写异步程序的新方法。以往的异步方法无外乎回调函数和Promise
。但是async/await
建立于Promise
之上。
① 什么是async/await?
- async/await是写异步代码的新方式,以前的方法有回调函数和Promise;
- async/await是基于Promise实现的,它不能用于普通的回调函数;
- async/await与Promise一样,是非阻塞的;
- async/await使得异步代码看起来像同步代码。
② peomise和async的区别:
- 代码简洁:
//promise: const makeRequest = () => getJSON() .then(data => { console.log(data) return "done" }) makeRequest() //async/await : const makeRequest = async () => { console.log(await getJSON()) return "done" } makeRequest()
- 可以同时处理
同步+异步
://promise const makeRequest = () => { try { getJSON() .then(result => { // this parse may fail const data = JSON.parse(result) console.log(data) }) } catch (err) { console.log(err) } } //async const makeRequest = async () => { try { // this parse may fail const data = JSON.parse(await getJSON()) console.log(data) } catch (err) { console.log(err) } }
- 简单实现多层嵌套:
同样的场景,使用async/await会非常简单://promise const makeRequest = () => { return promise1() .then(value1 => { // do something return promise2(value1) .then(value2 => { // do something return promise3(value1, value2) }) }) } //我们可以使用Promise.all来避免很深的嵌套 const makeRequest = () => { return promise1() .then(value1 => { // do something return Promise.all([value1, promise2(value1)]) }) .then(([value1, value2]) => { // do something return promise3(value1, value2) }) }
const makeRequest = async () => { const value1 = await promise1() const value2 = await promise2(value1) return promise3(value1, value2) }
使用async/await特点:
- 在主题函数之前使用了
async
关键字,在函数体内,使用了await
关键字; await
关键字只能出现在用async
声明的函数体内;- 当函数执行的时候,一旦遇到
await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
③ async 和 Generator
Gennerator函数是将函数分步骤阻塞,只有主动调用next()才能进行下一步。dva中异步处理用的是Generator。我们用async和Generator读取两个文件:
// Generator
var gen = function* () {
var f1 = yield readFile('/etc/fatab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
// async
var ayncReadFile = async function () {
var f1 = await readFile('/etc/fatab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
可以说async是Generator函数的语法糖。async对Generator函数做了以下4点改变:
- 内置执行器:async函数自带执行器,使用方法为asyncReadFile();
- 更好的语义:async表示函数里有异步操作,await表示紧跟后面的表达式需要等待结果;
- 更广的适用性:yield命令后,只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值;
- 返回值是Promise对象,可以使用then方法指定下一步操作。
简单地说async
函数相当于自执行的Generator
函数,相当于自带一个状态机,在await
的部分等待返回,返回后自动执行下一步。而且相较于Promise
,async
的优越性就是把每次异步返回的结果从then
中拿到最外层的方法中,不需要链式调用,只要用同步的写法就可以了。
比Promise
直观,但async
必须从一个Promise
对象开始,所以async
通常是和Promise
结合使用的。
五、ES6的继承和ES5相比有什么不同
- ES5的继承是通过
prototype
或构造函数机制来实现的; - ES5的继承实质上是先创建子类的实列对象,然后再将父类的方法添加到
this
上(parent.apply(this)
)。 - ES6的继承机制实质上是先创建父类的实例对象
this
(所以必须先调用父类的super()
方法),然后用子类的构造函数修改this
。具体为ES6通过class
关键字定义类,里面有构造方法,类之间通过extends
关键字实现继承。子类必须在constructor
方法中调用super
方法,否则新建实例报错。因为字类没有自己的this
对象,而是继承了父类的this对象,然后对其调用。如果不调用super()方法,子类得不到this对象。
注意: super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可以使用this
关键字,否则报错。
六、JS模块化(commonjs/AMD/CMD/ES6)
模块化是一个语言膨胀的必经之路,它能够帮助开发者拆分和组织代码。
Module模式
在模块化规范形成之前,JS开发者使用Module设计模式来解决JS全局作用域的污染问题。Module模式最初被定义为一种在传统软件工程中为类提供私有和公有封装的方法。在JavaScript中,Module模式使用匿名函数自调用(闭包)
来封装,通过自定义暴露行为来区分私有成员和公有成员。
let myModule = (function (window) {
let moduleName = 'module' // private
// public
function setModuleName(name) {
moduleName = name
}
// public
function getModuleName() {
return moduleName
}
return { setModuleName, getModuleName } // 暴露行为
})(window)
上面例子是Module模式的一种写法,它通过闭包的特性打开了一个新的作用域,缓解了全局作用域命名冲突和安全性的问题。但是,开发者并不能用它来组织和拆分代码,于是乎便出现了以此为基石的模块化规范。
模块化规范
1、CommonJS
CommonJS主要用在Node开发上,每个文件就是一个模块,每个文件都有自己的一个作用域。通过module.exports
暴漏public成员。例如:
// 文件名:x.js
let x = 1;
function add() {
x += 1;
return x;
}
module.exports.x = x;
module.exports.add = add;
此外,CommonJS通过require()引入模块依赖,require函数可以引入Node的内置模块、自定义模块和npm等第三方模块。
// 文件名:main.js
let xm = require('./x.js');
console.log(xm.x); // 1
console.log(xm.add()); // 2
console.log(xm.x); // 1
从上面代码我们可以看出,require函数同步加载了x.js
,并且返回了module.exports
输出字面量的拷贝值。可能有人会问module.exports.x = x;
不是赋值吗,怎么回事呢?我们说,Module模式是模块化规范的基石,CommonJS也是对Module模式的一种封装。我们完全可以用Module模式来实现上面的代码效果:
let xModule = (function (){
let x = 1;
function add() {
x += 1;
return x;
}
return { x, add };
})();
let xm = xModule;
console.log(xm.x); // 1
console.log(xm.add()); // 2
console.log(xm.x); // 1
通过Module模式模拟的CommonJS原理,我们就可以很好的解释CommonJS的特性了。因为CommonJS需要通过赋值的方式来获取匿名函数自调用的返回值,所以require函数在加载模块是同步的。然而CommonJS模块的加载机制局限了CommonJS在客户端上的使用,因为通过HTTP同步加载CommonJS模块是非常耗时的。
2、AMD和CMD
2.1、AMD
// 定义AMD规范的模块
define(function() {
return 模块
})
区别于CommonJS,AMD规范的被依赖
模块是异步加载的,而定义的模块是被当作回调函数来执行的,依赖于require.js模块管理工具库。当然,AMD规范不是采用匿名函数自调用
的方式来封装,我们依然可以利用闭包的原理来实现模块的私有成员和公有成员:
define(['module1', 'module2'], function(m1, m2) {
let x = 1;
function add() {
x += 1;
return x;
}
return { add };
})
2.2、CMD
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置,CMD 推崇依赖就近。
define(function(require, exports, module) {
// 同步加载模块
var a = require('./a');
a.doSomething();
// 异步加载一个模块,在加载完成时,执行回调
require.async(['./b'], function(b) {
b.doSomething();
});
// 对外暴露成员
exports.doSomething = function() {};
});
// 使用模块
seajs.use('path');
CMD集成了CommonJS和AMD的的特点,支持同步和异步加载模块。CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。因此,在CMD中require函数同步加载模块时没有HTTP请求过程。
3、ES6 module
ES6的模块化已经不是规范了,而是JS语言的特性。随着ES6的推出,AMD和CMD也随之成为了历史。ES6模块与模块化规范相比,有两大特点:
- 模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- 模块化规范是运行时加载,ES6 模块是编译时输出接口。
模块化规范输出的是一个对象,该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,ES6 module 是一个多对象输出,多对象加载的模型。从原理上来说,模块化规范是匿名函数自调用的封装,而ES6 module则是用匿名函数自调用去调用输出的成员
。
扩展
描述以下ES6中的“…”扩展运算符(2020.7.28)
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
如何理解JS中的Iterable(2020.8.3)
1、字面意思(含义)
就是可迭代的,可重复的。Iterable是ES6标准引用的新类型,Array、Map和Set都属于Iterable类型。
2、为什么加入Iterable(作用)
- 遍历Array可以采用下标循环;遍历Map和Set无法使用下表。结合类型不统一。
- 对于Array类型的for…in循环,当添加额外的属性后,会有意外效果,如:
/*
1. for ... in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
2. for ... in循环将把name包括在内,但Array的length属性却不包括在内。
*/
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
alert(x); // '0', '1', '2', 'name'
}
因此统一集合类型Iterable中的for…of循环。
3、Iterable使用方法
Iterable的forEach()方法,它接收一个函数,每次迭代就自动回调该函数。
//Array
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
alert(element);
});
//Set
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
alert(element);
});
//Map
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
alert(value);
});