JavaScript建構函式及原型對象

來源:互聯網
上載者:User

JavaScript建構函式及原型對象

JavaScript中沒有類的概念,所以其在對象建立方面與物件導向語言有所不同。

JS中對象可以定義為”無序屬性的集合”。其屬性可以包含基本值,對象以及函數。對象實質上就是一組沒有特定順序的值,對象中每個屬性、方法都有一個名字,每個名字都映射到了一個值,因此我們可以將對象想象稱為一個散列表。

JS是一種基於對象的語言,對象的概念在JS體系中十分的重要,因此有必要清楚地瞭解一下JS中對象建立的常用方法及各自的局限性。

使用Object或對象字面量建立對象原廠模式建立物件建構函數模式建立對象原型模式建立物件建構與原型混合模式建立對象使用Object或對象字面量建立對象

在說原廠模式建立對象之前,我們不妨回顧一下JS中最基本的建立對象的方法,比如說我想建立一個student對象怎麼辦?最簡單地,new一個Object:

var student = new Object();student.name = "easy";student.age = "20";

這樣,一個student對象就建立完畢,擁有2個屬性name以及age,分別賦值為"easy"20

如果你嫌這種方法有一種封裝性不良的感覺,我們也可以使用對象字面量的方式來建立student對象:

var sutdent = {  name : "easy",  age : 20};

這樣看起來似乎就完美了。但是馬上我們就會發現一個十分尖銳的問題:當我們要建立同類的student1,student2,…,studentn時,我們不得不將以上的代碼重複n次。

var sutdent1 = {  name : "easy1",  age : 20};var sutdent2 = {  name : "easy2",  age : 20};...var sutdentn = {  name : "easyn",  age : 20};

能不能像工廠車間那樣,有一個車床就不斷生產出對象呢?我們看”原廠模式”。

原廠模式建立對象

JS中沒有類的概念,那麼我們不妨就使用一種函數將以上對象建立過程封裝起來以便於重複調用,同時可以給出特定介面來初始化對象:

function createStudent(name, age) {  var obj = new Object();  obj.name = name;  obj.age = age;  return obj;}var student1 = createStudent("easy1", 20);var student2 = createStudent("easy2", 20);...var studentn = createStudent("easyn", 20);

這樣一來我們就可以通過createStudent函數源源不斷地”生產”對象了。看起來已經高枕無憂了,但貪婪的人類總有不滿足於現狀的天性:我們不僅希望”產品”的生產可以像工廠車間一般源源不斷,我們還想知道生產的產品究竟是哪一種類型的。

比如說,我們同時又定義了”生產”水果對象的createFruit()函數:

function createFruit(name, color) {  var obj = new Object();  obj.name = name;  obj.color = color;  return obj;}var v1 = createStudent("easy1", 20);var v2 = createFruit("apple", "green");

對於以上代碼建立的對象v1、v2,我們用instanceof操作符去檢測,他們統統都是Object類型。我們的當然不滿足於此,我們希望v1是Student類型的,而v2是Fruit類型的。為了實現這個目標,我們可以用自訂建構函式的方法來建立對象。

建構函式模式建立對象

在上面建立Object這樣的原生對象的時候,我們就使用過其建構函式:

var obj = new Object();

在建立原生數組Array類型對象時也使用過其建構函式:

var arr = new Array(10);  //構造一個初始長度為10的數組對象

在進行自訂建構函式建立對象之前,我們首先瞭解一下建構函式普通函數有什麼區別。

其一,實際上並不存在建立建構函式的特殊文法,其與普通函數唯一的區別在於調用方法。對於任意函數,使用new操作符調用,那麼它就是建構函式;不使用new操作符調用,那麼它就是普通函數。

其二,按照慣例,我們約定建構函式名以大寫字母開頭,普通函數以小寫字母開頭,這樣有利於顯性區分二者。例如上面的new Array(),new Object()。

其三,使用new操作符調用建構函式時,會經曆(1)建立一個新對象;(2)將建構函式範圍賦給新對象(使this指向該新對象);(3)執行建構函式代碼;(4)返回新對象;4個階段。

瞭解了建構函式普通函數的區別之後,我們使用建構函式將原廠模式的函數重寫,並添加一個方法屬性:

function Student(name, age) {  this.name = name;  this.age = age;  this.alertName = function(){    alert(this.name)  };}function Fruit(name, color) {  this.name = name;  this.color = color;  this.alertName = function(){    alert(this.name)  };}

這樣我們再分別建立Student和Fruit的對象:

var v1 = new Student("easy", 20);var v2 = new Fruit("apple", "green");

這時我們再來用instanceof操作符來檢測以上物件類型就可以區分出Student以及Fruit了:

alert(v1 instanceof Student);  //truealert(v2 instanceof Student);  //falsealert(v1 instanceof Fruit);  //falsealert(v2 instanceof Fruit);  //truealert(v1 instanceof Object);  //true 任何對象均繼承自Objectalert(v2 instanceof Object);  //true 任何對象均繼承自Object

這樣我們就解決了原廠模式無法區分物件類型的尷尬。那麼使用構造方法來建立對象是否已經完美了呢?

我們知道在JS中,函數是對象。那麼,當我們執行個體化不止一個Student對象的時候:

var v1 = new Student("easy1", 20);var v2 = new Student("easy2", 20);...var vn = new Student("easyn", 20);

其中共同的alertName()函數也被執行個體化了n次,我們可以用以下方法來檢測不同的Student對象並不共用alertName()函數:

