JS - DOM

基本语法

(0)有些HTML属性名是JavaScript的保留字,转为JavaScript属性时,必须改名。主要是以下两个。

  • for属性改为htmlFor
  • class属性改为className

若要在HTML元素上附加数据,供JavaScript脚本使用

  • 自定义属性:可能不符合标准,导致网页代码校验不通过
  • 标准 data-* 属性:利用元素节点对象的dataset属性

:data-后面的属性名只能包含小写字母、数字、连词线(-)、点(.)、冒号(:)和下划线(_)

(1)判断一个节点有无子节点,三种方法

  • node.hasChildNodes()
  • node.firstChild !== null
  • node.childNodes && node.childNodes.length > 0

:结合firstChild属性和nextSibling属性,遍历当前节点的所有后代节点

function DOMComb(parent, callback) {
  if (parent.hasChildNodes()) {
    for (var node = parent.firstChild; node; node = node.nextSibling) {
      DOMComb(node, callback);
    }
  }
  callback(parent);
}

// 用法
DOMComb(document.body, console.log)

(2)removeChild  

该方法是在当前结点的父节点上调用的。

:移除当前结点的所有子节点

var element = document.getElementById('xxx');
while (element.firstChild) {
  element.removeChild(element.firstChild);
}

结点的children属性是一个只读属性,随子节点的变化会实时更新。
在删除多个节点时,务必注意children属性时刻都在变化。

(3)compareDocumentPosition()

七个比特位的二进制值,表示参数节点与当前节点的关系

000000	0	两个节点相同
000001	1	两个节点不在同一个文档(即有一个节点不在当前文档)
000010	2	参数节点在当前节点的前面
000100	4	参数节点在当前节点的后面
001000	8	参数节点包含当前节点
010000	16	当前节点包含参数节点
100000	32	浏览器内部使用  

最终结果需要结合比特位运算。  

(4)结点集合 

NodeList 实例是一个类数组对象,可转化为真正的数组,支持for和foreach遍历

var children = document.body.childNodes;
var nodeArr = Array.prototype.slice.call(children);

NodeList实例可能是动态集合,也可能是静态集合。

动态集合是一个活的集合,DOM删除或新增一个相关节点,会立刻反映在NodeList实例上。

:目前只有node.childNodes返回的NodeList是动态集合。 

  • childNodes:所有子结点
  • children:子元素结点

其他返回NodeList的方法

  • document.getElementsByName
  • Element.querySelectorAll

HTMLCollection 实例是一个类数组对象,只包含元素(Element)结点,只支持for遍历,动态集合。

  • document.links
  • document.forms
  • document.images
  • document.scripts
  • document.embeds,document.plugins

以及元素定位方法

  • document.getElementsByTagName
  • document.getElementsByClassName

document.styleSheets属性返回文档内嵌或引入的样式表集合,类型StyleSheetList。

(5)HTML表单

常用标签

文本框:<input type="text">,用于输入文本;
口令框:<input type="password">,用于输入口令;
单选框:<input type="radio">,用于选择一项;
复选框:<input type="checkbox">,用于选择多项;
下拉框:<select>,用于选择一项;
多行文本框:<textarea>,定义多行的文本输入控件
隐藏文本:<input type="hidden">,用户不可见,但表单提交时会把隐藏文本发送到服务器。
文件选择:<input type="file">:HTML表单中用于上传文件

当一个表单包含<input type="file">时,表单的enctype必须为multipart/form-data,method必须为post,浏览器才能正确编码并以multipart/form-data格式发送表单数据。

(6)根据name属性获取标签结点

document.querySelectorAll('*[name="xxx"]');
document.getElementsByName("xxx");  

注意,没有name属性的<input>的数据不会被提交。  

(7)网页属性

a. 整张网页的总高度/宽度

document.documentElement.scrollHeight/scrollWidth
document.body.scrollHeight/scrollWidth

b. 整张网页的水平的和垂直的滚动距离

document.documentElement.scrollLeft
document.documentElement.scrollTop

注:该属性都可读写,设置该属性的值,会导致浏览器将当前元素自动滚动到相应的位置。

c. 计算元素左上角相对于整张网页的坐标 

