ES6

介绍目前常见前端面试题,参考链接 https://interview2.poetries.top/

ES6

ES6 的了解

  • 新增模板字符串(为JavaScript提供了简单的字符串插值功能)
  • 箭头函数(操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs。)
  • for-of(用来遍历数据—例如数组中的值。)
  • arguments对象可被不定参数和默认参数完美代替。
  • ES6promise对象纳入规范,提供了原生的Promise对象。
  • 增加了letconst命令,用来声明变量。
  • 增加了块级作用域。let命令实际上就增加了块级作用域。
  • ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;
  • let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。
  • 还有就是引入module模块的概念

了解ECMAScript6的新特性?

  • 块级作用区域 let a = 1;
  • 可定义常量 const PI = 3.141592654;
  • 变量解构赋值 var [a, b, c] = [1, 2, 3];
  • 字符串的扩展(模板字符串) var sum =${a + b};
  • 数组的扩展(转换数组类型) Array.from($('li'));
  • 函数的扩展(扩展运算符) [1, 2].push(...[3, 4, 5]);
  • 对象的扩展(同值相等算法) Object.is(NaN, NaN);
  • 新增数据类型(Symbol) let uid = Symbol('uid');
  • 新增数据结构(Map) let set = new Set([1, 2, 2, 3]);
  • for…of循环 for(let val of arr){};
  • Promise对象 var promise = new Promise(func);
  • Generator函数 function* foo(x){yield x; return x*x;}
  • 引入Class(类) class Foo {}
  • 引入模块体系 export default func;
  • 引入async函数[ES7]
1
2
3
4
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value)
}

❤ ES6的箭头函数

箭头函数与普通函数的区别?

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数
  • 得分点 没有this、this是从外部获取、不能使用new、没有arguments、没有原型和super

  • 标准回答 箭头函数相当于匿名函数,简化了函数定义。

    • 箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句,不可以省略{}和return。
      • 箭头函数最大的特点就是没有this,所以this是从外部获取,就是继承外部的执行上下文中的this,
      • 由于没有this关键字所以箭头函数也不能作为构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
      • 同时通过 call()apply() 方法调用一个函数时,只能传递参数(不能绑定this),第一个参数会被忽略。箭头函数也没有原型和super。
      • 不能使用yield关键字,因此箭头函数不能用作 Generator 函数。不能返回直接对象字面量。
  • 加分回答

    • 箭头函数的不适用场景:

      • 定义对象上的方法 当调用 dog.jumps 时,lives 并没有递减。因为 this 没有绑定值,而继承父级作用域。

        1
        2
        3
        4
        var dog = { 
        lives: 20,
        jumps: () => { this.lives--; }
        }
      • 不适合做事件处理程序 此时触发点击事件,this不是button,无法进行class切换

        1
        2
        3
        4
        var button = document.querySelector('button'); 
        button.addEventListener('click', () => {
        this.classList.toggle('on');
        });
    • 箭头函数函数适用场景:

      • 简单的函数表达式,内部没有this引用,没有递归、事件绑定、解绑定,适用于map、filter等方法中,写法简洁

        1
        2
        var arr = [1,2,3]; 
        var newArr = arr.map((num)=>num*num)
      • 内层函数表达式,需要调用this,且this应与外层函数一致时

        1
        2
        3
        4
        5
        6
        7
        8
        9
        let group = { 
        title: "Our Group",
        students: ["John", "Pete", "Alice"],
        showList() {
        this.students.forEach(
        student => alert(this.title + ': ' + student) );
        }
        };
        group.showList();

ES5、ES6和ES2015有什么区别?

ES2015特指在2015年发布的新一代JS语言标准,ES6泛指下一代JS语言标准,包含ES2015ES2016ES2017ES2018等。现阶段在绝大部分场景下,ES2015默认等同ES6ES5泛指上一代语言标准。ES2015可以理解为ES5ES6的时间分界线

