this、call、apply、bind
This指標
每一個方法或函式都會有一個this物件,this物件是方法(或函式)在執行時的那個環境,也可以說是這個函式在那個作用域下執行的。說的更通俗一點:this就相當於咱們平時說話時候說的“我”,“我家”的概念。就是說當一個方法在執行的時候,它是屬於誰的。它在執行的時候它的家是誰家。
在 ES5 中,其實 this 的指向,始終堅持一個原理:this 永遠指向最後呼叫它的那個物件
例1:物件的呼叫
var person = { name: 'Demi', describe: function () { return '姓名:' + this.name; } }; console.log(person.describe()) // 姓名:Demi
上面程式碼中,this.name是在describe方法中呼叫,而describe方法所在的當前物件是person,因此this最後呼叫的那個物件就是person, this.name就是person.name
例2:物件賦值給另外一個物件呼叫
var A = { name: 'Demi', describe: function () { return '姓名:' + this.name; } }; var B = { name: 'dingding' }; B.describe = A.describe; console.log(B.describe()) // 姓名:dingding
上面程式碼中,A.describe屬性被賦給B,於是B.describe就表示describe方法所在的當前物件是B,所以this.name就指向B.name
例3:函式呼叫
var name = "windowsName"; function a() { var name = "Demi"; console.log(this.name); // windowsName console.log("inner:" + this); // inner:[object Window] } a(); console.log("outer:" + this); // outer:[object Window]
上面程式碼中,this.name是在a()函式中呼叫, a()前面沒有呼叫的物件,那麼this會指向頂層物件Window,相當於window.a()
例4:this 永遠指向最後呼叫它的那個物件,不會向上一個物件尋找未定義變數
var name = "windowsName";
var a = {
// name: "Demi",
fn: function () {
console.log(this.name); // undefined
}
}
window.a.fn();
上面程式碼中,最後呼叫fn()的物件是a, 所以this指向的是a物件,也就是說 fn 的內部的 this 是物件 a,而物件 a 中並沒有對 name 進行定義,所以 log 的this.name的值是 undefined
例5:匿名函式this指向window
var obj = {
name: 'Demi',
times: [1, 2, 3],
print: function () {
this.times.forEach(function (n) {
console.log(this.name);
});
}
};
obj.print(); // 無輸出
上面程式碼中,obj.print內部this.times的this是指向obj的,這個沒有問題。但是,forEach方法的回撥函式內部的this.time卻是指向全域性物件,導致沒有辦法取到值。
如何改變this指向
this的動態切換,固然為 JavaScript 創造了巨大的靈活性,但也使得程式設計變得困難和模糊。有時,需要把this固定下來,避免出現意想不到的情況。改變this指向有以下幾種方法:
- 使用ES6箭頭函式
- 在函式內部使用_this=this
- JavaScript 提供了call、apply、bind這三個方法來切換this的指向
- new例項化一個物件
箭頭函式
this物件的指向是可變的,但是在箭頭函式中,它是固定的。箭頭函式體內的this物件,就是定義時所在的物件,而不是呼叫時所在的物件。
例5:通過箭頭函式改變原來指向window的this
let name = "windowsName";
let a = () => {
let name = "Demi";
console.log(this.name); // Demi
console.log("inner:" + this); // inner:[object a]
}
a();
console.log("outer:" + this); // outer:[object Window]
上面程式碼中,原來是window呼叫a()函式,this指向的是window,將函式改成箭頭函式,this指向定義時所在的物件,也就是說this.name指向a
在函式內部使用_this = this
那麼這種方式應該是最簡單的不會出錯的方式了,我們是先將呼叫這個函式的物件儲存在變數 _this中,然後在函式中都使用這個 _
this,這樣 _this就不會改變了
var obj = {
name: 'Demi',
times: [1, 2, 3],
print: function () {
var _this = this
this.times.forEach(function (n) {
console.log(_this.name);
});
}
};
obj.print(); // Demi Demi Demi
Function.prototype.call()
函式例項的call
方法,可以指定函式內部this
的指向(即函式執行時所在的作用域),然後在所指定的作用域中,呼叫該函式。
使用格式:func.apply(thisValue, arg1, arg2, ...) call
的第一個引數就是this
所要指向的那個物件,後面的引數則是函式呼叫時所需的引數。this.Value為空、null、undefined,則預設傳入全域性物件。
var obj = {
name: 'Demi'
}
function a() {
console.log(this.name);
};
a.call(obj); // Demi
function add(a, b) {
return a + b;
}
add.call(this, 1, 2) // 3
Function.prototype.apply()
apply方法的作用與call方法類似,也是改變this指向,然後再呼叫該函式。唯一的區別就是,它接收一個數組作為函式執行時的引數,使用格式如下。
使用格式:func.apply(thisValue, [arg1, arg2, ...]) 第一個引數也是this
所要指向的那個物件,如果thisValue設為null
或undefined
,則預設傳入全域性物件。第二個引數則是一個數組,該陣列的所有成員依次作為引數,傳入原函式。原函式的引數,在call
方法中必須一個個新增,但是在apply
方法中,必須以陣列形式新增。
function a(x, y) {
console.log(x + y);
};
a.call(null, 1, 1); // 2
a.call(null, [1, 1]); // 2
Function.prototype.bind()
bind()方法建立一個新的函式, 當被呼叫時,將其this關鍵字設定為提供的值,在呼叫新函式時,在任何提供之前提供一個給定的引數序列。bind 是建立一個新的函式,我們必須要手動去呼叫它才會執行。
使用格式:func.apply(thisValue, arg1, arg2, ...) () 第一個引數就是this
所要指向的那個物件,後面的引數則是函式呼叫時所需的引數。this.Value為空、null、undefined,則預設傳入全域性物件。
var a = {
name: "Demi",
fn: function (a, b) {
console.log(a + b)
}
}
var b = a.fn;
b.bind(a, 1, 2)() // 3
使用建構函式呼叫函式
如果函式呼叫前使用了 new 關鍵字, 則是呼叫了建構函式。
這看起來就像建立了新的函式,但實際上 JavaScript 函式是重新建立的物件
// 建構函式:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var a = new myFunction("Ding", "Demi");
console.log(a.lastName); // 返回 "Demi"
new例項化過程
- 建立一個空物件 obj;
- 將新建立的空物件的隱式原型指向其建構函式的顯示原型。
- 使用 call 改變 this 的指向
- 如果無返回值或者返回一個非物件值,則將 obj 返回作為新物件;如果返回值是一個新物件的話那麼直接直接返回該物件。
所以我們可以看到,在 new 的過程中,我們是使用 call 改變了 this 的指向。
var a = new myFunction("Ding","Demi");
new myFunction{
var obj = {};
obj.__proto__ = myFunction.prototype;
var result = myFunction.call(obj,"Ding","Demi");
return typeof result === 'obj'? result : obj;
}