前端基本功-常見概念(二)

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.重排/重繪

在討論重排(迴流)與重繪之前,我們要知道:

  1. 瀏覽器使用流式佈局模型 (Flow Based Layout)。
  2. 瀏覽器會把HTMLDOM,把CSS解析成CSSOMDOMCSSOM合併就產生了Render Tree
  3. 有了RenderTree,我們就知道了所有節點的樣式,然後計算他們在頁面上的大小和位置,最後把節點繪製到頁面上。
  4. 由於瀏覽器使用流式佈局,對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 刷新網頁時,跳過強緩存,但是會檢查協商緩存;

clipboard.png

強緩存

  • 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

  1. token和cookie一樣都是首次登陸時,由服務器下發,都是當交互時進行驗證的功能,作用都是爲無狀態的HTTP提供的持久機制。
  2. token存在哪兒都行,localstorage或者cookie。
  3. token和cookie舉例,token就是說你告訴我你是誰就可以。

    cookie 舉例:服務員看你的身份證,給你一個編號,以後,進行任何操作,都出示編號後服務員去看查你是誰。

    token 舉例:直接給服務員看自己身份證,服務器不需要去查看你是誰,不需要保存你的會話。

  4. 當用戶logout的時候,cookie和服務器的session都會註銷;但是當logout時候token只是註銷瀏覽器信息,不查庫。
  5. 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 的方式,但是存在一定的區別:

  1. link是XHTML標籤,除了能夠加載CSS,還可以定義RSS等其他事務;而@import屬於CSS範疇,只可以加載CSS。
  2. link引用CSS時,在頁面載入時同時加載;@import需要頁面完全載入以後再加載。
  3. link是XHTML標籤,無兼容問題;@import則是在CSS2.1提出的,低版本的瀏覽器不支持。
  4. link方式的樣式的權重 高於@import的權重.
  5. 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章