深刻探究js的原型與原型鏈

最近在看《javascript高級程序設計》,看完以後,以爲感觸,而後我今天又看到了一篇文章,說的很搞笑。就想整理下本身所學的。javascript

首先,若是咱們把ECMAScript的對象想象爲散列表,即一組名值對,其中值能夠是數據或函數。java

那究竟對象、原型對象、構造函數、繼承、原型鏈、 原型屬性的共享、原型的動態性、原型的總體重寫呢,來一組簡單粗暴的描述哈,仍是挺搞笑的。數組

1)人是人他媽生的,妖是妖他媽生的。人和妖都是對象實例,而人他媽和妖他媽就是原型。原型也是對象,叫原型對象。函數

2)人他媽和人他爸啪啪啪能生出一堆人寶寶、妖他媽和妖他爸啪啪啪能生出一堆妖寶寶,啪啪啪就是構造函數,俗稱造人。測試

3)人他媽會記錄啪啪啪的信息,因此能夠經過人他媽找到啪啪啪的信息,也就是說能經過原型對象找到構造函數。this

4)人他媽能夠生不少寶寶,但這些寶寶只有一個媽媽,這就是原型的惟一性。prototype

5)人他媽也是由人他媽他媽生的,經過人他媽找到人他媽他媽,再經過人他媽他媽找到人他媽他媽……,這個關係叫作原型鏈。設計

6)原型鏈並非無限的,當你經過人他媽一直往上找,最後發現你會發現人他媽他媽他媽……的他媽都不是人,也就是原型鏈最終指向null。對象

7)人他媽生的人會有人的樣子,妖他媽生的妖會有妖的醜陋,這叫繼承。繼承

8)你繼承了你媽的膚色,你媽繼承了你媽他媽的膚色,你媽他媽……,這就是原型鏈的繼承。

9)你談對象了,她媽讓你帶上房產證去提貨,你若沒有,那她媽會問你媽有沒有,你媽沒有那她媽會問你媽她媽有沒有……這就是原型鏈的向上搜索。

10)你會繼承你媽的樣子,可是你也能夠去染髮洗剪吹,就是說對象的屬性能夠自定義,會覆蓋繼承獲得的屬性。

11)雖然你洗剪吹了染成黃毛了,但你不能改變你媽的樣子,你媽生的弟弟妹妹跟你的黃毛洗剪吹沒一點關係,就是說對象實例不能改動原型的屬性。

12)可是你家被你玩火燒了的話,那就是說你家你媽家你弟們家都被燒了,這就是原型屬性的共享。

13)你媽外號阿珍,鄰居大娘都叫你阿珍兒,但你媽頭髮從飄柔作成了金毛獅王后,隔壁大嬸都改口叫你包租仔,這叫原型的動態性。

14)你媽愛漂亮,又跑到韓國整形,整到你媽他媽都認不出來,即便你媽頭髮換回飄柔了,但隔壁鄰居仍是叫你金毛獅王子。由於沒人認出你媽,整形後的你媽已經回爐再造了,這就是原型的總體重寫。

 哈哈,是否是簡單粗暴,還特別容易理解。

ECMAScript中有兩種屬性:數據屬性和訪問器屬性
(1)數據屬性:

[[Configurable]] 表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性,或者可否把屬性修改成訪問器屬性
[[Enumerable]] 表示可以經過for-in循環返回屬性
[[Writable]]表示可否修改屬性的值
[[Value]]包含這個屬性的數據值,
修改默認的屬性使用 Object.defineProperty() 這個方法

(2)訪問器屬性:包含getter和setter函數。getter:負責返回有效的值,setter函數傳入新值,訪問器屬性的4個特性:
[[Configurable]]表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性,可否把屬性修改成數據屬性。默認爲true.
[[Enumerable]]表示可否經過for-in循環返回屬性。
[[Get]]在讀取屬性時調用的函數,默認值爲undefined
[[Set]]在寫入屬性時調用的函數,默認值爲undefined

訪問器的屬性用Object.defineProperty()來定義

書上給了一個例子,來挺好理解的:

var book = {
  _year:2004,
  //_year前面的下劃線是一種經常使用的記號,**用來表示只能經過對象方法訪問的屬性**
  edition:1
};
Object.defineProperty(book,"year",{
  get:function(){
    return this._year;
  }
  set:function(newYear){
    if(newValue >2004){
      this._year = newValue;
      this.edition += newValue -2004;
    }
  }
});
//定義訪問器的舊有方法
book.__defineGetter__("year",function(){
  return this._year;
})
book.__defineSetter__("year",function(newValue){
  if(newValue >2014){
    this._year = newValue;
    this.edition += newValue -2004;
  }
})