babel是什么,有什么作用?

babel是一个 ES6 转码器,可以将 ES6 代码转为 ES5 代码,以便兼容那些还没支持ES6的平台

let有什么用,有了 var 为什么还要用let?

ES6之前,声明变量只能用varvar方式声明变量其实是很不合理的,准确的说,是因为ES5里面没有块级作用域是很不合理的。没有块级作用域回来带很多难以理解的问题,比如for循环var变量泄露,变量覆盖等问题。let声明的变量拥有自己的块级作用域,且修复了var声明变量带来的变量提升问题。

ECMAScript6 怎么写class么

  • 这个语法糖可以让有OOP基础的人更快上手js,至少是一个官方的实现了
  • 但对熟悉js的人来说,这个东西没啥大影响;一个Object.creat()搞定继承,比class简洁清晰的多

Promise

参考链接

这里你谈 promise的时候,除了将他解决的痛点以及常用的 API 之外,最好进行拓展把 eventloop 带进来好好讲一下,microtask(微任务)、macrotask(任务) 的执行顺序,如果看过 promise 源码,最好可以谈一谈 原生 Promise 是如何实现的。Promise 的关键点在于callback 的两个参数,一个是 resovle,一个是 reject。还有就是 Promise 的链式调用(Promise.then(),每一个 then 都是一个责任人)

  • PromiseES6 新增的语法,解决了回调地狱的问题。
  • 可以把 Promise看成一个状态机。初始是 pending 状态,可以通过函数 resolvereject,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
  • then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了。 对于 then 来说,本质上可以把它看成是 flatMap

1. Promise 的基本情况

简单来说它就是一个容器,里面保存着某个未来才会结束的事件(通常是异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息

一般 Promise 在执行过程中,必然会处于以下几种状态之一。

  • 待定(pending):初始状态,既没有被完成,也没有被拒绝。
  • 已完成(fulfilled):操作成功完成。
  • 已拒绝(rejected):操作失败。

待定状态的 Promise 对象执行的话,最后要么会通过一个值完成,要么会通过一个原因被拒绝。当其中一种情况发生时,我们用 Promisethen 方法排列起来的相关处理程序就会被调用。因为最后 Promise.prototype.thenPromise.prototype.catch 方法返回的是一个 Promise, 所以它们可以继续被链式调用

关于 Promise 的状态流转情况,有一点值得注意的是,内部状态改变之后不可逆,你需要在编程过程中加以注意。文字描述比较晦涩,我们直接通过一张图就能很清晰地看出 Promise 内部状态流转的情况

img

从上图可以看出,我们最开始创建一个新的 Promise 返回给 p1 ,然后开始执行,状态是 pending,当执行 resolve之后状态就切换为 fulfilled,执行 reject 之后就变为 rejected 的状态

2. Promise 的静态方法

  • all 方法
    • 语法: Promise.all(iterable)
    • 参数: 一个可迭代对象,如 Array
    • 描述: 此方法对于汇总多个promise的结果很有用,在 ES6 中可以将多个Promise.all异步请求并行操作,返回结果一般有下面两种情况。
      • 当所有结果成功返回时按照请求顺序返回成功结果。
      • 当其中有一个失败方法时,则进入失败方法
  • 我们来看下业务的场景,对于下面这个业务场景页面的加载,将多个请求合并到一起,用 all 来实现可能效果会更好,请看代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 在一个页面中需要加载获取轮播列表、获取店铺列表、获取分类列表这三个操作,页面需要同时发出请求进行页面渲染,这样用 `Promise.all` 来实现,看起来更清晰、一目了然。


//1.获取轮播数据列表
function getBannerList(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve('轮播数据')
},300)
})
}
//2.获取店铺列表
function getStoreList(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve('店铺数据')
},500)
})
}
//3.获取分类列表
function getCategoryList(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve('分类数据')
},700)
})
}
function initLoad(){
Promise.all([getBannerList(),getStoreList(),getCategoryList()])
.then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
}
initLoad()
  • allSettled方法
    • Promise.allSettled 的语法及参数跟 Promise.all 类似,其参数接受一个 Promise 的数组,返回一个新的 Promise唯一的不同在于,执行完之后不会失败,也就是说当 Promise.allSettled 全部处理完成后,我们可以拿到每个 Promise 的状态,而不管其是否处理成功
