壹 ❀ 引

同事最近在看angularjs源码,被源码中各种bind,apply弄的晕头转向;于是他问我,你知道apply,call与bind的区别吗?我说apply与call是函数应用,指定this的同时也将方法执行,bind不同,它只是负责绑定this并返回一个新方法,不会执行。

他又问,那如果一个方法bind对象a后,再bind对象b,最后再bind对象c,此时执行函数this指向谁呢?(他经常问这种奇葩问题...);我不加思索的回答,是c吧;真的吗?他反问到。

反问的一瞬间,我知道我应该是说错了,写了个小demo验证下,果不其然,所以我觉得有必要详细去了解下三个方法的区别了。

let o1 = {
a: 1
};
let o2 = {
a: 2
};
let o3 = {
a: 3
}; function fn(b, c) {
console.log(this.a);
}; let fn1 = fn.bind(o1);
let fn2 = fn1.bind(o2);
let fn3 = fn2.bind(o3);
fn3() //?

 贰 ❀ 函数调用与函数应用

我们知道执行一个函数有两种方式,一种是常见的函数调用,第二种就是函数应用了。

从主动被动的关系去解释,函数调用显得更为被动,而函数应用就显得十分主动了,我们来看个例子:

var name = "听风是风",
obj = {
name: 'echo'
}; function fn() {
console.log(this.name);
}; //函数调用
fn() //听风是风 //函数应用
fn.call(obj); //echo

fn() 等同于window.fn(),本质上方法fn是被window调用,所以this指向了window,这就像古代的娃娃亲,对于fn来说this被安排的明明白白,婚姻及其不自由。

而fn.call()虽然也等同于window.fn.call(),但call为fn提供了改变this的机会,此时的this也就是obj对象。可以看到随着思想的解放,fn有了选择婚姻的权利,更为自由和幸福。

那么说到这你应该明白了,call()与apply()就是提供函数应用的方法,它们让函数执行时的this指向更为灵活。

 叁 ❀ call与apply

站在函数应用的角度我们知道了call与apply的用途,那这两个方法又有什么区别呢,其实区别就一点,参数传递方式不同。

call方法中接受的是一个参数列表,第一个参数指向this,其余的参数在函数执行时都会作为函数形参传入函数。

fn.call(this, arg1, arg2, ...);

而apply不同的地方是,除了第一个参数作为this指向外,其它参数都被包裹在一个数组中,在函数执行时同样会作为形参传入。

fn.apply(this, [arg1, arg2, ...]);

除此之外,两个方法的效果完全相同:

let o = {
a: 1
}; function fn(b, c) {
console.log(this.a + b + c);
};
fn.call(o, 2, 3); //
fn.apply(o, [2, 3]); //

 肆 ❀ 关于bind

bind之所以要拿出来单独说,是因为它与call,apply又存在一些不同。call与apply在改变this的同时,就立刻执行,而bind绑定this后并不会立马执行,而是返回一个新的绑定函数。

let o = {
a: 1
}; function fn(b, c) {
console.log(this.a + b + c);
}; let fn1 = fn.bind(o, 2, 3); fn1();//

还记得文章开头我同事提的问题吗,我之所以觉得bind多次后执行,this会指向最后一次bind的对象,是因为没能正确理解bind返回函数的含义。

尝试打印返回的新函数fn1,可以看到它并不是一个普通的function,而是一个bound function,简称绑定函数:

它的TargetFunction指向了bind前的的函数,BoundThis就是绑定的this指向,BoundArgs便是传入的其它参数了。

当我们执行fn1时,就有点类似于TargetFunction.apply(BoundThis,BoundArgs)。

我们可以得出一个结论,当执行绑定函数时,this指向与形参在bind方法执行时已经确定了,无法改变。

let o1 = {
a: 1
};
let o2 = {
a: 2
}; function fn(b, c) {
console.log(this.a + b + c);
}; let fn1 = fn.bind(o1, 2, 3);
//尝试再次传入形参
fn1(4, 4); // //尝试改变this
fn1.call(o2); //

其实很好理解,当执行fn1时,本质上等于window.fn1(),如果this还能被改变,那this岂不是得指向window,那bind方法就没太大意义了。

 伍 ❀ 应用

说到这,我们大概知道了这三个方法的作用与区别,那这三个方法有啥用?其实很常用。

我们都知道Math.max()方法能取到一组数字中的最大值,比如:

Math.max(1, 10); //
Math.min(1, 10); //

那我们如何利用此方法求数组中最大最小呢,这里就可以利用apply的特性了:

Math.max.apply(null, [1, 2, 10]); //
Math.max.min(null, [1, 2, 10]); //

在非严格模式下,当我们让this指向null,或者undefined时,this本质上会指向window,不过这里的null就是一个摆设,我们真正想处理的其实是后面的数组。

还记得在考虑兼容情况下怎么判断一个对象是不是数组吗?再或者是不是一个函数?利用Object.prototype.toString()结合call方法就能解决这个问题:

let a = [];
let b = function () {};
Object.prototype.toString.call(a) === "[object Array]";//true
Object.prototype.toString.call(b) === "[object Function]";//true

 陆 ❀ 总

那么到这里,我们一起理解了我同事提出的奇葩问题,为何bind多次后执行,函数this还是指向第一次bind的对象。

