建立對象、原型、原型鏈

主要摘自《Javascript高級程序設計》es6

建立對象的方法

  • 工廠模式
  • 構造函數
  • 原型鏈
  • 組合(構造函數和原型鏈)
  • 寄生構造函數模式
  • 委託構造函數模式

工廠模式

var person = function(name,age){
  var o = new Object();
  o.name = name;
  o.age = age;
  o.sayName = function() {
    console.log(this.name)
  }
  return o;
} 

var person1 = person('li', 20);
var person2 = person('xiao', 18);

person1.sayName();  //li
person2.sayName();  //xiao
複製代碼
  • 先建立一個object
  • object綁定屬性和方法
  • 返回這個object

缺點

工廠模式雖然解決了建立多個類似對象的問題,可是卻沒有解決對象識別的問題,即怎樣知道一個對象的類型。數組

構造函數

var Person = function(name,age) {
  this.name = name;
  this.age = age;
  this.sayName = function() {
    console.log(this.name)
  }
}

var person1 = new Person('li',20)
var person2 = new Person('xiao',18)

person1.sayName()  //li
person2.sayName()  //xiao
複製代碼

構造函數和工廠函數不一樣之處在於:bash

  • 沒有顯示的建立對象
  • 直接將屬性和方法賦給了this對象
  • 沒有return語句
  • 函數名的首字母大寫
  • 要建立構造函數的實例,必須使用new操做符

** 調用構造函數會經歷下面4個步驟(即new幹了什麼)**:app

  • 建立一個新對象
  • 將構造函數的做用域賦給新對象(所以this指向了這個新對象)
  • 執行構造函數中的代碼(爲這個新對象添加屬性)
  • 返回新對象

上面的person1和person2分別保存Person的一個不一樣實例,這兩個對象都有一個constructor(構造函數)屬性,該屬性執行Person函數

person1.constructor == person2.constructor == Personui

能夠用constructor來識別對象類型,可是用instanceof 更可靠this

person1和person2同時也是object的實例,由於全部的對象都繼承自Objectes5

缺點

若是構造函數裏面有方法,那方法都會在每一個實例上都建立一遍,所以不一樣實例上的函數是不相等的spa

person1.sayName == person2.sayName // falseprototype

雖然能夠把方法移到構造函數外部,設置成全局函數,可是若是對象須要定義不少方法,就要定義不少個全局函數,

原型模式

function Person(){};
Person.prototype.name = "li";
Person.prototype.age = 20;
Person.prototype.sayName = function(){
  console.log(this.name)
};
var person1 = new Person()
var person2 = new Person()

person1.sayName()  //li
person2.sayName()  //li

person2.name = 'xiao'  //person2這個實例加了一個name屬性,重寫name屬性,但不會改變原型的值
person1.sayName()  //li
person2.sayName()  //xiao  //若是實例上有name屬性,取的是實例上的
複製代碼

好處

可讓全部對象實例共享它所包含的屬性和方法。

person1.sayName !== person2.sayName //true

原型對象

只要建立了一個函數,就會爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象。在默認狀況下,全部原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針(媽呀,很拗口,看代碼和圖比較直觀)

建立了自定義的構造函數後,其原型對象默認只會取得constructor屬性,至於其餘方法,都是從object繼承而來。當調用構造函數建立一個實例,該實例內部將包含一個指針,指向構造函數的原型對象,能夠用_proto_屬性訪問。這個連接存在與實例與構造函數的原型對象之間,而不存在於實例與構造函數之間。

Person.prototype.constructor = Person

person1._proto_ == Person.prototype

判斷是否是對象的原型

  • isPrototypeOf

Person.prototype.isPrototypeOf(person1) // true

  • Object.getPrototypeOf能夠取到一個實例的原型

Object.getPrototypeOf(person1) == Person.prototype // true Object.getPrototypeOf(person1).name //li

判斷一個屬性是存在實例中仍是原型中

  • hasOwnProperty()只在給定屬性存在實例中時,才返回true
person1.hasOwnProperty('name') // false
person2.hasOwnProperty('name') //true
複製代碼
  • in 無論屬性是存在實例上仍是原型中,都返回true

'name' in Person // true

hasOwnProperty和in一塊兒能夠判斷屬性是否是在原型中

!object.hasOwnProperty(name) && ('name' in object)

對象字面量

咱們將Person.prototype 設置爲等於一個以對象字面量形式建立的新對象,最終結果相同,可是constructor屬性再也不指向Person。這裏的語法,本質上完成重寫了默認的portotype對象,所以它的原型是Object,constructor是指向Object的構造函數。可是用instanceof 操做符還能返回正確結果

function Person(){};
Person.prototype = {
  name: 'li',
  age: 20,
  sayName: function(){
    console.log(this.name)
  }
};
var person1 = new Person()
console.log(Person.prototype.constructor == Person)  //false
console.log(Person.prototype.constructor == Object)  //true
console.log(person1 instanceof Person) // true
複製代碼

原型的動態性

