概览
一、作用域
[[scope]]
函数创建时,生成的一个js内部的隐式属性。
二、作用域链
scope chain
决定了各级上下文中的代码在访问变量和函数时的顺序。
三、预编译
AO - activation object 函数的执行期上下文。
GO - global object 全局的执行期上下文。
函数执行前会生成AO。
函数执行完成后,自身的AO会销毁。
作用域
函数也是对象,是引用类型
对象就有属性
有隐式属性、显示属性
显示属性可以访问,隐身属性不能访问,隐式属性是js引擎内部固有的,就像对象的私有属性,虽然不能.得到,但是存在。
[[scope]]—-中文释义范围
定义:1函数创建时生成时,创建的一个Js引擎内部固有的隐式属性,只能由Js引擎读取
2.函数存储作用域链的容器
作用域链AO/GO
AO函数执行期上下文
GO全局的执行期上下文
示例详解
a函数声明,不运行时,就有[[scope]]属性,存着GO
AO只有当函数执行时才有,函数如果不执行就不会有,在函数执行前一刻生成,是在执行前产生的,不是在执行时产生的,AO产生完函数没有执行
因为不运行没有AO,所以不存a函数的AO
a函数执行前一刻AO产生,AO把GO挤下去,成为第1个
b函数定义时,没有运行,所以没有形成b的AO,所以b的[[scope]]中只有GO与a的AO,定义时的[[scope]]永远和上一级被执行时一样
就像继承就写了个extends一样,继承父亲的所有,但只增不减
b函数执行时AO产生,产生的AO进入b函数的[[scope]]中
当b函数执行完,b函数的AO被销毁,回归定义的状态,最上面的被销毁
a函数不变,因为b函数执行完,之后a函数才结算执行完
b.scope中有b的AO,a的AO,GO的地址引用,这些引用被销毁,因为以后用不到b的AO了,为什么用不到,因为b的AO的引用被删除了,没有任何关于b的AO的引用了,所以b的AO就被删除了。a的AO,go依然有引用指向它们,所以不删除。
当a函数执行完,a函数的AO销毁,a.AO中有function b的声明,其中有b的scope,b的scope就被删除,函数b就不存在了。函数b就彻底没有了,就相当于原始代码根本没有写function b{…},没有函数b的声明,虽然原始代码有
当x的上一级结束被销毁,x的scope才被销毁
机器只看,只执行经过预编译之后的代码,原始的代码不看,不执行,预编译完再执行原始的代码就白预编译了,经过预编译之后的代码有类似的地方,也有不同,不同之处:引用值空间的地址等
依此类推,最后回到GO
问题
AO、GO是怎么存在scope里面的?
引用值,存的是地址
为什么是引用值?
大数据量占内存,占资源,如果想共用就指向大资源
GO、AO什么时候产生?
GO、AO都是预编译,预编译是在执行前。
GO在代码运行时,全局上下文,整段代码要运行,先产生GO,不运行不产生GO
AO是函数上下文,当函数要运行时产生AO,不运行则没有
为什么要有作用域链
为了判断变量之间关系,函数也是对象,是变量
代码题目
/**
* 1.
* GO = {
* a : function a() {}
* }
* 2. a函数执行前生成自身的AO
* AO = {
* b : function b() {}
* }
* 3. b函数执行前生成自身的AO
* AO = {
* c : function c() {}
* }
* 4. c函数执行前生成自身的AO
* AO = {}
*/
function a() {
function b() {
function c() {
}
c();
}
b();
}
a();
答案
1.GO = {
a : function a() {}
}
2. a函数执行前生成自身的AO
AO = {
b : function b() {}
}
3. b函数执行前生成自身的AO
AO = {
c : function c() {}
}
4. c函数执行前生成自身的AO
AO = {}
a定义:a.[[scope]] -> 0: GO
a执行:a.[[scope]] -> 0: a的AO
1:GO
b定义:b.[[scope]] -> 0:a的AO
1:GO
b执行:b.[[scope]] -> 0: b的AO
1:a的AO
2:GO
c定义:c.[[scope]] -> 0: b的AO
1:a的AO
2:GO
c执行:c.[[scope]] -> 0:c的AO
1:b的AO
2:a的AO
3:GO
c结束:c.[[scope]] -> 0:b的AO
1:a的AO
2:GO
b结束:b.[[scope]] -> 0:a的AO
1:GO
c.[[scope]]销毁
a结束:a.[[scope]] -> 0: GO
b.[[scope]] -> 销毁
答案简化
GO={a:func a}
a.AO={b:func b}
b.AO={c:func c}
c.AO={}
a定义 a.scope->0:GO
a执行 a.scope->0: a.AO
1:GO
b定义 a.scope->0: a.AO
1:GO
b执行 a.scope->0: b.AO
1: a.AO
2:GO
c定义 c.scope->0: b.AO
1: a.AO
2:GO
c执行 c.scope->0: c.AO
1: b.AO
2: a.AO
3: GO
c结束 c.scope->0: b.AO
1:a.AO
2:GO
b结束 b.scope->0 a.AO
1: GO
c.scope 销毁
a结束 a.scope->0:GO
b.scope销毁
闭包
代码示例
function test1() {
function test2() {
var b = 2;
console.log(a); // 1
}
var a = 1;
return test2;
}
var c = 3;
var test3 = test1();
test3()
答案:1
GO={test1:func test1,c:u->3,test3:u->func test2}
test1.AO={
test2:func,a:u->1
}
test2.AO={
b:u->2
}
问题
这个代码undo时发生了什么 do时发生了什么
undo
GO={test1:func test1,c:u,test3:u}
test1.AO={test2.func,a:u}
test2.AO={b:u}
do
GO={test1:func test1,c:u->3,test3:u->func test2}
test1.AO={test2.func,a:u->1}
test2.AO={b:u->2}
test1定义: test1.scope->0:GO
test1执行: test1.scope->0:test1.AO
1:GO
test2定义: test2.scope->0:test1.AO
1:GO
test2执行: test2.scope->0:test2.AO
1:test1.AO
2:GO
test1结束: test1. scope->0:GO
test1先结束,执行return test2,test1中AO的引用地址消失,但test2在。 test2函数此时没有执行,var test3 = test1()其实test3就是test2,test2执行
test2结束: test2.scope->0:test1的AO
1:GO
test1.scope销毁
应该是
test1 定义 :test1.scope->0:GO
test1 执行:test1.scope->0:test1.AO
1:GO
test2定义:test2.scope->0:test1.AO
1:GO
test1结束:test1.scope->0:GO
test1函数先结束,return了,函数肯定执行完成,test1.AO的地址删除,只有GO的地址
test3(test2) 执行:test2.scope->0:test2.AO
1:test1.AO
2:GO
test3结束:test2.scope->0:test1.AO
1:GO
因为test2被go的test3引用,解收,因为go不会被销毁,test3不会被销毁,所以test2永远不会被销毁,
test2中的test2.scope->0:test1.AO 1:GO不会被销毁,永远存在,test2.ao运行结束后被销毁
等再运行时,继续生成test2.ao,运行多次,重复生成ao,销毁ao
test1定义 | test1.scope | 0:GO |
---|---|---|
test1执行 | test1.scope | 0:test1.AO 1:GO |
test2定义 | test2.scope | 0:test1.AO 1:GO |
test1结束 | test1.scope | 0:GO |
test3(test2) 执行 | test2.scope | 0:test2.AO 1:test1.AO 2:GO |
test2结束 | test2.scope | 0:test1.AO 1:GO |
test1 dec :test1.scope->0:GO
test1 do:test1.scope->0:test1.AO
1:GO
test2 dec:test2.scope->0:test1.AO
1:GO
test1 done:test1.scope->0:GO
test3(test2) do:test2.scope->0:test2.AO
1:test1.AO
2:GO
test3 done:test2.scope->0:test1.AO
1:GO
GO储存test1:func,c:u,test3:u
AO执行,test2声明,test2.scope生成
return返回test2,test2函数没有执行,没有test2的AO,虽然test1执行完成,但test2被test3引用,所以test2不销毁,test2.scope没被销毁,test2.scope依然有test1的AO和GO的引用(的地址),所以存储他们的空间不被销毁,相当于代码中的函数test1依然存在
这时test1函数执行完成,因为都执行return了,函数肯定运行完毕了
test1.scope的test1.AO地址销毁,只有GO
test3执行,是test3指向的空间内的test2执行,test2的AO生成,test2.scope加入test2的AO,test2.AO没有变量a,往test1.AO找,找到变量a=1,所以打印1
当test3执行后,test2.AO被销毁,但test1.AO,GO并不会销毁,因为他们存在变量test3中,test3是全局变量永远存在,test1.AO与GO一样永远不会被销毁,GO不会被销毁
基础总结
当内部函数被返回到外部并被保存时,一定会产生闭包,闭包会产生原来的作用域链不释放,过度的闭包可能会导致内存泄漏,或加载过慢。
因为内部函数保存着外部函数的AO,内部函数被全局变量指向,外部函数的AO地址存在全局变量中,所以不会被释放
一般只有GO不销毁,常驻内存,但闭包会使AO运行完之后不会被销毁,常驻内存
闭包可以看作是一个现象,写闭包就是写一个能产生闭包现象的函数
作业
function test() {
var n = 100;
function add() {
n++;
console.log(n);
}
function reduce() {
n--;
console.log(n);
}
return [add, reduce];
}
var arr = test();
arr[0]();//101
arr[1]();//100
因为test.AO没有被销毁,常驻内存,n没有被销毁
返回多个方法,用数组
GO | test | func |
---|---|---|
arr | u->[add,reduce] | |
test.AO | n | u->100 |
add | func | |
reduce | func |
GO | test:func arr:u->[add,reduce] |
---|---|
test.AO | n:u->100 add:func reduce:func |
test声明 | test.scope | 0:GO |
---|---|---|
test执行 | test.scope | 0:test.AO 1:GO |
add声明 | add.scope | 0:test.AO 1:GO |
reduce声明 | reduce.scope | 0:test.AO 1:GO |
test结束 | test.scope | 0:GO |
add执行 | add.scope | 0:add.AO 1:test.AO 2:GO |
add结束 | add.scope | 0:test.AO 1:GO |
reduce执行 | reduce.scope | 0:reduce.AO 1:test.AO 2:GO |
reduce结束 | reduce.scope | 0:test.AO 1:GO |
function sunSched() {
var sunSched = '';
var operation = {
setSched: function(thing) {
sunSched = thing;
},
showSched: function() {
console.log('My schedule on sunday is ' + sunSched);
}
}
return operation;
}
var sunSched = sunSched();
sunSched.setSched('studying');
sunSched.showSched();
返回多个方法用对象
GO | sunSched:u->func->operation{} |
---|---|
sunSched.AO | sunSched:u->’’->’studying’ operation:u->{…} |
setSched.AO | thing:’studying’ |
showSched.AO |
/**
* GO = {
* c : undefined -> 3
* test3 : undefined -> function test2() {}
* test1 : function test1() {}
* }
*
* test1的AO
* AO = {
* a : undefined -> 1
* test2: function test2() {}
* }
*
* test2的AO
* AO = {
* b : undefined -> 2
* }
*/
function test1() {
function test2() {
var b = 2;
console.log(a); // 1
}
var a = 1;
return test2;
}
var c = 3;
var test3 = test1();
test3();
/**
* test1定义: test1.[[scope]] -> 0: GO
* test1执行: test1.[[scope]] -> 0: test1的AO
* 1:GO
* test2定义: test2.[[scope]] -> 0: test1的AO
* 1:GO
* test2执行: test2.[[scope]] -> 0: test2的AO
* 1: test1的AO
* 2:GO
* test1结束: test1.[[scope]] -> 0: GO
* test2结束: test2.[[scope]] -> 0: test1的AO
* 1: GO
* test1.[[scope]] 销毁
*
*/
//--------------------------------------------------------------------------
/**
* GO = {
* arr : undefined -> [add, reduce]
* test: function test() {}
* }
*
* AO = {
* n : undefined -> 100
* add : function add() {}
* reduce: function reduce() {}
* }
*
*/
function test() {
var n = 100;
function add() {
n++;
console.log(n);
}
function reduce() {
n--;
console.log(n);
}
return [add, reduce];
}
var arr = test();
arr[0]();
arr[1]();
/**
* test定义: test.[[scope]] -> 0 : GO
* test执行: test.[[scope]] -> 0 : test的AO
* 1 : GO
* add定义: add.[[scope]] -> 0: test的AO
* 1: GO
* add执行: add.[[scope]] -> 0: add的AO
* 1: test的AO
* 2: GO
* reduce定义: reduce.[[scope]] -> 0: test的AO
* 1: GO
* reduce执行: reduce.[[scope]] -> 0: reduce的AO
* 1: test的AO
* 2: GO
*/
//--------------------------------------------------------------------------
/**
* GO = {
* sunSched : undefined -> function sunSched() {} -> {...}
* }
*
* AO = {
* sunSched : undefined -> ''
* operation: undefined -> {...}
* }
*
*/
function sunSched() {
var sunSched = '';
var operation = {
setSched: function(thing) {
sunSched = thing;
},
showSched: function() {
console.log('My schedule on sunday is ' + sunSched);
}
}
return operation;
}
var sunSched = sunSched();
sunSched.setSched('studying');
sunSched.showSched();
/**
* sunSched定义:sunSched.[[scope]] -> 0: GO
* sunSched执行:sunSched.[[scope]] -> 0: sunSched的AO
* 1: Go
* setSched定义: setSched.[[scope]] -> 0: sunSched的AO
* 1: Go
* showSched定义: showSched.[[scope]] -> 0: sunSched的AO
* 1: Go
*/