理解 class, extends, super, new

152 阅读5分钟

最近被裁员了,所以重新复习和理解一下。 加深印象的同时,减少恐慌感 -_-!!

先写个 案例

class Person {
  name
  static age
  #sex
  constructor(name, age, sex) {
    this.name = name
    Person.age = age
    this.#sex = sex
  }
  sayName () { console.log(this.name) }
  sayAge () { console.log(Person.name) }
  saySex () { console.log(this.#sex) }
}
var tom = new Person('tom', 12, '男')
console.log(tom) // { name: 'tom' }
console.log(tom['__proto__']) // {}
console.log(Object.getOwnPropertyNames(tom['__proto__'])) // [ 'constructor', 'sayName', 'sayAge', 'saySex' ]
tom.sayName() // 'tom'
tom.sayAge() // 12
tom.saySex() //  '男'

上面案例,里面有 static, #, constructor, new, 还有一个隐藏的 public

先简单介绍一下每个关键字代表的意思

  • constructor:构造函数,class 最基础的元素,如果在 class 未声明 constructor, class 会默认自动带出
  • public:公有字段,定义在 this 或 constructor.prototype
  • static:静态字段,定义在 constructor
  • private: 私有字段,提案阶段,只能 类 自身调用
  • protected: 保护字段,es6暂没实现
  • super:子类的构造函数中代表 父类的构造函数,普通方法中代表 父类的原型对象,静态方法中代表 父类
  • new: 创建实例

先从 class 开始讲

class 是什么,class 是个语法糖 简单的转化一下

class Person {
    constructor (name) {
         this.name = name;
    }
    sayHi() {}
}
///////转换成////////
var Person = function (name) {
    this.name = name
}
Person.prototype.sayHi = function() {}

那为什么要这样写

  1. 更好的范式(工厂模式)
  2. 为了让 java 和 C++ 的程序员 更好的卷前端,狗头

class 跟 传统的构造函数生成实例,还是有些许差别的

  1. class 只能 new,不能像普通函数调用
  2. class 里面的所有定义的方法,都是不可枚举的
  3. 严格模式
  4. 保留一个私有作用域给 class, 为 private 作准备

手写一下 class

按上面的描述,手写一下 class

将上面的不同点,往里面塞

案例
class Person {
  name
  static age
  #sex
  constructor(name, age, sex) {
    this.name = name
    Person.age = age
    this.#sex = sex
  }
  sayInfo () { console.log(this.name,Person.age,this.#sex) }
}
简单实现
var _sex = Symbol();
var Person = function(name,age,sex) {
    this.name = name;
    Person.age = age;
    this[_sex] = sex; // 只能模拟实现private,不能完全实现,getOwnPropertySymbols可以获取
}
Person.prototype.sayInfo = function() { console.log(this.name,Person.age,this[_sex])  }
将不同点塞入实现
"use strict"; // 严格模式

var Person = (function() {
    // 保留一个私有作用域
    const _sex = Symbol();
     function Person(name,age,sex) {
        // 只能用new 
        if (!(this instanceof Person) throw new TypeError('must be new')
        this.name = name;
        // 将 sayInfo 方法 塞进 prototype里,并且是 不可枚举
        Object.defineProperty(Person.prototype, 'sayInfo', {
        enumerable:false,
        value: function() {
            console.log(this.name,Person.age,this[_sex]) 
        }
        })
        // 将 age 塞到 构造函数里, 并且也是 不可枚举
        Object.defineProperty(Person, 'age', {
            enumerable: false,
            value: age
        })
        // 将 sex 塞到 this 里
        Object.defineProperty(this, _sex, {
            enumerable: false,
            value: sex
        })
     ]
     
     return Person;
})()
优化一下, 将公共的提取出来
"use strict";
//  创建 class
var _createClass  = (function() {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configrable = true
            if ('value' in descriptor) {
                descriptor.writable = true
            }
            Object.defineProperty(target, descriptor.key, descriptor)
        }
    }
    
    return function(Constructor, instanceProps, protoProps, staticProps, privateProps) {
        if (instanceProps) 
            defineProperties(this, instanceProps)
    
        if (protoProps) 
            defineProperties(Constructor.prototype, protoProps)
        
        
        if (staticProps) 
            defineProperties(Constructor, staticProps)
        
        
        if (privateProps)
            defineProperties(this, privateProps)
    }
})()
// 判断是否是new创建
function _checkNew(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError('must be new')
    }
}

var Person = (function () {
    var _sex = Symbol();
    function Person (name, age, sex) {
        _checkNew(this, Person);
        _createClass.call(this, Person, [{
            key:'name',
            value: name,
            enumerable: true
        }], [{
            key: 'sayInfo',
            value: function () {
               console.log(this.name,Person.age,this[_sex])
            }
        }], [{
            key: 'age',
            value: age
        }], [{
            key: _sex,
            value: sex
        }])
    }
    
    return Person;
})()


  var tom = new Person('tom', 12, '男')
  console.log(tom) // { name: 'tom' }
  console.log(tom['__proto__']) // {}
  console.log(Object.getOwnPropertyNames(tom['__proto__'])) // [ 'constructor', 'sayInfo']
  tom.sayInfo() // tom 12 男

再手写一个 new

这个比较简单了 new 的作用,创建出来的实例可以访问到构造函数中的属性 new 做了什么

  1. 生成空对象
  2. 将空对象的原型指向构造函数的原型对象
  3. 构造函数在空对象的环境执行
  4. 如果返回值为对象,返回 返回值,否则返回前面的空对象
function _new(...args) {
    const fn = args.shift();
    const context = Object.create(fn.prototype, {
        constructor: {enumerable:false, value:fn}
    });
    const result = fn.apply(context, args);
    return typeof result === 'object'  || typeof result === 'function' ? result : context;
}

再来看看 extends

例子

  class Child extends Parent {
    constructor(name, age) {
      super(name, age)
      this.name = name
      this.age = age
    }
    getName () {
      return this.name
    }
  }
  class Parent {
    constructor(name, age) {
      this.name = name
      this.age = age
    }
    getName () {
      return this.name
    }
    getAge () {
      return this.age
    }
  }

extends 的作用,

  1. 将子类的原型对象(prototype)里的__proto__指向超类的原型对象
  2. 将子类的__proto__指向超类;
  3. 生成子类构造函数,执行超类的构造函数,执行结果如果是object 或 function,可以作为子类的 this

上面我们分开实现

先实现继承
  1. 将子类的原型对象(prototype)里的__proto__指向超类的原型对象
  2. 将子类的__proto__指向超类;
// subClass 子类
// superClass 超类
var _inherits = function(subClass, superClass) {
    subClass.prototype = Object.create(superClass,prototype, {
        constructor: {
            value: subClass,
            writable: true,
            configurable: true,
            enumerable: false
        }
    })
    
    Object.getPrototypeOf(subClass, superClass)
}

寄生组合式继承

再来完善一下,加点健壮性

  1. 判断subClass
  2. 判断superClass
  3. setPrototypeOf是否存在
var _inherits = function (subClass, superClass) {
    // 判断 sub 是否是 function
    if (typeof subClass !== 'function') throw new TypeError('subClass must be a function')
    // 判断 superClass 是否是 function 或者是 null()
    if (typeof superClass !== 'function' && superClass !== null) throw new TypeError('superClass must either be a function or null')

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        enumerable: false,
        configurable: true
      }
    })

    if (superClass)
      Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : (subClass.__proto__ = superClass)
}
再实现生成子类构造函数
  1. 生成子类构造函数,执行超类的构造函数,执行结果作为子类的 this;

