JavaScript基礎概念之----面向對象----建立對象(深刻原型及原型鏈)

1、使用Object構造函數或對象字面量建立對象安全

var person = new Object()
person.name = 'adhehe';
person.age = 23;
//...
// 
var person = {
  name:'adhehe',
  age:23,
  //...

}

缺點:使用同一個接口建立不少的對象,會產生大量的重複代碼。函數

2、工廠模式this

這種模式抽象了建立具體對象的過程。用函數來封裝以特定接口建立對象的細節。spa

function createPerson(name,age){
  //將建立的具體細節封裝起來,最後返回一個對象
var o = new Object(); o.name = name; o.age = age; o.getName = function(){ return this.name; } return o; } var person1 = createPerson('adhehe',23); var person2 = createPerson('Jhone',45);

缺點:雖然解決了建立多個類似對象的問題,但沒有解決 對象識別的問題,即怎麼知道一個對象的類型prototype

3、構造函數模式指針

構造函數能夠用來建立特定類型的對象。如Object、Array等。也能夠建立自定義構造函數。code

function Person(name,age){
    this.name = name;
    this.age = age;
    this.getName = function(){
        return this.name;
    }
}    

var person1 = new Person('adhehe',23);
var person2 = new Person('Jhone',45);

以new關鍵字建立Person的實例,經歷如下4個步驟:對象

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

instanceof 操做符 能夠檢測對象的類型blog

//以person爲例

console.log(person1 instanceof Person)
console.log(person2 instanceof Person)
console.log(person1 instanceof Object)
console.log(person2 instanceof Object)
//都輸出 true

構造函數與普通函數的惟一區別是:調用的方式不一樣。只要經過new操做符調用,它就是構造函數。接口

缺點:每一個方法都要在每一個實例上從新建立一遍。

//以上面 person爲例

person1.getName == preson2.getName
//false

 

4、原型模式

建立的每一個函數都有一個prototype(原型)屬性,它是一個指針,批向一個對象,而這個對象的用途就是:包含能夠共享的屬性和方法,給那些特定類型的全部實例。

function Person(){}

Person.prototype.name = 'adhehe'
Person.prototype.age = 23
Person.prototype.getName = function(){
  return this.name;
}

var preson1 = new Person()
var preson2 = new Person()

console.log(person1.getName())
console.log(person2.getName())
//都返回 adhehe

person1.getName == person2.getName
//true

 一、理解原型對象

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

全部原型對象都會自動得到一個constructor屬性(構造函數),這個屬性指向prototype屬性所在的函數的指針。

當調用構造函數建立一個實例後,實例內部將包含一個指針(__ptoto__),指向構造函數的原型對象。

Person.prototype.constructor == Person

person1.constructor == Person
person2.constructor == Person

person1.__proto__ == Person.prototype
person2.__proto__ == Person.prototype
 

isPrototypeOf()方法 肯定對象之間是否存在原型關係

Person.prototype.ifPrototypeOf(person1) //true
Person.prototype.ifPrototypeOf(person2) //true

Object.getPrototypeOf()方法 是ECMAScript5新增的檢測方法

Object.getPrototypeOf(person1) == Person.prototype //true
Object.getPrototypeOf(person2) == Person.prototype //true

每當讀取某個對象中的某個屬性時,首先搜索該對象自己,再搜索該對象指向的原型對象。若是在實例對象中某個屬性名與原型中的一個屬性同名,該屬性將會屏蔽原型中的屬性。

function Person(){}

Person.prototype.same = 1;

var person1 = new Person()
var person2 = new Person()
person1.same
= 2; console.log(person1.same) //2 console.log(person2.same) //1

使用delete操做符 能夠徹底刪除實例屬性,從而可以從新訪問原型中的屬性。

//接上例

delete person1.same;
console.log(person1.same) //1

hasOwnProperty()方法能夠檢測一個屬性是存在於實例中,仍是存在於原型中。

person1.hasOwnProperty('name') // true
person1.hasOwnProperty('hello') //false

二、in 操做符

有兩種方式使用:

  • 單獨使用
  • for-in

單獨使用:經過對象可以訪問指定屬性時返回true,不管該屬性存在於實例仍是原型中。

"name" in person1 //true

同時使用 hasOwnProperty()方法和 in 操做符,就能夠確認該屬性究竟是存在於對象中,仍是存在於原型中。

function has(obj,name){
    return !obj.hasOwnProperty(name) && (name in obj);
}

//in爲true,hasOwnProperty()返回false,就能夠肯定屬性是在原型中

for-in 循環:返回全部可以經過對象訪問的、可枚舉的屬性,其中既包括存在於實例中的屬性,也包括存在於原型中的屬性。屏蔽了原型中不可枚舉的屬性的實例屬性也會在for-in循環中返回。

Object.keys()方法 能夠取得對象上全部可枚舉的實例屬性。

function Person(){}

Person.prototype.name = 'adhehe'
Person.prototype.age = 23
Person.prototype.getName = function(){
  return this.name;
}

var keys = Object.keys(Person.prototype)
console.log(keys) // "name,age,getName"

