JS原型那些事兒

本篇主要是記錄一下對js中對於原型的理解...編程

原型

原型涉及到構造函數, 原型對象, 實例化對象三者之間的關係...數組

  • 構造函數安全

    function Person (name,age) {
           //(1)建立一個空對象: {}
           //(2)將this指向這個空對象 : this = {}
           //(3)執行構造函數賦值代碼(完成對象的賦值)
           this.name = name;
           this.age = age;
           this.sayHi = function () {
               console.log(this.name + 'hello world');
           };
           //(4)返回這個對象
       };
       var man = new Person('huahua',18); 
         
       1.何爲構造函數?
           構造函數:首先,它是函數,而且任何的函數均可以做爲構造函數存在,它的本質是初始化對象。
           構造函數都是和new關鍵詞一塊兒使用的。 new就是在建立對象,從聲明開始一共作了4件事(如上),構造函數就是在爲初始化的對象添加屬性和方法(成員)
    
       2.構造函數的特色:
           a:構造函數的首字母必須大寫,用來區分於普通函數
           b:內部使用的this對象,來指向即將要生成的實例對象
           c:使用New來生成實例對象
  • 實例對象函數

    1.上面的 man 就是經過Person這個構造函數實例化出來一個對象,咱們稱爲 **實例化對象**;何爲對象的實例化呢? 
    
       2.在我看來就是給一個空對象添加了一些屬性和方法,使其具備了一些特徵和行爲...也就是上面new關鍵字乾的事;跟面向對象中的一些概念比較相似...
       面向對象編程: 面向對象就是對現實中的事物進行抽象化...而後再給其設置特徵屬性和行爲使之具體化; 面向對象就是對面向過程進行封裝後的結果...
    
       3.實例對象中存在一個__proto__屬性; 這個屬性指向了構造函數的原型prototype...  
       注意: 實例對象訪問成員的規則:先看本身有沒有這個成員,若是有則訪問,沒有則訪問原型的
  • 原型對象this

    上面已經聊過構造函數和實例化對象了,那麼原型對象又是什麼呢?
           當咱們在聲明一個函數時, 系統會幫咱們建立一個與該函數對應的屬性prototype,咱們稱它爲原型對象;
           以上面的Person爲例,這個prototype是該函數的一個屬性,咱們能夠調用Person.prototype來修改其成員或者進行重寫;
    
           原型對象中有一個構造器指針constructor屬性來指向對應的構造函數,他的做用是可讓實例對象知道本身是哪個構造函數生成的;
           如 man.constructor 即man.__proto__.constructor指向了 Person.
  • 下面用一張圖來表示他們之間的關係...spa

    原型對象中能夠存儲不少成員屬性和方法,多個實例對象之間就能共享這些屬性和方法; 相似實現了面向對象中 繼承 的效果...prototype

    面向對象的三大特性:指針

    封裝:將功能代碼封裝到對象中,只暴露外部接口(API),使用者無需關心內部實現
       繼承:一個對象擁有另外一個對象全部的成員變量(屬性和方法)
       多態: 一個對象在不一樣狀況下的多種狀態; 一個對象通過不一樣操做後會有不一樣的行爲....
            (js從語法的角度上來講沒有多態,由於js是基於對象的語言)
    
       js實現繼承的方式:
           1. 咱們能夠遍歷父對象,將父對象的屬性動態添加到子對象中 (適用於一個子對象的繼承)
               for (var key in father){
                   son[key]  = father[key];
                };
           2. 替換原型:將父對象做爲子對象構造函數的原型(可是會丟失以前的原型對象的成員)
                // 子對象用構造函數來實例化
                function Son(name, age) {
                    this.name = name;
                    this,age = age;
                }
                Son.prototype.father = {
                    parent: 'laosong',
                    age: 47,
                } // Son 原型對象中的成員
                var son = new Son('xiaowang', 24)
                var father = {
                    name: 'laowang',
                    age: 48,
                }
                Son.prototype = father; // 至關於Son的原型被從新賦值,替換了,laosong不在了
    
           3. 綜合上面兩種狀況: 將父對象的成員動態添加到子對象的原型中, 這樣就不會丟失了
               for (var key in father){
                   Son.prototype[key]  = father[key];
                };
    
               /**混合式繼承封裝
               @param method:子對象的構造函數
                @param father:要繼承的父對象
                */
               function extendMehtd ( method,father ) {
                   for (var key in father){
                       method.prototype[key] = father[key];
                   }
               };
    
           4. 構造函數實現繼承
               // 經過更改this的指向來實現
               function Person(name, age) {
                   this.name = name || 'hello';
                   this.age = age || 200;
               };
               function Stu(sex, name, age) {
                   this.sex = sex;
                   // 調用Person構造函數,修改Person中的this指向爲當前Student這個構造函數中new建立的對象
                   // 繼承Person中默認的初始化屬性
                   Person.call(this, name, age);
               };
    
               var s = new Stu('male');
               console.log(s); // age: 200,name: "hello",sex: "male"

