本文模拟实现JS中call/apply/bind方法。本文内容参考自面试官问:能否模拟实现JS的bind方法和面试官问:能否模拟实现JS的call和apply方法两篇。
模拟实现 call 方法
func.call(thisArg, param1, param2)
-
call
方法接受多个参数,第一个参数thisArg
为替换func函数内this的对象,第二个参数以后都是传入函数的参数
。 -
严格模式下,
func
内的this
指向thisArg
-
非严格模式下,thisArg为null或undefined时,func内的this指向
全局对象
(浏览器中就是window对象),同时值为原始值
(数字,字符串,布尔值)的this会指向该原始值的自动包装对象
。
Function.prototype.callFn = function call(thisArg){
var argsArray = [];
var argumentsLength = arguments.length;
for(var i = 0; i < argumentsLength - 1; i++){=
argsArray[i] = arguments[i + 1];
}
return this.applyFn(thisArg, argsArray);
}
模拟实现 apply 方法
func.apply(thisArg, [argsArray])
-
apply
方法接受两个参数,第一个参数thisArg
为替换func函数内this的对象,第二个参数为传入函数的数组
。 -
严格模式下,
func
内的this
指向thisArg
-
非严格模式下,thisArg为null或undefined时,func内的this指向
全局对象
(浏览器中就是window对象),同时值为原始值
(数字,字符串,布尔值)的this会指向该原始值的自动包装对象
。
function getGlobalObject(){
return this;
}
Function.prototype.applyFn = function apply(thisArg, argsArray) {
if (typeof this !== 'function') {
throw new TypeError(this + 'is not a function.');
}
if (typeof argsArray === 'undefined' || argsArray === null) {
argsArray = [];
}
if (argsArray !== new Object(argsArray)) {
throw new TypeError('CreateListFromArrayLike called on non-object.')
}
if (typeof thisArg === 'undefined' || argsArray === null) {
thisArg = getGlobalObject();
}
thisArg = new Object(thisArg);
var fn = Symbol('fn');
thisArg[fn] = this;
var result = thisArg[fn](...argsArray);
delete thisArg[fn];
return result;
}
var obj = { name: 'wwx' };
function a (m, p, q) {
console.log(this.name);
console.log(m, p, q);
}
var applyResult = a.apply(obj, [1, 2, 3]);
模拟实现 bind 方法
console.log(typeof Function.prototype.bind); // function
console.log(typeof Function.prototype.bind()); // function
console.log(Function.prototype.bind.name); // bind
console.log(Function.prototype.bind().name); // bound
var obj = { name: 'wwx' };
function original(a, b) {
console.log(this.name);
console.log(a, b);
return false;
}
var bound = original.bind(obj, 1);
var boundResult = bound(2); // 'wwx', 1, 2
console.log(boundResult); // false
console.log(original.bind.name); // 'bind'
console.log(original.bind.length); // 1
console.log(original.bind().length); // 2 返回original函数的行参个数
console.log(bound.name); // 'bound original'
console.log((function(){}).bind().name); // 'bound '
console.log((function(){}).bind().length); // 0
第一版:修改this指向,合并参数
Function.prototype.bindFn = function bind(thisArg) {
if (typeof this !== 'function') {
throw new TypeError(this + 'must be a function.')
}
// 存储函数本身
var self = this;
var args = [].slice.call(arguments, 1);
var bound = function() {
var boundArgs = Array.from(arguments);
return self.apply(thisArg, [...args, ...boundArgs]);
}
return bound;
}
var obj = { name: 'wwx' };
function original(a, b) {
console.log(this.name);
console.log(a, b);
}
var bound = original.bind(obj, 1);
bound(2);
第二版:考虑new
var obj = {
name: 'wwx'
}
function original(a, b) {
this.num1 = a
this.num2 = b;
}
var bound = original.bind(obj, 1);
var newBoundResult = new bound(2);
console.log('newBoundResult:', newBoundResult);
// newBoundResult: original { num1: 1, num2: 2 }
// this指向了new bound()生成的新对象。
Function.prototype.bindFn = function bind(thisArg) {
if (typeof this !== 'function') {
throw new TypeError(this + 'must be a function.')
}
var self = this;
var args = [].slice.call(arguments, 1);
var bound = function () {
var boundArgs = Array.from(arguments);
var finalArgs = [...args, ...boundArgs];
if (this instanceof bound) {
if (self.prototype) {
function Empty() {}
Empty.prototype = self.prototype;
bound.prototype = new Empty();
}
var result = self.apply(this, finalArgs);
var isObject = typeof result === 'object' && result !== null;
var isFunction = typeof result === 'function';
if (isObject || isFunction) {
return result;
}
return this;
} else {
return self.apply(thisArg, finalArgs);
}
}
return bound;
}
bound函数的name
和length
通过Object.defineProperties
来实现:
Object.defineProperties(bound, {
'length': {
value: self.length
},
'name': {
value: 'bound ' + self.name
}
})