个人自学前端21-JS14-DOM和事件对象事件流

DOM和事件对象事件流

一 DOM操作

js 提供了操作页面元素的API。不过这些API不是ES的内容。DOM由w3c组织提供标准。

1.1 什么是DOM

DOM:document object model。文档对象模型。

DOM是页面元素的一种组织方式。它有点类似一个“多维数组”。

从结构上,它有点类似于一棵倒过来的树,因此DOM也称为DOM树。

DOM树的顶层对象是document对象。

DOM中的内容就是html文档的内容,DOM中的内容称之为DOM节点。

1.2 什么是节点

html文档中的所有内容存储在DOM树种,每一项内容,都是DOM的一个节点。

html文档中出现最多的内容是标签,但是标签只是节点的其中一种。

节点都是对象,属于Node类,每个节点都是Node类的一个实例。

例如:

网页上的一个注释也是一个节点,但是注释不是标签。

文档声明,也是一个节点,但是它不是标签。

节点的类型有12种。但是常见和常用的有以下几种:

document,属性节点,文本节点,注释节点,元素(标签)节点。

节点是一个 js 对象,注意,不是纯对象。节点对象有3个常用属性。

1:nodeName

2:nodeValue

3:nodeType

  1. 关于nodeType,需要记忆的:

    元素节点的nodeType是1

    属性节点的nodeType是2

    文本节点的nodeType是3

    注释节点的nodeType是8

    document节点的nodeType是9

    doctype节点的nodeType是10

  2. 关于nodeName,需要记忆的是:

    元素节点的nodeName是全大写的标签名

    属性节点的nodeName就是属性名。

    document节点的nodeName是#document

  3. 关于nodeValue,需要记忆的是:

    文本节点,注释节点,属性节点的值可以通过nodeValue获取

1.3 创建节点

可以通过document.createElement(标签名)来创建元素节点。

可以通过document.createTextNode(文本内容)来创建文本节点。

可以通过document.createComment(注释内容)来创建注释节点。

1.4 插入节点

在父元素中插入任何节点到最后面:父元素.appendChild(子节点);

在父元素中插入任何节点到指定位置:父元素.insertBefore(新节点,老节点)

1.5 删除节点

在父元素中删除任何节点:父元素.removeChild(子节点);

1.6 查找节点

查找子节点:父元素.childNodes

查找子元素:父元素.children

查找属性节点列表:元素.attributes

查找父节点:子元素.parentNode

查找父元素:子元素.parentElement

查找兄弟节点:元素.previousSibling,元素.nextSibling

查找兄弟元素:元素.previousElementSibling,元素.nextElementSibling

二 事件对象

事件句柄是事件触发的函数。事件句柄是系统自动调用的。

系统在调用事件句柄的同时,会给事件句柄自动传入一个事件对象,这个事件对象可以获取当前事件的很多信息。

// 获取事件对象。
oBtn.onclick = function(ev){
	console.log(ev)
}

注意:只能通过事件句柄的形参获取事件对象,你必须非常清楚哪个是事件句柄!

事件对象常用属性:

ev.clientX => 鼠标事件的鼠标相对于视口的横坐标。

ev.clientY => 鼠标事件的鼠标相对于视口的纵坐标。

ev.keyCode => 键盘事件的键盘码。

ev.target => 事件源。(触发事件的标签对象)

三 事件流

什么是事件流? 事件流是一种事件的传递机制。

为什么需要事件传递?因为需要实现事件委托。

为什么要实现事件委托?事件委托性能更好。

事件流因事件传递的方向不同而分为两种:冒泡和捕获。

冒泡事件流由微软公司提出,事件会沿着DOM树,从事件源传递到document。

捕获事件流由网景公司提出,事件会沿着DOM树,从document传递到事件源。

任何浏览器都支持冒泡,但是IE不支持捕获。因此我们习惯使用冒泡事件流。

事件冒泡 + 事件捕获 => 事件流

3.1 事件委托

事件流的最大意义就是用于实现事件委托。

事件委托可以让程序性能更好。

事件委托为什么能够提升性能?

例如,我们有100个li,每个都需要添加事件,正常情况我们需要循环100次,给每个li添加事件。

如果后续新增了li,还需要给新增的li添加事件。这样性能不太好。

如果使用事件委托,则无需给所有的 li 添加事件,转而给 li 的父元素添加事件。

由于事件流的存在,点击 li,事件也会传递给父元素,从而触发父元素的事件句柄。

// 事件委托里,通过ev.target获取被点击的li之元素。
oUl.onclick = function(ev){
	ev.target.style.backgroundColor = 'red'
}
    // ev.target => 事件源.(事件的源头)
    // ev.target和this有时候是重合的.(绑定事件的标签和事件源是同一个标签时,重合)

事件委托注意事项:

1:给祖先元素添加事件,而不是给子元素添加事件。

2:通过ev.target获取子元素。

