JavaScript中的DOM与BOM

DOM

概念

文档对象模型 (Document Object Model,简称DOM) 将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM 模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM 的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。

在我们的页面中,可以把页面结构看作成是一个 DOM 树结构,如下图所示:

DOM 把这些内容都看做是对象。

  1. 文档:一个页面就是一个文档,DOM 中使用 document 来表示;
  2. 元素:页面中所有标签都是元素,DOM 中使用 element 来表示;
  3. 节点:网页中所有内容都是节点(标签、属性、文本、注释等),DOM 中使用 node 来表示;

获取元素

getElementById

getElementById() 返回一个匹配特定 ID 的元素。由于元素的 ID 在大部分情况下要求是独一无二的,这个方法自然而然地成为了一个高效查找特定元素的方法。

var element = document.getElementById(id);

它返回一个匹配到 ID 的 DOM Element 对象。若在当前 Document 下没有找到,则返回 null。

<div id="title">hello DOM</div>

var title = document.getElementById("title")
console.log(title, typeof title)   // <div id="title">hello DOM</div> "object"

getElementsByClassName

返回一个包含了所有指定类名的子元素的类数组对象。当在 document 对象上调用时,会搜索整个DOM文档,包含根节点。你也可以在任意元素上调用 getElementsByClassName() 方法,它将返回的是以当前元素为根节点,所有指定类名的子元素。

var elements = document.getElementsByClassName(names);
var elements = rootElement.getElementsByClassName(names);

<div class="div"></div>

let div = document.getElementsByClassName("div")
console.log(div, typeof div)   // HTMLCollection [div.div] "object"

getElementsByTagName

返回一个包括所有给定标签名称的元素的 HTML 集合 HTMLCollection。整个文件结构都会被搜索,包括根节点。返回的 HTML 集合是动态的, 意味着它可以自动更新自己来保持和 DOM 树的同步而不用再次调用 document.getElementsByTagName()。

使用 getElementsByTagName() 方法可以返回带有指定标签名的对象集合。

var elements = document.getElementsByTagName(name);

它返回的是获取过来元素对象的集合,以伪数组的形式存储。如果页面没有指定标签名的这个元素,返回的是空的伪数组形式。

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

let li = document.getElementsByTagName("li")
console.log(li, typeof li)   // HTMLCollection(3) [li, li, li]  "object"

querySelector

文档对象模型 Document 引用的 querySelector() 方法返回文档中与指定选择器或选择器组匹配的第一个 HTMLElement 对象。 如果找不到匹配项,则返回 null。

var element = document.querySelector(selectors);

<div class="box">box1</div>
<div class="box">box2</div>
<div id="div">
  <ul>
    <li>li1</li>
    <li>li2</li>
  </ul>
</div>

var box = document.querySelector(".box")
console.log(box)   // <div class="box">box1</div>

var div = document.querySelector("#div")
console.log(div)   // <div id="div">...</div>

var li = document.querySelector("li")
console.log(li)   // <li>li1</li>

querySelectorAll

返回与指定的选择器组匹配的文档中的元素列表 (使用深度优先的先序遍历文档的节点)。返回的对象是 NodeList。根据指定选择器返回所有元素对象。

elementList = parentNode.querySelectorAll(selectors);

<div class="box">1</div>
<div class="box">2</div>
<ul>
  <li>li1</li>
  <li>li2</li>
</ul>

var box = document.querySelectorAll(".box")
console.log(box)   // NodeList(2) [div.box, div.box]

var li = document.querySelectorAll("li")
console.log(li)   // NodeList(2) [li, li]

body

返回当前文档中的 body 元素或者 frameset 元素。

var objRef = document.body;

let body = document.body;
console.log(body)   // <body>...</body>

html

Document.documentElement 是一个会返回文档对象(document)的根元素的只读属性(如HTML文档的 html 元素)。

注意:获取 html 元素是使用 document.documentElement,而不是document.html。

var html = document.documentElement
console.log(html)   // <html>...</html>

操作元素

改变元素内容

innerText

innerText 属性设置或返回指定节点及其所有后代的文本内容。它不能识别 HTML 标签。

<button>按钮</button>
<div>当前时间为:</div>

var btn = document.querySelector("button")
var div = document.querySelector("div")
btn.onclick = function() {
  div.innerText += new Date()
}

innerHTML

innerHTML 属性设置或获取 HTML 语法表示的元素的后代。它可以识别 HTML 标签。

<button>按钮</button>
<div></div>

var btn = document.querySelector("button")
var div = document.querySelector("div")
btn.onclick = function() {
  div.innerHTML += '<h1>hh</h1>'
}

区别
  1. 共同点:它们都可以获取并设置元素的内容;
  2. 不同点:
    • innerText 不能识别 HTML 标签,同时空格和换行也会去掉;
    • innerHTML 可以识别 HTML 标签,保留空格和换行;

修改元素属性

元素有很多属性,例如:src、href、id、alt、title 等等,但是有时候我们需要修改属性,可以进行如下操作进行修改。

