6-作用域链中变量的使用原则 闭包 回调/惰性/即时函数

作用域链中变量的使用原则

  • 在作用域链中使用(读取/设置)变量的时候, 首先在当前作用域链中查找, 找到就直接使用
  • 如果没有找到, 就去上一级作用域链中查找, 直到0级作用域

闭包

  • 一般情况下作用域: 内层作用域可以访问外层作用域, 反之不行

      function f1(){
         var n=999;
         function f2(){  
            alert(n); // 999
          }
        }
    
  • 有时候需要从外层作用域访问内层作用域

    1. 直接return, 一次性获取数据, 每次获取的不是同一份数据(变量创建后使用后就销毁了)

          function f1(){
             var n=999;
             function f2(){
               alert(n);
             }
             return f2;
          }
         var result=f1();
         result(); // 999
      
    2. 闭包技术: 可以间接访问封闭空间私有数据的方法

      • 闭包就是对直接return的数据进行包装(函数)
  • 闭包获取数据和设置数据

    • 判断不传形参的情况, 形参和变量不能同名 (同名的话, 形参在调用赋值时, 会先从当前作用域找同名变量)

闭包的好处

  • 获取数据只能通过指定的方法(接口)
  • 在设置数据的时候更加安全, 可以做一些校验工作
  • 延长变量的生命周期

setTimeout和闭包的执行

//第一种写法
for(var i=0; i<3; i++){
    (function(index){
        setTimeout(function(){
            console.log(index+'+++');
        },0);
    })(i);
}
//第二种写法
for(var i=0; i<3; i++){
    setTimeout((function(index){
        return function(){
            console.log(index+'----');
        }
    })(i),0);
}
//第三种方法
for(let i=0; i<3; i++){
    setTimeout(function(){            
        console.log(i+'----');            
    },1000);
}

div事件和闭包

  • JS的任务

    1. 渲染任务
    2. 代码的主要任务
    3. 事件性的任务(点击,定时器..)
  • JS是单线程

    1. 进程: 正在运行的应用程序 (工厂)
    2. 线程: 进程中用来执行任务的(工人),一个线程同一时间只能执行一个任务
    3. 串行执行: 多个任务一个一个的按顺序执行
    4. 并发执行: 多个任务同时执行
    5. 多程线:多条线程
    6. 多线程的原理: 1s = 1万个0.0001s cpu在多个任务之间来回的快速切换,造成多个任务同时执行的假象
    //JS单线程 先循环完 再触发点击事件时 i遍历完自加1变成5了--所以当点击div时变成5了
    for (var i = 0; i < divs.length; i++) {
        divs[i].onclick = function () {
            console.log('点击了第' + i + '个div');
        }
    }
    //方法一:闭包解决拿到i的值
    for (var i = 0; i < divs.length; i++) {    
        (function (j) {
            divs[j].onclick = function () {
                console.log('点击了第' + j+ '个div');
            }
        })(i);    
    }
    //方法二
    for (var i = 0; i < divs.length; i++) {
        divs[i].onclick = (function (j) {
            return function () {
                console.log('点击了第' + j + '个div');
            }
        })(i);
    }

函数的特殊性

  • 特殊性(特点): 函数本身是对象, 且对象可以提供作用域
    • 函数可以在运行时动态的创建, 还可以在程序执行过程中创建
    • 函数可以赋值给变量, 可以被扩展, 甚至删除
    • 函数可以作为其他函数的参数/返回值
    • 函数可以拥有自己的属性和方法
  • 注意
    • {}块在JS中不会创建作用域, 哪怕是if或者是while语句中使用var关键字申明的变量也并非局部变量, 函数是可以通过()调用并执行对象
  • 函数是第一类型对象
    • 函数可以向普通对象一样, 作为函数的参数 | 赋值给变量(函数表达式) | 作为函数的返回值返回

    • fun.name函数名可以获取, 但不能修改

        //函数作为参数传递
        setTimeout(function () {
            console.log(1);
        },100);
      
        //函数作为返回值
        function func() {
            return function () {
                console.log("demo");
            }
        }       
        var f = func();
        f();                //demo
      
        //函数赋值给变量
        var a = function(){}
        a();    //直接通过变量的名称调用函数
      

