0
点赞
收藏
分享

微信扫一扫

【ES】155-重温基础:ES6系列(六)

【ES】155-重温基础:ES6系列(六)_实例化

ES6系列目录

  • 1 let 和 const命令
  • 2 变量的解构赋值
  • 3 字符串的拓展
  • 4 正则的拓展
  • 5 数值的拓展
  • 6 函数的拓展
  • 7 数组的拓展
  • 8 对象的拓展
  • 9 Symbol
  • 10 Set和Map数据结构
  • 11 Proxy
  • 12 Promise对象
  • 13 Iterator和 for...of循环
  • 14 Generator函数和应用
  • 15 Class语法和继承
  • 16 Module语法和加载实现

所有整理的文章都收录到我《Cute-JavaScript》系列文章中,访问地址:http://js.pingan8787.com

14 Generator函数和应用

14.1 基本概念

Generator​​函数是一种异步编程解决方案。
原理
执行 ​​Genenrator​​函数会返回一个遍历器对象,依次遍历 ​Generator​​函数内部的每一个状态。
​​Generator​函数是一个普通函数,有以下两个特征:

  • function​关键字与函数名之间有个星号;
  • 函数体内使用 ​yield​表达式,定义不同状态;

通过调用 ​next​​方法,将指针移向下一个状态,直到遇到下一个 ​yield​​表达式(或 ​return​​语句)为止。简单理解, ​Generator​​函数分段执行, ​yield​​表达式是暂停执行的标记,而 ​next​恢复执行。

  1. function * f (){
  2.    yield 'hi';
  3.    yield 'leo';
  4.    return 'ending';
  5. }
  6. let a = f();
  7. a.next();  // {value: 'hi', done : false}
  8. a.next();  // {value: 'leo', done : false}
  9. a.next();  // {value: 'ending', done : true}
  10. a.next();  // {value: undefined, done : false}

14.2 yield表达式

yield​​表达式是暂停标志,遍历器对象的 ​next​方法的运行逻辑如下:

  1. 遇到 ​yield​​就暂停执行,将这个 ​​yield​​后的表达式的值,作为返回对象的 ​​value​​属性值。
  2. 下次调用 ​next​​往下执行,直到遇到下一个 ​​yield​​。
  3. 直到函数结束或者 ​return​​为止,并返回 ​​return​​语句后面表达式的值,作为返回对象的 ​​value​​属性值。
  4. 如果该函数没有 ​return​​语句,则返回对象的 ​​value​​为 ​​undefined​​ 。

注意:

  • yield​​只能用在 ​​Generator​​函数里使用,其他地方使用会报错。
  1. // 错误1
  2. (function(){
  3.    yiled 1;  // SyntaxError: Unexpected number
  4. })()

  5. // 错误2  forEach参数是个普通函数
  6. let a = [1, [[2, 3], 4], [5, 6]];
  7. let f = function * (i){
  8.    i.forEach(function(m){
  9.        if(typeof m !== 'number'){
  10.            yield * f (m);
  11.        }else{
  12.            yield m;
  13.        }
  14.    })
  15. }
  16. for (let k of f(a)){
  17.    console.log(k)
  18. }
  • yield​表达式如果用于另一个表达式之中,必须放在圆括号内。
  1. function * a (){
  2.    console.log('a' + yield);     //  SyntaxErro
  3.    console.log('a' + yield 123); //  SyntaxErro
  4.    console.log('a' + (yield));     //  ok
  5.    console.log('a' + (yield 123)); //  ok
  6. }
  • yield​表达式用做函数参数或放在表达式右边,可以不加括号
  1. function * a (){
  2.    f(yield 'a', yield 'b');    //  ok
  3.    lei i = yield;              //  ok
  4. }

14.3 next方法

yield​​本身没有返回值,或者是总返回 ​undefined​​, ​next​​方法可带一个参数,作为上一个 ​yield​表达式的返回值。

  1. function * f (){
  2.    for (let k = 0; true; k++){
  3.        let a = yield k;
  4.        if(a){k = -1};
  5.    }
  6. }
  7. let g =f();
  8. g.next();    // {value: 0, done: false}
  9. g.next();    // {value: 1, done: false}
  10. g.next(true);    // {value: 0, done: false}

