class与构造函数

关于E6之前的"构造函数"

[[prototype]]和prototype

对象有一个[[prototype]]的属性,在有些浏览器中可以用__proto__获得,对象在创建时,这个属性会被赋一个非空值(大部分情况),[[protopype]]最终会指向Object.prototype

const a = {};
a.__proto__
// {constructor: f ...}

所有函数会有名为prototype的不可枚举属性,它指向一个对象(原型对象),对象的constructor属性指向这个函数

const b = function() {};
b.prototype
// {constructor: f ...}

函数并不是构造函数,只是函数在使用new时,函数调用成了“构造函数调用”;结果是创建了一个新的对象,并将这个对象的[[prototype]]与函数的prototype发生关联

function Foo(name) {
  this.nameStr = `I am ${name}`;
}
Foo.prototype.say = function() { console.log(this.nameStr); }
const f = new Foo();
// f.__proto__ === Foo.prototype
// f.__proto__.constructor === Foo

创建这种关联的一个更直接的方法是使用Object.create(),这个方法会创建一个新的对象,并用现有对象提供新对象的__proto__

const b = Object.create(Foo.prototype);
// b.__proto__ === Foo.prototype

关于Object.create的ES5之前的实现

if (!Object.create) {
  Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
    // 返回的对象的__proto__与F.prototype也就是o关联
  }
}

还有一个方法是Object.setPrototypeOf(A, B),修改A的__proto__,让对象A的[[prototype]]与对象B关联;

“继承”的一种实现

function A() {}
Object.setPrototypeOf(A.prototype, Foo.prototype);
// 或者
function B() {}
B.prototype = Object.create(Foo.prototype);

区别:前者是直接修改prototype,后者会抛弃默认的prototype,用新创建的对象替换

A.prototype.constructor === A
// true
B.prototype.constructor === B
// false
B.prototype.constructor === Foo
// true

实际上constructor属性是函数声明时的默认属性,可以修改,在替换prototype对象后也可能无法获得,因此它不算严格的表示“被构造”

访问对象属性的时候会调用内部的[[Get]],如果这个属性不存在于这个对象中,[[Get]]操作就会查找对象内[[prototype]]关联的对象,这种关联关系定义了一条原型链,普通的[[prototype]]链尽头是Object.prototye

ES6的class

ES6中新增的class

class Foo {
  constructor() {
    this.helloStr = 'hello';
  }
  static log(msg) {
    console.log(msg)
  }
  sayHello() {
    console.log(this.helloStr);
  }
}

“类”的继承

class Bar extends Foo {
  constructor() {
    super();
    this.helloStr = 'world';
    // ....
  }
  static logSome(msg) {
    super.log(msg);
  }
  doSome() {
    super.sayHello();
  }
}

可以看做之前写法的语法糖,ES5的构造函数对应ES6 class的constructor方法,class本身指向protptype,class中的方法也都直接定义在了类的prototype属性上。很多地方表现上和ES5基本一致。

typeof Foo
// "function"
Foo.prototype.constructor === Foo
// true
var f = new Foo();
f.__proto__ === Foo.prototype
// true

静态方法
使用static添加静态方法

  • 静态属性/方法需要通过类调用
Foo.log('a'); // a

const f = new Foo();
f.log('b'); // Uncaught TypeError: f.log is not a function
  • 静态属性/方法可以继承
Bar.log('abc'); // abc

extends继承
用来建立原型委托的关系

  • 子类的__proto__表示构造函数的继承,指向父类
  • 子类的prototype__proto__表示方法的继承,指向父类的prototype

Bar.prototype[[prototype]]Foo.prototype关联

Bar.__proto__ === Foo
// true
Bar.prototype.__proto__ === Foo.prototype
// true

实现上相当于

Object.setPrototypeOf(Bar.prototype, Foo.prototype);
// Bar的实例继承Foo的静态属性
Object.setPrototypeOf(Bar, Foo);

super关键字
ES6中的“继承”,是先创造“父类”的实例对象this,然后再用“子类”的构造函数修改this,需要在子类的构造函数中调用super()后才能使用this;
ES5中的“继承”是先创建子类的this,再将父类的方法添加到this上Parent.apply(this)

super的使用:

  • 在构造函数中作为函数使用,指向父类的构造函数,super()相当于调用父类的构造函数(构造函数默认会返回this);只能在子类的构造函数中作为函数使用
  • 在普通方法中作为对象使用,指向父类的原型对象,即Foo.prototype,通过super调用父类的方法时会绑定子类的this
  • 在静态方法中作为对象使用,指向父类,即Foo
  • 因为对象都继承自其它对象,实际上在任何对象中都可以使用super
const b = new Bar();
b.doSome(); // world

class与function二者的一些区别

  • class的方法不可枚举,但是函数的prototype对象上的属性可枚举(二者的constructor属性都不可枚举)
  • class extends可以继承原生构造函数
  • class不存在函数那样的变量提升(父类要写在子类前)
Comments
Write a Comment