function Person(){};
var person1 = new Person()
Person.prototype = {
  name: 'li',
  age: 20,
  sayName: function(){
    console.log(this.name)
  }
};

person1.sayName()  //person1.sayName is not a function
複製代碼

先建立實例,在重寫其原型對象,會切斷現有原型與以前任何已經存在的對象實例之間的聯繫。person1引用的仍是原來的原型。能夠用下面這種寫法

function Person(){};

var person1 = new Person()

Person.prototype.sayName = function(name){
    console.log(name)
  }


person1.sayName('li')  //li
複製代碼

原型對象的缺點

  • 省略了爲構造函數傳遞初始化參數,全部實例在默認狀況下都取得相同的屬性值。
  • 若是有引用類型的屬性,會致使在一個實例裏修改引用類型的值,會把原型上的也修改了

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

  • 構造函數模式用於定義實例屬性
  • 原型模式用於定義方法和共享的屬性
var Person = function(name,age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayName = function(){
  console.log(this.name)
};
var person1 = new Person('li',20)
var person2 = new Person('xiao',18)

person1.sayName()  //li
person2.sayName()  //xiao
複製代碼
  • 動態原型模式
function Person(name, age){
  this.name = name;
  this.age = age
  if(typeof this.sayName != function) {
    Person.prototype.sayName = function(name){
      console.log(this.name)
    }
  }
};
複製代碼

判斷這個代碼只會在初次調用構造函數時執行

  • 寄生構造函數模式
  • 委託構造函數模式 不引用this

繼承

js只支持實現繼承 主要是依靠 原型鏈 來實現的.

利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法

方法:

  • 原型鏈
  • 借用構造函數
  • 組合繼承
  • 原型式繼承
  • 寄生式繼承
  • 寄生組合式繼承

原型鏈

每個構造函數都有一個原型對象,原型對象包含一個指向構造函數的指針,而實例都包含一個指向原型對象的指針。那麼,若是咱們讓原型對象等於另外一個類型的實例,此時的原型對象將包含一個指向另外一個原型的指針,相應的,另外一個原型中也包含着一個指向另外一個構造函數的指針,假如另外一個原型又是另外一個類型的實例。。。如此層層遞進,就構成了實例和原型的鏈條

若是引用對象(實例instance)的某個屬性,會如今這個對象內找,若是找不到,就會到這個對象的原型上去找,原型上找不到,就到原型的原型上去找,直到找到這個屬性或者返回null爲止

function Parent(name) {
  this.name = name
}

Parent.prototype.sayName = function() {
  console.log(this.name)
}

function Children(age) {
  this.age = age
}

Children.prototype = new Parent('Amy')  //子類的原型是父類的實例

let instance = new Children(10)

instance.sayName()
複製代碼

借用構造函數

在子類型構造函數的內部調用超類型構造函數

缺點

沒法複用函數,全部類型只能使用構造函數模式

組合繼承

原型鏈和借用構造函數組合,使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承

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

Parent.prototype.sayName = function() {
  console.log(this.name)
}

function Children(name,age) {
  Parent.call(this,name);
  this.age = age
}



Children.prototype = new Parent()


let instance1 = new Children('Amy',10)

instance1.sayName()
instance1.colors.push('green')
console.log(instance1.colors)

let instance2 = new Children('Mike',20)

instance2.sayName()
instance2.colors.push('blue')

console.log(instance2.colors)
複製代碼

缺點

調用兩次超類型構造函數,

原型式繼承

function object(o) {
    function F(){};
    F.prototype = o;   //F的原型指向傳入的對象o
    return new F()
}
複製代碼

Object.create(),這個方法接收兩個參數,一個是用做新對象原型的對象和(可選)一個爲新對象定義額外屬性的對象,只有一個參數時,Object.create()與Objec()同樣

包含引用類型值的屬性始終都會共享相應的值

寄生式繼承

建立一個僅用於封裝繼承過程的函數。不能作到函數複用

寄生組合式繼承

經過借用構造函數來實現對實例屬性的繼承,經過原型鏈的混成模式來繼承方法。僅調用一次超類型構造函數,避免建立沒必要要的多餘的屬性

function Parent(name) {
  this.name = name;
  this.colors = ['yellow']
}

Parent.prototype.sayName = function() {
  console.log(this.name)
}

function Children(name,age) {
  Parent.call(this,name);
  this.age = age
}

function inheritPrototype(parent,children) {
  var prototype = Object(parent.prototype)
  prototype.constructor = children;
  children.prototype = prototype
}

inheritPrototype(Parent, Children);
var instance1 = new Children('li', 20)
instance1.sayName()
複製代碼

es6的繼承和es5有什麼不一樣

ES5 的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制徹底不一樣,實質是先將父類實例對象的屬性和方法,加到this上面(因此必須先調用super方法),而後再用子類的構造函數修改this。

若是子類沒有定義constructor方法,這個方法會被默認添加,在子類的構造函數中,只有調用super以後,纔可使用this關鍵字,不然會報錯。這是由於子類實例的構建,基於父類實例,只有super方法才能調用父類實例。

相關文章
相關標籤/搜索