JS進(jìn)階系列之面向?qū)ο缶幊?/h1>

ECMAScript中沒有類的概念,里面對于對象的描述是:“無序?qū)傩缘募稀⑵鋵傩钥梢园局怠ο蠡蛘吆瘮?shù)”

一、理解對象

創(chuàng)建自定義對象的方法:

  • 以前:
var person = new Object();
person.name = "McRay";
person.sayName = function() { };
  • 現(xiàn)在(對象字面量):
var person = {
name: "McRay";
sayName : function(){}
};

二、創(chuàng)建對象

1、工廠模式:用函數(shù)來封裝以特定接口創(chuàng)建對象的細(xì)節(jié)

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("McRay",23,"Student");
var person2 = createPerson("Greg",27,"Doctor");

雖然解決了創(chuàng)建多個相似對象的問題,但是卻沒有解決對象識別的問題(即怎么知道對象的類型)。

2、構(gòu)造函數(shù)模式:創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對象類型的屬性和方法

function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){};
}
var person1 = new Person("McRay",23,"Student");
var person2 = new Person("Greg",27,"Doctor");

createPerson()中的不同之處:

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

創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將它的實例標(biāo)識為一種特定的類型。
上面的代碼中有一個缺點就是每一個構(gòu)造方法都必須在每個實例上重新創(chuàng)建一遍,可以通過把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來解決這個問題:

function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){}
var person1 = new Person("McRay",23,"Student");
var person2 = new Person("Greg",27,"Doctor");

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();
         var person2 = new Person();
         person2.sayName();
        alert(person1.sayName == person2.sayName);//true

下面是Person構(gòu)造函數(shù)、Person的原型屬性以及Person現(xiàn)有的兩個實例之間的關(guān)系圖:


原型對象.PNG

1、理解原型對象

  • 從圖中可以看出實例與構(gòu)造函數(shù)沒有直接的關(guān)系。
  • 每當(dāng)代碼讀取某個對象的某個屬性時,都會執(zhí)行一次搜索,首先從對象實例本身開始,然后一直搜索指針指向的原型對象,直到找到屬性。
  • 雖然可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。如果為實例添加了一個與實例原型中同名的屬性,那么該屬性會覆蓋原型中的那個屬性。
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"
  • 可以使用hasOwnProperty()方法檢測一個屬性是存在于實例中,還是存在于原型中。這個方法只在給定屬性存在于對象實例中,才會返回true,注意是只存在。

2、原型與in操作符

  • 當(dāng)單獨使用in操作符時,會在通過對象能夠訪問給定屬性時返回true,無論該屬性存在于實例中還是原型中。
  • 同時使用 hasOwnProperty()和in操作符就可以確定該屬性到底是存在于對象中,還是存在于原型中,如下所示
function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name) && (name in object);
}
  • 要取得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法。這個方法接收一個對象作為參數(shù),返回一個包含所有可枚舉屬性的字符串?dāng)?shù)組。如果你向得到所有實例屬性,無論是否枚舉,可以使用Object.getOwnPropertyNames()方法。

3、更簡單的原型語法

function Person(){}
Person.prototype = {
   name : "Nicholas",
   age : 27,
   job: "Software Engineer",
   sayName : function() { }
};
  • 需要注意的是原型對象的constructor屬性不再指向Person了,變成了新對象的constructor屬性,如果想指向原來的Person對象,可以如下設(shè)置:
function Person(){}
Person.prototype = {
   constructor : Person,
   name : "Nicholas",
   age : 27,
   job: "Software Engineer",
   sayName : function() { }
};

