Javascript面向對象的程序設計

最近由於在給一個小同窗作學習計劃,因此也記錄一些知識點,便於後面的同窗的學習交流。這篇文章是關於Javascript的面向對象的程序設計,主要從三個方面來介紹,1. 理解對象屬性; 2. 理解並建立對象; 3. 理解繼承函數

1、理解對象屬性
首先咱們來理解Javascript對象是什麼?在Javascript中,萬物皆對象。其中建立自定義對象的最簡單的方式就是建立一個Object的實例,以下:性能

var person = new Object();
person.age = 29;

// 對象字面量的形式:
var person = {
     age: 29
};

ECMAScript中有兩種屬性:數據屬性和訪問器屬性。學習

數據屬性:
其中數據屬性有四個描述其行爲的特性:
Configurable: 表示能都經過delete刪除屬性從而從新定義屬性。
Enumerable: 表示可否經過for in 循環返回屬性。
Writable: 表示可否修改屬性的值。
Value: 包含這個屬性的數據值。
要修改屬性默認的配置,必須使用Object.defineProperty(), 這個方法接收三個參數:屬性所在的對象,屬性的名字和一個描述性對象。
好比:this

var person = {};
Object.defineProperty(person, ’name’, {
      writable: false,
      value: ’Nicholas'
});

alert(person.name); //Nicholas
person.name = ‘Greg’;
alert(person.name); //Nicholas

訪問器屬性:
訪問器屬性包含一對setter和getter函數。包含以下4個特性:
Configurable:可否被delete刪除屬性從新定義。默認值:true
Enumerable:可否被for-in枚舉。默認值:true
Get:讀取屬性值。默認值:undefined
Set:寫入屬性值。默認值:undefinedprototype

var dog = {
    _age: 2,
    weight: 10
}

Object.defineProperty(dog, 'age', {
    get: function () {
        return this._age
    },
    set: function (newVal) {
        this._age = newVal
        this.weight += 1
    }
})

知道了對象的屬性,那麼咱們建立對象的方式是什麼呢?設計

2、建立對象的方式指針

建立對象的方式一般有下面幾種方式:
一、工廠模式
咱們舉個例子:code

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 o;
}

var person = createPerson(‘Greg’, 27, ‘Doctor’);

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

二、構造函數模式
咱們舉個例子:
**function Person(name, age, job) {繼承

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

};
}
var person = new Person(‘Greg’, 27, ‘Doctor’);**

構造函數始終都應該以一個大寫字母開頭,而非構造函數則應該以一個小寫字母開頭。這裏要提的一個屬性是Constructor, 每一個new 出來的實例都有一個Constructor(構造函數)屬性,該屬性指向構造函數。
對象的Constructor屬性最初是用來標識對象類型的。可是,提到檢測對象類型,仍是instanceof操做符更好可靠一些。
alert(person1 instanceof Object); //true

構造函數的問題問題就是,每一個方法都要在每一個實例上從新建立一遍,固然,能夠把函數定義轉移到構造函數外部來解決這個問題,以下實例:

**function Person(name, age, job) {

this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;

}
function sayName() {

alert(this.name);

}
var person = new Person(‘Greg’, 27, ‘Doctor’);**

那麼這裏的問題就是:在全局做用域中定義的函數實際上被只能被某個對象調用,這讓全局做用域有點名存實亡。而更讓人沒法接受的是:若是獨享須要定義不少方法,那麼就要定義不少個全局函數。下面咱們來看看原型模式是不能解決這個問題。

三、原型模式
prototype就是經過調用構造函數而建立的那個對象實例的原型對象。咱們來看下面一個例子來理解這句話:

function Person() {
}
Person.prototype.name = 「Nicholas」;
Person.prototype.age = 29;
Person.prototype.job = 「Software Engineer」;
Person.prototype.sayName = function() {
    alert(this.name);
};
var person1 = new Person();
person1.sayName(); // 「Nicholas」

與構造函數模式不一樣的是,新對象的這些屬性和方法是由全部實例共享的。
針對這個特性,咱們要注意的幾個點就是。
第一,若是實例的屬性與方法與原型的屬性和方法同名,那誰的優先級高呢?
固然是建立出來的實例的屬性和方法的優先級高。
第二,實例的屬性與方法的修改會影響原型同名的屬性和方法嗎?
不會
這裏提一下hasOwnProperty(), 使用hasOwnProperty()方法能夠檢測一個屬性是存在於實例中,仍是存在原型中。
第三,如何判斷某個屬性是存在原型中?

function hasPrototypeProperty(object, name) {
    return !Object.hasOwnProperty(name) && (name in object);
}

hasOwnProperty()只在屬性存在於實例中才返回true, 所以只要in操做符返回true而hasOwnProperty()返回false, 就能夠肯定屬性是原型中的屬性、這裏說下Constructor, 每建立一個函數,就會同時建立它的Prototype對象,這個對象也會自動得到Constructor屬性。
咱們來看一個例子:

function Person() {

}

Person.prototype = {
    constructor: Person,
    name: 「Nichloas」,
    age: 29,
    job: 「Software Engineer」,
    sayName: function () {
         alert(this.name);
    }  
};

以上代碼特地包含了一個Constructor屬性,並將它的值設置爲Person, 從而確保了經過該屬性可以訪問到適當的值。可是,以這種方式重設Constructor屬性會致使它的Enumerable特性被設置爲true, 默認狀況下原生的Constructor屬性是不可枚舉的。

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 = [「Shelby」, 「Court」];
} 

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

var person = new Person(‘Greg’, 27, ‘Doctor’);

知道了建立對象的方式,那麼在Javascript中咱們如何來繼承對象呢?

3、繼承
一、原型鏈
構造函數,原型和實例的關係:每個構造函數都也有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那麼,假如咱們讓原型對象等於另外一個類型的實例,結果會怎麼樣呢?顯然,此時的原型對象將包含一個指向另外一個原型的指針,相應的,另外一個原型中也包含也包含着一個指向一個構造函數的指針,假如另外一個原型又是另外一個類型的實例,那麼上訴關係依然成立,咱們來看下面一個例子.

function SuperType() {
    this.property = true;
}

SuerType.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
這個例子中,SubType繼承了SuperType,而SuperType繼承了Object。若是要肯定原型和實例的關係,能夠用instanceOf操做符與isPropertyOF()方法。
如 instance instanceOf object; // true
Object.prototype isPropertype(instance); // true

可是經過原型鏈實現繼承時,不能使用對象字面量建立原型方法。

function SuperType() {
    this.property = true;
}

SuerType.prototype.getSuperValue = function () {
     return this.property;
};

function SubType() {
    This.subProperty = false;
}

// 繼承了SuperType
SubType.prototype = new SuperType();

// 使用字面量添加新方法,會致使上一行代碼無效
SubType.prototype = {
      getSubValue: function () {
            return this.subproperty;
       },
      someOtherMethod: function () {
            return false;
      }
};

var instance = new SubType();
alert(instance.getSuperValue); // error

接下來,咱們來講下原型鏈繼承的問題:
第一,最主要的問題來自包含引用類型值的原型。包含引用類型值的原型屬性會被全部實例共享,而這也正是爲何要在構造函數中,而不是在原型對象中定義屬性的緣由。
第二,在建立子類型的實例時候,不能向超類型的構造函數中傳遞參數。

二、借用構造函數

function SuperType(name) {
       this.name = name;
}

function SubType() {
    // 繼承了SuperType, 同時還傳遞了參數
    SuperType.call(this, 「Nicholas」);
    this.age = 29;
}

var instance = new SubType();
alert(instance.name);  // 「Nicholas」

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

三、組合繼承
組合繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和放大的績效,而經過借用構造函數來實現對實例屬性的繼承。

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.sayAge = function () {
    alert(this.age);
};


var instance = new SubType(「Nicholas」, 29);
instance.colors.push(「black」);
alert(instance.colors); // red, blue, green, black

組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,成爲Javascript中最多見的繼承模式。

四、原型式繼承

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
]

var person = {
    Name: 「Nicholas」,
    friends: [「Shelby」, 「Court」, 「Van"]
};

var anotherPerson = object(person);
anotherPerson.name = 「Greg」;

ECMAScipt5經過新增Object.create()方法規範化了原型式繼承。
接着上面的例子:

var anotherPerson = Object.create(person);
anotherPerson.name = 「Greg」;

五、寄生式繼承
寄生式繼承是與原型式繼承緊密相關的一種思路,它與工廠模式相似,即建立一個僅用於封裝過程的函數,該函數在內部以某種方法來加強對象,最後再像真地是它作了全部工做同樣返回對象。
例如:

function createAnother(original) {
     var clone = object(original);
     clone.sayHi = function () {
           alert(‘hi’);
     };
     return clone; 
}

var person = {
    name: ’Nochplas’,
    friends: [’Shelby’, ‘Court’, ‘Van']
};

var anotherPerson = createAnother(person);

六、組合寄生式繼承

組合繼承是Javascript最經常使用的繼承模式,不過,組合繼承最大的問題就是不管什麼狀況下,都會調用兩次超類型構建函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。

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);   // 第二次調用 SuperType()
     this.age = age;
} 

// 繼承方法
SubType.prototype = new SuperType(); // 第一次調用 SuperType()

SubType.prototype.sayAge = function () {
    alert(this.age);
};

所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。沒必要爲了制定子類型的原型而調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果制定給子類型的原型。以下:

function inheritPrototype (subType, superType) {
   var prototype = object (superType.prototype); // 前面的原型式繼承object方法
   prototype.constructor = subtype;
   subtype.prototype = prototype;
}


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;
} 

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function () {
    alert(this.age);
};
相關文章
相關標籤/搜索