讀書筆記--對象、實例、原型、繼承

建立對象的設計模式

  • 工廠模式設計模式

    • 抽象了建立具體對象的過程,用函數封裝以特定接口建立對象的細節數組

    • 解決了建立多個類似對象的問題,沒有解決對象識別的問題app

function createPerson(name,age){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.sayName = function(){
    alert(this.name)
  }

return obj;
}

var person = createPerson('aa',12);
  • 構造函數模式函數

    • 構造函數能夠用來建立特定類型的對象,能夠用instanceof檢測類型測試

    • 使用new操做符會經歷如下四個步驟this

      • 建立一個新對象es5

      • 將構造函數的做用域賦給新對象(所以this指向了這個新對象)spa

      • 執行構造函數中的代碼(爲這個新對象添加屬性)prototype

      • 返回新對象設計

    • 缺點:

      • 每一個方法都要在每一個實例上從新建立一遍

      • 每一個方法(函數)就是一個對象,不一樣實例上的同名函數是不相等的

  • 將函數抽離到外部使多個對象共共享全局做用域的函數雖然能夠解決上面問題,但讓this.sayName = sayName全局方法會破壞了對象的封裝性

function Person(name,age){
  this.name = name;
  this.age = age;
  this.sayName = function(){
    alert(this.name)
  }
}

var person = new Person('aa',12);

person instanceof Person => true

原型模式

  • prototype

    • 只要建立了一個新函數,就會爲函數建立一個prototype屬性,這個屬性指向函數的原型對象

      • 包含了特定類型的全部對象實例共享的方法和屬性

      • prototype是經過調用構造函數而建立的那個對象實例的原型對象

    • 默認狀況下全部原型對象會得到一個constructor(構造函數屬性),這個屬性指向prototype屬性所在函數的指針

    • 當調用構造函數建立一個新實例時,內部將包含一個指針,指向原型對象,鏈接實例與原型對象

function Person(name,age){
 
}
Person.prototype.name = name;
Person.prototype.age = age;
Person.prototype.sayName = function(){
    alert(this.name)
  }
var person = new Person('aa',12);

person instanceof Person => true
  • 判斷原型對象

    • Person.prototype.isPropertyOf(person) => true

    • Object.getPropertyOf(person) == Person.prototype => true (es5)

  • 多個對象實例共享原型所保存的屬性和方法的基本原理

    • 每當代碼讀取某個對象的某個屬性時,搜索從對象實例自己開始,若是在實例中找到具備給定名字的屬性則返回該屬性的值

    • 若是沒有找到則繼續搜索指針指向的原型對象,若是在原型對象中找到給定名字的屬性,則返回這個值

  • 雖然能夠經過對象實例訪問保存在原型實例中的值,可是不能經過對象實例重寫原型中的值,若是在實例中添加一個與原型實例中的一個屬性同名,訪問時實例屬性會屏蔽原型中這個屬性的值,只能訪問到這個實例上屬性的值。能夠經過delete操做符刪除實例屬性,從新訪問原型中的屬性

  • 使用hasOwnProperty()能夠檢測一個屬性是存在實例中仍是原型中,只有存在實例中才會返回true;person.hasOwnProperty('name')

  • 'name' in person 不管name是存在實例中仍是原型對象中都會返回true

//判斷對象原型上是否有這個屬性
  function hasPrototypeProperty(obj, attr){
    return (attr in obj) && !obj.hasOwnProperty(attr);
  }
  • 在使用for-in 循環時,返回的是可以經過對象訪問的,可枚舉的屬性,包括了實例上的屬性和原型上的屬性。原型上不可枚舉的屬性,可是在實例中定義了也能夠獲取獲得,如在實例中定義toString

  • Object.keys() (es5)

    • 取得對象上全部可枚舉的實例屬性,返回一個包含全部可枚舉的字符串數組

    • 也能夠傳入一個原型對象,Object.keys(Person.prototype),但不會沿着原型鏈往上尋找,只返回當前prototype下的屬性

  • Object.getOwnPropertyNames()(ie9+)

    • 枚舉全部實例屬性,不論是否可枚舉

