JS面向對象之原型

面向對象之原型

爲何要使用原型

因爲 js 是解釋執行的語言, 那麼在代碼中出現的函數與對象, 若是重複執行, 那麼會建立多個副本, 消耗更多的內存, 從而下降性能java

傳統構造函數的問題

function Foo( ... ){
        this.name = name;
        this.age = age;
        this.sayHello = function(){
            ...
        }
    }
  1. 因爲對象是由 new Foo() 建立出來的, 所以每個對象在建立的時候, 函數 sayHello 都會被建立一次
  2. 每個對象都含有一個對立的, 不容的, 可是功能邏輯同樣的函數, 應該將單獨的方法抽取出來, 讓全部的對象共享該方法
  3. 使用傳統方法的定義方式會影響性能, 在代碼中建立方法就是消耗性能, 好比說內存
  4. 能夠考慮將方法所有放到外面, 可是可能存在安全隱患
    • 在開發中會引入各類框架和庫, 自定義的成員越多, 出現命名衝突的概率越大
    • 在開發中可能會存在多個構造函數, 每個構造函數應該會有多種方法, 方法越多就越不容易維護
function sayHello(){}
    function Foo(){
        this.sayHello = sayHello;
    }
  1. 每個函數在建立的時候, 都會建立一個原型對象
  2. 任意一個對象都會默認連接到他的原型
    • 只要建立一個函數, 就會附帶建立一個特殊的對象, 這個對象使用函數.prototype引用, 這個特殊的對象是這個函數的原型屬性
    • 每個由這個函數做爲構造函數建立的對象, 都會默認連接到這個對象上( proto )
    • 在該對象訪問某一個成員( 方法或屬性 ), 若是該對象中沒有, 那麼就會到這個原型對象中去查找
var f1 = new Foo();
    var f2 = new Foo();
    f1.sayHello();    // 若是 f1 沒有 sayHello, 那麼就會在 Foo.prototype 中去找
    f2.sayHello();    // 若是 f2 沒有改方法, 那麼就會在 Foo.prototype 中去找
  1. 由構造函數建立出來的多個對象會共享一個原型對象, 就是 構造函數.prototype
  2. 合理解決辦法: 將共享的, 重複使用會多消耗性能的東西放到 構造函數.prototype 中, 那麼全部由這個構造函數建立的對象就能夠共享這些成員
function Foo() {}
    Foo.prototype.sayHello = function () {
        console.log( ... );
    };
    var f1 = new Foo();
    f1.sayHello();
    var f2 = new Foo();
    f2.sayHello();

    f1.sayHello === f2.sayHello

原型使用注意點

  1. 寫 構造函數.prototype 的時候, 最好不要將屬性也加到裏面.
function Person() {}
    Person.prototype.name = '張三';
    var p = new Person();
  1. 賦值的錯誤
    • 若是是訪問數據, 當前對象中若是沒有該數據就到構造函數的原型屬性中去找
    • 若是是寫數據, 當對象中有該數據的時候, 就是修改值; 若是對象沒有該數據, 那麼就添加值
    function Person() {}
     Person.prototype.name = '張三';
     var p1 = new Person();
     var p2 = new Person();
     p1.name = '李四';
    
     console.log( p1.name );
     console.log( p2.name );

    原型相關概念

面向對象的相關概念

類 class : 在 js 中就是構造函數

* 在傳統的面嚮對象語言中( c, java ), 使用一個叫作類的東西定義模板, 而後使用模板建立對象
* 在構造方法中也具備相似功能, 叫作類

實例( instance ) 與對象( Object )

* 實例通常是指摸一個構造函數建立出來的對象, 叫作 XXX 構造函數的實例對象
* 實例就是對象, 對象是一個泛稱
* 實例與對象是一個近義詞

鍵值對與屬性和方法

* 在 js 中, 鍵值對的集合被叫作對象
* 若是值爲數據( 非函數 ), 就稱該鍵值對爲屬性 property
* 若是值爲函數( 方法 ), 就稱該鍵值對爲方法 method

父類( 基類 )和子類( 派生類 )

* 傳統的面向對象的語言中使用類來實現繼承, 那麼就有父類, 子類
* 父類又稱爲基類, 子類有稱爲派生類
* 在 js 中, 經常被稱爲扶對象, 子對象, 基對象, 派生對象

原型的相關概念

原型對象相對於構造函數被稱爲原型屬性

* 原型對象就是構造函數的**原型屬性**
* 簡稱原型

原型對象與構造函數所建立的實例對象也有聯繫

