这篇文章实在是很难下笔,因为网上相关文章不胜枚举。

巧合的是前些天看到阮老师的一篇文章的一句话:

“对我来说,博客首先是一种知识管理工具,其次才是传播工具。我的技术文章,主要用来整理我还不懂的知识。我只写那些我还没有完全掌握的东西,那些我精通的东西,往往没有动力写。炫耀从来不是我的动机,好奇才是。"

对于这句话,不能赞同更多,也让我下决心好好写这篇,网上文章虽多,大多复制粘贴,且晦涩难懂,我希望能够通过这篇文章,能够清晰的提升对apply、call、bind的认识,并且列出一些它们的妙用加深记忆。

 

apply、call

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

JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。

先来一个栗子:

function fruits() {}

fruits.prototype = {
color: "red",
say: function() {
console.log("My color is " + this.color);
}
} var apple = new fruits;
apple.say(); //My color is red

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

banana = {
color: "yellow"
}
apple.say.call(banana); //My color is yellow
apple.say.apply(banana); //My color is yellow

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

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 这个伪数组来遍历所有的参数。
 
为了巩固加深记忆,下面列举一些常用用法:

数组之间追加

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

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

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

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

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

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

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

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);
};

bind 详解

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

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

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

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

由于 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 是无法生效的。

  

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 则是立即调用 。

JS ----- Javascript中apply、call、bind的更多相关文章

  1. Javascript 中apply call bind

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

  2. javascript中apply,call,bind区别,bind兼容等问题总结

    1 三者的相似之处: (1).都是用来改变函数的this对象的指向的 (2).都是用第一个参数来做this对象的指向 (3).都可以传参数进去 那么,具体到它们有什么区别呢?请看下面的例子: 两个对象 ...

  3. javascript中apply、call和bind的区别,容量理解,值得转!

    a)  javascript中apply.call和bind的区别:http://www.cnblogs.com/cosiray/p/4512969.html b)  深入浅出 妙用Javascrip ...

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

    原文连接 深入浅出 妙用Javascript中apply.call.bind 网上文章虽多,大多复制粘贴,且晦涩难懂,我希望能够通过这篇文章,能够清晰的提升对apply.call.bind的认识,并且 ...

  5. 解析JavaScript中apply和call以及bind

    函数调用方法 在谈论JavaScript中apply.call和bind这三兄弟之前,我想先说下,函数的调用方式有哪些: 作为函数 作为方法 作为构造函数 通过它们的call()和apply()方法间 ...

  6. 一个简单的例子让你很轻松地明白JavaScript中apply、call、bind三者的用法及区别

    JavaScript中apply.call.bind三者的用法及区别 引言 正文 一.apply.call.bind的共同用法 二. apply 三. call 四. bind 五.其他应用场景 六. ...

  7. JavaScript中apply与call方法

    一.定义 apply:应用某一对象的一个方法,用另一个对象替换当前对象. call:调用一个对象的一个方法,以另一个对象替换当前对象. 二.apply //apply function Person( ...

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

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

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

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

  10. Javascript中apply、call、bind

    网上文章虽多,大多复制粘贴,且晦涩难懂,我希望能够通过这篇文章,能够清晰的提升对apply.call.bind的认识,并且列出一些它们的妙用加深记忆. apply.call 在 javascript ...

随机推荐

  1. iOS应用程序发布流程:从测试到上架的完整指南

    ​ 目录 转载:iOS应用程序的签名.重签名和安装测试 前言 打开要处理的IPA文件 设置签名使用的证书和描述文件 开始ios ipa重签名 转载:iOS应用程序的签名.重签名和安装测试 前言 ipa ...

  2. Walrus 入门教程:如何创建模板以沉淀可复用的团队最佳实践

    模板是 Walrus 的核心功能之一,模板创建完成后用户可以重复使用,并在使用过程中逐渐沉淀研发和运维团队的最佳实践,进一步简化服务及资源的部署.用户可以使用 HCL 语言自定义创建模板,也可以一键复 ...

  3. 零售品牌如何挑选达人 KOL?火山引擎 VeDI 有妙招!

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 国内消费市场正在发生一些新变化. 近日,由中国人民大学国家发展与战略研究院.经济学院等联合主办的第 57 期宏观经 ...

  4. Excel 查找替换 -- 快速填充

    单元格匹配,这样就不会把 70 的 0 替换成 7零 了 Ctrl + E 快速填充 一列变多行 快速填充 1. 快速拆分数据 一列数据中包含了姓名和手机号码,这时你需要进行数据拆分,快速填充可以实现 ...

  5. MySQL 创建存储过程注意项

    MySQL server version for the  right syntax to use near 'IF' MySQL server version for the right synta ...

  6. “n个球放到m个盒子”问题整理(Twelvefold way)

    这个算法的正式名字是:"Twelvefold way",共用12种情况. 本文转载自:自为风月马前卒的博文:浅谈"n个球"和"m个盒子"之间 ...

  7. 第五届蓝桥杯(2014)C/C++大学A组省赛题解

    第一题.猜年龄 小明带两个妹妹参加元宵灯会.别人问她们多大了,她们调皮地说:"我们俩的年龄之积是年龄之和的6倍".小明又补充说:"她们可不是双胞胎,年龄差肯定也不超过8岁 ...

  8. 记一次 .NET某道闸收费系统 内存溢出分析

    一:背景 1. 讲故事 前些天有位朋友找到我,说他的程序几天内存就要爆一次,不知道咋回事,找不出原因,让我帮忙看一下,这种问题分析dump是最简单粗暴了,拿到dump后接下来就是一顿分析. 二:Win ...

  9. vue 引入echars 亲测版

    网上找了很多例子,其中有修改main.js的配置的,反正我没搞成功,最后实验成功的步骤如下 1.首先创建一个Vue CLI 的工程 . 注:具体步骤查看以前的博客 https://www.cnblog ...

  10. 我让 ChatGPT 化身为全知全能的文档小助理,啥姿势她都会......

    ChatGPT 虽然只是一个对话型人工智能,但已经震惊了全世界,有人甚至认为人工智能的奇点已经到来.未来一定会有很多人失业,从工业革命开始,每出现一次重大的技术变革,就必然会有一批人失业,我们要直面现 ...