JS基础知识(十一)DOM0和DOM2级事件

JS中的DOM0和DOM2事件

JS中的事件基础及核心原理以及项目实战

事件的定义

事件:元素天生具备的行为方式(和写不写JS代码没有关系)【onclick不是事件,click才是事件,浏览器会把一些常用事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定】,当我们去操作元素的时候会触发元素的很多事件
事件绑定:给当前元素的某个事件绑定方法,目的是让当前元素某个事件被触发时,做出一些反应

事件绑定的两种方法
  • DOM0级事件绑定
    • curEle.onclick=function(){};
  • DOM2级事件绑定
    • 标准浏览器:curEle.addEventListener('click',function(){},false)
    • IE6-8:curEle.attachEvent('onclick',function(){})

DOM0事件

常用事件汇总

[PC端]

  • 表单元素常用的事件

    • blur:失去焦点
    • focus:获取焦点
    • change:内容改变
    • select:被选中事件
  • 键盘常用事件行为

    • keydown:键盘按下
    • keyup:键盘抬起
    • keypress:键盘按下(中文输入法不能触发,英文状态下可以触发,因为文本被输入到文本框中)
  • 鼠标常用事件行为

    • click:点击(不是单击)
    • dblclick:双击(300ms之内连续触发两次点击事件)
    • mouseover:鼠标滑过
    • mouseout:鼠标滑出
    • mouseenter: 鼠标进入
    • mouseleave: 鼠标离开
    • mousedown:鼠标按下(左键)
    • mouseup:鼠标左键抬起
    • mousewheel:鼠标滚轮滚动
  • 其他常用事件行为‘

    • load:加载成功
    • error:加载失败
    • scroll:
    • resize:大小改变事件:window.onresize:浏览器窗口改变大小触发该事件

[移动端]

移动端键盘一般都是虚拟键盘,虽然存在keydown,keyup,但兼容不好,使用input事件代替
移动端没有鼠标,所以鼠标类事件在移动端兼容性非常差,mouse类事件基本无法使用
移动端大部分事件是靠手指完成的,所以它独有手指事件
- 单手指事件:touchstart touchmove touchend touchcancel
- 多手指事件:gesturestart gestuerchange gestuerend…..

移动端还有很多事件是靠硬件完成的:手机传感器,陀螺仪,重力感应器等
移动端兼容click事件,它把click当做单击使用(会有300MS的延迟事件)

事件对象

对以下事件绑定的严谨版描述:基于DOM0级事件的绑定方式,给box的click事件绑定了一个方法。当手动触发click事件时,会把绑定的方法执行
box.onclick=function(e){
arguments[0]===e;//=>e就是事件对象
e=e||window.event;(兼容版获取事件对象的写法)
};

当元素的某个事件行为被触发,不仅会把之前绑定的方法执行,还会给绑定的方法传递一个值(浏览器默认传递的),我们把传递的这个值称为事件对象:(标准浏览器)
- 这个值是个对象类型的值,里面存储了很多的属性和方法,
- 这些存储的值都是当前本次操作的基本信息,例如:鼠标位置,触发行为类型。。。。【只和本次操作有关,一个页面中只有一个事件对象

IE6-8下关于事件对象的机制
- 方法被触发执行时,浏览器并没有把事件对象当做值传递给函数;(e是undefined)
- IE6-8下的事件对象需要我们通过window.event来获取

