官术网_书友最值得收藏!

4.4 創(chuàng)建對象

JS中的function除了前面介紹的兩種用法之外,還有一種非常重要的用法,那就是創(chuàng)建object實例對象。關于object類型對象的具體內容,本書將在下一章詳細講解,本節(jié)主要介紹如何使用function來創(chuàng)建object對象以及創(chuàng)建時的一些細節(jié)問題。

4.4.1 創(chuàng)建方式

使用function對象創(chuàng)建object類型對象的方法非常簡單,直接在function對象前面使用new關鍵字就可以了,例如下面的例子。

    function F(){
        this.v = 1;
    }
    var obj = new F();  //創(chuàng)建F類型對象obj
    console.log(obj.v); //1

這個例子中,首先定義了一個function類型的對象F,然后使用F創(chuàng)建了object類型的對象obj,最后在控制臺打印出obj對象的v屬性的值。

使用function(例如F)創(chuàng)建object類型的對象(例如obj),只需要在function對象(F)前加new關鍵字就可以了。也就是說,對于一個function類型的對象,如果調用時前面沒有new關鍵字,那么調用方法處理業(yè)務,如果前面有new關鍵字,那么用來創(chuàng)建對象。當然,創(chuàng)建對象時函數(shù)體也會被執(zhí)行,對于具體創(chuàng)建的步驟下一節(jié)將詳細介紹。

其實,經常使用的Array、Date等對象也都是function類型,可以使用new關鍵字來創(chuàng)建相應的object類型的對象實例。

為了區(qū)分主要用于處理業(yè)務的function和主要用于創(chuàng)建對象的function,一般會將主要用于創(chuàng)建對象的function的首字母大寫而將主要用于處理業(yè)務的function的首字母小寫。但這只是人為區(qū)分,實際使用時并沒有什么影響。

4.4.2 創(chuàng)建過程

使用function創(chuàng)建object類型對象的過程可以簡單地分為以下兩步(可以這么理解,實際創(chuàng)建過程要復雜一些)。

1)創(chuàng)建function對應類型的空object類型對象。

2)將function的函數(shù)體作為新創(chuàng)建的object類型對象的方法來執(zhí)行(主要目的是初始化object對象)。

例如下面的例子。

    function Car(color, displacement){
        this.color = color;
        this.displacement = displacement;
    }
    var car = new Car("black", "2.4T");
    console.log(car.color+", "+car.displacement);    //black, 2.4T

這個例子中,首先創(chuàng)建了function類型的Car,然后使用它新建object類型的car實例對象。在新建car對象時首先會新建Car類型的空對象car,然后再將Car函數(shù)作為新建對象的方法來調用,從而初始化新建的car實例對象,相當于下面的過程。

    function Car(){}
    var car = new Car();
    car.init = function (color, displacement){
        this.color = color;
        this.displacement = displacement;
    };
    car.init("black", "2.4T");
    console.log(car.color+", "+car.displacement);    //black, 2.4T

上述示例將原來Car對象中的函數(shù)體的內容放到新建的car的init方法中,在使用Car創(chuàng)建完car實例對象后,再調用init方法初始化,這種方式和前面例子中將初始化內容放到Car的函數(shù)體內的效果是完全相同的。

需要特別注意的是,創(chuàng)建過程的第二步,也就是說,在使用function對象新建object對象時依然會執(zhí)行function的函數(shù)體。通過下面的例子可以更加直觀地看到這一點。

    var name="和珅";
    function Sikuquanshu(){
      name="紀曉嵐";
    }
    console.log(name);               //和珅
    var skqs = new Sikuquanshu();
    console.log(name);               //紀曉嵐

這個例子中存在一個全局變量name,原值為“和珅”,在函數(shù)Sikuquanshu內部將其改為“紀曉嵐”,在上述代碼中并沒有直接執(zhí)行此函數(shù),但在使用它創(chuàng)建skqs對象時其函數(shù)體得到了執(zhí)行,這從創(chuàng)建skqs對象前后打印出的內容就能看出來,全局變量name被修改了。在使用function對象創(chuàng)建實例對象時一定要注意這一點。

