写这个笔记是因为最近在看《JavaScript高级程序设计(第三版)》,原书写得非常好,讲解也非常细致,甚至可以作为一本手册(我少有喜欢一本手册式的教材,不过这本绝对是个例外!)。在这本书里学到了不少东西,不动笔墨不读书,为了自己还是决定总结一些笔记出来,笔记里的东西基本都出自书中,不过以自己的方式说出来而已。再次热烈推荐原书,真的好赞!
1. 原型(prototype) 是什么呢?
如果解释的广义一点,应该说原型是某一类对象的共同的性质或者特征。具体说来,在JavaScript中里原型是定义在 function 和 对象里的一项属性。更明确一点说,比如每当创建了一个 function, 这个 function 内部就默认有一个 prototype 的属性。
2. 原型本身是什么类型呢?
原型本身其实是一个对象,是object 。
3. function 和 对象里的原型有什么不一样的吗?
function 的原型是可以显式地通过 prototype 访问的,你可以动态的去添加属性,方法,你甚至可以自己创建一个 object, 将它赋值给一个 function 的 prototype;
而 object 的原型是无法通过标准方式显式地得到的,说得更具体一点,每当创建了一个object,无论是通过何种方式创建,这个 object 内部都会包含一个指针,这是一个内部属性,ECMA-262第5版中管这个指针叫 [ [ Prototype ] ] , 虽然没有一个标准的方式去访问它,但是在 Firefox, Chrome, Safari 里每个对象上都支持一个__proto__ 属性, 通过这个属性可以去访问原型,而在其他浏览器的实现中,这个属性则完全是不可见的。
4. 通过 function 得到的原型和通过对象的到的原型有什么联系吗?
终于到重点了,(严格的说,把这部分内容描述为两种原型的联系其实是不正确的,但是当了解完这部分内容后,怎么定义其实并不重要啦,),我们来看示例,首先定义一个函数(构造函数),
1: function Person(name, age) {
2: this.name = name;
3: this.age = age;
4: }
这时我们就有了一个 function,而且这个 function 会有一个 prototype 的属性, 通过这个属性可以访问该函数的原型,具体见下图,
从图中可以看出,其实不仅仅是可以通过 Person.prototype 访问原型,原型中也有一个 constructor 的属性,Person.prototype.constructor 就指向 Person 函数自身。
接下来,我们再创建两个 Person 的实例,
1: var p1 = new Person("Bob", 20);
2: var p2 = new Person("Peter", 22);
这时候发生了什么呢,来看看下图,
当创建了实例 p1 和 p2 之后,p1, p2 内都存在内部属性 [ [ Prototype ] ], 而这个属性,其实就指向构造函数 Person 的原型,有趣的是 p1 和 p2 和构造函数 Person 本身没有直接的关系, 它们的联系就是它们都指向了同一个原型!
5. 我在构造函数的原型中添加了方法,为什么通过构造函数创建的实例也可以直接访问这个方法呢?
也许写一段code能帮助理解上面的问题,
1: Person.prototype.sayHi = function () {
2: alert("Hi");
3: }
4:
5: var p1 = new Person();
6: p1.sayHi(); // why this works?
其实,每当读取对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性,搜索首先从对象实例本身开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值,如果没有找到,则继续搜索该实例的原型,在原型对象中查找具有给定名字的属性。
6. 如果我没有采用构造函数的方式创建一个对象实例,那该实例的 [[ Prototype ]] 会指向什么呢?
倘若采用一般的方式创建一个对象实例,比如
1: var obj1 = {};
2: var obj2 = new Object();
想必你已经猜到,此时 [ [ Prototype ] ] 会指向 Object 的 prototype。
7. 当创建了一个对象实例的时候,它的默认的toString等方法藏在哪里?
没错,就藏在原型里,但不妨讲得更具体一点,假设之前的 script 已经运行过,现在,我们有了一个 Person 函数,我们有了 Person 的实例 p1,当我们直接调用 p1.toString() 会怎样呢,根据问题5,我们知道当在实例本身中搜索不到 toString 时,会转而搜索实例的 [ [ Prototype ] ] 指向的 Person.prototype, 但是 Person.prototype 里其实仍然没有定义 toString ! 然后,我们意识到其实 Person.prototype 也是一个Object!那么继续,再次根据问题5,Person.prototype 的[[Prototype]] 指向哪里呢? 没错就是 Object 的 prototype, toString 就在这里。
其实我们可以用console.dir(Person.prototype.__proto__)查看,你会发现你看到的就是Object Prototype,这里面定义了 valueOf, toString 等等方法。
看到这里,我们已经可以隐约感到JavaScript中面向对象的继承机制了…… 原型的运用对继承的实现至关重要!
对原型的总结就到这里,这里所列的仅仅是一部分,但我认为这是理解原型中比较重要的一部分,还有一些有关原型的方法,就不再赘述了。