1.對(duì)象是什么
對(duì)象就是若干屬性的集合。
-
在JS中一切引用類(lèi)型都是對(duì)象:數(shù)組是對(duì)象葫盼,函數(shù)是對(duì)象残腌,對(duì)象還是對(duì)象。對(duì)象里面的一切都是屬性剪返,只有屬性废累,沒(méi)有方法。那么這樣方法如何表示呢脱盲?方法也是一種屬性邑滨。因?yàn)樗膶傩员硎緸殒I值對(duì)的形式。如:
var obj = { a: 10, b: function() {}, c: { c1: '100', c2: function() {} } }
-
上面說(shuō)到函數(shù)钱反、數(shù)組都是對(duì)象掖看,那么它們也可以隨意添加屬性。如:
function fn() {} fn.a = 10 fn.b = function() { return 100 } 這個(gè)特性有什么作用呢面哥? jQeury中'$'或者'jQuery',這個(gè)變量其實(shí)是一個(gè)函數(shù)哎壳。 typeof $ // function 而通過(guò)$可以調(diào)用很多方法,如:$.trim('abc ') //'abc' 很明顯這是在$函數(shù)上加了一個(gè)trim屬性尚卫,而這個(gè)屬性是一個(gè)函數(shù)归榕。
如何判斷一個(gè)變量是否是對(duì)象:xxx instanceof Object
2.函數(shù)和對(duì)象的關(guān)系
-
第一節(jié)說(shuō)到,函數(shù)是一種對(duì)象吱涉,因?yàn)橥ㄟ^(guò)instanceof函數(shù)可以判斷刹泄。但是函數(shù)與對(duì)象之間,卻不僅僅是一種包含和被包含的關(guān)系怎爵,函數(shù)和對(duì)象之間的關(guān)系比較復(fù)雜特石,甚至有一點(diǎn)雞生蛋蛋生雞的邏輯。如:
function fn() { this.name = 'bill', this.age = '99' } var fn1 = new fn() // fn1:{name: "bill", age: "99"} 這個(gè)例子說(shuō)明了對(duì)象可以通過(guò)函數(shù)來(lái)創(chuàng)建鳖链。
-
其實(shí)所有對(duì)象都是通過(guò)函數(shù)來(lái)創(chuàng)建的,有人可能有疑問(wèn):
var obj = {} var arr = [] 同過(guò)這種方式也能創(chuàng)建對(duì)象澳氛骸! 但這種方式只是‘快捷方式’而已芙委,在編程語(yǔ)言中一般稱(chēng)其為‘語(yǔ)法糖’逞敷。以上代碼的本質(zhì)是: var obj = new Object() var arr = new Array() typeof Object // function typeof Array // function 綜上,可以說(shuō)對(duì)象都是通過(guò)函數(shù)來(lái)創(chuàng)建的题山。
3.prototype(原型)
-
每個(gè)函數(shù)都默認(rèn)有個(gè)prototype屬性兰粉,這個(gè)屬性的值是一個(gè)對(duì)象,而這個(gè)對(duì)象默認(rèn)只有一個(gè)屬性constructor,指向這個(gè)函數(shù)本身顶瞳。如圖:(左側(cè)是一個(gè)函數(shù),而右側(cè)是它的原型)
-
既然prototype是個(gè)對(duì)象,那么就可以為它們添加屬性:
function fn () {} fn.prototype.name = 'bill' fn.prototype.age = function () { return 99 }
-
每個(gè)對(duì)象都有一個(gè)隱藏屬性——"__proto__"慨菱,這個(gè)屬性引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype焰络。也就是說(shuō),每個(gè)對(duì)象都可以共同使用它們的父函數(shù)的原型對(duì)象中的屬性符喝。
第一節(jié)例子中所說(shuō)的jQery的其實(shí)就是根據(jù)這個(gè)原理實(shí)現(xiàn)的: $.prototype.trim = function() { .... }
函數(shù)的prototype也是一個(gè)對(duì)象闪彼,那么這個(gè)對(duì)象也有一個(gè)"__proto_"屬性,它指向Object.prototype协饲。但是Object.prototype是一個(gè)特例——它的__proto_指向的是null
-
上面所過(guò)函數(shù)也是一個(gè)對(duì)象畏腕,那么函數(shù)的"__proto__"屬性指向什么?
function fn(){} 等價(jià)于=》 var fn = new Function() 所以說(shuō)函數(shù)的\_\_proto__就指向Function的prototype茉稠。 而Funtion也是一個(gè)函數(shù)描馅,既然是函數(shù),那么它一定是被Function創(chuàng)建,它的\_\_proto__就指向它自身的prototype而线。
4.instanceof
- instanceof用于判斷一個(gè)對(duì)象的類(lèi)型铭污,那么它的判斷規(guī)則是什么?
Instanceof運(yùn)算符的第一個(gè)變量是一個(gè)對(duì)象膀篮,暫時(shí)稱(chēng)為A嘹狞;第二個(gè)變量一般是一個(gè)函數(shù),暫時(shí)稱(chēng)為B誓竿。
-
Instanceof的判斷規(guī)則是:沿著A的__proto__這條線來(lái)找磅网,同時(shí)沿著B(niǎo)的prototype這條線來(lái)找,如果兩條線能找到同一個(gè)引用筷屡,即同一個(gè)對(duì)象涧偷,那么就返回true。如果找到終點(diǎn)還未重合速蕊,則返回false嫂丙。
通過(guò)以上規(guī)則可以解釋很多怪異現(xiàn)象 Object instanceof Function //true //Object是一個(gè)函數(shù),所以O(shè)bject.__proto__指向Function.prototype Function instanceof Object //true //Funtion是一個(gè)函數(shù)规哲,所以Function.__proto__指向Function.prototype,而Function.prototype.__proto__指向Object.prototype
5.繼承/原型鏈
-
JS中的繼承是通過(guò)原型鏈實(shí)現(xiàn)的跟啤,先看下面的例子:
function Foo() {} var f1 = new Foo() f1.a = 10 Foo.prototype.a = 100 Foo.prototype.b = 200 f1.a //10 f1.b //200 以上代碼中,f1是Foo函數(shù)new出來(lái)的對(duì)象唉锌,f1.a是f1對(duì)象的基本屬性隅肥,f1.b是怎么來(lái)的呢?——從Foo.prototype得來(lái)袄简,因?yàn)閒1.__proto__指向的是Foo.prototype
訪問(wèn)一個(gè)對(duì)象的屬性時(shí)腥放,先在基本屬性中查找,如果沒(méi)有绿语,再沿著proto這條鏈向上找秃症,這就是原型鏈候址。
-
那么我們?cè)趯?shí)際應(yīng)用中如何區(qū)分一個(gè)屬性到底是基本的還是從原型中找到的呢?大家可能都知道答案了——hasOwnProperty种柑,特別是在for…in…循環(huán)中岗仑,一定要注意。
f1.hasOwnProperty(b) // false f1本身沒(méi)有這個(gè)方法聚请,它是從Object.prototype中來(lái)的荠雕。如圖
由于所有的對(duì)象的原型鏈都會(huì)找到Object.prototype,因此所有的對(duì)象都會(huì)有Object.prototype的方法驶赏。這就是所謂的“繼承”炸卑。你可以利用原型鏈來(lái)實(shí)現(xiàn)自己的繼承。
-
如果繼承的方法不合適煤傍,可以做出添加/修改盖文。
var obj = {a: 1, b: 2} obj.toString() //[object Object] var arr = [1, 2, 3] arr.toString() // 1,2,3 Object和Array的toString()方法不一樣』季茫肯定是Array.prototype.toString()方法做了修改椅寺。
6.執(zhí)行上下文
-
先看一段代碼:
console.log(a) // referenceError console.log(b) // undifined var b console.log(c) // undifined var c = 1 第一句報(bào)錯(cuò),a未定義蒋失,很正常返帕。第二句、第三句輸出都是undefined篙挽,說(shuō)明瀏覽器在執(zhí)行console.log(b)時(shí)荆萤,已經(jīng)知道了b是undefined,但卻不知道c是1铣卡。
-
在一段js代碼拿過(guò)來(lái)真正一句一句運(yùn)行之前链韭,瀏覽器已經(jīng)做了一些“準(zhǔn)備工作”,其中就包括對(duì)變量的聲明煮落,而不是賦值胸梆。變量賦值是在賦值語(yǔ)句執(zhí)行的時(shí)候進(jìn)行的怀吻◇菁遥可用下圖模擬:
-
第二種情況:第一種情況只是對(duì)變量進(jìn)行聲明(并沒(méi)有賦值)瑰钮,而此種情況直接給this賦值。這也是“準(zhǔn)備工作”情況要做的事情之一轿衔。
-
第三種情況:需要注意代碼注釋中的兩個(gè)名詞——“函數(shù)表達(dá)式”和“函數(shù)聲明”沉迹。雖然兩者都很常用,但是這兩者在“準(zhǔn)備工作”時(shí)害驹,卻是兩種待遇鞭呕。
在“準(zhǔn)備工作”中,對(duì)待函數(shù)表達(dá)式就像對(duì)待“ var a = 10 ”這樣的變量一樣宛官,只是聲明葫松。而對(duì)待函數(shù)聲明時(shí)瓦糕,卻把函數(shù)整個(gè)賦值了。
-
我們總結(jié)一下进宝,在“準(zhǔn)備工作”中完成了哪些工作:
- 變量刻坊、函數(shù)表達(dá)式——變量聲明枷恕,默認(rèn)賦值為undefined党晋;
- this——賦值;
- 函數(shù)聲明——賦值徐块;
- 這三種數(shù)據(jù)的準(zhǔn)備情況我們稱(chēng)之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”未玻。
javascript在執(zhí)行一個(gè)"代碼段"之前,都會(huì)進(jìn)行這些“準(zhǔn)備工作”來(lái)生成執(zhí)行上下文胡控。這個(gè)“代碼段”其實(shí)分三種情況——全局代碼扳剿,函數(shù)體,eval代碼昼激。eval不常用庇绽,也不推薦大家用。
-
如果在函數(shù)中橙困,除了以上數(shù)據(jù)之外瞧掺,還會(huì)有其他數(shù)據(jù)。先看以下代碼:
以上代碼展示了在函數(shù)體的語(yǔ)句執(zhí)行之前凡傅,arguments變量和函數(shù)的參數(shù)都已經(jīng)被賦值辟狈。從這里可以看出,函數(shù)每被調(diào)用一次夏跷,都會(huì)產(chǎn)生一個(gè)新的執(zhí)行上下文環(huán)境哼转。因?yàn)椴煌恼{(diào)用可能就會(huì)有不同的參數(shù)。
-
函數(shù)在定義的時(shí)候(不是調(diào)用的時(shí)候)槽华,就已經(jīng)確定了函數(shù)體內(nèi)部變量的作用域壹蔓。如圖:
給執(zhí)行上下文環(huán)境下一個(gè)通俗的定義——在執(zhí)行代碼之前,把將要用到的所有的變量都事先拿出來(lái)猫态,有的直接賦值了佣蓉,有的先用undefined占個(gè)空。
7.this
在函數(shù)中this到底取何值懂鸵,是在函數(shù)真正被調(diào)用執(zhí)行的時(shí)候確定的偏螺,函數(shù)定義的時(shí)候確定不了。因?yàn)閠his的取值是執(zhí)行上下文環(huán)境的一部分匆光,每次調(diào)用函數(shù)套像,都會(huì)產(chǎn)生一個(gè)新的執(zhí)行上下文環(huán)境。
this的取值终息,分四種情況夺巩。
-
情況1:構(gòu)造函數(shù):所謂構(gòu)造函數(shù)就是用來(lái)new對(duì)象的函數(shù)贞让。
- 其實(shí)嚴(yán)格來(lái)說(shuō),所有的函數(shù)都可以new一個(gè)對(duì)象柳譬,但是有些函數(shù)的定義是為了new一個(gè)對(duì)象喳张,而有些函數(shù)則不是。
- 如果函數(shù)作為構(gòu)造函數(shù)用美澳,那么其中的this就代表它即將new出來(lái)的對(duì)象销部。
- 不僅僅是構(gòu)造函數(shù)的prototype,即便是在整個(gè)原型鏈中制跟,this代表的也都是當(dāng)前對(duì)象的值舅桩。
-
情況2:函數(shù)作為對(duì)象的一個(gè)屬性
- 如果函數(shù)作為對(duì)象的一個(gè)屬性時(shí),并且作為對(duì)象的一個(gè)屬性被調(diào)用時(shí)雨膨,函數(shù)中的this指向該對(duì)象擂涛。
-
情況3:函數(shù)用call或者apply調(diào)用
- 當(dāng)一個(gè)函數(shù)被call和apply調(diào)用時(shí),this的值就取傳入的對(duì)象的值聊记。
-
情況4:全局 & 調(diào)用普通函數(shù)
- 在全局環(huán)境下撒妈,this永遠(yuǎn)是window.
- 普通函數(shù)在調(diào)用時(shí),其中的this也都是window排监。
8.執(zhí)行上下文棧
-
執(zhí)行全局代碼時(shí)狰右,會(huì)產(chǎn)生一個(gè)執(zhí)行上下文環(huán)境,每次調(diào)用函數(shù)都又會(huì)產(chǎn)生執(zhí)行上下文環(huán)境社露。當(dāng)函數(shù)調(diào)用完成時(shí)挟阻,這個(gè)上下文環(huán)境以及其中的數(shù)據(jù)都會(huì)被消除,再重新回到全局上下文環(huán)境峭弟。處于活動(dòng)狀態(tài)的執(zhí)行上下文環(huán)境只有一個(gè)附鸽。這是一個(gè)壓棧出棧的過(guò)程。如下圖(藍(lán)色表示活動(dòng)狀態(tài)):
9.作用域
- “javascript沒(méi)有塊級(jí)作用域”瞒瘸。所謂“塊”坷备,就是大括號(hào)“{}”中間的語(yǔ)句。所以情臭,我們?cè)诰帉?xiě)代碼的時(shí)候省撑,不要在“塊”里面聲明變量,要在代碼的一開(kāi)始就聲明好了俯在。以避免發(fā)生歧義竟秫。
- javascript除了全局作用域之外,只有函數(shù)可以創(chuàng)建的作用域跷乐。
- 所以肥败,我們?cè)诼暶髯兞繒r(shí),全局代碼要在代碼前端聲明,函數(shù)中要在函數(shù)體一開(kāi)始就聲明好馒稍。除了這兩個(gè)地方皿哨,其他地方都不要出現(xiàn)變量聲明。而且建議用“單var”形式纽谒。
- 如上圖证膨,全局代碼和fn、bar兩個(gè)函數(shù)都會(huì)形成一個(gè)作用域鼓黔。而且央勒,作用域有上下級(jí)的關(guān)系,上下級(jí)關(guān)系的確定就看函數(shù)是在哪個(gè)作用域下創(chuàng)建的请祖。例如订歪,fn作用域下創(chuàng)建了bar函數(shù),那么“fn作用域”就是“bar作用域”的上級(jí)肆捕。
- 作用域最大的用處就是隔離變量,不同作用域下同名變量不會(huì)有沖突盖高。
- 除了全局作用域之外慎陵,每個(gè)函數(shù)都會(huì)創(chuàng)建自己的作用域,作用域在函數(shù)定義時(shí)就已經(jīng)確定了喻奥。而不是在函數(shù)調(diào)用時(shí)確定席纽。
- 作用域只是一個(gè)“地盤(pán)”,一個(gè)抽象的概念撞蚕,其中沒(méi)有變量润梯。要通過(guò)作用域?qū)?yīng)的執(zhí)行上下文環(huán)境來(lái)獲取變量的值。同一個(gè)作用域下甥厦,不同的調(diào)用會(huì)產(chǎn)生不同的執(zhí)行上下文環(huán)境纺铭,繼而產(chǎn)生不同的變量的值。所以刀疙,作用域中變量的值是在執(zhí)行過(guò)程中產(chǎn)生的確定的舶赔,而作用域卻是在函數(shù)創(chuàng)建時(shí)就確定了。
- 如果要查找一個(gè)作用域下某個(gè)變量的值谦秧,就需要找到這個(gè)作用域?qū)?yīng)的執(zhí)行上下文環(huán)境竟纳,再在其中尋找變量的值。
- 什么是“自由變量”:
- 在A作用域中使用的變量x疚鲤,卻沒(méi)有在A作用域中聲明(即在其他作用域中聲明的)锥累,對(duì)于A作用域來(lái)說(shuō),x就是一個(gè)自由變量集歇。
- 那么到哪里去取自由變量呢桶略? 要到創(chuàng)建這個(gè)函數(shù)的那個(gè)作用域中取值——是“創(chuàng)建”,而不是“調(diào)用”
- 如果跨了一步,還沒(méi)找到呢删性?——接著跨亏娜!——一直跨到全局作用域?yàn)橹埂R窃谌肿饔糜蛑卸紱](méi)有找到蹬挺,那就是真的沒(méi)有了维贺。這個(gè)一步一步“跨”的路線,我們稱(chēng)之為——作用域鏈巴帮。
10.閉包
- 在一個(gè)作用域中引用另一個(gè)作用域中的變量就形成了閉包溯泣。
- 閉包應(yīng)用的兩種情況:
-
第一,函數(shù)作為返回值:
- 如上代碼榕茧,bar函數(shù)作為返回值垃沦,賦值給f1變量。執(zhí)行f1(15)時(shí)用押,用到了fn作用域下的max變量的值肢簿。
-
第二,函數(shù)作為參數(shù)被傳遞:
- 如上代碼中蜻拨,fn函數(shù)作為一個(gè)參數(shù)被傳遞進(jìn)入另一個(gè)函數(shù)池充,賦值給f參數(shù)。執(zhí)行f(15)時(shí)缎讼,max變量的取值是10收夸,而不是100。
-
- 有些情況下血崭,函數(shù)調(diào)用完成之后卧惜,其執(zhí)行上下文環(huán)境不會(huì)接著被銷(xiāo)毀。這就是需要理解閉包的核心內(nèi)容夹纫。
- 如上圖當(dāng)bar()被作為函數(shù)返回時(shí)咽瓷,fn函數(shù)執(zhí)行完畢,正常情況下fn的執(zhí)行上下文環(huán)境應(yīng)該被銷(xiāo)毀捷凄,而此時(shí)bar函數(shù)中有一個(gè)自由變量max引用自fn的執(zhí)行上下文忱详,所以fn的執(zhí)行上下文就不會(huì)被銷(xiāo)毀,否則就找不到max的值了跺涤。
11.上下文環(huán)境和作用域的關(guān)系
- 上下文環(huán)境:
- 儲(chǔ)存著作用域中所有變量的對(duì)象匈睁。
- 對(duì)于函數(shù)來(lái)說(shuō),上下文環(huán)境是在調(diào)用時(shí)創(chuàng)建的桶错,這個(gè)很好理解航唆。拿參數(shù)做例子,你不調(diào)用函數(shù)院刁,我哪兒知道你要給我傳什么參數(shù)糯钙?
- 作用域:
- 首先,它很抽象。第二任岸,記住一句話:除了全局作用域再榄,只有函數(shù)才能創(chuàng)建作用域。創(chuàng)建一個(gè)函數(shù)就創(chuàng)建了一個(gè)作用域享潜,無(wú)論你調(diào)用不調(diào)用困鸥,函數(shù)只要?jiǎng)?chuàng)建了,它就有獨(dú)立的作用域剑按,就有自己的一個(gè)“地盤(pán)”疾就。
- 關(guān)系:
- 一個(gè)作用域下可能包含若干個(gè)上下文環(huán)境。有可能從來(lái)沒(méi)有過(guò)上下文環(huán)境(函數(shù)從來(lái)就沒(méi)有被調(diào)用過(guò))艺蝴;有可能有過(guò)猬腰,現(xiàn)在函數(shù)被調(diào)用完畢后,上下文環(huán)境被銷(xiāo)毀了猜敢;有可能同時(shí)存在一個(gè)或多個(gè)(閉包)姑荷。