* 原型對象相對於構造函數建立的實例對象稱爲**原型對象**
* 簡稱原型

對象繼承自原型

* 構造函數建立的實例對象 繼承自 構造函數的原型屬性( 構造函數.prototype )
* 構造函數建立的實例對象 繼承自 該對象的原型對象
* 構造函數建立的實例對象與構造函數的原型屬性表示的對象是兩個不一樣的對象
    * 原型中的成員, 能夠直接被實例對象調用
    * 實例對象 "*含有*" 原型中的成員
    * 實例對象 繼承自 原型
    * 這樣的繼承就是 "原型繼承"

構造函數是什麼

* 凡是對象都有構造函數
    * {}  => Object
    * []  => Array
    * /./ => Regexp
    * function...  => Function

如何使用原型

  1. 使用對象的動態特性
    • 構造函數.prototype.XXX = vvv;
  2. 直接替換
function Person() {}
    Person.prototype = {
        constructor: Person
    };

    // 拆解
    function Person() {}
    var o = {};
    o.costructor = Person; // 屬性中就存儲着函數的地址

    Person.prototype = o;

    Person = 123;

這兩種使用方法的區別

  1. 原型指向發生了變化
  2. 構造函數所建立的對象所繼承的原型不一樣
  3. 新增的對象默認是沒有 constructor 屬性
    注意: 在使用替換的方式修改原型的時候, 通常都會添加 constructor 屬性

proto

  1. 之前訪問原型, 必須使用構造函數才能實現, 沒法直接使用實例對象來訪問原型
  2. firefox 最先引入了__proto__屬性, 來表示使用實例對象引用原型, 早期是非標準的
  3. 經過__proto__屬性能夠容許使用實例對象直接訪問原型
function Person() {}
    // 原型對象就是 Person.prototype
    // 那麼只有使用 構造函數 才能夠訪問它
    var o = new Person();
    // 之前不能直接使用 o 來訪問神祕對象
    // 如今有了 __proto__ 後
    // o.__proto__ 也能夠直接訪問原型對象( 兩個下劃線 )
    // 那麼 o.__proto__ === Person.prototype
  1. 原型對象中默認都有一個屬性 constructor , 構造器 , 將該原型和該原型的構造函數聯繫起來
  2. __proto__的做用
    • 訪問原型
    • 在開發中除非特殊需求, 不建議使用實例對象去修改原型的成員, 該屬性使用較少
    • 在調試過程當中比較方便, 能夠輕易的訪問原型, 從而查當作員
  3. 間接訪問原型的方法
var o = new Person();
    o.constructor.prototype;
  1. 給實例對象繼承自原型的屬性進行賦值
function Foo() {}
    Foo.prototype.name = 'test';
    var o1 = new Foo();
    var o2 = new Foo();
    o1.name = '張三'; // 不是修改原型中的 name 而是本身增長了一個 name 屬性
    console.log( o1.name + ', ' + o2.name );

繼承

  1. 最簡單的繼承就是 將別的對象的屬性加到這個對象身上, 那麼這個對象就有這個成員了
  2. 利用原型也能夠實現繼承, 不須要在這個對象身上添加任何成員, 只要原型有, 實例對象也有
  3. 因此將屬性和方法等成員利用 混入 的辦法, 加到構造函數的原型上, 那麼構造函數的實例就都具備這些屬性和方法了

靜態成員和實例成員

  1. 靜態成員表示的是 靜態方法 和 靜態屬性, 靜態就是由 構造函數提供的
  2. 實例成員表示的是 實例方法 和 實例屬性, 實例就是由 構造函數建立的實例對象
  3. 通常狀況下, 工具方法都是由 靜態成員 提供, 與實例對象有關的方法由 實例成員 表示

構造函數 與 原型 與 構造函數建立的實例對象 的關係

function Person(name){
           this.name = name;
   }
    var P1 = new Person('Jim');

屬性搜索原則( 就近原則 )

  1. 所謂的屬性搜索原則, 就是對象在訪問屬性與方法的時候, 首先在當前對象中查找
  2. 若是當前對象中存儲在屬性或方法, 中止查找, 直接使用該屬性與方法
  3. 若是對象沒有改爲員, 那麼再其原型對象中查找
  4. 若是原型對象含有該成員, 那麼中止查找, 直接使用
  5. 若是原型尚未, 就到原型的原型中查找
  6. 如此往復, 直到直到 Object.prototype 尚未, 那麼就返回 undefied.
  7. 若是是調用方法就包錯, 該 xxxx 不是一個函數
相關文章
相關標籤/搜索