前端面试题目汇总摘录(JS 基础篇)
溫故而知新,保持空杯心態(tài)
JS 基礎(chǔ)
JavaScript 的 typeof 返回那些數(shù)據(jù)類型
object number function boolean undefined string
typeof null; // object typeof isNaN; // function typeof isNaN(123); //boolean typeof []; // object Array.isArray(); // false toString.call([]); // [object Array] var arr = []; arr.constructor; // ? Array() { [native code] } 復(fù)制代碼強(qiáng)制類型轉(zhuǎn)換和隱式類型轉(zhuǎn)換?
顯示轉(zhuǎn)換(強(qiáng)制類型轉(zhuǎn)換)
js 提供了以下幾種轉(zhuǎn)型函數(shù):
| 數(shù)值類型 | Number(mix),parseInt(string,radix),parseFloat(string); |
| 字符串類型 | toString(radix),String(mix) |
| 布爾類型 | Boolean(mix) |
Number(mix) 函數(shù),可以將任意類型的參數(shù) mix 轉(zhuǎn)換為數(shù)值類型,規(guī)則為
下表是對(duì)象的 valueOf() 的返回值
| Array | 數(shù)組的元素被轉(zhuǎn)換為字符串,這些字符串由逗號(hào)分隔,連接在一起。其操作與 Array.toString 和 Array.join 方法相同。 |
| Boolean | Boolean 值。 |
| Date | 存儲(chǔ)的時(shí)間是從 1970 年 1 月 1 日午夜開(kāi)始計(jì)的毫秒數(shù) UTC。 |
| Function | 函數(shù)本身。 |
| Number | 數(shù)字值。 |
| Object | 對(duì)象本身。這是默認(rèn)情況。 |
| String | 字符串值。 |
由于 Number()函數(shù)在轉(zhuǎn)換字符串時(shí)原理比較復(fù)雜,且不夠合理,因此在處理字符串時(shí),更常用的是 parseInt() 函數(shù)
parstInt(string,radix) 函數(shù),將字符串轉(zhuǎn)換為整數(shù)類型的數(shù)值,其規(guī)則為
parseFloat(string)函數(shù),將字符串轉(zhuǎn)換為浮點(diǎn)數(shù)類型的數(shù)值。
與parseInt()函數(shù)類似,parseFloat()也是從第一個(gè)字符(位置0)開(kāi)始解析每個(gè)字符。而且也是一直解析到字符串末尾,或者解析到遇見(jiàn)一個(gè)無(wú)效的浮點(diǎn)數(shù)字字符為止。也就是說(shuō),字符串中的第一個(gè)小數(shù)點(diǎn)是有效的,而第二個(gè)小數(shù)點(diǎn)就是無(wú)效的了,因此它后面的字符串將被忽略。
toString(radix)
除 undefined 和 null之外的所有類型的值都具有 toString() 方法,其作用是返回對(duì)象的字符串表示。
多數(shù)情況下,調(diào)用toString()方法不必傳遞參數(shù)。但是,在調(diào)用數(shù)值的toString()方法時(shí),可以傳遞一個(gè)參數(shù):輸出數(shù)值的基數(shù)。默認(rèn)情況下,toString()方法以十進(jìn)制格式返回?cái)?shù)值的字符串表示。
| Array | 將 Array 的元素轉(zhuǎn)換為字符串。結(jié)果字符串由逗號(hào)分隔,且連接起來(lái)。 |
| Boolean | 如果 Boolean 值是 true,則返回 “true”。否則,返回 “false”。 |
| Date | 返回日期的文字表示法。 |
| Error | 返回一個(gè)包含相關(guān)錯(cuò)誤信息的字符串。 |
| Function | 返回如下格式的字符串,其中 functionname 是被調(diào)用 toString 方法函數(shù)的名稱:function functionname( ) { [native code] } |
| Number | 返回?cái)?shù)字的文字表示。 |
| String | 返回 String 對(duì)象的值。 |
| 默認(rèn) | 返回 “[object objectname]”,其中 objectname 是對(duì)象類型的名稱。 |
在不知道要轉(zhuǎn)換的值是不是null或undefined的情況下,還可以使用轉(zhuǎn)型函數(shù)String(),這個(gè)函數(shù)能夠?qū)⑷魏晤愋偷闹缔D(zhuǎn)換為字符串。
String(mix)函數(shù),將任何類型的值轉(zhuǎn)換為字符串,其規(guī)則為:
Boolean(mix)函數(shù),將任何類型的值轉(zhuǎn)換為布爾值。
以下值會(huì)被轉(zhuǎn)換為false:false、”"、0、NaN、null、undefined,其余任何值都會(huì)被轉(zhuǎn)換為true。
隱式轉(zhuǎn)換(非強(qiáng)制轉(zhuǎn)換類型)
在某些情況下,即使我們不提供顯示轉(zhuǎn)換,Javascript也會(huì)進(jìn)行自動(dòng)類型轉(zhuǎn)換,主要情況有:
用于檢測(cè)是否為非數(shù)值的函數(shù):isNaN(mix)
isNaN()函數(shù),經(jīng)測(cè)試發(fā)現(xiàn),該函數(shù)會(huì)嘗試將參數(shù)值用 Number() 進(jìn)行轉(zhuǎn)換,如果結(jié)果為“非數(shù)值”則返回 true,否則返回 false。
遞增遞減操作符(包括前置和后置)、一元正負(fù)符號(hào)操作符(經(jīng)過(guò)對(duì)比發(fā)現(xiàn),其規(guī)則與Number()規(guī)則基本相同)
加法運(yùn)算操作符
加號(hào)運(yùn)算操作符在Javascript也用于字符串連接符,所以加號(hào)操作符的規(guī)則分兩種情況:
如果兩個(gè)操作值都是數(shù)值,其規(guī)則為:
如果有一個(gè)操作值為字符串,則:
可以看出,加法運(yùn)算中,如果有一個(gè)操作值為字符串類型,則將另一個(gè)操作值轉(zhuǎn)換為字符串,最后連接起來(lái)。
乘除、減號(hào)運(yùn)算符、取模運(yùn)算符
這些操作符針對(duì)的是運(yùn)算,所以他們具有共同性:如果操作值之一不是數(shù)值,則被隱式調(diào)用Number() 函數(shù)進(jìn)行轉(zhuǎn)換。具體每一種運(yùn)算的詳細(xì)規(guī)則請(qǐng)參考ECMAScript中的定義。
邏輯操作符(!、&&、||)
邏輯非(!)操作符首先通過(guò)Boolean()函數(shù)將它的操作值轉(zhuǎn)換為布爾值,然后求反。
邏輯與(&&)操作符,如果一個(gè)操作值不是布爾值時(shí),遵循以下規(guī)則進(jìn)行轉(zhuǎn)換:
邏輯或(||)操作符,如果一個(gè)操作值不是布爾值,遵循以下規(guī)則
關(guān)系操作符(<, >, <=, >=)
與上述操作符一樣,關(guān)系操作符的操作值也可以是任意類型的,所以使用非數(shù)值類型參與比較時(shí)也需要系統(tǒng)進(jìn)行隱式類型轉(zhuǎn)換:
注:NaN 是非常特殊的值,它不和任何類型的值相等,包括它自己,同時(shí)它與任何類型的值比較大小時(shí)都返回 false。
相等操作符(==)
相等操作符會(huì)對(duì)操作值進(jìn)行隱式轉(zhuǎn)換后進(jìn)行比較:
split()、join()的區(qū)別
前者是切割成數(shù)組的形式
后者是將數(shù)組轉(zhuǎn)換為字符串
數(shù)組方法pop/push/unshift/shift
| pop() | 刪除原數(shù)組最后一項(xiàng),并返回刪除元素的值;如果數(shù)組為空則返回undefined |
| push() | 將參數(shù)添加到原數(shù)組末尾,并返回?cái)?shù)組的長(zhǎng)度 |
| unshift() | 將參數(shù)添加到原數(shù)組開(kāi)頭,并返回?cái)?shù)組的長(zhǎng)度 |
| shift() | 刪除原數(shù)組第一項(xiàng),并返回刪除元素的值;如果數(shù)組為空則返回undefined |
事件綁定和普通事件有什么區(qū)別
普通事件中的onclick是DOM0級(jí)事件只支持單個(gè)事件,會(huì)被其他onclick事件覆蓋,而事件綁定中的addEventListener是DOM2級(jí)事件可以添加多個(gè)事件而不用擔(dān)心被覆蓋
普通添加事件的方法:
var btn = document.getElementById("hello"); btn.onclick = function(){alert(1); } btn.onclick = function(){alert(2); } 復(fù)制代碼執(zhí)行上面的代碼只會(huì)alert 2
事件綁定方式添加事件:
var btn = document.getElementById("hello"); btn.addEventListener("click",function(){alert(1); },false); btn.addEventListener("click",function(){alert(2); },false); 復(fù)制代碼執(zhí)行上面的代碼會(huì)先alert 1 再 alert 2
IE 和 DOM 事件流有什么區(qū)別
事件
HTML元素事件是瀏覽器內(nèi)在自動(dòng)產(chǎn)生的,當(dāng)有事件發(fā)生時(shí)html元素會(huì)向外界(這里主要指元素事件的訂閱者)發(fā)出各種事件,如click,onmouseover,onmouseout等等。
DOM事件流
DOM(文檔對(duì)象模型)結(jié)構(gòu)是一個(gè)樹(shù)型結(jié)構(gòu),當(dāng)一個(gè)HTML元素產(chǎn)生一個(gè)事件時(shí),該事件會(huì)在元素結(jié)點(diǎn)與根結(jié)點(diǎn)之間的路徑傳播,路徑所經(jīng)過(guò)的結(jié)點(diǎn)都會(huì)收到該事件,這個(gè)傳播過(guò)程可稱為DOM事件流。
冒泡型事件(Bubbling)
這是IE瀏覽器對(duì)事件模型的實(shí)現(xiàn)。冒泡,顧名思義,事件像個(gè)水中的氣泡一樣一直往上冒,直到頂端。從DOM樹(shù)型結(jié)構(gòu)上理解,就是事件由葉子結(jié)點(diǎn)沿祖先結(jié)點(diǎn)一直向上傳遞直到根結(jié)點(diǎn);從瀏覽器界面視圖HTML元素排列層次上理解就是事件由具有從屬關(guān)系的最確定的目標(biāo)元素一直傳遞到最不確定的目標(biāo)元素.
捕獲型事件(Capturing)
Netscape Navigator的實(shí)現(xiàn),它與冒泡型剛好相反,由DOM樹(shù)最頂層元素一直到最精確的元素,直觀上的理解應(yīng)該如同冒泡型,事件傳遞應(yīng)該由最確定的元素,即事件產(chǎn)生元素開(kāi)始。
DOM標(biāo)準(zhǔn)事件模型
因?yàn)閮蓚€(gè)不同的模型都有其優(yōu)點(diǎn)和解釋,DOM標(biāo)準(zhǔn)支持捕獲型與冒泡型,可以說(shuō)是它們兩者的結(jié)合體。它可以在一個(gè)DOM元素上綁定多個(gè)事件處理器,并且在處理函數(shù)內(nèi)部,this關(guān)鍵字仍然指向被綁定的DOM元素,另外處理函數(shù)參數(shù)列表的第一個(gè)位置傳遞事件event對(duì)象。
首先是捕獲式傳遞事件,接著是冒泡式傳遞,所以,如果一個(gè)處理函數(shù)既注冊(cè)了捕獲型事件的監(jiān)聽(tīng),又注冊(cè)冒泡型事件監(jiān)聽(tīng),那么在DOM事件模型中它就會(huì)被調(diào)用兩次。
實(shí)例:
<body><div><button>點(diǎn)擊這里</button></div> </body> 復(fù)制代碼冒泡:button -> div -> body (IE 事件流)
捕獲:body -> div -> button (Netscape事件流)
DOM: body -> div -> button -> button -> div -> body(先捕獲后冒泡)
**事件偵聽(tīng)函數(shù)的區(qū)別 **
// IE使用: [Object].attachEvent("name_of_event_handler", fnHandler); //綁定函數(shù) [Object].detachEvent("name_of_event_handler", fnHandler); //移除綁定 // DOM使用: [Object].addEventListener("name_of_event", fnHandler, bCapture); //綁定函數(shù) [Object].removeEventListener("name_of_event", fnHandler, bCapture); //移除綁定 復(fù)制代碼如何取消瀏覽器事件的傳遞與事件傳遞后瀏覽器的默認(rèn)處理
取消事件傳遞是指,停止捕獲型事件或冒泡型事件的進(jìn)一步傳遞。
事件傳遞后的默認(rèn)處理是指,通常瀏覽器在事件傳遞并處理完后會(huì)執(zhí)行與該事件關(guān)聯(lián)的默認(rèn)動(dòng)作(如果存在這樣的動(dòng)作)。例如,如果表單中input type 屬性是 “submit”,點(diǎn)擊后在事件傳播完瀏覽器就就自動(dòng)提交表單。又例如,input 元素的 keydown 事件發(fā)生并處理后,瀏覽器默認(rèn)會(huì)將用戶鍵入的字符自動(dòng)追加到 input 元素的值中。
要取消瀏覽器的事件傳遞,IE與DOM標(biāo)準(zhǔn)又有所不同。
在IE下,通過(guò)設(shè)置 event 對(duì)象的 cancelBubble 為 true 即可。
function someHandle() { window.event.cancelBubble = true; } 復(fù)制代碼DOM標(biāo)準(zhǔn)通過(guò)調(diào)用 event對(duì)象的 stopPropagation() 方法即可。
function someHandle(event) {event.stopPropagation(); } 復(fù)制代碼因些,跨瀏覽器的停止事件傳遞的方法是:
function someHandle(event) { event = event || window.event; if(event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; } 復(fù)制代碼取消事件傳遞后的默認(rèn)處理,IE與DOM標(biāo)準(zhǔn)又不所不同。
在IE下,通過(guò)設(shè)置 event 對(duì)象的 returnValue 為 false 即可。
function someHandle() { window.event.returnValue = false; } 復(fù)制代碼DOM標(biāo)準(zhǔn)通過(guò)調(diào)用 event 對(duì)象的 preventDefault() 方法即可。
function someHandle(event) { event.preventDefault(); } 復(fù)制代碼因些,跨瀏覽器的取消事件傳遞后的默認(rèn)處理方法是:
function**` `someHandle(event) { event = event || window.event; if(event.preventDefault) event.preventDefault(); else event.returnValue = false; } 復(fù)制代碼IE 和標(biāo)準(zhǔn)下有哪些兼容性的寫(xiě)法
var ev = ev || window.event document.documentElement.clinetWidth || document.body.clientWidth var target = ev.srcElement || ev.target 復(fù)制代碼call 和 apply 的區(qū)別
call 和 apply 相同點(diǎn): 都是為了用一個(gè)本不屬于一個(gè)對(duì)象的方法,讓這個(gè)對(duì)象去執(zhí)行
基本使用
call()
function.call(obj[,arg1[, arg2[, [,.argN]]]]]) 復(fù)制代碼- 調(diào)用call的對(duì)象必須是個(gè)函數(shù)function
- call的第一個(gè)參數(shù)將會(huì)是function改變上下文后指向的對(duì)象.如果不傳,將會(huì)默認(rèn)是全局對(duì)象window
- 第二個(gè)參數(shù)開(kāi)始可以接收任意個(gè)參數(shù),這些參數(shù)將會(huì)作為function的參數(shù)傳入function
- 調(diào)用call的方法會(huì)立即執(zhí)行
apply()
function.apply(obj[,argArray]) 復(fù)制代碼與call方法的使用基本一致,但是只接收兩個(gè)參數(shù),其中第二個(gè)參數(shù)必須是一個(gè)數(shù)組或者類數(shù)組,這也是這兩個(gè)方法很重要的一個(gè)區(qū)別
數(shù)組與類數(shù)組小科普
數(shù)組我們都知道是什么,它的特征都有哪些呢?
類數(shù)組顧名思義,具備的特征應(yīng)該與數(shù)組基本相同,那么可以知道,一個(gè)形如下面這個(gè)對(duì)象的對(duì)象就是一個(gè)類數(shù)組
var arrayLike = {0: 'item1',1: 'item2',2: 'item3',length: 3 } 復(fù)制代碼類數(shù)組arrayLike可以通過(guò)角標(biāo)進(jìn)行調(diào)用,具有l(wèi)ength屬性,同時(shí)也可以通過(guò) for 循環(huán)進(jìn)行遍歷
我們經(jīng)常使用的獲取dom節(jié)點(diǎn)的方法返回的就是一個(gè)類數(shù)組,在一個(gè)方法中使用 arguments關(guān)鍵字獲取到的該方法的所有參數(shù)也是一個(gè)類數(shù)組
但是類數(shù)組卻不能通過(guò)forEach進(jìn)行遍歷,因?yàn)閒orEach是數(shù)組原型鏈上的方法,類數(shù)組畢竟不是數(shù)組,所以無(wú)法使用
不同點(diǎn)
call方法從第二個(gè)參數(shù)開(kāi)始可以接收任意個(gè)參數(shù),每個(gè)參數(shù)會(huì)映射到相應(yīng)位置的func的參數(shù)上,可以通過(guò)參數(shù)名調(diào)用,但是如果將所有的參數(shù)作為數(shù)組傳入,它們會(huì)作為一個(gè)整體映射到func對(duì)應(yīng)的第一個(gè)參數(shù)上,之后參數(shù)都為空
function func (a,b,c) {}func.call(obj, 1,2,3) // function接收到的參數(shù)實(shí)際上是 1,2,3func.call(obj, [1,2,3]) // function接收到的參數(shù)實(shí)際上是 [1,2,3],undefined,undefined 復(fù)制代碼apply方法最多只有兩個(gè)參數(shù),第二個(gè)參數(shù)接收數(shù)組或者類數(shù)組,但是都會(huì)被轉(zhuǎn)換成類數(shù)組傳入func中,并且會(huì)被映射到func對(duì)應(yīng)的參數(shù)上
func.apply(obj, [1,2,3]) // function接收到的參數(shù)實(shí)際上是 1,2,3func.apply(obj, {0: 1,1: 2,2: 3,length: 3 }) // function接收到的參數(shù)實(shí)際上是 1,2,3 復(fù)制代碼b 繼承 a 的方法
方法一:對(duì)象冒充
function Parent(username){this.username = username;this.hello = function(){console.log(this.username);} } function Child(username,password){this.method = Parent; // this.method 作為一個(gè)臨時(shí)的屬性,并且指向了 Parent所指向的對(duì)象函數(shù)this.method(username); // 執(zhí)行 this.method 方法,即執(zhí)行了 Parent 所指向的對(duì)象函數(shù)delete this.method; // 銷毀 this.method 屬性,即此時(shí) Child 就已經(jīng)擁有了 Parent 的所有方法和屬性 this.password = password;this.world = function(){console.log(this.password);} } const parent = new Parent('hello parent'); const child = new Child('hello child','123456'); console.log(child); parent.hello(); child.hello(); child.world(); 復(fù)制代碼**方法二:call() **
call 方法是 Function 類中的方法 call 方法的第一個(gè)參數(shù)的值賦值給類(即方法)中出現(xiàn)的 this call 方法的第二個(gè)參數(shù)開(kāi)始依次賦值給類(即方法)所接受的參數(shù)
function Parent(username){this.username = username;this.hello = function(){console.log(this.username);} } function Child(username,password){Parent.call(this,username);this.password = password;this.world = function(){console.log(this.password);} } const parent = new Parent('hello parent'); const child = new Child('hello child','123456'); parent.hello(); child.hello(); child.world(); 復(fù)制代碼方法三:apply()
apply方法接受2個(gè)參數(shù)
第一個(gè)參數(shù)與call方法的第一個(gè)參數(shù)一樣,即賦值給類(即方法)中出現(xiàn)的this
第二個(gè)參數(shù)為數(shù)組類型,這個(gè)數(shù)組中的每個(gè)元素依次賦值給類(即方法)所接受的參數(shù)
function Parent(username){this.username = username;this.hello = function(){console.log(this.username);} } function Child(username,password){Parent.apply(this,new Array(username));this.password = password;this.world = function(){console.log(this.password);} } const parent = new Parent('hello parent'); const child = new Child('hello child','123456'); parent.hello(); child.hello(); child.world(); 復(fù)制代碼方法四:原型鏈
即子類通過(guò) prototype 將所有在父類中通過(guò) prototype 追加的屬性和方法都追加到 Child ,從而實(shí)現(xiàn)繼承
function Parent(){} Parent.prototype.hello = "hello"; Parent.prototype.sayHello = function(){console.log(this.hello); } function Child(){} Child.prototype = new Parent();// 將 Parent 中所有通過(guò) prototype 追加的屬性和方法都追加到 Child 從而實(shí)現(xiàn)了繼承 Child.prototype.world = "world"; Child.prototype.sayWorld = function(){console.log(this.world); } const child = new Child(); child.sayHello(); child.sayWorld(); 復(fù)制代碼方法五:混合方式,call()+ 原型鏈
function Parent(hello){this.hello = hello; } Parent.prototype.sayHello = function(){console.log(this.hello); } function Child(hello,world){Parent.call(this,hello); // 將父類的屬性繼承過(guò)來(lái)this.world = world; } Child.prototype = new Parent(); //將父類的方法繼承過(guò)來(lái) Child.prototype.sayWorld = function(){ // 新增方法console.log(this.world); } const child = new Child("hello","world"); child.sayHello(); child.sayWorld(); 復(fù)制代碼JavaScript this 指針、閉包、作用域
**js 中的this 指針 **
在函數(shù)執(zhí)行時(shí),this 總是指向調(diào)用該函數(shù)的對(duì)象。要判斷 this 的指向,其實(shí)就是判斷 this 所在的函數(shù)屬于誰(shuí)。
在《javaScript語(yǔ)言精粹》這本書(shū)中,把 this 出現(xiàn)的場(chǎng)景分為四類,簡(jiǎn)單的說(shuō)就是:
1)有對(duì)象就指向調(diào)用對(duì)象
var myObject = { value: 123 } myObject.getValue = function(){console.log(this.value); // 123console.log(this); // {value: 123, getValue: ?} } myObject.getValue(); 復(fù)制代碼2)沒(méi)調(diào)用對(duì)象就指向全局對(duì)象
var myObject = { value: 123 } myObject.getValue = function(){var foo = function(){console.log(this.value); // undefinedconsole.log(this); // Window?{postMessage: ?, blur: ?, focus: ?, close: ?, frames: Window,?…} // foo函數(shù)雖然定義在getValue 函數(shù)體內(nèi),但是不屬于 getValue也不屬于 myObject,所以調(diào)用的時(shí)候,它的 this 指針指向了全局對(duì)象}foo();return this.value; } console.log(myObject.getValue()); // 123 復(fù)制代碼3) 用new構(gòu)造就指向新對(duì)象
// js 中通過(guò) new 關(guān)鍵詞來(lái)調(diào)用構(gòu)造函數(shù),此時(shí) this 會(huì)綁定雜該新對(duì)象上 var someClass = function(){this.value = 123; } var myCreate = new someClass(); console.log(myCreate.value); // 123 復(fù)制代碼4)通過(guò) apply 或 call 或 bind 來(lái)改變 this 的指向
var myObject = { value: 123 }; var foo = function(){console.log(this); } foo(); // Window?{postMessage: ?, blur: ?, focus: ?, close: ?, frames: Window,?…} foo.apply(myObject); // {value: 123} foo.call(myObject); // {value: 123} var newFoo = foo.bind(myObject); newFoo(); // {value: 123} 復(fù)制代碼閉包
閉包英文是 Closure ,簡(jiǎn)而言之,閉包就是
作為局部變量都可以被函數(shù)內(nèi)的代碼訪問(wèn),這個(gè)和靜態(tài)語(yǔ)言是沒(méi)有差別的,閉包的差別在于局部變量可以在函數(shù)執(zhí)行結(jié)束后仍然被函數(shù)外的代碼訪問(wèn),這意味著函數(shù)必須返回一個(gè)指向閉包的“引用”,或?qū)⑦@個(gè)“引用”賦值給某個(gè)外部變量,才能保證閉包中局部變量被外部代碼訪問(wèn),當(dāng)然包含這個(gè)引用的實(shí)體應(yīng)該是一個(gè)對(duì)象。但是ES并沒(méi)有提供相關(guān)的成員和方法來(lái)訪問(wèn)包中的局部變量,但是在ES中,函數(shù)對(duì)象中定義的內(nèi)部函數(shù)是可以直接訪問(wèn)外部函數(shù)的局部變量,通過(guò)這種機(jī)制,可以用如下方式完成對(duì)閉包的訪問(wèn)。
function greeting(name){var text = "Hello " + name; // 局部變量// 每次調(diào)用時(shí),產(chǎn)生閉包,并返回內(nèi)部函數(shù)對(duì)象給調(diào)用者return function(){console.log(text);} } var sayHello = greeting('Closure'); // 通過(guò)閉包訪問(wèn)到了局部變量text sayHello(); // 輸出Hello Closure 復(fù)制代碼在 ECMAscript 的腳本函數(shù)運(yùn)行時(shí),每個(gè)函數(shù)關(guān)聯(lián)都有一個(gè)執(zhí)行上下文場(chǎng)景(Exection Context),這個(gè)執(zhí)行上下文包括三個(gè)部分
- 文法環(huán)境(The LexicalEnvironment)
- 變量環(huán)境(The VariableEnvironment)
- this綁定
其中第三點(diǎn)this綁定與閉包無(wú)關(guān),不在本文中討論。文法環(huán)境中用于解析函數(shù)執(zhí)行過(guò)程使用到的變量標(biāo)識(shí)符。我們可以將文法環(huán)境想象成一個(gè)對(duì)象,該對(duì)象包含了兩個(gè)重要組件,環(huán)境記錄(Enviroment Recode),和外部引用(指針)。環(huán)境記錄包含包含了函數(shù)內(nèi)部聲明的局部變量和參數(shù)變量,外部引用指向了外部函數(shù)對(duì)象的上下文執(zhí)行場(chǎng)景。全局的上下文場(chǎng)景中此引用值為NULL。這樣的數(shù)據(jù)結(jié)構(gòu)就構(gòu)成了一個(gè)單向的鏈表,每個(gè)引用都指向外層的上下文場(chǎng)景。
例如上面我們例子的閉包模型應(yīng)該是這樣,sayHello函數(shù)在最下層,上層是函數(shù)greeting,最外層是全局場(chǎng)景。如下圖:
因此當(dāng)sayHello被調(diào)用的時(shí)候,sayHello會(huì)通過(guò)上下文場(chǎng)景找到局部變量text的值,因此在屏幕的對(duì)話框中顯示出”Hello Closure”
針對(duì)一些例子來(lái)幫助大家更加深入的理解閉包,下面共有5個(gè)樣例,例子來(lái)自于JavaScript Closures For Dummies(鏡像)。
例子1:閉包中局部變量是引用而非拷貝
function say667(){var num = 666;var sayConsole = function(){console.log(num);}num++;return sayConsole; } var sayConsole = say667(); sayConsole(); // 667 復(fù)制代碼例子2:多個(gè)函數(shù)綁定同一個(gè)閉包,因?yàn)樗麄兌x在同一個(gè)函數(shù)內(nèi)。
function setupSomeGlobals(){var num = 666;gConsoleNumber = function() { console.log(num); }gIncreaseNumber = function() { num++; }gSetNumber = function(x) { num = x; } } setupSomeGlobals(); gConsoleNumber(); // 666 gIncreaseNumber(); gConsoleNumber(); // 667 gSetNumber(12); gConsoleNumber(); // 12 復(fù)制代碼例子3:當(dāng)在一個(gè)循環(huán)中賦值函數(shù)時(shí),這些函數(shù)將綁定同樣的閉包
function buildList(list){var result = [];for(var i = 0; i < list.length; i++){var item = 'item' + list[i];result.push(function(){console.log(item+' '+list[i]);})}return result; } function testList(){var fnList = buildList([1,2,3]);for(var j = 0; j < fnList.length; j++){fnList[j]();} } testList(); // 輸出3次 item3 undefined 復(fù)制代碼testList的執(zhí)行結(jié)果是彈出item3 undefined窗口三次,因?yàn)檫@三個(gè)函數(shù)綁定了同一個(gè)閉包,而且item的值為最后計(jì)算的結(jié)果,但是當(dāng)i跳出循環(huán)時(shí)i值為4,所以list[4]的結(jié)果為undefined.
例子4:外部函數(shù)所有局部變量都在閉包內(nèi),即使這個(gè)變量聲明在內(nèi)部函數(shù)定義之后。
function sayAlice(){var sayConsole = function(){console.log(alice);}var alice = "Hello Alice";return sayConsole; } var helloAlice=sayAlice(); helloAlice(); 復(fù)制代碼執(zhí)行結(jié)果輸出”Hello Alice”的窗口。即使局部變量聲明在函數(shù)sayAlert之后,局部變量仍然可以被訪問(wèn)到。
例子5:每次函數(shù)調(diào)用的時(shí)候創(chuàng)建一個(gè)新的閉包
function newClosure(someNum,someRef){var num = someNum;var anArray = [1,2,3];var ref = someRef;return function(x){num += x;anArray.push(num);console.log('num: ' + num +'\nanArray ' + anArray.toString() +'\nref.someVar ' + ref.someVar);} }closure1=newClosure(40,{someVar:'closure 1'}); closure2=newClosure(1000,{someVar:'closure 2'});closure1(5); // num: 45 anArray 1,2,3,45 ref.someVar closure 1 closure2(-10); // num: 990 anArray 1,2,3,990 ref.someVar closure 2 復(fù)制代碼閉包的缺點(diǎn):
(1)由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會(huì)造成網(wǎng)頁(yè)的性能問(wèn)題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
(2)閉包會(huì)在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對(duì)象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時(shí)一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
例子:
function Cars(){this.name = "Benz";this.color = ["white","black"]; } Cars.prototype.sayColor = function(){var outer = this;return function(){return outer.color}; };var instance = new Cars(); console.log(instance.sayColor()()) 復(fù)制代碼改造:
function Cars(){this.name = "Benz";this.color = ["white","black"]; } Cars.prototype.sayColor = function(){var outerColor = this.color; //保存一個(gè)副本到變量中return function(){return outerColor; //應(yīng)用這個(gè)副本};outColor = null; //釋放內(nèi)存 };var instance = new Cars(); console.log(instance.sayColor()()) 復(fù)制代碼作用域
在JS當(dāng)中一個(gè)變量的作用域(scope)是程序中定義這個(gè)變量的區(qū)域。變量分為兩類:全局(global)的和局部的。其中全局變量的作用域是全局性的,即在JavaScript代碼中,它處處都有定義。而在函數(shù)之內(nèi)聲明的變量,就只在函數(shù)體內(nèi)部有定義。它們是局部變量,作用域是局部性的。函數(shù)的參數(shù)也是局部變量,它們只在函數(shù)體內(nèi)部有定義。
我們可以借助JavaScript的作用域鏈(scope chain)更好地了解變量的作用域。每個(gè)JavaScript執(zhí)行環(huán)境都有一個(gè)和它關(guān)聯(lián)在一起的作用域鏈。這個(gè)作用域鏈?zhǔn)且粋€(gè)對(duì)象列表或?qū)ο箧湣.?dāng)JavaScript代碼需要查詢變量x的值時(shí)(這個(gè)過(guò)程叫做變量解析(variable name resolution)),它就開(kāi)始查看該鏈的第一個(gè)對(duì)象。如果那個(gè)對(duì)象有一個(gè)名為x的屬性,那么就采用那個(gè)屬性的值。如果第一個(gè)對(duì)象沒(méi)有名為x的屬性,JavaScript就會(huì)繼續(xù)查詢鏈中的第二個(gè)對(duì)象。如果第二個(gè)對(duì)象仍然沒(méi)有名為x的屬性,那么就繼續(xù)查詢下一個(gè)對(duì)象,以此類推。如果查詢到最后(指頂層代碼中)不存在這個(gè)屬性,那么這個(gè)變量的值就是未定義的。
var a,b; (function(){alert(a); // undefined alert(b); // undefined var a = b = 3;alert(a); // 3alert(b); // 3 })();alert(a); // undefined alert(b); // 3 復(fù)制代碼以上代碼相當(dāng)于
var a,b; (function(){alert(a);alert(b);var a = 3;b = 3;alert(a);alert(b); })();alert(a);復(fù)制代碼事件委托是什么?
概述
什么叫做事件委托,別名叫事件代理,JavaScript 高級(jí)程序設(shè)計(jì)上講。事件委托就是利用事件冒泡,只指定一個(gè)事件處理程序,就可以管理某一類型的所有事件。
實(shí)際例子:
有三個(gè)同事預(yù)計(jì)會(huì)在周一收到快遞,為簽收快遞,有兩種方法:一是三個(gè)人在公司門口等快遞,二是委托給前臺(tái)的小姐代為簽收。現(xiàn)實(shí)生活中,我們大多采用委托的方案(公司也不會(huì)容忍那么多人站在門口)。前臺(tái)小姐收到快遞后,會(huì)判斷收件人是誰(shuí),按照收件人的要求簽收,甚至是代付。這種方案還有一個(gè)好處就是,即使公司來(lái)了很多新員工(不管多少),前臺(tái)小姐也會(huì)在收到寄給新員工們的快遞后核實(shí)代為簽收。
這里有2層意思: 第一、現(xiàn)在委托前臺(tái)的小姐是可以代為簽收的,即程序中的現(xiàn)有的 DOM 節(jié)點(diǎn)是有事件的。
第二、新員工也可以被前臺(tái)小姐代為簽收,即程序中新添加的 DOM 節(jié)點(diǎn)也是有事件的。
為什么要使用事件委托
一般來(lái)說(shuō),DOM 需要有事件處理程序,就會(huì)直接給它設(shè)處理程序,但是如果是很多 DOM 需要添加處理事件呢?例如我們有100個(gè) li,每個(gè) li 都有相同的 click 點(diǎn)擊事件,可能我們會(huì)用到 for 循環(huán),來(lái)遍歷所有 li ,然后給它們添加事件,那么會(huì)存在什么樣的問(wèn)題?
在 JavsScript 中,添加到頁(yè)面上的事件處理程序數(shù)量將直接影響到整體運(yùn)行性能,因?yàn)樾枰粩嗟嘏c DOM 進(jìn)行交互,訪問(wèn) DOM 的次數(shù)越多,引起瀏覽器重繪與重排的次數(shù)也就越多,就會(huì)延長(zhǎng)整個(gè)頁(yè)面的交互就緒時(shí)間,這就是為什么性能優(yōu)化的主要思想之一就是減少 DOM 操作的原因、如果要用到事件委托,就會(huì)將所有的操作都放在 js 程序里面,與 DOM 的操作就只需要交互一次,這樣就可以大大減少與 DOM 的交互次數(shù),提高性能。
每個(gè)函數(shù)都是一個(gè)對(duì)象,是對(duì)象就會(huì)占用內(nèi)存,對(duì)象越多,內(nèi)存占用率就越大,自然性能就越差了(內(nèi)存不夠用,是硬傷,哈哈),比如上面的100個(gè)li,就要占用100個(gè)內(nèi)存空間,如果是1000個(gè),10000個(gè)呢,那只能說(shuō)呵呵了,如果用事件委托,那么我們就可以只對(duì)它的父級(jí)(如果只有一個(gè)父級(jí))這一個(gè)對(duì)象進(jìn)行操作,這樣我們就需要一個(gè)內(nèi)存空間就夠了,是不是省了很多,自然性能就會(huì)更好。
事件委托的原理
事件委托是利用事件的冒泡原理來(lái)實(shí)現(xiàn)的,何為事件冒泡?就是事件從最深的節(jié)點(diǎn)開(kāi)始執(zhí)行,然后逐步向上傳播事件,例子:
頁(yè)面上有一個(gè)節(jié)點(diǎn)樹(shù),div>ul>li>a,比如給最里面的 a 加一個(gè) click 點(diǎn)擊事件,那么這個(gè)事件就會(huì)一層一層的往外執(zhí)行,執(zhí)行順序 a>li>ul>div,有這么一個(gè)機(jī)制,那么我們給最外面的 div 加點(diǎn)擊事件,那么里面的 ul,li,a 做點(diǎn)擊事件的時(shí)候,都會(huì)冒泡到最外層的 div 上面,都會(huì)觸發(fā),這就是事件委托,委托他們父級(jí)代為執(zhí)行事件。
事件委托怎么實(shí)現(xiàn)
<ul id="ul"><li>111</li><li>222</li><li>333</li><li>444</li> </ul> 復(fù)制代碼實(shí)現(xiàn)功能是點(diǎn)擊li,彈出123:
window.onload = function(){var oUl = document.getElementById('ul');var aLi = oUl.getElementsByTagName('li');for(var i = 0; i < aLi.length; i++){aLi[i].onclick = function(){alert(123);}} } 復(fù)制代碼上面的代碼的意思很簡(jiǎn)單,相信很多人都是這么實(shí)現(xiàn)的,我們看看有多少次的dom操作,首先要找到ul,然后遍歷li,然后點(diǎn)擊li的時(shí)候,又要找一次目標(biāo)的li的位置,才能執(zhí)行最后的操作,每次點(diǎn)擊都要找一次li;
那么我們用事件委托的方式做又會(huì)怎么樣呢?
window.onload = function(){var oUl = document.getElementById('ul');oUl.onclick = function(){alert(123);} } 復(fù)制代碼這里用父級(jí)ul做事件處理,當(dāng)li被點(diǎn)擊時(shí),由于冒泡原理,事件就會(huì)冒泡到ul上,因?yàn)閡l上有點(diǎn)擊事件,所以事件就會(huì)觸發(fā),當(dāng)然,這里當(dāng)點(diǎn)擊ul的時(shí)候,也是會(huì)觸發(fā)的,那么問(wèn)題就來(lái)了,如果我想讓事件代理的效果跟直接給節(jié)點(diǎn)的事件效果一樣怎么辦,比如說(shuō)只有點(diǎn)擊li才會(huì)觸發(fā),不怕,我們有絕招:
Event對(duì)象提供了一個(gè)屬性叫target,可以返回事件的目標(biāo)節(jié)點(diǎn),我們成為事件源,也就是說(shuō),target就可以表示為當(dāng)前的事件操作的dom,但是不是真正操作dom,當(dāng)然,這個(gè)是有兼容性的,標(biāo)準(zhǔn)瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時(shí)只是獲取了當(dāng)前節(jié)點(diǎn)的位置,并不知道是什么節(jié)點(diǎn)名稱,這里我們用nodeName來(lái)獲取具體是什么標(biāo)簽名,這個(gè)返回的是一個(gè)大寫(xiě)的,我們需要轉(zhuǎn)成小寫(xiě)再做比較(習(xí)慣問(wèn)題):
window.onload = function(){var oUl = document.getElementById("ul");oUl.onclick = function(ev){var ev = ev || window.event;var target = ev.target || ev.srcElement;if(target.nodeName.toLowerCase() == "li"){alert(target.innerHTML);}} } 復(fù)制代碼這樣改下就只有點(diǎn)擊li會(huì)觸發(fā)事件了,且每次只執(zhí)行一次dom操作,如果li數(shù)量很多的話,將大大減少dom的操作,優(yōu)化的性能可想而知!
上面的例子是說(shuō)li操作的是同樣的效果,要是每個(gè)li被點(diǎn)擊的效果都不一樣,那么用事件委托還有用嗎?
var Add = document.getElementById("add"); var Remove = document.getElementById("remove"); var Move = document.getElementById("move"); var Select = document.getElementById("select"); Add.onclick = function(){alert('添加'); }; Remove.onclick = function(){alert('刪除'); }; Move.onclick = function(){alert('移動(dòng)'); }; Select.onclick = function(){alert('選擇'); } 復(fù)制代碼上面實(shí)現(xiàn)的效果我就不多說(shuō)了,很簡(jiǎn)單,4個(gè)按鈕,點(diǎn)擊每一個(gè)做不同的操作,那么至少需要4次dom操作,如果用事件委托,能進(jìn)行優(yōu)化嗎?
var oBox = document.getElementById("box"); oBox.onclick = function(ev){var ev = ev || window.event;var target = ev.target || ev.srcElement;if(target.nodeName.toLowerCase() == 'input'){switch(target.id) {case 'add':alert('添加');break;case 'remove':alert('刪除');break;case 'move':alert('移動(dòng)');break;case 'select':alert('選擇');break;default :alert('業(yè)務(wù)錯(cuò)誤');break;}} } 復(fù)制代碼用事件委托就可以只用一次dom操作就能完成所有的效果,比上面的性能肯定是要好一些的
現(xiàn)在講的都是document加載完成的現(xiàn)有dom節(jié)點(diǎn)下的操作,那么如果是新增的節(jié)點(diǎn),新增的節(jié)點(diǎn)會(huì)有事件嗎?也就是說(shuō),一個(gè)新員工來(lái)了,他能收到快遞嗎?
<input type="button" name="" id="btn" value="添加" /> <ul id="ul1"><li>111</li><li>222</li><li>333</li><li>444</li> </ul> 復(fù)制代碼現(xiàn)在是移入li,li變紅,移出li,li變白,這么一個(gè)效果,然后點(diǎn)擊按鈕,可以向ul中添加一個(gè)li子節(jié)點(diǎn)
window.onload = function(){var oBtn = document.getElementById("btn");var oUl = document.getElementById("ul");var aLi = oUl.getElementsByTagName("li");var num = 4;// 鼠標(biāo)移入變紅,移出變白for(var i = 0; i < aLi.length; i++){aLi[i].onmouseover = function(){this.style.background = 'red';}aLi[i].onmouseout = function(){this.style.background = '#fff';}}// 新增節(jié)點(diǎn)oBtn.onclick = function(){num++;var oLi = document.createElement('li');oLi.innerHTML = 111 * num;oUl.appendChild(oLi);} } 復(fù)制代碼這是一般的做法,但是你會(huì)發(fā)現(xiàn),新增的li是沒(méi)有事件的,說(shuō)明添加子節(jié)點(diǎn)的時(shí)候,事件沒(méi)有一起添加進(jìn)去,這不是我們想要的結(jié)果,那怎么做呢?一般的解決方案會(huì)是這樣,將for循環(huán)用一個(gè)函數(shù)包起來(lái),命名為mHover,如下:
// 將鼠標(biāo)移出移入包裝為一個(gè)函數(shù) window.onload = function(){var oBtn = document.getElementById("btn");var oUl = document.getElementById("ul");var aLi = oUl.getElementsByTagName("li");var num = 4;// 鼠標(biāo)移入變紅,移出變白function mHover(){for(var i = 0; i < aLi.length; i++){aLi[i].onmouseover = function(){this.style.background = 'red';}aLi[i].onmouseout = function(){this.style.background = '#fff';}}}mHover();// 新增節(jié)點(diǎn)oBtn.onclick = function(){num++;var oLi = document.createElement('li');oLi.innerHTML = 111 * num;oUl.appendChild(oLi);mHover();} } 復(fù)制代碼雖然功能實(shí)現(xiàn)了,看著還挺好,但實(shí)際上無(wú)疑是又增加了一個(gè)dom操作,在優(yōu)化性能方面是不可取的,那么有事件委托的方式,能做到優(yōu)化嗎?
window.onload = function(){var oBtn = document.getElementById("btn");var oUl = document.getElementById("ul");var aLi = oUl.getElementsByTagName("li");var num = 4;// 事件委托 鼠標(biāo)移入變紅,移出變白// 添加的子元素也有事件oUl.onmouseover = function(){var ev = ev || window.event;var target = ev.target || ev.srcElement;if(target.nodeName.toLowerCase() == 'li'){target.style.background = 'red';}} oUl.onmouseout = function(){var ev = ev || window.event;var target = ev.target || ev.srcElement;if(target.nodeName.toLowerCase() == 'li'){target.style.background = '#fff';}}// 新增節(jié)點(diǎn)oBtn.onclick = function(){num++;var oLi = document.createElement('li');oLi.innerHTML = 111 * num;oUl.appendChild(oLi);} } 復(fù)制代碼另外一個(gè)思考的問(wèn)題
現(xiàn)在給一個(gè)場(chǎng)景 ul > li > div > p,div占滿li,p占滿div,還是給ul綁定時(shí)間,需要判斷點(diǎn)擊的是不是li(假設(shè)li里面的結(jié)構(gòu)是不固定的),那么e.target就可能是p,也有可能是div,這種情況你會(huì)怎么處理呢?
<ul id="test"><li><p>11111111111</p></li><li><div>22222222</div></li><li><span>3333333333</span></li><li>4444444</li> </ul> 復(fù)制代碼如上列表,有4個(gè)li,里面的內(nèi)容各不相同,點(diǎn)擊li,event對(duì)象肯定是當(dāng)前點(diǎn)擊的對(duì)象,怎么指定到li上,下面我直接給解決方案:
var oUl = document.getElementById('test'); oUl.addEventListener('click',function(ev){var target = ev.target;while (target !== oUl) {if(target.tagName.toLowerCase() == 'li'){alert(target.innerHTML);break;}target = target.parentNode;} }); 復(fù)制代碼如何阻止事件冒泡和默認(rèn)事件
在解答這個(gè)問(wèn)題之前,先來(lái)看看事件的執(zhí)行順序
<div><ul><li>冒泡/捕獲</li></ul> </div> 復(fù)制代碼實(shí)例:
var html = document.documentElement; var body = document.body; var div = body.querySelector('div'); var ul = body.querySelector('ul'); var li = body.querySelector('li');// 捕獲 ul.addEventListener('click',captureCallback,true); li.addEventListener('click',captureCallback,true); div.addEventListener('click',captureCallback,true); body.addEventListener('click',captureCallback,true); html.addEventListener('click',captureCallback,true); // 冒泡 ul.addEventListener('click',bubblingCallback,false); li.addEventListener('click',bubblingCallback,false); div.addEventListener('click',bubblingCallback,false); body.addEventListener('click',bubblingCallback,false); html.addEventListener('click',bubblingCallback,false);function captureCallback(e){// e.stopPropagation();var target = e.currentTarget;console.log(target.tagName); } function bubblingCallback(e){// e.stopPropagation();var target = e.currentTarget;console.log(target.tagName); } 復(fù)制代碼點(diǎn)擊 html 中的冒泡/捕獲,可以得到以下結(jié)果
capturing&bubbling phase.html:36 HTML capturing&bubbling phase.html:36 BODY capturing&bubbling phase.html:36 DIV capturing&bubbling phase.html:36 UL capturing&bubbling phase.html:36 LI capturing&bubbling phase.html:40 LI capturing&bubbling phase.html:40 UL capturing&bubbling phase.html:40 DIV capturing&bubbling phase.html:40 BODY capturing&bubbling phase.html:40 HTML 復(fù)制代碼總結(jié)就是先捕獲,后冒泡,捕獲是從上到下,冒泡是從下到上。(形象說(shuō)法:捕獲像石頭沉入海底,冒泡像氣球冒出水面)
去掉 函數(shù) bubblingCallback 中的 e.stopPropagation()的注釋,則只會(huì)進(jìn)行捕獲,輸出
capturing&bubbling phase.html:40 HTML capturing&bubbling phase.html:40 BODY capturing&bubbling phase.html:40 DIV capturing&bubbling phase.html:40 UL capturing&bubbling phase.html:40 LI capturing&bubbling phase.html:45 LI 復(fù)制代碼而取消 函數(shù) captureCallback中的 e.stopPropagation() 注釋,則只捕獲到 最上面的標(biāo)簽
capturing&bubbling phase.html:40 HTML 復(fù)制代碼通過(guò)上面,我們可以得知, event.stopPropagation(),可以阻止 捕獲和冒泡階段當(dāng)前事件的進(jìn)一步傳播。
規(guī)則:
事件不同瀏覽器處理函數(shù)
- element.addEventListener(type, listener[, useCapture]); // IE6~8不支持(捕獲和冒泡通過(guò)useCapture,默認(rèn)false)
- element.attachEvent(’on’ + type, listener); // IE6~10,IE11不支持(只執(zhí)行冒泡事件)
- element[’on’ + type] = function(){} // 所有瀏覽器(默認(rèn)執(zhí)行冒泡事件)
W3C 中定義了 3個(gè)事件階段,依次是 捕獲階段、目標(biāo)階段、冒泡階段。事件對(duì)象按照上圖的傳播路徑依次完成這些階段。如果某個(gè)階段不支持或事件對(duì)象的傳播被終止,那么該階段就會(huì)被跳過(guò)。舉個(gè)例子,如果Event.bubbles屬性被設(shè)置為false,那么冒泡階段就會(huì)被跳過(guò)。如果Event.stopPropagation()在事件派發(fā)前被調(diào)用,那么所有的階段都會(huì)被跳過(guò)。
- 捕獲 階段:在事件對(duì)象到達(dá)事件目標(biāo)之前,事件對(duì)象必須從 window 經(jīng)過(guò)目標(biāo)的祖先節(jié)點(diǎn)傳播到事件目標(biāo)。這個(gè)階段被我們稱為捕獲階段,在這個(gè)階段注冊(cè)的事件監(jiān)聽(tīng)器在事件到達(dá)其目標(biāo)前必須先處理事件。
- 目標(biāo) 階段:事件對(duì)象到達(dá)其事件目標(biāo),這個(gè)階段被我們稱之為目標(biāo)階段。一旦事件對(duì)象達(dá)到事件目標(biāo),該階段的事件監(jiān)聽(tīng)器就要對(duì)它進(jìn)行處理。如果一個(gè)事件對(duì)象類型被標(biāo)志為不能冒泡。那么對(duì)應(yīng)的事件對(duì)象在到此階段時(shí)就會(huì)終止傳播。
- 冒泡 階段:事件對(duì)象以一個(gè)與捕獲階段相反的方向從事件目標(biāo)傳播經(jīng)過(guò)其祖先節(jié)點(diǎn)傳播到 window ,這個(gè)階段被稱之為冒泡階段,在此階段注冊(cè)的事件監(jiān)聽(tīng)器會(huì)對(duì)應(yīng)的冒泡事件進(jìn)行處理
在一個(gè)事件完成了所有階段的傳播路徑后,它的Event.currentTarget會(huì)被設(shè)置為null并且Event.eventPhase會(huì)被設(shè)為0。Event的所有其他屬性都不會(huì)改變(包括指向事件目標(biāo)的Event.target屬性)
跨瀏覽器的事件處理函數(shù)
var EventUtil = {addHandler: function(element, type, handler) {if (element.addEventListener) { // DOM2element.addEventListener(type, handler, false);} else if (element.attachEvent) { // IEelement.attachEvent('on' + type, handler);} else { // DOM0element['on' + type] = handler;}},removeHandler: function(element, type, handler) {if (element.removeEventListener) {element.removeEventListener(type, handler, false);} else if (element.detachEvent) {element.detachEvent('on' + type, handler);} else {element['on' + type] = null;}} }; 復(fù)制代碼跨瀏覽器的事件對(duì)象:
var EventUtil = {getEvent: function(e) {return e ? e : window.e;},getTarget: function(e) {return e.target || e.srcElement;},preventDefault: function(e) {if (e.preventDefault) {e.preventDefault();} else {e.returnValue = false;}},stopPropagation: function(e) {if (e.stopPropagation) {e.stopPropagation()} else {e.cancelBubble = true;}} } 復(fù)制代碼js冒泡和捕獲是事件的兩種行為,使用event.stopPropagation()起到阻止捕獲和冒泡階段中當(dāng)前事件的進(jìn)一步傳播。使用event.preventDefault()可以取消默認(rèn)事件。
防止冒泡和捕獲
w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true
topPropagation也是事件對(duì)象(Event)的一個(gè)方法,作用是阻止目標(biāo)元素的冒泡事件,但是會(huì)不阻止默認(rèn)行為。什么是冒泡事件?如在一個(gè)按鈕是綁定一個(gè)”click”事件,那么”click”事件會(huì)依次在它的父級(jí)元素中被觸發(fā) 。stopPropagation就是阻止目標(biāo)元素的事件冒泡到父級(jí)元素。如:
<div id='div' onclick='alert("div");'><ul onclick='alert("ul");'><li onclick='alert("li");'>test</li></ul> </div> 復(fù)制代碼單擊時(shí),會(huì)依次觸發(fā)alert(“l(fā)i”),alert(“ul”),alert(“div”),這就是事件冒泡。
阻止冒泡
window.event? window.event.cancelBubble = true : e.stopPropagation(); 復(fù)制代碼取消默認(rèn)事件
w3c的方法是e.preventDefault(),IE則是使用e.returnValue = false
preventDefault它是事件對(duì)象(Event)的一個(gè)方法,作用是取消一個(gè)目標(biāo)元素的默認(rèn)行為。既然是說(shuō)默認(rèn)行為,當(dāng)然是元素必須有默認(rèn)行為才能被取消,如果元素本身就沒(méi)有默認(rèn)行為,調(diào)用當(dāng)然就無(wú)效了。什么元素有默認(rèn)行為呢?如鏈接 <a>,提交按鈕 <input type="submit"> 等。當(dāng)Event 對(duì)象的 cancelable為false時(shí),表示沒(méi)有默認(rèn)行為,這時(shí)即使有默認(rèn)行為,調(diào)用preventDefault也是不會(huì)起作用的。
我們都知道,鏈接 <a> 的默認(rèn)動(dòng)作就是跳轉(zhuǎn)到指定頁(yè)面,下面就以它為例,阻止它的跳轉(zhuǎn):
//假定有鏈接<a href="http://laibh.top/" id="testA" >laibh.top</a> var a = document.getElementById("test"); a.onclick =function(e){ if(e.preventDefault){e.preventDefault();}else{window.event.returnValue == false;} } 復(fù)制代碼return false
javascript的return false只會(huì)阻止默認(rèn)行為,而是用jQuery的話則既阻止默認(rèn)行為又防止對(duì)象冒泡
下面這個(gè)使用原生js,只會(huì)阻止默認(rèn)行為,不會(huì)停止冒泡
<div id='div' onclick='alert("div");'> <ul onclick='alert("ul");'><li id='ul-a' onclick='alert("li");'><a href="http://caibaojian.com/"id="testB">caibaojian.com</a></li> </ul> </div> <script>var a = document.getElementById("testB");a.onclick = function(){return false;}; </script> 復(fù)制代碼總結(jié)使用方法
當(dāng)需要停止冒泡行為時(shí),可以使用
function stopBubble(e) { //如果提供了事件對(duì)象,則這是一個(gè)非IE瀏覽器 if ( e && e.stopPropagation ) //因此它支持W3C的stopPropagation()方法 e.stopPropagation(); else //否則,我們需要使用IE的方式來(lái)取消事件冒泡 window.event.cancelBubble = true; } 復(fù)制代碼當(dāng)需要阻止默認(rèn)行為時(shí),可以使用
//阻止瀏覽器的默認(rèn)行為 function stopDefault( e ) { //阻止默認(rèn)瀏覽器動(dòng)作(W3C) if ( e && e.preventDefault ) e.preventDefault(); //IE中阻止函數(shù)器默認(rèn)動(dòng)作的方式 else window.event.returnValue = false; return false; } 復(fù)制代碼事件注意點(diǎn)
- event代表事件的狀態(tài),例如觸發(fā)event對(duì)象的元素、鼠標(biāo)的位置及狀態(tài)、按下的鍵等等;
- event對(duì)象只在事件發(fā)生的過(guò)程中才有效。
firefox里的event跟IE里的不同,IE里的是全局變量,隨時(shí)可用;firefox里的要用參數(shù)引導(dǎo)才能用,是運(yùn)行時(shí)的臨時(shí)變量。
在IE/Opera中是window.event,在Firefox中是event;而事件的對(duì)象,在IE中是window.event.srcElement,在Firefox中是event.target,Opera中兩者都可用。
function a(e){var e = (e) ? e : ((window.event) ? window.event : null); var e = e || window.event; // firefox下window.event為null, IE下event為null } 復(fù)制代碼查找 添加 刪除 替換 插入到某個(gè)節(jié)點(diǎn)的方法
// 查找節(jié)點(diǎn) document.getElementById('id'); // 通過(guò)id查找,返回唯一的節(jié)點(diǎn),如果有多個(gè)將會(huì)返回第一個(gè),在IE6、7中有個(gè)bug,會(huì)返回name值相同的元素,所有要做一個(gè)兼容 document.getElementsByClassName('class'); // 通過(guò)class查找,返回節(jié)點(diǎn)數(shù)組 document.getElementsByTagName('div'); // 通過(guò)標(biāo)簽名// 創(chuàng)建節(jié)點(diǎn) document.createDocumentFragment(); // 創(chuàng)建內(nèi)存文檔碎片 document.createElement(); // 創(chuàng)建元素 document.createTextNode(); // 創(chuàng)建文本節(jié)點(diǎn)// 添加節(jié)點(diǎn) var oDiv = document.createElement('div');// 插入 Dom 節(jié)點(diǎn) // 方法1:appendChild() 把節(jié)點(diǎn)插入到父節(jié)點(diǎn)的末尾 document.body.appendChild(oDiv) // 把 div 插入到 body 中,并且位于末尾 // 方法2:insertBefore() 把節(jié)點(diǎn)插入到父節(jié)點(diǎn)的某個(gè)兄弟節(jié)點(diǎn)的前面 var oP = createElement('p'); // 創(chuàng)建一個(gè) p 節(jié)點(diǎn) document.body.insertBefore(oP,oDiv); // 把 p 節(jié)點(diǎn)插入到 div 的前面// 刪除節(jié)點(diǎn) document.body.removeChild(oP); // 刪除 p 節(jié)點(diǎn)// 替換 Dom 節(jié)點(diǎn) var oSpan = document.createElement('span'); document.body.replaceChild(oSpan,oBox); // 用 span 標(biāo)簽替換 div 標(biāo)簽復(fù)制代碼javaScript 的本地對(duì)象,內(nèi)置對(duì)象和宿主對(duì)象
內(nèi)部對(duì)象
js中的內(nèi)部對(duì)象包括Array、Boolean、Date、Function、Global、Math、Number、Object、RegExp、String以及各種錯(cuò)誤類對(duì)象,包括Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError。
本地對(duì)象
Array、Boolean、Date、Function、Number、Object、RegExp、String、Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError 等 new 可以實(shí)例化
內(nèi)置對(duì)象
其中Global和Math這兩個(gè)對(duì)象又被稱為“內(nèi)置對(duì)象”,這兩個(gè)對(duì)象在腳本程序初始化時(shí)被創(chuàng)建,不必實(shí)例化這兩個(gè)對(duì)象。
宿主對(duì)象
宿主環(huán)境:一般宿主環(huán)境由外殼程序創(chuàng)建與維護(hù),只要能提供js引擎執(zhí)行的環(huán)境都可稱之為外殼程序。如:web瀏覽器,一些桌面應(yīng)用系統(tǒng)等。即由web瀏覽器或是這些桌面應(yīng)用系統(tǒng)早就的環(huán)境即宿主環(huán)境。
那么宿主就是瀏覽器自帶的 document , window 等
== 和 === 的不同
前者會(huì)自動(dòng)轉(zhuǎn)換類型,而后者不會(huì)。
比較過(guò)程:
雙等號(hào)==:
三等號(hào) === :
javaScript 的同源策略
同源策略的含義:腳本只能讀取和所屬文檔來(lái)源相同的窗口和文檔的屬性。
同源指的是主機(jī)名、協(xié)議和端口號(hào)的組合
同源策略帶來(lái)的限制
cookie、LocalStorage 和 IndexDB無(wú)法讀取
DOM 無(wú)法獲取
AJAX請(qǐng)求無(wú)法發(fā)送
主流跨域請(qǐng)求解決方案
1、JSONP 實(shí)現(xiàn)跨域
為了便于客戶端使用數(shù)據(jù),逐漸形成了一種非正式傳輸協(xié)議。人們把它稱作JSONP。該協(xié)議的一個(gè)要點(diǎn)就是允許用戶傳遞一個(gè)callback參數(shù)給服務(wù)端,然后服務(wù)端返回?cái)?shù)據(jù)時(shí)會(huì)將這個(gè)callback參數(shù)作為函數(shù)名來(lái)包裹住JSON數(shù)據(jù),這樣客戶端就可以隨意定制自己的函數(shù)來(lái)自動(dòng)處理返回?cái)?shù)據(jù)了。
jsonp的核心是動(dòng)態(tài)添加 script標(biāo)簽 來(lái)調(diào)用服務(wù)器提供的js腳本。
說(shuō)說(shuō)JSON和JSONP
JSONP實(shí)現(xiàn)原理?
JSONP的局限性
JSONP 方式,固然方便強(qiáng)大。但是他的局限性在于,它無(wú)法完成POST請(qǐng)求。即是我們將type改為post,在發(fā)送請(qǐng)求時(shí),依然會(huì)是以Get的方式。
2、CORS跨域
CORS原理
CORS(Cross-Origin-Resource Sharing,跨院資源共享)是一種允許多種資源(圖片,Css文字,Javascript等)在一個(gè)Web頁(yè)面請(qǐng)求域之外的另一個(gè)域的資源的機(jī)制。 跨域資源共享這種機(jī)制讓W(xué)eb應(yīng)用服務(wù)器支持跨站訪問(wèn)控制,從而使得安全的進(jìn)行跨站數(shù)據(jù)傳輸成為了可能。
通過(guò)這種機(jī)制設(shè)置一系列的響應(yīng)頭,這些響應(yīng)頭允許瀏覽器與服務(wù)器進(jìn)行交流,實(shí)現(xiàn)資源共享。
各語(yǔ)言設(shè)置響應(yīng)頭的方法
CORS 解決方案相對(duì)于JSONP 更加靈活,而且支持POST請(qǐng)求,是跨域的根源性解決方案
3、代理層
JSONP 和CORS 是主流的 跨域問(wèn)題 的解決方案。除了他們吶,還有一種解決方案,就是代理層。簡(jiǎn)要說(shuō)一下
JS 調(diào)用本源的后臺(tái)的方法(這樣就不存在跨域的問(wèn)題),而通過(guò)后臺(tái)(任何具有網(wǎng)絡(luò)訪問(wèn)功能的后臺(tái)語(yǔ)言,ASP.NET ,JAVA,PHP等)去跨域請(qǐng)求資源,而后將結(jié)果返回至前臺(tái)。
另外也可以看看
同源策略
JavaScript的同源策略 Redirect 1
編寫(xiě)一個(gè)數(shù)組去重的方法
var arr = [1,1,3,4,2,4,7] => [1,2,4,2,7]
// 方法1 雙重循環(huán),逐個(gè)判斷數(shù)組中某個(gè)元素是否與其他元素相同,相同則去掉,并且索引減1,重復(fù)執(zhí)行直到雙重遍歷完成 function DuplicateRemoval(arr){for(var i = 0; i < arr.length-1; i++){for(var j = i + 1; j < arr.length; j++){if(arr[i] == arr[j]){arr.splice(j,1);j--;}}}return arr; } var arr=[1,1,3,4,2,4,7]; console.log(DuplicateRemoval(arr));// 方法2 借助 indexOf() 方法判斷此元素在該數(shù)組中首次出現(xiàn)位置下標(biāo)與循環(huán)的下標(biāo)是否相同 function DuplicateRemoval(arr){for(var i = 0; i < arr.length; i++){if(arr.indexOf(arr[i]) !== i){arr.splice(i,1);i--}}return arr; } var arr=[1,1,3,4,2,4,7]; console.log(DuplicateRemoval(arr)); // 方法3 利用數(shù)組的方法 filter() var arr=[1,1,3,4,2,4,7]; var result = arr.filter( (element, index, self) => self.indexOf(element) === index ); console.log(result);// 方法4 利用新數(shù)組 通過(guò)indexOf判斷當(dāng)前元素在數(shù)組中的索引如果與循環(huán)相同則添加到新數(shù)組中 var arr=[1,1,3,4,2,4,7]; function Add(arr){var result = [];for(var i = 0; i < arr.length; i++){if(arr.indexOf(arr[i]) === i){result.push(arr[i]);}}return result; } console.log(Add(arr));// 方法5 ES6方法 Set() Set函數(shù)可以接受一個(gè)數(shù)組(或類似數(shù)組的對(duì)象)作為參數(shù),用來(lái)初始化。 var arr=[1,1,3,4,2,4,7]; console.log([...new Set(arr)]); 復(fù)制代碼JavaScript 的數(shù)據(jù)類型都有什么
基本數(shù)據(jù)類型:String,Boolean,number,undefined,object,Null
引用數(shù)據(jù)類型:Object(Array,Date,RegExp,Function)
類型檢測(cè)
typeof
typeof 運(yùn)算精度只能是基礎(chǔ)類型也就是 number , string , undefined , boolean , object ,要注意的是 null 和數(shù)組使用 typeof 運(yùn)算符得到的也是 object
console.log(typeof 123); // number console.log(typeof 'type'); // string console.log(typeof null); // object console.log(typeof undefined); // undefined console.log(typeof true); // boolean console.log(typeof []); // object console.log(typeof {}); // object console.log(typeof function(){}); // function 復(fù)制代碼instanceof
用于判斷一個(gè)變量是否某個(gè)對(duì)象的實(shí)例,或用于判斷一個(gè)變量是否某個(gè)對(duì)象的實(shí)例
instanceof 運(yùn)算符可以精確到是哪一種類型的引用,但 instanceof 也有一個(gè)缺陷就是對(duì)于直接賦值的數(shù)字,字符串,布爾值以及數(shù)組是不能將其識(shí)別為Number,String,Boolean,Array。
console.log(123 instanceof Number); // false console.log('type' instanceof String); // false console.log(true instanceof Boolean); // false console.log([] instanceof Array); // true console.log({} instanceof Object); // true console.log(function(){} instanceof Function); // true 復(fù)制代碼toString.call()
console.log(toString.call(123) ); // [object Number] console.log(toString.call('type')); // [object String] console.log(toString.call(null)); // [object Null] console.log(toString.call(undefined)); // [object Undefined] console.log(toString.call(true)); // [object Boolean] console.log(toString.call([])); // [object Array] console.log(toString.call({})); // [object Object] console.log(toString.call(function(){})); // [object Function] 復(fù)制代碼constructor
構(gòu)造函數(shù)的屬性 用來(lái)判別對(duì)創(chuàng)建實(shí)例對(duì)象的函數(shù)的引用,另外 constructor 可以被改寫(xiě)
var number = 123, string = 'type', boolean = true, arr = [], obj = {}, fn = function(){};console.log( number.constructor); // ? Number() { [native code] } console.log( string.constructor); // ? String() { [native code] } console.log( boolean.constructor); // ? Boolean() { [native code] } console.log( arr.constructor); // ? Array() { [native code] } console.log( obj.constructor); // ? Object() { [native code] } console.log( fn.constructor); // ? Function() { [native code] } 復(fù)制代碼也可以用 constructor.name就會(huì)返回函數(shù)名
Object.prototype.toString.call()
這個(gè)方法其實(shí)跟上面 toString()是一樣的,只是上面那個(gè)方法有可能會(huì)被重寫(xiě)。所以用這個(gè)比較穩(wěn)妥,所有對(duì)象都包含有一個(gè)內(nèi)部屬性[[Class]] ,這個(gè)屬性不能直接訪問(wèn),但是我們可以通過(guò)Object.prototype.toString.call()
console.log(Object.prototype.toString.call(123) ); // [object Number] console.log(Object.prototype.toString.call('type')); // [object String] console.log(Object.prototype.toString.call(null)); // [object Null] console.log(Object.prototype.toString.call(undefined)); // [object Undefined] console.log(Object.prototype.toString.call(true)); // [object Boolean] console.log(Object.prototype.toString.call([])); // [object Array] console.log(Object.prototype.toString.call({})); // [object Object] console.log(Object.prototype.toString.call(function(){})); // [object Function] 復(fù)制代碼需要注意的是IE6/7/8中 Object.prototype.toString.apply(null)返回“[object Object]”。
js 中 innerText/value/innerHTML三個(gè)屬性的區(qū)別
<div value = "3" id="target3"><input type="text" value="1" id="target1"><span id="target2" value="2">span</span> </div> 復(fù)制代碼var a = document.getElementById("target1"); var b = document.getElementById("target2"); var c = document.getElementById("target3"); console.log('----------'); console.log(a.value); // 1 console.log(a.innerHTML); console.log(a.innerText); console.log('----------'); console.log(b.value); // undefined console.log(b.innerHTML); // span console.log(b.innerText); // span console.log('----------'); console.log(c.value); // undefined console.log(c.innerHTML); // <input type="text" value="1" id="target1"><span id="target2" value="2">span</span> console.log(c.innerText); // span 復(fù)制代碼總結(jié):
希望獲取到頁(yè)面中所有的 checkbox 怎么做?( ( 不使用第三方框架) )
html:
<input type="checkbox"> <input type="checkbox"> <input type="checkbox"> 復(fù)制代碼window.onload = function(){var domList = document.getElementsByTagName('input');var checkBoxList = []; // 返回所有的 checkbox var len = domList.length; // 緩存到局部變量while(len--){if(domList[len].type = 'checkbox'){checkBoxList.push(domList[len]);}}console.log(checkBoxList); // ?[input, input, input] } 復(fù)制代碼當(dāng)一個(gè) Dom節(jié)點(diǎn)被點(diǎn)擊時(shí)候,我們希望能夠執(zhí)行一個(gè)函數(shù),應(yīng)該怎么做?
<!-- 直接在 DOM 里綁定事件:--> <div onlick = "test()"> 復(fù)制代碼// 在js 里面通過(guò) onclick 綁定 xxx.onclick = test // 在事件里面添加綁定 addEventListener(xxx, 'click', test) 復(fù)制代碼Javascript 的事件流模型都有什么?
"事件冒泡":事件開(kāi)始由最具體的元素接受,然后逐級(jí)向上傳播
“事件捕捉”:事件由最不具體的節(jié)點(diǎn)先接收,然后逐級(jí)向下,一直到最具體的
“DOM 事件流”:三個(gè)階段,事件捕捉,目標(biāo)階段。事件冒泡。
已知有字符串 foo=”get-element-by-id”,寫(xiě)一個(gè) function 將其轉(zhuǎn)化成駝峰表示法”——getElementById”。
var foo = "get-element-by-id"; function combo(msg){var arr = msg.split('-');for(var i=1;i < arr.length; i++){arr[i] = arr[i].charAt(0).toUpperCase()+arr[i].substr(1,arr[i].length-1);}msg = arr.join('');return msg; } console.log(combo(foo)); // getElementById 復(fù)制代碼輸出今天的日期,以 YYYY-MM-DD 的方式
var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; month = month < 10 ? '0' + month : month; var day = date.getDate(); day = day < 10 ? '0' + day : day; console.log([year,month,day].join('-')) // 2018-11-01 復(fù)制代碼將字符串 ” <tr><td>{${$name}</td></tr>” 中的 {$id} 替換 成 10 , {$name} 替換成 Tony (使用正則表達(dá)式)
var str = '<tr><td>{$id}</td><td>{$name}</td></tr>'; str.replace(/{\$id}/g,10).replace(/{\$name}/,'Tony') 復(fù)制代碼為了保證頁(yè)面輸出安全,我們經(jīng)常需要對(duì)一些特殊的字符進(jìn)行轉(zhuǎn)義,請(qǐng)寫(xiě)一個(gè)函數(shù) escapeHtml ,將 <, >, &, “ 進(jìn)行轉(zhuǎn)義
function escapeHtml(str){return str.replace(/[<>”&]/g,function(match){switch(match) {case "<":return '<';break;case ">":return '>'break;case "\”":return '"'break; case "&":return '&'break;}}); } 復(fù)制代碼參考鏈接:
轉(zhuǎn)載于:https://juejin.im/post/5bcd892d6fb9a05d2e1bd758
總結(jié)
以上是生活随笔為你收集整理的前端面试题目汇总摘录(JS 基础篇)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 国产掌机升级AMD锐龙7 7735U:锐
- 下一篇: IE兼容问题 动态生成的节点IE浏览器无