在當前執行上下文中(不管是全局,仍是函數執行私有的),
JS
代碼自上而下執行以前,首先會默認把全部帶VAR
和FUNCTION
關鍵字的進行聲明或者定義。javascript
- 帶
VAR
的只是提早聲明- 帶
FUNCTION
的會提早聲明+定義
拿var n = 100
爲例,咱們以前講過的操做步驟應該是:java
值100
存儲到棧內存中(引用數據類型首先建立堆,存完數據後,把堆的地址存放到棧中)var a
;這是咱們以前說過的建立變量並賦值的過程;面試
變量聲明(declare)
;變量定義(defined)
上面咱們所說的是咱們常規的建立變量並賦值的操做。瀏覽器
那當咱們只聲明變量時,也就是
var a
;可是沒有給變量賦值(也就是未定義),因此默認值是undefined
未定義 (這也是undefined
的由來)bash
爲了更好的理解變量提高,咱們仍是得先從瀏覽器的機制提及:函數
執行環境棧ECStack(Execution Context Stack)
這裏咱們不在過多講解堆棧內存,前面已經專門講過。ui
有了棧內存代碼就能夠自上而下的執行了,只不過剛開始,是要把全局下的代碼先執行;spa
二、開始執行全局下的代碼,就會造成一個全局執行上下文EC(GLOBAL 簡寫 爲G)
(上下文就是上文下文加一塊兒就是環境的意思);code
一、2 兩步總體原理:咱們的目的是爲了執行代碼,因此圍繞代碼執行引起的下列操做:cdn
- ==> 瀏覽器加載頁面,想讓代碼執行,首先會造成一個
棧內存(執行環境棧 ECStack)
;而後開始讓代碼準備執行;- ==> 最開始要執行的必定是全局下的代碼,
- ==> 此時造成一個
全局代碼的執行環境(全局執行上下文 EC(G))
- ==> 造成以後,
把EC(G)壓縮到棧內存中去執行(進棧
);每個函數的執行也是這樣操做的...- ==> 有些上下文在
代碼執行完成後,會從棧內存中移除去(出棧)
,可是有些狀況是不能移出去的(例如:全局上下文就不能移除...);- ==> 在下一次有新的執行上下文進棧的時候,會把
以前沒有移除的都放到棧內存的底部,讓最新要執行的在頂部執行
三、對應代碼會在本身所屬的執行上下文中執行,而這個環境中有一個存放變量的地方:變量對象(VO/AO)
全局變量對象VO(G) ->全稱Variable Object (Global)
在咱們以前的理解,建立完一系列的環境後,就能夠執行代碼了,然而並無,在代碼執行以前還有一系列的操做要作;例如:詞法解析 => 形參賦值 => 變量提高...等好多好多事情;詞法解析和形參賦值咱們後面會說,如今咱們先來理解:變量提高
變量提高:就是在當前上下文中,JS代碼執行以前要作的事情;首先會默認把全部帶
VAR
和FUNCTION
關鍵字的進行聲明或者定義。
- 帶
VAR
的只是提早聲明- 帶
FUNCTION
的會提早聲明+定義
咱們如下題爲例
console.log(a);
console.log(func);
var a = 10;
function func(){
console.log(b);
var b=20;
console.log(b);
}
func();
console.log(a);
console.log(func);
複製代碼
VAR
和先聲明,找到帶FUNCTION
的聲明+定義
var a
;function func(){...}
;(按照函數建立的步驟,即聲明又定義)
func
與十六進制地址關聯;conlose.log(a)
:=> 此時在當前執行上下文中,已經聲明過a
可是並無賦值,因此打印結果爲undefined
;console.log(func)
:=> 此時當前上下文中已經聲明瞭func
變量,而且已經賦值,因此打印func
的結果爲f func(){...}
var a = 10
:=> 以前咱們在變量提高階段,已經完成了var a
操做(瀏覽器是又懶惰性的,作完了這件事,不會在作第二遍了),因此此時只建立了一個值10
,沒有在建立變量a
;把10
與以前在變量提高階段聲明的變量a
關聯;function func(){...}
:=> 上面咱們說了,同一件事情瀏覽器不會作兩便;很明顯以前在變量提高階段已經作過這件事了,因此此步就會跳過;直接進行下一步;func()
:=> 讓函數執行;
私有上下文EC(func隨便起的名字)
;私有變量對象AO(func1隨便起的名字) ->全稱Active Object
;VAR
和先聲明,找到帶FUNCTION的聲明+定義
var b
;function
的console.log(b)
:=> 此時b
以聲明爲定義,因此是undefined
;var b=20
:=> 同全局同樣,跳過var b
,直接建立值,並與之聲明的b
關聯;console.log(b)
:=> 此時,b
已經被賦值,因此結果爲20
;console.log(a)
:=> 此時a
的值爲10
;console.log(func)
:=> 此時func
依然指向堆內存所對應的十六進制地址,因此打印結果爲f func(){...}
;上面咱們簡單的理解了變量提高的含義;
func();
function func(){
console.log('OK');
}
+ 答案是不會報錯
複製代碼
func();
var func = function (){
console.log('OK');
}
+ 答案是會報錯:
+ 變量提高階段:var func; 只定義不賦值 (默認值是undefined)
+ //=> undefined() =>Uncaught TypeError: func is not a function
複製代碼
對比上面兩題的區別,咱們得出結論:
- 函數表達式方式建立函數,在變量提高階段,並不會給函數賦值,這樣只有代碼執行的過程當中,函數賦值後才能夠執行(不能在賦值以前執行) =>符合嚴謹的執行邏輯 =>真實項目中推薦的方式
- 爲了保證語法的規範性,JS中本來建立的匿名函數,咱們最好設置一個名字,可是這個名字在函數外面仍是沒法使用的,只能在本函數體中使用(通常也不用) 例如:
var func = function anonymous() {
// console.log(anonymous); //=>當前這個函數
};
// console.log(anonymous); //=>Uncaught ReferenceError: anonymous is not defined
func();
//================================================
var func = function func() {
console.log(func); //=>函數名func
};
console.log(func); //=>變量func,存儲的值是函數
func();
複製代碼
截止目前(注意是目前),咱們的上下文只有兩種:
- => 全局上下文
- => 函數執行產生的私有上下文
VAR
的/* * 全局上下文中的變量提高: * [VO(G) 全局變量對象] * var n; 不論IF條件是否成立都進行(只要不在函數裏面,帶VAR的都要變量提高) * 不管是IF仍是FOR中的VAR都進行提高; */
console.log(n); //=>undefined
if (1 > 1) { //=>條件不成立
var n = 100;
}
console.log(n); //=>undefined
複製代碼
FUNCTION
的function func(){ ... }
聲明+定義都處理了function func
; 用判斷或者循環等包裹起來的函數,在變量提高階段,不論條件是否成立,只會先聲明/* * 全局上下文中的變量提高: * [VO(G) 全局變量對象] * [IE 10及之前以及谷歌等瀏覽器低版本狀態下] * function func(){ ... } 聲明+定義都處理了 * * [最新版本的瀏覽器中,機制變了] * function func; 用判斷或者循環等包裹起來的函數,在變量提高階段,不論條件是否成立,此處只會先聲明 */
console.log(func); //=>undefined
if (1 === 1) {
// 此時條件成立,進來的第一件事情仍是先把函數定義了(迎合ES6中的塊做用域) => func=function(){ .... }
console.log(func); //=>函數
function func() {
console.log('OK');
}
console.log(func); //=>函數
}
console.log(func); //=>函數
複製代碼
一、在當前執行上下文中,無論條件是否成立,變量提高是有效的
二、[IE 10及之前以及谷歌等瀏覽器低版本狀態下]:
function func(){ ... }
聲明+定義都處理了三、[最新版本的瀏覽器中,機制變了]:
function func
; 用判斷或者循環等包裹起來的函數,在變量提高階段,不論條件是否成立,只會先聲明 重點在寫一遍強調:
// 瀏覽器有一個特徵:作過的事情不會從新再作第二遍(例如:不會重複聲明)
/* * 全局上下文中的變量提高 * fn = function(){ 1 } 聲明+定義 * = function(){ 2 } * var fn; 聲明這一步不處理了(已經聲明過了) * = function(){ 4 } * = function(){ 5 } * 結果:聲明一個全局變量fn,賦的值是 function(){ 5 } */
fn(); //=>5
function fn(){ console.log(1); } //=>跳過(變量提高的時候搞過了)
fn(); //=>5
function fn(){ console.log(2); } //=>跳過
fn(); //=>5
var fn = function(){ console.log(3); } //=>var fn; 這一步跳過,可是賦值這個操做在變量提高階段沒有搞過,須要執行一次 => fn = function(){ 3 }
fn(); //=>3
function fn(){ console.log(4); } //=>跳過
fn(); //=>3
function fn(){ console.log(5); } //=>跳過
fn(); //=>3
複製代碼
/* * 低版本瀏覽器(包含IE10及之內) * 全局上下文中變量提高 * 沒有 */
f=function (){return true;};//給GO中設置一個屬性 f = function () {return true;}
g=function (){return false;};//給GO中設置一個屬性 g = function () {return false;}
(function () {
/* * 自執行函數執行,造成一個私有的執行上下文 * [變量提高] * function g(){return true;} */
// 條件解析:
// g() => 私有的G執行 TRUE
// []==![] => []==false => 0==0 => TRUE
if (g() && [] == ![]) { //=>條件成立
f = function () {return false;} //f不是本身私有的,則向上查找,屬於全局對象中的f,此處是把全局對象中的 f = function () {return false;}
function g() {return true;} //跳過(變量提高處理過了)
}
})();
console.log(f()); //=>FALSE
console.log(g()); //=>FALSE 這個G找全局的(函數裏面的G是本身私有的)
複製代碼
/* * 高版本瀏覽器 * 全局上下文中變量提高:沒有 */
f=function (){return true;};//給GO中設置一個屬性 f = function () {return true;}
g=function (){return false;};//給GO中設置一個屬性 g = function () {return false;}
(function () {
/* * 自執行函數執行,造成一個私有的執行上下文 * [變量提高] * function g; 高版本瀏覽器中,在判斷和循環中的函數,變量提高階段只聲明不定義 */
// 條件解析:
// g() => undefined() => Uncaught TypeError: g is not a function 下面操做都不在執行了
if (g() && [] == ![]) {
f = function () {return false;}
function g() {return true;}
}
})();
console.log(f());
console.log(g());
複製代碼