1. class

直接上个例子先,一个UI控件按钮的实例

  1. class Widget{
  2. constructor(width, height){
  3. this.width = width || 50;
  4. this.height = height || 50;
  5. this.$elem = null;
  6. }
  7. render($where){
  8. if(this.$elem){
  9. // 样式
  10. this.$elem.css({
  11. width: `${this.width}px`,
  12. height: `${this.height}px`
  13. // 放到哪个位置上
  14. }).appendTo($where)
  15. }
  16. }
  17. }
  18. class Button extends Widget {
  19. constructor(width,height,label){
  20. super(width,height);
  21. this.label = label || "Default";
  22. this.$elem = $("<button>").text(this.label);
  23. }
  24. render($where){
  25. super.render($where);
  26. this.$elem.click(this.onclick.bind(this)); // 绑定
  27. }
  28. onclick(evt){
  29. console.log(`Button ${this.label} clicked!`)
  30. }
  31. }

语法好看之外,还解决了什么❓问题

  • 不再引用杂乱的.prototype了
  • Button声明时,直接”继承“了Widget,不再需要通过Object.create(…)来替换.prototype对象
  • 可以通过super(..)来实现相对多态,这样任何方法都可以引用原型链上层的同名方法,解决了之前构造函数的问题,构造函数不属于类,所以无法相互引用
  • class字面语法不能声明属性,只能声明方法,这样子可以规避犯错
  • 通过class .. extends .. 很自然地扩展对象(子)类型,甚至是内置的对象(子)类型,比如Aarray或者RegExp,使扩展变得容易了

2. class的陷阱

class只是语法糖🍬,本质上还是[[Prototype]](委托)机制的上的使用,并没有复制的功能

当你修改或者替换父类中的一个方法,那子类和所有实例都会受到影响,因为没有进行复制

  1. class C{
  2. constructor() {
  3. this.num = Math.random();
  4. }
  5. rand(){
  6. console.log(`Random:${this.num}`);
  7. }
  8. }
  9. var c1 = new C();
  10. c1.rand();// Random:0.4324299
  11. // 修改了父类的方法
  12. C.prototype.rand = function (){
  13. console.log(`Random:${Math.round(this.num*1000)}`);
  14. }
  15. var c2 = new C();
  16. // 实例化对象都修改了
  17. c2.rand(); // "Random:867"
  18. c1.rand(); // "Random:432"

因为class语法无法定义类成员属性(只能定义方法),当你需要跟踪实例之间共享状态必须要使用.prototype语法

  1. class C {
  2. constuctor() {
  3. // 确保修改的是共享状态而不是在实例上创建一个屏蔽属性
  4. C.prototype.count++
  5. // this.count可以通过委托实现我们想要的功能
  6. console.log(this.count);
  7. }
  8. }
  9. // 直接向prototype对象添加一个共享状态
  10. c.prototype.count = 0;
  11. var c1 = new C(); // 1
  12. var c2 = new C(); // 2
  13. c1.count === 2; // true
  14. c1.count === c2.count; // true

其实它也是违背class语法本意,在实现中暴露了.prototype,但是遇到这种情况也只能这样子用

还有个意外屏蔽的问题出现在class上面

  1. class C {
  2. constructor() {
  3. // id属性会屏蔽了id()方法
  4. this.id = id;
  5. }
  6. id() {
  7. console.log("ID:" + id);
  8. }
  9. }
  10. var c1 = new C("c1");
  11. c1.id(); // Typeerror

还有一个,super的绑定方法,无论目前的方法在原型链中处于什么位置,super总会绑到链的上一层

  1. class P{
  2. foo() {console.log("P foo")}
  3. }
  4. class C extends P{
  5. // super当作函数使用就是代表父类的构造函数
  6. // 相当于P.prototype.foo.call(this)
  7. foo(){super()}
  8. }
  9. var c1 = new C();
  10. c1.foo(); // "p foo"
  11. var D = {
  12. foo:function(){console.log("D foo")}
  13. }
  14. var E={
  15. foo:C.prototype.foo
  16. }
  17. // 把E委托到D
  18. Object.setPrototypeOf(E,D);
  19. E.foo();// "P foo"