第一章:this
是什么?
在学习使用this
之前我们首先要清楚两点:
1.this
既不是函数自身的引用,也不是函数词法作用域的引用。
2.this
其实是在函数调用时才建立的一个绑定,它的指向与函数声明的位置无关,而与函数调用的位置有关。
为什么要用this
总结:用this
可以简化代码,不用利用频繁传参的方式来实现调用。
两段代码对比:
|
|
|
|
虽然两段代码实现的效果是一样的,但是有this
的存在代码会更简洁。尤其是在使用模式更复杂的情况下,越能深刻的感受到这一点。所以这就是我们要学会使用this
的原因。
困惑
它自己
对于this
的第一种误解就是:它指向函数自己(我最开始就是这么认为的)。
用一个栗子来解释这一点:
|
|
这段代码的原意是想要追踪foo
被调用了多少次。但是,最后的foo.count
却没有发生变化,依然为0
。
其中,foo.count
向foo
添加了一个count
属性。但是,this.count
并没有指向这个添加的count
,即使属性名称相同,但它们的根对象是不同的。
这是我们可以用两种方式来解决这一问题:
第一种:
|
|
通过建立一个全局变量,让函数foo
改变全局变量中持有的count
。虽然解决了问题,但是它回避了this
。
第二种:
|
|
第三种:
|
|
但是上面三种方法都回避了this
。
它的作用域
对于this
的第二种误解就是:它指向函数的作用域。
参考下面的代码:
|
|
写下这段代码的开发者试图用 this
在 foo()
和 bar()
的词法作用域间建立一座桥,使得bar()
可以访问 foo()
内部作用域的变量 a
。这样的桥是不可能的。 你不能使用 this
引用在词法作用域中查找东西。这是不可能的。
每当你感觉自己正在试图使用 this
来进行词法作用域的查询时,提醒你自己:这里没有桥。
什么是this
?
this
不是编写时绑定,而是运行时绑定。它依赖于函数调用的上下文条件。this
绑定与函数声明的位置没有任何关系,而与函数被调用的方式紧密相连。
第二章:this
豁然开朗
调用点(Call-site)
调用点(call-site):函数在代码中被调用的位置(不是被声明的位置);
调用栈(call-stack):使我们到达当前执行位置而被调用的所有方法的堆栈。
关于调用点和调用栈的栗子:
|
|
可以通过console.trace()
在控制台中查看调用栈。
四种规则
下面介绍判断this
指向的四种判断规则:
默认绑定(Default Binding)
这是函数调用最常用的情况:独立函数调用。也可以认为这种this
规则是在没有其他规则适用时的默认规则。
当函数是 一个直白,毫无修饰的调用时,即默认绑定时,有两种情况:
1、foo
内容没在strict mode
下,this
指向全局变量。
2、foo
内容在strict mode
下,this
将被设置为undefined
。
|
|
此时的this
实施了默认绑定,使this
指向了全局变量。所以this.a
引用的是全局变量中的a
。
但是如果strict mode
在这里生效,name对于默认绑定来说是不合法的。此时的this
将被设置为undefined
。
|
|
一个微妙但是重要的细节是:即便所有的 this
绑定规则都是完全基于调用点的,但如果 foo()
的 内容 没有在 strict mode
下执行,对于 默认绑定 来说全局对象是 唯一 合法的;foo()
的调用点的 strict mode
状态与此无关。
|
|
隐含绑定(Implicit Binding)
举个栗子:
|
|
观察这段代码会发现,foo()
函数是在obj
的环境下调用的,所以它的调用点是在obj
对象这里。因此this
就很自然的绑定在obj
对象上。
在看一个栗子:
|
|
书上解释这一段代码的时候说:只有对象属性引用链的最后一层是影响调用点的。
其实,原理也很简单,因为foo()
函数是在obj2
环境下被调用的,所以它的调用点在obj2
这里。自然而然的this.a
就和obj2.a
等价了,我么判断这种隐含绑定的时候关注调用点就对了。
隐含丢失(Implicitly Lost)
当隐含绑定丢失时,这通常以为着this
会回退到默认绑定。然后根据strict mode
存在与否来判断,this
的指向是全局对象还是undefined
。
第一种:调用点的改变
|
|
因为函数bar()
真正的 调用点没有任何其他的修饰所以它的this
就为默认绑定。
第二种:传递一个回调函数时
|
|
其实就可以理解为:回调函数导致了函数调用点的变化。
|
|
将setTimeout
函数展开为下面形式,即js环境内建的实现,就很好理解了:
|
|
明确绑定(Explicit Binding)
从上述的隐含绑定中可以看出这种绑定都是隐性的,下面我们就要看到一个很直接的绑定方式。
其实前面我们就有接触了,call()
和apply()
就是明确绑定很好的实现方式。
它们接收的第一个参数都是一个用于 this
的对象,之后使用这个指定的 this
来调用函数。因为你已经直接指明你想让 this
是什么,所以我们称这种方式为 明确绑定(explicit binding)。
|
|
不幸的是,单独依靠 明确绑定 仍然不能为我们先前提到的问题提供解决方案,也就是函数“丢失”自己原本的 this
绑定,或者被第三方框架覆盖,等等问题。
硬绑定(Hard Binding)
明确绑定的一种变种:
|
|
new
绑定(new
Binding)
当在函数前面被加入 new
调用时,也就是构造器调用时,下面这些事情会自动完成:
- 一个全新的 对象会凭空创建(就是被构建)
- 这个新构建的对象会被接入原形链([[Prototype]]-linked)
- 这个新构建的对象被设置为函数调用的
this
绑定 - 除非函数返回一个它自己的其他 对象,否则这个被
new
调用的函数将 自动 返回这个新构建的对象。
注意:new构建的是一个全新的对象而不是一个函数,是将构造器中的this绑定到新的对象上。
|
|
通过在前面使用 new
来调用 foo(..)
,我们构建了一个新的对象并把这个新对象作为 foo(..)
调用的 this
。 new 是函数调用可以绑定 this 的最后一种方式,我们称之为 new 绑定(new binding)。
一切皆有顺序
比较隐含绑定和明确绑定哪一个优先?
|
|
明确绑定优先于隐含绑定
比较硬绑定与new
绑定的优先级
|
|
参考“山寨”绑定帮助函数
|
|
根据上面的代码我们会发现:new
绑定无法将硬绑定时的obj1
对象覆盖。所以硬绑定的优先级要大于new
绑定。
new
可以覆盖硬绑定一种情况:
|
|
判定this
函数是通过
new
被调用的吗(new 绑定)?如果是,this
就是新构建的对象。var bar = new foo()
函数是通过
call
或apply
被调用(明确绑定),甚至是隐藏在bind
硬绑定 之中吗?如果是,this
就是那个被明确指定的对象。var bar = foo.call( obj2 )
函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,
this
就是那个环境对象。var bar = obj1.foo()
否则,使用默认的
this
(默认绑定)。如果在strict mode
下,就是undefined
,否则是global
对象。var bar = foo()
以上,就是理解对于普通的函数调用来说的 this
绑定规则 所需的全部。是的……几乎是全部。
绑定的特例
主要介绍规则的一些例外。
被忽略的this
如果你传递 null
或 undefined
作为 call
、apply
或 bind
的 this
绑定参数,那么这些值会被忽略掉,取而代之的是 默认绑定 规则将适用于这个调用。
|
|
使用这种做法的原因:
- 使用
apply(...)
来展开一个数组,作为函数调用的参数; bind(...)
可以柯里化参数(增加预设值)
|
|
注意:ES6中已经有一个扩展操作符:...
,它可以让你无需使用 apply(..)
而在语法上将一个数组“散开”作为参数,比如 foo(...[1,2])
表示 foo(1,2)
。
可是,在你不关心 this
绑定而一直使用 null
的时候,有些潜在的“危险”。如果你这样处理一些函数调用(比如,不归你管控的第三方包),而且那些函数确实使用了 this
引用,那么 默认绑定 规则意味着它可能会不经意间引用(或者改变,更糟糕!)global
对象(在浏览器中是 window
)。
更安全的this
更安全的做法是创建一个‘DMZ’(非军事区)对象——只不过是一个完全为空。没有委托的对象。
|
|
间接
创建对函数的“间接引用”。
常见的间接引用产生方式是通过赋值:
|
|
赋值表达式 p.foo = o.foo
,a
的值到底为什么我们应该关心函数foo
的调用点, p.foo = o.foo
的作用是在对象p
中创建了一个foo:foo
,而真正调用foo
的是在全局下。所以,输出值理所为2
。
软化绑定
我们之前看到 硬绑定 是一种通过将函数强制绑定到特定的 this
上,来防止函数调用在不经意间退回到 默认绑定的策略(除非你用 new
去覆盖它!)。问题是,硬绑定 极大地降低了函数的灵活性,阻止我们手动使用 隐含绑定或后续的 明确绑定 来覆盖 this
。
如果有这样的办法就好了:为 默认绑定 提供不同的默认值(不是 global
或 undefined
),同时保持函数可以通过 隐含绑定 或 明确绑定 技术来手动绑定 this
。
我们可以构建一个所谓的 软绑定 工具来模拟我们期望的行为。
|
|
这里提供的 softBind(..)
工具的工作方式和 ES5 内建的 bind(..)
工具很相似,除了我们的 软绑定 行为。它用一种逻辑将指定的函数包装起来,这个逻辑在函数调用时检查 this
,如果它是 global
或 undefined
,就使用预先指定的 默认值 (obj
),否则保持 this
不变。它也提供了可选的柯里化行为(见先前的 bind(..)
讨论)。
我们来看看它的用法:
|
|
软绑定版本的 foo()
函数可以如展示的那样被手动 this
绑定到 obj2
或 obj3
,如果 默认绑定 适用时会退到 obj
。
词法this
箭头函数与使用四种标准的this
规则不同的是,箭头函数从封闭它的(函数或全局)作用域采用this
绑定。即,箭头函数以声明它的函数为它的全局,再利用四种规则来决定this
的引用。
|
|
产生上述结果的原因是:一个箭头函数的此法绑定是不能被覆盖的(就连new
也不行)。
ES6之前的一种引用形式:
|
|
在编码时应该统一风格,词法和this
不应该混用。
第三章:对象
语法
字面语法:
|
|
构造形式:
|
|
类型
js的七种基本数据类型:
- string
- number
- boolean
- null
- undefined
- object
- Symbol
前五种基本类型自身不是object
,null
有时会被当成一个对象类型。typeof(null)
会返回object
,实际山,null
是它自己的基本类型。
一个常见的错误论断是“JavaScript中的一切都是对象”。这明显是不对的
function
和数组都是对象。
内建对象
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
这些内建函数的每一个都可以被当做构造器。
|
|
但更推荐使用字面形式的值,而非构造的对象形式。
|
|
内容
属性访问和键访问
|
|
属性访问:.
操作符后面需要一个标识符
兼容的属性名;
键访问:[]
中可以接受任何兼容UTF-8/unicode的字符串作为属性名。
例:名为“Super-Fun!”的属性,不得不使用["Super-Fun"]
语法访问。
[]
中还可以跟一个变量:
|
|
[]
中的属性名总是字符串,不要将对象和数组使用的数字搞混:
|
|
计算型属性名
|
|
ES6
中添加的新的数据类型Symbol
的应用:
|
|
属性vs.方法
每次你访问一个对象的属性都是一个 属性访问,无论你得到什么类型的值。如果你 恰好 从属性访问中得到一个函数,它也没有魔法般地在那时成为一个“方法”。一个从属性访问得来的函数没有任何特殊性(隐含的 this
绑定的情况在刚才已经解释过了)。
|
|
someFoo
和 myObject.someFoo
只不过是同一个函数的两个分离的引用,它们中的任何一个都不意味着这个函数很特别或被其他对象所“拥有”。如果上面的 foo()
定义里面拥有一个 this
引用,那么 myObject.someFoo
的 隐含绑定 将会是这个两个引用间 唯一 可以观察到的不同。它们中的任何一个都没有称为“方法”的道理。
数组
数组也是对象。所以虽然每个索引都是正整数,还是可以在数组上添加属性:
|
|
注意:添加命名属性(不论是使用.
还是[]
操作符语法)不会改变数组length
的值。
小心: 如果你试图在一个数组上添加属性,但是属性名 看起来 像一个数字,那么最终它会成为一个数字索引(也就是改变了数组的内容):
|
|
(?)复制对象
栗子:
|
|
浅拷贝:得到一个新的对象,a
是值2
的拷贝,但b
、c
、d
属性仅仅是引用;
深拷贝:不仅复制myobject
,还会复制anotherObject
和 anotherArray
,就会得到一个无限循环的问题。
浅拷贝易懂,所以ES6为此任务定义了Object.assign(...)
。
|
|
属性描述符
查看属性描述:
|
|
定义属性描述:
|
|
可写性(writable)
即表示该属性值能否被更改:
|
|
在strict mode
下:
|
|
可配置型(Configurable)
即是否能定义属性描述。
|
|
注意:这里直接导致了TypeError,与strict mode
无关。
注意:将configurable
设置为false
是一个单项操作,不可撤销!
configurable:false
阻止的另外一个事情是使用 delete
操作符移除既存属性的能力。
|
|
如你所见,最后的 delete
调用(无声地)失败了,因为我们将 a
属性设置成了不可配置。
delete
仅用于直接从目标对象移除该对象的(可以被移除的)属性。如果一个对象的属性是某个其他对象/函数的最后一个现存的引用,而你 delete
了它,那么这就移除了这个引用,于是现在那个没有被任何地方所引用的对象/函数就可以被作为垃圾回收。但是,将 delete
当做一个像其他语言(如 C/C++)中那样的释放内存工具是 不 恰当的。delete
仅仅是一个对象属性移除操作 —— 没有更多别的含义。
可枚举性(Enumerable)
即属性呢能否在特定对象-属性枚举操作中出现,比如for...in
循环。
不可变性(Immutability)
即属性或对象的不可变性。
ES5实现不可变性的方法都只是实现了浅不可变性。也就是,他们仅影响对象和它的直属属性的性质。如果对象拥有对其他对象(数组、对象、函数等)的引用,那个对象的内容就不会受影响。
|
|
对象常量(Object Constant)
将writable:false
和configurable
组合,可以在实质上创建一个作为对象属性的常量,如:
|
|
防止扩展(Prevent Extensions)
防止对象添加新的属性,保留既存的对象属性。调用Object.preventExtensions(...)
:
|
|
在非strict mode
模式下,b
的创建无声地失效;在strict mode
模式下,会抛出TypeError
。
封印(Seal)
Object.seal(...)
创建一个封印对象,相当于在实质上调用Object.preventExtensions(...)
的同时,将它所有既存属性标记为configurable:false
。
因此,既不能添加属性,也不能重新配置或删除既存属性(虽然你依然 可以 修改它们的值)。
(?)冻结(Freeze)
Object.freeze(..)
创建一个冻结的对象,这意味着它实质上在当前的对象上调用 Object.seal(..)
,同时也将它所有的“数据访问”属性设置为 writable:false
,所以它们的值不可改变。
这种方法是你可以从对象自身获得的最高级别的不可变性,因为它阻止任何对对象或对象直属属性的改变(虽然,就像上面提到的,任何被引用的对象的内容不受影响)。
你可以“深度冻结”一个对象:在这个对象上调用 Object.freeze(..)
,然后递归地迭代所有它引用的(目前还没有受过影响的)对象,然后也在它们上面调用 Object.freeze(..)
。但是要小心,这可能会影响其他你并不打算影响的(共享的)对象。
[[Get]]
关于属性访问如何工作:
|
|
根据语言规范,上面的代码实际上在 myObject
上执行了一个 [[Get]]
操作(有些像 [[Get]]()
函数调用)。对一个对象进行默认的内建 [[Get]]
操作,首先检查对象,寻找一个拥有被请求的名称的属性,如果找到,就返回相应的值。
然而,如果按照被请求的名称 没能 找到属性,[[Get]]
的算法定义了另一个重要的行为。遍历 [[Prototype]]
链,如果有的话。
但 [[Get]]
操作的一个重要结果是,如果它通过任何方法都不能找到被请求的属性的值,那么它会返回 undefined
。
|
|
这个行为和你通过标识符名称来引用 变量 不同。如果你引用了一个在可用的词法作用域内无法解析的变量,其结果不是像对象属性那样返回 undefined
,而是抛出一个 ReferenceError
。
|
|
从 值 的角度来说,这两个引用没有区别 —— 它们的结果都是 undefined
。然而,在 [[Get]]
操作的底层,虽然不明显,但是比起处理引用 myObject.a
,处理 myObject.b
的操作要多做一些潜在的“工作”。
如果仅仅考察结果的值,你无法分辨一个属性是存在并持有一个 undefined
值,还是因为属性根本 不 存在所以 [[Get]]
无法返回某个具体值而返回默认的 undefined
。但是,你很快就能看到你其实 可以 分辨这两种场景。
[[Put]]
它和[[Get]]是存在微妙不同的:
调用 [[Put]]
时,它根据几个因素表现不同的行为,包括(影响最大的)属性是否已经在对象中存在了。
如果属性存在,[[Put]]
算法将会大致检查:
- 这个属性是访问器描述符吗(见下一节”Getters 与 Setters”)?如果是,而且是 setter,就调用 setter。
- 这个属性是
writable
为false
数据描述符吗?如果是,在非 strict mode 下无声地失败,或者在 strict mode 下抛出 TypeError。 - 否则,像平常一样设置既存属性的值。
如果属性在当前的对象中还不存在,[[Put]]
操作会变得更微妙和复杂。我们将在第五章讨论 [[Prototype]]
时再次回到这个场景,更清楚地解释它。
Getters与Setters
Getter:调用一个隐藏函数来取得值的属性;Setter:调用一个隐藏函数来设置值的属性。
当一个属性被定义为拥有getter或setter,那么它们的定义就成了“访问描述符”(与“数据描述符”相对)。
访问描述符的 value
和 writable
性质因没有意义而被忽略,取而代之的是 JS 将会考虑属性的 set
和 get
性质(还有 configurable
和 enumerable
)。
|
|
|
|
定义setter:
|
|
存在性(Existence)
myObject.a
属性访问会得到一个undefined
值,怎么区别它是存储着undefined
还是没有被定义:
|
|
对于第二种方式更健壮的方式:
object.prototype.hasOwnProperty.call(myObject,"a")
枚举(Enumeration)
|
|
另一个可以区分可枚举和不可枚举属性的方法是:
|
|
(*)迭代(Iteration)
几种迭代方法:
forEach(...)
:用于迭代数组中的值,忽略回调返回值
|
|
every(...)
:一直迭代到最后,或回调返回一个false
值
|
|
some(...)
:一直迭代到最后,或回调返回一个true
值
|
|
for...of
循环:用来迭代数组和有迭代器的对象
|
|
关于迭代器@@iterator
它本身不是迭代器对象,而是一个返回迭代器对象的方法:
数组中拥有内建的迭代器。对象中没有。
使用内建的@@iterator
手动迭代数组:
|
|
为想要的迭代对象定义自己的默认@@iterator
:
|
|
一个“无穷”迭代器:
|
|
第四章:混合(淆)“类”的对象
讨论:面向对象(oo)编程,类(class):实例化、继承、多态
类理论
(略)哈哈哈哈哈哈哈哈
第五章:原型(Prototype)
[[Prototype]]
[[Prototype]]
是对象的一个内部属性,它是一个其他对象的引用。对象被创建时,这个属性被赋予了一个非null
值。
一个对象拥有一个空的[[Prototype]]
链接是可能的。
|
|
myObject.a
操作用到了[[Get]]
。
第一步:检查对象本身是否有一个a
属性,如果有就使用它;
第二步:当没有在对象本身找到时,就沿着[[Prototype]]
链继续往下找,直到找到该属性,或者原型链为空返回undefined
。
使用[[Prototype]]
:
|
|
对于for...in
循环,它也会像查询一样,枚举链条上可以找到的属性。
|
|
Object.prototype
[[Prototype]]
链在Object.prototype
处终结,即每个普通的 [[Prototype]]
链的最顶端,是内建的 Object.prototype
。
js中所有普通的对象都“衍生自”Object.prototype
对象。
设置与遮蔽属性
|
|
进行上述操作会出现四种情况:
1、当foo
属性不直接存在于myObject
上,也不存在于myObject
的[[Prototype]]
链的更高层时。foo
作为一个新的属性被添加到myObject
上。
当 foo
不直接存在 于 myObject
,但 存在 于 myObject
的 [[Prototype]]
链的更高层时:
2、如果一个普通的名为 foo
的数据访问属性在 [[Prototype]]
链的高层某处被找到,而且没有被标记为只读(writable:false),那么一个名为 foo
的新属性就直接添加到 myObject
上,形成一个 遮蔽属性。
3、如果一个 foo
在 [[Prototype]]
链的高层某处被找到,但是它被标记为 只读(writable:false) ,那么设置既存属性和在 myObject
上创建遮蔽属性都是 不允许 的。如果代码运行在 strict mode
下,一个错误会被抛出。否则,这个设置属性值的操作会被无声地忽略。不论怎样,没有发生遮蔽。
4、如果一个 foo
在 [[Prototype]]
链的高层某处被找到,而且它是一个 setter(见第三章),那么这个 setter 总是被调用。没有 foo
会被添加到(也就是遮蔽在)myObject
上,这个 foo
setter 也不会被重定义。
如果想在第三种和第四种情况下遮蔽foo
,就不能使用=
赋值,而使用Object.defineProperty(...)
将foo
添加到myObject
。
注意下面这一段代码:
|
|
“类”
“类函数”
在js中,所有的函数默认都会得到一个公有的,不可枚举的属性,称为prototype
,它可以指向任意的对象。
|
|
每个由调用new Foo()
而创建的对象将最终被[[Prototype]]
链接到这个Foo.prototype
对象。
即:
|
|
通过new Foo()
创建a
时,发生的事情之一是a
得到一个内部[[Prototype]]
链接,此链接链到Foo.prototype
所指向的对象。
所以我们可以看到,在new Foo()
这一过程我们并没有做任何从一个类到一个实体对象的拷贝,我们只是将两个对象互相链接在了一起。
“构造器”(Constructors)
|
|
Foo.prototype
对象默认得到一个公有的.constructor
属性,这个属性是不可枚举的并且指向Foo
。
a.constructor === Foo;
虽然为true
,但a
并没有拥有.constructor
。
构造器还是调用?
|
|
结论:函数自身不是构造器,当且仅当被new
调用时,函数调用是一个“构造器调用”。
机制
|
|
分析:
a
和b
的创建:通过new
将Foo
中的this
分别绑定到a
和b
上,因此在a
和b
上分别添加了name
属性。
两个返回值:根据a
和b
创建的原理可知,他们都被链接到了Foo.prototype
上,所以根据[[Get]]
的原理,会得到“a”和”b“这两个输出值。
复活“构造器”
|
|
分析:
首先我们要明白:默认的Foo.prototype
中,含有一个constructor: f Foo()
。
所以,当我们运行a1.constructor === Foo;
时,并不是a1
中含有construtor
这个属性而是根据[[Get]]
在Foo.prototype
中得到。
然后通过新建对象,改变了constructor
的值。
因此当运行a1.constructor === Object;
会返回true
,因为根据[[Get]]
的原理,我们会在Object.prototype
中找到constructor: Object
。
误解,消除!
将 .constructor
加回到 Foo.prototype
对象上,并且具有原生行为中的不可枚举性:
|
|
“(原型)继承”
完成上述图中链接方式的代码:
|
|
分析:
通过Bar.prototype = Object.create( Foo.prototype );
创建一个新的Bar.prototype
链接到Foo.prototype
,并将原来错误的链对象扔掉。
下面两种方法也能工作,但和预期的不同:
|
|
分析:
第一种方式:是将Bar
直接连接到Foo.prototype
上,而不是将Bar.prototype
链接到Foo.prototype
上,所以当运行 Bar.prototype.myLabel = ...
时,实际上是修改的Foo.prototype
对象本身。
第二种方式:利用构造器链接就很容易出现,Foo()
中的this
被绑定到了Bar.prototype
这种情况。
对比ES6 之前和 ES6 标准的技术如何处理将 Bar.prototype
链接至 Foo.prototype
:
|
|
考察“类”关系
主要讨论如何检验两个对象间是否有委托关系:
|
|
第一种方式:
利用instanceof
:instanceof
运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype
属性。
|
|
第二种方式:(也是最简洁的方式)
|
|
|
|
|
|
|
|
|
|
.__proto__
实际上不存在于a
上,而是存在于内建的 Object.prototype
上。而且,.__proto__
虽然看起来像一个属性,但实际上将它看做是一个 getter/setter(见第三章)更合适。
|
|
对象链接
主要讲解Object.create(...)
:
用法:
|
|
var bar = Object.create( foo );
即创建一个链接到foo
上的新对象bar
。
填补Object.create(...)
因为Object.create(...)
是在ES5中引入的,所以要支持ES5之前的环境,就需要用以下方式来填补:
|
|
另一种不可填补的方法:
|
|
Object.create(..)
的第二个参数通过声明每个新属性的 属性描述符指定了要添加在新对象上的属性。
因为,属性描述符在ES5之前的环境是不可填补的,所以Object.create(..)
这一方法无法填补。
链接作为候补?
|
|
如果引用上述方法是将anotherObject
作为候补,来访问cool
属性,那么上述方式是不提倡的,而应该用:
|
|
复习
toString()
,valueOf()
,和其他几种共同工具都存在于这个 Object.prototype
对象上,这解释了语言中所有的对象是如何能够访问他们的。
那个用 new
调用的函数有一个被随便地命名为 .prototype
的属性,这个属性所引用的对象恰好就是这个新对象链接到的“另一个对象”。带有 new
的函数调用通常被称为“构造器”,尽管实际上它们并没有像传统的面向类语言那样初始化一个类。
第六章:行为委托
迈向面向委托设计
类理论
|
|
每个实例都拷贝了完成计划任务的所有行为。所以,在构建完成之后,你通常仅会与这些实例交互。
委托理论
|
|
作为与面向类(OO——面向对象)的对比,我们成上述代码为“OLOO”(链接到其他对象的对象))。
相互委托(不允许)
你不能在两个或多个对象间相互地委托(双向地)对方来创建一个 循环 。如果你使 B
链接到 A
,然后试着让 A
链接到 B
,那么你将得到一个错误。