JavaScript面向?qū)ο蟮某绦蛟O(shè)計(jì)——?jiǎng)?chuàng)建對(duì)象

創(chuàng)建對(duì)象

Object構(gòu)造函數(shù)或?qū)ο笞置媪縿?chuàng)建對(duì)象的缺點(diǎn)是使用同一個(gè)接口創(chuàng)建很多對(duì)象。

1.工廠模式

工廠模式抽象了創(chuàng)建具體對(duì)象的過(guò)程。用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象。

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("Icey",25,"Softerware Engineer");
var person2 = createPerson("Root",21,"Softerware Engineer");
2.構(gòu)造函數(shù)模式

構(gòu)造函數(shù)可以用來(lái)創(chuàng)建特定類型的對(duì)象。

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

var person1 = new Person("Icey",25,"Softerware Engineer");
var person2 = new Person("Root",21,"Softerware Engineer");
  • 沒(méi)有顯示的創(chuàng)建對(duì)象;
  • 直接將屬性和方法賦給了this對(duì)象;
  • 沒(méi)有return語(yǔ)句。
    按照慣例,構(gòu)造函數(shù)始終都應(yīng)該以一個(gè)大寫(xiě)字母開(kāi)頭。構(gòu)造函數(shù)本身也是函數(shù),只不過(guò)是用來(lái)創(chuàng)建對(duì)象而已。
    要?jiǎng)?chuàng)建Person的新實(shí)例,必須使用new操作符:
  • (1) 創(chuàng)建一個(gè)新對(duì)象
  • (2) 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象)
  • (3) 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
  • (4) 返回新對(duì)象

person1和person2分別保存這Person的一個(gè)不同的實(shí)例,這兩個(gè)對(duì)象都有一個(gè)constructor(構(gòu)造函數(shù))屬性,該屬性指向Person。

alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true

constructor屬性用來(lái)表示對(duì)象類型,檢測(cè)對(duì)象類型用instanceof操作符更可靠。

alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
alert(person2 instanceof Object);//true
alert(person2 instanceof Person);//true

創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來(lái)可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型;這正式構(gòu)造函數(shù)模式勝過(guò)工廠模式的地方。
以這種方式定義的構(gòu)造函數(shù)是定義在Global對(duì)象中的。

1.將構(gòu)造函數(shù)當(dāng)作函數(shù)

構(gòu)造函數(shù)與其他函數(shù)的唯一區(qū)別,就在于調(diào)用它們的方式。任何函數(shù),只有通過(guò)new操作符來(lái)調(diào)用,那它就可以作為構(gòu)造函數(shù)。

//當(dāng)作構(gòu)造函數(shù)使用
var person = new Person("Icey",25,"Softerware Engineer");
person.sayName(); //"Icey"

//作為普通函數(shù)調(diào)用
Person("Root",24,"Softerware Engineer"); //添加到window 
window.sayName(); //"Root"

//在另一個(gè)對(duì)象的作用域中調(diào)用
var o = new Object();
Person.call(o,"Icey",25,"Softerware Engineer");
o.sayName(); //"Icey"

2.構(gòu)造函數(shù)問(wèn)題

構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。
ECMAScript中的函數(shù)是對(duì)象,因此每定義一個(gè)函數(shù),也就是實(shí)例化了一個(gè)對(duì)象。

function Person(name,age,job) {
    this.name = name;
    this.age = age;
    this.job = job;
    //與聲明函數(shù)在邏輯上是等價(jià)的
    this.sayName = new Function("alert(this.name)"); 
}

每個(gè)Person實(shí)例都包含一個(gè)不同的Function實(shí)例。以這種方式創(chuàng)建函數(shù),會(huì)導(dǎo)致不同的作用域鏈和標(biāo)識(shí)符解析,但創(chuàng)建Function的機(jī)制任然是相同的。不同實(shí)例上的同名函數(shù)是不想等的。

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

可以通過(guò)把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外面來(lái)解決這個(gè)問(wèn)題。

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