鼠标事件对象(mouseevent)
  • clientX/clientY:当前鼠标触发点距离当前窗口左上角的X/Y坐标
  • pageX/pageY:当前鼠标触发点距离BODY左上角的X/Y坐标(IE6-8中没有这两个属性)
    兼容处理
  box.onckick=function(e){
        if(typeof e==='undefined'){
        e=window.event;   //->处理e
        //->pageX/pageY
        e.pageX=e.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft);
        e.pageY=e.clientY+(document.documentElement.scrollTop||document.body.scrollTop);
        //->target
        e.target=e.srcElement;
        //->preventDafault
        e.preventDefault=function(){
            e.returnValue=false;
        };
        //=>stopPropagation
        e.stopPropagation=function(){
            e.cancelBubble=true
        }
        }
        }//该兼容比较完整,当处理一个简单兼容时可简单处理
  • type:当前触发事件的类型
  • target:事件源(当前鼠标操作的是那个元素)(IE6-8没有该属性,用srcElement这个属性代表事件源)
  • preventDefault:作用:阻止事件的默认行为;(IE6-8没有该方法,使用 returnValue=false处理)
  • stopPropagation:阻止事件的冒泡传播 [IE6-8不兼容,使用cancelBubble=true来处理]
键盘事件对象(keyboardEvent)
  • code:当前按得键盘按键是哪一个 【IE6-8没有这个属性】key属性和code一样,储存形式不一样
  • keyCode:存储的是当前按键的码值(大部分按键都有自己的码值)
  • which:和keyCode一样,也是当前按键的码值(which不兼容IE6-8)
  • 这几个码值需要记住(删除:8 、 回车:13、 空格:32、 tab :9、)
手指事件对象(TouchEvent)

touches&&changedTouches&&targetTouches:存储的是当前屏幕上每一个手指操作的位置信息
- touches:只有手指在屏幕上我们才能获取相关信息,手指离开后,这样touchend事件中我们就无法通过touches获取手指信息
- changedTouches:手指在屏幕上时,和touches一样可以获得相关信息,手指离开后也可以记录手指离开屏幕一瞬间所在的位置信息(最常用的)

touchesEvent
    type:'touchStart',
    target:事件源,
    touches:
    0:{
    clientX:xxx,
    clientY:xxx,
    pageX:xxx,
    pageY:xxx,
    .....
    }
    ...
    lentth:1
    chan
使用touchstart,touchmove,toucheend来处理移动端click事件300ms延迟问题

简单处理:

box.ontouchend=function(){
//不管你怎么操作,我只需要知道,手指离开就算点击即可,存在一些问题:
//1、手指按住不离开,超过750MS,应该时长按,不是点击,手指离开不应该按照点击处理
//2、手指在屏幕上滑动了,此时应该时滑动,不应该按照点击处理
}

详细处理

let oBox = document.querySelector('#box');
oBox.ontouchstart = function (e) {
   let point = e.changedTouches[0];
   //=>记录当前手指的起始坐标位置(记录在当前元素的自定义属性上)
  this.strX = point.pageX;
    this.strY = point.pageY;
 this.isMove = false;
};
oBox.ontouchmove = function (e) {
   let point = e.changedTouches[0];
  //=>一般我们手指操作,都会或多或少的发生一些偏移(习惯性偏移),此时不应该算作滑动,只有滑动的距离超出一定范围,我们按照滑动处理即可(一般都是把10px作为偏差值)
    let changeX = point.pageX - this.strX,
     changeY = point.pageY - this.strY;
    this.changeX = changeX;
  this.changeY = changeY;
   if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {
       this.isMove = true;
   }
};
oBox.ontouchend = function (e) {
    let point = e.changedTouches[0];
   //=>手指离开的时候:验证是否发生滑动
  if (!this.isMove) {
        //=>点击操作
        console.log('我是点击操作~~');
        return;
    }
   //=>滑动操作
  let dir = null;
    if (Math.abs(this.changeX) > Math.abs(this.changeY)) {
      //=>左右滑动
       dir = this.changeX < 0 ? 'LEFT' : 'RIGHT';
    } else {
       //=>上下滑动
        dir = this.changeY < 0 ? 'UP' : 'DOWN';
    }
   console.log(`当前手指滑动的方向为:${dir}`);
};

手机移动端的点击、长按、滑动等都是基于内置的原生的touchstart/touchmove/touchend事件模拟出来的一些效果,没有现成的事件
市场上有许多成熟的类库,我们直接使用即可
1、fastclick.js:目的就是解决了移动端click事件300ms的延迟问题
2、百度云touch手指事件库
3、hammer.js
4、zepto.js:提供移动端事件操作的板块,也是目前市场上使用率最高的

