JavaScript执行环境和作用域链

1执行环境(执行上下文):执行环境是一个对象。

2全局执行环境是window对象,每个函数也都有它的执行环境。

3执行环境中有变量对象,变量对象保存者执行环境中定义的所有变量和函数。

4函数的内部属性[[Scope]]保存有**作用域链,**在创建函数时预先创建。函数执行环境中也有作用域链,在函数调用时创建,复制函数的作用域链并在前端加上函数的执行环境的变量对象(活动对象)。

5this,是函数的内部属性,不是执行环境的。在函数调用时,活动对象自动取得其值。

6环境栈:JavaScript程序运行开始,创建一个空间,数据结构是栈,称为环境栈。

栈最初始的元素是全局执行环境,函数调用时创建函数执行环境并将其入栈,函数执行完毕之后,其执行环境会出栈。

环境栈有很多执行环境,执行环境中有定义了各自的变量,那么变量和函数的访问权限和顺序需要有一定的机制进行规范,那就是作用域链。

7作用域链:以链式数据解构组织起各个执行环境中的变量对象。

一个执行环境的作用域链的前端是当前执行环境的变量对象,下一个变量对象来自外部(包含)环境。假设全局执行环境调用了函数1,函数1又调用了函数2,

var color="yellow"
function f1(){
	console.log("f1",color);
	f2()
	function f2(){
		console.log("f2",color);
	}
}
f1()
复制代码

那么作用域链可以表示为:

8变量的访问机制:基于作用域链,当访问某个变量时,会沿着作用域链一个个节点搜索,搜索到该变量时就停止搜索。

9怎么理解对象的方法调用时,函数的执行环境?

var color="yellow"
var obj={
	color:"blue",
	f1:function(){
		console.log(color)
	}
}
obj.f1()
输出:"yellow"
复制代码

所以,函数作为对象的方法调用,和作为普通函数调用,是一样的道理。在上面的例子中,obj.f1()的变量对象没有找到color变量,于是沿着作用域链找到全局执行环境的变量对象。

我想,将this指向的理解和执行环境、作用域链脱离开来,会更容易理解。this指向和执行环境无关,只和谁调用了函数有关。this的值在函数调用之前是不确定的,谁调用了函数,this就指向谁。对比下面的代码,可以看出函数的this指向和执行环境就没有必然联系。

var color="yellow"
var obj1={
	color:"blue",
	f1:function(){
		console.log(this.color)
	}
}
obj1.f1()
输出:blue
复制代码

10闭包:闭包是有权访问另一个函数作用域中变量的函数。创建闭包的常见方式,就是在函数内部创建另一个函数。

function createCompareFunction(propName){
	return function(obj1,obj2){
		var value1=obj1[propName];
		var value2=obj2[propName];
		if(value1<value2){
			return -1
		}else if(value1>value2){
			return 1
		}else{
			return 0
		}
	}
}
var compare=createCompareFunction("name")             //1、创建函数
var result=compare({name:"Nicholas"},{name:"Greg"})  //2、调用函数
compare=null                                         //3、消除对匿名函数引用
复制代码

(1)代码1createCompareFunction函数执行返回后,其执行环境和其中的作用域链会销毁,但是变量对象(活动)还是会保存,因为匿名函数的环境对象仍然被闭包函数的作用链引用着。

(2)代码3销毁createCompareFunction后,它的变量对象才被真正回收。

基于包含函数(createCompareFunction)最后留在的指示变量对象,其里面保存的就是函数执行完毕时变量中最后的镜像。下面的代码本来预期是result[0]是返回0,result[9]是返回9的,但是和预期不一样。

function createFunctions(){
	var result=new Array();
	for (var i=0;i<10;i++){
		result[i]=function(){
			return i
		}
	}
	return result
}
var result=createFunctions()
复制代码

调用完毕后,createFunctions函数留存的变量变量对象中有result和i的值,result数组保存有10个函数,每个函数形式为ƒ (){ return i }最后的值时10;

result[0]()   //输出结果是10
复制代码

调用时,在函数自身活动对象中找不到i,所以就去createFunctions函数中找i,找到的i正是10。result中每个元素都是10。要想实现预期,就是以下的代码。

function createFunctions(){
	var result=new Array();
	for (var i=0;i<10;i++){
		result[i]=function(num){
			return function(){
				return num;
			};
		}(i)
	}
	return result
}
复制代码

要理解这段代码,这段认识很重要:“函数的内部属性[[Scope]]保存有作用域链,在创建函数时预先创建。函数执行环境中也有作用域链,在函数调用时创建,复制函数的作用域链并在前端加上函数的执行环境的变量对象(活动对象)。”

最内层的闭包函数function(){return num}有两个外层函数,而闭包函数在创建之时,就已经有作用域链,这个作用域链只有外层函数的变量对象,不包含自己的。

以上代码中。result数组保存有10个闭包函数,每个函数形式为ƒ (){ return num },沿着作用域链外层函数的变量对象,每个都是不一样的,num不一样,实现了预期。

11无块级作用域

作用域只有全局和函数级别的,代码块没有作用域,所以代码块执行也就没有执行环境入栈出栈销毁的概念。下面的代码中,变量i是保存在output()函数的变量对象中的,在for循环外边也可以访问到。

function output(){
	for(var i=0;i<10;i++){
		console.log(i)
	}
	console.log("outside for statement",i)  
        //输出10,即时在函数内重新声明也会被忽略声明,值还是10
}
复制代码

模仿块级作用域,可以使用以下形式,把块级作用域放在匿名函数中,并立即调用。函数调用就有执行环境入栈出栈销毁的概念,最终函数执行完毕,执行环境销毁、变量对象销毁,可以避免变量濡染全局执行环境的问题。

(function(){
    //这里是块级作用域
})()

function output(){
	(function(){
		for(var i=0;i<10;i++){
			console.log(i);
		}
	})();
	console.log("outside for statement",i)
        //报错Uncaught ReferenceError: i is not defined
}
复制代码

12延长作用域链——with语句(严格模式下不允许使用)

var obj={
	name:"Alice",
	age:20,
}
console.log(obj.name,obj.age)
复制代码

等同于

with(obj){
	console.log(name,age)
}
复制代码

with语句中使用的变量(name、age),如果找不到,就会查询obj中是否有同名变量,如果有,就找到了。实际上,with语句能够延长作用域链。在下面的代码中,with的变量对象添加在函数f变量对象的前端,函数f作用域链:with的变量对象>函数f的变量对象->全局环境变量对象。所以with语句中可以使用函数f变量对象中的hobby,当然,函数f也可以使用with变量对象中的desc。

function f(){
	var obj={name:"Alice",age:20};
	var hobby="singing";
	with(obj){
		var desc=name+age+hobby
	}
	return desc
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享