模拟实现call、apply和bind
基本原理
对象的方法调用时this指向是指向该对象的,利用此原理我们可以模拟实现call\bind\apply
1. 实现call
版本v1
利用基本原理我们可以实现版本1
1 | Function.prototype.call2 = function (ctx, ...rest) { |
版本v2
版本1的缺点是fn属性可能已经存在于ctx中了,我们这样写可能会导致原fn属性被删除
1 | Function.prototype.call2 = function (ctx, ...rest) { |
版本v3
上面的版本还忽略了一个问题,当传入的ctx为null或undefined时候,this指向window;当传入的ctx为其他基本数据类型时候,会改为Object类型
1 | Function.prototype.call2 = function (ctx, ...rest) { |
加分版
上面所有的版本我们都使用了es6的方法,如果不依赖es6的方法,将如何实现呢?
- let改为var
- Symbol使用时间戳代替,利用
hasOwnProperty
判断该值是否为对象的原有属性 - 使用eval运行函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20Function.prototype.call2 = function () {
var ctx = arguments[0]
var key = ''
var args = []
if (ctx === null || ctx === undefined) ctx = window
if (typeof ctx !== 'object') ctx = new Object(ctx)
while (!key || ctx.hasOwnProperty(key)) {
key = 'fn_' + new Date().getTime()
}
ctx[key] = this
// 参数数组
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']')
}
// 字符串拼接会直接调用数组的toString方法 [1,2].toString() === '1,2'
// 此处变为 eval('ctx[key](arguments[1], arguments[2],...)')
var result = eval('ctx[key](' + args + ')')
delete ctx[key]
return result
}2. 实现apply
与模拟实现call原理一致,我们直接修改call模拟实现的加分版如果不想使用eval,可以使用new Function()根据字符串生成函数来调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22Function.prototype.apply2 = function (ctx, args) {
var key = ''
var argsStr = []
if (ctx === null || ctx === undefined) ctx = window
if (args === null || args === undefined) args = []
if (typeof ctx !== 'object') ctx = new Object(ctx)
if (!(args instanceof Object)) {
throw new Error('TypeError: CreateListFromArrayLike called on non-object')
}
while (!key || ctx.hasOwnProperty(key)) {
key = 'fn_' + new Date().getTime()
}
ctx[key] = this
// 参数数组
for (var i = 0, len = args.length; i < len; i++) {
argsStr.push('args[' + i + ']')
}
// 字符串拼接会直接调用数组的toString方法 [1,2].toString() === '1,2',因为[[1,2],3].toString() === '1,2,3',数组参数会被拍平,所以借助argsStr
var result = eval('ctx[key](' + argsStr + ')')
delete ctx[key]
return result
}改为1
var result = eval('ctx[key](' + argsStr + ')')
1
var result = new Function(['ctx', 'key', 'args'], 'return ctx[key](' + argsStr + ')')(ctx, key, args)
3. 实现bind
v1版本,沿用之前的思想,封装函数返回
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
26Function.prototype.bind2 = function () {
var ctx = arguments[0]
var args = []
var _this = this
if (ctx === null || ctx === undefined) ctx = window
if (typeof ctx !== 'object') ctx = new Object(ctx)
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
return function () {
var key = ''
while (!key || ctx.hasOwnProperty(key)) {
key = 'fn_' + new Date().getTime()
}
ctx[key] = _this
for (var i = 0, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
for (var i = 0, len = args.length, argsStr = []; i < len; i++) {
argsStr.push('args[' + i + ']')
}
var result = eval('ctx[key](' + argsStr + ')')
delete ctx[key]
return result
}
}v2版本,利用之前实现的call或apply
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Function.prototype.bind2 = function () {
var ctx = arguments[0]
var args = []
var self = this
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
return function () {
for (var i = 0, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
return self.apply2(ctx, args)
}
}v3版本,考虑生成的函数作为构造函数的情况
- 生成的函数应该继承原函数的原型链上的属性
- bind后的函数作为构造函数调用时,this指向new创建的实例【参考】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Function.prototype.bind2 = function () {
var ctx = arguments[0]
var args = []
var self = this
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
var F = function () {
for (var i = 0, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
return self.apply2(this instanceof F ? this : ctx, args)
}
var FNOP = function () {}
FNOP.prototype = self.prototype
F.prototype = new FNOP()
return F
}
感谢您的阅读,本文由 Astar 版权所有。如若转载,请注明出处:Astar(http://example.com/2022/01/09/%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0call%E3%80%81apply%E5%92%8Cbind/)