把prototype属性整明白(之一)

长久以来,看到js代码中的prototype属性都有一种神经错乱的感觉,时而明白,时而又糊涂了,甚至我都没搞明白自己到底糊涂在哪个地方上,经过这些天的死磕,终于把这事儿整明白了。

下面写一些我的心得,关于原型的基本概念就不讲了,请先参考相关书籍,这里只讲容易陷入误区的地方:

首先看这段代码:

function O(){}
O.foo = function(){
     console.log('O.foo');        
}
O.prototype.foo = function(){
     console.log('O.prototype.foo');        
}
var o1 = new O();
o1.foo();

var o2 = Object.create(O);
o2.foo();


先不看下面答案,你能看出代码的输出结果吗?

===============================================

下面是答案

===============================================

输出:

O.prototype.foo
O.foo

你答对了吗?答对了是明白人,就不用继续看了,像我一样糊涂的继续看:

function O(){}  创建了一个函数

当使用 var o1 = new O(); 时,O就不是一个普通的函数了,而是一个构造函数

凡是用new +构造函数创建的对象,它的原型对象就是构造函数的prototype属性,在这里

o1的原型对象就是 O.prototype

我以前有个误区,以为o1的原型对象是O,这是不对的,应该是O.prototype

当执行 o1.foo()时,先查询o1自己有没有foo属性(或者说方法也行,在js中数据和代码分的没那么清的),如果没有,就到它的原型对象中去找,前面说了,o1的原型对象是 O.prototype,原型对象里面有foo,所以输出 ‘O.prototype.foo’。

这个其实很好理解,一般人光看这行代码也不会有什么糊涂的,如果和其他一些代码放在一起,就会糊涂了,那些代码我以后会讲到。

再看 var o2 = Object.create(O);

Object.create()方法是ECMAScript5中定义的方法(不是所有浏览器都支持,但很容易模拟),它实现了基于原型的继承!它返回一个新对象,传入的第一个参数是这个新对象的原型。

在这里,传入的参数是O,返回的对象是o2,也就是说:

o2的原型是O。

看出和o1的不同了吗?o1的原型是O.prototype,而o2的原型是O。

所以执行 o2.foo(),执行的是 O.foo()

Object.create()是如何实现继承的呢?这需要深入到Ojbect.create内部,抛去其他代码,核心代码其实只有三行:

Object.create = function(o){

     function f(){};

     f.prototype = o;

     return new f()

}


为了方便理解,我们不如把 var o2 = Object.create(O) 替换成下面三行代码:

function F(){};

F.prototype = O;

o2 = new F()


o2.foo();


F是一个构造函数,F的prototype属性是O,前面已经讲过,通过new+构造函数创建的对象,它的原型对象是构造函数的prototype属性,也就是 F.prototype,也就是 O,

所以 o2的原型对象是O

开始看可能有点绕,看一会儿就明白了。