原生js底层基础(七)JavaScript 中的作用域scope和闭包

作用域可以说是闭包的基础,在此之上再理解闭包就很简单了,因此我将他们放在一起。

1.作用域

1)[[scope]](作用域) :

JavaScript函数是一个特殊对象,对象中有些属性我们可以访问,但是有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们说的作用域,其中存储了运行期上下文的集合

2)作用域链:

[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,这个链式链接就是作用域链。

3)运行期上下文:

当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文就定义了一个函数执行的环境。函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。

4)查找变量:从作用域链的顶端依次向下查找
5)作用域链形成过程:
function a (){
	function b(){
		var bb = '234';
		aa = 0;
	}
	var aa = 123;
	b();
	console.log(aa)
}
var glob = 100;
a();
1.a函数刚被定义时,里面只存了全局对象(Global Objecr,以下简称GO)

在这里插入图片描述

2.函数a执行时,产生一个执行期上下文(Activation Object ,以下简称AO),链到作用域顶端,当这个函数的链数组有一个以上内容时,按照自顶向下查找变量

在这里插入图片描述

3)b函数被创建时,得到了a函数作用域链的引用(即b函数拿到的a的AO和GO与a那里的是同一个)

在这里插入图片描述

4)b在被执行的时候,产生了自己的AO,链在自己的作用域链的顶端。当在b函数中去访问对象,也遵循自顶向下的原则。

在这里插入图片描述
分析完过程后,再看执行结果,最后输出aa为0,因为b函数执行aa = 0这句时,会去 作用域链查找,第0位找不到aa这个变量,向下查找,找到第1位有变量aa,将其赋值为0,而这个作用域链第一位是a的AO,可以说是他们共用的一块区域,所以也就不难得出为什么输出0了

function a (){
	function b(){
		var bb = '234';
		aa = 0;
	}
	var aa = 123;
	b();
	console.log(aa)
}
var glob = 100;
a();

2.闭包

1)概念:

这是《JavaScript权威指南》一书中对闭包的解释,
" 实现闭包:
回顾上面对作用域链过程的描述,我们将作用域链描述为一个对象列表,不是绑定的栈。每次调用 Javascript函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。
当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他引]用指向这个绑定对象,它就会被当做垃圾回收掉。如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收。但是如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。它就不会被当做圾回收,并且它所指向的变量绑定对象也不会被当做拉圾回收。"

如果你疲于看大量概念或者不太理解这段话,可以简单理解为:

当内部函数被保存到外部时,将会生成闭包,闭包会导致原有作用域链不释>放,造成内存泄漏
2)例子:
function a(){
	function b(){
		var bbb = 234;
		console.log(aaa);
	}
	var aaa = 123;
	return b;
}

var glob = 100;
var demo = a();
demo();

var demo = a();这一句,b在a的环境下,被定义时就拥有a的scope(GO和AO 的内容),a在函数最后一句return了b的引用,并传给demo,demo和b有相同的引用,a在执行以后被销毁,但是demo(或者说b)已经拥有了a的scope,这就是闭包生成的过程。

3)应用

①解决for循环输出不正常问题

function test(){
	var arr = [];
	for(var i= 0; i <10; i++){
		arr[i] = function (){
			console.log(i + " ");
		}
	}
	return arr;
}
var myArr = test();
for(var j = 0; j < 10;j ++){
	myArr[j]();
}

最后输出结果 f

10 10 10 10 10 10 10 10 10 10

因为myArr[j]();这一句是在外部执行,函数在执行时才去变现函数体,所以此时 i 已经变成了10,myArr数组访问的arr里面的i都是同一个,所以是输出十个10,test()里面的for循环是在myArr[j]()这一句的时候才执行(在test函数里只是定义没执行,系统不会管里面写什么),执行的是

 arr[0] = function(){  console.log( 10+ " ")   ;}
 arr[1] = function(){  console.log( 10+ " ")   ;}
 arr[2] = function(){  console.log( 10+ " ")   ;}

解决方法

function test(){
	var arr = [];
	for(var i = 0;i < 10;i ++){
		(function (j) {
			 arr[j] = function(){
				 console.log(j+" ");//这个不会在定义 的时候执行,但是这是赋值语句,已经把i给了j
			 }
		}(i));//每循环一次就执行了一次这个立即执行函数,输出j就不会有那个问题了
	 }
	return arr;
}
var myArr = test();
for(var j = 0; j < 10;j ++){
	myArr[j]();
}

②使用闭包使变量私有化,不容易和全局重名

栗子:

var name="bcd";
var init=(function(){

	var name ="abc";
	function call(){
		console.log(name);
		}
	return function (){call();}
}())
init();   //返回abc",不会返回“bcd”

即在一个团队中,多人写的代码会有重名的问题,如例子中使用闭包,将个人写的代码包裹在一个小范围,是一个避免污染全局变量的好办法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值