JavaScript启示录

978-7-115-33494-7
作者: 【美】Cody Lindley
译者: 徐涛
编辑: 陈冀康

图书目录:

详情

本书帮助JavaScript的中级开发者来加深和巩固对JavaScript的理解,特别是,如果读者有过使用JavaScript库的经验的话,将会受益匪浅。本书有针对性地介绍JavaScript的内部情况,帮助读者更深刻理解其核心,从而对库能够更加的得心应手地使用,甚至开发属于自己的第三方库。

图书摘要

版权信息

书名:JavaScript启示录

ISBN:978-7-115-33494-7

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著     [美] Cody Lindley

  译    徐 涛

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright© 2013 by Q’Reilly Media, Inc. Simplified Chinese Edition、jointly published by Q’Reilly Media, Inc. and Posts  Telecom Press, 2014. Authorized translation of the English edition, 2013 Q’Reilly Media, Inc., the owner of all rights to publish and sell the same. All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体版由Q’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。 版权所有,侵权必究。


JavaScript是Web开发人员必须掌握的一门编程语言,但JavaScript语言及其相关技术正在变得越来越复杂。如何掌握JavaScript的基本概念和核心技术,往往让初学者和JavaScript新手感到束手无策。

本书力图在有限的篇幅内,通过考察原生JavaScript对象和所支持的细微差别,来给读者展现准确的JavaScript世界观,涉及对象、属性、复杂值、原始值、作用域、继承、this关键字、head对象等重要概念。本书帮助读者厘清这些概念,进而掌握应用它们的技术和技巧。

本书适合希望通过深入了解JavaScript对象来巩固对语言理解的高级初学者或中级JavaScript开发人员阅读,也适合准备研究JavaScript幕后知识的JavaScript库使用老手参考。


Michael Richardson

