工作中我们常常会用到call、apply、bind,所以抽了些时间在网上查查它们的基本实现原理,然后按自己理解手写了一版,方便随着日后知识的积累来查阅对照和修正。
- call(ctx, arg1, arg2, ...argn)
function A() {}
A.call(ctx, 1, 2);
描述:call是挂在Function.prototype原型上的一个属性,调用call方法时函数A会立即被执行
特性:
1. 修改函数A中的上下文this的指向
Function.prototype.myCall = function(ctx) {
if (typeof this !== 'function') {
throw new TypeError(this + 'must be function!');
}
function toArr(data) {
var list = [];
for (var i = 0; i < data.length; i++) {
list.push(data[i]);
}
return list;
}
var context = ctx || {};
var fn = this;
var fnName = ['fn', Date.now(), Math.random().toString().slice(2)].join('_');
var args = toArr(arguments).slice(1);
let origin;
// 如果fn上存在属性[fnName],则先暂存下来
if (context[fnName]) {
origin = context[fnName];
}
context[fnName] = fn;
var result = eval('context.' + fnName + '(' + args.join(',') + ')');
// 如果fn上之前存在过[fnName]属性值,则复原
if (origin) {
context[fnName] = origin;
} else {
delete context[fnName];
}
return result;
}
- apply(ctx, args)
function A() {}
A.apply(ctx, [1, 2]);
描述:apply是挂在Function.prototype原型上的一个属性,调用apply方法时函数A会立即被执行
特性:
1. 修改函数A中的上下文this的指向
2. 若要传参,则用数组包裹放在第二个参数,第二个参数类型只能是null、undefined、数组或者类数组中的一个
Function.prototype.myApply = function(ctx) {
if (typeof this !== 'function') {
throw new TypeError(this + 'must be function!');
}
var args = arguments[1];
if (args !== null && args !== undefined && !(args instanceof Array || 'length' in args)) {
throw new TypeError('apply 方法调用第二个参数只能是null、undefined、数组')
}
function toArr(data) {
var list = [];
for (var i = 0; i < data.length; i++) {
list.push(data[i]);
}
return list;
}
var context = ctx || {};
var fn = this;
var fnName = ['fn', Date.now(), Math.random().toString().slice(2)].join('_');
var origin;
// 如果fn上存在属性[fnName],则先暂存下来
if (context[fnName]) {
origin = context[fnName];
}
context[fnName] = fn;
var result;
if (args) {
result = eval('context.' + fnName + '(' + toArr(args).join(',') + ')');
} else {
result = eval('context.' + fnName + '()');
}
// 如果fn上之前存在过[fnName]属性值,则复原
if (origin) {
context[fnName] = origin;
} else {
delete context[fnName];
}
return result;
}
- bind(ctx, arg1, arg2, ...argn)
function A() {}
var B = A.bind(ctx, 1,2);
B();
// new B();
描述:bind是挂在Function.prototype原型上的一个属性,调用bind方法会创建一个新的绑定函数,它包装了原函数对象。调用绑定函数即是调用包装函数。
特性:
1. 函数A调用bind方法不会立即执行函数A,只会返回一个新的包装函数
2. 返回的包装函数中的this上下文指向调用bind时传入的第一个参数,且之后再调用这个包装函数它的this上下文不会再变更(除了用 new 的方式调用包装函数)
3.
Function.prototype.myBind = function(ctx) {
// 因为bind是挂在Function.prototype上的,所以调用bind方法的对象不是函数则抛出错误提示
if (typeof this !== 'function') {
throw new TypeError(this + 'must be function!');
}
var fn = this;
// 调用bind函数除了ctx上下文之外的参数
var args1 = [].slice.call(arguments, 1);
var fnBound = function() {
// 调用包装函数(bind之后返回的函数)的参数
var args2 = [].slice.call(arguments);
// 两次函数调用参数合并在一起
var finalArgs = args1.concat(args2);
if (this instanceof fnBound) { // 通过new方式调用bind后的函数
return fn.apply(this, finalArgs);
}
return fn.apply(ctx, finalArgs);
}
// 调用bind方法的对象必须是一个普通函数(箭头函数没有prototype属性)
// 修改包装函数的this上下文,让它指向原函数
if (fn.prototype) {
var empty = function() {};
empty.prototype = fn.prototype;
fnBound.prototype = new empty();
empty.prototype = null;
}
return fnBound;
}