这一特点,可以让 ​Generator​函数开始执行之后,可以从外部向内部注入不同值,从而调整函数行为。

  1. function * f(x){
  2.    let y = 2 * (yield (x+1));
  3.    let z = yield (y/3);
  4.    return (x + y + z);
  5. }
  6. let a = f(5);
  7. a.next();   // {value : 6 ,done : false}
  8. a.next();   // {value : NaN ,done : false}  
  9. a.next();   // {value : NaN ,done : true}
  10. // NaN因为yeild返回的是对象 和数字计算会NaN

  11. let b = f(5);
  12. b.next();     // {value : 6 ,done : false}
  13. b.next(12);   // {value : 8 ,done : false}
  14. b.next(13);   // {value : 42 ,done : false}
  15. // x 5 y 24 z 13

14.4 for...of循环

for...of​​循环会自动遍历,不用调用 ​next​​方法,需要注意的是, ​for...of​​遇到 ​next​​返回值的 ​done​​属性为 ​true​​就会终止, ​return​​返回的不包括在 ​for...of​循环中。

  1. function * f(){
  2.    yield 1;
  3.    yield 2;
  4.    yield 3;
  5.    yield 4;
  6.    return 5;
  7. }
  8. for (let k of f()){
  9.    console.log(k);
  10. }
  11. // 1 2 3 4  没有 5

14.5 Generator.prototype.throw()

throw​方法用来向函数外抛出错误,并且在Generator函数体内捕获。

  1. let f = function * (){
  2.    try { yield }
  3.    catch (e) { console.log('内部捕获', e) }
  4. }

  5. let a = f();
  6. a.next();

  7. try{
  8.    a.throw('a');
  9.    a.throw('b');
  10. }catch(e){
  11.    console.log('外部捕获',e);
  12. }
  13. // 内部捕获 a
  14. // 外部捕获 b

14.6 Generator.prototype.return()

return​​方法用来返回给定的值,并结束遍历Generator函数,如果 ​return​​方法没有参数,则返回值的 ​value​​属性为 ​undefined​。

  1. function * f(){
  2.    yield 1;
  3.    yield 2;
  4.    yield 3;
  5. }
  6. let g = f();
  7. g.next();          // {value : 1, done : false}
  8. g.return('leo');   // {value : 'leo', done " true}
  9. g.next();          // {value : undefined, done : true}

14.7 next()/throw()/return()共同点

相同点就是都是用来恢复Generator函数的执行,并且使用不同语句替换 ​yield​表达式。

  • next()​​将 ​​yield​​表达式替换成一个值。
  1. let f = function * (x,y){
  2.    let r = yield x + y;
  3.    return r;
  4. }
  5. let g = f(1, 2);
  6. g.next();   // {value : 3, done : false}
  7. g.next(1);  // {value : 1, done : true}
  8. // 相当于把 let r = yield x + y;
  9. // 替换成 let r = 1;
  • throw()​​将 ​​yield​​表达式替换成一个 ​​throw​​语句。
  1. g.throw(new Error('报错'));  // Uncaught Error:报错
  2. // 相当于将 let r = yield x + y
  3. // 替换成 let r = throw(new Error('报错'));
  • next()​​将 ​​yield​​表达式替换成一个 ​​return​​语句。
  1. g.return(2); // {value: 2, done: true}
  2. // 相当于将 let r = yield x + y
  3. // 替换成 let r = return 2;

14.8 yield* 表达式

用于在一个Generator中执行另一个Generator函数,如果没有使用 ​yield*​会没有效果。

  1. function * a(){
  2.    yield 1;
  3.    yield 2;
  4. }
  5. function * b(){
  6.    yield 3;
  7.    yield * a();
  8.    yield 4;
  9. }
  10. // 等同于
  11. function * b(){
  12.    yield 3;
  13.    yield 1;
  14.    yield 2;
  15.    yield 4;
  16. }
  17. for(let k of b()){console.log(k)}
  18. // 3
  19. // 1
  20. // 2
  21. // 4

