js建立對象的幾種方法及繼承

建立對象

經過Object構造函數或對象字面量建立單個對象

 這些方式有明顯的缺點:使用同一個接口建立不少對象,會產生大量的重複代碼。爲了解決這個問題,出現了工廠模式。  數組

工廠模式

   考慮在ES中沒法建立類(ES6前),開發人員發明了一種函數,用函數來封裝以特定接口建立對象的細節。(實現起來是在一個函數內建立好對象,而後把對象返回)。    bash

function createPerson(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
        alert(this.name);
    };
    return 0;
}

var person1=createPerson("Nicholas",29,"Software Engineer");
var person2=createPerson("Greg",27,"Doctor");
複製代碼

構造函數模式

像Object和Array這樣的原生構造函數,在運行時會自動出如今執行環境。此外,也能夠建立自定義的構造函數,從而定義自定義對象類型的屬性和方法。app

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

var person1=new Person(...);
var person2=new Person(...);
複製代碼

 與工廠模式相比,具備如下特色:  函數

  •  沒有顯式建立對象;測試

  • 直接將屬性和方法賦給了this對象;ui

  • 沒有return語句;this

  • 要建立新實例,必須使用new操做符;(不然屬性和方法將會被添加到window對象)spa

  • 可使用instanceof操做符檢測對象類型(instanceof運算符用於測試構造函數的prototype屬性是否出如今對象的原型鏈中的任何位置)prototype

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true
複製代碼

構造函數的問題:指針

構造函數內部的方法會被重複建立,不一樣實例內的同名函數是不相等的。可經過將方法移到構造函數外部解決這一問題,但面臨新問題:封裝性很差。

原型模式

咱們建立的每一個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。(prototype就是經過調用構造函數而建立的那個對象實例的原型對象)。

 使用原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。換句話說,沒必要在構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中。    

function Person(){
}

Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="...";
Person.prototype.sayName=function(){
    ...
};

var person1=new Person();
person1.sayName();//"Nicholas"
複製代碼

更常見的作法是用一個包含全部屬性和方法的對象字面量來重寫整個原型對象,並重設constructor屬性。

function Person(){
}

Person.prototype={
    name:"...",
    age:29,
    job:"...",
    sayName:function(){
        ...
    }
};

Object.defineProperty(Person.prototype,"constructor",{
    enumerable:false,
    value:Person,
});
複製代碼

 原型對象的問題:  

  •  他省略了爲構造函數傳遞初始化參數這一環節,結果全部實例在默認狀況下都將取得相同的屬性值,雖然這會在必定程度帶來必定的不便,但不是最大的問題,最大的問題是由其共享的本性所決定的。
  • 對於包含基本值的屬性能夠經過在實例上添加一個同名屬性隱藏原型中的屬性。而後,對於包含引用數據類型的值來講,會致使問題。

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

這是建立自定義類型的最多見的方式。

 構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。因此每一個實例都會有本身的一份實例屬性的副本,但同時共享着對方法的引用,最大限度的節省了內存。同時支持向構造函數傳遞參數。    

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["S","C"];
}

Person.prototype={
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
};

var person1=new Person(...);
複製代碼

動態原型模式

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;

    if(typeof this.sayName!="function"){
        Person.prototype.sayName=function(){
            alert(this.name);
        };
    }
}
複製代碼

 這裏只有sayName()不存在的狀況下,纔會將它添加到原型中,這段代碼只會在初次調用構造函數時才執行。這裏對原型所作的修改,可以馬上在全部實例中獲得反映。  

Object.create()

ES5定義了一個名爲Object.create()的方法,它建立一個新對象,其中第一個參數是這個對象的原型,第二個參數對對象的屬性進行進一步描述。

Object.create()介紹

Object.create(null) 建立的對象是一個空對象,在該對象上沒有繼承 Object.prototype 原型鏈上的屬性或者方法,例如:toString(), hasOwnProperty()等方法

Object.create()方法接受兩個參數:Object.create(obj,propertiesObject) ;

obj:一個對象,應該是新建立的對象的原型。

propertiesObject:可選。該參數對象是一組屬性與值,該對象的屬性名稱將是新建立的對象的屬性名稱,值是屬性描述符(這些屬性描述符的結構與Object.defineProperties()的第二個參數同樣)。注意:該參數對象不能是 undefined,另外只有該對象中自身擁有的可枚舉的屬性纔有效,也就是說該對象的原型鏈上屬性是無效的。

var o = Object.create(Object.prototype, {
  // foo會成爲所建立對象的數據屬性
  foo: { 
    writable:true,
    configurable:true,
    value: "hello" 
  },
  // bar會成爲所建立對象的訪問器屬性
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});
console.log(o);//{foo:'hello'}
var test1 = Object.create(null) ;
console.log(test1);// {} No Properties 
由於在bar中設置了configurable 使用set,get方法默認都是不起做用,因此bar值沒法賦值或者獲取
這裏的o對象繼承了 Object.prototype  Object上的原型方法
咱們能夠 對象的 __proto__屬性,來獲取對象原型鏈上的方法 如:
console.log(o.__proto__);//{__defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, __lookupSetter__: ƒ, …}
console.log(test1.__proto__);//undefined
複製代碼

