理解javascript中的原型
一.概念
在了解概念之前,我们需要明白这几个概念是不是一回事儿:
- 原型
- prototype属性
- __proto__属性
JavaScript中有两个特殊的对象:Object与Function。Object.prototype是所有对象的原型,处于原型链的最底层。Function.prototype是所有函数对象的原型,包括构造函数。
我们看一个demo及图示:
代码:
// a constructor function function Foo(y) { this.y = y; }// inherited property "x" Foo.prototype.x = 10; // and inherited method "calculate" Foo.prototype.calculate = function (z) { return this.x + this.y + z; }; // now create our "b" and "c" // objects using "pattern" Foo var b = new Foo(20); var c = new Foo(30); // call the inherited method b.calculate(30); // 60 c.calculate(40); // 80 // let's show that we reference // properties we expect console.log( b.__proto__ === Foo.prototype, // true c.__proto__ === Foo.prototype, // true b.constructor === Foo, // true c.constructor === Foo, // true Foo.prototype.constructor === Foo // true b.calculate === b.__proto__.calculate, // true b.__proto__.calculate === Foo.prototype.calculate // true );
图示:
一个对象的真正原型是被对象内部的[[Prototype]]属性(property)所持有。ECMA引入了标准对象原型访问器Object.getPrototypeOf(object),到目前为止只有Firefox和chrome实现了此访问器。除了IE,其他的浏览器支持非标准的访问器__proto__,如果这两者都不起作用的,我们需要从对象的构造函数中找到的它原型属性。
上面一句话引自:http://blog.jobbole.com/9648/。
无论是哪篇文章也好,一个对象的原型我们其实还是有办法去访问的,IE除外。
- 通过非标准实现__proto__去访问
- 通过标准实现Object.getPrototypeOf(object)去访问
好,根据代码和图,我们来给出一实例对象的一些结论,你可以假定存在__proto__这么一个东西:
- 实例对象没有prototype属性
- 实例对象都有一个__proto__属性,指向其原型(原型对象)
- 实例对象的__proto__属性类型是个对象 (js基础类型的对象除外)
- 原型是一个对象,所以原型对象也有__proto__属性
- 函数也是对象,它同时存在__proto__和prototype属性
再给出构造函数(函数)的有关原型的一些结论:
- 构造函数有__proto__属性和prototype属性
- 构造函数的__proto__指向Function.prototype,若该构造函数是非内置的,则其prototype属性为Object类型, 而Number,String,Object的prototype属性同其类型。
- 基本类型的实例的__proto__其实并不是object,但是typeof会对此进行转换
code:
/* *自定义的构造函数 */ var F=function(){}; var f1=new F(); console.log(f1.__proto===F.prototype);//true console.log(Function.prototype);//empty function console.log(F.__proto__===Function.prototype);//true console.log(Object.__proto__===Function.__proto__);//true console.log(f.constructor===F);//true /* *内置包装类,原理其实还是构造函数 */ var str='lalalala',str2=new String('llalalalla'); console.log(str.__proto__);//string类型 console.log(typeof str.__proto__);//运算转换成了object类型 console.log(str.__proto__===String.prototype);//true console.log(str2.__proto__===String.prototype);//true
Object和Function:
- Function 是Object的实例,Object又是Function的实例
- Function是函数的构造函数,而Object也是函数,Function自身也是函数
console.log(Function instanceof Object);//true console.log(Object instanceof Function);//true console.log(Object.__proto__===Function.prototype);//true console.log(Function.__proto__===Object.prototype);//false,这个我就无法理解了,既然Function也是Object的实例,那么为何这个就错了呢 console.log(Function.__proto__===Object.__proto__);//true console.log(typeof Function);//function console.log(typeof Object);//function
二.构造函数prototype属性的修改
给出两个代码示例:
demo1:
var F=function() { // body... } F.prototype={ show:function(){ }, hide:function(){ } }; console.log(F.constructor===Function);//true console.log(F.prototype.constructor===Object);//true
demo2:
var Parent=function(){}; var Son=function(){}; Son.prototype=new Parent(); var parent1=new Parent(); var son1=new Son(); console.log(parent1.constructor===Parent);//true console.log(son1.constructor===Parent);//true
从以上代码可以看出,一旦构造函数的prototype属性发生了更改,那么该构造函数的原型对象的constructor也就发生了改变(这不是废话么)。
通常情况下,构造函数实例化的对象的constructor属性指向其构造函数(最好说成指向其原型对象的constructor)。比如:
var A=function(){}; var a=new A(); console.log(a.constructor===A);//true
但是,构造函数的prototype属性发生了更改(构造函数的原型对象也就发生了改变),于是,构造函数实例化的对象的constructor属性也就变了。
三.构造函数prototype的constructor属性更改
鉴于以上出现的问题,通常有两种做法:
- 重置constructor到原来的构造函数,F.prototype.constructor=F
- 实例化时,将实例的constructor属性重置。当然这个可以在构造函数中处理。如:var Son=function(){ this.constructor=Son; }
两种方法,各有利弊。如果要正确访问原型链,则使用第二种方法,虽然效率差了点;否则,请使用第一种方法。
---------------------------------------------------------------
后续:
其实,写这篇文章初期是为了证明:javascript中的原型是一个对象。但是后来发现函数(js中没有构造函数之说法)的__proto__属性指向的是Function.prototype,而这个只是个空函数。并非对象类型。
后来提了问题去segmentfault(详见:http://segmentfault.com/q/1010000000209832),看了作者的翻译,我才明白,javascript中往大了说,就是两种值类型:原始值和对象。所以函数也是对象,这一点也可以从new Function()这种创建函数的形式可以看出。