js中原型和原型鏈「javascript原型鏈詳解」

一、原型

1.函數原型

在JavaScript中,函數不僅僅是一個可以重用的代碼塊,而且還可以作為一種數據使用。在堆空間中為函數分配了它的存儲空間,函數名或函數的其他形式的引用保存了這個存儲空間的引用地址。所以JavaScript中的函數是一種引用數據類型,這就是為什麼我們說JavaScript中的函數也是對象。

那麼函數這樣的對象有很多特殊的性質,原型就是其中之一。每一個函數對象都包含一個屬性:prototype。當我們聲明一個函數時,函數對象就創建好了。而函數對象創建的同時,系統還會同時創建一個對象,並讓函數對象的prototype屬性指向它。

比如說,當我們執行下面代碼時:

function Person(perName,perAge){//…}

就聲明了一個函數,系統會將這個函數對象創建出來。內存中應該是這樣的情況:

JavaScript的原型與原型的作用

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

JavaScript的原型與原型的作用

2.對象原型

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

下圖表示了它們之間的關係

JavaScript的原型與原型的作用

所以既然函數的原型對象是可以為所有被創建對象共享的,那麼就可以將我們要為所有對象都添加的屬性添加到原型對象上。

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屬性。

JavaScript的原型與原型的作用

二、原型鏈

在研究了對象原型和函數原型的關係後,我們還可以進一步深入思考:既然原型對象是一個「對象」,那麼這個對象有沒有__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

說明從本質上來說,任何對象的創建都依賴對應的構造器函數,當然也包含原型機制。既然如此,那麼原型對象本身也是一個對象,這個對象也指向一個原型對象,那麼原型對象的原型對象也是對象,可以繼續指向一個原型對象……這就是原型鏈。

JavaScript的原型與原型的作用

但原型鏈並不是無止境的,到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的實例了。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/256077.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-15 12:33
下一篇 2024-12-15 12:33

相關推薦

發表回復

登錄後才能評論