阻止事件的默认行为
A标签的默认行为及阻止
  • A标签有哪些默认行为

    1.超链接:点击A标签可以实现页面的跳转
    2、锚点定位:通过HASH值定位到当前页面的指定ID盒子位置
    - 首先URL地址栏末尾追加了一个HASH;
    - 如果地址栏包含hash值,浏览器在渲染页面后,会默认定位到hash值的位置


真实项目中我们想用A标签做一个普通按钮(优势:它的:hover样式是兼容所有浏览器的)此时,要把之前提到的两个默认行为阻止掉才可以
  • 阻止A标签的默认行为
    • 在HTML中阻止默认行为(常用)
      • <a href='javascript:;'>;

    • 在JS中阻止
      • 单点击A标签时 首先会触发click事件
      • 其次按照href中的地址页面进行跳转
        link.onclick=function(){ return false;}//阻止页面行为
        link.onclick=function(e){
        >e=e||window.event;
        >e.preventDefault?e.preventDefault():e.returnValue=false;
        >}

事件的传播机制

事件传播有三个阶段

Event.prototype:

  - 0  NONE:默认值,不代表任何意思
  - 1  `CAPTURING_PHASE  捕获阶段`
  - 2 ` AT_TARGET  目标阶段(当前事件源)`
  - 3  `BUBBLING _PHASE :冒泡阶段`

当前元素的某个事件行为被触发,它的所有的祖先元素(一直到document)的相关事件行为也会被触发执行(由里向外),我们把这种传播机制叫做冒泡传播

mouseover和mouseenter事件的区别
mouseover:鼠标滑到元素上:存在事件的冒泡传播机制 mouseenter:鼠标进入元素里;浏览器阻止了它的冒泡传播机制; 不同点
区别mouseovermouseenter
区别 1存在冒泡传播机制冒泡传播机制被浏览器阻止
区别 2当鼠标从父元素进入到子元素时,首先会触发父元素的mouseout事件,再触发子元素的mouseover事件,由于冒泡传播机制,导致父元素的mouseover事件也被触发当鼠标从父元素进入到子元素时,并不会触发父元素的mouseout事件,但是触发了子元素的mouseenter事件,由于浏览器阻止了它的冒泡传播,所以父元素的该事件不会被触发
事件委托(very important)
`原理`:利用事件的冒泡传播机制完成(mouseenter不存在冒泡传播)

当一个容器内的很多元素都要为同一事件绑定方法,那么我们只需要给外层容器的该事件绑定方法,当里层元素的事件被出发时,会通过冒泡传播机制传到最外层容器那里,触发外层容器绑定的方法执行,在方法执行时,我们只需要根据判断事件源的不同而做不同的事情。(利用事件委托可提高50%左右的性能)

拖拽当中鼠标焦点丢失问题
在拖拽登陆窗口的案例中(11.25) 当鼠标移动速度过快时,鼠标离开了h3,因为盒子跟不上奔跑的速度,所以导致以下几个问题 - 鼠标在h3之外飞,不会触发H3的mousemove,盒子就不动了; - 鼠标在h3之外抬起,也不会触发h3的mouseup,那么原有绑定的dragmove无法被移除,鼠标重新进入h3,此时不管鼠标是否按下,都会触发H3的dragmove; `原因:鼠标飞出h3,再怎么操作就和h3没有关系` 解决方案: 1.把mouseup和mousemove事件绑定给document,原因:不管鼠标怎么飞,都飞不出document;(使用箭头函数)只要你鼠标还在文档中,那么mousemove和mouseup永远生效 2.setCapture(不兼容谷歌,支持Ie)

DOM2事件绑定

    //=>标准浏览器
    oBox.addEventLister('click',function(e){
    //this:obx
    },false)
    //false=>让事件在冒泡传播时执行
   //true=>让事件在捕获阶段执行(非常少见)
