不好的实践

函数或方法的接收者(即绑定到特殊关键字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方法自定义接收者来调用方法的更多相关文章

  1. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  2. [Effective JavaScript 笔记]第38条:在子类的构造函数中调用父类的构造函数

    示例 场景类 场景图(scene)是在可视化的过程中(如游戏或图形仿真场景)描述一个场景的对象集合.一个简单的场景包含了在该场景中的所有对象(称角色),以及所有角色的预加载图像数据集,还包含一个底层图 ...

  3. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  4. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  5. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  6. [Effective JavaScript 笔记]第22条:使用arguments创建可变参数的函数

    第21条讲述使用可变参数的函数average.该函数可处理任意数量的参数并返回这些参数的平均值. 如何创建可变参数的函数 1.实现固定元数的函数 书上的版本 function averageOfArr ...

  7. [Effective JavaScript 笔记]第42条:避免使用轻率的猴子补丁

    41条对违反抽象原则行为的讨论之后,下面聊一聊终极违例.由于对象共享原型,因此每一个对象都可以增加.删除或修改原型的属性.这个有争议的实践通常称为猴子补丁. 猴子补丁示例 猴子补丁的吸引力在于其强大. ...

  8. [Effective JavaScript 笔记]第44条:使用null原型以防止原型污染

    第43条中讲到的就算是用了Object的直接实例,也无法完全避免,Object.prototype对象修改,造成的原型污染.防止原型污染最简单的方式之一就是不使用原型.在ES5之前,并没有标准的方式创 ...

  9. [Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法

    前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...

随机推荐

  1. 开源分布式实时计算引擎 Iveely Computing 之 安装部署(2)

          在Github中下载代码和二进制程序中,您都会看到一个bin\iveely computing目录,里面即是Iveely Computing的运行库.              以前总是有 ...

  2. Bootstrap系列 -- 31.嵌套分组

    我们常把下拉菜单和普通的按钮组排列在一起,实现类似于导航菜单的效果.使用的时候,只需要把当初制作下拉菜单的“dropdown”的容器换成“btn-group”,并且和普通的按钮放在同一级 <di ...

  3. Socket网络编程--FTP客户端(1)(Windows)

    已经好久没有写过博客进行分享了.具体原因,在以后说. 这几天在了解FTP协议,准备任务是写一个FTP客户端程序.直接上干货了. 0.了解FTP作用 就是一个提供一个文件的共享协议. 1.了解FTP协议 ...

  4. emmet 缩写API

    http://docs.emmet.io/cheat-sheet/

  5. Winform中的PictureBox读取图像文件无法释放的问题

    今天做一拍照程序,相机SDK什么都搞定,就为了显示图像并且保存照片的步骤卡了半天. 原因是预览图像使用了PictureBox,载入图片文件的方式为: pictureBoxPhoto.Image = I ...

  6. 浅谈Logistic回归及过拟合

    判断学习速率是否合适?每步都下降即可.这篇先不整理吧... 这节学习的是逻辑回归(Logistic Regression),也算进入了比较正统的机器学习算法.啥叫正统呢?我概念里面机器学习算法一般是这 ...

  7. Visio绘制时序图

    用visio建立时序图 1.选择模版 2.常见符号 时序图创建步骤 1.确定交互过程的上下文: 2.识别参与过程的交互对象: 3.为每个对象设置生命线: 4.从初始消息开始,依次画出随后消息: 5.考 ...

  8. 在cmd下编译一个简单的servlet时出现程序包javax.servlet不存在

    由于servlet和JSP不是Java平台JavaSE(标准版)的一部分,而是Java EE(企业版)的一部分,因此,必须告知编译器servlet的位置. 解决“软件包 javax.servlet不存 ...

  9. 破解受保护的excel中的密码

    今天朋友给我发了一张excel表,她说不能删除数据,让我帮忙弄弄,我当时就觉得这张表应该是受到保护了,就在视图选项中准备撤销保护,但是却需要密码,后来在网上查找了 下资料,发现有个方法可以将密码破解出 ...

  10. MyEclipse2014中SVN的使用方法

    MyEclipse中的SVN操作手册 1.导入项目 点击工具栏上的[File-Import],进入下图 (如果你的对话框中没有SVN这一条目,可能是因为你没有安装SVN插件,请安装完成后,在看这篇博客 ...