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。