1. 程式人生 > >JavaScript中閉包的使用和各種繼承介紹

JavaScript中閉包的使用和各種繼承介紹

一、什麼是閉包?

    (1)閉包的概念:a、閉包就是函式巢狀時,讓區域性變數變成自由變數的環境,是一種讓區域性變數進化的方式。                      b、定義在一個函式內部的函式。          (2)垃圾回收機制:用過一次的東西,先放在一個記憶體中,不立即刪掉,可以隨時進行還原或再次使用,直到沒有任何作用的時候再清除。       tip: 如家用電器,電腦回收站。   二、閉包的應用場景:
        //1、for迴圈之中:
            // for迴圈之中的i變數會因為for的迴圈次數被覆蓋,所以在for迴圈內部存在函式時,而且這個函式內會呼叫i變數,這種情況下就需要用到閉包。

            for (var i = 0; i < 10; i++) {
                console.log(i);        //可以訪問到每次的i
            }

            // 必須滿足兩個條件:
            //     1.在for迴圈記憶體在函式
            //     2.函式內會呼叫這個變數

            var ali = document.getElementsByTagName("li");
            for(var i=0;i<10;i++){
                ali[i].onclick = function(){
                    console.log(i);        //在函式內部就無法訪問到外部變數
                }
            }
            
            // 如何形成閉包
            var ali = document.getElementsByTagName("li");
            for(var i=0;i<10;i++){
                (function(a){
                    ali[a].onclick = function(){
                        console.log(a);
                    }
                })(i)
            }
        //     一旦內部函式呼叫外部函式的區域性變數,那麼這個時候,這個區域性變數就會變成內部函式的私有變數


        // 2、當需要給setTimeout的回撥函式傳參時:
            setTimeout(function (a){
                console.log(a); //兩秒後,undefined
            }, 2000);
            // 或者
            setTimeout(fn(10), 2000);
            function fn(a){
                console.log(a); //沒有延遲,直接列印10
            }
            
            // 怎麼使用閉包:

            function fn(a){
                return function(){
                    console.log(a); //兩秒後,10
                }
            }
            var f = fn(10);
            setTimeout(f, 2000);

三、閉包的特點:

  (1)閉包是將函式內部和函式外部連線起來的橋樑.

  (2)可以讀取函式內部的變數。

  (3)讓這些變數的值,始終儲存在記憶體中,不會在呼叫結束後被系統回收。

  (4)避免全域性變數名稱空間的汙染。

  (5)記憶體消耗很大,不能濫用。

  (6)閉包會在父函式外部,改變父函式內部變數的值。

四、建構函式繼承:

        // 在建構函式中,同樣屬於兩個新建立的函式,也是不相等的
            function Fn(name){
                this.name = name;
                this.show = function(){
                    alert(this.name);
                }
            }
            var obj1 = new Fn("AAA");
            var obj2 = new Fn("BBB");
            obj1.show()
            obj2.show()
        // 此時,任何一個new出來的例項上都有了show方法,可以視為最基礎的繼承。

