5、面向?qū)ο蟮某绦蛟O(shè)計1(JS高級筆記)

聲明:此文集下的文章都是看了Javascript高級程序設(shè)計所做的筆記进倍。此章對應(yīng)書中的第六章。

一购对、理解對象

JS早期開發(fā)中經(jīng)常使用以下兩種方式創(chuàng)建對象:

var person = new Object();
person.name = "Tom";
person.age = 23;
person.job = "Software Engineer";
person.sayName = function(){
    console.log(this.name);
}
var person = {
    name : "Tom",
    age : 29,
    job : "Software Engineer",
    sayName : function(){
        console.log(this.name);
    }
};

說明:這里創(chuàng)建了一個對象猾昆,此對象有三個屬性和一個方法。在第二種方式中骡苞,屬性名可以是name垂蜗、'name'或者"name",但是如果屬性是保留字或者以數(shù)字開頭則必須加引號解幽。

1.1 屬性類型

ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性贴见。
1、數(shù)據(jù)屬性

  • [[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性躲株,能否修改屬性的特性片部,或者能否把屬性修改為訪問器屬性,對于直接在對象上定義的屬性(如上)霜定,默認為true档悠,表示可以廊鸥。
  • [[Enumerable]]:表示能否通過for-in循環(huán)返回每個屬性,對于直接在對象上定義的屬性辖所,默認為true惰说。
  • [[Writable]]:表示能否修改屬性的值,對于直接在對象上定義的屬性奴烙,默認為true助被。
  • [[Value]]:包含這個屬性的數(shù)據(jù)值。讀取屬性值的時候切诀,從這個位置讀揩环;寫入屬性值的時候,把新值保存在這個位置幅虑。默認為undefined丰滑。

如果要修改屬性默認的特性,必須使用ECMAScript5中的Object.defineProperty()方法倒庵。接收三個參數(shù):屬性所在對象褒墨、屬性的名字和一個描述符對象,其中描述符對象必須是:configurable擎宝、enumberable郁妈、writable、value绍申,比如:

        var person = {};
        Object.defineProperty(person, 'name', {
            writable : false,
            value : "Tom"
        });
        console.log(person.name);
        person.name = "Jerry";
        console.log(person.name);

說明:這里我們將writable屬性設(shè)置為了false噩咪,標明此屬性的值不能被修改了,可以看到使用此方法就能精確控制每個屬性了极阅。這里沒有設(shè)置的屬性則取false(這和之前定義時不同)胃碾。當然在后面的代碼中我們還可以將此屬性改為true,但是如果屬性configurable被設(shè)置為了false則表示不能使用delete將某個屬性刪除掉了筋搏,同時也表示此屬性變?yōu)椴豢膳渲昧似桶伲慈绻渲昧?code>writable,則在后面的代碼中不能改變其值了奔脐。同時configurable本身也不能改變俄周。如:

        var person = {};
        Object.defineProperty(person, 'name', {
            configurable : false,
            value : "Tom"
        });
        //拋出錯誤
        Object.defineProperty(person, 'name', {
            configurable : true,
            value : "Tome"
        });

2、訪問器屬性(存取器屬性)
訪問器屬性不包括數(shù)據(jù)值髓迎,它們是一對兒gettersetter函數(shù)(不過峦朗,這兩個函數(shù)都不是必須的),有以下四個屬性:

  • [[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性竖般,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性茶鹃,對于直接在對象上定義的屬性涣雕,默認為true艰亮,表示可以。
  • [[Enumerable]]:表示能否通過for-in循環(huán)返回每個屬性挣郭,對于直接在對象上定義的屬性迄埃,默認為true
  • [[Get]]:在讀取屬性時調(diào)用的函數(shù)兑障。默認值為undefined侄非。
  • [[Set]]:在寫入屬性時調(diào)用的函數(shù)。默認值為undefined流译。

訪問器屬性不能直接定義逞怨,必須使用Object.defineProperty()定義。比如:

        var book = {
            _year: 2004,
            edition: 1
        };

        Object.defineProperty(book, "year", {
            get: function() {
                 return this._year;
            },
            set: function(newValue) {
                if (newValue > 2004) {
                    this._year = newValue;
                    this.edition += newValue - 2004;
                }
            }
        });

        book.year = 2005;
        alert(book.edition); //2
        book._year = 3000;
        alert(book._year);

說明:以上代碼中我們給對象book定義了兩個屬性_year福澡、edition叠赦,其中_year前面的下劃線是一種標記,表示只能通過對象方法訪問的屬性革砸,當然這里我們還是可以直接訪問的除秀。這里還定義了一個訪問器屬性year,包含getter算利、setter方法册踩,修改year的值會導致_year值改變,而edition變?yōu)?code>2效拭,起始訪問器屬性就像一種標志暂吉,可以在方法中做一些操作來達到某個目的,比如這里在setter方法中我們就讓edition的值隨著year的值改變而改變允耿。
定義gettersetter方法還可以這樣:

        var person = {
            name: "Tom",
            get age() {
                return 12;
            }
        }
        console.log(person.age);

而此時age也是對象的一個屬性借笙。

在這兩個方法之前一般使用的是兩個非標準的方法:__defineGetter__()、__defineSetter__()较锡,使用這兩個方法實現(xiàn)之前的代碼:

        var book = {
            _year: 2004,
            edition: 1
        };
          
        //legacy accessor support
        book.__defineGetter__("year", function(){
            return this._year;    
        });
        
        book.__defineSetter__("year", function(newValue){
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }    
        });
        book.year = 2005;
        alert(book.edition);   //2

1.2 定義多個屬性

使用Object.defineProperty()方法定義屬性太麻煩业稼,我們可以使用Object.defineProperties()一次性定義多個屬性,如下:

        var book = {};
        Object.defineProperties(book, {
            _year: {
                value: 2004
            },
            edition: {
                value: 1
            },
            year: {            
                get: function(){
                    return this._year;
                },
                set: function(newValue){
                    if (newValue > 2004) {
                        this._year = newValue;
                        this.edition += newValue - 2004;
                    }                  
                }            
            }        
        });
           
        book.year = 2005;
        book._year = 100;//無效蚂蕴,因為這樣定義的屬性是不能修改的
        console.log(book._year);
        console.log(book.edition);//2

1.3 讀取屬性的特性

使用ECMAScript 5Object.getOwnPropertyDescriptor()方法低散,可以取得給定屬性的描述符。接收兩個參數(shù):屬性所在對象和要讀取其描述符的屬性名稱骡楼。返回值是一個對象熔号,如果是訪問器屬性,則這個對象的屬性有configurable鸟整、enumberable引镊、get、set;如果是數(shù)據(jù)屬性弟头,則這個對象的屬性有configurable吩抓、enumberable、writable赴恨、value疹娶。如:

        var book = {};
        
        Object.defineProperties(book, {
            _year: {
                value: 2004
            },
            
            edition: {
                value: 1
            },
            
            year: {            
                get: function(){
                    return this._year;
                },
                
                set: function(newValue){
                    if (newValue > 2004) {
                        this._year = newValue;
                        this.edition += newValue - 2004;
                    }                  
                }            
            }        
        });
           
        var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
        alert(descriptor.value);          //2004
        alert(descriptor.configurable);   //false
        alert(typeof descriptor.get);     //"undefined"
        
        var descriptor = Object.getOwnPropertyDescriptor(book, "year");
        alert(descriptor.value);          //undefined
        alert(descriptor.enumerable);     //false
        alert(typeof descriptor.get);     //"function"

二、創(chuàng)建對象

雖然使用Object構(gòu)造函數(shù)或?qū)ο笞置媪慷伎梢杂脕韯?chuàng)建單個對象伦连,但這些方式使用同一個接口創(chuàng)建很多對象雨饺,會產(chǎn)生大量的重復(fù)代碼。下面看幾種其他的方式惑淳。