理解了對象創(chuàng)建的過程就可以理解為什么在構造函數(shù)(例如Car)中使用this可以將屬性添加到新創(chuàng)建的對象上。因為這時的函數(shù)體就相當于新創(chuàng)建的對象的一個方法,方法中的this指的就是新創(chuàng)建的對象自身,給this賦值就是給新創(chuàng)建的對象賦值,因此在function對象中使用this就可以給新創(chuàng)建出來的對象添加屬性,就像本書第一個例子中的Car方法的“this.color = color; ”語句,這條語句會給新創(chuàng)建的car對象添加color屬性并將color參數(shù)的值賦給它。對于這一點,在后面講到object的屬性時還會做進一步介紹。

如果覺得這里不容易理解也沒關系,因為還沒有學習object類型對象和this關鍵字的具體含義,可以先繼續(xù)往下看,等學習完相關內容之后再返回來理解就容易了。

多知道點

對象在內存中是怎么保存的

前面主要介紹了對象屬性、函數(shù)參數(shù)以及變量在內存中的保存方法,那么對象自身在內存中怎么保存呢?一般來說主要分為兩大類方法。一類方法是直接將名稱(或者名稱的哈希值)和值(可能是實際內容也可能是地址)全部保存進去,這時可以使用類似JSON的格式。這種方式使用起來會比較靈活,但是在執(zhí)行效率上會存在些問題,因為查找屬性的過程會比較費時間。另一類方法是使用類似C語言中結構體的方式來保存,即只保存值而不保存變量,變量通過偏移量來查找。例如,{width:10, length:15}這個對象可以直接用8個字節(jié)來表示,width的偏移量是0, length的偏移量是4。這樣在使用時不需要查找,直接按偏移量調用,效率就比第一類方法高,C++中的類就采用這種方法。但是,JS中的對象有些特殊,因為它的對象的屬性是不確定的,而且可以隨時修改(例如添加新的屬性),這對于編程來說會很方便,但是對于按照第二類方法來保存對象數(shù)據(jù)來說就有點麻煩了。具體使用哪種方法來保存對象數(shù)據(jù)是由具體引擎的設計者來定的,不同的引擎可能會采用不同的處理方法。早期的JS引擎以第一類方法為主,而新的引擎為了提高效率也有采用第二類方法來保存的。

對于第二類方法來說,用同一個function創(chuàng)建的實例對象具有相同的結構,這時就可以將其看作同一類型(類似于C語言中的同一個結構體)來處理,但所創(chuàng)建的對象自身的屬性也是可以修改的,在修改之后就成了新的類型。

在使用function創(chuàng)建對象時需要注意一種特殊情況,當function的函數(shù)體返回一個對象類型時,使用new關鍵字創(chuàng)建的對象就是返回的對象而不是function所對應的對象,例如下面的例子。

    function F(){}
    function Car(color, displacement){
        this.color = color;
        this.displacement = displacement;
        return new F();
    }
    var car = new Car("black", "2.4T");
    console.log(car.color+", "+car.displacement);   //undefined, undefined
    console.log(car instanceof Car);                //false
    console.log(car instanceof F);                  //true

這個例子中存在兩個function對象:F和Car。在Car的函數(shù)體中返回了新建的F類型實例對象,這時使用Car新建出來的car對象就成了F類型的實例對象,而不是Car類型的實例對象。

4.4.3 prototype屬性與繼承

1.繼承方法

繼承是Java、C++等基于類的語言中的一個術語。它的含義是子類的對象可以調用父類的屬性和方法。基于對象的ES語言根本沒有類的概念,當然也就不存在基于類的那種繼承方式,但是,它可以通過prototype屬性來達到類似于繼承的效果。

prototype是ES中function類型對象的一個特殊屬性。每個function類型的對象都有prototype屬性,prototype屬性的值是object類型的對象。在FireBug中可以看到Object(Object本身是function類型)的prototype屬性類型如圖4-8所示。

圖4-8 FireBug中的prototype屬性類型

