[Effective JavaScript 笔记]第20条:使用call方法自定义接收者来调用方法
不好的实践
函数或方法的接收者(即绑定到特殊关键字this的值)是由调用者的语法决定的。
方法调用语法将方法被查找的对象绑定到this变量,(可参阅之前文章《理解函数调用、方法调用及构造函数调用之间的不同》)。
有时需要使用自定义接收者来调用函数,因为该函数可能并不是期望的接收者对象的属性。
可以将方法作为一个新的属性添加到接收者对象中。使用如下代码:
var obj={
temporary:function(a,b,c){console.log('obj')}
}
var f=function(arg1,arg2,arg3){console.log('f')};
var arg1=0,arg2=1,arg3=2;
obj.temporary=f;//标识1
var result=obj.temporary(arg1,arg2,arg3);
delete obj.temporary;
标识1,如果temporary的属性名在对象中已经存在,则会对对象造成功能的破坏。而且取任何名称,都没法确保不和原来的对象名重名,可能这个对象并不是自己创建的。对象可以对属性进行冻结或密封以防止修改和添加(详细见附录)任何新属性。
函数对象call方法
基本描述:用途是在特定的作用域下来执行函数,实际上等于设置函数体内this对象的值。
函数对象具有一个内置的方法call来自定义接收者。
f.call(obj,arg1,arg2,arg3)
此行为和直接调用函数自身很相似。
f(arg1,arg2,arg3)
不同点在于,第一个参数指定了一个显式的接收者对象(即指定函数内部this的指向)
1、当调用的方法已经被删除、修改或者覆盖时,call方法就可以派上用场了。
以hasOwnProperty方法为例,因为这个方法是Object的方法,所有对象都可以访问调用这个方法。
var obj={
foo:'good'
};
obj.hasOwnProperty('foo');//true
然后当这个方法被覆盖时
obj.hasOwnProperty=1;
obj.hasOwnProperty('foo');//Uncaught TypeError: obj.hasOwnProperty is not a function(…)
这时,call方法就可以派上用场了
var hasOwnProperty={}.hasOwnProperty;
delete obj.hasOwnProperty;
hasOwnProperty.call(obj,'foo');//true
hasOwnProperty.call(obj,'hasOwnProperty');//false
2、定义高阶函数时call方法也特别实用。
高阶函数的一个惯用法是接收一个可选的参数作为调用该函数的接收者。
示例:有一个表示键值对列表的对象,提供了名为forEach的方法。
var table={
entries:[],
addEntry:function(key,value){
this.entries.push({key:key,value:value});
},
forEach:function(f,thisArg){
var entries=this.entries;
for(var i=0,n=entries.length;i<n;i++){
var entry=entries[i];
f.call(thisArg,entry.key,entry.value,i);
}
}
};
上面这个例子里允许table对象的使用者,将一个方法作为table.forEach的回调函数f,并为该方法提供一个合理的接收者。例如,实现将table的内容复制到另一个中。
table1.forEach(table2.addEntry,table2);
我们把上面的代码直接带入上面的代码大家可以晰看到它的执行过程。
var entries=table1.entries;
for(var i=0,n=entries.length;i<n;i++){
var entry=entries[i];
table2.addEntry.call(table2,entry.key,entry.value,i);
}
这段代码从table2中提取addEntry方法,forEach方法将table2作为接收者,并反复调用该addEntry方法。
提示
使用call方法自定义接收者来调用函数
使用call方法可以调用在给定的对象中不存在的方法
使用call方法定义高阶函数允许使用者给回调函数指定接收者
附录一:对象属性
注:以下内容出自《javascript高级程序设计语言》第3版
属性类型
ECMAScript-262第5版在定义只有内部才用的特性时,描述了属性的特征。描述这些特性是为了实现JS引擎用的,因此js中不能直接访问它们。为了表示特性是内部值,该规范把它们放到了两对儿方括号中。
例如:[[Enumerable]]。
ECMAScript中有两种属性:数据属性和访问器属性。
数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。
数据属性有4个描述其行为的特性。
- [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
- [[Enumerable]]:表示能否通过for-in循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
- [[Writable]]:表示能否修改属性的值。
- [[Value]]:包含这个属性的数据值。读取属性值的时候,这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined。
示例:
var person={
name:'Nicholas'
}
这里创建的一个名为name的属性,为它指定的值是'Nicholas'。
也就是说person对象的name属性的[[Value]]特性将被设置为'Nicholas',对这个值的任何修改都将反映在这个位置。
修改属性的方法
修改属性默认特性的方法,必须使用 ES5的Object.defineProperty()方法。
这个方法接收三个参数:
- 属性所在的对象
- 属性的名字
- 一个描述符对象
描述符对象的属性必须是:configurable,enumerable,writable和value。设置其中的一或多个值,可以修改对应的特性值。
例如:
var person={};
Object.defineProperty(preson,'name',{
writable:false,
value:'li lei'
});
person.name;//"li lei"
person.name='han mei mei';
person.name;//"li lei"
这里把name属性设置为只读的属性,所以无法对name进行修改。严格模式下会报错。
下面是一个不可配置的示例:
var person={};
Object.defineProperty(preson,'name',{
configurable:false,
value:'li lei'
});
person.name;//"li lei"
delete person.name;
person.name;//"li lei"
把configurable设置为false,表示不能从对象中删除属性。如果在严格模式下,上面的代码会报错。一旦把属性定义为不可配置的,就不能再把它变回为可配置的。此时再调用Object.defineProperty()方法修改除writable之外的特性,都会导致错误。
注意:在调用Object.defineProperty()方法时,如果不指定,configurable,enumerable,writable特性的默认值都是false。多数情况下,都没必要利用Object.defineProperty()方法提供的这些的高级功能。对于理解js对象非常有用。
访问器属性
访问器属性不包含数据值;它们包含一对儿getter和setter函数(不过,这两个函数都不是必需的)。
读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter函数并传入新值,这个函数负责决定如何处理数据。
访问器属性有如下4个特性:
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。
[[Enumerable]]:表示能否通过for-in循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为true。
[[Get]]:在读取属性时调用的函数。默认值为undefined。
[[Set]]:在写入属性时调用的函数。默认值为undefined。
访问器属性不能直接定义,必须使用Object.defineProperty()来定义。
示例:
var book={
_year:2004,
edition:1
};
Object.defineProperty(book,'year',{
get:function(){
return this._year;
},
set:function(newVal){
if(newVal>2004){
this._year=newVal;
this.edition+=newVal-2004;
}
}
});
book.year=2005;
book.edition;//2
book._year;//2005
book.year;//2005
定义多个属性
直接上代码
var book={};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},
year:{
get:function(){
return this._year;
},
set:function(newVal){
if(newVal>2004){
this._year=newVal;
this.edition+=newVal-2004;
}
}
}
})
Object.defineProperties()方法,可能通过描述符一次定义多个属性。
接收两个对象参数:
- 第一个对象是要添加和修改其属性的对象
- 第二个对象的属性与第一个对象中要添加或修改的属性一一对应
读取属性的特性
Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。
接收两个参数:
- 属性所在的对象
- 要读取其描述符的属性名称
返回值是一个对象,视属性的类型不同,对象的属性也不同,具体参见上面的属性类型。
示例
var book={};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},
year:{
get:function(){
return this._year;
},
set:function(newVal){
if(newVal>2004){
this._year=newVal;
this.edition+=newVal-2004;
}
}
}
})
var descriptor=Object.getOwnPropertyDescriptor(book,'_year');
descriptor;//Object {value: 2004, writable: false, enumerable: false, configurable: false}
descriptor=Object.getOwnPropertyDescriptor(book,'year');
descriptor;//Object {configurable:false,enumerable:false,get:function(){...},set:function(newVal){...}
[Effective JavaScript 笔记]第20条:使用call方法自定义接收者来调用方法的更多相关文章
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记]第38条:在子类的构造函数中调用父类的构造函数
示例 场景类 场景图(scene)是在可视化的过程中(如游戏或图形仿真场景)描述一个场景的对象集合.一个简单的场景包含了在该场景中的所有对象(称角色),以及所有角色的预加载图像数据集,还包含一个底层图 ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第22条:使用arguments创建可变参数的函数
第21条讲述使用可变参数的函数average.该函数可处理任意数量的参数并返回这些参数的平均值. 如何创建可变参数的函数 1.实现固定元数的函数 书上的版本 function averageOfArr ...
- [Effective JavaScript 笔记]第42条:避免使用轻率的猴子补丁
41条对违反抽象原则行为的讨论之后,下面聊一聊终极违例.由于对象共享原型,因此每一个对象都可以增加.删除或修改原型的属性.这个有争议的实践通常称为猴子补丁. 猴子补丁示例 猴子补丁的吸引力在于其强大. ...
- [Effective JavaScript 笔记]第44条:使用null原型以防止原型污染
第43条中讲到的就算是用了Object的直接实例,也无法完全避免,Object.prototype对象修改,造成的原型污染.防止原型污染最简单的方式之一就是不使用原型.在ES5之前,并没有标准的方式创 ...
- [Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法
前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...
随机推荐
- session放到mongo里边
为了session的共享和保存,常常把session放数据库里边,但是很多时候都放redis里边,今天看了一个放Mongo中的记录下,说不懂什么时候就用到了. app.use(session({ se ...
- linq查询语句转mongodb
&& (与操作) 子表达式可以通过&&合并来查询满足所有子表达式的文档 var query = from c in collection.AsQueryable< ...
- [Json.net]Linq to Json
引言 上篇学习了json.net的基本知识,这篇学习linq to json. 上篇文章:[Json.net]快速入门 Linq to Json Linq to Json是用来快速操作json对象的, ...
- 年终福利:调试.NET Framework源代码
前言 要问JAVA语言最大的优势之一,那就是开源.开源的JAVA框架让JAVA程序员可以不断的通过看源代码来学习.成长.解决问题.并随着时间增长能力越来越强,自然薪水就越来越高.而DONET程序员要看 ...
- angular的编辑器tinymce
angular的插件的确挺少的, 编辑器更是少, ui-tinymce是angular-ui推荐的一款编辑器(GIT: https://github.com/angular-ui/ui-tinymce ...
- angular的splitter案例学习
angular的splitter案例学习,都有注释了,作为自己的备忘. <!DOCTYPE html> <html ng-app="APP"> <he ...
- java连接sql server2000/2005
接触Java或者JSP,难免会使用到数据库SQL Server 2000/2005(我使用2005标准版[9.0.3054]测试),经过自己的搜索和研究,使用JDBC连接SQL Server成功,特此 ...
- OC基础--成员变量的封装
一.封装的作用: 1.重用 2.不必关心具体的实现 3.面向对象三大特征之一 4.具有安全性 二.OC中成员变量的命名规范以及注意事项 1.命名规范--.成员变量都以下划线“_”开头 1)为了跟get ...
- 【HDU 5115】Dire Wolf
题意 n只狼排成一行,每次击败第i只狼需要ai+bi-1+bi+1代价,击败后,相当于出列了,与i相邻两只狼成了相邻的.求击败所有狼的最小总代价. 分析 我开始一直以为是个环TAT.. 区间dp,dp ...
- 【CodeForces 557B】Pasha and Tea
题 题意 总共有 w 克蛋糕,2n 个盘子,第 i 个盘子容量为 ai ,n 个女孩和 n 个男孩,男孩得到的是女孩得到的蛋糕的两倍,求他们得到蛋糕的最大值. 分析 把盘子从小到大排序,然后 女生得到 ...