其次,除了函数调用,我们理解了函数应用的概念,也知道如何使用call和apply去实现一个函数应用,顺便通过bind还知道了绑定函数(bound function)的概念。

所以到这里,你知道call、apply与bind的区别了吗?

js中call、apply、bind到底有什么区别?bind返回的方法还能修改this指向吗?的更多相关文章

  1. 深入理解js中的apply、call、bind

    概述 js中的apply,call都是为了改变某个函数运行时的上下文环境而存在的,即改变函数内部的this指向. apply() apply 方法传入两个参数:一个是作为函数上下文的对象,另外一个是作 ...

  2. 关于js中for in和foreach in的区别

    js 中for in 和foreach in的区别 两个的作用都用来遍历对象,但为什么有了for in语句了还要foreach in语句呢,后来看了下foreach in开发的文档,foreach i ...

  3. [转]js中confirm实现执行操作前弹出确认框的方法

    原文地址:http://www.jb51.net/article/56986.htm 本文实例讲述了js中confirm实现执行操作前弹出确认框的方法.分享给大家供大家参考.具体实现方法如下: 现在在 ...

  4. js中加“var”和不加“var”的区别

    JavaScript 拥有动态类型.这意味着相同的变量可用作不同的类型: var x // x 为 undefined var x = 6; // x 为数字 var x = "Bill&q ...

  5. C#中??和?分别是什么意思? 在ASP.NET开发中一些单词的标准缩写 C#SESSION丢失问题的解决办法 在C#中INTERFACE与ABSTRACT CLASS的区别 SQL命令语句小技巧 JQUERY判断CHECKBOX是否选中三种方法 JS中!=、==、!==、===的用法和区别 在对象比较中,对象相等和对象一致分别指的是什么?

    C#中??和?分别是什么意思? 在C#中??和?分别是什么意思? 1. 可空类型修饰符(?):引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空.例如:string str=null; ...

  6. JS中构造函数和普通函数有什么区别

    JS中构造函数有普通函数有什么区别? 1.一般规则 构造函数都应该以 一个大写字母开头,eg: function Person(){...} 而非构造函数则应该以一个小写字母开头,eg: functi ...

  7. js中使用function定义类、实例化,函数的调用方法

    function Test002(name, age){ name, age, this.printInfo = function(){ //定义的公有方法 console.log(name, age ...

  8. js中call、apply和bind到底有什么区别?

    介绍 在js中,每个函数的原型都指向Function.prototype对象(js基于原型链的继承).因此,每个函数都会有apply,call,和bind方法,这些方法继承于Function. 它们的 ...

  9. js 中call,apply,bind的区别

    call.apply.bind方法的共同点与区别: apply.call.bind 三者都是用来改变函数的this对象的指向: apply.call.bind 三者都可以利用后续参数传参: bind ...

随机推荐

  1. docker进阶之路-基础篇 | 二:portainer安装与基本使用

    转载请注明作者及出处: 作者:银河架构师 原文链接:https://www.cnblogs.com/luas/p/12061755.html ​简介 Portainer 是轻量级,跨平台,开源的管理D ...

  2. doGet()方法和doPost()方法有什么区别?

    1. 一般上,get是从服务器上获取数据,post是向服务器传送数据. 2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到.pos ...

  3. 剑指Offer-46.孩子们的游戏(圆圈中最后剩下的数)(C++/Java)

    题目: 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指定 ...

  4. 如何编写一个TS程序?

    第一步:我们首先需要个代码编辑器-VSCode  点击此处下载(你会下载到rar文件) 第二步:我们还需要下载NodeJS,因为这里有npm,npm是包管理工具,可以下载TypeScript. 注意: ...

  5. Xamarin.Forms 界面布局

    <!--margin表示控件相对StackLayout的位置是设置组件之间的距离,或者距离父组件边缘的距离,    他的四个值是左边距,上边距,右边距,下边距  -->    <!- ...

  6. 常见SQL编写和优化

    常见的SQL优化方式 对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by 涉及的列上建立索引. 应尽量避免在 where 子句中对字段进行null 值判断,否则将导致引擎放弃 ...

  7. Cortex-A7处理器算数运算指令和逻辑运算指令

      汇编中也可以进行算术运算, 比如加减乘除,常用的运算指令用法如表所示: 常用运算指令 在嵌入式开发中最常会用的就是加减指令,乘除基本用不到. 我们用 C 语言进行CPU 寄存器配置的时候常常需要用 ...

  8. Aery的UE4 C++游戏开发之旅(3)蓝图

    目录 蓝图 蓝图命名规范 蓝图优化 暴露C++至蓝图 暴露C++类 暴露C++属性 暴露C++函数 暴露C++结构体/枚举 暴露C++接口 蓝图和C++的结合方案 使用继承重写蓝图 使用组合重写蓝图 ...

  9. LINUX OS EXERCISE 08

    1 配置crontab计划任务时,记录的格式是什么? 分钟 小时 日期 月份 星期 可执行语句 2 配置crontab计划任务实例. 以root用户身份添加计划任务,每天早上7:30启动sshd服务, ...

  10. 删除列表中重复元素以及求list中元素个数

    Python 去除列表中重复的元素 来自比较容易记忆的是用内置的set l1 = ['b','c','d','b','c','a','a'] l2 = list(set(l1)) print l2 还 ...