function getElementPosition(e) {
  var x = 0;
  var y = 0;
  while (e !== null)  {
    x += e.offsetLeft;
    y += e.offsetTop;
    e = e.offsetParent;
  }
  return {x: x, y: y};
}

document

获取document对象的方法

  • 正常的网页,直接使用documentwindow.document
  • iframe框架里面的网页,使用iframe节点的contentDocument属性
  • Ajax 操作返回的文档,使用XMLHttpRequest对象的responseXML属性
  • 内部节点的ownerDocument属性

document对象通常有2个子结点

(1)document.doctype:指向<DOCTYPE>节点,通常为<!DOCTYPE html>,与document.firstChild等效

(2)document.documentElement:指向当前文档的根节点,通常为<html>

document.head和document.body属性分别直接指向<head>节点和<body>节点。

状态属性

document.visibilityState

该属性返回文档的可见状态:

  • visible:页面可见。页面可能是部分可见,即不是焦点窗口,前面被其他窗口部分挡住了
  • hidden: 页面不可见,有可能窗口最小化,或者浏览器切换到了另一个Tab页面
  • prerender:页面处于正在渲染状态,对于用于来说,该页面不可见
  • unloaded:页面从内存里面卸载了

应用场景:

  • 页面加载时,防止加载某些资源
  • 页面不可见时,停掉一些页面功能

document.readyState

该属性返回当前文档的状态:

  • loading:加载HTML代码阶段(尚未完成解析)
  • interactive:加载外部资源阶段
  • complete:加载完成

页面加载过程:

  • 浏览器开始解析HTML文档,document.readyState属性等于loading
  • 浏览器遇到HTML文档中的<script>元素,并且没有asyncdefer属性,就暂停解析,开始执行脚本,这时document.readyState属性还是等于loading
  • HTML文档解析完成,document.readyState属性变成interactive
  • 浏览器等待图片、样式表、字体文件等外部资源加载完成,一旦全部加载完成,document.readyState属性变成complete

方法

document.createTextNode()

用于生成文本节点(Text实例)。

该方法可以确保返回的节点,被浏览器当作文本渲染,而不是当作HTML代码渲染。因此,可以用来展示用户的输入,避免 XSS 攻击。 

var div = document.createElement('div');
div.appendChild(
    document.createTextNode('<span>Foo & bar</span>')
);
console.log(div.innerHTML)
结果:&lt;span&gt;Foo &amp; bar&lt;/span&gt;

会对大于号和小于号进行转义,保证即使用户输入的内容包含恶意代码,也能正确显示。

但该方法不对单引号和双引号转义,所以不能用来对HTML属性赋值。

document.createDocumentFragment()

该方法生成一个空的存在于内存中的文档片段对象。
新对象不属于当前文档,常用来生成一段较复杂的DOM结构,然后再插入当前文档,避免直接修改当前DOM引发网页的重新渲染。

该方法等同于浏览器原生的DocumentFragment构造函数

var docFrag = new DocumentFragment();

注意,DocumentFragment节点本身不能被插入当前文档,而是其所有子结点插入当前文档中。插入完成后,该DocumentFragment对象变为空结点(textContent属性为空串),可以被再次使用。
若想保留其子结点,可以通过cloneNode()方法

node.appendChild(docFrag.cloneNode(true));

document.createNodeIterator()
该方法返回一个子节点遍历器对象(NodeFilter实例)

var nodeIterator = document.createNodeIterator(
  document.body, //要遍历的根节点
  NodeFilter.SHOW_ELEMENT //要遍历的节点类型
);

:遍历器返回的第一个节点,总是根节点。

  • nextNode:先返回遍历器指向的节点A,然后将指针移到下一个节点B
  • previousNode:先将指针移到上一个节点D,然后返回该节点D
var currentNode = nodeIterator.nextNode();
var previousNode = nodeIterator.previousNode();
currentNode === previousNode  //true

document.createTreeWalker()

该方法返回一个DOM的子树遍历器对象(TreeWalker实例)
:遍历器返回的第一个节点,不是根节点。

var treeWalker = document.createTreeWalker(
  document.body, //要遍历的根节点
  NodeFilter.SHOW_ELEMENT //要遍历的节点类型
);

相同条件下,treeWalker 比 nodeIterator 少一个根节点。  

