javascript
[转]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函數源源不斷地”生產”對象了??雌饋硪呀浉哒頍o憂了,但貪婪的人類總有不滿足于現狀的天性:我們不僅希望”產品”的生產可以像工廠車間一般源源不斷,我們還想知道生產的產品究竟是哪一種類型的。
比如說,我們同時又定義了”生產”水果對象的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); //true alert(v2 instanceof Student); //false alert(v1 instanceof Fruit); //false alert(v2 instanceof Fruit); //true alert(v1 instanceof Object); //true 任何對象均繼承自Object alert(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(); //easy stu2.alertName(); //easy alert(stu1.alertName == stu2.alertName); //true 二者共享同一函數?
以上代碼,我們在Student的protptype對象中添加了name、age屬性以及alertName()方法。但創建的stu1和stu2中并不包含name、age屬性以及alertName()方法,而只包含一個[[prototype]]指針屬性。當我們調用stu1.name或stu1.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 共享函數以上,在構造函數中定義實例屬性,在原型中定義共享屬性的模式,是目前使用最廣泛的方式。通常情況下,我們都會默認使用這種方式來定義引用類型變量。
?
引用自:http://blog.csdn.net/a153375250/article/details/51083245
?
轉載于:https://www.cnblogs.com/tracyzeng/p/6183211.html
總結
以上是生活随笔為你收集整理的[转]JavaScript构造函数及原型对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c#unity
- 下一篇: ant 安装及基础教程 !