开启Javascript中apply、call、bind的用法之旅模式


Posted in Javascript onOctober 28, 2015

我希望能够通过这篇文章,能够清晰的提升对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 这个数组来遍历所有的参数。

为了巩固加深记忆,下面列举一些常用用法:

1、数组之间追加

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] */

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

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 使用其方法。

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

functionisArray(obj){ 
  return Object.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);
};

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

好了,本文关于开启Javascript中apply、call、bind的用法之旅模式的相关教程,到此给大家介绍完了,希望大家喜欢。

Javascript 相关文章推荐
javascript 写类方式之三
Jul 05 Javascript
从面试题学习Javascript 面向对象(创建对象)
Mar 30 Javascript
Jquery 实现table样式的设定
Jan 28 Javascript
JavaScript中的函数(二)
Dec 23 Javascript
javascript实现tab响应式切换特效
Jan 29 Javascript
详解AngularJS中$http缓存以及处理多个$http请求的方法
Feb 06 Javascript
浅析jquery与checkbox的checked属性的问题
Apr 27 Javascript
ES5学习教程之Array对象
Apr 01 Javascript
微信小程序点击图片实现长按预览、保存、识别带参数二维码、转发等功能
Jul 20 Javascript
vue实现匀速轮播效果
Jun 29 Javascript
JavaScript中arguments的使用方法详解
Dec 20 Javascript
vue动态设置路由权限的主要思路
Jan 13 Vue.js
JavaScript多并发问题如何处理
Oct 28 #Javascript
JS实现双击屏幕滚动效果代码
Oct 28 #Javascript
基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式
Oct 28 #Javascript
jQuery实现滑动页面固定顶部显示(可根据显示位置消失与替换)
Oct 28 #Javascript
jquery实现的动态回到顶部特效代码
Oct 28 #Javascript
JavaScript如何调试有哪些建议和技巧附五款有用的调试工具
Oct 28 #Javascript
jQuery实现鼠标滑过链接控制图片的滑动展开与隐藏效果
Oct 28 #Javascript
You might like
example2.php
2006/10/09 PHP
php 操作数组(合并,拆分,追加,查找,删除等)
2012/07/20 PHP
解析PHP中DIRECTORY_SEPARATOR,PATH_SEPARATOR两个常量的作用
2013/06/21 PHP
PHP中session跨子域的三种实现方法
2016/07/25 PHP
PHP实现打包下载文件的方法示例
2017/10/07 PHP
php中青蛙跳台阶的问题解决方法
2018/10/14 PHP
图片格式的JavaScript和CSS速查手册
2007/08/20 Javascript
国外Lightbox v2.03.3 最新版 下载
2007/10/17 Javascript
图像替换新技术 状态域方法
2010/01/28 Javascript
jquery 选项卡效果 新手代码
2011/07/08 Javascript
JavaScript中使用构造器创建对象无需new的情况说明
2012/03/01 Javascript
用客户端js实现带省略号的分页
2013/04/27 Javascript
基于jquery实现的自动补全功能
2015/03/12 Javascript
jQuery的position()方法详解
2015/07/19 Javascript
Javascript数组循环遍历之forEach详解
2016/11/07 Javascript
JavaScript+Html5实现按钮复制文字到剪切板功能(手机网页兼容)
2017/03/30 Javascript
在百度搜索结果中去除掉一些网站的资料(通过js控制不让显示)
2017/05/02 Javascript
Vue-router路由判断页面未登录跳转到登录页面的实例
2017/10/26 Javascript
详解用webpack的CommonsChunkPlugin提取公共代码的3种方式
2017/11/09 Javascript
原生nodejs使用websocket代码分享
2018/04/07 NodeJs
JS实现可用滑块滑动的缓动图代码
2019/09/01 Javascript
Vue-CLI项目中路由传参的方式详解
2019/09/01 Javascript
20多个小事例带你重温ES10新特性(小结)
2019/09/29 Javascript
[36:52]DOTA2真视界:基辅特锦赛总决赛
2017/05/21 DOTA
理解Python中的With语句
2016/03/18 Python
python游戏开发之视频转彩色字符动画
2019/04/26 Python
django项目用higcharts统计最近七天文章点击量
2019/08/17 Python
Python 炫技操作之合并字典的七种方法
2020/04/10 Python
Python日志打印里logging.getLogger源码分析详解
2021/01/17 Python
中职应届生会计求职信
2013/10/23 职场文书
幼儿园安全责任书范本
2014/07/24 职场文书
仓管员岗位职责
2015/02/03 职场文书
埃及王子观后感
2015/06/16 职场文书
毕业生求职自荐信(2016最新版)
2016/01/28 职场文书
2016年百日安全生产活动总结
2016/04/06 职场文书
导游词之千岛湖
2019/09/23 职场文书