总结javascript基础概念系列计划分为三个部分:作用域,事件循环,原型链。

Posted banyue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了总结javascript基础概念系列计划分为三个部分:作用域,事件循环,原型链。相关的知识,希望对你有一定的参考价值。

主要问题: 
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基础概念系列计划分为三个部分:作用域,事件循环,原型链。的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript基础之数据类型部分总结

JavaScript面试题总结系列

Raft实战系列,先说说一些基本概念

Raft实战——基本概念

每日1刷系列软件测试常见面试题—测试基础(概念)

JavaScript基础:DOM操作详解