再学JavaScript(四)

214 阅读13分钟

34. DOM

什么是DOM?

  1. DOM 》 Document Object Model。
  2. DOM 定义了表示和修改文档所需的方法。DOM 对象即宿主对象,有浏览器厂商定义,用来操作 HTML 和 XML 功能的一类对象的集合。也有人称 DOM 是对 HTML 以及 XML 的标准接口。
  3. Document 代表整个文档。

DOM 的基本操

1. 对 DOM 节点的增删改查

查询
  • 查看元素节点

    • document.getElementById(); 通过ID获取元素。(IE8及以下的浏览器是不区分大小写的)
    • .getElementsByTagName(); 通过标签名获取元素的集合放在类数组中
    • .getElementsByName(); 需注意,只有部分标签 name 可生效(表单、表单元素,img,iframe),目前各浏览器对其他标签兼容情况得到了优化,但是不常用
    • .getElementsByClassName(); 通过类名选取元素,IE8及以下IE版本中没有
    • .querySelector('div > span > ...'); css选择器,选择一个,在ie7及以下版本中没有
    • .querySelectorAll('div > span > ...'); css选择器,选择一组,在ie7及以下版本中没有

    注意^: querySelector 和 querySelectorAll 都是不推荐使用的,因为他们选择出来的都是不是实时的,是静态的是副本。(例如:n 个 div 删除其中一个,用getElement……选中的元素是 n - 1 个, 但是用queryS……依然是 n 个。

  • 遍历节点树

    • parentNode - 父节点(最顶端的parentNode 为 #document)

    • childNodes - 子节点们,会选出所有节点(下面会有节点类型的介绍)

    • firstChild - 第一个子节点

    • lastChild - 最后一个子节点

    • nextSiBling - 下一个兄弟元素节点

    • previousSiBling - 上一个兄弟元素节点

  • 基于元素节点树的遍历(除去 children外,其它在IE9及以下不兼容)

    • parentElement - 父元素节点(IE9及以下不兼容)
    • children - 子元素节点
    • node.childElementCount === node.children.length 当前元素节点的子元素个数(IE9及以下不兼容)
    • firstElementChild - 第一个元素子节点(IE9及以下不兼容)
    • lastElementChild - 最后一个元素子节点 (IE9及以下不兼容)
    • nextElementSiBling - 下一个兄弟元素 (IE9及以下不兼容)
    • previousElementSiBling - 上一个兄弟元素 (IE9及以下不兼容)
增加
  • document.createElement(); 创建元素节点
  • document.createTextNode(); 创建文本节点
  • document.createComment(); 创建注释节点
  • document.createDocumentFragment(); 创建文档碎片节点
插入
  • PARENTNODE.appendChild(); 在哪个父节点插入节点。类似push操作,把已有的元素插入到另一个元素时,执行的是剪切操作

  • PARENTNODE.insertBefore(a, b); 将 a 元素插入到父级元素 b 元素之前, insert a before b

删除
  • parent.removeChild(); 父节点删除子节点,返回剪切结果
  • child.remove(); 子节点调用删除自身的方法,直接删除
替换
  • parent.replaceChild(newEl , originEl); 拿新元素替换旧元素


2. 节点类型 - 节点类型值

  • 元素节点 - 1

  • 属性节点 - 2

  • 文本节点 - 3

  • 注释节点 - 8

  • document - 9

  • documentFragment(文档碎片) - 11


3. 节点的四个属性
  • nodeName - 元素的标签名,以大写的形式表示,只读

  • nodeValue - Text 节点或 Comment 节点的文本内容,可读写

  • nodeType - 该节点的类型,只读

  • attributes - Element 节点的属性集合


4. 节点的一个方法
  • Node.hasChildNodes(); - 判断是否有子节点


5.DOM节点树

文档中元素的继承关系,如下图。

DOM 继承关系图

  • getElementById方法定义在 Document.prototype 上,即Element节点上不能使用。

  • getElementByName方法定义在HTMLDocument.prototype上,即非html中的document以外不能使用(xml document.Element)

  • getElementByTagName方法定义在Document.prototype 和 Element.prototype 上

  • HTMLDocument.prototype定义了一些常用的属性,body、head分别指代HTML文档中的<html><body> 标签

    • document.body: body 标签
    • document.head: head 标签
  • Document.prototype 上定义了 documentElement 属性,指代文档的根元素,在HTML文档中总是指代

    • <html> 元素
  • getElementsByClassName、querySelector、querySelectorAll 在Document、Element类中均有定义。


6. Element 节点的一些属性
  • innerHTML: 写入、读取 HTML结构,默认会覆盖原有内容,可以使用 += 先取值再赋值

  • innerText:(老版本火狐不兼容) / textContent(老版本IE不好使) 写入、读取 文本


7. Element 节点的一些方法
  • el.setAttribute('propName', 'propValue'); 写入行间属性,可以设置系统没有的
  • el.getAttribute('propName') 读取行间属性
  • 改变Class时还可以使用 dom.className

35. Date 日期对象

日期对象中使用的方法都是系统定义好的

语法: var date = new Date()

Date 对象中的方法

  • date.getDate(); 返回今天是这个月的第几天

  • date.getDay(); 返回今天是这周的第几天,从零开始,0 表示 星期天

  • date.getMonth(); 返回月份,0 ~ 11 操作时需要加 1

  • date.getFullYear(); 返回四位数的年份

  • date.getHours(); 返回小时

  • date.getMinutes(); 返回分钟

  • date.getSeconds(); 返回秒

  • date.getMilliseconds(); 返回毫秒

  • date.getTime(); 返回自 1970 年 1 月 1 日至今的毫秒数(计算机的纪元时间)

时间戳

new Date().getTime(); 作为时间戳


36. 定时器&定时循环器

setInterval定时循环器, 精度不准确
setInterval(function() {
    console.log('a');
}, 1000); // 间隔1000毫秒执行一次循环 1000毫秒 == 1秒

var timer = setInterval(function() { // setInterval 会返回一个唯一标识给 timer 用来清除定时循环器 
    console.log('a');
}, 1000); 
clearInterval(timer); // 清除定时循环,可以在方法写在内部,到一定条件时清除

var timer = 1000;
setInterval(function() {
    console.log('a');
}, timer); // 这里的 timer 只识别一次,后续修改不起作用
timer = 2000;
setTimeout定时器
var timer = setTimeout(function() {
	console.log('时间到了');
}, 1000);
// 使用方法与 定时循环器 相同

37. 获取窗口属性和 dom元素尺寸

查看滚动条的滚动距离

  • window.pageXOffset/pageYOffset

    • IE8及以下浏览器不兼容

    • IE8及以下浏览器查看滚动距离

      • document.body.scrollLeft/scrollTop
      • document.documentElement.scorllLeft/Top: ie7/6
    • 方法封装

      function getScrollOffset () {
        if(window.pageXOffset) {
          return {
            x : window.pageXOffset,
            y : window.pageYOffset
          }
        } else {
          return { // 兼容IE8及以下浏览器
            x : document.body.scrollLeft + document.documentElement.scrollLeft,
            y : document.body.scrollTop + document.documentElement.scrollTop
          }
        }
      }
      

查看视口尺寸

  • window.innerWidth/innerHeight

    • IE8 及以下浏览器不兼容
  • document.documentElement.clientWidth/clientHeight

    • 标准模式下,任意浏览器都兼容
  • document.body.clientWidth/clientHeight

    • 适用于怪异模式
  • 代码封装

    function getViewportOffset() {
      if(!window.innerWidth) {
        return {
          w : window.innerWidth,
          h : window.innerHeight
        }
      } else {
        if(document.compatMode === "BackCompat") { // BackCompat 为怪异模式
          return {
            w : document.body.clientWidth,
            h : document.body.clientHeight
          }
        } else {
          return {
            w : document.documentElement.clientWidth,
            h : document.documentElement.clientHeight
          }
        }
    	}
    }
    

查看元素的几何尺寸(基本报废)

  • documentElement.getBoundingClientRect()
    • 兼容性很好
    • 该方法返回一个对象,对象里面有left、top、right、bottom等属性。left和top代表元素左上角的X和Y坐标,right和bottom代表元素右下角的x和y坐标
    • height 和 width 属性老版本IE并未实现
    • 返回的结果并不是 实时的

查看元素尺寸(视觉上的尺寸,包含padding、border 等宽高)

  • dom.offsetWidth - dom.offsetHeight

查看元素的位置

  • dom.offsetLeft - dom.offsetTop
    • 对于无定位父级的元素,返回相对于文档的坐标。对于定位的父级返回相对于最近的有定位的父级的坐标(只要父级有定位,不论这个距离是如何生成的,margin也可以)
  • dom.offsetParent
    • 返回最近的有定位打的父级,如无,返回 body,body.offsetParent 返回 null

设置滚动条滚动

window 上有三个方法

  1. scroll(x, y): x / y 滚动条滚动设置距离的位置
  2. scrollTo(x, y): 同上
  3. scrollBy(x, y): 会在之前的数据基础之上做累加滚动距离

38. 脚本化CSS

读写元素 css 属性

  • dom.style.prop
    • 可读写行间样式(仅限于行间样式),没有任何兼容性问题,碰到 float 这样的关键字属性,前面应加 css

      el.style.cssFloatLeft
      
    • 复合属性可以最好可以拆解

      el.style.border = "1px solid red";  /* 复合属性 */ 
      el.style.borderWidth = "1px";
      el.style.borderStyle = "solid";
      el.style.borderColor = "red";
      
    • 组合单词变成小驼峰式命名法

      // border-width
      borderWidth = "1px";
      backgroundColor = "red";
      
    • 写入的值必须是字符串


查询计算样式

  • window.getComputedStyle(elem, null); null: 可以获取指定伪元素样式表

    • 获取的是元素最后展示的值,计算样式只读,不区分是否是行间、内联和外部
    • IE8及以下不兼容
      • elem.currentStyle.prop
        • 计算样式只读,IE独有,返回的计算样式的值不是经过转换的绝对值
    window.getComputedStyle(elem, null).width; // 获取元素的宽
    window.getComputedStyle(box, 'after').width; // 获取元素中伪元素的宽
    
  • 脚本化样式表

    • document.styleSheets
      • 该属性存储了 HTML 文档里面所有 CSS 样式表的集合

39. 事件

绑定事件处理函数和执行环境

onclick

  var div = document.getElementById('demo');

  div.onclick = function() {
    console.log('点击了div', this);
  }
  /*
    这种方式是最原始、兼容性最好的绑定方式
    缺点就是,只能为dom元素绑定一个 事件处理函数,因为属于赋值,后面的会覆盖之前的
    这种编写方式等同于在行间编写事件处理
    <div onclick = "console.log('点击了div')">点击了div</div>  这种书写方式被称为 句柄
    执行环境:
      this 指向元素本身
  */

addEventListener()

// addEventListener('事件类型',处理函数, false) {}
var div = document.getElementById('demo');
div.addEventListener('click', function() {
  console.log('点击了div', this);
}, false);

/*
  能够给一个事件绑定多个处理函数(不能给同一个函数绑定多个事件),先绑定先执行
  IE9及以下不兼容
  执行环境:
    this 指向元素本身
*/

attachEvent()

 // attachEvent('on' + 事件类型, 处理函数)
div.attachEvent('onclick', function() {});
/*
  IE独有,和addEventListener类似,多个处理函数时先绑定后执行,同一个函数可以绑定多个事件
  执行环境:
    this 指向 window
*/

绑定事件方法封装

/**
* 添加事件	处理函数
* @param {elem} DOM元素 
* @param {type} 事件类型 
* @param {handle} 事件处理函数
*/
function addEvent(elem, type, handle) {
  if (elem.addEventListener) {
    elem.addEventListener(type, handle, false);
  } else if (elem.attachEvent) {
    elem.attachEvent('on' + type, handle);
  } else {
    elem['on' + type] = handle;
  }
}

解除事件处理程序

onclick()

elem.onclick = function() { }
elem.onclick = null; // false / "" / null

addEventListener()

div.addEventListener('click', test, false);
div.removeEventListener('click', test, false); // 必须使用事件名来解除
function test() {
    console.log('a');
}

attachEvent()

elem.detachEvent('on' + type, fn);

事件处理模型: 事件冒泡 / 捕获

1. 事件冒泡模型

结构上(非视觉上)嵌套关系的元素,会存在事件冒泡功能,即同一事件,自子元素冒泡向父元素。(自底向上)

示例
<style type="text/css">
  .wrapper{
    width: 300px;
    height: 300px;
    background-color: rgba(255, 0, 0, 0.8);
  }
  .content{
    width: 200px;
    height: 200px;
    background-color: rgba(0, 255, 0, 0.8);
  }
  .aside{
    width: 100px;
    height: 100px;
    background-color: rgba(0, 0, 255, 0.8);
  }
</style>
<div class="wrapper">
    <div class="content">
        <div class="aside"></div>
    </div>
</div>
<script type="text/javascript">
    var wrapper = document.getElementsByClassName('wrapper')[0];
    var content = document.getElementsByClassName('content')[0];
    var aside = document.getElementsByClassName('aside')[0];

    wrapper.addEventListener('click', function() {
        console.log('wrapper');
    }, false)
    content.addEventListener('click', function() {
        console.log('content');
    }, false)
    aside.addEventListener('click', function() {
        console.log('aside');
    }, false)
</script>

2. 事件捕获模型

结构上(非视觉上)嵌套关系的元素,会存在事件捕获功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自底向上) 对象上的一个事件类型,只能存在一个事件模型,例如:冒泡 or 捕获

示例
<style type="text/css">
    .wrapper{
        width: 300px;
        height: 300px;
        background-color: rgba(255, 0, 0, 0.8);
    }
    .content{
        width: 200px;
        height: 200px;
        background-color: rgba(0, 255, 0, 0.8);
    }
    .aside{
        width: 100px;
        height: 100px;
        background-color: rgba(0, 0, 255, 0.8);
    }
</style>
<div class="wrapper">
    <div class="content">
        <div class="aside"></div>
    </div>
</div>
<script type="text/javascript">
    var wrapper = document.getElementsByClassName('wrapper')[0];
    var content = document.getElementsByClassName('content')[0];
    var aside = document.getElementsByClassName('aside')[0];

    wrapper.addEventListener('click', function() {
        console.log('wrapper');
    }, true)
    content.addEventListener('click', function() {
        console.log('content');
    }, true) 
    aside.addEventListener('click', function() {
        console.log('aside');
    }, true)  // 注意这里的第三个参数是 true

    // 事件捕获正好是相反的,是由外而内依次触发事件
</script>

执行

  • IE 上没有事件捕获
  • 一个元素的一个事件类型一个事件处理函数只能存在一个模型
  • 如果一个元素、一个事件类型、绑定了两个事件处理函数,则可以同时拥有事件冒泡模型和事件捕获模型,执行顺序是先捕获再冒泡
  • 执行顺序: 捕获wrapper - 捕获content - 捕获中执行aside(这两个看代码的执行顺序)冒泡中执行aside - 冒泡content - 冒泡wrapper
  • 不冒泡的事件类型
    1. focus
    2. blur
    3. change
    4. submit
    5. reset
    6. select

取消冒泡和阻止默认事件

  • 取消冒泡

    1. W3C标准, IE9以下不支持

      el.onclick = function(e) {  // “e”: 事件源对象后续补充
        e.stopPropagation(); // 取消冒泡事件
      }
      
    2. IE浏览器独有

      el.onclick = function(e) {
        e.cancelBubble = true; // IE独有
      } 
      
  • 阻止默认事件

    1. return false; 只能以句柄的方式绑定或el.onclick = fn()的事件才能生效

      el.onclick = function() {
        return false; // 阻止默认事件
      }
      
    2. event.preventDefault(); W3C标准, IE9以下不兼容

    3. event.returnValue = false; 兼容IE

    4. 协议限定符

      // 阻止 a 标签的默认事件
      <a href="javascript:void()">test</a>
      

事件对象 / 事件源对象

  • 事件对象

    • 处理函数可以传入一个形参 e,是系统传进来的 event对象

      1. event: 普通浏览器
      2. window.event: IE!!!
    • 兼容写法

        el.onclick = function(e) {
          var event = e || window.event;  // 获取事件对象方法的兼容
        }
      
  • 事件源对象

    • 事件源对象是执行而非捕获

      1. event.target : 火狐独有
      2. event.srcElement : IE独有
      3. 以上 Chrome 都有
    • 兼容写法

      var target = event.target || event.srcElement;
      

事件委托

利用事件冒泡和事件源对象进行处理

  • 优点
    1. 性能 - 不需要循环所有的元素一个个绑定事件
    2. 灵活 - 当有新的子元素时不需要重新绑定事件
示例
<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
    <li>9</li>
    <li>10</li>
</ul>
<script type="text/javascript">
    var ul = document.getElementsByTagName('ul')[0];
    ul.onclick = function(e) {
        var event = e || window.event;
        var target = event.target || event.srcElement;
        console.log(target.innerText);
    }
</script>

事件分类

  • 鼠标事件

    事件名称事件描述事件类别
    click在元素上按下并释放任意鼠标按键。鼠标事件
    contextmenu右键点击鼠标事件
    dblclick在元素上双击鼠标按钮鼠标事件
    mousedown在元素上按下任意鼠标按钮。鼠标事件
    mouseup在元素上释放任意鼠标按键。鼠标事件
    mouseenter指针移到有事件监听的元素内鼠标事件
    mouseleave指针移出元素范围外(不冒泡 )鼠标事件
    mousemove指针移入元素。鼠标事件
    mouseover指针移出元素鼠标事件
    mouseout指针移出元素,或者移到它的子元素上。鼠标事件
    pointerlockchange鼠标被锁定或者解除锁定发生时。鼠标事件
    pointerlockerror可能因为一些技术的原因鼠标锁定被禁止时。鼠标事件
    select有文本被选中。鼠标事件
    wheel滚轮向任意方向滚动。鼠标事件
  • 区分鼠标的左右按键

    • mousedown

    • mouseup

      document.onmousedown = function (e) { // 使用onmousedown || onmouseup
        console.log(e.button); // 使用事件对象中的button属性来判断,左:0 中:1 右:2
      }
      
      > DOM3标准规定:click 事件只能监听左键,只能通过`mousedown``mouseup` 来判断鼠标键
      
  • 键盘事件

    1. keydown : 键盘按下事件

    2. keyup : 键盘抬起事件

    3. keypress : 键盘持续触发事件

    • keydown > keypress > keyup

    • keydownkeypress 的区别:

      1. keydown 可以响应任意键盘按键,

      2. keypress keypress只可以监听到字符类键盘按键,返回ASCII码,可以转换成相应字符

  • 文本类事件

    1. input : 内容改变监听事件

    2. change : 聚焦和失焦时两个状态是否发生改变

    3. focus : 元素聚焦

    4. blur : 元素失焦

    • 窗体类事件(window上的事件)

      1. scroll : 滚动条滚动时触发
      2. load : 文档解析并加载完成之后执行脚本(性能低,不推荐)

40. 浏览器渲染机制

打开网页时浏览器首先识别 HTML 代码并行成 DOMTree,然后解析 CSS 形成 CSSTree,然后将 DOMTree 和 CSSTree 进行结合形成新的 RenderTree,然后浏览器会根据 RenderTree 绘制页面

  • 浏览器资源加载顺序

    1. 识别 html 代码进行解析,然后形成DOM树 DOMTree

      /*
        DOMTree:
                  <html>
        <head>              <body>
                      <div>  <span> <stong>
      
      根据节点的所在位置绘制成一个树结构
      DOMTree 绘制机制符合**深度优先**原则,直到某一条枝干上没有其他元素,才会更改枝干再进行绘制(解析绘制时不必等元素引用的资源加载完成)
      */
      
    2. CSSTree

      /*
        DOMTree 创建完成之后,解析CSS并创建CSSTree
      */
      
    3. RenderTree

      /*
      将 DOMTree 和 CSSTree 进行结合形成新的 RenderTree,然后浏览器会依据 RenderTree 进行绘制页面
      */
      
  • 重排 reflow

    • DOM节点的添加、删除,DOM节点的宽高变化、位置变化,display、offsetWidth、offsetHeight 都会致使 RenderTree 重构,极大损耗了性能

  • 重绘 repaint

    • 修改字体颜色、背景颜色等等会触发重绘,它会致使 renderTree 部分改变,想对 reflow 会好很多