ECMAScript规范发展以及ES6常用语法(一)

130 阅读3分钟

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。

  1. 而let引入了块级作用域的概念, 创建setTimeout函数时,变量i在作用域内。对于循环的每个迭代,引用的i是i的不同实例。

  2. 还存在变量提升的问题

console.log(i)
var i = 1;

console.log(letI)
let letI = 2;
  1. const就很简单了, 在let的基础上, 不可被修改.

箭头函数

  1. 最大的区别:箭头函数里的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());
  1. 简写箭头函数
const arrowFn = (value) => Number(value);

console.log(arrowFn('aaa'))
  1. 注意, 箭头函数不能被用作构造函数

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]);
    }
}

解构

  1. 数组的解构
// 基础类型解构
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
  1. 对象的结构
// 对象属性解构
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
  1. 解构的原理是什么?

针对可迭代对象的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);
}

待续。。。。。。