ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptによるイベントの解析(詳細)

JavaScriptによるイベントの解析(詳細)

不言
不言オリジナル
2018-09-10 16:28:482056ブラウズ

この記事の内容は JavaScript でのイベントの分析に関するものです (詳細)。必要な方は参考にしていただければ幸いです。

JavaScript、ブラウザ、イベントの関係

JavaScript プログラムは非同期イベント駆動プログラミング (Event-driven programming) モデルを採用しています。Wikipedia では次のように説明されています。

イベント駆動プログラミング (Event-driven programming)コンピュータープログラミングモデル。このモデルのプログラム実行プロセスは、ユーザーのアクション (マウス ボタンの押下、キーボードのキーの押下アクションなど) または他のプログラムからのメッセージによって決定されます。バッチ プログラミングと比較して、プログラムの実行の流れはプログラマによって決定されます。バッチ プログラミングは、プログラミング入門コースで使用される手法です。ただし、イベント駆動型プログラム設計の設計モデルは、対話型プログラムのコンテキストで生まれました。つまり、Web フロントエンド プログラミングでは、JavaScript はブラウザーによって提供されるイベント モデル API を介してユーザーと対話し、ユーザー入力を受け取ります。

ユーザーの行動は不確実であるため。このシナリオは、ユーザーが操作を完了するのを待ってから後続のコードを実行することができないため、従来の同期プログラミング モデルでは解決できません。したがって、JavaScript では非同期イベントが使用されます。これは、

js 内のイベントはすべて非同期で実行されます

ことを意味します。 イベント ドライバー モデルの基本的な実装原理は、基本的にイベント ループ (イベント ループ) の使用です。この部分には、ブラウザーのイベント モデルとコールバックの原理が含まれます。

JavaScript DOM および BOM モデルでは、setTimeout や XMLHTTPRequest などの API も非同期であり、JavaScript 言語自体にネイティブではありません。

イベントバインディングの方法

イベントバインディングには3つの方法があります:

インラインバインディング

を設定してDOM要素にイベントハンドラーを直接バインドします。例:

<a>点击我</a>
on + eventType この方法には 2 つの欠点があります:

    イベント ハンドラーと HTML 構造が混在しており、MVX の仕様に準拠していません。コンテンツ、プレゼンテーション、動作を分離するには、このような書き方は避けるべきです。
  1. このように書かれたコードはグローバルスコープを持つと判断され、名前の競合を引き起こし、予期せぬ重大な結果につながる可能性があります。
  2. DOM 要素でイベント コールバック関数を直接書き換えます

DOM 要素で on +eventType 属性 API を使用します

var el = getElementById('button');  //button是一个<button>元素
el.onclick = function(){ alert('button clicked.') };
el.onclick = function(){ alert('Button Clicked.') };
//实际之弹出'Button Clicked.',函数发生了覆盖</button>

この方法には欠点もあります。ポストバインド関数が以前の関数を上書きすることです。たとえば、window.onload イベントを登録すると、ライブラリ内の既存のイベント関数が上書きされる可能性があります。もちろん、これには解決策があります:

function addEvent(element, EventName, fun) {   //EventName = 'on' + eventType
    var oldFun = element[EventName];
    if (typeof oldFun !== 'function') {
        element[EventName] = fun;
    } else {
        element[EventName] = function() {
            oldFun();
            fun();
        };
    }
}

addEvent(window, "onload", function() { alert('onload 1') });
addEvent(window, "onload", function() { alert('onload 2') });

もちろん、通常の状況では、DOM Ready を使用するだけで十分です。DOM がロードされた後に JavaScript を実行できるためです

標準のバインド方法

標準的なバインド方法が 2 つありますaddEventListener とattachEventの前者は標準ブラウザでサポートされるAPIであり、後者はIE8以下のブラウザでサポートされるAPIです:

//例如给一个button注册click事件
var el = getElementById('button');  //button是一个<button>元素
if(el.addEventLister){
    el.addEventListener("click", function(e){
        alert("button clicked.");
    },false);
}
if(el.attachEvent){
    el.attachEvent("onclick", function(e){
        alert("button clicked.");
    });
}</button>