3:事件内this指向父元素,ev.target指向子元素。

4:应避免父元素触发事件句柄,因此需要判断事件源的标签类型。

3.2 阻止冒泡

有些时候事件冒泡会给我们带来麻烦。如果需要阻止冒泡:

1:ev.stopPropagation()

2:ev.cancelBubble = true (IE)

3.3 阻止默认事件

浏览器有很多的默认事件,例如右键事件。如果要禁止默认事件:

1:ev.preventDefault()

2:return false

四 其他

4.1 动态获取和静态获取

  1. querySelectorAll => HTML5新增的DOM方法 => 静态获取 => 不会随着页面上的元素增减而变化

  2. 其他方法: => 动态获取 => 会随着页面上的元素增减而变化.
    getElementsByTagName
    getElementsByClassName
    children

4.2 鼠标移动

    const [oDiv] = document.querySelectorAll('div');

    // 事件对象常见的名字
    // e,ev,event

    document.onmousemove = function(ev) {
      // top变成鼠标纵坐标.
      oDiv.style.top = ev.clientY + 'px';
      // left鼠标横坐标.
      oDiv.style.left = ev.clientX + 'px';
    }

4.3 键盘事件

    oText.onkeydown = function(ev) {
      // 键盘码
      // 键盘上的每个按键都对应一个阿拉伯数字.
      // 可以通过keyCode获取这个数字.
      // console.log(ev.keyCode);
    }
// 组合键
    oText.onkeydown = function(ev) {
      // 如何按的是回车
      // if (ev.keyCode === 13) {
      //   oDiv.innerHTML += '<p>' + this.value + '</p>';
      // }

      // ev.ctrlKey => 按下ctrl键会变成true
      // ev.altKey => 按下alt键会变成true
      // ev.shiftKey => 按下shift键会变成true

      if (ev.keyCode === 13 && ev.ctrlKey) {
        oDiv.innerHTML += '<p>' + this.value + '</p>';
        // 清空文本框
        oText.value = '';
      }
    }

4.4 事件绑定

添加事件
1: 通过标签的事件属性onclick添加
2: 通过js对象的onclick属性添加
3: 绑定事件.addEventListener添加

    // 绑定事件。可以添加多个事件句柄。(工作中应该用这个方法添加事件)
    // 语法 => 标签.addEventListener(事件名, 事件句柄, 是否使用捕获事件流)
    oBtn.addEventListener('click', show);
	function show() {
      alert(100)
    }
    const [oBtn] = document.querySelectorAll('button');

    // addEventListener => 默认是冒泡事件流.
    // 第三个参数填true,就可以实现捕获事件流.
    // 捕获事件流的顺序 => 从顶层对象把事件传递给事件源.(根冒泡是相反的)

    oBtn.addEventListener('click', () => {
      console.log('按钮')
    }, true);

    document.body.addEventListener('click', () => {
      console.log('body')
    }, true);

    document.addEventListener('click', () => {
      console.log('document')
    }, true);
    let [bindBtn, unbindBtn] = document.querySelectorAll('button');
    // 绑定事件.
    bindBtn.addEventListener('click', show);
    function show() {
      alert(10000);
    }
    unbindBtn.addEventListener('click', () => {
      // 当bindBtn被点击时,不触发指定的回调函数.
      // 解绑的函数,必须和绑定时的函数是同一个函数.(必须同函数名来引用)
      bindBtn.removeEventListener('click', show);
    });
	// bindBtn.onclick = function() {
    //   alert(1000);
    // }

    // unbindBtn.onclick = function() {
    //   只要不赋值函数,都可以解绑on+事件
    //   bindBtn.onclick = null;
    // }

4.5 元素尺寸

元素尺寸 => 元素.clientWidth (width + padding); 元素.offsetWidth (width + padding + border), 元素.scrollHeight
元素偏移 => 元素.offsetLeft(Top);
滚动偏移 => 元素.scrollTop(Left);

窗口变化事件 => window.onresize
滚动条滚动事件 => 元素.onscroll

    // 这个div占页面的宽是多少?

    const [oDiv] = document.querySelectorAll('div');

    // width + padding => 内容尺寸
    console.log(oDiv.clientWidth);
    console.log(oDiv.clientHeight);

    // width + padding + border => 总体尺寸
    console.log(oDiv.offsetWidth);
    console.log(oDiv.offsetHeight);

    // 内容实际的高(有滚动条后)
    console.log(oDiv.scrollHeight);

    // 视口的宽
    console.log(document.documentElement.clientWidth);

    // BOM的属性.视口宽
    console.log(window.innerWidth);
    console.log(window.innerHeight);
    // 浏览器窗口的宽
    console.log(window.outerWidth);

    // 窗口尺寸变化事件
    window.onresize = function() {
      // 窗口尺寸变化事件,获取最新的视口
      console.log(document.documentElement.clientWidth);
    }
  1. 例子:拖拽登录窗