var person1 = new Person()
person1.name = 'Rob'

var person1Keys = Object.keys(person1)
console.log(person1Keys) //"name"

Object.getOwnPropertyNames()方法 能夠獲得全部實例屬性,不管它是否可枚舉。

var keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys)
//"constructor,name,age,getName"

三、更簡單的原型語法

function Person(){}

Person.prototype = {
    name:'adhehe',
    age:23,
    getName:function(){
        return this.name
    }
}

在這裏,constructor屬性再也不指向Person。能夠 將constructor屬性值設置回適當的值

function Person(){}

Person.prototype = {
    constructor:Person,
    name:'adhehe',
    age:23,
    getName:function(){
        return this.name
    }
}

以上面這種方式重設constructor屬性會致使它的Enumerable特性被設置爲true。默認狀況下原生的constructor是不可枚舉的。可使用Object.defineProperty()修改

 

function Person(){}

Person.prototype = {
    name:'adhehe',
    age:23,
    getName:function(){
        return this.name
    }
}

Object.defineProperty(Person.prototype,'constructor',{
    enumberable:false,
    value:Person
})

 

四、原型的動態性

對原型對象的修改都可以當即 從實例上反映出來。能夠隨時爲原型添加屬性和方法。

var o = new Person();

Person.prototype.sayHi = function(){
    //...
}

o.sayHi()  //沒有問題

可是,若是重寫原型對象,則會切斷 構造函數與最初原型之間的聯繫。實例中的指針僅指向原型,而不指向構造函數。

function Person(){}

var o = new Person();

Person.prototype = {
    constructor:Person,
    name:'adhehe',
    age:23,
    getName:function(){
        //....
    }
}

o.getName() // 報錯

五、原生對象的原型

全部原生的引用類型,都在其構造函數的原型上定義了方法。

咱們也能夠在原生對象的原型上定義新的方法或修改原型,但不推薦這麼作。

缺點:

  • 省略了爲構造函數傳遞初始化參數的環節,致使全部的實例在默認狀況下都將取到相同的屬性值
  • 因爲原型的共享本性的緣由,在其中一個實例對象上添加或修改了原型屬性的值,其餘實例對象都會被修改
function Person(){}

Person.prototype = {   
    constructor:Person,
    name:'adhehe',
    age:23,
    friends:['a','b'],
    getName:function(){
        //...
    }
}

var o1 = new Person()
var o2 = new Person()

o1.friends.push('c')

console.log(o1.friends) //a,b,c
console.log(o2.friends) //a,b,c
//兩個對象實例的friends返回了相同的值

5、構造函數模式 與 原型模式 組合

 

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

Person.prototype = {
    constructor:Person,
    getName:function(){
        //...
    }
}

var o1 = new Person('adhehe',23);
var o2 = new Person('Greg',45);

o1.friends.push('c');

console.log(o1.friends)  //a,b,c
console.log(o2.friends)  //a,b
console.log(o1.friends === o2.friends)  //false
console.log(o1.getName === o2.getName)  //true

6、動態原型模式

在構造函數中初始化原型(僅在必要的狀況下),它保持了同時使用構造函數和原型的優勢。

它只會在初次調用構造函數時纔會執行,此後,原型已經完成初始化,不須要再作什麼修改。

使用動態原型模式時,不能使用對象字面量重寫原型。會切斷現有實例與新原型之間的聯繫。

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

    if(typeof this.getName != 'function'){
        Person.prototype.getName = function(){
            //...
        }
    }
}

var o = new Person('adhehe',23)
o.getName()

7、寄生構造函數模式

建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象。

這個模式與工廠模式是如出一轍的,不一樣的是這裏使用 new 操做符建立實例對象,並把包裝函數叫做構造函數。

function Person(name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.getName = function(){
        //...
    }

    return o;
}

var person1 = new Person('adhehe',23)
person1.getName();

缺點:返回的對象與構造函數或構造函數的原型屬性之間沒有任何關係,因此不能依賴 instanceof 操做符來肯定對象類型。在有其餘模式可用的狀況下,不推薦使用這種模式

8、穩妥構造函數模式

穩妥對象:指的是沒有公共屬性,其方法也不引用 this 的對象。

穩妥對象 最適合在一些安全的環境中,或者在防止數據被其餘應用程序改動時使用。

此模式 與 寄生構造函數相似的模式,有兩點不一樣:

  • 新建立對象的實例方法不引用 this
  • 不使用 new 操做符調用構造函數
function Person(name,age){
    var o = new Object();

    //在這裏定義私有變量與屬性

    o.getName = function(){
        return name;
    }

   return o;
}

var person1 = Person('adhehe',23)
person1.getName() //adhehe

//在這種模式建立的對象中,除了使用getName()方法以外,沒有其餘辦法能夠訪問name

缺點:與寄生構造函數模式相似,返回的對象與構造函數或構造函數的原型屬性之間沒有任何關係,因此不能依賴 instanceof 操做符來肯定對象類型。

相關文章
相關標籤/搜索