<button id="btn1">btn1</button>
<button id="btn2">btn2</button>
<img src="">
var btn1 = document.getElementById("btn1")
var btn2 = document.getElementById("btn2")
var img = document.querySelector("img")
btn1.onclick = function() {
  img.src = "#"
}
btn2.onclick = function() {
  img.src = "#"
}

修改其他属性同理。


表单元素的属性操作

利用 DOM 可以操作如下表单元素的属性: type、value、checked、selected、disabled。

  1. 获取属性值:元素对象.属性名
  2. 设置属性值:元素对象.属性名 = 值
<button>btn</button>
<input type="text">

var btn = document.querySelector("button")
var input = document.querySelector("input")
btn.onclick = function() {
  input.value = "哈哈哈"

  // 等价于:btn.disabled = true
  // this指向的是事件函数的调用者
  this.disabled = true
}

例子:显示 / 隐藏密码

<input type="password">
<button>显示</button>

var input = document.querySelector("input")
var btn = document.querySelector("button")
var flag = 0
btn.onclick = function() {
  if (flag === 0) {
    input.type = "text"
    btn.innerText = "隐藏"
    flag = 1
  } else {
    input.type = "password"
    btn.innerText = "显示"
    flag = 0
  }
}

样式的属性操作

可以通过 JS 修改元素的大小、颜色、位置等样式。

  1. element.style
  2. element.className

注意:

  1. JS 里的样式采用驼峰命名法。
  2. JS 修改 style 样式,产生的是行内样式,css 权重比较高。
element.style

示例代码


element.className

示例代码

注意:element.className 会直接更改元素类名,会覆盖原有的类名。


案例:密码框格式错误的提示信息


自定义属性的操作

获取属性值

获取属性值有下面两种方法:

  1. element.属性
  2. element.getAttribute('属性')

区别:

  • element.属性:获取内置属性值(元素本身自带的属性)
  • element.getAttribute('属性'):主要获得自定义的属性
<div id="demo" index=1></div>

var div = document.getElementById("demo")
console.log(div.id)   // demo
console.log(div.index)   // undefined
console.log(div.getAttribute('id'))   // demo
console.log(div.getAttribute('index'))   // 1

设置属性值

设置属性值有下面两种方法:

  1. element.属性 = '值'
  2. element.setAttribute('属性','值')

区别:

  • element.属性 = '值':设置内置属性值
  • element.setAttribute('属性','值'):主要设置自定义属性
<div id="demo" class="content" index="1"></div>

var div = document.getElementById("demo")
div.id = "test"
console.log(div.id)   // test

div.className = "main"
console.log(div.className)   // main

div.setAttribute('class', 'foot')
console.log(div.getAttribute('class'))   // foot

div.setAttribute('index', 'hi')
console.log(div.getAttribute('index'))   // hi

移除属性

element.removeAttribute('属性')

<div id="demo" class="content" index="1"></div>

var div = document.getElementById("demo")
div.removeAttribute('index')
console.log(div)   // <div id="demo" class="content"></div>

自定义属性

自定义属性是通过 getAttribute 来获取的。

自定义属性目的:为了保存并使用数据,有些数据可以保存到页面中而不用保存到数据库中。

但是有些自定义属性容易引起歧义,不容易判断是元素内置属性还是自定义属性,因此 H5 新增了自定义属性,并规定自定义属性以 data- 开头做为属性名并且赋值。

设置自定义属性:

<div data-index="1"></div>
// element.setAttribute('data-index',2)

获取自定义属性:

  1. element.getAttribute('data-index')
  2. element.dataset.index 或者 element.dataset['index'],IE11开始支持
<div data-index="1" data-con="main" data-my-name="zww"></div>

var div = document.querySelector("div")

console.log(div.dataset)   // DOMStringMap {index: "1", con: "main", myName: "zww"}

console.log(div.dataset.index)   // 1
console.log(div.dataset['index'])   // 1

console.log(div.dataset.myName)   // zww
console.log(div.dataset['myName'])   // zww

节点操作

网页中所有内容都是节点(标签、属性、文本、注释等),在 DOM 中,节点使用 node 来表示。

一般节点至少拥有 nodeType(节点类型)、nodeName(节点名称)和 nodeValue(节点值) 这三个基本属性。

• 元素节点 nodeType 为 1
• 属性节点 nodeType 为 2
• 文本节点 nodeType 为 3(包含文字、空格、换行等)

节点层级

利用 DOM 树可以把节点划分为不同的层级关系,常见的是父子兄层级关系。

父节点-node.parentNode

得到的是离元素最近的父级节点,如果找不到父节点就返回为 null。

<div class="demo3">
  <div class="demo2">
    <span class="demo1"></span>
  </div>
</div>
var demo = document.querySelector(".demo1");
console.log(demo.parentNode)   // <div class="demo2">...</div>

子节点
parentNode.childNodes(标准)

返回包含指定节点的子节点的集合。

返回值里面包含了所有的子节点,包括元素节点、文本节点等。

<ul>
  <li></li>
  <li></li>
</ul>
var ul = document.querySelector("ul")
console.log(ul.childNodes)   // NodeList(5) [text, li, text, li, text]

如果只想获得里面的元素节点,需要做如下处理:

var ul = document.querySelector("ul")
for (var i = 0; i < ul.childNodes.length; i++) {
  if (ul.childNodes[i].nodeType === 1) {
    console.log(ul.childNodes[i])
  }
}

parentNode.children(非标准)

这是一个只读属性,返回所有的子元素节点。

var ul = document.querySelector("ul")
console.log(ul.children)   // HTMLCollection(2) [li, li]

parentNode.firstChild、parentNode.lastChild

parentNode.firstChild 返回第一个子节点,找不到返回 null,包含所有的节点。

parentNode.lastChild 返回最后一个子节点,找不到返回 null,包含所有的节点。

var ul = document.querySelector("ul")
console.log(ul.firstChild)   // #text
console.log(ul.lastChild)   // #text

parentNode.firstElementChild、parentNode.lastElementChild

parentNode.firstElementChild 返回第一个子元素节点,找不到返回 null。

parentNode.lastElementChild 返回最后一个子元素节点,找不到返回 null。

注意:仅 IE9 以上支持!

var ul = document.querySelector("ul")
console.log(ul.firstElementChild)   // <li></li>
console.log(ul.lastElementChild)   // <li></li>

解决方案:既没有兼容性问题又可以返回第一个和最后一个子元素节点:

var ul = document.querySelector("ul")
console.log(ul.children[0])   // <li></li>
console.log(ul.children[ul.children.length - 1])   // <li></li>

案例:下拉菜单


兄弟节点
node.nextSibling、node.previousSibling

node.nextSibling 返回当前元素下一个兄弟节点,找不到返回 null,包含所有的节点。

node.previousSibling 返回当前元素上一个兄弟节点,找不到返回 null,包含所有的节点。

<div>div1</div>
<span>span1</span>
var div = document.querySelector("div")
console.log(div.nextSibling)   // #text
console.log(div.previousSibling)   // #text

node.nextElementSibling、node.previousElementSibling

node.nextElementSibling 返回当前元素下一个兄弟元素节点,找不到返回 null。

node.previousElementSibling 返回当前元素上一个兄弟元素节点,找不到返回 null。

注意:仅 IE9 以上支持!

var div = document.querySelector("div")
console.log(div.nextElementSibling)   // <span>span1</span>
console.log(div.previousElementSibling)   // null

创建节点

var element = document.createElement(tagName[, options]);

<ul>
  <li>1</li>
</ul>

var li = document.createElement("li")

添加节点

node.appendChild(child)

该方法将一个节点添加到指定父节点的子节点列表末尾。

var li = document.createElement("li")

var ul = document.querySelector("ul")
ul.appendChild(li)

node.insertBefore(child,指定元素)

该方法将一个节点添加到父节点的指定子节点前面。

var li = document.createElement("li")

var ul = document.querySelector("ul")
ul.insertBefore(li, ul.children[0])

案例:简易版发布留言功能


删除节点

node.removeChild(child)

该方法从 DOM 中删除一个子节点,返回删除的节点。

<button>删除节点</button>
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
var btn = document.querySelector("button")
var ul = document.querySelector("ul")
btn.onclick = function(){
  if(ul.children.length == 0){
    btn.disabled = true
  }else{
      ul.removeChild(ul.children[0])
  }
}

案例:删除留言


复制节点

node.cloneNode()

该方法返回调用该方法的节点的一个副本。

注意:

  1. 如果括号的参数为空或者为 false,则是浅拷贝,就只克隆复制节点本身,不克隆里面的子节点。
  2. 如果括号参数为 true,则是深拷贝,会复制节点本身以及里面所有的子节点。
<ul>
  <li>1</li>
  <li>2</li>
</ul>
var ul = document.querySelector("ul")
var li = ul.children[0].cloneNode(true)
ul.appendChild(li)

案例:动态生成表格


三种动态创建元素的区别

  • document.write()
  • element.innerHTML
  • document.createElement()

区别:

  1. document.write() 是直接将内容写入页面的内容流,但是文档流执行完毕,会导致页面全部重绘。
  2. innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘。
  3. innerHTML 创建多个元素效率更高(在不要拼接字符串,采取数组形式拼接的前提下)。
  4. createElement() 创建多个元素效率稍低一点点。

采用 element.innerHTML 字符串形式:

function fn() {
  var data1 = +new Date()

  var str = ''
  for (var i = 0; i < 1000; i++) {
    document.body.innerHTML += '<div style="border:1px solid red;"></div>'
  }
  var data2 = +new Date()
  console.log(data2 - data1)   // 102左右
}
fn()

采用 element.innerHTML 数组形式:

function fn() {
  var data1 = +new Date()
  var arr = []
  for (var i = 0; i < 1000; i++) {
    arr.push('<div style="border:1px solid red;"></div>')
  }
  document.body.innerHTML = arr.join("")
  var data2 = +new Date()
  console.log(data2 - data1)   // 3左右
}
fn()

采用 element.createElement():

function fn() {
  var data1 = +new Date()
  for (var i = 0; i < 1000; i++) {
    var div = document.createElement("div")
    div.style.border = "1px solid red"
    document.body.appendChild(div)
  }
  var data2 = +new Date()
  console.log(data2 - data1)   // 10左右
}
fn()

