JavaScript原型及原型链

2,443 阅读7分钟

主要参考:

辅助参考:

1.普通对象和函数对象

在JavaScript中,一切皆为对象。分为两种,一种叫普通对象,一种叫函数对象。

凡是通过 new Function() 创建的对象都叫函数对象,其他的都叫普通对象。

//下面三个都是普通对象
var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

//下面三个都是函数对象
function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

2.构造函数

function Person(){
    
}
var person1 = new Person();
person1.name = 'Kevin';
console.log(person1.name) // Kevin

上面例子中,Person 就是一个构造函数,person1 就是 Person 的实例对象。 实例对象可以有一个 constructor(构造函数)属性,这个属性是一个指针,指向的是 Person。

 console.log(person1.constructor == Person); //true

在下面的原型对象里面会介绍实际情况并不是这样,实例对象 person1 里面其实是没有 cosntructor 属性的。

3.原型对象

要点

  • 原型对象,当 Person 构造函数被定义出来的时候,它就存在了,是一个有特殊地位的普通对象。

  • 在JavaScript中,每一个已定义的对象都有一些预定义的属性。

  • 在这些预定义的属性中,所有的对象都含有一个__proto__属性,其中所有的函数对象都含有额外的一个prototype属性,这个属性指向函数的原型对象。即:Person.prototype就是Person的原型对象。

  • 每一个原型对象都含有一个 constructor (构造函数)属性,这个属性指向的又是它对应的那个构造函数本身。即:Person.prototype.constructor == Person

  • 原型对象是普通对象,但是 Function.prototype 除外,这个是一个函数对象,并且没有 prototype 属性(之前提到所有的函数对象都有一个 prototype 属性,但是这个特殊的也得除外)。

实例对象和原型对象

下面来解释为什么实例对象没有 constructor 属性,但是 person1.constructor == Person依然成立?

其实是因为 __proto__ 属性,这个是关于原型链的东西,下面会讲,person1 没有 constructor 属性,但是它可以顺着原型链往原型对象上寻找,所以找到了 Person.prototype 上面的 constructor 属性,又因为:

Person.prototype.constructor == Person

所以:

person1.constructor == Person

这个成立。

再举个例子:

function Person() {

}

Person.prototype.name = 'Kevin';

var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin

在这个例子中:

  1. 我们先在构造函数Person的原型对象中添加了一个name属性为 Kevin,接着实例化了 Person 为 person。
  2. 在 person 中添加了name属性为 Daisy。
  3. 当我们输出 person 的name时,因为这时 person 中有这个属性,自然就返回了 Daisy。
  4. 后面我们删除了 person 中的这个属性,找不到了,于是js顺着 person 的原型链往上找,找到了 person 的构造函数 Person 的原型对象中有name属性,于是返回了Kevin。

假如原型对象上也没有这个属性,那么会顺着原型对象的__proto__继续往上找,最终会找到 Object.prototype.__proto__,这个是原型链的起点,为 null。如果还是没有就会返回 undefined。

作用

原型对象的作用是用来继承。

  var Person = function(name){
    this.name = name;
  };
  Person.prototype.getName = function(){
    return this.name;
  }
  var person1 = new person('Mick');
  person1.getName(); //Mick

通过给 Person 的原型对象设置了一个函数对象的属性 getName,使得 Person 的实例 person1 继承了这个属性。实现方法是通过原型链。

4. __proto__

前面提到每个对象都会有一个 __proto__ 的预定义属性,这个是用来指向创建它的构造函数的原型对象的

对象 person1 有一个__proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype,所以有:

person1.__proto__ == Person.prototype

__proto__实际上并不存在于Person.prototype中,而是在Object.prototype中,与其说是一个属性,倒不如说是一个getter/setter,当使用 obj.__proto__时,可以理解成返回了Object.getPrototypeOf(obj)

最后是关于继承的,前面所述的每个对象会从原型上继承属性。但是实际上继承的说法并不准确。因为继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

5.构造器

即用于创建对象的构造函数。他们本身都是函数对象。常见的有:Object,Array,Date,Function等。

