摘要:回顧一下關(guān)鍵詞的過程創(chuàng)建一個新的對象使得的指向構(gòu)造函數(shù)的原型對象執(zhí)行構(gòu)造函數(shù)中的,改變的指向為如果結(jié)果是對象類型,則返回結(jié)果,否則返回指向的是調(diào)用時傳遞的第一個參數(shù)。
this = ?
在JS中,當(dāng)一個函數(shù)執(zhí)行時,都會創(chuàng)建一個執(zhí)行上下文用來確認(rèn)當(dāng)前函數(shù)的執(zhí)行環(huán)境,執(zhí)行上下文分為 全局執(zhí)行上下文和 函數(shù)執(zhí)行上下文。而 this 就是指向這個執(zhí)行上下文的對象。所以,this是在運行時決定的,可以簡單的理解為 誰調(diào)用,this指向誰。
分四種情況來看:
普通函數(shù)調(diào)用
對象方法調(diào)用
構(gòu)造函數(shù)調(diào)用
call、apply、bind
普通函數(shù)調(diào)用當(dāng)函數(shù)作為函數(shù)獨立調(diào)用的時候,則是在全局環(huán)境中運行,this 則指向全局對象 window
一個簡單的例子
function demo() { console.log(this); // window } demo();
demo 函數(shù)獨立調(diào)用,所以 this 指向全局對象 window
接著
function outer() { function inner() { console.log(this); // window } inner(); } outer();
雖然在 outer 函數(shù)內(nèi)部聲明了一個 inner 函數(shù),但實際上 inner 函數(shù)是獨立調(diào)用的,所以依然是在全局環(huán)境,this仍然是指向了 window。
function demo(func) { func(); } demo(function () { console.log(this); // window });
給demo函數(shù)傳入一個匿名函數(shù),執(zhí)行匿名函數(shù)func的時候,依然是作為函數(shù)獨立調(diào)用,所以this仍然指向window。
理解一下什么是作為函數(shù)獨立調(diào)用:
當(dāng)定義一個函數(shù),例如var demo = function () {} 等號右邊的函數(shù)是獨立放在內(nèi)存中的,然后賦予demo變量的指向為函數(shù)所在的內(nèi)存地址,當(dāng)直接調(diào)用 demo(),相當(dāng)于直接找到函數(shù)本身執(zhí)行,所以函數(shù)內(nèi)部創(chuàng)建的上下文為全局上下文,this 則指向了全局對象 window。
當(dāng)調(diào)用一個對象方法時,this 代表了對象本身。
let obj = { name: "invoker", getName: function () { console.log(this); // obj console.log(this.name); // "invoker" } } obj.getName();
定義了一個 obj 對象,調(diào)用其內(nèi)部的getName,this 則指向了 obj 對象。
稍微修改一下
var name = "windowName"; let obj = { name: "invoker", getName: function () { console.log(this); // window console.log(this.name); // windowName } } var getName = obj.getName; getName();
當(dāng)用一個變量 getName 接收 obj 對象的 getName方法, 再執(zhí)行 getName,發(fā)現(xiàn) this 指向了 window,因為此時變量 getName 直接指向了函數(shù)本身,而不是通過 obj 去調(diào)用,此時就變成了函數(shù)獨立調(diào)用的情況了。
再看個例子
let obj = { test: function() { function fn() { console.log(this); // window } fn(); }, test1: function (fn) { fn() } } obj.test(); obj.test1(function () { console.log(this) // window });
雖然在 obj 對象的 test 方法內(nèi)定義了 fn ,但執(zhí)行時同樣屬于函數(shù)獨立調(diào)用,所以 this 指向 window。
將函數(shù)作為參數(shù)傳入 obj 的 test1 方法,也屬于函數(shù)獨立調(diào)用,this 同樣指向 window。
使用 new 關(guān)鍵字調(diào)用函數(shù),則是構(gòu)造函數(shù)調(diào)用,this 指向了該構(gòu)造函數(shù)新創(chuàng)建的對象。
function person(name) { this.name = name } let p = new person("invoker") console.log(p.name) // "invoker"
回顧一下 new 關(guān)鍵詞的過程:
創(chuàng)建一個新的對象 obj
使得 obj 的 __proto__ 指向 構(gòu)造函數(shù)的原型對象
執(zhí)行構(gòu)造函數(shù)中的 constructor,改變this的指向為 obj
如果結(jié)果是對象類型,則返回結(jié)果,否則返回obj
function myNew(Fn) { let obj = {} obj.__proto__ = Fn.prototype const res = Fn.prototype.constructor.call(obj) if (typeof res === "object") { obj = res } return obj }call、apply、bind
this 指向的是 call 、 apply、bind 調(diào)用時傳遞的第一個參數(shù)。
let obj = { name: "invoker" } function demo() { console.log(this.name) // "invoker" } demo.call(obj) demo.apply(obj) demo.bind(obj)()箭頭函數(shù)
箭頭函數(shù)在執(zhí)行時并不會創(chuàng)建自身的上下文,它的 this 取決于自身被定義的所在執(zhí)行上下文。
例子:
let obj = { fn: () => { console.log(this) // window } } obj.fn()
obj 的 fn 指向一個箭頭函數(shù),由于只有函數(shù)可以創(chuàng)建執(zhí)行上下文,而箭頭函數(shù)外部并沒有包裹函數(shù),所以箭頭函數(shù)所在的執(zhí)行上下文為全局的執(zhí)行上下文,this 指向 window
包裹一個函數(shù)看看唄?
let obj = { fn: function () { console.log("箭頭函數(shù)所在執(zhí)行上下文", this) // "箭頭函數(shù)所在執(zhí)行上下文" obj var arrow = () => { console.log(this) //obj } arrow() } } obj.fn()
箭頭函數(shù) arrow 被定義在 obj.fn 內(nèi),所以 fn 中的 this 就是 arrow 中的 this
箭頭函數(shù)一次綁定上下文后便不可更改:
let obj = { name: "invoker" } var demo = () => { console.log(this) // window } demo.call(obj)
雖然使用了 call 函數(shù)間接修改 this 的指向,但并不起作用。
為什么會有this的設(shè)計javascript中存在 this 的設(shè)計,跟其內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)有關(guān)系。
假設(shè)定義 let obj = { name: "invoker" }
此時會先生成一個對象 { name: "invoker" } 并放在內(nèi)存當(dāng)中
將 { name: "invoker } 所在的內(nèi)存地址賦予 obj
所以 obj 其實就是個指向某個對象的地址,如果要讀取 obj.name,則先要找到 obj 所在地址,然后從地址中拿到原始對象,讀取 name 屬性。
對象中每個屬性都有一個屬性描述對象:可通過 Object.getOwnPropertyDescriptor(obj, key) 來讀取。
也就是說上面所說的 obj 的 name 屬性實際是下面這樣的
{ name: { [[value]]: "invoker", [[configurable]]: true, [[enumerable]]: true, [[writable]]: true } }
value 就是獲得的值。
現(xiàn)在假設(shè)對象的屬性是一個函數(shù):
let name = "windowName" let obj = { name: "invoker", sayHello: function () { console.log("my name is " + this.name) } } let descriptor = Object.getOwnPropertyDescriptor(obj, "sayHello") console.log(descriptor) //這個sayHello的屬性描述對象為: sayHello: { [[value]]: ? (), [[configurable]]: true, [[enumerable]]: true, [[writable]]: true }
sayHello 的 value 值是一個函數(shù),這個時候,引擎會多帶帶將這個函數(shù)放在 內(nèi)存 當(dāng)中,然后將函數(shù)的內(nèi)存地址賦予 value。
因此可以得知,這個函數(shù)在 內(nèi)存 中是多帶帶的,并不被誰擁有,所以它可以在不同的上下文執(zhí)行。
由于函數(shù)可以在不同上下文執(zhí)行,所以需要一種機制去獲取當(dāng)前函數(shù)內(nèi)部的執(zhí)行上下文。所以,就有了 this,它指向了當(dāng)前函數(shù)執(zhí)行的上下文。
// 接著上面代碼 obj.sayHello() // my name is invoker let sayHello = obj.sayHello sayHello() // my name is windowName
obj.sayHello() 是通過 obj 找到 sayHello,也就是對象方法調(diào)用,所以就是在 obj 環(huán)境執(zhí)行。
當(dāng) let sayHello = obj.sayHello,變量 sayHello 就直接指向函數(shù)本身,所以 sayHello() 也就是函數(shù)獨立調(diào)用,所以是全局環(huán)境執(zhí)行。
this 的出現(xiàn),跟JS引擎內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)有關(guān)系。
當(dāng)發(fā)現(xiàn)一個函數(shù)被執(zhí)行時,通過上面的多種情況。
分析函數(shù)怎么調(diào)用(多帶帶調(diào)用、對象方法、構(gòu)造方法)
是否有使用 call、apply 等間接調(diào)用
是否有箭頭函數(shù)
甚至還可能分析是否為嚴(yán)格模式
這樣就能很好的確認(rèn) this 的指向。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/110291.html
摘要:本文記錄一些日常編程中的小妙招,并使用進行交互測試,讓我們更好的了解和學(xué)習(xí)的一些特性。兩變量交換語法測試免去了利用一個臨時變量進行過渡交互。相互轉(zhuǎn)換看看各自的能不能排上用場。 ...
摘要:請欣賞語法清單后端掘金語法清單翻譯自的,從屬于筆者的入門與實踐系列。這篇一篇框架整合友好的文章三后端掘金一理論它始終是圍繞數(shù)據(jù)模型頁面進行開發(fā)的。 RxJava 常用操作符 - Android - 掘金 原文地址 http://reactivex.io/documenta... ... RxJava 和 Retrofit 結(jié)合使用完成基本的登錄和注冊功能 - Android - 掘...
閱讀 2322·2021-11-22 12:01
閱讀 2000·2021-11-12 10:34
閱讀 4526·2021-09-22 15:47
閱讀 2837·2019-08-30 15:56
閱讀 2869·2019-08-30 15:53
閱讀 2410·2019-08-30 13:53
閱讀 3386·2019-08-29 15:35
閱讀 3131·2019-08-29 12:27