【JS】画图法快速理解原型链prototype/constructor与JS继承

613 阅读5分钟

原型相关名词概念解释


名词用途描述其他
[[Construct]]对象的一个内置方法,用于创建对象,通过new或者super关键字调用。该内置方法的第一个参数是传入的参数列表。第二个参数是new所初始化的对象。最后返回一个Obejct。constructor调用实现该内置方法
constructor对象用来创建和初始化一个对象。是个具有prototype属性的function对象。(这个prototype属性保存的是一个指针,指向了prototype对象所在的位置)。通过new创建对象的时候会调用constructor方法,不使用new调用constructor的话,根据具体实现可能会得到不同结果。比如:new Date()得到的是当前日期Date对象,Date()则是当前时间字符串
constructor属性指向constructor对象保存的是引用
prototype属性指向一个prototype对象所在的位置每个函数都有该属性,这个属性中保存的是引用,而不是具体的对象
prototype对象对象的用途是包含可以由特定类型的所有实例共享的属性和方法。该对象的 __proto____属性指向了Object的原型
proto_实例的属性,指向实例的原型对象保存的也是对象的引用

ecma262/#constructor

function Parent(){
  this.name = 'parent';
}
var a = new Parent();
console.log(Parent.prototype);
console.log(Parent.constructor);
console.log(Parent.prototype.constructor);
console.log(a.prototype);
console.log(a.__proto__);
console.log(a.constructor);

根据上述代码,我们来分析每个函数对应的prototype和constructor

对象取值
Parent的prototype对象一个具有constructor属性的Object(图:prototype 1-1 )
Parent.prototype属性Parent的prototype对象的引用
Parent.constructorƒ Function() { [native code] }
Parent.prototype.constructorƒ Parent(){this.name = 'parent';}
a.prototypeundefined
a.proto__与Parent.prototype相同
a.constructorƒ Parent(){this.name = 'parent';}

prototype 1-1

把这些关系抽象成关系图就是如下:(非常建议大家自己写段代码,然后自己画一遍这个关系图,就会非常清晰)

prototypeimg.png

  • 抽象的原型构造函数关系图

function prototype relationship

Function prototype reference

new具体的过程


  1. 一个继承自 *Foo*.prototype 的新对象被创建。
  2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new *Foo* 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
new Parent() = {
    var obj = {};
	obj.__proto__ = Parent.prototype; // 此时便建立了obj对象的原型链:
	// obj->Parent.prototype->Object.prototype->null
	var result = Person.call(obj); // 相当于obj.Person("John")
	return typeof result === 'object' ? result : obj; // 如果无返回值或者返回一个非对象值,则将obj返回作为新对象
}

手写一个new方法

function _new(fn,...arg){
    const obj = Object.create(fn.prototype);
    const ret = fn.apply(obj,arg);
    return ret instanceof Object?ret:obj;//考虑到基本数据类型
}

mozilla.org-new

alexzhong22c js-new-happen

Object.getPrototypeOf

返回指定对象的原型(内部[[Prototype]]属性的值)。 var o = new C();

//o是C的实例,所以

Object.getPrototypeOf(o) === C.prototype

Object.getPrototypeOf 返回的是实例对象的原型

因为Object是构造函数

Object.getPrototypeOf(Object) 得到的是// f(){[native code]}

Object.getPrototypeOf( Object ) === Function.prototype; // true

因为把Object看作对象,Object是个函数

所以Object.getPrototypeOf( Object )返回的就是函数对象的原型Function.prototype

Object.prototype是构造出来的对象的原型。

继承的实现方式


构造函数直接实现

function SuperType(){
    this.property =true;
}

function SubType(){
      SuperType.call(this);
}

//修改父类上的原型内容
SuperType.prototype.getType = function(){
    return 'add';
}
var instance = new SubType();

console.log(instance.property);
//true
console.log(instance.getType());
//报错:Uncaught TypeError: instance.getType is not a function
  • 问题:通过构造方法继承的子类,可以获取到父类构造函数当中的所有属性。
    • 子类就无法获取到父类prototype上变化的属性和方法。
    • 不好进行函数复用

原型继承

function SuperType(){
    this.property =true;
    this.colors = ['red','green'];
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}

var parent = new SuperType();
function SubType(){
    this.property = false;//子类重写父类属性
}
//把子类的原型设置为父类的一个新的实例对象
//父类的实例的__proto__中指向它自己的原型对象
//所以这样子类也可以成功访问到
SubType.prototype = new SuperType(); 
SubType.prototype.getSubType = function(){
    return this.property;
}
var instance = new SubType();
console.log(instance.getSuperValue());//false
instance.colors.push('blue');
console.log(parent.colors);//'red','green',
var instance2 = new SubType();
console.log(instance2.colors);//'red','green','blue'
  • 问题:
    • 不同子类实例会共享同一个引用类型数据,所以如果有一个修改了它,其他实例访问到的也是修改之后的。
    • 创建子类实例的时候不能向构造参数传递参数。

原型继承.PNG

组合继承:构造函数+原型继承

function SuperType(){
    this.property =true;
    this.colors = ['red','green'];
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}

var parent = new SuperType();
function SubType(arg){
    SuperType.call(this,arg);
    this.property = false;//子类重写父类属性
}

SubType.prototype = new SuperType(); 

SubType.prototype.getSubType = function(){
    return this.property;
}
var instance = new SubType();
console.log(instance.getSuperValue());//false
instance.colors.push('blue');
console.log(instance.colors);//'red','green','blue'
console.log(parent.colors);//'red','green',
var instance2 = new SubType();
console.log(instance2.colors);//'red','green'
  • 组合继承,具有了两种集成方式的有点,同时因为借用了父类的构造函数,所以每个子类实例获得了父类的属性。
    • 不足之处:每次都会调用两次超类的构造函数。一次是创建子类原型的时候,另一次是在子类构造函数内部。

组合继承.PNG

寄生继承

  • 调用函数创建对象+增强该对象的属性和方法
 function createAnother(original){
  var clone = object(original);
  clone.sayHi = function(){
    console.log('hi');
  }
  return clone;
}

var person = {
  name:"sherry",
  friends:['lisa']
}
var p = createAnother(person);

最佳实践:寄生组合继承

function inheritPrototype(subType,superType){
  var prototype = object(superType.prototype);  
  prototype.contructor = subType;
  subType.prototype = prototype;
}

另一种实现方式:

 function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
  }
  • 只调用一次superType的构造函数
  • 原型链也没有改变

参考:《Javascript高级教程》