14.9 应用场景

  1. 控制流管理
    解决回调地狱:
  2. // 使用前
  3. f1(function(v1){
  4.    f2(function(v2){
  5.        f3(function(v3){
  6.            // ... more and more
  7.        })
  8.    })
  9. })

  10. // 使用Promise
  11. Promise.resolve(f1)
  12.    .then(f2)
  13.    .then(f3)
  14.    .then(function(v4){
  15.        // ...
  16.    },function (err){
  17.        // ...
  18.    }).done();

  19. // 使用Generator
  20. function * f (v1){
  21.    try{
  22.        let v2 = yield f1(v1);
  23.        let v3 = yield f1(v2);
  24.        let v4 = yield f1(v3);
  25.        // ...
  26.    }catch(err){
  27.        // console.log(err)
  28.    }
  29. }
  30. function g (task){
  31.    let obj = task.next(task.value);
  32.  // 如果Generator函数未结束,就继续调用
  33.  if(!obj.done){
  34.      task.value = obj.value;
  35.      g(task);
  36.  }
  37. }
  38. g( f(initValue) );
  39. 异步编程的使用 在真实的异步任务封装的情况:
  40. let fetch = require('node-fetch');
  41. function * f(){
  42.    let url = 'http://www.baidu.com';
  43.    let res = yield fetch(url);
  44.    console.log(res.bio);
  45. }
  46. // 执行该函数
  47. let g = f();
  48. let result = g.next();
  49. // 由于fetch返回的是Promise对象,所以用then
  50. result.value.then(function(data){
  51.    return data.json();
  52. }).then(function(data){
  53.    g.next(data);
  54. })

15 Class语法和继承

15.1 介绍

ES6中的 ​class​可以看作只是一个语法糖,绝大部分功能都可以用ES5实现,并且,类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式

  1. // ES5
  2. function P (x,y){
  3.    this.x = x;
  4.    this.y = y;
  5. }
  6. P.prototype.toString = function () {
  7.  return '(' + this.x + ', ' + this.y + ')';
  8. };
  9. var a = new P(1, 2);

  10. // ES6
  11. class P {
  12.    constructor(x, y){
  13.        this.x = x;
  14.        this.y = y;
  15.    }
  16.    toString(){
  17.        return '(' + this.x + ', ' + this.y + ')';
  18.    }
  19. }
  20. let a = new P(1, 2);

值得注意: ES6的的所有方法都是定义在 ​prototype​属性上,调用类的实例的方法,其实就是调用原型上的方法。

  1. class P {
  2.    constructor(){ ... }
  3.    toString(){ ... }
  4.    toNumber(){ ... }
  5. }
  6. // 等同于
  7. P.prototyoe = {
  8.    constructor(){ ... },
  9.    toString(){ ... },
  10.    toNumber(){ ... }
  11. }

  12. let a = new P();
  13. a.constructor === P.prototype.constructor; // true

类的属性名可以使用表达式

  1. let name = 'leo';
  2. class P {
  3.    constructor (){ ... }
  4.    [name](){ ... }
  5. }

Class不存在变量提升: ES6中的类不存在变量提升,与ES5完全不同:

  1. new P ();   // ReferenceError
  2. class P{...};

Class的name属性
​​name​​属性总是返回紧跟在 ​class​后的类名。

  1. class P {}
  2. P.name;  // 'P'

15.2 constructor()方法

constructor()​​是类的默认方法,通过 ​new​​实例化时自动调用执行,一个类必须有 ​constructor()​​方法,否则一个空的 ​constructor()​​会默认添加。
​​constructor()​​方法默认返回实例对象(即 ​this​)。

  1. class P { ... }
  2. // 等同于
  3. class P {
  4.    constructor(){ ... }
  5. }

15.3 类的实例对象

与ES5一样,ES6的类必须使用 ​new​命令实例化,否则报错。

  1. class P { ... }
  2. let a = P (1,2);     // 报错
  3. let b = new P(1, 2); // 正确

