1. 程式人生 > 其它 >♠ JS函式的this指向

♠ JS函式的this指向

為什麼需要this?

在常見的程式語言中,幾乎都有this這個關鍵字(Objective-C中使用的是self),但是JavaScript中的this和常見的面向物件語言中的this不太一樣:常見面向物件的程式語言中,比如Java、C++、Swift、Dart等等一系列語言中,this通常只會出現在類的方法中。也就是你需要有一個類,類中的方法(特別是例項方法)中,this代表的是當前呼叫物件。但是JavaScript中的this更加靈活,無論是它出現的位置還是它代表的含義。

我們來看一下編寫一個obj的物件,有this和沒有this的區別

點選檢視程式碼
// 從某些角度來說, 開發中如果沒有this, 很多的問題我們也是有解決方案
// 但是沒有this, 會讓我們編寫程式碼變得非常的不方便
var obj100 = {
  name: "why",
  eating: function() {
    console.log(this.name + "在吃東西")
  },
  running: function() {
    console.log(this.name + "在跑步")
  },
  studying: function() {
    console.log(this.name + "在學習")
  }
}

var info = {
  name: "why",
  eating: function() {
    console.log(this.name + "在吃東西")
  },
  running: function() {
    console.log(this.name + "在跑步")
  },
  studying: function() {
    console.log(this.name + "在學習")
  }
}

var person = {
  name: "kobe",
  eating: function() {
    console.log(this.name + "在吃東西")
  },
  running: function() {
    console.log(this.name + "在跑步")
  },
  studying: function() {
    console.log(this.name + "在學習")
  }
}

obj100.eating()
obj100.running()
obj100.studying()

♣ this指向什麼呢?

我們先說一個最簡單的,this在全域性作用於下指向什麼?

這個問題非常容易回答,在瀏覽器中測試就是指向window但是,開發中很少直接在全域性作用於下去使用this,通常都是在函式中使用所有的函式在被呼叫時,都會建立一個執行上下文:這個上下文中記錄著函式的呼叫棧、AO物件等;this也是其中的一條記錄;

點選檢視程式碼
// 在大多數情況下, this都是出現在函式中
// 在全域性作用域下
// 瀏覽器: window(globalObject)
// Node環境: {}
console.log(this)
// console.log(window)

node是空物件

  • 1.函式在呼叫時,JavaScript會預設給this繫結一個值;
  • 2.this的繫結和定義的位置(編寫的位置)沒有關係;
  • 3.this的繫結和呼叫方式以及呼叫的位置有關係;
  • 4.this是在執行時被繫結的;

那麼this到底是怎麼樣的繫結規則呢?一起來學習一下吧

  • 繫結一:預設繫結;
  • 繫結二:隱式繫結;
  • 繫結三:顯示繫結;
  • 繫結四:new繫結;

預設繫結

什麼情況下使用預設繫結呢?獨立函式呼叫。獨立的函式呼叫我們可以理解成函式沒有被繫結到某個物件上進行呼叫;

點選檢視程式碼
// 預設繫結: 獨立函式呼叫
// 1.案例一:
// function foo() {
//   console.log(this)
// }

// foo()

// 2.案例二:
// function foo1() {
//   console.log(this)
// }

// function foo2() {
//   console.log(this)
//   foo1()
// }

// function foo3() {
//   console.log(this)
//   foo2()
// }

// foo3()


// 3.案例三:
// var obj = {
//   name: "why",
//   foo: function() {
//     console.log(this)
//   }
// }

// var bar = obj.foo
// bar() // window


// 4.案例四:
// function foo() {
//   console.log(this)
// }
// var obj = {
//   name: "why",
//   foo: foo
// }

// var bar = obj.foo
// bar() // window

// 5.案例五:
function foo() {
  function bar() {
    console.log(this)
  }
  return bar
}

var fn = foo()
fn() // window

var obj = {
  name: "why",
  eating: fn
}

obj.eating() // 隱式繫結

隱式繫結

另外一種比較常見的呼叫方式是通過某個物件進行呼叫的:也就是它的呼叫位置中,是通過某個物件發起的函式呼叫。