book.year = 2015;
alert(book.edition); //2
讀取屬性的特性:Object.getOwnPropertyDesciptor()取得給定屬性的描述符。

最後說回原型鏈:原型鏈繼承的主要問題在於屬性的共享,不少時候咱們只想共享方法而並不想要共享屬性,理想中每一個實例應該有獨立的屬性。所以,原型繼承就有了下面的兩種改良方式:

1)組合繼承

function Mother(age){

  this.age = age;

  this.bobby = ['running','football']

}

Mother.prototype.showAge = function(){

  console.log(this.age);

};

function Person(name,age){

  Mother.call(this.age);         //第二次執行

  this.name = name;

}

Person.prototype = new Mother();    //第一次執行

Person.prototype.constructor = Person;

Person.prototype.showName = function(){

  console.log(this.name);

}

var p1 = new Person('Jack',20);

p1.hobby.push('basketball'); //p1:'Jack';_proto_:20,['running','football']

var p2 = new Person('Mark',18); //p2:'Mark';_proto_:18,['running','football']

(2)寄生組合式繼承

function object(o){

  function F(){}

  F.prototype = o;

  return new F();

}

function inheritPrototype(Person,Mother){

  var prototype = object(Mother.prototype);

  prototype.constructor = Person;

  Person.prototype = prototype;

}

function Mother(age){

  this.age = age;

  this.hobby = ['running','football']

}

Mother.prototype.showAge = function(){

  console.log(this.age);

};

function Person(name,age){

  Mother.call(this,age);

  this.name = name;

}

inheritPrototype(Person,Mother);

Person.prototype.showName = function(){

  console.log(this.name);

}

var p1 = new Person('Jack',20);

p1.hobby.push('backetball');    //p1:'Jack';_proto_:20,['running','football']

var p2 = new Person('Mark',18); //p2:'Mark';_proto_:18,['running','football']

js建立對象的各類方法

(1)原型模式

//1原型模式:對象字面量方式

var person = {

  name : 'Jack',

  age:18,

  sayName:function(){alert(this.name);}

};

//2原型模式:Object構造函數方式

var person = new Object();

person.name = 'Jack';

person.age = 18;

person.sayName = function(){

   alert(this.name); 

};

(2)工廠模式

//工廠模式,定義一個函數建立對象

function creatPerson(name,age){

  var person = new Object();

  person.name = name;

  person.age = age;

  person.sayName = function(){

    alert(this.name);

  };

  return person;

}

工廠模式就是批量化生產,簡單調用就能夠進入造人模式(啪啪啪。。。)指定姓名年齡就能夠造一堆小寶寶啦,解放雙手。可是因爲是工廠暗箱操做的,因此你不能識別這個對象究竟是什麼類型、是人仍是狗傻傻分不清(instanceof 測試爲 Object),另外每次造人時都要建立一個獨立的temp對象,代碼臃腫

(3)構造函數

//3構造函數模式,爲對象定義一個構造函數

function Person(name,age){

  this.name = name;

  this.age = age;

  this.sayName = function(){

    alert(this.name);

  };

}

var p1 = new Person('Jack',18);    //建立一個p1對象

Person('Jack',18);

構造函數與C++、JAVA中類的構造函數相似,易於理解,另外Person能夠做爲類型識別(instanceof 測試爲 Person 、Object)。可是全部實例依然是獨立的,不一樣實例的方法實際上是不一樣的函數。這裏把函數兩個字忘了吧,把sayName當作一個對象就好理解了,就是說張三的 sayName 和李四的 sayName是不一樣的存在,但顯然咱們指望的是共用一個 sayName 以節省內存。

(4)原型模式

//原型模式,直接定義prototype屬性

function Person(){}

Person.prototype.name = 'Jack';

Person.prototype.age = 18;

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

//原型模式。字面量定義方式

function Person(){}

Person.prototype = {

  name:'Jack',

  age:18,

  sayName:function(){alert(this.name);}

};

var p1 = new Person();  //name = 'Jack'

var p2 = new Person(); //name = 'Jack'

這裏須要注意的是原型屬性和方法的共享,即全部實例中都只是引用原型中的屬性方法,任何一個地方產生的改動會引發其餘實例的變化。

 (5)混合模式

//原型構造函數組合模式

function Person(name,age){

  this.name = name;

  this.age = age;

}

Person.prototype = {

  hobby:['running','football'];

  sayName:function(){alert(this.name);},

  sayAge:function(){alert(this.age);}

}

var p1 = new Person('Jack',20);

var p2 = new Person('Mark',18);

作法是將須要獨立的屬性方法放入構造函數中,而能夠共享的部分則放入原型中,這樣作能夠最大限度節省內存而又保留對象實例的獨立性。

相關文章
相關標籤/搜索