JS 創(chuàng)建對象(工廠模式耕驰、構造函數(shù)模式、原型模式录豺、混合模式朦肘、動態(tài)原型模式,寄生構造函數(shù)模式双饥,穩(wěn)妥構造函數(shù)模式)

構造函數(shù)媒抠,原型對象,實例化對象的關系:

  • 每個函數(shù)(包括構造函數(shù))都有一個原型對象(prototype)
  • 原型對象都包含一個指向構造函數(shù)的指針(constructor)
  • 原型對象上的屬性和方法咏花,都可以被構造函數(shù)的實例化對象所繼承(所有實例化對象共享一個原型對象)
  • 實例化對象又都包含一個指向構造函數(shù)的原型對象的內(nèi)部指針(__proto__)趴生,(p1.__proto__ === Person.prototype)(p1.__proto__.constructor === Person.prototype.constructor)(p1.__proto__.constructor === Person)
  • 實例化對象的 constructor 屬性指向構造函數(shù)(p1.constructor === Person)
image.png
關系圖.png

prototype 是函數(shù)才有的屬性,切記昏翰,切記

__proto__ 是每個對象(包括函數(shù))都有的屬性

1.工廠模式

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("a1", 12, "程序");
var person2 = createPerson("b1", 18, "銷售");
console.log(person1.name) // a1
console.log(person2.age) // 18

2.構造函數(shù)模式

構造函數(shù)苍匆,是用來創(chuàng)建對象的函數(shù),本質(zhì)上也是函數(shù)
任何函數(shù)棚菊,只要通過 new 操作符來調(diào)用浸踩,那它就可以作為構造函數(shù) ;
任何函數(shù)统求,如果不通過 new 操作符來調(diào)用检碗,那它跟普通函數(shù)也沒有什么兩樣。

// 創(chuàng)建函數(shù)
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
}

// 當作構造函數(shù)使用
var person = new Person('Nicholas', 29, 'Software Engineer'); // this --> person
person.sayName(); // 'Nicholas'
這里的 Person 就稱為構造函數(shù)码邻,person 稱為 Person 函數(shù)對象的一個實例(復制品)


// 當作普通函數(shù)調(diào)用
Person('Greg', 27, 'Doctor'); // this --> window
window.sayName(); // 'Greg'

“構造函數(shù)模式” 與 “工廠模式” 對比

* 1折剃、沒有顯式地創(chuàng)建對象
* 2、直接將屬性和方法賦給了 this 對象
* 3像屋、沒有 return 語句
* 4怕犁、構造函數(shù)都應該以 一個大寫字母開頭 function Person(){...},而非構造函數(shù)則應該以一個小寫字母開頭 function person(){...}
* 5、使用 new 創(chuàng)建對象
* 6因苹、能夠識別對象(這正是構造函數(shù)模式勝于工廠模式的地方)
構造函數(shù)模式的問題

使用構造函數(shù)的主要問題就是每個方法都要在每個實例上重新創(chuàng)建一遍

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();

person1 和 person2 都有一個名為 sayName() 的方法苟耻,但是這兩個方法不是同一個 Function 的實例

console.log(person1.sayName == person2.sayName) // false

然而,創(chuàng)建兩個完成同樣任務的 Function 實例的確沒有必要扶檐,大可像下面這樣凶杖,通過把函數(shù)定義轉(zhuǎn)移到構造函數(shù)外部來解決這個問題

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

console.log(person1.sayName == person2.sayName) // true

在這個例子中,我們把 sayName() 函數(shù)的定義轉(zhuǎn)移到了構造函數(shù)外部款筑。而在構造函數(shù)內(nèi)部智蝠,我們將 sayName 屬性設置成等于全局的 sayName 函數(shù)。這樣一來奈梳,由于 sayName 包含的是一個指向函數(shù)的指針杈湾,因此 person1 和 person2 對象就共享了在全局作用域中定義的同一個 sayName() 函數(shù)。這樣做確實解決了兩個函數(shù)做同一件事的問題攘须,可是新問題又來了:在全局作用域中定義的函數(shù)實際上只能被某個對象調(diào)用漆撞,這讓全局作用域有點名不副實。而更讓人無法接受的是:如果對象需要定義很多方法于宙,那么就要定義很多個全局函數(shù)浮驳,于是我們這個自定義的引用類型就絲毫沒有封裝性可言了。好在這些問題可以通過使用原型模式來解決捞魁。