alert(v1.alertName == v2.alertName);  //flase

這無疑是一種記憶體的浪費。我們知道,this對象是在運行時基於函數的執行環境進行綁定的。在全域函數中,this對象等同於window;在對象方法中,this指向該對象。在上面的建構函式中:

this.alertName = function(){    alert(this.name)  };

我們在建立對象(執行alertName函數之前)時,就將alertName()函數綁定在了該對象上。我們完全可以在執行該函數的時候再這樣做,辦法是將對象方法移到建構函式外部:

function Student(name, age) {  this.name = name;  this.age = age;  this.alertName = alertName;}function alertName() {  alert(this.name);}var stu1 = new Student("easy1", 20);var stu2 = new Student("easy2", 20);

在調用stu1.alert()時,this對象才被綁定到stu1上。

我們通過將alertName()函數定義為全域函數,這樣對象中的alertName屬性則被設定為指向該全域函數的指標。由此stu1和stu2共用了該全域函數,解決了記憶體浪費的問題。

但是,通過全域函數的方式解決對象內部共用的問題,終究不像一個好的解決方案。如果這樣定義的全域函數多了,我們想要將自訂對象封裝的初衷便幾乎無法實現了。更好的方案是通過原型對象模式來解決。

原型模式建立對象函數的原型對象對象執行個體和原型對象的關聯使用原型模型建立對象原型模型建立對象的局限性函數的原型對象

在瞭解如何使用原型模式建立對象之前,有必要先搞清楚什麼是原型對象。

我們建立的每一個函數都有一個prototype屬性,該屬性是一個指標,該指標指向了一個對象。對於我們建立的建構函式,該對象中包含可以由所有執行個體共用的屬性和方法。如下如所示:

在預設情況下,所有原型對象會自動包含一個constructor屬性,該屬性也是一個指標,指向prototype所在的函數:

對象執行個體和原型對象的關聯

在調用建構函式建立新的執行個體時,該執行個體的內部會自動包含一個[[Prototype]]指標屬性,該指標指便指向建構函式的原型對象。注意,這個指標關聯的是執行個體與建構函式的原型對象而不是執行個體與建構函式

使用原型模型建立對象直接在原型對象中添加屬性和方法

瞭解了原型對象之後,我們便可以通過在建構函式原型對象中添加屬性和方法來實現對象間資料的共用了。例如:

function Student() {}Student.prototype.name = "easy";Student.prototype.age = 20;Student.prototype.alertName = function(){                                alert(this.name);                              };var stu1 = new Student();var stu2 = new Student();stu1.alertName();  //easystu2.alertName();  //easyalert(stu1.alertName == stu2.alertName);  //true 二者共用同一函數

以上代碼,我們在Student的protptype對象中添加了name、age屬性以及alertName()方法。但建立的stu1和stu2中並不包含name、age屬性以及alertName()方法,而只包含一個[[prototype]]指標屬性。當我們調用stu1.namestu1.alertName()時,是如何找到對應的屬性和方法的呢?

當我們需要讀取對象的某個屬性時,都會執行一次搜尋。首先在該對象中尋找該屬性,若找到,返回該屬性值;否則,到[[prototype]]指向的原型對象中繼續尋找。

由此我們也可以看出另外一層意思:如果對象執行個體中包含和原型對象中同名的屬性或方法,則對象執行個體中的該同名屬性或方法會屏蔽原型對象中的同名屬性或方法。原因就是“首先在該對象中尋找該屬性,若找到,返回該屬性值;”

擁有同名執行個體屬性或方法的:

中,我們在訪問stu1.name是會得到”EasySir”:

alert(stu1.name);  //EasySir
通過對象字面量重寫原型對象

很多時候,我們為了書寫的方便以及直觀上的”封裝性”,我們往往採用對象字面量直接重寫整個原型對象:

function Student() {}Student.prototype = {  constructor : Student,  name : "easy",  age : 20,  alertName : function() {    alert(this.name);  }};

要特別注意,我們這裡相當於用對象字面量重新建立了一個Object對象,然後使Student的prototype指標指向該對象。該對象在建立的過程中,自動獲得了新的constructor屬性,該屬性指向Object的建構函式。因此,我們在以上代碼中,增加了constructor : Student使其重新指回Student建構函式。

原型模型建立對象的局限性

原型模型在對象執行個體共用資料方面給我們帶來了很大的便利,但通常情況下不同的執行個體會希望擁有屬於自己單獨的屬性。我們將建構函式模型和原型模型結合使用即可兼得資料共用和”不共用”。

構造與原型混合模式建立對象

我們結合原型模式在共用方法屬性以及建構函式模式在執行個體方法屬性方面的優勢,使用以下的方法建立對象:

//我們希望每個stu擁有屬於自己的name和age屬性function Student(name, age) {  this.name = name;  this.age = age;}//所有的stu應該共用一個alertName()方法Student.prototype = {  constructor : Student,  alertName : function() {                alert(this.name);              }}var stu1 = new Student("Jim", 20);var stu2 = new Student("Tom", 21);stu1.alertName();  //Jim  執行個體屬性stu2.alertName();  //Tom  執行個體屬性alert(stu1.alertName == stu2.alertName);  //true  共用函數

以上,在建構函式中定義執行個體屬性,在原型中定義共用屬性的模式,是目前使用最廣泛的方式。通常情況下,我們都會預設使用這種方式來定義參考型別變數。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.