構造函數(shù)-js-高級程序設計-第六章筆記

前言

本文是《JavaScript高級程序設計》中第六章的面向?qū)ο蟪绦蛟O計的幾個 構造函數(shù)模式的筆記

理解對象

// ...

創(chuàng)建對象

// ...

構造函數(shù)模式

先來看看典型的構造函數(shù):

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.sayName=function(){
        console.log(this.name);
    };
}
var person1=new Person("小明",20);

由上邊可以直接看出構造函數(shù)模式的特點:

  1. 直接將屬性和方法賦給了 this 對象;
  2. 沒有 return 語句
  3. 沒有 顯式創(chuàng)建對象

優(yōu)點:
直觀,方便,符合OO編程直覺上的使用

存在的問題:
每個方法都會在實例上重新創(chuàng)建一遍,導致了每一個person1 / person2 等實例之間的sayName()方法其實是不相等的

    person1.sayName==person2.sayName 
    //false

原型模式

無論什么時候,只要創(chuàng)建了一個新函數(shù),就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個protoype屬性,這個屬性指向函數(shù)的原型對象.
那么:

function Person(){}

Person.prototype.name="小明";
Person.prototype.age=20;
Person.prototype.sayName=function(){
    console.log(this.name);
};

var person1=new Person();

但是這樣寫太麻煩了,想想每次要增加一個屬性,就得寫一長串的Person.prototype.xxx

所以可以簡略為:

function Person(){}

Person.prototype={
    name:"小明",
    age:20,
    sayName:function(){
        console.log(this.name);
    }
};

但是要注意的是, 這種做法本質(zhì)上 是 完全重寫了默認的prototype 對象,因此 constructor屬性也就變成了指向Object構造函數(shù)的constructor屬性,不再指向Person函數(shù).
雖然 instanceof 操作符仍能返回正確結果,但constructor無法確定對象類型:

var friend=new Person();
friend instanceof Object //true
friend instanceof Person //true
friend.constructor == Person //false
friend.constructor == Object //true

//我的疑問:
//既然instanceof能確定對象類型,那為什么還要把constructor顯式轉(zhuǎn)為Person?

所以最好要加上 constructor:Person

function Person(){}

Person.prototype={
    constructor:Person,
    name:"小明",
    age:20,
    sayName:function(){
        console.log(this.name);
    }
};

注意的是,當以這種方式重設constructor屬性會導致它的 [[Enumerable]]特性被設置為true.
在默認情況下,原生的constructor屬性是不可枚舉的,因此在兼容ES5的情況下,使用Object.defineProperty()

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

//我的疑問:
//那假如我不把它設置為false又會怎樣?

但是原型模式存在著一個很大的問題, 就是其共享的本性!
原型中所有屬性都是被實例共享的,對于包含引用類型值(非基礎類型)的屬性來說,就很糟糕:

function Person(){}

Person.prototype={
    constructor:Person,
    name:"小明",
    age:20,
    friends:["sb","nb"]
};
var person1=new Person();
var person2=new Person();

person1.friends.push("Van");

console.log(person1.friends); //"sb,nb,Van"
console.log(person2.friends); //"sb,nb,Van"
console.log(person1.friends===person2.friends); //true

組合使用構造函數(shù)模式和原型模式

既然 構造函數(shù)模式的實例的方法和屬性沒有存在共享性,而原型模式的方法能保持方法的引用,那么結合起來就是 ,利用構造函數(shù)模式的方式來使得屬性沒有共享性,再利用原型模式使方法都能共用!

function Person(name,age){

    this.name=name;
    this.age=age;
    this.friends=["sb","nb"];
}

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

var person1=new Person("xm",20);

動態(tài)原型模式

但問題還是有的, 在其他OO開發(fā)人員看到這樣獨立的構造函數(shù)和原型的時候,第一感覺是懵逼的, 所以 動態(tài)原型模式 應運而生.

它把所有信息都封裝在構造函數(shù)中 , 然后在構造函數(shù)中初始化原型,這樣能夠同時保證之前組合方式的優(yōu)點.

function Person(name,age){
    this.name=name;
    this.age=age;
    
    if(typeof this.sayName != "function"){
        Person.prototype.sayName=function(){
            console.log(this.name);
        }
    }
    //這只會在sayName()方法不存在的情況下才會添加到原型
    //一旦添加到原型之后,再次創(chuàng)建實例的時候,原型已經(jīng)存在了這個方法.
    //只需要用if檢查一個方法之后全部寫到prototype中即可,不用每一個方法都寫一個if
}

