摘要:一看這二逼就是周杰倫的死忠粉看看控制臺輸出,確實沒錯就是對象。從根本上來說,作用域是基于函數的,而執行環境是基于對象的例如全局執行環境即全局對象。全局對象全局屬性和函數可用于所有內建的對象。全局對象只是一個對象,而不是類。
覺得本人寫的不算很爛的話,可以登錄關注一下我的GitHub博客,博客會堅持寫下去。
今天同學去面試,做了兩道面試題,全部做錯了,發過來給我看,我一眼就看出來了,因為這種題我做過,至于為什么結果是那樣,我也之前沒有深究過,他問我為什么,我也是一臉的懵逼,不能從根源上解釋問題的原因,所以并不能完全讓他信服。今天就借著這個機會深扒一下,如果沒有耐心可以點擊右上角,以看小說的心態看技術文章,走馬觀花,不加思考,這樣的量變并不能帶來質的改變。花上10+分鐘認真閱讀我相信你會受益匪淺,沒收獲你買把武昌火車站同款菜刀砍我?。因為我是寫完這篇博客再回頭寫這段話的,在寫的過程中也學到了很多,所以在此分享一下共同學習。
登高自卑,與君共勉。
下面一起看看這道題,同學微信發給我截圖:
如果看的不太清楚,我把代碼敲一遍,給大家看看:
var name = "jay"; //一看這二逼就是周杰倫的死忠粉 var person = { name: "kang", pro: { name: "Michael", getName: function() { return this.name; } } }; console.log(person.pro.getName()); var pepole = person.pro.getName; console.log(pepole());
這里我就不賣關子了,不少童鞋也應該遇到過做過類似的題目,就是考察this,我們先看看答案:
console.log(person.pro.getName());//Michael console.log(pepole());//jay
第一個很簡單,this就是指向person.pro的引用,那么this.name就是person.pro.name,于是第一個就是輸出Michael,再來看看第二個就蹊蹺了,和第一個明明是一樣的方法,為什么輸出的結果是jay呢?
既然我們知道結果是jay了,反著推理一步步來,不難推出調用people()這個方法時候的this.name就相當于和var name = "jay",var聲明的全局變量和全局環境下的this的變量有什么聯系呢?;那么這個this到底是什么,總得是一個具體東西吧?
我們一步步分析,this.name這個this有一個name屬性,很明顯就是一個對象,那具體是什么對象呢?this的指向是在函數被調用的時候確定的,于是有人說就是Window對象,沒錯是沒錯,確實是Window對象,然后var name聲明的全局變量name和window.name是相同的作用;但是你只只知其然,而不知其所以然,學深一門語言就是要有刨根問底的精神,打破砂鍋問到底,知其然還要知其所以然。
我們就先驗證一下,那個this到底是不是window對象吧。我們把代碼稍微調整一下,輸出this。
var name = "jay"; //一看這二逼就是周杰倫的死忠粉 var person = { name: "kang", pro: { name: "Michael", getName: function() { console.log(this); return this.name; } } }; console.log(person.pro.getName()); var pepole = person.pro.getName; console.log(pepole());
看看控制臺輸出,確實沒錯就是window對象。
再來看看var name聲明的name和window.name是否相等呢?
var name; console.log(name===window.name)
確實是一樣的,類型和值沒有任何的不同。
好滴,那么你說this就是window對象,至于為什么是這樣你也不清楚,是否永遠是這樣呢?我們看看這段代碼輸出又會是咋樣呢?
"use strict"; var name = "jay"; //一看這二逼就是周杰倫的死忠粉 var person = { name: "kang", pro: { name: "Michael", getName: function() { console.log(this); return this.name; } } }; console.log(person.pro.getName()); var pepole = person.pro.getName; console.log(pepole());
還會是跟上面一樣的結果嗎?我們拭目以待.
看到結果沒:Cannot read property "name" of undefined,這是什么意思想必大家已經很清楚了,此時的this成了undefined了,undefined當然也就沒有name這個屬性,所以瀏覽器報錯了。那么為什么會這樣呢?
同樣換種寫法再來看看這段代碼輸出什么呢?
var name = "jay"; var person = { name : "kang", getName : function(){ return function(){ return this.name; }; } }; console.log(person.getName()());
控制臺自己輸出一下看看,我想此時你的心情一定是這樣的:
在弄明白這些問題之前,我們先弄清楚全局環境下的this,var聲明的全局變量和window對象之間的聯系與區別:
先看四個簡單的例子對比,均在js非嚴格模式測試,也就是沒有聲明"use strict":
demo1:
var name="jawil"; console.log(name); console.log(window.name) console.log(this.name)
demo2:
name="jawil"; console.log(name); console.log(window.name) console.log(this.name)
demo3:
window.name="jawil"; console.log(name); console.log(window.name) console.log(this.name)
demo4:
this.name="jawil"; console.log(name); console.log(window.name) console.log(this.name)
其實這四個demo是一個意思,輸出的結果沒有任何差別,為什么沒有差別呢?因為他們在同一個環境,也就是全局環境下:
我們換一種在不同的環境下執行這段代碼看一看結果:
demo5:
var name="jawil"; var test={ name:"jay", getName:function(){ console.log(name); console.log(window.name) console.log(this.name) } } test.getName();
最后結果一次輸出為:
console.log(name);//jawil console.log(window.name)//jawil console.log(this.name)//jay
因為此處的this不再指向全局對象了,所以結果肯定不同,我們先來看看全局對象和全局環境下的this,暫不考慮其他環境下的this。
那么又有人會問什么是全局環境,什么又是全局對象,全局對象該怎么理解? 題外話其實我們看技術文章,總覺得似懂非懂,一知半解,不是看不懂代碼,而是因為很多時候我們對一些概念沒有比較深入的了解,但是也沒有去認真繼續下去考究,這也不能怪我們,畢竟開發時候不太深入這些概念對我們業務也沒啥影響,但是我發現我自己寫東西時候,不把概念說清楚,總不能讓人信服和徹底明白你講的是什么玩意,我想寫博客最大的好處可以讓自己進一步提高,更深層次的理解你所學過的東西,你講的別人都看不懂,你確認你真的懂了嗎?
說到全局環境,我們就會牽扯到另一個概念那就是執行環境和函數的作用域既然扯到這么深,就順便扯扯執行環境和作用域,這些都是js這門語言的重點和難點,沒有一定的沉淀很難去深入探討這些東西的.
函數的每次調用都有與之緊密相關的作用域和執行環境。從根本上來說,作用域是基于函數的,而執行環境是基于對象的(例如:全局執行環境即全局對象window)。
我們還是先說一說全局對象吧,因為全局執行環境是基于全局對象的。 JavaScript 全局對象全局屬性和函數可用于所有內建的 JavaScript 對象。
全局對象描述例子全局對象是預定義的對象,作為 JavaScript 的全局函數和全局屬性的占位符。通過使用全局對象,可以訪問所有其他所有預定義的對象、函數和屬性。全局對象不是任何對象的屬性,所以它沒有名稱。
在頂層 JavaScript 代碼中,可以用關鍵字 this 引用全局對象。但通常不必用這種方式引用全局對象,因為全局對象是作用域鏈的頭,這意味著所有非限定性的變量和函數名都會作為該對象的屬性來查詢。例如,當JavaScript 代碼引用 parseInt() 函數時,它引用的是全局對象的 parseInt 屬性。全局對象是作用域鏈的頭,還意味著在頂層 JavaScript 代碼中聲明的所有變量都將成為全局對象的屬性。
全局對象只是一個對象,而不是類。既沒有構造函數,也無法實例化一個新的全局對象。
在 JavaScript 代碼嵌入一個特殊環境中時,全局對象通常具有環境特定的屬性。實際上,ECMAScript 標準沒有規定全局對象的類型,JavaScript 的實現或嵌入的 JavaScript 都可以把任意類型的對象作為全局對象,只要該對象定義了這里列出的基本屬性和函數。例如,在允許通過 LiveConnect 或相關的技術來腳本化 Java 的 JavaScript 實現中,全局對象被賦予了這里列出的 java 和 Package 屬性以及 getClass() 方法。而在客戶端 JavaScript 中,全局對象就是 Window 對象,表示允許 JavaScript 代碼的 Web 瀏覽器窗口。
在 JavaScript 核心語言中,全局對象的預定義屬性都是不可枚舉的,所有可以用 for/in 循環列出所有隱式或顯式聲明的全局變量,如下所示:
上一篇博客我就講到遍歷對象屬性的三種方法:
for-in循環、Object.keys()以及Object.getOwnPropertyNames()不同的區別,想要了解可以細看我這篇博客:傳送門
var variables = ""; for (var name in this) { variables += name + "再回過頭來談談執行環境和函數的作用域 一開始要明白的
"; } document.write(variables);
首先,我們要知道執行環境和作用域是兩個完全不同的概念。
函數的每次調用都有與之緊密相關的作用域和執行環境。從根本上來說,作用域是基于函數類型的(當然函數也是對象,這里我們細分一下),而執行環境是基于對象類型的(例如:全局執行環境即window對象)。
換句話說,作用域涉及到所被調用函數中的變量訪問,并且不同的調用場景是不一樣的。執行環境始終是this關鍵字的值,它是擁有當前所執行代碼的對象的引用。每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數據時會在后臺使用它。
一些概念首先來說說js中的執行環境,所謂執行環境(有時也稱環境)它是JavaScript中最為重要的一個概念。執行環境定義了變量或函數有權訪問的其他數據 ,決定了它們各自的行為。而每個執行環境都有一個與之相關的變量對象,環境中定義的所有變量和函數都保存在這個對象中。
當JavaScript解釋器初始化執行代碼時,它首先默認進入全局執行環境,從此刻開始,函數的每次調用都會創建一個新的執行環境。
每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中(execution stack)。在函數執行完后,棧將其環境彈出,把控制權返回給之前的執行環境。ECMAScript程序中的執行流正是由這個便利的機制控制著。執行環境可以分為創建和執行兩個階段。在創建階段,解析器首先會創建一個變量對象(variable object,也稱為活動對象activation object),它由定義在執行環境中的變量、函數聲明、和參數組成。在這個階段,作用域鏈會被初始化,this的值也會被最終確定。在執行階段,代碼被解釋執行。
Global Code,即全局的、不在任何函數里面的代碼,例如:一個js文件、嵌入在HTML頁面中的js代碼等。
Eval Code,即使用eval()函數動態執行的JS代碼。
Function Code,即用戶自定義函數中的函數體JS代碼。
不同類型的JavaScript代碼具有不同的Execution Context
Demo:
特別說明:圖片來自于笨蛋的座右銘博客
當javascript代碼被瀏覽器載入后,默認最先進入的是一個全局執行環境。當在全局執行環境中調用執行一個函數時,程序流就進入該被調用函數內,此時JS引擎就會為該函數創建一個新的執行環境,并且將其壓入到執行環境堆棧的頂部。瀏覽器總是執行當前在堆棧頂部的執行環境,一旦執行完畢,該執行環境就會從堆棧頂部被彈出,然后,進入其下的執行環境執行代碼。這樣,堆棧中的執行環境就會被依次執行并且彈出堆棧,直到回到全局執行環境。
此外還要注意一下幾點:
單線程
同步執行
唯一的全局執行環境
局部執行環境的個數沒有限制
每次某個函數被調用,就會有個新的局部執行環境為其創建,即使是多次調用的自身函數(即一個函數被調用多次,也會創建多個不同的局部執行環境)。
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈(scope chain。作用域鏈的用途是保證對執行環境有權訪問的所有變量和函數的有序訪問。
作用域鏈包含了執行環境棧中的每個執行環境對應的變量對象.
通過作用域鏈,可以決定變量的訪問和標識符的解析。
注意:全局執行環境的變量對象始終都是作用域鏈的最后一個對象。
在訪問變量時,就必須存在一個可見性的問題(內層環境可以訪問外層中的變量和函數,而外層環境不能訪問內層的變量和函數)。更深入的說,當訪問一個變量或調用一個函數時,JavaScript引擎將不同執行環境中的變量對象按照規則構建一個鏈表,在訪問一個變量時,先在鏈表的第一個變量對象上查找,如果沒有找到則繼續在第二個變量對象上查找,直到搜索到全局執行環境的變量對象即window對象。這也就形成了Scope Chain的概念。
特別說明:圖片來自于笨蛋的座右銘博客
作用域鏈圖,清楚的表達了執行環境與作用域的關系(一一對應的關系),作用域與作用域之間的關系(鏈表結構,由上至下的關系)。
Demo:
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 這里可以訪問color, anotherColor, 和 tempColor } // 這里可以訪問color 和 anotherColor,但是不能訪問 tempColor swapColors(); } changeColor(); // 這里只能訪問color console.log("Color is now " + color);
上述代碼一共包括三個執行環境:全局執行環境、changeColor()的局部執行環境、swapColors()的局部執行環境。
全局環境有一個變量color和一個函數changecolor();
changecolor()函數的局部環境中具有一個anothercolor屬性和一個swapcolors函數,當然,changecolor函數中可以訪問自身以及它外圍(即全局環境)中的變量;
swapcolor()函數的局部環境中具有一個變量tempcolor。在該函數內部可以訪問上面的兩個環境(changecolor和window)中的所有變量,因為那兩個環境都是它的父執行環境。
上述代碼的作用域鏈如下圖所示:
從上圖發現。內部環境可以通過作用域鏈訪問所有的外部環境,但是外部環境不能訪問內部環境中的任何變量和函數。
標識符解析(變量名或函數名搜索)是沿著作用域鏈一級一級地搜索標識符的過程。搜索過程始終從作用域鏈的前端開始,然后逐級地向后(全局執行環境)回溯,直到找到標識符為止。
執行環境為全局執行環境和局部執行環境,局部執行環境是函數執行過程中創建的。
作用域鏈是基于執行環境的變量對象的,由所有執行環境的變量對象(對于函數而言是活動對象,因為在函數執行環境中,變量對象是不能直接訪問的,此時由活動對象(activation object,縮寫為AO)扮演VO(變量對象)的角色。)共同組成。
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈。作用域鏈的用途:是保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。
window.a之所以是undefined,是因為var a = 1;發生了變量聲明提升。相當于如下代碼:
更多關于變量提升和執行上下文詳細解說這里就不多少了,不然越扯越深,有興趣可以看看這篇圖解,淺顯易懂:
前端基礎進階(二):執行上下文詳細圖解
相信大家看到這里,也很累了,但是也有收獲,大概有了一些深刻印象,對概念也有一些比較深入的理解了。
這里我就稍微總結一下,上面講了一些什么,對接下來的解析應該有很大的幫助。
**1. 瀏覽器的全局對象是window
全局執行環境即window對象所創建的,局部執行環境是函數執行過程中創建的。
全局對象,可以訪問所有其他所有預定義的對象、函數和屬性。
當javascript代碼被瀏覽器載入后,默認最先進入的是一個全局執行環境。
明白了執行上下文和作用域的一些概念,知道其中的運行機制和原理。**
我們再回頭看看這兩個demo比較,我們解釋清楚這個demo執行的結果。
demo1:
var name="jawil"; console.log(name);//jawil console.log(window.name)//jawil console.log(this.name)//jawill
demo2:
name="jawil"; console.log(name);//jawil console.log(window.name)//jawil console.log(this.name)//jawil
好,從例子看以看出,這兩個name都是全局屬性,未通過var聲明的變量a和通過var聲明的變量b,都可以通過this和window訪問到.
我們可以在控制臺打印出windowd對象,發現name成了window對象的一個屬性:
var name="jawil"; console.log(window); name2="test"; console.log(window);
這是其實一個作用域和上下文的問題。在JavaScript中,this指向當前的上下文,而var定義的變量值在當前作用域中有效。JavaScript有兩種作用域,全局作用域和局部作用域。局部作用域就是在一個函數里。var關鍵字使用來在當前作用于中創建局部變量的,而在瀏覽器中的JavaScript全局作用域中使用var語句時,會把申明的變量掛在window上,而全局作用域中的this上下文恰好指向的又是window,因此在全局作用域中var申明的變量和window上掛的變量,即this可訪問的變量有間接的聯系,但沒有直接聯系,更不是一樣的。
上面的分析我們知道了,全局變量,全局環境下的this,還有全局對象之間的關系了,具體總結一下就是:
**1. 全局環境的this會指向全局對象window,此時this===window;
全局變量會掛載在window對象下,會成為window下的一個屬性。
如果你沒有使用嚴格模式并給一個未聲明的變量賦值的話,JS會自動創建一個全局變量。**
那么用var聲明的全局變量賦值和未聲明的全局變量賦值到底有什么不同呢?這里不再是理解理解這道面試題的重點,想深入探究可以看看這篇文章:javascript中加var和不加var的區別 你真的懂嗎.
該回頭了,好累?,再來看看這道面試題:
![](http://odssgnnpf.bkt.clouddn.com/1440757221622060.jpg)
var name = "jay"; //一看這二逼就是周杰倫的死忠粉 var person = { name: "kang", pro: { name: "Michael", getName: function() { return this.name; } } }; console.log(person.pro.getName()); var pepole = person.pro.getName; console.log(pepole());
最后就成了為什么person.pro.getName()的this是person.pro而pepole()的this成了window對象。這里我們就要了解this的運行機制和原理。
在這里,我們需要得出一個非常重要一定要牢記于心的結論,this的指向,是在函數被調用的時候確定的。也就是執行上下文被創建時確定的。因此我們可以很容易就能理解到,一個函數中的this指向,可以是非常靈活的。
在一個函數上下文中,this由調用者提供,由調用函數的方式來決定。
如果調用者函數,被某一個對象所擁有,那么該函數在調用時,內部的this指向該對象。如果函數獨立調用,那么該函數內部的this,則指向undefined。但是在非嚴格模式中,當this指向undefined時,它會被自動指向全局對象。
person.pro.getName()中,getName是調用者,他不是獨立調用,被對象person.pro所擁有,因此它的this指向了person.pro。而pepole()作為調用者,盡管他與person.pro.getName的引用相同,但是它是獨立調用的,因此this指向undefined,在非嚴格模式,自動轉向全局window。
再來看一個例子,來加深理解這段話:
var a = 20; function getA() { return this.a; } var foo = { a: 10, getA: getA } console.log(foo.getA()); // 10
靈機一動,再來一個。如下例子。
function foo() { console.log(this.a) } function active(fn) { fn(); // 真實調用者,為獨立調用 } var a = 20; var obj = { a: 10, getA: foo } active(obj.getA);
這個例子提示一下,關于函數參數的傳遞賦值問題。
JS是按值傳遞還是按引用傳遞?
這里我就不多做解答了,大家自行揣摩。
以上關于this解答來自波同學的引用,我這里就偷了個懶在,直接拿來引用。
原文地址:前端基礎進階(五):全方位解讀this
最后把知道面試題梳理一下:
console.log(person.pro.getName());//Michael var pepole = person.pro.getName; console.log(pepole());//jay
person.pro.getName()中,getName是調用者,他不是獨立調用,被對象person.pro所擁有,因此它的this指向了person.pro,所以this.name=person.pro.name="Michael";
而pepole()作為調用者,盡管他與person.pro.getName的引用相同,但是它是獨立調用的,因此this指向undefined,在非嚴格模式,自動轉向全局window。
這道題實在非嚴格模式下,所以this指向了window,又因為全局變量掛載在window對象下,所以this.name=window.name=“jay”
完畢~寫的有點啰嗦,只是盡量想說明白,講清一些概念的東西,反正我是收獲很多,你呢?
參考文章:
JavaScript 全局對象
原生JS執行環境與作用域深入理解
理解Javascript_12_執行模型淺析
前端基礎進階(二):執行上下文詳細圖解
前端基礎進階(五):全方位解讀this
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88144.html
摘要:今天同學去面試,做了兩道面試題全部做錯了,發過來給道典型的面試題前端掘金在界中,開發人員的需求量一直居高不下。 排序算法 -- JavaScript 標準參考教程(alpha) - 前端 - 掘金來自《JavaScript 標準參考教程(alpha)》,by 阮一峰 目錄 冒泡排序 簡介 算法實現 選擇排序 簡介 算法實現 ... 圖例詳解那道 setTimeout 與循環閉包的經典面...
摘要:項目組長給我看了一道面試別人的面試題。打鐵趁熱,再來一道題來加深下理解。作者以樂之名本文原創,有不當的地方歡迎指出。 showImg(https://segmentfault.com/img/bVbur0z?w=600&h=400); 剛入職新公司,屬于公司萌新一枚,一天下午對著屏幕看代碼架構時。BI項目組長給我看了一道面試別人的JS面試題。 雖然答對了,但把理由說錯了,照樣不及格。 ...
摘要:通過查看的文檔可以發現整個分為個階段定時器相關任務,中我們關注的是它會執行和中到期的回調執行某些系統操作的回調內部使用執行,一定條件下會在這個階段阻塞住執行的回調如果或者關閉了,就會在這個階段觸發事件,執行事件的回調的代碼在文件中。 showImg(https://segmentfault.com/img/bVbd7B7?w=1227&h=644); 這次我們就不要那么多前戲,直奔主題...
摘要:背景項目中通過遠程調用服務框架調用了許多其它的服務其中有一個服務需要升級其升級不是版本上的升級而是整個服務重新取了一個名字使用的也是全新的包但是調用的方法沒有改變因此在升級時只是在調用服務類中修改了調用地址和調用返回實體由改為該中返回該調用 背景 項目中通過遠程調用服務框架調用了許多其它的服務,其中有一個服務wx/subscribe/contract/CircleService 需要升...
摘要:本篇就來教大家如何使用來爬取這些精美的英雄皮膚。有了英雄編號的對應關系,再找尋下英雄皮膚的鏈接規律。將圖片保存下來,并以英雄名稱皮膚序號方式命名效果展示最終的爬取效果如下圖所示。 showImg(https://segmentfault.com/img/remote/1460000018627654); 0.引言 作為一款現象級游戲,王者榮耀,想必大家都玩過或聽過,游戲里中各式各樣的英...
閱讀 1394·2021-11-08 13:14
閱讀 747·2021-09-23 11:31
閱讀 1038·2021-07-29 13:48
閱讀 2781·2019-08-29 12:29
閱讀 3371·2019-08-29 11:24
閱讀 1899·2019-08-26 12:02
閱讀 3688·2019-08-26 10:34
閱讀 3435·2019-08-23 17:07