3.原型模式

在 JS 中至会,無論什么時候,只要你創(chuàng)建了一個新函數(shù)谱俭,就會根據(jù)一組特定的規(guī)定為該函數(shù)創(chuàng)建一個 prototype 的屬性奉件,這個屬性指向函數(shù)的原型對象。而在默認情況下,所有的原型對象都會自動獲得一個 constructor (構造函數(shù))屬性,這個屬性包含一個指向 prototype 屬性所在函數(shù)的指針

使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法鸯两,換句話說,不必在構造函數(shù)中定義對象實例的信息窃这,而是可以將這些信息直接添加到原型對象中

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"

var person2 = new Person();
person2.sayName(); // "Nicholas"

alert(person1.sayName == person2.sayName); // true

在此,我們將 sayName() 方法和所有屬性直接添加到了 Person 的 prototype 屬性中征候,構造函數(shù)變成了空函數(shù)杭攻。與構造函數(shù)模式不同的是,新對象的這些屬性和方法是由所有實例共享的疤坝。換句話說兆解,person1 和 person2 訪問的都是同一組屬性和同一個 sayName() 函數(shù)

雖然可以通過對象實例訪問保存在原型中的屬性和方法,但卻不能通過對象實例重寫原型中的屬性和方法(可以通過對象的 __proto__ 重寫)
如果我們在實例中添加了一個屬性和方法跑揉,而該屬性和方法與實例原型中的同名锅睛,那我們就在實例中創(chuàng)建該屬性和方法埠巨,該屬性和方法將會屏蔽原型中的那個屬性和方法

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();
var person2 = new Person();

person1.name = "Grag";
person1.sayName = function(){
    alert(this.age);
};

alert(person1.name); // "Grag"---來自實例
person1.sayName(); // "29"---來自實例

alert(person2.name); // "Nicholas"--來自原型
person2.sayName(); // "Nicholas"---來自原型

通過使用 delete 操作符可以完全刪除實例屬性

var person1 = new Person();
var person2 = new Person();

person1.name = "Grag";
alert(person1.name); // "Grag"---來自實例
alert(person2.name); // "Nicholas"--來自原型

delete person1.name;
alert(person1.name); // "Nicholas"--來自原型

通過使用 hasOwnProperty() 方法,可以檢測一個屬性是存在實例中现拒,還是存在于原型中

alert(person1.hasOwnProperty("name")); // false
alert(hasPrototypeProperty("person", "name")); // true

person1.name = "Grag";
alert(person1.name); // "Grag"---來自實例
alert(person1.hasOwnProperty("name")); // true
alert(hasPrototypeProperty("person", "name")); // false
alert(person2.name); // "Nicholas"--來自原型
alert(person2.hasOwnProperty("name")); // false

delete person1.name;
alert(person1.name) // "Nicholas"--來自原型
alert(person1.hasOwnProperty("name")); // false

更簡單的原型寫法

用一個包含所有屬性和方法的對象字面量來重寫整個原型對象

function Person(){}
Person.prototype = {
    // 由于字面量寫法辣垒,等于以一個字面量形式創(chuàng)建的新對象,本質(zhì)上完全重寫了默認的 prototype 對象印蔬,
    // 導致 constructor (構造函數(shù)的指針)不再指向 Person 了勋桶,
    // 因此 constructor 屬性也就變成了新對象的 constructor 屬性(指向 Object 構造函數(shù)),所以我們需要特意將它設置適當?shù)闹?    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function(){
        alert(this.name);
    }
}

var person1 = new Person();
alert(person1.name) // "Nicholas"

原型對象的問題

首先它省略了為構造函數(shù)傳遞初始化參數(shù)這一環(huán)節(jié)侥猬,結(jié)果所有實例在默認情況下都將取得相同的屬性值例驹,但還不是原型的最大問題,其實最大的問題還是原型中所有屬性是被很多實例共享的退唠,這種共享對于函數(shù)非常合適鹃锈。對于那些包含基本值的屬性倒也說得過去,畢竟通過在實例上添加一個同名屬性瞧预,可以隱藏原型中的對應屬性屎债。然而,對于包含引用類型值的屬性來說松蒜,就有問題了---全部共享一個屬性(無論怎樣修改扔茅,其他實例的值都是一樣的)

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

