JavaScript前端

【前端 JavaScript WebAPI】 03 - 节点操作

2020-12-03  本文已影响0人  itlu

1. 节点操作

1.1 删除节点

  1. node.removeChild()方法从node 节点中删除一个子节点,返回删除的节点。
<body>
<button>删除</button>
<ul>
  <li>熊大</li>
  <li>熊二</li>
  <li>光头强</li>
</ul>
<script>
  let ul = document.querySelector('ul');
  let btn = document.querySelector('button');
  /**
   * 点击一下删除一个ul的节点
   */
  btn.onclick = function () {
    if (ul.children.length === 0) {
      alert('已经删除完了!');
    } else {
      ul.removeChild(ul.children[0]);
    }
  }
</script>
</body>

1.2 案例:删除留言

删除节点案例
<div class="content">
  <div class="edit" style="overflow: hidden;">
    <textarea name="" id=""></textarea>
    <button>发布</button>
  </div>
  <ul></ul>
</div>
  let button = document.querySelector('button');
  let textarea = document.querySelector('textarea');
  let ul = document.querySelector('ul');

  button.onclick = function () {
    if (textarea.value === '') {
      alert("您输入的内容为空!");
      return false;
    }
    let li = document.createElement('li');
    // 拿到文本域的内容
    li.innerHTML = textarea.value + "<a href='javascript:void(0);'>删除</a>";
    // 新发布的内容永远是第一个的前面
    ul.insertBefore(li, ul.children[0]);
    // 发送完成之后清空文本域
    textarea.value = '';

    /**
     * 删除留言操作
     * @type {ElementTagNameMap[string] | null}
     */
    // 获取 a
    let as = ul.querySelector('a');
    as.onclick = function () {
      ul.removeChild(as.parentNode);
    }
  };
删除留言的逻辑

1.3 复制(克隆)节点

node.cloneNode();
  1. node.cloneNode()方法返回调用该方法的节点的一个副本。也称为克隆节点、拷贝节点。
注意
  1. 如果括号参数为空或者为false,则是浅拷贝,即只克隆赋值节点本身,不克隆里面的子节点。

  2. 如果括号参数为true,则是深拷贝,会复制节点本身及里面的子节点。

<body>
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
<script>
  let ul = document.querySelector('ul');
  // 克隆一个ul的子节点放到最后
  // 参数为空或者里面是false则是浅拷贝 只复制标签不复制里面的内容
  let li = ul.children[1].cloneNode(true);
  ul.appendChild(li);
</script>
</body>

1.4 动态生成表格

  1. 因为对象数组中存储的学生信息是动态的,所以需要Js动态的生成表格。

  2. 根据数组中对象的个数循环创建表格的行。

  3. 根据每个对象中属性的个数循环创建表格的单元格个数。

 table {
      width: 500px;
      margin: 100px auto;
      border-collapse: collapse;
      text-align: center;
    }

    td,
    th {
      border: 1px solid #333333;
    }

    thead tr {
      height: 40px;
      background-color: #ccc;
    }
<table>
  <thead>
  <tr>
    <th>姓名</th>
    <th>科目</th>
    <th>成绩</th>
    <th>操作</th>
  </tr>
  </thead>
  <tbody>

  </tbody>
</table>
 let tbody = document.querySelector('tbody');

  // 创建需要渲染的数据
  let data = [
    {
      name: '张三',
      subject: 'JavaScript',
      score: 99
    }
  ];

  // 循环数据创建行
  for (let i = 0; i < data.length; i++) {
    let tr = document.createElement('tr');
    tbody.appendChild(tr);
    for (let key in data[i]) {
      let td = document.createElement('td');
      td.innerHTML = data[i][key];
      tr.appendChild(td);
    }
    // 循环遍历对象创建单元格
    let optTd = document.createElement('td');
    optTd.innerHTML = "<a href='javascript:void(0);'>删除</a>";
    // 将删除操作按钮添加到行中
    tr.appendChild(optTd);

    // 获取到该行中的 a
    let as = tr.querySelector('a');
    // 为a定义一个删除的单击事件
    as.onclick = function () {
      console.log(as.parentNode);
      tbody.removeChild(as.parentNode.parentNode)
    };

  }

  let trs = tbody.children;

  for (let i = 0; i < trs.length; i++) {
    trs[i].onmouseover = function () {
      this.style.backgroundColor = 'skyblue'
    };

    trs[i].onmouseout = function () {
      this.style.backgroundColor = ''
    }
  }