Element

属性

innerHTML VS textContent VS  innerText

  • 读属性值时,若文本节点包含 &、<、> ,innerHTML属性会转义为实体形式 &amp;、&lt;、&gt; 。若想得到原文,建议使用element.textContent属性。
  • 写属性值时,不存在上述转义问题;
  • 为了安全考虑,如果插入的是文本,优先textContent属性;

方法

Element.insertAdjacentHTML()

将一个HTML字符串,解析生成DOM结构(转义字符),插入相对于当前节点的指定位置。

  • 执行速度比innerHTML属性快得多
  • 不转义HTML字符串,导致不能用来插入用户输入的内容,否则会有安全风险

Element.closest()

接受一个CSS选择器作为参数,返回匹配该选择器的、最接近当前节点的一个祖先节点(包括当前节点本身)。

如果没有任何节点匹配 CSS 选择器,则返回null。

Element.getBoundingClientRect()

返回一个rect对象,提供当前元素节点的大小、位置等信息(CSS盒状模型的所有信息)

x:元素左上角相对于视口的横坐标
y:元素左上角相对于视口的纵坐标
/// 元素本身 + padding + border
height:元素高度
width:元素宽度
/// 随着页面滚动变化而变动
left:元素左上角相对于视口的横坐标,与x属性相等
right:元素右边界相对于视口的横坐标(等于x + width)
top:元素顶部相对于视口的纵坐标,与y属性相等
bottom:元素底部相对于视口的纵坐标(等于y + height)

若要得到绝对值,可以将left属性加上window.scrollXtop属性加上window.scrollY

rect对象没有自身属性,全部继承于原型属性,所以:Object.keys(rect) // [] 

另外,Element.getClientRects 方法返回类数组对象,与Element.getBoundingClientRect()相比,该方法:

  • 返回对象有多少个成员,取决于该元素在页面上占据多少行
  • 主要用于判断行内元素是否换行,以及行内元素的每一行的位置偏移
  • 考虑行内元素的换行符

此外,对于页面元素的焦点问题,考虑以下属性和方法

Element.focus()
Element.blur()
document.activeElement

/// 让xxx元素获得焦点,并滚动到可见区域
function getFocus() {
  document.getElementById('xxx').focus({preventScroll:false});
}

DOM事件

事件驱动编程模式(event-driven),通过监听函数对事件做出反应。 所有DOM的事件操作(监听和触发),都定义在EventTarget接口

  • addEventListener:绑定事件的监听函数
  • removeEventListener:移除事件的监听函数
  • dispatchEvent:触发指定事件

若向监听函数传递参数,可用匿名函数包装监听函数

btn.addEventListener('click',
    function () {
        alert(this.nodeName); //监听函数内部的this,指向当前事件所在的那个对象
        fun('Hello');
    }, false);

其中,false标志冒泡阶段,true标志捕获阶段,默认false。

事件绑定

JS提供三种为事件绑定监听函数的方法:

  • HTML的on-属性:冒泡阶段触发,值为函数立即执行代码
el.setAttribute('onclick', 'doSomething()');
// 等同于
<Element onclick="doSomething()">

违反了HTML与JavaScript代码相分离的原则。

  • 元素节点对象的事件属性:冒泡阶段触发,值为函数名
div.onclick = function (event) {
  console.log('触发事件');
};

缺点是同一个事件只能定义一个监听函数。

  • EventTarget.addEventListener()

推荐使用

  • 同一个事件可以添加多个监听函数
  • 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数
  • 除了DOM节点,其他对象(比如windowXMLHttpRequest等)也有这个接口,它等于是整个JavaScript统一的监听函数接口

事件传播

propagation,事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.body)

  • 第一阶段:(始于window,由外向内)从window对象传到目标节点(上层传到底层),称为“捕获阶段”(capture phase)
  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)
  • 第三阶段:(由内向外,终于window)从目标节点传回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)

注意,浏览器总是假定click事件的目标节点,是点击位置嵌套最深的那个节点。

对事件传播的详细讲解过程,参见:JS教程 - 事件模型 的propagation部分。

事件代理

delegation,把监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。 

可以通过事件对象的stopPropagation方法阻止事件的传播