經過打印發現, 將{}點開,顯示的是 No Properties ,也就是在對象自己不存在屬性跟方法,原型鏈上也不存在屬性和方法,

new object()

var test1 = {x:1};

var test2 = new Object(test1);

var test3 = Object.create(test1);
console.log(test3);//{} 
//test3等價於test5
var test4 = function(){
  
}
test4.prototype = test1;
var test5 = new test4();
console.log(test5);
console.log(test5.__proto__ === test3.__proto__);//true
console.log(test2);//{x:1}
複製代碼
var test1 = {};
var test2 = new Object();
var test3 = Object.create(Object.prototype);
var test4 = Object.create(null);//console.log(test4.__proto__)=>undefined 沒有繼承原型屬性和方法
console.log(test1.__proto__ === test2.__proto__);//true
console.log(test1.__proto__ === test3.__proto__);//true
console.log(test2.__proto__ === test3.__proto__);//true
console.log(test1.__proto__ === test4.__proto__);//false
console.log(test2.__proto__ === test4.__proto__);//false
console.log(test3.__proto__ === test4.__proto__);//false
複製代碼

總結:使用Object.create()是將對象繼承到__proto__屬性上

var test = Object.create({x:123,y:345});
console.log(test);//{}
console.log(test.x);//123
console.log(test.__proto__.x);//3
console.log(test.__proto__.x === test.x);//true

var test1 = new Object({x:123,y:345});
console.log(test1);//{x:123,y:345}
console.log(test1.x);//123
console.log(test1.__proto__.x);//undefined
console.log(test1.__proto__.x === test1.x);//false

var test2 = {x:123,y:345};
console.log(test2);//{x:123,y:345};
console.log(test2.x);//123
console.log(test2.__proto__.x);//undefined
console.log(test2.__proto__.x === test2.x);//false
複製代碼

繼承

我這裏就介紹一種吧,剩下的能夠去權威指南里看去

原型鏈

ECMAScript 中描述了原型鏈的概念,並將原型鏈做爲實現繼承的主要方法。其基本思想是利用原 型讓一個引用類型繼承另外一個引用類型的屬性和方法。簡單回顧一下構造函數、原型和實例的關係:每 個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型 對象的內部指針。那麼,假如咱們讓原型對象等於另外一個類型的實例,結果會怎麼樣呢?顯然,此時的 原型對象將包含一個指向另外一個原型的指針,相應地,另外一個原型中也包含着一個指向另外一個構造函數 的指針。假如另外一個原型又是另外一個類型的實例,那麼上述關係依然成立,如此層層遞進,就構成了實 例與原型的鏈條。這就是所謂原型鏈的基本概念。

實現原型鏈有一種基本模式,其代碼大體以下。

function SuperType(){
        this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
    };
    var instance = new SubType();
alert(instance.getSuperValue());
//true
複製代碼

以上代碼定義了兩個類型:SuperType 和 SubType。每一個類型分別有一個屬性和一個方法。它們 的主要區別是 SubType 繼承了 SuperType,而繼承是經過建立 SuperType 的實例,並將該實例賦給 SubType.prototype 實現的。實現的本質是重寫原型對象,代之以一個新類型的實例。換句話說,原 來存在於 SuperType 的實例中的全部屬性和方法,如今也存在於 SubType.prototype 中了。在確立了 繼承關係以後,咱們給 SubType.prototype 添加了一個方法,這樣就在繼承了 SuperType 的屬性和方 法的基礎上又添加了一個新方法。這個例子中的實例以及構造函數和原型之間的關係如圖 6-4 所示。

原型鏈的問題

原型鏈雖然很強大,能夠用它來實現繼承,但它也存在一些問題。其中,最主要的問題來自包含引 用類型值的原型。想必你們還記得,咱們前面介紹過包含引用類型值的原型屬性會被全部實例共享;而 這也正是爲何要在構造函數中,而不是在原型對象中定義屬性的緣由。在經過原型來實現繼承時,原 型實際上會變成另外一個類型的實例。因而,原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性了。 下列代碼能夠用來講明這個問題

function SuperType(){
        this.colors = ["red", "blue", "green"];
        }
function SubType(){
}
//繼承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType(); 
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType(); 
alert(instance2.colors); //"red,blue,green,black"
複製代碼