1
2
3
4
5
6
7
8
9
10
11
const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// [
// { status: 'fulfilled', value: 2 },
// { status: 'rejected', reason: -1 }
// ]

从上面代码中可以看到,Promise.allSettled 最后返回的是一个数组,记录传进来的参数中每个 Promise 的返回值,这就是和 all 方法不太一样的地方。

  • any方法
    • 语法: Promise.any(iterable)
    • 参数: iterable 可迭代的对象,例如 Array
    • 描述: any 方法返回一个 Promise,只要参数 Promise 实例有一个变成 fulfilled状态,最后 any返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态。
1
2
3
4
5
6
7
8
const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const anyPromise = Promise.any([resolved, rejected]);
anyPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// 2

从改造后的代码中可以看出,只要其中一个 Promise 变成 fulfilled状态,那么 any 最后就返回这个promise。由于上面 resolved 这个 Promise 已经是 resolve 的了,故最后返回结果为 2

  • race方法
    • 语法: Promise.race(iterable)
    • 参数: iterable 可迭代的对象,例如 Array
    • 描述: race方法返回一个 Promise,只要参数的 Promise 之中有一个实例率先改变状态,则 race 方法的返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数
  • 我们来看一下这个业务场景,对于图片的加载,特别适合用 race 方法来解决,将图片请求和超时判断放到一起,用 race 来实现图片的超时判断。请看代码片段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){ resolve(img); }
img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){ reject('图片请求超时'); }, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});

// 从上面的代码中可以看出,采用 Promise 的方式来判断图片是否加载成功,也是针对 Promise.race 方法的一个比较好的业务场景

img

promise手写实现,面试够用版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function myPromise(constructor){
let self=this;
self.status="pending" //定义状态改变前的初始状态
self.value=undefined; //定义状态为resolved的时候的状态
self.reason=undefined; //定义状态为rejected的时候的状态
function resolve(value){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
// 定义链式调用的then方法
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}

详细源码

https://segmentfault.com/a/1190000013396601

对 promise 的了解

  • 依照 Promise/A+ 的定义,Promise 有四种状态:
    • pending: 初始状态, 非 fulfilledrejected.
    • fulfilled: 成功的操作.
    • rejected: 失败的操作.
    • settled: Promise已被fulfilledrejected,且不是pending
  • 另外, fulfilledrejected一起合称 settled
  • Promise 对象用来进行延迟(deferred) 和异步(asynchronous) 计算
  • 可以把 Promise看成一个状态机。初始是 pending 状态,可以通过函数 resolvereject,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
  • then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then调用就失去意义了

Promise 的构造函数

  • 构造一个 Promise,最基本的用法如下:
1
2
3
4
5
6
7
var promise = new Promise(function(resolve, reject) {
if (...) { // succeed
resolve(result);
} else { // fails
reject(Error(errMessage));
}
});
  • Promise 实例拥有 then 方法(具有 then 方法的对象,通常被称为thenable)。它的使用方法如下:
1
promise.then(onFulfilled, onRejected)
  • 接收两个函数作为参数,一个在 fulfilled 的时候被调用,一个在rejected的时候被调用,接收参数就是 futureonFulfilled 对应resolve, onRejected对应 reject

什么是 Promise ?

  • Promise 就是一个对象,用来表示并传递异步操作的最终结果
  • Promise 最主要的交互方式:将回调函数传入 then 方法来获得最终结果或出错原因
  • Promise 代码书写上的表现:以“链式调用”代替回调函数层层嵌套(回调地狱)