总结:在不同浏览器下,innerHTML 效率要比 createElement 效率高(前提是 innerHTML 使用数组形式)


事件

注册事件

给元素添加事件,称为 注册事件 或 绑定事件。

注册事件有两种方式:传统方式方法监听注册方式

传统注册方式
  • 利用 on 开头的事件 onclick
  • <button onclick="alert("hello")">
  • btn.onclick=function(){}

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

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


方法监听注册方式
  • w3c 标准
  • addEventListener() 是一个方法
  • IE9 以前不支持,可使用 attachEvent() 代替

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

按注册顺序依次执行。


addEventListener事件监听方式

eventTarget.addEventListener(type,listener[,useCapture])

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

该方法接收三个参数:
• type:事件类型字符串,比如 click、mouseover 等等,这里不要带 on。
• listener:事件处理函数,事件发生时,会调用该监听函数
• useCapture:可选参数,是一个布尔值,默认是 false。

<button>addEventListener</button>

var btn = document.querySelector("button")
btn.addEventListener("click", function() {
  alert("hello addEventListener")
})

注册事件兼容性解决方案
function addEventListener(element, eventName, fn) {
  if (element.addEventListener) {
    element.addEventListener(eventName, fn)
  } else if (element.attachEvent) {
    element.attachEvent("on" + eventName, fn)
  } else {
    element["on" + eventName] = fn
  }
}

删除事件

传统方式

eventTarget.onclick = null

<div>传统方式</div>

var div = document.querySelector("div")
div.onclick = function(){
  alert("hello")
  div.onclick = null
}

方法监听注册方式

eventTarget.removeEventListener(type,listener[,useCapture])

var div = document.querySelector("div")
div.addEventListener('click', fn)
function fn() {
  alert("hello")
  div.removeEventListener('click', fn)
}

兼容性解决方案
function removeEventListener(element, eventName, fn) {
  if (element.removeEventListener) {
    element.removeEventListener(eventName, fn)
  } else if (element.detachEvent) {
    element.detachEvent("on" + eventName, fn)
  } else {
    element["on" + eventName] = null
  }
}

DOM事件流

事件流描述的是从页面中接收事件的顺序。

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

DOM 事件流分为三个阶段:

  1. 捕获阶段
  2. 当前目标阶段
  3. 冒泡阶段

注意:

  1. JS 代码中只能执行捕获或冒泡其中的一个阶段。
  2. onclick 和 attachEvent 只能得到冒泡阶段。
  3. addEventListener 的第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是 false(不写默认为false),表示在事件冒泡阶段调用事件处理程序。
  4. 有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave。
捕获
<div class="father">
  <div class="son"></div>
</div>
var father = document.querySelector(".father")
var son = document.querySelector(".son")

father.addEventListener('click', function() {
  alert("father")
}, true)

son.addEventListener('click', function() {
  alert("son")
}, true)

// 点击son后,先弹出father,再弹出son
// 点击father,只弹出father

冒泡
father.addEventListener('click', function() {
  alert("father")
}, false)

son.addEventListener('click', function() {
  alert("son")
}, false)

// 点击son后,先弹出son,再弹出father
// 点击father,只弹出father

事件对象

event 对象代表事件的状态,比如键盘按键的状态、鼠标按钮的状态等。

  1. event 就是一个事件对象,写到监听函数的小括号里,当形参来看。
  2. 事件对象只有当有了事件才会存在,它是系统给我们自动创建的,不需要我们传递参数。
  3. 事件对象可以自己命名,比如 event、evt、e。
  4. 事件对象也有兼容性问题,在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。

兼容性解决方案:

var div = document.querySelector("div")
div.onclick = function(e){
  e = e || window.event
  console.log(e)
}

事件对象常见的属性方法
事件对象属性方法 说明
e.target 返回触发事件的对象(标准)
e.srcElement 返回触发事件的对象,IE6~8使用(非标准)
e.type 返回事件的类型
e.cancelBubble 阻止冒泡,IE6~8使用(非标准)
e.returnValue 阻止默认事件,IE6~8使用(非标准)
e.preventDefault() 阻止默认事件(标准)
e.stopPropagation() 阻止冒泡(标准)

e.target与this的区别

区别:
• e.target 点击了哪个元素就返回哪个元素。
• this 哪个元素绑定了这个点击事件,那么就返回谁。

跟这个 this 有个非常相似的属性:currentTarget,但是存在兼容性问题,IE6~8不兼容!

<ul>
  <li>1</li>
  <li>2</li>
</ul>

// 当点击li时,this打印<ul>...</ul>,而e.target打印<li></li>
var ul = document.querySelector("ul")
ul.addEventListener("click",function(e){
  console.log(this)
  console.log(e.target)
})

e.target兼容性问题的解决方案
div.onclick = function(e) {
  e = e || window.event
  var target = e.target || e.srcElement
  console.log(target)
}

返回事件类型
var div = document.querySelector('div')
div.addEventListener("click", fn)

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

阻止默认事件
// 阻止默认行为,让链接不跳转
<a href="https://zhouwanwen.com">阻止链接跳转</a>

