面向?qū)ο?/h1>

1.對象的定義

在ECMAScript-262中八回,對象被定義為“無序?qū)傩缘募希鋵傩钥梢园局捣园樱瑢ο蠡蛘吆瘮?shù)”毒姨。也就是說,在JavaScript中钾恢,對象無非就是由一些列無序的key-value對組成手素。其中value可以是基本值,對象或者函數(shù)瘩蚪。

// 這里的person就是一個對象

  var person = {
      name: 'Tom',
      age: 18,
      getName: function() {},
      parent: {}
  }

創(chuàng)建對象
我們可以通過new的方式創(chuàng)建一個對象泉懦。

  var obj = new Object();

也可以通過對象字面量的形式創(chuàng)建一個簡單的對象。

  var obj = {};

當(dāng)我們想要給我們創(chuàng)建的簡單對象添加方法時疹瘦,可以這樣表示崩哩。

  // 可以這樣
  var person = {};
  person.name = "TOM";
  person.getName = function() {
        return this.name;
  }
  // 也可以這樣
  var person = {
      name: "TOM",
      getName: function() {
          return this.name;
      }
  }

訪問對象的屬性和方法
假如我們有一個簡單的對象如下:

  var person = {
      name: 'TOM',
      age: '20',
      getName: function() {
          return this.name
      }
  }

當(dāng)我們想要訪問他的name屬性時,可以用如下兩種方式訪問言沐。

  person.name
  // 或
  person['name']

如果我們想要訪問的屬性名是一個變量時邓嘹,常常會使用第二種方式。例如我們要同時訪問person的name與age险胰,可以這樣寫:

  ['name', 'age'].forEach(function(item) {
      console.log(person[item]);
  })

2.工廠模式

使用上面的方式創(chuàng)建對象很簡單汹押,但是在很多時候并不能滿足我們的需求。就以person對象為例起便。假如我們在實際開發(fā)中棚贾,不僅僅需要一個名字叫做TOM的person對象窖维,同時還需要另外一個名為Jake的person對象,雖然他們有很多相似之處妙痹,但是我們不得不重復(fù)寫兩次铸史。

  var perTom = {
      name: 'TOM',
      age: 20,
      getName: function() {
          return this.name
      }
  };

  var perJake = {
      name: 'Jake',
      age: 22,
      getName: function() {
          return this.name
      }
  }

我們可以使用工廠模式的方式解決這個問題。顧名思義怯伊,工廠模式就是我們提供一個模子琳轿,然后通過這個模子復(fù)制出我們需要的對象。我們需要多少個耿芹,就復(fù)制多少個崭篡。

  var createPerson = function (name, age) {
  // 聲明一個對象,該對象就是工廠模式的模子
  var o = new Object();
      // 依次添加我們需要的屬性和方法
      o.name = name;
      o.age = age;
      o.getName = function () {
          return this.name;
      }
      return o;
  }

// 創(chuàng)建兩個實例

  var perTom = createPerson('TOM', 20);
  var perJake = createPerson('Jake', 20);

相信上面的代碼并不難理解猩系,也不用把工廠模式看得太過高大上媚送。很顯然,工廠模式幫助我們解決了重復(fù)代碼上的麻煩寇甸,讓我們可以寫很少的代碼塘偎,就能夠創(chuàng)建很多個person對象。但是這里還有兩個麻煩拿霉,需要我們注意吟秩。 這種方法的問題是,perTom和perJake之間沒有內(nèi)在的聯(lián)系绽淘,不能反映出它們是同一個原型對象的實例涵防。 第一個麻煩就是這樣處理,我們沒有辦法識別對象實例的類型沪铭。使用instanceof可以識別對象的類型壮池,如下例子:

var obj = {};
var foo = function() {}
console.log(obj instanceof Object);  // true
console.log(foo instanceof Function); // true

因此在工廠模式的基礎(chǔ)上,我們需要使用構(gòu)造函數(shù)的方式來解決這個麻煩杀怠。

3.構(gòu)造函數(shù)

所謂”構(gòu)造函數(shù)”椰憋,其實就是一個普通函數(shù),但是內(nèi)部使用了this變量赔退。對構(gòu)造函數(shù)使用new運(yùn)算符橙依,就能生成實例,并且this變量會綁定在實例對象上硕旗。

比如窗骑,貓的原型對象現(xiàn)在可以這樣寫,

   function Cat(name,color){
       this.name=name;
       this.color=color;
   }

