目錄
寫在最後
)
#前言等等經典的專案知識點。取名為專案進階
是因為它們在許多場合的「出鏡率高」很高,為了避免化身
,《JavaScript專案進階系列》誕生了! ! !
一、有趣的現象
#依照大家的常識,JavaScript程式碼在執行是一定是自上而下的,你需要輸出一個字串,當然需要事先宣告一個
1.1 我以為的開局
var str = '123';console.log(str); // 123
我們調換程式碼的位置在再看:console.log(str); // undefinedvar str = '123';
#當我看完了前兩段程式碼並且進行了「深度思考」後,我好像找到規律了,那就是:在當前程式碼區塊後函數中,在變數宣告和初始化
使用變量,會拿不到正確的值。
1.2 其實是這樣的
帶著上面的「結論」我來到了這裡var val = '余光';(function(){
console.log(val); // 余光})();
果然如此! ,在
耶穌也阻擋不了我拿到val的值,我說的! ! !
當我看到下面一段程式碼時,我已經產生了動搖,此事必要蹊蹺。
var val = '余光';(function(){ console.log(val); // undefined var val = '测试';})();
Ps:如果大家立即執行函數有疑問,不妨看看《JavaScript之深入理解立即調用函數表達式(IIFE)》吧~
這…我慫了,是什麼原因導致這樣的現象發生的呢? Js又是如果處理的呢?
二、Js的預解析
在目前的作用域內,無論在哪裡變數聲明,在幕後,都會進行一次看不見的移動。 注意:僅宣告被「移動」
。即聲明和賦值在某些時候被動分開了。而這次看不見的移動其實就是
。
來看一段《你知不知道的Js》中經典的例子:name = '余光'; // 未添加关键字(未声明),name为全局变量,,即window.name = '余光'var name; // 再次声明name,此时name未进行初始化,它的值是undefined吗?console.log(name); // ?
結果是成功打印“余光”,這樣
2.1 核心:預解析
為了搞清楚這個核心問題,我們需要回顧一下,引擎會在解釋JavaScript程式碼之前先將其編譯。編譯階段中的一部分工作就是找到所有的聲明,並用適當的作用域將它們關聯起來。有興趣的小夥伴可以閱讀《JavaScript中的變數物件》和《從作用域到作用域鏈》這兩篇文章哦~因此,發生這樣的事情,包括
變數
和函數
在內的所有宣告都會在任何程式碼被執行前先被處理。當你看到
即程式碼是這樣寫的:
// 我们看到的代码:var name = '余光';
但Js會將它解析成:
// 声明(Declaration)var name; // 声明但未初始化,所以分配 undefined// 初始化(Initialization)name = '余光'; // 初始化(赋值)
var name; // 声明name提到作用域顶部,并被分配了一个undefinedname = '余光'; // 进行初始化操作console.log(name); // '余光'
2.2 注意:只有宣告被提升了
只有宣告會被提升,而賦值和其他程式碼邏輯會在執行到程式碼的位置時才會生效。所以會有下面的問題:
foo();function foo(){ console.log(name); // undefined var name = '余光';}
2.3 每個作用域都會進行提升操作
還是上面的程式碼:
foo();function foo(){ console.log(name); // undefined var name = '余光';}
實際上它在編譯時是這樣的:
function foo(){ var name; // 声明 console.log(name); // undefined name = '余光'; // 初始化}foo(); // 函数执行
三、提升之間的優先權
既然我們知道了
變數
和
我们分析下面的代码:
foo();var foo; // 1function foo(){ console.log('余光');}foo = function(){ console.log('小李');}
本着函数优先提升的原则,他会被解析成这样:
function foo(){ console.log('余光');}foo(); // 余光foo = function(){ console.log('小李');}
注意,var foo
因为是一个重复声明,且优先级低于函数声明
所以它被忽略掉了。
最直观的例子,就是在函数字面量前调用该函数:
foo();var foo = function(){ console.log(1);}// TypeError: foo is not a function
这段程序中:
foo
被提升并分配给所在作用域(在这里是全局作用域),因此在执行foo()时不会导致ReferenceError(),而是会提示你 foo is not a function
。四、ES6和小结
ES6新增了两个命令let
和const
,用来声明变量,有关它们完整的概念我会在《ES6基础系列》中总结,提起它们,是因为变量提升在它们身上不会存在。
let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
// var 的情况console.log(foo); // 输出undefinedvar foo = 2;// let 的情况console.log(bar); // 报错ReferenceErrorlet bar = 2;
上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
在变量提升上,const和let一样,只在声明所在的块级作用域内有效,也不会变量提升
最后提炼一下:JavaScript引擎并不总是按照代码的顺序来进行解析。在编译阶段,无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,这个过程被称为提升。声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
相关免费学习推荐:javascript(视频)
以上是JavaScript專題之一:變數提升與預編譯的詳細內容。更多資訊請關注PHP中文網其他相關文章!