1. 程式人生 > >總結javascript基礎概念系列計劃分為三個部分:作用域,事件循環,原型鏈。

總結javascript基礎概念系列計劃分為三個部分:作用域,事件循環,原型鏈。

嚴格模式 增加 throw 相互 語法錯誤 第一個 scope val 分析

主要問題:
1、javaScript代碼的編譯和執行過程,詞法作用域規則?
2、this的動態綁定方式有幾種?
3、全局和函數之外是不是還有其他的作用域?
4、為什麽代碼規範多禁止with、eval?

一、js編譯 執行

(一)、(預)編譯期
JS的執行過程分為兩個階段:編譯期(預處理)與執行期。報錯可以據此分為兩類,編譯期錯誤和運行期錯誤:

  SyntaxError是解析代碼時發生的語法錯誤\

  ReferenceError是引用一個不存在的變量時發生的錯誤。

  RangeError是當一個值超出有效範圍時發生的錯誤。

  TypeError是變量或參數不是預期類型時發生的錯誤。

JavaScript引擎,不是逐條解釋執行javaScript代碼,而是按照代碼塊一段段編譯解釋執行。

1、代碼塊
JavaScript中的代碼塊是指由<script>標簽分割的代碼段,這樣可以提高引擎的執行效率。JS是按照代碼塊來進行編譯和執行的,代碼塊間相互獨立,但變量和方法共享。

step 1. 讀入第一個代碼塊。
step 2. 做語法分析,有錯則報語法錯誤(比如括號不匹配等),並跳轉到step 5。此時的報錯類型為語法錯誤(比如括號不匹配等)、中英文等,不影響下面代碼塊的編譯執行
step 3. 對var變量和function定義做“預編譯處理”(永遠不會報錯的,因為只解析正確的聲明)。預編譯階段,變量聲明在已存在語法樹中,復制移動至變量對象中。
step 4. 執行代碼段,有錯則報錯(比如變量未定義引用錯誤、變量類型錯誤)。
step 5. 如果還有下一個代碼段,則讀入下一個代碼段,重復step2。
step 6. 結束。

(二)、執行過程

作用域:一套變量,數據查詢的規則。

JavaScript語法采用的是詞法作用域(lexcical scope),也就是說JavaScript的變量和函數作用域是在寫代碼定義時決定的,所以 JavaScript解釋器只需要通過靜態分析就能確定每個變量、函數的作用域,這種作用域也稱為靜態作用域(static scope)。

執行環境、變量對象、內部變量表、函數表等概念都很抽象。可以用一些代碼試著解釋一下執行過程。

<script>
	hi = ‘hello‘;
	var num = 1;

	function fn(a){
		console.log(this.num);
		console.log(hi);
		console.log(a);
		console.log(arguments[0]);
	}
	var fn2 = function(){
		var b = 2;
		console.log(‘fn2‘);
		return function(){
			console.log(b);
		}
	}

	fn(2);
	var fn3 = fn2();
	fn3();
</script>		    

  執行過程如下:

<script>
	//解釋型語言,以代碼塊為單位進行翻譯、執行
	//	##step 1:  語法報錯  報錯類型
	//
	debugger;
	//GEC = {  //全局執行環境
	//	##step 2:  vo變量對象與ao函數內的活動對象
	//	##step 2.1:  變量提升,函數聲明優先,兩種命名方式差別
	// 	vo:{
	// 		fn:{
	// 			type:function,
	// 			ao:{
	// 				arguments:[],
	// 				a:undefined,
	// 				this:undefined
	// 				//##step 3.1:  this 指向的動態綁定  幾種使用形式
	// 				//##step 3.2:  形參與arguments間聯動
	// 			},
	// 			scopeChain:[GEC.vo]
	// 		},
	// 		num:undefined,
	// 		fn2:undefined,
	// 		fn3:undefined,
	// 		this:window
	// 	},
	//  scopeChain:[GEC.vo],
	//	##step 3:  作用域鏈,包含各級變量對象指針的鏈表
	// 	callStack : [GEC]
	//	##step 4:  調用棧,作用控制代碼執行流
	//}
	hi = ‘hello‘;
	//順著作用域鏈查找變量對象上是否已有hi,有則賦值 ,沒有且在非嚴格模式下會聲明一個最外層變量
	var num = 1;
	//num 賦值
	
	function fn(a){
		console.log(this.num);
		console.log(hi);
		console.log(a);
		console.log(arguments[0]);
	}
	//創建函數並賦值
	var fn2 = function(){
		var b = 2;
		console.log(‘fn2‘);
		return function(){
			console.log(b);
		}
	}
	// GEC = {  
	//	此時的全局執行環境
	// 	vo:{
	// 		fn:{
	// 			type:function,
	// 			ao:{
	// 				arguments:[],
	// 				a:undefined,
	// 				this:undefined
	// 			},
	// 			scopeChain:[GEC.vo]
	// 		},
	// 		num:1,
	// 		fn2:{
	// 			type:function,
	// 			ao:{
	// 				arguments:[],
	// 				b:undefined,
	// 				this:undefined
	// 			},
	// 			scopeChain:[GEC.vo]
	// 		}
	// 		hi:‘hello‘
	// 		fn3:undefined,
	// 		this:window
	// 	},
	//  scopeChain:[GEC.vo],
	// 	callStack : [GEC]
	//}
	fn(2);
	
		// FNEC = {
		// 	vo:{
		// 		arguments:[2],
		// 		a:2,
		// 		this:window
		// 	},
		// 	scopeChain:[GEC.vo,FNEC.vo],
		// 	callStack : [GEC,FNEC]
		// }
		// 函數的執行環境執行結束後銷毀
	
	var fn3 = fn2();
	
		// FN2EC = {
		// 	vo:{
		// 		arguments:[],
		// 		b:2,
		// 		##step 5:  匿名函數創建,調用的全局性,賦值則閉包形成
		// 		anonymous:{
		// 			type:function,
		// 			ao:{
		// 				arguments:[],
		// 				this:undefined
		// 			},
		// 			scopeChain:[GEC.vo,FN2EC.vo]
		// 		},
		// 		this:window
		// 	},
		// 	scopeChain:[GEC.vo,FN2EC.vo],
		// 	callStack : [GEC,FN2EC]
		// }
		// 執行後返回一個匿名函數,回到全局環境 賦值給fn3 ,FN2EC.vo留在內存中。生成閉包占用內存,易造成內存泄漏
	
		// GEC = {  //全局執行環境
		//  	vo:{
		//  		fn:{
		// 			type:function,
		// 			ao:{
		// 				arguments:[],
		// 				a:undefined,
		// 				this:undefined
		// 			},
		// 			scopeChain:[GEC.vo]
		// 		},
		//  		num:1,
		//  		fn2:{
		// 			type:function,
		// 			ao:{
		// 				arguments:[],
		// 				b:undefined,
		// 				this:undefined
		// 			},
		// 			scopeChain:[GEC.vo]
		// 		}
		// 		hi:‘hello‘
		// 		fn3:{
		// 			type:function,
		// 			ao:{
		// 				arguments:[],
		// 				this:undefined
		// 			}
		// 			scopeChain:[GEC.vo,FN2EC.vo]
		// 		}
		//  		this:window
		//  	},
		// 	scopeChain:[GEC.vo],
		//  	callStack : [GEC]
		// }


		// FN3EC = {
		// 	vo:{
		// 		arguments:[],
		// 		this:window
		// 	},
		// 	scopeChain:[GEC.vo,FN2EC.vo,FN3EC.vo],
		// 	callStack : [GEC,FN3EC]
		// }		
</script>

  

(三)、作用域欺騙

欺騙詞法作用域:with,eval
with 會在作用域鏈前增加一個對象,會從對象屬性中查找,修改賦值。但無法新增屬性;對於查找不到的賦值會向外層查詢。
技術分享圖片


eval();

可以傳入執行字符串代碼,就像本就在那個位置。

兩個最大的問題是會影響引擎的代碼優化,性能下降。

技術分享圖片

塊級作用域

全局和函數作用域之外,存在另外的作用域

Catch 捕獲的變量只在內部有意義

<script>
      // ES6代碼:
    {
        let a = 2;
        console.log(a);
    };
    console.log(a);
    // 轉為ES5代碼:
    try{
        throw undefined;
    }catch(a){
        a = 2;
        console.log(a);
    }
    console.log(a);    
</script>

  

 

總結javascript基礎概念系列計劃分為三個部分:作用域,事件循環,原型鏈。