回调函数

  • 定义: 把一个函数作为其他函数的参数
    • 把函数作为另一个函数的参数
    //01 提供一个对象,该对象中永远showName方法
    var obj = {
        name:"默认的名字",
        age:30,
        showName:function () {
            console.log(this.name);
        },
        showAge:function () {
            console.log(this.age);
        }
    };                          

    //02 提供一个函数,该函数接受一个参数(函数引用)
    function demo(callBack,callBack_obj) {
        //处理第一个参数传递对象方法字符串的形式
        if(typeof callBack == 'string') {
            callBack = callBack_obj[callBack];
        }

        if (typeof callBack == 'function') {
            callBack.call(callBack_obj);
        }
    }

    //demo(obj.showName,obj);
    //demo(obj.showAge,obj);

    //传递字符串和对象来进行调用
    demo("showName",obj);

    //(01)以上代码传入两个参数,分别为具体的回调函数,和该回调函数所属的对象
    //(02)该函数的参数接受两种方式的回调传递(一种是直接传递函数引用,一种是直接以字符串的方式传递对象方法的字符串)
    //(03)在函数内部对传入的回调参数做处理,修正this的问题
  • 函数作为返回值
    • 使用闭包实现一个计数器(在该示例中setup函数的返回值为一个函数)
    • 通过调用返回值(一个函数),可以操作setup函数中的变量
    

惰性函数

  • 定义: 函数真正的内容需要执行一次函数才能确定, 实现自我更新

          function foo() {
              console.log("foo!");
              //函数是引用型数据, foo的存储地址被重新定义了
              foo = function () { 
                  console.log("new foo!");
              }
          }
      
          //函数的调用
          //foo();  //foo!
          //foo();  //new foo!
    
  • 应用场: 函数有一些初始化的准备工作要做,且只需要执行一次的情况。

  • 注意点

    • 在函数上添加的属性或方法, 自我更新后无法访问
    • 把惰性函数赋值给变量, 以变量的(对象的方法)方式来调用, 不会更新(调用仍然是同一份数据)


      6-作用域链中变量的使用原则 闭包 回调/惰性/即时函数_第1张图片
      惰性函数以对象的形式访问.png

      6-作用域链中变量的使用原则 闭包 回调/惰性/即时函数_第2张图片
      惰性函数赋值给变量.png

即时函数

  • 定义: 在函数定义之后立即执行该函数。

           //第一种写法
           (function () {
               console.log("即时函数的第一种写法");
           })();
    
           //第二种写法
           ;(function () {
                console.log("即时函数的第二种写法");
           })();
          
           //补充写法
           (function (a) {
                console.log(a);
           }(20));
    
           +function (b) {
                 console.log(b);
           }(30);
    
           -function (b) {
                console.log(b);
           }(40);
    
  • 模式组成

    • 使用函数表达式来定义函数(匿名函数, 不能使用函数声明方式)
    • 在函数表达式末尾添加一组(), 表示立即执行当前函数
    • 将整个函数包装在()中, 有两种方式
  • 作用

    • 用来将所有的代码包装到当前的作用域中,并且不会将任何的变量泄露到全局作用域中。
    • js中没有代码块作用域,而函数是js中唯一可以创建作用域的。
    • 即时函数就是利用了函数创建作用域这一点,来实现对一些需要封装且不允许外部访问的操作。
  • 优点

    • 不会产生全局变量,在即时函数内部定义的所有变量都仅仅只是该函数的局部变量,不会造成全局变量污染问题。
    • 具有更好的封装性,外部无法访问到该函数内部的数据。
  • 即时函数的传参和返回值

       //01 接受参数
       (function (str) {
           console.log(str);           //hello
       })("hello");
    
       //02 提供返回值并赋值给新的变量
       var foo = (function () {
          return 2 + 1;
        })();
    
       console.log(foo);           //3
    

即时对象初始化

  • 结构特征
    • 提供一个对象, 在该对象内部提供一个init初始化方法
    • 使用()把对象包装起来(让字面量变成表达式)
    • 然后随即调用init方法, 完成初始化操作
  • 基本结构
    • 即时对象初始化: ({init: function(){}}).init();
  • 模式优点
    • 在执行一次性的初始化任务时保护全局的命名空间
({
    name:"张三",
    age:23,
    getDescript:function () {
    console.log("名字:" + this.name + "年龄:" + this.age);
},

//注意:在对象中访问对象的属性和方法都需要使用this.前缀
init:function () {
    this.getDescript();
    //其他的初始化处理
}
}).init();

设计模式: 为了解决开发中遇到的一类问题而提出的一套方法

  • 要求: 一般一套系统需要设计模式
  • 来源: 建筑行业
  • 工厂模式核心
    • 提供一个父构造函数-->开了一家工厂
    • 设置父构造函数的原型对象-->产品公共东西
    • 在父构造函数上提供一个静态的工厂方法-->生产产品
      1. 接受传入的参数
      2. 判断是否支持生产
      3. 设置子构造函数的原型对象是父构造函数的一个实例(原型链继承)
      4. 利用子构造函数创建对象并且返回
    • 定制合作伙伴
    • 利用父构造函数的静态工厂方法创建对象
  • 作用
    • 通过统一的方法/接口创建对象, 并且根据不同的类型创建不同的对象, 便于代码的维护和扩展

你可能感兴趣的:(6-作用域链中变量的使用原则 闭包 回调/惰性/即时函数)