Tracy JS 小笔记 - 预编译,闭包

预编译

  • 它就是用来解决执行顺序的
  • 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.这个时候预编译结束,开始真正一行一行执行函数里的东西了
    1. 第一条打印,读取 AO 对象里的 a 值 "function a(){}"
    2. 然后 "函数里的变量声明1" 这条语句的声明,已经在预编译里执行了,因此这里只执行赋值,给 QA 对象赋值
      AO{
      a : 123,
      b : undefined
      d: "function d(){}"
      }
       
    3. 第二条打印,读取 AO 对象里的 a 值 123
    4. 然后读取 "函数声明1" 它已经在预编译里执行了,所以这条忽略
    5. 第三条打印,读取 AO 对象里的 a 值 123 没有变
    6. 然后 "函数里的变量声明2" 这条语句的声明,已经在预编译里执行了,因此这里只执行赋值,给 QA 对象赋值
      AO{
      a : 123,
      b : "function(){}"
      d: "function d(){}"
      }
    7. 第四条打印,读取 AO 对象里的 b 值 "function(){}"
    8. 然后读取 "函数声明2" 它已经在预编译里执行了,所以这条忽略
  • 全局里的预编译,就是少了一个统一形参和实参的步骤,其他都一样儿,生成的对象不叫 AO, 叫 GO

    1. 生成 GO 对象 Global Object 注意 这里的 GO === window
    2. 找变量声明, 将变量名作为 GO 属性名, 值为 undefined
    3. 在函数体找函数声明, 值赋于函数体
  • 预编译顺序: 先生成 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
    1. 生成 GO 对象
    2. 找变量声明, 将变量名作为 GO 属性名, 值为 undefined
      GO{
      test : undefined,
      }
    3. 在函数体找函数声明, 值赋于函数体
      GO{
      test : function test(){....},
      }
    4. 接下来开始执行了
      第一条打印,读取 GO 对象里的 test 值 是整个函数体
    5. 然后读取 "函数声明1" 它已经在预编译里执行了,所以这条忽略
    6. 然后读到了调用函数 test(1); 函数预编译就要要调用这个函数之前开始编译了
    7. 生成 AO 对象
    8. 找形参和变量声明, 将变量和形参名作为 AO 属性名, 值为 undefined
      AO{
      test : undefined;
      }
    9. 将实参值和形参统一
      AO{
      test : 1;
      }
    10. 在函数体找函数声明, 值赋于函数体
      AO{
      test : "function test(){}",
      }
    11. 开始执行函数内部语句
    12. 第二条打印,读取 AO 对象里的 test 值 function test(){}

      这里 AO, GO 都有同一个变量嵌套关系是,AO 有的就用 AO 的,AO 里没有的 才去访问 GO 的值

    13. 读取 var test = 234; 忽略变量声明 var test, 因为已经在预编译里声明过了,这里只看赋值
      AO{
      test : 234,
      }
    14. 第三条打印,读取 AO 对象里的 test 值 234
    15. 然后读取 "函数声明2" 它已经在预编译里执行了,所以这条忽略
    16. 然后往下读 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
    1. 生成 GO 对象
    2. 找变量声明, 将变量名作为 GO 属性名, 值为 undefined
      GO {
      global : undefined,
      fn: undefined
      }
    3. 在函数体找函数声明, 值赋于函数体
      GO {
      global : undefined,
      fn: function fn(){...}
      }
    4. 接下来开始执行了 global = 100;
      GO {
      global : 100,
      fn: function fn(){...}
      }
    5. 遇到函数声明 fn 已经在预编译里执行了,所以忽略
    6. 遇到调用函数 fn(); 调用之前开始预编译函数里的东西
      AO{
      global: undefined,
      }
    7. 第一条打印,读取 AO 对象里的 global 值 是 undefined;
    8. 接下来正常读就行了
  • 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.可以实现封装,属性私有化
     
    
    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');
        
    
    
    这里的 prepareWife 是 对象 deng 私有的,你 deng.prepare 是看不到的,因为它不是 this. 这样创建出来的,但是里面的函数 deng.sayPreparewife() 可以访问 (这里就是闭包,它生成的时候自带了 原来函数的 AO 对象,里面就有了 prepareWife 变量)
  • 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(实参)
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值