寄生構造函數(shù)模式

跟簡單工廠模式只有 調(diào)用時new的差別:

簡單工廠:

function createPerson(name,age){
    var o =new Object();
    o.name=name;
    o.age=age;
    o.sayName=function(){
        //...
    }
    return o;
}
var person1=createPerson("a",20);

寄生構造函數(shù)模式:

function createPerson(name,age){
    var o =new Object();
    o.name=name;
    o.age=age;
    o.sayName=function(){
        //...
    }
    return o;
}
var person1=new createPerson("a",20);

這個模式可以在特殊情況下為對象創(chuàng)建構造函數(shù),例如:
我們想創(chuàng)建一個具有額外方法的特殊數(shù)組,因為不能簡單粗暴地直接修改Array構造函數(shù),所以可以使用一下模式:

function SpecialArray(){
    var values= new Array();
    values.push.apply(values,arguments);
    values.toPipedString=function(){
        return this.join("|");
    };
    return values;

}
var colors=new SpecialArray("red","blue","green");
colors.toPipedString(); 
// "red|blue|green"

穩(wěn)妥構造函數(shù)模式

  1. 不使用 this
  2. 不使用 new 操作符調(diào)用構造函數(shù)
function Person(name, age, job) {
    var o = new Object();
 
    // private members
    var nameUC = name.toUpperCase();

    // public members
    o.sayName = function() {
        alert(name);
    };
    o.sayNameUC = function() {
        alert(nameUC);
    };

    return o;
}

var person = Person("Nicholas", 32, "software Engineer");

person.sayName(); // "Nicholas"
person.sayNameUC(); // "NICHOLAS"

alert(person.name);  // undefined
alert(person.nameUC);  // undefined

除了調(diào)用本身提供的sayName()方法外,沒有別的方式可以訪問到其數(shù)據(jù)成員.故而這種方式提供了良好的安全性,適合在某些安全執(zhí)行環(huán)境中.

繼承

如何繼承

原型鏈

直接上代碼好理解


//先用 組合構造函數(shù)模式和原型模式 創(chuàng)建兩個類型:

function Animal(){
    this.name="animal";
}

Animal.prototype.getName=function(){
    return this.name;
}

function Dog(){
    this.age=2;
}
//用Animal的實例重寫Dog的原型
//Dog 將會獲得Animal全部的屬性和方法
//這就實現(xiàn)了繼承
Dog.prototype=new Animal();

Dog.prototype.getAge=function(){
    return this.age;
}

var adog=new Dog();
adog.getAge();      //2


但是你有沒有注意到 , 這樣實現(xiàn)繼承的時候,Dog的原型是一個Animal 的實例.
當Animal中存在一個引用值類型的屬性時, 會出現(xiàn)什么問題?

function Animal(){
    this.colors=["red","blue","green"];
}
function Dog(){}

Dog.prototype=new Animal();
Dog.prototype.constructor=Dog;
var adog=new Dog();
adog.colors.push("black");

var bdog=new Dog();
console.log(bdog);

// "red,blue,green,black"

由上面代碼可以看出,這導致了每個實例都對父類的引用值類型產(chǎn)生了共享性.

所以一般很少會單獨使用原型鏈來實現(xiàn)繼承.

那么 , 接下來祭出 構造函數(shù)

借用構造函數(shù)

function Animal(){
    this.colors=["red","blue"];
}
function Dog(){
    Animal.call(this);
}
var adog=new Dog();
adog.colors.push("black");

var bdog=new Dog();
console.log(bdog);
//"red,blue"

除了能解決 引用值類型的共享性問題之外, 相對于 單獨使用原型鏈還有一個很大的優(yōu)勢 , 也就是能在子類型構造函數(shù)中向 父類構造函數(shù)傳遞參數(shù)

function Animal(name){
    this.name=name;
}
function Dog(){
    Animal.call(this,"dog");

    //dog 屬性
    this.age=2;
}
var adog=new Dog();
adog.name //"dog"
adog.age //2

