1、ES5、ES6和ES2015有什么区别?
ES2015
特指在2015年发布的新一代JS语言标准。ES6
泛指下一代JS语言标准,包含ES2015
、ES2016
、ES2017
、ES2018
等。现阶段在大部分场景下,ES2015
默认等同于ES6
。ES5
泛指上一代语言标准。ES2015
可以理解为ES5
和ES6
的时间分界线。
2、Babel是什么?有什么作用?
Babel
是一个ES6
转码器,可以将ES6
代码转为ES5
代码,以便兼容哪些还没支持ES6
的平台。
3、let有什么用?有了var为什么还要用let?
在
ES6
之前,声明变量只能用var,var声明变量其实是很不合理的,准确的说,是因为ES5
里面没有块级作用域是很不合理的,没有块级作用域带来了很多难以理解的问题,比如for
循环中var
声明变量泄露问题,变量覆盖等问题。let
声明的变量拥有自己的块级作用域,且修复了var
声明变量带来的变量提升问题。
4、举一些ES6对String字符串类型做的常用升级优化?
优化部分
ES6
新增了字符串模板,在拼接大段字符串时,用反斜杠(``)取代以往的字符串相加的形式,能保留所有空格和换行,使得字符串拼接看起来更加直观、更加优雅。
升级部分
ES6
在String
原型上新增了includes
方法,用于取代传统的只能用indexOf
查找包含字符串的方法(indexOf
返回-1表示没查找到,不如includes
方法返回false
更明确,语义更清晰),此外还新增了startsWith()
,endsWith()
,padStart()
,padEnd()
,repeat()
等方法,可方便的用于查找,补全字符串。
5、举一些ES6对Array数组类型做的常用升级优化?
优化部分
- 数组解构赋值。
ES6
可以直接以let [a,b,c]=[1,2,3]
的形式进行变量赋值,在声明较多变量时,不用再写很多let
,且映射关系清晰,且支持赋默认值。 - 扩展运算符。
ES6
新增的扩展运算符(...
)可以轻松的实现数组和松散序列的相互转化,可以取代arguments
对象和apply
方法,轻松获取未知参数个数情况下的参数集合。(尤其是在ES5
中,arguments
并不是一个真正的数组,而是一个类数组的对象,但是扩展运算符的逆运算却可以返回一个真正的数组)。扩展运算符还可以轻松方便的实现数组的复制和解构赋值(let a = [1,2,3]
;let b = [...a]
)
升级部分
ES6
在Array
原型上新增了find()
方法,用于取代传统的只能用indexOf
查找包含数组项目的方法,且修复了indexOf
查找不到NaN
的bug([NaN].indexOf(NaN) === -1
),此外还新增了copyWithin()
,includes()
,fill()
,flat()
等方法,可方便的用于数组的查找,补全,转换等。
6、举一些ES6对Number数字类型做的常用升级优化
优化部分
ES6
在Number
原型上新增了isFinite()
,isNaN()
方法,用来取代传统的全局isFinite()
,isNaN()
方法检测数值是否有限、是否是NaN
。ES5
的isFinite()
,isNaN()
方法都会先将非数值类型的参数转化为Number
类型再做判断,这其实是不合理的,最终造成isNaN('NaN')===true
的奇怪行为,'NaN'
是一个字符串,但是isNaN
却说这就是NaN
。而Number.isFinite()
和Number.isNaN()
则不会有此类问题(Number.isNaN('NaN') === false
)。
升级部分
ES6
在Math
对象上新增了Math.cbrt()
,Math.trunc()
,Math.hypot()
等较多的科学计数法运算方法,可以更加全面的进行立方根、求和立方根等科学计算。Math.cbrt()
方法返回任意数字的立方根。Math.trunc()
方法会将数字的小数部分去掉,只保留整数部分。Math.hypot()
方法返回所有参数的平方和的平方根。
7、举一些ES6对Object类型做的常用升级优化?(重要)
优化部分
对象属性变量式声明。
ES6
可以直接以变量形式声明对象属性或方法,比传统的键值对形式声明更加简洁,更加方便,语义更加清晰。
let [apple,orange]=['red','yellow'];
let fruits = {apple, orange}; // let fruits = {apple: 'red', orange: 'yellow'};
尤其在对象解构赋值或者模块输出变量时,这种写法的好处体现的更为明显。
let {keys, values, entries} = Object;
let myOwnMethods = {keys, values, entries}; // let myOwnMethods = {keys:keys,values:values,entries:entries};
可以看到属性变量式声明看起来更加简洁明了。方法也可以采用简介写法
let es5Fun = {
method: function(){}
};
let es6Fun = {
method() {}
}
对象的解构赋值。
ES6
中对象也可以像数组解构赋值那样,进行变量的解构赋值
let {apple, orange} = {apple: 'red', orange: 'yellow'};
对象的扩展运算符(
...
)。ES6对象的扩展运算符和数组扩展运算符用法本质上差别不大,毕竟数组也是特殊的对象。对象的扩展运算符一个最常用也最好用的用处就在于可以轻松的取出一个目标对象内全部或者部分的可遍历属性,从而进行对象的合并和分解。
let {apple, orange, ...otherFruits} = {apple: 'red', orange: 'yellow', grape: 'purple', peach: 'pink'};
//otherFruits:{grape: 'purple', peach: 'pink'}
// 注意:对象的扩展运算符用在解构赋值时,扩展运算符只能用在最后一个参数(otherFruits后面不能再跟其他参数)
let moreFruits = {watermelon: 'green'};
let allFruits = {apple, orange, ...otherFruits, ...moreFruits};
super
关键字。ES6
在Class
类中新增了类似this
的关键字super
。同this
总是指向当前函数所在的对象不同,super
关键字总是指向当前函数所在对象的原型对象。
升级部分
ES6
在Object
原型上新增了is()
方法,做两个目标对象的相等比较,用来完善===
方法。===
方法中NaN === NaN //false
其实是不合理的,Object.is()
修复了这个小bug。Object.is(NaN, NaN) //true
ES6
在Object
原型上新增了assign()
方法,用于对象新增属性或者多个对象合并。
let target = {a: 1};
let source1 = {b: 2};
let source2 = {c: 3};
Object.assign(target, source1, source2);
// target: {a: 1, b: 2, c: 3};
注意:
assign()
合并的对象target
只能合并source1
、source2
中的自身属性,并不会合并source1
、source2
中的继承属性,也不会合并不可枚举的属性,且无法正确复制get
和set
属性(会直接执行get/set
函数,取return
的值)
ES6
在Object
原型上新增了getOwnPropertyDescriptors()
方法,此方法增强了ES5
中getOwnPropertyDescriptor()
方法,可以获取指定对象所有自身属性的描述对象。结合defineProperties()
方法,可以完美复制对象,包括复制get
和set
属性。ES6
在Object
原型上新增了getPrototypeOf()
和setPrototypeOf()
方法,用来获取或设置当前对象的prototype
对象。这个方法的意义在于,ES5
中获取/设置prototype
对象是通过__proto__
属性来实现的,然而__proto__
并不是ES规范中的标准属性,只是浏览器各大厂商私自加上去的属性,只不过因为使用范围广泛而被默认使用了,在非浏览器环境中不一定可以使用。所以稳妥起见,获取或设置当前对象的prototype
对象时,使用ES6
新增的标准用法。ES6
在Object
原型上还新增了Object.keys()
、Object.values()
、Object.entries()
方法,用来获取对象的所有键、所有值和所有键值对数组。
8、举一些ES6对Function函数类型做的常用升级优化?
优化部分
箭头函数(核心)。箭头函数是ES6核心的升级之一,箭头函数里没有自己的this,这改变了以往js函数中最让人难以理解的this运行机制。主要优化点:
- 箭头函数内this指向的是函数定义时所在的对象,而不是函数执行时所在的对象。ES5函数里的this总是指向函数执行时所在的对象,这使得很多情况下this的指向变得难以理解,尤其是非严格模式下,this有时会指向全局对象,这甚至可以归结为语言层面的bug之一。ES6的箭头函数优化了这一点,它的内部没有自己的this,这也就导致了this总是指向上一层的this,如果上一层还是箭头函数,则继续向上指,直到指向到有自己this的函数为止,并作为自己的this。
- 箭头函数不能用作构造函数,因为它没有自己的this,无法实例化。
- 也是因为箭头函数没有自己的this,所以箭头函数内也不存在
arguments
对象。(可以用扩展运算符代替) - 函数默认赋值。ES6之前,函数的形参是无法给默认值的,只能在函数内部通过变通方法实现。ES6以更简洁更明确的方式进行函数默认赋值。
function es6Fun(x, y='6') {
console.log(x,y);
}
es6Fun(4) // 4,6
升级部分
ES6新增了双冒号运算符,用来取代以往的
bind
,call
和apply
。(浏览器暂不支持,Babel已经支持转码)
foo::bar;
//等同于
bar.bind(foo);
foo::bar(...arguments);
//等同于
bar.apply(foo, arguments);
9、Symbol是什么,有什么作用?
Symbol
是ES6中引入的一种原始数据类型,所有Symbol()
生成的值都是独一无二的,可以从根本上解决对象属性太多导致属性名冲突覆盖的问题。对象中Symbol()
属性不能被for...in
遍历,但是也不是私有属性。
10、Set是什么,有什么作用?
Set
是ES6
中引入的一种类似Array
的新的数据结构,Set
实例的成员类似于数组item
成员,区别是Set
实例的成员都是唯一的,不重复的。这个特性可以轻松的实现数组去重。
let arr = [1,2,3,2,5,1];
let uniq = Array.from(new Set(arr));
// uniq [1,2,3,5]
11、Map是什么,有什么作用?
Map
是ES6
中引入的一种类似Object
的新的数据结构。Map
可以理解为是Object
的超集,打破了以传统键值对形式定义对象,对象的key
不再局限于字符串,也可以是Object
,可以更加全面的描述对象的属性。
12、Proxy是什么,有什么作用?
Proxy
是ES6
新增的一个构造函数,可以理解为js语言的一个代理,用来改变js默认的一些语言行为,包括拦截默认的get/set
等底层方法,使得js的使用自由度更高,可以最大限度的满足开发者的需求。比如通过拦截对象的get/set
方法,可以轻松的定制自己想要的key
或value
。下面的例子可以看到,随便定义一个myOwnObj
的key
,都可以变成自己想要的函数。
function createMyOwnObj() {
// 想把所有的key都变成函数,或者promise,或者anything
return new Proxy({}, {
get(target, propKey, receiver) {
return new Promise((resolve, reject) => {
setTimeout(()=> {
let randomBoolean = Math.random() > 0.5;
let message;
if (randomBoolean) {
message = `你的${propKey}运气不错,成功了`;
resolve(message);
} else {
message = `你的${propKey}运气不行,失败了`;
reject(message);
}
}, 1000);
})
}
})
}
let myOwnObj = createMyOwnObj();
myOwnObj.hahaha.then(result => {
console.log(result); // 你的hahaha运气不错,成功了
}).catch(error => {
console.log(error); // 你的hahaha运气不行,失败了
})
13、Reflect是什么,有什么作用?
Reflect
是ES6
引入的一个新的对象,它的作用主要有两点:一是将原生的一些零散分布在Object
、Function
或者全局函数里的方法(如apply
、delete
、get
、set
等等),统一整合到Reflect
上,这样可以更加方便更加统一的管理一些原生API,其次就是因为Proxy
可以改写默认的原生API,如果一旦原生API被改写可能就找不到了,所以Reflect
也可以起到备份原生API的作用,使得即使原生API被改写,也可以在被改写之后的API用上默认的API。
14、Promise是什么,有什么作用?
Promise
是ES6
引入的一个新的对象,它的主要作用是用来解决js异步机制里,回调机制产生的“回调地狱”。它并不是什么突破性的API,只是封装了异步回调形式,使得异步回调可以写的更加优雅,可读性更高,而且可以链式调用。
15、Iterator是什么,有什么作用?(重要)
Iterator
是ES6
中一个很重要的概念,它并不是对象,也不是任何一种数据类型。因为ES6
新增了Set
、Map
类型,它们和Array
、Object
类型很像,Array
、Object
都是可以遍历的,但是Set
、Map
都不能用for
循环遍历,解决这个问题有两种方案,一种是为Set
、Map
单独新增一个用来遍历的API,另一种是为Set
、Map
、Array
、Object
新增一个统一的遍历API,显然,第二种更好,ES6
也就顺其自然的需要一种设计标准,来统一所有可遍历类型的遍历方式。Iterator
正是这样一种标准,或者说是一种规范理念。- 就好像
JavaScript
是ECMAScript
标准的一种具体实现一样,Iterator
标准的具体实现是Iterator
遍历器。Iterator
标准规定,所有部署了key
值为[Symbol.iterator]
,且[Symbol.iterator]
的value
是标准的Iterator
接口函数(标准的Iterator
接口函数:该函数必须返回一个对象,且对象中包含next
方法,且执行next()
能返回包含value/done
属性的Iterator
对象)的对象,都称之为可遍历对象,next()
后返回的Iterator
对象也就是Iterator
遍历器。
// obj就是可遍历的,因为它遵循了Iterator标准,且包含[Symbol.iterator]方法,方法函数也符合标准的Iterator接口规范。
// obj.[Symbol.iterator]()就是Iterator遍历器。
let obj = {
data: ['hello', 'world'],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
}
}
}
ES6
给Set
、Map
、Array
、String
都加上了[Symbol.iterator]
方法,且[Symbol.iterator]
方法函数也符合标准的Iterator
接口规范,所以Set
、Map
、Array
、String
默认都是可以遍历的。
// Array
let array = ['red', 'green', 'blue'];
array[Symbol.iterator]() // Iterator遍历器
array[Symbol.iterator]().next() // {value: 'red', done: false}
// String
let string = 'abcdef';
string[Symbol.iterator]() // Iterator遍历器
string[Symbol.iterator]().next() // {value: 'a', done: false}
// Set
let set = new Set(['red', 'green', 'blue']);
set[Symbol.iterator]() // Iterator遍历器
set[Symbol.iterator]().next() // {value: 'red', done: false}
// Map
let map = new Map();
let obj = {map: 'map'};
map.set(obj, 'mapValue');
map[Symbol.iterator]().next() // {value: Array(2), done: false}
16、for…in和for…of有什么区别?
如果看到问题15,那么就很好回答。问题15提到了ES6统一了遍历标准,制定了可遍历对象,那么用什么方法去遍历呢?答案就是用
for...of
。ES6规定,所有部署了Iterator
接口的对象(可遍历对象)都可以通过for...of
去遍历,而for...in
仅仅可以遍历对象。
- 这也就意味着,数组也可以用
for...of
遍历,这极大地方便了数组的取值,且避免了很多程序用for...in
去遍历数组的恶习。
17、Generator函数是什么,有什么作用?
- 如果说
JavaScript
是ECMAScript
标准的一种具体实现,Iterator
遍历器是Iterator
的具体实现,那么Generator
函数可以说是Iterator
接口的具体实现方式。 - 执行
Generator
函数会返回一个遍历器对象,每一次Generator
函数里面的yield
都相当于一次遍历器对象的next()
方法,并且可以通过next(value)
方法传入自定义的value,来改变Generator
函数的行为。 Generator
函数可以通过配合Thunk
函数更轻松更优雅的实现异步编程和控制流管理。
18、async函数是什么,有什么作用?
async
函数可以理解为内置自动执行器的Generator
函数语法糖,它配合ES6
的Promise
近乎完美的实现了异步编程。
19、Class、extends是什么,有什么作用?
ES6
的Class
可以看作是一个ES5
生成实例对象的构造函数的语法糖,它参考了java
语言,定义了一个类的概念,让对象原型写法更加清晰,对象实例化更像是一种面向对象编程。Class
类可以通过extends
实现继承。它和ES5构造函数的不同点:
- 类的内部定义的所有方法,都是不可枚举的。
// ES5
function ES5Fun (x, y) {
this.x = x;
this.y = y;
}
ES5Fun.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
}
var p = new ES5Fun(1, 3);
p.toString();
Object.keys(ES5Fun.prototype); // ['toString']
// ES6
class ES6Fun {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`
}
}
Object.keys(ES6Fun.prototype); // []
ES6
的class
类必须用new
命令操作,而ES5
的构造函数不用new
也可以执行。ES6
的class
类不存在变量提升,必须先定义class
之后才能实例化,不像ES5
中可以将构造函数写在实例化之后。ES5
的继承,实质是先创造了子类的实例对象this
,然后再将父类的方法添加到this
上面。ES6
的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
20、module、export、import是什么,有什么作用?
module
、export
、import
是ES6
中用来统一前端模块化方案的设计思路和实现方案。export
、import
的出现统一了前端模块化的实现方案,整合规范了浏览器、服务端的模块化方法,用来取代传统的AMD/CMD
、requireJS
、seaJS
、commonJS
等一系列前端模块不同的实现方案,使得前端模块化更加统一规范,JS也能更适应大型的应用程序的开发。import
引入的模块是静态加载(编译时加载)而不是动态加载(运行时加载)。import
引入export
导出的接口值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
21、日常前端代码开发中,有哪些值得用ES6去改进的编程优化或者规范?
- 用箭头函数取代
var self = this;
- 用
let
取代var
- 用数组/对象的解构赋值来命名变量,结构更清晰,语义更明确,可读性更好。
- 在长字符串多变量组合场合,用模板字符串来取代字符串累加,获得更好的效果和阅读体验。
- 用
class
类取代传统的构造函数,来生成实例化对象。 - 在大型应用开发中,要保持
module
模块化开发思维,分清模块之间的关系,常用import
、export
方法。
22、谈谈对ES6的了解
新增模板字符串(为JavaScript提供了简单的字符串插值功能)、箭头函数(操作符左边为入参,而右边则是进行的操作以及返回的值inputs=>outputs)、
for...of
(用来遍历数据-例如数组中的值),arguments对象可被不定参数和默认参数完美代替,ES6将promise对象纳入规范,提供了原生的promise对象。增加了let和const命令,用来声明变量。增加了块级作用域。let命令实际上就增加了块级作用域。ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let、const、class声明的全局变量,不属于全局对象的属性。还有就是引入module模块的概念。
23、说说你对Promise的理解
- 依照Promise/A+的定义,Promise有四种状态:
- pending:初始状态,非fulfilled或rejected
- fulfilled:成功的操作
- rejected:失败的操作
- settled:Promise已被fulfilled或rejected,且不是pending
- 另外,fulfilled与rejected一起合称settled
- Promise对象用来进行延迟(deferred)和异步(asynchronous)计算
24、Promise的构造函数
- 构造一个Promise,最基本的用法如下:
const promise = new Promise((resolve, reject) => {
if(...) {
resolve(result);
} else {
reject(Error(errMessage));
}
})
- Promise实例拥有
then()
方法(具有then方法的对象,通常被称为thenable)。它的使用方法如下:
promise.then(onFulfilled, onRejected)
- promise接收两个函数作为参数,一个在fulfilled的时候被调用,一个在rejected的时候被调用,接收参数就是future,onFulfilled对应resolve,onRejected对应reject
25、什么是Promise?
- Promise就是一个对象,用来表示并传递异步操作的最终结果
- Promise最主要的交互方式:将回调函数传入then方法来获得最终结果或出错原因
- Promise代码书写上的表现:以链式调用代替回调函数层层嵌套(回调地狱)
26、谈谈你了解的ES6的新特性
- 块级作用域
let a = 1;
- 可定义常量
const PI = 3.141592654;
- 变量解构赋值
var [a, b, c] = [1, 2, 4];
- 字符串的扩展(模板字符串)
- 数组的扩展(转换数组类型)
- 函数的扩展(扩展运算符)
- 对象的扩展(同值相等算法)
Object.is(NaN, NaN);
- 新增原始数据类型(Symbol)
let uid = Symbol('uid');
- 新增数据结构(Set和Map)
let set = new Set([1,2,2,4]);
- for…of循环
for(let val of arr){}
- Promise对象
let promise = new Promise(func);
- Generator函数
function* foo(x){yield x; return x*x;}
- 引入Class类
class Foo {}
- 引入模块体系
export default func
- 引入async函数(es2017引入)
async function asyncPrint(value, ms) {
await setTimeout(() => {
value ++;
}, ms);
console.log(value)
}
27、Object.is()与原来的比较操作符===、== 的区别?
==
相等运算符,比较时会自动进行数据类型转换===
严格相等运算符,比较时不进行隐式类型转换Object.is()
同值相等算法,在===
基础上对0和NaN特别处理
+0 === -0 // true
NaN === NaN // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
28、什么是Babel?
Babel
是一个JS编译器,自带ES6语法转化器,用于转化JS代码。这些转化器让开发者提前使用最新的JS语法(ES6/ES7),而不用等浏览器全部兼容。Babel
默认只转换新的JS语法,而不转换新的API。