JavaScript秘密花园 - Object, Prototype

http://bonsaiden.github.com/JavaScript-Garden/zh/#object.forinloop

简介(Intro)

JavaScript秘密花园是一个不断更新的文档,主要关心JavaScript一些古怪用法。 对于如何避免常见的错误,难以发现的问题,以及性能问题和不好的实践给出建议, 初学者可以籍此深入了解JavaScript的语言特性。

JavaScript秘密花园不是用来教你JavaScript。为了更好的理解这篇文章的内容, 你需要事先学习JavaScript的基础知识。在Mozilla开发者网络中有一系列非常棒的JavaScript学习向导

关于作者(The authors)

这篇文章的作者是两位Stack Overflow的用户, Ivo Wetzel(写作) 和 Zhang Yi Jiang (设计)。

贡献者(Contributors)

许可(License)

JavaScript花园在MIT license许可协议下发布,并存放在开源社区GitHub。 如果你发现错误或者打字错误,请file an issue或者pull request。 你也可以在Stack Overflow的聊天室JavaScript room找到我们。

中文翻译(Chinese Translation)

本中文翻译由三上石上原创,博客园首发,转载请注明出处。

对象(Objects) #top

JavaScript中所有变量都是对象,除了两个例外nullundefined

?
1
<code> false .toString() // 'false'<br>[1, 2, 3].toString(); // '1,2,3'<br><br>function Foo(){}<br>Foo.bar = 1;<br>Foo.bar; // 1<br></code>

一个常见的误解是数字的字面值(literal)不是对象。这是因为JavaScript解析器的一个错误, 它试图将点操作符解析为浮点数字面值的一部分。

?
1
<code>2.toString(); // 出错:SyntaxError<br></code>

有很多变通方法可以让数字的字面值看起来像对象。

?
1
<code>2..toString(); // 第二个点号可以正常解析<br>2 .toString(); // 注意点号前面的空格<br>(2).toString(); // 2先被计算<br></code>

对象作为数据类型(Objects as a data type)

JavaScript的对象可以作为哈希表使用,主要用来保存命名的键与值的对应关系。

使用对象的字面语法 - {} - 可以创建一个简单对象。这个新创建的对象从Object.prototype继承下面,没有任何自定义属性

?
1
<code> var foo = {}; // 一个空对象<br><br>// 一个新对象,拥有一个值为12的自定义属性'test'<br>var bar = {test: 12};<br></code>

访问属性(Accessing properties)

有两种方式来访问对象的属性,点操作符或者中括号操作符。

?
1
<code> var foo = {name: 'Kitten' }<br>foo.name; // kitten<br>foo['name']; // kitten<br><br>var get = 'name';<br>foo[get]; // kitten<br><br>foo.1234; // SyntaxError<br>foo['1234']; // works<br></code>

两种语法是等价的,但是中括号操作符在下面两种情况下依然有效 - 动态设置属性 - 属性名不是一个有效的变量名(译者注:比如属性名中包含空格,或者属性名是JS的关键词) (译者注:在JSLint语法检测工具中,点操作符是推荐做法)

删除属性(Deleting properties)

删除属性的唯一方法是使用delete操作符;设置属性为undefined或者null并不能真正的删除属性, 而仅仅是移除了属性和值的关联。

?
1
<code> var obj = {<br>    bar: 1,<br>    foo: 2,<br>    baz: 3<br>};<br>obj.bar = undefined;<br>obj.foo = null ;<br> delete obj.baz;<br><br> for ( var i in obj) {<br>    if (obj.hasOwnProperty(i)) {<br>        console.log(i, '' + obj[i]);<br>    }<br>}<br></code>

上面的输出结果有bar undefinedfoo null - 只有baz被真正的删除了,所以从输出结果中消失。

属性名的语法(Notation of keys)

