ECMAScript规范发展简介
相信大家都听说过ES6, ES7, ES2015, ES2016, ES2017...等等这些错综复杂的名字. 有的人以版本号描述, 比如ES6, ES7. 有的人以年份描述, 比如ES2015, ES2016.
那么他们之间到底是什么关系? 就要从js的发展看起了.
历史
总之: 严格来说, ES6是指2015年6月发布的ES2015标准, 但是很多人在谈及ES6的时候, 都会把ES2016 ES2017等标准的内容也带进去. 所以严谨的说, 在谈论ECMAScript标准的时候, 用年份更好一些. 但是也无所谓, 纠结这个没多大意义。
ESNext
其实ESNext是一个泛指, 它永远指向下一个版本. 比如当前最新版本是ES2020, 那么ESNext指的就是2021年6月将要发布的标准.
ES6及以后新增的常用API解析
let 和 const
先来一道经典面试题
for(var i=0;i<=3;i++){
setTimeout(function() {
console.log(i)
}, 10);
}
分别会输出什么? 为什么? 如何修改可以使其输出0,1,2,3?
for(var i = 0; i <=3; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 10);
})(i);
}
for(let i=0;i<=3;i++){
setTimeout(function() {
console.log(i)
}, 10);
}
原因: var定义的变量是全局的, 所以全局只有一个变量i. setTimeout是异步, 在下一轮事件循环, 等到执行的时候, 去找i变量的引用。所以函数找到了遍历完后的i, 此时它已经变成了4。
-
而let引入了块级作用域的概念, 创建setTimeout函数时,变量i在作用域内。对于循环的每个迭代,引用的i是i的不同实例。
-
还存在变量提升的问题
console.log(i)
var i = 1;
console.log(letI)
let letI = 2;
- const就很简单了, 在let的基础上, 不可被修改.
箭头函数
- 最大的区别:箭头函数里的this是定义的时候决定的, 普通函数里的this是使用的时候决定的。
const teacher = {
name: 'lubai',
getName: function() {
return `${this.name}`
}
}
console.log(teacher.getName());
const teacher = {
name: 'lubai',
getName: () => {
return `${this.name}`
}
}
console.log(teacher.getName());
- 简写箭头函数
const arrowFn = (value) => Number(value);
console.log(arrowFn('aaa'))
- 注意, 箭头函数不能被用作构造函数
class
class Test {
_name = '';
constructor() {
this.name = 'lubai';
}
static getFormatName() {
return `${this.name} - xixi`;
}
get name() {
return this._name;
}
set name(val) {
console.log('name setter');
this._name = val;
}
}
console.log(new Test().name)
console.log(Test.getFormatName())
模板字符串
const b = 'lubai'
const a = `${b} - xxxx`;
const c = `我是换行
我换行了!
我又换行了!
`;
面试题来一道. 编写render函数, 实现template render功能.
const year = '2021';
const month = '10';
const day = '01';
let template = '${year}-${month}-${day}';
let context = { year, month, day };
const str = render(template)({year,month,day});
console.log(str) // 2021-10-01
function render(template) {
return function(context) {
return template.replace(/\$\{(.*?)\}/g, (match, key) => context[key]);
}
}
解构
- 数组的解构
// 基础类型解构
let [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1, 2, 3
// 对象数组解构
let [a, b, c] = [{name: '1'}, {name: '2'}, {name: '3'}]
console.log(a, b, c) // {name: '1'}, {name: '2'}, {name: '3'}
// ...解构
let [head, ...tail] = [1, 2, 3, 4]
console.log(head, tail) // 1, [2, 3, 4]
// 嵌套解构
let [a, [b], d] = [1, [2, 3], 4]
console.log(a, b, d) // 1, 2, 4
// 解构不成功为undefined
let [a, b, c] = [1]
console.log(a, b, c) // 1, undefined, undefined
// 解构默认赋值
let [a = 1, b = 2] = [3]
console.log(a, b) // 3, 2
- 对象的结构
// 对象属性解构
let { f1, f2 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2
// 可以不按照顺序,这是数组解构和对象解构的区别之一
let { f2, f1 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2
// 解构对象重命名
let { f1: rename, f2 } = { f1: 'test1', f2: 'test2' }
console.log(rename, f2) // test1, test2
// 嵌套解构
let { f1: {f11}} = { f1: { f11: 'test11', f12: 'test12' } }
console.log(f11) // test11
// 默认值
let { f1 = 'test1', f2: rename = 'test2' } = { f1: 'current1', f2: 'current2'}
console.log(f1, rename) // current1, current2
- 解构的原理是什么?
针对可迭代对象的Iterator接口,通过遍历器按顺序获取对应的值进行赋值.
3.1 那么 Iterator 是什么?
Iterator是一种接口,为各种不一样的数据解构提供统一的访问机制。任何数据解构只要有Iterator接口,就能通过遍历操作,依次按顺序处理数据结构内所有成员。ES6中的for of的语法相当于遍历器,会在遍历数据结构时,自动寻找Iterator接口。
3.2 Iterator有什么用?
- 为各种数据解构提供统一的访问接口
- 使得数据解构能按次序排列处理
- 可以使用ES6最新命令 for of进行遍历
function generateIterator(array) {
let nextIndex = 0
return {
next: () => nextIndex < array.length ? {
value: array[nextIndex++],
done: false
} : {
value: undefined,
done: true
}
};
}
const iterator = generateIterator([0, 1, 2])
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
3.3 可迭代对象是什么?
可迭代对象是Iterator接口的实现。这是ECMAScript 2015的补充,它不是内置或语法,而仅仅是协议。任何遵循该协议点对象都能成为可迭代对象。可迭代对象得有两个协议:可迭代协议和迭代器协议。
-
可迭代协议:对象必须实现iterator方法。即对象或其原型链上必须有一个名叫Symbol.iterator的属性。该属性的值为无参函数,函数返回迭代器协议。
-
迭代器协议:定义了标准的方式来产生一个有限或无限序列值。其要求必须实现一个next()方法,该方法返回对象有done(boolean)和value属性。
3.4 我们自己来实现一个可以for of遍历的对象?
通过以上可知,自定义数据结构,只要拥有Iterator接口,并将其部署到自己的Symbol.iterator属性上,就可以成为可迭代对象,能被for of循环遍历。
const obj = {
count: 0,
[Symbol.iterator]: () => {
return {
next: () => {
obj.count++;
if (obj.count <= 10) {
return {
value: obj.count,
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
}
for (const item of obj) {
console.log(item)
}
或者
const iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator],
};
for (const item of iterable) {
console.log(item);
}