Javascript中的關鍵字'this'學習筆記

你的好友上線了發表於2018-06-12

目標

  • 定義什麼是this關鍵字
  • 理解四種常用的方法去找出關鍵字this代表什麼
  • 儘可能的在語句中不要使用this

'this' 是什麼

  • JavaScript中的保留關鍵字
  • 通常由函式的呼叫方式決定(我們稱之為執行上下文)
  • 可以用四種規則來確定(全域性(預設繫結), 物件/隱式, 顯式, new)

1. 全域性環境/預設繫結(global context)

  1. 當“this”不在已宣告物件的內部時, this 會指向window
  console.log(this)  // Window 
複製程式碼
   function whatIsThis () {
     return this;
   }
   whatIsThis()
複製程式碼
  function variablesInThis(){this.person = 'elie'}
  variablesInThis() //this ---> window
  console.log(person) //  elie
複製程式碼
  1. 嚴格模式下面
   "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)

  1. 當“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'的值仍然是視窗!

  1. 巢狀物件 (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 
複製程式碼
  1. 顯式繫結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的例子

  1. 假設我們要選擇頁面上所有的“divs”
  var divs = document.getElementsByTagName('divs')  //類陣列物件
複製程式碼
  1. 然後我們想找到含有"hello"的div, 我們想使用filter
divs.filter // undefined  它是一個類似物件的陣列,所以過濾器不會起作用。
複製程式碼
  1. 解決辦法 使用陣列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';
})
複製程式碼
  1. 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
複製程式碼
  1. 再來看看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"
複製程式碼

總結

  1. 關鍵字“this”是JavaScript中的保留關鍵字,其值在執行時確定
  2. 它可以使用全域性上下文、物件繫結、顯式繫結或new關鍵字來設定
  3. 當在函式的全域性上下文中設定時,它要麼是全域性物件(在瀏覽器中是視窗),要麼是未定義的物件(如果我們使用嚴格模式)
  4. 要顯式地設定關鍵字“this”的值,我們使用call、apply或bind
  5. 我們還可以使用“new”關鍵字來設定“this”的上下文

相關文章