1.5 创建元素的三种方式

  1. document.write();

  2. element.innerHtml;

  3. document.createElement();

三种创建方式的区别
  1. document.write(); : 是直接将内容写入页面的内容流,但是文档流执行完毕之后使用,则导致页面重绘。

  2. element.innerHtml;:是将内容写入某个DOM节点,不会导致页面全部重绘。 创建多个元素效率高(不要使用字符串拼接的方式,因为这样会出现太多的字符串拼接操作,频繁的创建导致内存消耗,而是采用数组拼接的方式 array.push)。

  3. document.createElement();:创建多个元素效率 稍微低一点点,但是结构清晰。

  4. 总结:不同的浏览器下,innerHtml 效率要比createElement高。

<body>

<button>document.write()导致页面重绘</button>
<div>456</div>
<div class="inner"></div>
<div class="create"></div>

<script>

  // 1. document.write() 在文档中写一个标签 如果文档流执行完毕,会导致页面重绘
  let button = document.querySelector('button');

  /**
   * 这样将导致页面重绘
   */
  button.onclick = function () {
    document.write('123');
  }
  // 2. innerHtml 创建元素 拼接字符串需要重新开辟内存 比较耗时
  let inner = document.querySelector('.inner');
  // 使用拼接字符串的方式
  for (let i = 0; i < 100; i++) {
    inner.innerHTML += ' <a href="javascript:void(0);">hello</a> ';
    if (i % 25 === 0) {
      inner.innerHTML += '</br>'
    }
  }

  // 3. document.createElement() 在innerHtml使用字符串拼接的时候 该方式创建的效率高于 innerHtml的字符串拼接的方式
  let create = document.querySelector('.create');
  for (let i = 0; i < 100; i++) {
    let div = document.createElement('div');
    div.style.width = '20px';
    div.style.height = '20px';
    div.style.backgroundColor = 'pink';
    div.style.marginTop = '2px';
    create.appendChild(div);
  }
</script>
</body>

1.6 innerTHML和createElement效率对比

innerHtml使用拼接字符串创建元素效率测试
<div class="inner"></div>
<script>
  let inner = document.querySelector('.inner');
  let startTime = +new Date();
  /**
   * 使用拼接字符串的方式创建元素
   */
  for (let i = 0; i < 1000; i++) {
    inner.innerHTML += '<div style="width: 10px; height: 10px;border: 1px solid red;"></div>';
  }
  let endTime = +new Date();
  console.log((endTime - startTime)); // 700 ~ 800ms
</script>
</body>
测试使用createElement()追加元素的方式创建元素
 .innerDiv {
      width: 10px;
      height: 10px;
      border: 1px solid red;
    }
<body>
<div class="create"></div>
<script>
  let create = document.querySelector('.create');
  let startTime = +new Date();
  for (let i = 0; i < 10000; i++) {
    let div = document.createElement('div');
    div.setAttribute('class', 'innerDiv');
    create.appendChild(div);
  }
  let endTime = +new Date();
  console.log((endTime - startTime)); // 10 ~ 30ms
</script>
</body>
innerHtml使用数组push的方式创建元素
   .innerDiv {
      width: 10px;
      height: 10px;
      border: 1px solid red;
    }
<body>
<div class="inner"></div>
<script>
  let inner = document.querySelector('.inner');
  let startTime = +new Date();
  let arr = [];
  for (let i = 0; i < 10000; i++) {
    arr.push('<div class="innerDiv"></div>');
  }
  inner.innerHTML = arr.join('');
  let endTime = +new Date();
  console.log((endTime - startTime)); // 9 ~ 30ms
</script>
</body>

2. 事件高级

2.1 注册事件(2种方式)

给元素添加事件,称为注册事件或者绑定事件;
  1. 利用on 开头的事件onclick;

  2. // 1. 传统方式注册事件 btns[0].onclick = function () { alert('点我点我!'); };

  3. 特点:注册事件的唯一性。

  4. 同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数。

注册事件有两种方式:传统方式和监听注册方式。
  1. w3c标准,推荐方式。

  2. addEventListener它是一个注册事件的方法。

  3. IE9之前的IE不支持此方法,可以使用attachEvent代替。

  4. 特点:同一个元素同一个事件可以注册多个监听器。

  5. 按照注册顺序依次执行。

