你真的完全理解原型與原型鏈?

前言:原型與原型鏈兩個(gè)名詞對(duì)于大部分的前端初學(xué)者來(lái)說(shuō),對(duì)這兩個(gè)概念很模糊,無(wú)從入手,而且還可能會(huì)一臉懵逼,本人不才,準(zhǔn)備介紹一下原型以及原型鏈。本文從下面幾個(gè)方面講解,然后在深入講解原型與原型鏈的相關(guān)知識(shí)。

  • 全局對(duì)象
  • 簡(jiǎn)單類型與對(duì)象
  • Number、Boolean、String、Object四個(gè)對(duì)象
  • (共用屬性)原型
  • __proto__與 prototype
  • 燒腦的等式
  • 奇葩的Function

全局對(duì)象Window

ECMAScript 規(guī)定全局對(duì)象叫做 global,但是瀏覽器把 window 作為全局對(duì)象(瀏覽器先存在的)

ECMAScript規(guī)定 瀏覽器自己加的屬性(私有)
parseInt alert
parseFloat comfirm
Number console.log
String console.dir
Boolean document
Object history

注:

  • window對(duì)象中的所有方法都可以省去window,alert()方法可以寫成window.alert(),或者省去window,直接寫做alert()
  • 瀏覽器自己加的屬性,因?yàn)闆](méi)有標(biāo)準(zhǔn),所以瀏覽器上呈現(xiàn)的效果是不一樣的

簡(jiǎn)單類型與對(duì)象

聲明一個(gè)數(shù)值類型的變量

//聲明簡(jiǎn)單的數(shù)據(jù)類型
var n1 = 1;
//聲明一個(gè)對(duì)象,可以用n2.toString()將其轉(zhuǎn)換成字符串
var n2 = new Number(1);

下面通過(guò)內(nèi)存圖查看它們之間的區(qū)別


簡(jiǎn)單類型與對(duì)象.png

它們?cè)趦?nèi)存中的存儲(chǔ)方式是不同的,n1是直接聲明簡(jiǎn)單的數(shù)據(jù)類型(number),存儲(chǔ)在stack棧內(nèi)存中,而n2是聲明了一個(gè)對(duì)象stack內(nèi)存中存儲(chǔ)著該對(duì)象的內(nèi)存地址,對(duì)象的內(nèi)容存儲(chǔ)在heap堆內(nèi)存中。
但是在平常寫代碼的時(shí)候我們沒(méi)有使用n2的寫法,也可以使用valueOf() 方法 和toSting()方法,這里有一段黑歷史(JS之父為了滿足Boss的需求,為了讓JavaScript長(zhǎng)得像Java),可以自行g(shù)oogle
原因是:JS使用妙計(jì):臨時(shí)轉(zhuǎn)換(用完了就沒(méi)了),如果你使用n1的寫法,調(diào)用了了如valueOf方法,那么JS會(huì)創(chuàng)建一個(gè)臨時(shí)的對(duì)象,然后調(diào)用該對(duì)象的方法,調(diào)用結(jié)束后臨時(shí)對(duì)象就會(huì)被垃圾回收掉。

踩坑時(shí)間到:

var n =1;
n.xxx = 2;
//①n.xxx = 2;會(huì)報(bào)錯(cuò)嗎?
//n.xxx的值是多少?

你覺(jué)得是結(jié)果會(huì)怎樣,下面就來(lái)揭秘啦
答案:n.xxx = 2;這句話不會(huì)報(bào)錯(cuò),執(zhí)行這句話時(shí),JS會(huì)創(chuàng)建一個(gè)臨時(shí)對(duì)象,為臨時(shí)對(duì)象添加屬性,執(zhí)行這句話后,臨時(shí)對(duì)象就會(huì)被回收,如果再執(zhí)行n.xxx,結(jié)果是undefined,因?yàn)榻on添加完屬性后臨時(shí)對(duì)象就會(huì)被回收,n本質(zhì)上還是一個(gè)數(shù)值,沒(méi)有臨時(shí)對(duì)象,再去重新創(chuàng)建一個(gè)新的對(duì)象,里面沒(méi)有這個(gè)屬性(有個(gè)這屬性的對(duì)象已經(jīng)被刪了)。

Number對(duì)象

Number的常用屬性 含義
Number.valueOf() 獲取對(duì)象本身的值
Number.toString() 將數(shù)值轉(zhuǎn)化為字符串
Number.toFixed() 將其轉(zhuǎn)換為小數(shù)
Number.toExponential() 將其轉(zhuǎn)化為科學(xué)計(jì)數(shù)法

如:

var n2 = new Number(1);
n2.toString();  //"1"
n2.valueOf();  //1
n2.toFixed(2);  //"1.00"
n2.toExponential();  //"1e+0"

注:number類型的的toString方法可以加參數(shù),表示按照什么進(jìn)制來(lái)解析(不加參數(shù)默認(rèn)按十進(jìn)制解析),如:

(100).toString(16);  //"64"
(100).toString(2); //"1100100"

