摘要:引用是從匿名函數內部引用自身的唯一方法,不過,最好的方法是避免使用匿名函數,至少在那些需要引用自身的時候,使用命名函數或者表達式。
[翻譯]Chapter1 this or that
第一次翻譯,翻譯的不好,已經再盡全力s去翻譯了,如果哪里看不明點,請出門左轉下邊原文地址
英文原文點擊這里
javascript中最令人困惑的東西就是this關鍵字,它在每個函數作用域中都會自動定義的一個特殊的標識符,但是,它折磨著每一個javascript開發者,哪怕是有經驗的。
任何科技的充分進步,都和魔法沒什么區別
javascript的this機制事實上并不是先進的,但是開發者經常按照他們自己的觀點把this解釋的復雜和混亂。毫無疑問這是因為缺少深刻的理解。
why this既然this機制讓開發者甚至是經驗豐富的程序員感到困惑。那為什么會是有用的,thia弊大于利嗎?在此之前,我們先跳過how的問題,去審查一下why的問題。
讓我們來說明使用this的動機和實用性。
function identify() { return this.name.toUpperCase(); } function speak() { var greeting = "Hello, I"m " + identify.call( this ); console.log( greeting ); } var me = { name: "Kyle" }; var you = { name: "Reader" }; identify.call( me ); // KYLE identify.call( you ); // READER speak.call( me ); // Hello, I"m KYLE speak.call( you ); // Hello, I"m READER
如果看不明白這個代碼塊,不要著急!我們很快會解釋,把這些問題放到一邊,我們先來看關于why的問題。
這個代碼塊允許identify()和speak()兩個函數分別被兩個對象me和you重用,而不是為每個對象多帶帶寫一個版本的函數。
通過依賴this。你可以用一種更明確的方式在環境對象中使用兩個方法。
function identify(context) { return context.name.toUpperCase(); } function speak(context) { var greeting = "Hello, I"m " + identify( context ); console.log( greeting ); } identify( you ); // READER speak( me ); // Hello, I"m KYLE
this機制提供一種更加優雅的方式去傳遞一個對象引用,從而實現更加清楚的API設計和更簡單的重用。
你用的模式越復雜,你就越能清晰的看到,傳遞上下文的時候一個顯示的參數要比傳遞一個this上下文更加麻煩。當我們查看對象和原型的時候,你將能看到一個能夠自動引用正確的函數集合上下文對象的實用性。
困惑點我們很快會開始解釋this實際上是如何工作的,但是首先要糾正一下錯誤的觀念。
itself第一個經常被解釋成困惑的是this指代函數本身,至少這倒是一個合理的解釋。
為什么你會需要在函數內部引用函數自身,最可能的原因是遞歸(在函數內部調用函數自身)或者事件處理的時候當第一次調用時解綁事件。
開發者最新的js機制是把函數當成一個對象一樣來引用(js里所有的函數都是對象),這樣會導致需要在函數互相調用之間存儲狀態(屬性值)。當然這種機制可行的,也有一些有限的用處。這本書剩下的部分將會闡述許多其他的模式,以便更好的存儲函數對象之外的狀態。
但是等一會,我們將會探索一種模式,來說明this是如何不讓函數獲得自身的引用,就像我們之前假設的一樣。
考慮下邊的代碼,我們嘗試去跟蹤一下foo函數被調用了多少次。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called this.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 0 -- WTF?
foo.count依然是0,即使經過了四次console之后我們能清晰的看到foo事實上調用了四次,這個讓人失望的源頭在于對this是什么的解釋太字面。
當代碼執行到foo.count = 0的時候,確實,它給foo函數對象添加了一個屬性count,但是this.count只是在函數內部引用,this并不是指向所有其他的函數對象,即使屬性名稱相同,因為this所在的對象不一樣,所以困惑就產生了。
一個有責任的開發者應該問到這一點:如果我增加一個count屬性但是這個屬性并不是我想要的,那我是否增加了。事實上,如果開發者更深的挖掘,她會發現她創建了一個變量count,這個變量當前的值是NAN,一旦她注意到這個奇怪的結果,她將有一系列的疑問,全局對象是什么,為什么這個值是NAN而不是數值。(在foo中打印count,會顯示出NAN)。
有責任的開發者不應該在這一點停止而是應當深入挖掘為什么這個引用的表現沒有想預想的那樣,去回答這個棘手但是很重要的問題。許多開發者簡單的避免這個問題,并且采取一些其他的解決辦法,比如創建另一個對象去存儲count這個屬性:
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called data.count++; } var data = { count: 0 }; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( data.count ); // 4
雖然這個確實解決了問題,但是不幸的是它忽略了真正的問題,不能理解this到底是什么以及她是如何工作的,而是回到了一個更加熟悉的舒適環境,詞法作用域(靜態作用域)。
詞法作用域是一個完美而有用的機制,我不會以任何方式輕視它,但是,不斷的猜測如何使用this,但是通常會帶來錯誤,退回到詞法作用域去解決問題并不是一個好原因。
為了在一個函數對象引用自身,this有著顯而易見的不足,你通常需要通過指向詞法標識符(變量)去引用函數對象。
function foo() { foo.count = 4; // `foo` refers to itself } setTimeout( function(){ // anonymous function (no name), cannot // refer to itself }, 10 );
在第一個方法中,稱為命名函數,foo是一個引用,可以被用來在函數內部引用函數本身。
但是第二個例子,這個沒有名稱標識的函數是通過setTimeout執行回調(所以叫匿名函數),所以,這個時候沒有合適的方法通過函數名引用函數對象自身。
上邊是舊的用法,現在已經棄用,一個函數中的arguments.callee引用指向當前執行的函數的函數對象。this引用是從匿名函數內部引用自身的唯一方法,不過,最好的方法是避免使用匿名函數,至少在那些需要引用自身的時候,使用命名函數或者表達式。而arguments.callee已經被棄用,不建議使用。
所以,另外一個解決方法解決我們運行的例子是我們在每個地方用foo這個標識符作為函數對象的引用,而不是this。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called foo.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 4
然而這個方法依然側重于this的實際理解,并且依賴于foo的詞法作用域中的變量。
還有另外一種方法更關注this在foo函數對象中實際指向的問題。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called // Note: `this` IS actually `foo` now, based on // how `foo` is called (see below) this.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { // using `call(..)`, we ensure the `this` // points at the function object (`foo`) itself foo.call( foo, i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 4
避免用this,我們擁抱這種方法,我們將會稍微解釋一下這個技術是如何更完整的工作的,所以,如果你仍然有一點困惑,別著急。
its scope第二個常見的錯誤觀點是認為this的意思是以某種形式指代函數作用域,這是一個讓人困惑的問題,因為在一定意義上這是有道理的,但是另一方面,這是很讓人誤導的。
要明確的是,在任何情況下,this并不是指代函數的詞法作用域,在內部,作用域是一個類似可以訪問每個標識符屬性的對象,但是這個作用域對象在js代碼中是不能訪問的,他是引擎內部執行的一部分。
下邊代碼嘗試去跨越代碼的邊界去使用this隱式的指向函數作用域。
function foo() { var a = 2; this.bar(); } function bar() { console.log( this.a ); } foo(); //undefined
這個代碼塊里有不止一個錯誤,似乎它可能得出想要的結果,這個你看到的代碼。。。(原文這里嘲諷了這塊代碼)。
首先,你想通過this.bar()來引用bar方法,當運行起來無疑會出事故,我們要盡快解釋為何報錯,調用bar()最自然的方式是省略this,只對標識符進行詞法引用。
然而,開發者嘗試使用this的目的是在foo和bar兩個函數之間創造一個橋梁,是的bar可以訪問到foo內部作用域中的變量,但是并沒有這樣的橋梁,你不能用this引用去查找詞法作用域下的一些東西,這是不可能的。
每當你感覺你想嘗試把慈父作用域的查找和this混合的時候,記得,這樣的橋是不存在的。
what this拋開那些不正確的解釋,現在我們把注意力,轉移到this機制是如何工作的。
我們之前說過,this是運行的時候綁定而不是聲明的時候綁定,它是基于函數調用情況下的上下文,this綁定和函數生命的位置沒有關系,而是與函數的調用方式有關系。
當一個函數被調用,將會創建一個激活記錄,也就是所謂的執行上下文。該記錄包含了函數調用的位置信息(堆棧),以及函數是如何被調用的,還有參數是如何傳遞的等等,這個記錄的其中一個屬性是this引用,將會在函數執行的期間被使用。
在下一章,我們要學習去找一個函數的調用點去確定在函數執行的時候是如何綁定this的。
回顧this綁定對于沒有花時間去搞懂這個機制具體如何工作的js開發者來說是一個恒定不斷的困難。來自stackoverfolw的回答者的猜測,試錯和盲目的復制粘貼并不是利用這種機制的有效方法。
學習this,首先要去了解this不是什么,盡管任何的假設或者誤解都可能導致你。。。this既不是函數本身的引用,也不是函數詞法作用域的引用。
this實際上是函數調用時進行的綁定,它的引用綁定完全由函數的調用位置所決定。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82472.html
摘要:運行規則根據的運作原理,我們可以看到,的值和調用棧通過哪些函數的調用運行到調用當前函數的過程以及如何被調用有關。 1. this的誕生 假設我們有一個speak函數,通過this的運行機制,當使用不同的方法調用它時,我們可以靈活的輸出不同的name。 var me = {name: me}; function speak() { console.log(this.name); }...
摘要:基本概念首先,函數不能存儲的值,指向哪里,取決于調用它的對象。如果沒有這個對象,那默認就是調用非嚴格模式下。也就是說是在運行的時候定義的,不是在綁定的時候定義的。 基本概念 首先,函數不能存儲this的值,this指向哪里,取決于調用它的對象。如果沒有這個對象,那默認就是window調用(非嚴格模式下)。也就是說this是在運行的時候定義的,不是在綁定的時候定義的。 funct...
摘要:為什么會存在跨域問題同源策略由于出于安全考慮,瀏覽器規定不能操作其他域下的頁面,不能接受其他域下的請求不只是,引用非同域下的字體文件,還有引用非同域下的圖片,也被同源策略所約束只要協議域名端口有一者不同,就被視為非同域。 showImg(https://segmentfault.com/img/remote/1460000017093859?w=1115&h=366); Why 為什么...
摘要:在我們的程序中有很多變量標識符,我們現在或者將來將使用它。當我們使用時,如果并沒有找到這個變量,在非嚴格模式下,程序會默認幫我們在全局創建一個變量。詞法作用域也就是說,變量的作用域就是他聲明的時候的作用域。 作用域 定義 首先我們來想想作用域是用來干什么的。在我們的程序中有很多變量(標識符identifier),我們現在或者將來將使用它。那么多變量,我咋知道我有沒有聲明或者定義過他呢,...
摘要:一到底是一門什么樣的計算機編程語言表里不一表面上是動態解釋執行的腳本語言,實際上它是一門編譯語言。與眾不同與傳統語言不同的是,它不是提前編譯的,編譯記過也不能在分布式系統中進行移植。千篇一律引擎進行編譯的步驟和傳統的編譯語言非常相似。 一、JavaScript到底是一門什么樣的計算機編程語言? JavaScript表里不一:表面上是動態、解釋執行的腳本語言,實際上它是一門編譯語言。 ...
閱讀 1578·2021-10-14 09:42
閱讀 3818·2021-09-07 09:59
閱讀 1302·2019-08-30 15:55
閱讀 575·2019-08-30 11:17
閱讀 3341·2019-08-29 16:06
閱讀 504·2019-08-29 14:06
閱讀 3130·2019-08-28 18:14
閱讀 3649·2019-08-26 13:55