一、原型
1.函數原型
在JavaScript中,函數不僅僅是一個可以重用的代碼塊,而且還可以作為一種數據使用。在堆空間中為函數分配了它的存儲空間,函數名或函數的其他形式的引用保存了這個存儲空間的引用地址。所以JavaScript中的函數是一種引用數據類型,這就是為什么我們說JavaScript中的函數也是對象。
那么函數這樣的對象有很多特殊的性質,原型就是其中之一。每一個函數對象都包含一個屬性:prototype。當我們聲明一個函數時,函數對象就創建好了。而函數對象創建的同時,系統還會同時創建一個對象,并讓函數對象的prototype屬性指向它。
比如說,當我們執行下面代碼時:
function Person(perName,perAge){//…} |
就聲明了一個函數,系統會將這個函數對象創建出來。內存中應該是這樣的情況:


但是更深層次挖掘一下,我還需要知道創建函數對象的同時,還會創建一個“原型對象”,由函數對象的prototype屬性指向這個原型對象。


2.對象原型
函數可以和以它為構造器所創建的所有對象共享原型對象。就比如本文最初展示的代碼中,per01和per02都是Person函數創建的,那么per01或per02就可以通過自身的__proto__屬性關聯到Person對象的原型。
下圖表示了它們之間的關系


所以既然函數的原型對象是可以為所有被創建對象共享的,那么就可以將我們要為所有對象都添加的屬性添加到原型對象上。
function Person(perName,perAge,gender){this.perName = perName;this.perAge = perAge;this.toString = function(){return “PersonName=”+this.perName+” PersonAge=”+this.perAge;};}var per01 = new Person(“Bob”, 20);var per02 = new Person(“Kate”, 25);Person.prototype.message = “Atguig is very good”;console.log(per01.toString()+” message=”+per01.message);console.log(per02.toString()+” message=”+per02.message); |
執行結果: |
PersonName=Bob PersonAge=20 message=Atguig is very goodPersonName=Kate PersonAge=25 message=Atguig is very good |
JavaScript引擎在讀取per01對象的message屬性時先在當前對象本身的空間內查找,如果能找到則直接返回,如果找不到則沿著__proto__屬性找到原型對象,再在原型對象中查找message屬性。


二、原型鏈
在研究了對象原型和函數原型的關系后,我們還可以進一步深入思考:既然原型對象是一個“對象”,那么這個對象有沒有__proto__這個屬性呢?當然有!
function Person(perName,perAge,gender){this.perName = perName;this.perAge = perAge;this.toString = function(){return “PersonName=”+this.perName+” PersonAge=”+this.perAge;};}var person = new Person(“Tom”, 20, “male”);console.log(person.__proto__);console.log(person.__proto__.__proto__); |
執行結果: |
Person {}Object {} |
說明person.__proto__所指向的對象是由Person函數創建的,而
person.__proto__.__proto__所指向的對象是由Object函數創建的。
就對象的本質而言,任何一個對象都是以new 構造器函數的方式創建的,所以所有對象都和構造器函數共享原型對象。這樣說可能你會有疑問,我可以通過{屬性名:屬性值}的方式創建對象呀,這里并沒有用到構造器函數呀?那么情看下面的代碼:
var obj = {“myName”:”Jerry”,”myAge”:15};console.log(obj.constructor);console.log(obj.__proto__ === Object.prototype); |
執行結果: |
function Object()true |
說明從本質上來說,任何對象的創建都依賴對應的構造器函數,當然也包含原型機制。既然如此,那么原型對象本身也是一個對象,這個對象也指向一個原型對象,那么原型對象的原型對象也是對象,可以繼續指向一個原型對象……這就是原型鏈。


但原型鏈并不是無止境的,到Object()函數為止。
var obj = {“myName”:”Jerry”,”myAge”:15};console.log(obj.__proto__.__proto__); |
執行結果: |
null |
三、原型的作用
JavaScript中原型有很廣泛的用途,在此我們僅舉兩例,供大家參考。
1.格式化日期
在JavaScript中對日期格式化的支持不是很完善,需要我們自己彌補。但是用到日期格式化的地方又很多,這畢竟是個基礎操作,那如何能夠一勞永逸的解決這個問題呢?
①通過原型機制將格式化日期的函數添加到Date()函數對象上
代碼如下:
// 對Date的擴展,將 Date 轉化為指定格式的String// 月(M)、日(d)、小時(h)、分(m)、秒(s)、季度(q) 可以用 1-2 個占位符,// 年(y)可以用 1-4 個占位符,毫秒(S)只能用 1 個占位符(是 1-3 位的數字)// 例子:// (new Date()).Format(“yyyy-MM-dd hh:mm:ss.S”) ==> 2006-07-02 08:09:04.423// (new Date()).Format(“yyyy-M-d h:m:s.S”) ==> 2006-7-2 8:9:4.18//var time1 = new Date().format(“yyyy-MM-dd HH:mm:ss”);////var time2 = new Date().format(“yyyy-MM-dd”);Date.prototype.Format = function(fmt) { // author: meizzvar o = {“M+” : this.getMonth() + 1, // 月份“d+” : this.getDate(), // 日“h+” : this.getHours(), // 小時“m+” : this.getMinutes(), // 分“s+” : this.getSeconds(), // 秒“q+” : Math.floor((this.getMonth() + 3) / 3), // 季度“S” : this.getMilliseconds()// 毫秒};if (/(y+)/.test(fmt))fmt = fmt.replace(RegExp.$1, (this.getFullYear() + “”).substr(4 – RegExp.$1.length));for ( var k in o)if (new RegExp(“(” + k + “)”).test(fmt))fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]): ((“00” + o[k]).substr((“” + o[k]).length)));return fmt;} |
②將上述代碼保存到js文件中
③使用時引入這個js 文件即可調用format()函數格式化日期
2.模擬繼承
在JavaScript中沒有類的概念,用于創建對象的構造器函數很類似于Java中的類。而面向對象中的很多思想在JavaScript中也只能模擬實現。
①情景舉例
聲明一個Person構造器函數和一個Student構造器函數。
function Person(theName,theAge){this.theName = theName;this.theAge = theAge;}function Student(theName,theAge,subject){this.theName = theName;this.theAge = theAge;this.subject;} |
很明顯,這兩個函數從語義上來說存在著繼承關系,學生是人的一種,Student對象應該是Person對象的實例。同時給姓名和年齡賦值的語句在兩個函數中也是重復的。
所以模擬繼承時我們需要解決兩個問題:
i.將Student中的重復代碼使用Person來代替
ii.讓Student對象是Person的實例,即student instanceof Person要返回true
②提取重復代碼
function Person(theName,theAge){this.theName = theName;this.theAge = theAge;}function Student(theName,theAge,subject){Person.apply(this, arguments);this.subject;} |
③instanceof
function Person(theName,theAge){this.theName = theName;this.theAge = theAge;}function Student(theName,theAge,subject){Person.apply(this, arguments);this.subject;}Student.prototype = Person.prototype;var student = new Student(“Tom”, 20, “Java”);console.log(student);console.log(student instanceof Person); |
那么這是為什么呢?在JavaScript中,判斷一個對象是否是某個構造器函數的實例,就是看分別沿著對象和函數的原型鏈能否找到同一個原型對象。
例如:student對象為什么能夠是Object的實例呢?
console.log(student instanceof Object); //trueconsole.log(student.__proto__.__proto__ === Object.prototype); //true |
那么現在student.__proto__和Person.prototype相等,student自然就可以是Person的實例了。
版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。