前言:原型與原型鏈兩個(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()方法可以寫(xiě)成window.alert()店展,或者省去window,直接寫(xiě)做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)存中糖赔。
但是在平常寫(xiě)代碼的時(shí)候我們沒(méi)有使用n2的寫(xiě)法,也可以使用valueOf() 方法 和toSting()方法,這里有一段黑歷史(JS之父為了滿足Boss的需求轩端,為了讓JavaScript長(zhǎng)得像Java)放典,可以自行g(shù)oogle
原因是:JS使用妙計(jì):臨時(shí)轉(zhuǎn)換(用完了就沒(méi)了),如果你使用n1的寫(xiě)法,調(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ì)象都要寫(xiě)一次這些方法這樣寫(xiě)的話會(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)有寫(xiě)代碼的時(shí)候,瀏覽器都已經(jīng)初始化好了拟淮,prototype是瀏覽器提前準(zhǔn)備好的干茉。
當(dāng)我們寫(xiě)代碼的時(shí)候:
var s = new String(' hello ') ;
我們創(chuàng)建的對(duì)象的__proto__
會(huì)用來(lái)指向原有的String對(duì)象,使得我們可以調(diào)用String對(duì)象的公有屬性很泊。
總結(jié):
-
__proto__
是某對(duì)象的共用屬性的引用角虫,是為了用戶使用其共用屬性中的方法而存在的 。(使用的) - prototype 是瀏覽器寫(xiě)的委造,本身就存在戳鹅,是某對(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ō)中的原型與原型鏈