我們現(xiàn)在就可以生成實例對象了漆枚。

    var cat1 = new Cat("大毛","黃色");
    var cat2 = new Cat("二毛","黑色");
    alert(cat1.name); // 大毛
    alert(cat1.color); // 黃色

這時cat1和cat2會自動含有一個constructor屬性创译,指向它們的構(gòu)造函數(shù)。

    alert(cat1.constructor == Cat); //true
    alert(cat2.constructor == Cat); //true

Javascript還提供了一個instanceof運(yùn)算符墙基,驗證原型對象與實例對象之間的關(guān)系软族。

    alert(cat1 instanceof Cat); //true
    alert(cat2 instanceof Cat); //true

構(gòu)造函數(shù)方法很好用辛藻,但是存在一個浪費(fèi)內(nèi)存的問題。

請看互订,我們現(xiàn)在為Cat對象添加一個不變的屬性type(種類),再添加一個方法eat(吃)痘拆。那么仰禽,原型對象Cat就變成了下面這樣:

    function Cat(name,color){
        this.name = name;
        this.color = color;
        this.type = "貓科動物";
        this.eat = function(){
            alert("吃老鼠");
        };   
    }

還是采用同樣的方法,生成實例:

    var cat1 = new Cat("大毛","黃色");
    var cat2 = new Cat ("二毛","黑色");
    alert(cat1.type); // 貓科動物
    cat1.eat(); // 吃老鼠

表面上好像沒什么問題纺蛆,但是實際上這樣做吐葵,有一個很大的弊端。那就是對于每一個實例對象桥氏,type屬性和eat()方法都是一模一樣的內(nèi)容温峭,每一次生成一個實例,都必須為重復(fù)的內(nèi)容字支,多占用一些內(nèi)存凤藏。這樣既不環(huán)保,也缺乏效率堕伪。 能不能讓type屬性和eat()方法在內(nèi)存中只生成一次揖庄,然后所有實例都指向那個內(nèi)存地址呢?回答是可以的欠雌。

4.Prototype模式(原型模式)

Javascript規(guī)定蹄梢,每一個構(gòu)造函數(shù)都有一個prototype屬性,指向另一個對象富俄。這個對象的所有屬性和方法禁炒,都會被構(gòu)造函數(shù)的實例繼承。

這意味著霍比,我們可以把那些不變的屬性和方法幕袱,直接定義在prototype對象上。

    function Cat(name, color) {
        this.name = name;
        this.color = color;
    }
    Cat.prototype.type = "貓科動物";
    Cat.prototype.eat = function () {
        alert("吃老鼠");
    }    

然后桂塞,生成實例凹蜂。

    var cat1 = new Cat("大黑", "黑色");
    var cat1 = new Cat("大黃", "黃色");

這是所有實例的type屬性和eat()方法,其實都是同一個內(nèi)存地址阁危,指向prototype對象玛痊,因此提高了運(yùn)行效率。

    alert(cat1.eat == cat2.eat); //true

5.構(gòu)造函數(shù)的繼承

現(xiàn)在有一個“動物”對象的構(gòu)造函數(shù)狂打。

    function Animal (){
        this.species = "動物";
    }

還有一個“貓”對象的構(gòu)造函數(shù)擂煞。

    function Cat(name, color) {
        this.name = name;
        this.color = color;
    }

怎樣使“貓”繼承“動物”呢?

(1)構(gòu)造函數(shù)綁定
第一種方法就是使用call或apply方法趴乡,將父對象的構(gòu)造函數(shù)綁定在子對象上对省,即在子對象構(gòu)造函數(shù)中加一行:

    function Cat(name,color){
        Animal.apply(this, arguments); // arguments指參數(shù)數(shù)組[name,color]
        this.name = name;
        this.color = color;
    }
    var cat1 = new Cat("大黑", "黑色")蝗拿;
    console.log(cat1.species); //動物

(2)prototype模式
第二種方法更常見,使用prototype屬性蒿涎。

Javascript規(guī)定哀托,每一個構(gòu)造函數(shù)都有一個prototype屬性,指向另一個對象劳秋。這個對象的所有屬性和方法仓手,都會被構(gòu)造函數(shù)的實例繼承。

如果“貓”的prototype對象玻淑,指向一個Animal的實例嗽冒,那么所有“貓”的實例,就能繼承Animal了补履。

    Cat.prototype = new Animal();
    Cat.prototype.constructor = Cat;
    var cat1 = new Cat("大黑", "黑色");
    console.log(cat1.species); //動物

