[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;
}
2
3
4
5
6
如果块级作用域中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错,称之为暂时性死区(temporal dead zone)。
类--Class
类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承(采用寄生组合继承实现)。
Class本质是什么?
class本质上还是基于构造函数实现的。
class Person {}
console.log(Person instanceof Function); // true
2
原型继承和Class继承
主要涉及:原型如何实现继承?Class如何实现继承?ES5继承与ES6继承的区别?Class本质是什么?
- ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
- ES6的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
在子类构造函数中为什么必须调用super函数
调用super函数其实就是调用父类的构造函数。
所以,当我们声明了constructor后必须主动调用super(),否则无法调用父构造函数,无法完成继承。
前端模块化
主要涉及:为什么要使用模块化?前端实现模块化的方式有哪几种,各有什么特点?
使用一个技术肯定是有原因的,那么使用模块化可以给我们带来以下好处:
- 解决命名冲突;
- 提高代码复用性;
- 提高代码可维护性。
- AMD:requirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置
- CMD:seajs 在推广过程中对模块定义的规范化产出,延迟执行,推崇依赖就近
- CommonJS:模块输出的是一个值的 copy,运行时加载,加载的是一个对象(module.exports 属性),该对象只有在脚本运行完才会生成
- ES6 Module:模块输出的是一个值的引用,编译时输出接口,ES6 模块不是对象,它对外接口只是一种静态定义,在代码静态解析阶段就会生成。
CommonJS中的require/exports和ES6中的import/export区别?
- CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。
- ES6模块是动态引用,如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
- import/export 最终都是编译为 require/exports 来执行的。
- CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports )是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。
- export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
Proxy
主要涉及:Proxy可以实现什么功能?相比于Object.defineProperty的优势在哪里?
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
箭头函数与普通函数的区别是什么?
主要涉及:构造函数可以使用new生成实例,那么箭头函数可以吗?为什么?
箭头函数表达式的语法比函数表达式更简洁,并且箭头函数没有的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:
- 箭头函数内的this对象,就是函数定义时所在的对象,而不是调用函数的对象。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替。
- 不可以使用yield命令,因此箭头函数不能用作Generator函数。
- 不可以使用new命令,因为:
- 没有自己的this,无法调用call,apply。
- 没有prototype属性 ,而 new 命令在执行时需要将构造函数的 prototype 赋值给新的对象的
__proto__
。
主要区别在this指向问题:
- 普通函数的this指向调用它的那个对象,例如obj.func,那么func中的this就是obj;箭头函数没有this,所以需要通过查找作用域链来确定this的值,如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this;
- 箭头函数不能作为构造函数,不能使用new,没有this、arguments、super等,这些只依赖包含箭头函数最接近的函数。
- 箭头函数,箭头函数的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
}
}
}
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;
}
2
3
4
5
6
7
8
9
10
11
12
模板字符串
模板字符串优势:
- 不需要转义字符即可实现多行的效果
- 字符串拼接更加简单
模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。
//ES5 Version
var greet = 'Hi I\'m Mark';
//ES6 Version
let greet = `Hi I'm Mark`;
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
`;
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} !`;
}
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);
});
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);
});
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);
2
3
4
5
6
7
8
9
10