在平常开发过程中,就算不使用现在主流的框架也至少得使用个jquery,这些工具帮我们统一不同浏览器平台之间的差异和细节,可以将注意力集中到开发上来.不过有意思的一点是,在看完高程的N年后我居然连event对象中的target和currentTarget属性的区别都忘记了.
先提几个引子:
如果这几个内容你都熟悉了,那么这篇文章不会给你带来太多的帮助.
在正文开始之前先来浏览一个表格,来看一下不同浏览器之间Event对象的属性有何不同:
<button id="button">click me then change word</button>
var button = document.getElementById('button');
button.addEventListener('click',function (event) {
console.log(event);
});
在下方的表格中我们记录了不同浏览器之间click点击后event可用的属性列表(删除了控制台输出的原型和函数引用):
firefox67 | chrome72 | edge44.17763.1.0 | ie11 | ie9 |
---|---|---|---|---|
altKey | altKey | altKey | altKey | altKey |
bubbles | bubbles | bubbles | AT_TARGET | AT_TARGET |
button | button | button | bubbles | bubbles |
buttons | buttons | buttons | BUBBLING_PHASE | BUBBLING_PHASE |
cancelBubble | cancelBubble | cancelable | button | button |
cancelable | cancelable | cancelBubble | buttons | buttons |
clientX | clientX | clientX | cancelable | cancelable |
clientY | clientY | clientY | cancelBubble | cancelBubble |
composed | composed | ctrlKey | CAPTURING_PHASE | CAPTURING_PHASE |
ctrlKey | ctrlKey | currentTarget | clientX | clientX |
currentTarget | currentTarget | defaultPrevented | clientY | clientY |
defaultPrevented | defaultPrevented | detail | constructor | constructor |
detail | detail | eventPhase | ctrlKey | ctrlKey |
eventPhase | eventPhase | fromElement | currentTarget | currentTarget |
explicitOriginalTarget | fromElement | height | defaultPrevented | defaultPrevented |
isTrusted | isTrusted | isPrimary | detail | detail |
layerX | layerX | isTrusted | deviceSessionId | eventPhase |
layerY | layerY | layerX | eventPhase | fromElement |
metaKey | metaKey | layerY | fromElement | isTrusted |
movementX | movementX | metaKey | height | layerX |
movementY | movementY | movementX | hwTimestamp | layerY |
mozInputSource | offsetX | movementY | isPrimary | metaKey |
mozPressure | offsetY | offsetX | isTrusted | offsetX |
offsetX | pageX | offsetY | layerX | offsetY |
offsetY | pageY | pageX | layerY | pageX |
originalTarget | path | pageY | metaKey | pageY |
pageX | relatedTarget | pointerId | offsetX | relatedTarget |
pageY | returnValue | pointerType | offsetY | screenX |
rangeOffset | screenX | pressure | pageX | screenY |
rangeParent | screenY | relatedTarget | pageY | shiftKey |
region | shiftKey | returnValue | pointerId | srcElement |
relatedTarget | sourceCapabilities | screenX | pointerType | target |
returnValue | srcElement | screenY | pressure | timeStamp |
screenX | target | shiftKey | relatedTarget | toElement |
screenY | timeStamp | srcElement | rotation | type |
shiftKey | toElement | target | screenX | view |
srcElement | type | tiltX | screenY | which |
target | view | tiltY | shiftKey | x |
timeStamp | which | timeStamp | srcElement | y |
type | x | toElement | target | |
view | y | twist | tiltX | |
which | type | tiltY | ||
x | view | timeStamp | ||
y | which | toElement | ||
width | type | |||
x | view | |||
y | which | |||
width | ||||
x | ||||
y |
通过这个表格我们可以观察Event对象在不同浏览器之间结构是不同的,出人意料的是即使是在现代浏览器中事件对象也存在着差异.
当然这篇文章可不是将所有的Event属性都将一遍,要知道不同的事件的Event对象结构是不同的.
吐槽:本来是打算提供IE8的但是ie8不能使用addEventListener来监听事件懒得去搞ie那套数据了.
一句话:
哪个元素上监听的事件,event.currentTarget返回的就是这个对象的本身的引用.
如果你的一个事件监听函数被注册到了多个dom元素上,利用这个属性你就可以判断是谁触发的事件.
回调函数中的this === event.currentTarget.
button.addEventListener('click',function (event) {
console.log(event.currentTarget === this); // true
});
event.target和上面的三者不同,这里面涉及到了DOM中的一个基本知识事件冒泡和事件拦截.
关于这两点我相信大家都已经了解了,即使不了解网上介绍的文章也有一大堆.
我们用事件冒泡来举例,并且改写我们之前的那个例子:
<div id="wrap">
<button id="button">test click</button>
</div>
var
wrap = document.getElementById('wrap'),
button = document.getElementById('button');
// 注意我们监听的是wrap的click事件,而不是button的click事件
wrap.addEventListener('click',function (event) {
// event.target指向的是按钮,因为我们点击的是按钮
console.log(event.target === button && event.target === event.srcElement); // true
// 当我们点击按钮触发的事件冒泡到了wrap,所以触发了wrap的click事件,
// 此时currentTarget指向的是wrap
console.log(wrap===this && wrap === event.currentTarget); // true
// 直接打印event然后控制台中查看currentTaget会返回null
// 你可以将他赋值到一个变量在打印输出这个变量
// see https://github.com/vuejs/vue/issues/6867#issuecomment-338195468
})
在这个例子中,我们点击页面中的按钮,然后再按钮的包裹div中接收到了button冒泡上来的事件,这其中:
事件委托也是event.target最常见的用途之一:
// Make a list
var ul = document.createElement('ul');
document.body.appendChild(ul);
var li1 = document.createElement('li');
var li2 = document.createElement('li');
ul.appendChild(li1);
ul.appendChild(li2);
function hide(e){
// e.target 引用着 <li> 元素
// 不像 e.currentTarget 引用着其父级的 <ul> 元素.
e.target.style.visibility = 'hidden';
}
// 添加监听事件到列表,当每个 <li> 被点击的时候都会触发。
ul.addEventListener('click', hide, false);
https://developer.mozilla.org...
简单理解event.srcElement === event.target.
Event.srcElement 是标准的 Event.target 属性的一个别名。它只对老版本的IE浏览器有效。
参考之前的表格后看来这个属性还没有被干掉,在目前最新的浏览器上它依然存在,不过已经不建议使用,除非你需要向下兼容.
当我们在使用如下的方法的时候:
实际上是在使用EventTarget接口上的功能.
例如我们可以创建一个新的EventTarget对象来添加事件监听:
const a = new EventTarget;
a.addEventListener('click',()=>{
})
但是这没有任何意义,因为这里没有事件被触发.我们仅仅是添加了事件监听器而已.
为了达到我们目的通过编程的方式来执行完整的事件流程我们还需要完成如下的几步:
首先我们来继承EventTarget对象:
在浏览器中大部分可以添加删除事件的对象都继承了EventTarget对象.
你可以在控制台选择一个html元素一路查找原型链得到.
但是他们进过重重继承,都有自己的独特属性和事件类型甚至是不同的构造函数.
为了和已有的事件进行区分我们这里需要对EventTarget进行继承:
// --- 包装EventTarget开始
function MyEventTarget() {
var target = document.createTextNode(null);
this.addEventListener = target.addEventListener.bind(target);
this.removeEventListener = target.removeEventListener.bind(target);
this.dispatchEvent = target.dispatchEvent.bind(target);
}
MyEventTarget.prototype = EventTarget.prototype;
// --- 包装EventTarget结束
// --- 创建我们继承EventTarget的构造函数
function myElem() {
}
myElem.prototype = new MyEventTarget;
myElem.prototype.constructor = myElem;
// 创建实例
const instance = new myElem();
instance.addEventListener('click',()=>{
// 现在我们实例可以监听事件了
});
console.log(instance);
继承的过程看似非常复杂,尤其是包装EventTarget显得多此一举.但是搞定EventTarget的继承确实花了我大量的时间去寻找解决方案.
你完全可以编写自己的继承方式来去继承EventTarget,不过你会发现这其中的坑非常深.
简单来说,EventTarget在JavaScript中真的就是一个接口,虽然是以函数的形式存在,但是它不是构造函数(这点在Chrome64 和firefox59后进行了修改).
总之通过原型链继承的EventTarget统统无法工作,如果使用ES6的类式继承在现代浏览器中(Chrome64和firefox59后)可以使用class来进行继承.
详细参考:https://stackoverflow.com/que...
获取一个event:
document.getElementById('button').addEventListener('click',(event)=>{
// event
console.log(event);
})
如果你在浏览器中运行这段代码并且在控制台中查看,你会发现变量event的名称MouseEvent,如果你沿着原型链向上你会发现继承的是UIEvent再次向上查看则是真正的Event.
事件触发中传递的第一个参数我们通常叫它event,所有的event对象都基于Event,但是这不意味着这种关系的窗户纸就只有一层,click事件中的event和Event之间就隔着一个UIEvent.
通常随着event继承的层数越多,event对象身上的属性也会越来越多.
现在我们来创建一个标准的Event对象:
// 使用全局的Event
new Event('test',{ // 事件类型
bubbles:false, // 是否冒泡 默认false
cancelable:false,// 是否可以被取消 默认false
});
https://developer.mozilla.org...
如果你在浏览器中观察这个对象,你会发现事件上常见的属性诸如:
都在这个new Event()返回的对象中,由于其他类型的事件都继承自Event这也解释了为什么事件对象中总是有这些属性.
和继承EventTarget一样,使用Event的过程也同样艰难,总的来说使用Event的难点在于它有两套api:
一篇值得参考的文章,使用createEvent api
此外不同种类的事件,都有自己的全局构造函数,不同类型的构造函数的第二个参数中的选项也是不同的.
其他的构造函数请参考这里.
触发事件就显得简单多了,我们需要使用EventTarget.dispatchEvent方法.
在我们之前创建的实例上进行事件的触发:
function MyEventTarget() {
var target = document.createTextNode(null);
this.addEventListener = target.addEventListener.bind(target);
this.removeEventListener = target.removeEventListener.bind(target);
this.dispatchEvent = target.dispatchEvent.bind(target);
}
MyEventTarget.prototype = EventTarget.prototype;
function myElem() {
}
myElem.prototype = new MyEventTarget;
myElem.prototype.constructor = myElem;
const instance = new myElem();
instance.addEventListener('test', (event) => {
console.log(event); // 监听事件并且打印实例
});
const myEvent = new Event('test'); // 创建Event实例
instance.dispatchEvent(myEvent); // 触发事件
当你调用dispatchEvent的时候,EventTarget会按照对应事件注册的顺序来同步执行这些事件监听器.
如果在事件监听器中调用了event.preventDefault,那么dispatchEvent就返回false反之返回true(前提是cancleable为true).
详细参考https://developer.mozilla.org...
我们在页面中来一次具体的实战,首先建立如下的HTML结构:
<div id="wrap">
<button id="button">test click</button>
</div>
我们在#wrap中监听click事件,然后在#button触发click事件.
这样我们可以练习一下Event中bubbles(允许冒泡)参数的使用,
另外还可以测试click事件中的Event对象如果不是MouseEvent的实例那么监听器是否会被触发.
const
button = document.getElementById('button'),
wrap = document.getElementById('wrap');
wrap.addEventListener('click', (event) => {
console.log(event); // 打印event对象
});
const myEvent1 = new Event('click', {
bubbles: false, // 不可以冒泡
});
const myEvent2 = new Event('click', {
bubbles: true, // 可以冒泡
});
button.dispatchEvent(myEvent1); // 这次没有打印出内容
button.dispatchEvent(myEvent2); // 这次打印出了内容
结论很明确:
您的浏览器禁用了JS脚本运行,请启用该功能。怎么解除浏览器禁用js?这篇文章将总结整理各个浏览器如何开启、禁用javascript的方法总汇。
浏览器使用流式布局模型 (Flow Based Layout)。浏览器会把HTML解析成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了Render Tree。有了RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。
IE6不支持min-height,解决办法使用css hack,ol内li的序号全为1,不递增。解决方法:为li设置样式display: list-item;定位父元素overflow: auto;,包含position: relative;子元素,子元素高于父元素时会溢出。解决办法:
由于不同的浏览器默认的样式也不同,所以在网页开发前设置一个公用样式,来清除各个浏览器的默认样式,已达到做的网页在各个浏览器中达到统一。
浏览器访问网站的步骤:Chrome搜索自身的DNS缓存、读取本地HOST文件、浏览器发起一个DNS的一个系统调用、浏览器获得域名对应的IP地址后,发起HTTP三次握手、TCP/IP连接建立起来、服务器端接受到了这个请求、浏览器根据拿到的资源对页面进行渲染
Browsh是一个纯文本浏览器,可以运行在大多数的TTY终端环境和任何浏览器。目前,终端客户端比浏览器客户端更先进。终端客户端即时更新和交付,以便于体验新的功能,例如,你可以观看视频。
一般说的浏览器内核是指浏览器最重要的核心部分,RenderingEngine,翻译成中文大概意思就是“解释引擎”,我们一般称为浏览器内核。由于不同的内核各自有一套自己的渲染网页和解释页面代码的机制,所以就会有一些问题存在。
主流浏览器之争从上个世纪开就开始,已经持续了很长的时间。人们都在笑话IE,纷纷转向其它浏览器。今天,我向大家分享一下针对IE的搞笑图片,只是逗乐而已,喝杯咖啡,坐下来慢慢享受吧。
有时候我们希望在浏览器中执行一些低优先级的任务,比如记录统计数据、做一些耗时的数据处理等,暂且将其称为后台任务。这些任务跟动画计算、合成帧、响应用户输入等高优先级的任务共享主线程
浏览器的事件循环,前端再熟悉不过了,每天都会接触的东西。但我以前一直都是死记硬背:事件任务队列分为macrotask和microtask,浏览器先从macrotask取出一个任务执行,再执行microtask内的所有任务,接着又去macrotask取出一个任务执行
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!