例如:
var a = 100;
console.log(window.a); //100
if(1){
a = 10;
}
console.log(window.a); //10
脚本代码块script执行前,系统执行的操作
例如
console.log(a); //undefined
console.log(func); //func
var a = 100;
function func(){
return 1;
}
函数执行前执行的操作
a = 100;
function demo(e){
function e(){}
arguments[0] = 2;
console.log(e);
if(a){
var b = 123;
function c(){}
}
var c;
a = 10;
var a;
console.log(b);
f = 123;
console.log(c);
console.log(a);
}
var a;
demo(1);
console.log(a);
console.log(f);
使用预编译来分析一下这道题
1,首先创建一个GO对象
GO = {}
2,扫描代码预编译,经过62,46行的定义,所以有
G0 = {
a:undefined,
demo:function(){...}
}
3,开始执行代码,经过45行的赋值,所以有
GO = {
a:100,
demo:function(){...}
}
4,63行执行函数,创建函数执行上下文
AO={}
5,找到函数体中的形参,变量声明作为AO的属性名,值为undefined
AO={
e:undefined,
d:undefined,
c:undefined,
d:undefined,
}
6,实参形参同一
AO={
e:1,
d:undefined,
c:undefined,
d:undefined,
}
7,将定义的函数名挂在AO的属性中,值为函数体
AO={
e:function(){...},
d:undefined,
c:undefined, //不走if,所以不定义函数
d:undefined,
}
8,执行函数
function demo(e){
function e(){}
arguments[0] = 2; //挂载到AO中,此时AO中e为2
console.log(e); //2
if(a){ //不走if
var b = 123;
function c(){}
}
var c;
a = 10; //挂载到AO中,此时AO中a为10
var a;
console.log(b); //未声明,undefined
f = 123; //未使用var,归GO所有
console.log(c); //声明未赋值,undefined
console.log(a); //10
}
var a;
demo(1);
console.log(a); //打印GO上的a变量
console.log(f); //打印GO上面的f变量
最后结果
2
undefined
undefined
10
100
123
JS有一个全局对象,window,在全局声明的变量都属于window的属性,比如:
var a = 10;
b = 10;
function fun(){
c = 10;
var d = 10;
}
fun();
console.log("window.a",window.a); //10
console.log("window.b",window.b); //10
console.log("window.c",window.c); //10
console.log("window.d",window.d); //undefined
我们在定义函数的时候,函数会默认存在一个叫scope的隐式属性,即域,这个属性指向一个数组,数组中存的则是一组链式的函数执行上下文。
在函数被定义的时候,会直接拥有父级模块的作用域,比如在window中被定义的函数,会直接拥有window的作用域。
在函数执行的时候,会生成一个其专属的执行期上下文。
还是这个例子
var a=10
//相当于 window{
// a:10
//}
function fun1(){
var b=20;
function fun2(){
//
}
fun2();
}
fun1();
如上,通过预编译和作用域来解读一下代码运行的具体步骤
1.首先,全局存在作用域GO,归window所有
2.定义函数fun1时,会继承window的作用域,其scope属性被创建,指向一个链表,其第一项为GO,
即scope(fun1):GO -->
2执行函数fun1时,会生成一个属于fun1的函数执行上下文AO,这是scope第一项为这个AO对象,
即scope(fun1):AO(fun1) --> GO -->
3.执行函数fun1时,在fun1函数体中,由于定义了函数fun2,所以创建fun2的scope属性,直接继承自fun1,
即scope(fun2): AO(fun1) --> GO -->
4.之后在执行函数fun2时,会创建一个专属于fun2的执行上下文,放入,fun2的scope属性的最顶端,
即scope(fun2): AO(fun2) --> AO(fun1) --> GO -->
5,执行完函数fun2后,销毁其作用域
6,执行完函数fun1后,销毁其作用域
var cc = 123;
function a(){
function b(){
var bb = 234;
aa = 0;
console.log(cc);
}
var aa = 123;
var cc = 111;
b();
console.log(aa);
}
a();
假如现在要在函数b中访问一个变量,系统则会到函数b的scope中去寻找,scope是一个数组,它从第0位开始访问,第一位是函数b的作用域,找不到的话会继续想下寻找,即函数a的作用域,再找不到,便会继续向下,即在window的作用域中寻找,最后也无法找的变量的话,则会抛出错误。
所以函数执行结果为
111
0
当内部函数被保存到外部时,将会生成闭包。生成闭包后,内部函数依旧可以访问其所在的外部函数的变量。
在内部函数被定义的时候会创建一个属于内部函数的scope属性保存着作用域链,它会直接继承父函数的作用域链,当它有对父级函数的变量的访问时,这个作用域链在父级函数销毁时不会被销毁,此时内部函数依旧可以访问父级函数的变量,这个就是闭包。
立即执行函数、let
使用闭包实现一个计数器
function counterCreate(){
var count = 0;
return function(){
count++;
console.log(`计数${count}次`);
}
}
var addCount = counterCreate(); //将函数保存到外部
addCount();//计数1次
addCount();//计数2次
addCount();//计数3次
addCount();//计数4次
使用闭包做循环打印
for (var i = 0; i < 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 100);
}
//结果5 5 5 5 5
使用立即执行函数
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 100);
})(i);
}
//结果 0 1 2 3 4
容易造成内存泄漏