ECMAScript中的變量值類型
在將一個值賦給變量時解析器必須肯定這個值是基本類型值仍是引用類型值。javascript
基本數據類型是按值訪問的, 由於能夠操做保存在變量中的實際的值。前端
引用類型則不一樣, 它的值是保存在堆內存中的對象, 而JavaScript不容許直接訪問內存中的位置。java
因此在操做對象時其實是在操做對象的引用, 即引用類型的值是按引用訪問的。瀏覽器
基本類型的特色 :閉包
1.值不會改變 2. 不能夠添加屬性和方法app
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
引用類型的特色 :ide
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.
從上面的代碼不難看出在進行復制變量的時候基本類型進行的是相似建立副本的操做, 而引用類型則是對指向對象的指針的複製因此在複製操做結束後兩個變量將引用同一個對象。所以改變其中一個變量就會影響到另外一個變量。this
ECMAScript中規定全部函數的參數都是按值傳遞的。prototype
function setAge(obj) { obj.age = 18; obj = {}; obj.age = 25; } var person = {} setAge(person); console.log(person.age) //18
在函數內部從新聲明瞭對象並修改了obj.age
的值, 若參數傳遞是按引用傳遞的那麼person.age
應該輸出25
, 但事實卻不是這樣。因爲此時對象是按值傳遞, 故原始的引用仍然未變。事實上在函數被執行完畢後這個新建立的局部對象就會被當即銷燬。
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中有三種代碼的執行環境 :
每次新建立的一個執行上下文會被添加到做用域鏈的頂部,有時也稱爲執行或調用棧。瀏覽器老是運行位於做用域鏈頂部的當前執行上下文。一旦完成,當前執行上下文將從棧頂被移除而且將控制權歸還給以前的執行上下文。
下面來詳細講解一下函數執行環境的創建過程:
創建階段
代碼執行階段
(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 : 另外再解釋一下幾個容易使人混淆或者說是難懂的概念。
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的調用位置 --> 全局做用域
當函數獨立調用, 即直接使用不帶任何修飾的函數引用進行調用時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操做符時被調用的普通函數, 即發生構造函數調用時會執行如下操做:
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》(上)