五、js中的call和apply繼承:

        function Father(skill){
                this.skill = skill;
                this.show = function(){
                    alert("我會"+this.skill);
                }
            }
            function Son(abc){
                //這裡的this指向函式Son的例項化物件
                //將Father裡面的this改變成指向Son的例項化物件,當相遇將father裡面所有的屬性和方法都複製到了son身上
                //Father.call(this,abc);//繼承結束,call適合固定引數的繼承
                //Father.apply(this,arguments);//繼承結束,apply適合不定引數的繼承
            }
            var f = new Father("絕世木匠”);
            var s = new Son("一般木匠");
            f.show()
            s.show();
            // 優點:
            //     建立子類例項時,可以向父類的構造器傳參;
            // 缺點:
            //     只能繼承構造器中定義的屬性和方法,不能繼承原型上定義的屬性和方法

六、js中prototype的概念:

  (1)prototype是原型物件

  (2)指標  constructor  表示當前函式屬於誰,用來指向當前原型所屬的函式

  (3)原型指標  [[prototype]]  __proto__  相當於一根原型指標,指向當前物件的“父級”

  (4)可以在當前函式的原型身上新增各種屬性和方法,以供使用

  (5)JS中萬物皆物件,所有內容的指標終點都指向Object

七、原型鏈繼承:

        // 方法一:
            Son.prototype = new Father();
            //建立一個函式Father,用來做原始物件
            function Father(){
                this.skill = "鐵匠"
            };
            Father.prototype.show = function(){
                console.log(this.skill)
            }
            //建立一個函式Son,準備繼承Father的原型
            function Son(){};
            //將Son點原型,賦值為一個指標,指向Father的原型
            Son.prototype = new Father();

            //此時就可以通過執行new Son()獲取到從建構函式Father上繼承過來屬性和方法
            var s = new Son()
            s.show();        //鐵匠

            // 優點:
            //     1.可以訪問父類原型上的方法和屬性
            //     2.簡單方便,易於實現

            // 缺點:
            //     1.建立子類例項時,無法向父類的構造器傳參

        // 方法二:
            //建立一個函式Father,用來做原始物件
            function Father(){
                this.skill = "鐵匠";
            };
            Father.prototype.show = function(){
                console.log("hello world")
            }
            //建立一個函式Son,準備繼承Father的原型
            function Son(){};
            //將Son的原型,設定為Father的原型
            //Son.prototype = Father.prototype;
            //但是這種拷貝方式為物件的淺拷貝,一旦後期修改Son原型上的方法,會影響到Father的原型
            //需採用物件的深拷貝方法
            for(var i in Father.prototype){
                Son.prototype[i] = Father.prototype[i];
            }
            Son.prototype.show = function(){
                console.log("這是子類修改之後的show方法")
            }
            var f = new Father()
            f.show()
            var s = new Son()
            s.show();
            
            // 優點:
            //     完全將父類原型上的方法和屬性拷貝到子類原型

            // 缺點:
            //     只能繼承原型上的方法和屬性,建構函式無法傳參和繼承

八、混合繼承:

        // 特點:
        //         使用call或apply繼承父類的構造器中的內容,使用原型繼承,繼承父類的原型
            function Father(skill,id){
                this.skill = skill;
                this.id = id;
            }
            Father.prototype.show = function(){
                alert("我是father,這是我的技能"+this.skill);
            }

            function Son(){
                Father.apply(this,arguments);
            }
            //如果不做Son的原型繼承Father的原型,此時會報錯:son.show is not a function
            for(var i in Father.prototype){
                Son.prototype[i] = Father.prototype[i];
            }
            //因為,如果不讓Son的原型等於Father的原型,Son使用apply是繼承不到原型上的方法
            Son.prototype.show = function(){
                alert("我是son,這是我的技能"+this.skill);
            }
            var f = new Father("專家級鐵匠","father");
            var s = new Son("熟練級鐵匠","son");
            f.show();
            s.show();

九、ES6中class的繼承:

        class Father{
                constructor(){
                }
                show(){
                }
            }

        class Son extends Father{
            constructor(){
                super()
            }
            show(){
            }
        }

十、原型物件相關概念解析:

1、物件中的__proto__是什麼:   js中萬物皆物件,每個資料都會有一個__proto__的屬性,這個屬性叫隱式原型,一個物件(obj)的隱式原型(__proto__)指向構造該物件(obj)的建構函式(object())的原型屬性(object.prototype),這樣做的原因是為了能夠保證例項(obj)能夠訪問到在建構函式(object())的原型屬性(object.prototype)中定義的屬性和方法。   2、函式中的prototype是什麼:   (1)函式(Function)是一個特殊的物件,除了和其他物件一樣有上述__proto__屬性之外,還有自己特有的屬性——原型(prototype),這個屬性被描述成指標,他指向一個物件型別的資料,這個物件的用途就是包含所有將來使用該函式構造出來的可被共享的屬性和方法(我們把這個物件叫做原型物件)。   (2)原型物件內也有一個屬性,叫做constructor,這個屬性包含了一個指標,指回原函式(類似於arguments.callee。但是arguments只能在函式內部獲得,而函式原型物件內的constructor屬性,可以在任何能訪問到這個函式的位置使用)。   3、建構函式,原型,例項之間的關係:   (1)建構函式Fn身上有屬性prototype為原型物件,原型物件內有constructor屬性指向當前prototype所在的建構函式Fn。   (2)在new執行建構函式Fn時,創造了一個例項物件f,例項物件f的__proto__指向建構函式Fn的原型prototype。   (3)因為例項物件f的__proto__指向建構函式Fn的原型prototype,所以例項物件f可以間接訪問到Fn原型prototype的方法。                                         4、檢視例項物件f是否有指標指向建構函式Fn的原型:   (1)isPrototypeOf()用於檢測兩個物件之間似乎否存在這種關係,使用方法如下:     Fn.prototype.isPrototypeOf(f)    // 檢視 Fn 的 prototype 物件,是否是 f 原型   (2)類似的還有instanceof運算子,使用方法如下:     console.log(f instanceof Fn)     // 檢視 f 物件是否是建構函式 Fn 的例項     console.log(f instanceof Object)     tip:兩種使用,如果是返回ture,如果不是返回false。     tip:instanceof運算子右側為建構函式,並且js中所有原型都來自Object建構函式。 5、javascript中解析器訪問屬性順序:   (1)當訪問例項 f 的屬性或方法時,會先在當前例項物件 f 中查詢,如果沒有,則沿著__proto__繼續向上尋找,如果找到最頂頭的Object還是找不到,則會丟擲undefined。如果在例項中找到,或某層原型中找到,就會讀取當前值並執行,同時停止向上找尋。   (2)由此可見,解析器的解析順序遵循就近原則,如果在最近的位置發現屬性存在,便不會繼續向上找尋。