function對象中prototype屬性對象的作用是這樣的:在function對象創(chuàng)建出的object類型對象實例中可以直接調用function對象的prototype屬性對象中的屬性(包括方法屬性),例如下面的例子。

    function Car(color, displacement){
        this.color = color;
        this.displacement = displacement;
    }
    Car.prototype.logMessage = function(){
        console.log(this.color+", "+this.displacement);
    }
    var car = new Car("black", "2.4T");
    car.logMessage();                //black, 2.4T

這個例子中,給Car的prototype屬性對象添加了logMessage方法,這樣使用Car創(chuàng)建的car對象就可以直接調用logMessage方法。雖然這里可以使用car調用logMessage方法,但是car對象本身并不會添加這個方法,只是可以調用而已。

function創(chuàng)建的實例對象在調用屬性時會首先在自己的屬性中查找,如果找不到就會去function的prototype屬性對象中查找。但是,創(chuàng)建的對象只是可以調用prototype中的屬性。但是并不會實際擁有那些屬性,也不可以對它們進行修改(修改操作會在實例對象中添加一個同名屬性)。當創(chuàng)建的實例對象定義了同名的屬性后就會覆蓋prototype中的屬性,但是原來prototype中的屬性并不會發(fā)生變化,而且當創(chuàng)建出來的對象刪除了添加的屬性后,原來prototype中的屬性還可以繼續(xù)調用,請看下面的例子。

    function Car(color, displacement){
        this.color = color;
        this.displacement = displacement;
    }
    Car.prototype.logMessage = function(){
        console.log(this.color+", "+this.displacement);
    }
    var car = new Car("black", "2.4T");
    car.logMessage();                //black,2.4T
    car.logMessage = function() {
        console.log(this.color);
    }
    car.logMessage();                //black
    delete car.logMessage;
    car.logMessage();                //black,2.4T

在這個例子中,使用Car直接創(chuàng)建的car對象并沒有l(wèi)ogMessage方法,所以第一次調用logMessage方法時會調用Car的prototype屬性對象中的logMessage方法,然后給car定義了logMessage方法,這時再調用logMessage方法就會調用car自己的logMessage方法了,最后又刪除了car的logMessage方法,此時調用logMessage方法就會再次調用Car的prototype屬性對象中的logMessage方法,而且Car的prototype屬性對象中的logMessage方法的內容也沒有發(fā)生變化。代碼執(zhí)行后的輸出結果如下。

    black,2.4T
    black
    black,2.4T

我們可以通過FireBug將整個過程看得更加清楚,在增加斷點后可以看到圖4-9所示的結構。

圖4-9 在增加斷點后可以看到的結構

從圖4-9中可以看出,Car的prototype屬性并沒有發(fā)生變化,而car對象自己的屬性中先被添加,然后又被刪除了logMessage屬性方法。

2.多層繼承

function的prototype屬性是object類型的屬性對象,其本身可能也使用function創(chuàng)建的對象,通過這種方法就可以實現(xiàn)多層繼承,例如下面的例子。

    function log(msg){
        console.log(msg);
    }


    function Person(){}
    Person.prototype.logPerson = function () {
        log("person");
    }


    function Teacher(){
        this.logTeacher = function () {
            log("teacher");
        }
    }
    Teacher.prototype = new Person();
    Teacher.prototype.logPrototype = function () {
        log("prototype");
    }


    var teacher = new Teacher();
    teacher.logTeacher();
    teacher.logPrototype();
    teacher.logPerson();

這個例子中,因為Teacher的prototype屬性是Person創(chuàng)建的實例對象,而使用Teacher創(chuàng)建出來的teacher對象可以調用Teacher的prototype屬性對象的屬性,所以teacher對象可以調用Person創(chuàng)建的實例對象的屬性。又因為Person創(chuàng)建的實例對象可以調用Person的prototype屬性對象中的屬性,所以teacher對象也可以調用Person的prototype屬性對象中的屬性方法logPerson。另外,因為此程序給Teacher的prototype屬性對象添加了logPrototype方法,所以teacher也可以調用logPrototype方法。最后的輸出結果如下。

    teacher
    prototype
    person

這種調用方法相當于基于類語言中的多層繼承,它的結構如圖4-10所示。

