本片文章主要讲三个方法的原理和实现。
首先,这三个方法跟this指向息息相关,因为可以通过他们实现this的硬绑定。
一、call和apply 使用方面,两者区别仅仅就是传入参数的方式。
call的参数是一个个传入,apply是以数组形式传入。两个方法实际是执行绑定的函数,以下是两者的实现:
Function.prototype.call = function (thisArg) {
if (!this) {
new TypeError('this is not defined');
}
// 拿到被调用函数的引用
var func = Object(this);
// 绑定当前上下文this
thisArg.func = func;
// 获取参数
var funcArguments
if (arguments.length > 1) {
funcArguments = Array.from(arguments).slice(1);
return thisArg.func(...funcArguments);
}
return thisArg.func();
}
Function.prototype.apply = function (thisArg) {
if (!this) {
new TypeError('this is not defined');
}
// 拿到被调用函数的引用
var func = Object(this);
// 绑定当前上下文this
thisArg.func = func;
// 获取参数
const funcArguments = arguments[1];
return thisArg.func(...funcArguments);
}
二、bind
bind与call(apply)的区别是,外面包裹了一层函数,返回的是一个函数而不是直接执行函数。实现如下:
function bind(context) {
var func = Object(this);
return function () {
// return func.apply(context);
return func.call(context);
}
}
是不是bind过于简单了?可以看看core.js的实现方式:
function bind(that /* , ...args */) {
// 绑定的是一个函数
var fn = aFunction(this);
// 获取第二个参数 就是绑定函数的参数列表,是一个数组
var partArgs = arraySlice.call(arguments, 1);
var bound = function (/* args... */) {
var args = partArgs.concat(arraySlice.call(arguments));
return this instanceof bound ? construct(fn, args.length, args) : invoke(fn, args, that);
};
// 继承
if (isObject(fn.prototype)) bound.prototype = fn.prototype;
return bound;
}
var construct = function (F, len, args) {
if (!(len in factories)) {
for (var n = [], i = 0; i < len; i++) n[i] = 'a[' + i + ']';
// eslint-disable-next-line no-new-func
// 使用Function定义构造函数
factories[len] = Function('F,a', 'return new F(' + n.join(',') + ')');
} return factories[len](F, args);
};
function aFunction(it) {
if (typeof it != 'function') throw TypeError(it + ' is not a function!');
return it;
}
function invoke(fn, args, that) {
var un = that === undefined;
switch (args.length) {
case 0: return un ? fn()
: fn.call(that);
case 1: return un ? fn(args[0])
: fn.call(that, args[0]);
case 2: return un ? fn(args[0], args[1])
: fn.call(that, args[0], args[1]);
case 3: return un ? fn(args[0], args[1], args[2])
: fn.call(that, args[0], args[1], args[2]);
case 4: return un ? fn(args[0], args[1], args[2], args[3])
: fn.call(that, args[0], args[1], args[2], args[3]);
} return fn.apply(that, args);
}
function isObject(it) {
return typeof it === 'object' ? it !== null : typeof it === 'function';
}
可以看出来,与我写的区别是,core.js做了一个判断
return this instanceof bound ? construct(fn, args.length, args) : invoke(fn, args, that);
而invoke其实就是我写的方法,重点看一下construct实现:
var construct = function (F, len, args) {
if (!(len in factories)) {
for (var n = [], i = 0; i < len; i++) n[i] = 'a[' + i + ']';
// eslint-disable-next-line no-new-func
// 使用Function定义构造函数
factories[len] = Function('F,a', 'return new F(' + n.join(',') + ')');
} return factories[len](F, args);
};
其实就是用Function定义了一个函数,其中,
factories[len] = Function('F,a', 'return new F(' + n.join(',') + ')') 这一行可以这么理解
factories[len] = function(F,a){
// a是一个数组
return new F(...a);
}
所以,core.js想的更全面了,bind返回的函数有可能会作为构造函数来用 比如,
function A(){},
var B=A.bind(null);
new B()
这个时候就会走construct逻辑,其实是new A(),
但是,
所以,core.js后面加了一句,
if (isObject(fn.prototype)) bound.prototype = fn.prototype;
再次印证了,construct(fn, args.length, args)是作为构造函数来实现的。