当我开始学习JavaScript的对象模型时,第一反应就是难以置信。我完全被它的原型本质给弄糊涂了,毕竟这是我头一次遇到以原型为基础的语言。因为JS中有构造函数这个概念,所以我看不出使用原型能给JS带来任何的好处。我敢说你们中的大部分人也有同样的经历。
但是当我更多的使用JavaScript后,我不仅开始理解它的对象模型,甚至还喜欢上了它的一部分。感谢JavaScript让我见识到了原型语言的优雅与灵活。现在的我十分推崇原型语言,原因就是相对于以类为基础的语言,原型语言的有着更简单、更灵活的对象模型。
JavaScript中的原型
绝大部分的指南或者教程在开始讲解JavaScript对象时,总是直接从「构造函数」讲起。我觉得这是错误的,因为过早的引入相对复杂的概念,只能让JavaScript看起来更复杂,学起来更迷惑。所以让我们暂时抛开这个概念,先讲讲原型的基础。
原型链(又叫做原型继承)
JavaScript中的所有对象都有原型。当对象收到一个消息(译者注:在这里可以理解为属性查找或方法调用),JavaScript便尝试先从对象自身查找属性,如果查不到,那么该消息就会传递给对象的原型并以此类推。这个工作机制很像以类为基础的语言中的单根继承。
原型链的长度没有限制,但通常来说,过长的原型链会造成代码维护与理解上的困难,因此不值得推荐。
__proto__对象
理解JavaScript原型链最简单的方式就是通过__proto__属性。可惜的是,在ES 6之前,__proto__并不是JavaScript中的标准接口,所以一定不要在工作代码中使用它。尽管有这样的限制,这个属性使我们讲解原型更容易了。
// 让我们先创建一个alien对象 var alien = { kind: 'alien' } // 然后是一个person对象 var person = { kind: 'person' } // 最后是一个叫做zack的对象 var zack = {}; // 设置alien为zack的原型 zack.__proto__ = alien // zack现在与alien关联了起来 // 它继承了alien的所有属性 console.log(zack.kind); //=> ‘alien’ // 接下来将person设置为zack的原型 zack.__proto__ = person // 这时候zack又和person关联在一起了 console.log(zack.kind); //=> ‘person’
如你所见,__proto__属性的含义与用法简单明了。即便我们不能在工作的代码中使用__proto__,但我想上面那些例子已经为你理解JavaScript对象模型打下了坚实的基础。
你可以像下面这么操作,来判断一个对象是否是另一个对象的原型:
console.log(alien.isPrototypeOf(zack)) //=> true
原型查找是动态的
你可以在任何时候为原型增加新属性,原型链查找机制会如你所愿找到这些新属性。
var person = {} var zack = {} zack.__proto__ = person //当前zack没有kind属性 console.log(zack.kind); //=> undefined // 让我们在person上增加kind属性 person.kind = 'person' // 现在有反应了,因为它从person上找到了kind属性 console.log(zack.kind); //=> 'person'
新建/更新的属性被赋值给了对象,而不是它的原型
如果你更新一个在原型上已经存在的属性会发生什么?我们来试试:
var person = { kind: 'person' } var zack = {} zack.__proto__ = person zack.kind = 'zack' console.log(zack.kind); //=> 'zack' // zack 现在拥有kind属性 console.log(person.kind); //=> 'person' // person上的属性没有被修改
注意:「kind」属性已经同时存在于person和zack上了。
Object.create
前面解释过使用__proto__为对象设置原型这个办法并不通用。所以我们介绍另外一种简单方法:Object.create()。这是在ES 5中新增的方法,对于那些古老的浏览器或者JS引擎,可以使用es5-shim来模拟。
var person = { kind: 'person' } // 创建一个原型为person的新对象 var zack = Object.create(person); console.log(zack.kind); // => ‘person’
你可以向Object.create()方法中传入一个对象,用来给新生成的对象设置特定的属性:
var zack = Object.create(person, {age: {value: 13} }); console.log(zack.age); // => ‘13’
是不是觉得传进去的对象很麻烦?实际上它必须是这个样子。详细信息可以查看文档。
Object.getPrototype
可以使用Object.getPrototypeOf()方法来获得对象的原型:
ar zack = Object.create(person); Object.getPrototypeOf(zack); //=> person
这里可没有Object.setPorototype这样设置原型的方法。
构造函数
在JavaScript中,构造函数是用来创建原型链最常用的方法。构造函数如此流行的原因在于:它是创建类型的唯一途径。另外一个值得考虑的因素是大部分引擎都对构造函数进行了高度优化。
很不幸的是,构造函数容易让人困惑,在我看来,它是让初学者认为JavaScript难以理解的主要原因。但构造函数是JS语言中的一个重要部分,我们必须对其深刻理解。
作为构造器的函数
在JavaScript中,创建函数的实例可以这么来做:
function Foo(){} var foo = new Foo(); //foo 现在是Foo的一个实例 console.log(foo instanceof Foo ) //=> true
对函数使用关键字new,使得函数从本质上来讲表现的像工厂,即函数能够生成新的对象。后面会讲到,新创建的对象通过函数的原型与函数保持关联。在JavaScript中,我们将这个对象称为函数的实例。
隐式赋值的「this」
当我们使用「new」,JavaScript以「this」关键字的形式向函数中注入了新创建对象的隐式引用。在函数运行结尾处也会隐式的返回该引用。
当我们这么做:
function Foo() { this.kind = ‘foo’ } var foo = new Foo(); foo.kind //=> ‘foo’
实际上会相当于这么做:
function Foo() { var this = {}; // 这里的this是无效的,我们这么做只是为了演示 this.__proto__ = Foo.prototype; this.kind = ‘foo’ return this; }
需要注意的是:只有在使用「new」的时候,「this」才会被赋值为新创建的对象。如果你忘记书写「new」,那么「this」会指向全局对象。忘记new是造成众多bug的原因,所以千万不要忘记写new。
有一个惯例我十分喜欢,那就是如果一个函数被用作构造函数,那么它的函数名首字母要大写,如果你忘记写new关键字,就会很容易发觉。
「函数原型」
JavaScript中的每个函数都有一个特殊属性「prototype」。
function Foo(){ } Foo.prototype
让人有些不敢相信的是:此「prototype」并非函数真正的原型(__proto__)。
foo.__proto__ === foo.prototype //=> false
如果「prototype」术语的含义如此不明确,可想而知会对使用它的人们产生多大的困扰。有一个澄清的方法我认为不错,那就是始终将函数的「prototype」属性称为「函数的原型」,永远不要称其为「原型」。
「prototype」属性指向一个对象,这个对象就是当对函数使用「new」时创建的实例的原型。听起来糊涂?用例子来解释是最容易不过的了:
function Person(name) { this.name = name; } // 函数person拥有一个prototype属性 // 我们可以为函数的原型增加新属性 Person.prototype.kind = ‘person’ // 当我们使用new来生成新对象时 var zack = new Person(‘Zack’); // 新对象的原型将指向person.prototype zack.__proto__ == Person.prototype //=> true // 在这个新对象中,我们能够访问在Person.prototype上定义的属性 zack.kind //=> person
这就是关于JavaScript对象模型的绝大部分内容了。理解__proto__与function.prototype是如何关联的将会给你带来无尽的满足感,当然也可能相反。
文章有错误?看着还迷惑?给我留言吧!
相关推荐
———————————————————————————————————————— 麻雀虽小五脏俱全,该课设用最简单的语句基本上完成了要求的所有功能(仅使用DEVC++)。 ——————————————————...
Java设计模式——原型模式 原型模式Java设计模式——原型模式概念使用场景Java里的克隆代码理解prototype(原型)问题总结优缺点模型优点模型缺点 概念 原型模式是创建型模式的最后一种,讲到原型模式就不得不提到...
本书是《Struts 2权威指南》的第二版,本书介绍的Struts 2是Struts 2.1。本书第二版保留了第一版通俗易懂的写作风格:按Struts 2.1的架构体系,细致地介绍了Struts 2.1各个知识点。在介绍过程中,作者依照读者的学习...
本书是《Struts 2权威指南》的第二版,本书介绍的Struts 2是Struts 2.1。本书第二版保留了第一版通俗易懂的写作风格:按Struts 2.1的架构体系,细致地介绍了Struts 2.1各个知识点。在介绍过程中,作者依照读者的学习...
本书是《Struts 2权威指南》的第二版,本书介绍的Struts 2是Struts 2.1。本书第二版保留了第一版通俗易懂的写作风格:按Struts 2.1的架构体系,细致地介绍了Struts 2.1各个知识点。在介绍过程中,作者依照读者的学习...
本书是《Struts 2权威指南》的第二版,本书介绍的Struts 2是Struts 2.1。本书第二版保留了第一版通俗易懂的写作风格:按Struts 2.1的架构体系,细致地介绍了Struts 2.1各个知识点。在介绍过程中,作者依照读者的学习...
本书是《Struts 2权威指南》的第二版,本书介绍的Struts 2是Struts 2.1。本书第二版保留了第一版通俗易懂的写作风格:按Struts 2.1的架构体系,细致地介绍了Struts 2.1各个知识点。在介绍过程中,作者依照读者的学习...
他们在编撰本书时,参考了国内外的多种教材和科研成果,力求使本书内容与国际教材风格保持一致,同时又通俗易懂。 教材编写的过程中,多位来自不同高校的专家学者参与了审稿和讨论,提出了大量宝贵的意见,以确保...
### 最通俗的多播技术详解——交换机组播技术学习手册 #### 一、多播技术概览 随着数据通信技术的飞速发展,基于互联网的新业务不断涌现,如视频点播、远程教育、网络电视等,这些新型业务的共同特点是需要从单一...
### JavaScript入门教程知识点详解 #### 一、JavaScript简介与学习理由 **JavaScript**是一种轻量级的编程语言,因其强大的兼容性和简易性成为了前端开发人员的首选。它由Netscape公司在1995年首次推出,起初被...
本文旨在为初学者提供一个简单易懂的入门指南,帮助大家快速掌握卡片机的基本操作方法。 #### 二、图像质量和胶片模式设置 - **图像质量**:在拍摄前,应该设置图像的质量等级。通常情况下,可以选择“中等”或...
在JavaScript的世界里,函数是第一公民,闭包、原型链、异步编程(如回调函数、Promise、async/await)等核心概念的理解至关重要。源码可能包含这些内容的实例,帮助我们更好地掌握它们。此外,DOM操作、事件处理、...
Photoshop_CS6经典教程——由浅入深_通俗易懂.
"狂神说——HTML完整教学通俗易懂"这个教程旨在为初学者提供一个全面且易于理解的HTML学习资源,帮助他们掌握网页设计的基础。 1. HTML基本结构:HTML文档通常由<!DOCTYPE>声明开始,定义了文档类型。接着是标签,...
本教程“狂神说——CSS3最新教程快速入门通俗易懂”旨在帮助初学者快速掌握CSS3的核心概念和实际应用,通过实例解析,使学习过程更为直观易懂。 一、选择器增强 1. 类选择器:CSS3扩展了类选择器的用法,如`....
项目分类:[编程语言] [学习社区] ...推荐理由:一份通俗易懂、风趣幽默的Java学习指南。内容涵盖Java基础、Java并发编程、Java虚拟机、Java企业级开发、Java面试等核心知识点,旨在帮助学习者更好地掌握Java编程。
这本书通俗易懂,非常适合0基础入门,不仅教会你知识,还教会你学习的方法,语言非常的亲切,就像一个智者在言传身教一样。本电子书是PDF高清版,看着很舒服。
JavaScript是一种广泛应用于Web开发的脚本语言,尤其适合初学者学习。它的主要优点在于良好的浏览器兼容性,几乎所有的现代浏览器都支持JavaScript,使得开发者能够轻松创建动态和交互性的网页内容。JavaScript并非...