前端面试题目汇总摘录(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ī)則為
下表是對象的 valueOf() 的返回值
| Array | 數(shù)組的元素被轉(zhuǎn)換為字符串,這些字符串由逗號分隔,連接在一起。其操作與 Array.toString 和 Array.join 方法相同。 |
| Boolean | Boolean 值。 |
| Date | 存儲的時(shí)間是從 1970 年 1 月 1 日午夜開始計(jì)的毫秒數(shù) UTC。 |
| Function | 函數(shù)本身。 |
| Number | 數(shù)字值。 |
| Object | 對象本身。這是默認(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()也是從第一個字符(位置0)開始解析每個字符。而且也是一直解析到字符串末尾,或者解析到遇見一個無效的浮點(diǎn)數(shù)字字符為止。也就是說,字符串中的第一個小數(shù)點(diǎn)是有效的,而第二個小數(shù)點(diǎn)就是無效的了,因此它后面的字符串將被忽略。
toString(radix)
除 undefined 和 null之外的所有類型的值都具有 toString() 方法,其作用是返回對象的字符串表示。
多數(shù)情況下,調(diào)用toString()方法不必傳遞參數(shù)。但是,在調(diào)用數(shù)值的toString()方法時(shí),可以傳遞一個參數(shù):輸出數(shù)值的基數(shù)。默認(rèn)情況下,toString()方法以十進(jìn)制格式返回?cái)?shù)值的字符串表示。
| Array | 將 Array 的元素轉(zhuǎn)換為字符串。結(jié)果字符串由逗號分隔,且連接起來。 |
| Boolean | 如果 Boolean 值是 true,則返回 “true”。否則,返回 “false”。 |
| Date | 返回日期的文字表示法。 |
| Error | 返回一個包含相關(guān)錯誤信息的字符串。 |
| Function | 返回如下格式的字符串,其中 functionname 是被調(diào)用 toString 方法函數(shù)的名稱:function functionname( ) { [native code] } |
| Number | 返回?cái)?shù)字的文字表示。 |
| String | 返回 String 對象的值。 |
| 默認(rèn) | 返回 “[object objectname]”,其中 objectname 是對象類型的名稱。 |
在不知道要轉(zhuǎn)換的值是不是null或undefined的情況下,還可以使用轉(zhuǎn)型函數(shù)String(),這個函數(shù)能夠?qū)⑷魏晤愋偷闹缔D(zhuǎn)換為字符串。
String(mix)函數(shù),將任何類型的值轉(zhuǎn)換為字符串,其規(guī)則為:
Boolean(mix)函數(shù),將任何類型的值轉(zhuǎn)換為布爾值。
以下值會被轉(zhuǎn)換為false:false、”"、0、NaN、null、undefined,其余任何值都會被轉(zhuǎn)換為true。
隱式轉(zhuǎn)換(非強(qiáng)制轉(zhuǎn)換類型)
在某些情況下,即使我們不提供顯示轉(zhuǎn)換,Javascript也會進(jìn)行自動類型轉(zhuǎn)換,主要情況有:
用于檢測是否為非數(shù)值的函數(shù):isNaN(mix)
isNaN()函數(shù),經(jīng)測試發(fā)現(xiàn),該函數(shù)會嘗試將參數(shù)值用 Number() 進(jìn)行轉(zhuǎn)換,如果結(jié)果為“非數(shù)值”則返回 true,否則返回 false。
遞增遞減操作符(包括前置和后置)、一元正負(fù)符號操作符(經(jīng)過對比發(fā)現(xiàn),其規(guī)則與Number()規(guī)則基本相同)
加法運(yùn)算操作符
加號運(yùn)算操作符在Javascript也用于字符串連接符,所以加號操作符的規(guī)則分兩種情況:
如果兩個操作值都是數(shù)值,其規(guī)則為:
如果有一個操作值為字符串,則:
可以看出,加法運(yùn)算中,如果有一個操作值為字符串類型,則將另一個操作值轉(zhuǎn)換為字符串,最后連接起來。
乘除、減號運(yùn)算符、取模運(yùn)算符
這些操作符針對的是運(yùn)算,所以他們具有共同性:如果操作值之一不是數(shù)值,則被隱式調(diào)用Number() 函數(shù)進(jìn)行轉(zhuǎn)換。具體每一種運(yùn)算的詳細(xì)規(guī)則請參考ECMAScript中的定義。
邏輯操作符(!、&&、||)
邏輯非(!)操作符首先通過Boolean()函數(shù)將它的操作值轉(zhuǎn)換為布爾值,然后求反。
邏輯與(&&)操作符,如果一個操作值不是布爾值時(shí),遵循以下規(guī)則進(jìn)行轉(zhuǎn)換:
邏輯或(||)操作符,如果一個操作值不是布爾值,遵循以下規(guī)則
關(guān)系操作符(<, >, <=, >=)
與上述操作符一樣,關(guān)系操作符的操作值也可以是任意類型的,所以使用非數(shù)值類型參與比較時(shí)也需要系統(tǒng)進(jìn)行隱式類型轉(zhuǎn)換:
注:NaN 是非常特殊的值,它不和任何類型的值相等,包括它自己,同時(shí)它與任何類型的值比較大小時(shí)都返回 false。
相等操作符(==)
相等操作符會對操作值進(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ù)組的長度 |
| unshift() | 將參數(shù)添加到原數(shù)組開頭,并返回?cái)?shù)組的長度 |
| shift() | 刪除原數(shù)組第一項(xiàng),并返回刪除元素的值;如果數(shù)組為空則返回undefined |
事件綁定和普通事件有什么區(qū)別
普通事件中的onclick是DOM0級事件只支持單個事件,會被其他onclick事件覆蓋,而事件綁定中的addEventListener是DOM2級事件可以添加多個事件而不用擔(dān)心被覆蓋
普通添加事件的方法:
var btn = document.getElementById("hello"); btn.onclick = function(){alert(1); } btn.onclick = function(){alert(2); } 復(fù)制代碼執(zhí)行上面的代碼只會alert 2
事件綁定方式添加事件:
var btn = document.getElementById("hello"); btn.addEventListener("click",function(){alert(1); },false); btn.addEventListener("click",function(){alert(2); },false); 復(fù)制代碼執(zhí)行上面的代碼會先alert 1 再 alert 2
IE 和 DOM 事件流有什么區(qū)別
事件
HTML元素事件是瀏覽器內(nèi)在自動產(chǎn)生的,當(dāng)有事件發(fā)生時(shí)html元素會向外界(這里主要指元素事件的訂閱者)發(fā)出各種事件,如click,onmouseover,onmouseout等等。
DOM事件流
DOM(文檔對象模型)結(jié)構(gòu)是一個樹型結(jié)構(gòu),當(dāng)一個HTML元素產(chǎn)生一個事件時(shí),該事件會在元素結(jié)點(diǎn)與根結(jié)點(diǎn)之間的路徑傳播,路徑所經(jīng)過的結(jié)點(diǎn)都會收到該事件,這個傳播過程可稱為DOM事件流。
冒泡型事件(Bubbling)
這是IE瀏覽器對事件模型的實(shí)現(xiàn)。冒泡,顧名思義,事件像個水中的氣泡一樣一直往上冒,直到頂端。從DOM樹型結(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樹最頂層元素一直到最精確的元素,直觀上的理解應(yīng)該如同冒泡型,事件傳遞應(yīng)該由最確定的元素,即事件產(chǎn)生元素開始。
DOM標(biāo)準(zhǔn)事件模型
因?yàn)閮蓚€不同的模型都有其優(yōu)點(diǎn)和解釋,DOM標(biāo)準(zhǔn)支持捕獲型與冒泡型,可以說是它們兩者的結(jié)合體。它可以在一個DOM元素上綁定多個事件處理器,并且在處理函數(shù)內(nèi)部,this關(guān)鍵字仍然指向被綁定的DOM元素,另外處理函數(shù)參數(shù)列表的第一個位置傳遞事件event對象。
首先是捕獲式傳遞事件,接著是冒泡式傳遞,所以,如果一個處理函數(shù)既注冊了捕獲型事件的監(jiān)聽,又注冊冒泡型事件監(jiān)聽,那么在DOM事件模型中它就會被調(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(先捕獲后冒泡)
**事件偵聽函數(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)處理是指,通常瀏覽器在事件傳遞并處理完后會執(zhí)行與該事件關(guān)聯(lián)的默認(rèn)動作(如果存在這樣的動作)。例如,如果表單中input type 屬性是 “submit”,點(diǎn)擊后在事件傳播完瀏覽器就就自動提交表單。又例如,input 元素的 keydown 事件發(fā)生并處理后,瀏覽器默認(rèn)會將用戶鍵入的字符自動追加到 input 元素的值中。
要取消瀏覽器的事件傳遞,IE與DOM標(biāo)準(zhǔn)又有所不同。
在IE下,通過設(shè)置 event 對象的 cancelBubble 為 true 即可。
function someHandle() { window.event.cancelBubble = true; } 復(fù)制代碼DOM標(biāo)準(zhǔn)通過調(diào)用 event對象的 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下,通過設(shè)置 event 對象的 returnValue 為 false 即可。
function someHandle() { window.event.returnValue = false; } 復(fù)制代碼DOM標(biāo)準(zhǔn)通過調(diào)用 event 對象的 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)下有哪些兼容性的寫法
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): 都是為了用一個本不屬于一個對象的方法,讓這個對象去執(zhí)行
基本使用
call()
function.call(obj[,arg1[, arg2[, [,.argN]]]]]) 復(fù)制代碼- 調(diào)用call的對象必須是個函數(shù)function
- call的第一個參數(shù)將會是function改變上下文后指向的對象.如果不傳,將會默認(rèn)是全局對象window
- 第二個參數(shù)開始可以接收任意個參數(shù),這些參數(shù)將會作為function的參數(shù)傳入function
- 調(diào)用call的方法會立即執(zhí)行
apply()
function.apply(obj[,argArray]) 復(fù)制代碼與call方法的使用基本一致,但是只接收兩個參數(shù),其中第二個參數(shù)必須是一個數(shù)組或者類數(shù)組,這也是這兩個方法很重要的一個區(qū)別
數(shù)組與類數(shù)組小科普
數(shù)組我們都知道是什么,它的特征都有哪些呢?
類數(shù)組顧名思義,具備的特征應(yīng)該與數(shù)組基本相同,那么可以知道,一個形如下面這個對象的對象就是一個類數(shù)組
var arrayLike = {0: 'item1',1: 'item2',2: 'item3',length: 3 } 復(fù)制代碼類數(shù)組arrayLike可以通過角標(biāo)進(jìn)行調(diào)用,具有l(wèi)ength屬性,同時(shí)也可以通過 for 循環(huán)進(jìn)行遍歷
我們經(jīng)常使用的獲取dom節(jié)點(diǎn)的方法返回的就是一個類數(shù)組,在一個方法中使用 arguments關(guān)鍵字獲取到的該方法的所有參數(shù)也是一個類數(shù)組
但是類數(shù)組卻不能通過forEach進(jìn)行遍歷,因?yàn)閒orEach是數(shù)組原型鏈上的方法,類數(shù)組畢竟不是數(shù)組,所以無法使用
不同點(diǎn)
call方法從第二個參數(shù)開始可以接收任意個參數(shù),每個參數(shù)會映射到相應(yīng)位置的func的參數(shù)上,可以通過參數(shù)名調(diào)用,但是如果將所有的參數(shù)作為數(shù)組傳入,它們會作為一個整體映射到func對應(yīng)的第一個參數(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方法最多只有兩個參數(shù),第二個參數(shù)接收數(shù)組或者類數(shù)組,但是都會被轉(zhuǎn)換成類數(shù)組傳入func中,并且會被映射到func對應(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 的方法
方法一:對象冒充
function Parent(username){this.username = username;this.hello = function(){console.log(this.username);} } function Child(username,password){this.method = Parent; // this.method 作為一個臨時(shí)的屬性,并且指向了 Parent所指向的對象函數(shù)this.method(username); // 執(zhí)行 this.method 方法,即執(zhí)行了 Parent 所指向的對象函數(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 方法的第一個參數(shù)的值賦值給類(即方法)中出現(xiàn)的 this call 方法的第二個參數(shù)開始依次賦值給類(即方法)所接受的參數(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個參數(shù)
第一個參數(shù)與call方法的第一個參數(shù)一樣,即賦值給類(即方法)中出現(xiàn)的this
第二個參數(shù)為數(shù)組類型,這個數(shù)組中的每個元素依次賦值給類(即方法)所接受的參數(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ù)制代碼方法四:原型鏈
即子類通過 prototype 將所有在父類中通過 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 中所有通過 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); // 將父類的屬性繼承過來this.world = world; } Child.prototype = new Parent(); //將父類的方法繼承過來 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ù)的對象。要判斷 this 的指向,其實(shí)就是判斷 this 所在的函數(shù)屬于誰。
在《javaScript語言精粹》這本書中,把 this 出現(xiàn)的場景分為四類,簡單的說就是:
1)有對象就指向調(diào)用對象
var myObject = { value: 123 } myObject.getValue = function(){console.log(this.value); // 123console.log(this); // {value: 123, getValue: ?} } myObject.getValue(); 復(fù)制代碼2)沒調(diào)用對象就指向全局對象
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 指針指向了全局對象}foo();return this.value; } console.log(myObject.getValue()); // 123 復(fù)制代碼3) 用new構(gòu)造就指向新對象
// js 中通過 new 關(guān)鍵詞來調(diào)用構(gòu)造函數(shù),此時(shí) this 會綁定雜該新對象上 var someClass = function(){this.value = 123; } var myCreate = new someClass(); console.log(myCreate.value); // 123 復(fù)制代碼4)通過 apply 或 call 或 bind 來改變 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 ,簡而言之,閉包就是
作為局部變量都可以被函數(shù)內(nèi)的代碼訪問,這個和靜態(tài)語言是沒有差別的,閉包的差別在于局部變量可以在函數(shù)執(zhí)行結(jié)束后仍然被函數(shù)外的代碼訪問,這意味著函數(shù)必須返回一個指向閉包的“引用”,或?qū)⑦@個“引用”賦值給某個外部變量,才能保證閉包中局部變量被外部代碼訪問,當(dāng)然包含這個引用的實(shí)體應(yīng)該是一個對象。但是ES并沒有提供相關(guān)的成員和方法來訪問包中的局部變量,但是在ES中,函數(shù)對象中定義的內(nèi)部函數(shù)是可以直接訪問外部函數(shù)的局部變量,通過這種機(jī)制,可以用如下方式完成對閉包的訪問。
function greeting(name){var text = "Hello " + name; // 局部變量// 每次調(diào)用時(shí),產(chǎn)生閉包,并返回內(nèi)部函數(shù)對象給調(diào)用者return function(){console.log(text);} } var sayHello = greeting('Closure'); // 通過閉包訪問到了局部變量text sayHello(); // 輸出Hello Closure 復(fù)制代碼在 ECMAscript 的腳本函數(shù)運(yùn)行時(shí),每個函數(shù)關(guān)聯(lián)都有一個執(zhí)行上下文場景(Exection Context),這個執(zhí)行上下文包括三個部分
- 文法環(huán)境(The LexicalEnvironment)
- 變量環(huán)境(The VariableEnvironment)
- this綁定
其中第三點(diǎn)this綁定與閉包無關(guān),不在本文中討論。文法環(huán)境中用于解析函數(shù)執(zhí)行過程使用到的變量標(biāo)識符。我們可以將文法環(huán)境想象成一個對象,該對象包含了兩個重要組件,環(huán)境記錄(Enviroment Recode),和外部引用(指針)。環(huán)境記錄包含包含了函數(shù)內(nèi)部聲明的局部變量和參數(shù)變量,外部引用指向了外部函數(shù)對象的上下文執(zhí)行場景。全局的上下文場景中此引用值為NULL。這樣的數(shù)據(jù)結(jié)構(gòu)就構(gòu)成了一個單向的鏈表,每個引用都指向外層的上下文場景。
例如上面我們例子的閉包模型應(yīng)該是這樣,sayHello函數(shù)在最下層,上層是函數(shù)greeting,最外層是全局場景。如下圖:
因此當(dāng)sayHello被調(diào)用的時(shí)候,sayHello會通過上下文場景找到局部變量text的值,因此在屏幕的對話框中顯示出”Hello Closure”
針對一些例子來幫助大家更加深入的理解閉包,下面共有5個樣例,例子來自于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:多個函數(shù)綁定同一個閉包,因?yàn)樗麄兌x在同一個函數(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)在一個循環(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)檫@三個函數(shù)綁定了同一個閉包,而且item的值為最后計(jì)算的結(jié)果,但是當(dāng)i跳出循環(huán)時(shí)i值為4,所以list[4]的結(jié)果為undefined.
例子4:外部函數(shù)所有局部變量都在閉包內(nèi),即使這個變量聲明在內(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之后,局部變量仍然可以被訪問到。
例子5:每次函數(shù)調(diào)用的時(shí)候創(chuàng)建一個新的閉包
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)由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
(2)閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對象(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; //保存一個副本到變量中return function(){return outerColor; //應(yīng)用這個副本};outColor = null; //釋放內(nèi)存 };var instance = new Cars(); console.log(instance.sayColor()()) 復(fù)制代碼作用域
在JS當(dāng)中一個變量的作用域(scope)是程序中定義這個變量的區(qū)域。變量分為兩類:全局(global)的和局部的。其中全局變量的作用域是全局性的,即在JavaScript代碼中,它處處都有定義。而在函數(shù)之內(nèi)聲明的變量,就只在函數(shù)體內(nèi)部有定義。它們是局部變量,作用域是局部性的。函數(shù)的參數(shù)也是局部變量,它們只在函數(shù)體內(nèi)部有定義。
我們可以借助JavaScript的作用域鏈(scope chain)更好地了解變量的作用域。每個JavaScript執(zhí)行環(huán)境都有一個和它關(guān)聯(lián)在一起的作用域鏈。這個作用域鏈?zhǔn)且粋€對象列表或?qū)ο箧湣.?dāng)JavaScript代碼需要查詢變量x的值時(shí)(這個過程叫做變量解析(variable name resolution)),它就開始查看該鏈的第一個對象。如果那個對象有一個名為x的屬性,那么就采用那個屬性的值。如果第一個對象沒有名為x的屬性,JavaScript就會繼續(xù)查詢鏈中的第二個對象。如果第二個對象仍然沒有名為x的屬性,那么就繼續(xù)查詢下一個對象,以此類推。如果查詢到最后(指頂層代碼中)不存在這個屬性,那么這個變量的值就是未定義的。
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 高級程序設(shè)計(jì)上講。事件委托就是利用事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。
實(shí)際例子:
有三個同事預(yù)計(jì)會在周一收到快遞,為簽收快遞,有兩種方法:一是三個人在公司門口等快遞,二是委托給前臺的小姐代為簽收。現(xiàn)實(shí)生活中,我們大多采用委托的方案(公司也不會容忍那么多人站在門口)。前臺小姐收到快遞后,會判斷收件人是誰,按照收件人的要求簽收,甚至是代付。這種方案還有一個好處就是,即使公司來了很多新員工(不管多少),前臺小姐也會在收到寄給新員工們的快遞后核實(shí)代為簽收。
這里有2層意思: 第一、現(xiàn)在委托前臺的小姐是可以代為簽收的,即程序中的現(xiàn)有的 DOM 節(jié)點(diǎn)是有事件的。
第二、新員工也可以被前臺小姐代為簽收,即程序中新添加的 DOM 節(jié)點(diǎn)也是有事件的。
為什么要使用事件委托
一般來說,DOM 需要有事件處理程序,就會直接給它設(shè)處理程序,但是如果是很多 DOM 需要添加處理事件呢?例如我們有100個 li,每個 li 都有相同的 click 點(diǎn)擊事件,可能我們會用到 for 循環(huán),來遍歷所有 li ,然后給它們添加事件,那么會存在什么樣的問題?
在 JavsScript 中,添加到頁面上的事件處理程序數(shù)量將直接影響到整體運(yùn)行性能,因?yàn)樾枰粩嗟嘏c DOM 進(jìn)行交互,訪問 DOM 的次數(shù)越多,引起瀏覽器重繪與重排的次數(shù)也就越多,就會延長整個頁面的交互就緒時(shí)間,這就是為什么性能優(yōu)化的主要思想之一就是減少 DOM 操作的原因、如果要用到事件委托,就會將所有的操作都放在 js 程序里面,與 DOM 的操作就只需要交互一次,這樣就可以大大減少與 DOM 的交互次數(shù),提高性能。
每個函數(shù)都是一個對象,是對象就會占用內(nèi)存,對象越多,內(nèi)存占用率就越大,自然性能就越差了(內(nèi)存不夠用,是硬傷,哈哈),比如上面的100個li,就要占用100個內(nèi)存空間,如果是1000個,10000個呢,那只能說呵呵了,如果用事件委托,那么我們就可以只對它的父級(如果只有一個父級)這一個對象進(jìn)行操作,這樣我們就需要一個內(nèi)存空間就夠了,是不是省了很多,自然性能就會更好。
事件委托的原理
事件委托是利用事件的冒泡原理來實(shí)現(xiàn)的,何為事件冒泡?就是事件從最深的節(jié)點(diǎn)開始執(zhí)行,然后逐步向上傳播事件,例子:
頁面上有一個節(jié)點(diǎn)樹,div>ul>li>a,比如給最里面的 a 加一個 click 點(diǎn)擊事件,那么這個事件就會一層一層的往外執(zhí)行,執(zhí)行順序 a>li>ul>div,有這么一個機(jī)制,那么我們給最外面的 div 加點(diǎn)擊事件,那么里面的 ul,li,a 做點(diǎn)擊事件的時(shí)候,都會冒泡到最外層的 div 上面,都會觸發(fā),這就是事件委托,委托他們父級代為執(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ù)制代碼上面的代碼的意思很簡單,相信很多人都是這么實(shí)現(xiàn)的,我們看看有多少次的dom操作,首先要找到ul,然后遍歷li,然后點(diǎn)擊li的時(shí)候,又要找一次目標(biāo)的li的位置,才能執(zhí)行最后的操作,每次點(diǎn)擊都要找一次li;
那么我們用事件委托的方式做又會怎么樣呢?
window.onload = function(){var oUl = document.getElementById('ul');oUl.onclick = function(){alert(123);} } 復(fù)制代碼這里用父級ul做事件處理,當(dāng)li被點(diǎn)擊時(shí),由于冒泡原理,事件就會冒泡到ul上,因?yàn)閡l上有點(diǎn)擊事件,所以事件就會觸發(fā),當(dāng)然,這里當(dāng)點(diǎn)擊ul的時(shí)候,也是會觸發(fā)的,那么問題就來了,如果我想讓事件代理的效果跟直接給節(jié)點(diǎn)的事件效果一樣怎么辦,比如說只有點(diǎn)擊li才會觸發(fā),不怕,我們有絕招:
Event對象提供了一個屬性叫target,可以返回事件的目標(biāo)節(jié)點(diǎn),我們成為事件源,也就是說,target就可以表示為當(dāng)前的事件操作的dom,但是不是真正操作dom,當(dāng)然,這個是有兼容性的,標(biāo)準(zhǔn)瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時(shí)只是獲取了當(dāng)前節(jié)點(diǎn)的位置,并不知道是什么節(jié)點(diǎn)名稱,這里我們用nodeName來獲取具體是什么標(biāo)簽名,這個返回的是一個大寫的,我們需要轉(zhuǎn)成小寫再做比較(習(xí)慣問題):
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會觸發(fā)事件了,且每次只執(zhí)行一次dom操作,如果li數(shù)量很多的話,將大大減少dom的操作,優(yōu)化的性能可想而知!
上面的例子是說li操作的是同樣的效果,要是每個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('移動'); }; Select.onclick = function(){alert('選擇'); } 復(fù)制代碼上面實(shí)現(xiàn)的效果我就不多說了,很簡單,4個按鈕,點(diǎn)擊每一個做不同的操作,那么至少需要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('移動');break;case 'select':alert('選擇');break;default :alert('業(yè)務(wù)錯誤');break;}} } 復(fù)制代碼用事件委托就可以只用一次dom操作就能完成所有的效果,比上面的性能肯定是要好一些的
現(xiàn)在講的都是document加載完成的現(xiàn)有dom節(jié)點(diǎn)下的操作,那么如果是新增的節(jié)點(diǎn),新增的節(jié)點(diǎn)會有事件嗎?也就是說,一個新員工來了,他能收到快遞嗎?
<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變白,這么一個效果,然后點(diǎn)擊按鈕,可以向ul中添加一個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ù)制代碼這是一般的做法,但是你會發(fā)現(xiàn),新增的li是沒有事件的,說明添加子節(jié)點(diǎn)的時(shí)候,事件沒有一起添加進(jìn)去,這不是我們想要的結(jié)果,那怎么做呢?一般的解決方案會是這樣,將for循環(huán)用一個函數(shù)包起來,命名為mHover,如下:
// 將鼠標(biāo)移出移入包裝為一個函數(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í)際上無疑是又增加了一個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ù)制代碼另外一個思考的問題
現(xiàn)在給一個場景 ul > li > div > p,div占滿li,p占滿div,還是給ul綁定時(shí)間,需要判斷點(diǎn)擊的是不是li(假設(shè)li里面的結(jié)構(gòu)是不固定的),那么e.target就可能是p,也有可能是div,這種情況你會怎么處理呢?
<ul id="test"><li><p>11111111111</p></li><li><div>22222222</div></li><li><span>3333333333</span></li><li>4444444</li> </ul> 復(fù)制代碼如上列表,有4個li,里面的內(nèi)容各不相同,點(diǎn)擊li,event對象肯定是當(dāng)前點(diǎn)擊的對象,怎么指定到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)事件
在解答這個問題之前,先來看看事件的執(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é)就是先捕獲,后冒泡,捕獲是從上到下,冒泡是從下到上。(形象說法:捕獲像石頭沉入海底,冒泡像氣球冒出水面)
去掉 函數(shù) bubblingCallback 中的 e.stopPropagation()的注釋,則只會進(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ù)制代碼通過上面,我們可以得知, event.stopPropagation(),可以阻止 捕獲和冒泡階段當(dāng)前事件的進(jìn)一步傳播。
規(guī)則:
事件不同瀏覽器處理函數(shù)
- element.addEventListener(type, listener[, useCapture]); // IE6~8不支持(捕獲和冒泡通過useCapture,默認(rèn)false)
- element.attachEvent(’on’ + type, listener); // IE6~10,IE11不支持(只執(zhí)行冒泡事件)
- element[’on’ + type] = function(){} // 所有瀏覽器(默認(rèn)執(zhí)行冒泡事件)
W3C 中定義了 3個事件階段,依次是 捕獲階段、目標(biāo)階段、冒泡階段。事件對象按照上圖的傳播路徑依次完成這些階段。如果某個階段不支持或事件對象的傳播被終止,那么該階段就會被跳過。舉個例子,如果Event.bubbles屬性被設(shè)置為false,那么冒泡階段就會被跳過。如果Event.stopPropagation()在事件派發(fā)前被調(diào)用,那么所有的階段都會被跳過。
- 捕獲 階段:在事件對象到達(dá)事件目標(biāo)之前,事件對象必須從 window 經(jīng)過目標(biāo)的祖先節(jié)點(diǎn)傳播到事件目標(biāo)。這個階段被我們稱為捕獲階段,在這個階段注冊的事件監(jiān)聽器在事件到達(dá)其目標(biāo)前必須先處理事件。
- 目標(biāo) 階段:事件對象到達(dá)其事件目標(biāo),這個階段被我們稱之為目標(biāo)階段。一旦事件對象達(dá)到事件目標(biāo),該階段的事件監(jiān)聽器就要對它進(jìn)行處理。如果一個事件對象類型被標(biāo)志為不能冒泡。那么對應(yīng)的事件對象在到此階段時(shí)就會終止傳播。
- 冒泡 階段:事件對象以一個與捕獲階段相反的方向從事件目標(biāo)傳播經(jīng)過其祖先節(jié)點(diǎn)傳播到 window ,這個階段被稱之為冒泡階段,在此階段注冊的事件監(jiān)聽器會對應(yīng)的冒泡事件進(jìn)行處理
在一個事件完成了所有階段的傳播路徑后,它的Event.currentTarget會被設(shè)置為null并且Event.eventPhase會被設(shè)為0。Event的所有其他屬性都不會改變(包括指向事件目標(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ù)制代碼跨瀏覽器的事件對象:
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也是事件對象(Event)的一個方法,作用是阻止目標(biāo)元素的冒泡事件,但是會不阻止默認(rèn)行為。什么是冒泡事件?如在一個按鈕是綁定一個”click”事件,那么”click”事件會依次在它的父級元素中被觸發(fā) 。stopPropagation就是阻止目標(biāo)元素的事件冒泡到父級元素。如:
<div id='div' onclick='alert("div");'><ul onclick='alert("ul");'><li onclick='alert("li");'>test</li></ul> </div> 復(fù)制代碼單擊時(shí),會依次觸發(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它是事件對象(Event)的一個方法,作用是取消一個目標(biāo)元素的默認(rèn)行為。既然是說默認(rèn)行為,當(dāng)然是元素必須有默認(rèn)行為才能被取消,如果元素本身就沒有默認(rèn)行為,調(diào)用當(dāng)然就無效了。什么元素有默認(rèn)行為呢?如鏈接 <a>,提交按鈕 <input type="submit"> 等。當(dāng)Event 對象的 cancelable為false時(shí),表示沒有默認(rèn)行為,這時(shí)即使有默認(rèn)行為,調(diào)用preventDefault也是不會起作用的。
我們都知道,鏈接 <a> 的默認(rèn)動作就是跳轉(zhuǎn)到指定頁面,下面就以它為例,阻止它的跳轉(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只會阻止默認(rèn)行為,而是用jQuery的話則既阻止默認(rèn)行為又防止對象冒泡
下面這個使用原生js,只會阻止默認(rèn)行為,不會停止冒泡
<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) { //如果提供了事件對象,則這是一個非IE瀏覽器 if ( e && e.stopPropagation ) //因此它支持W3C的stopPropagation()方法 e.stopPropagation(); else //否則,我們需要使用IE的方式來取消事件冒泡 window.event.cancelBubble = true; } 復(fù)制代碼當(dāng)需要阻止默認(rèn)行為時(shí),可以使用
//阻止瀏覽器的默認(rèn)行為 function stopDefault( e ) { //阻止默認(rèn)瀏覽器動作(W3C) if ( e && e.preventDefault ) e.preventDefault(); //IE中阻止函數(shù)器默認(rèn)動作的方式 else window.event.returnValue = false; return false; } 復(fù)制代碼事件注意點(diǎn)
- event代表事件的狀態(tài),例如觸發(fā)event對象的元素、鼠標(biāo)的位置及狀態(tài)、按下的鍵等等;
- event對象只在事件發(fā)生的過程中才有效。
firefox里的event跟IE里的不同,IE里的是全局變量,隨時(shí)可用;firefox里的要用參數(shù)引導(dǎo)才能用,是運(yùn)行時(shí)的臨時(shí)變量。
在IE/Opera中是window.event,在Firefox中是event;而事件的對象,在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ù)制代碼查找 添加 刪除 替換 插入到某個節(jié)點(diǎn)的方法
// 查找節(jié)點(diǎn) document.getElementById('id'); // 通過id查找,返回唯一的節(jié)點(diǎn),如果有多個將會返回第一個,在IE6、7中有個bug,會返回name值相同的元素,所有要做一個兼容 document.getElementsByClassName('class'); // 通過class查找,返回節(jié)點(diǎn)數(shù)組 document.getElementsByTagName('div'); // 通過標(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)的某個兄弟節(jié)點(diǎn)的前面 var oP = createElement('p'); // 創(chuàng)建一個 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 的本地對象,內(nèi)置對象和宿主對象
內(nèi)部對象
js中的內(nèi)部對象包括Array、Boolean、Date、Function、Global、Math、Number、Object、RegExp、String以及各種錯誤類對象,包括Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError。
本地對象
Array、Boolean、Date、Function、Number、Object、RegExp、String、Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError 等 new 可以實(shí)例化
內(nèi)置對象
其中Global和Math這兩個對象又被稱為“內(nèi)置對象”,這兩個對象在腳本程序初始化時(shí)被創(chuàng)建,不必實(shí)例化這兩個對象。
宿主對象
宿主環(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 等
== 和 === 的不同
前者會自動轉(zhuǎn)換類型,而后者不會。
比較過程:
雙等號==:
三等號 === :
javaScript 的同源策略
同源策略的含義:腳本只能讀取和所屬文檔來源相同的窗口和文檔的屬性。
同源指的是主機(jī)名、協(xié)議和端口號的組合
同源策略帶來的限制
cookie、LocalStorage 和 IndexDB無法讀取
DOM 無法獲取
AJAX請求無法發(fā)送
主流跨域請求解決方案
1、JSONP 實(shí)現(xiàn)跨域
為了便于客戶端使用數(shù)據(jù),逐漸形成了一種非正式傳輸協(xié)議。人們把它稱作JSONP。該協(xié)議的一個要點(diǎn)就是允許用戶傳遞一個callback參數(shù)給服務(wù)端,然后服務(wù)端返回?cái)?shù)據(jù)時(shí)會將這個callback參數(shù)作為函數(shù)名來包裹住JSON數(shù)據(jù),這樣客戶端就可以隨意定制自己的函數(shù)來自動處理返回?cái)?shù)據(jù)了。
jsonp的核心是動態(tài)添加 script標(biāo)簽 來調(diào)用服務(wù)器提供的js腳本。
說說JSON和JSONP
JSONP實(shí)現(xiàn)原理?
JSONP的局限性
JSONP 方式,固然方便強(qiáng)大。但是他的局限性在于,它無法完成POST請求。即是我們將type改為post,在發(fā)送請求時(shí),依然會是以Get的方式。
2、CORS跨域
CORS原理
CORS(Cross-Origin-Resource Sharing,跨院資源共享)是一種允許多種資源(圖片,Css文字,Javascript等)在一個Web頁面請求域之外的另一個域的資源的機(jī)制。 跨域資源共享這種機(jī)制讓W(xué)eb應(yīng)用服務(wù)器支持跨站訪問控制,從而使得安全的進(jìn)行跨站數(shù)據(jù)傳輸成為了可能。
通過這種機(jī)制設(shè)置一系列的響應(yīng)頭,這些響應(yīng)頭允許瀏覽器與服務(wù)器進(jìn)行交流,實(shí)現(xiàn)資源共享。
各語言設(shè)置響應(yīng)頭的方法
CORS 解決方案相對于JSONP 更加靈活,而且支持POST請求,是跨域的根源性解決方案
3、代理層
JSONP 和CORS 是主流的 跨域問題 的解決方案。除了他們吶,還有一種解決方案,就是代理層。簡要說一下
JS 調(diào)用本源的后臺的方法(這樣就不存在跨域的問題),而通過后臺(任何具有網(wǎng)絡(luò)訪問功能的后臺語言,ASP.NET ,JAVA,PHP等)去跨域請求資源,而后將結(jié)果返回至前臺。
另外也可以看看
同源策略
JavaScript的同源策略 Redirect 1
編寫一個數(shù)組去重的方法
var arr = [1,1,3,4,2,4,7] => [1,2,4,2,7]
// 方法1 雙重循環(huán),逐個判斷數(shù)組中某個元素是否與其他元素相同,相同則去掉,并且索引減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ù)組 通過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ù)可以接受一個數(shù)組(或類似數(shù)組的對象)作為參數(shù),用來初始化。 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)
類型檢測
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
用于判斷一個變量是否某個對象的實(shí)例,或用于判斷一個變量是否某個對象的實(shí)例
instanceof 運(yùn)算符可以精確到是哪一種類型的引用,但 instanceof 也有一個缺陷就是對于直接賦值的數(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ù)的屬性 用來判別對創(chuàng)建實(shí)例對象的函數(shù)的引用,另外 constructor 可以被改寫
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就會返回函數(shù)名
Object.prototype.toString.call()
這個方法其實(shí)跟上面 toString()是一樣的,只是上面那個方法有可能會被重寫。所以用這個比較穩(wěn)妥,所有對象都包含有一個內(nèi)部屬性[[Class]] ,這個屬性不能直接訪問,但是我們可以通過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三個屬性的區(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é):
希望獲取到頁面中所有的 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)一個 Dom節(jié)點(diǎn)被點(diǎn)擊時(shí)候,我們希望能夠執(zhí)行一個函數(shù),應(yīng)該怎么做?
<!-- 直接在 DOM 里綁定事件:--> <div onlick = "test()"> 復(fù)制代碼// 在js 里面通過 onclick 綁定 xxx.onclick = test // 在事件里面添加綁定 addEventListener(xxx, 'click', test) 復(fù)制代碼Javascript 的事件流模型都有什么?
"事件冒泡":事件開始由最具體的元素接受,然后逐級向上傳播
“事件捕捉”:事件由最不具體的節(jié)點(diǎn)先接收,然后逐級向下,一直到最具體的
“DOM 事件流”:三個階段,事件捕捉,目標(biāo)階段。事件冒泡。
已知有字符串 foo=”get-element-by-id”,寫一個 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ù)制代碼為了保證頁面輸出安全,我們經(jīng)常需要對一些特殊的字符進(jìn)行轉(zhuǎn)義,請寫一個函數(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 基础篇)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国产掌机升级AMD锐龙7 7735U:锐
- 下一篇: IE兼容问题 动态生成的节点IE浏览器无