1.let、const/var
let 是更完美的var,不是全局變量,具有塊級函數作用域,大多數情況不會發生變量提升。
const 定義常量值,不能夠重新賦值,如果值是一個對象,可以改變對象裏邊的屬性值
-
var存在的問題
- var有作用域問題(會污染全局作用域)
- var可已重複聲明
- var會變量提升預解釋
- var不能定義常量
-
let、const特性
- let、const不可以重複聲明
- let、const不會聲明到全局作用域上
- let、const不會預解釋變量
- const做常量聲明(一般常量名用大寫)
- let聲明的變量具有塊級作用域
- let聲明的變量不能通過window.變量名進行訪問
- 形如for(let x..)的循環是每次迭代都爲x創建新的綁定
下面是 var 帶來的不合理場景
var a = []
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[5]() // 10
在上述代碼中,變量i是var聲明的,在全局範圍類都有效。所以每一次循環,新的i值都會覆蓋舊值,導致最後輸出都是10
而如果對循環使用let語句的情況,那麼每次迭代都是爲x創建新的綁定代碼如下
var a = []
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[5]() // 5
重溫一下閉包和立即函數兩種方法
- 閉包的方法
function showNum(i) {
return function () {
console.log(i)
}
}
var a = []
for (var i = 0; i < 5; i++) {
a[i] = showNum(i)
}
- 立即函數的方法
var a = []
for (var i = 0; i < 5; i++) {
a[i] = (function (i) {
return function () {
console.log(i)
}
})(i)
}
a[2]()
本節參考文章:前端面試之ES6篇
2.重排/重繪
在討論重排(迴流)與重繪之前,我們要知道:
- 瀏覽器使用流式佈局模型 (Flow Based Layout)。
- 瀏覽器會把
HTML
成DOM
,把CSS
解析成CSSOM
,DOM
和CSSOM
合併就產生了Render Tree
。 - 有了
RenderTree
,我們就知道了所有節點的樣式,然後計算他們在頁面上的大小和位置,最後把節點繪製到頁面上。 - 由於瀏覽器使用流式佈局,對
Render Tree
的計算通常只需要遍歷一次就可以完成,但table
及其內部元素除外,他們可能需要多次計算,通常要花3倍於同等元素的時間,這也是爲什麼要避免使用table
佈局的原因之一。
一句話:重排必將引起重繪,重繪不一定會引起重排。
重排 (Reflow)
當Render Tree中部分或全部元素的尺寸、結構、或某些屬性發生改變時,瀏覽器重新渲染部分或全部文檔的過程稱爲重排。
會導致重排的操作:
- 頁面首次渲染
- 瀏覽器窗口大小發生改變
- 元素尺寸或位置發生改變
- 元素內容變化(文字數量或圖片大小等等)
- 元素字體大小變化
- 添加或者刪除可見的DOM元素
- 激活CSS僞類(例如::hover)
- 查詢某些屬性或調用某些方法
一些常用且會導致重排的屬性和方法:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- scrollIntoView()、scrollIntoViewIfNeeded()
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
重繪 (Repaint)
當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:color、background-color、visibility等),瀏覽器會將新樣式賦予給元素並重新繪製它,這個過程稱爲重繪。
性能影響
迴流比重繪的代價要更高。
有時即使僅僅迴流一個單一的元素,它的父元素以及任何跟隨它的元素也會產生迴流。
現代瀏覽器會對頻繁的迴流或重繪操作進行優化:
瀏覽器會維護一個隊列,把所有引起迴流和重繪的操作放入隊列中,如果隊列中的任務數量或者時間間隔達到一個閾值的,瀏覽器就會將隊列清空,進行一次批處理,這樣可以把多次迴流和重繪變成一次。
當你訪問以下屬性或方法時,瀏覽器會立刻清空隊列:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- width、height
- getComputedStyle()
- getBoundingClientRect()
因爲隊列中可能會有影響到這些屬性或方法返回值的操作,即使你希望獲取的信息與隊列中操作引發的改變無關,瀏覽器也會強行清空隊列,確保你拿到的值是最精確的。
如何避免
-
CSS
- 避免使用table佈局。
- 儘可能在DOM樹的最末端改變class。
- 避免設置多層內聯樣式。
- 將動畫效果應用到position屬性爲absolute或fixed的元素上。
- 避免使用CSS表達式(例如:calc())。
-
JavaScript
- 避免頻繁操作樣式,最好一次性重寫style屬性,或者將樣式列表定義爲class並一次性更改class屬性。
- 避免頻繁操作DOM,創建一個documentFragment,在它上面應用所有DOM操作,最後再把它添加到文檔中。
- 也可以先爲元素設置display: none,操作結束後再把它顯示出來。因爲在display屬性爲none的元素上進行的DOM操作不會引發迴流和重繪。
- 避免頻繁讀取會引發迴流/重繪的屬性,如果確實需要多次使用,就用一個變量緩存起來。
- 對具有複雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及後續元素頻繁迴流。
本節參考文章:[瀏覽器的迴流與重繪 (Reflow & Repaint)](https://juejin.im/post/5a9923e9518825558251c96a)
3.函數節流(throttle)與函數去抖(debounce)
- Debounce:一部電梯停在某一個樓層,當有一個人進來後,20秒後自動關門,這20秒的等待期間,又一個人按了電梯進來,這20秒又重新計算,直到電梯關門那一刻纔算是響應了事件。
- Throttle:好比一臺自動的飲料機,按拿鐵按鈕,在出飲料的過程中,不管按多少這個按鈕,都不會連續出飲料,中間按鈕的響應會被忽略,必須要等這一杯的容量全部出完之後,再按拿鐵按鈕纔會出下一杯。
4.強緩存/協商緩存
瀏覽器緩存分爲強緩存和協商緩存,優先讀取強制緩存。
當客戶端請求某個資源時,獲取緩存的流程如下:
- 先根據這個資源的一些 http header 判斷它是否命中強緩存,如果命中,則直接從本地獲取緩存資源,不會發請求到服務器;
- 當強緩存沒有命中時,客戶端會發送請求到服務器,服務器通過另一些request header驗證這個資源是否命中協商緩存,稱爲http再驗證,如果命中,服務器將請求返回,但不返回資源,而是告訴客戶端直接從緩存中獲取,客戶端收到返回後就會從緩存中獲取資源;
- 強緩存和協商緩存共同之處在於,如果命中緩存,服務器都不會返回資源;
- 區別是,強緩存不對發送請求到服務器,但協商緩存會。
- 當協商緩存也沒命中時,服務器就會將資源發送回客戶端。
- 當 ctrl+f5 強制刷新網頁時,直接從服務器加載,跳過強緩存和協商緩存;
- 當 f5 刷新網頁時,跳過強緩存,但是會檢查協商緩存;
強緩存
- Expires(該字段是 http1.0 時的規範,值爲一個絕對時間的 GMT 格式的時間字符串,代表緩存資源的過期時間)
- Cache-Control:max-age(該字段是 http1.1 的規範,強緩存利用其 max-age 值來判斷緩存資源的最大生命週期,它的值單位爲秒)
協商緩存
協商緩存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】這兩對Header來管理的
- Last-Modified,If-Modified-Since
Last-Modified 表示本地文件最後修改日期,瀏覽器會在request header加上If-Modified-Since(上次返回的Last-Modified的值),詢問服務器在該日期後資源是否有更新,有更新的話就會將新的資源發送回來
但是如果在本地打開緩存文件,就會造成 Last-Modified 被修改,所以在 HTTP / 1.1 出現了 ETag
- ETag、If-None-Match
Etag就像一個指紋,資源變化都會導致ETag變化,跟最後修改時間沒有關係,ETag可以保證每一個資源是唯一的
If-None-Match的header會將上次返回的Etag發送給服務器,詢問該資源的Etag是否有更新,有變動就會發送新的資源回來
ETag的優先級比Last-Modified更高
- 一些文件也許會週期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認爲這個文件被修改了,而重新GET;
- 某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
- 某些服務器不能精確的得到文件的最後修改時間。
推薦必讀:http協商緩存VS強緩存、瀏覽器緩存知識小結及應用、緩存(二)——瀏覽器緩存機制:強緩存、協商緩存
5.原始類型 / 引用類型
JavaScript中的內存分爲棧內存和堆內存。棧內存和堆內存區別:棧內存運行效率比堆內存高,空間相對堆內存來說較小。
區別:
- 值類型屬於不可變類型, 由於具有固定長度大小, 其地址和具體內容都存在與棧內存中
- 而引用類型屬於可變類型, 一個對象可以賦予多個屬性及值,屬性值又可以爲一個新的引用對象。其地址存在棧內存,其具體內容存在堆內存中。
6.cookie/token
- token和cookie一樣都是首次登陸時,由服務器下發,都是當交互時進行驗證的功能,作用都是爲無狀態的HTTP提供的持久機制。
- token存在哪兒都行,localstorage或者cookie。
-
token和cookie舉例,token就是說你告訴我你是誰就可以。
cookie 舉例:服務員看你的身份證,給你一個編號,以後,進行任何操作,都出示編號後服務員去看查你是誰。
token 舉例:直接給服務員看自己身份證,服務器不需要去查看你是誰,不需要保存你的會話。
- 當用戶logout的時候,cookie和服務器的session都會註銷;但是當logout時候token只是註銷瀏覽器信息,不查庫。
- token優勢在於,token由於服務器端不存儲會話,所以可擴展性強,token還可用於APP中。
小結:
Token 完全由應用管理,所以它可以避開同源策略
Token 可以避免 CSRF 攻擊
Token 可以是無狀態的,可以在多個服務間共享
如果你的用戶數據可能需要和第三方共享,或者允許第三方調用 API 接口,用 Token,如果之上自己的那就無所謂了。
本節參考文章:cookie和token的五點區別
推薦必讀:前後端常見的幾種鑑權方式
7.cookie/sessionStorage/localStorage
1.cookie
cookie分爲cookie機制和session機制(請大神判斷正確性)
Session: 是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據可以保存在集羣、數據庫、文件中,通過在服務器端記錄信息確定用戶身份
Cookie: 是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式,通過在客戶端記錄信息確定用戶身份
如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那麼Session機制就是通過檢查服務器上的“客戶明細表”來確認客戶身份。Session相當於程序在服務器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。
cookie機制
cookie可以通過設置domain屬性值,可以不同二級域名下共享cookie,而Storage不可以,比如http://image.baidu.com
的cookie http://map.baidu.com
是可以訪問的,前提是Cookie的domain設置爲.http://baidu.com
,而Storage是不可以的
session機制
當程序需要爲某個客戶端的請求創建一個session時,
- 服務器首先檢查這個客戶端的請求裏是否已包含了一個session標識---稱爲session id,
- 如果已包含則說明以前已經爲此客戶端創建過session,服務器就按照session id把這個session檢索出來使用(檢索不到,會新建一個),
- 如果客戶端請求不包含session id,則爲此客戶端創建一個session並且生成一個與此session相關聯的session id,session id的值應該是一個既不會重複,又不容易被找到規律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。
比較
- session 在服務器端,cookie 在客戶端(瀏覽器)
- session保存在服務器,客戶端不知道其中的信息;反之,cookie保存在客戶端,服務器能夠知道其中的信息
- session會在一定時間內保存在服務器上,當訪問增多,會比較佔用你服務器的性能,考慮到減輕服務器性能方面,應當使用cookie
- session中保存的是對象,cookie中保存的是字符串
- cookie不是很安全,別人可以分析存放在本地的cookie並進行cookie欺騙,考慮到安全應當使用session。用戶驗證這種場合一般會用 session
- 用戶驗證這種場合一般會用 session,因此,維持一個會話的核心就是客戶端的唯一標識,即 session id
- session 的運行依賴 session id,而 session id 是存在 cookie 中的,也就是說,如果瀏覽器禁用了 cookie ,同時 session 也會失效(但是可以通過其它方式實現,比如在 url 中傳遞 session_id)
- session不能區分路徑,同一個用戶在訪問一個網站期間,所有的session在任何一個地方都可以訪問到,而cookie中如果設置了路徑參數,那麼同一個網站中不同路徑下的cookie互相是訪問不到的
JavaScript原生的用法。
Cookie 以名/值對形式存儲
例如username=John Doe,這裏的數據是string類型,如要是其他格式注意進行格式轉換。
JavaScript 可以使用 document.cookie 屬性來創建 、讀取、及刪除 cookie。JavaScript 中,創建 cookie 如下所示:
document.cookie="username=John Doe";
您還可以爲 cookie 添加一個過期時間(以 UTC 或 GMT 時間)。默認情況下,cookie 在瀏覽器關閉時刪除:
document.cookie="username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT";
您可以使用 path 參數告訴瀏覽器 cookie 的路徑。默認情況下,cookie 屬於當前頁面。
document.cookie="username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT; path=/";
設置cookie
function setCookie(cname,cvalue,exdays){
var SetTime = new Date(); //設置過期時間
SetTime.setTime(SetTime.getTime()+(exdays*24*60*60*1000)); //設置過期時間
var expires = "expires="+SetTime.toGMTString(); //設置過期時間
document.cookie = cname + "=" + cvalue + "; " + expires; //創建一個cookie
}
讀取cookie
function getCookie(c_name){
if (document.cookie.length>0) {
c_start=document.cookie.indexOf(c_name + "=")
if (c_start!=-1){
c_start=c_start + c_name.length+1
c_end=document.cookie.indexOf(";",c_start)
if (c_end==-1) c_end=document.cookie.length
return unescape(document.cookie.substring(c_start,c_end))
}
}
return ""
}
刪除cookie
將cookie的有效時間改成昨天。
使用jquery.cookies.2.2.0.min.js插件
添加/修改cookie並設定過期時間:
$.cookies.set('cookie_id', 'cookie_value', { hoursToLive: 10 });
這裏設置的是過期時間是10小時, 還可以這樣設置過期時間:
expireDate = new Date();
expireDate.setTime( expireDate.getTime() + ( 10 * 60 * 60 * 1000 ) );
$.cookies.set('cookie_id', 'cookie_value', {expiresAt:expireDate});
獲取cookie
$.cookies.get('cookie_id');
刪除cookie
$.cookies.del('cookie_id');
2.Storage:localStorage、sessionStorage
大小:官方建議是5M存儲空間
類型:只能操作字符串,在存儲之前應該使用JSON.stringfy()方法先進行一步安全轉換字符串,取值時再用JSON.parse()方法再轉換一次
存儲的內容: 數組,圖片,json,樣式,腳本。。。(只要是能序列化成字符串的內容都可以存儲)
區別:sessionStorage將數據臨時存儲在session中,瀏覽器關閉,數據隨之消失,localStorage將數據存儲在本地,理論上來說數據永遠不會消失,除非人爲刪除
注意:數據是明文存儲,毫無隱私性可言,絕對不能用於存儲
基礎操作API
保存數據
localStorage.setItem( key, value );
sessionStorage.setItem(keyName,value); // 將value存儲到key字段中
//或者
sessionStorage.keyName='value';
讀取數據
localStorage.getItem( key );
sessionStorage.getItem(keyName); //獲取指定key的本地存儲的值
//或者
var keyName=sessionStorage.key;
刪除單個數據
localStorage.removeItem( key );
sessionStorage.removeItem( key );
刪除全部數據
localStorage.clear( );
sessionStorage.clear( );
獲取索引的key
localStorage.key( index );
sessionStorage.key( index );
監聽storage事件
可以通過監聽 window 對象的 storage 事件並指定其事件處理函數,當頁面中對 localStorage 或 sessionStorage 進行修改時,則會觸發對應的處理函數
window.addEventListener('storage',function(e){
console.log('key='+e.key+',oldValue='+e.oldValue+',newValue='+e.newValue);
})
localstorage是瀏覽器多個標籤共用的存儲空間,可以用來實現多標籤之間的通信(ps:session是會話級的存儲空間,每個標籤頁都是單獨的
觸發事件的時間對象(e 參數值)有幾個屬性:
key : 鍵值。
oldValue : 被修改前的值。
newValue : 被修改後的值。
url : 頁面url。
storageArea : 被修改的 storage 對象。
3.對比
共同點:都是保存在瀏覽器端、且同源的,都受同源策略的制約。
區別:
- cookie數據始終在同源的http請求中攜帶(即使不需要),即cookie在瀏覽器和服務器間來回傳遞,而sessionStorage和localStorage不會自動把數據發送給服務器,僅在本地保存。cookie數據還有路徑(path)的概念,可以限制cookie只屬於某個路徑下
- 存儲大小限制也不同,cookie數據不能超過4K,同時因爲每次http請求都會攜帶cookie、所以cookie只適合保存很小的數據,如會話標識。sessionStorage和localStorage雖然也有存儲大小的限制,但比cookie大得多,可以達到5M或更大
- 數據有效期不同,sessionStorage:僅在當前瀏覽器窗口關閉之前有效;localStorage:始終有效,窗口或瀏覽器關閉也一直保存,因此用作持久數據;cookie:只在設置的cookie過期時間之前有效,即使窗口關閉或瀏覽器關閉
- 作用域不同,sessionStorage不在不同的瀏覽器窗口中共享,即使是同一個頁面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
本節參考文章:緩存(三)——數據存儲...
其他閱讀:關於Cookie、session和Web Storage
8.js事件
1.事件
事件指可以被 JavaScript 偵測到的行爲。即鼠標點擊、頁面或圖像載入、鼠標懸浮於頁面的某個熱點之上、在表單中選取輸入框、確認表單、鍵盤按鍵等操作。
事件通常與函數配合使用,當事件發生時函數纔會執行。
事件名稱:click/mouseover/blur("不帶on")
事件處理程序(事件偵聽器):響應某個事件的函數,名稱爲:onclick/onmouseove/onblur,例如<button onclick="alert('hello')"></button>
2.DOM事件模型:冒泡和捕獲
冒泡:往上
捕獲:向下
3.事件流
事件流指從頁面中接收事件的順序,也可理解爲事件在頁面中傳播的順序。
DOM2級事件規定的事件流包括三個階段:
(1)事件捕獲階段(2)處於目標階段(3)事件冒泡階段。
當事件發生時,最先得到通知的是window,然後是document,由上至下逐級依次而入,直到真正觸發事件的那個元素(目標元素)爲止,這個過程就是捕獲。
接下來,事件會從目標元素開始起泡,由下至上逐級依次傳播,直到window對象爲止,這個過程就是冒泡。
所以捕獲比冒泡先執行。
希望註冊在DOM元素上的事件處理程序在捕獲階段還是在冒泡階段觸發,取決於 addEventListener() 方法的第三個參數爲 true 還是 false 。
其中DOM3級事件在DOM2的基礎之上添加了更多的事件類型。
描述DOM事件捕獲的具體流程
window-->document-->html(document.documentElement)-->body(document.body)...
4.DOM級別/DOM事件
DOM級別一共可以分爲4個級別:DOM0級,DOM1級,DOM2級和DOM3級,
而DOM事件分爲3個級別:DOM0級事件處理,DOM2級事件處理和DOM3級事件處理。
其中1級DOM標準中並沒有定義事件相關的內容,所以沒有所謂的1級DOM事件模型。
DOM0:element.onclick = function(){}
DOM2:element.addEventlistenter('click',function(){},flase)
DOM3:element.addEventlistenter('keyup',function(){},flase)
-
HTML事件處理程序
<button onclick="alert('hello')"></button> <button onclick="doSomething()"></button> <button onclick="try{doSomething();}catch(err){}"></button>
-
DOM0級事件處理程序
btn.onclick=function(){ alert("hello"); }
btn.onclick = null;//來刪除指定的事件處理程序。
如果我們嘗試給事件添加兩個事件,如:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.onclick=function(){ alert("hello"); } btn.onclick=function(){ alert("hello again"); } </script>
輸出,hello again,很明顯,第一個事件函數被第二個事件函數給覆蓋掉了, 所以,DOM0級事件處理程序不能添加多個,也不能控制事件流到底是捕獲還是冒泡。
-
DOM2級事件處理程序
addEventListener() ---添加事件偵聽器
函數均有3個參數,
第一個參數是要處理的事件名(不帶on前綴的纔是事件名)
第二個參數是作爲事件處理程序的函數
第三個參數是一個boolean值,默認false表示使用冒泡機制,true表示捕獲機制。<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.addEventListener('click',hello,false); btn.addEventListener('click',helloagain,false); function hello(){ alert("hello"); } function helloagain(){ alert("hello again"); } </script>
removeEventListener() //刪除事件偵聽器
可以綁定多個事件處理程序,但是注意,如果定義了一摸一樣時監聽方法,是會發生覆蓋的,即同樣的事件和事件流機制下相同方法只會觸發一次,事件觸發的順序是添加的順序
```
// 爲了兼容IE瀏覽器和標準的瀏覽器,我們需要編寫通用的方法來處理:
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["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;
}
}
};
```
5.事件對象
事件對象是用來記錄一些事件發生時的相關信息的對象,但事件對象只有事件發生時纔會產生,並且只能是事件處理函數內部訪問,在所有事件處理函數運行結束後,事件對象就被銷燬!
//currentTarget、eventPhase 一個例子:
<button id="btn">點擊</button>
<script>
var btn=document.getElementById("btn");
btn.ddEventListener('click', doCurrent, true);
// 判斷事件的屬性
function doCurrent(event) {
//獲取當前事件觸發的div
var target = event.currentTarget;
//通過判斷事件的event.eventPhase屬性返回事件傳播的當前階段
//1:捕獲階段、2:正常事件派發和3:起泡階段。
//得到當前階段和id值並輸出
var msg = (event.eventPhase == 1 ? '捕獲階段:' : '冒泡階段:')+ target.attributes["id"].value;;
console.log(msg);
}
</script>
當然,事件對象也存在一定的兼容性問題,在IE8及以前本版之中,通過設置屬性註冊事件處理程序時,調用的時候並未傳遞事件對象,需要通過全局對象window.event來獲取。解決方法如下:
function getEvent(event) {
event = event || window.event;
}
在IE瀏覽器上面是event事件是沒有preventDefault()這個屬性的,所以在IE上,我們需要設置的屬性是returnValue
window.event.returnValue=false
stopPropagation()也是,所以需要設置cancelBubble,cancelBubble是IE事件對象的一個屬性,設置這個屬性爲true能阻止事件進一步傳播。
event.cancelBubble=true
常見屬性 | 解析 |
---|---|
event.preventDefault() | 阻止默認行爲 |
event.stopPropagation() | 阻止冒泡。使用了stopPropagation()之後,事件就不能進一步傳播了,同時如果是同一個div上有捕獲和冒泡兩種事件監聽,在捕獲階段傳播阻止後冒泡階段事件監聽也不會觸發。 |
event.stopImmediatePropagation() | 使用了stopImmediatePropagation()之後,綁定的後續事件監聽都會忽略。 |
event.currentTarget | 當前綁定的事件 |
event.target | 事件代理時 點擊的元素 |
關於捕獲和冒泡:理解 addEventListener、捕獲和冒泡
6.自定義事件
jq
// 添加一個適當的事件監聽器
$('#foo').on('custom', function(event, param1, param2) {
alert(param1 + "\n" + param2);
});
$('#foo').trigger('custom', ['Custom', 'Event']);
原生Event:
var eve = new Event('custome')
element.addEventListenter('custome',function(){
console.log('custome')
})
element.dispatchEvent(eve)
原生CustomEvent
// 添加一個適當的事件監聽器
obj.addEventListener("custom", function(e) {
console.log(JSON.stringify(e.detail));
})
// 創建並分發事件
var event = new CustomEvent("custom", {"detail":{"Custom":true}});
obj.dispatchEvent(event)
本節參考文章:一個能拖動,能調整大小,能更新bind值的vue指令-vuedragx
7.事件委託(代理)
事件委託就是利用事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。
案例一:
<button id="btnAdd">添加</button>
<ul id="ulList">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var btnAdd = document.getElementById('btnAdd');
var ulList = document.getElementById('ulList');
var list = document.getElementsByTagName('li');
var num = 3;
btnAdd.onclick = function () {
num++;
var li = document.createElement('li');
li.innerHTML = num;
ulList.appendChild(li)
}
for (i = 0; i < list.length; i++) {
list[i].onclick = function(){
alert(this.innerHTML);
}
}
</script>
//例子說明,我們爲ul添加新的li,
//其中對li標籤元素綁定了click事件,
//但是發現,後增加的元素沒有辦法觸發我們的click事件。
解決方法:事件委託
<button id="btnAdd">添加</button>
<ul id="ulList">
<li class="class-1">1</li>
<li class="class-1">2</li>
<li class="class-1">3</li>
</ul>
<script>
var btnAdd = document.getElementById('btnAdd');
var ulList = document.getElementById('ulList');
var num = 3;
ulList.onclick = function(event){
var event = event || window.event;
var target = event.target || event.srcElement;
// if (target.matches('li.class-1'))
//#ulList 元素下的 li 元素(並且它的 class 爲 class-1)
if(target.nodeName.toLowerCase() == 'li'){
alert(target.innerHTML);
}
}
btnAdd.onclick = function () {
num++;
var li = document.createElement('li');
li.innerHTML = num;
ulList.appendChild(li);
}
</script>
案例二:
點擊“添加”按鈕添加一個按鈕,點擊添加的按鈕移除這個按鈕
<div class="wrap" id="wrap">
<div class="btn" data-type="btn" data-feat="add">添加</div>
<div class="btn" data-type="btn" data-feat="delete">繪畫</div>
<div class="btn" data-type="btn" data-feat="delete">散步</div>
<div class="btn" data-type="btn" data-feat="delete">靜坐</div>
</div>
document.getElementById('wrap').addEventListener('click', function(e){
var target = e.target;
while(target !== this){
var type = target.dataset.type;
if(type == 'btn'){
var feat = target.dataset.feat;
switch(feat){
case 'add':
this.innerHTML += '<div class="btn" data-type="btn" data-feat="delete">靜坐</div>'
return;
case 'delete':
target.parentNode.removeChild(target);
return;
}
}
target = target.parentNode;
}
}, false);
適合用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress。
推薦閱讀:JavaScript 事件委託詳解
本節參考文章:前端小知識--JavaScript事件流
9.link / @import
兩者都是外部引用 CSS 的方式,但是存在一定的區別:
- link是XHTML標籤,除了能夠加載CSS,還可以定義RSS等其他事務;而@import屬於CSS範疇,只可以加載CSS。
- link引用CSS時,在頁面載入時同時加載;@import需要頁面完全載入以後再加載。
- link是XHTML標籤,無兼容問題;@import則是在CSS2.1提出的,低版本的瀏覽器不支持。
- link方式的樣式的權重 高於@import的權重.
- link支持使用Javascript控制DOM改變樣式;而@import不支持。
本節參考文章:前端面試題-url、href、src
10.異步編程的實現方式
- 1.回調函數
優點:簡單、容易理解
缺點:不利於維護,代碼耦合高 - 2.事件監聽(採用時間驅動模式,取決於某個事件是否發生):
優點:容易理解,可以綁定多個事件,每個事件可以指定多個回調函數
缺點:事件驅動型,流程不夠清晰 - 3.發佈/訂閱(觀察者模式)
類似於事件監聽,但是可以通過‘消息中心’,瞭解現在有多少發佈者,多少訂閱者 - 4.Promise對象
優點:可以利用then方法,進行鏈式寫法;可以書寫錯誤時的回調函數;
缺點:編寫和理解,相對比較難 - 5.Generator函數
優點:函數體內外的數據交換、錯誤處理機制
缺點:流程管理不方便 - 6.async函數
優點:內置執行器、更好的語義、更廣的適用性、返回的是Promise、結構清晰。
缺點:錯誤處理機制
11.documen.write/ innerHTML的區別
document.write只能重繪整個頁面
innerHTML可以重繪頁面的一部分
12.isPrototypeOf()/instanceof
isPrototypeOf() 與 instanceof 運算符不同。
- 在表達式 "object instanceof AFunction"中,object 的原型鏈是針對 AFunction.prototype 進行檢查的,而不是針對 AFunction 本身。
- isPrototypeOf() 方法允許你檢查一個對象是否存在於另一個對象的原型鏈上。
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
//isPrototypeOf
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
if (Foo.prototype.isPrototypeOf(baz)) {
// do something safe
}
//instanceof
console.log(baz instanceof Baz); // true
console.log(baz instanceof Bar); // true
console.log(baz instanceof Foo); // true
console.log(baz instanceof Object); // true
var obj1 = {
name: 'esw'
}
var obj2 = Object.create(obj1)
// isPrototypeOf()方法
Object.prototype.isPrototypeOf(obj1) // true
obj1.isPrototypeOf(obj2) // true
Object.prototype.isPrototypeOf(obj2) // true
13.constructor、__proto__與prototype
在javascript中我們每創建一個對象,該對象都會獲得一個__proto__
屬性(該屬性是個對象),該屬性指向創建該對象的構造函數的原型
即prototype
,同時__proto__
對象有一個constructor
屬性指向該構造函數。這裏我們需要注意的是隻有函數纔有prototype
,每個對象(函數也是對象)都有__proto__
,Object
本身是個構造函數。舉例來說:
var obj = new Object()
// 也可以使用對象字面量創建,但使用Object.create()情況會不一樣
// Object本身是個構造函數
Object instanceof Function // true
obj.__proto__ === Object.prototype // true
obj.__proto__.constructor === Object // true
// 我們一般習慣這樣寫
obj.constructor === Object // true
當我們訪問obj.constructor
的時候,obj
本身是沒有constructor
屬性的,但屬性訪問會沿着__proto__
向上查找,即在obj.__proto__
裏面尋找constructor
屬性,如果找到了就返回值,如果未找到則繼續向上查找直到obj.__proto__.__proto__...(__proto__) === null
爲止,沒有找到則返回undefined。這樣由__proto__
構成的一條查找屬性的線稱爲‘原型鏈’。
本節參考文章:重新認識javascript對象(三)——原型及原型鏈、一篇文章帶你進一步瞭解object屬性
14.淺拷貝/深拷貝
1.淺拷貝只能複製值類型的屬性。對於引用類型的屬性,複製前後的兩個對象指向同一內存地址,操作其中一個對象的引用類型屬性,另一個對象也會相應發生改變;也就是說只有改變值類型的屬性兩個對象纔不會相互影響。
2.深拷貝不僅可以複製值類型的屬性,還可以複製引用類型的屬性,無論兩個對象怎麼改變都不會相互影響。
淺複製
var obj = {
a : 1,
b: {
c: 2
}
}
// 淺複製
function lowerClone(obj){
var newObj=obj.constructor === Array ? [] : {};
for(var i in obj){
newObj[i]=obj[i]
}
return newObj;
}
var objClone = lowerClone(obj)
objClone.a // 1
obj.a // 1
objClone.a = 100
// 改變複製對象的值類型屬性,值類型屬性的值不變
obj.a // 1
objClone.b.c = 200
// 改變複製對象的引用類型的屬性,引用類型的屬性值改變
obj.b.c //200
深複製
var obj = {
a : 1,
b: {
c: 2
}
}
function deepClone(obj){
if( typeof obj != 'object'){
return obj;
}
var newObj=obj.constructor === Array ? [] : {};
for(var i in obj){
newObj[i]=deepClone(obj[i]);
}
return newObj;
}
var objClone = deepClone(obj)
objClone.a // 1
obj.a // 1
objClone.a = 100
// 改變複製對象的值類型屬性,值類型屬性的值不變
obj.a // 1
objClone.b.c = 200
// 改變複製對象的引用類型的屬性,引用類型的屬性值不變
obj.b.c // 2
本節參考文章:javascript淺複製與深複製
15.apply/call/bind
apply 、 call 、bind 三者都是用來改變函數的this對象的指向的;
apply 、 call 、bind 三者第一個參數都是this要指向的對象,也就是想指定的上下文;
apply 、 call 、bind 三者都可以利用後續參數傳參;
bind是返回對應函數,便於稍後調用;apply 、call 則是立即調用 。
call apply 的區別是他們指定參數的方式不同。
案例
function fn(a,b){
console.log(this);
console.log(a);
console.log(b);
}
// bind(this,args...)
bf = fn.bind("Bind this",10); // 沒有任何輸出,也就是說沒有執行這個函數
bf(); // "Bind this",10,undefined
bf(20);// “Bind this”,10,20
// 原函數不受影響
fn(1,2); //window, 1,2
bf2 = fn.bind("Bind this",1,2);
bf2(); // "Bind this",1,2
// call(this,args...)
fn.call("Call this",1) // "Call this",1,undefined
fn.call("Call this",1,2) // "Call this",1,2
// apply(this,[args])
fn.apply("Apply this",[1]) // "Apply this",1,undefined
fn.apply("Apply this",[1,2]) // "Apply this",1,2