JS物件導向設計模式

尋夢環遊發表於2019-04-12

建立物件

工廠模式

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 person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = 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 p1 = new Person("Nicholas", 29, "Software Engineer");
var p2 = new Person("Greg", 27, "Doctor");
複製程式碼

建構函式也存在問題,每個方法都要在例項上建立一遍。也就是說p1和p2的sayName()方法雖然作用相同,但這兩個方法並不是同一個函式

解析一道題

new操作符做了什麼:

(1)建立一個新物件

(2)將建構函式的作用域賦給新物件(因此this就指向了這個新物件)

(3)執行建構函式中的程式碼

(4)返回新物件

原型模式

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"
複製程式碼

當我們改變 值為引用型別的物件的屬性 時,這個改變的結果會被其他物件共享。

建構函式 + 原型模式

建構函式模式的屬性沒毛病。缺點是:無法共享方法

原型模式的方法沒毛病。缺點是:當原形物件的屬性的值為引用型別時,對其進行修改會反映到所有例項中 

那我們就將兩者的結合,物件的屬性使用建構函式模式建立,方法則使用原型模式建立

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 person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
複製程式碼

繼承

原型鏈

JavaScript中引入了原型鏈的概念,具體思想: 子建構函式的原型物件初始化為父建構函式的例項,孫建構函式的原型物件初始化為子建構函式的例項…… ,這樣子物件就可以通過原型鏈一級一級向上查詢,訪問父建構函式中的屬性以及方法。

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());
複製程式碼

當我們改變 值為引用型別的原型物件的屬性 時,這個改變的結果會被所有子物件共享。這個缺點某些時候相當致命,所以我們很少使用這種方法來繼承

借用建構函式

function SuperObject(){
  this.colors = ['red','blue'];
  this.sayBye= function(){
    console.log('Bye')
  }
}
function SubObject(){
  SuperObject.call(this);  // 在子類中呼叫父類的構造方法,實際上子類和父類已經沒有上下級關係了
}
​
​
var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors); //['red','blue']
console.log(instance2 instanceof SuperObject); // false
console.log(instance1.sayBye === instance2.sayBye)  // false
複製程式碼

這個方法雖然彌補了原型鏈的缺點,但是又暴露出了新的缺點:

 1 子類和父類沒有上下級關係,instance2 instanceof SuperObject 結果是false

 2 父類中的方法在每個子類中都會生成一遍,父類中的方法沒有被複用。

組合繼承

組合繼承就是將原型鏈繼承和借用構造方法繼承組合,發揮兩者之長。

function SuperType(name){
    this.name = name;
    this.colors = ['blue'];
}
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('zz',18);
instance1.sayName(); //zz
instance1.sayAge(); //18
instance1.colors.push('red');
console.log(instance1.colors); // ['blue','red']
​
var instance2 = new SubType('tt',22);
console.log(instance2.colors); // ['blue']
組合繼承會呼叫兩次父型別函式

原型式繼承
程式碼塊
function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}
​
var Person = {
    name: 'Nicholas'
    friends: ['zz','cc']
}
var anotherPerson = object(Person);
anotherPerson.friends.push('dd');
console.log(anotherPerson.friends); //['zz','cc','dd']
複製程式碼

寄生式繼承

function createAnother(o){
    var clone = object(o);
    o.sayHi = function(){
        console.log('hi');
    }
    return clone;
}
複製程式碼

寄生組合式繼承

雖然組合繼承沒啥大缺點,但是愛搞事情的有強迫症的程式猿們覺得,組合繼承會呼叫兩次父型別函式(在上面的程式碼中標註了),不夠完美。於是道格拉斯就提出了寄生組合繼承。

思路是構造一箇中間函式,將中間函式的prototype指向父函式的原型物件,將子函式的prototype指向中間函式,並將中間函式的constructor屬性指向子函式。

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}
​
function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);
    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)
}
var instance1 = new SubType('zz', 18);
instance1.sayName();
複製程式碼

ES6的繼承

JS物件導向設計模式
這段程式碼有兩條原型鏈

JS物件導向設計模式

參考資料

MDN:developer.mozilla.org/zh-CN/docs/…

JS高階語言程式設計第六章(物件導向的程式設計)

ES6(類):es6.ruanyifeng.com/#docs/class

相關文章