預解析:瀏覽器
所謂的預解析就是:在當前做用域中,JavaScript代碼執行以前,瀏覽器首先會默認的把全部帶var和function聲明的變量進行提早的聲明或者定義。閉包
1.聲明和定義函數
var num = 24;
這行簡單的代碼實際上是兩個步驟:聲明和定義。
var num;
告訴瀏覽器在全局做用域中有一個num變量了,若是一個變量只是聲明瞭,可是沒有賦值,默認值是undefined。num = 12;
定義就是給變量進行賦值。2.var聲明的變量和function聲明的函數在預解析的區別spa
var聲明的變量和function聲明的函數在預解析的時候有區別,code
var聲明的變量在預解析的時候只是提早的聲明,function聲明的函數在預解析的時候會提早聲明而且會同時定義。ip
也就是說var聲明的變量和function聲明的函數的區別是在聲明的同時有沒同時進行定義。作用域
3. 預解析只發生在當前的做用域下io
程序最開始的時候,只對window下的變量和函數進行預解析,只有函數執行的時候纔會對函數中的變量很函數進行預解析。console
console.log(num); // undefined var num = 24; console.log(num); // 20 func(100 , 200); function func(num1 , num2) { var total = num1 + num2; console.log(total); // 300 }
第一次輸出num的時候,因爲預解析的緣由,只聲明瞭尚未定義,因此會輸出undefined;第二次輸出num的時候,已經定義了,因此輸出24。function
因爲函數的聲明和定義是同時進行的,因此func()
雖然是在func函數定義聲明處以前調用的,可是依然能夠正常的調用,會正常輸出300。
做用域鏈
1.先理解如下三個概念:
在私有做用域中,代碼執行的時候,遇到了一個變量,首先須要肯定它是否爲私有變量,若是是私有變量,那麼和外面的任何東西都沒有關係,若是不是私有的,則往當前做用域的上級做用域進行查找,若是上級做用域也沒有則繼續查找,一直查找到window爲止,這就是做用域鏈。
2.當函數執行的時候,首先會造成一個新的私有做用域,而後按照以下的步驟執行:
函數造成一個新的私有的做用域,保護了裏面的私有變量不受外界的干擾(外面修改不了私有的,私有的也修改不了外面的),這也就是閉包的概念。
console.log(total); var total = 0; function func(num1, num2) { console.log(total); var total = num1 + num2; console.log(total); } func(100 , 200); console.log(total);
以上代碼執行的時候,第一次輸出total的時候會輸出undefined(由於預解析),當執行func(100,200)的時候,會執行函數體裏的內容,此時func函數會造成一個新的私有做用域,按照以前描述的步驟:
由於在func函數內進行了預解析,因此func函數裏面的total變量會被預解析,在函數內第一次輸出total的時候,會輸出undefined,接着爲total賦值了,第二次輸出total的時候就輸出300。 由於函數體內有var聲明的變量total,函數體內的輸出total並非全局做用域中的total。
最後一次輸出total的時候,輸出0,這裏輸出的是全局做用域中的total。
console.log(total); var total = 0; function func(num1, num2) { console.log(total); total = num1 + num2; console.log(total); } func(100 , 200); console.log(total);
3.全局做用域下帶var和不帶var的區別
在全局做用域中聲明變量帶var能夠進行預解析,因此在賦值的前面執行不會報錯;聲明變量的時候不帶var的時候,不能進行預解析,因此在賦值的前面執行會報錯。
console.log(num1); // undefined var num1 = 12; console.log(num2); // 報錯 : num2 is not defined. num2 = 12;
至關於給window增長了一個num2的屬性名,屬性值是12;
至關於給全局做用域增長了一個全局變量num1,可是不只如此,它也至關於給window增長了一個屬性名num,屬性值是12;
num2 = 12;var num1 = 12;
問題:在私有做用域中出現一個變量,不是私有的,則往上級做用域進行查找,上級沒有則繼續向上查找,一直找到window爲止,若是window也沒有呢?
獲取值:console.log(total);
--> 報錯 Uncaught ReferenceError: total is not defined.
設置值:total= 100;
--> 至關於給window增長了一個屬性名total,屬性值是100
function fn() { // console.log(total); // Uncaught ReferenceError: total is not defined total = 100; } fn(); console.log(total);
注意:JS中,若是在不進行任何特殊處理的狀況下,上面的代碼報錯,下面的代碼都再也不執行了!
預解析中的一些變態機制:
1 無論條件是否成立,都要把帶var的進行提早的聲明
if (!('num' in window)) { var num = 12; } console.log(num); // undefined
JavaScript進行預解析的時候,會忽略全部if條件,由於在ES6以前並無塊級做用域的概念。
以上示例會先將num預解析,而預解析會將該變量添加到window中,做爲window的一個屬性。
那麼 'num' in window 就返回true,取反以後爲false,這時代碼執行不會進入if塊裏面,num也就沒有被賦值,
最後輸出爲undefined。console.log(num)
2 只預解析「=」左邊的,右邊的是值,不參與預解析
fn(); // -> undefined(); // Uncaught TypeError: fn is not a function var fn = function () { console.log('ok'); } fn(); -> 'ok' function fn() { console.log('ok'); } fn(); -> 'ok'
建議:聲明變量的時候儘可能使用var fn = ...的方式。
3 自執行函數:定義和執行一塊兒完成
(function (num) { console.log(num); })(100);
自執行函數定義的那個function在全局做用域下不進行預解析,當代碼執行到這個位置的時候,定義和執行一塊兒完成了。
補充:其餘定義自執行函數的方式
~ function (num) {}(100) + function (num) {}(100) - function (num) {}(100) ! function (num) {}(100)
4 return下的代碼依然會進行預解析
function fn() { console.log(num); // -> undefined return function () { }; var num = 100; } fn();
函數體中return下面的代碼,雖然再也不執行了,可是須要進行預解析,return中的代碼,都是咱們的返回值,因此不進行預解析。
5 名字已經聲明過了,不須要從新的聲明,可是須要從新的賦值
var fn = 13; function fn() { console.log('ok'); } fn(); // Uncaught TypeError: fn is not a function
eg: 經典題目
fn(); // -> 2 function fn() {console.log(1);} fn(); // -> 2 var fn = 10; // -> fn = 10 fn(); // -> 10() Uncaught TypeError: fn is not a function function fn() {console.log(2);} fn();
歡迎留言,歡迎踩踩踩!