// 事件传播到 node 元素后,就不再向下传播了
node.addEventListener('click', function (event) {
  event.stopPropagation();
}, true); ///捕获阶段

// 事件冒泡到 node 元素后,就不再向上冒泡了
node.addEventListener('click', function (event) {
  event.stopPropagation();
}, false); ///冒泡阶段

若同时阻止node节点的其他click事件的监听函数,彻底阻止这个事件的传播,请移步:stopImmediatePropagation方法。

事件对象

浏览器原生提供一个Event对象,所有的事件都是该对象的实例,或者说继承了Event.prototype对象。

var event = new Event(
  'look', {
     'bubbles': true,
     'cancelable': false
  });

其中,只读属性bubble的true标志冒泡阶段,false标志捕获阶段,默认false。

由Event构造函数生成的事件,Event.isTrusted属性返回false,标志是脚本产生的,而非真正的用户行为产生的。 

除Event外,浏览器原生提供CustomEvent()构造函数,用来生成自定义的事件实例。

  • 在触发事件的同时,传入指定的数据
  • 数据通过其属性detail带入 

Event.eventPhase

返回一个整数常量,表示事件目前所处的阶段,只读属性

  • 0,事件目前没有发生
  • 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中
  • 2,事件到达目标节点,即Event.target属性指向的那个节点
  • 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中

Event.cancelable

返回一个布尔值,表示事件是否可以取消,只读属性,默认false

推荐用法:调用Event.preventDefault()之前,先预判

function preventEvent(event) {
  if (event.cancelable) {
    event.preventDefault();
  } else {
    console.warn('This event can not be canceled.');
  }
}

Event.target VS Event.currentTarget

  • target属性:返回原始触发事件的那个节点,即事件最初发生的节点
  • currentTarget属性:返回事件当前所在的节点,即正在执行的监听函数所绑定的那个节点

事件传播过程中,不同节点的监听函数内部的Event.target与Event.currentTarget属性的值是不一样的,前者总是不变的,后者则是指向监听函数所在的那个节点对象。

Event.preventDefault()

取消浏览器对当前事件的默认行为,前提是事件对象的cancelable属性为true。

该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。

Event.composedPath()

返回一个数组,成员是事件的最底层节点和依次冒泡经过的所有上层节点。

事件类型

  1. 鼠标事件:继承MouseEvent接口

在父节点内部进入子节点,不会触发mouseenter事件,但是会触发mouseover事件;

在父节点内部离开子节点,不会触发mouseleave事件,但是会触发mouseout事件;

relatedTarget:节点对象,表示事件的相关节点,默认为null。

mouseenter和mouseover事件时,表示鼠标刚刚离开的那个元素节点;

mouseout和mouseleave事件时,表示鼠标正在进入的那个元素节点;

拓展注意,不同事件的target属性和relatedTarget属性的不同:

事件名称	target 属性	   relatedTarget 属性
focusin	接受焦点的节点	丧失焦点的节点
focusout	丧失焦点的节点	接受焦点的节点
mouseenter	将要进入的节点	将要离开的节点
mouseleave	将要离开的节点	将要进入的节点
mouseout	将要离开的节点	将要进入的节点
mouseover	将要进入的节点	将要离开的节点
dragenter	将要进入的节点	将要离开的节点
dragexit	将要离开的节点	将要进入的节点
  1. 滚轮事件:WheelEvent接口继承MouseEvent的实例

浏览器原生提供WheelEvent()构造函数,用来生成WheelEvent实例。

var wheelEvent = new WheelEvent('wheel', options);  
  1. 键盘事件:继承KeyboardEvent接口、Event接口

浏览器原生提供KeyboardEvent构造函数,用来新建键盘事件的实例。

属性key表示当前按下的键,默认为空字符串。

注意,如果一直按键不松开,会一直触发事件,直至松开

keydown --> keypress --> keydown --> keypress --> …(重复以上过程) --> keyup

通过KeyboardEvent.repeat属性返回一个布尔值,代表该键是否被按着不放,以便判断是否重复这个键。

  1. 进度事件:继承ProgressEvent接口

描述资源加载/文件上传的进度,主要由 AJAX请求、<img>、<audio>、<video>、<style>、<link> 等外部资源的加载触发。

