開(kāi)發(fā)過(guò)程中遇到的一些奇妙的事情:(基礎(chǔ)知識(shí)擎浴,碰到了忘了又百度了又怕忘了就記下來(lái)了)
1. 變量、函數(shù)聲明提前:
js中變量的聲明和函數(shù)聲明會(huì)上升棵介,例如:
if ('a ' in window){
var a =10;
console.log(true) // true
}
var a
上升到if
語(yǔ)句之前樊诺。 函數(shù)的聲明優(yōu)于變量聲明,這里的優(yōu)于可以理解為晚于變量聲明后眠屎,如果函數(shù)名和變量名相同,函數(shù)聲明就能覆蓋變量聲明肆饶。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); //輸出undefined
相當(dāng)于
function f() {
var tmp;
console.log(tmp);
if (false) {
tmp = 'hello world';
}
}
es5只有函數(shù)作用域和全局作用域组力,沒(méi)有塊級(jí)作用域,因此if
里面的var tmp
會(huì)提升到f
函數(shù)內(nèi)部的頂部
1 function Foo() {
2 getName = function () { console.log (1); };
3 return this;
4 }
5 Foo.getName = function () { console.log (2);};
6 Foo.prototype.getName = function () { console.log (3);};
7 var getName = function () { console.log (4);};
8 function getName() { console.log (5);}
9
10 //以下輸出值為多少抖拴?
11 Foo.getName();
12 getName();
13 Foo().getName();
14 getName();
15 new Foo.getName();
16 new Foo().getName();
17 new new Foo().getName();
瀏覽器執(zhí)行Js程序的時(shí)候燎字,分兩步:
(1)預(yù)解析
在代碼解讀之前發(fā)生,相當(dāng)于一個(gè)"倉(cāng)庫(kù)"阿宅,放一些東西候衍,比如var、function洒放、參數(shù)等蛉鹿。
預(yù)解析時(shí)變量都是未定義的,函數(shù)則是整個(gè)函數(shù)塊往湿。
預(yù)解析時(shí)遇到重名的妖异,會(huì)覆蓋。變量與函數(shù)领追,留函數(shù)他膳;函數(shù)與函數(shù),留后面解析的绒窑。
當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí)棕孙,會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈。是域就要預(yù)解析些膨。
(2)執(zhí)行代碼
代碼自上而下蟀俊,從左往右的執(zhí)行,讀到表達(dá)式時(shí)订雾,預(yù)解析倉(cāng)庫(kù)中的東西發(fā)生改變肢预。
Foo.getName(); //2
相當(dāng)于執(zhí)行第5行的console
getName(); //4
預(yù)解析里直接取,相當(dāng)于執(zhí)行第7行的console
Foo().getName(); // 1
首先Foo().getName()與Foo.getName()有什么差別呢洼哎,差別就在于Foo()與Foo烫映,F(xiàn)oo()是已經(jīng)把
Foo()的內(nèi)容執(zhí)行了一遍的,F(xiàn)oo則是一個(gè)函數(shù)變量谱净,F(xiàn)oo()里面return this;窑邦,這里鏈?zhǔn)秸{(diào)用的用法,
詳細(xì)的介紹可以搜一下壕探,F(xiàn)oo.getName() 中 getName()是Foo的屬性,但是Foo().getName()中郊丛,
getName()不是Foo()的屬性李请,而是全局變量var getName = function(){return(4);};瞧筛,
Foo().getName()的意思是Foo()執(zhí)行完后,回到全局中导盅,再調(diào)用getName()较幌,這樣就叫鏈?zhǔn)秸{(diào)用。
那為什么結(jié)果是1不是4白翻,因?yàn)镕oo()執(zhí)行的結(jié)果使 getName = function(){return(1);};乍炉,
所以再次調(diào)用getName(),結(jié)果就是1滤馍,
getName(); //1
跟window.getName()一樣岛琼,
當(dāng)于執(zhí)行第2行的console
new Foo.getName(); // 2
執(zhí)行第5行的Foo.getName(),
new操作符調(diào)用構(gòu)造函數(shù)創(chuàng)建對(duì)象的步驟?
(1)創(chuàng)建一個(gè)新對(duì)象巢株;
(2)將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象)
(3)執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
(4)返回新對(duì)象
點(diǎn)(.)的優(yōu)先級(jí)高于new操作槐瑞,遂相當(dāng)于是:
new (Foo.getName)();
new操作符是要執(zhí)行構(gòu)造函數(shù)中的代碼的;
所以實(shí)際上將getName函數(shù)作為了構(gòu)造函數(shù)來(lái)執(zhí)行阁苞,遂打印2
new Foo().getName(); // 3
Foo()執(zhí)行返回this困檩,此時(shí)this指向new出來(lái)的新實(shí)例對(duì)象,
實(shí)例對(duì)象從本身找不到getName屬性那槽,順著原型鏈找到第6行的getName悼沿,打印3
執(zhí)行第6行console
new new Foo().getName(); // 3
以實(shí)例的getName方法為構(gòu)造函數(shù)new實(shí)例,執(zhí)行構(gòu)造函數(shù)骚灸,打印3
執(zhí)行第6行
P.s 這道題目網(wǎng)上有很多显沈。具體出處找不到了,但是很有意思逢唤。
2. 遍歷節(jié)點(diǎn)的Js規(guī)范:
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
doSomething(divs[i]);
}
這樣的操作拉讯,假如獲取節(jié)點(diǎn)長(zhǎng)度(divs.length)的復(fù)雜度是O(n),獲取某個(gè)子節(jié)點(diǎn)的復(fù)雜度也是O(n)鳖藕,則由于每次要計(jì)算節(jié)點(diǎn)長(zhǎng)度魔慷,因而遍歷一遍所有節(jié)點(diǎn),這個(gè)算法的時(shí)間復(fù)雜度就為O(n^2)著恩。用以下方法可降低時(shí)間復(fù)雜度:
var divs = document.getElementsByTagName('div');
var size = divs.length;
for (var i = 0; i < size; i++) {
doSomething(divs[i]);
}
這里先把節(jié)點(diǎn)長(zhǎng)度存到一個(gè)局部變量中院尔,以后每次循環(huán)不再計(jì)算節(jié)點(diǎn)長(zhǎng)度,因此總的時(shí)間復(fù)雜度變?yōu)榱薕(n) + 1;
TGuildeJs規(guī)范
3. 事件監(jiān)聽(tīng):
<ul id="list">
<li id="item1">item1</li>
<li id="item2">item2</li>
<li id="item3">item3</li>
<li id="item4">item4</li>
</ul>
a. 以前為li綁定事件都是獲取li節(jié)點(diǎn)喉誊,然后綁定事件:
window.onload=function(){
var ulNode=document.getElementById("list");
var liNodes=ulNode.childNodes||ulNode.children;
for(var i=0;i<liNodes.length;i++){
liNodes[i].addEventListener('click',function(e){
alert(e.target.innerHTML);
},false);
}
}
由上可以看出來(lái)邀摆,假如不停的刪除或添加li,則function()也要不停的更改操作伍茄,易出錯(cuò)栋盹,因此推薦使用事件代理。在傳統(tǒng)的事件處理中敷矫,你按照需要為每一個(gè)元素添加或者是刪除事件處理器例获。然而汉额,事件處理器將有可能導(dǎo)致內(nèi)存泄露或者是性能下降——你用得越多這種風(fēng)險(xiǎn)就越大。JavaScript事件代理則是一種簡(jiǎn)單的技巧榨汤,通過(guò)它你可以把事件處理器添加到一個(gè)父級(jí)元素上蠕搜,這樣就避免了把事件處理器添加到多個(gè)子級(jí)元素上。
b. 使用事件代理
事件代理用到了兩個(gè)在JavaSciprt事件中兩個(gè)特性:事件冒泡以及目標(biāo)元素收壕。使用事件代理妓灌,我們可以把事件處理器添加到一個(gè)元素上,等待一個(gè)事件從它的子級(jí)元素里冒泡上來(lái)蜜宪,并且可以得知這個(gè)事件是從哪個(gè)元素開(kāi)始的虫埂。
window.onload=function(){
e = e || window.event;/*在函數(shù)體內(nèi)不用使用event = event || window.event; 來(lái)標(biāo)準(zhǔn)化Event 對(duì)象;*/
target = e.target || e.srcElement;
var ulNode=document.getElementById("list");
ulNode.addEventListener('click',function(e){
if(target.nodeName.toUpperCase()=="LI"){/*獲取目標(biāo)元素端壳,判斷目標(biāo)事件是否為li*/
alert(e.target.innerHTML);
}
},false);/*false 默認(rèn)參數(shù)告丢,冒泡階段*/
};
4. 一次完整ajax是如何實(shí)現(xiàn)的?
5. 一次完整Http請(qǐng)求是如何實(shí)現(xiàn)的损谦?
6. 對(duì)跨作用域的變量處理建議:
- 將常用的跨作用域變量?jī)?chǔ)存在局部變量中岖免,然后直接訪問(wèn)局部變量。
7. 關(guān)于作用域鏈
- 每一個(gè)JS函數(shù)都表示一個(gè)對(duì)象照捡,F(xiàn)unction是對(duì)象的一個(gè)實(shí)例颅湘,因此Function對(duì)象擁有可以訪問(wèn)的屬性、方法栗精,和不可以訪問(wèn)僅供JS引擎存取的內(nèi)部屬性闯参。其中一個(gè)內(nèi)部屬性是[[scope]];
[[scope]]包含了一個(gè)函數(shù)被創(chuàng)建的作用域?qū)ο蟮募希@個(gè)集合就是作用域鏈悲立,作用域鏈決定了函數(shù)有權(quán)訪問(wèn)哪些變量和方法鹿寨,作用域鏈中每個(gè)對(duì)象成為可變對(duì)象。
function sum(x,y){
var a =10;
return x+y+a;
}
sum(2,3)
- 當(dāng)sum函數(shù)創(chuàng)建之后薪夕,sum函數(shù)的內(nèi)部屬性[[scope]]所包含的作用域鏈插入了一個(gè)可變對(duì)象脚草,此時(shí)的可變對(duì)象又稱為全局對(duì)象,包含著
諸如window原献,navigator馏慨,document,sum本身 等等姑隅。 - 當(dāng)sum(2,3)執(zhí)行時(shí)写隶,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境,多次執(zhí)行函數(shù)會(huì)創(chuàng)建多個(gè)執(zhí)行環(huán)境讲仰,函數(shù)執(zhí)行完畢后慕趴,執(zhí)行環(huán)境會(huì)自動(dòng)銷毀。
每個(gè)執(zhí)行環(huán)境都有自己的作用域鏈,用于解析標(biāo)識(shí)符秩贰,當(dāng)執(zhí)行環(huán)境被創(chuàng)建時(shí)霹俺,它的作用域鏈初始化為當(dāng)前函數(shù)的[[scope]]屬性中的對(duì)象柔吼,函數(shù)的作用域鏈會(huì)插入一個(gè)可變對(duì)象毒费,此時(shí)的可變對(duì)象稱為活動(dòng)對(duì)象,包含著所有函數(shù)內(nèi)部的局部變量愈魏,參數(shù)觅玻,參數(shù)集合(arguments),this培漏。當(dāng)執(zhí)行環(huán)境被銷毀時(shí)溪厘,活動(dòng)對(duì)象也被銷毀。 - 活動(dòng)對(duì)象會(huì)被推入作用域的最前端牌柄,因此在函數(shù)內(nèi)部訪問(wèn)變量的時(shí)候會(huì)先反問(wèn)活動(dòng)對(duì)象畸悬,再訪問(wèn)外部的對(duì)象,因此才會(huì)有第6條的內(nèi)容珊佣。
8. 閉包:
在理解上述的關(guān)于作用域鏈的描述之后蹋宦,閉包會(huì)更容易理解。
常見(jiàn)閉包場(chǎng)景咒锻,就是函數(shù)A內(nèi)部返回一個(gè)函數(shù)B冷冗,這樣函數(shù)B可以訪問(wèn)函數(shù)A內(nèi)部的變量,即使在函數(shù)A執(zhí)行完之后自身的執(zhí)行環(huán)境被銷毀惑艇。
function sum(a,b){
return function add(){
return a+b;
}
}
var t = sum(1,2)
t();
- 當(dāng)sum函數(shù)創(chuàng)建時(shí)蒿辙,會(huì)產(chǎn)生一個(gè)全局對(duì)象。
- 當(dāng)sum(1,2)執(zhí)行時(shí)滨巴,會(huì)產(chǎn)生一個(gè)活動(dòng)對(duì)象思灌,包含a,b。
- 當(dāng)add函數(shù)創(chuàng)建時(shí)(及閉包創(chuàng)建)恭取,它的[[scope]]屬性被初始化為以上的兩個(gè)對(duì)象泰偿。
- 當(dāng)t()執(zhí)行時(shí)(及閉包執(zhí)行),會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境秽荤,它的作用域鏈與屬性[[scope]]中所引用的兩個(gè)相同的作用域鏈對(duì)象一起被初始化甜奄,然后一個(gè)活動(dòng)對(duì)象被閉包自身創(chuàng)建。
- 此時(shí)閉包的作用域鏈包含3個(gè)對(duì)象窃款,一個(gè)全局對(duì)象课兄,一個(gè)sum活動(dòng)對(duì)象,一個(gè)自身的活動(dòng)對(duì)象晨继。所以閉包可以訪問(wèn)外部函數(shù)的屬性烟阐。
- 外部函數(shù)執(zhí)行環(huán)境被銷毀,但由于外部函數(shù)的活動(dòng)對(duì)象被閉包引用,因此這些活動(dòng)對(duì)象無(wú)法被銷毀蜒茄,這意味著腳本中的閉包需要更多內(nèi)存消耗唉擂。
9. 關(guān)于使用"use strict"
對(duì)于這個(gè)問(wèn)題,既簡(jiǎn)要又最重要的答案是檀葛,use strict 是一種在JavaScript代碼運(yùn)行時(shí)自動(dòng)實(shí)行更嚴(yán)格解析和錯(cuò)誤處理的方法玩祟。那些被忽略或默默失敗了的代碼錯(cuò)誤,會(huì)產(chǎn)生錯(cuò)誤或拋出異常屿聋。通常而言空扎,這是一個(gè)很好的做法。
嚴(yán)格模式的一些主要優(yōu)點(diǎn)包括:
- 使調(diào)試更加容易润讥。那些被忽略或默默失敗了的代碼錯(cuò)誤转锈,會(huì)產(chǎn)生錯(cuò)誤或拋出異常,因此盡早提醒你代碼中的問(wèn)題楚殿,你才能更快地指引到它們的源代碼撮慨。
- 防止意外的全局變量。如果沒(méi)有嚴(yán)格模式脆粥,將值分配給一個(gè)未聲明的變量會(huì)自動(dòng)創(chuàng)建該名稱的全局變量砌溺。這是JavaScript中最常見(jiàn)的錯(cuò)誤之一。在嚴(yán)格模式下冠绢,這樣做的話會(huì)拋出錯(cuò)誤抚吠。
"use strict"
function f(){
b=2;
console.log(b) //ERROR
}
f();
- 消除 this 強(qiáng)制。如果沒(méi)有嚴(yán)格模式弟胀,引用null或未定義的值到 this 值會(huì)自動(dòng)強(qiáng)制到全局變量楷力。這可能會(huì)導(dǎo)致許多令人頭痛的問(wèn)題和讓人恨不得拔自己頭發(fā)的bug。在嚴(yán)格模式下孵户,引用 null或未定義的 this 值會(huì)拋出錯(cuò)誤萧朝。
- 不允許重復(fù)的屬性名稱或參數(shù)值。當(dāng)檢測(cè)到對(duì)象(例如夏哭,var object = {foo: "bar", foo: "baz"};)中重復(fù)命名的屬性检柬,或檢測(cè)到函數(shù)中(例如,function foo(val1, val2, val1){})重復(fù)命名的參數(shù)時(shí)竖配,嚴(yán)格模式會(huì)拋出錯(cuò)誤何址,因此捕捉幾乎可以肯定是代碼中的bug可以避免浪費(fèi)大量的跟蹤時(shí)間。
- 使eval() 更安全进胯。在嚴(yán)格模式和非嚴(yán)格模式下用爪,eval() 的行為方式有所不同。最顯而易見(jiàn)的是胁镐,在嚴(yán)格模式下偎血,變量和聲明在 eval() 語(yǔ)句內(nèi)部的函數(shù)不會(huì)在包含范圍內(nèi)創(chuàng)建(它們會(huì)在非嚴(yán)格模式下的包含范圍中被創(chuàng)建诸衔,這也是一個(gè)常見(jiàn)的問(wèn)題源)。
- 在 delete使用無(wú)效時(shí)拋出錯(cuò)誤颇玷。delete操作符(用于從對(duì)象中刪除屬性)不能用在對(duì)象不可配置的屬性上笨农。當(dāng)試圖刪除一個(gè)不可配置的屬性時(shí),非嚴(yán)格代碼將默默地失敗帖渠,而嚴(yán)格模式將在這樣的情況下拋出異常谒亦。