圖4-10 基于類語言中的多層繼承

Teacher創(chuàng)建出來的teacher對象在調用屬性時會首先在自己的屬性中查找,如果找不到就會到Teacher的prototype屬性對象的屬性中查找,如果還找不到就會到Person的prototype屬性對象的屬性中查找,而Teacher的prototype又由兩部分組成,一部分是用Person創(chuàng)建的person對象,另一部分是直接定義的logPrototype方法。

3.使用prototype時的注意事項

在function的prototype屬性對象中默認存在一個名為constructor的屬性。這個屬性默認指向function方法自身,例如上節(jié)例子中Person的prototype屬性對象的constructor屬性就指向了Person。但是,Teacher的prototype由于被賦予了新的值,因此它的constructor屬性就不存在(使用Person創(chuàng)建的person對象自身并沒有constructor屬性)。這時,如果調用teacher.constructor就會返回Person函數(shù)(因為最后會沿著prototype找到person的prototype屬性對象的constructor屬性)。為了可以使用constructor屬性得到正確的構造函數(shù),可以手動給Teacher的prototype屬性對象的constructor屬性賦值為Teacher,代碼如下所示。

    Teacher.prototype =  new Person();
    Teacher.prototype.logPrototype = function () {
        console.log("prototype");
    }
    Teacher.prototype.constructor = Teacher;

使用prototype時應注意以下三點。

一是,prototype是屬于function類型對象的屬性,prototype自身的屬性可以被function創(chuàng)建的object類型的實例對象使用,但是object類型的實例對象自身并沒有prototype屬性。

二是,如果要給function對象的prototype屬性賦予新的值并且又要添加新的屬性,則需要先賦予新值,然后再添加新的屬性,否則在賦值時,會將原先添加的屬性覆蓋掉,例如下面的代碼。

    function log(msg){
        console.log(msg);
    }


    function Person(){}
    function Teacher(){}
    Teacher.prototype.logPrototype = function () {
        log("prototype");
    }
    Teacher.prototype = new Person();


    var teacher = new Teacher();
    teacher.logPrototype();

在上述代碼中,在執(zhí)行最后一行代碼teacher.logPrototype()的時候會報錯,這是因為給Teacher的prototype屬性對象添加了logPrototype屬性方法后,又將prototype賦值為new Person(),而新的prototype中并沒有l(wèi)ogPrototype方法,所以調用就會出錯,也就是說logPrototype被新的對象覆蓋。

function創(chuàng)建的對象在調用屬性時是實時按prototype鏈依次查找的,而不是將prototype中的屬性關聯(lián)到創(chuàng)建的對象本身,因此創(chuàng)建完對象后,再修改function的prototype也會影響到創(chuàng)建的對象的調用,例如下面的例子。

    function Teacher(){}
    var teacher = new Teacher();
    Teacher.prototype.log = function (msg) {
        console.log(msg);
    }
    teacher.log("hello");   //hello

這里的log方法是在teacher對象已經創(chuàng)建完成后添加的,但是在teacher對象中仍然可以使用,也就是說prototype中的屬性是動態(tài)查詢的。

另外,使用prototype除了可以實現(xiàn)繼承之外,還可以節(jié)約內存,因為無論使用function創(chuàng)建多少對象,它們所指向的prototype對象在內存中都只有一份。但是,使用prototype中的屬性比直接使用對象中定義的屬性在執(zhí)行效率上理論來說會低一些。

主站蜘蛛池模板: 于都县| 阿克陶县| 浦北县| 桂东县| 瓮安县| 洪雅县| 萨嘎县| 湟源县| 隆昌县| 江津市| 荔浦县| 武城县| 呼伦贝尔市| 邯郸市| 黑龙江省| 上虞市| 牙克石市| 河东区| 沧源| 凤凰县| 鄂托克旗| 南皮县| 芒康县| 霸州市| 常山县| 安新县| 阿克陶县| 扶绥县| 宁海县| 两当县| 仙桃市| 海丰县| 什邡市| 平南县| 双江| 都安| 平顶山市| 鄂伦春自治旗| 临城县| 通州市| 芜湖市|