JavaScript原型与原型链
要了解原型和原型链,首先来看看构造函数:
构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
var person1 = new Person('Tom',5);
var person2 = new Person('Jack',3);
要创建 Person 的新实例,必须使用 new 操作符。new操作符的原理:
-
创建一个新对象;
-
将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
-
执行构造函数中的代码(为这个新对象添加属性);
-
返回新对象。
上面的例子中,person1 和 person2 都是构造函数 Person 的实例,这两个实例都有一个 constructor(构造函数)属性,该属性包含一个指向构造函数 Person 的指针。
console.log(person1.constructor == Person);//true
console.log(person2.constructor == Person);//true
事实上,说实例拥有构造函数不太准确,具体的应该是:
原型与原型链
放一张经典的图和我自己的理解:
只要创建了一个新函数,就会为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。
所有原型对象都会自动获得一个 constructor (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针[[prototype]](内部属性__proto__),指向构造函数的原型对象。
function Foo(){};
var f = new Foo();
console.log(Foo.prototype.constructor == Foo);//true
console.log(f.__proto__ == Foo.prototype);//true
所有通过 new Function() 创建的对象都是函数对象,可以理解为 Function 的实例对象,其内部属性__proto__指向Function.prototype。
function Foo(){};
var Foo2 = function(){};
var Foo3 = new Function('str','console.log(str)');
console.log(Foo.__proto__ == Function.prototype);//true
console.log(Foo2.__proto__ == Function.prototype);//true
console.log(Foo3.__proto__ == Function.prototype);//true
var obj = {};
它等同于下面这样:
var obj = new Object();
obj 是构造函数(Object)的一个实例。所以:
obj.constructor === Object
obj.__proto__ === Object.prototype
创建对象的构造器不仅仅有 Object,我们也可以构造函数来创建 Function、Array、Number、Boolean、String、Date、RegExp对象,这些构造器也是函数对象(用Function构造的是函数):
console.log(Object.__proto__ == Function.prototype);//true
console.log(Function.__proto__ == Function.prototype);//true
console.log(Array.__proto__ == Function.prototype);//true
console.log(Number.__proto__ == Function.prototype);//true
console.log(String.__proto__ == Function.prototype);//true
console.log(Boolean.__proto__ == Function.prototype);//true
默认的原型:
所有引用类型默认都继承了 Object,而 这个继承也是通过原型链实现的。
所有函数的默认原型 prototype 都是 Object 的实例,因此默认原型都会包含一个内部指针__proto__,指向 Object.prototype。这也正是所有自定义类型都会继承 toString()、 valueOf() 等默认方法的根本原因。
console.log(Foo.prototype.__proto__ == Object.prototype);//true
console.log(Function.prototype.__proto__ == Object.prototype);//true
console.log(Array.prototype.__proto__ == Object.prototype);//true
console.log(Number.prototype.__proto__ == Object.prototype);//true
console.log(String.prototype.__proto__ == Object.prototype);//true
console.log(Boolean.prototype.__proto__ == Object.prototype);//true
//null是原型链的顶端
console.log(Object.prototype.__proto__ == null);
原型链的基本概念:
每个函数都有一个原型对象,原型对象都自动包含一个指向函数的指针,而实例都包含一个指向原型对象的内部指针。
假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型对象的指针,相应地,另一个原型对象中也包含着一个指向另一个函数的指针。
假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。
连接是存在与实例与原型对象之间的,即原型链的实现是通过__proto__实现的
console.log(Foo.prototype.constructor == Foo);//true
console.log(f.__proto__ == Foo.prototype);//true
//下面这个关系是通过原型链的搜索实现的
console.log(f.constructor == Foo);//true
实例对象本身是没有 constructor 属性的,实例包含一个内部属性__proto__,该属性仅仅指向了 Person.prototype。对 constructor 的访问是通过查找对象属性的过程来实现的。先搜索实例对象 f 本身,没有找到,则搜索Foo.prototype,在Foo.prototype中找到了constructor属性,该属性指向构造函数Foo。
红宝书中给出的栗子的关系图:
原型搜索机制:
访问一个实例属性时,首先会在实例本身中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。