//=>IE6-8浏览器
        oBox.attachEvent('onclick',function(e){
        //e:事件对象,不同于DOM0级事件,浏览器会默认将事件对象传递进来,与window.event的值相同,因此对于:pageX/pageY/target...等依旧存在兼容;
        })
        //=>绑定的方法都是在冒泡传播阶段执行
DOM1?

在DOM第一代升级迭代的时候,事件绑定依然沿用DOM0绑定的方式..so…

DOM0于DOM2事件绑定的区别
DOM0事件绑定的原理
  • 给当前元素的某一私有属性(onXXX)赋值的过程;(之前属性默认值是null,如果我们赋值了一个函数,就相当于绑定了一个方法)
  • 当我们赋值成功(赋值一个函数),此时浏览器会把DOM元素和赋值的的函数建立关联,以及建立DOM元素的行为监听,当某一行为被用户触发,浏览器会把赋值的函数执行;
DOM0事件绑定的特点
  • 只有DOM元素天生拥有这个私有属性(onxxx事件私有属性),我们赋值的方法才叫事件绑定,否则属于设置自定义属性
  • 移除事件绑定的时候,我们只需要赋值为null;
  • 在DOM0事件绑定中,只能给当前元素的某一个事件行为绑定一个方法,绑定多个方法,最后一次的绑定的会替换前面绑定的
DOM2事件绑定的原理
  • DOM2事件绑定使用的 addEventListener/attachEvent方法都是在eventTarget这个内置类的原型上定义的,我们调用的时候,首先要通过原型链找到这个方法,然后执行完成事件绑定的效果
  • 浏览器会给当前元素的某个事件行为开辟一个事件池(事件队列)【浏览器有一个统一的事件池,每个元素绑定的行为都放在这里,通过相关标志区分】,当我们通过 addEventListener/attachEvent进行事件绑定的时候,会把绑定的方法放在事件池中
  • 当元素的某一行为被触发,浏览器回到对应事件池中,把当前放在事件池的所有方法按序依次执行

    特点

  • 所有DOM0支持的行为,DOM2都可以用,DOM2还支持DOM0没有的事件行为(这样说比较笼统)
    (核心)【浏览器会把一些常用事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定,DOM2:凡是浏览器给元素天生设置的事件在DOM2中都可以使用】
    例如:onDOMContentLoaded(所有的DOM0和IE6-8的DOM2都不支持)

    onDOMContentLoaded//当前浏览器中的DOM结构加载完成,就会触发这个事件

  • DOM2中可以给当前元素的某一事件行为绑定多个不同方法(因为绑定的所有方法都放在事件池中);

  • 事件的移除:事件类型、绑定的方法、传播阶段三个完全一致,才可以完成移除(因此在绑定方法时,尽量不要用匿名函数,否则不好移除)
//=>ON:给当前元素的某个事件绑定某个方法
   var on = function (curEle, type, fn) {
   if (document.addEventListener) {
       //=>标准浏览器
        curEle.addEventListener(type, fn, false);
       return;
   }
  //=>IE6~8
  curEle.attachEvent('on' + type, fn);
};
//=>OFF:移除当前元素某个事件绑定的某个方法
13.var off = function (curEle, type, fn) {
14.    if (document.removeEventListener) {
15.        curEle.removeEventListener(type, fn, false);
16.        return;
17.    }
18.    //=>IE6~8
19.    curEle.detachEvent('on' + type, fn);
20.};

xxx.removeEventLister('click',function(){},false)

==DOM0和DOM2绑定的方法是毫无联系的(因为是两套完全不同的机制),即使绑定的方法相同,也是执行两次,谁先绑定,就先执行谁==

window.onload&&$(document).ready()的区别

window.onload:当浏览器中的所有资源(DOM结构、文本内容、图片)都加载完成,触发load事件;
- 它是基于DOM0的事件绑定机制完成的,所以在同一页面中只能为他绑定一个方法,绑定多个,以最后一个为主;
- 如果想在一个页面中使用多次,应该是基于DOM2绑定的;