2.2 事件监听

addEventListener()事件监听(IE9以后支持)
eventTarget.addEventListener
  1. eventTarget.addEventListener()方法将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。

  2. 该方法接收三个参数 :

type:事件类型字符串,比如 clickmouseover,注意这里不要带on
listener:事件处理函数,事件发生时,会调用监听函数
useCaptrue:可选参数是一个布尔值,默认是false。确定事件流所在的阶段,默认是事件冒泡。true代表事件捕获。

attacheEvent()事件监听(IE678支持)
eventTartget.attacheEvent();
事件监听兼容性解决方案
  1. 封装一个函数,函数中判断浏览器的类型:
事件监听兼容性解决方案

2.3 删除事件(解绑事件)

传统注册方式解绑
eventTarget.onclick = null;
方法监听注册方式解绑
eventTarget.removeEventListener();
  1. 事件解绑代码案例:
<body>
<div>1</div>
<div>2</div>
</body>
 let divs = document.querySelectorAll('div');
  // 解绑事件
  divs[0].onclick = function () {
    alert('只能点我一次');
    divs[0].onclick = null;
  };

  divs[1].addEventListener('click', fn);

  function fn() {
    alert('我也只能点一次');
    // 解绑事件
    divs[1].removeEventListener('click', fn);
  }

2.4 DOM事件流

  1. html中的标签都是相互嵌套的,我们可以将元素想象成一个盒子装一个盒子,document 是最外面的大盒子。当你单击一个div时,同时你也单击了div的父元素,甚至整个页面。
  2. 那么是先执行父元素的单击事件,还是先执行div的单击事件 ???
事件流
  1. 描述的是从页面中接收事件的顺序。

  2. 事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流。

  1. 比如:我们给页面中的一个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document。
事件流
事件冒泡:IE最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到DOM 最顶层节点的过程。
事件捕获:网景公司最早提出,由DOM 最顶层节点开始,然后 逐级向下传播到最具体的元素接收过程。

当时的2大浏览器霸主谁也不服谁!IE 提出从目标元素开始,然后一层一层向外接收事件并响应,也就是冒泡型事件流。
Netscape(网景公司)提出从最外层开始,然后一层一层向内接收事件并响应,也就是捕获型事件流。
江湖纷争,武林盟主也脑壳疼!!!
最终,w3c 采用折中的方式,平息了战火,制定了统一的标准 —--— 先捕获再冒泡。
现代浏览器都遵循了此标准,所以当事件发生时,会经历3个阶段。

