call apply bind 箭頭函數 的實現原理

call apply bind 箭頭函數 的實現原理

(一)call 源碼

call() 和 apply() 類似於借用,相當於 A 對象內部有了 B 對象的方法,實質是函數在運行時指定 this 值,打破瞭解析器在函數調用時創建執行環境時this綁定的規則。

例子:

const val = {
    a:1
}
function add(b,c){
    return this.a + b + c
}
console.log(add.call(val,2,3))	//6

相當於:

1.給 val 對象增加方法 add

2.執行 val 對象的方法 add

3.刪除 add 方法

val.add = function(b,c){...}
console.log(val.add(2,3))	//6
delete val.add

call 原生實現:

Function.prototype.callFunc = function(context){
    var context = context || window		//undefined 時 this 綁定在全局
    context.fn = this
    var args = []
    for(var i = 1, len = arguments.length; i < len; i++){
        args.push('arguments[' + i + ']')		//i 爲1,除去第一個參數
    }
    var result = eval('context.fn(' + args + ')')	//eval 執行字符串內容
    delete context.fn
    return result
}

通過在第一個參數 context 的屬性中存儲了調用 callFunc 函數的方法,然後再在 context 的執行環境中調用該屬性(即執行該方法)

es6 的 rest 寫法:

Function.prototype.callFunc = function(context){
    var context = context || window		//undefined 時 this 綁定在全局
    context.fn = this
    var args = []
    for(var i = 1, len = arguments.length; i < len; i++){
        args.push(arguments[i])		//i 爲1,除去第一個參數
    }
    var result = context.fn(...args)
    delete context.fn
    return result
}

(二)apply 源碼

和 call 發生的過程一樣,不過需要判斷第二個參數是否是數組

apply 原生實現:

Function.prototype.applyFn = function(context,arr){
    var context = context || window
    context.fn = this	
    var result
    if(!arr){		//不輸入第二個參數時
        result = context.fn();
    }else{
        if(!(arr instanceof Array))
            throw new Error('params must be array');
        else
            result = eval('context.fn('+arr+')');
            // es6 的 rest 寫法: var result = context.fn(...args)
    }
    delete context.fn
    return result
}

(三)bind 源碼

bind 和 call、apply 表現有很大差別

區別:

(1)bind() 方法會創建一個新函數。 當這個新函數被調用時,bind() 的第一個參數將作爲它運行時的 this,之後的一系列參數將會在傳遞的實參後傳入作爲它的參數。

例子:

function test(c,d){
	this.a = 1
	console.log(this.a + this.b + c + d)
}
var func = test.bind({b: 2}, 3);
func(4)		//1+2+3+4,輸出爲10

由此可知與 call、apply 不同,該方法返回的是一個函數而不是執行結果

(2)通過 new 的方式創建這個綁定對象的實例,該實例的 bind() 函數對於 this 的綁定將失效,但是傳遞的參數仍保留

function test(c,d){
 	this.a = 1
	console.log(this.a + this.b + c + d)
}
var func = test.bind({b: 2}, 3);
new func(4)		//輸出 NaN,因爲該實例內 this.b 爲 undefined
func(4)			//1+2+3+4,輸出爲10

bind 源碼:

Function.prototype.bind = function(context){
	if (typeof this !== "function") 
		throw new TypeError('what is trying to be bound is not callback');
		
	var _this =  this			//保存原函數的執行環境
	var args = Array.prototype.slice.call(arguments,1)	//保存之前傳入參數
	
	var sub = function(){
		//把之前傳入的參數與之後傳入的參數合併
		/注意這裏的 arguments 與 函數外的 arguments 不是同一個
		var argsBind = args.concat(Array.prototype.slice.call(arguments))
		
		//判斷函數是作爲構造函數還是普通函數
		//如果是構造函數,返回 true ,把 this 綁定在該函數實例
		//如果是普通函數,則 this 繼續綁定在 context 中
		var  env  = this instanceof Super ? this : context;
		
		return _this.apply(env,argsBind )
	}
	
	function Super(){}
	Super.prototype = this.prototype;
	sub.prototype  = new Super()
	//上面三句話相當於 sub.prototype = Object.create(this.prototyoe)
	
	return sub 	//返回函數
}

1.最開始通過 args 保存之前傳入的參數實現閉包,而後使用返回函數的 arguments (後來傳入的參數)和 args 拼接,實現了傳入參數的保存

2.通過判斷返回函數 sub 中的 this 是否 Super 的實例來判斷函數是作爲構造函數還是普通函數,因爲如果是構造函數,new 實例化後這個 this 指向 sub 的實例,而 sub 繼承 Super ,所以該實例是 Super 的實例;如果是普通函數,則這個 this 指向 sub 這個構造函數本身,不是 Super 的實例

(四)箭頭函數

直接上箭頭函數轉化成 ES5 例子

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

由此可知,箭頭函數裏面根本沒有自己的this,而是引用外層的this。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章