ES6基础系列 —— class的糖怎么吃

背景

原型前前后后大概分享过也不止3次了,面试的时候也经常在提,虽然老话常谈,也没什么好讲的东西

不过,还是要重点强调下,原型和闭包是JS中核心中的核心,这两个东西会玩,基本上日常类的需求、bug改造起来会驾轻就熟很多

先来几个栗子

1
const A = function () {};
A.prototype = {
    num: 1,
    text: 'aaa'
};

// 第一题
const x = new A();
console.log(x.num); 
console.log(x.text);


const y = new A();
A.prototype = {
    num: 2
};
// 第二题
console.log(y.num); 
console.log(y.text); 


y.num = 3;
const z = new A();
// 第三题
console.log(z.num); 
console.log(z.text);

一张图描述ES 5 prototype与继承

引用值更改

1
function A () {}
A.prototype = {
    obj: {
        str: 'aaa'
    }
}
var x = new A();
var z = new A();
x.obj.str = 'bbb';
console.log(z.obj.str);

会发现:obj.str 属性共享了,混乱了

一般推荐的正常做法是,属性放在实例上也就是构造函数上,每个实例各自创建,而方法放在原型上,用于继承与共享

ES 6 class 小例子

1
class A {
    constructor() {
        this.obj = {
            str: 'aaa'
        };
    }
    alert() {
        alert(this.obj.str);
    }
};
y = new A();
h = new A();
y.obj.str = 'bbb';

y.alert();
h.alert();

这里的obj.str 没有共享了,为什么?

ES 6 语法糖 extends、super

1
class B extends A {
    constructor(props) {
        super(props);
        // super 必须在最开始
        // 一方面的原因从下面这行也可以看出来
        this.str2 = this.obj.str + 'bbb';
    }

    alertAndLog() {
        super.alert();
        console.log(this.str2);
    }
}

// ------ 等价于 ------

// 构造函数
function B (props) {
    A.call(this, props);
    this.str2 = this.obj.str + 'bbb';
}
B.prototype = Object.create(A.prototype);
B.prototype.alertAndLog = function () {
    A.prototype.alert();
    console.log(this.str2);
};
  1. 上面class的写法是不是更一目了然?这大概就是ES 6 class 语法糖的意义
  2. 注意上面的 super 的区别,在constructor 中,它代表父类型的构造函数,在原型方法中,则代表父类型的原型
  3. ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。因此,如果有constructor 就必须有 super()

7. static 关键字

1
class B extends A {
    static console () {
        console.log(this.str2);
    }
}

var t = new B();
t.console(); // 是什么?

等价于
B.console = function () {
    console.log(this.num);
};

8. 静态方法里使用 super 是怎么样的?

1
class C extends B {
    constructor(props) {
        super(props);
    },
    static consoleAndAlert() {
        // 跟 constructor 功效一致
        super.console();
        alert(this.str2);
    }
}

C.str2 = 'ccc';
C.consoleAndAlert();

9. 缺陷

没有 private 关键字

a. 使用ES6 Symbol模拟

1
const bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{
    // 公有方法
    foo(baz) {
        return this[bar](baz);
    }

    // 私有方法
    [bar](baz) {
        return this[snaf] = baz;
    }
};

b. 使用闭包模拟

1
myClass = (()=> {
    let num = 1;
    return class A {
        foo(num2) {
            num += num2;
        }
    };
})();

c. ES7 提案 —— 用 # 号

1
class Point {
    #x = 0;
    constructor() {
        #x; // 0
    }
}

静态属性、属性没法在class外层中定义,ES7 提案

1
class A {
    a = 10;
    static a = 20;
}

10. 与Java的类继承区别

  1. JS 始终还是基于原型做的继承实现,基于的属性、闭包变量搜索
  2. 而像JAVA,更多是通过重新构建的形式完成继承,类和实例是不同的东西
  3. 类在定义完成后,运行时是不可动态添加属性
  4. 至于特性方面,JAVA有非常多的关键字,也是基于关键字的作用域实现各种各样的功能,抽象、多态、重写、重载
  5. 例如:public、protected、private、abstract、final

参考链接

ECMAScript 6 入门

JavaScript 原型系统的变迁,以及 ES6 class

Java 继承详解

JavaScript 原型和原型链的理解