摘要:瀏覽器總是運行位于作用域鏈頂部的當前執行上下文。作用域的前端永遠是當前執行代碼所在環境的變量對象而全局執行環境的變量對象始終是作用域鏈中的最后一個對象。調用棧為了達到當前執行位置所調用的所有函數。
ECMAScript中的變量值類型
基本類型 : Number, String, Boolean, Undefined, Null
引用類型 : Object, Array, Function, Date, RegExp
在將一個值賦給變量時解析器必須確定這個值是基本類型值還是引用類型值。
基本數據類型是按值訪問的, 因為可以操作保存在變量中的實際的值。
引用類型則不同, 它的值是保存在堆內存中的對象, 而JavaScript不允許直接訪問內存中的位置。
所以在操作對象時實際上是在操作對象的引用, 即引用類型的值是按引用訪問的。
基本類型的特點 :
1.值不會改變 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
引用類型的特點 :
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.
從上面的代碼不難看出在進行復制變量的時候基本類型進行的是類似創建副本的操作, 而引用類型則是對指向對象的指針的復制所以在復制操作結束后兩個變量將引用同一個對象。因此改變其中一個變量就會影響到另一個變量。
ECMAScript中規定所有函數的參數都是按值傳遞的。
function setAge(obj) { obj.age = 18; obj = {}; obj.age = 25; } var person = {} setAge(person); console.log(person.age) //18
在函數內部重新聲明了對象并修改了obj.age 的值, 若參數傳遞是按引用傳遞的那么person.age 應該輸出25, 但事實卻不是這樣。由于此時對象是按值傳遞, 故原始的引用仍然未變。事實上在函數被執行完畢后這個新創建的局部對象就會被立即銷毀。
typeof 用于檢測基本類型
instanceof 在檢測引用類型時, 用于判斷它是什么類型的對象( 因為所有引用類型的值都是Object的實例 )。
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
執行環境(Execution Context)與作用域
執行環境也被稱為執行上下文, 每一個執行環境中都有一個關聯的變量對象, 環境中定義的所有變量和函數都保存在這個對象中。
在Javascript中有三種代碼的執行環境 :
全局執行環境 --- 默認的最外圍的執行環境, 在瀏覽器中其關聯的變量對象被認為是window對象
函數執行環境 --- 每當調用一個函數時, 一個新的執行上下文就會被創建出來
Eval --- 接受字符串作為參數, 并將其作為javascript代碼去運行, eval函數并不會創建新的作用域
每次新創建的一個執行上下文會被添加到作用域鏈的頂部,有時也稱為執行或調用棧。瀏覽器總是運行位于作用域鏈頂部的當前執行上下文。一旦完成,當前執行上下文將從棧頂被移除并且將控制權歸還給之前的執行上下文。
下面來詳細講解一下函數執行環境的建立過程:
建立階段
建立arguments對象, 參數, 函數, 變量 ( 注意創建的順序 !)
建立作用域鏈
確定this的值
代碼執行階段
變量賦值
函數引用
執行其他代碼
(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);
這段代碼充分說明了函數執行環境建立再到執行的過程, 即首先是參數的創建, 然后再是在函數體內去尋找函數的聲明, 最后是變量聲明。值得注意的是當javascript引擎在尋找函數聲明時首先找到了foo 這個函數, 因而之后定義的變量則不會重新覆蓋其屬性, 引擎接下來就開始查找具體代碼段里面的變量聲明并添加到關聯變量對象的屬性中,并將其賦值為undefined , 因而像變量提升這種經典的問題又可以從執行環境創建過程的角度來回答并解決了。
作用域鏈與閉包當代碼在一個環境中執行時, 會創建變量對象的一個作用域鏈, 其用途就是保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域的前端永遠是當前執行代碼所在環境的變量對象, 而全局執行環境的變量對象始終是作用域鏈中的最后一個對象。在進行變量查找的時候就是通過作用域鏈一級一級的向上查找。而閉包中的一部分特性則是由作用域鏈這個重要特性決定的。
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.
這段代碼是一個簡單的閉包, 但它卻說明了作用域鏈中最重要的特性:
!!即內部環境可以通過作用域鏈訪問所有外部環境, 但外部環境不能訪問內部環境中的任何變量和函數 !!
PS : 另外再解釋一下幾個容易令人混淆或者說是難懂的概念。
變量對象在執行環境的建立階段被創建, 在未進入執行階段之前其中的屬性不能被訪問, 而當其進入執行階段后變量對象變為活動對象, 接下來就可以進行執行階段中的步驟了。
作用域與執行環境是兩個完全不同的概念, javascript代碼執行的過程其實有兩個階段即代碼編譯階段和代碼執行階段, 作用域是在編譯階段創建的一套規則, 用來管理引擎如何在當前作用域以及嵌套的子作用域中根據標識符名稱進行變量查找, 而執行上下文的創建則是在代碼執行階段進行的。作用域鏈是由一系列變量對象組成, 我們可以在這個單向通道中, 查詢變量對象中的標識符, 這樣就可以訪問到上一層作用域中的變量了。
this詳解
在理解this的綁定過程之前, 必須要理解調用棧和調用位置這兩個概念, 因為this的綁定完全取決于從調用棧中分析出的調用位置。而調用位置就在當前正在執行的函數的前一個調用中。
調用棧:為了達到當前執行位置所調用的所有函數。
調用位置:函數在代碼中被調用的位置( 而不是聲明的位置 )。
function head() { //當前調用棧為head console.log("first"); body(); //body的調用位置 --> head } function body() { //當前調用棧為head -> body console.log("second"); footer(); //footer的調用位置 --> body } function footer() { //當前的調用棧為head -> body -> footer console.log("third"); } head(); //head的調用位置 --> 全局作用域
默認綁定
隱式綁定
顯示綁定
new綁定
當函數獨立調用, 即直接使用不帶任何修飾的函數引用進行調用時this使用默認綁定, 此時this指向全局對象。
var a = 2; function foo() { console.log( this.a ); } foo(); // 2
當函數引用有上下文對象時, 隱式綁定規則會把函數調用中的this綁定到這個上下文對象。
var obj = { a : 2, foo : foo } function foo() { console.log( this.a ); } obj.foo(); //2
因為調用foo()時this被綁定到obj, 因此這里的this 相當于obj 。
當隱式綁定的函數被顯式或者隱式賦值時會丟失綁定對象, 從而把this綁定到全局對象上或者undefined上。而在回調函數中的this綁定會丟失也正是因為參數傳遞其實就是一種隱式賦值。
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 ->內置函數中的隱式賦值,類似于下面這段代碼 function setTimeout(fn, delay) { //等待delay毫秒 fn(); }
通過Function.prototype 中的call , apply , bind 來直接指定this的綁定對象。
call和apply都是立即執行的函數, 并且接受參數的形式不同。
bind則是創建一個新的包裝函數并且返回, 而不是執行。
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方法可接受參數并將變量傳遞到下層函數 } excute(5,10); //2 5 10 20 var baz = calculate.bind(obj); //bind方法將this綁定到obj對象上 baz(8,5); //2 8 5 21
在JavaScript中構造函數只是一些使用new操作符時被調用的普通函數, 即發生構造函數調用時會執行以下操作:
創建一個全新的對象
新對象被執行[[Prototype]]連接
新對象會綁定到函數調用的this
若函數沒有返回對象, new表達式中的函數調用會自動返回這個新對象
function Foo(a) { this.a = a; } var bar = new Foo(6); console.log( bar.a ); //6
new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定
ES6中的箭頭函數并不使用this的四種標準原則,它是根據外層( 函數或者全局 )作用域來決定this。
先來看下一種常見的this綁定丟失情景:
function foo() { setTimeout(function() { console.log( this.a ); },1000); } var obj = { a : 2 } foo.call(obj); //undefined
這里由于setTimeout中發生的隱式丟失因而this應用了默認規則, 因而輸出undefined 。那么如何將this綁定到我們想要的obj對象上呢?
var obj = { a : 2 } function foo() { setTimeout( () => { console.log( this.a ); },1000); } foo.call(obj) //2
顯然箭頭函數中的this 在詞法上繼承了foo , 因而它會捕獲調用時foo的this, 即this被綁定到obj對象上。
參考書籍:
《JavaScript高級程序設計》
《你不知道的JavaScript》(上)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92824.html
摘要:申明與賦值立即執行的函數表達式,通過創建一個函數,并且立即執行,來構造一個新的域,從而控制的范圍。函數接受一個的形參,該參數是一個對象引用,并執行了。在最新的標準中,引入了一個新概念。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄...
摘要:申明與賦值立即執行的函數表達式,通過創建一個函數,并且立即執行,來構造一個新的域,從而控制的范圍。函數接受一個的形參,該參數是一個對象引用,并執行了。在最新的標準中,引入了一個新概念。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄...
摘要:申明與賦值立即執行的函數表達式,通過創建一個函數,并且立即執行,來構造一個新的域,從而控制的范圍。函數接受一個的形參,該參數是一個對象引用,并執行了。在最新的標準中,引入了一個新概念。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄...
摘要:全局執行環境的變量對象始終是作用域鏈中的最后一個變量對象。綜上,每個函數對應一個執行環境,每個執行環境對應一個變量對象,而多個變量對象構成了作用域鏈,如果當前執行環境是函數,那么其活動對象在作用域鏈的前端。 1.幾個概念 先說幾個概念:函數、執行環境、變量對象、作用域鏈、活動對象。這幾個東東之間有什么關系呢,往下看~ 函數 函數大家都知道,我想說的是,js中,在函數內部有兩個特殊...
閱讀 2312·2021-11-17 09:33
閱讀 843·2021-10-13 09:40
閱讀 574·2019-08-30 15:54
閱讀 778·2019-08-29 15:38
閱讀 2417·2019-08-28 18:15
閱讀 2475·2019-08-26 13:38
閱讀 1842·2019-08-26 13:36
閱讀 2129·2019-08-26 11:36