成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

「前端面試題系列7」Javascript 中的事件機(jī)制(從原生到框架)

Tony / 1039人閱讀

摘要:要想注冊(cè)過(guò)的事件能夠被解除,必須將回調(diào)函數(shù)保存起來(lái),否則無(wú)法解除。當(dāng)用阻止瀏覽器的默認(rèn)行為時(shí),會(huì)做下面這件事停止回調(diào)函數(shù)執(zhí)行并立即返回。

前言

這是前端面試題系列的第 7 篇,你可能錯(cuò)過(guò)了前面的篇章,可以在這里找到:

理解函數(shù)的柯里化

ES6 中箭頭函數(shù)的用法

this 的原理以及用法

偽類(lèi)與偽元素的區(qū)別及實(shí)戰(zhàn)

如何實(shí)現(xiàn)一個(gè)圣杯布局?

今日頭條 面試題和思路解析

最近,小伙伴L(zhǎng) 在溫習(xí) 《JavaScript高級(jí)程序設(shè)計(jì)》中的 事件 這一章節(jié)時(shí),產(chǎn)生了困惑。

他問(wèn)了我這樣幾個(gè)問(wèn)題:

了解事件流的順序,對(duì)日常的工作有什么幫助么?

在 vue 的文檔中,有一個(gè)修飾符 native ,把它用 . 的形式 連結(jié)在事件之后,就可以監(jiān)聽(tīng)原生事件了。它的背后有什么原理?

事件的 event 對(duì)象中,有好多的屬性和方法,該如何使用?

瀏覽器中的事件機(jī)制,也經(jīng)常在面試中被提及。所以這回,我們共同探討了這些問(wèn)題,并最終整理成文,希望幫到有需要的同學(xué)。

事件流的概念

先從概念說(shuō)起,DOM 事件流分為三個(gè)階段:捕獲階段、目標(biāo)階段、冒泡階段。先調(diào)用捕獲階段的處理函數(shù),其次調(diào)用目標(biāo)階段的處理函數(shù),最后調(diào)用冒泡階段的處理函數(shù)。

網(wǎng)景公司提出了 事件捕獲 的事件流。這就好比采礦的小游戲,每次都會(huì)從地面開(kāi)始一路往下,拋出抓斗,捕獲礦石。在上圖中就是,某個(gè) div 元素觸發(fā)了某個(gè)事件,最先得到通知的是 window,然后是 document,依次往下,直到真正觸發(fā)事件的那個(gè)目標(biāo)元素 div 為止。

事件冒泡 則是由微軟提出的,與之順序相反。還是剛才的采礦小游戲,命中目標(biāo)后,抓斗再沿路收回,直到冒出地面。在上圖中就是,事件會(huì)從目標(biāo)元素 div 開(kāi)始依次往上,直到 window 對(duì)象為止。

w3c 為了制定統(tǒng)一的標(biāo)準(zhǔn),采取了折中的方式:先捕獲在冒泡。同一個(gè) DOM 元素可以注冊(cè)多個(gè)同類(lèi)型的事件,通過(guò) addEventListener 和 removeEventListener 進(jìn)行管理。addEventListener 的第三個(gè)參數(shù),就是為了捕獲和冒泡準(zhǔn)備的。

注冊(cè)事件(addEventListener) 有三個(gè)參數(shù),分別為:"事件名稱(chēng)", "事件回調(diào)", "捕獲/冒泡"(布爾型,true代表捕獲事件,false代表冒泡事件)。

target.addEventListener(type, listener[, useCapture]);

type 表示事件類(lèi)型的字符串。

listener 是一個(gè)實(shí)現(xiàn)了 EventListener 接口的對(duì)象,或者是一個(gè)函數(shù)。當(dāng)所監(jiān)聽(tīng)的事件類(lèi)型觸發(fā)時(shí),會(huì)接收到一個(gè)事件通知對(duì)象(實(shí)現(xiàn)了 Event 接口的對(duì)象)。

capture 表示 listener 會(huì)在該類(lèi)型的事件捕獲階段,傳播到該 EventTarget 時(shí)觸發(fā),它是一個(gè) Boolean 值。

