目標
- 定義什麼是this關鍵字
- 理解四種常用的方法去找出關鍵字this代表什麼
- 儘可能的在語句中不要使用this
'this' 是什麼
- JavaScript中的保留關鍵字
- 通常由函式的呼叫方式決定(我們稱之為執行上下文)
- 可以用四種規則來確定(全域性(預設繫結), 物件/隱式, 顯式, new)
1. 全域性環境/預設繫結(global context)
- 當“this”不在已宣告物件的內部時, this 會指向window
console.log(this) // Window
複製程式碼
function whatIsThis () {
return this;
}
whatIsThis()
複製程式碼
function variablesInThis(){this.person = 'elie'}
variablesInThis() //this ---> window
console.log(person) // elie
複製程式碼
- 嚴格模式下面
"use strict"
console.log(this); // window
function whatIsThis(){
return this;
}
whatIsThis(); // undefined
function variablesInThis(){
this.person = "Elie";
}
variablesInThis(); // TypeError, can't set person on undefined!
複製程式碼
2. 隱式繫結/物件 (Implicit/Object)
- 當“this”在已宣告物件的內部時, this會被繫結到物件,但是巢狀物件中this會丟失! 會丟失!
var person = {
firstName:'Elie',
sayHi: function() {
return "Hi" + this.firstName
},
determineContext: function() {
return this === person
}
}
// 測試
person.sayHi() // "Hi Elie"
person.determineContext(); // true
複製程式碼
思考 這裡的關鍵字“this”應該指什麼?
var person = {
firstName: "張三",
determineContext: this
}
person.determineContext; // window
複製程式碼
函式執行時定義一個關鍵字“this”!這裡沒有執行一個函式來建立關鍵字'this'的新值,所以'this'的值仍然是視窗!
- 巢狀物件 (Nested Objects) 當我們有一個巢狀物件的時候,this繫結會如何
var person = {
firstName: '張三',
sayHi: function () {
return 'Hi' + this.firstName
},
determineContext: function() {
return this === person
},
dog: {
sayHello: function() {
return 'Hello' + this.firstName
},
determineContext: function() {
return this === person
}
}
}
person.sayHi(); // "Hi張三"
person.determineContext(); // true
// 巢狀物件中的this
person.dog.sayHello(); // "Helloundefined"
person.dog.determineContext(); // false
複製程式碼
巢狀物件中的this 丟失了!!! 丟失了!!!
3. 顯式繫結 (Explicit Binding)
顯式繫結一般通過使用 call apply 和 bind ,三種方法的區別
方法名 引數 立即呼叫?
Call thisArg, a, b, c, d , ... YES
Apply thisArg, [a,b,c,d, ...] YES
Bind thisArg, a, b, c, d , ... NO
複製程式碼
- 顯式繫結call 修復了巢狀物件丟失this問題
var person = {
firstName: '張三',
sayHi: function () {
return 'Hi' + this.firstName
},
determineContext: function() {
return this === person
},
dog: {
sayHello: function() {
return 'Hello' + this.firstName
},
determineContext: function() {
return this === person
}
}
}
// 測試
person.dog.sayHello.call(person); // "Hello張三"
person.dog.determineContext.call(person); // true
複製程式碼
一個普通的例子
var javabook = {
name:'java book',
showBook: function() {
return 'this book"s name is ' + this.name
}
}
var phpbook = {
name:'php book',
showBook: function() {
return 'this book"s name is ' + this.name
}
}
javabook.showBook() // "this book"s name is php book"
phpbook.showBook() // "this book"s name is php book"
// 通過call 我們可以讓showBook() 成為大家的
function showBook() {
return 'this book"s name is ' + this.name
}
var cssbook = {
name:'css book'
}
var htmlbook = {
name: 'html book'
}
// test
showBook.call(cssbook)
// "this book"s name is css book"
showBook.call(htmlbook)
// "this book"s name is html book"
複製程式碼
再來一個call的例子
- 假設我們要選擇頁面上所有的“divs”
var divs = document.getElementsByTagName('divs') //類陣列物件
複製程式碼
- 然後我們想找到含有"hello"的div, 我們想使用filter
divs.filter // undefined 它是一個類似物件的陣列,所以過濾器不會起作用。
複製程式碼
- 解決辦法 使用陣列slice方法
Array.prototype.slice.call(arguments),你也可以簡單的使用 [].slice.call(arguments) 來代替 slice 不修改原陣列,只會返回一個淺複製了原陣列中的元素的一個新陣列。原陣列的元素會按照下述規則拷貝:
- 如果該元素是個物件引用 (不是實際的物件),slice 會拷貝這個物件引用到新的陣列裡。兩個物件引用都引用了同一個物件。如果被引用的物件發生改變,則新的和原來的陣列中的這個元素也會發生改變。
- 對於字串、數字及布林值來說(不是 String、Number 或者 Boolean 物件),slice 會拷貝這些值到新的陣列裡。在別的陣列裡修改這些字串或數字或是布林值,將不會影響另一個陣列。
var divsArray = [].slice.call(divs);
divsArray.filter(function(val){
return val.innerText === 'Hello';
})
複製程式碼
- Apply? 看程式碼
function showBook() {
return 'this book"s name is ' + this.name
}
var cssbook = {
name:'css book'
}
var htmlbook = {
name: 'html book'
}
// test
showBook.call(cssbook) // "this book"s name is css book"
showBook.apply(cssbook) // "this book"s name is css book"
// 這樣用看起來好像沒有區別
複製程式碼
似乎相同的…但是如果我們開始新增引數會發生什麼呢?
function addNumbers(a,b,c,d){
return this.name + "計算: " + (a+b+c+d);
}
var cssbook = {
name:'css book'
}
var htmlbook = {
name: 'html book'
}
addNumbers.call(cssbook,1,1,1,1)
// "css book計算: 4"
addNumbers.apply(cssbook,1,1,1,1)
// VM273:1 Uncaught TypeError: CreateListFromArrayLike called on non-object
// at <anonymous>:1:12
addNumbers.apply(cssbook,[1,1,1,1])
// "css book計算: 4"
複製程式碼
所以 我們什麼時候使用apply呢 當一個函式不接受一個陣列時,apply將為我們在一個陣列中展開值!
var nums = [5,7,1,4,2]
Math.max(nums) // NaN
Math.max.apply(this, nums) //7
複製程式碼
function sumValues(a,b,c){
return a+b+c;
}
var values = [4,1,2];
sumValues(values); // "4,1,2undefinedundefined"
sumValues.apply(this,[4,1,2]); // 7
複製程式碼
- 再來看看bind
引數的工作方式類似於call,但是bind返回一個具有“this”繫結上下文的函式! 不會立刻執行需要呼叫
function addNumbers(a,b,c,d){
return this.name + " just calculated " + (a+b+c+d);
}
var htmlbook = {
name: 'html book'
}
// test
var htmlbookCalc = addNumbers.bind(htmlbook, 1,2,3,4) // f(){}
htmlbookCalc() // "html book just calculated 10"
複製程式碼
bind的使用
在我們不想馬上執行的函式中!,通常我們會失去“this”的上下文
var colt = {
firstName: "Colt",
sayHi: function(){
setTimeout(function(){
console.log("Hi " + this.firstName);
},1000);
}
}
// test
colt.sayHi(); // Hi undefined (1000 milliseconds later)
複製程式碼
丟掉了 以前的解決方法 利用作用域儲存this
var colt = {
firstName: "Colt",
sayHi: function(){
var that = this
setTimeout(function(){
console.log("Hi " + that.firstName);
},1000);
}
}
// test
colt.sayHi(); // Hi Colt
複製程式碼
使用bind來設定“this”的正確上下文
var colt = {
firstName: "Colt",
sayHi: function(){
setTimeout(function(){
console.log("Hi " + this.firstName);
}.bind(this),1000);
}
}
colt.sayHi(); // Hi Colt
複製程式碼
4. new繫結
我們可以使用“new”關鍵字來設定關鍵字“this”的上下文
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
// test
var Lily = new Person("Lily", "Study")
Lily.firstName; // "Lily"
Lily.lastName; // "Study"
複製程式碼
總結
- 關鍵字“this”是JavaScript中的保留關鍵字,其值在執行時確定
- 它可以使用全域性上下文、物件繫結、顯式繫結或new關鍵字來設定
- 當在函式的全域性上下文中設定時,它要麼是全域性物件(在瀏覽器中是視窗),要麼是未定義的物件(如果我們使用嚴格模式)
- 要顯式地設定關鍵字“this”的值,我們使用call、apply或bind
- 我們還可以使用“new”關鍵字來設定“this”的上下文