0%

重新认识JS对象(三)-- 原型及原型链

原型及原型链是Javascript里面很重要的概念,本文深入探讨了两者的关系及区别.

一、原型检测

javascript中提供Object.getPrototypeOf()方法来获得对象的直接原型。

1
2
3
4
5
6
7
8
9
10
11
12
function Person() {
this.name = 'sillywa'
}
var person1 = new Person()
Object.getPrototypeOf(person1) // {constructor: ƒ Person()}
Object.getPrototypeOf(person1.__proto__) // Object.prototype

var person = {
name: 'sillywa'
}
var person2 = Object.create(person)
Object.getPrototypeOf(person2) // {name: "sillywa"}

javascript有以下几种方法检测一个对象的原型:

  1. isPrototypeOf():检测一个对象是否是另一个对象的原型
  2. obj.constructor.prototype:检测非Object.create()创建的对象的原型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var obj1 = {
    name: 'sillywa'
    }
    var obj2 = Object.create(obj1)

    // isPrototypeOf()方法
    Object.prototype.isPrototypeOf(obj1) // true
    obj1.isPrototypeOf(obj2) // true
    Object.prototype.isPrototypeOf(obj2) // true

    // obj.constructor.prototype
    obj1.constructor.prototype === Object.prototype // true
    // obj1是obj2的原型,以下等式应为true
    obj2.constructor.prototype === obj1 // false
    // 而实际上
    obj2.constructor.prototype === Object.prototype // true
    以上代码中obj1obj2的原型,obj2.constructor.prototype === obj1应为true但是实际上却是false,因为obj2__proto__里面并没有一个constructor属性,obj2.constructor实际上是obj1__proto__里面的constructor,所以obj2.constructor.prototype === Object.prototype

    二、constructor__proto__prototype之间的关系

    在javascript中我们每创建一个对象,该对象都会获得一个__proto__属性(该属性是个对象),该属性指向创建该对象的构造函数的原型prototype,同时__proto__对象有一个constructor属性指向该构造函数。这里我们需要注意的是只有函数才有prototype,每个对象(函数也是对象)都有__proto__Object本身是个构造函数。举例来说:
    1
    2
    3
    4
    5
    6
    7
    8
    var obj = new Object()
    // 也可以使用对象字面量创建,但使用Object.create()情况会不一样
    // Object本身是个构造函数
    Object instanceof Function // true
    obj.__proto__ === Object.prototype // true
    obj.__proto__.constructor === Object // true
    // 我们一般习惯这样写
    obj.constructor === Object // true
    当我们访问obj.constructor的时候,obj本身是没有constructor属性的,但属性访问会沿着__proto__向上查找,即在obj.__proto__里面寻找constructor属性,如果找到了就返回值,如果未找到则继续向上查找直到obj.__proto__.__proto__...(__proto__) === null 为止,没有找到则返回undefined。这样由__proto__构成的一条查找属性的线称为‘原型链’。

    三、进一步探讨

    我们知道JS是单继承的,Object.prototype是原型链的顶端,所有对象从它继承了包括toString等等方法和属性。

前面我们说到Object本身是构造函数,那么它继承了Function.prototype;Function也是对象,继承了Object.prototype。这里就有一个鸡和蛋的问题:

1
2
Object instanceof Function  // true
Function instanceof Object // true

以下是ES规范的解释:

Function本身就是函数,Function.__proto__是标准的内置对象Function.prototype
Function.prototype.__proto__是标准的内置对象Object.prototype

1
2
3
4
function Person(name) {
this.name = name
}
var person1 = new Person('sillywa')

总的来说:先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,FunctionObject和其它构造函数继承Function.prototype而产生。

四、Object.create()

我们知道通过Object.create()创建的对象实际上等于将该对象的__proto__指向Object.create()里面的参数对象,那么当涉及到原型时它是怎么工作的呢?

1
2
3
4
5
6
7
8
9
var a = {
name: 'sillywa'
}
var b = Object.create(a)

b.__proto__ === Object.prototype // false
b.__proto__ === a // true
b.__proto__.constructor === Object // true
b.__proto__.hasOwnProperty('constructor') // false

下面我们来具体看一看当var b = Object.create(a)到底发生了什么,以下实在浏览器中的结果:

我们可以看到当var b = Object.create(a)实际上是把b__proto__指向了a。当访问b.constructor时,实际上访问的是b.__proto__.__proto__.constructor

五、实例与总结

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.name = name
}
var person1 = new Person('sillywa')

person1.__proto__ === Person.prototype
person1.__proto__.__proto__ === Person.prototype.__proto__
person1.__proto__.__proto__ === Object.prototype
Person.prototype.__proto__ === Object.prototype
person1.__proto__.__proto__.__proto__ === null

Person.__proto__ === Function.prototype

以上均返回true,前五个等式和第一部分内容相关,最后一个等式为第二部分内容,需要注意的是IE浏览器里面并没有实现__proto__,为了便于理解我们可以这样解释,但是最好不要在实际中使用