apply和call的用法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了apply和call的用法相关的知识,希望对你有一定的参考价值。

apply和call的用法


 

call 和 apply

EC3给Function的原型定义了两个方法,它们是 Function.prototype.call 和 Function.prototype.apply。在实际的开发中,特别是函数式编程风格的代码中,call和apply尤为重要。能熟练的使用这两个方法模式我们真正成为一名javascript程序员的重要一步。


 

call 和 apply 的区别

它们的作用其实是一模一样的,区别仅仅在于传入的参数形式不同。

  • apply 接受两个参数,第一个参数用来制定函数体内this的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数。
 
  1. var fn =function(a,b,c){
  2. alert([a,b,c,]);// [1,2,3]
  3. };
  4. fn.apply(null,[1,2,3])
  • call 传入的参数数量不固定,第一个参用来制定函数体内的this指向,从第二个参数开始,每个参数被依次传入函数体内。
 
  1. var fn =function(a,b,c){
  2. alert([1,2,3])
  3. }
  • 当使用 call 或者 apply 时,如果我们传入的第一个参数为null,函数体内的this会默认指向宿主对象,在浏览器中,如果使用严格模式,则还为null。
 
  1. var fn =function(){
  2. alert(this=== window)//true
  3. }
  4. fn.call(null)
  5. var fn2 =function(){
  6. "use strict"
  7. alert(this===null)//true
  8. }
  9. fn2.call(null)

 

call 和 apply 的用途

 

1.改变this指向,直接看代码

 
  1. var obj1 ={
  2. name:"fq"
  3. };
  4. var obj2 ={
  5. name:"mm"
  6. }
  7. window.name =‘window‘;
  8. var getName =function(){
  9. alert(this.name)
  10. }
  11. getName()// window
  12. getName.call(obj1)//fq
  13. getName.call(obj2)//mm
  • 在实际开发中,经常会遇到this指向被不经意改变的场景,比如有一个div节点,div节点的onclick事件中的this本来是指向这个div的。
 
  1. document.getElementById(‘div‘).onclick =function(){
  2. alert(this.id)//div
  3. }
  • 假设该事件函数中有一个内部的函数fn,在事件内部调用fn函数时,fn函数体内的this就指向了window,而不是我们预期的div,这个时候我们就可以用call 和 apply去改变this指向了。
 
  1. document.getElementById(‘div‘).onclick =function(){
  2. alert(this.id)//div
  3. var fn =function(){
  4. alert(this.id)//undefined
  5. };
  6. fn();
  7. };
  8. //之前都是保存一下this,更优雅的做法可以这样
  9. document.getElementById(‘div‘).onclick =function(){
  10. alert(this.id)//div
  11. var fn =function(){
  12. alert(this.id)//undefined
  13. };
  14. fn.call(this);
  15. };
  • 案例:内部丢失的this
    或许你某天会觉得 document.getElementById函数有点太长了,也去你会这么做:
 
  1. var getId = document.getElementById;
  2. getId(‘div‘);//但是会报错...

这是因为document.getElementById内部的this实际上在调用的时候 是需要指向document的,所以我们需要手动修正this

 
  1. document.getElementById =(function(fn){
  2. returnfunction(){
  3. return fn.apply(document,arguments);
  4. }
  5. })(document.getElementById)

对于上面的代码,等式右边的函数自执行的结果为内部的匿名函数,但是执行的时候相当于先把之前的 document.getElementById 保存到fn中了,如下:

 
  1. var fn = document.getElementById;
  2. document.getElementById =function(){
  3. return fn.apply(document,arguments)//传进来的实参在arguments中
  4. }

然后当用变量再次存储document.getElementById的时候这时候实际运行的是上面第二个等式后面的函数,然后返回的之前存储的fn运行的结果,但是在函数执行的时候,通过apply修正了this指向document。

 

2.Function.prototype.bind

大部分高级浏览器都实现了内置的Function.prototype.bind方法,用来指定内部的this指向,它返回一个修改this之后的函数,但是并不会想apply和
call那样直接执行函数,来看下面的代码:

 
  1. var obj ={
  2. fn(){
  3. console.log(this);
  4. }
  5. }
  6. setTimeout(obj.fn,1000);//window
  7. setTimeout(obj.fn.bind(obj),1000);//obj