function fn1(){
}
  function fn1(){
}
window.addEventListener('load',fn1,false);
window.addEventListener('load',fn2,false);

$(function(){})或者$(document).ready(function(){});
当文档中的DOM结构加载完成,就会触发执行,在一个页面中可以使用多次
- JQ中提供的方法,JQ是基于onDOMContentLoaded这个事件完成操作的
- JQ中的事件绑定都是基于DOM2事件绑定的
- onDOMContentLoaded在IE6-8下attachEvent也是不支持的,JQ在IE6-8下使用readystatechange来完成

DOM2事件的兼容处理

语法上的兼容
标准: curEle.addEventListener(’type’,fn,false);
IE6-8: curEle.attachEvent(’on’+type,fn);
顺序问题
标准:按照绑定的顺序依次执行
IE6-8:当事件行为被触发,IE6-8执行是乱序

重复问题
标准:可以自动去重,已经存在的方法不允许再次添加
IE6-8:在向事件池中增加方法时没有去重机制
this问题
标准:当事件行为被触发,方法中的this指向当前元素本身
IE6-8:当事件行为被触发,方法中的this指向window

IE6-8事件池机制vs标准浏览器事件池机制(比较规范的回答)

1、向事件池中添加方法时,标准浏览器是使用addEventListener,IE6-8使用的是attachEvent;而且标准浏览器有自动去重的机制,已经添加的方法不允许再次添加。IE6-8没有去重机制;
2、浏览器执行事件池中的方法时,不仅把方法执行,还把事件对象当作实参传递给给对应的方法,但是也是有区别的,IE6-8传递的事件对象的值和window.event是相同 的,因此存在兼容问题;
3.当事件行为被触发,标准浏览器是依次执行,方法中的this指向当前元素;IE6-8下,是乱序执行,且方法中的this指向window;

不兼容的本质:IE6-8低版本浏览器对于他的内置事件池处理机制的不完善导致的。
DOM2事件绑定兼容处理机制原理:自己创建一个类似于标准浏览器的自定义事件池(针对IE6-8)
Alt text

  • on :手动创建一个自定义事件池,把需要绑定的方法全部存在自定义事件池中
  • off: 把不需要绑定的方法从自定义事件池中移除
  • run:把run放在内置事件池中,当行为触发,需要浏览器把run执行,在run中把自定义事件池的方法执行。
//向事件池追加方法
var on = function (curEle, type, fn) {
    //标准浏览器无需处理
    if (document.addEventListener) {
        curEle.addEventListener(type, fn, false);
        return;
    }
    //创建自定义事件池:创建到当前元素的自定义属性上,不受作用域限制,以后在其他方法中需要使用,可直接获取(不放在全局是防止全局污染)

    // 每一个事件都有一个独立的事件池
    if (typeof curEle[type + 'pond'] === 'undefined') {
        curEle[type + 'pond'] = [];
        //只要执行on,就说明当前方法已被绑定
        //把run放到内置事件池中;
        // curEle.attachEvent('on'+type,run);//需要保证run中的 this是当前元素
        curEle.attachEvent('on' + type, function () {
            run.call(curEle, e);//将run放在一个匿名函数中,强制改变this
        });
    }

    var aryPond = curEle[type + 'pond'];
    // 去重操作,当前事件池中是否有当前事件
    for (var i = 0; i < ary.length; i++) {
        if (aryPond[i] === fn) {
            return;
        }
    }
    aryPond.push(fn);
};

//移除事件池的某个方法
var off = function (curEle, type, fn) {
    if (document.removeEventListener) {
        curEle.removeEventListener(type, fn, false);
        return;
    }

    var aryPond = curEle[type + 'pond'];
    if (!aryPond) return;
    for (var i = 0; i < ary.length; i++) {
        if (aryPond[i] === fn) {
            // aryPond.splice(i,1);此方法会导致原始数组索引改变,此时正在执行的run循环,索引和想要的索引不一致,数组塌陷
            //我们不能让当前数组的索引改变;
            aryPond[i] = null;//置空,索引没变

        }

    }

};


