区分JS中的
我们知道javascript是基于原型(prototype)继承的语言。关于javascript原型继承的更多内容请参阅之前的这篇文章浅谈Javascript中的原型继承,内容绝对不会让你失望。
不过本篇文章将会详细阐述javascript中用于原型继承的两个重要的东西__proto__和prototype,以及由它们延伸出的一点容易让人疑惑的方面。
__proto__和prototype的概念
prototype就是原型的意思,就是函数(构造器)的一个属性,每个函数都将会有一个prototype属性。此属性是一个引用类型(指针),它指向函数的原型集对象。我们一般可以通过prototype修改函数的原型属性。而__proto__是一个对象(可以是某个构造器的实例)的属性,它在对象(实例)被创建时跟随被创建,它也是一个引用类型(指针),指向函数(构造器)的prototype属性。
两者的关系如下,
function Foo() {
}
var foo = new Foo();
console.log(foo.__proto__ === Foo.prototype); // true额外提一点,__proto__属性目前在IE浏览器中貌似不能直接访问,不过在Chrome和Firefox中是可以直接访问的。
new的过程
如下代码,
function Foo() {
}
var foo = new Foo();我们使用new创建了构造器Foo的一个实例foo。那么这个过程究竟是怎么样的呢?它将分为以下几步,
- 创建一个对象
foo,并且foo = {} - 将构造器的原型链到
foo的__proto__上,即foo.__proto__ = Foo.prototype。这一步至关重要,为实例能够访问构造器原型方法及原型链查找等操作做好了铺垫。 - 执行构造器
Foo,并将对象foo绑定到其上下文环境中,即Foo.call(foo)。其实就是将Foo中的this指针指向foo。
示例
第一个示例
下面让我们来看个完整的例子来探索一下prototype和__proto属性的相互关系,
function Animal() {
this.eats = true;
}
var cat = new Animal();
Animal.prototype.jumps = true;
console.log(cat.eats); // true;
console.log(cat.jumps); // true
// change constructor's prototype
Animal.prototype = {
bark: true
};
var dog = new Animal();
console.log(cat.bark); // undefined
console.log(dog.bark); // true
console.log(cat.__proto === Animal.prototype); // false;
console.log(dog.__proto === Animal.prototype); // true;下面是一张关于上述代码中Animal(构造器)及两个实例(cat及dog)的原型关系图,
从图中我们可以看出,
cat和dog是Animal构造出的实例cat的__proto__属性指向一个对象,此对象是Animal改变prototype之前的指向。dog的__proto__属性指向一个对象,此对象是Animal改变prototype之后的指向。和Animal.prototype的指向一致。- 通过
cat或者dog的__proto__属性,经过层层查找,最终的都会指向同一个对象,即Object.prototype,而Object.prototype的__proto__指向null,此时原型链已经到顶。
第二个示例
下面我们再来看个例子来探索一下javascript是如何在其原型链上进行属性查找的,
function Person() {
this.name = 'gejiawen';
this.secret = function() {
console.log('I am a secret!');
};
};
Person.prototype.sayName = function() {
console.log('My name is ', this.name);
};
Person.prototype.secret = function() {
console.log('I am a secret on prototype!');
};
var p = new Person();
console.log(p.name); // gejiawen
console.log(p.secret()); // I am a secret!
console.log(p.sayName()); // My name is gejiawen下面是实例对象p在Chrome console中的打印,
所以,各个打印的解释如下,
p.name,直接在对象p中找到了,将其打印出来。p.secret,在对象p中也直接找到了,执行相应方法。p.sayName,在对象p中并未找到相关定义,此时将会开始检索p.__proto__中对象,发现了sayName的定义,此时就终止检索,执行相应函数。
有两点需要额外提出来,
- 我们可以看到在构造器和
prototype中定义了一个同名方法secret,从上述代码中可以看出,是不会执行prototype中的secret函数。因为javascript在示例对象p自身的定义找到secret时就终止检索了。 - 针对上面的第三点解释,如果我们在
p.__proto__仍然未找到sayName的相关定义,那么javascript会继续向上检索,继续检查p.__proto__.__proto__,如此反复,直至到Object.prototype。
示例总结
通过上面的示例,我们可以看出,示例对象的__proto__在javascript的原型继承模型中扮演着不可或缺的角色。
可以说__proto__就是将一个个的对象串成一条完整原型链的粘合器。
个人觉得prototype相对于__proto__更加像是一个开放的接口,而__proto__更倾向是原型链模型的内部实现。我们在平时进行javascript开发时,可以在适当的时候应用一些prototype的知识来提高代码质量。
一些额外的问题
Object.create方法
Object.create是ES5中引入的方法,
Object.create(proto[, propertiesObject])MDN上给出的定义如下,
The
Object.create()method creates a new object with the specified prototype object and properties.
意思就是我们可以在创建对象时,可以自定义其原型。(需要注意的是,当给Object.create传入的参数不是一个对象或者null时,它将会抛出一个错误)
看下面的代码,
var animal = {
eats: true
};
var rabbit = Object.create(animal);
console.log(rabbit.eats); // true此时,rabbit.__proto__上将会有eats属性,如关系如下图,
请注意各个对象的__proto__之间的关系。
instanceof背后的逻辑
我们知道instanceof可以判断某个实例对象是否是某个构造器的实例。那么instanceof判断的依据是什么呢?
function A() {
}
var a = new A();
console.log(a instanceof A); // trueinstanceof的过程如下,
- 对比
a.__proto__和A.prototype。 - 若第一步的返回为
true,则表示a即为A的实例。 - 若第一步的返回为
false,此时执行类似这样的操作a = a.__proto__,然后重复第一步。
其实说白了,instanceof的实质就是比较__proto__和prototype,我们来看个示例来看下是否能说明这个问题,
function Animal() {
this.name = 'wangcai';
}
function Dog() {
}
Dog.prototype = new Animal();
var dog = new Dog();
console.log(dog.name); // 'wangcai'
console.log(dog instanceof Animal); // true
// change the prototype of Class
Dog.prototype = {
name: 'yingcai'
};
console.log(dog instanceof Dog); // false
console.log(dog instanceof Animal); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true这个例子中,我们中间改变了Dog的prototype,此时造成的后果就是破坏了原先dog对象的__proto__与Dog.prototype的引用关系,从而dog instanceof Dog返回的结果为false。