[TOC] 箭头函数与普通函数的区别 promise、async await、Generator的区别 ES6的继承与ES5相比有什么不同 js模块化(commonjs/AMD/CMD/ES6)

ES6新特性

  • let/const(块级作用域)
  • 支持函数形参设置默认值
  • 解构赋值(对象和数组)
  • 剩余运算符和扩展运算符(...)
  • Set/Map
  • Symbol
  • 模板字符串
  • 箭头函数
  • Proxy代理
  • class类
  • Promise
  • async/await
  • 模块化

var、let及const区别

let/const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明(在同一个作用域内)、全局声明不会挂载到window对象上。 const: 声明常量,无法修改。

  • var声明的变量可以修改,如果不初始化会输出undefined,不会报错,其作用域为该语句所在的函数内,且存在变量提升现象;
  • let和const定义块级作用域变量,都不存在变量提升,必须先声明后使用
  • let声明的变量,其作用域为该语句所在的代码块内(块级作用域,函数内部使用let定义后,对函数外部无影响),不存在变量提升;
  • let和const不允许在相同作用域内,重复声明同一个变量。
  • const定义只读变量(即常量,内存地址不变),其定义的变量不可以修改(用来声明常量),而且必须初始化
  • const声明变量的同时必须赋值,const声明的变量必须初始化,一旦初始化完毕就不允许修改
  • const定义的对象\数组中的属性值可以修改,基础数据类型不可以

暂时性死区理解

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
1
2
3
4
5
6

如果块级作用域中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错,称之为暂时性死区(temporal dead zone)。

类--Class

类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承(采用寄生组合继承实现)。

Class本质是什么?

class本质上还是基于构造函数实现的。

class Person {}
console.log(Person instanceof Function); // true
1
2

原型继承和Class继承

主要涉及:原型如何实现继承?Class如何实现继承?ES5继承与ES6继承的区别?Class本质是什么?

  1. ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
  2. ES6的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

在子类构造函数中为什么必须调用super函数

调用super函数其实就是调用父类的构造函数。

所以,当我们声明了constructor后必须主动调用super(),否则无法调用父构造函数,无法完成继承

前端模块化

主要涉及:为什么要使用模块化?前端实现模块化的方式有哪几种,各有什么特点?

使用一个技术肯定是有原因的,那么使用模块化可以给我们带来以下好处:

  1. 解决命名冲突;
  2. 提高代码复用性;
  3. 提高代码可维护性。
  • AMD:requirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置
  • CMD:seajs 在推广过程中对模块定义的规范化产出,延迟执行,推崇依赖就近
  • CommonJS:模块输出的是一个值的 copy,运行时加载,加载的是一个对象(module.exports 属性),该对象只有在脚本运行完才会生成
  • ES6 Module:模块输出的是一个值的引用,编译时输出接口,ES6 模块不是对象,它对外接口只是一种静态定义,在代码静态解析阶段就会生成。

CommonJS中的require/exports和ES6中的import/export区别?

  1. CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。
  2. ES6模块是动态引用,如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
  3. import/export 最终都是编译为 require/exports 来执行的。
  4. CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports )是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性
  5. export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

Proxy

主要涉及:Proxy可以实现什么功能?相比于Object.defineProperty的优势在哪里?

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

箭头函数与普通函数的区别是什么?

主要涉及:构造函数可以使用new生成实例,那么箭头函数可以吗?为什么?

箭头函数表达式的语法比函数表达式更简洁,并且箭头函数没有的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数

箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:

  1. 箭头函数内的this对象,就是函数定义时所在的对象,而不是调用函数的对象。
  2. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替
  3. 不可以使用yield命令,因此箭头函数不能用作Generator函数
  4. 不可以使用new命令,因为:
    • 没有自己的this,无法调用call,apply。
    • 没有prototype属性 ,而 new 命令在执行时需要将构造函数的 prototype 赋值给新的对象的__proto__

主要区别在this指向问题:

  1. 普通函数的this指向调用它的那个对象,例如obj.func,那么func中的this就是obj;箭头函数没有this,所以需要通过查找作用域链来确定this的值,如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this;
  2. 箭头函数不能作为构造函数,不能使用new,没有this、arguments、super等,这些只依赖包含箭头函数最接近的函数
  3. 箭头函数,箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向,如call/bind/apply(或者说箭头函数中的this指向的是定义时的this,而不是执行时的this)。
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
            console.log(this.name); // My Object
         return function(){
           return this.name; // The Window
          };
    },
    b: () => {
           return this.name; // the window
       },
       c() {
           return () => {
              return this.name; // My Object
          }
       }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

