JavaScript高階(1)
一、作用域與作用域鏈
1.作用域
(1)什麼是作用域
a.就是一塊“地盤”,一個程式碼所在的區域
b.作用域是靜態的(相對於執行上下文物件),在編寫程式碼時就確定了
c.作用域是一套非常嚴格的規則,這套規則跟變數的查詢有關
(2)作用域分類
1.全域性作用域:是針對於全域性變數來說,宣告在函式外部的變數(所有沒有var直接賦值的變數都屬於全域性變數)
2.函式作用域:使針對於區域性變數來說的,宣告在函式內部的變數(所有沒有var直接賦值的變數都屬於全域性變數)在函式中定義的變數在函式外不能被獲取
3.沒有塊作用域(ES6有了)
(3)作用域的作用
隔離變數,不同作用域下同名變數不會有衝突
(4)作用域的定義
1.廣義上來說:
作用域是負責收集並維護由所有宣告的識別符號(變數)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前執行的程式碼對這些識別符號的訪問許可權。
狹義上來說:
2.作用域是程式程式碼中定義的一個區域,其控制著變數與引數的可見性及生命週期
總結:
作用域與變數的讀取跟儲存有著密不可分的聯絡。
2.作用域鏈
(1)什麼是作用域鏈
1. 多個上下級關係的作用域形成的鏈, 它的方向是從下向上的(從內到外) 2. 查詢變數時就是沿著作用域鏈來查詢的
3.作用域鏈的個數=n+1(n表示函式宣告的個數)
(2)查詢一個變數的規則
1. 在當前作用域下的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入上一級 2. 在上一級作用域的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入上一級 3. 直到全域性作用域, 如果還找不到就丟擲找不到的異常
3.左右查詢
(1)左查詢
等號的左邊
在變數所在的作用域鏈中找不到對應變數的宣告,瀏覽器的JS引擎會主動在全域性作用域內宣告一個
(2)右查詢
等號的非左邊
在變數所在的作用域鏈中找不到對應變數的宣告,瀏覽器引擎會丟擲ReferenceError: a is not defined(報錯)。
(3)練習
console.log(b) ;//報錯 找不到變數的宣告
function(){ function test(a){ var b=a; console.log(b);//2 } test(2); })() console.log(b);//b is not defined
作用域面試題
<script>
/*
問題: 結果輸出多少?
*/
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f();
}
show(fn);
</script>
先var一個x=10,然後繼續往下執行,看到函式的宣告跳過,(函式的宣告,當函式被呼叫時才會執行),
var fn = function () {
console.log(fn)
}
fn()
var obj = {
fn2: function () {
console.log(fn2)
}
}
obj.fn2()
(4)注意點
function test() { // var a = b = c =1; var a=1; b = 1; c =1; } test(); console.log(b,c) // 用var a= b = c =1 的方式宣告變數會使b和c的值汙染全域性 function test() { // var a , b , c =1; var a; var b; var c=1; console.log(a,b,c) } test(); console.log(b,c) // 用var a , b , c =1;的方式宣告變數,a , b的值為undefined
4.記憶體回收機制
JavaScript 具有自動垃圾收集機制,也就是說,執行環境會負責管理程式碼執行過程中使用的記憶體。
這種垃圾收集機制的原理其實很簡單:找出那些不再繼續使用的變數,然後釋放其佔用的記憶體。為此,垃圾收集器會按照固定的時間間隔週期性地執行這一操作。
變數的釋放 -->瞬間的
記憶體的回收 -->週期性的
二、執行上下文和執行上下文棧
1.執行上下文(EC:execute context)
(1)什麼是執行上下文
1.廣義上來說: 執行上下文是一套非常嚴格的規則 2.狹義上來說: 執行上下文是瀏覽器的一個c++物件
執行上下文是動態的
執行上下文從屬於其對應的作用域
多少個執行上下文=n+1 (n表示函式執行的次數)
一般情況下:一個時間點,一個作用域對應一個執行上下文
特殊情況下:一個時間點,一個作用域對應多個執行上下文 遞迴時(一個作用域很有可能對應多個執行上下文)
一個作用域中只會存在一個處於活動狀態的執行上下文
(2)執行上下文分類
1. 全域性執行上下文 2. 函式執行上下文
2.執行上下文棧
(1)壓棧
1. 當全域性程式碼開始執行前,先建立全域性執行上下文環境
2. 當全域性執行上下文環境建立好了以後將上下文中的所有內容放入棧記憶體
3. 最先放入的在最下邊(global)
4. 其他執行的函式的執行上下文依次放入(放入的順序是程式碼的執行順序)
5. 棧中最後放入的執行完最先出棧。
(2)圖解
(3)練習
1. 依次輸出什麼?
2. 整個過程中產生了幾個執行上下文?
-->
<script">
//找變數 依據作用域鏈 找作用域所對應的活動狀態的執行上下文中找對應的值
var i;
console.log(i)
i = 1
foo(1);
function foo(i) {
if (i == 4) {
return;
}
console.log(i);
foo(i + 1);
console.log(i);
}
console.log(i)
</script>
3.作用域和執行上下文棧
(1)區別1
1. 除全域性作用域之外,每個函式都會建立自己的作用域,作用域在函式定義時就已經確定了。而不是在函式呼叫時 2. 全域性執行上下文環境是在全域性作用域確定之後, js程式碼馬上執行之前建立 3. 函式執行上下文環境是在呼叫函式時, 函式體程式碼執行之前建立
(2)區別2
1. 作用域是靜態的, 在編碼時就被定義,只要函式定義好了就一直存在, 且不會再變化 2. 上下文環境是動態的, 呼叫函式時建立, 函式呼叫結束時上下文環境就會被釋放
(3)聯絡
1. 上下文環境(物件)是從屬於所在的作用域 2. 全域性上下文環境==>全域性作用域 3. 函式上下文環境==>對應的函式使用域
4.規則
1.全域性執行上下文
在執行全域性程式碼前將window確定為全域性執行上下文 對全域性資料進行預處理 1.var定義的全域性變數==>undefined, 新增為window的屬性 2.function宣告的全域性函式==>賦值(fun), 新增為window的方法 3. 提升 4.this==>賦值(window) 開始執行全域性程式碼
console.log(window.parseInt("123PX"));
var a = "a-val";
function test(){
console.log("test");
}
console.log(window.a);
window.test();
console.log(this);
2.函式執行上下文
在呼叫函式, 準備執行函式體之前, 建立對應的函式執行上下文物件 對區域性資料進行預處理 1.形參變數==>賦值(實參) 2.arguments==>賦值(實參列表) 3.處理 var定義的區域性變數 4.處理 function宣告的函式 5.提升 6.this==>賦值(呼叫函式的物件)
開始執行函式體程式碼
function test() {
var a="a";
function inner() {
}
}
test();
window.test();
console.log(window.a);//原型鏈
console.log(a);//作用域鏈
3.變數的查詢
如果要查詢一個作用域下某個變數的值,就需要找到這個作用域對應的處於活動狀態的執行上下文環境,再在其中尋找變數的值
5.提升
1.變數的提升
變數的提升 : 將變數的宣告提到當前作用域的最頂層
var a;
console.log(a);
a =1;
2.函式的提升
函式的提升 : 將整個函式提到當前作用域的最頂層
function test() {
console.log("test");
}
test();
3.提升的注意點
函式的提升是整體的提升
變數的提升是宣告的提升
函式的提升優於變數的提升
提升是指提升到本層作用域的最頂層
var a = 2;
var a = 3;
//
function test() {
console.log("test1")
}
function test() {
console.log("test2")
}
test();
4.練習
<script type="text/javascript"> /*測試題1: 先預處理變數, 後預處理函式*/ function a() {} var a; console.log(typeof a) // /*測試題2: 變數預處理, in操作符*/ var b; if (!(b in window)) { b = 1; } console.log(b) /*if (!(b in window)) { var b = 1; } console.log(b)*/ /*測試題3: 預處理, 順序執行*/ function c(c) { console.log(c) var c = 3 } var c ; c = 1 c(2) /* var c = 1 function c(c) { console.log(c) var c = 3 } c(2)*/
/*測試題4*/
/* function test(){
function foo(){
console.log(1);
}
foo=5;
}
var foo;
foo=2;
test();
console.log(foo);*/
var foo=2;
test();
function test(){
foo=5;
function foo(){
console.log(1);
}
}
console.log(foo);
/*測試題5*/
/*function test(){
console.log(a);
}
var a;
test();
a=2;*/
test();
var a=2;
function test(){
console.log(a);
}
5.編碼規範
1.使用變數前,一定要先宣告(避免汙染全域性 迎合Js引擎的提升規則)
2.在分支結構中最好不要定義函式