与 ES5 一样,实例的属性除非显式定义在其本身(即定义在 ​this​​对象上),否则都是定义在原型上(即定义在 ​class​上)。

  1. class P {
  2.    constructor(x, y){
  3.        this.x = x;
  4.        this.y = y;
  5.    }
  6.    toString(){
  7.        return '(' + this.x + ', ' + this.y + ')';
  8.    }
  9. }
  10. var point = new Point(2, 3);

  11. point.toString() // (2, 3)

  12. point.hasOwnProperty('x') // true
  13. point.hasOwnProperty('y') // true
  14. point.hasOwnProperty('toString') // false
  15. point.__proto__.hasOwnProperty('toString') // true
  16. // toString是原型对象的属性(因为定义在Point类上)

15.4 Class表达式

与函数一样,类也可以使用表达式来定义,使用表达式来作为类的名字,而 ​class​后跟的名字,用来指代当前类,只能再Class内部使用。

  1. let a = class P{
  2.    get(){
  3.        return P.name;
  4.    }
  5. }

  6. let b = new a();
  7. b.get(); // P
  8. P.name;  // ReferenceError: P is not defined

如果类的内部没用到的话,可以省略 ​P​,也就是可以写成下面的形式。

  1. let a = class { ... }

15.5 私有方法和私有属性

由于ES6不提供,只能变通来实现:

  • 1.使用命名加以区别,如变量名前添加 ​_​,但是不保险,外面也可以调用到。
  1. class P {
  2.    // 公有方法
  3.    f1 (x) {
  4.        this._x(x);
  5.    }
  6.    // 私有方法
  7.    _x (x){
  8.        return this.y = x;
  9.    }
  10. }
  • 2.将私有方法移除模块,再在类内部调用 ​call​方法。
  1. class P {
  2.    f1 (x){
  3.        f2.call(this, x);
  4.    }
  5. }
  6. function f2 (x){
  7.    return this.y = x;
  8. }
  • 3.使用 ​Symbol​为私有方法命名。
  1. const a1 = Symbol('a1');
  2. const a2 = Symbol('a2');
  3. export default class P{
  4.    // 公有方法
  5.    f1 (x){
  6.        this[a1](x);
  7.    }
  8.    // 私有方法
  9.    [a1](x){
  10.        return this[a2] = x;
  11.    }
  12. }

15.6 this指向问题

类内部方法的 ​this​​默认指向类的实例,但单独使用该方法可能报错,因为 ​this​指向的问题。

  1. class P{
  2.    leoDo(thing = 'any'){
  3.        this.print(`Leo do ${thing}`)
  4.    }
  5.    print(text){
  6.        console.log(text);
  7.    }
  8. }
  9. let a = new P();
  10. let { leoDo } = a;
  11. leoDo(); // TypeError: Cannot read property 'print' of undefined
  12. // 问题出在 单独使用leoDo时,this指向调用的环境,
  13. // 但是leoDo中的this是指向P类的实例,所以报错

解决方法

  • 1.在类里面绑定 ​this
  1. class P {
  2.    constructor(){
  3.        this.name = this.name.bind(this);
  4.    }
  5. }
  • 2.使用箭头函数
  1. class P{
  2.    constructor(){
  3.        this.name = (name = 'leo' )=>{
  4.            this.print(`my name is ${name}`)
  5.        }
  6.    }
  7. }

15.7 Class的getter和setter

使用 ​get​​和 ​set​关键词对属性设置取值函数和存值函数,拦截属性的存取行为。

  1. class P {
  2.    constructor (){ ... }
  3.    get f (){
  4.        return 'getter';
  5.    }
  6.    set f (val) {
  7.        console.log('setter: ' + val);
  8.    }
  9. }

  10. let a = new P();
  11. a.f = 100;   // setter : 100
  12. a.f;          // getter

15.8 Class的generator方法

只要在方法之前加个( ​*​)即可。

  1. class P {
  2.    constructor (...args){
  3.        this.args = args;
  4.    }
  5.    *[Symbol.iterator](){
  6.        for (let arg of this.args){
  7.            yield arg;
  8.        }
  9.    }
  10. }
  11. for (let k of new P('aa', 'bb')){
  12.    console.log(k);
  13. }
  14. // 'aa'
  15. // 'bb'

15.9 Class的静态方法

由于类相当于实例的原型,所有类中定义的方法都会被实例继承,若不想被继承,只要加上 ​static​关键字,只能通过类来调用,即“静态方法”。

  1. class P (){
  2.    static f1 (){ return 'aaa' };
  3. }
  4. P.f1();    // 'aa'
  5. let a = new P();
  6. a.f1();    // TypeError: a.f1 is not a function

