谈谈javascript的Function中那些隐藏的属性/方法:caller/callee/apply/call/bind
2016-07-21 14:42
736 查看
javascript的Function中有不少不那么常用,又或者用了也是知其然而不知其所以然的属性/方法,本文就来谈谈这一系列属性/方法:
直接上DEMO比较好理解:
我们先来按照思路一步一步来看这段代码:
首先我们看到定义了俩function:handleCaller和callerDemo,并且还可以看出handleCaller函数里是调用了callerDemo函数的。
在callerDemo函数里,我们看到了本文介绍的主角之一:
在callerDemo函数里,有一段判断
继续看
这么分析下来,
在全局环境中调用函数是不会生成此
只有在当前函数的内部(上下文环境)才能调用当前函数的
还是先放代码:
有了上文对
与
可配合
函数递归时用起来比用函数名调用函数更带感!
这俩方法性质一样,只是用法稍有不同,因此放在一起来介绍。还记得我上一篇文章《javascript如何判断变量的数据类型》中介绍的利用
在借用的过程中,
这里的circle只是一个普通的Object对象,不含任何自定义的成员方法,但通过
另外,从代码里也可以看出,
从上面这段DEMO可以看出,
其思路是利用
源引:https://segmentfault.com/a/1190000004265632
caller/
callee/
apply/
call/
bind。
caller
属性
直接上DEMO比较好理解:// caller demo { function callerDemo() { if (callerDemo.caller) { var a = callerDemo.caller.toString(); console.log(a); } else { console.log("this is a top function"); } } function handleCaller() { callerDemo(); } handleCaller(); //"function handleCaller() { callerDemo(); }" callerDemo(); //"this is a top function"
我们先来按照思路一步一步来看这段代码:
首先我们看到定义了俩function:handleCaller和callerDemo,并且还可以看出handleCaller函数里是调用了callerDemo函数的。
在callerDemo函数里,我们看到了本文介绍的主角之一:
caller属性,并且可以看出这
caller属性是函数对象本身的一个成员属性。
在callerDemo函数里,有一段判断
caller属性是否存在的代码,这段代码有什么意义呢?这就要看最后的结果了:直接调用callerDemo()发现此时
callerDemo.caller是为空的,而反观通过调用handleCaller()并在其内部调用callerDemo()则
callerDemo.caller不为空。这说明只有在函数里调用函数,才会生成
caller属性,而直接在全局环境里调用函数则不会生成。
继续看
var a = callerDemo.caller.toString();console.log(a);,这里打印出来的居然是handleCaller整个函数体,这说明,此时的
callerDemo.caller实际上就是对于handleCaller这个函数对象的一个引用。
这么分析下来,
caller属性就很容易明白了:
caller属性是帮助我们在当前函数里获取调用当前函数的某个未知函数,之所以称未知函数,是因为我们在写一个函数时,很可能根本不知道哪个函数会调用到我们的这个函数。
在全局环境中调用函数是不会生成此
caller属性,因为不符合此属性的存在意义/价值(见上条)。
只有在当前函数的内部(上下文环境)才能调用当前函数的
caller属性,不能从外部调用。
callee
属性
还是先放代码:function calleeDemo() { console.log(arguments.callee); }
有了上文对
caller属性的认知,
callee属性就很好理解了,它实际上就是对当前函数对象的一个引用。有以下的点需要注意:
callee属性隶属于Function的一个隐藏对象——
arguments中,这个
arguments对象大家应该不陌生,表示的就是当前函数传入的参数,一般用于函数不限制参数数量的传参。
与
caller属性一样,也是要在当前函数的内部(上下文环境)才有效。
可配合
caller属性一起使用:
arguments.callee.caller,这样就可以完全忽略到具体的函数名了。
函数递归时用起来比用函数名调用函数更带感!
apply
/call
方法
这俩方法性质一样,只是用法稍有不同,因此放在一起来介绍。还记得我上一篇文章《javascript如何判断变量的数据类型》中介绍的利用Object.prototype.toString.call来判断数据类型的方法么:
function type(obj) { return Object.prototype.toString.call(obj).slice(8, -1); //换成用apply方法亦可 }
apply/
call方法的意义在于借用其它对象的成员方法来对目标对象执行操作。
在借用的过程中,
apply/
call方法会改变被借用的成员方法的上下文环境:把
this这一与上下文环境高度相关的变量指向目标对象,而非原来的对象。看下面的这段代码:
function Point(x, y){ this.x = x; this.y = y; } Point.prototype.move = function(x, y) { this.x += x; this.y += y; } var p = new Point(0,0); var circle = {x:1, y:1, r:1}; //只是一个普通的Object对象 p.move.call(circle, 2, 1); //借用了Point类对象中的move方法 //p.move.apply(circle, [2, 1]); //等价于p.move.call(circle, 2, 1);
这里的circle只是一个普通的Object对象,不含任何自定义的成员方法,但通过
apply/
call方法,可以借用Point类对象定义的move方法来帮助circle达到目的(本例其实是圆心在坐标轴上的移动)。在借用Point类对象的move方法时,move方法中的this就不再指向p,而是指向circle了,达到了上下文环境改变的效果。
另外,从代码里也可以看出,
call方法与
apply方法的区别仅在于:
call方法直接把需要传入的参数列在目标对象其后,而
apply方法则以数组的形式整体传入。
bind
方法
bind方法与
apply/
call方法也非常类似,相当于稍微再封装了一下,仍以上述DEMO作为案例:
function Point(x, y){ this.x = x; this.y = y; } Point.prototype.move = function(x, y) { this.x += x; this.y += y; } var p = new Point(0,0); var circle = {x:1, y:1, r:1}; // p.move.call(circle, 2, 1); // p.move.apply(circle, [2, 1]); var circleMove = p.move.bind(circle, 2, 1); //此时并不执行move方法 circleMove(); //此时才执行
从上面这段DEMO可以看出,
bind方法其实是给
apply/
call方法缓了一下,也可以说是封装了一下方便后续调用,其实质上相当于下面的这段代码:
function circleMove() { p.move.call(circle, 2, 1); } circleMove();
bind
方法兼容性适应
bind方法,即
Function.prototype.bind,属于
ECMAScript 5,IE从
IE 10版本才开始支持,那怎么做兼容性适应呢?
if(typeof Function.prototype.bind !== 'function') { Function.prototype.bind = function() { var thatFunc = this; var args = []; for(var i = 0; i < arguments.length; i++) { args[i] = arguments[i]; } return function() { var context = args.shift(); thatFunc.apply(context, args); } } }
其思路是利用
apply方法来封装成
bind方法。
源引:https://segmentfault.com/a/1190000004265632
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 最后一次说说闭包
- Ajax
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- Redux系列02:一个炒鸡简单的react+redux例子