JavaScript 創(chuàng)建對象 3 原型模式

我們創(chuàng)建的每個函數都有一個 prototype (原型)屬性,這個屬性是一個指針,指向一個對象玉罐,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。如果按照字面意思來理解脐雪,那么 prototype 就是通過調用構造函數而創(chuàng)建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法恢共。換句話說战秋,不必在構造函數中定義對象實例信息,而是可以將這些信息直接添加到原型對象中讨韭。

        function Person() {
        }

        Person.prototype.name = "Neo";
        Person.prototype.age = 29;
        Person.prototype.job = "Teacher";
        Person.prototype.sayName = function() {
            console.log(this.name);
        }

        var person1 = new Person();
        person1.sayName();
        var person2 = new Person();
        person2.name = "Toby";
        person2.age = 30;
        person2.sayName();
        console.log(person1.sayName === person2.sayName);

上面的代碼的輸出是:

代碼的輸出
  1. 理解原型對象

原型模式脂信,有點類似于 C++ 的繼承,每個對象都繼承了來自原型對象中的值透硝,這種繼承是只讀的狰闪,我們不能重寫原型對象中的值,如果我們在實例中對原型對象中的同名屬性賦值濒生,其實是在實例中添加了一個新的屬性埋泵,這個屬性會屏蔽來自原型對象中的屬性,而這點就類似于 C++ 中的覆蓋罪治。下面是實例:

        function Person() {
        }

        Person.prototype.name = "Neo";
        Person.prototype.age = 29;
        Person.prototype.job = "Teacher";
        Person.prototype.sayName = function() {
            console.log(this.name);
        }

        var person1 = new Person();
        person1.sayName(); // 來自原型的值
        var person2 = new Person();
        person2.name = "Toby";
        person2.age = 30;
        person2.sayName(); // 來自實例的值
        person1.sayName(); // 來自原型的值

輸出結果:

輸出結果

每當代碼讀取某個對象的某個屬性時丽声,都會執(zhí)行一次搜索,目標是具有給定名字的屬性觉义。搜索首先從對象實例本身開始雁社。如果在實例中找到了具有給定名字的屬性,則返回該屬性的值晒骇;如果沒有找到霉撵,則繼續(xù)搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性洪囤。如果在原型對象中找到了這個屬性徒坡,則返回該屬性的值。

我們已經知道瘤缩,如果我們在實例中對原型對象中的同名屬性賦值崭参,其實是在實例中添加了一個新的屬性,這個屬性會屏蔽來自原型對象中的屬性款咖。即使將這個屬性設置為 null何暮,也只會在實例中設置這個屬性奄喂,而不會恢復其指向原型的連接。不過海洼,使用 delete 操作符則可以完全刪除實例屬性跨新,從而讓我們可以重新訪問原型中的屬性,如下所示:

        function Person() {
        }

        Person.prototype.name = "Neo";
        Person.prototype.age = 29;
        Person.prototype.job = "Teacher";
        Person.prototype.sayName = function() {
            console.log(this.name);
        }

        var person2 = new Person();
        person2.name = "Toby";
        person2.age = 30;
        person2.sayName(); // 來自實例的值
        delete person2.name;
        person2.sayName(); // 來自原型的值

輸出結果:

輸出結果

使用 hasOwnProperty() 方法可以檢測一個屬性是存在于實例中坏逢,還是存在于原型中域帐。這個方法(不要忘記它是從 Object 繼承來的)只在給定屬性存在于對象實例中時,才會返回 true是整。下面是個實例:

        function Person() {
        }

        Person.prototype.name = "Neo";
        Person.prototype.age = 29;
        Person.prototype.job = "Teacher";
        Person.prototype.sayName = function() {
            console.log(this.name);
        }

        var person1 = new Person();
        person1.sayName(); // 來自原型的值
        console.log("person1.hasOwnProperty(\"name\") :", person1.hasOwnProperty("name"));

        var person2 = new Person();
        person2.name = "Toby";
        person2.age = 30;
        console.log("person2.hasOwnProperty(\"name\") :", person2.hasOwnProperty("name"));
        person2.sayName(); // 來自實例的值

        delete person2.name;
        person2.sayName(); // 來自原型的值
        console.log("person2.hasOwnProperty(\"name\") :", person2.hasOwnProperty("name"));

