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