一、call 方法的模拟
在实现 call 方法之前,我们先来看一个 call 的调用示范:
var me = {
name: 'xiaoming'
}
function showName() {
console.log(this.name)
}
showName.call(me) // xiaoming
结合 call 表现出的特性,我们首先至少能想到以下两点:
1、call 是可以被所有的函数继承的,所以 call 方法应该被定义在 Function.prototype 上
2、call 方法做了两件事:
- 改变 this 的指向,将 this 绑到第一个入参指定的的对象上去;
- 根据输入的参数,执行函数。
结合这两点,我们一步一步来实现 call 方法。首先,改变 this 的指向:
showName 在 call 方法调用后,表现得就像是 me 这个对象的一个方法一样。
所以我们最直接的一个联想是,如果能把 showName 直接塞进 me 对象里就好了,像这样:
var me = {
name: 'xiaoming',
showName: function() {
console.log(this.name)
}
}
me.showName()
用户在传入 me 这个对象的时候, 想做的仅仅是让 call 把 showName 里的 this 给改掉,而不想给 me 对象新增一个 showName 方法。来写第一版代码:
Function.prototype.myCall = function(context) {
// step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
context.func = this
// step2: 执行函数
context.func()
// step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
delete context.func
}
call方法允许传入参数:
Function.prototype.myCall = function(context, ...args) {
// step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
context.func = this
// step2: 执行函数,利用扩展运算符将数组展开
context.func(...args)
// step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
delete context.func
}
this 参数可以传 null,当为 null 的时候,视为指向 window:
Function.prototype.myCall = function (context,...args) {
var context = context || window;
context.fn = this;
context.func(...args)
delete context.fn
}
二、apply 方法的模拟
apply 的实现跟 call 类似,只是传参不同:
Function.prototype.myApply = function (context,args) {
var context = context || window;
context.fn = this;
context.func(...args)
delete context.fn
}
三、bind 方法的模拟
bind函数的两个特点:
- 返回一个函数
- 可以传入参数
Function.prototype.myBind = function (context,...outArgs) {
return (...args) => this.call(context,...outArgs,...args)
}