如果静态方法包含 ​this​​关键字,则 ​this​指向类,而不是实例。

  1. class P {
  2.    static f1 (){
  3.        this.f2();
  4.    }
  5.    static f2 (){
  6.        console.log('aaa');
  7.    }
  8.    f2(){
  9.        console.log('bbb');
  10.    }
  11. }
  12. P.f2();  // 'aaa'

并且静态方法可以被子类继承,或者 ​super​对象中调用。

  1. class P{
  2.    static f1(){ return 'leo' };
  3. }
  4. class Q extends P { ... };
  5. Q.f1();  // 'leo'

  6. class R extends P {
  7.    static f2(){
  8.        return super.f1() + ',too';
  9.    }
  10. }
  11. R.f2();  // 'leo , too'

15.10 Class的静态属性和实例属性

ES6中明确规定,Class内部只有静态方法没有静态属性,所以只能通过下面实现。

  1. // 正确写法
  2. class P {}
  3. P.a1 = 1;
  4. P.a1;      // 1

  5. // 无效写法
  6. class P {
  7.    a1: 2,          // 无效
  8.    static a1 : 2,  // 无效
  9. }
  10. P.a1;      // undefined

新提案来规定实例属性和静态属性的新写法

  • 1.类的实例属性
    类的实例属性可以用等式,写入类的定义中。
  1. class P {
  2.    prop = 100;   // prop为P的实例属性 可直接读取
  3.    constructor(){
  4.        console.log(this.prop); // 100
  5.    }
  6. }

有了新写法后,就可以不再 ​contructor​​方法里定义。
为了可读性的目的,对于那些在 ​​constructor​里面已经定义的实例属性,新写法允许直接列出

  1. // 之前写法:
  2. class RouctCounter extends React.Component {
  3.    constructor(prop){
  4.        super(prop);
  5.        this.state = {
  6.            count : 0
  7.        }
  8.    }
  9. }

  10. // 新写法
  11. class RouctCounter extends React.Component {
  12.    state;
  13.    constructor(prop){
  14.        super(prop);
  15.        this.state = {
  16.            count : 0
  17.        }
  18.    }

  19. }
  • 2.类的静态属性
    只要在实例属性前面加上 ​​static​关键字就可以。
  1. class P {
  2.    static prop = 100;
  3.    constructor(){console.log(this.prop)}; // 100
  4. }

新写法方便静态属性的表达。

  1. // old
  2. class P  { .... }
  3. P.a = 1;

  4. // new
  5. class P {
  6.    static a = 1;
  7. }

15.11 Class的继承

主要通过 ​extends​​关键字实现,继承父类的所有属性和方法,通过 ​super​​关键字来新建父类构造函数的 ​this​对象。

  1. class P { ... }
  2. class Q extends P { ... }

  3. class P {
  4.    constructor(x, y){
  5.        // ...
  6.    }
  7.    f1 (){ ... }
  8. }
  9. class Q extends P {
  10.    constructor(a, b, c){
  11.        super(x, y);  // 调用父类 constructor(x, y)
  12.        this.color = color ;
  13.    }
  14.    f2 (){
  15.        return this.color + ' ' + super.f1();
  16.        // 调用父类的f1()方法
  17.    }
  18. }

子类必须在 ​constructor()​调用 ​super()​否则报错,并且只有 ​super​方法才能调用父类实例,还有就是,父类的静态方法,子类也可以继承到

  1. class P {
  2.    constructor(x, y){
  3.        this.x = x;
  4.        this.y = y;
  5.    }
  6.    static fun(){
  7.        console.log('hello leo')
  8.    }
  9. }
  10. // 关键点1 调用super
  11. class Q extends P {
  12.    constructor(){ ... }
  13. }
  14. let a = new Q(); // ReferenceError 因为Q没有调用super

  15. // 关键点2 调用super
  16. class R extends P {
  17.    constructor (x, y. z){
  18.        this.z = z; // ReferenceError 没调用super不能使用
  19.        super(x, y);
  20.        this.z = z; // 正确
  21.    }
  22. }

  23. // 关键点3 子类继承父类静态方法
  24. R.hello(); // 'hello leo'