代碼的第一行添坊,我們將Cat的prototype對象指向一個Animal的實例。

    Cat.prototype = new Animal();

相當(dāng)于完全刪除了prototype對象原先的值箫锤,然后賦予一個新值贬蛙。 但是在這一行之前Cat.prototype.constructor是指向Cat的,加了這一行后谚攒,Cat.prototype.constructor指向了Animal速客。 由于每一個實例也有一個constructor屬性,默認(rèn)調(diào)用prototype對象的constructor屬性五鲫。 這會導(dǎo)致構(gòu)造函數(shù)Cat生成實例的constructor卻指向了Animal,從而造成繼承鏈的紊亂溺职。

所以第二行就是將Cat.prototype.constructor改回指向Cat。

    Cat.prototype.constructor = Cat;

所以在編程中位喂,如果替換了prototype對象浪耘,必須為新的prototype對象加上constructor屬性,并將這個屬性指回原來的構(gòu)造函數(shù)塑崖。

(3)直接繼承prototype
第三種方法是對第二種方法的改進(jìn)七冲。由于Animal對象中,不變的屬性都可以直接寫入Animal.protype规婆。所以澜躺,我們也可以讓Cat()跳過Animal(),直接繼承Animal.prototype抒蚜。

現(xiàn)在掘鄙,我們將Animal對象改寫:

    function Animal (){ }
    Animal.prototype.species = "動物";

然后嗡髓,將Cat的prototype對象操漠,然后指向Animal的prototype對象,這樣就完成了繼承饿这。

    Cat.prototype = Animal.prototype;
    Cat.prototype.constructor = Cat;
    var cat1 = new Cat("大黃","黃色");
    alert(cat1.species); // 動物

與前一種方法相比浊伙,這樣做的優(yōu)點是效率比較高(不用執(zhí)行和建立Animal的實例了)撞秋,比較省內(nèi)存。缺點是Cat.prototype和Animal.prototype現(xiàn)在指向了同一個對象嚣鄙,那么任何對Cat.prototype的修改吻贿,都會影響到Animal.prototype。

所以哑子,上面的代碼是有問題的廓八。第二行

    Cat.prototype.constructor = Cat;

這一句實際上把Animal.prototype對象的屬性也改掉了

    alert(Animal.prototype.constructor); // Cat

(4)利用空對象作為中介
由于直接繼承prototype存在上述的缺點,所以就有第四種方法赵抢,利用一個空對象作為中介。

    var F = function (){};
    F.prototype = Animal.prototype;
    Cat.prototype = new F();
    Cat.prototype.constructor = Cat;

F是空對象声功,所以幾乎不占內(nèi)存烦却。這時,修改Cat的prototype對象先巴,就不會影響到Animal的prototype對象其爵。

    alert(Animal.prototype.constructor); //Animal

我們將上面的方法,封裝成一個函數(shù)伸蚯,便于使用摩渺。

    function extend(Child, Parent) {
        var F = function() {};
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototype.constructor = Clild;
        Child.uber = Parent.prototype;
        //這里的意思是為子對象設(shè)置一個uber屬性,直接指向父對象的prototype屬性剂邮。
        這等于在子對象上打開一條通道摇幻,可以直接調(diào)用父對象的方法。
        這一行放在這里挥萌,只是為了實現(xiàn)繼承的完備性绰姻,純屬備用性質(zhì) 
    }

使用的時候

    extend(Cat, Animal);
    var cat1 = new Cat("大黑", "黑色");
    console.log(cat1.species); // 動物

(5)拷貝繼承
上面采用prototype對象,實現(xiàn)繼承引瀑。我們換一種思路狂芋,純粹采用“拷貝”方法實現(xiàn)繼承。簡單說憨栽,如果把父對象的所有屬性和方法帜矾,拷進(jìn)子對象,不也能實現(xiàn)繼承嗎屑柔?這樣我們就有了第五種方法屡萤。

首先,還是把Animal的所有不變屬性掸宛,都放到它的prototype對象上灭衷。

    function Animal(){}
    Animal.prototype.species = "動物";

然后旁涤,再寫一個函數(shù)翔曲,實現(xiàn)屬性拷貝的目的迫像。

    function extend2(Child, Parent){
        var p = Parent.prototype;
        var c = Child.prototype;
        for(var i in p) {
            c[i] = p[i];
        }
        c.uber = p;
    }

這個函數(shù)的作用,就是將父對象的prototype對象中的屬性瞳遍,一一拷貝給Child對象的prototype對象闻妓。 使用的時候

    extend2(Cat, Animal);
    var cat1 = new Cat("大黑", "黑色");
    console.log(cat1.species); // 動物