2.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("Nicholas", 29, "Software Engineer");
        var person2 = createPerson("Greg", 27, "Doctor");
        
        person1.sayName();   //"Nicholas"
        person2.sayName();   //"Greg"

說明:這里使用函數(shù)來封裝以特定接口創(chuàng)建對象的細節(jié)额港,這中方式雖然解決了相似對象的問題,但是卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)汛聚。

2.2 構(gòu)造函數(shù)模式

可以使用構(gòu)造函數(shù)將前面例子重寫:

        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
                alert(this.name);
            };    
        }
        
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");

說明:這里使用Person()函數(shù)取代了createPerson()函數(shù)锹安,有幾點不同的地方:

  • 沒有顯式地創(chuàng)建對象
  • 直接將屬性和方法賦給this對象
  • 沒有return語句

這里要創(chuàng)建Person新實例,必須使用new操作符倚舀,上述代碼創(chuàng)建了兩個Person實例叹哭,都有一個相同的construtor(構(gòu)造函數(shù))屬性:

console.log(person1.construtor == Person);//true
console.log(person2.construtor == Person);//true

對象的construtor屬性最初是來標識對象類型的,但是痕貌,在檢測對象類型時风罩,還是使用instanceof操作符更可靠一些。

console.log(person1 instanceof Object);//true
console.log(person1 instanceof Person);//true
console.log(person2 instanceof Object);//true
console.log(person2 instanceof Person);//true

