call、apply、bind()方法

apply,call和bind的基本介绍

语法

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)

返回值

all/apply: 返回fun执行的结果
bind: 返回fun的拷贝,并拥有指定的this值和初始参数

参数

thisArg(可选)

  • fun的this指向thisArg对象。
  • 在非严格模式下,thisArg指定null,undefined,fun中this指向window对象。
  • 在严格模式下,fun的this指向undefined
  • 值是原始值(比如:数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如:String,Number,Boolean。

param1,param2(可选): 传给fun的参数。

如果param不传或为 null/undefined,则表示不需要传入任何参数.
apply第二个参数为数组,数组内的值为传给fun的参数。

call与apply

在JavaScript中,call和apply都是为了改变函数执行的上下文而存在的,也就是
为了改变函数体内部的this的指向。
JavaScript的一大特点是函数存在「定义是上下文」和「运行是上下文」以及上下文是可以被改变的。

为何要改变执行上下文?举一个生活中的小例子:平时没时间做饭的我,周末想给孩子炖个腌笃鲜尝尝。但是没有适合的锅,而我又不想出去买。所以就问邻居借了一个锅来用,这样既达到了目的,又节省了开支,一举两得。
改变执行上下文也是一样的,A 对象有一个方法,而 B 对象因为某种原因,也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?当然是借用 A 对象的啦,既完成了需求,又减少了内存的占用。

另外,它们的写法也很类似,调用call和apply的对象必须包含一个函数Function。

function fruit() {}

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

这个时候如果我们又想重新定义一个banana={color: "yellow"};,我们不想重新定义一个say方法,那么这个时候我们就可以使用call和apply方法:

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

所以从上面可以看出,call和apply是为了动态改变this而出现的,当一个对象没有某个方法的时候(本例子中banner对象没有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对象,call把参数按照顺序传递进去,而apply是把参数放在数组里再传进去。

apply和call该用哪个呢?

  • 参数数据、顺序确定就用call,参数数量/殊勋不确定的话就用apply
  • 考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。
  • 参数集合已经是一个数组的情况,用apply,比如上下文的获取数组最大值/最小值。
    参数数量/顺序不确定的话就用apply,比如以下示例:

const obj = {
    age: 24,
    name: 'linKGe'
}
const obj2 = {
    age: 27
}
callObj(obj, handle);
callObj(obj2, handle);
//根据某些条件决定要传递参数的数量,以及顺序
function callObj(thisAge,fn) {
    let params = [];
    if(thisAge.name) {
        params.push(thisAge.name);
    }
    if(thisAge.age) {
        params.push(thisAge.age);
    }
    fn.apply(thisAge,params)// 数量和顺序不确定,不能使用call
}
function handle(...params) {
    console.log('params',params);
}

结果:
params [ 'linKGe', 24 ]
params [ 27 ]

call和apply的应用场景

下面会分别列举 call 和 apply 的一些使用场景。声明:例子中没有哪个场景是必须用 call 或者必须用 apply 的,只是个人习惯这么用而已。

1.call的使用场景

1.1 对象的继承

function superClass () {
    this.a = 1;
    this.print = function () {
        console.log(this.a);
    }
}

function subClass () {
    superClass.call(this);
    this.print();
}

subClass(); //1

subClass 通过 call 方法,继承了 superClass 的 print 方法和 a 变量。此外,subClass 还可以扩展自己的其他方法。

1.2 借用方法

如果一个类数组想使用Array原型上的方法,可以使用:

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

这样,domNodes 就可以使用 Array 下的所有方法了。

2.apply应用场景

apply获取数组最大值和最小值
apply直接传递数组做要调用方法的参数,也省一步展开数组,比如使用Math.max、Math.min 来获取数组的最大值和最小值。

const arr = [15, 6, 12, 13, 16];
const max = Math.max.apply(Math, arr); // 16
const min = Math.min.apply(Math, arr); // 6

面试题

定义一个 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() {
    let arg = Array.prototype.slice.call(arguments);
    arg.unshift('(app)');
    console.log.apply(console,arg);
}
log('hello world');  //(app) hello world

bind

在学习bind之前我们先来看一下这道题题目:

var altwrite = document.write;
altwrite("hello");

结果报错:Uncaught TypeError: Illegal invocation ,altwrite()函数改变了this的指向global或者window对象,导致执行的时候提示非法调用异常,正确的方案就是使用bind()方法。

altwrite.bind(document)('hello');

当然也可以使用call()方法。

altwrite.call(document,'hello');

绑定函数

bind()最简单的方法就是绑定函数,使这个函数无论怎么调用都有同样的this值,常见的错误就像上面的例子一样,将方法从对象中拿出来,然后调用,并且希望this指向原来的对象。如果不做特殊处理,一般会丢失原来的对象。使用bind()方法能够很好的解决这个问题:

this.num = 9;
var mymodule = {
  num: 81,
  getNum: function() {
      console.log(this.num);
  }
};
mymodule.getNum();  //81   //this是mymodule

var getNum = mymodule.getNum;
getNum(); //9  //这时候this是window

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81

bind() 方法与apply和call相似,也是可以改变函数体内this的指向。
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));
    }
}

