作用链
先上定义
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
*/