6.非構(gòu)造函數(shù)的繼承

一、什么是”非構(gòu)造函數(shù)”的繼承掠械? 比如下面有兩個普通對象由缆,不是構(gòu)造函數(shù)

    var Chinese = {
        nation: '中國'
    }; 

    var Doctor = {
        career: '醫(yī)生'
    };  

怎么讓“醫(yī)生”去繼承“中國人”,然后生成一個“中國醫(yī)生”的對象猾蒂?
1.object()方法

    function object(o) {
        function F(){}
        F.prototype = o;
        return new F();
    }

這個object()函數(shù)均唉,其實只做一件事,就是把子對象的prototype屬性肚菠,指向父對象舔箭,從而使得子對象與父對象連在一起。

使用的時候蚊逢,第一步先在父對象的基礎(chǔ)上层扶,生成子對象:

    var Doctor = object(Chinese);

然后,再加上子對象的屬性:

    Doctor.career = '醫(yī)生';

這時烙荷,子對象已經(jīng)繼承了父對象的屬性了镜会。

2.淺拷貝 除了使用“prototype鏈”以外,還有另一招那個思路:把父對象的屬性全部拷貝給子對象终抽,也能實現(xiàn)繼承戳表。

    function extendCopy(p) {                
        var c = {};             
        for (var i in p) {      
            c[i] = p[i];    
        }               
        c.uber = p;  
        return c;  
    }

    var Doctor = extendCopy(Chinese);
    Doctor.career = '醫(yī)生';
    alert(Doctor.nation); // 中國

但是,這樣的拷貝有一個問題昼伴。那就是扒袖,如果父對象的屬性等于數(shù)組或另一個對象,那么實際上亩码,子對象獲得的只是一個內(nèi)存地址季率,而不是真正拷貝,因此存在父對象被篡改的可能描沟。

3.深拷貝 所謂”深拷貝”飒泻,就是能夠?qū)崿F(xiàn)真正意義上的數(shù)組和對象的拷貝。它的實現(xiàn)并不難吏廉,只要遞歸調(diào)用”淺拷貝”就行了泞遗。

    var Chinese = {    
        nation: '中國',
        birthPlaces : ['北京','上海','香港'], 
    };

    function deepCopy(p, c) {
        var c = c || {};
        for (var i in p) {
            if (typeof p[i] === 'object') {
                c[i] = (p[i].constructor === Array) ? [] : {};
                deepCopy(p[i], c[i]);
            } else {                             
                c[i] = p[i];
            }    
        }               
        return c;  
    }
    var Doctor = deepCopy(Chinese);
   Doctor.birthPlaces.push('廈門');
    console.log(Doctor.birthPlaces, Chinese.birthPlaces);

目前,jQuery庫使用的就是這種繼承方法席覆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末史辙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聊倔,老刑警劉巖晦毙,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異耙蔑,居然都是意外死亡见妒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門甸陌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來须揣,“玉大人,你說我怎么就攤上這事钱豁〕芸ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵牲尺,是天一觀的道長卵酪。 經(jīng)常有香客問我,道長秸谢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任霹肝,我火速辦了婚禮估蹄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沫换。我一直安慰自己臭蚁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布讯赏。 她就那樣靜靜地躺著垮兑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪漱挎。 梳的紋絲不亂的頭發(fā)上系枪,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音磕谅,去河邊找鬼私爷。 笑死,一個胖子當(dāng)著我的面吹牛膊夹,可吹牛的內(nèi)容都是我干的衬浑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼放刨,長吁一口氣:“原來是場噩夢啊……” “哼工秩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤助币,失蹤者是張志新(化名)和其女友劉穎浪听,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奠支,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡馋辈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倍谜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迈螟。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尔崔,靈堂內(nèi)的尸體忽然破棺而出答毫,到底是詐尸還是另有隱情,我是刑警寧澤季春,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布洗搂,位于F島的核電站,受9級特大地震影響载弄,放射性物質(zhì)發(fā)生泄漏耘拇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一宇攻、第九天 我趴在偏房一處隱蔽的房頂上張望惫叛。 院中可真熱鬧,春花似錦逞刷、人聲如沸嘉涌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仑最。三九已至,卻和暖如春帆喇,著一層夾襖步出監(jiān)牢的瞬間警医,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工坯钦, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留法严,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓葫笼,卻偏偏與公主長得像深啤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子路星,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355