// 1. 普通浏览器e.preventDefault()
var a = document.querySelector("a")
a.addEventListener('click', function (e) {
  e.preventDefault()
})

// 2.低版本浏览器:IE6~8,e.returnValue
a.onclick = function (e) {
  e.returnValue
}

// 3.return false,没有兼容性问题,但是return后面的代码不会执行,只限于传统的注册方式
a.onclick = function () {
  return false
}

阻止事件冒泡

阻止事件冒泡的两种方式:

  1. 标准写法:e.stopPropagation()
  2. 非标准写法(IE6~8):cancelBubble
<div class="father">
  <div class="son"></div>
</div>
var father = document.querySelector(".father")
var son = document.querySelector(".son")

son.addEventListener('click', function(e) {
  alert("son")
  e.stopPropagation()
})
father.addEventListener('click', function() {
  alert("father")
})

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

事件委托(代理、委派)

原理:不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。

作用:只操作了一次 DOM,提高了程序的性能。

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
var ul = document.querySelector("ul")
ul.addEventListener('click', function(e) {
  e.target.style.backgroundColor = 'red'
})

鼠标事件
鼠标事件 触发条件
onclick 鼠标点击左键触发
onmouseover 鼠标经过触发
onmouseout 鼠标离开触发
onfocus 获得鼠标焦点触发
onblur 失去鼠标焦点触发
onmousemove 鼠标移动触发
onmouseup 鼠标弹起触发
onmousedown 鼠标按下触发
禁止鼠标右键菜单-contextmenu

contextmenu 主要控制应该何时显示上下文菜单,主要用于取消默认的上下文菜单。

document.addEventListener('contextmenu', function(e) {
  e.preventDefault()
})

禁止鼠标选中-selectstart
document.addEventListener('selectstart', function(e) {
  e.preventDefault()
})

鼠标事件对象
鼠标事件对象 说明
e.clientX 返回鼠标相对于浏览器窗口可视区的X坐标
e.clientY 返回鼠标相对于浏览器窗口可视区的Y坐标
e.pageX 返回鼠标相对于文档页面的X坐标,IE9+支持
e.pageY 返回鼠标相对于文档页面的Y坐标,IE9+支持
e.screenX 返回鼠标相对于电脑屏幕的X坐标
e.screenY 返回鼠标相对于文档页面的Y坐标

案例:图片随鼠标移动


键盘事件
键盘事件 触发条件
onkeyup 某个键盘按键被松开时触发
onkeydown 某个键盘按键被按下时触发
onkeypress 某个键盘按键被按下时触发,但是不识别功能键,如ctrl、shift等等

注意:

  1. 如果使用 addEventListener 就不需要加 on
  2. 三个事件的执行顺序:keydown - keypress - keyup

键盘事件对象
键盘事件对象属性 说明
keyCode 返回该键的ASCII值

注意:

  1. keyup、keydown 事件是不区分字母大小写的,a 和 A 得到的都是 65。
  2. keypress 事件是区分大小写的,a 得到 97,A 得到 65。

案例:模拟京东,按下s键,定位到搜索框
案例:模拟京东快递单号查询-当输入文本时,文本框上方自动显示大字号的内容


mouseenter与mouseover区别

  • mouseover 鼠标经过自身盒子会触发,经过子盒子也会触发。
  • mouseenter 只会经过自身盒子触发。
  • mouseenter 不会冒泡,跟它搭配的鼠标离开 mouseleave 同样不会冒泡。



BOM

概念