但是如果這樣設(shè)置的話,會將constructor屬性的[[Enumerable]]特性被設(shè)置為true。所以還可以使用Object.defineProperty()```方法

//重構(gòu)構(gòu)造函數(shù)
Object.defineProperty(Person.prototype,"constructor",{
      enumerable : false,
      value : Person
});

4、原型的動態(tài)性
由于在原型中查找值得過程是一次搜索,因此我們對原型對象所做的任何修改都能夠立即從實例中反映出來——即使是先創(chuàng)建了實例后修改原型也照樣如此.

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

原因可以歸結(jié)為實例與原型之間的松散連接關(guān)系。盡管可以隨時為原型添加屬性和方法,但是如果重寫整個原型對象,就會切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對象實例之間的聯(lián)系,因為實例中的指針僅指向原型,而不指向構(gòu)造函數(shù)。

5、原型對象的問題
由于其共享的本性,如果對于包含引用類型值的屬性來說,問題就比較突出了,請看下面的例子:

function Person(){}
Person.prototype = {
   constructor : Person,
   name : "Nicholas",
   age : 27,
   job: "Software Engineer",
   friends : ["Shelby","Court"],
   sayName : function() { }
};
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ù)模式和原型模式
構(gòu)造函數(shù)模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性,解決了原型模式的問題。

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

Person.prototype = {
  constructor : Person,
  sayName : function(){
      alert(this.name);
}
}
var person1 = new Person("McRay",23,"Student");
var person2 = new Person("Greg",27,"Doctor");

person1.friends.push("Van");

alert(person1.friends);//"Shelby,Court,Van"
alert(person2.friends);//"Shelby,Court"
alert(person1.friends == person2.friends);//"false"

5、動態(tài)原型模式

function Person(name,age,job){
  //屬性
   this.name = name;
   this.age = age;
   this.job = job;
   //方法
   if(typeof this.sayName != "function") {
        Person.prototype.sayName = function(){}
};
}
var friend = new Person("Nicholas",27,"Software Engineer");
friend.sayName();

6、寄生構(gòu)造函數(shù)模式
基本思想就是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后再返回新創(chuàng)建的對象。

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("Nicholas",27,"Software Engineer");
friend.sayName();

7、穩(wěn)妥構(gòu)造函數(shù)模式
與寄生構(gòu)造函數(shù)模式的不同是:新創(chuàng)建對象的實例方法不引用this;不適用new操作符調(diào)用構(gòu)造函數(shù)。

function Person(name,age,job){
   var o = new Object();
   o.sayName = function(){};
   return o;
}
var friend = Person("Nicholas",27,"Software Engineer");
friend.sayName();

四、繼承

談到面向?qū)ο螅筒坏貌徽f到繼承這個概念,因為javascript中的函數(shù)是沒有簽名的,所以ECMAScript只支持實現(xiàn)繼承。

1、原型鏈

原型鏈?zhǔn)荍S繼承中的一個重要概念,其基本思想就是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。具體的做法就是讓原型對象等于另一個類型的實例,此時原型對象將包含一個指向另一個原型的指針,相應(yīng)地,另一個原型中也包含著指向另一個構(gòu)造函數(shù)的指針,如此層層遞進(jìn),就構(gòu)成了實例與原型的鏈條,這就是原型鏈的概念。

function SuperType(){
        this.property = true;
      }
      SuperType.prototype.getSuperValue = function(){
        return this.property;
      };
      function SubType(){
        this.subproperty = false;
      }
      SubType.prototype = new SuperType();
      SubType.prototype.getSubValue = function(){
        return this.subproperty;
      };
      var instance = new SubType();
      alert(instance.getSuperValue());//true

關(guān)系圖如下:

繼承.PNG
  • 別忘記默認(rèn)的原型Object。
  • 不管怎樣,給原型添加方法的代碼一定要放在替換原型的語句之后。
  • 在通過原型鏈實現(xiàn)繼承時,不能使用對象字面量創(chuàng)建原型方法,因為這樣做就會重寫原型鏈。
  • 原型鏈的問題還是包含引用類型值得原型的問題。

借用構(gòu)造函數(shù)

解決原型中包含引用類型值所帶來問題的過程中,可以借用構(gòu)造函數(shù),別忘了函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對象,因此通過使用apply()和call()方法可以在新創(chuàng)建對象上執(zhí)行構(gòu)造函數(shù),如下所示:

function SuperType(name){
  this.name = name;
   this.color = ["red","blue","green"];
}

function SubType(){
//繼承了SuperType
    SuperType.call(this,"Nicholas");
}

var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.color);//"red,blue,green"
  • 借用構(gòu)造函數(shù)帶來的問題是方法都在構(gòu)造函數(shù)中定義,因此函數(shù)復(fù)用就無從談起。

組合繼承

將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合一起,使用原型鏈實現(xiàn)對原型屬性和方法的繼承,通過借用構(gòu)造函數(shù)來實現(xiàn)對實例屬性的繼承。

function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}

SuperType.prototype.sayName = function(){
alert(this.name);
};

function SubType(name,age){
   //繼承實例屬性
   SuperType.call(this,name);//第二次調(diào)用SuperType();
   this.age = age;
}

//繼承原型屬性和方法
SubType.prototype = new SuperType(); //第一次調(diào)用SuperType();
SubType.prototype.constuctor = SubType;
SubType.prototype.sayAge = function(){
   alert(this.age);
};

var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.color);//"red,blue,green"

組合繼承的問題是多次調(diào)用超類型構(gòu)造函數(shù)而導(dǎo)致的低效率問題,可以與寄生模式組合使用

原型式繼承

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

可以使用Object.create()方法規(guī)范化了原型式繼承。接收兩個參數(shù),一個是用作新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象。

寄生式繼承

function createAnother(original){
 var clone = object(original);
 clone.sayHi = function() {
}
return clone;
}

寄生式組合繼承

背后思路是:不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),我們所需要的無非就是超類型原型的一個副本而已。本質(zhì)上,就是使用寄生式繼承來繼承超類型的原型,然后再將結(jié)果指定給子類型的原型。

function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
   //繼承實例屬性
   SuperType.call(this,name);//第二次調(diào)用SuperType();
   this.age = age;
}
inheritPrototype(SubType,SuperType){
   var prorotype = Object(superTYpe.prototype);
   prototype.constructor = subType;
   subType.prototype = prototype;
}
//繼承原型屬性和方法
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
   alert(this.age);
};
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.color);//"red,blue,green"
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,482評論 2 379

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