在每個方法中,關鍵字 this 表示隱式參數。 —— 《Java 核心技術 卷Ⅰ》javascript
瞭解 python 的同窗可能會知道,python 構造函數中老是會出現 self 參數。這個參數用來表示建立的實例對象。java
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
複製代碼
在 JavaScript 和 Java 中這個參數被隱藏了。咱們沒必要在參數列表中顯式聲明這個參數,就能夠在函數中使用這個參數。這個參數就是 this 。python
function Student(name, score){
this.name = name
this.score = score
}
var studentA = new Student('a', 100)
console.log(studentA.name, studentA.score) // a 100
複製代碼
援引 《Java 核心技術 卷Ⅰ》 中的一句話:在每個方法中,關鍵字 this 表示隱式參數。 所謂的隱式參數,就是沒有在參數列表中顯式聲明的參數。隱式參數和參數列表中定義的顯式參數統稱爲形式參數。與形式參數相對應的是實際參數。bash
形式參數,簡稱形參。形參就是在定義函數的時候使用的參數,用來接收調用該函數時傳遞的參數。如上述代碼中的 name ,score 參數都是形參。閉包
實際參數,簡稱實參,實參就是調用該函數時傳遞的參數。如上述代碼中的 'a' , 100 都是實參。app
《 你不知道的JavaScript(上卷)》中提了一個問題,問:爲何採用詞法做用域的 JavaScript 中的 this 的值是在調用時肯定的?函數
在理解了形參和實參以後,咱們便能很好地理解這個問題了。oop
由於 this 是一個形參,形參的值是由實參決定的。而傳參這個操做時在調用時發生的,因此 this 的值是在調用時肯定的。ui
既然 this 的值是由實參的值決定的,那麼這個實參的值究竟是什麼呢?this
參考 《Java 核心技術 卷Ⅰ》 中的一句話:隱式參數的值是出如今函數名以前的對象。看成爲構造函數時,this 用來表示建立的實例對象。來看兩個例子:
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
foo.bar() // foo
複製代碼
this 指向函數名(bar)以前的 foo 對象
function Student(name, score){
this.name = name
this.score = score
}
var studentA = new Student('a', 100)
console.log(studentA.name, studentA.score) // a 100
複製代碼
this 指向建立的實例對象 studentA
JavaScript 也提供了幾個函數去改變 this 的值。這幾個函數都會返回一個原函數的拷貝,並在這個拷貝上傳遞 this 的值。因此從結果上看,咱們能夠看到原有的 this 會被覆蓋。
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
var obj = {
name: 'obj',
}
foo.bar.call(obj) // obj
複製代碼
this 指向新的對象 obj 。
《 你不知道的JavaScript(上卷)》中描述了一種現象:this 丟失了原來的綁定對象,指向了全局對象。書中稱爲隱式丟失。來看示例:
function foo() {
console.log( this.a )
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo // 賦值操做
var a = "oops, global"
bar() // "oops, global"
複製代碼
JavaScript 只有值傳遞,沒有引用傳遞。在賦值操做的時候,實際上是將一個引用的拷貝賦值給另一個變量。var bar = obj.foo
在這個語句中,沒有傳參操做,因此 this 的值是由 bar 函數在調用時傳遞的那個實參決定的。這個實參如未顯式指定,那麼即是指向全局對象。因此上述代碼中的 this 指向了全局對象。
一樣的,咱們在函數傳參的過程當中,常常發現隱式丟失問題,緣由也是中間發生了一次賦值操做。代碼示例以下:
var name = 'global'
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
function callFunc(func){
func()
}
callFunc(foo.bar) // global
複製代碼
在傳參的過程當中,發生了func = foo.bar
的賦值操做,致使最後 this 的值指向了全局對象。
可是若是咱們使用 bind 綁定了 this 的值,那麼在發生賦值操做時,this 的值將再也不改變。來看下面例子。
bind 和 call ,apply 有一點不一樣的是 call,apply 返回的是調用結果,而 bind 返回的是綁定 this 後的函數對象。那麼當綁定 this 後的函數做爲實參傳入函數時,與未綁定 this 的結果就徹底不一樣了。
來看下面的例子。
var name = 'global'
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
function callFunc(func){
func()
}
callFunc(foo.bar.bind(foo)) // foo
複製代碼
將 bar 函數中的 this 綁定到 foo 再傳入 callFunc 函數中,最後打印的結果是 foo 。
實際上, bind 函數內部維護了一個閉包,使得調用始終發生在函數內部,來保證 this 的值不變。來看 MDN 提供的 ployfill
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)))
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype
}
fBound.prototype = new fNOP()
return fBound
}
}
複製代碼
// return 部分
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)))
複製代碼
在 return 的時候使用了 apply 函數來改變 this ,若未發生 new 操做,那麼這個 this 的值將綁定到 bind 函數提供的那個對象。
當發生 new 操做時,this 將綁定到這個實例對象。 從上面這個 ploy fill 能夠看出 new 操做中的 this 值會覆蓋原有 this 的值。來看例子
function bar () {
this.name = 'bar'
}
var foo = {
name: 'foo',
}
var a = bar.bind(foo)
a()
console.log(foo.name) // bar
var b = new a()
console.log(b.name) // bar
複製代碼
當執行 new 操做以前,a 函數中的 this 指向 foo。當執行 new 操做以後,a 函數中的 this 指向了 b 。
new 操做會返回一個從新綁定 this 後的新對象。因此當發生 new 操做以後,原有的 this 發生了改變。具體步驟以下:
箭頭函數中的 this 繼承了父做用域的 this。
var name = 'global'
var foo = {
name: 'foo',
bar: () => {
console.log(this.name)
}
}
foo.bar() // global
複製代碼
箭頭函數的父做用域爲全局做用域,全局做用域的 this 指向全局對象,因此 this 指向了全局對象。
var name = 'global'
var foo = {
name: 'foo',
bar: function () {
setTimeout(() => {
console.log(this.name)
},100)
}
}
foo.bar() // foo
複製代碼
箭頭函數的父做用域爲 bar 函數,在調用時,父做用域 bar 函數中的 this 指向了 foo 函數,因此箭頭函數中的 this 指向了 foo 。
嚴格模式下禁止 this 指向全局對象。在嚴格模式下當 this 指向全局對象的時候會變成 undefined 。