手写call,apply,bind

call的实现

先看下call的实例

let person = {
	name: 'SpwaN'
}
function getName() {
	console.log(this.name)
}
getName.call(person)   // SpwaN

call的特点:

  1. 改变this指向
  2. 直接调用函数
  3. 传null的时候,指向window
  4. 可以传参,参数分开传

然后我们一个一个实现,因为我们需要向原生 call 一样调用,所以就在 Function 原型上扩展其方法。

Function.prototype.call1 = function (context, ...args) {
	context.fn = this                  // 解决第一步
	var context = context || window    // 解决第三步
	context.fn(...args)                // 解决第二步,第四步
	delete context.fn                  // 处理小尾巴
}

但是存在几个问题:

  1. 我们是在 context 上直接扩展了一个名为 fn 的属性,但是如果 context 本身就存在该属性,就会造成覆盖问题。
  2. 如果我们对 call1 传入一个基本数据类型,就会报错。
  3. 没有返回值
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 的特点:

  1. 改变this指向
  2. 直接调用函数
  3. 传null的时候,指向window
  4. 可以传参,参数是个数组
Function.prototype.apply1 = function (context, args = []) {
  return this.call1(context, ...args)
}

这里我们直接利用我们之前实现的 call1 方法就可以了,只需要把参数拆开传给 call1 即可。
其实最难的是 bind 的实现。

bind的实现

bind的特点

  1. 改变this指向
  2. 返回一个函数
  3. 传null的时候,指向window
  4. 可以传参,参数分开传,也可以先传一部分,再调用函数的时候再传一部分
  5. 返回的函数如果当做构造函数的话,那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)

看看 xbar 原型上的 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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值