• 从零开始学习前端JAVASCRIPT — 14、闭包与继承


    一、闭包

    1 . 概念:闭包就是能够读取其他函数内部变量的函数。在JS中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解为”定义在一个函数内部的函数”。

    2 . 闭包的特点

    1)可以读取函数内部的变量。

    2)让这些变量的值始终保存在内存中。

    3 . 闭包的原理

    理解闭包,首先必须理解JS变量的作用域。变量的作用域无非就是两种(es5):全局变量和局部变量。

    JS语言的特殊之处,就在于函数内部可以直接读取全局变量。另一方面,函数外部自然无法读取函数内的局部变量。

    注意:

    1)函数内部声明变量的时候,一定要使用var声明。如果不用的话,你实际上声明了一个全局变量。

    2)局部变量的作用域,在函数定义的时候就已经确定下来了。

    出于各种原因,我们有时候需要得到函数内部的局部变量。但是正常情况下这是办不到的。只有变通一下才能实现,那就是在函数内部再定义一个函数。外部变量不能访问内部变量,内部变量却能访问外部变量,这正是因为JS特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以父对象的所有变量,对子对象都是可见的,反之则不成立。我们只需要把子函数返回出来,我们就可以在外部读取内部变量了。

    4 . 闭包的应用场景

    1)函数作为返回值。 

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>闭包</title>
    </head>
    <body>
        <script>
            function f1() {
                var n = 999;
                nAdd = function () {
                    n += 1;
                }
                function f2() {
                    console.log(n)
                }
                return f2;
            }
            var result = f1();
            console.log("result的第一次执行")
            result();//999
            console.log("nAdd的执行")
            nAdd();//无输出
            console.log("result的第二次执行")
            result();//1000
        </script>
    </body>
    </html>

       2)函数作为参数被传递。 

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>闭包</title>
    </head>
    <body>
        <script>
            function fun(n, o) {
                console.log(o);
                return {
                    fun: function (m) {
                        return fun(m, n);
                    }
                };
            }
            var a = fun(0); //undefined
            // 执行完并未销毁保存在内存中
            a.fun(1); //0
            a.fun(2); //0
            a.fun(3); //0
    
            fun(0).fun(1).fun(2).fun(3);
            //undefined、0、1、2
            var a = fun(0).fun(1); 
            //undefined、0
            a.fun(2); 
            //undefined、1
            a.fun(3);        
            //undefined、1
        </script>        
    </body>
    </html>

     5 . 使用闭包注意点

    1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包。否则会造成网页性能问题,在IE中可能导致内存泄漏。解决方法就是在函数退出之前,将不使用的局部变量删除(值置为null,垃圾回收机制就会处理)

    2)闭包会在父函数外部,改变父函数内部变量的值。所以不要随便改变父函数内部变量的值。

    6 . demo通过js闭包实现鼠标滑过隔行换色的效果 

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>闭包实现各行换色</title>
        <style type="text/css">
            *{
                margin: 0;
                padding: 0;
            }
            h3{
                text-align: center;
                font-size: 24px;
                line-height: 60px;
            }
            .newList{
                 80%;
                margin: 0 auto;
                list-style: none;
            }
            .newList li{
                text-indent: 24px;
                line-height: 50px;
                font-size: 16px;
                border-top: 1px dashed #eeeeee;
            }
        </style>
    </head>
    <body>
        <h3>新闻列表</h3>
        <ul id="newList" class="newList">
            <li>这是第1条新闻</li>
            <li>这是第2条新闻</li>
            <li>这是第3条新闻</li>
            <li>这是第4条新闻</li>
            <li>这是第5条新闻</li>
            <li>这是第6条新闻</li>
            <li>这是第7条新闻</li>
            <li>这是第8条新闻</li>
            <li>这是第9条新闻</li>
        </ul>
    </body>
    <script type="text/javascript">
        var oNewList=document.getElementById('newList');
        var oNewListArr=Array.from(oNewList.children);
        oNewListArr.forEach(function (v,i) {
            // 隔行换色
            if(i % 2 === 0) {
                oNewListArr[i].style.background = '#f3f3f3';
            };
            //鼠标滑过改变背景色
            v.onmouseover=function () {
                this.style.background="#87ceeb";
            };
            //鼠标滑过恢复原背景色
            (function (m){
                oNewListArr[m].onmouseout=function () {
                    if(m % 2 === 0) {
                        oNewListArr[i].style.background = '#f3f3f3';
                    }
                    else{
                        oNewListArr[i].style.background = '#ffffff';
                    }
                }
            })(i);
        })
    </script>
    </html>

     二、构造函数的继承

     所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new 运算符,就能生成实例,并且this变量会绑定在实例对象上。

     详解new的执行过程:

    1. 在内存生成一个实例对象obj。
    2. 指定实例对象的__proto__到构造函数的prototype。
    3. 运行构造函数,相当于运行fn.call(obj)。
    4. 检查返回值,如果返回值为基本数据类型,则无视该返回值,而将生成的对象返回。如果为引用类型,则将该返回值返回。

    构造函数很好用,但是存在浪费内存的问题。

    JS规定,每一个构造函数都有一个prototype属性,指向另一个对象。此对象的所有属性和方法,都会被构造函数的实例继承。 

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>构造函数</title>
    </head>
    <body>    
    </body>
    <script type="text/javascript">
        //构造函数的命名首字母一般大写
        function Car(carlogo) {
            this.carLogo = carlogo;
            this.whistle = function () {
                console.log("正在鸣笛......")
            }
        }
        Car.prototype.run = function() {
            console.log("正在行走......")
        };
        var bmw=new Car("BMW");
        var audi=new Car("Audi");
        console.log("构造函数创建的实例:")
        console.log(bmw === audi)
        console.log(bmw,audi)
        console.log("构造函数whisle属性:")
        console.log(bmw.whisle === audi.whisle)
        console.log("原型run方法:");
        console.log(bmw.run === audi.run);
    
    </script>
    </html>

    •  instanceof运算符:判断对象是不是构造函数的实例,是则true,否则false。
        console.log(bmw instanceof Car)//true
        console.log(bmw instanceof Array)//false
    • isPrototypeOf:判断prototype对象和某个实例之间的关系。
        console.log(Car.prototype.isPrototypeOf(bmw));//true
        console.log(Array.prototype.isPrototypeOf(bmw));//false
    • hasOwnProperty:每个实例对象都有一个hasOwnProperty方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
        bmw.hasOwnProperty('carlogo');//true
        bmw.hasOwnProperty('run');//false
    •  in运算符:in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
    console.log('run' in bmw);//true

     注:

    详解instanceof 运算符。         // 会沿着原型链查找。

    详解hasOwnProperty方法。 // 不会沿着原型链查找。

    详解isPrototypeOf方法。   // 会沿着原型链查找。

    详解in运算符。                        // 会沿着原型链查找。 


    三、call/apply继承

    call和apply都是为了改变某个函数运行时的context即上下文而存在的,即改变函数内部this的指向。

    Fn.call(obj, arg1, arg2 [, argN]);

    fn,.apply(obj, [arg1, arg2,…, argN]);

    作用相同,apply以数组的形式传参,call是以列表的形式。 

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>构造函数</title>
    </head>
    <body>    
    </body>
    <script type="text/javascript">
    //构造函数
    function Person(name) {
        this.name = name;
    }
    
    var xiaoJun = {
        name: '小军'
    };
    
    var xiaoHua = {
        name: '小花',
        sing: function (where, music) {
            console.log(this.name + '正在' + where + '' + music);
        }
    };
    
    xiaoHua.sing('马路上', '葫芦娃');
    //列表形式传参
    xiaoHua.sing.call(xiaoJun, '音乐厅', '屋顶');
    //数组方式传参
    xiaoHua.sing.apply(xiaoJun, ['KTV', '我们不一样']);
    //列表形式传参,且与call、apply的执行方式不同,需要调用
    var func = xiaoHua.sing.bind(xiaoJun);
    func('教室', '荡起双桨');
    
    </script>
    </html>

    apply和call实现继承:

    将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>继承</title>
    </head>
    <body>    
    </body>
    <script type="text/javascript">
        function Animal () {
            this.eyes = 2;
        }
        function Dog(name) {
            Animal.call(this);
            this.name = name;
        }
        var oWangCai = new Dog('旺财');
        console.log(oWangCai);
    
    </script>
    </html>

    四、prototype的概念

    1. 一切引用类型都是对象。
    2. 对象是属性的集合。
    3. 对象都是通过构造函数创建的。
    4. 每个函数都有一个prototype属性,即原型。对象都有__proto__属性,可以成为隐式原型。这个__proto__属性是一个隐藏的属性,JS并不希望开发者能够用到这个属性,有的低版本浏览器甚至不支持这个属性值。
    5. 每个对象的__proto__属性指向创建该对象的函数的prototype。
    6. Object.prototype.__proto__指向null。

    原型链:

    访问一个对象的属性时,先在本地属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。

     

    constructor属性:函数的prototype有一个constructor属性,该属性指向了函数本身。对象没有constructor属性,它沿着原型链使用的是对象的构造函数的constructor属性。

    五、this的使用情况

    1:构造函数(在new的情况下)

         this指向的是新构建出来的对象。

    2:函数作为对象的一个属性

         函数中的this指向的是该对象。

    3:函数call或者apply

         当函数被call或者apply调用时,this的值就取传入对象的值。

    4:全局函数 & 普通函数

         全局函数或者普通函数中,this指向的是window。

    5:在prototype原型对象中,this指向的是调用构造函数实例出来的对象。

    注:this关键字的值在函数运行的时候才会被指定。

    六、原型链的继承:

    将一个构造函数的原型指向另一个构造函数的实例对象来实现继承。 

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>原型链继承</title>
    </head>
    <body>    
    </body>
    <script type="text/javascript">
    function Person() {
        this.age = 0;
    }
    
    function Man() {
        this.beard = '胡子';
    }
    
    var person = new Person();
    //改变man的原型指向
    Man.prototype = person;
    var man1  = new Man();
    console.log(man1.beard);
    console.log(man1.age);
    
    </script>
    </html>

    原型链的继承必须将Man的prototype.constructor指向更改过来,否则它将会指向People,发生原型混乱。也就是下一点讲述的混合继承


    七、混合继承:

    原型链与构造函数的优点,组合而成的一个模式,即原型链继承方法,而在构造函数继承属性

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>混合继承</title>
    </head>
    <body>    
    </body>
    <script type="text/javascript">
    function Person() {
        this.age = 0;
    }
    
    Person.prototype.introduce = function () {
        console.log('我有' + this.beard + ';今年' + this.age + '');
    }
    
    function Man() {
        Person.call(this);
        this.beard = '胡子';
    }
    
    var person = new Person();
    console.log("prototype.constructor指向未改:")
    Man.prototype = person;//改变函数原型指向
    person.age = 1;
    var man1  = new Man();
    var man2 = new Man();
    console.log(man1.age);
    console.log(man2.age);
    
    console.log("prototype.constructor指向改变:")
    Man.prototype.constructor = Man;//改变构造函数指向
    var man3  = new Man();
    var man4 = new Man();
    man3.age = 1;
    console.log(man3);
    console.log(man4);
    man3.introduce();
    man4.introduce();
    
    </script>
    </html>

     

  • 相关阅读:
    MVC中使用EF(2):实现基本的CRUD功能
    ibatis学习之道:ibatis的<[CDATA]>dynamic属性跟#$的应用
    css-选择器
    postman进行http接口测试
    使用HttpClient测试SpringMVC的接口
    http接口测试—自动化测试框架设计
    接口测试自动化框架搭建
    JAVA利用HttpClient进行POST请求(HTTPS)
    java.io.IOException: Attempted read from closed stream. 异常,解决
    java.lang.OutOfMemoryError:GC overhead limit exceeded填坑心得
  • 原文地址:https://www.cnblogs.com/witkeydu/p/8481648.html
Copyright © 2020-2023  润新知