开启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 相关文章推荐
js获取select选中的option的text示例代码
Dec 19 Javascript
悬浮数字的实现案例
Feb 19 Javascript
js写的方法实现上传图片之后查看大图
Mar 05 Javascript
jquery.validate 自定义验证方法及validate相关参数
Jan 18 Javascript
jQuery基础的工厂函数以及定时器的经典实例分析
May 20 Javascript
原生js仿jquery实现对Ajax的封装
Oct 04 Javascript
AngularJS中的路由使用及实现代码
Oct 09 Javascript
详解javascript appendChild()的完整功能
Aug 18 Javascript
JavaScript创建、读取和删除cookie
Sep 03 Javascript
Vue程序化的事件监听器(实例方案详解)
Jan 07 Javascript
《javascript设计模式》学习笔记五:Javascript面向对象程序设计工厂模式实例分析
Apr 08 Javascript
Vue中ref和$refs的介绍以及使用方法示例
Jan 11 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
php增删改查示例自己写的demo
2013/09/04 PHP
PHP实现在线阅读PDF文件的方法
2015/06/23 PHP
PHP实现的简单适配器模式示例
2017/06/22 PHP
在Laravel的Model层做数据缓存的实现
2019/09/26 PHP
Javascript和Ajax中文乱码吐血版解决方案
2009/12/21 Javascript
Jquery+Ajax+PHP+MySQL实现分类列表管理(上)
2015/10/28 Javascript
jQuery特殊符号转义的实现
2016/11/30 Javascript
js时间控件只显示年月
2017/01/08 Javascript
js获取html页面代码中图片地址的实现代码
2018/03/05 Javascript
webstrom Debug 调试vue项目的方法步骤
2018/07/17 Javascript
vue+canvas实现炫酷时钟效果的倒计时插件(已发布到npm的vue2插件,开箱即用)
2018/11/05 Javascript
ES6基础之解构赋值(destructuring assignment)
2019/02/21 Javascript
Vue实现日历小插件
2019/06/26 Javascript
JavaScript前端实现压缩图片功能
2020/03/06 Javascript
python dict remove数组删除(del,pop)
2013/03/24 Python
python基于mysql实现的简单队列以及跨进程锁实例详解
2014/07/07 Python
Python 26进制计算实现方法
2015/05/28 Python
判断网页编码的方法python版
2016/08/12 Python
python绘制简单折线图代码示例
2017/12/19 Python
python3解析库pyquery的深入讲解
2018/06/26 Python
Python多线程原理与用法详解
2018/08/20 Python
python中利用matplotlib读取灰度图的例子
2019/12/07 Python
Python实现图片查找轮廓、多边形拟合、最小外接矩形代码
2020/07/14 Python
Selenium alert 弹窗处理的示例代码
2020/08/06 Python
学习党课思想汇报
2013/12/29 职场文书
2014年五一活动策划方案
2014/03/15 职场文书
超市客服工作职责
2014/06/11 职场文书
班级文化建设标语
2014/06/23 职场文书
2014年社区民政工作总结
2014/12/02 职场文书
2014年监理个人工作总结
2014/12/11 职场文书
2016年社区植树节活动总结
2016/03/16 职场文书
2019财务毕业实习报告
2019/06/27 职场文书
pytorch 如何使用float64训练
2021/05/24 Python
Springboot如何同时装配两个相同类型数据库
2021/11/17 Java/Android
尝试使用Python爬取城市租房信息
2022/04/12 Python
Python 读取千万级数据自动写入 MySQL 数据库
2022/06/28 Python