構造函數(shù)模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性秸苗,結(jié)果,每個實例都會有自己的一份實例屬性的副本运褪,但同時又共享著對方方法的引用惊楼,這種混成模式還支持向構造函數(shù)傳遞參數(shù)

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("Grag", 27, "Doctor");

person1.friends.push("Van");

alert(person1.friends); // Shelby,Court,Van
alert(person2.friends); // Shelby,Court
alert(person1.friends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true

這種構造函數(shù)與原型混成的模式,是目前在 ECMAScript 中使用最廣泛秸讹,認同度最高的一種創(chuàng)建自定義類型的方法檀咙。可以說璃诀,這是用來定義引用類型的一種默認模式

5.動態(tài)原型模式

有其他 OO 語言經(jīng)驗的開發(fā)人員在看到獨立的構造函數(shù)和原型時弧可,很可能會感到非常困惑。動態(tài)原型模式正是致力于解決這個問題的一個方案劣欢,它把所有信息都封裝在了構造函數(shù)中棕诵,而通過在構造函數(shù)中初始化原型(僅在必要的情況下),又保持了同時使用構造函數(shù)和原型的優(yōu)點凿将。換句話說校套,可以通過檢香某個應該存在的方法是否有效,來決定是否需要初始化原型

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);
        }
    }
}
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName();

這里只在 sayName() 方法不存在的情況下牧抵,才會將它添加到原型中笛匙。這段代碼只會在初次調(diào)用構造函數(shù)時才會執(zhí)行侨把。此后,原型已經(jīng)完成初始化妹孙,不需要在做什么修改了秋柄。
使用動態(tài)原型模式時,不能使用對象字面量重寫原型蠢正。

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

類似于工廠模式與構造函數(shù)模式結(jié)合體

function Person(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 = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"

構造函數(shù)在不返回值的情況下华匾,默認會返回新對象的實例。而通過在構造函數(shù)的末尾添加一個 return 語句机隙,可以重寫調(diào)用構造函數(shù)時返回的值

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

所謂穩(wěn)妥對象蜘拉,指的是沒有公共屬性,而且其方法也不引用 this 對象有鹿,不使用 new 操作符調(diào)用構造函數(shù)旭旭,穩(wěn)妥對象最適合在一些安全的環(huán)境中(這些環(huán)境中會禁止使用 this 和 new),或者在防治數(shù)據(jù)被其他應用程序改動時使用

function Person(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 = Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"

這種模式創(chuàng)建的對象中葱跋,除了使用 sayName() 方法之外持寄,沒有其他辦法訪問 name 的值

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市娱俺,隨后出現(xiàn)的幾起案子稍味,更是在濱河造成了極大的恐慌,老刑警劉巖荠卷,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件模庐,死亡現(xiàn)場離奇詭異,居然都是意外死亡油宜,警方通過查閱死者的電腦和手機掂碱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慎冤,“玉大人疼燥,你說我怎么就攤上這事∫系蹋” “怎么了醉者?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長披诗。 經(jīng)常有香客問我撬即,道長,這世上最難降的妖魔是什么藤巢? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任搞莺,我火速辦了婚禮,結(jié)果婚禮上掂咒,老公的妹妹穿的比我還像新娘才沧。我一直安慰自己迈喉,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布温圆。 她就那樣靜靜地躺著挨摸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岁歉。 梳的紋絲不亂的頭發(fā)上得运,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音锅移,去河邊找鬼熔掺。 笑死,一個胖子當著我的面吹牛非剃,可吹牛的內(nèi)容都是我干的置逻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼备绽,長吁一口氣:“原來是場噩夢啊……” “哼券坞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肺素,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤恨锚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后倍靡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猴伶,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年菌瘫,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜗顽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡雨让,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忿等,到底是詐尸還是另有隱情栖忠,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布贸街,位于F島的核電站庵寞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏薛匪。R本人自食惡果不足惜捐川,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逸尖。 院中可真熱鬧古沥,春花似錦瘸右、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盹沈,卻和暖如春龄章,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乞封。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工做裙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肃晚。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓锚贱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親陷揪。 傳聞我的和親對象是個殘疾皇子惋鸥,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容