DOM 事件流会经历3个阶段:
  1. 捕获阶段;

  2. 当前目标阶段;

  3. 冒泡阶段。

  4. 举例: 我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点( 最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。

DOM事件流会经历的3个阶段

事件发生时会在元素节点之间按照特定的传播顺序传播,这个传播过程就是DOM事件流。

注意
  1. JS代码中只能执行捕获或者冒泡其中的一个阶段。

  2. onclickaddEventListener只能 得到冒泡阶段。

  3. addEventListener第三个参数是true表示 事件捕获阶段调用事件处理函数;如果是false(不写默认是false),表示在事件冒泡阶段调用事件处理程序。

  4. 实际开发中我们很少使用事件捕获,我们更加关注的是事件冒泡。

  5. 有些事件 是没有事件冒泡的,比如onblur,onfocus、onmouseover、onmouseleave。

  6. 事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事情。

  1. 代码案例 :
事件流的三个阶段
 .father {
      position: relative;
      width: 300px;
      height: 300px;
      background-color: purple;
      margin: 100px auto;
    }

    .son {
      position: absolute;
      width: 200px;
      height: 200px;
      line-height: 200px;
      text-align: center;
      color: #fff;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background-color: pink;
    }
<div class="father">
  <div class="son">
    son盒子
  </div>
</div>
// dom事件流的三个阶段

  // 1. JS代码中只能执行捕获或者冒泡其中的一个阶段
  // 2. onclick 和 attachEvent(ie)只能的到冒泡阶段
  // 3. 捕获阶段如果addEventListener第三个参数是true那么则处于捕获阶段
  // 事件捕获顺序: document -> html ->  body -> father -> son
  let father = document.querySelector('.father');
  let son = document.querySelector('.father');

  father.addEventListener('click', function () {
    alert('捕获阶段 : 您点了我!我是father');
  }, true);

  son.addEventListener('click', function () {
    alert('捕获阶段 : 我是son');
  });

  // 4. 冒泡阶段如果 addEventListener 第三个参数是 false 那么则处于冒泡阶段
  // 事件冒泡顺序: son -> father -> body -> html -> document
  father.addEventListener('click', function () {
    alert('冒泡阶段 : 我是father');
  }, false);

  son.addEventListener('click', function () {
    alert('冒泡阶段 : 我是son');
  }, false);

  document.body.addEventListener('click', function () {
    alert('冒泡阶段 : 我是body');
  }, false);
  // 有的事件是没有冒泡的: onblur / onfocus / onmouseenter / onmouseleave

2.5 事件对象

什么是事件对象
  1. 事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象。
比如:
  1. 谁绑定了这个事件。
  2. 鼠标触发事件的话,会得到鼠标的相关信息,如鼠标位置。
  3. 键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。
事件对象的使用
  1. 事件触发发生时就会产生事件对象,并且系统会以实参的形式传给事件处理函数。所以,在事件处理函数中声明1个形参用来接收事件对象。
<div>123</div>
  // 1. event 就是一个事件对象写在我们监听函数的小括号里面当形参来看

  // 2. 事件对象只有有了事件才会存在,它是系统给我们创建的,不需要我们传递参数
  let div = document.querySelector('div');

  div.onclick = function (ev) {
    // 兼容性处理
    ev = ev || window.event;
    console.log(ev);
  };

  /**
   * 默认是事件冒泡阶段
   */
  div.addEventListener('click', function (e) {
    console.log(e);
  });
事件对象的兼容性处理

事件对象本身的获取存在兼容问题:

  1. 标准浏览器中是浏览器给方法传递的参数,只需要定义形参 e 就可以获取到。

  2. 在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。

  3. 解决:

e = e || windows.event;
  • 只要“||”前面为false, 不管“||”后面是true 还是 false,都返回 “||” 后面的值。
  • 只要“||”前面为true, 不管“||”后面是true 还是 false,都返回 “||” 前面的值。
事件对象的属性和方法
事件对象的属性和方法
e.target 和 this 的区别
  1. this 是事件绑定的元素(绑定这个事件处理函数的元素);

  2. e.target 是事件触发的元素。

常情况下tergetthis是一致的,但有一种情况不同,那就是在事件冒泡时(父子元素有相同事件,单击子元素,父元素的事件处理函数也会被触发执行),这时候this指向的是父元素,因为它是绑定事件的元素对象,而target指向的是子元素,因为他是触发事件的那个具体元素对象。

<div>123</div>
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
let div = document.querySelector('div');

  div.addEventListener('click', function (ev) {
    console.log(ev.target);
    console.log(this);
    // 上面两者的区别
    // 1. ev.target 返回的是触发事件的对象 this 返回的是绑定事件的对象
    // 有一个和this非常相似的属性
    console.log(ev.currentTarget);
  });

  // 测试  ev.target 和 this的区别
  let ul = document.querySelector('ul');

  ul.addEventListener('click', function (ev) {
    // e.target 执行我们点击的那个对象谁触发这个事件
    console.log(ev.target);
    console.log(this);
    console.log(ev.currentTarget);
  });

2.6 阻止默认行为

html 中一些标签有默认行为,例如 a 标签被单击后,默认会进行页面跳转。

<a href="https://www.baidu.com">百度</a>

<form action="https://www.baidu.com">
  <input type="submit" value="提交">
</form>
  // 返回事件类型
  let div = document.querySelector('div');
  div.addEventListener('click', fn);

  function fn(e) {
    console.log(e.type);
  }

  // 2.阻止默认行为事件,让链接不跳转或者让提交按钮不提交
  let a = document.querySelector('a');
  a.addEventListener('click', function (ev) {
    // 阻止默认行为事件
    ev.preventDefault();
  });

  // 3. 阻止默认行为事件兼容性写法
  a.onclick = function (ev) {
    // 常用
    ev.preventDefault();
    // 低版本浏览器
    ev.returnValue;
    // 或者使用 return false
    return false; // 没有兼容性问题 但是 return 后面的代码无法执行 而且只限于传统的注册方式。
  }

2.7 阻止事件冒泡

事件冒泡本身的特性,会带来的坏处,也会带来的好处。
  1. 阻止事件冒泡的标准写法:
ev.stopPropagation(); // 停止传播 阻止事件冒泡
  1. 非标准写法:IE6~8利用事件对象 cancelBubble 属性:
ev.cancelBubble = true;
<div class="father">
  <div class="son">son</div>
</div>
  let father = document.querySelector('.father');
  let son = document.querySelector('.son');

  father.addEventListener('click', function (ev) {
    alert('我是father');
  });

  son.addEventListener('click', function (ev) {
    alert('我是son');
    ev.stopPropagation(); // 停止传播 阻止事件冒泡
    ev.cancelBubble = true;
  });
  // addEventListener 默认是 事件冒泡阶段 son -> father

  // 阻止事件冒泡兼容性解决方案
  if (e && e.stopPropagation) {
    e.stopPropagation();
  } else {
    window.event.cancelBubble = true;
  }
  1. 阻止事件冒泡的兼容性写法:
 // 阻止事件冒泡兼容性解决方案
  if (e && e.stopPropagation) {
    e.stopPropagation();
  } else {
    window.event.cancelBubble = true;
  }

2.8 事件委托

  1. 事件冒泡本身的特性,会带来的坏处,也会带来的好处。
什么是事件委托

把事情委托给别人,代为处理。

  1. 事件委托也称为事件代理,在jQuery 里面称为事件委派。

说白了就是,不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行。

js事件中的代理:
<ul>
  <li>点我点我</li>
  <li>点我点我</li>
  <li>点我点我</li>
  <li>点我点我</li>
  <li>点我点我</li>
</ul>
 // 事件委托的原理就是利用事件冒泡 ,将子节点的父节点设置事件监听器,由于存在事件冒泡,子盒子的触发事件会冒到父盒子。
  let ul = document.querySelector('ul');
  ul.addEventListener('click', function (ev) {
    for (let i = 0; i < ul.children.length; i++) {
      this.children[i].style.backgroundColor = '';
    }
    ev.target.style.backgroundColor = 'pink';
    alert('哈哈哈哈');
  });
事件委托的原理
给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素
事件委托的作用
  1. 我们只操作了一次 DOM ,提高了程序的性能。
  2. 动态新创建的子元素,也拥有事件。

3. 常用鼠标事件

常见的鼠标事件

3.1 案例:禁止选中文字和禁止右键菜单

  1. 禁止鼠标右键菜单:
  /**
   * 禁止右键菜单
   */
  document.addEventListener('contextmenu', function (ev) {
    // 禁用右键菜单
    ev.preventDefault();
  });
  1. 禁止鼠标选中事件:
  /**
   * 禁止选择
   */
  document.addEventListener('selectstart', function (ev) {
    ev.preventDefault();
  });

3.2 鼠标事件对象

  1. event事件对象是事件相关的一系列的信息集合。

  2. 现阶段我们主要用鼠标事件对象MouseEvent 和键盘事件对象 KeyboardEvent

鼠标事件对象
  body {
      height: 3000px;
    }
document.addEventListener('click', function (ev) {
    console.log(ev);
    console.log(ev.type);
    console.log(ev.clientX); // 这是鼠标相对于浏览器左缘的距离
    console.log(ev.clientY); // 这是鼠标相对于浏览器上缘的距离
    console.log('--------------------');
    console.log(ev.pageX); // 相对于文档页面的 x坐标 ie9 以上才支持
    console.log(ev.pageY); // 相对于文档页面的 y坐标 ie9 以上才支持
    console.log(ev.screenX); // 距离的是自己电脑屏幕左缘的距离
    console.log(ev.screenY);  // 距离的是自己电脑屏幕上缘的距离
  });

3.3 案例:跟随鼠标移动的天使

移动的小天使
img {
      position: absolute;
    }
img src="../images/angel.gif" alt="">
  let img = document.querySelector('img');

  document.addEventListener('mousemove', function (ev) {
    console.log(ev.pageY);
    console.log(ev.pageX);
    img.style.top = ev.pageY - 50 + 'px';
    img.style.left = ev.pageX - 50 + 'px';
  });
上一篇下一篇

猜你喜欢

热点阅读