call的实现
先看下call的实例
let person = {
name: 'SpwaN'
}
function getName() {
console.log(this.name)
}
getName.call(person) // SpwaN
call的特点:
- 改变this指向
- 直接调用函数
- 传null的时候,指向window
- 可以传参,参数分开传
然后我们一个一个实现,因为我们需要向原生 call 一样调用,所以就在 Function 原型上扩展其方法。
Function.prototype.call1 = function (context, ...args) {
context.fn = this // 解决第一步
var context = context || window // 解决第三步
context.fn(...args) // 解决第二步,第四步
delete context.fn // 处理小尾巴
}
但是存在几个问题:
- 我们是在
context
上直接扩展了一个名为fn
的属性,但是如果context
本身就存在该属性,就会造成覆盖问题。 - 如果我们对
call1
传入一个基本数据类型,就会报错。 - 没有返回值
Function.prototype.call1 = function (context, ...args) {
if (context === null || context === undefined) { // 修复Bug2
context = window
} else {
context = Object(context)
}
const symbol = Symbol() // 修复Bug1
context[symbol] = this
const result = context[symbol](...args)
delete context[symbol]
return result // 修复Bug3
}
这样就完美实现了 call 方法,现在我们看看 apply 的实现,他俩最大的区别就是传参不同,apply 接收一个 Array 类型参数
apply 的实现
apply 的特点:
- 改变this指向
- 直接调用函数
- 传null的时候,指向window
- 可以传参,参数是个数组
Function.prototype.apply1 = function (context, args = []) {
return this.call1(context, ...args)
}
这里我们直接利用我们之前实现的 call1
方法就可以了,只需要把参数拆开传给 call1
即可。
其实最难的是 bind
的实现。
bind的实现
bind的特点
- 改变this指向
- 返回一个函数
- 传null的时候,指向window
- 可以传参,参数分开传,也可以先传一部分,再调用函数的时候再传一部分
- 返回的函数如果当做构造函数的话,那bind中的this将无效,但参数依然生效
// 第一版 实现this绑定和传参 (未实现当把return的函数当做构造函数时候的this绑定失效)
Function.prototype.bind1 = function (context) {
var _this = this
// 拿到除绑定对象之后的参数
var args = Array.prototype.slice.call(arguments, 1)
return function () {
// 拿到返回函数的所有参数
var newArgs = Array.prototype.slice.call(argument)
return _this.apply(this, args.concat(newArgs))
}
}
// 第二版
Function.prototype.bind2 = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1)
var middlewares = {}
var f = function () {
var newArgs = Array.prototype.slice.call(arguments)
// 如果this等于实例,那么就把this绑定到实例上,如果不是就把bind上绑定的this添加过来
_this.apply(this instanceof f ? this : context, args.concat(newArgs))
}
middlewares .prototype = this.prototype
// new一个中间函数 防止修改f的原型而改变了this的原型
f.prototype = new middlewares()
return f
}
// 第三版 ES6写法
Function.prototype = function (context, ...args) {
let _this = this
let obj = {}
let f = function (...newArgs) {
return _this.apply(this instanceof f ? this : context, [...args, ...newArgs])
}
obj.prototype = this.prototype
f.prototype = new obj()
return f
}
第一版:
Function.prototype = function(context, ...args) {
return () => this.apply1(context, args)
}
现在我们实现了 bind 特点的 1,2,3。接着我们需要再来实现特点 4
Function.prototype = function(context, ...args) {
return (...arguments) => this.apply1(context, [...args, ...arguments])
}
bind
的特点5,可以看一下例子:
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
而我们现在实现的效果在下面的例子中是会报错的。
var foo = {
name: 'haha'
}
function bar() {
console.log(5)
}
const x = bar.bind(foo, 3, 4, 5)
new x()
所以我们需要实现特点 5:
Function.prototype = function(context, ...args) {
const _this = this // 这里需要保存着之前的 this
const bound = function(...extraArgs) { // 这里不要用 arrow function,会改变 this 指向,我们需要拿到 new Fun 之后的实例
return _this.apply1(this instance of bound ? this : context, [...args, ...extraArgs])
}
bound.prototype = this.prototype // 这里需要把原型挂载到 bound 的原型上
return bound
}
看起来比较完美,但有一个问题,现在我们修改 bound
原型,其 this
原型也会相应被修改。
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
const x = bar.bind1(foo, '18')
x.prototype.friend = 'SpawN'
console.dir(x)
console.dir(bar)
看看 x
和 bar
原型上的 friend
的值是否都发生了变化。
所以我们需要需要使用一个中间对象进行隔离。
Function.prototype = function(context, ...args) {
const _this = this // 这里需要保存着之前的 this
const fn = function(){}
const bound = function(...extraArgs) { // 这里不要用 arrow function,会改变 this 指向,我们需要拿到 new Fun 之后的实例
return _this.apply1(this instance of bound ? this : context, [...args, ...extraArgs])
}
fn.prototype = this.prototype
bound.prototype = new fn()
return bound
}
当然也可以使用 Object.create
来实现
Function.prototype = function(context, ...args) {
const _this = this // 这里需要保存着之前的 this
const bound = function(...extraArgs) { // 这里不要用 arrow function,会改变 this 指向,我们需要拿到 new Fun 之后的实例
return _this.apply1(this instance of bound ? this : context, [...args, ...extraArgs])
}
bound.prototype = Object.create(this.prototype)
return bound
}