解除事件(removeEventListener) 也有三個(gè)參數(shù),分別為:"事件名稱(chēng)", "事件回調(diào)", "捕獲/冒泡"(Boolean 值,這個(gè)必須和注冊(cè)事件時(shí)的類(lèi)型一致)。

target.removeEventListener(type, listener[, useCapture]);

要想注冊(cè)過(guò)的事件能夠被解除,必須將回調(diào)函數(shù)保存起來(lái),否則無(wú)法解除。例如這樣:

const btn = document.getElementById("test");

//將回調(diào)存儲(chǔ)在變量中
const fn = function(e){
    alert("ok");
};

//綁定
btn.addEventListener("click", fn, false);

//解除
btn.removeEventListener("click", fn, false);
事件捕獲和冒泡的5個(gè)注意點(diǎn)

當(dāng)有多層交互嵌套時(shí),事件捕獲和冒泡的先后順序,似乎不是那么好理解。接下來(lái),將分 5 種情況討論它們的順序,以及如何規(guī)避意外情況的發(fā)生。

1.在外層 div 注冊(cè)事件,點(diǎn)擊內(nèi)層 div 來(lái)觸發(fā)事件時(shí),捕獲事件總是要比冒泡事件先觸發(fā)(與代碼順序無(wú)關(guān))

假設(shè),有這樣的 html 結(jié)構(gòu):

然后,我們?cè)谕鈱?div 上注冊(cè)兩個(gè) click 事件,分別是捕獲事件和冒泡事件,代碼如下:

const btn = document.getElementById("test");
 
//捕獲事件
btn.addEventListener("click", function(e){
    alert("capture is ok");
}, true);
 
//冒泡事件
btn.addEventListener("click", function(e){
    alert("bubble is ok");
}, false);

點(diǎn)擊內(nèi)層的 div,先彈出 capture is ok,后彈出 bubble is ok。只有當(dāng)真正觸發(fā)事件的 DOM 元素是內(nèi)層的時(shí)候,外層 DOM 元素才有機(jī)會(huì)模擬捕獲事件和冒泡事件。

2.當(dāng)在觸發(fā)事件的 DOM 元素上注冊(cè)事件時(shí),哪個(gè)先注冊(cè),就先執(zhí)行哪個(gè)

html 結(jié)構(gòu)同上,js 代碼如下:

const btnInner = document.getElementById("testInner");

//冒泡事件
btnInner.addEventListener("click", function(e){
    alert("bubble is ok");
}, false);
 
//捕獲事件
btnInner.addEventListener("click", function(e){
    alert("capture is ok");
}, true);

本例中,冒泡事件先注冊(cè),所以先執(zhí)行。所以,點(diǎn)擊內(nèi)層 div,先彈出 bubble is ok,再?gòu)棾?capture is ok

3.當(dāng)外層 div 和內(nèi)層 div 同時(shí)注冊(cè)了捕獲事件時(shí),點(diǎn)擊內(nèi)層 div 時(shí),外層 div 的事件一定會(huì)先觸發(fā)

js 代碼如下:

const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");

btnInner.addEventListener("click", function(e){
    alert("inner capture is ok");
}, true);

btn.addEventListener("click", function(e){
    alert("outer capture is ok");
}, true);

雖然外層 div 的事件注冊(cè)在后面,但會(huì)先觸發(fā)。所以,結(jié)果是先彈出 outer capture is ok,再?gòu)棾?inner capture is ok。

4.同理,當(dāng)外層 div 和內(nèi)層 div 都同時(shí)注冊(cè)了冒泡事件,點(diǎn)擊內(nèi)層 div 時(shí),一定是內(nèi)層 div 事件先觸發(fā)。
const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");

btn.addEventListener("click", function(e){
    alert("outer bubble is ok");
}, false);

btnInner.addEventListener("click", function(e){
    alert("inner bubble is ok");
}, false);

先彈出 inner bubble is ok,再?gòu)棾?outer bubble is ok。

5.阻止事件的派發(fā)

通常情況下,我們都希望點(diǎn)擊某個(gè) div 時(shí),就只觸發(fā)自己的事件回調(diào)。比如,明明點(diǎn)擊的是內(nèi)層 div,但是外層 div 的事件也觸發(fā)了,這是就不是我們想要的了。這時(shí),就需要阻止事件的派發(fā)。

