模擬實現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

複製代碼

收工~

相關文章
相關標籤/搜索