實例的輸出結果:

輸出結果
  1. 原型與 in 操作符

有兩種方式使用 in 操作符:單獨使用和在 for-in 循環(huán)中使用肖揣。在單獨使用時,in 操作符會在通過對象能夠訪問給定屬性時返回 true浮入,無論該屬性存在于實例中還是原型中龙优,下面是使用實例:

        function Person() {
        }

        Person.prototype.name = "Neo";
        Person.prototype.age = 29;
        Person.prototype.job = "Teacher";
        Person.prototype.sayName = function() {
            console.log(this.name);
        }

        var person1 = new Person();
        person1.sayName(); // 來自原型的值
        console.log("person1.hasOwnProperty(\"name\"): ", person1.hasOwnProperty("name"));
        console.log("\"name\" in person1: ", "name" in person1);

        var person2 = new Person();
        person2.name = "Toby";
        person2.age = 30;
        console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
        console.log("\"name\" in person2: ", "name" in person2);
        person2.sayName(); // 來自實例的值

        delete person2.name;
        person2.sayName(); // 來自原型的值
        console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
        console.log("\"name\" in person2: ", "name" in person2);
        console.log("\"gender\" in person2: ", "gender" in person2);

輸出結果:

輸出結果

同時使用 hasOwnProperty 方法和 in 操作符,就可以確定該屬性到底是存在于對象中事秀,還是存在于原型中彤断。

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

下面是其使用實例:

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

        function Person() {
        }

        Person.prototype.name = "Neo";
        Person.prototype.age = 29;
        Person.prototype.job = "Teacher";
        Person.prototype.sayName = function() {
            console.log(this.name);
        }

        var person1 = new Person();
        person1.sayName(); // 來自原型的值
        console.log("person1.hasOwnProperty(\"name\"): ", person1.hasOwnProperty("name"));
        console.log("\"name\" in person1: ", "name" in person1);
        console.log("hasPrototypeProperty(person1, \"name\"): ", hasPrototypeProperty(person1, "name"));

        var person2 = new Person();
        person2.name = "Toby";
        person2.age = 30;
        console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
        console.log("\"name\" in person2: ", "name" in person2);
        console.log("hasPrototypeProperty(person2, \"name\"): ", hasPrototypeProperty(person2, "name"));
        person2.sayName(); // 來自實例的值

        delete person2.name;
        person2.sayName(); // 來自原型的值
        console.log("person2.hasOwnProperty(\"name\"): ", person2.hasOwnProperty("name"));
        console.log("\"name\" in person2: ", "name" in person2);
        console.log("hasPrototypeProperty(person2, \"name\"): ", hasPrototypeProperty(person2, "name"));

其輸出結果如下:

輸出結果
  1. 更簡單的原型語法
        function Person() {
        }

        Person.prototype = {
            name: "Neo",
            age: 29,
            job: "Teacher",
            sayName: function() {
                console.log(this.name);
            }
        }
        // 以下代碼,確保通過 constructor 屬性還能像之前的語法那樣能夠訪問到適當的值
        Object.defineProperty(Person.prototype, "constructor", {
                                  enumerable: false,
                                  value: Person
                              });
  1. 原型的動態(tài)性

由于在原型中查找值的過程是一次搜索易迹,因此我們對原型對象所做的任何修改都能夠立即從實例上反映出來 —— 即使是先創(chuàng)建了實例后修改原型也照樣如此宰衙,下面是一個實例:

        function Person() {
        }

        var friend = new Person();

        Person.prototype.sayHi = function() {
            console.log("hi");
        }

        friend.sayHi();

輸出結果:

輸出結果

