• ES6简述(持续更新…)


    前言

    ES6,全称ECMAScript 6,是ECMA委员会在 2015年6月正式发布的新ECMAScript标准。所以又称ECMAScript 2015,也就是说,ES6就是ES2015。至今各大浏览器厂商所开发的 JavaScript 引擎都还没有完成对 ES2015 中所有特性的完美支持,于是乎如 babelTraceur 等编译器便出现了。它们能将尚未得到支持的 ES2015 特性转换为 ES5 标准的代码,使其得到浏览器的支持。其中,babel 因其模块化转换器(Transformer)的设计特点赢得了绝大部份 JavaScript 开发者的青睐。

    一、变化

    一言以蔽之:ES2015 标准提供了许多新的语法和编程特性以提高 JavaScript 的开发效率和体验。

    二、新的语法

    1、let、const

    他们是继 var 之后,新的变量定义方法。

    const 更容易被理解:const 也就是 constant 的缩写,跟 C/C++ 等经典语言一样,用于定义常量,即不可变量。

    ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。第一种场景就是你现在看到的内层变量覆盖外层变量。而let则实际上为JavaScript新增了块级作用域。用它所声明的变量,只在let命令所在的代码块内有效。

    2、箭头函数 (arrow function)

    这个恐怕是ES6最最常用的一个新特性了,用它来写function比原来的写法要简洁清晰很多:

    function(i){ return i + 1; } //ES5
    (i) => i + 1 //ES6

    如果方程比较复杂,则需要用{}把代码包起来:

    function(x, y) { 
        x++;
        y--;
        return x + y;
    }
    (x, y) => {x++; y--; return x+y}

    除了看上去更简洁以外,arrow function还有一项超级无敌的功能!
    长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。例如:

    class Animal {
        constructor(){
            this.type = 'animal'
        }
        says(say){
            setTimeout(function(){
                console.log(this.type + ' says ' + say)
            }, 1000)
        }
    }
    
     var animal = new Animal()
     animal.says('hi')  //undefined says hi

    运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。所以为了让它能够正确的运行,传统的解决方法有两种:

    (1)第一种是将this传给self,再用self来指代this

    says(say){
       var self = this;
       setTimeout(function(){
         console.log(self.type + ' says ' + say)
    }, 1000);

    (2)第二种方法是用bind(this),即:

    says(say){
       setTimeout(function(){
         console.log(self.type + ' says ' + say)
    }.bind(this), 1000);

    但现在我们有了箭头函数,就不需要这么麻烦了:

    class Animal {
        constructor(){
            this.type = 'animal'
        }
        says(say){
            setTimeout( () => {
                console.log(this.type + ' says ' + say)
            }, 1000)
        }
    }
     var animal = new Animal()
     animal.says('hi')  //animal says hi

    当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。

    3、模板字符串(template string)

    这个东西也是非常有用,当我们要插入大段的html内容到文档中时,传统的写法非常麻烦,所以之前我们通常会引用一些模板工具库。

    可以先看下面一段代码:

    $("#result").append(
      "There are <b>" + basket.count + "</b> " +
      "items in your basket, " +
      "<em>" + basket.onSale +
      "</em> are on sale!"
    );

    我们要用一堆的'+'号来连接文本与变量,而使用ES6的新特性模板字符串``后,我们可以直接这么来写:

    $("#result").append(`
      There are <b>${basket.count}</b> items
       in your basket, <em>${basket.onSale}</em>
      are on sale!
    `);

    用反引号(`)来标识起始,用${}来引用变量,而且所有的空格和缩进都会被保留在输出之中。

    4、对象字面量扩展语法

    4.1 方法属性省略 function

    // es5
    function bar() {
      return 'bar'
    },
    
    // es6
    bar() {
      return 'bar'
    }

    4.2 支持 __proto__ 注入

    在 ES2015 中,我们可以给一个对象硬生生的赋予其 __proto__,这样它就可以成为这个值所属类的一个实例了。

    class Foo {
      constructor() {
        this.pingMsg = 'pong'
      }
    
      ping() {
        console.log(this.pingMsg)
      }
    }
    
    let o = {
      __proto__: new Foo()
    }
    
    o.ping() //=> pong

    有什么用呢?当我想扩展或者覆盖一个类的方法,并生成一个实例,但觉得另外定义一个类就感觉浪费了。那我可以这样做:

    let o = {
      __proto__: new Foo(),
    
      constructor() {
        this.pingMsg = 'alive'
      },
    
      msg: 'bang',
      yell() {
        console.log(this.msg)
      }
    }
    
    o.yell() //=> bang
    o.ping() //=> alive

    4.3 同名方法属性省略语法

    也是看上去有点鸡肋的新特性,不过在做 JavaScript 模块化工程的时候则有了用武之地。

    // module.js
    export default {
      someMethod
    }
    
    function someMethod() {
      // ...
    }
    
    // app.js
    import Module from './module'
    
    Module.someMethod()

    4.4 可以动态计算的属性名称(这个我觉得还是非常有用的)

    let arr = [1, 2, 3]
    let outArr = arr.map(n => {
      return {
        [ n ]: n,
        [ `${n}^2` ]: Math.pow(n, 2)
      }
    })
    console.dir(outArr) //=>
      [
        { '1': 1, '1^2': 1 },
        { '2': 2, '2^2': 4 },
        { '3': 3, '3^2': 9 }
      ]

    5、表达式解构(destructuring)

    这是es6相当有用的一个特性。

    ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

    例:

    let cat = 'ken'
    let dog = 'lili'
    let zoo = {cat: cat, dog: dog}
    console.log(zoo)  //Object {cat: "ken", dog: "lili"}

    用ES6完全可以像下面这么写:

    let cat = 'ken'
    let dog = 'lili'
    let zoo = {cat, dog}
    console.log(zoo)  //Object {cat: "ken", dog: "lili"}

    反过来可以这么写:

    let dog = {type: 'animal', many: 2}
    let { type, many} = dog
    console.log(type, many)   //animal 2

     6、默认参数(default)和后续参数(rest)

    6.1 默认参数

    调用animal()方法时忘了传参数,传统的做法就是加上这一句type = type || 'cat'来指定默认值。

    function animal(type){
        type = type || 'cat'  
        console.log(type)
    }
    animal()

    如果用ES6我们可以直接这么写:

    function animal(type = 'cat'){
        console.log(type)
    }
    animal()

    6.2 后续参数

    我们知道,函数的 call 和 apply 在使用上的最大差异便是一个在首参数后传入各个参数,一个是在首参数后传入一个包含所有参数的数组。

    如果我们在实现某些函数或方法时,也希望实现像 call 一样的使用方法,在ES5中我们得使用arguments

    function fetchSomethings() {
      var args = [].slice.apply(arguments)
    
      // ...
    }
    function doSomeOthers(name) {
      var args = [].slice.apply(arguments, 1)
    
      // ...
    }

    而在 ES6 中,我们可以很简单的使用 ... 语法糖来实现:

    function fetchSomethings(...args) {
      // ...
    }
    function doSomeOthers(name, ...args) {
      // ...
    }

    要注意的是,...args 后不可再添加。

    虽然从语言角度看,arguments 和 ...args 是可以同时使用 ,但有一个特殊情况则不可:arguments 在箭头函数中,会跟随上下文绑定到上层,所以在不确定上下文绑定结果的情况下,尽可能不要再箭头函数中再使用 arguments,而使用 ...args

    注意事项

    默认参数值后续参数需要遵循顺序原则,否则会出错。

    function(...args, last = 1) {
      // This will go wrong
    }

    三、新的数据类型

    在 ES5 中,JavaScript 中基本的数据类型:

    • String 字符串
    • Number 数字(包含整型和浮点型)
    • Boolean 布尔值
    • Object 对象
    • Array 数组

    其中又分为值类型引用类型,Array 其实是 Object 的一种子类。

    1、Set(集) 和 WeakSet(弱集)

    高中数学中,集不能包含相同的元素。

    let s = new Set()
    s.add('hello').add('world').add('hello')
    console.log(s.size) //=> 2
    console.log(s.has('hello')) //=> true

    在实际开发中,我们有很多需要用到集的场景,如搜索、索引建立等。

    WeakSet 在 JavaScript 底层作出调整(在非降级兼容的情况下),检查元素的变量引用情况。如果元素的引用已被全部解除,则该元素就会被删除,以节省内存空间。这意味著无法直接加入数字或者字符串。另外 WeakSet 对元素有严格要求,必须是 Object,当然了,你也可以用 new String('...') 等形式处理元素。

    let weaks = new WeakSet()
    weaks.add("hello") //=> Error
    weaks.add(3.1415) //=> Error
    
    let foo = new String("bar")
    let pi = new Number(3.1415)
    weaks.add(foo)
    weaks.add(pi)
    weaks.has(foo) //=> true
    foo = null
    weaks.has(foo) //=> false

    2、Map 和 WeakMap

    从数据结构的角度来说,映射(Map)跟原本的 Object 非常相似,都是 Key/Value 的键值对结构。但是 Object 有一个让人非常不爽的限制:key 必须是字符串或数字。在一般情况下,我们并不会遇上这一限制,但若我们需要建立一个对象映射表时,这一限制显得尤为棘手。

    而 Map 则解决了这一问题,可以使用任何对象作为其 key,这可以实现从前不能实现或难以实现的功能,如在项目逻辑层实现数据索引等。

    let map = new Map()
    let object = { id: 1 }
    
    map.set(object, 'hello')
    map.set('hello', 'world')
    map.has(object) //=> true
    map.get(object) //=> hello

    而 WeakMap 和 WeakSet 很类似,只不过 WeakMap 的键和值都会检查变量引用,只要其一的引用全被解除,该键值对就会被删除。

    let weakm = new WeakMap()
    let keyObject = { id: 1 }
    let valObject = { score: 100 }
    
    weakm.set(keyObject, valObject)
    weakm.get(keyObject) //=> { score: 100 }
    keyObject = null
    weakm.has(keyObject) //=> false

    四、类(Class)

    回想一下在 ES5 中,我们是怎么在 JavaScript 中实现类的?

    function Foo() {}
    var foo = new Foo()

    ES6 中的只是一种语法糖,用于定义原型(Prototype)的。

    1、语法

    1.1 定义

    class Person {
      constructor(name, gender, age) {
        this.name = name
        this.gender = gender
        this.age = age
      }
    
      isAdult() {
        return this.age >= 18
      }
    }
    
    let me = new Person('Me', 'man', 19)
    console.log(me.isAdult()) //=> true

    1.2 继承

    class Animal {
      say() {
        console.log('say')
      }
    }
    
    class Person extends Animal {
      constructor(name, gender, age) {
        super() // must call `super` before using `this` if this class has a superclass
    
        this.name = name
        this.gender = gender
        this.age = age
      }
    
      isAdult() {
        return this.age >= 18
      }
    }
    
    class Man extends Person {
      constructor(name, age) {
        super(name, 'man', age)
      }
    }
    
    let me = new Man('Me', 19)
    console.log(me.isAdult()) //=> true
    me.say() //=> say

    ES6 中若要是一个类继承于另外一个类而作为其子类,只需要在子类的名字后面加上 extends {SuperClass} 即可。

    1.3 静态方法

    ES6 中的类机制支持 static 类型的方法定义,比如说 Man 是一个类,而我希望为其定义一个 Man.isMan() 方法以用于类型检查,我们可以这样做:

    class Man {
      // ...
    
      static isMan(obj) {
        return obj instanceof Man
      }
    }
    
    let me = new Man()
    console.log(Man.isMan(me)) //=> true

    遗憾的是,ES2015 的类并不能直接地定义静态成员变量,但若必须实现此类需求,可以用static 加上 get 语句和 set 语句实现。

    class SyncObject {
      // ...
    
      static get baseUrl() {
        return 'http://example.com/api/sync'
      }
    }

    遗憾与期望

    就目前来说,ES6 的类机制依然很鸡肋:

    1. 不支持私有属性(private
    2. 不支持前置属性定义,但可用 get 语句和 set 语句实现
    3. 不支持多重继承
    4. 没有类似于协议(Protocl)或接口(Interface)等的概念

    五、生成器(Generator)

    Generator 的设计初衷是为了提供一种能够简便地生成一系列对象的方法,如计算斐波那契数列(Fibonacci Sequence)(俗称兔子数列):

    function* fibo() {
      let a = 1
      let b = 1
    
      yield a
      yield b
    
      while (true) {
        let next = a + b
        a = b
        b = next
        yield next
      }
    }
    
    let generator = fibo()
    
    for (var i = 0; i < 10; i++)
      console.log(generator.next().value) //=> 1 1 2 3 5 8 13 21 34 55

    如果你没有接触过 Generator,你一定会对这段代码感到很奇怪:为什么 function 后会有一个 *?为什么函数里使用了 while (true)却没有进入死循环而导致死机?yield 又是什么鬼?

    1、基本概念

    1.1 Generator Function

    生成器函数用于生成生成器(Generator),它与普通函数的定义方式的区别就在于它需要在 function 后加一个 * 。

    function* FunctionName() {
      // ...Generator Body
    }

    生成器函数的声明形式不是必须的,同样可以使用匿名函数的形式:

    let FunctionName = function*() { /* ... */ }

    生成器函数的函数内容将会是对应生成器的运行内容,其中支持一种新的语法 yield。它的作用与 return 有点相似,但并非退出函数,而是切出生成器运行时

    你可以把整个生成器运行时看成一条长长的面条(while (true) 则就是无限长的),JavaScript 引擎在每一次遇到 yield 就要切一刀,而切面所成的“纹路”则是 yield 出来的值。

    1.2 Generator

    生成器在某种意义上可以看做为与 JavaScript 主线程分离的运行时,它可以随时被 yield 切回主线程(生成器不影响主线程)。

    每一次生成器运行时被 yield 都可以带出一个值,使其回到主线程中;此后,也可以从主线程返回一个值回到生成器运行时中:

    let inputValue = yield outputValue

    生成器切出主线程并带出 outputValue,主函数经过处理后(可以是异步的),把 inputValue 带回生成器中;主线程可以通过 .next(inputValue) 方法返回值到生成器运行时中。

    2、基本使用方法

    2.1 构建生成器函数

    使用 Generator 的第一步自然是要构建生成器函数,理清构建思路。拿斐波那契数列作为例子:

    斐波那契数列的定义:第 n (n ≥ 3) 项是第 n - 1 项和第 n - 2 之和,而第 1 项和第 2 项都是 1。

    function* fibo() {
      let [a, b] = [1, 1]
    
      yield a
      yield b
    
      while (true) {
        [a, b] = [b, a + b]
        yield b
      }
    }

    这样设计生成器函数,就可以先把预先设定好的首两项输出,然后通过无限循环不断把后一项输出。

    2.2  启动生成器

    生成器函数不能直接用来作为生成器使用,需要先使用这个函数得到一个生成器,用于运行生成器内容和接收返回值。

    let gen = fibo()

    2.3 运行生成器内容

    得到生成器以后,我们就可以通过它进行数列项生成了。此处演示获得前 10 项。

    let arr = []
    for (let i = 0; i < 10; i++)
      arr.push(gen.next().value)
    
    console.log(arr) //=> [ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ]

    六、原生的模块化

    在ES6之前, 前端就使用RequireJS或者seaJS实现模块化, requireJS是基于AMD规范的模块化库,  而像seaJS是基于CMD规范的模块化库,  两者都是为了为了推广前端模块化的工具。

    现在ES6自带了模块化, 也是JS第一次支持module, 在很久以后 ,我们可以直接作用importexport在浏览器中导入和导出各个模块了, 一个js文件代表一个js模块;

    现代浏览器对模块(module)支持程度不同, 目前都是使用babelJS, 或者Traceur把ES6代码转化为兼容ES5版本的js代码。

    1、ES6的模块化的基本规则或特点:

    (1):每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;

    (2):每一个模块内声明的变量都是局部变量, 不会污染全局作用域;

    (3):模块内部的变量或者函数可以通过export导出;

    (4):一个模块可以导入别的模块。

    //lib.js
    //导出常量
    export const sqrt = Math.sqrt;
    //导出函数
    export function square(x) {
        return x * x;
    }
    //导出函数
    export function diag(x, y) {
        return sqrt(square(x) + square(y));
    }
    
    //main.js
    import { square, diag } from './lib';
    console.log(square(11)); // 121
    console.log(diag(4, 3)); // 5

    2、几种导出方式:

    2.1 内联导出

    export class Employee{  
      constructor(id, name, dob){  
        this.id = id;  
        this.name=name;  
        this.dob= dob;  
      }  
      getAge(){  
        return (new Date()).getYear() - this.dob.getYear();  
      }  
    }  
    export function getEmployee(id, name, dob){  
      return new Employee(id, name, dob);  
    }  
    var emp = new Employee(1, "Rina", new Date(1987, 1, 22));  

    案例中的模块导出了两个对象: Employee类,getEmployee函数。因对象emp未被导出,所以其仍为模块私有。

    2.2 导出一组对象

    在模块的末尾单独进行导出声明,以导出该模块中的需要导出的对象。

    class Employee{  
      constructor(id, name, dob){  
        this.id = id;  
        this.name=name;  
        this.dob= dob;  
      }  
      getAge(){  
        return (new Date()).getYear() - this.dob.getYear();  
      }  
    }  
    function getEmployee(id, name, dob){  
      return new Employee(id, name, dob);  
    }  
    var x = new Employee(1, "Rina", new Date(1987, 1, 22));  
    export {Employee, getEmployee};  

    在导出时,重命名对象也是可以的。如下例所示,Employee在导出时名字改为了Associate,函数GetEmployee改名为getAssociate。

    export {  
        Associate as Employee,  
        getAssociate as getEmployee  
    };  

    2.3 Default导出

    使用关键字default,可将对象标注为default对象导出。default关键字在每一个模块中只能使用一次。它既可以用于内联导出,也可以用于一组对象导出声明中。

    这种导出的方式不需要知道变量的名字, 相当于是匿名的, 直接把开发的接口给export;

    如果一个js模块文件就只有一个功能, 那么就可以使用default导出:

    function foo(..) {  
    // ..  
    }  
    export default foo;  
    // or:
    export{ foo as default }; 

    3 、导入

    3.1 无对象导入

    import './module1.js'; 

    3.2 导入默认对象

    import foo from "foo";  
    // or:  
    import { default as foo } from "foo";  

    3.3 导入命名的对象

    import { foo } from "foo";  

    当然也可在同一个声明中导入默认对象和命名对象。这种情况下,默认对象必须定义一个别名:

    import {default as d, foo} from './module1.js';  

    3.4 导入所有对象

    import * as allFromModule1 from './module1.js';  

    3.5 可编程式的按需导入

    如果想基于某些条件或等某个事件发生后再加载需要的模块,可通过使用加载模块的可编程API(programmatic API)来实现。使用System.import方法,可按程序设定加载模块。这是一个异步的方法,并返回Promise。

    System.import('./module1.js')  
        .then(function(module1){  
            //use module1  
        }, function(e){  
            //handle error  
        });  

    如果模块加载成功且将导出的模块成功传递给回调函数,Promise将会通过。如果模块名称有误或由于网络延迟等原因导致模块加载失败,Promise将会失败。

    等会,什么是Promise?

    七、Promise

    Promise 是一种用于解决回调函数无限嵌套的工具(当然,这只是其中一种),其字面意义为“保证”。它的作用便是“免去”异步操作的回调函数,保证能通过后续监听而得到返回值,或对错误处理。它能使异步操作变得井然有序,也更好控制。

    1、基本用法

    要为一个函数赋予 Promise 的能力,先要创建一个 Promise 对象,并将其作为函数值返回。Promise 构造函数要求传入一个函数,并带有 resolve 和 reject 参数。这是两个用于结束 Promise 等待的函数,对应的成功失败。而我们的逻辑代码就在这个函数中进行。

    function fetchData() {
        return new Promise((resolve, reject) => {
            if (/* 异步操作成功 */){
              resolve(value);
            } else {
              reject(error);
            }
        });
    }

    Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。

    如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);

    如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。

    基本的 api

    1. Promise.resolve()
    2. Promise.reject()
    3. Promise.prototype.then()
    4. Promise.prototype.catch()
    5. Promise.all() // 所有的完成

    6. Promise.race() // 竞速,完成一个即可

    2、进阶

    promises 的奇妙在于给予我们以前的 return 与 throw,每个 Promise 都会提供一个 then() 函数,和一个 catch(),实际上是 then(null, ...) 函数:

    somePromise().then(functoin(){
      // do something
    });

    我们可以做三件事:

    1. return 另一个 promise
    2. return 一个同步的值 (或者 undefined)
    3. throw 一个同步异常 ` throw new Eror('');`

     2.1 封装同步与异步代码

    new Promise(function (resolve, reject) {
      resolve(someValue); });
    // 写成 Promise.resolve(someValue);

    2.2  捕获同步异常

    new Promise(function (resolve, reject) {
      throw new Error('悲剧了,又出 bug 了'); }).catch(function(err){   console.log(err); });

    如果是同步代码,可以写成:

    Promise.reject(new Error("什么鬼"));

    2.3 多个异常捕获,更加精准的捕获

    somePromise.then(function() {
      return a.b.c.d();
    }).catch(TypeError, function(e) {
    //If a is defined, will end up here because
    //it is a type error to reference property of undefined
    }).catch(ReferenceError, function(e) {
    //Will end up here if a wasn't defined at all
    }).catch(function(e) {
    //Generic catch-the rest, error wasn't TypeError nor
    //ReferenceError
    });

    2.4 获取两个 Promise 的返回值

    2.4.1 .then 方式顺序调用

    2.4.2 设定更高层的作用域

    2.4.3 spread

    (未完待续……)

  • 相关阅读:
    自定义“浏览文件夹”对话框
    CYABFFW:这是另一个文件夹包装器
    CYABFFW:这是另一个文件夹包装器
    ToDoList样式表:教程
    7.2.23 -一个有效而灵活的方法来掌握你的任务
    使用。net SDK编写位图按钮控件
    在MVC应用程序中使用自动程序进行CRUD操作
    imp
    openpyxl
    fabric
  • 原文地址:https://www.cnblogs.com/fydxx/p/6652042.html
Copyright © 2020-2023  润新知