這里所有對象均繼承自Object舵稠。

2.2.1 將構(gòu)造函數(shù)當作函數(shù)

任何函數(shù)超升,只要通過new操作符來調(diào)用,那它就可以作為構(gòu)造函數(shù)哺徊,而任何函數(shù)室琢,如果不通過new操作符來調(diào)用,那它跟普通的函數(shù)也一樣落追。如前面定義的Person構(gòu)造函數(shù):

var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName();   //"Nicholas"
        
Person("Greg", 27, "Doctor");  //adds to window
window.sayName();   //"Greg"
        
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName();    //"Kristen"

說明:當在全局作用域中調(diào)用一個函數(shù)時盈滴,this對象總是指向Global對象(在瀏覽器中就是window對象)。也可以使用call()apply()調(diào)用轿钠。

2.2.2 構(gòu)造函數(shù)的問題

構(gòu)造函數(shù)模式的主要問題就是每個方法都要在每個實例上重新創(chuàng)建一遍巢钓。因為函數(shù)就是對象,因此沒定義一個函數(shù)疗垛,也就是實例化了一個對象症汹,于是構(gòu)造函數(shù)可以定義為這樣:

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

說明:不同實例上的同名函數(shù)sayName不是相等的。這里我們可以將函數(shù)定義在構(gòu)造函數(shù)之外:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
  alert(this.name);
}
var person1 = new Person("Tom", 29, "Software Engineer");
var person2 = new Person("Jerry", 28, "Doctor");

說明:此時兩個實例中的同名函數(shù)sayName就是同一個了贷腕,但是新問題又來了:在全局作用域中定義的函數(shù)實際上只能被某個對象調(diào)用背镇,這讓全局作用域有點名不副實咬展,而且,如果要定義很多方法瞒斩,那就得定義多個全局函數(shù)挚赊,那自定義的引用類型就沒有封裝性了。這需要使用原型模式解決济瓢。

2.2.3 原型模式

我們創(chuàng)建了每個函數(shù)都有一個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"

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

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

說明:我們將sayName()方法和所有屬性都直接添加到了Personprototype屬性中箕宙,構(gòu)造函數(shù)變成了空函數(shù),當然還是可以通過構(gòu)造函數(shù)來創(chuàng)建新對象铺纽,而且新對象還會具有相同的屬性和方法柬帕。

1、理解原型對象

1

說明:無論什么時候狡门,只要創(chuàng)建一個新函數(shù)陷寝,就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性,這個屬性指向函數(shù)的原型對象其馏,如圖中的構(gòu)造函數(shù)Person凤跑、實例person1、person2其中的prototype屬性都指向原型對象叛复。創(chuàng)建了自定義的構(gòu)造函數(shù)之后仔引,其原型對象默認只會取得construtor屬性,至于其他方法褐奥,都是從Object繼承或我們自己添加的咖耘。一般使用屬性__proto__訪問原型對象。
雖然在所有實現(xiàn)中都無法訪問到[[Prototype]]撬码,但是可以通過isPrototypeOf()方法來確定對象之間是否存在這種關(guān)系儿倒,本質(zhì)上講,如果[[Prototype]]指向調(diào)用isPrototypeOf()方法的對象(Person.prototype)耍群,那么這個方法就返回true