但是如果是重寫整個原型對象,那么情況就不一樣了睹欲。我們知道供炼,調用構造函數時會為實例添加一個指向最初原型 [[prototype]] 的指針,而把原型修改為另外一個對象就等于切斷了構造函數與最初原型之間的聯系窘疮。請記拙Ⅱ摺:實例中的指針僅指向原型,而不指向構造函數考余。下面是一個實例:

        function Person() {
        }

        var friend = new Person();

        Person.prototype = {
            name: "Neo",
            age: 29,
            job: "Teacher",
            sayName: function() {
                console.log(this.name);
            }
        }
        // 以下代碼先嬉,確保通過 constructor 屬性還能像之前的語法那樣能夠訪問到適當的值
        Object.defineProperty(Person.prototype, "constructor", {
                                  enumerable: false,
                                  value: Person
                              });

        friend.sayName();

這個例子會報錯:

例子報錯

因為 friend 指向的原型中不包含以 sayName 命名的函數。重寫原型對象切斷了現有原型與任何之前已經存在的對象實例之間的聯系楚堤;它們引用的仍然是最初的原型疫蔓。

  1. 原型對象的問題

原型模式也不是沒有缺點。首先身冬,他省略了為構造函數傳遞初始化參數這一環(huán)節(jié)衅胀,結果所有實例在默認情況下都將取得相同的屬性值。雖然這會在某種程度上帶來一些不方便酥筝,但還不是原型的最大問題滚躯。原型模式的最大問題是由其共享的本性所導致的。

原型中所有屬性是被很多實例共享的,這種共享對于函數非常合適掸掏。對于那些包含基本值的屬性倒也說得過去茁影,畢竟,通過在實例上添加一個同名屬性丧凤,可以隱藏原型中的對應屬性募闲。然而,對于包含引用類型值的屬性來說愿待,問題就比較突出了浩螺。請看下面這個例子:

        function Person() {
        }

        Person.prototype = {
            name: "Neo",
            age: 29,
            job: "Teacher",
            friends: ["Toby", "Tina"],
            sayName: function() {
                console.log(this.name);
            }
        }
        // 以下代碼,確保通過 constructor 屬性還能像之前的語法那樣能夠訪問到適當的值
        Object.defineProperty(Person.prototype, "constructor", {
                                  enumerable: false,
                                  value: Person
                              });

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

        person2.friends.push("Tim");

        console.log(person1.friends);
        console.log(person2.friends);
        console.log(person1.friends === person2.friends);

輸出結果:

輸出結果

在此仍侥,假如我們的初衷就是所有對象共享一個 friends 數組的話要出,是沒有問題的。但是現實中农渊,這樣的情況少之又少患蹂,因此,我們不應該單獨使用原型模式腿时,我們該怎么做呢况脆?請關注下一節(jié)中介紹的內容饭宾。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末批糟,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子看铆,更是在濱河造成了極大的恐慌徽鼎,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弹惦,死亡現場離奇詭異否淤,居然都是意外死亡,警方通過查閱死者的電腦和手機棠隐,發(fā)現死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門石抡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人助泽,你說我怎么就攤上這事啰扛。” “怎么了嗡贺?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵隐解,是天一觀的道長。 經常有香客問我诫睬,道長煞茫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮续徽,結果婚禮上蚓曼,老公的妹妹穿的比我還像新娘。我一直安慰自己炸宵,他們只是感情好辟躏,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著土全,像睡著了一般捎琐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裹匙,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天瑞凑,我揣著相機與錄音,去河邊找鬼概页。 笑死籽御,一個胖子當著我的面吹牛,可吹牛的內容都是我干的惰匙。 我是一名探鬼主播技掏,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼项鬼!你這毒婦竟也來了哑梳?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绘盟,失蹤者是張志新(化名)和其女友劉穎鸠真,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體龄毡,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡吠卷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了沦零。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祭隔。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖路操,靈堂內的尸體忽然破棺而出疾渴,到底是詐尸還是另有隱情伟叛,我是刑警寧澤西潘,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站旦袋,受9級特大地震影響祭钉,放射性物質發(fā)生泄漏瞄沙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望距境。 院中可真熱鬧申尼,春花似錦、人聲如沸垫桂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诬滩。三九已至霹粥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疼鸟,已是汗流浹背后控。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留空镜,地道東北人浩淘。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像吴攒,于是被迫代替她去往敵國和親张抄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容