最近在看一下node package的源码,发现很多里面都包含了function这个对象的apply、call、bind这三个方法,于是想拿出来再看看。。

apply、call

在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。JavaScript 的一大特点是,函数存在【定义时上下文】和【运行时上下文】以及【上下文是可以改变的】这样的概念。

下面先来看个小例子吧。。。。莫着急,咱们慢慢说

function TestFunc () {}TestFunc.prototype = {color: "red",doSomething: function() {console.log("My color is " + this.color);}};var testFunc = new TestFunc();
testFunc.doSomething();  //  My color is red

但是如果我们有一个对象other= {color : "yellow"} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 apple 的 say 方法:

var other = {color: "yellow"};testFunc.doSomething.call(other);    //  My color is yellow
testFunc.doSomething.apply(other);   //  My color is yellow

所以,可以看出 call 和 apply 是为了动态改变 this 而出现的,当一个 object 没有某个方法(本栗子中other没有doSomething方法),但是其他的有(本栗子中testFunc有doSomething方法),我们可以借助call或apply用其它对象的方法来操作。

利用上面的代码,我们改一下来输出这两个不同对象调用doSomething是在控制台输出this,看一下这两种调用中this是不是不同。。。

function TestFunc () {}TestFunc.prototype = {color: "red",doSomething: function() {console.log("My color is " + this.color);console.log(this);}};var testFunc = new TestFunc();testFunc.doSomething();  //  My color is redvar other = {color: "yellow"};testFunc.doSomething.call(other);    //  My color is yellowtestFunc.doSomething.apply(other);   //  My color is yellow

控制台如下:你会发现确实不同哦

当我们再调用apply或call的时候,不传入任何参数,或者是第一个参数为null的话又会是怎样的呢,再看下面这个栗子:

var hehe = "Global";var color = 'blue';function TestFunc () {}TestFunc.prototype = {color: "red",doSomething: function() {console.log("My color is " + this.color);console.log(this);}};var testFunc = new TestFunc();testFunc.doSomething();  //  My color is redvar other = {color: "yellow"};testFunc.doSomething.call(other);    //  My color is yellowtestFunc.doSomething.apply(other);   //  My color is yellow
testFunc.doSomething().call();     //  My color is bluetestFunc.doSomething().call(null);  //  //  My color is blue

从输出的结果你会看出但什么也不传或者是第一个参数为null的时候,这个【上下文】应该就是函数【运行时上下文】,放在<script>里面其实就是window对象。

apply、call 的区别

对于 apply、call 二者而言,作用完全一样,只是接受参数的方式不太一样。例如,有一个函数定义如下:

var func = function(arg1, arg2) {};

我们就可以通过如下方式来调用:

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])

其中 this 是你想指定的上下文,他可以是任何一个 JavaScript 对象(JavaScript 中一切皆对象),call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。

JavaScript 中,某个函数的参数数量是不固定的,因此要说适用条件的话,当你的参数是明确知道数量时用 call 。而不确定的时候用 apply,然后把参数 push 进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个数组来遍历所有的参数。为了巩固加深记忆,下面列举一些常用用法:

1、数组之间追加