super关键字
既可以当函数使用,还可以当对象使用。

  • 1.当函数调用,代表父类的构造函数,但必须执行一次。
  1. class P {... };
  2. class R extends P {
  3.    constructor(){
  4.        super();
  5.    }
  6. }
  • 2.当对象调用,指向原型对象,在静态方法中指向父类。
  1. class P {
  2.    f (){ return 2 };
  3. }
  4. class R extends P {
  5.    constructor (){
  6.        super();
  7.        console.log(super.f()); // 2
  8.    }
  9. }
  10. let a = new R()

注意: ​super​​指向父类原型对象,所以定义在父类实例的方法和属性,是无法通过 ​super​​调用的,但是通过调用 ​super​​方法可以把内部 ​this​指向当前实例,就可以访问到。

  1. class P {
  2.    constructor(){
  3.        this.a = 1;
  4.    }
  5.    print(){
  6.        console.log(this.a);
  7.    }
  8. }
  9. class R extends P {
  10.    get f (){
  11.        return super.a;
  12.    }
  13. }
  14. let b = new R();
  15. b.a; // undefined 因为a是父类P实例的属性

  16. // 先调用super就可以访问
  17. class Q extends P {
  18.    constructor(){
  19.        super();   // 将内部this指向当前实例
  20.        return super.a;
  21.    }
  22. }
  23. let c = new Q();
  24. c.a; // 1

  25. // 情况3
  26. class J extends P {
  27.    constructor(){
  28.        super();
  29.        this.a = 3;
  30.    }
  31.    g(){
  32.        super.print();
  33.    }
  34. }
  35. let c = new J();
  36. c.g(); // 3  由于执行了super()后 this指向当前实例

16 Module语法和加载实现

16.1 介绍

ES6之前用于JavaScript的模块加载方案,是一些社区提供的,主要有 ​CommonJS​​和 ​AMD​两种,前者用于服务器,后者用于浏览器
ES6提供了模块的实现,使用 ​​export​​命令对外暴露接口,使用 ​import​命令输入其他模块暴露的接口。

  1. // CommonJS模块
  2. let { stat, exists, readFire } = require('fs');

  3. // ES6模块
  4. import { stat, exists, readFire } = from 'fs';

16.2 严格模式

ES6模块自动采用严格模式,无论模块头部是否有 ​"use strict"​​。
严格模式有以下限制

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用 ​with​语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量 ​deleteprop​​,会报错,只能删除属性 ​​delete*global[prop]​
  • eval​不会在它的外层作用域引入变量
  • eval​​和 ​​arguments​​不能被重新赋值
  • arguments​不会自动反映函数参数的变化
  • 不能使用 ​arguments.callee
  • 不能使用 ​arguments.caller
  • 禁止 ​this​指向全局对象
  • 不能使用 ​fn.caller​​和 ​​fn.arguments​​获取函数调用的堆栈
  • 增加了保留字(比如 ​protected​​、 ​​static​​和 ​​interface​​)

特别是,ES6中顶层 ​this​​指向 ​undefined​​,即不应该在顶层代码使用 ​this​。

16.3 export命令

使用 ​export​向模块外暴露接口,可以是方法,也可以是变量。

  1. // 1. 变量
  2. export let a = 'leo';
  3. export let b = 100;

  4. // 还可以
  5. let a = 'leo';
  6. let b = 100;
  7. export {a, b};

  8. // 2. 方法
  9. export function f(a,b){
  10.    return a*b;
  11. }

  12. // 还可以
  13. function f1 (){ ... }
  14. function f2 (){ ... }
  15. export {
  16.    a1 as f1,
  17.    a2 as f2
  18. }

可以使用 ​as​​重命名函数的对外接口。
特别注意
​​export​暴露的必须是接口,不能是值。

  1. // 错误
  2. export 1; // 报错

  3. let a = 1;
  4. export a; // 报错

  5. // 正确
  6. export let a = 1; // 正确

  7. let a = 1;
  8. export {a};       // 正确

  9. let a = 1;
  10. export { a as b}; // 正确

