js笔记 三.作用链,立即执行函数,闭包

作用链

先上定义
在这里插入图片描述
在这里插入图片描述

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

老规矩嘛,先建立Go对象,然后执行外部代码
等到执行a函数时,a函数这个对象它会有一个作用链
例如下图,本来a函数啥都没有,只有一个全局的Go对象
现在要执行a函数,哪还有啥好说的,按js的尿性,建立属于a函数的Ao对象呗

要注意,函数都是很傲娇的,他们觉得自己的东西都是最新潮的,所以越是岁数越大的东西越被他们放在下面,所以一般在作用链上找东西的顺序是,自己–>父亲 -->爷爷…

在这里插入图片描述
到了b函数这里与a差不多
但是b函数有一点特殊,它有父域,也就是a函数
所以b一出生就完全继承了a函数的所有域,拥有了a的Ao对象与全局的Go对象
现在要执行b了,那就生成b的Ao对象,然后把b的Ao对象放在链的最上端
即 bAo —> aAo —> GO

笔试题
function a(){
	function b(){
		function c(){
		
		}
		c();
	}
	b();
}
a();

a defined a.[[scope]] ===> 0:Go

a doning a.[[scope]] ===> 0 : aAo
						  1 : Go

b defined b.[[scoped]] ===> 0 : aAo
						    1 : Go
							
b doning b.[[scoped]] ===> 0 : bAo
						   1 : aAo
						   2 : Go

c defined c.[[scoped]] ===> 0 : bAo
						    1 : aAo
						    2 : Go
							
c defined c.[[scoped]] ===> 0 : cAo
							1 : bAo
						    2 : aAo
						    3 : Go
							
		

闭包

先上定义
在这里插入图片描述

function a(){
	var aaa = 124;
	console.log(a);
	return function b(){};
}
var glob = 100;
a();

按照上面流程

a函数的域为 aAo —> Go
b函数的域为 bAo —> aAo —> Go

本来正常流程是

将b函数执行完然后将b函数销毁掉
b函数的域为 :空
接下来将a函数执行完,然后将a函数销毁掉
a函数的域为 :空
这时aAo,与bAo这两块内存没有任何人使用它
操作系统就会将它回收掉

但是现在出现了一个神奇的现象

a函数将b函数作为返回值给return出去了
这时因为b函数根本就没有执行,所以它的域不会被销毁
a函数因为已经执行了return语句,引擎认为a函数执行完毕,将a函数的所有的域进行销毁

通过上述流程后,

a函数的域为 空
b函数的域为 aAo —> Go(因为b函数还没被执行,所以不创造bAo)

我们就发现本来应该被销毁的aAO内存
因为b的作用链内还存着aAo的引用,
操作系统会认为aAo可能还会被使用,所以不将aAO内存回收掉
结果就是,本应销毁的aAO内存成了泄露掉的内存
这个bug有个牛逼哄哄的名称-------闭包

在这里插入图片描述

笔试题

第一题

利用闭包实现累加器

<button id = "btn1" onclick = "run(this)">未开始</button>
<button id = "btn2" onclick = "run(this)">未开始</button>
<button id = "btn3" onclick = "run(this)">未开始</button>
	
	var global = {};//全局状态机,用于存放所有button的闭包
		
	function add(target){
		var num = 0;
		return function (){
			document.getElementById(target).innerHTML = ++num;
		}
	}
	
	function run(btn){
		if(!global[btn.id]){
			//如果该button未定义
			//在全局状态机中以该button的id为键定义一个该button的闭包
			global[btn.id] = add(btn.id);
		}
		global[btn.id]();//执行该button的累加操作
	}
	

运行结果
在这里插入图片描述

第二题

分析该代码,写出运行结果

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

var myArr = test();
for(var t of myArr){
	t();
}

解析

这题表面看起啦很简单,arr装了10个函数
这十个函数分别打印0至9
但是这里存在一个闭包的问题

首先这十个函数的打印值肯定是一样的

这十个函数被返回出去了,但是他们都保存着test函数的域
而且要注意,他们保存的域都是同一份内存
所以它们共用一个变量i,所以它们打印的i肯定是一样的

这十个函数打印的值都是10

