call、apply 及 bind 函数内部实现是怎么样的?
首先从以下几点考虑如何实现这几个函数
- 不传入第一个参数,那么上下文默认为
window
- 改变
this
指向,让新的对象可以执行改函数,并能接受参数。
# 先来手撸 call
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。
Function.prototype.qlCall = function (context) {
if (typeof this !== "function") {
throw new TypeError("Error");
}
if (!context) {
context = typeof window !== "undefined" ? window : global;
}
/**暴露处理 context 有可能传的不是对象*/
context = Object(context);
/**使用 Symbol 值命名 防止与context中的属性名冲突 */
const fnName = Symbol("key");
context[fnName] = this;
const args = [...arguments].slice(1);
const result = context[fnName](...args);
delete context[fnName];
return result;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
分析 call 的实现原理
首先
context
为可选参数,如果不传的话默认上下文为 window | global(兼容 node.js)接下来就是 通过
Object
构造函数创建一个包装类对象,避免传入的context
不是对象。使用
Symbol值
值命名需要被调用的函数名称防止与context
中的属性名冲突。将调用的函数赋值给
context[ fnName ]
。因为
call
可以传入对个参数作为调用函数的参数,所以使用数组的slice方法
浅拷贝传入的多个参数。最后调用函数并使用展开运算符入参,之后再删除
context
上的函数
# 手撸 apply
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。
Function.prototype.qlApply = function (context) {
if (typeof this !== "function") {
throw new TypeError("Error");
}
if (!context) {
context = typeof window !== "undefined" ? window : global;
}
/**暴露处理 context 有可能传的不是对象*/
context = Object(context);
/**使用 Symbol 值命名 防止与context中的属性名冲突 */
const fnName = Symbol("key");
context[fnName] = this;
/**call 和 apply 的区别就是处理参数 */
let result;
if (arguments[1]) {
result = context[fnName](...arguments[1]);
} else {
result = context[fnName]();
}
delete context[fnName];
return result;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
分析 apply
的实现原理
基本和
call
的实现是一样的,除了处理参数和call
有区别call
接受一个参数列表,但apply
接受一个参数的单数组
# 手撸 bind
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind
的实现对比其他两个函数略微地复杂了一点,因为bind
需要返回一个函数,需要判断一些边界问题
比如 我们可能会 通过 new Func()
调用返回的函数,或者是普通的 func()
调用。
我们先分析一下 new
执行的过程中会发现什么:
- 自动创建一个新对象
- 新对象把它的[[prototype]]关联到构造函数的原型上
- 将构造函数中的 this 绑定到新建的对象上
- 根据构造函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理。
function qlNew(func, ...args) {
if (typeof func !== "function") {
throw "func must be a function";
}
// 1.创建一个新对象
// const obj = {};
// 2.新对象原型指向构造函数原型对象
// obj.__proto__ = func.prototype;
/**
* 1,2步还可以更简化处理
*/
let obj = Object.create(func.prototype);
/** Object.create 实际模拟如下
* let Ctor = function () {}
* Ctor.prototype = func.prototype
* Ctor.prototype = func.prototype
* let obj = new Ctor()
*/
// 3.将构建函数的this指向新对象
let result = func.apply(obj, args);
// 4.根据返回值判断
const flag =
(typeof result === "Object" && result !== null) ||
typeof result === "function";
return flag ? result : obj;
}
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
Function.prototype.qlBind = function (context, ...args) {
if (typeof this !== "function") {
throw new TypeError("bind must be called on a function");
}
const executeBound = function (
sourceFunc,
boundFunc,
context,
callingContext,
args
) {
if (!(callingContext instanceof boundFunc)) {
// 调用方式不是 new Func 的形式就直接调用 sourceFunc,并且给对应的参数即可
return sourceFunc.apply(context, args);
} else {
// 下面就是处理 new Func() 的调用形式了
const self = Object.create(sourceFunc.prototype);
const result = sourceFunc.apply(self, args);
const flag =
(typeof result === "Object" && result !== null) ||
typeof result === "function";
return flag ? result : obj;
}
};
const func = this;
const bound = function (...innerArgs) {
/**
* 因为 bind 的普通调用的传参可能是这样的 func.bind(obj, 1)(2) 所以入参是需要
* args.concat(...innerArgs) 这样就能将两边的参数拼接起来了。
*/
return executeBound(func, bound, context, this, args.concat(...innerArgs));
};
return bound;
};
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
37
38