原型的動態性

  • 在原型中查找值的過程是一次搜索,對原型 對象所作的任何修改都能當即從實例上反映出來--即便是先建立了實例後修改原型也照樣如此

var friend = new Person();
  Person.prototype.sayHi = function(){
      
    alert('hi')
  }
  friend.sayHi() //hi
  • 實例和原型之間鬆散的鏈接關係,能夠隨時爲原型添加屬性和方法,而且修改可以當即在全部對象實例中反映出來

  • 可是重寫整個原型對象就不同了,調用構造函數時會爲實例添加一個指向最初原型的[[prototype]]指針,而把原型修改成另一個對象就等於切斷了構造函數與最初原型的聯繫

  • 實例中的指針僅指向原型對象,而不指向構造函數

var friend = new Person();
  Person.prototype = {
    sayHi:function(){
      alert('hi')
    }
  }
  friend.sayHi() //error

Paste_Image.png

  • 原型模式的缺點

    • 省略了爲構造函數傳遞初始化參數,全部實例在默認狀況下都將取得相同的值

  • 當使用原型屬性時會只要在一個實例上修改都會影響到全部的實例,例如在一個實例上修改數組

組合使用構造函數和原型模式

  • 構造函數定義實例屬性,原型模式定義方法和屬性

動態原型模式

  • 經過檢查某個應該存在的方法是否有效,來決定是否須要初始化原型

function Person(name,age){
  this.name = name;
  this.age = age;
  **if( typeof this.sayName != 'funcction'){
     Person.prototype.sayName = function(){
      alert('hi')
    }
  }**
}
  • 只有初次調用構造函數時纔會執行將函數添加到原型中

寄生構造函數模式

  • 相似於工廠模式,封裝建立對象的代碼,而後返回新建立對象,return語句能夠重寫調用構造函數時返回的值