//把自定义事件池中的方法依次执行
var run = function (e) {
    // this=>curEle
    //e:window.event,不兼容,需要处理
    if (typeof e.target === 'undefined') {
        e.target = e.srcElement;
        e.which = e.keyCode;
        e.pageX = e.clientX + document.documentElement.scrollLeft || document.body.scrollLeft;
        e.stopPropagation = e.cancelBubble = true;
        e.preventDefault = e.returnValue = false;
    }


    var aryPond = curEle[type + 'pond'];
    if (!aryPond) return;
    for (var i = 0; i < aryPond.length; i++) {
        var itemFn = aryPond[i];
        // itemFn();=>这样执行itemFn里的this时window,须将他替换成函数里的this
        if (itemFn === null) {
            //当前这一项在执行的时候,可以将off中置空的那一项删除
            aryPond.slice(i, 1);
            i--;
            continue;
        }
        itemFn.call(this, e);
    }

};
Bind方法的封装
//ES5封装  BIND
   Function.prototype.myBind = function myBind(context) {
        //this=>当前需要处理的函数
        //context=>我们需要预先改变的this值(如果不传,默认window);
        //arguments=>存储包含context在内的所有实参;
        context = context || window;
        var outerArg = Array.prototype.slice.call(arguments, 1);
        var _this = this;
        if('bind' in this){//如果不是IE6-8,直接返回
           return _this.bind.apply(_this,arguments) ;
        }

        return function () {
            var innerArg = Array.prototype.slice.call(arguments);
            outerArg = outerArg.contact(innerArg);
            _this.apply(context, outerArg);
        }
    }
//ES6方法
 Function.prototype.myBind = function myBind(context = window, ...outer) { //使用ES6中的剩余运算符
        if ('bind' in this) {
            return this.bind(arguments);
        }
        return function (...inner) {
            this.apply(context, outer.concat(inner));
        }
    };
  • 23
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
操作 HTML 元素和 CSS 样式是 JavaScript 中重要的 DOM 操作。以下是一些心得: 1. 获取元素:可以使用 `document.getElementById()`、`document.getElementsByClassName()`、`document.getElementsByTagName()` 和 `document.querySelector()` 等方法来获取 HTML 元素。这些方法都返回一个元素对象,可以使用这个对象来操作元素。 2. 操作元素属性:可以使用 `element.attribute` 或 `element.getAttribute()` 来获取元素属性,使用 `element.attribute = value` 或 `element.setAttribute(attribute, value)` 来设置元素属性。例如,可以使用 `element.style.backgroundColor` 或 `element.style["background-color"]` 来设置元素的背景颜色。 3. 操作元素内容:可以使用 `element.innerHTML` 或 `element.textContent` 来获取或设置元素的内容。 `innerHTML` 返回元素内部 HTML,而 `textContent` 返回元素内部文本。 4. 操作元素样式:可以使用 `element.style` 对象来设置元素样式。例如,可以使用 `element.style.backgroundColor` 或 `element.style["background-color"]` 来设置元素的背景颜色。也可以使用 `element.classList` 对象来添加、删除或切换元素的 CSS 类。 5. 动态创建元素:可以使用 `document.createElement()` 方法创建新的元素,使用 `element.appendChild()` 方法将新元素添加到文档中。例如,可以使用以下代码创建一个新的 `<div>` 元素,并将其添加到文档中: ```javascript var newDiv = document.createElement("div"); newDiv.innerHTML = "This is a new div!"; document.body.appendChild(newDiv); ``` 6. 事件处理程序:可以使用 `element.addEventListener()` 方法来添加事件处理程序。例如,可以使用以下代码在单击按钮时显示警告框: ```javascript var button = document.getElementById("myButton"); button.addEventListener("click", function() { alert("Button clicked!"); }); ``` 以上是一些基本的操作 HTML 元素和 CSS 样式的技巧和心得。在实际开发中,可以根据具体需求采用不同的方法和技巧来操作 DOM

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值