都是通过 new 后面跟构造器来创建实例对象的。所以存在:

var a = new Object();
a.constructor === Object
a.__proto__ === Object.prototype;

var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;

var c = new Date(); 
c.constructor === Date;
c.__proto__ === Date.prototype;

var d = new Function();
d.constructor === Function;
d.__proto__ === Function.prototype;

6.原型链

五个问题:

  1. person1.__proto__ 是什么?
  2. Person.__proto__ 是什么?
  3. Person.prototype.__proto__ 是什么?
  4. Object.__proto__ 是什么?
  5. Object.prototype.__proto__ 是什么?

解答:

  1. 每个对象的__proto__都是指向它的构造函数的原型对象的。所以:
person1.__proto__ === Person.prototype
  1. Person这个构造函数是一个函数对象,是通过 Function 构造器产生的。所以:
Person.__proto__ === Function.prototype
  1. Person 的原型对象本身是一个普通对象,而普通对象的构造函数都是Object。所以:
Person.prototype.__proto__ === Object.prototype
  1. 刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function 构造产生的。所以:
Object.__proto__ === Function.prototype
  1. Object 的原型对象也有__proto__属性,但是比较特殊,是null。因为null是处于原型链的顶端。
Object.prototype.__proto__ === null

这里有一个问题,就是所有的对象都是继承自Object对象,但是Object对象又是继承自Function对象,这似乎是进入了一个无限套娃的死循环。关于 Object 和 Function 以及其他的对象关系,可以总结为四点:

  1. 一切对象都是继承自Object对象,Object 对象直接继承自根源对象 null。
  2. 一切的函数对象(包括 Object 对象),都是继承自 Function 对象。
  3. Object 对象直接继承自 Function 对象。
  4. Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象。

下面来理解这几个点:

1.一切对象都是继承自Object对象,Object对象继承自根源对象null。

理解:

  1. 一切对象的原型链最终追溯到顶层都是 …… → Object.prototype → null。也就是顺着__proto__属性走到上面都是指向Object的原型对象,然后再指向 null。
  2. 一切的对象都包含有Object的原型方法,比如 toString,valueOf 等。

2.一切的函数对象都是继承自Function对象。

理解:函数对象都是由 Function 构造的,所以函数对象的__proto__都指向 Function 的原型对象。

String.__proto__ === Function.prototype;
Object.__proto__ === Function.prototype;
Function.__proto__ === Function.prototype;

3&4. Object对象直接继承自Function对象。Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象。

理解:

Function的原型链是:Function → Function.prototype → Object.prototype → null

Object的原型链是:Object → Function.prototype → Object.prototype → null

和上面的结论其实不冲突。只是在 Function 这里绕了一圈。

几点问题

一切对象继承自Object,Object又继承自Function,那一切对象是不是都有Function的原型方法?

答:不对。普通对象都没有Function的原型方法。有两种解释:

  1. 从我们所写原型链中可以看出,Object 是继承自 Function,而 Object 也有 Function 的原型方法(比如bind),但 Object 继承得到的方法储存于__proto__属性中,普通对象从 Object 继承到的原型方法却在于 prototype 属性中,因而不对。
  2. 一切对象都继承自 Object.prototype,而一切函数对象都继承自 Function.prototype(且Function.prototype会最终继承自Object.prototype),也就是说普通对象和函数对象的区别是:普通对象直接继承了Object.prototype,而函数对象在中间还继承了Function.prototype。所以只有函数对象才有Function的原型方法。
Function对象自己继承自己?

答:应该不能这么理解。Function作为一个内置对象,是运行前就已经存在的东西,所以根本就不会根据自己生成自己,所以就没有什么鸡生蛋蛋生鸡,就是鸡生蛋。至于为什么Function.__proto__ === Function.prototype。我认为有两种可能:一是为了保持与其他函数一致,二是就是表明一种关系而已。 简单的说,我认为:就是先有的Function,然后实现上把原型指向了Function.prototype,但是我们不能倒过来推测:“因为Function.__proto__ === Function.prototype,所以 Function 调用了自己生成了自己。”