• JS高级(三)--原型链、闭包、作用域、函数的四种调用方式


    一、原型链(家族族谱)


    • 概念:JS里面的对象可能会有父对象,父对象还会有父对象,。。。。。祖先

    • 根本:继承

    属性:对象中几乎都会有一个__proto__属性,指向他的父对象

    意义:可以实现让该对象访问到父对象中相关属性

    • 根对象:Object.prototype

    var arr=[1,3,5]

    arr.__proto__:Array.prototype

    arr.__proto__.__proto__就是找到了根对象

    function Animal(){}
    var cat=new Animal();
    //cat.__proto__:Animal.prototype
    //cat.__proto__.__proto__:根对象
    • 错误的理解:在js中,万物继承自Object? -->在js中万物继承自Object.prototype

    二、作用域


    (一)、变量作用域 

    • 变量作用域的概念:就是一个变量可以使用的范围

    • 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也可以访问
    }
    console.log(age); //也可以访问
    
    //-->1级作用域
    var gender="男";
    function fn(){
        console.log(age);    //因为age是在fn作用域内声明的
        //age:undefined:既然有值就是可以访问
        console.log(height);//height不是在该作用域内部声明的,所以不能访问
        //-->2级作用域
        return function(){
            //-->3级作用域
            var height=180;
        }
        var age=5;
    }
    
    //注意:变量的声明和赋值是在两个不同时期的
    function fn(){
        console.log(age);   //undeinfed
        var age=18;
        console.log(age);   //18
    }
    
    //fn函数执行的时候,首先找到函数内部所有的变量、函数声明,把他们放在作用域中,给变量一个初始值:undefined    -->变量可以访问
    //逐条执行代码,在执行代码的过程中,如果有赋值语句,对变量进行赋值
    
    function fn(){
        var age;        //初始值:undefined
        console.log(age);   //undeinfed
        age=18;     //修改了变量的值
        console.log(age);   //18
    }

    (二)、作用域链

    • 由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?这个问题就需要好好地探究一下了,我们把这个变量的查找过程称之为变量的作用域链

    • 作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)

    • 简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)

    1. 查看当前作用域,如果当前作用域声明了这个变量,就确定结果

    2. 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明

    3. 再查找上级函数的上级函数,直到全局作用域为止

    4. 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)

    function fn(callback){
        var age=18;
        callback()
    }
    
    fn(function(){
        console.log(age);
        //分析:age变量:
        //1、查找当前作用域:并没有
        //2、查找上一级作用域:全局作用域
        //-->难点:看上一级作用域,不是看函数在哪里调用,而是看函数在哪里编写
        //-->因为这种特别,我们通常会把作用域说成是:词法作用域
    })
    

    举例1:

        var name="张三";
        function f1(){
            var name="abc";
            console.log(name);  //abc
        }
        f1();
    

    举例2:

        var name="张三";
        function f1(){
            console.log(name);  //underfind
            var name="abc";
        }
        f1();

    举例3:

        var name="张三";
        function f1(){
            return function(){
                console.log(name); //underfind
            }
            var name="abc";
        }
        var fn=f1();
        fn();
    

    举例4:

        var name="张三";
        function f1(){
            return {
                say:function(){
                    console.log(name);  //underfind
                    var name="abc";
                }
            }
        }
        var fn=f1();
        fn.say()

    三、闭包 


    (一)、认识闭包

        var divs=document.getElementsByTagName("div");
        for (var i = 0; i < divs.length; i++) {
            const element = divs[i];
            element.onclick=function(){
                //i是来自于全局作用域
                alert(i)
            }
        }
        // 执行完for循环之后,i的值已经变成了5

    闭包的解决

        var divs=document.getElementsByTagName("div"); 
        for (var i = 0; i < divs.length; i++) {
            const element = divs[i];
            //闭包的解决方案
            element.onclick=(function(j){
                return function(){
                    //i是来自于全局作用域
                    alert(j)
                }
            })(i);
        }   

    例一:

        function fn(){
            var a=5;
            return function(){
                a++;
                console.log(a);     //a变量肯定是可以访问的
            }
        }
        var f1=fn();        //f1指向匿名函数
        f1();       //6
        f1();       //7
        f1();       //8
        //代码执行到8行fn函数执行完毕,返回匿名函数
        //      -->一般认为函数执行完毕,变量就会释放,但是此时由于js引擎发现匿名函数要使用a变量,所以a变量并不能得到释放,而是把a变量放在匿名函数可以访问到的地方去了
        //      -->a变量存在于f1函数可以访问到的地方,当然此时a变量只能被f1函数访问  

    例二:

        function fn(){
            var a=5;
            return function(){
                a++;
                console.log(a);     //a变量肯定是可以访问的
            }
        }
        var f1=fn();        //f1指向匿名函数
        f1();       //6
        f1();       //7
        f1();       //8
        //把a变量的值放在f1函数可以访问到的地方
    
        var f2=fn();
        f2();       //6
        f2();       //7
        f2();       //8
        //又一次执行了fn,又初始化了一个新的a变量,值为5;返回匿名函数f2,并且把新的a变量放在了f2可以访问到的地方
    
        var f3=fn();
        f3();       //6
        //又一次执行了fn,又初始化了一个新的a变量,值为5;返回匿名函数f2,并且把新的a变量放在了f2可以访问到的地方 

    例三:

        function q1(){
            var a={};
            return a;
        }
        var r1=q1();
        var r2=q1();
        console.log(r1==r2);  //fasle
    
    
        function q2(){
            var a={}
            return function(){         
                return a;
            }
        }
        var t3=q2();//创建一个新的a对象,把a对象放在t3可以访问到的位置
        var o5=t3();    //返回值a就是那个a
        
        var w3=q2();//创建了一个新的a对象,把新的a对象放在w3可以访问到的位置
        var o8=w3();//此时获取到的是一个新的a对象
        console.log(o5==o8);    //false 

    (二)、闭包的应用场景

    • 模块化

    • 防止变量被破坏

        //模块化思想:也是一种设计模式
        var ktv=(function KTV(){
            //为了保护leastPrice变量,将它放在函数内部
            var leastPrice=1000;
            var total=0;
            return {
                //购物
                buy:function(price){
                    total+=price;
                },
                //结账
                pay:function(){
                    if(total<leastPrice){
                        console.log('请继续购物');
                    }else{
                        console.log('欢迎下次光临');
                    }
                },
                editLeast:function(id,price){
                    if(id===888){
                        leastPrice=price;
                        console.log("现在最低消费金额为:",leastPrice);
                    }else{
                        console.log('权限不足');
                    }
                }
            }
        })()
    
        //假设:来了朋友要来唱K
        //——>可能老板需要去修改最低消费的金额
        //-->但是并不能让老板直接去修改leastPrice,或者说不能把leastPrice作为全局变量

    (三)、闭包问题的产生原因 

    • 函数执行完毕后,作用域中保留了最新的a变量的值 
        function f1(){
            var a=5;
            return function(){
                a++;
                console.log(a);
            }
        }
        var q1=f1();
        
        //要想释放q1里面保存的a,只能通过释放q1
        q1=null;    //q1=undefined 

    四、函数的四种调用方式


    •  在`ES6之前`,函数内部的this是由该函数的调用方式决定的
    • 在ES6的箭头函数之前的时代,想要判断一个函数内部的this指向谁,就是根据下面的四种方式来决定的
    • 函数内部的this跟大小写、书写位置无关

    (一)、函数调用

    • 函数内部的this指向window 

    例一:

        var age=18;
        var p={
            age:15,
            say:function(){
                console.log(this.age); //18
            }
        }
        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);  //abc
          }
        } 
        //函数的第一种调用方式:函数调用 
        // -->函数内部的this指向window 
        // -->window对象中的方法都是全局函数,window对象中的属性都是全局变量 
        Person("abc");
        var p1=Person.prototype.say
        p1()  

    例三:

        function fn() {
          console.log(this.age)  //undefined
        }
        fn(); 

    (二)、方法调用

    • 函数内部的this指向调用该方法的对象

    例一:

        function Person() {
          this.age = 20;
        }
        Person.prototype.run = function () {
          console.log(this.age);
        }
        var p1 = new Person();
        p1.run();
        //打印结果:20  

     例二:

        var p2={
            height:180,
            travel:function(){
                console.log(this.height);
            }
        }
        p2.travel()     //打印结果:180

    例三:

        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 
    
        var tony={ d:clear,length:30 };
        tony.d();       
        //方法调用的方式,所以clear函数内部的this指向tony的,

    (三)、构造函数的调用方式(new调用)

    • this指向构造函数的实例

    例一:

        function fn(name){
            this.name=name;
        }
        //通过new关键字来调用的,那么这种方式就是构造函数的构造函数的调用方式,那么函数内部的this就是该构造函数的实例
        var _n=new fn("小明");  //_n有个name属性,值为:小明  

    例二:

        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
                //变量声明了未赋值才是undefind、属性不存在也是undefined
            }
        } 

    例三:

        function jQuery(){
            var _init=jQuery.prototype.init;
            //_init就是一个构造函数
            return new _init();
        }
        jQuery.prototype={
            constructor:jQuery,
            length:100,
            init:function(){
                //this指向init构造函数的实例
                //-->1、首先查看本身有没有length属性
                //-->2、如果本身没有该属性,那么去它的原型对象中查找
                //-->3、如果原型对象中没有,那么就去原型对象的原型对象中查找,最终一直找到根对象(Object.prototype)
                //-->4、最终都没有找到的话,我们认为该对象并没有该属性,如果获取该属性的值:undefined
                console.log(this.length);   //100   
            }
        }
        var $init=jQuery.prototype.init;
        //修改了init函数的默认原型,指向新原型
        $init.prototype=jQuery.prototype;
        jQuery();

    (四)、上下文调用方式(call、apply、bind)

    • call方法

    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)

        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方法 

    1、上述call的代码apply完全可以替换

    2、call和apply都可以改变函数内部的this的值

    3、不同的地方:传参的形式不同

        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方法 

    bind是es5中才有的(IE9+)

    例一:

        var obj = {
            age:18,
            run : function(){
                console.log(this);  //this:obj         
                var _that=this;
                setTimeout(function(){
                    //this指向window
                    console.log(this.age); 
                    console.log(_that.age); 
                    //undefined是正确的
                },50);
            }
        } 

    例二:

        var obj5 = {
            age:18,
            run : function(){
                console.log(this);  //this:obj5
                setTimeout((function(){
                    console.log(this.age); 
                }).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();    //"橙汁"
    

      

  • 相关阅读:
    docker部署遇到的问题集合【持续更新】
    docker开发常用命令
    idea使用三步曲
    防缓存穿透设计
    亿级数据库分片分库架构设计亿【转】
    java-web项目换装servlet3.1.0后性能飙升到10000tps
    spring-kafka消费者配置
    分布式disconf+spring5使用遇到重复加载的问题
    jmeter性能压测
    springboot多profile环境maven配置
  • 原文地址:https://www.cnblogs.com/junjingyi/p/9191792.html
Copyright © 2020-2023  润新知