zoukankan      html  css  js  c++  java
  • 使用原生 JavaScript 操作 DOM

    微软官方放弃了 IE10-,所以现在可以放心使用原生 JavaScript 操作 DOM 了。

    文章针对如下几个点进行介绍:

    1. 查询修改 DOM。
    2. 修改类和特性。
    3. 事件监听
    4. 动画

    一、查询 DOM

    1.1 .querySelector()

    使用 CSS 选择器获取元素(一个),是网页中符合查询条件的元素快照,不是即时的。

    const myElement = document.querySelector('#foo > div.bar');

    1.2 .matches()

    元素是否匹配指定选择器?

    myElement.matches('div.bar') === true

    1.3 .querySelectorAll()

    .querySelector() 使用 CSS 选择器获取元素(多个),是网页中符合查询条件的元素快照,不是即时的。

    const myElements = document.querySelectorAll('.bar');

    1.4 在 HTMLElement 元素上使用

    .querySelector()/.querySelectorAll() 不仅可以在 document 上使用,还可以在 HTMLElement 元素上使用。

    const myChildElemet = myElement.querySelector('input[type="submit"]');
    
    // 等同于
    // document.querySelector('#foo > div.bar input[type="submit"]');

    1.5 .getElementsByTagName()

    根据标签来查询元素,是即时的。

    // HTML
    <div></div>
    
    // JavaScript
    const elements1 = document.querySelectorAll('div')
    const elements2 = document.getElementsByTagName('div')
    const newElement = document.createElement('div')
    
    document.body.appendChild(newElement)
    elements1.length // 1
    elements2.length // 2

    二、操作 NodeList

    .querySelectorAll() 查询的结果是 NodeList 类型的,没有法使用数组方法(比如 .forEach() 方法),所以需要:

    1. 把 NodeList 元素装换成数组。
    2. 借用数组的方法。

    2.1 把 NodeList 元素装换成数组。

    Array.prototype.slice.call(myElements).forEach(doSomethingWithEachElement);
    
    // 或者使用 ES6 方法 `Array.from()`
    
    Array.from(myElements).forEach(doSomethingWithEachElement);

    2.2 借用数组的方法

    Array.prototype.forEach.call(myElements, doSomethingWithEachElement);
    
    // 或者
    
    [].forEach.call(myElements, doSomethingWithEachElement);

    2.3 查询亲属

    每个 Element 元素还提供了查询亲属结点的只读属性。

    myElement.children
    myElement.firstElementChild
    myElement.lastElementChild
    myElement.previousElementSibling
    myElement.nextElementSibling

    Element 元素又继承自 Node,所以还拥有下面的属性:

    myElement.childNodes
    myElement.firstChild
    myElement.lastChild
    myElement.previousSibling
    myElement.nextSibling
    myElement.parentNode
    myElement.parentElement

    可以通过结点的 nodeType 属性值,确定结点类型。

    myElement.firstChild.nodeType === 3 // 判断是否为文本结点

    三、修改类和特性

    3.1 .classList API

    myElement.classList.add('foo')
    myElement.classList.remove('bar')
    myElement.classList.toggle('baz')
    // 获取元素的属性 `value` 的值
    const value = myElement.value
    
    // 设置元素的属性 `value` 的值
    myElement.value = 'foo'

    3.2 Object.assign()

    // 使用 `Object.assign()` 为元素同是设置多个属性
    Object.assign(myElement, {
      value: 'foo',
      id: 'bar'
    })
    
    // 删除元素属性
    myElement.value = null

    .getAttibute().setAttribute() 和 .removeAttribute() 会直接修改 HTML 特性,会引起浏览器重绘,代价高,不建议使用。如要永久更改 HTML,可以通过使用父元素的 .innerHTML 做到。

    3.3 添加 CSS 样式

    myElement.style.marginLeft = '2em';

    通过 .style 属性获得的属性值是没有经过计算的。要获取经过计算的值,使用 .window.getComputedStyle()

    window.getComputedStyle(myElement).getPropertyValue('margin-left');

    四、 修改 DOM

    4.1 .appendChild() 和 .insertBefore()

    // 将 element2 追加为 element1 的最后一个孩子
    element1.appendChild(element2);
    
    // 在 element1 的孩子 element3 之前插入 element2
    element1.insertBefore(element2, element3);

    4.2 .cloneNode()

    要插入一个克隆的元素,可以使用 .cloneNode() 方法。

    const myElementClone = myElement.cloneNode();
    myParentElement.appendChild(myElementClone);

    .cloneNode() 还可接收一个布尔值参数,true 表示深复制——元素的孩子也会被克隆。

    4.3 创建元素

    const myNewElement = document.createElement('div');
    const myNewTextNode = document.createTextNode('some text');
    myParentElement.removeChild(myElement);
    
    myElement.parentNode.removeChild(myElement);

    4.4 .innerHTML 和 .textContent

    每个元素都有属性 .innerHTML 和 .textContent(或者类似的 .innerText)。

    // 替换掉 myElement 内部的 HTML
    myElement.innerHTML = `
      <div>
        <h2>New content</h2>
        <p>beep boop beep boop</p>
      </div>
    `
    
    // 删除 myElement 元素的所有子节点
    myElement.innerHTML = null
    
    // 为 myElement 元素追加内部的 HTML
    myElement.innerHTML += `
      <a href="foo.html">continue reading...</a>
      <hr/>
    `

    为元素追加内部的 HTML 并不好,因为会丢失之前的已更改的所有属性和事件监听绑定。

    追加元素较好的方式是这样的:

    const link = document.createElement('a');
    const text = document.createTextNode('continue reading...');
    const hr = document.createElement('hr');
    
    link.href = 'foo.html';
    link.appendChild(text);
    
    myElement.appendChild(link);
    myElement.appendChild(hr);

    上面的追加代码会导致浏览器两次重绘,而不是 .innerHTML 的一次重绘。这时可以借助 DocumentFragment

    const fragment = document.createDocumentFragment();
    
    fragment.appendChild(text);
    fragment.appendChild(hr);
    myElement.appendChild(fragment);

    五、事件监听

    5.1 DOM 0 级

    myElement.onclick = function onclick (event) {
      console.log(event.type + ' got fired')
    }

    这种方式只能为某一事件添加一个事件处理函数。若想添加多个,可以使用 .addEventListener()

    5.2 DOM 3 级

    myElement.addEventListener('click', function (event) {
      console.log(event.type + ' got fired');
    })
    
    myElement.addEventListener('click', function (event) {
      console.log(event.type + ' got fired again');
    })

    在事件处理函数内部,event.target 指向触发事件的元素(或使用箭头函数里的 this)。

    5.3 阻止浏览器的默认行为

    使用 .preventDefault() 可以阻止浏览器的默认行为(比如点击超链接、提交表单时)。

    myForm.addEventListener('submit', function (event) {
      const name = this.querySelector('#name');
    
      if (name.value === 'Donald Duck') {
        alert('You gotta be kidding!');
        event.preventDefault();
      }
    })

    另外一个重要的方法是 .stopPropagation()——阻止事件冒泡至祖先结点。

    绑定监听事件时,还可以指定第三个参数:可选的配置对象/是否在捕获阶段触发事件的布尔值(默认 false,即在冒泡阶段触发事件)。

    5.4 .addEventListener() 可选的第三个参数

    target.addEventListener(type, listener[, options]);
    target.addEventListener(type, listener[, useCapture]);

    可选的配置对象有下列 3 个布尔值属性(默认都为 false):

    1. capture:为 true 时,表示在捕获阶段触发事件(即到达事件目标之前,会立即触发事假)。
    2. once:为 true 时,表示事件只能被触发一次。
    3. passive:为 true 时,会忽略 event.preventDefault() 代码,不会阻止默认行为的发生(通常会引起控制台发出警告)。

    这三个中,最经常使用的是 .capture,这样,就可以使用可选的表示“是否在捕获阶段触发事件的布尔值”替代“可选的配置对象”了。

    // 在捕获阶段触发事件
    myElement.addEventListener(type, listener, true);

    5.5 .removeEventListener()

    删除事件监听使用 .removeEventListener()。比如可选的配置对象的 once 属性可以这样实现:

    myElement.addEventListener('change', function listener (event) {
      console.log(event.type + ' got triggered on ' + this);
      this.removeEventListener('change', listener);
    })

    六、事件代理

    这是一个很有用的模式。现在有一个表单,当表单元素里的输入框发生 change 事件时,我们要对此监听。

    myForm.addEventListener('change', function (event) {
      const target = event.target;
      if (target.matches('input')) {
        console.log(target.value);
      }
    })

    这样的一个好处是——即是元素中的输入框个数发生了改变,也不会影响监听事件起作用。

    七、动画

    使用 CSS 原生的动画效果已经很好了(通过 transition 属性和 @keyframes),但如果需要更加复杂的动画效果,可以使用 JavaScript。

    JavaScript 实现动画效果,主要有两种方式:

    1. window.setTimeout():在动画完成后,停止对 window.setTimeout() 的调用,但可能会出现动画不连续。
    2. window.requestAnimationFrame()
    const start = window.performance.now();
    const duration = 4000;
    
    window.requestAnimationFrame(function fadeIn (now) {
      const progress = now - start;
      myElement.style.opacity = progress / duration;
    
      if (progress < duration) {
        window.requestAnimationFrame(fadeIn);
      }
    })

    八、写辅助方法

    第一种方式:

    const $ = function $ (selector, context = document) {
    
        const elements = (selector, context = document) => context.querySelectorAll(selector);
        const element = elements[0];
    
        return {
            element,
            elements,
    
            html (newHtml) {
              this.elements.forEach(element => {
                element.innerHTML = newHtml;
              })
    
              return this;
            },
    
            css (newCss) {
              this.elements.forEach(element => {
                Object.assign(element.style, newCss);
              })
    
              return this;
            },
    
            on (event, handler, options) {
              this.elements.forEach(element => {
                element.addEventListener(event, handler, options);
              })
    
              return this;
            }
        };
    
    };

    第二种方式:

    const $ = (selector, context = document) => context.querySelector(selector);
    const $$ = (selector, context = document) => context.querySelectorAll(selector);
    
    const html = (nodeList, newHtml) => {
      Array.from(nodeList).forEach(element => {
        element.innerHTML = newHtml;
      })
    }

    (完)

  • 相关阅读:
    水题大战Vol.3 B. DP搬运工2
    火题小战 C. 情侣?给我烧了!
    火题小战 B. barbeque
    火题小战 A.玩个球
    P6087 [JSOI2015]送礼物 01分数规划+单调队列+ST表
    NOI2020D1T1美食家
    Java 的随机数
    MySQL 配置文件的配置
    NOIP2020准(ge)备(zi)日记
    android开发EditText禁止输入中文密码的解决方法
  • 原文地址:https://www.cnblogs.com/libin-1/p/6672883.html
Copyright © 2011-2022 走看看