暴露方法也是一样:

  1. // 错误
  2. function f(){...};
  3. export f;

  4. // 正确
  5. export function f () {...};

  6. function f(){...};
  7. export {f};

16.4 import命令

加载 ​export​暴露的接口,输出为变量。

  1. import { a, b } from '/a.js';
  2. function f(){
  3.    return a + b;
  4. }

import​​后大括号指定变量名,需要与 ​export​​的模块暴露的名称一致。
也可以使用 ​​as​为输入的变量重命名。

  1. import { a as leo } from './a.js';

import​不能直接修改输入变量的值,因为输入变量只读只是个接口,但是如果是个对象,可以修改它的属性。

  1. // 错误
  2. import {a} from './f.js';
  3. a = {}; // 报错

  4. // 正确
  5. a.foo = 'leo';  // 不报错

import​​命令具有提升效果,会提升到整个模块头部最先执行,且多次执行相同 ​import​只会执行一次。

16.5 模块的整体加载

当一个模块暴露多个方法和变量,引用时可以用 ​*​整体加载。

  1. // a.js
  2. export function f(){...}
  3. export function g(){...}

  4. // b.js
  5. import * as obj from '/a.js';
  6. console.log(obj.f());
  7. console.log(obj.g());

但是,不允许运行时改变:

  1. import * as obj from '/a.js';
  2. // 不允许
  3. obj.a = 'leo';  
  4. obj.b = function(){...};

16.6 export default 命令

使用 ​exportdefault​命令,为模块指定默认输出,引用的时候直接指定任意名称即可。

  1. // a.js
  2. export default function(){console.log('leo')};

  3. // b.js
  4. import leo from './a.js';
  5. leo(); // 'leo'

exportdefault​暴露有函数名的函数时,在调用时相当于匿名函数。

  1. // a.js
  2. export default function f(){console.log('leo')};
  3. // 或者
  4. function f(){console.log('leo')};
  5. export default f;

  6. // b.js
  7. import leo from './a.js';

exportdefault​​其实是输出一个名字叫 ​default​的变量,所以后面不能跟变量赋值语句。

  1. // 正确
  2. export let a= 1;

  3. let a = 1;
  4. export default a;

  5. // 错误
  6. export default let a = 1;

exportdefault​​命令的本质是将后面的值,赋给 ​default​​变量,所以可以直接将一个值写在 ​exportdefault​之后。

  1. // 正确
  2. export detault 1;
  3. // 错误
  4. export 1;

16.7 export 和 import 复合写法

常常在先输入后输出同一个模块使用,即转发接口,将两者写在一起。

  1. export {a, b} from './leo.js';

  2. // 理解为
  3. import {a, b} from './leo.js';
  4. export {a, b}

常见的写法还有:

  1. // 接口改名
  2. export { a as b} from './leo.js';

  3. // 整体输出
  4. export *  from './leo.js';

  5. // 默认接口改名
  6. export { default as a } from './leo.js';

常常用在模块继承

16.8 浏览器中的加载规则

ES6中,可以在浏览器使用 ​<script>​​标签,需要加入 ​type="module"​​属性,并且这些都是异步加载,避免浏览器阻塞,即等到整个页面渲染完,再执行模块脚本,等同于打开了 ​<script>​​标签的 ​defer​属性。

  1. <script type="module" src="./a.js"></script>

另外,ES6模块也可以内嵌到网页,语法与外部加载脚本一致:

  1. <script type="module">
  2.    import a from './a.js';
  3. </script>

注意点

  • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  • 模块脚本自动采用严格模式,不管有没有声明 ​usestrict​。
  • 模块之中,可以使用 ​import​​命令加载其他模块( ​​.js​​后缀不可省略,需要提供 ​​绝对UR​​L 或 ​​相对UR​​L),也可以使用 ​​export​​命令输出对外接口。
  • 模块之中,顶层的 ​this​​关键字返回 ​​undefined​​,而不是指向 ​​window​​。也就是说,在模块顶层使用 ​​this​​关键字,是无意义的。
  • 同一个模块如果加载多次,将只执行一次。

【ES】155-重温基础:ES6系列(六)_实例化_02


举报

相关推荐

0 条评论