事件觸發(fā)時(shí),會(huì)默認(rèn)傳入一個(gè) event 對(duì)象,這個(gè) event 對(duì)象上有一個(gè)方法:stopPropagation。MDN 上的解釋是:阻止 捕獲 和 冒泡 階段中,當(dāng)前事件的進(jìn)一步傳播。所以,通過(guò)此方法,讓外層 div 接收不到事件,自然也就不會(huì)觸發(fā)了。

btnInner.addEventListener("click", function(e){
    //阻止冒泡
    e.stopPropagation();
    alert("inner bubble is ok");
}, false);
事件代理

我們經(jīng)常會(huì)遇到,要監(jiān)聽(tīng)列表中多項(xiàng) li 的情況,假設(shè)我們有一個(gè)列表如下:

  • item1
  • item2
  • item3
  • item4

如果我們要實(shí)現(xiàn)以下功能:當(dāng)鼠標(biāo)點(diǎn)擊某一 li 時(shí),輸出該 li 的內(nèi)容,我們通常的寫(xiě)法是這樣的:

window.onload=function(){
    const ulNode = document.getElementById("list");
    const liNodes = ulNode.children;
    for(var i=0; i

在傳統(tǒng)的事件處理中,我們可能會(huì)按照需要,為每一個(gè)元素添加或者刪除事件處理器。然而,事件處理器將有可能導(dǎo)致內(nèi)存泄露,或者性能下降,用得越多這種風(fēng)險(xiǎn)就越大。JavaScript 的事件代理,則是一種簡(jiǎn)單的技巧。

用法及原理

事件代理,用到了在 JavaSciprt 事件中的兩個(gè)特性:事件冒泡 和 目標(biāo)元素。使用事件代理,我們可以把事件處理器添加到一個(gè)元素上,等待一個(gè)事件從它的子級(jí)元素里冒泡上來(lái),并且可以得知這個(gè)事件是從哪個(gè)元素開(kāi)始的。

改進(jìn)后的 js 代碼如下:

window.onload=function(){
    const ulNode=document.getElementById("list");
    ulNode.addEventListener("click", function(e) {
        /*判斷目標(biāo)事件是否為li*/
        if(e.target && e.target.nodeName.toUpperCase()=="LI"){
            console.log(e.target.innerHTML);
        }
    }, false);
};
一些常用技巧

回到文章開(kāi)頭的問(wèn)題:了解事件流的順序,對(duì)日常的工作有什么幫助呢?我總結(jié)了以下幾個(gè)注意點(diǎn)。

1. 阻止默認(rèn)事件

比如 href 的鏈接跳轉(zhuǎn),submit 的表單提交等??梢栽诜椒ǖ淖詈?,加上一行 return false;。它會(huì)阻止通過(guò) on 的方式綁定的事件的默認(rèn)事件。

ele.onclick = function() {
    ……
    // 通過(guò)返回 false 值,阻止默認(rèn)事件行為
    return false;
}

另外,重寫(xiě) onclick 會(huì)覆蓋之前的屬性,所以解綁事件可以這么寫(xiě):

// 解綁事件,將 onlick 屬性設(shè)為 null 即可
ele.onclick = null;
2. stopPropagation 和 stopImmediatePropagation

前面說(shuō)過(guò) stopPropagation 的定義是:終止事件在傳播過(guò)程的捕獲、目標(biāo)處理或起泡階段進(jìn)一步傳播。事件不再被分派到其他節(jié)點(diǎn)上。

// 事件捕獲到 ele 元素后,就不再向下傳播了
ele.addEventListener("click", function (event) {
  event.stopPropagation();
}, true);

// 事件冒泡到 ele 元素后,就不再向上傳播了
ele.addEventListener("click", function (event) {
  event.stopPropagation();
}, false);

但是,stopPropagation 只會(huì)阻止當(dāng)前元素 同類(lèi)型的 事件冒泡或捕獲的傳播,并不會(huì)阻止該元素上 其他類(lèi)型 事件的監(jiān)聽(tīng)。以 click 事件為例:

ele.addEventListener("click", function (event) {
  event.stopPropagation();
  console.log(1);
});

ele.addEventListener("click", function(event) {
  // 仍然可以觸發(fā)
  console.log(2);
});

如果想禁用之后所有的 click 事件,就要用到 stopImmediatePropagation 了。但是,需要注意的是,stopImmediatePropagation 只會(huì)禁用之后注冊(cè)的同類(lèi)型的監(jiān)聽(tīng)事件。就比如阻止了之后的 click 事件監(jiān)聽(tīng)函數(shù),但別的事件類(lèi)型如 mousedown、dblclick 之類(lèi),還是可以監(jiān)聽(tīng)到的。

ele.addEventListener("click", function (event) {
    event.stopImmediatePropagation();
    console.log(1);
});

ele.addEventListener("click", function(event) {
    // 不會(huì)觸發(fā)
    console.log(2);
});

ele.addEventListener("mousedown", function(event) {
    // 會(huì)觸發(fā)
    console.log(3);
});
3. jquery 中的 return false;

jquery 中的 on 是事件冒泡。當(dāng)用 return false; 阻止瀏覽器的默認(rèn)行為時(shí),會(huì)做下面這 3 件事:

event.preventDefault();

event.stopPropagation();

停止回調(diào)函數(shù)執(zhí)行并立即返回。

這 3 件事中,只有 preventDefault 是用來(lái)阻止默認(rèn)行為的。除非你還想阻止事件冒泡,否則直接用 return false; 會(huì)埋下隱患。

4. angular 中的 $event

angular 是個(gè)包羅萬(wàn)象的框架,似乎學(xué)完它的一整套之后,就能玩轉(zhuǎn)世界了。它加工封裝了許多原生的東西,其中就包括了 event,只是前面需要加一個(gè) $,表示這是 angular 中的特有對(duì)象。

// template
// js doSomething($event: Event) { $event.stopPropagation(); ... }

$event 在這里作為一個(gè)變量,顯式地 傳入回調(diào)函數(shù),之后就可以將 $event 當(dāng)做原生的事件對(duì)象來(lái)用了。

5. vue 中的 native 修飾符

在 vue 的自定義組件中綁定原生事件,需要用到修飾符 native。

那是因?yàn)?,我們的自定義組件,最終會(huì)渲染成原生的 html 標(biāo)簽,而非類(lèi)似于 這樣的自定義組件。如果想讓一個(gè)普通的 html 標(biāo)簽觸發(fā)事件,那就需要對(duì)它做事件監(jiān)聽(tīng)(addEventListener)。修飾符 native 的作用就在這里,它可以在背后幫我們綁定了原生事件,進(jìn)行監(jiān)聽(tīng)。

一個(gè)常用的場(chǎng)景是,配合 element-ui 做登錄界面時(shí),輸完賬號(hào)密碼,想按一下回車(chē)就能登錄。就可以像下面這樣用修飾符:


el-input 就是自定義組件,而 keyup 就是原生事件,需要用 native 修飾符進(jìn)行綁定才能監(jiān)聽(tīng)到。

6. react 中的合成事件

想要在 react 的事件回調(diào)中使用 event 對(duì)象,會(huì)產(chǎn)生困擾,會(huì)發(fā)現(xiàn)不少原生的屬性都是 null。

那是因?yàn)樵?react 中的事件,其實(shí)是合成事件(SyntheticEvent),并不是瀏覽器的原生事件,但它也符合 w3c 規(guī)范。

