在没有类的情况下,JavaScript如何创建对象、实现继承?

来源:互联网 发布:莎莎源码是什么意思 编辑:程序博客网 时间:2024/06/05 02:02

 首先,我们有必要了解一下JavaScript在设计时为何没有类的概念。

我参考阅读了 两篇博文:

1.《JavaScript继承机制的设计思想  》http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html

2.《理解JavaScript面向对象的思路》http://www.cnblogs.com/winter-cn/archive/2009/05/16/1458390.html

了解到94年网景公司(Netscape)在发布了浏览器的某一个版本后,出现了一个问题:表单的验证是由服务器来完成了,很消耗资源。因而急需一种浏览器脚本语言,能跟网页互动,在用户端就能做一些简单的表单验证,从而分担服务器的压力。这个任务交给了工程师Brendan Eich。当时面向对象程序设计语言(C++和Java)方兴未艾,他受其影响使JavaScript也面向了对象并引入了new操作符和构造函数。但他没有引入类的概念,因为那样一来,JavaScript就成为完整的面向对象编程语言了,就不简单易学了,而是引入了prototype(“原型”的意思)。

 

       具体的实现方式在对象创建时,在创建对象时new后跟的是构造函数(而不是类),同时为每个对象规定一个私有属性[[prototype]],并使其指向构造函数的prototype属性所指向的对象,即原型对象。当读取一个对象的属性时,如果对象本身没有这个属性,会尝试访问原型对象的相应属性。具体实现中,[[prototype]]所指向的对象仍然可以有[[prototype]],实际的访问就是一个链式的操作,直到找到这个属性或者[[prototype]]为空为止,所以常常听到原型链的说法,JavaScript主要就是通过它来实现继承的。为了防止[[prototype]]出现循环,JS引擎会在任何对象的[[prototype]]属性被修改时检查。按照标准,这个[[prototype]]语言使用者是无法访问的,不过FireFox的JS引擎把[[prototype]]暴露出来,作为公有属性"__proto__"。另外,为了类型识别,对构造函数(其实不仅限于构造函数,而是对所有的函数)规定了prototype属性(不同于对象,函数的prototype属性可以直接访问),指向原型对象,而所有的原型对象都会在创建一个新函数时自动获得一个constructor属性,它默认指向构造函数。

 

总结一下,实例、原型对象、构造函数三者之间的关系。那就是:

1.每个构造函数都有一个指向原型对象的指针(prototype属性);

2.每个原型对象都有一个指向构造函数的指针(constructor属性);

3.每个实例(对象)都有一个指向原型对象的内部指针([[prototype]])。

 

下面探讨JavaScript中对象的创建。创建对象所采用的模式越来越好。

1.采用new Object()或对象字面量的模式创建对象。-----弊端:会产生大量重复的代码。

2.采用工厂模式。解决了创建多个相似对象的问题。----弊端:无法解决对象识别问题。

3.采用构造函数模式。解决了对象识别的问题-----弊端:同种引用类型的方法不能共享。

4.采用原型模式。实现了同种类型的属性共享。----弊端:当属性值本身又是引用类型时,共享时,不同的实例都持有同一份引用,都能对其操作,这样的属性不方便。

5.组合使用构造函数模式和原型模式。将公用的属性、方法放在原型对象里定义,将个性化的属性、方法放在构造函数里定义,从而最大限度地节省内存。

 

最后探讨JavaScript中的继承机制。实现继承所采用的范式也是越来越好。

1.原型链。重写原型对象,使其具有一个内部指针指向另一个类型的原型对象,从而实现继承。----弊端:当原型对象中包含有引用类型的属性值时,不方便(其弊端类似于创建对象时的模式4)。

2.组合继承。借用构造函数与原型链相结合。----弊端:两次调用了父类型的构造函数。

3.寄生组合式继承。采用寄生式继承的模式,使子类型的原型对象具有一个指向父类型原型对象的内部指针(这就是所谓的寄生),另外,在子类的构造函数中借用父类型的构造函数。这是开发人员普遍认为最理想的继承范式。