模拟JS中call,apply函数

前言

曾经看到一家面试题让当场模拟call/apply函数,当时好像不是很懂给的例子是啥意思,就搁置了(),直到最近看到了一个JavaScript深入系列,其中一篇模拟call和apply的文章,讲解的非常容易理解,所以想拿出来做个笔记..

call()和apply()

关于call和apply用法和例子,网上各种五花八门的教程….掏一个高程三的例子修改版来说

1
2
3
4
5
6
7
8
9
10
var value = 'a'
var obj = {
value: 'b'
}
function f() {
console.log(this.value)
}
f.call(this) //a
f.call(window) //a
f.call(obj) //b

网上有很多复杂的解释,换成我能理解的两个重点就是

  1. 运行的还是call前面那个函数(还是f)

  2. this的指向换了(换成了obj)

至于apply,就是第二个参数需要是个参数组成的数组或类数组,要模拟这两个函数,还是要实现上面两个重点。

实现过程

第一步

原文的核心思路就是

  1. 将函数设为对象的属性

  2. 执行该函数

  3. 删除该函数

换成代码就是

1
2
3
obj.fn = f
obj.fn()
delete obj.fn
第二步

下一步则是考虑call的参数问题,call的参数不定,需要提取出来

1
2
3
4
var args = []
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}

所以模拟call的基本框架就是

1
2
3
4
5
6
7
8
9
Function.prototype.call2 = function (context) {
context.fn = this
var args = []
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
context.fn(...args)
delete context.fn
}

原文用的是ES3的方法,这里我用了ES6的…运算符

第三步

再下一步是考虑,函数可以有返回值,于是修改一下基本的框架,但在这之前还有一个细节就是call(null)的问题,最终的完全体就是

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.call2 = function (context) {
var context = context || window
context.fn = this
var args = []
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
var result = context.fn(...args)
delete context.fn
return result
}

apply

apply的区别就是参数的问题,修改的完全体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.apply2 = function (context, arr) {
var context = context || window
context.fn = this
var result
if (!arr) {
result = context.fn()
} else {
var args = []
for (var i = 0, len = arr.length; i < len; i++) {
args.push(arr[i])
}
result = context.fn(...args)
}
delete context.fn
return result
}