BOM(Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window。

DOM 与 BOM 区别:
DOM:
• 文档对象模型
• DOM 就是把文档当做一个对象来看待
• DOM 的顶级对象是 document
• DOM 主要是操作页面元素
• DOM 是 W3C 标准规范

BOM:
• 浏览器对象模型
• 把浏览器当做一个对象来看待
• BOM 的顶级对象是 window
• BOM 主要是浏览器窗口交互的一些对象
• BOM 是浏览器厂商在各自浏览器上定义的,兼容性较差

BOM 比 DOM 更大,它包含了 DOM。window 包含了 document、location、navigation、screen、history。

window 对象是浏览器的顶级对象,它具有双重角色。

  1. 它是 JS 访问浏览器窗口的一个接口。
  2. 它是一个全局对象。定义在全局作用域中的变量、函数都会变成 window 对象的属性和方法。

注意:window 下的一个特殊属性 window.name。


window对象常见事件

窗口加载事件

onload / load

window.onload 是窗口(页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS文件等等),就调用的处理函数。

window.onload = function(){}
window.addEventListener('load',function(){})

注意:

  1. 有了 window.onload 就可以把 JS 代码写到页面元素的上方,因为 onload 是等页面内容全部加载完毕再去执行处理函数。
  2. window.onload 传统注册事件方式只能写一次,如果有多个,将以最后一个 window.onload 为准。
  3. 如果使用 addEventListener 则没有限制。
<button>按钮</button>

window.onload = function () {
  var btn = document.querySelector("button")
  btn.onclick = function () {
    alert("点击")
  }
}

DOMContentLoaded

document.addEventListener('DOMContentLoaded',function(){})

DOMContentLoaded 事件触发时,仅当 DOM 加载完成,不包括样式表,图标,flash 等等。IE9 以上支持!

如果页面的图片这些有很多的话,从用户访问到 onload 触发可能需要较长的时间,从而导致这段时间内交互效果不能实现,必然影响用户的体验,此时用 DOMContentLoaded 事件比较合适。

// 先执行DOMContentLoaded,后执行load
window.addEventListener('load',function(){
  alert("load")
})

document.addEventListener('DOMContentLoaded',function(){
  alert("DOMContentLoaded")
})

注意:

  1. load 是等页面内容全部加载完毕,包含了页面 DOM 元素、图片、CSS 等等。
  2. DOMContentLoaded 是 DOM 加载完毕后执行,不包含图片、CSS 等等。
  3. DOMContentLoaded 加载速度比 load 更快一些。

调整窗口大小事件

window.onresize = function(){}
window.addEventListener('resize',function(){})

window.onresize 是调整窗口大小加载事件,当触发时就调用的处理函数。

注意:

  1. 只要窗口大小发生了像素变化,就会触发该事件。
  2. 可以利用该事件完成响应式布局。window.innerWidth 当前屏幕宽度,window.innerHeight 当前屏幕高度。

定时器

window 对象提供了两种定时器方法:

  1. setTimeout()
  2. setInterval()

setTimeout()定时器

window.setTimeout(调用函数,[延迟的毫秒数])

setTimeout() 方法用于设置一个定时器,该定时器在定时器到期后执行调用函数。

注意:

  1. window 可以省略。
  2. 这个调用函数可以直接写函数,或者写函数名。
  3. 延迟的毫秒数可以省略,省略默认是 0。
function callback(){
  console.log("出现了")
}
var time1 = setTimeout(callback,2000)
var time2 = setTimeout(callback,3000)

停止setTimeout()定时器

window.clearTimeout(timeoutID)

clearTimeout() 方法取消先前通过调用 setTimeout() 建立的定时器。

var btn = document.querySelector("button")
var time = setTimeout(function() {
  alert("弹出了")
}, 6000)
btn.addEventListener('click', function() {
  clearTimeout(time)
})

setInterval()定时器

window.setInterval(回调函数,[间隔毫秒数])

setInterval() 方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。

案例:倒计时效果


停止setInterval()定时器

window.clearInterval(timeoutID)

clearInterval() 方法取消先前通过调用 setInterval() 建立的定时器。

<button class="open">开启setInterval定时器</button>
<button class="close">关闭setInterval定时器</button>
var open = document.querySelector(".open")
var close = document.querySelector(".close")
var timer = null
open.addEventListener('click', function() {
  timer = setInterval(function() {
    console.log('hello')
  }, 500)
})
close.addEventListener('click', function() {
  clearInterval(timer)
})

案例:发送短信效果


this指向

this 的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定 this 到底指向谁,一般情况下 this 的最终指向的是那个调用它的对象。

// 1.全局作用域或普通函数中的this指向全局对象window

console.log(this)   // window

function fn() {
  console.log(this)   // window
}
fn()

setTimeout(function(){
  console.log(this)   // window
},1000)

// 2.方法调用中,谁调用this指向谁
var o = {
  sayHi: function() {
    console.log(this)   // {sayHi: ƒ}
  }
}
o.sayHi()

// 3.构造函数中,this指向构造函数的实例
function Fn(){
  console.log(this)   // Fn {}
}
var fn = new Fn()

JS执行队列

JavaScript 语言的一大特点就是单线程,也就是说,同一时间只能做一件事。

单线程也就意味着,所有任务需要排队,前一个任务结束后,才会执行后一个任务。

这样就导致一个问题:如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

为解决这个问题,JS 中出现了同步与异步:

  1. 同步:前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。
  2. 异步:在做一件事时,因为这件事会花费很长的时间,在做这件事的同时,还可以去处理其他事情。

同步任务:都在主线程上执行,形成一个执行栈。
异步任务:JS 的异步是通过回调函数实现的。

一般来说,异步任务有以下几种类型:

  1. 普通时间,如 click、resize 等。
  2. 资源加载,如 load、error 等。
  3. 定时器,包括 setInterval、setTimeout 等。

异步任务相关回调函数添加到任务队列中(消息队列)。

执行机制:

  1. 先执行执行栈中的同步任务。
  2. 异步任务(回调函数)放入任务队列中。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。

由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,这种机制被称为事件循环


location对象

window 对象提供了一个 location 属性,用于获取或设置窗体的 url,并且可以用于解析 url。

location对象的属性

location对象属性 返回值
location.href 获取或设置整个url
location.host 返回主机(域名)
location.port 返回端口号
location.pathname 返回路径
location.search 返回参数
location.hash 返回片段

location对象的方法

location对象方法 返回值
location.assign() 跟href一样,可以跳转页面,记录历史,能后退页面
location.replace() 替换当前页面,不记录历史,不能后退页面
location.reload() 重新加载页面

navigator 对象包含了浏览器的信息,它有很多属性,常用的是 userAgent,该属性可以返回由客户机发送服务器的 user-agent 头部的值。

// 判断用户打开的是移动端页面还是PC端

if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
  location.href = "";   // 手机
} else {
  location.href = "";   // 电脑
}