var person1 = Person("Icey",25,"Softerware Engineer");
var person2 = Person("Root",21,"Softerware Engineer");

sayName包含的是一個(gè)指向函數(shù)的指針,因此person1和person2對(duì)象共享了全局作用域中定義的同一個(gè)sanName()函數(shù)。可是這樣,自定義的引用類型就沒(méi)有封裝性可言可,通過(guò)原型模式可以解決。

3.原型模式

創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,這個(gè)對(duì)象的用途是包含可以有特定類型的所有實(shí)例共享的屬性和方法。
prototype就是通過(guò)調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象實(shí)例的原型對(duì)象。使用原型對(duì)象的好處是可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法。

function Person() {
}

Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();
person1.sayName(); //"Icey"

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

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

1.理解原型對(duì)象

無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)情況下,所有原型對(duì)象會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性是一個(gè)指向prototype屬性所在函數(shù)的指針。Person.prototype.constructor指向Person。通過(guò)構(gòu)造函數(shù),可以繼續(xù)為原型對(duì)象添加屬性和方法。
創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型對(duì)象只會(huì)取得constructor屬性,至于其他方法都是從Object繼承而來(lái)。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對(duì)象。ECMA-262第5版管這個(gè)指針叫[[Prototype]]。雖然腳本中沒(méi)喲標(biāo)準(zhǔn)方式訪問(wèn)[[Prototype]],但Firefox、Safari和Chrome在每個(gè)對(duì)象上都支持一個(gè)屬性__proto__。這個(gè)連接存在與實(shí)例與構(gòu)造函數(shù)的原型對(duì)象之間,而不是存在與實(shí)例與構(gòu)造函數(shù)之間。

原型對(duì)象

雖然所有實(shí)現(xiàn)中都無(wú)法訪問(wèn)到[[Prototype]],但可以通過(guò)isPrototypeOf()方法來(lái)確定對(duì)象之間是否存在這種關(guān)系。

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

ECMAScript5增加了,Object.getPrototypeOf(),這個(gè)方法返回[[Prototype]]的值。

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

這在利用原型實(shí)現(xiàn)繼承的情況下非常重要。

每當(dāng)代碼讀取某個(gè)對(duì)象的屬性是,都會(huì)執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。搜索首先從對(duì)象實(shí)例本身開(kāi)始,如果實(shí)例中找到了,具有給定名字的屬性,則返回該屬性的值,如果沒(méi)有找到,則繼續(xù)搜索指針指向的原型對(duì)象,在原型對(duì)象中查找具有給定名字的屬性。如果原型對(duì)象中找到了,則返回該屬性的值。
原型最初值包含constructor屬性。

function Person() {
}

Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
person1.sayName(); //"Greg" 來(lái)自實(shí)例
person2.sayName(); //"Icey" 來(lái)自原型

當(dāng)為對(duì)象實(shí)例添加一個(gè)屬性時(shí),這個(gè)屬性就會(huì)屏蔽原型對(duì)象中保存的同名屬性。

function Person() {
}

Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
person1.sayName(); //"Greg" 來(lái)自實(shí)例
person2.sayName(); //"Icey" 來(lái)自原型、

delete person1.name;
alert(person1.name); //"Icey" 來(lái)自原型

使用delete操作符可以完全刪除實(shí)例屬性,能夠重新訪問(wèn)原型中的屬性。

hsaOwnProperty()方法可以檢測(cè)屬性存在與實(shí)例中,還是存在與原型中。

function Person() {
}

Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware 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" 來(lái)自實(shí)例
alert(person1.hasOwnProperty("name")); //true

alert(person1.name); //"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false

delete person1.name;
alert(person1.name);//"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
實(shí)例與原先的關(guān)系

2.原型與in操作符
有兩種方式使用in操作符:?jiǎn)为?dú)使用和在for-in循環(huán)中使用。在單獨(dú)使用時(shí),in操作符會(huì)在通過(guò)對(duì)象能夠訪問(wèn)給定屬性是返回true,無(wú)論改屬性存在于實(shí)例中還是原型中。