在上述代码里,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()时全局作用域。

偏函数(Partial Functions)

Partial Function也叫Partial Application,这里截取一段关于偏函数的定义:

Partial application can be described as taking a function that accepts some number of arguments, binding values to one or more of those arguments, and returning a new function that only accepts the remaining, un-bound arguments.
可以将部分应用程序描述为采用一个接受一些参数的函数,将值绑定到这些参数中的一个或多个,然后返回一个仅接受其余未绑定参数的新函数。
这是一个很好的特性,使用bind()我们设定函数的预定义参数,然后调用的时候传入其他参数即可:

function list() {
    return Array.prototype.slice.call(arguments);
  }
  
  var list1 = list(1, 2, 3); // [1, 2, 3]
  
  // 预定义参数37
  var leadingThirtysevenList = list.bind(undefined, 37);
  
  var list2 = leadingThirtysevenList(); // [37]
  var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

和setTimeout一起使用

function Bloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 1秒后调用declare函数
Bloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 100);
};

Bloomer.prototype.declare = function() {
  console.log('我有 ' + this.petalCount + ' 朵花瓣!');
};

var bloo = new Bloomer();
bloo.bloom(); //我有 5 朵花瓣!

注意:对于事件处理函数和setInterval方法也可以使用上面的方法

绑定函数和构造函数

绑定函数也适用于使用new操作符来构造目标函数的实例,当使用绑定函数来构造实例,注意:this会被忽略,但是传入的参数仍然可用。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() { 
  console.log(this.x + ',' + this.y);
};

var p = new Point(1, 2);
p.toString(); // '1,2'


var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// 实现中的例子不支持,
// 原生bind支持:
var YAxisPoint = Point.bind(null, 0/*x*/);

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true

捷径

bind()也可以为需要特定this值的函数创造捷径。
例如要将一个类数组对象转换为真正的数组:

var slice = Array.prototype.slice;
slice.call(arguments);

如果使用bind()的话,情况变得更加简单。

var  unboundSlice = Array.prototype.slice;
var slice = Function.protorype.call.bind(unboundSlice);
//...
slice(arguments);

面试题--bind的应用场景

1. 保存函数参数:

首先来看一下这一道经典的面试题:

for (var i = 1; i <= 5; i++) {
   setTimeout(function test() {
        console.log(i) // 依次输出:6 6 6 6 6
    }, i * 1000);
}

造成这个现象的原因是等到setTimeout异步执行时,i已经变成6了。
那么如何使它输出:1,2,3,4,5呢?

  • 可以使用闭包保存变量

 for (var i = 1; i <= 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log('闭包:', i); // 依次输出:1 2 3 4 5
        }, i * 1000);
    }(i));
}
  • bind

for (var i = 1; i <= 5; i++) {
    // 缓存参数
    setTimeout(function (i) {
        console.log('bind', i) // 依次输出:1 2 3 4 5
    }.bind(null, i), i * 1000);
}

实际山这里也是用了闭包,我们知道bind会返回一个函数,这个函数也是闭包。
它保存了函数的this指向、初始参数,每次i的变更都会被bind的闭包存起来,所以输出1-5.
具体细节可从下面的手写bind深入研究。

  • let
    用let声明i也可以输出1-5;因为let是块级作用域,所以每次都会创建一个新的变量,所以setTimeout每次读的值都是不同的。

参考:
https://segmentfault.com/a/1190000018270750
https://www.imooc.com/article/290456

转载: 

随笔记录--call、apply、bind方法理解 - 简书apply,call和bind的基本介绍 语法 返回值 all/apply: 返回fun执行的结果bind: 返回fun的拷贝,并拥有指定的this值和初始参数 参数 thi...https://www.jianshu.com/p/83806277575b

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值