Michael Richardson是一名Web和应用开发人员,居住在爱达荷州博伊西。很久以前,他在沙拉劳伦斯学院获得了创意写作的艺术硕士学位,并于2003年发表了名为《蘑菇电台计划(Plans for a Mushroom Radio)》的小说。最近,他在与他的妻子和孩子共度美好时光之余,还管理着自己的小型Web应用程序Timeglider(http://timeglider.com)。

Kyle Simpson

Kyle Simpson是一位来自得克萨斯州奥斯汀的JavaScript系统架构师。他主攻JavaScript、Web性能优化和“中端”应用程序架构。如果使用JavaScript或Web堆栈技术解决不了某个问题,他可能就会感到不耐烦了。他负责多个开源项目,包括LABjs、HandlebarJS(http://handlebarjs.com)和BikechainJS(http://bikechainjs.com)。Kyle是Mozilla开发工具团队里的一名软件工程师。

Nathan Smith

Nathan Smithhttp://sonspring.com)是在惠普工作的UX开发人员。他拥有亚斯毕理神学院的神学硕士学位。他在20世纪晚期开始建立网站,爱好手工编写HTML、CSS和JavaScript代码。他创建了960 Grid System(http://960.gs/),这是一种用于描绘、设计和编写页面布局的设计和CSS框架。他还创建了Formalize(http://formalize.com),一种使表格式样清晰的JavaScript和CSS框架。

Ben Nadel

Ben Nadel是Epicenter Consulting的首席软件工程师,Epicenter Consulting是一家位于曼哈顿的Web应用开发公司,专门从事创新定制软件,帮助客户转变其业务方式。他也是Adobe社区专家以及高级ColdFusion的Adobe认证专家。在业余的时间,他在www.bennadel.com上撰写关于Web应用开发等方面的博客。

Ryan Florence

Ryan Florencehttp://ryanflorence.com)是来自犹他州盐湖城的前端Web开发人员,自20世纪90年代早期就一直在建立网站。他尤其感兴趣的是将自己的体验提供给终端用户和延续项目的开发人员。Ryan时常活跃在JavaScript社区,编写插件,帮助创建流行的JavaScript库,在各种会议和聚会上发表演讲,并将经验写在网上。他目前在Clock Four担任高级技术顾问。

Nathan Logan

Nathan Loganhttp://nathanlogan.com)是一名专业的Web开发人员,有8年的开发经验。他主攻客户端技术,但也涉及服务器端技术。他目前就职于Memolane,与本书作者是同事。Nathan有很贤惠的妻子和可爱的儿子,爱好骑山地自行车、泡温泉、吃辛辣食物,他是苏格兰人,信仰基督教。


Cody Lindley是一名客户端工程师(也称为前端开发人员)及Flash开发者。他在HTML、CSS、JavaScript、Flash、客户端性能技术方面有丰富的工作经验(11年以上),这些技术都与Web开发有关。如果他不在编写客户端代码,就很可能是在研究界面/交互设计,或是在准备创作材料以及在各种会议上要用的演讲稿。当他不坐在电脑前时,他肯定正和他的妻子和孩子在爱达荷州的博伊西游玩,他们在博伊西可以进行三项全能运动训练、滑雪、骑山地自行车和公路自行车、登山、阅读、看电影或者讨论基督教世界观的理论依据。


徐涛(网名:汤姆大叔;微博:@TomXuTao),微软最有价值专家(MVP)、项目经理、软件架构师,擅长大型互联网产品的架构与设计,崇尚敏捷开发模式,熟悉设计模式、前端技术、以及各种开源产品,曾获MCP、MCSE、MCDBA、MCTS、MCITP、MCPD、PMP认证。《JavaScript编程精解》译者,博客地址:tp://www.cnblogs.com/tomxu


本书无关于JavaScript设计模式,也无关于JavaScript面向对象代码实现。本书的写作目的也不是鉴别JavaScript语言特点的好坏。本书并不是一本完整的参考指南。它面向的读者人群并不是编程新手或对JavaScript完全陌生的人员。同时,它也不是一本JavaScript攻略手册。关于上述这些方面的书籍都已经面世。

本书的撰写意图是通过考察原生JavaScript对象和不同环境对原生对象的支持的细微差别,来给读者展现准确的JavaScript世界观:复杂值、原始值、作用域、继承、head对象等。我希望本书是关于ECMAScript第三版规范的简单易懂的总结,重点介绍JavaScript中对象的特性。

如果你是只使用过JavaScript库(如jQuery、MooTools、Zepto、YUI、Dojo等)的设计师或开发人员,我希望本书中的资料能够使你从JavaScript库用户转变成为JavaScript开发人员。

首先,我必须承认,写这本书是为了我自己。说实话,精心编制这些资料,这样我就可以品尝自己制作的“饮品”,并始终记得它的“味道”。换句话说,我想用自己的语言来编写参考书籍,以便在需要时用来唤起我的记忆。另外:

本书面向两种人群。第一种是希望通过深入了解JavaScript对象来巩固对语言理解的高级初学者或中级JavaScript开发人员。第二种是准备研究JavaScript幕后知识的JavaScript库使用老手。本书不适合编程新手、JavaScript库使用新手以及JavaScript开发新手。

在本书中,我重点介绍的是JavaScript 1.5版本(相当于ECMAScript第3版),因为它是到目前为止实现最为广泛的JavaScript版本。本书的下一版肯定会转向日渐重要的ES6和ES5版本。

正如前面所说的,本书不是关于JavaScript的详尽参考指南,相反,本书致力于将对象作为了解JavaScript的透镜。因此,我决定在书中不涉及Date()、Error()和RegEx()对象,尽管这些对象都很有用,但是掌握这些对象的细节内容对JavaScript对象的总体理解不会产生很大的影响。我希望大家能够将在这里所学到的知识应用到JavaScript环境中可用的所有对象中。

在开始学习本书之前,了解本书所采用的各种模式是非常重要的。请不要跳过本节,因为它包含一些有助于阅读本书的重要信息。

请仔细检查代码示例。应将文本视为仅次于代码本身的内容。我认为一个代码示例胜过一大堆文字。不要担心刚开始对一些解释感到困惑。检查代码,研究代码,重读一遍代码注释,重复这个过程,直到清晰了解所解释的概念。你最好有一定的专业知识,这样你只需要有良好文档记录的代码就可以深刻理解编程概念。

你可能会讨厌我老是重复,以及使用了如此全面的代码示例。我可能会受到埋怨,但我宁可要精确、详细和反复,而不像有的作者常常把错误假设提供给读者。是的,不管将要引入的知识是什么,详细和反复都有可能令人厌烦,但它们对于那些想详细学习某一主题的人来说仍是很有用的。

在JavaScript代码示例中(如下例所示),粗体用于突出显示与所讨论概念直接相关的代码。任何用来支持粗体代码的其他代码都将用正常文本表示。

<!DOCTYPE html><html lang="en"><body><script>

// this is a comment about a specific part of the code
var foo = 'calling out this part of the code';

</script></body></html>

本书中的大多数代码示例都有相应的jsFiddle页面链接(http://jsfiddle.net),可以调整和在线执行代码。jsFiddle示例已经配置使用了Firebug lite-dev插件(http://fbuggooglecode.com/svn/lite/branches/firebug1.3/content/firebug-lite-dev.js),以便无论浏览器是否有自己的控制台,日志功能(即console.log)都能够在大多数现代浏览器中工作。在阅读本书之前,要确保自己可以接受console.log的用法和目的。

如果使用jsFiddle和Firebug lite-dev导致JavaScript代码复杂,则会将JS Bin(http://js-bin.tumblr.com/about)和Firebug lite-dev组合使用。我试图通过使用Firebug lite-dev来避免产生对浏览器控制台的依赖,但该解决方案本身的相关代码示例会阻碍代码执行。在这些情况下,就不得不利用构建到Web浏览器中的控制台来输出日志。如果不想使用有内置JavaScript控制台的浏览器,我建议升级或切换浏览器(http://brousehappy.com)。

使用JS Bin时,要记住的是,必须要手动执行代码(单击Render),因为这不同于由jsFiddle执行的页面加载。

本书使用下列排版约定(参照编码约定(P3)):

斜体(Italic)

表示第一次出现的专业词汇、链接(URL)、电子邮件地址、文件名和文件扩展名。

等宽字体(Constant width)

表示广义上的程序清单,包括变量或函数名、数据库、数据类型、环境变量、语句和关键字。

等宽粗体(Constant width bold)

表示应该由用户逐字输入的命令或其他文本。

等宽斜体(Constant width italic)

表示应该由用户提供的值或取决于上下文的值。

这个图标表示提示、建议或一般说明。

这个图标表示警告或提醒。

这本书是为了帮助你做好工作。一般来说,可以在你的程序和文档中使用本书的代码,无须联系我们获取许可。例如,使用本书中的几段代码写一个程序是不需要许可的;出售和散布O’Reilly书中用例的光盘(CD-ROM)是需要许可的;通过引用本书用例和代码来回答问题是不需要许可的;在你的产品文档中引用本书中大量的用例代码是需要许可的。

我们赞赏但不强求注明信息来源。一条信息来源通常包括标题、作者、出版者和国际标准书号(ISBN),例如,“JavaScript Enlightenment by Cody Lindley (O’Reilly). Copyright 2013 Cody Lindley, 978-1-449-34288-3”。

如果你感到对示例代码的使用超出了正当引用或这里给出的许可范围,请随时通过permissions@oreilly.com联系我们。

Safari在线图书(Safari Books Online:www.safaribooksonline.com)是一家按需服务的数字图书馆,提供来自世界领先作者的技术类和商业类专业参考书目和视频。

专业技术人员、软件开发人员、Web设计师、商业和创意专家将Safari 在线图书作为他们研究、解决问题、学习和认证培训的主要资源。

Safari 在线图书为组织、政府机构和个人提供一系列的产品组合和定价计划。用户可以在一个来自各个出版社的完全可搜索的数据库中访问成千上万的书籍、培训视频和正式出版前的手稿。这些出版社包括:O’Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、微软出版社、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology等。欲获得有关Safari 在线图书的更多信息,请在线访问我们。

如果您对本书有意见和问题,请联系出版社:

美国:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京西城区西直门南工街2号

成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们已将本书做成一个网页,我们在那里列出了勘误表、示例及其他信息。您可以打开网址http://oreil.ly/javascript_enlightenment访问这个页面。

如需对本书发布评论或提出技术问题,请发送电子邮件至bookquestions@oreilly.com。

欲获知有关我们的图书、课程、会议及新闻的更多信息,请访问我们的网站http://www.oreilly.com

Facebook网址:http://facebook.com/oreilly

Twitter网址:http://twitter.com/oreillymedia

YouTube网址:http://www.youtube.com/oreillymedia


在JavaScript中,对象为“王”:JavaScript里的几乎所有东西都是对象或者用起来像对象。理解了对象,就能够理解JavaScript。因此,让我们来查看一下JavaScript中的对象创建。

对象只是一组有命名值(也称为属性)集合的容器。在阅读JavaScript代码之前,让我们先来推理一下。以我自己为例,我们可以用简单的语言在表格中表达“cody”:

cody
property: property value:
living true
age 33
gender male

上述表格中的“cody”一词只是一组属性名和所对应值的标签,这些属性和值构成了cody。正如表格中所看到的:我还活着,33岁,男性。

然而,JavaScript不会用表格来表达,它是用对象来表达的,就像“cody”表格中包含的那样。将上述表格转化成实际的JavaScript对象应是这样的:

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/ckVA5/

<!DOCTYPE html><html lang="en"><body><script>

// 创建cody对象
var cody = new Object();

// 为cody对象的各种属性赋值(使用点表示法)
cody.living = true;
cody.age = 33;
cody.gender = 'male';

console.log(cody); // 输出Object {living = true, age = 33, gender = 'male'}

</script></body></html>

最重要的是要记住:对象只是属性的容器,每个属性都有一个名称和一个值。JavaScript采用具有命名值属性的容器(即对象)这一概念作为在JavaScript中表达值的构建块。cody对象是一个值,通过创建对象将这个值表示为JavaScript对象,给对象命名,然后将属性赋值给对象。

到目前为止,我们所讨论的cody对象只有静态信息。鉴于正在研究JavaScript编程语言,我们希望为cody对象编写代码,做一些真正有意义的事情。不然,也就只有一个类似JSON(http://www.json.org/)的数据库。为了使cody对象有生命力,需要添加一个属性:方法(method)。方法用于执行函数(function)。确切地讲(http://bclary.com/2004/11/07/%23a-4.3.3),在JavaScript中,方法是包含Function()对象的属性,其目的是对函数内部的对象进行操作。

更新cody表格,加入getGender方法,用浅显的英语表示如下:

cody
property: property value:
living true
age 33
gender male
getGender return the value of gender

用JavaScript代码表示,更新后的上述“cody”表格中的getGender方法应是这样的:

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/3gBT4/

<!DOCTYPE html><html lang="en"><body><script>

var cody = new Object();
cody.living = true;
cody.age = 33;
cody.gender = 'male';
cody.getGender = function(){ return cody.gender;};

console.log(cody.getGender()); // 输出 'male'

</script></body></html>

getGender方法是cody对象的属性,用于返回cody的其他属性值:存储在gender属性中的"male"值。必须知道的是,如果没有方法,除了用于存储静态属性以外,对象就没有其他太多用途。

截至目前,我们讨论的cody对象是一种Object()对象。通过使用调用Object()构造函数而得到的空对象创建了cody对象。将构造函数视为模板或饼干模具来生成预定义对象。对于cody对象示例,使用Object()构造函数来生成空对象,然后将它命名为cody。鉴于cody是由Object()构造函数构造出的一个对象,所以将cody称为Object()对象。大家真正需要了解的是,除了创建像cody这样简单的Object()对象外,JavaScript中的大多数值都是对象("foo"、5和true等原始值例外,但它们拥有等效包装器对象)。

由Object()构造函数创建的cody对象与通过string()构造函数创建的字符串对象没有太大的区别。为了证明其真实性,可检验和对比下面的代码:

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/XcfC5/

<!DOCTYPE html><html lang="en"><body><script>
var myObject = new Object(); // 生成一个Object()对象
myObject['0'] = 'f';
myObject['1'] = 'o';
myObject['2'] = 'o';

console.log(myObject); // 输出Object { 0="f", 1="o", 2="o"}

var myString = new String('foo'); // 生成一个String()对象

console.log(myString); // 输出foo { 0="f", 1="o", 2="o"}

</script></body></html>

正如这里所显示的,myObject和myString都是对象!它们都可以有属性、继承属性,并且都由构造函数生成。包含"foo"字符串值的myString变量看似很简单,但令人惊讶的是,在其表面之下有一个对象结构。查看生成的这两个对象,你会发现这两个对象虽然在本质上是相同的,但在类型上是不同的。更重要的是,希望你开始了解,JavaScript是使用对象来表示值的。


注意

看到字符串值采用"foo"对象形式,你可能会觉得很奇怪,因为通常字符串在JavaScript中被表示为原始值(如var myString = 'foo';)。我在这里特地用一个字符串对象值来强调所有的东西都可以是对象,包括通常不将其视为对象的值(即字符串、数字、布尔值)。同时,我认为这有助于解释为什么有些人说JavaScript中的所有东西都可以是一个对象。

JavaScript将String()和Object()构造函数作为其自身的语言,以便使String()对象和Object()对象的创建变得简单。但是作为JavaScript程序员,也可以创建同样强大的构造函数。如下代码,定义了一个非原生的自定义Person()构造函数,以便用它创建people对象。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/zQDSw/

<!DOCTYPE html><html lang="en"><body><script>

// 定义Person构造函数,以便稍后创建自定义的Person()对象
var Person = function (living, age, gender) {
 this.living = living;
 this.age = age;
 this.gender = gender;
 this.getGender = function () { return this.gender; };
};

// 实例化Person对象,并将它保存到cody变量
var cody = new Person(true, 33, 'male');

console.log(cody);

/* 下面的String()构造函数由JavaScript自身所定义,有同样的模式。因为string构造函数是JavaScript内置的,获取字符串所要做的就是将它实例化,但无论是用原生的String()还是用户自定义的构造函数Person(),模式都是一样的*/

// 实例化一个String对象,并保存到myString变量
var myString = new String('foo');

console.log(myString);

</script></body></html>

用户自定义的Person()构造函数可以生成person对象,就像原生String()构造函数可以生成字符串对象一样。Person()构造函数的能力和延展性并不比原生String()构造函数或JavaScript中的其他原生构造函数差。

还记得我们开始看到的cody对象是如何从Object()生成的吗?重要的是要注意,在上个代码示例中的Object()构造函数和new Person()构造函数可以产生相同的结果。两者都可以生成具有相同属性和属性方法的相同对象。查看下面两段代码,结果表明,尽管生成方式不同,codyA和codyB却拥有相同的对象值。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/Du5YV/

<!DOCTYPE html><html lang="en"><body><script>

// 使用Object()构造函数创建codyA对象

var codyA = new Object();
codyA.living = true;
codyA.age = 33;
codyA.gender = 'male';
codyA.getGender = function () { return codyA.gender; };

console.log(codyA); // 输出Object {living=true, age=33, gender="male", ...}

/*  下面创建了同样的cody对象,但不是使用原生的Object()构造函数创建一次性的cody对象,首先定义自己的Person()构造函数来创建cody对象(或者其他任意Person对象),然后使用”new”关键字进行实例化 */

var Person = function (living, age, gender) {
    this.living = living;
    this.age = age;
    this.gender = gender;
    this.getGender = function () { return this.gender; };
};
// 输出Object {living=true, age=33, gender="male", ...}
var codyB = new Person(true, 33, 'male');

console.log(codyB); 

</script></body></html>

codyA和codyB对象之间的主要区别不在于对象本身,而是在于生成对象的构造函数。codyA对象是使用Object()构造函数实例来产生的。Person()构造函数创建了codyB,但也可以把Person()构造函数当成一个强大的、集中定义的对象“工厂”,用于创建更多的Person()对象。为自定义对象创建自定义构造函数的同时,也为Person()实例创建了原型继承。

这两个方案都能够创建相同的复杂对象。这两个模式最常用于构造对象。

JavaScript实际上是一种预包装若干原生对象构造函数的语言。这些构造函数用于生成一些表达特定类型值(如数字、字符串、函数、对象、数组等)的复杂对象,同样,也可以通过Function()对象创建自定义的对象构造函数(例如Person())。不管是否是用于创建对象的模式,产生的最终结果通常都是创建一个复杂的对象。

理解对象的创建、本质、用法以及其原始等价代码是本书其余部分的重点。

构造函数的作用是创建多个共享特定特性和行为的对象。构造函数主要是一种用于生成对象的饼干模具,这些对象具有默认属性和属性方法。

如果说“构造函数只是一个函数”,那么我会说“你是对的,除非使用new关键字来调用该函数。”(如new String('foo'))。如果使用new调用某函数,该函数则担任一个特殊的角色,JavaScript给予该函数特殊待遇,将该函数的this值设置为正在构建的新对象。除了这个特殊行为,该函数还默认返回新创建的对象(即this),而不是虚假值。该函数返回的新对象则被认为是构建该对象的构造函数的实例。

再次思考Person()构造函数,但这一次要仔细阅读下面代码中的注释,因为其内容强调了new关键字的作用。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/YPR6Q/

<!DOCTYPE html><html lang="en"><body><script>

// Person是一个构造函数,可以使用new关键字进行实例化 

var Person = function Person(living, age, gender) {
    // 下面的this表示即将创建的新对象(即,this = new Object();)
    this.living = living;
    this.age = age;
    this.gender = gender;
    this.getGender = function () { return this.gender; };
    // 一旦该Person函数使用new关键字调用,就返回this,而不是undefined
};

// 实例化Person对象,命名为cody
var cody = new Person(true, 33, 'male');

// cody是一个对象,并且是Person()的一个实例
console.log(typeof cody); // 输出object
console.log(cody); // 输出cody内部的属性和值
console.log(cody.constructor); // 输出Person() 函数

</script></body></html>

上述代码利用了自定义构造函数(即Person())来创建cody对象。这与Array()构造函数创建Array()对象(如new Array())没有什么不同:

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/cKa3a/

<!DOCTYPE html><html lang="en"><body><script>

// 实例化Array对象,命名为myArray
var myArray = new Array(); // myArray是Array的一个实例

// myArray是一个对象,并且是Array()构造函数的一个实例
console.log(typeof myArray); // 输出object! 什么? 是的,数组是object类型

console.log(myArray); // 输出[ ]

console.log(myArray.constructor); // 输出Array()

</script></body></html>

在JavaScript中,大多数值(不包括原始值)都涉及正在被创建的对象,或者是从构造函数实例化的对象。构造函数返回的对象被称为实例。读者要熟悉这些语义,同样要熟悉利用构造函数来构建对象的模式。

JavaScript语言包含9个原生(或内置)对象构造函数。JavaScript使用这些对象来构建JavaScript语言。“构建”的意思是指,这些对象是用于表达JavaScript代码中的对象值,以及协调语言中的多个特性。因此,原生对象构造函数是多方面的,它们生成对象,但也被用于促进语言的编程约定的形成。例如,函数是Function()构造函数创建的对象,但作为构造函数,使用new关键字调用后,它们也可用于创建其他对象。

下面列出了JavaScript预包装的9个原生对象构造函数:

JavaScript主要是由这9个对象(以及字符串、数字和布尔原始值)来创建的。深入理解这些对象,对充分利用JavaScript独特的编程力量和语言灵活性是非常关键的。


注意

  • Math对象在这里是很古怪的。它是一个静态对象,而不是构造函数,也就是说,我们不能这么做:var x = new Math()。但我们可以使用它,就好像它已经实例化好了一样(如Math.PI)。实际上,Math只是一个由JavaScript设置的对象命名空间,用于存储数学函数。
  • 原生对象有时也被称为“全局对象”,因为它们是JavaScript中原生就可以使用的对象。不要混淆的术语是拥有"head"全局对象的global object(全局对象),它是作用域链中的最高层级。例如,所有Web浏览器中都有的window对象。
  • Number()、String()和Boolean()构造函数不仅能够构建对象,而且能为字符串、数字和布尔值提供原始值,这取决于如何充分利用构造函数。如果直接调用这些构造函数,那么就会返回一个复杂对象。如果只是简单地在代码中表示一个数字、字符串或布尔值(5、“foo”和true等原始值),那么构造函数将返回一个原始值,而不是一个复杂的对象值。

正如在Person()构造函数中所看到的,我们可以创建自己的构造函数,从中可以生成不只一个,而是多个自定义对象。

下面列出了熟悉的Person()构造函数:

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/GLMr8/

<!DOCTYPE html><html lang="en"><body><script>
var Person = function (living, age, gender) {
 this.living = living;
 this.age = age;
 this.gender = gender;
 this.getGender = function () { return this.gender; };
};

var cody = new Person(true, 33, 'male');
console.log(cody); // 输出Object {living=true, age=33, gender="male", ...}

var lisa = new Person(true, 34, 'female');
console.log(lisa); // 输出Object {living=true, age=34, gender="female", ...}

</script></body></html>

正如你所看到的,通过传递特定的参数和调用Person()构造函数,可以轻松地创建大量的特定people对象。当需要两个或三个以上具有相同属性但具有不同值的对象时,这样做是非常有用的。细想一下,这正是JavaScript使用原生对象所发挥的作用。Person()构造函数与Array()构造函数一样也遵循同样的原则。因此new Array('foo','bar')与new Person(true, 33, 'male')确实没有多大差异。创建自定义构造函数只是在使用JavaScript本身在原生构造函数中所使用的相同的模式。


注意

  • 虽然这不是必需的,但当创建将要与new操作符一起使用的自定义构造函数时,最佳做法是保持构造函数名称的第一个字符大写:是Person(),而不是person()。
  • 关于构造函数比较复杂的一点就是this值在函数内部的使用方式。请记住,构造函数只是一个饼干模具,在将它与new关键字一起使用时,它会创建一个拥有构造函数内部定义的属性和值的对象。在使用new关键字时,this值的字面意思是基于构造函数内部的语句创建的新对象/新实例。另一方面,如果创建一个构造函数,且不使用new关键字进行调用,那么this值将引用包含该函数的“父”对象。更多细节内容请参考第6章。
  • 可以放弃使用new关键字和构造函数的概念,方法是使该函数显式地返回一个对象。必须显式地编写构建并返回Object()对象的函数:var myFunction = function(){return {prop: val}};。但是,这样做会避开原型继承的使用。

构造函数从根本上说是用于创建预配置对象的饼干模具模板。以String()为例,这个函数在与new操作符[new String('foo') ]一起使用时会创建基于String()模板的字符串实例。让我们来看一个示例。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/FKdsp/

<!DOCTYPE html><html lang="en"><body><script>

var myString = new String('foo');

console.log(myString); // 输出foo {0 = "f", 1 = "o", 2 = "o"}

</script></body></html>

上述代码创建了一个新的字符串对象,它是String()构造函数的一个实例。就像这样,我们在JavaScript中表达了一个字符串值。


注意

我并不是建议使用构造函数,而不使用等价方式:字面量/原始值,如var string="foo";。但我建议,要理解字面量/原始值背后的内容。

如前所述,JavaScript语言具有以下原生预定义的构造函数:Number()、String()、Boolean()、Object()、Array()、Function()、Date()、RegExp()和Error()。可以在任意一个构造函数上应用new操作符来实例化一个对象实例。如下代码,构建了这9类原生JavaScript对象。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/M9cWA/

<!DOCTYPE html><html lang="en"><body><script>

// 使用new关键字实例化每个原生构造函数

var myNumber = new Number(23);
var myString = new String('male');
var myBoolean = new Boolean(false);
var myObject = new Object();
var myArray = new Array('foo', 'bar');
var myFunction = new Function("x", "y", "return x*y");
var myDate = new Date();
var myRegExp = new RegExp('\bt[a-z]+\b');
var myError = new Error('Crap!');

// 输出/验证哪个构造函数创建了该对象
console.log(myNumber.constructor); // 输出Number()
console.log(myString.constructor); // 输出String()
console.log(myBoolean.constructor); // 输出Boolean()
console.log(myObject.constructor); // 输出Object()
console.log(myArray.constructor); // 在现在的浏览器中,输出Array()
console.log(myFunction.constructor); // 输出Function()
console.log(myDate.constructor); // 输出Date()
console.log(myRegExp.constructor); // 输出RegExp()
console.log(myError.constructor); // 输出Error()

</script></body></html>

通过使用new操作符,告诉JavaScript解释器,我们需要一个对应于构造函数实例的对象。例如,在上述代码中,Date()构造函数用于创建日期对象。Date()构造函数是日期对象的饼干模具。也就是说,它从Date()构造函数定义的默认模式中生成了日期对象。

至此,大家应该熟悉从原生构造函数[例如new String(‘foo’)]和用户自定义构造函数[例如new Person(true, 33, ‘male’)]创建对象实例的方法了。


注意

时刻记住,Math是一个静态对象——其他方法的容器,它不是使用new运算符的构造函数。

JavaScript提供了叫做“字面量”的快捷方式——用于创建大多数原生对象值,而不必使用new Foo()或new Bar()这样的方式。大多数情况下,字面量语法与使用new操作符的效果相同。但是也有例外:Number()、String()和Boolean(),请看下面的注释。

如果你有其他编程背景,可能更熟悉用字面量方式创建对象。下面我使用new操作符实例化原生JavaScript构造函数,然后创建相应的字面量等价形式。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/Nbkw4/

<!DOCTYPE html><html lang="en"><body><script>

var myNumber = new Number(23); // 对象
var myNumberLiteral = 23; // 原始数字值,而非对象

var myString = new String('male'); // 对象
var myStringLiteral = 'male'; //原始字符值,而非对象

var myBoolean = new Boolean(false); // 对象
var myBooleanLiteral = false; //原始布尔值,而非对象

var myObject = new Object();
var myObjectLiteral = {};

var myArray = new Array('foo', 'bar');
var myArrayLiteral = ['foo', 'bar'];

var myFunction = new Function("x", "y", "return x*y");
var myFunctionLiteral = function (x, y) { return x * y };

var myRegExp = new RegExp('\bt[a-z]+\b');
var myRegExpLiteral = /\bt[a-z]+\b/;

// 验证创建自同样的构造函数的字面量

console.log(myNumber.constructor, myNumberLiteral.constructor);
console.log(myString.constructor, myStringLiteral.constructor);
console.log(myBoolean.constructor, myBooleanLiteral.constructor);
console.log(myObject.constructor, myObjectLiteral.constructor);
console.log(myArray.constructor, myArrayLiteral.constructor);
console.log(myFunction.constructor, myFunctionLiteral.constructor);
console.log(myRegExp.constructor, myRegExpLiteral.constructor);

</script></body></html>

这里你需要注意的是,在一般情况下,使用字面量只是隐藏了与使用new操作符相同的基本过程。重要的是,它方便多了!

但在原始字符串、数字和布尔值方面,事情变得更复杂了。在这些情况下,字面量值具有原始值的特点,而不是复杂对象值的特点。请查看如下注意事项。


注意

在针对字符串、数字和布尔值使用字面量值时,只有在该值被视为对象的情况下才会创建实际的复杂对象。换句话说,在尝试使用与构造函数有关联的方法或检索属性(如var charactersInFoo = 'foo'.length)之前,一直在使用原始数据类型。当这种情况发生时,JavaScript会在幕后为字面量值创建一个包装器对象,以便将该值视为一个对象。调用方法以后,JavaScript即抛弃包装器对象,该值返回字面量类型。这就是字符串、数字和布尔被认为是原始(或简单)数据类型的原因。我希望这能够澄清对“JavaScript中的所有东西都是对象”与“JavaScript中所有东西都用成对象”这两个概念的误解。

5、“foo”、true、false,以及null和undefined等JavaScript值都被视为原始值,因为它们是不可细化的。也就是说,数字是数字,字符是字符,布尔值则是true或false,null和undefined就是null和undefined。这些值本身是很简单的,不能表示由其他值组成的值。

查看下面的代码,并思考一下字符串、数字、布尔值、null和undefined值是否可以更加复杂。将它与你所知道的Object()实例或Array()实例或其他任何复杂对象进行比较。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/xUQTC/

<!DOCTYPE html><html lang="en"><body><script>

var myString = 'string'
var myNumber = 10;
var myBoolean = false; // 可以是true,也可以是false
var myNull = null;
var myUndefined = undefined;

console.log(myString, myNumber, myBoolean, myNull, myUndefined);

// 假设一个像数组或对象这样的复杂对象可以由多个原始值组成,并由此变成一个复杂的多值集
var myObject = {
    myString: 'string',
    myNumber: 10,
    myBoolean: false,
    myNull: null,
    myUndefined: undefined
};

console.log(myObject);

var myArray = ['string', 10, false, null, undefined];

console.log(myArray);

</script></body></html>

很简单,原始值是表示JavaScript中可用的数据/信息的最底层形式(即最简单的形式)。


注意

  • 与使用字面量语法创建值相反,在使用new关键字创建String()、Number()或Boolean()值时,创建的对象实际上是一个复杂对象。
  • 了解String()、Number()和Boolean()构造函数是两种目的的构造函数,分别是用于创建字面量/原始值以及复杂值的,这是非常重要的。这些构造函数并不总是返回对象,相反,在不使用new操作符的情况下,它返回实际复杂对象值的原始表示。

null和undefined都是非常简单的值,它们不需要构造函数,也没有使用new操作符来将自己创建为JavaScript值。欲使用null或undefined,只需将它们看做操作符来使用即可。从技术上来讲,从构造函数返回的字符、数字、布尔值并不是对象。

下面对原始值和其他原生JavaScript对象之间的差异进行了比较。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/ZwgqD/

<!DOCTYPE html><html lang="en"><body><script>

// 使用原始值的时候,不会创建对象,注意没有使用new关键字
var primitiveString1 = "foo";
var primitiveString2 = String('foo');
var primitiveNumber1 = 10;
var primitiveNumber2 = Number('10');
var primitiveBoolean1 = true;
var primitiveBoolean2 = Boolean('true');

// 确认不是对象类型
console.log(typeof primitiveString1, typeof primitiveString2);
   // 输出 'string,string'
console.log(typeof primitiveNumber1, typeof primitiveNumber2);
   // 输出 'number,number'
console.log(typeof primitiveBoolean1, typeof primitiveBoolean2);
   // 输出 'boolean,boolean'

// 比较用于创建对象的构造函数和new关键字的用法

var myNumber = new Number(23);
var myString = new String('male');
var myBoolean = new Boolean(false);
var myObject = new Object();
var myArray = new Array('foo', 'bar');
var myFunction = new Function("x", "y", "return x * y");
var myDate = new Date();
var myRegExp = new RegExp('\\bt[a-z]+\\b');
var myError = new Error('Crap!');

// 输出 'object object object object object function object function object'
console.log(
typeof myNumber,
typeof myString,
typeof myBoolean,
typeof myObject,
typeof myArray,
typeof myFunction, // 注意所有的function对象返回的类型都是function 
typeof myDate,
typeof myRegExp, // 注意RegExp()的类型返回的是function
typeof myError
);

</script></body></html>

希望大家从前面的代码中可以掌握:原始值不是对象。原始值的特殊之处在于它们是用于表示简单值。

原始值在“面值(face value)”中的存储和操作,理解这一点相当重要。这可能听起来很简单,但是这意味着如果将字符串值“foo”存储在myString变量中,那么从字面上讲,“foo”值同样存储于内存中。为什么了解这个知识很重要?因为一旦开始操作(即复制)值,我们就必须了解它,因为原始值是真实值复制。

在下面的示例中,我们在变量myStringCopy中存储了myString值('foo')的一个副本,其值是真实值复制。即使我们改变原来的值,变量myStringCopy引用的复制值仍保持不变。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/Gh3dW/

<!DOCTYPE html><html lang="en"><body><script>

var myString = 'foo' //创建原始值字符对象
var myStringCopy = myString; //复制字符并存储到新变量上

var myString = null; //操作存储在myString变量中的值

/* 原来的值从myString复制到了myStringCopy上,更新myString以后,再检测myStringCopy的值即可证实这点*/

console.log(myString, myStringCopy); // 输出 'null foo'

</script></body></html>

这里的重点是,原始值是作为不可细化的值进行存储和操作的。引用它们会转移其值。在上面的示例中,我们复制或者克隆了myString值并存储到myStringCopy变量中。当更新myString值时,myStringCopy值仍是旧myString值的副本。请记住这点,并对这里的技术细节和复杂对象进行比较(将在下面讨论)。

可以通过比较原始值来确定其值在字面上是否相同。从逻辑上讲,如果将一个包含数值10的变量与另一个包含数值10的变量进行比较,JavaScript将会认为它们是相等的,因为10与10是相同的(即10===10)。同样,如果将原始字符串“foo”与另一个拥有‘foo’值的原始字符串进行比较也会采用同样的方法。比较的结果是,根据它们的值,它们是彼此相等的(即‘foo’===‘foo’)。

在下面的代码中,我使用原始数字演示了“值比较”的概念,并将它与复杂数字对象进行比较。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/NewQU/

<!DOCTYPE html><html lang="en"><body><script>

var price1 = 10;
var price2 = 10;
var price3 = new Number('10'); // 复杂数字对象,因为使用了new 
var price4 = price3;

console.log(price1 === price2); // 输出true

// 输出false,因为price3包含了一个复杂数字对象,而price1是原始值
console.log(price1 === price3);

// 输出true,因为复杂对象采用引用比较,而不是值比较
console.log(price4 === price3);

// 如果使price4的值变为原始值,那又如何呢?
price4 = 10;
console.log(price4 === price3); /* 输出false: price4此时是原始值,而非复杂对象 */

</script></body></html>

这里的重点是,在进行比较时,原始值会去检查表示的值是否相等。当使用new关键字创建字符串、数字或布尔值时[如new Number('10')],这个值就不再是原始值。同样地,如果通过字面量语法创建的值进行比较,结果就不同了。这并不奇怪,因为原始值是按值来存储的(10===10成立吗?),而复杂值是按引用来存储的(price3和price4包含对相同值的引用吗?)。

原始值被当作构造函数创建的一个对象来使用时,JavaScript会将其转换为一个对象,以便可以使用对象的特性(如方法),而后抛弃对象性质,并将它变回到原始值。下面的代码采用了原始值,并展示了将值作为对象使用时会发生什么事情。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/gSTNp/

<!DOCTYPE html><html lang="en"><body><script>

// 生成原始值
var myNull = null;
var myUndefined = undefined;
var primitiveString1 = "foo";
var primitiveString2 = String('foo'); // 没有使用new因此获得的是原始值
var primitiveNumber1 = 10;
var primitiveNumber2 = Number('10'); // 没有使用new,因此获得的是原始值
var primitiveBoolean1 = true;
var primitiveBoolean2 = Boolean('true'); // 没有使用new,因此获得的是原始值

/* 使用toString()方法(从object.prototype继承而来)来演示:当把原始值用作对象时,原始值就会转化成对象 */

// 输出 "string string"
console.log(primitiveString1.toString(), primitiveString2.toString());

// 输出 "number number"
console.log(primitiveNumber1.toString(), primitiveNumber2.toString());

// 输出 "boolean boolean"
console.log(primitiveBoolean1.toString(), primitiveBoolean2.toString());

// 因为null和undefined没有转化成对象,也没有构造函数,所以下面2条语句会抛出错误
console.log(myNull.toString());
console.log(myUndefined.toString());

</script></body></html>

上述代码示例中,所有的原始值(除null和undefined)都被转换为对象,以便充分利用toString()方法。一旦调用和返回该方法,对象就会转换成原始值。

原生对象构造函数Object()、Array()、Function()、Date()、Error()和RegExp()是复杂类型,因为它们可以包含一个或多个原始值或复杂值。本质上,复杂值可以由很多不同类型的JavaScript对象组成。可以这样说,复杂对象其在内存中的大小是未知的,因为复杂对象可以包含任何值,而不是一个特定的已知值。在如下代码中,我们创建了一个包含所有原始值的对象和一个包含所有原始值的数组。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/JeFqt/

<!DOCTYPE html><html lang="en"><body><script>
var object = {
    myString: 'string',
    myNumber: 10,
    myBoolean: false,
    myNull: null,
    myUndefined: undefined
};

var array = ['string', 10, false, null, undefined];

// 相比简单的原始值,原始值不能表示复杂值,而复杂值可以封装任意JavaScript值
var myString = 'string';
var myNumber = 10;
var myBoolean = false;
var myNull = null;
var myUndefined = undefined;

</script></body></html>

这里的重点是,复杂值是各种值的组合,并且在复杂性和组合方面与原始值不同。


注意

“复杂对象”这一术语在其他作品中也被表达为“复合对象”或“引用类型”。如果还不太清楚,可以思考这些名称,它们都描述了JavaScript值的特性,不包括原始值在内。原始值不是“值引用”,不能表示其他值的组合(即一个由几部分或几个元素组成的东西)。另一方面,复杂对象是“值引用”,可以包含或封装其他值。

复杂值是通过引用进行存储和操作的,理解这一点相当重要。创建一个包含复杂对象的变量时,其值是内存中的一个引用地址。引用一个复杂对象时,使用它的名称(即变量或对象属性)通过内存中的引用地址获取该对象值。当考虑试图复制一个复杂值时会发生什么事的时候,这就非常重要了。下面创建一个对象,并将它保存在变量myObject中,然后将myObject中的这个值复制到变量copyOfMyObject中。实际上,这并不是复制对象,更像是复制对象的地址。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/hypZC/

<!DOCTYPE html><html lang="en"><body><script>

var myObject = {};
var copyOfMyObject = myObject; //没有复制值,而是复制了引用

myObject.foo = 'bar'; // 操作myObject中的值

/* 现在如果输出myObject和copyOfMyObject,则均会输出foo属性,因为它们引用的是同一个对象*/
console.log(myObject, copyOfMyObject); /* 输出 'Object { foo="bar"}   
                                        Object { foo="bar"}' */

</script></body></html>

我们必须认识到的是,与复制值的原始值不同,对象(也称为复杂值)是通过引用进行存储的。因此,复制的是引用(也称为地址),而不是实际的值。这意味着根本就没有复制对象。就像我说过的,复制的是内存堆栈中对象的地址或引用。在代码示例中,myObject和copyOfMyObject都指向存储于内存中的同一个对象。

这里的重点是,当更改复杂值时,所有引用相同地址的变量的值都会被修改,因为复杂值是通过引用进行存储的。在上述代码示例中,更新myObject和copyOfMyObject中任何一个变量中的值,两者都会被更改。


注意

  • 使用new关键字创建String()、Number()和Boolean()值时,或者这些值在幕后被转换成复杂对象时,值依然是按照值进行存储/复制的。因此,即使原始值可以被看做是复杂值,它们也不具备通过引用进行复制的特性。
  • 若要真正地复制一个对象,必须要从旧的对象中提取值,并将提取的值注入新对象。

复杂对象只有在引用相同的对象(即有相同的地址)时才相等。包含相同对象的两个变量彼此不相等,因为它们并不指向同一个对象。

下面的示例中,objectFoo和objectBar有相同的属性,实际上也是完全相同的对象,但通过===询问它们是否相等时,JavaScript会告诉我们:它们是不相等的。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/g4CfS/

<!DOCTYPE html><html lang="en"><body><script>

var objectFoo = { same: 'same' };
var objectBar = { same: 'same' };

// 输出false, JS并不关注它们的类型是否相同
console.log(objectFoo === objectBar);

// 复杂对象如何才相等
var objectA = { foo: 'bar' };
var objectB = objectA;
console.log(objectA === objectB); // 输出true,因为它们引用的是同一个对象

</script></body></html>

这里的重点是,指向内存中复杂对象的变量,只有在引用相同“地址”的情况下才是相等的。相反,两个单独创建的对象,即使具有相同的类型并拥有完全相同的属性,它们也是不相等的。

一个新变量,指向现有的复杂对象,并没有复制该对象。这就是复杂对象有时被称为引用对象的原因。复杂对象可以根据需求有任意多个引用,即使对象改变,它们也总是指向同一个对象。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/SSsVC/

<!DOCTYPE html><html lang="en"><body><script>

var objA = { property: 'value' };
var pointer1 = objA;
var pointer2 = pointer1;

// 更新objA.property, 所有引用(pointer1和pointer2)都被更新了
objA.property = null;

// 输出 'null null null' ,因为objA, pointer1和pointer2引用的都是同一对象
console.log(objA.property, pointer1.property, pointer2.property);

</script></body></html>

复杂对象支持动态对象属性,因为我们可以定义对象,然后创建引用,再更新对象,并且所有指向该对象的变量都会“获得”更新。

typeof操作符用于返回正在使用值的类型。但它返回的值并不一致,或者说,逻辑上不一致。下面的代码展示了使用typeof操作符所返回的值。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/QM95R/

<!DOCTYPE html><html lang="en"><body><script>

// 原始值
var myNull = null;
var myUndefined = undefined;
var primitiveString1 = "string";
var primitiveString2 = String('string');
var primitiveNumber1 = 10;
var primitiveNumber2 = Number('10');
var primitiveBoolean1 = true;
var primitiveBoolean2 = Boolean('true');

console.log(typeof myNull); // 输出object,需要注意!!!
console.log(typeof myUndefined); // 输出undefined
console.log(typeof primitiveString1, typeof primitiveString2);
   // 输出string string
console.log(typeof primitiveNumber1, typeof primitiveNumber2);
   // 输出number number
console.log(typeof primitiveBoolean1, typeof primitiveBoolean2);
   // 输出boolean boolean

// 复杂值
var myNumber = new Number(23);
var myString = new String('male');
var myBoolean = new Boolean(false);
var myObject = new Object();
var myArray = new Array('foo', 'bar');
var myFunction = new Function("x", "y", "return x * y");
var myDate = new Date();
var myRegExp = new RegExp('\\bt[a-z]+\\b');
var myError = new Error('Crap!');

console.log(typeof myNumber); // 输出object
console.log(typeof myString); // 输出object
console.log(typeof myBoolean); // 输出object
console.log(typeof myObject); // 输出object
console.log(typeof myArray); // 输出object
console.log(typeof myFunction); // 输出function,需要注意!!!
console.log(typeof myDate); // 输出object
console.log(typeof myRegExp); // 输出function,需要注意!!!
console.log(typeof myError); // 输出object

</script></body></html>

在这些值上使用该操作符时,考虑到正在使用的值(原始值或复杂值)的类型,我们应该特别注意潜在的返回值。

复杂对象是由动态属性构成的。这使得用户自定义对象和大多数原生对象可以产生突变。这意味着JavaScript中的大多数对象都可以随时更新或更改。正因为如此,可以通过增加原生对象,来改变JavaScript本身的原生预配置特性。然而,我并不是建议大家这样做,事实上,我认为不应该这样做,但我们不能掩盖这些用法的有用之处。

也就是说,我们可以在原生构造函数上存储属性,并在原型对象上,向原生对象额外添加新方法。

如下代码,改变了String()构造函数和String.prototype。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/QvbDw/

<!DOCTYPE html><html lang="en"><body><script>

// 利用augmentedProperties属性扩展内置的String构造函数Function()
String.augmentedProperties = [];

if (!String.prototype.trimIT) { // 如果原型中没有trimIT(),就添加它
    String.prototype.trimIT = function () {
 return this.replace(/^\s+|\s+$/g, '');
    }
    // 添加trimIT字符到augmentedProperties数组
    String.augmentedProperties.push('trimIT');
}
var myString = ' trim me ';
console.log(myString.trimIT()); // 调用自定义的trimIT方法,输出 'trim me'

console.log(String.augmentedProperties.join()); // 输出 'trimIT'

</script></body></html>

希望大家能够明白,JavaScript中的对象是动态的。这使得JavaScript中的对象是可以改变的。从本质上讲,整个JavaScript语言都可以变为自定义版本(例如trimIT方法)。再次说明,我不推荐这种做法,我只是指出它是JavaScript中对象特性的一部分。


注意

如果更改了JavaScript的原生内部运作机制,你可能会获得一个自定义版本的JavaScript来进行程序处理。但一定要谨慎,因为大多数人会认为JavaScript不管在什么时候使用,都是相同的。

任何对象实例化时,都是在幕后将constructor属性创建为对象/实例的属性。这是指创建对象的构造函数。下面创建一个Object()对象,保存在变量foo中,然后验证constructor属性在创建的对象中是否可用。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/ZtewV/

<!DOCTYPE html><html lang="en"><body><script>

var foo = {};
console.log(foo.constructor === Object) /* 输出true, 因为object()构建了  
                                        foo */
console.log(foo.constructor) // 指向Object()构造函数

</script></body></html>

如下功能非常方便:如果正在使用一些实例,而无法看到是谁或者是什么创建了它(尤其是别人编写的代码),那么可以以此确定它是否是一个数组或一个对象等。

下面实例化JavaScript语言中的大多数预配置对象。请注意,在字面量/原始值上使用constructor属性能够指向正确的构造函数。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/yJqaF/

<!DOCTYPE html><html lang="en"><body><script>

var myNumber = new Number('23');
var myNumberL = 23; // 字面量方式
var myString = new String('male');
var myStringL = 'male'; // 字面量方式
var myBoolean = new Boolean('true');
var myBooleanL = true; // 字面量方式
var myObject = new Object();
var myObjectL = {}; // 字面量方式
var myArray = new Array();
var myArrayL = []; // 字面量方式
var myFunction = new Function();
var myFunctionL = function () { }; // 字面量方式
var myDate = new Date();
var myRegExp = new RegExp('/./');
var myRegExpL = /./; // 字面量方式
var myError = new Error();

console.log( // 所有这些都返回true
    myNumber.constructor === Number,
    myNumberL.constructor === Number,
    myString.constructor === String,
    myStringL.constructor === String,
    myBoolean.constructor === Boolean,
    myBooleanL.constructor === Boolean,
    myObject.constructor === Object,
    myObjectL.constructor === Object,
    myArray.constructor === Array,
    myArrayL.constructor === Array,
    myFunction.constructor === Function,
    myFunctionL.constructor === Function,
    myDate.constructor === Date,
    myRegExp.constructor === RegExp,
    myRegExpL.constructor === RegExp,
    myError.constructor === Error
);

</script></body></html>

constructor属性也适用于用户自定义的构造函数。如下代码中,我们定义了一个CustomConstructor()构造函数,然后使用new关键字调用构造函数来生成一个对象。一旦创建了对象,就可以使用constructor属性了。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/MDs2t/

<!DOCTYPE html><html lang="en"><body><script>

var CustomConstructor = function CustomConstructor() { return 'Wow!'; };
var instanceOfCustomObject = new CustomConstructor();

// 输出true
console.log(instanceOfCustomObject.constructor === CustomConstructor);

// 返回CustomConstructor()函数的一个引用
// 返回 'function() { return 'Wow!'; };'
console.log(instanceOfCustomObject.constructor);

</script></body></html>


注意

大家可能会感到困惑,当不返回对象时,原始值为何拥有指向构造函数的constructor属性。使用原始值的时候,依然调用了构造函数,因此原始值和构造函数依然有关系。然而,最终的结果是一个原始值。

  • 对用户自定义的构造函数表达式,如果想让constructor属性记录构造函数的实际名称,则必须给予构造函数表达式一个实际名称(如var Person = function Person(){};)。

通过使用instanceof操作符,可以确定(true或false)一个对象是否是特定构造函数的实例。

如下代码,要验证的是InstanceOfCustomObject对象是否是CustomConstructor构造函数的实例。它适用于用户自定义对象,同时也适用于用new操作符创建的原生对象。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/g9Tt6/

<!DOCTYPE html><html lang="en"><body><script>

// 用户自定义对象构造函数
var CustomConstructor = function () { this.foo = 'bar'; };

// 初始化CustomConstructor的实例
var instanceOfCustomObject = new CustomConstructor();
// 输出true
console.log(instanceOfCustomObject instanceof CustomConstructor); 

// 原生对象亦是如此
console.log(new Array('foo') instanceof Array) // 输出true

</script></body></html>


注意

  • 使用instanceof操作符时需要注意的一件事情是,任何时间判断一个对象是否是Object的实例时,它都将返回true,因为所有对象都继承自Object()构造函数。
  • 原始值使用对象包装器,判断实例时(如 'foo' instanceof String //返回false),instanceof操作符将返回false。如果使用new操作符创建字符串 'foo',instanceof操作符会返回true。所以,请记住,instanceof只适用于构造函数创建返回的复杂对象和实例。

在JavaScript中,对象在任何时候都可以扩展(即动态属性)。正如前面提到的,确切地说,JavaScript拥有易变对象(mutable object)。这意味着通过构造函数创建的对象可以扩展属性。

下面的代码通过Array()构造函数创建了一个实例,然后利用其属性进行扩展。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/RuQfJ/

<!DOCTYPE html><html lang="en"><body><script>

var myArray = new Array();
myArray.prop = 'test';
console.log(myArray.prop) // 输出 'test'

</script></body></html>

通过Object()、RegExp()或者任何其他非原生构造函数,甚至是Boolean(),都可以实现同一目的。

Fiddle地址:http://jsfiddle.net/javascriptenlightenment/GnbPf/

<!DOCTYPE html><html lang="en"><body><script>

// 原生构造函数创建的对象也可以实现同一目的
var myString = new String();
var myNumber = new Number();
var myBoolean = new Boolean(true);
var myObject = new Object();
var myArray = new Array();
var myFunction = new Function('return 2+2');
var myRegExp = new RegExp('\bt[a-z]+\b');

myString.prop = 'test';
myNumber.prop = 'test';
myBoolean.prop = 'test';
myObject.prop = 'test';
myArray.prop = 'test';
myFunction.prop = 'test';
myRegExp.prop = 'test';

// 输出 'test', 'test', 'test', 'test', 'test', 'test', 'test'
console.log(myString.prop, myNumber.prop, myBoolean.prop, myObject.prop,
   myArray.prop, myFunction.prop, myRegExp.prop);

// 注意:原始值/字面量不能实现该功能
var myString = 'string';
var myNumber = 1;
var myBoolean = true;

myString.prop = true;
myNumber.prop = true;
myBoolean.prop = true;

// 输出undefined, undefined, undefined
console.log(myString.prop, myNumber.prop, myBoolean.prop);

</script></body></html>

向通过构造函数创建的对象添加属性的情况并不少见。请记住:通过构造函数创建的对象实例只是普通对象。


注意

请记住,除了自己的属性外,实例还可以拥有继承自原型链的属性。或者,正如上述代码所示,实例化后属性被添加至构造函数,这强调了JavaScript中对象的动态特性。

不要将一般术语“JavaScript对象”与Object()对象混淆。前者指的是JavaScript中对象的概念。Object()对象(如var myObject = new Object())是JavaScript中表示的一个非常特殊的值。就像Array()对象是一种被称为array的对象类型,Object()对象是一种被称为object的对象类型。重点是,Object()构造函数生成空的通用对象容器,该容器叫做Object()对象。同样,Array()构造函数生成数组对象,该对象叫做Array()对象。

在本书中,术语“JavaScript对象”指的是JavaScript中的所有对象,因为JavaScript中的大部分值都可以像对象一样使用。这是由于大多数JavaScript值都是由能够生成特定类型对象的原生构造函数所创建的。

要记住的是,Object()对象是一种特定类型的值,它是一个通用的空对象。不要将其与术语“JavaScript对象”混淆,“JavaScript对象”用于表示JavaScript中可以作为对象的绝大多数值。


相关图书

深入浅出Spring Boot 3.x
深入浅出Spring Boot 3.x
JavaScript核心原理:规范、逻辑与设计
JavaScript核心原理:规范、逻辑与设计
JavaScript入门经典(第7版)
JavaScript入门经典(第7版)
JavaScript函数式编程指南
JavaScript函数式编程指南
PHP、MySQL和JavaScript入门经典(第6版)
PHP、MySQL和JavaScript入门经典(第6版)
JavaScript学习指南(第3版)
JavaScript学习指南(第3版)

相关文章

相关课程