alert(Person.prototype.isPrototypeOf(person1));//true
alert(Person.prototype.isPrototypeOf(person2));//true

ECMAScript 5中增加了一個新方法义桂,叫Object.getPrototypeOf(),在所有支持的實現(xiàn)中蹈垢,這個方法返回[[Prototype]]的值慷吊,如:

alert(Object.getPrototypeOf(person1) == Person.prototype);//true
alert(Object.getPrototypeOf(person1).name);//"Tom"

說明:每當代碼讀取某個對象的某個屬性時,都會執(zhí)行搜索曹抬,目標是具有給定名字的屬性溉瓶。搜索首先從對象實例本身開始。如果在實例中找了具有給定名字的屬性,則返回該屬性的值堰酿;如果沒有疾宏,則繼續(xù)搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性触创,如果找到則返回相關(guān)的值坎藐。
雖然可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值哼绑。如果我們在實例中添加了一個屬性岩馍,而該屬性與實例原型中的一個屬性同名,那我們就在實例中創(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 = "Greg";
alert(person1.name);   //"Greg"來自實例
alert(person2.name);   //"Nicholas"來自原型

說明:在實例中增加一個和原型對象中同名屬性茂浮,只會屏蔽原型中的屬性双谆,并不會覆蓋。當在搜素時席揽,如果在實例中搜索到了相關(guān)屬性顽馋,則不會繼續(xù)向原型中搜索了。當然使用delete方法可以完全刪除實例中的某個屬性幌羞,從而放我們能夠重新訪問原型中的屬性趣避。

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

使用hasOwnProperty()方法可以檢測一個屬性是存在于實例中,還是存在于原型中新翎,這個方法是從Object繼承過來的程帕。只在給定屬性存在于對象實例中,才會返回true地啰。

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();
alert(person1.hasOwnProperty("name"));//false

person1.name = "Greg";
alert(person1.name);   //"Greg"來自實例
alert(person1.hasOwnProperty("name"));//true

alert(person2.name);   //"Nicholas"來自原型
alert(person2.hasOwnProperty("name"));//false

delete person1.name;
alert(person1.name); //"Nicholas"來自原型
alert(person1.hasOwnProperty("name"));//false
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愁拭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亏吝,更是在濱河造成了極大的恐慌岭埠,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔚鸥,死亡現(xiàn)場離奇詭異惜论,居然都是意外死亡,警方通過查閱死者的電腦和手機止喷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門馆类,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弹谁,你說我怎么就攤上這事乾巧【湎玻” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵沟于,是天一觀的道長咳胃。 經(jīng)常有香客問我,道長旷太,這世上最難降的妖魔是什么展懈? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮供璧,結(jié)果婚禮上标沪,老公的妹妹穿的比我還像新娘。我一直安慰自己嗜傅,他們只是感情好,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布檩赢。 她就那樣靜靜地躺著吕嘀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贞瞒。 梳的紋絲不亂的頭發(fā)上偶房,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音军浆,去河邊找鬼棕洋。 笑死,一個胖子當著我的面吹牛乒融,可吹牛的內(nèi)容都是我干的掰盘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼赞季,長吁一口氣:“原來是場噩夢啊……” “哼愧捕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起申钩,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤次绘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撒遣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邮偎,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年义黎,在試婚紗的時候發(fā)現(xiàn)自己被綠了禾进。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡廉涕,死狀恐怖命迈,靈堂內(nèi)的尸體忽然破棺而出贩绕,到底是詐尸還是另有隱情,我是刑警寧澤壶愤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布淑倾,位于F島的核電站,受9級特大地震影響征椒,放射性物質(zhì)發(fā)生泄漏娇哆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一勃救、第九天 我趴在偏房一處隱蔽的房頂上張望碍讨。 院中可真熱鬧,春花似錦蒙秒、人聲如沸勃黍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽覆获。三九已至,卻和暖如春瓢省,著一層夾襖步出監(jiān)牢的瞬間弄息,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工勤婚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留摹量,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓馒胆,卻偏偏與公主長得像缨称,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子祝迂,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

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