history对象

window 对象提供了一个 history 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的 url。

history对象方法 作用
back() 后退功能
forward() 前进功能
go(参数) 前进后退功能,参数如果是1则前进一个页面,如果是-1则后退一个页面



JavaScript特效三大系列

元素偏移量offset系列

概述

offset 系列属性可以动态的获得该元素的位置(偏移)、大小等。
• 获取元素距离带有定位父元素的位置。
• 获取元素自身的大小(宽度、高度)。
• 返回的数值都不带单位。

offset 系列常用属性:

属性 作用
element.offsetParent 返回作为该元素带有定位的父级元素,如果父级都没有定位则返回body
element.offsetTop 返回元素相对带有定位父元素上方的偏移
element.offsetLeft 返回元素相对带有定位父元素左边框的偏移
element.offsetWidth 返回自身包括padding、边框、内容区的宽度
element.offsetHeight 返回自身包括padding、边框、内容区的高度
<div class="father">
  <div class="son"></div>
</div>
* {
  margin: 0;
  padding: 0;
}
.father {
  position: relative;
  width: 200px;
  height: 200px;
  background-color: red;
  margin-left: 100px;
  margin-top: 50px;
}
.son {
  width: 100px;
  height: 100px;
  background-color: green;
  margin-left: 40px;
  padding: 10px;
}
var father = document.querySelector(".father")
var son = document.querySelector(".son")
console.log(father.offsetLeft)   // 100
console.log(father.offsetTop)   // 50

console.log(son.offsetTop)   // 0
console.log(son.offsetLeft)   // 40

console.log(father.offsetWidth)   // 200
console.log(father.offsetHeight)   // 200

console.log(son.offsetWidth)   // 120
console.log(son.offsetHeight)   // 120

console.log(son.offsetParent)   // <div class="father">...</div>

offset与style区别

offset:
• offset 可以得到任意样式表中的样式值。
• offset 系列获得的数值是没有单位的。
• offsetWidth 包含 padding + border + width。
• offsetWidth 这些属性是只读属性,只能获取,不能赋值。

style:
• style 只能得到行内样式表中的样式值。
• style.width 获得的是带有单位的字符串。
• style.width 获得不包含 padding、border 的值。
• style.width 等属性是可读写属性,可以获取,也可以赋值。

因此,如果我们想要获取元素的大小位置,用 offset 更合适;如果想要给元素更改值,则需要使用 style。


案例:鼠标在盒子里获取坐标

案例:模态框拖拽


元素可视区client系列

使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到该元素的边框大小、元素大小等。

属性 作用
element.clientTop 返回元素上边框的大小
element.clientLeft 返回元素左边框的大小
element.clientWidth 返回自身包括padding、内容区宽度,不含边框,返回数值不带单位
element.clientHeight 返回自身包括padding、内容区高度,不含边框,返回数值不带单位

元素滚动scroll系列

使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。

属性 作用
element.scrollTop 返回被卷去的上侧距离,返回数值不带单位
element.scrollLeft 返回被卷去的左侧距离,返回数值不带单位
element.scrollWidth 返回自身实际的宽度,不含边框,返回数值不带单位
element.scrollHeight 返回自身实际的高度,不含边框,返回数值不带单位
<div>hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello </div>
div{
  width:100px;
  height:200px;
  overflow: auto;
  border: 1px solid red;
  padding: 2px;
}
var div = document.querySelector('div')
console.log(div.scrollWidth)   // 87

// scroll滚动事件,当滚动条发生变化会触发的事件
div.addEventListener('scroll',function(){
  console.log(div.scrollTop)
})

三大系列总结

对比 作用
element.offsetWidth 返回自身包括padding、边框、内容区的宽度,返回数值不带单位
element.clientWidth 返回自身包括padding、内容区的宽度,不含边框,返回数值不带单位
element.scrollWidth 返回自身实际的宽度,不含边框,返回数值不带单位

他们的主要用法:

  1. offset 经常用于获得元素位置:offsetLeft、offsetTop。
  2. client 经常用于获取元素大小:clientWidth、clientHeight。
  3. scroll 经常用于获取滚动距离:scrollTop、scrollLeft。
  4. 页面滚动距离通过 window.pageXOffset 获得。



动画函数封装

动画实现原理

核心原理:通过定时器 setInterval 不断移动盒子位置。

实现步骤:

  1. 获得盒子当前位置。
  2. 让盒子在当前位置加上一个移动距离。
  3. 利用定时器不断重复操作。
  4. 结束定时器。
  5. 需要给此元素添加定位。
<div></div>
div {
  width: 100px;
  height: 100px;
  background-color: red;
  position: absolute;
  left: 0;
}
var div = document.querySelector('div')
var timer = setInterval(function() {
  if (div.offsetLeft >= 400) {
    clearInterval(timer)
  }
  div.style.left = div.offsetLeft + 5 + 'px'
}, 30)