abort:外部资源中止加载时(比如用户取消)触发。如果发生错误导致中止,不会触发该事件。
error:由于错误导致外部资源无法加载时触发。
load:外部资源加载成功时触发。
loadstart:外部资源开始加载时触发。
loadend:外部资源停止加载时触发,发生顺序排在error、abort、load等事件的后面。
progress:外部资源加载过程中不断触发。
timeout:加载超时时触发。

推荐方法,避免出现图片在脚本未执行完就加载完毕、监听函数不会执行的情况

function loaded() { // ... }

if (image.complete) {
  loaded();
} else {
  image.addEventListener('load', loaded);
}

注意,由于DOM元素节点没有提供是否加载错误的属性,所以error事件的监听函数最好放在<img>元素的HTML代码中,这样才能保证发生加载错误时百分之百会执行错误提示。而且,error事件不会冒泡,子元素的error事件,不会触发父元素的error事件监听函数。

  1. 拖拉事件:继承DragEvent接口,同时继承MouseEvent接口和Event接口

浏览器原生提供一个DragEvent()构造函数,用来生成拖拉事件的实例对象。

关于拖拉事件,有以下几个注意点

  • 某个元素节点的draggable属性设为true,就无法再用鼠标选中该节点内部的文字或子节点。
  • 拖拉过程只触发以上这些拖拉事件,尽管鼠标在移动,但是鼠标事件不会触发。
  • 将文件从操作系统拖拉进浏览器,不会触发dragstartdragend事件。
  • dragenterdragover事件的监听函数,用来取出拖拉的数据(即允许放下被拖拉的元素)。由于网页的大部分区域不适合作为放下拖拉元素的目标节点,所以这两个事件的默认设置为当前节点不允许接受被拖拉的元素。如果想要在目标节点上放下的数据,首先必须阻止这两个事件的默认行为。

通过如下例子:将一个节点从当前父节点,拖拉到另一个父节点中,理解拖拉事件

/* HTML 代码如下
 <div class="dropzone">
   <div id="draggable" draggable="true">
     该节点可拖拉
   </div>
 </div>
 <div class="dropzone"></div>
 <div class="dropzone"></div>
 <div class="dropzone"></div>
*/

// 被拖拉节点
var dragged;

document.addEventListener('dragstart', function (event) {
  // 保存被拖拉节点
  dragged = event.target;
  // 被拖拉节点的背景色变透明
  event.target.style.opacity = 0.5;
}, false);

document.addEventListener('dragend', function (event) {
  // 被拖拉节点的背景色恢复正常
  event.target.style.opacity = '';
}, false);

document.addEventListener('dragover', function (event) {
  // 阻止事件的默认行为,防止拖拉效果被重置,允许被拖拉的节点放入目标节点
  event.preventDefault();
}, false);

document.addEventListener('dragenter', function (event) {
  // 目标节点的背景色变紫色
  // 由于该事件会冒泡,所以要过滤节点
  if (event.target.className === 'dropzone') {
    event.target.style.background = 'purple';
  }
}, false);

document.addEventListener('dragleave', function( event ) {
  // 目标节点的背景色恢复原样
  if (event.target.className === 'dropzone') {
    event.target.style.background = '';
  }
}, false);

document.addEventListener('drop', function( event ) {
  // 防止事件默认行为(比如某些元素节点上可以打开链接),
  event.preventDefault();
  if (event.target.className === 'dropzone') {
    // 恢复目标节点背景色
    event.target.style.background = '';
    // 将被拖拉节点插入目标节点
    dragged.parentNode.removeChild(dragged);
    event.target.appendChild( dragged );
  }
}, false);

注意,drag、dragstart、dragend是针对被拖拉对象,dragenter、dragover、dragleave、drop是针对目标结点对象。  

DataTransfer

所有拖拉事件的实例都有一个DragEvent.dataTransfer属性,用来读写需要拖拉传递的数据。该属性的值是一个DataTransfer接口的实例。 

浏览器原生提供一个DataTransfer()构造函数,无参

var dataTrans = new DataTransfer();

拖拉事件开始时,开发者可以提供数据类型和数据值。拖拉过程中,开发者通过dragenter和dragover事件的监听函数,检查数据类型以确定是否允许放下(drop)被拖拉的对象。发生drop事件时,监听函数取出拖拉的数据即可。

属性

dropEffect VS effectAllowed

  • dropEffect设置接受拖拉结点的区域的效果,effectAllowed设置本次拖拉中被拖拉的节点允许的效果;
  • dropEffect在dragenter和dragover事件的监听函数中设置,effectAllowed在dragstart事件的监听函数中设置;

该2个属性是同一件事的两个方面,通常配合使用

source.addEventListener('dragstart', function (e) {
  e.dataTransfer.effectAllowed = 'move';
});

target.addEventListener('dragover', function (e) {
  ev.dataTransfer.dropEffect = 'move';
});

types VS items

  • 拖拉的数据格式(通常是 MIME 值),只读数组
  • 类似数组的只读对象(DataTransferItemList 实例),每个成员就是本次拖拉的一个对象(DataTransferItem 实例)

该2个属性通常在drop事件的监听函数中获取。

:若想为拖拉事件添加数据,只能在dragstart中设置;若想从拖拉事件中获取数据,只能在drop事件监听函数中。 

  1. 触摸事件:触摸点(Touch)、触摸点集合(TouchList)、触摸事件(TouchEvent)

TouchEvent接口继承Event接口,表示由触摸引发的事件实例

  • touchesTouchList实例,代表所有的当前处于活跃状态(触摸中)的触摸点,默认值是一个空数组[]
  • targetTouchesTouchList实例,代表所有处在触摸的目标元素节点内部、且仍然处于活动状态的触摸点,默认值是一个空数组[]
  • changedTouchesTouchList实例,代表本次触摸事件的相关触摸点,默认值是一个空数组[]

若 ev.touches.length === ev.targetTouches.length,表明所有触摸点均在目标元素结点内。

:若想在发生触摸事件的同时阻止鼠标事件,使用:event.preventDefault方法。

  1. 表单事件
// input事件(连续触发)
 当<input>、<select>、<textarea>的值发生变化,
 单复选框改变选项,
 打开contenteditable属性的元素结点的值发生变化时触发
// select事件
 在<input>、<textarea>里面选中文本时触发
// Change事件(不会连续触发)
 当<input>、<select>、<textarea>的值发生变化且只有当全部修改完成时才会触发
 [1]. 激活单选框(radio)或复选框(checkbox)时触发。
 [2]. 用户提交时触发。比如,从下列列表(select)完成选择,在日期或文件输入框完成选择。
 [3]. 当文本框或<textarea>元素的值发生改变,并且丧失焦点时触发。
// invalid 事件
用户提交表单时,如果表单元素的值不满足校验条件时触发
// reset 事件,submit 事件:发生在表单对象<form>上,而不是发生在表单的成员上
reset事件当表单重置(所有表单成员变回默认值)时触发。
submit事件当表单数据向服务器提交时触发。 
  1. 其他事件

a. 资源事件

  • beforeunload事件:资源将要卸载前触发
  • unload 事件:窗口关闭或者document对象将要卸载时触发。它的触发顺序排在beforeunloadpagehide事件后面。事件发生时,所有资源依然存在,但是对用户来说都不可见,UI互动全部无效。缓存存在,则事件无效。
  • load事件:页面或某个资源加载成功时触发。缓存存在,则事件无效。

推荐:兼容性好

window.addEventListener('beforeunload', function(e) {
   var confirmationMessage = '确认关闭窗口?';
   e.returnValue = confirmationMessage;
   return confirmationMessage;
});  

b. 焦点事件

在元素节点和document对象上面触发,与获得/失去焦点相关。

  • focus:元素节点获得焦点后触发,该事件不会冒泡
  • blur:元素节点失去焦点后触发,该事件不会冒泡
  • focusin:元素节点将要获得焦点时触发,发生在focus事件之前,该事件会冒泡
  • focusout:元素节点将要失去焦点时触发,发生在blur事件之前,该事件会冒泡

focusin --> focus --> focusout --> blur 

这四个事件都继承FocusEvent接口,提供属性:

  • FocusEvent.target:事件的目标节点
  • FocusEvent.relatedTarget:对于focusin事件,返回失去焦点的节点;对于focusout事件,返回将要接受焦点的节点;对于focusblur事件,返回null

对于focus和blur事件,通常将addEventListener方法的第三个参数需要设为true,因为只能在捕获阶段触发。

form.addEventListener('focus', function (event) {
  event.target.style.background = 'pink';
}, true);

form.addEventListener('blur', function (event) {
  event.target.style.background = '';
}, true);

c. session历史事件

默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进/后退”按钮时,浏览器就会从缓存中加载页面。

  • pageshow事件:页面加载时触发,包括第一次加载和从缓存加载两种情况。第一次加载时,触发顺序在load事件后。从缓存加载时,load事件不会触发。利用pageshow事件的persisted属性,可以判断页面是从何处加载而来:false标志第一次加载,true标志缓存加载。
  • pagehide事件:通过“前进/后退”按钮,离开当前页面时触发。利用pagehide事件的persisted属性,设置为true,表示页面要保存在缓存中;反之,不保存。
  • hashchange事件:URL的hash 部分(#号后面的部分,包括#号)发生变化时触发,在window对象上监听。
  • popstate事件:只在浏览器的history对象的当前记录发生显式切换时触发。(比如鼠标点击“后退/前进”按钮)

d. 网页状态事件

  • DOMContentLoaded事件

网页下载并解析完成以后,浏览器就会在document对象上触发,远早于load事件。

:网页的JavaScript脚本是同步执行的,脚本一旦发生堵塞,将推迟触发DOMContentLoaded事件。

  • readystatechange事件

当Document对象和XMLHttpRequest对象的readyState属性发生变化时触发。readyState有3种状态:

  1. loading:网页正在加载
  2. interactive:网页已经解析完成,但是外部资源仍然处在加载状态
  3. 和complete:网页和所有外部资源已经结束加载,load事件即将触发

e. 窗口事件

主要是scroll事件:在文档或文档元素滚动时触发(用户拖动滚动条)。 

  • 该事件会连续地大量触发,监听函数内不应有耗时操作;(resize事件亦是)
  • 推荐使用requestAnimationFrame或setTimeout控制该事件的触发频率,结合customEvent抛出一个新事件;

目前lodash函数库提供了现成的throttle函数,可以直接使用:将一个函数的调用频率限制在一定阈值内

window.addEventListener('scroll', _.throttle(callback, 1000));

关于函数节流(throttle)和函数去抖(debounce)的问题,具体参见:函数节流与函数去抖 - sqh

f. 剪贴板事件

  • cut:将选中的内容从文档中移除,加入剪贴板时触发
  • copy:进行复制动作时触发
  • paste:剪贴板内容粘贴到文档后触发

这三个事件都是ClipboardEvent接口的实例。ClipboardEvent的实例属性clipboardData是一个DataTransfer对象,存放剪贴的数据。

关于事件类型的详细信息,参见:JS教程 - 事件种类; 

扩展

(1)自定义渲染方法

即自定义jQuery插件,涉及:

  • $.fn:定义方法
  • $.extend(target, obj1, obj2, ...):定义参数 

具体地,编写一个jQuery插件的原则:

  1. $.fn绑定函数,实现插件的代码逻辑
  2. 插件函数最后要return this;以支持链式调用
  3. 插件函数要有默认值,绑定在$.fn.<pluginName>.defaults
  4. 用户在调用时可传入设定值以便覆盖默认值
$.fn.方法名 = function (options) {
    // 合并默认值和用户设定值:
    var opts = $.extend({}, $.fn.方法名.defaults, options);
    // 样式渲染
    this.css('backgroundColor', opts.backgroundColor)
         .css('color', opts.color);
    // 返回this,支持链式调用
    return this;
}
// 设定默认值:
$.fn.方法名.defaults = {
    color: '#d85030',
    backgroundColor: '#fff8de'
}

支持用户自定义默认值

$.fn.方法名.defaults.color = '#fff';
$.fn.方法名.defaults.backgroundColor = '#000';  

也可以传参覆盖默认值。 

(2)underscore库

完善的函数式编程接口,如同jQuery会绑定到$上,underscore会绑定到_上。

安装:npm install underscore

具体参见:underscore.js;  

posted @ 2018-06-17 22:46  万箭穿心,习惯就好。  阅读(335)  评论(0编辑  收藏  举报