注意してください:

    addEventListerの最初のパラメータイベントタイプはon というプレフィックスが付いていません。on プレフィックスをattachEventに追加する必要があります。
  1. addEventLister のイベント コールバック関数の this はイベント要素ターゲット自体を指しますが、attachEvent のイベント コールバック関数の this はウィンドウを指します。
  2. addEventLister には 3 番目のパラメーターがあり、true はイベントがキャプチャフェーズで動作していることを示し、false はバブリングフェーズを示します (デフォルト値: false)。そして、attachEvent はバブリング段階でのみ機能します。
  3. Chrome で次のコードを実行します:
<a>click me</a>
<script>
    var link = document.getElementById(&#39;link&#39;);
    link.onclick = function() { alert(3); };  //覆盖了行内的onclick定义
    link.addEventListener(&#39;click&#39;, function() { alert(4); },false);
    link.addEventListener(&#39;click&#39;, function() { alert(5); },false);
</script>

クリック後のポップアップ シーケンスは次のとおりです: 3 -> 4 -> 5 -> 1

ここのコードの 4 行目は、この行では、入力順序は 2 -> 4 -> 5 -> 1 となり、addEventListener 間で上書きは行われません。

イベントのバインド解除

上記の最初の 2 つのメソッドでは、イベントのバインドを解除するには、対応するイベント関数を null に設定するだけです:

var el = document.getElementById('button');
el.onclick = null;

上記の 3 番目のメソッドでは、removeListen() メソッドを使用します。つまり、はい、 IE8 では、それに応じて detachEvent() が使用されます。なお、上記の登録方法は 1 対 1 で対応しており、混合することはできません。

//这是一段错误代码,不能实现事件移除

//建立一个事件
var el = document.getElementById('button');  //button是一个<button>元素
if(el.addEventLister){
    el.addEventListener("click", function(e){
        alert("button clicked.");
    },false);
}
if(el.attachEvent){
    el.attachEvent("onclick", function(e){
        alert("button clicked.");
    });
}

//试图移除这个事件
if(el.removeEventLister){
    el.addEventListener("click", function(e){
        alert("button clicked.");
    },false);
}
if(el.detachEvent){
    el.datachEvent("onclick", function(e){
        alert("button clicked.");
    });
}

//移除失败</button>

上記のエラーは、このようにイベント関数を定義した場合、見た目は全く同じですが、メモリ上のアドレスが異なるというものです。このようにすると、コンピュータはリリースされた関数とバインドされた関数を同じ関数とは認識しなくなり、当然正しくリリースされなくなります。次のように記述する必要があります:

//建立一个事件
var el = document.getElementById('button');  //button是一个<button>元素
var handler = function(e){alert("button clicked.");};
if(el.addEventLister){
    el.addEventListener("click", handler,false);
}
if(el.attachEvent){
    el.attachEvent("onclick", handler);
}

//试图移除这个事件
if(el.removeEventLister){
    el.addEventListener("click", handler, false);
}
if(el.detachEvent){
    el.datachEvent("onclick", handler);
}

//移除成功</button>

イベントのキャプチャとバブリング

addEventListener 関数の 3 番目のパラメーターがキャプチャとバブリングを表すと前述しましたが、これは重要な点です。

それらの定義を私自身で説明しましょう:

バブリング

: 要素でトリガーされたイベントは、この要素の親要素で内側から外側に、ウィンドウ要素に至るまでトリガーされます。 キャプチャ

: 要素でイベントがトリガーされると、イベントはこの要素の各レイヤーのすべてのサブ要素でトリガーされ、すべての要素にサブ要素がなくなるまでレイヤーごとに内側に進みます。 以下に示すように(注:写真はBaidu検索からのものです)

JavaScriptによるイベントの解析(詳細)

事件间回到函数参数是一个事件对象,它里面包括许多事件属性和方法,比如,我们可以用以下方式阻止冒泡和默认事件:

//该例子只写了handler函数
function handler(event) {
    event = event || window.event;
    //阻止冒泡
    if (event.stopPropagation) {
        event.stopPropagation();      //标准方法
    } else {
        event.cancelBubble = true;    // IE8
    }
    //组织默认事件
    if (event.perventDefault) {
        event.perventDefault();      //标准方法
    } else {
        event.returnValue = false;    // IE8
    }
}

其次,普通注册事件只能阻止默认事件,不能阻止冒泡

element = document.getElemenById("submit");
element.onclick = function(e){
    /*...*/
    return false;    //通过返回false,阻止冒泡
}

事件对象

事件函数中有一个参数是事件对象,它包含了事件发生的所有信息,比如键盘时间会包括点击了什么按键,包括什么组合键等等,而鼠标事件会包括一系列屏幕中的各种坐标和点击类型,甚至拖拽等等。当然,它里面也会包括很多DOM信息,比如点击了什么元素,拖拽进入了什么元素,事件的当前状态等等。

这里关于事件兼容性有必要强调一下:

document.addEventListener('click', function(event) {
    event = event || window.event;   //该对象是注册在window上的
    console.log(event);   //可以输出事件对象看一看, 属性很多很多
    var target = event.target || event.srcElement;  //前者是标准事件目标,后者是IE的事件目标
},false);

关于鼠标事件坐标的问题,可以看另一篇博客:元素和鼠标事件的距离属性

事件触发

除了用户操作以外,我们也可以写代码主动触发一个事件,以ele元素的click事件为例:

ele.click();   //触发ele元素上的单击事件

事件代理

有时候我们需要给不存在的的一段DOM元素绑定事件,比如用户动态添加的元素,或者一段 Ajax 请求完成后渲染的DOM节点。一般绑定事件的逻辑会在渲染前执行,但绑定的时候找不到元素所以并不能成功。

为了解决这个问题,我们通常使用事件代理/委托(Event Delegation)。而且通常来说使用 事件代理的性能会比单独绑定事件高很多,我们来看个例子。

  • 传统注册事件方法,当内容很多时效率低,不支持动态添加元素


        
  • item-1
  •     
  • item-2
  •     
  • item-3
  •     
  • item-4
  •     
  • item-5
<script> var lists = document.getElementsByTagName(&#39;li&#39;); for(var i = 0; i < lists.length; ++i){ lists[i].onclick = (function(i){ return function(){ console.log("item-" + (i + 1)); }; })(i); } //添加节点 var list = document.getElementById(&#39;list&#39;); var newNode = document.createElement(&#39;li&#39;); newNode.innerHTML = "item-6"; list.appendChild(newNode); </script>
  • 事件委托注册方法,不论内容有多少都只注册1次,支持动态添加元素:


        
  • item-1
  •     
  • item-2
  •     
  • item-3
  •     
  • item-4
  •     
  • item-5
<script> var list = document.getElementById(&#39;list&#39;); var handler = function(e){ e = e || window.event; var target = e.target || e.srcElement; if(target.nodeName && target.nodeName === "LI"){ console.log(target.innerHTML); } }; if(list.addEventListener){ list.addEventListener("click", handler); } else { list.attachEvent("onclick", handler); } //添加节点 var list = document.getElementById(&#39;list&#39;); var newNode = document.createElement(&#39;li&#39;); newNode.innerHTML = "item-6"; list.appendChild(newNode); </script>

事件封装

很明显,处理浏览器兼容太麻烦了,所以这里把js中的事件注册相关函数封装一下,作为整理。

//均采用冒泡事件模型
var myEventUtil={
    //添加事件函数
    addEvent: function(ele, event, func){
        var target = event.target || event.srcElement;
        if(ele.addEventListener){
            ele.addEventListener(event, func, false);
        } else if(ele.attachEvent) {
            ele.attachEvent('on' + event, func);   //func中this是window
        } else {
            ele['on' + event] = func;    //会发生覆盖
        }
    },
    //删除事件函数
    delEvent:function(ele, event, func) {
        if(ele.removeEventListener){
            ele.removeEventListener(event, func, false);
        } else if(ele.detachEvent) {
            ele.detachEvent('on' + event, func);
        } else {
            ele['on' + event] = null;
        }
    },
    //获取触发事件的源DOM元素
    getSrcElement: function(event){
        return event.target || event.srcElement;
    },
    //获取事件类型
    getType: function(event){
        return event.type;
    },
    //获取事件
    getEvent:function(event){
        return event || window.event;
    },
    //阻止事件冒泡
    stopPropagation: function(event) {
        if(event.stopPropagation) {
            event.stopPropagation();
        } else {
            event.cancelBuble = false;
        }
    },
    //禁用默认行为
    preventDefault: function(event){
        if(event.preventDefault){
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    }
};

jQuery中的事件

需要注意的是: JQuery中的事件都工作在冒泡阶段,且只能工作在冒泡阶段

注册、解除事件

  • 方法一:

//不会发生覆盖,但不利于解除,不能动态操作事件
<button>here</button>
$("#button").click(function(){   //注册一个click事件,当然可以用其他事件名的函数注册其他事件
  console.log("clicked");
});
  • 方法二:

//不会发生覆盖,利于解除,不能动态操作事件
<button>here</button>
//注册一个事件
$("#button").bind("click", function() {    //注册一个click事件,当然可以用其他事件名的函数注册其他事件
  console.log("clicked");
});
//当然还可以这样写,给事件指定命名空间
$(document).bind('click.handler1', function() { console.log(1);})
$(document).bind('click.handler2', function() { console.log(2);})

//解除一个事件
$("#button").unbind(".handler1");    //解除元素上所以handler1命名空间中的事件
$("#button").unbind('click.handler2');   // 解除元素上的click.handler2事件
$("#button").unbind('click');            // 解除元素上所有点击事件
$("#button").unbind()                    // 解除元素上所有事件

//bind()方法还介受3个参数形式,这里就不赘述了,感兴趣可以自己看看相关资料。
  • 方法三:

//不会发生覆盖,但不利于解除,能动态操作事件,依赖于事件冒泡

//注册事件
$(document).delegate(".item", "click", function(){console.log(this.innerHTML);});   //第一个是选择器, 第二个是事件类型, 第三个是事件函数

//移除事件
$(document).undelegate(".item", "click", handler);  //移除元素上指定事件
$(document).undelegate(".item", "click");  //移除元素上所有click事件
$(document).undelegate(".item");  //移除元素上所有事件
  • 方法四:

//不会发生覆盖,但不利于解除,能动态操作事件,不依赖于事件冒泡

//注册事件
#(".item").live("click", function(){console.log(this.innerHTML);})  //第一参数是事件类型, 第二参数是事件函数

//移除事件
$(".item").die("click", handler);  //移除元素上指定click事件
$(".item").die("click");  //移除元素上所有click事件
  • 两个简化方法:

//hover方法
$("#button").hover(function(){
        //鼠标移入时的动作,不冒泡
    }, function(){
        //鼠标移出时的动作,不冒泡
});

//toggle方法
$("#button").toggle(function(){
        //第一次点击时的动作
    }, function(){
        //第二次点击时的动作
}, .../*可以放多个函数,依次循环响应*/);

事件触发

//不能触发addEventListener和attachEvent
//主动触发一个事件
$("#button").trigger("click");   //触发所有click事件
$("#button").trigger("click.handler1");   //触发所有click.handler1事件
$("#button").trigger(".handler1");   //触发所有handler1命名空间的事件
$("#button").trigger("click!");   //触发所有没有命名空间的click事件
$("#button").trigger(event);   //在该元素上触发和事件event一样的事件
$("#button").trigger({type:"click", sync: true});   //触发click事件,同步

相关推荐:

带你快速理解javascript中的事件模型

JavaScript事件类型中UI事件详解_javascript技巧

以上がJavaScriptによるイベントの解析(詳細)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。