點選檢視程式碼
// 隱式繫結: object.fn()
// object物件會被js引擎繫結到fn函式的中this裡面

function foo() {
  console.log(this)
}

// 獨立函式呼叫
// foo()

// 1.案例一:
// var obj = {
//   name: "why",
//   foo: foo
// }

// obj.foo() // obj物件

// 2.案例二:
// var obj = {
//   name: "why",
//   eating: function() {
//     console.log(this.name + "在吃東西")
//   },
//   running: function() {
//     console.log(obj.name + "在跑步")
//   }
// }

// // obj.eating()
// // obj.running()

// var fn = obj.eating
// fn()


// 3.案例三:
var obj1 = {
  name: "obj1",
  foo: function() {
    console.log(this)
  }
}

var obj2 = {
  name: "obj2",
  bar: obj1.foo
}

obj2.bar()

顯示繫結

隱式繫結有一個前提條件:

  • 必須在呼叫的物件內部有一個對函式的引用(比如一個屬性);
  • 如果沒有這樣的引用,在進行呼叫時,會報找不到該函式的錯誤;
  • 正是通過這個引用,間接的將this繫結到了這個物件上;

如果我們不希望在 物件內部 包含這個函式的引用,同時又希望在這個物件上進行強制呼叫,該怎麼做呢?

JavaScript所有的函式都可以使用call和apply方法(這個和Prototype有關)。

其實非常簡單,第一個引數是相同的,後面的引數,apply為陣列,call為引數列表;這兩個函式的第一個引數都要求是一個物件,這個物件的作用是什麼呢?就是給this準備的。在呼叫這個函式時,會將this繫結到這個傳入的物件上。因為上面的過程,我們明確的綁定了this指向的物件,所以稱之為 顯示繫結

點選檢視程式碼
// function foo() {
//   console.log("函式被呼叫了", this)
// }

// 1.foo直接呼叫和call/apply呼叫的不同在於this繫結的不同
// foo直接呼叫指向的是全域性物件(window)
// foo()

// var obj = {
//   name: "obj"
// }

// call/apply是可以指定this的繫結物件
// foo.call(obj)
// foo.apply(obj)
// foo.apply("aaaa")


// 2.call和apply有什麼區別?
function sum(num1, num2, num3) {
  console.log(num1 + num2 + num3, this)
}

sum.call("call", 20, 30, 40)
sum.apply("apply", [20, 30, 40])

// 3.call和apply在執行函式時,是可以明確的繫結this, 這個繫結規則稱之為顯示繫結

如果我們希望一個函式總是顯示的繫結到一個物件上,可以怎麼做呢?

點選檢視程式碼
function foo() {
  console.log(this)
}

// foo.call("aaa")
// foo.call("aaa")
// foo.call("aaa")
// foo.call("aaa")

// 預設繫結和顯示繫結bind衝突: 優先順序(顯示繫結)
var newFoo = foo.bind("aaa")

newFoo()
newFoo()
newFoo()
newFoo()
newFoo()
newFoo()

var bar = foo
console.log(bar === foo)
console.log(newFoo === foo)

♣ new繫結

JavaScript中的函式可以當做一個類的建構函式來使用,也就是使用new關鍵字。使用new關鍵字來呼叫函式是,會執行如下的操作:

  • 1.建立一個全新的物件;
  • 2.這個新物件會被執行prototype連線;
  • 3.這個新物件會繫結到函式呼叫的this上(this的繫結在這個步驟完成);
  • 4.如果函式沒有返回其他物件,表示式會返回這個新物件;

點選檢視程式碼
// 我們通過一個new關鍵字呼叫一個函式時(構造器), 這個時候this是在呼叫這個構造器時創建出來的物件
// this = 創建出來的物件
// 這個繫結過程就是new 繫結

function Person(name, age) {
  this.name = name
  this.age = age
}

var p1 = new Person("why", 18)
console.log(p1.name, p1.age)

var p2 = new Person("kobe", 30)
console.log(p2.name, p2.age)


var obj = {
  foo: function() {
    console.log(this)
  }
}