• 05-js讲义(闭包、作用域、函数的4种调用方式)


    闭包

    变量作用域

    • 变量作用域的概念:就是一个变量可以使用的范围
    • JS中首先有一个最外层的作用域:称之为全局作用域
    • JS中还可以通过函数创建出一个独立的作用域,其中函数可以嵌套,所以作用域也可以嵌套
    var age=18;     //age是在全局作用域中声明的变量:全局变量
    
    function f1(){
        console.log(name);      //可以访问到name变量
        var name="周董" //name是f1函数内部声明的变量,所以name变量的作用域就是在f1函数内部
    
        console.log(name);      //可以访问到name变量
    
        console.log(age);       //age是全局作用域中声明的,所以age也可以访问
    }
    
        //多级作用域
        //-->1级作用域
        var gender="男";
        function fn(){
            //gender:可以访问
            //age:  可以访问,值为undefined
            //height: 不能访问
    
            //-->2级作用域
            return function(){
                //gender:   通过一级一级作用域的查找,发现gender是全局作用域中声明的变量
                //age:      可以访问,值为undefined
                //height:  可以访问,值为undefined
                console.log(gender);
    
                //-->3级作用域
                var height=180;
            }
            var age=5;
        }
    
    • 注意:变量的声明和赋值是在两个不同时期的
        function fn(){
            console.log(age);   //undeinfed
            var age=18;
            console.log(age);   //18
        }
    
    - fn函数执行的时候,首先找到函数内部所有的变量、函数声明,把他们放在作用域中,给变量一个初始值:undefined    -->变量可以访问
    - 逐条执行代码,在执行代码的过程中,如果有赋值语句,对变量进行赋值
    

    作用域链

    • 由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?我们把这个变量的查找过程称之为变量的作用域链
    • 作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)
    • 简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)
      • 查看当前作用域,如果当前作用域声明了这个变量,就确定结果
        • 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
          • 再查找上级函数的上级函数,直到全局作用域为止
            • 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
    function fn(callback){
        var age=18;
        callback()
    }
        
    fn(function(){
        console.log(age); //undefined
        var age = 15;
        //分析:age变量:
        //1、查找当前作用域:并没有
        //2、查找上一级作用域:全局作用域
        //-->难点:看上一级作用域,不是看函数在哪里调用,而是看函数在哪里编写
        //-->因为这种特别,我们通常会把作用域说成是:词法作用域
    })
    
    

    闭包概念

    各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
    由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
    所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
    

    闭包的用途

    闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

    使用闭包的注意点

    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

    闭包问题的产生原因

    • 函数执行完毕后,作用域中保留了最新的a变量的值

    闭包的应用场景

    • 模块化
    • 防止变量被破坏

    函数的4种调用方式

    • ES6之前,函数内部的this是由该函数的调用方式决定的
      • 函数内部的this跟大小写、书写位置无关
    • 在ES6的箭头函数之前的时代,想要判断一个函数内部的this指向谁,就是根据上面的四种方式来决定的
    • 1、函数调用
        var age=18;
        var p={
            age:15
            say:function(){
                console.log(this.age);
            }
        }
        var f1=p.say; //f1是函数
        f1();       //函数调用-->this:window    -->this.age=18
    
        function Person(name){
            this.name=name;
        }
        Person.prototype={
            constructor:Person,
            say:function(){
                console.log(this.name);
            }
        }
    
        //函数的第一种调用方式:函数调用    
        //  -->函数内部的this指向window
        Person("abc"); //window.name --> abc
    
        //注:window对象中的方法都是全局函数,window对象中的属性都是全局变量
    
    • 2、方法调用
        var clear=function(){
            console.log(this.length);
        }
        
        var length=50;
        var tom={ c:clear,length:100 };
        tom.c();        //这里是方法调用的方式        
        //打印this.length 是50 还是100?
        //-->相当于:this是指向window还是指向tom呢? 
        //  -->结果为:100  
        //      -->this:tom
    
        //结论:由于clear函数被当成tom.c()这种方法的形式来进行调用,所以函数内部的this指向调用该方法的对象:tom 
    
    • 3、new调用(构造函数)
        //1、
        function fn(name){
            this.name=name;
        }
        //通过new关键字来调用的,那么这种方式就是构造函数的构造函数的调用方式,那么函数内部的this就是该构造函数的实例
        var _n=new fn("小明");  //_n有个name属性,值为:小明
    
        //2、
        function jQuery(){
            var _init=jQuery.prototype.init;
            //_init就是一个构造函数
            return new _init();
        }
        jQuery.prototype={
            constructor:jQuery,
            length:100,
            init:function(){
                //this可以访问到实例本身的属性,也可以访问到init.prototype中的属性
                //这里的init.prototype并不是jQuery.prototype
                console.log(this.length);   // undefined
            }
        }
    
    
    • 4、上下文调用方式(call、apply、bind)

      • 上下文模式应用场景:

        • 一些需要指定this的情况,比如$.each方法回调函数内部的this
        • 判断数据类型:
          • Object.prototype.toString.call(1);
      • call、apply

       //call、apply
        function f1(){
            console.log(this);
        }
        //call方法的第一个参数决定了函数内部的this的值
        f1.call([1,3,5])
        f1.call({age:20,height:1000})
        f1.call(1)      
        f1.call("abc")
        f1.call(true);
        f1.call(null)
        f1.call(undefined);
      
        //上述代码可以用apply完全替换
      
        //总结:
        //call方法的第一个参数:
        //1、如果是一个对象类型,那么函数内部的this指向该对象
        //2、如果是undefined、null,那么函数内部的this指向window
        //3、如果是数字-->this:对应的Number构造函数的实例
        //      -->   1   --> new Number(1)
        //4、如果是字符串-->this:String构造函数的实例
        //      --> "abc"   --> new String("abc")
        //5、如果是布尔值-->this:Boolean构造函数的实例
        //      --> false   --> new Boolean(false)
      
      
      • call和apply异同:
        • call和apply都可以改变函数内部的this的值
        • 不同的地方:传参的形式不同
        function toString(a,b,c){
            console.log(a+" "+b+" "+c);
        }
        toString.call(null,1,3,5)     //"1 3 5"
        toString.apply(null,[1,3,5])  //"1 3 5"
      
      • bind
        var obj = {
            age:18,
            run : function(){
                console.log(this);  //this:obj
                
                var _that=this;
      
                setTimeout(function(){
                    //this指向window
                    console.log(this.age); //undefined是正确的
                    console.log(_that.age); //18
                    
                },50);
            }
        }
        obj.run();
      
        //bind是es5中才有的(IE9+)
        var obj5 = {
            age:18,
            run : function(){
                console.log(this);  //this:obj5
      
                setTimeout((function(){
                    console.log(this.age); //18
                }).bind(this),50);  //this:obj5
                //通过执行了bind方法,匿名函数本身并没有执行,只是改变了该函数内部的this的值,指向obj5
            }
        }
        obj5.run();
        
        //bind基本用法
        function speed(){
            console.log(this.seconds);
        }
        //执行了bind方法之后,产生了一个新函数,这个新函数里面的逻辑和原来还是一样的,唯一的不同是this指向{seconds:100}
        var speedBind = speed.bind({ seconds:100 });
        speedBind();    //100
      
        (function eat(){
            console.log(this.seconds);
        }).bind({ seconds:360 })()  //360
      
        var obj={
            name:"西瓜",
            drink:(function(){
                //this指向了:{ name:"橙汁" }
                console.log(this.name);
            }).bind({ name:"橙汁" })
        }
        obj.drink();    //"橙汁"
      
        var p10={
            height:88,
            run:function(){
                //this
                setInterval((function(){
                    console.log(this.height);   //88
                }).bind(this),100)  
            }
        }
        p10.run();
      ``
  • 相关阅读:
    重量传感器
    cotex_m3内核提供的ITM串口打印调试
    unsigned char 转字符串:
    手工双面打印
    Windows系统(服务器)忘记管理员登录密码:
    标准电流信号为什么是4-20MA?(网络摘录)
    LPC1768之ISP
    万用表的位数
    485收发控制器:
    [LeetCode] 534. Design TinyURL 设计短网址
  • 原文地址:https://www.cnblogs.com/vicky123/p/12911228.html
Copyright © 2020-2023  润新知