JavaScript原型与原型链

202 阅读5分钟

一、 原型

在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个Prototype内部属性,这个属性所对应的就是该对象的原型。

Prototype作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome中提供了__proto__这个非标准(不是所有浏览器都支持)的访问器(ECMA引入了标准对象原型访问器”Object.getPrototype(object)”)。

在JavaScript的原型对象中,还包含一个”constructor”属性,这个属性对应创建所有指向该原型的实例的构造函数

  • 每个对象都有一个__proto__属性,并且指向它的prototype原型对象
  • 每个构造函数都有一个prototype原型对象
  • prototype原型对象里的constructor指向构造函数本身

v2-e722d5325f7d4215169f1d04296e0f89_r.png

二、 原型链

因为每个对象和原型都有原型,对象的原型指向原型对象

而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。

所谓原型”“,不管是现实中的锁链,还是计算机算法中的链表,他们都是把一个一个节点串联起来,从而形成一条链。

原型对象是节点,而私有属性 __proto__ 就是串联连接两个原型对象节点的”线“ 当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined

我们可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性;使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true

    function Person() {}
    Person.prototype.a = 123;
    Person.prototype.sayHello = function () {
      alert("hello");
    };
    var person = new Person()
    console.log(person.a)//123
    console.log(person.hasOwnProperty('a'));//false
    console.log('a'in person)//true

person实例中没有a这个属性,从 person 对象中找不到 a 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,很幸运地得到a的值为123。那假如 person.__proto__中也没有该属性,又该如何查找?

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。Object是JS中所有对象数据类型的基类(最顶层的类)在Object.prototype上没有__proto__这个属性。

console.log(Object.prototype.__proto__ === null) // true

3174701-18a76d28c0a9ea1b.jpg

三、 prototype 和 __proto__

有的同学可能会问prototype 和 __proto__有什么用呢?

实例对象的__proto__指向构造函数的prototype,从而实现继承。

prototype对象相当于特定类型所有实例对象都可以访问的公共容器

v2-1ae63b09f2f38aee29efc79f1400b8d3_r.jpg 看代码

function Person(nick, age){
    this.nick = nick;
    this.age = age;
}
Person.prototype.sayName = function(){
    console.log(this.nick);
}

var p1 = new Person('Byron', 20);

var p2 = new Person('Casper', 25);

p1.sayName()  // Byron

p2.sayName()  // Casper

p1.__proto__ === Person.prototype       //true

p2.__proto__ === Person.prototype   //true

p1.__proto__ === p2.__proto__           //true

Person.prototype.constructor === Person  //true

注意

  1. Object.prototype.__proto__ 已被大多数浏览器厂商所支持的今天,其存在和确切行为仅在ECMAScript 2015规范中被标准化为传统功能,以确保Web浏览器的兼容性。为了更好的支持,建议只使用 Object.getPrototypeOf()
  2. Object.create(null) 新建的对象是没有__proto__属性的。

四、 JavaScript 中的继承

继承是指一个对象直接使用另外一个对象的属性和方法 由此可见只要实现属性和方法的继承,就达到继承的效果

  • 得到一个对象的属性
  • 得到一个对象的方法

4.1 原型链继承

利用原型让一个引用类型继承另一个引用类型的属性和方法

function SuperType() {
    this.name = 'tt';
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    this.name = 'oo';
}
SubType.prototype = new SuperType()

var instance = new SubType()

instance.sayName() // oo
instance instanceof SubType // true
instance instanceof SuperType // ture

以上的试验中,我们创建了两个构造函数 SuperType 和 SubType ,并且让 SubType 的原型指向 SuperTypeSubType 也就继承了 SuperType 原型对象中的方法。所以在创建 instance 实例的时候,实例本身也就具有了 SuperType 中的方法,并且都处在它们的原型链中

SubType.prototype.constructor == SubType // false
SubType.prototype.constructor == SuperType // true

需要注意的是:这个时候 SubType.prototype.constructor 是指向 SuperType 的,相当于重写了 SubType 的原型对象。

SubType.prototype 相当于 SuperType 的实例存在的,所以 SubType.prototype.constructor 就指向 SuperType

4.2 原型继承的特点

优点:

  • 简单、易于实现
  • 父类新增原型方法/原型属性,子类都能访问到
  • 非常纯粹的继承关系,实例是子类的实例,也是父类的实例

缺点:

  • 无法实现多继承
  • 想要为子类 SubType 添加原型方法,就必须在 new SuperType 之后添加(会覆盖)
  • 来自原型对象的所有属性被所有实例共享(引用类型的值修改会反映在所有实例上面)
  • 创建子类实例时,无法向父类构造函数传参

五、总结

prototype 和 __proto__

  • 每个对象都有一个__proto__属性,并且指向它的prototype原型对象

  • 每个构造函数都有一个prototype原型对象

  • prototype原型对象里的constructor指向构造函数本身

原型链

每个对象都有一个__proto__,它指向它的prototype原型对象,而prototype原型对象又具有一个自己的prototype原型对象,就这样层层往上直到一个对象的原型prototypenull,原型链的终点为 null 对象,即无中生有。

这个查询的路径就是原型链