bind,call,apply的用法及实现

bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

参数

thisArg

调用绑定函数时作为 this 参数传递给目标函数的值。

  1. 如果使用new运算符构造绑定函数,则忽略该值。
  2. 当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object
  3. 如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

arg1, arg2, ...

当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

返回值

返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

描述

bind() 函数会创建一个新的绑定函数bound function,BF)。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数
绑定函数具有以下内部属性:

  • [[BoundTargetFunction]] - 包装的函数对象(即新生成的函数)。
  • [[BoundThis]] - 在调用包装函数时始终作为 this 值传递的值。
  • [[BoundArguments]] - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。
  • [[Call]] - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个this值和一个包含通过调用表达式传递给函数的参数的列表。

当调用绑定函数**[[BoundTargetFunction]]**时,它调用 [[BoundTargetFunction]] 上的内部方法 **[[Call]]**,就像这样 Call(*boundThis*, *args*)。其中,boundThis[[BoundThis]]args[[BoundArguments]] 加上通过函数调用传入的参数列表。

绑定函数也可以使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。

用法

改变this指向(创建绑定this指向的函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = 'outterA'

let obj = {
a: 'innerA'
}

function printA(){
console.log(this.a)
}

printA() //'outterA'

let innerPrintA = printA.bind(obj)
innerPrintA() //innerA

偏函数

通俗的讲,偏函数就是指通过一个初始函数A,创建出另外的函数A1,A2,这两个函数含有了初始参数1,2。这里使用bind是利用了他的两个特点。

  1. bind返回的是绑定函数,区别与Function.prototype.call()Function.prototype.apply()的立即执行
  2. 其接受的是参数列表,并且会与绑定函数调用时的参数合并作用[[call]]原函数,区别于Function.prototype.apply()
1
2
3
4
5
6
7
8
9
function add(arg1, arg2){
return arg1 + arg2
}

let addThirtynine = add.bind(null, 39)
let addEleven = add.bind(null, 11)

addThirtynine(3) //42
addEleven(3) //14

改变setTimeout,setInterval的this指向

由于setTimeoutsetInterval的函调函数中this的指向总是window(即使在严格模式下)。所以可以使用bind改变回调函数中的this指向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
message: '内部消息'
}

message = '外部消息' //这种方法是吧message变量挂载在window上

setTimeout(function(){
console.log(this.message)
}, 1000)
//一秒后打印出:'外部消息'

setTimeout(function(){
console.log(this.message)
}.bind(obj), 1000) //将this的指向改变为obj
//一秒后打印出:'内部消息'

作为构造函数使用的绑定函数

与偏函数用法类似,相当于为构造器提供默认参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Point(x, y){
this.x = x
this.y = y
}

Point.prototype.toString = function(){
return `${this.x},${this.y}`
}
let p = new Point(1, 2)
p.toString() //'1,2'

let emptyObj = {}
let YAxisPoint = Point.bind(null, 0/*x*/)

let axisPoint5 = new YAxisPoint(5)
axisPoint5.toString() //'0,5'

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

快捷调用

一些特定的函数被绑定在特定数据类型种,最典型的例子就是Array.prototype.slice,其只能由数组调用。实际上这个函数也接受类数组的对象(array-like object),这个时候实际上是要改变this的调用。

一般的用法:

1
2
3
4
5
6
7
let slice = Array.prototype.slice

function convert(){
return slice.apply(arguments) //arguments是一个类数组对象
}

convert(1,2,3,4) //[1, 2, 3, 4]

在使用bind后,由于其可以生成绑定函数,所以将要使用的函数作为this就可以不用每次都使用apply

1
2
3
4
5
6
7
8
let unboundSlice = Array.prototype.slice;
let slice = Function.prototype.apply.bind(unboundSlice); //这个slice就是上面的slice.apply()的一个绑定函数

function convert(){
return slice(arguments) //arguments是一个类数组对象
}

convert(1,2,3,4) //[1, 2, 3, 4]

兼容性

bind

Pollyfill