?
1
<code> var test = {<br>    'case' : 'I am a keyword so I must be notated as a string' ,<br>    delete : 'I am a keyword too so me' // 出错:SyntaxError<br>};<br></code>

对象的属性名可以使用字符串或者普通字符声明。但是由于JavaScript解析器的另一个错误设计, 上面的第二种声明方式在ECMAScript 5之前会抛出SyntaxError的错误。

这个错误的原因是delete是JavaScript语言的一个关键词;因此为了在更低版本的JavaScript引擎下也能正常运行, 必须使用字符串字面值声明方式。

原型(The prototype) #top

JavaScript不包含传统的类继承模型,而是使用prototypical原型模型。

虽然这经常被当作是JavaScript的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。 实现传统的类继承模型是很简单,但是实现JavaScript中的原型继承则要困难的多。 (It is for example fairly trivial to build a classic model on top of it, while the other way around is a far more difficult task.)

由于JavaScript是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的。

第一个不同之处在于JavaScript使用原型链的继承方式。

注意: 简单的使用Bar.prototype = Foo.prototype将会导致两个对象共享相同的原型。 因此,改变任意一个对象的原型都会影响到另一个对象的原型,在大多数情况下这不是希望的结果。

?
1
<code> function Foo() {<br>    this .value = 42;<br>}<br>Foo.prototype = {<br>    method: function () {}<br>};<br><br> function Bar() {}<br><br> // 设置Bar的prototype属性为Foo的实例对象<br>Bar.prototype = new Foo();<br>Bar.prototype.foo = 'Hello World';<br><br>// 修正Bar.prototype.constructor为Bar本身<br>Bar.prototype.constructor = Bar;<br><br>var test = new Bar() // 创建Bar的一个新实例<br><br>// 原型链<br>test [Bar的实例]<br>    Bar.prototype [Foo的实例] <br>        { foo: 'Hello World' }<br>        Foo.prototype<br>            {method: ...};<br>            Object.prototype<br>                {toString: ... /* etc. */};<br><br></code>

上面的例子中,test对象从Bar.prototypeFoo.prototype继承下来;因此, 它能否访问Foo的原型方法method。但是它不能访问Foo的实例属性value, 因为这个属性在Foo构造函数中定义。 (But it will not have access to the property value of a Foo instance, since that property gets defined in the constructorof Foo. But this constructor has to be called explicitly.)

(译者注:我认为这个描述是错误的,test.value是可以访问的。 因为在设置Bar.prototype = new Foo();时,value也就成为Bar.prototype上的一个属性。 如果你有不同观点,可以到我的博客评论。)

注意: 不要使用Bar.prototype = Foo,因为这不会执行Foo的原型,而是指向函数Foo。 因此原型链将会回溯到Function.prototype而不是Foo.prototype,因此method将不会在Bar的原型链上。

属性查找(Property lookup)

当查找一个对象的属性时,JavaScript会向上遍历原型链,直到找到给定名称的属性为止。

到查找到达原型链的顶部 - 也就是Object.prototype - 但是仍然没有找到指定的属性,就会返回undefined

原型属性(The prototype property)

当原型属性用来创建原型链时,可以把任何类型的值赋给它(prototype)。 然而将原子类型赋给prototype的操作将会被忽略。

?
1
<code> function Foo() {}<br>Foo.prototype = 1; // no effect<br></code>

而将对象赋值给prototype,正如上面的例子所示,将会动态的创建原型链。

性能(Performance)

如果一个属性在原型链的上端,则对于查找时间将带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。

并且,当使用for-in循环遍历对象的属性时,原型链上的所有属性都将被访问。

扩展内置类型的原型(Extension of native prototypes)

一个错误特性被经常使用,那就是扩展Object.prototype或者其他内置类型的原型对象。

这种技术被称之为monkey patching并且会破坏封装。虽然它被广泛的应用到一些JS类库中比如Prototype,但是我仍然不认为为内置类型添加一些非标准的函数是个好主意。

扩展内置类型的唯一理由是为了和新的JavaScript保持一致,比如Array.forEach。 (译者注:这是编程领域常用的一种方式,称之为Backport,也就是将新的补丁添加到老版本中。)The only good reason for extending a built-in prototype is to backport  the features of newer JavaScript engines; for example, Array.forEach.

总结(In conclusion)

在写复杂的JavaScript应用之前,充分理解原型链继承的工作方式是每个JavaScript程序员必修的功课。 要提防原型链过长带来的性能问题,并知道如何通过缩短原型链来提高性能。 更进一步,绝对不要扩展内置类型的原型,除非是为了和新的JavaScript引擎兼容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值