那么咱们看看bind的实现原理是什么

 
  1. Function.prototype.bind =function(context){
  2. var _this =this;
  3. returnfunction(){
  4. return _this.apply(context,arguments);
  5. }
  6. }

也就是先把 之前的函数的引用保存起来,然后返回一个新的函数,只不过这个函数在执行的时候 返回的是保存的引用改变this之后的执行结果。

 

3.借用其它对象的方法

我们都知道,杜鹃既不会筑巢,也不会孵雏,而是把自己的蛋寄托给云雀等其他鸟类,让他们代为孵化和养育。同样,在JavaScript中也存在类似的借用现象。

借用方法的第一种场景是“借用构造函数”,通过这种技术,可以实现一些类似继承的效果:

 
  1. var A =function(name){
  2. console.log(name)
  3. };
  4. var B =function(){
  5. A.apply(this,agruments);
  6. };
  7. B.prototype.getName =function(){
  8. console.log(this.name)
  9. }
  10. var b =new B(‘momo‘);
  11. b.getName();// momo

借用方法的第二种场景跟我们更加密切。
函数的参数列表arguments是一个类数组的对象,虽然它也有“小标”,但它并非正在的数组,所以不能像数组一样进行排序操作或者往集合里面添加一个新元素。这种情况下,我们常常会借用Array.prototype对象上的方法。比如想往arguments中添加一个新元素,通常会借用Array.prototype.push;

 
  1. (function(){
  2. Array.prototype.push.call(arguments,3);
  3. console.log(arguments);// [1, 2, 3]
  4. })(1,2)

在操作arguments的时候我们经常频繁的去找Array.prototype对象借用方法。
想把arguments转换成真正的数组的时候,可以借用Array.prototype.slice方法,想截取arguments列表中第一个元素的时候,由可以借用Array.prototype.shift方法。这些借用其实很常见,没什么好说的,那么他们内部实现的机制原理是什么呢? 不妨咱们翻开v8引擎的源码来看看吧!

 
  1. functionArrayPush(){
  2. var n = TO_UINT32(this.length);//被push对象的length
  3. var m =%_ArgumentsLength();//push的参数个数
  4. for(var i=0; i<m; i++){
  5. this[i+n]=%_Arguments[i];//赋值元素
  6. }
  7. this.length = m + n;
  8. returnthis.length;
  9. }

通过上面这段代码可以看到,Array.prototype.push实际上是一个属性赋值的过过程,把参数按照下标依次添加到被push的对象上面,顺便修改了这个对象的length属性。至于被修改的对象是谁,到底是个数组还是个对象,这个并不重要。

那么改写成 JavaScript 的代码 push 应该是这样的

 
  1. varUtils={
  2. push(){
  3. var n = arguments[0].length ||0,
  4. m = arguments.length -1;
  5. for(var i=0; i < m; i++){
  6. arguments[0][i+n]= arguments[i +1]
  7. }
  8. arguments[0].length = m + n;
  9. return arguments[0].length;
  10. }
  11. }
  12. var o ={};
  13. Utils.push(o,1,2,3);// 3
  14. console.log(o);//Object {0: 1, 1: 2, 2: 3, length: 3}

由此可以推断我们可以把“任意”的对象传入Array.prototype.push。为什么要把“任意”这两个字加引号呢? 因为这个对象其实还要满足2各条件:

  • 对象本身可以存储属性
  • 对象的length属性可读可写

对于第一个条件,对象本身存取属性并没有问题,但是如果借用Array.prototype.push方法的不是一个Object类型数据,而是一个number类型的数据呢?我们无法在number身上存取其他数据,那么从下面的测试代码可以发现,一个number类型的数据不可能借用到这个方法:

 
  1. var a =1;
  2. Array.prototype.push.call(a,‘first‘);
  3. alert(a.length)// undefined
  4. alert(a[0])//undefined

对于第二个条件,函数的length属性就是只读的,表示形参的个数,我们尝试把一个函数当做this传入Array.prototype.push:

 
  1. var fn =function(){};
  2. Array.prototype.push.call(fn,‘first‘);//报错
  3. alert(fn.length);

以上是关于apply和call的用法的主要内容,如果未能解决你的问题,请参考以下文章

js call apply 用法区别

js call apply 用法区别

apply()和call()用法

简述JS中 appy 和 call 的详细用法

apply和call的用法

apply和call的用法