玩命加载中 . . .

原型链


原型链的基本构想

Q:什么是原型链?
A:当对象查找一个属性的时候,如果没有在自身找到,那么就会查找自身的原型,如果原型还没有找到,那么会继续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null ,查找停止。这种通过 通过原型链接的逐级向上的查找链被称为原型链。

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。下面 step by step 来探究原型链的奥妙。
原型链中的各种关系
从上图我们可以了解到构造函数(Person)、原型(Person.prototype)和实例(person)之间的关系:
每个构造函数都有一个原型对象(用Person.prototype),原型对象都包含一个指向构造函数的指针(用Person.prototype.constructor获取),而实例都包含一个指向原型对象的内部指针(用person._proto_)。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

下面来认识 constructor_proto_

  1. constructor
    function Person() {}
    var person = new Person();
    console.log(person.constructor === Person); // true

    在解释为什么之前,我们需要了解到原型链的搜索机制:在读取实例上的属性时,首先会在实例上搜索这个属性。如果没找到,则会继承搜索实例的原型。在通过原型链实现继承之后,搜索就可以继承向上,搜索原型的原型。对属性和方法的搜索会一直持续到原型链的末端

那么现在可以来看看为什么代码块中的打印会为 true 了:当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到 constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性。

  1. proto
    是实例对象指向原型对象的指针,隐式原型,是每个对象都会有的一个属性。

来看个例子:

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

function SubType() {
  this.subproperty = false;
}

// 继承了 SuperType
SubType.prototype = new SuperType();

SubType.prototpye.getSubValue = function() {
  return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue()); // true

上面代码我们可以看到,SubType 通过创建 SuperType 的实例继承了 SuperType,于是 SubType 从构造函数变成了实例,SuperType 的实例变成了 SubType 的原型。这样一来,原本存在于 SuperType 的实例中的所有属性和方法,现在也存在于 SubType.prototype中了。
后面的代码还在继承的基础上,给 SubType 添加了一个新方法。于是:
提醒:getSuperValue()方法仍然还在 SuperType.prototype中,但property则位于SubType.prototype中。这是因为property是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype现在是SuperType的实例,那么property当然就位于该实例中了。
于是我们懂了,当原型对象等于另一个类型的实例后,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么如此递进,就会构成实例与原型的链条,也就是原型链。

默认原型

实际上,原型链中还有一环。我们知道所有引用类型默认都继承自 Object ,所有函数的默认原型也都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype 。这也正是所有自定义类型都会继承 toString()valueOf() 等默认方法的根本原因。

原型与继承关系

原型与实例的关系可以通过两种方式来确定。
①是使用 instanceof 操作符,如果一个实例的原型链中出现过相应的构造函数,则 instanceof 返回 true。如下所示:

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

// 继承了 SuperType
SubType.prototype = new SuperType();
var instance = new SubType();

console.log(instance instanceof Object); // true
console.log(instance instanceof SubType); // true
console.log(instance instanceof SuperType); // true

从技术上讲,instanceObjectSubTypeSuperType 的实例,因为 instance 的原型链中包含这些构造函数的原型。

②是使用 isPrototypeOf() 方法。原型链中的每个原型都可以调用这个方法,只要原型链中包含这个原型,这个方法就返回 true 。如下所示:

console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true

原型链的问题:

  1. 原型中如果存在引用类型的值,会被所有实例共享,如下所示:
    function SuperType() {
        this.colors = ['red', 'blue', 'greeb'];
    }
    function SubType() {}
    SubType.prototype = new SuperType();
    
    var instance1 = new SubType();
    instance1.colors.push('black');
    console.log(instance1.colors) // "red, blue, green, black"
    
    var instance2 = new SubType();
    console.log(instance2.colors); // "red, blue, green, black"
    如果原型的属性被所有的实例共享,就会存在一个实例修改了这个属性后,也会影响到其他实例,这往往会造成一些不必要的麻烦。因此,通常的做法是在构造函数中,而不是在原型中定义属性,如:
    function SuperType() {}
    function SubType() {
        this.colors = [];
    }
    SubType.prototype = new SuperType();
    
    var instance1 = new SubType();
    instance1.colors.push('black');
    console.log(instance1.colors); // ["black"]
    
    var instance2 = new SubType();
    instance2.colors.push('red');
    console.log(instance2.colors); // ["red"]
  2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数

实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。有鉴于此,再加上由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。
为了解决这些问题,可以使用一种叫做 借用构造函数的技术(有时候也叫做伪造对象或经典继承),即在子类型构造函数的内部调用超类型构造函数。

function SuperType(name) {
    this.name = name;
}
function SubType(name, age) {
    // 继承了 SuperType ,同时还传递了参数
    SuperType.call(this, name);
    // 实例属性
    this.age = age;
}

var instance1 = new SubType('siyang', 29);
console.log(instance1.name); // "siyang"
console.log(instance1.age); // 29

var instance2 = new SubType('hg', 28);
console.log(instance2.name); // "hg"
console.log(instance2.age); // 28

以上代码在 SubType 内部使用 call 实现了对 SuperType 的”继承”,同时每个实例都有自己的实例属性,互不影响;而且创建实例时还可以向超类型 SuperType 传递参数。

说到原型链的继承,也是一个 JS 必须学习的一个重点,在我另一篇博客中也有详细介绍:原型链的继承


文章作者: hcyety
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hcyety !
评论
  目录