function Person(name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){
      alert('hi');
    }
  return o;
  }
  • 使用場景:特殊狀況下用來爲對象創造構造函數。

  • 假設建立一個具備額外方法的特殊數組,因爲不能直接修改Array構造函數

    function SpecialArray(){
      var arr = new Array();
      arr.push.apply(values,arguments);
      arr.toPipedString = function(){
      return this.join('|');  
    }
    return arr;
  1. colors = new SpecialArray('green','red');
    colors.toPipedString()

  • 缺點:返回的對象和構造函數或者與構造函數的原型屬性之間沒有什麼關係,不能使用instanceof來肯定對象類型

穩妥構造函數模式

  • 特色

    • 新建立對象的實例方法不引用this

    • 不使用new操做符調用構造函數

    • 除了返回對象上的屬性和方法,沒有其餘辦法訪問到構造函數內的數據

    function Person(name,age){
      var o = new Object();
      //這裏能夠定義私有數據
      o.sayName = function(){
        alert(name)
    }
    return o;
  1. person = Person('green',12);
    person.sayName() //只能經過sayName()方法去訪問name的值


繼承

原型鏈繼承

  • js以原型鏈做爲實現繼承的主要方法

  • 基本思想是利用原型鏈讓一個引用類型繼承另外一個引用類型的屬性和方法

  • 構造函數、原型、實例的關係

    • 每一個構造函數都有一個原型對象

    • 原型對象都有一個指向構造函數的方法

    • 實例都包含一個指向原型對象的一個指針

function SuoperType(){
  this.property = true; 
}
SuperType.prototype.getSuperValue = function(){
  return this.property;
}
function SubType(){
  this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubType = function(){
  return this.subprototype;
}
var instance = new SubType()
instance.getSuperValue();//true

833879510766076138.jpg

  • SubType不只具備做爲一個SuperType的實例所擁有的所有屬性和方法,並且擁有一個指向SuperType原型的指針

  • instance指向SubType原型,SubType原型又指向SuperType的原型

  • 肯定實例與原型的關係

    • instanceof操做符 只要用這個操做符來測試實例與原型中出現過的構造函數,結果就會返回true

    • isPropertyOf() 只要是原型鏈中出現的原型,均可以說是該原型鏈所派生的實例的原型,都會返回true

  • 缺點:

    • 原型屬性會被全部實例共享,若是在父構造函數中有一個this.colors=[]的數組,子構造函數繼承後的實例能夠修改這個存在於子構造函數原型對象上的原型屬性

    • 在建立子類型的實例時,不能向超類型的構造函數傳遞參數。其實是沒有辦法在不影響全部實例的狀況下給超類型的構造函數傳遞參數

借用構造函數

  • 在子類型構造函數的內部調用超類型構造函數,經過call,apply 改變對象的this指向

function SuoperType(name){
  this.colors = ['red']; 
  this.name = name;
}

function SubType(name){
  SuperType.call(this,name);
}
SubType.prototype = new SuperType();
var instance = new SubType('1')
instance.colors.push('green') => ['red','green']

var instance2 = new SubType('2')
instance2.colors.push('black') => ['red','black']

組合繼承

  • 將原型鏈和借用構造函數的技術組合到一塊兒

  • 使用原型鏈實現對原型屬性和方法的繼承

  • 使用借用構造函數實現對實例屬性的繼承

function SuperType(name){
  this.colors = ['red']; 
  this.name = name;
}
SuperType.prototype.sayName = function(){
  alert(this.name)
}
function SubType(name,age){
  SuperType.call(this,name);
  this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayName = function(){
  alert(this.name)
}
var instance = new SubType('1',12)
instance.sayName() => 1
instance.colors => ['red']
var instance2 = new SubType('2',21)
instance2.sayName() => 2
instance2.colors => ['red']

原型式繼承

  • 基於一個對象上,這個對象至關於做爲原型,再根據需求對獲得的對象加以修改

  • 在沒有必要興師動衆建立構造函數,而只想讓一個對象與另外一個對象保持相似的狀況下使用

function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
  }
  var person = {
    name:'n',
    friends:['a','b']
  }
  var anotherPerson = object(person);
  anotherPerson.name = 'y';  =>y
  anotherPerson.colors.push('c');  => ['a','b','c']
  var person = object(person);
 person.name = 'yy';  =>yy
person.color; =>['a','b','c']
  • Object.create() es5新增規範原型式繼承

    var person = {
     name:'n',
     friends:['a','b']
    }
    var anotherPerson = Object.create(person);
    anotherPerson.name = 'y';  =>y
    anotherPerson.colors.push('c');  => ['a','b','c']
    var person =  Object.create(person);
    person.name = 'yy';  =>yy
    person.color; =>['a','b','c']

寄生式繼承

  • 建立一個僅用於封裝繼承過程的函數,在函數內部以某種方式加強對象

function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
  }
  function createAnother(original){
    var clone = object(original);
    clone.sayHi = function(){
      alert('hi')
    }
  return clone;
  }
  var person = {
    name:'n',
    friends:['a','b']
  }
  var anotherPerson = createAnother(person);
  anotherPerson.sayHi(); =>hi

寄生組合式繼承

  • 組合繼承最大的問題是不管什麼狀況下都會調用兩次超類型構造函數

    • 一次是建立子類型原型時,一次是子函數的內部構造函數

  • 寄生組合繼承經過借用構造函數來繼承屬性,經過原型鏈混成形式來繼承方法。

  • 思路是沒必要爲了指定的子類型原型而調用超類型的構造函數。

  • 使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型

function inheritPrototype(subType,superType){
      var prototype = object(superType);
      prototype.constructor = subType;
      subType.prototype = prototype;
  }

function SuperType(name){
  this.colors = ['red']; 
  this.name = name;
}
SuperType.prototype.sayName = function(){
  alert(this.name)
}
function SubType(name,age){
  SuperType.call(this,name);
  this.age = age;
}
SubType.prototype =inheritPrototype(subType,superType) ;
SubType.prototype.sayName = function(){
  alert(this.name)
}
相關文章
相關標籤/搜索