舉一個(gè)簡(jiǎn)單的例子,我們要實(shí)現(xiàn)一個(gè)組件,它有一個(gè)按鈕,點(diǎn)擊按鈕后會(huì)顯示一張圖片,點(diǎn)擊這張圖片之外的任意區(qū)域,可以隱藏這張圖片,但是點(diǎn)擊該圖片本身時(shí),不會(huì)隱藏。代碼如下:

class ShowImg extends Component {
    constructor(props) {
        super(props);
        this.state = {
          active: false
        };
    }
  
    componentDidMount() {
        document.addEventListener("click", this.hideImg.bind(this));
    }

    componentWillUnmount() {
        document.removeEventListener("click", this.hideImg);
    }
    
    hideImg () {
        this.setState({ active: false });
    }
    
    handleClickBtn() {
        this.setState({ active: !this.state.active });
    }
  
    handleClickImg (e) {
        e.stopPropagation();
    }

    render() {
        return (
            
); } }

按照之前說(shuō)的原生事件機(jī)制,我們會(huì)錯(cuò)誤地認(rèn)為通過(guò):

handleClickImg (e) {
    e.stopPropagation();
}

就可以阻止事件的派發(fā)了,但其實(shí)沒(méi)法這么做。想要解決這個(gè)問(wèn)題,當(dāng)然也不復(fù)雜,就把 react 的事件和原生事件分開(kāi)即可。

