摘要:瀏覽器總是運(yùn)行位于作用域鏈頂部的當(dāng)前執(zhí)行上下文。作用域的前端永遠(yuǎn)是當(dāng)前執(zhí)行代碼所在環(huán)境的變量對(duì)象而全局執(zhí)行環(huán)境的變量對(duì)象始終是作用域鏈中的最后一個(gè)對(duì)象。調(diào)用棧為了達(dá)到當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù)。
ECMAScript中的變量值類(lèi)型
基本類(lèi)型 : Number, String, Boolean, Undefined, Null
引用類(lèi)型 : Object, Array, Function, Date, RegExp
在將一個(gè)值賦給變量時(shí)解析器必須確定這個(gè)值是基本類(lèi)型值還是引用類(lèi)型值。
基本數(shù)據(jù)類(lèi)型是按值訪(fǎng)問(wèn)的, 因?yàn)榭梢圆僮鞅4嬖谧兞恐械膶?shí)際的值。
引用類(lèi)型則不同, 它的值是保存在堆內(nèi)存中的對(duì)象, 而JavaScript不允許直接訪(fǎng)問(wèn)內(nèi)存中的位置。
所以在操作對(duì)象時(shí)實(shí)際上是在操作對(duì)象的引用, 即引用類(lèi)型的值是按引用訪(fǎng)問(wèn)的。
基本類(lèi)型的特點(diǎn) :
1.值不會(huì)改變 2. 不可以添加屬性和方法
var name = "BarryAllen"; name.substring(5); //"Allen" console.log(name) //BarryAllen name.identity = "Flash"; console.log(name.identity) //undefined name.skill = function() { console.log("Running very fast.") } name.skill(); //name.skill is not a function
引用類(lèi)型的特點(diǎn) :
1.值可以被修改 2. 可以添加屬性和方法
var obj = {}; obj.name = "BarryAllen"; var change = obj; change.name = "OliverQueen"; console.log(obj.name); //OliverQueen obj.identity = "Flash"; console.log(obj.identity) //Flash obj.skill = function() { console.log("Running very fast.") } obj.skill(); //Running very fast.
從上面的代碼不難看出在進(jìn)行復(fù)制變量的時(shí)候基本類(lèi)型進(jìn)行的是類(lèi)似創(chuàng)建副本的操作, 而引用類(lèi)型則是對(duì)指向?qū)ο蟮闹羔樀膹?fù)制所以在復(fù)制操作結(jié)束后兩個(gè)變量將引用同一個(gè)對(duì)象。因此改變其中一個(gè)變量就會(huì)影響到另一個(gè)變量。
ECMAScript中規(guī)定所有函數(shù)的參數(shù)都是按值傳遞的。
function setAge(obj) { obj.age = 18; obj = {}; obj.age = 25; } var person = {} setAge(person); console.log(person.age) //18
在函數(shù)內(nèi)部重新聲明了對(duì)象并修改了obj.age 的值, 若參數(shù)傳遞是按引用傳遞的那么person.age 應(yīng)該輸出25, 但事實(shí)卻不是這樣。由于此時(shí)對(duì)象是按值傳遞, 故原始的引用仍然未變。事實(shí)上在函數(shù)被執(zhí)行完畢后這個(gè)新創(chuàng)建的局部對(duì)象就會(huì)被立即銷(xiāo)毀。
typeof 用于檢測(cè)基本類(lèi)型
instanceof 在檢測(cè)引用類(lèi)型時(shí), 用于判斷它是什么類(lèi)型的對(duì)象( 因?yàn)樗幸妙?lèi)型的值都是Object的實(shí)例 )。
var num = 786; var bol = true; var name = "Violet"; console.log(typeof num +"~"+ typeof bol +"~"+ typeof name); //number~boolean~string var arr = []; var func = new Function(); console.log(arr instanceof Array) //true console.log(func instanceof Function) //true
執(zhí)行環(huán)境(Execution Context)與作用域
執(zhí)行環(huán)境也被稱(chēng)為執(zhí)行上下文, 每一個(gè)執(zhí)行環(huán)境中都有一個(gè)關(guān)聯(lián)的變量對(duì)象, 環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。
在Javascript中有三種代碼的執(zhí)行環(huán)境 :
全局執(zhí)行環(huán)境 --- 默認(rèn)的最外圍的執(zhí)行環(huán)境, 在瀏覽器中其關(guān)聯(lián)的變量對(duì)象被認(rèn)為是window對(duì)象
函數(shù)執(zhí)行環(huán)境 --- 每當(dāng)調(diào)用一個(gè)函數(shù)時(shí), 一個(gè)新的執(zhí)行上下文就會(huì)被創(chuàng)建出來(lái)
Eval --- 接受字符串作為參數(shù), 并將其作為javascript代碼去運(yùn)行, eval函數(shù)并不會(huì)創(chuàng)建新的作用域
每次新創(chuàng)建的一個(gè)執(zhí)行上下文會(huì)被添加到作用域鏈的頂部,有時(shí)也稱(chēng)為執(zhí)行或調(diào)用棧。瀏覽器總是運(yùn)行位于作用域鏈頂部的當(dāng)前執(zhí)行上下文。一旦完成,當(dāng)前執(zhí)行上下文將從棧頂被移除并且將控制權(quán)歸還給之前的執(zhí)行上下文。
下面來(lái)詳細(xì)講解一下函數(shù)執(zhí)行環(huán)境的建立過(guò)程:
建立階段
建立arguments對(duì)象, 參數(shù), 函數(shù), 變量 ( 注意創(chuàng)建的順序 !)
建立作用域鏈
確定this的值
代碼執(zhí)行階段
變量賦值
函數(shù)引用
執(zhí)行其他代碼
(function (obj) { console.log(typeof obj); //number console.log(typeof foo); //function console.log(typeof boxer); //undefined var foo = "Mashics"; function foo() { document.write("This is a function."); } var boxer = function() { document.write("I am a boxer."); } })(666);
這段代碼充分說(shuō)明了函數(shù)執(zhí)行環(huán)境建立再到執(zhí)行的過(guò)程, 即首先是參數(shù)的創(chuàng)建, 然后再是在函數(shù)體內(nèi)去尋找函數(shù)的聲明, 最后是變量聲明。值得注意的是當(dāng)javascript引擎在尋找函數(shù)聲明時(shí)首先找到了foo 這個(gè)函數(shù), 因而之后定義的變量則不會(huì)重新覆蓋其屬性, 引擎接下來(lái)就開(kāi)始查找具體代碼段里面的變量聲明并添加到關(guān)聯(lián)變量對(duì)象的屬性中,并將其賦值為undefined , 因而像變量提升這種經(jīng)典的問(wèn)題又可以從執(zhí)行環(huán)境創(chuàng)建過(guò)程的角度來(lái)回答并解決了。
作用域鏈與閉包當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí), 會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈, 其用途就是保證對(duì)執(zhí)行環(huán)境有權(quán)訪(fǎng)問(wèn)的所有變量和函數(shù)的有序訪(fǎng)問(wèn)。作用域的前端永遠(yuǎn)是當(dāng)前執(zhí)行代碼所在環(huán)境的變量對(duì)象, 而全局執(zhí)行環(huán)境的變量對(duì)象始終是作用域鏈中的最后一個(gè)對(duì)象。在進(jìn)行變量查找的時(shí)候就是通過(guò)作用域鏈一級(jí)一級(jí)的向上查找。而閉包中的一部分特性則是由作用域鏈這個(gè)重要特性決定的。
var outer = "Margin"; function foo() { var mider = "Padding"; function baz() { var inner = "Content"; console.log( "Gotcha! " + outer + " and " + mider + " . " ); } return baz; } var fn = foo(); fn(); //Gotcha! Margin and Padding . console.log(inner); //inner is not defined.
這段代碼是一個(gè)簡(jiǎn)單的閉包, 但它卻說(shuō)明了作用域鏈中最重要的特性:
??!即內(nèi)部環(huán)境可以通過(guò)作用域鏈訪(fǎng)問(wèn)所有外部環(huán)境, 但外部環(huán)境不能訪(fǎng)問(wèn)內(nèi)部環(huán)境中的任何變量和函數(shù) ?。?/p>
PS : 另外再解釋一下幾個(gè)容易令人混淆或者說(shuō)是難懂的概念。
變量對(duì)象在執(zhí)行環(huán)境的建立階段被創(chuàng)建, 在未進(jìn)入執(zhí)行階段之前其中的屬性不能被訪(fǎng)問(wèn), 而當(dāng)其進(jìn)入執(zhí)行階段后變量對(duì)象變?yōu)榛顒?dòng)對(duì)象, 接下來(lái)就可以進(jìn)行執(zhí)行階段中的步驟了。
作用域與執(zhí)行環(huán)境是兩個(gè)完全不同的概念, javascript代碼執(zhí)行的過(guò)程其實(shí)有兩個(gè)階段即代碼編譯階段和代碼執(zhí)行階段, 作用域是在編譯階段創(chuàng)建的一套規(guī)則, 用來(lái)管理引擎如何在當(dāng)前作用域以及嵌套的子作用域中根據(jù)標(biāo)識(shí)符名稱(chēng)進(jìn)行變量查找, 而執(zhí)行上下文的創(chuàng)建則是在代碼執(zhí)行階段進(jìn)行的。作用域鏈?zhǔn)怯梢幌盗凶兞繉?duì)象組成, 我們可以在這個(gè)單向通道中, 查詢(xún)變量對(duì)象中的標(biāo)識(shí)符, 這樣就可以訪(fǎng)問(wèn)到上一層作用域中的變量了。
this詳解
在理解this的綁定過(guò)程之前, 必須要理解調(diào)用棧和調(diào)用位置這兩個(gè)概念, 因?yàn)閠his的綁定完全取決于從調(diào)用棧中分析出的調(diào)用位置。而調(diào)用位置就在當(dāng)前正在執(zhí)行的函數(shù)的前一個(gè)調(diào)用中。
調(diào)用棧:為了達(dá)到當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù)。
調(diào)用位置:函數(shù)在代碼中被調(diào)用的位置( 而不是聲明的位置 )。
function head() { //當(dāng)前調(diào)用棧為head console.log("first"); body(); //body的調(diào)用位置 --> head } function body() { //當(dāng)前調(diào)用棧為head -> body console.log("second"); footer(); //footer的調(diào)用位置 --> body } function footer() { //當(dāng)前的調(diào)用棧為head -> body -> footer console.log("third"); } head(); //head的調(diào)用位置 --> 全局作用域
默認(rèn)綁定
隱式綁定
顯示綁定
new綁定
當(dāng)函數(shù)獨(dú)立調(diào)用, 即直接使用不帶任何修飾的函數(shù)引用進(jìn)行調(diào)用時(shí)this使用默認(rèn)綁定, 此時(shí)this指向全局對(duì)象。
var a = 2; function foo() { console.log( this.a ); } foo(); // 2
當(dāng)函數(shù)引用有上下文對(duì)象時(shí), 隱式綁定規(guī)則會(huì)把函數(shù)調(diào)用中的this綁定到這個(gè)上下文對(duì)象。
var obj = { a : 2, foo : foo } function foo() { console.log( this.a ); } obj.foo(); //2
因?yàn)檎{(diào)用foo()時(shí)this被綁定到obj, 因此這里的this 相當(dāng)于obj 。
當(dāng)隱式綁定的函數(shù)被顯式或者隱式賦值時(shí)會(huì)丟失綁定對(duì)象, 從而把this綁定到全局對(duì)象上或者undefined上。而在回調(diào)函數(shù)中的this綁定會(huì)丟失也正是因?yàn)閰?shù)傳遞其實(shí)就是一種隱式賦值。
var a = "Global"; var obj = { a : 2, foo : foo } function foo() { console.log( this.a ); } var bar = obj.foo; bar(); //Global ->顯示賦值 function doFoo(fn) { fn(); } doFoo( obj.foo ); //Global ->隱式賦值 setTimeout(obj.foo, 1000); //Global ->內(nèi)置函數(shù)中的隱式賦值,類(lèi)似于下面這段代碼 function setTimeout(fn, delay) { //等待delay毫秒 fn(); }
通過(guò)Function.prototype 中的call , apply , bind 來(lái)直接指定this的綁定對(duì)象。
call和apply都是立即執(zhí)行的函數(shù), 并且接受參數(shù)的形式不同。
bind則是創(chuàng)建一個(gè)新的包裝函數(shù)并且返回, 而不是執(zhí)行。
var obj = { a : 2 } function foo() { console.log( this.a ); } var bar = function() { foo.call(obj); } bar(); //2 -->硬綁定 function calculate(b, c) { console.log(this.a, b, c); return (this.a * b) + c; } var excute = function() { return calculate.apply(obj, arguments); //apply方法可接受參數(shù)并將變量傳遞到下層函數(shù) } excute(5,10); //2 5 10 20 var baz = calculate.bind(obj); //bind方法將this綁定到obj對(duì)象上 baz(8,5); //2 8 5 21
在JavaScript中構(gòu)造函數(shù)只是一些使用new操作符時(shí)被調(diào)用的普通函數(shù), 即發(fā)生構(gòu)造函數(shù)調(diào)用時(shí)會(huì)執(zhí)行以下操作:
創(chuàng)建一個(gè)全新的對(duì)象
新對(duì)象被執(zhí)行[[Prototype]]連接
新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this
若函數(shù)沒(méi)有返回對(duì)象, new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象
function Foo(a) { this.a = a; } var bar = new Foo(6); console.log( bar.a ); //6
new綁定 > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定
ES6中的箭頭函數(shù)并不使用this的四種標(biāo)準(zhǔn)原則,它是根據(jù)外層( 函數(shù)或者全局 )作用域來(lái)決定this。
先來(lái)看下一種常見(jiàn)的this綁定丟失情景:
function foo() { setTimeout(function() { console.log( this.a ); },1000); } var obj = { a : 2 } foo.call(obj); //undefined
這里由于setTimeout中發(fā)生的隱式丟失因而this應(yīng)用了默認(rèn)規(guī)則, 因而輸出undefined 。那么如何將this綁定到我們想要的obj對(duì)象上呢?
var obj = { a : 2 } function foo() { setTimeout( () => { console.log( this.a ); },1000); } foo.call(obj) //2
顯然箭頭函數(shù)中的this 在詞法上繼承了foo , 因而它會(huì)捕獲調(diào)用時(shí)foo的this, 即this被綁定到obj對(duì)象上。
參考書(shū)籍:
《JavaScript高級(jí)程序設(shè)計(jì)》
《你不知道的JavaScript》(上)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92824.html
摘要:申明與賦值立即執(zhí)行的函數(shù)表達(dá)式,通過(guò)創(chuàng)建一個(gè)函數(shù),并且立即執(zhí)行,來(lái)構(gòu)造一個(gè)新的域,從而控制的范圍。函數(shù)接受一個(gè)的形參,該參數(shù)是一個(gè)對(duì)象引用,并執(zhí)行了。在最新的標(biāo)準(zhǔn)中,引入了一個(gè)新概念。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專(zhuān)欄...
摘要:申明與賦值立即執(zhí)行的函數(shù)表達(dá)式,通過(guò)創(chuàng)建一個(gè)函數(shù),并且立即執(zhí)行,來(lái)構(gòu)造一個(gè)新的域,從而控制的范圍。函數(shù)接受一個(gè)的形參,該參數(shù)是一個(gè)對(duì)象引用,并執(zhí)行了。在最新的標(biāo)準(zhǔn)中,引入了一個(gè)新概念。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專(zhuān)欄...
摘要:申明與賦值立即執(zhí)行的函數(shù)表達(dá)式,通過(guò)創(chuàng)建一個(gè)函數(shù),并且立即執(zhí)行,來(lái)構(gòu)造一個(gè)新的域,從而控制的范圍。函數(shù)接受一個(gè)的形參,該參數(shù)是一個(gè)對(duì)象引用,并執(zhí)行了。在最新的標(biāo)準(zhǔn)中,引入了一個(gè)新概念。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專(zhuān)欄...
摘要:全局執(zhí)行環(huán)境的變量對(duì)象始終是作用域鏈中的最后一個(gè)變量對(duì)象。綜上,每個(gè)函數(shù)對(duì)應(yīng)一個(gè)執(zhí)行環(huán)境,每個(gè)執(zhí)行環(huán)境對(duì)應(yīng)一個(gè)變量對(duì)象,而多個(gè)變量對(duì)象構(gòu)成了作用域鏈,如果當(dāng)前執(zhí)行環(huán)境是函數(shù),那么其活動(dòng)對(duì)象在作用域鏈的前端。 1.幾個(gè)概念 先說(shuō)幾個(gè)概念:函數(shù)、執(zhí)行環(huán)境、變量對(duì)象、作用域鏈、活動(dòng)對(duì)象。這幾個(gè)東東之間有什么關(guān)系呢,往下看~ 函數(shù) 函數(shù)大家都知道,我想說(shuō)的是,js中,在函數(shù)內(nèi)部有兩個(gè)特殊...
閱讀 2338·2021-11-17 09:33
閱讀 862·2021-10-13 09:40
閱讀 587·2019-08-30 15:54
閱讀 792·2019-08-29 15:38
閱讀 2427·2019-08-28 18:15
閱讀 2490·2019-08-26 13:38
閱讀 1857·2019-08-26 13:36
閱讀 2142·2019-08-26 11:36