[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 ...
随机推荐
- Web API 基于ASP.NET Identity的Basic Authentication
今天给大家分享在Web API下,如何利用ASP.NET Identity实现基本认证(Basic Authentication),在博客园子搜索了一圈Web API的基本认证,基本都是做的Forms ...
- 初识 swift 封装轮播图
一.简介 换了一家公司.换了一个环境刚开始来公司自然不能有一丝一毫的放松,每天即使是没有什么工作也是看看这个博客.那个源码.尽量让自己更充实.慢慢的开始写几篇博客记录下自己遇到的一些问题和解决方法.其 ...
- 15.C#回顾及匿名类型(八章8.1-8.5)
今天的篇幅应该会很长,除了回顾前面学的一些,还有写一些关于匿名类型的相关知识,总体上对后续的学习很有帮助,学好了,后面更容易理解,不明白的,那就前面多翻几次,看多了总是会理解的.那么,进入正题吧. 自 ...
- [C#基础]说说委托+=和-=的那些事
写在前面 为什么会突然想说说委托?原因吗,起于一个同事的想法,昨天下班的路上一直在想这个问题,如果给委托注册多个方法,会不会都执行呢?为了一探究性,就弄了个demo研究下. += 大家都知道委托都继承 ...
- Symfony学习--原创。。。。
{{ constant('Symfony\\Component\\HttpKernel\\Kernel::VERSION') }} //显示当前symfony的版本 #div { opacity: 0 ...
- Go语言开发环境搭建
1.Go的安装 (1)下载go安装程序 下载地址:https://golang.org/dl/ (墙内下载地址http://www.golangtc.com/download),如果是您的系统是win ...
- Servlet学习 (HttpServletRequest HttpServletResponse)
1.什么事servlet? 答:服务器端程序, 自定义的服务类型.tomcat是一个容器,这个容器提供各种菜的服务, 我们需要为里面放菜! web服务器就是我们的开发环境和开发基础. 实现了servl ...
- SEO 相关知识
传统SEO 观念 换独立服务器 加硬件 减少http请求,压缩网页体积 css 放页面顶部,js 放页面底部 一个中等规模网站的架构 技术角度 切换不同的 User-agent Chrome 的 Ne ...
- Java 关键字 native
native 关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件中,而是在用其他语言实现的文件中.Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言 ...
- jsp学习(五)
在进行jsp与jdbc连接时,出现这样一个错误,提示如下: java.net.ConnectException: Connection refused: connect 后来发现是由于mysql数据库 ...