這個例子中的 SuperType 構造函數定義了一個 colors 屬性,該屬性包含一個數組(引用類型值)。 SuperType 的每一個實例都會有各自包含本身數組的 colors 屬性。當 SubType 經過原型鏈繼承了 SuperType 以後,SubType.prototype 就變成了 SuperType 的一個實例,所以它也擁有了一個它自 己的 colors 屬性——就跟專門建立了一個 SubType.prototype.colors 屬性同樣。但結果是什麼 呢?結果是 SubType 的全部實例都會共享這一個 colors 屬性。而咱們對 instance1.colors 的修改 可以經過 instance2.colors 反映出來,就已經充分證明了這一點。

原型鏈的第二個問題是:在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上, 應該說是沒有辦法在不影響全部對象實例的狀況下,給超類型的構造函數傳遞參數。有鑑於此,再加上 前面剛剛討論過的因爲原型中包含引用類型值所帶來的問題,實踐中不多會單獨使用原型鏈。

借用構造函數

在解決原型中包含引用類型值所帶來問題的過程當中,開發人員開始使用一種叫作借用構造函數 (constructor stealing)的技術(有時候也叫作僞造對象或經典繼承)。這種技術的基本思想至關簡單,即 在子類型構造函數的內部調用超類型構造函數。別忘了,函數只不過是在特定環境中執行代碼的對象, 所以經過使用 apply()和 call()方法也能夠在(未來)新建立的對象上執行構造函數,以下所示:

function SuperType(){
    this.colors = ["red", "blue", "green"];
    }
 function SubType(){
//繼承了 SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green"
複製代碼

代碼中加粗的那一行代碼「借調」了超類型的構造函數。經過使用 call()方法(或 apply()方法 也能夠),咱們其實是在(將來將要)新建立的 SubType 實例的環境下調用了 SuperType 構造函數。 這樣一來,就會在新 SubType 對象上執行 SuperType()函數中定義的全部對象初始化代碼。結果, SubType 的每一個實例就都會具備本身的 colors 屬性的副本了。

1. 傳遞參數

相對於原型鏈而言,借用構造函數有一個很大的優點,便可以在子類型構造函數中向超類型構造函 數傳遞參數。看下面這個例子。

function SuperType(name){
    this.name = name;
}
function SubType(){
//繼承了 SuperType,同時還傳遞了參數 SuperType.call(this, "Nicholas");
//實例屬性
    this.age = 29;
}
var instance = new SubType();
alert(instance.name);    //"Nicholas";
alert(instance.age);     //29
複製代碼

以上代碼中的 SuperType 只接受一個參數 name,該參數會直接賦給一個屬性。在 SubType 構造 函數內部調用 SuperType 構造函數時,其實是爲 SubType 的實例設置了 name 屬性。爲了確保 SuperType 構造函數不會重寫子類型的屬性,能夠在調用超類型構造函數後,再添加應該在子類型中 定義的屬性。

2. 借用構造函數的問題

若是僅僅是借用構造函數,那麼也將沒法避免構造函數模式存在的問題——方法都在構造函數中定 義,所以函數複用就無從談起了。並且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結 果全部類型都只能使用構造函數模式。考慮到這些問題,借用構造函數的技術也是不多單獨使用的。

組合繼承

組合繼承(combination inheritance),有時候也叫作僞經典繼承,指的是將原型鏈和借用構造函數的 技術組合到一塊,從而發揮兩者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方 法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數 複用,又可以保證每一個實例都有它本身的屬性。下面來看一個例子。

function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
}
    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.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();
//"red,blue,green,black"
//"Nicholas";
//29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);
instance2.sayName();
instance2.sayAge();
//"red,blue,green"
//"Greg";
//27
複製代碼

在這個例子中,SuperType 構造函數定義了兩個屬性:name 和 colors。SuperType 的原型定義 了一個方法 sayName()。SubType 構造函數在調用 SuperType 構造函數時傳入了 name 參數,緊接着 又定義了它本身的屬性 age。而後,將 SuperType 的實例賦值給 SubType 的原型,而後又在該新原型 上定義了方法 sayAge()。這樣一來,就可讓兩個不一樣的 SubType 實例既分別擁有本身屬性——包 括 colors 屬性,又可使用相同的方法了

組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,成爲 JavaScript 中最經常使用的繼 承模式。並且,instanceof 和 isPrototypeOf()也可以用於識別基於組合繼承建立的對象。 9

原型式繼承

能夠在沒必要預先定義構造函數的狀況下實現繼承,其本質是執行對給定對象的淺 複製。而複製獲得的副本還能夠獲得進一步改造。

寄生式繼承

與原型式繼承很是類似,也是基於某個對象或某些信息建立一個對象,而後加強 對象,最後返回對象。爲了解決組合繼承模式因爲屢次調用超類型構造函數而致使的低效率問 題,能夠將這個模式與組合繼承一塊兒使用。

寄生組合式繼承

集寄生式繼承和組合繼承的優勢與一身,是實現基於類型繼承的最有效方式。

相關文章
相關標籤/搜索