js中的预编译,作用域链

预编译

1.js代码的执行步骤

  1. 语法分析:
    主要扫描代码有没有语法上的错误(比如少些括号,写了中文符号)
  2. 预编译:
    进行变量的声明提升,函数整体提升,函数执行前一刻的准备工作。
  3. 解释执行:
    对js代码进行执行,解释一行,执行一行。

2.预编译的前奏

  1. 暗示全局变量:任何变量未经声明就赋值,此变量归全局所有。
  2. 一切全局变量都是window的属性

例如:

var a = 100;
console.log(window.a); //100
if(1){
    a = 10;
}
console.log(window.a); //10

3.预编译

脚本代码块script执行前,系统执行的操作

  1. 创建一个GO对象,即window全局对象
  2. 查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined
  3. 查找函数声明,函数名作为全局对象的属性,值为函数引用

例如

console.log(a);     //undefined
console.log(func);  //func
var a = 100;
function func(){
    return 1;
}

函数执行前执行的操作

  1. 创建一个AO对象(执行期上下文)
  2. 找到函数体中的形参,变量声明作为AO的属性名,值为undefined
  3. 将实参和形参统一
  4. 将定义的函数名挂在AO的属性中,值为函数体。

来看一道面试题

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

作用域

1.全局作用域

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

2.作用域链

我们在定义函数的时候,函数会默认存在一个叫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
闭包的好处
  1. 希望一个变量长期存储在内存中
  2. 避免全局变量的污染
  3. 私有成员的存在
  4. 用于缓存
闭包的坏处

容易造成内存泄漏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值