原型鏈

js中, 每個實例對象都存在一個__proto__屬性指向了本身的原型prototype; 可是原型自己也是一個對象,
也有本身的__proto__屬性,指向本身的原型,以此類推就造成一個鏈式結構,稱之爲原型鏈...

對象訪問原型鏈中成員規則:就近原則
先看對象本身有沒有,有則訪問,沒有則看原型有沒有,有則訪問,沒有則看原型的原型有沒有,以此類推...直到原型鏈的終點(null);
若是尚未 : 若是是訪問屬性:則返回undefined 若是訪問的是方法:則會報錯 xxxx is not a functioncode

  • 圖解原型鏈

    以數組對象爲例:對象

  • DOM中的原型鏈

圖解完整原型鏈

JS中原型鏈完整圖

以上即是JS中完整的原型鏈圖解了...

關於對象的一些知識點補充

1.靜態成員和實例成員
    靜態成員: 函數對象持有的成員(屬性,方法)
    實例成員: 構造函數實例化出來的對象持有的成員

2.instanceof 關鍵字
    語法: 對象 instanceof 構造函數
    做用: 用來檢測右邊函數的原型 是否 在左邊對象的原型鏈中(true/false)
    如: Object instanceof Object // true

3.Object.prototype(對象原型)
    --全部對象的原型鏈中都會指向它;因此全部的對象均可以訪問Object.prototype原型中的成員

    經常使用幾個成員:
        1.hasOwnProperty(): 檢查對象是否包含某個成員;
            條件: 本身的成員
        2.isPrototypeOf(): 檢查(左邊)一個對象是否是(右邊)另外一個對象的原型

        3.propertyIsEnumerable(): 檢查對象是否能夠枚舉某個屬性
            條件: (1)是本身的成員  (2)能夠被for-in循環遍歷 (本身的和原型的)    

4.Function.prototype(函數對象Function的原型)
    --全部的函數對象原型都會指向Function構造函數的原型,全部的函數對象均可以訪問Function.prototype中的成員

    經常使用的一些成員:
        1. name:獲取函數名 (比較雞肋)

        2.caller:獲取調用本函數的引用;經過console.log(fn.caller)能夠知道本身在哪一個地方被人調用(全局調用函數,這裏的caller指向null)

        3.length:獲取函數形參的數量; fun.length 能夠知道函數設置的形參個數

        4.arguments:獲取函數全部的實參; 
            能夠理解爲函數內部一個隱藏的形參,做用是獲取函數全部的實參,與形參一一對應...

            arguments對象的兩個經常使用屬性:
                1.callee:指向函數自身, 應用於匿名函數的遞歸調用...
                  arguments.callee === fn //true
                
                2. length: 實參的個數
                  arguments是一個僞數組...

        5.給內置的構造函數原型添加自定義成員
            當內置構造函數自帶的方法不夠用,沒法實現需求時,咱們就須要給添加自定義方法;直接添加可能會出現多我的員操做出現相同的方法名,致使被覆蓋掉了
            因此須要採用安全的方法添加來避免覆蓋...

            使用替換原型繼承(自定義構造函數,將原型指向內置對象)
                // 經過構造函數的方式來添加;
                function NewArr(name) {
                    this.name = name;
                };
                
                NewArr.prototype = []; // 修改成一個空數組對象;此時NewArr的原型擁有數組對象全部的方法
                NewArr.prototype.hello = {
                    name: 'hello world',
                };
                NewArr.prototype.min = function () {
                    var min = Infinity;
                    for(var i=0; i< this.length; i++) {
                        if (this[i] < min) {
                            min = this[i];
                        }
                    };
                    return min;
                };

                // 建立一個新對象
                var arr1 = new NewArr('huhua');
                var arr3 = new NewArr();
                arr3.push(1,2,3,-1);

                console.log(arr3.min());
                console.log(arr1);
                console.log(arr1.__proto__);
                console.log(NewArr);
                console.log(NewArr.prototype);
                console.log(NewArr.__proto__.constructor);

                var arr2 = [12,241,21];
                console.log(arr2.min());  // 不能訪問

                Array.prototype:對象類型賦值的時候拷貝的是地址,修改了NewArr的原型以後,Array.prototype也會修改
                []:  因爲空數組的原型會指向Array.prototype,根據原型鏈中成員訪問規則,NewArr實例對象能夠訪問數組成員的成員
                      而且,修改MyArr的原型對象,本質上是修改這個空數組,不會對Array.protpotype形成影響
相關文章
相關標籤/搜索