按照之前的 通過構造函數(shù)/原型鏈 來創(chuàng)建對象 的經(jīng)驗就知道 , 單獨使用某一種模式都會顧此失彼,最好就是雙劍合璧,那就是 組合繼承. 這也是最常用的繼承模式.

組合繼承

function Animal(name){
    this.name=name;
    this.colors=["red","blue"];
}

Animal.prototype.sayName=function(){
    console.log(this.name);
}
function Dog(name,age){
    Animal.call(this,name);
    this.age=age;
}

Dog.prototype=new Animal();
Dog.prototype.constructor=Dog;
Dog.prototype.sayAge=function(){
    console.log(this.age);
}
var adog=new Dog("a",2);
adog.colors.push("black");
adog.colors;    //"red,blue,black"
adog.sayName(); // "a"
adog.sayAge();  // 2

var bdog=new Dog("b",3);
bdog.colors;    //"red,blue"
bdog.sayName(); //"b"
bdog.sayAge();  //3

這樣adog和bdog 就能分別擁有自己的屬性,也能同時使用相同的方法.

原型式繼承

借助原型基于已有對象來創(chuàng)建新對象,同時不必因此創(chuàng)建自定義類型.

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

很顯然這跟上邊 使用原型鏈實現(xiàn)繼承的方式非常相似 , 它們之間 在調(diào)用的時候就差了一個 new,這是因為函數(shù)object()把傳進去的實例直接賦給了 臨時函數(shù)的 prototype.

ES5 中 新增了一個 Object.create() 方法規(guī)范花了原型式繼承

注意,這種原型式繼承,當包含著引用類型值時會時鐘共享相應的值,跟原型鏈實現(xiàn)繼承一樣

寄生式繼承

function createAnother(original){
    var clone =object(original);
    clone.sayHi=function(){
        console.log("hi");
    }
    return clone;
}

也就是通過淺拷貝原有對象,然后在內(nèi)部以某種方式增強對象,最后返回對象.

這跟單用構造函數(shù)模式類似, 但由于不能做到函數(shù)復用而降低效率.

寄生組合式繼承

現(xiàn)在先回顧一下組合繼承:

function Animal(name){
    this.name=name;
    this.colors=["red","blue"];
}

Animal.prototype.sayName=function(){
    console.log(this.name);
}
function Dog(name,age){
    Animal.call(this,name); //第二次: 創(chuàng)建Dog實例時候
    this.age=age;
}

Dog.prototype=new Animal(); //第一次 : 創(chuàng)建子類型原型的時候
Dog.prototype.constructor=Dog;
Dog.prototype.sayAge=function(){
    console.log(this.age);
}
var adog=new Dog("a",2);


雖說組合繼承這是最常用的繼承模式,但它有一些不足,就是無論什么情況下,都會調(diào)用兩次父類的構造函數(shù).

那么如何解決呢

接下來就是寄生組合式繼承登場了

function Animal(name){
    this.name=name;
    this.colors=["red","blue"];
}

Animal.prototype.sayName=function(){
    console.log(this.name);
}
function Dog(name,age){
    Animal.call(this,name); 
    this.age=age;
}

Dog.prototype=object(Animal.prototype); 
Dog.prototype.constructor=Dog;
Dog.prototype.sayAge=function(){
    console.log(this.age);
}
var adog=new Dog("a",2);

抽象出邏輯就是:

function inheritPrototype(subType,superType){
    var prototype=object(superType.prototype);
    prototype.constructor=subType;
    subType.prototype=prototype;
}

等價于

function Animal(name){
    this.name=name;
    this.colors=["red","blue"];
}

Animal.prototype.sayName=function(){
    console.log(this.name);
}
function Dog(name,age){
    Animal.call(this,name); 
    this.age=age;
}

// Dog.prototype=object(Animal.prototype); 
// Dog.prototype.constructor=Dog;
inheritPrototype(Dog,Animal);
Dog.prototype.sayAge=function(){
    console.log(this.age);
}
var adog=new Dog("a",2);

確定原型和實例的關系?

以上面的 adog 為例子 :
有兩個方法:

1. 使用 instanceof

 adog instanceof Object //true 
 adog instanceof Animal //true
 adog instanceof Dog    //true

2. 使用 isPrototypeOf()

 Object.prototype.isPrototypeOf(adog) //true 
 Animal.prototype.isPrototypeOf(adog) //true
 Dog.prototype.isPrototypeOf(adog)    //true
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容