String對(duì)象

String的常用屬性 含義
String.charAt() 獲取字符串中某一位的字符
String.charCodeAt() 獲取字符串中某一位的字符的Unicode編碼
String.trim() 刪除字符串中多余的空格
String1.concat(String2) 連接字符串1和字符串2
String.slice(start,end) 切片,截取字符串(包前不包后),從start到end
String.replace('e','o') 將字符串中的e替換成o(只能替換第一次出現(xiàn)的字符)
String.indexOf() 搜索字符串中的內(nèi)容(只檢測(cè)到第一次出現(xiàn)的字符),沒(méi)搜到返回-1
String.split() 分隔
String.substr(start[, length]) 截取,返回一個(gè)字符串中從指定位置開(kāi)始到指定字符數(shù)的字符

如:

var s = new String('hello World');

//獲取字符串中某一位的字符
s.charAt(1) ; //"e"

//獲取字符串中某一位的字符的Unicode編碼
s.charCodeAt(0);  //104

//刪除字符串中多余的空格,是左右兩面的空格
s.trim();  //"hello World"

//連接字符串1和字符串2,字符串1和字符串2是沒(méi)有被改變的
var s1 = "hello";
var s2 = " world";
s1.concat(s2);  //"hello world"
console.log(s1);  //"hello"
console.log(s2);  //" world"

//切片,截取字符串(包前不包后),從start到end
s.slice(0,2);  //"he"

//將字符串中的e替換成o(只能替換第一次出現(xiàn)的字符)
s.replace('e','o');  //"hollo World"

//搜索字符串中的內(nèi)容(只檢測(cè)到第一次出現(xiàn)的字符),沒(méi)搜到返回-1
s.indexOf('s')  //-1

//根據(jù)字符串中間的空格分隔字符串,變成數(shù)組
s.split();  //["hello World"]

//返回一個(gè)字符串中從指定位置開(kāi)始到指定字符數(shù)的字符
s.substr(0);  //"hello World"
s.substr(0,5);  //"hello"

Boolean對(duì)象

介紹一個(gè)兩種不同的賦值方法下容易出錯(cuò)的地方。

var f1 = false;
var f2 = new Boolean(false);
if(f1) { console.log('1') } ;
if(f2) { console.log('2') } ;

注:
f1和f2的值都是false,但是f2是對(duì)象,一切對(duì)象(不論是否是空對(duì)象)都是truey,所以使用if判斷語(yǔ)句,會(huì)將f2轉(zhuǎn)化為了true,打印出2

Object對(duì)象

Object對(duì)象,兩種賦值方法是一樣的,沒(méi)有任何區(qū)別。
注:

var obj1 = {};
var obj2 = {};
obj1 === obj2; // false

為什么obj1不恒等于obj2???同樣都是空獨(dú)享,但是它們?cè)趕tack棧內(nèi)存中存儲(chǔ)的內(nèi)容是heap堆內(nèi)存中的地址,每個(gè)對(duì)象的內(nèi)容在heap內(nèi)存中的地址是不會(huì)一樣的,所以對(duì)象與對(duì)象一般都是不相等的。(除非你將一個(gè)對(duì)象的內(nèi)存地址復(fù)制給另一個(gè)對(duì)象)

敲黑板,重頭戲來(lái)了

原型/共用屬性

所有對(duì)象都有 toString 和 valueOf 屬性,那么我們是否有必要給每個(gè)對(duì)象一個(gè) toString 和 valueOf 呢?答案是不需要的。因?yàn)镴S每次聲明一個(gè)對(duì)象都要寫一次這些方法這樣寫的話會(huì)非常占用內(nèi)存,而且內(nèi)存還那么,所以JS 的做法是把所有的對(duì)象共用的屬性全部放在heap堆內(nèi)存的一個(gè)對(duì)象(共用屬性組成的對(duì)象),然后讓每一個(gè)對(duì)象的 __proto__存儲(chǔ)這個(gè)「共用屬性組成的對(duì)象」的地址。而這個(gè)共用屬性,就是傳說(shuō)中的原型
原型的目的:可以減少不必要的內(nèi)存浪費(fèi)
如:

var s1 = new String('hi');
var s2 = new String('he');
var n1 = new Number(1);
var n2 = new Number(2);
var b1 = new Boolean(true);
var b2 = new Boolean(false);
var o1 = {
    name: 'xiao',
    age: 6
}
var o2 = {
    name: 'ming',
    age: 18
}

根據(jù)下面內(nèi)存圖分析原型

原型.png

圖中紅色的箭頭(不是很明顯,是有兩個(gè)箭頭的),所連成的線,就組成了一條原型鏈

  • __proto__就是這些共用屬性的引用。
  • 聲明Number對(duì)象、String對(duì)象、Boolean對(duì)象時(shí),如聲明Number對(duì)象,在stack棧內(nèi)存中存儲(chǔ)著該對(duì)象的內(nèi)存地址,對(duì)象的內(nèi)容存儲(chǔ)在heap堆內(nèi)存中。對(duì)象的內(nèi)容里面有__proto__,它指向的Number的共用屬性(Number.prototype)。
    某些等式:
//對(duì)象的__proto__指向Object對(duì)象的prototype
var o1= {};
o1.__proto__ === Object.prototype;

var n1 = new Number(2);
//數(shù)值的__proto__指向Number對(duì)象的共用屬性
n1.__proto__ === Number.prototype;
//Number對(duì)象的共用屬性的__proto__指向Object對(duì)象的共用屬性。
n1.__proto__.__proto__ === Object.prototype;

其他對(duì)象也可以得出類似的等式

__proto__ 與 prototype

prototype是瀏覽器本身就寫好的.png

上圖是我們還沒(méi)有寫代碼的時(shí)候,瀏覽器都已經(jīng)初始化好了,prototype是瀏覽器提前準(zhǔn)備好的。
當(dāng)我們寫代碼的時(shí)候:

var s = new String(' hello ') ;

我們創(chuàng)建的對(duì)象的__proto__ 會(huì)用來(lái)指向原有的String對(duì)象,使得我們可以調(diào)用String對(duì)象的公有屬性。
總結(jié):

  • __proto__是某對(duì)象的共用屬性的引用,是為了用戶使用其共用屬性中的方法而存在的 。(使用的)
  • prototype 是瀏覽器寫的,本身就存在,是某對(duì)象的共同屬性的引用,為了不讓對(duì)象的共用屬性因沒(méi)有被調(diào)用而被垃圾回收而存在。(防止回收)

一些燒腦的等式

通過(guò)var 對(duì)象 = new 函數(shù);推出其他燒腦的等式

var n = new Number(1);
//var 對(duì)象 = new 函數(shù);

//對(duì)象的__proto__最終指向某對(duì)象的共用屬性,構(gòu)造某對(duì)象的函數(shù)的prototype也指向某對(duì)象的共用屬性
//__proto__ 是對(duì)象的屬性,prototype是函數(shù)的屬性
對(duì)象.__proto__ === 函數(shù).prototype

//函數(shù)的prototype是對(duì)象,這個(gè)對(duì)象對(duì)應(yīng)的就是最簡(jiǎn)單的函數(shù)Object
函數(shù).prototype.__proto__ === Object.prototype

//由于函數(shù)本身即是函數(shù)(最優(yōu)先被視為函數(shù)),也是對(duì)象,而函數(shù)的構(gòu)造函數(shù)是Function
函數(shù).__proto__ === Function.prototype

//Function即是對(duì)象,也是函數(shù),但他優(yōu)先是個(gè)函數(shù)
Function.__proto__ === Function.prototype

//Function.prototype也是對(duì)象,是普通的對(duì)象,所以其對(duì)應(yīng)的函數(shù)是Object
Funciton.prototype.__proto__=== Object.prototype

奇葩的Function

我們經(jīng)過(guò)上面的推導(dǎo),發(fā)現(xiàn)Function,他即是函數(shù),也是對(duì)象,所以他有函數(shù)的prototype,也有對(duì)象的__proto__,即Function.prototype 與Funciton.__proto__互相引用。
注:
Object.__proto__ === Function.prototype,因?yàn)?Function 是 Object 的構(gòu)造函數(shù)。

參考資料:
初識(shí)傳說(shuō)中的原型與原型鏈

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言:原型與原型鏈這兩個(gè)名詞毋庸置疑聽(tīng)起來(lái)是很高大上的,以前總是出現(xiàn)在傳說(shuō)中,這兩天看了教程,準(zhǔn)備介紹一下這傳說(shuō)中...
    EnochQin閱讀 734評(píng)論 0 3
  • 什么是原型語(yǔ)言 只有對(duì)象,沒(méi)有類;對(duì)象繼承對(duì)象,而不是類繼承類。 “原型對(duì)象”是核心概念。原型對(duì)象是新對(duì)象的模板,...
    zhoulujun閱讀 2,454評(píng)論 0 12
  • 在這篇文章里我們將要了解以下幾個(gè)方面: 關(guān)于內(nèi)存的那點(diǎn)事兒 關(guān)于垃圾回收那點(diǎn)事兒 包裝對(duì)象 什么是原型 什么是原型...
    長(zhǎng)鯨向南閱讀 864評(píng)論 0 1
  • 寫在前面: 因?yàn)樯蠈W(xué)交手機(jī),其實(shí)根本不能按時(shí)更新,在微博看見(jiàn)催更的時(shí)候又開(kāi)心又惶恐,我其實(shí)怕我寫的東西就是垃圾,所...
    三R哥閱讀 632評(píng)論 7 2
  • 陸奇最大的遺憾是未能憑借自己的能力把百度這艘大船真正調(diào)頭。應(yīng)該也不是陸奇與百度內(nèi)部具體某個(gè)人的沖突。參加婚禮的賓客...
    ciwwoetyve閱讀 204評(píng)論 0 0

友情鏈接更多精彩內(nèi)容