预编译
- 它就是用来解决执行顺序的
- JS 执行三部曲: 语法分析;预编译;解释执行
- 语法分析: 通篇扫描看看有没有语法错误
- 预编译 规则:
- imply global 暗示全局变量: 任何变量,如果未经声明就赋值,此变量就为全局对象所有。
Demo: var a = b = 123; 先把 123 赋值给b, 然后声明一个 a 再把 b 的值付给 a; b 变量还没有声明,
所以 b 是暗示全局变量, 现在 window.b 就可以用了 - window 就是全局: 一切的全局变量,全是 window 的属性。 === 全局预编译里的 GO
我们在访问一个全局的变量,其实就是在访问 window 的属性
也就是说一个全局变量 var c = 123 相当于:
window{
c: 123;
}
(window 对象添加了一个属性 c)任何变量,如果未经声明就赋值,那么此变量就归 window 所有
既 a = 10; 相当于 window.a = 10;
全局上的任何变量, 即使声明了,也归 window 所有
既 var b = 10; 相当于 window.a = 10; - 预编在什么时候发生: 在函数执行的前一刻
-
函数预编译四部曲: 必须背下来
1.创建 AO 对象
(activation object 活跃对象) 执行期上下文,就是由于这个函数执行所产生的的存储空间库
AO {
}2. 找形参和变量声明, 将变量和形参名作为 AO 属性名, 值为 undefined
3. 将实参值和形参统一
4. 在函数体找函数声明, 值赋于函数体
Demo 函数 :
形参名和函数名和变量名都一样的情况下,
怎么去读取呢?
function test(a){
console.log(a); //第一条打印
var a = 123; //函数里的变量声明1
console.log(a); //第二条打印
function a(){} //函数声明1
console.log(a); //第三条打印
var b = function(){} //函数里的变量声明2
console.log(b);
//第四条打印
function d(){} //函数声明2
}
test(1);
最终的打印结果是:
function a(){}
123
123
function b(){}1.编译第一步 创建 AO 对象 AO{}
2.找形参和变量声明, 将变量和形参名作为 AO 属性名, 值为 undefined, 函数从上到下,我们开始读 形参是 a, 变量声明是 var a, 这两个 a 就是 AO 的同一个属性 a, 没有什么区别,而且值为 undefined;
(也就是对于 AO 来说,无论是形参 a, 函数a, 函数里面的变量a 都是一个东西,没有什么区别,只不过在函数执行的时候它们所被赋予的值不断变化) AO{
a : undefined, //形参 a 和 函数里的变量声明 a
b : undefined //函数变量
}
3.将实参值和形参统一 也就是说把实参值赋给形参
AO{
a : 1,
b : undefined
}
4.在函数体找函数声明,把“函数声明的名” 作为 “AO 对象的属性名”, 给它们值赋为它们自己的函数体
AO{
a : "function a(){}", // a 也是函数声明,所以此时值又变了
b : undefined
d: "function d(){}"
}
5.这个时候预编译结束,开始真正一行一行执行函数里的东西了- 第一条打印,读取 AO 对象里的 a 值 "function a(){}"
- 然后 "函数里的变量声明1" 这条语句的声明,已经在预编译里执行了,因此这里只执行赋值,给 QA 对象赋值
AO{
a : 123,
b : undefined
d: "function d(){}"
}
- 第二条打印,读取 AO 对象里的 a 值 123
- 然后读取 "函数声明1" 它已经在预编译里执行了,所以这条忽略
- 第三条打印,读取 AO 对象里的 a 值 123 没有变
- 然后 "函数里的变量声明2" 这条语句的声明,已经在预编译里执行了,因此这里只执行赋值,给 QA 对象赋值
AO{
a : 123,
b : "function(){}"
d: "function d(){}"
} - 第四条打印,读取 AO 对象里的 b 值 "function(){}"
- 然后读取 "函数声明2" 它已经在预编译里执行了,所以这条忽略
-
全局里的预编译,就是少了一个统一形参和实参的步骤,其他都一样儿,生成的对象不叫 AO, 叫 GO
- 生成 GO 对象 Global Object 注意 这里的 GO === window
- 找变量声明, 将变量名作为 GO 属性名, 值为 undefined
- 在函数体找函数声明, 值赋于函数体
-
预编译顺序: 先生成 GO 后生成 AO
Demo code :
console.log(test); //第一条打印
function test(){ //函数声明1
console.log(test); //第二条打印
var test = 234;
console.log(test); //第三条打印
function test(){} //函数声明2
}
test(1);
var test = 123;
打印结果
function test(){
console.log(test);
var test = 234;
console.log(test);
function test(){}
}
function test(){}
234- 生成 GO 对象
- 找变量声明, 将变量名作为 GO 属性名, 值为 undefined
GO{
test : undefined,
} - 在函数体找函数声明, 值赋于函数体
GO{
test : function test(){....},
} - 接下来开始执行了
第一条打印,读取 GO 对象里的 test 值 是整个函数体 - 然后读取 "函数声明1" 它已经在预编译里执行了,所以这条忽略
- 然后读到了调用函数 test(1); 函数预编译就要要调用这个函数之前开始编译了
- 生成 AO 对象
- 找形参和变量声明, 将变量和形参名作为 AO 属性名, 值为 undefined
AO{
test : undefined;
} - 将实参值和形参统一
AO{
test : 1;
} - 在函数体找函数声明, 值赋于函数体
AO{
test : "function test(){}",
} - 开始执行函数内部语句
- 第二条打印,读取 AO 对象里的 test 值 function test(){}
这里 AO, GO 都有同一个变量嵌套关系是,AO 有的就用 AO 的,AO 里没有的 才去访问 GO 的值
- 读取 var test = 234; 忽略变量声明 var test, 因为已经在预编译里声明过了,这里只看赋值
AO{
test : 234,
} - 第三条打印,读取 AO 对象里的 test 值 234
- 然后读取 "函数声明2" 它已经在预编译里执行了,所以这条忽略
- 然后往下读 var test = 123; 忽略变量声明 var test, 因为已经在预编译里声明过了,这里只看赋值
GO{ test : 123, }
-
这里 AO, GO 都有同一个变量嵌套关系是,AO 里,AO 自己有的就用 AO 的,AO 里没有的,它才去访问 GO 的值,比较要强
Demo code:
global = 100;
function fn(){
console.log(global); //第一条打印
global = 200;
console.log(global); //第二条打印
var global = 300;
console.log(global);
}
fn();
var global;
打印结果为:
undefined
200
300- 生成 GO 对象
- 找变量声明, 将变量名作为 GO 属性名, 值为 undefined
GO {
global : undefined,
fn: undefined
} - 在函数体找函数声明, 值赋于函数体
GO {
global : undefined,
fn: function fn(){...}
} - 接下来开始执行了 global = 100;
GO {
global : 100,
fn: function fn(){...}
} - 遇到函数声明 fn 已经在预编译里执行了,所以忽略
- 遇到调用函数 fn(); 调用之前开始预编译函数里的东西
AO{
global: undefined,
} - 第一条打印,读取 AO 对象里的 global 值 是 undefined;
- 接下来正常读就行了
- function aa(a) {
console.log(b); //这里会报错,因为变量b 没有声明直接调用
}
aa(1);
function fn(a) {
console.log(b); //这里不会报错,因为变量 b 在 if 语句里被声明了,只要在函数体里被声明,无论是函数体的什么地方都会正常放入 AO 对象中,输出的值是 undefined;
if(a > 1){var b = 100;}
}
fn(1);
闭包
- 在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
- 但凡是内部的函数,通过 return 的方式被保存到了外部,它一定生成闭包
闭包会导致原有的 作用域链不释放,造成内存泄漏 - b 是一个内部函数, 通过一个 return 的方式被保存到了外部
这里的内部函数 b, 在一出生的时候, 就保存了函数 a 的 劳动成果 (aAO, GO)
然后还没有等到 b 执行, 它又 return b, 被扔到了函数的外部 demo 里 现在 demo 就是定义状态的 b 了
此时 a 函数执行结束 a 函数会砍断自己 和 aAO 之间的连线,恢复出生状态 (仅有 GO) (销毁执行期上下文)
但是 b 此时紧紧链接着它出生时候的 (aAO, GO)
那执行 demo(); 函数, 就相当于执行了 b.
b 执行的时候会产生 一个 bAO. 但是 bAO 里没有 b 要打印的 aaa ,所以就相当于它去 aAO 里找 aaa 的值.
demo 即使执行完, 也是要找 bAO 去释放,原来的 aAO 永远不释放。 造成内存泄漏。 - 内存泄漏: 就是你占用的内存越多了, 你剩的就少了
想象成手里的沙子, 你漏出去的沙子多了,手离剩下的就少了 -
闭包的作用
1.实现公有变量 : 函数累加器: 相当于记录一个函数执行了多少次 - 2.可以做缓存 (存储结构)
是一个外部不可见的存储结构, 这里的 obj 是一个对象, 只不过说 对象里的属性不是一个值,而是一个函数了 -
- 3.可以实现封装,属性私有化
这里的 prepareWife 是 对象 deng 私有的,你 deng.prepare 是看不到的,因为它不是 this. 这样创建出来的,但是里面的函数 deng.sayPreparewife() 可以访问 (这里就是闭包,它生成的时候自带了 原来函数的 AO 对象,里面就有了 prepareWife 变量)function Deng(name,wife){ var preparewife = "xiaoxu"; this.name = name; this.wife = wife; this.divorce = function (){this.wife = preparewife;} this.changePreparewife = function(target){ preparewife = target; } this.sayPreparewife = function(){ console.log(preparewife); } } var deng = new Deng('deng','xiaoxu');
- 4.模块化开发,防止污染全局变量
公司合作开发常用var name = "abc"; var initLiu = (function(){ //最开始的入口函数,初始化的函数,我们叫作 init var name = "bcd"; //这里的 name 是私有的,不会污染全局变量,并且外部调用不到它 function callName(){####} return fuction(){ //这个函数被保存到了外面 callName(); } }()); var initDeng = (function(){ var name = "bcd"; function callName(){####} return fuction(){ callName(); } }());
解决思路:
增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标),不过只限于基本类型(基本类型值传递,对象类型引用传递)
for(var i = 0;i < arr.length;i++){
//声明一个匿名函数,若传进来的是基本类型则为值传递,故不会对实参产生影响,
//该函数对象有一个本地私有变量arg(形参) ,该函数的 function scope 的 closure 对象属性有两个引用,一个是 arr,一个是 i
//尽管引用 i 的值随外部改变 ,但本地私有变量(形参) arg 不会受影响,其值在一开始被调用的时候就决定了.
(function (arg) {
arr[i].onclick = function () { //onclick函数实例的 function scope 的 closure 对象属性有一个引用 arg,
alert(arg); //只要 外部空间的 arg 不变,这里的引用值当然不会改变
}
})(i); //立刻执行该匿名函数,传递下标 i(实参)
}