动画函数简单封装

var div = document.querySelector('div')
function animate(obj, target) {
  clearInterval(obj.timer)
  obj.timer = setInterval(function() {
    if (obj.offsetLeft >= target) {
      clearInterval(obj.timer)
    }
    obj.style.left = obj.offsetLeft + 1 + 'px'
  }, 30)
}
animate(div, 100)

缓动效果原理

缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来。

var div = document.querySelector('div')
function animate(obj, target) {
  clearInterval(obj.timer)
  obj.timer = setInterval(function() {
    var step = Math.ceil((target - obj.offsetLeft) / 100)
    if (obj.offsetLeft == target) {
      clearInterval(obj.timer)
    }
    obj.style.left = obj.offsetLeft + step + 'px'
  }, 15)
}
animate(div, 300)



移动端特效

触屏事件

概述

触屏事件 touch(也称触摸事件),touch 对象代表一个触摸点。

常见的触屏事件:

触屏touch事件 说明
touchstart 手指触摸到一个DOM元素时触发
touchmove 手指在一个DOM元素上滑动时触发
touchend 手指在一个DOM元素上移开时触发

触摸事件对象(TouchEvent)

TouchEvent 是一类描述手指在触摸平面的状态变化的事件。

touchstart、touchmove、touchend 三个事件都会有各自的事件对象。

触摸列表 说明
touches 正在触摸屏幕的所有手指的一个列表
targetTouches 正在触摸当前DOM元素上的手指的一个列表
changedTouches 手指状态发生了改变的列表

移动端拖动元素

touchstart、touchmove、touchend 可以实现拖动元素。

<div></div>
div {
  width: 100px;
  height: 100px;
  background-color: red;
  position: absolute;
}
var div = document.querySelector('div')
var startX = 0
var startY = 0
var boxX = 0
var boxY = 0
div.addEventListener('touchstart', function(e) {
  startX = e.targetTouches[0].pageX
  startY = e.targetTouches[0].pageY
  boxX = this.offsetLeft
  boxY = this.offsetTop
})
div.addEventListener('touchmove', function(e) {
  var moveX = e.targetTouches[0].pageX - startX
  var moveY = e.targetTouches[0].pageY - startY
  this.style.left = moveX + boxX + 'px'
  this.style.top = moveY + boxY + 'px'
  e.preventDefault()
})

classList属性

这是 HTML5 新增的一个属性,返回元素的类名。IE10以上支持。

该属性用于在元素中添加、移除、切换 CSS 类。

添加类

element.classList.add('类名')

这是在后面追加一个类,不会覆盖以前的类名。

<div class="one"></div>
var div = document.querySelector("div")
console.log(div.classList)   // DOMTokenList ["one", value: "one"]
div.classList.add('two')
console.log(div.classList)   // DOMTokenList(2) ["one", "two", value: "one two"]

移除类

element.classList.remove('类名')

div.classList.remove('one')
console.log(div.classList)   // DOMTokenList ["two", value: "two"]

切换类

element.classList.toggle('类名')

如果存在该类名,则去掉该类名;如果不存在,则添加。

<button>切换</button>
.bg {
  background-color: black;
}
var btn = document.querySelector("button")
btn.addEventListener('click', function() {
  document.body.classList.toggle('bg')
})

click延迟解决方案

在移动端 click 事件会有 300ms 的延迟,原因是移动端屏幕双击会缩放页面。

解决方案如下:

  1. 禁用缩放。浏览器禁用默认的双击缩放行为并且去掉 300ms 的点击延迟。
<meta name="viewport" content="user-scalable=no">
  1. 利用 touch 事件自己封装这个事件解决。
    原理:
    (1)当我们手指触摸屏幕,记录当前触摸时间。
    (2)当我们手指离开屏幕,用离开的时间减去触摸的时间。
    (3)如果时间小于 150ms,并且没有滑动过屏幕,那么就定义为点击。
  2. 使用fastclick插件解决。



本地存储

本地存储特性:

  1. 数据存储在用户浏览器中。
  2. 设置、读取方便,甚至页面刷新不丢失数据。
  3. 容量较大,sessionStorage 约 5M,localStorage 约 20M。
  4. 只能存储字符串,可以将对象 JSON.stringify() 编码后存储。

window.sessionStorage

  1. 生命周期为关闭浏览器窗口。
  2. 在同一个窗口(页面)下数据可以共享。
  3. 以键值对的形式存储使用。

存储数据:

sessionStorage.setItem(key,value)


获取数据:

sessionStorage.getItem(key)


删除数据:

sessionStorage.removeItem(key)


删除所有数据:

sessionStorage.clear()


window.localStorage

  1. 生命周期永久生效,除非手动删除,否则关闭页面也会存在。
  2. 可以多窗口(页面)共享(同一浏览器可以共享)。
  3. 以键值对的形式存储使用。

存储数据:

localStorage.setItem(key,value)


获取数据:

localStorage.getItem(key)


删除数据:

localStorage.removeItem(key)


删除所有数据:

localStorage.clear()

posted @ 2020-06-20 15:01  LqZww  阅读(209)  评论(0编辑  收藏  举报