因为Ao只会在函数执行前才会生成
所以这十个函数的Ao都是在倒数第二行生成的
所以每个函数打印的i,并不会是我们所想得到那样,分别是0,1,2,3…9
而是将a函数执行后的i的值,也就是将for循环执行完以后的值 —> 10

答案

10个10

立即执行函数

形式 :

(function(){
	//code...
}())   //建议使用此种形式  


(function(){
	//code...
})()

eg:

(function abc(){
	var a = 10,
		b = 20;
		console.log(a + b);
}())
//30

定义

只执行一次的函数 /针对初始化功能的函数








逗号语法

引擎运行时,会将所有的运算执行完毕后,再返回最后一个数据

eg:

var a = (1,2 +1,3*4);//a = 12








()

定义

()在js中是执行符号,会将表达式执行
所有被执行符号执行的函数,函数名会被忽略,成为立即执行函数

eg :

var test = function (){
	console.log("123")
}() 
//打印123


(function demo(a,b){
	console.log(a + b);
})(10,20)
console.log(demo);
//30
//undefined

	
(function demo(a,b){
	console.log(a + b);
}(10,20))
console.log(demo);
//30
//undefined




+ function test(){
	console.log("123");//打印123
}();
console.log(test);//undefined
/*
虽然
function test(){...}是函数声明
但是因为有一个+,所以变成了一个表达式
所有后面的()会将test这个函数名忽视,将表达式立即执行
所以再打印test会显示undefined
*/





function demo(a,b){
	console.log(a + b);
}(10,20)
console.log(demo);
//打印结果为function demo

/*
因为在上面没有立即执行函数,所以()不能被理解为立即执行
执行引擎会把代码:

function demo(a,b){
//code
}(10,20)

翻译成两句
第一句就是普通的函数声明语句
function demo(a,b){
//code
}

第二句就是普通的逗号语法
(10,20)
两句之间没有任何关系,系统也不会报错
*/
笔试题

现在我们再看看上面那道闭包的题

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

var myArr = test();
for(var t of myArr){
	t();
}

现在我们知道他会打印10个10
那么我们怎么利用现有知识让它依次打印0,1,2,3,4,5呢

答案

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

var myArr = test();
for(var t of myArr){
	t();
}
//0 1 2 3 4 5 6 7 8 9

解析

//因为是立即执行函数,所以i被当作实参传入立即执行函数,被立即执行了
//代码
for(var i = 0;i < 10;i++){
	(function(j){
		arr[j] = function (){
			console.log(j);
		}
	})(i)
}

//实际上相当于立即执行了下面的语句
arr[0] = function (){
	console.log(0);
}
arr[1] = function (){
	console.log(1);
}
arr[2] = function (){
	console.log(2);
}
.
.
.
arr[9] = function (){
	console.log(9);
}

/*
它与第一种写法不一样
在第一种写法中i相当于传了一个引用进来
所以10个函数使用的i都是一样的,指向的是同一块内存,所以打印的都是一样的值10

第二种写法中,i被当作实参,被立即函数使用
所以计算机会新开辟一块内存给形参使用,然后将实参i的值赋给形参
即,这10个函数,指向的是10块不同的内存,分别存着实参传来的值0,1,2,3,...9
*/


这里有一个大坑,那就是实参传变量还是传引用的问题

function test(){
    var arr = [];
    var obj ={age:100};
    for(var i = 0;i < 10;i++){
            arr[i] = function (){
                obj.age += i;
                console.log(obj.age + "  " + i);
            }
    }
    return arr;
}

var myArr = test();
for(var t of myArr){
    t();
}
/*
110  10 
120  10 
130  10 
140  10
150  10 
160  10
170  10
180  10
190  10
200  10
*/


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

var myArr = test();
for(var t of myArr){
	t();
}
/*
100  0 
101  1 
103  2 
106  3 
110  4 
115  5 
121  6 
128  7 
136  8 
145  9
*/

//从两种不同的结果可以看出
//当实参是基本值时,系统会开辟一块新内存
//当实参是Object这种引用值类型时,系统不会开辟新内存,而是传一个引用给形参
//因为实参是Object这种引用值类型时系统也是开辟新内存的话
//obj.age += j; =====> 100 += j
//结果会为:
/*
100   
101  
102
103
104
105
106
107
108
109
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值