法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Does not work with `new (funcA.bind(thisArg, args))`
if (!Function.prototype.bind) (function(){
let slice = Array.prototype.slice;
Function.prototype.bind = function() {
let thatFunc = this, thatArg = arguments[0];
let args = slice.call(arguments, 1);
if (typeof thatFunc !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - ' +
'what is trying to be bound is not callable');
}
return function(){
let funcArgs = args.concat(slice.call(arguments)) //合并参数
return thatFunc.apply(thatArg, funcArgs); //绑定this指向
};
};
})();

这里利用了Function.prototype.apply构造一个函数,运行及执行apply方法,达到bind的特点。

法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//  Yes, it does work with `new (funcA.bind(thisArg, args))`
if (!Function.prototype.bind) (function(){
let ArrayPrototypeSlice = Array.prototype.slice;
Function.prototype.bind = function(otherThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}

let baseArgs= ArrayPrototypeSlice.call(arguments, 1),
baseArgsLength = baseArgs.length,
fToBind = this,
fNOP = function() {},
fBound = function() {
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments);
return fToBind.apply(
fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
);
};

if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();

return fBound;
};
})();

call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

参数

thisArg

可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

arg1, arg2, ...

指定的参数列表。(与bind一样)

描述

call方法相较于bind,他会生成一个绑定函数并立即调用。

用法

基本方法:改变this指向

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
message: 'inner-message'
}

message = 'outter-message'

function log(){
console.log(this.message)
}

log() //'outter-message'
log.call(obj) //'inner-message'

使用 call 方法调用父构造函数

使用call方法绑定this对象为自己子类中,则可以完成对父构造器的调用。达到简单的继承效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Product(name, price){
this.name = name
this.price = price
}

function Food(name, price){
Product.call(this, name, price) //调用父构造器,并将this绑定为Food的示例
this.category = 'food'
}

function Toy(name, price){
Product.call(this, name, price) //调用父构造器,并将this绑定为Toy的示例
this.category = 'toy'
}

let cheese = new Food('feta', 5)
let fun = new Toy('robot', 40)

为匿名函数指定this对象

1
2
3
4
5
6
7
let obj = {
message: 'obj-message'
}
(function(){
console.log(this.message)
}).call(obj)
//'obj-message'

兼容性

call

Pollyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (!Function.prototype.call) {
Function.prototype.call = function () {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError(
'Function.prototype.call - what is trying to be bound is not callable'
);
}
let func = this
let that = arguments[0]
let args = []
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i])
}
that.func = func
that.func(...args)
delete that.func
};
}

apply

apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

参数

  • thisArg

    必选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

  • argsArray

    可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

返回值

调用有指定**this**值和参数的函数的结果。

描述

  1. applycall() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或数组对象, 如 fun.apply(this, new Array('eat', 'bananas'))

  2. 你也可以使用 arguments对象作为 argsArray 参数。 arguments 是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。 你可以使用arguments来把所有的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。

  3. 从 ECMAScript 第5版开始,可以使用任何种类的类数组对象,就是说只要有一个 length 属性和(0..length-1)范围的整数属性。例如现在可以使用 NodeList 或一个自己定义的类似 {'length': 2, '0': 'eat', '1': 'bananas'} 形式的对象。

    需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。

用法

基本方法:改变this指向

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
message: 'inner-message'
}

message = 'outter-message'

function log(){
console.log(this.message)
}

log() //'outter-message'
log.apply(obj) //'inner-message'

函数数组参数变为列表参数+使用内置函数

1
2
3
4
let arr = [2,3,4,5,7]

let max = Math.max.apply(null, arr)
//7

在ES6中扩展运算符(spread)···来实现函数数组参数变为列表参数:

1
2
3
4
let arr = [2,3,4,5,7]

let max = Math.max(...arr)
//7

兼容性

apply

Pollyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (!Function.prototype.apply) {
Function.prototype.apply = function () {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError(
'Function.prototype.apply - what is trying to be bound is not callable'
);
}
let func = this
let that = arguments[0]
let args = []
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i])
}
that.func = func
that.func(args)
delete that.func
};
}

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :