前言
本文不再说明他们的具体用法,直接说明他们的内部实现原理。
一、call的实现
思考一个问题:我们在执行call方法时总是是对某函数调用该方法,既然想让每个函数都有该方法,所以call这个方法应该写到它的原型上,即Function.prototype.call=function(){},那我们又知道call的作用是改变this指向,这个原理是怎么样的呢 我们先进行如下尝试,看看this究竟是什么
function person()
{
console.log(this.name)
}
var egg={name:'123'}
Function.prototype.mycall=function(obj)
{
console.log(this)
}
person.mycall(egg)
可以看到我们若不对this指向进行修改,则它依旧指向的是函数本身(this指向这个函数意思也就是它和这个函数值相同!!!),而我们的目的是将this指向obj这个参数,那么怎么改this呢?我们知道this总是指向最后调用它的对象,所以我们要通过obj去调用一下person方法,就可以成功让this指向obj然后再删除这个调用。也就完成了this的显示绑定。
1.初实现:
(记住this总是指向最后调用它的那个对象!!!)
Function.prototype.myCall=function(obj)
{
console.log(this) //谁调用myCall谁就是this
obj.fn=this //令这个obj的一个属性=this
obj.fn()//通过调用(执行)绑定this到obj上
delete obj.fn //它的使命完成了 删除即可
}
2.进一步完善
我们还要在这个初级版本进一步考虑各种情况然后完善。
考虑1:若调用myCall的对象不为函数时怎么办?抛出错误就好啦。
考虑2:若不为它传参数怎么解决?this即为window。
Function.prototype.mycall=function(obj)
{
if( typeof obj=='function')
{
throw new TypeError('not a function')
}
obj=obj||window
obj.fn=this
obj.fn()
delete obj.fn
}
3.终极实现
但是我们还要考两个问题:
一是参数的问题,因为call方法第一个参数为this要指向的对象,第2-n个参数为要传给function的参数。2-n参数用…剩余参数以数组模式传入,然后调用这个函数时解构传入就好啦。
二是返回值,调用call函数默认是返回该函数的执行结果的,所以要保存一下执行结果并返回。
Function.prototype.myCall = function(context, ...args) {
// 如果context是null或undefined,设置为全局对象
// 如果context是一个基本类型,将其包装成对象
context = context == null ? globalThis : Object(context);
// 为了避免与context上已有的属性冲突,使用一个独一无二的key
const uniqueKey = Symbol('key');
// 将当前函数作为context的一个方法
context[uniqueKey] = this;
// 调用这个方法,并传入args
const result = context[uniqueKey](...args);
// 删除这个方法
delete context[uniqueKey];
// 返回结果
return result;
};
至此,call方法就已经完整实现了一遍。
二、apply的实现
apply和call的区别只是传参不同,apply第二个参数为一个数组,而call第2-n个参数类型不确定,其余都一样,所以在来试试叭~
Function.prototype.myApply = function(context, args) {
// 如果context是null或undefined,设置为全局对象
// 如果context是一个基本类型,将其包装成对象
context = context == null ? globalThis : Object(context);
// 如果args不是一个数组或类似数组的对象,抛出一个错误
if (typeof args !== 'undefined' && (typeof args !== 'object' || args === null)) {
throw new TypeError('CreateListFromArrayLike called on non-object');
}
// 为了避免与context上已有的属性冲突,使用一个独一无二的key
const uniqueKey = Symbol('key');
// 将当前函数作为context的一个方法
context[uniqueKey] = this;
// 调用这个方法,并传入args
const result = context[uniqueKey](...(args || []));
// 删除这个方法
delete context[uniqueKey];
// 返回结果
return result;
};
三、call、apply总结
所以说call和apply的内部过程就是:
1.给想要绑定的对象设置一个属性,且指向this(即需要调用的对象)
2.通过该对象调用该函数
3.结束调用后删除该属性
四、bind的实现
先来说一下它的实现原理,我们知道bind是返回一个函数但是不执行,什么时候调用什么时候执行,此外,bind有两种运用场景,一是普通函数调用、二是构造函数调用,所以还要判别一下调用它的函数类型。
实现流程大概如下:
bind实现思路:
判断是否是函数调用,若非函数调用抛异常
返回函数。
判断函数的调用方式,是否是被new出来的
new出来的话返回空对象,是new出来的话实例的__proto__指向_this的prototype。
不是则直接绑定this到第一个参数传入的对象上。
Function.prototype.myBind = function(context, ...args) {
const self = this; // 保存当前函数的引用
// 创建一个中间函数,用于处理原型链
function Temp() {}
// 创建一个新的绑定函数
function BoundFunction(...innerArgs) {
// 如果BoundFunction作为构造函数被调用,则使用新创建的对象作为this值
// 否则,使用指定的context作为this值
const isCalledAsConstructor = this instanceof BoundFunction;
const thisArg = isCalledAsConstructor ? this : context;
// 调用原函数,并将thisArg作为this值,同时合并外部参数和内部参数
const result = self.call(thisArg, ...args, ...innerArgs);
// 如果原函数返回了一个对象,那么将其作为绑定函数的返回值
// 否则,如果绑定函数作为构造函数被调用,将新创建的对象作为返回值
// 否则,返回原函数的返回值
return (typeof result === 'object' && result !== null) || typeof result === 'function'
? result
: isCalledAsConstructor
? thisArg
: undefined;
}
// 设置中间函数的原型为原函数的原型
Temp.prototype = self.prototype;
// 设置绑定函数的原型为中间函数的实例
// 这样,绑定函数的实例将能够访问原函数的原型链
BoundFunction.prototype = new Temp();
// 返回绑定函数
return BoundFunction;
};
或者直接用apply
Function.prototype.myBind = function(context, ...args) {
const self = this; // 保存当前函数的引用
// 返回一个新的函数
return function(...innerArgs) {
// 调用原函数,并将context作为this值,同时合并外部参数和内部参数
return self.apply(context, [...args, ...innerArgs]);
};
};