前言:原型與原型鏈兩個(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ū)別

它們?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)存圖分析原型

圖中紅色的箭頭(不是很明顯,是有兩個(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

上圖是我們還沒(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ō)中的原型與原型鏈