var array1 = [12 , "foo" , {name:"Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
console.log(array1);
/* array1 值为 [12 , "foo" , {name :"Joe"} , -2458 , "Doe" , 555 , 100] */

2、获取数组中的最大值和最小值

var  numbers = [5, 458 , 120 , -215 ];
var maxInNumbers = Math.max.apply(Math, numbers),   //458maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458

number 本身没有 max 方法,但是 Math 有,我们就可以借助 call 或者 apply 使用其方法。

3、验证是否是数组(前提是toString()方法没有被重写过)

functionisArray(obj){returnObject.prototype.toString.call(obj) === '[object Array]' ;
}

4、类(伪)数组使用数组方法

var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

Javascript中存在一种名为伪数组的对象结构。比较特别的是 arguments 对象,还有像调用 getElementsByTagName , document.childNodes 之类的,它们返回NodeList对象都属于伪数组。不能应用 Array下的 push , pop 等方法。

但是我们能通过 Array.prototype.slice.call 转换为真正的数组的带有 length 属性的对象,这样 domNodes 就可以应用 Array 下的所有方法了。

深入理解运用apply、call

下面就借用一道面试题,来更深入的去理解下 apply 和 call 。定义一个 log 方法,让它可以代理 console.log 方法,常见的解决方法是:

function log(msg) {console.log(msg);
}
log(1);    //1
log(1,2);    //1

上面方法可以解决最基本的需求,但是当传入参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用 apply 或者 call,注意这里传入多少个参数是不确定的,所以使用apply是最好的,方法如下:

function log(){console.log.apply(console, arguments);
};
log(1);    //1
log(1,2);    //1 2

接下来的要求是给每一个 log 消息添加一个"(app)"的前辍,比如:

log("hello world");    //(app)hello world

该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组,通过 Array.prototype.slice.call 转化为标准数组,再使用数组方法unshift,像这样:

function log(){var args = Array.prototype.slice.call(arguments);args.unshift('(app)');console.log.apply(console, args);
};

是不是对apply,call这两个function有了不同的理解了呢。。。表急。。咱们来看看bind。。

bind

说完了 apply 和 call ,再来说说bind。bind() 方法与 apply 和 call 很相似,也是可以改变函数体内 this 的指向。

  MDN的解释是:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

  直接来看看具体如何使用,在常见的单体模式中,通常我们会使用 _this , that , self 等保存 this ,这样我们可以在改变了上下文之后继续引用到它。 像这样:

var foo = {bar : 1,eventBind: function(){$('.someClass').on('click',function(event) {/* Act on the event */console.log(this.bar);      //1}.bind(this));}
}

由于 Javascript 特有的机制,上下文环境在 eventBind:function(){ } 过渡到 $('.someClass').on('click',function(event) { }) 发生了改变,上述使用变量保存 this 这些方式都是有用的,也没有什么问题。当然使用 bind() 可以更加优雅的解决这个问题:

var foo = {bar : 1,eventBind: function(){$('.someClass').on('click',function(event) {/* Act on the event */console.log(this.bar);      //1}.bind(this));}
}

在上述代码里,bind() 创建了一个函数,当这个click事件绑定在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。因此,这里我们传入想要的上下文 this(其实就是 foo ),到 bind() 函数中。然后,当回调函数被执行的时候, this 便指向 foo 对象。再来一个简单的栗子:

var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3

这里我们创建了一个新的函数 func,当使用 bind() 创建一个绑定函数之后,它被执行的时候,它的 this 会被设置成 foo , 而不是像我们调用 bar() 时的全局作用域。有个有趣的问题,如果连续 bind() 两次,亦或者是连续 bind() 三次那么输出的值是什么呢?像这样:

var bar = function(){console.log(this.x);
}
var foo = {x:3
}
var sed = {x:4
}
var func = bar.bind(foo).bind(sed);
func(); //?var fiv = {x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //?

答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

function getName() {console.log(this.hehe);}var obj11 = {hehe: "jason li"};//   直接运行方法结果是
        getName();//  通过call或者是apply来使用这个function
        getName.call(obj11);//  通过使用bind构建一个新的Function,并指定运行时的上下文,根据bind(绑定)就好像把getName绑定在了obj11对象上一样var func = getName.bind(obj11);func();

apply、call、bind比较

那么 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个栗子:

var obj = {x: 81,
};var foo = {getX: function() {return this.x;}
}console.log(foo.getX.bind(obj)());  //81
console.log(foo.getX.call(obj));    //81
console.log(foo.getX.apply(obj));   //81

三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。

再总结一下:

  • apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
  • apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用后续参数传参;
  • bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

转载于:https://www.cnblogs.com/duhuo/p/4382410.html

深入Javascript中apply、call、bind相关推荐

  1. Javascript 中 apply、call、bind

    在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向. JavaScript 的一大 ...

  2. [转] 深入浅出 妙用Javascript中apply、call、bind

    [From] http://www.admin10000.com/document/6711.html 网上文章虽多,大多复制粘贴,且晦涩难懂,我希望能够通过这篇文章,能够清晰的提升对apply.ca ...

  3. 【优雅代码】深入浅出 妙用Javascript中apply、call、bind

    这篇文章实在是很难下笔,因为网上相关文章不胜枚举. 巧合的是前些天看到阮老师的一篇文章的一句话: "对我来说,博客首先是一种知识管理工具,其次才是传播工具.我的技术文章,主要用来整理我还不懂 ...

  4. 深入浅出妙用 Javascript 中 apply、call、bind

    这篇文章实在是很难下笔,因为网上相关文章不胜枚举. 巧合的是前些天看到阮老师的一篇文章的一句话: "对我来说,博客首先是一种知识管理工具,其次才是传播工具.我的技术文章,主要用来整理我还不懂 ...

  5. JavaScript 中 apply 、call 的详解

    apply 和 call 的区别 ECMAScript 规范给所有函数都定义了 call 与 apply 两个方法,它们的应用非常广泛,它们的作用也是一模一样,只是传参的形式有区别而已. 原文作者:林 ...

  6. 关于javascript中apply()和call()方法

    ref:http://www.popo4j.com/article/the-differences-of-apply-and-call.html 如果没接触过动态语言,以编译型语言的思维方式去理解ja ...

  7. javascript中apply、call和bind的区别

    在JS中,这三者都是用来改变函数的this对象的指向的,他们有什么样的区别呢. 在说区别之前还是先总结一下三者的相似之处: 1.都是用来改变函数的this对象的指向的. 2.第一个参数都是this要指 ...

  8. JavaScript中apply的用法

    1.对象的继承,一般的做法是复制:Object.extend prototype.js的实现方式是: Object.extend = function(destination, source) { f ...

  9. 关于javascript中apply()和call()方法的区别

    原文地址:http://www.cnblogs.com/fighting_cp/archive/2010/09/20/1831844.html 转载于:https://www.cnblogs.com/ ...

最新文章

  1. linux哪个版本支持多线程,关于Linux操作系统的叙述错误的是()A、Linux是多用户、多任务、支持多线程的操作系统B、Linux的源...
  2. 苹果让Transformer抛弃注意力机制,一切只为效率,项目已开源丨华人一作
  3. arcgis里python窗口运行,在 Python 窗口中执行工具
  4. 修复Debian grub
  5. api 和 C# 里的接口的区别?
  6. 校招需要看的书 巩固的知识
  7. hive kerberos java_Kerberos身份验证错误 - Sqoop通过Hive从SQL导入HDFS
  8. 树形结构 —— 并查集 —— 并查集的删除操作
  9. html5背景切换效果,html5 游戏背景切换
  10. python能做什么程序-python都能做什么
  11. 邹城的关于机器人教育_【喜报】我校机器人队问鼎全国大学生机器人大赛冠军!...
  12. SparkSQL JSON数据操作(1.3-1.4)
  13. 曼联队选择Tezos作为官方区块链和训练工具包合作伙伴
  14. Java验证码图片生成实现
  15. [Matlab]使用textscan读取.csv文件时候只读取到了第一行
  16. 【喜讯 · 喜讯】讲师自营销计划奖励又双叒叕来了!
  17. 软件设计师刷题与知识点总结 笔记
  18. 骁龙8gen1都有哪些手机 骁龙8gen1打游戏怎么样
  19. 14.Yum详解,yum安装,查找,info,反向查找,组,依赖,缓存等所有操作
  20. Android 中更改了默认app图标,在手机上还是显示默认图标

热门文章

  1. 用maven运行指定java类main方法
  2. [读书笔记]TCP/IP详解V1读书笔记-4 5
  3. 关于注入(css/c.js)
  4. IE Firefox css 差别 [转]
  5. JAVA Servlet API简介及接口与类的用法
  6. python清空列表clear_如何在Python中清空列表?
  7. 华为鸿蒙2.0什么核心,鸿蒙系统2.0:安卓最核心部分基本已去除,将带来全新的体验...
  8. linux菜单系统,Linux修改grub菜单
  9. Web安全-伪静态网页
  10. 微软ms10_018漏洞的利用