componentDidMount() {
    document.addEventListener("click", this.hideImg.bind(this));
    
    document.addEventListener("click", this.imgStopPropagation.bind(this));
}

componentWillUnmount() {
    document.removeEventListener("click", this.hideImg);
    
    document.removeEventListener("click", this.imgStopPropagation);
}

hideImg () {
    this.setState({ active: false });
}

imgStopPropagation (e) {
    e.stopPropagation();
}
7. 事件對(duì)象 event

當(dāng)對(duì)一個(gè)元素進(jìn)行事件監(jiān)聽(tīng)的時(shí)候,它的回調(diào)函數(shù)里就會(huì)默認(rèn)傳遞一個(gè)參數(shù) event,它是一個(gè)對(duì)象,包含了許多屬性。我列出了一些比較常用的屬性:

event.target:指的是觸發(fā)事件的那個(gè)節(jié)點(diǎn),也就是事件最初發(fā)生的節(jié)點(diǎn)。

event.target.matches:可以對(duì)關(guān)鍵節(jié)點(diǎn)進(jìn)行匹配,來(lái)執(zhí)行相應(yīng)操作。

event.currentTarget:指的是正在執(zhí)行的監(jiān)聽(tīng)函數(shù)的那個(gè)節(jié)點(diǎn)。

event.isTrusted:表示事件是否是真實(shí)用戶觸發(fā)。

event.preventDefault():取消事件的默認(rèn)行為。

event.stopPropagation():阻止事件的派發(fā)(包括了捕獲和冒泡)。

event.stopImmediatePropagation():阻止同一個(gè)事件的其他監(jiān)聽(tīng)函數(shù)被調(diào)用。

總結(jié)

事件機(jī)制在瀏覽器中非常有用,所有用戶的交互型操作,都依賴(lài)于它。現(xiàn)代 JavaScript 框架應(yīng)用中,我們也都離不開(kāi)與原生事件的交互。

所以,在理解了事件流的概念,清楚了事件捕獲與冒泡的順序,掌握了一些原生事件的技巧之后,相信下次再遇到坑的時(shí)候,可以少走一些彎路了。

PS:歡迎關(guān)注我的公眾號(hào) “超哥前端小棧”,交流更多的想法與技術(shù)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/102052.html

相關(guān)文章

  • JavaScript - 收藏集 - 掘金

    摘要:插件開(kāi)發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開(kāi)發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。插....

    izhuhaodev 評(píng)論0 收藏0
  • 前端文檔收集

    摘要:系列種優(yōu)化頁(yè)面加載速度的方法隨筆分類(lèi)中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁(yè)性能管理詳解離線緩存簡(jiǎn)介系列編寫(xiě)高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問(wèn)性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁(yè)面加載速度的方法 隨筆分類(lèi) - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁(yè)性能管理詳解 HTML5 ...

    jsbintask 評(píng)論0 收藏0
  • 前端文檔收集

    摘要:系列種優(yōu)化頁(yè)面加載速度的方法隨筆分類(lèi)中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁(yè)性能管理詳解離線緩存簡(jiǎn)介系列編寫(xiě)高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問(wèn)性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁(yè)面加載速度的方法 隨筆分類(lèi) - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁(yè)性能管理詳解 HTML5 ...

    muddyway 評(píng)論0 收藏0
  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠?lái)都是中的主導(dǎo)范式。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠?lái)都是JavaScript中的主導(dǎo)范式。JavaScript作為一門(mén)多范式編程語(yǔ)言,然而,近幾年,函數(shù)式編程越來(lái)越多得受到開(kāi)發(fā)者的青睞。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。因此,...

    cfanr 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<