跟生成class写的一样

var Child = (function(_Parent) {
    var Child  = function (name, age) {
        _checkNew(this, Child)
        var _this = _Parent.call(this, name, age) || this;
        
        _createClass(Child, [
        {key:'name',value:name,enumerable:true},
        {key:'age',value:age,enumerable:true}
        ], [
        {key:'getName',value: function() {return this.name}}
        ])
        
        return _this;
    }
    return Child;
})(_Parent)

完善一下

  1. 调用 _Parent 构造函数时,对返回值进行判断,返回值如果不是 object 或 function, 则返回 this;
var Child = (function(_Parent) {
    var _possibleConstructorReturn = function(self, call) {
        if (!self) throw new Error()
        return call && (typeof call === 'function' || typeof call === 'object') ? call : self;
    }

    var Child = function(name, age) {
        _checkNew(this, Child);
        
        var _this = _possbleConstructorReturn(this, Object.getPrototypeOf(Child).call(this, name, age))
        
        _createClass.call(this, Child, [
        {key:'name',value:name,enumerable:true},
        {key:'age',value:age,enumerable:true}
        ], [
        {key:'getName',value: function() {return this.name}}
        ])
        
        return _this;
    }
})(_Parent)

_inherits 和 生成构造函数结合

var Child = (function(_Parent) {
    _inherits(Child, _Parent);
   function Child(name, age) {
        _checkNew(this, Child);
        var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Child).call(this,name,age))
        _createClass.call(this, Child, [
        {key:'name',value:name,enumerable:true},
        {key:'age',value:age,enumerable:true}
        ], [
        {key:'getName',value: function() {return this.name}}
        ])
        return _this;
    }
    return Child;
})(_Parent)

super

super的情况很复杂,在不同的地方调用,会是不同的值。

我们先看看《阮一峰老师 ECMAScript 6 入门 》关于 super 的讲解

  1. 第一种情况,super作为函数调用时,代表父类的构造函数。(这个没啥问题,就是上面的 _prossibleConstrurctorReturn
  2. 第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

来看个书上的案例,和输出值

由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

class A {
  constructor() {
    this.x = 1;
  }}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }}

let b = new B();

书里的意思是,如果是 super.x 赋值为 3,这时等于 this.x 赋值为 3; 如果是读取 super.x 的值时,读的是 A.prototype.x,所以返回undefined; 这里很奇怪, 赋值时,super 是 this 读取时,super 是 A.prototype

super属性的 读写是不对称的

具体原因,看了很多别人的文章,基本上是复制粘贴。

有兴趣的,可以看一下

ES6 使用 super 访问父类属性,在 Chrome、Babel、TypeScript 不一致? - 知乎 (zhihu.com)

image.png

总结一下

  1. class
    1. 只能 new
    2. 字段
      1. 公有:public,属性在实例,方法在原型对象
      2. 静态:static,在类本身
      3. 私有:private,#,私有作用域,类自己用
      4. 保护: protected
  2. extends
    1. 继承
      1. 寄生组合式函数
      2. 子类的prototype =>proto => 父类的prototype
      3. 子类 => proto => 父类
    2. 创造构造函数
      1. super(),调用父类的构造函数,如果返回值是object或function,就替换当前this
      2. 返回this
  3. new
    1. 生成空对象
    2. 空对象的prototype指向构造函数
    3. 将构造函数在空对象的上下文中执行
    4. 如果执行结果是object或function,否则返回空对象
  4. super
    1. 函数:执行父类的构造函数,看执行结果是否覆盖 this
    2. 对象:
      1. 普通方法
        1. 赋值:super.x = 3 => this.x = 3
        2. 取值:super.x => 父类.prototype.x
      2. 静态方法
        1. 指父类