<style>
    div{
      width: 200px;
      height: 200px;
      background-color: red;
      position: absolute;
    }
    *{
      width: 0;
      margin: 0;
    }
</style>
<body>
  <div></div>

  <script>
    const [oDiv] = document.querySelectorAll('div');

    // oDiv的样式应该怎么设置
    // 为什么是给document添加mousemove
    // 为什么document的mousemove事件要写在oDiv的mousedown事件里面.

    oDiv.addEventListener('mousedown', function(ev) {
      // 鼠标按下时,鼠标距离div左边的距离
      let disX = ev.clientX - oDiv.offsetLeft;
      // 鼠标按下时,鼠标距离div上边的距离
      let disY = ev.clientY - oDiv.offsetTop;

      // 鼠标移动事件
      document.onmousemove = function(ev) {
        // 移动时,oDiv的left变成移动的横坐标 - disX
        // 移动时,oDiv的top变成移动的纵坐标 - disY
        oDiv.style.left = ev.clientX - disX + 'px';
        oDiv.style.top = ev.clientY - disY + 'px';
      };

      // 鼠标松开时,把鼠标移动事件清除.
      document.addEventListener('mouseup', function() {
        document.onmousemove = null;
      });

    });
  </script>
</body>
  1. 例子:回到顶部,回到底部
      // 设为0就是回到顶部.
      // document.documentElement.scrollTop = 0;

      const html = document.documentElement;
	  // 回到底部.
      html.scrollTop = html.scrollHeight - html.clientHeight;

	  // 判断是否触底
	  if (this.scrollTop >= this.scrollHeight - this.clientHeight) {}

	  // 一直触底
	  oUl.scrollTop = oUl.scrollHeight - oUl.clientHeight;

五 BOM

ES => 语法标准 ECMA国际提供的标准
DOM => W3C提供的标准。
BOM => 没标准.(不同浏览器的方法没有统一,兼容性问题)

document对象的是DOM的api
window对象的是BOM的api

location => 网址对象
navigator => 客户端对象
history => 历史记录对象

	// 网址对象
    console.log(window.location);

    // 网页的完整地址
    console.log(window.location.href);
    // 哈希值.(锚点带#的部分)
    console.log(window.location.hash);
    // 端口
    console.log(window.location.port);
    // 主机
    console.log(window.location.host);
    // 源
    console.log(window.location.origin);

    const [oBtn] = document.querySelectorAll('button');

    oBtn.onclick = function() {
      // 刷新网页。(F5)
      location.reload();
    }


    // http://127.0.0.1:5500/12.BOM.html

    // 网页url
    // 1:orgin(源) => a:协议,b:ip(域名hostname),c:端口 => http://127.0.0.1:5500
    // 2:文件路径 => /12.BOM.html

    // 客户端对象。
    // console.log(navigator);

    if (navigator.userAgent.search('Chrome') != -1) {
      alert('这是一个谷歌内核的浏览器');
    } else {
      alert('这不是一个谷歌内核的浏览器');
    }
    // 历史记录 => 访问过的网页,会存储到一个"页面栈"中.
    // 前进后退功能,就是在浏览器页面栈中的网页.
    // ['考试云平台', '菜鸟教程' ,'ES6']

    // 历史记录对象
    console.log(history);
    // 页面栈中的页面个数.
    console.log(history.length);

    // 前进
    // history.forward();
    // 后退
    // history.back();
    // 指定跳转.前进2页
    // history.go(2);
    // 后退两页
    // history.go(-2);
    // 后退1页(和back作用一致);
    // history.go(-1);
    // 前进1页(forward作用一样.);
    // history.go(1);
    // 刷新(location.reload作用一样);
    // history.go(0);
<body>
  <button>跳转到01页面</button>
  <button>关闭01页面</button>
  <script>
    const [oBtn, oClose] = document.querySelectorAll('button');

    // oBtn.addEventListener('click', function() {
    //   有些浏览器禁止修改href.
    //   location.href = 'http://127.0.0.1:5500/01.%E6%80%BB%E7%BB%93.html';
    // });

    let newWin = null;

    oBtn.addEventListener('click', function() {
      // 新建一个窗口打开指定的地址
      // window.open('01.总结.html');

      // 第二个窗口默认可以设置是新建标签页打开还是当前页面打开
      // _blank => 空白标签页打开 (默认值)
      // _self => 当前页面打开
      // window.open('01.总结.html','_self');

      // 返回新打开的窗口对象.
      newWin = window.open('01.总结.html');
      // 给新窗口创建一个全局变量.
      newWin.x = 200;
    });

    oClose.addEventListener('click', function() {
      console.log(newWin);
      // 关闭当前窗口
      window.close();
      // 关闭新窗口
      // newWin.close();
    })
  </script>
</body>
posted @ 2021-07-26 16:23  暗鸦08  阅读(130)  评论(0)    收藏  举报