function Person() {
}

Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};

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

alert(person1.hasOwnProperty("name")); //false
alert("name" in person1);//true

person1.name = "Greg";
alert(person1.name);//"Greg" 來(lái)自實(shí)例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1);//true

alert(person1.name); //"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1);//true

delete person1.name;
alert(person1.name);//"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1);//true

···

確定屬性是原型中的屬性

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

Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var person = new Person();
alert(hasPrototypeProperty(person,"name")); //true
person.name = "Greg";
alert(hasPrototypeProperty(person,"name")); //false

使用for-in循環(huán)時(shí),返回的是所有能夠通過(guò)對(duì)象訪問(wèn)的、可枚舉的屬性,既包括存在與實(shí)例中的屬性,也包括存在于原型中的屬性。屏蔽了原型中不可枚舉的屬性([[Enumerable]]標(biāo)記為false的屬性)的實(shí)例也會(huì)在for-in循環(huán)中返回。

Object.keys()方法接收一個(gè)對(duì)象作為參數(shù),返回一個(gè)包含所有可枚舉屬性的字符串數(shù)組

function Person() {
}

Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys);//"name,age,job,sayName"

var p1 = new Person();
p1.name = "Rob";
p1.age = 22;
var p1keys = Object.keys(p1);
alert(p1keys);//"name,age"

Object.getOwnPropertyNames()可以得到所有實(shí)例屬性。

var keys = Object.getOwnPropertyNames(Person.prototype);
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);//"constructor,name,age,job,sayName"

3.更簡(jiǎn)單的原型語(yǔ)法
用一個(gè)包含所有屬性和方法的對(duì)象字面量來(lái)重寫(xiě)整個(gè)原型對(duì)象。

       function Person() {}
        Person.prototype = {
            name: "Icey",
            age: 25,
            job: "Softerware Engineer",
            sayName: function() {
                alert(this.name);
            }
        }

constructor屬性不再指向Person了。

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

如果constructor真的很重要,可以特意將它設(shè)置回適當(dāng)?shù)闹怠?/p>

        function Person() {}
        Person.prototype = {
            constructor: Person,
            name: "Icey",
            age: 25,
            job: "Softerware Engineer",
            sayName: function() {
                alert(this.name);
            }
        }

這種方式重設(shè)constructor屬性會(huì)導(dǎo)致它的[[Enumerable]]特性被設(shè)置為true。默認(rèn)情況下,原生的construcor屬性是不可枚舉的。

        //重設(shè)構(gòu)造函數(shù),只是用于ECMAScript兼容的瀏覽器
        Object.defineProperty(Person.prototype,"constructor",{
            enumerable: false,
            value: Person
        });

4.原型的動(dòng)態(tài)性

        var friend = new Person();
        Person.prototype.sayHi = function () {
            alert('hi');
        };
        friend.sayHi();//"hi"

實(shí)例中的指針指向原型, 而不指向構(gòu)造函數(shù)。

        function Person() {}
        
        var friend = new Person();

        Person.prototype = {
            constructor: Person,
            name: "Icey",
            age: 25,
            job: "Softerware Engineer",
            sayName: function() {
                alert(this.name);
            }
        };

        friend.sayName();//error

先創(chuàng)建Person的一個(gè)實(shí)例,有重寫(xiě)了其原型對(duì)象。

重寫(xiě)原型對(duì)象

重寫(xiě)原型對(duì)象切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對(duì)象實(shí)例之間的聯(lián)系;它們引用的任然是最初的原型。

5.原生動(dòng)態(tài)原型
原生的引用類型也采用這種原型模式創(chuàng)建,所有原生引用類型(Object、Array、String等)都在其構(gòu)造函數(shù)的原型上定義了方法。

        alert(typeof Array.prototype.sort);//"function"
        alert(typeof String.prototype.substring);//"function"

