call

原生call

1
2
3
4
5
6
7
8
9
function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}

run.call({
a: 'a',
b: 'b'
}, 1, 2)

在实现的过程有个关键:

如果一个函数作为一个对象的属性,那么通过对象的.运算符调用此函数,this 就是此对象

1
2
3
4
5
6
7
8
9
10
11
let obj = {
a: 'a',
b: 'b',
test: function (arg1, arg2) {
console.log(arg1, arg2)
// this.a 就是 a; this.b 就是 b
console.log(this.a, this.b)
}
}

obj.test(1, 2)
手动模拟call
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
Function.prototype.call2 = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Error')
}

// 默认上下文是window
context = context || window
// 保存默认的fn
const { fn } = context

// 前面讲的关键,将函数本身作为对象context的属性调用,自动绑定this
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)

// 恢复默认的fn
context.fn = fn
return result
}

// 以下是测试代码
function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}

test.call2({
a: 'a',
b: 'b'
}, 1, 2)
bind

bind有两个特点:

  • 本身返回一个全新的函数,所以需要考虑new的清空
  • 可以保留参数,内部实现了参数的拼接
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
32
33
34
35
36
Function.prototype.bind2 = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Error')
}

const that = this
// 保留之前的参数,为了下面的参数拼接
const args = [...arguments].slice(1)

return function F() {
// 如果被new创建实例,不会被改变上下文!
if(this instanceof F) {
return new that(...args, ...arguments)
}

// args.concat(...arguments): 拼接之前和现在的参数
// 注意:arguments是个类Array的Object, 用解构运算符..., 直接拿值拼接
return that.apply(context, args.concat(...arguments))
}
}

/**
* 以下是测试代码
*/

function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}

const test2 = test.bind2({
a: 'a',
b: 'b'
}, 1) // 参数 1

test2(2) // 参数 2
apply

apply和call实现类似,只是传入的参数形式是数组形式,而不是逗号分隔的参数序列。

因此,借助es6提供的…运算符,就可以很方便的实现数组和参数序列的转化。

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
32
33
34
Function.prototype.apply2 = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Error')
}

context = context || window
const { fn } = context

context.fn = this
let result
if(Array.isArray(arguments[1])) {
// 通过...运算符将数组转换为用逗号分隔的参数序列
result = context.fn(...arguments[1])
} else {
result = context.fn()
}

context.fn = fn
return result
}

/**
* 以下是测试代码
*/

function test(arg1, arg2) {
console.log(arg1, arg2)
console.log(this.a, this.b)
}

test.apply2({
a: 'a',
b: 'b'
}, [1, 2])