1. 程式人生 > >JS之預解釋原理

JS之預解釋原理

# 預解釋的原理 - 預解釋的不同機制 - `var`的預解釋機制 - `function` 的預解釋機制 - 預解釋機制 - 面試題練習 ## 預解釋的的不同機制 預解釋也叫預宣告,是提前解釋宣告的意思;預解釋是**針對變數和函式**來說的;但是變數和function的的預解釋是兩套不同的機制; - 當瀏覽器載入我們的HTML頁面的時候,首先會提供一個供JS程式碼執行的環境->全域性作用域global(瀏覽器中的全域性作用域,也叫**頂級作用域是window**) - JS中的記憶體空間分為兩種:棧記憶體、堆記憶體 - 棧記憶體;提供JS程式碼執行的環境,儲存基本資料型別的值;->全域性作用域或者私有的作用域其實都是棧記憶體; - 堆記憶體;儲存引用資料型別的值(物件是把屬性名和屬性值儲存進去,函式是把函式體內的程式碼當做字串儲存進去) - 在當前的作用域中,JS程式碼執行之前,瀏覽器首先會預設的把所有代var和function的進行提前的宣告或者定義,->“預解釋”(也叫變數提升) ## `var`的預解釋機制 > var a=1 - 1、程式碼執行之前,先掃描有沒有帶`var`關鍵字的變數名,有的話,為這個變數名在記憶體裡開一個空間;這時候變數名`a`是不代表任何值的;用`undefined`來表示;`undefined`是一個識別符號/記號,表示找不到這個變數名所代表的資料;不存在的意思;這個階段叫變數的**宣告**; - 2、當代碼執行的時候,則給資料1開闢一個記憶體空間; - 3、讓資料1和變數名a繫結在一起;變數型別指的就是資料型別;按照js語言的原理來說變數型別有undefined型別;但是資料型別是沒有undefined這種資料型別的;只有”undecided”這種字串型別(字串型別是資料型別的一種);同理也沒有unll這個資料型別,但是有”null”這種字串型別; ```javascript var num; //1、宣告(declare):var num; ->告訴瀏覽器在當前作用域中有一個num的變量了,如果一個變數只是聲明瞭但是沒有賦值,預設的值是undefined console.log(num);//->undefined num = 12; //2、定義(defined):num=12; ->給我們的變數進行賦值 console.log(num);//->12 //變數提前使用的話,就是undefined console.log(testStr);//undefined var testStr="22222222" ``` ## `function` 關鍵字的預解釋步驟 > function fn(){……} 在程式碼執行之前,把所有的帶function關鍵字的指令碼都掃描一遍,然後定義變數;並且同時給變數賦值; - 1、函式的定義只是**儲存一些字串**;預解釋的時候在記憶體裡儲存`fn`大括號裡面的字串; - 2、程式碼執行時候,讀到`fn()`時候,這個時候就是函式的執行;函式的執行,會先開闢一個**堆記憶體**把字串當做程式碼在堆記憶體中再次執行,函式產生的作用域內**還會再進行預解釋**和程式碼執行; 函式如果多次執行;會產生多個作用域;但是產生的多個作用域裡面的內容都是相互獨立的;互相沒有關係;(在原型和原型鏈時候再仔細研究原理;) ```javascript fn(100,200);//->可以在上面執行,因為預解釋的時候宣告+定義就已經完成了 function fn(num1, num2) { var total = num1 + num2; console.log(total); } ``` ## 總結 - 1、`var`和`function`關鍵字的在預解釋的時候操作還是**不一樣的** - `var` -> 在預解釋的時候只是提前的聲明瞭這個變數,只有當代碼執行的時候才會完成賦值操作 - `function` -> 在預解釋的時候會提前的把宣告加定義都完成了(在程式碼執行的時候遇到定義的程式碼直接的跳過) - 2、預解釋只發生在**當前的作用域**下,例如:開始只對window下的進行預解釋,只有函式執行的時候才會對函式中的進行預解釋; > **[重要]剛開始只對window下的進行預解釋,fn函式中目前儲存的都是字串,所以var total沒啥實際的意義,所以不進行預解釋 -> “預解釋是發生在當前作用域下的”**   綜合題; ```javascript console.log(obj);//->undefined var obj = {name: "xie", age: 25}; function fn(num1, num2) {//程式碼執行到這一行的時候直接的跳過這一塊的程式碼,因為在預解釋的時候我們已經完成了宣告加定義 var total = num1 + num2; console.log(total); } var num1 = 12; fn(num1, 100);//執行fn,把全域性變數num1的值賦值給形參num1,把100賦值給形參num2 ``` - 下面是一個預解釋思路 ```javascript var a, b = 0, fn = function () { var a = b = 2; }; fn(); console.log(a, b); ``` > 把上面解析成下面就好理解了 ```javascript var a; window.b = 0; window.fn = function () { //var a = b = 2; var a = 2;//a是私有的和全域性沒關係 b = 2;//b是全域性的 }; fn();//window.fn() console.log(a, b);//undefined 2 ``` ## 預解釋機制 - 1、不管條件是否成立都要進行預解釋 ```javascript console.log(a);//->undefined if (!!("a" in window)) {//"a" in window -> true var a = "xie"; } console.log(a);//->xie ``` > 例子中的if是不成立的,預解釋的時候,碰到非`functon`內的`var`,都會宣告,無論你寫在if else 還是別的判斷裡; 假設if語句起作用的話,那麼第一次`log(a)`的時候,就會報錯了(沒有宣告的變數,是不能直接用的,除非`typeof` ),而宣告並且沒有賦值的表現才是`undefined`;假設不成立; 最開始總結的預解釋步驟:**程式碼執行之前,先掃描有沒有帶var關鍵字的變數名,有的話,為這個變數名,在記憶體裡開一個空間;**預解釋是發生在程式碼執行前的,**所以if根本阻擋不了預解釋;** - 2、預解釋只發生在”=“的左邊,**只把左邊的進行預解釋**,右邊的是值是不進行預解釋的 > 匿名函式之函式表示式:把函式定義的部分當做值賦值給一個變數或者元素的事件 ```javascript fn1();//->undefined() Uncaught TypeError: fn is not a function JS中只有函式可以執行 && JS上面的程式碼如果報錯了,在不進行任何的特殊處理情況下我們下面的程式碼都不在執行了 var fn1 = function () { console.log("ok"); }; fn1(); ``` ```javascript //預解釋的時候:fn=xxxfff000 fn2();//->"ok" function fn2() { console.log("ok"); } fn2();//->"ok" ``` **預解釋的時候**:`var fn1 = function()...` ->`fn`的預設值是`undefined`;這裡即使有`function`,也是**不能進行預解釋的** - 3、函式體中return下面的程式碼都不在執行了,但是下面的程式碼需要參加預解釋;而return後面的東西是需要處理的,但是由於它是當做一個值返回的,所以不進行預解釋; ```javascript function fn() { console.log(total); return function sum() {};//return是把函式中的值返回到函式的外面,這裡是把function對應的記憶體地址返回的到函式的外面,例如:return xxxfff111;函式體中return下面的程式碼都不在執行了 var total = 10; console.log(total); } ``` - 4、匿名函式的function在全域性作用域下是不進行預解釋的; 匿名函式之自執行函式:定義和執行一起完成了;函式內的宣告,只是在函式內使用; ```javascript (function(num){ var testStr="test"+num; console.log(num); })(100); console.log(testStr);// testStr is not defined ``` - 5、在預解釋的時候,如果遇到名字重複了,**只宣告一次**。 不重複的宣告,但是賦值還是要重複的進行的   預解釋: ```javascript var fn; 宣告 fn = xxxfff000; [宣告]不用了+定義 fn = xxxfff111; [宣告]不用了+定義 // ->fn=xxxfff111 var fn = 12;//window.fn=12 function fn() {//window.fn=function(){} } ```   JS中**作用域**只有兩種: - window全域性作用域; - 函式執行形成的私有作用域; - `{name:“”} ` `if(){}` `for(){} ` `while(){}` `switch(){}` 這些都不會產生作用域; ES6可以用`let`形成**塊級作用域**;http://www.cnblogs.com/snandy/archive/2015/05/10/4485832.html ## 面試題 ```javascript // 涉及this的指向和閉包 var num = 20; var obj = { num: 37, fn: (function (num) { this.num *= 3; // global num * 3 num += 15; // global num + 15 var num = 45; return function () { this.num *= 4; // 37*4 num += 20; // 呼叫外部函式的num (45+20) console.log(num); }; })(num), //->把全域性變數num的值20賦值給了自執行函式的形參,而不是obj下的30,如果想是obj下的30,我們需要寫obj.num }; var fn = obj.fn; // 執行了第1次 fn(); //->65 , 執行了第2次=> window.num = 240 obj.fn(); //->85 閉包, obj.num = 37*4 = 148 console.log(window.num, obj.num); // 240,