可以通過(guò)原生對(duì)象的原型定義新方法。

        String.prototype.satrtsWith = function (text) {
            return this.indexOf(text) == 0;
        };

        var msg = "Hello world!";
        alert(msg.satrtsWith("Hello"));//true

當(dāng)前環(huán)境中所有字符串都可以調(diào)用它。

6.原型對(duì)象的問(wèn)題
原型中的共享對(duì)于包含引用類型值的屬性來(lái)說(shuō),問(wèn)題比較突出。

function Person() {}

        Person.prototype = {
            constructor: Person,
            name: "Icey",
            age: 25,
            job: "Softerware Engineer",
            friends:["Shelby","Court"],
            sayName: function() {
                alert(this.name);
            }
        };

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

        person1.friends.push("Van");

        alert(person1.friends);//"Shelby,Court,Van"
        alert(person2.friends);//"Shelby,Court,Van"
        alert(person1.friends === person2.friends);//true
4.組合使用構(gòu)造函數(shù)模式和原型模式

創(chuàng)建自定義類型的最常見(jiàn)方式,就是組合使用構(gòu)造函數(shù)模式與原型模式。構(gòu)造函數(shù)模式用于定義實(shí)例屬性,原型模式由于定義方法和共享的屬性。

function Person(name,age,job) {
            this.name = name;
            this.age = age;
            this.job = job;
            this.friends = ["Shellby","Court"];
        }

        Person.prototype = {
            constructor: Person,
            sayName: function () {
                alert(this.name);
            }
        };
        
        var person1 = new Person("Icey",25,"Softerware Engineer");
        var person2 = new Person("Root",22,"Softerware Engineer");

        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
5.動(dòng)態(tài)原型模式

通過(guò)檢測(cè)某個(gè)應(yīng)該存在的方法是否有效,來(lái)決定是否需要初始化原型。

        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 friend = new Person("Icey",25,"Softerware Engineer");
        friend.sayName();
6.寄生構(gòu)造函數(shù)模式

創(chuàng)建一個(gè)函數(shù),封裝創(chuàng)建對(duì)象的代碼,再返回新創(chuàng)建的對(duì)象。

        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 friend = new Person("Icey",25,"Softerware Engineer");
        friend.sayName();//"Icey"

通過(guò)在構(gòu)造函數(shù)的末尾添加一個(gè)return語(yǔ)句,可以重寫(xiě)調(diào)用構(gòu)造函數(shù)時(shí)的返回值。
這個(gè)模式可以在特殊情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們像創(chuàng)建一個(gè)具有額外方法的特殊數(shù)組,由于不能直接修改Array構(gòu)造函數(shù),可以使用這個(gè)模式。

function SpecialArray() {
            //創(chuàng)建數(shù)組
            var values = new Array();
            //添加值
            values.push.apply(values,arguments);
            //添加方法
            values.toPipedString = function () {
                return this.join("|");
            };

            return values;
        }

        var colors = new SpecialArray("red","blue","green");
        alert(colors.toPipedString());//"red|blue|green"
7.穩(wěn)妥構(gòu)造函數(shù)模式

穩(wěn)妥對(duì)象沒(méi)有公共屬性,新創(chuàng)建的實(shí)例方法不引用this,不使用new操作符調(diào)用構(gòu)造函數(shù)。

        function Person(name,age,job) {
            //創(chuàng)建要返回的對(duì)象
            var o = new Object();
            //可以在這里定義私有變量和函數(shù)
            //添加方法
            o.sayName = function () {
                alert(name);
            };
            //返回對(duì)象
            return o;
        }

在以這種模式創(chuàng)建的對(duì)象中,除了使用sayName()方法之外,沒(méi)有其他辦法訪問(wèn)name的值。

          var friend = Person("Icey",25,"Softerware Engineer");
          friend.sayName();//"Icey"

它非常適合在某些安全執(zhí)行環(huán)境。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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