摘要:講清楚之中的這一節來探討。所以當函數作為構造函數調用,則函數內部的綁定到該函數上。在通過構造函數實例化對象時,對象內部的也同樣指向該實例對象。
講清楚之 javascript中的this
這一節來探討this。 在 javascript 中 this 也是一個神的存在,相對于 java 等語言在編譯階段確定,而在 javascript 中, this 是動態綁定,也就是在運行期綁定的。這導致了 javascript 中 this 的靈活性,而且對識別對象不同的調用場景下 this 指向帶來了一些困擾。
在全局環境中this指向window,即this === window。this 的靈活性主要體現在函數環境中,容易判定出錯的也是函數環境下的 this 指向.
this 是函數內部的可見屬性之一(另一個是 arguments), 在函數內部我們可以直接使用this訪問指定對象的屬性。那么指定對象是如何確定的?
下面就圍繞 this 的指向對象來梳理
this 是怎么確定的?首先 this 是在函數被調用時確定的, 也就是在進入函數后,函數內表達式、語句執行前的變量對象創建階段確定的。
而它指向什么取決于函數在哪里被調用(在什么對象上被調用).
一般情形需要關注函數在哪里被調用、被怎么調用.
下面主要分析函數在不同調用場景下this的指向.
當函數作為方法被對象擁有并調用時 this 指向該對象,否則 this 為 undefind把標題展開描述就是: 當函數調用時是被某一個對象所擁有,函數內的this將綁定到該對象上。如果函數是獨立調用的,則函數內部的 this 在嚴格模式下為 undefind, 在非嚴格模式下 this 會指向 window(node.js中指向global)。
根據上面的原則,我們首要判斷的是函數被誰所擁有,舉幾個栗子更好理解:
栗子1:
let a = 1 function foo () { console.log(this.a) } foo() // 1
foo() 是在全局環境下獨立調用的,此時函數 foo 被全局對象擁有(this 指向 window),所以this.a獲取的是 window 全局對象下面的 a.
栗子2:
var a = 1 var foo = function (){ console.log(this.a) } var too = { a: 2, b: foo } var bar = too.b foo() // 1 too.b() // 2 bar() // 1
函數執行時確定 this 指向的大致邏輯:
foo() :
在全局對象 window 下調用,所以輸出1。
too.b():
對象 too 的屬性 b 指向函數 foo,此時函數 foo 是 對象 too 內部的一個方法;
too.b()執行時,b 是被對象 too調用的,此時內部的 this 指向 對象 too;
所以this.a獲取的是too.a,輸出2;
bar():
對象 too 的方法被賦值給 bar, 即此時 bar 標識符同 foo 標識符一樣都指向同一個棧地址所代表的函數。
執行bar()此時在全局對下 window 下調用,所以輸出1。
栗子3:
var a = 1 var foo = function () { console.log(this.a) } var too = function (fn) { var a = 2 fn() } too(foo) // 1
too(foo) :這里函數 foo 作為參數被傳遞到函數 too的內部執行, fn()執行時并沒有被其他對象顯示擁有,所以我們隱式的判定fn()是在全局對象 window 下面執行的,所以輸出 1 。
這個栗子很容易搞錯(我自己感覺每過一段時間再看還是會錯o(︶︿︶)o),第一印象輸出的應該是2,那是應為把 this 與作用域鏈弄混淆了。始終要記住作用域鏈是在源代碼編碼階段就確定了,而 this 是在函數運行階段才確定,屬于執行上下文的概念,是在運行階段依據 this所在函數被誰調用來確定的。
我們再來把上面的栗子稍微修改一下
栗子4:
var a = 1 var foo = function () { console.log(this.a) // 輸出 1 console.log(a) // 區別在這里, 輸出 1 } var too = function (fn) { var a = 2 fn() } too(foo) // 1, 1
表達式 | - | - | 值 |
---|---|---|---|
console.log(this.a) | 基于上下文 this | 表達式所屬 foo 函數在 too 函數內調用時 this 指向 window | 1 |
console.log(a) | 基于作用域鏈 | 全局上下文中的變量 a 在 foo 函數作用域鏈上 | 1 |
不知道你理解了沒有,這個栗子也體現了上下文與作用域在進行變量/屬性查找時的區別
栗子5:
let c = 1 let foo = { c: 2 } let too = function () { console.log(this.c) } foo.a = { b: too, c: 3 } foo.a.b() // 3
this的綁定只受最靠近的成員引用的影響。foo.a.b()函數b作為對象foo.a的方法被調用,所以此時的this綁定到foo.a。b與對象foo的包含成員沒有多大關系,最靠近的對象才決定this的綁定。
最后console.log(this.c)取到的是foo.a里c的值 3 .
栗子5:
let a = 1 let foo = { a: 2, msg: function () { console.log(`hi, ${this.a}`) } } let too = Object.create(foo) too.msg() // hi, 2
上面用對象foo作為原型創建新對象too, 所以對象 too 繼承對象 foo 的所有屬性、方法。too.msg()執行時,msg 函數被 too 調用,此時this就指向對象too, 所以console.log(hi, ${this.a})訪問的是從對象foo繼承來的 a.
所以對于在對象原型鏈上某處定義的方法,同樣的概念也適用。如果該方法存在于一個對象的原型鏈上,那么對象實例的this指向的是調用這個方法的對象,通過this可以訪問到原形鏈上的方法。
通過上面的幾個栗子驗證了我們的總結:
當函數作為對象方法調用時 this 指向該對象,作為函數獨立調用時 this 指向全局對象 window (嚴格模式下 this 為 undefind )。
大部分時候依據上面的原則來判斷 this 的指向是沒有問題,但是 this 還有以下幾種特殊場景需要注意。
當函數作為構造函數調用,此時函數內部的 this 指向函數自身函數也是對象
栗子:
function Foo (a) { this.a = a // 實例化后 this 指向 too } let too = new Foo(1)
我們知道函數 this 在運行期確定,而構造函數實例化時在內部其實是為我們創建了一個新的對象,通過一系列的操作后將 this 指向了這個新對象。
new操作符執行時的邏輯推導如下:
創建一個新的空對象;
將構造函數的 this 指向這個新對象;
將構造函數的原形添加到新對象的原形鏈里,將屬性、方法掛載在新對象上;
返回這個新對象
返回的新對象就是我們實例化的對象。即, new 操作符調用構造函數時,this 是指向內部創建的新對象,最后會將新創建的對象返回給實例變量。
所以當函數作為構造函數調用,則函數內部的 this 綁定到該函數上。在通過構造函數實例化對象時,對象內部的 this 也同樣指向該實例對象。
當函數使用 cal、apply 方法調用執行,此時 this 指向 call、apply 方法傳入的對象在 javascript 里函數也是對象。 所有函數都繼承于 Function 構造函數,而 call、apply 是 Function.prototype 原形的方法,所以函數都從原形的原形里繼承了 call、apply 方法。
call、apply 用于向函數注入 this 對象和變量(call 與 apply 的區別在于傳遞的參數不一樣,其他沒有區別)。
let a = 1 let too = { a: 2 } function foo () { console.log(this.a) } foo.call(too) // 2
函數 foo 里面的 this 此時指向call傳遞進來的對象 too,所以this.a打印的是 2.
如果傳遞給 this 的值不是一個對象,JavaScript 會嘗試使用內部 ToObject 操作將其轉換為對象。因此,如果傳遞的值是一個原始值比如 7 或 "foo",那么就會使用相關構造函數將它轉換為對象,所以原始值 7 會被轉換為對象: new Number(7) ,而字符串 "foo" 轉化成 new String("foo") 。
function bar() { console.log(this, Object.prototype.toString.call(this)); } //原始值 7 被隱式轉換為對象 bar.call(7); // Number {[[PrimitiveValue]]: 7}__proto__: Number[[PrimitiveValue]]: 7 "[object Number]"
ECMAScript 5 引入了 Function.prototype.bind。當函數調用f.bind(someObject)會創建一個與f具有相同函數體和作用域的函數,但是在這個新函數中,this將永久地被綁定到了bind對象someObject。
栗子:
let a = 1 let too1 = { a: 2 } function foo () { console.log(this.a) } let bar = foo.bind(too1) let bar2 = { a: 4, b: bar } bar() // 2 bar.call({a: 3}) // 2 bar2.b() // 2
當foo 通過bind創建一個新函數時,新函數的this強制綁定到了傳入的對象too1, 后續執行中bar即使是作為對象方法調用還是使用 call、apply 都無法替換使用 bind 綁定的 this。
當函數是以箭頭函數方式創建的,此時的 this 指向箭頭函數執行時的宿主函數的上下文栗子:
function foo () { let that = this let too = () => { console.log(this === that) // true } too() } foo()
too 為箭頭函數,內部的 this 被指向為他創建時的上下文,即 foo 的 this 。反過來說就是箭頭函數沒有自己的上下文,他共享的是封閉詞法上下文。
注意這里提到的 “this 是動態綁定,在運行期綁定的” 主要是指進入函數后,函數運行前的上下文創建階段(預處理),此時函數內的表達式、語句并沒有執行。但這里都統稱為函數運行期,詳細請關注變量對象一節的描述(^.^)。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95267.html
摘要:中的繼承并不是明確規定的,而是通過模仿實現的。繼承中的繼承又稱模擬類繼承。將函數抽離到全局對象中,函數內部直接通過作用域鏈查找函數。這種范式編程是基于作用域鏈,與前面講的繼承是基于原型鏈的本質區別是屬性查找方式的不同。 這一節梳理對象的繼承。 我們主要使用繼承來實現代碼的抽象和代碼的復用,在應用層實現功能的封裝。 javascript 的對象繼承方式真的是百花齊放,屬性繼承、原型繼承、...
閱讀 2751·2021-11-22 13:54
閱讀 2688·2021-10-14 09:42
閱讀 3987·2021-09-28 09:47
閱讀 2162·2021-09-03 10:28
閱讀 1203·2021-07-26 23:38
閱讀 2557·2019-08-30 15:54
閱讀 2639·2019-08-29 16:35
閱讀 1426·2019-08-29 15:42