new过程大致是这样的:

function newFunc(father, ...rest) {
  var result = {};
  result.__proto__ = father.prototype;
  var result2 = father.apply(result, rest);
  if (
    (typeof result2 === 'object' || typeof result2 === 'function') &&
    result2 !== null
  ) {
    return result2;
  }
  return result;
}
1
2
3
4
5
6
7
8
9
10
11
12

模板字符串

模板字符串优势:

  1. 不需要转义字符即可实现多行的效果
  2. 字符串拼接更加简单

模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。

//ES5 Version
var greet = 'Hi I\'m Mark';

//ES6 Version
let greet = `Hi I'm Mark`;
1
2
3
4
5

在ES5中我们需要使用一些转义字符来达到多行的效果,在模板字符串不需要这么麻烦:

//ES5 Version
var lastWords = '\n'
  + '   I  \n'
  + '   Am  \n'
  + 'Iron Man \n';


//ES6 Version
let lastWords = `
    I
    Am
  Iron Man
`;
1
2
3
4
5
6
7
8
9
10
11
12
13

在ES5版本中,我们需要添加\n以在字符串中添加新行。在模板字符串中,我们不需要这样做。

// ES5 Version
function greet(name) {
  return 'Hello ' + name + '!';
}


// ES6 Version
function greet(name) {
  return `Hello ${name} !`;
}
1
2
3
4
5
6
7
8
9
10

在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用+运算符。在模板字符串s中,我们可以使用${expr}嵌入一个表达式,这使其比 ES5 版本更整洁。

Set、Map、WeakSet和WeakMap的区别?

Map和Set的区别:

  • Set对象类似于数组,且成员的值都是唯一的。
  • Map对象是键值对集合,和JSON对象类似,但是key不仅可以是字符串还可以是对象。

WeakMap和Map的区别:

WeakMap结构与Map结构基本类似,唯一的区别是:WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。 WeakMap最大的好处是:可以避免内存泄漏。一个仅被WeakMap作为key而引用的对象,会被垃圾回收器回收掉。

WeakMap拥有和Map类似的set(key, value) 、get(key)、has(key)、delete(key) 和 clear() 方法,没有任何与迭代有关的属性和方法。

Promise 的理解

Promise对象代表一个异步操作,有三种状态:Pending(进行中)Resolved(已完成,又称Fulfilled)Rejected(已失败)

Promise中catch和reject的区别

const p = new Promise((resolve, reject) => {
    // throw Error('test');
    resolve(11);
});

p.then(data => {
    console.log(data);
    throw Error('test');
}, err => {
    console.log('err11', err);
}).catch(err => {
    console.log('catch22', err);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
const p = new Promise((resolve, reject) => {
    throw Error('test');
    // resolve(11);
});

p.then(data => {
    console.log(data);
}).catch(err => {
    console.log('catch22', err);
});
1
2
3
4
5
6
7
8
9
10
  • catch方法可以捕获到catch之前整条promise链路上所有抛出的异常。
  • then方法的第二个参数捕获的异常依赖于上一个Promise对象的执行结果。

promise.then(successCb, faildCd) 接收两个函数作为参数,来处理上一个promise对象的结果。then方法返回的是promise对象。第一种链式写法,使用catch,相当于给前面一个then方法返回的promise 注册回调,可以捕获到前面then没有被处理的异常。第二种是回调函数写法,仅为上一个promise注册异常回调。

如果是promise内部报错,reject抛出错误后,then 的第二个参数就能捕获得到,如果then的第二个参数不存在,则catch方法会捕获到。

如果是then的第一个参数函数 resolve 中抛出了异常,即成功回调函数出现异常后,then的第二个参数reject 捕获捕获不到,catch方法可以捕获到

Generator

遍历器对象生成函数,最大的特点是可以交出函数的执行权。

  • function 关键字与函数名之间有一个星号;
  • 函数体内部使用 yield 表达式,定义不同的内部状态;
  • next指针移向下一个状态

这里你可以说说 Generator 的异步编程,以及它的语法糖 async 和 awiat,传统的异步编程。ES6 之前,异步编程大致如下

  • 回调函数
  • 事件监听
  • 发布/订阅

async/await

async/await是Generator函数的语法糖。有更好的语义、更好的适用性、返回值是 Promise。

async => * await => yield

async function timeout (ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}
async function asyncConsole (value, ms) {
  await timeout(ms)
  console.log(value)
}
asyncConsole('hello async and await', 1000);
1
2
3
4
5
6
7
8
9
10