模拟实现apply/call/bind

call()apply()的区别在于,call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组数组

callbash

需知足:app

1.改变this的指向。函数

2.新对象能够执行该函数。测试

3.考虑this为null和undefined时,this指向window。 this为基本类型时,原生的call会用Object自动转换。ui

4.能传入参数。this

Function.prototype.call1 = function (context) {
	context = context ? Object(context) : window // 实现3
	// 模拟传入的对象中有一个调用该对象的函数
	// 做用是为了改变函数的做用域指向该对象
	context.fn = this
	
	//接收参数,如有。
	let args = [...arguments].slice(1) // 第0个为this
	let result = context.fn(...args) // 执行fn
	delete context.fn //删除fn
	
	return result
}
复制代码

测试一下:spa

var value = 111;

var obj = {
    value: 999
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

function foo() {
    console.log(this);
}

bar.call1(null); // 111
foo.call1(520); // Number {520, fn: ƒ}

bar.call1(obj, 'jarva', 3);
// 999
// {
//   age: 3
//	 name: "jarva"
//	 value: 999
//  }
复制代码

applyprototype

与call实现基本一致code

Function.prototype.apply1 = function (context) {
    context = context ? Object(context) : window
    context.fn = this
  
    let result;
    if (arguments[1]) {
		result = context.fn(...arguments[1])  
    } else {
        result = context.fn()
    }
      
    delete context.fn
    return result;
}
复制代码

bind

bind() 方法会建立一个新函数,当这个新函数被调用时,它的 this 值是传递给 bind() 的第一个参数,传入bind方法的第二个以及之后的参数加上绑定函数运行时自己的参数按照顺序做为原函数的参数来调用原函数。bind返回的绑定函数也能使用 new 操做符建立对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

bind 方法与 call / apply 最大的不一样就是前者返回一个绑定上下文的函数,然后二者是直接执行了函数。

需知足:

1.指定this。

2.返回函数。

3.传入参数。

4.对new类型时,须要忽略this。

如今先考虑前三个条件,实现以下:

Function.prototype.bind1 = function(context) {
	let _this = this // 记住当前做用域,指向调用者。

	let args = Array.prototype.slice.call(arguments, 1) // 去掉第一个this参数
	// let args = [].slice.call(arguments, 1)
	// let args = [...arguments].slice(1)
	
	return function () {
        // 由于 bind 能够实现相似这样的代码 fn.bind(obj, 1)(2) 
	    // 因此要合并返回参数
		// let bindArgs = Array.prototype.slice.call(arguments);
		let bindArgs = [...arguments]
		return _this.apply(context, args.concat(bindArgs)) // 指定this。
	}
}
复制代码

测试用例

var value = 111

var foo = {
    value: 999
};

function bar(name, age) {
    return {
		value: this.value,
		name: name,
		age: age
    }
}

var bindFoo = bar.bind1(foo, "Jack")
bindFoo(20);
//{ age: 20, name: "Jack", value: 999 }
复制代码

一个绑定函数也能使用new操做符建立对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

如今来实现完整的bind模拟。

Function.prototype.bind2 = function(context) {

	// 若是调用的不是函数,要抛出异常
	if (typeof this !== 'function') {
	    throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
	}
	let _this = this // 记住当前做用域,指向调用者。

	let args = Array.prototype.slice.call(arguments, 1) // 去掉第一个this参数
	// let args = [].slice.call(arguments, 1)
	// let args = [...arguments].slice(1)
	var fn = function () {
        // 由于 bind 能够实现相似这样的代码 fn.bind(obj, 1)(2) 
	    // 因此要合并返回参数
		// let bindArgs = Array.prototype.slice.call(arguments)
		let bindArgs = [...arguments]
		// 看成为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 true,可让实例得到来自绑定函数的值
		return _this.apply(this instanceof fn ? this : context, args.concat(bindArgs)) // 指定this。
	}
	// 还要考虑修改返回函数的prototype为绑定函数的prototype,
	// 使得实例能够继承原型的值。 
	// 为了修改fn.prototype时不影响原型的值,使用ES5的 Object.create()方法生成一个新对象
	fn.prototype = Object.create(this.prototype)
	return fn
}	

复制代码

测试一下

// 测试用例
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.bind2(foo, 'Jack'); // bind2
var obj = new bindFoo(20); // 返回正确
// undefined
// Jack
// 20

obj.habit; // 返回正确
// shopping

obj.friend; // 返回正确
// kevin

obj.__proto__.friend = "Kitty"; // 修改原型

bar.prototype.friend; // kevin

复制代码

收工~

相关文章
相关标签/搜索