DOM

文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。
W3C 已经定义了一系列的 DOM 接口,通过这些 DOM 接口可以改变网页的内容、结构和样式。

image.png

文档:一个页面就是一个文档,DOM 中使用 document 表示

元素:页面中的所有标签都是元素,DOM 中使用 element 表示

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

DOM 把以上内容都看做是对象

获取页面元素

获取页面中的元素可以使用以下几种方式:

  • 根据 ID 获取
  • 根据标签名获取
  • 通过 HTML5 新增的方法获取
  • 特殊元素获取

根据 ID 获取

使用 getElementById() 方法可以获取带有 ID 的元素对象。

  1. document.getElementById('id');

使用 console.dir() 可以打印我们获取的元素对象,更好的查看对象里面的属性和方法。

根据标签名获取

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

  1. document.getElementsByTagName('标签名');

通过 HTML5 新增的方法获取

  1. document.getElementsByClassName(‘类名’);// 根据类名返回元素对象集合
  2. document.querySelector('选择器'); // 根据指定选择器返回第一个元素对象
  3. document.querySelectorAll('选择器'); // 根据指定选择器返回

querySelector 和 querySelectorAll里面的选择器需要加符号,比如:document.querySelector(‘#nav’);

获取特殊元素(body,html)

  1. doucumnet.body // 返回body元素对象
  2. document.documentElement // 返回html元素对象

改变 HTML 元素

方法 描述
element.innerHTML = new html content 改变元素的 inner HTML
element.attribute = new value 改变 HTML 元素的属性值
element.setAttribute(attribute, value) 改变 HTML 元素的属性值
element.style.property = new style 改变 HTML 元素的样式

添加和删除元素

方法 描述
document.createElement(element) 创建 HTML 元素
document.removeChild(element) 删除 HTML 元素
document.appendChild(element) 添加 HTML 元素
document.replaceChild(element) 替换 HTML 元素
document.write(text) 写入 HTML 输出流

查找 HTML 对象

属性
document.anchors
document.applets
document.baseURI
document.body
document.cookie
document.doctype
document.documentElement
document.documentMode
document.documentURI
document.domain
document.domConfig
document.embeds
document.forms
document.head
document.images
document.implementation
document.inputEncoding
document.lastModified
document.links
document.readyState
document.referrer
document.scripts

节点操作

获取元素通常使用两种方式:

  1. 利用 DOM 提供的方法获取元素(document.getElementById()等)
  2. 利用节点层级关系获取元素

这两种方式都可以获取元素节点,我们后面都会使用,但是节点操作更简单

网页中的所有内容都是节点(标签、属性、文本、注释等),在DOM 中,节点使用 node 来表示。
HTML DOM 树中的所有节点均可通过 JavaScript 进行访问,所有 HTML 元素(节点)均可被修改,也可以创建或删除。

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

元素节点 nodeType 为 1
属性节点 nodeType 为 2
文本节点 nodeType 为 3 (文本节点包含文字、空格、换行等)
我们在实际开发中,节点操作主要操作的是元素节点

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

  1. 父级节点
  • node.parentNode
    parentNode 属性可返回某节点的父节点,注意是最近的一个父节点
    如果指定的节点没有父节点则返回 null
  1. 子节点
  • parentNode.childNodes(标准)
    parentNode.childNodes 返回包含指定节点的子节点的集合,该集合为即时更新的集合。
    注意:返回值里面包含了所有的子节点,包括元素节点,文本节点等。
    如果只想要获得里面的元素节点,则需要专门处理。 所以我们一般不提倡使用childNodes
  • parentNode.children(非标准)
    parentNode.children 是一个只读属性,返回所有的子元素节点。它只返回子元素节点,其余节点不返回 (这个是我们重点掌握的)。
  • parentNode.firstChild
    firstChild 返回第一个子节点,找不到则返回null。同样,也是包含所有的节点。
  • parentNode.lastChild
    lastChild 返回最后一个子节点,找不到则返回null。同样,也是包含所有的节点。
  1. 兄弟节点
  • node.nextSibling
    nextSibling 返回当前元素的下一个兄弟元素节点,找不到则返回null。同样,也是包含所有的节点。
  • node.previousSibling
    previousSibling 返回当前元素上一个兄弟元素节点,找不到则返回null。同样,也是包含所有的节点。
  • node.nextElementSibling
    nextElementSibling 返回当前元素下一个兄弟元素节点,找不到则返回null。
  • node.previousElementSibling
    previousElementSibling 返回当前元素上一个兄弟节点,找不到则返回null。

创建节点:

  • document.createElement(‘tagName’)
    document.createElement() 方法创建由 tagName 指定的 HTML 元素。因为这些元素原先不存在,是根据我们的需求动态生成的,所以我们也称为动态创建元素节点。

添加节点:

  • node.appendChild(child)
    node.appendChild() 方法将一个节点添加到指定父节点的子节点列表末尾。类似于 CSS 里面的 after 伪元素。
  • node.insertBefore(child, 指定元素)
    node.insertBefore() 方法将一个节点添加到父节点的指定子节点前面。类似于 CSS 里面的 before 伪元素。

删除节点:

  • node.removeChild(child)
    node.removeChild() 方法从 DOM 中删除一个子节点,返回删除的节点。

复制节点(克隆节点):

  • node.cloneNode()
  1. 如果括号参数为空或者为 false ,则是浅拷贝,即只克隆复制节点本身,不克隆里面的子节点。
  2. 如果括号参数为 true ,则是深度拷贝,会复制节点本身以及里面所有的子节点。

三种动态创建元素区别:

  1. document.write 是直接将内容写入页面的内容流,但是文档流执行完毕,则它会导致页面全部重绘
  2. innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘
  3. innerHTML 创建多个元素效率更高(不要拼接字符串,采取数组形式拼接),结构稍微复杂
  4. createElement() 创建多个元素效率稍低一点点,但是结构更清晰
    总结:不同浏览器下,innerHTML 效率要比 creatElement 高

事件基础

  1. document.getElementById(id).onclick = function(){code}

事件三要素

  1. 事件源 (谁)
  2. 事件类型 (什么事件)
  3. 事件处理程序 (做啥)

仿京东显示密码

image.png

  1. <head>
  2. <meta charset="UTF-8">
  3. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  4. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  5. <title>Document</title>
  6. <style>
  7. .box {
  8. position: relative;
  9. width: 400px;
  10. border-bottom: 1px solid #ccc;
  11. margin: 100px auto;
  12. }
  13. .box input {
  14. width: 370px;
  15. height: 30px;
  16. border: 0;
  17. outline: none;
  18. }
  19. .box img {
  20. position: absolute;
  21. top: 2px;
  22. right: 2px;
  23. width: 24px;
  24. }
  25. </style>
  26. </head>
  27. <body>
  28. <div class="box">
  29. <label for="">
  30. <img src="images/close.png" alt="" id="eye">
  31. </label>
  32. <input type="password" name="" id="pwd">
  33. </div>
  34. <script>
  35. // 1. 获取元素
  36. var eye = document.getElementById('eye');
  37. var pwd = document.getElementById('pwd');
  38. // 2. 注册事件 处理程序
  39. var flag = 0;
  40. eye.onclick = function() {
  41. // 点击一次之后, flag 一定要变化
  42. if (flag == 0) {
  43. pwd.type = 'text';
  44. eye.src = 'images/open.png';
  45. flag = 1; // 赋值操作
  46. } else {
  47. pwd.type = 'password';
  48. eye.src = 'images/close.png';
  49. flag = 0;
  50. }
  51. }
  52. </script>
  53. </body>

循环精灵图背景

可以利用 for 循环设置一组元素的精灵图背景

image.png

  1. <head>
  2. <meta charset="UTF-8">
  3. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  4. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  5. <title>Document</title>
  6. <style>
  7. * {
  8. margin: 0;
  9. padding: 0;
  10. }
  11. li {
  12. list-style-type: none;
  13. }
  14. .box {
  15. width: 250px;
  16. margin: 100px auto;
  17. }
  18. .box li {
  19. float: left;
  20. width: 24px;
  21. height: 24px;
  22. background-color: pink;
  23. margin: 15px;
  24. background: url(images/sprite.png) no-repeat;
  25. }
  26. </style>
  27. </head>
  28. <body>
  29. <div class="box">
  30. <ul>
  31. <li></li>
  32. <li></li>
  33. <li></li>
  34. <li></li>
  35. <li></li>
  36. <li></li>
  37. <li></li>
  38. <li></li>
  39. <li></li>
  40. <li></li>
  41. <li></li>
  42. <li></li>
  43. </ul>
  44. </div>
  45. <script>
  46. // 1. 获取元素 所有的小li
  47. var lis = document.querySelectorAll('li');
  48. for (var i = 0; i < lis.length; i++) {
  49. // 让索引号 乘以 44 就是每个li 的背景y坐标 index就是我们的y坐标
  50. var index = i * 44;
  51. lis[i].style.backgroundPosition = '0 -' + index + 'px';
  52. }
  53. </script>
  54. </body>

排他思想

image.png

  1. <body>
  2. <button>按钮1</button>
  3. <button>按钮2</button>
  4. <button>按钮3</button>
  5. <button>按钮4</button>
  6. <button>按钮5</button>
  7. <script>
  8. // 1. 获取所有按钮元素
  9. var btns = document.getElementsByTagName('button');
  10. // btns得到的是伪数组 里面的每一个元素 btns[i]
  11. for (var i = 0; i < btns.length; i++) {
  12. btns[i].onclick = function() {
  13. // (1) 我们先把所有的按钮背景颜色去掉 干掉所有人
  14. for (var i = 0; i < btns.length; i++) {
  15. btns[i].style.backgroundColor = '';
  16. }
  17. // (2) 然后才让当前的元素背景颜色为pink 留下我自己
  18. this.style.backgroundColor = 'pink';
  19. }
  20. }
  21. //2. 首先先排除其他人,然后才设置自己的样式 这种排除其他人的思想我们成为排他思想
  22. </script>
  23. </body>

全选反选

image.png

  1. <script>
  2. // 1. 全选和取消全选做法: 让下面所有复选框的checked属性(选中状态) 跟随 全选按钮即可
  3. // 获取元素
  4. var j_cbAll = document.getElementById('j_cbAll'); // 全选按钮
  5. var j_tbs = document.getElementById('j_tb').getElementsByTagName('input'); // 下面所有的复选框
  6. // 注册事件
  7. j_cbAll.onclick = function() {
  8. // this.checked 它可以得到当前复选框的选中状态如果是true 就是选中,如果是false 就是未选中
  9. console.log(this.checked);
  10. for (var i = 0; i < j_tbs.length; i++) {
  11. j_tbs[i].checked = this.checked;
  12. }
  13. }
  14. // 2. 下面复选框需要全部选中, 上面全选才能选中做法: 给下面所有复选框绑定点击事件,每次点击,都要循环查看下面所有的复选框是否有没选中的,如果有一个没选中的, 上面全选就不选中。
  15. for (var i = 0; i < j_tbs.length; i++) {
  16. j_tbs[i].onclick = function() {
  17. // flag 控制全选按钮是否选中
  18. var flag = true;
  19. // 每次点击下面的复选框都要循环检查4个小按钮是否全被选中
  20. for (var i = 0; i < j_tbs.length; i++) {
  21. if (!j_tbs[i].checked) {
  22. flag = false;
  23. break; // 退出for循环 这样可以提高执行效率 因为只要有一个没有选中,剩下的就无需循环判断了
  24. }
  25. }
  26. j_cbAll.checked = flag;
  27. }
  28. }
  29. </script>

事件高级

注册事件(绑定事件)

给元素添加事件,称为注册事件或者绑定事件。
注册事件有两种方式:传统方式和方法监听注册方式

传统注册方式

  • 利用 on 开头的事件 onclick
  • btn.onclick = function() {}
  • 特点: 注册事件的唯一性
  • 同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数

方法监听注册方式

  • w3c 标准 推荐方式
  • addEventListener() 它是一个方法
  • IE9 之前的 IE 不支持此方法,可使用 attachEvent() 代替
  • 特点:同一个元素同一个事件可以注册多个监听器
  • 按注册顺序依次执行

addEventListener 事件监听方式

  • eventTarget.addEventListener(type, listener[, useCapture])
    eventTarget.addEventListener()方法将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。
    该方法接收三个参数:
    type:事件类型字符串,比如 click 、mouseover ,注意这里不要带 on
    listener:事件处理函数,事件发生时,会调用该监听函数
    useCapture:可选参数,是一个布尔值,默认是 false。学完 DOM 事件流后,我们再进一步学习

删除事件的方式

  1. 传统注册方式
    eventTarget.onclick = null;
  2. 方法监听注册方式
    eventTarget.removeEventListener(type, listener[, useCapture]);

DOM 事件流

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

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

比如我们给一个div 注册了点击事件:

DOM 事件流分为3个阶段:

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

image.png

事件冒泡: IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到 DOM 最顶层节点的过程。
事件捕获: 网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到到最具体的元素接收的过程。

注意

  1. JS 代码中只能执行捕获或者冒泡其中的一个阶段。
  2. onclick 和 attachEvent 只能得到冒泡阶段。
  3. addEventListener(type, listener[, useCapture])第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是 false(不写默认就是false),表示在事件冒泡阶段调用事件处理程序。
  4. 实际开发中我们很少使用事件捕获,我们更关注事件冒泡。
  5. 有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave
  6. 事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事件,我们后面讲解。

事件对象

什么是事件对象

  1. eventTarget.onclick = function(event) {}
  2. eventTarget.addEventListener('click', function(event) {})
  3. // 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt

官方解释:event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态。
简单理解:事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象 event,它有很多属性和方法。
比如:

  1. 谁绑定了这个事件。
  2. 鼠标触发事件的话,会得到鼠标的相关信息,如鼠标位置。
  3. 键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。

事件对象的使用语法

  1. eventTarget.onclick = function(event) {
  2. // 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
  3. }
  4. eventTarget.addEventListener('click', function(event) {
  5. // 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
  6. })

这个 event 是个形参,系统帮我们设定为事件对象,不需要传递实参过去。
当我们注册事件时, event 对象就会被系统自动创建,并依次传递给事件监听器(事件处理函数)。

事件对象的常见属性和方法

e.target 和 this 的区别:
this 是事件绑定的元素, 这个函数的调用者(绑定这个事件的元素)
e.target 是事件触发的元素。

image.png

阻止事件冒泡

标准写法:利用事件对象里面的 stopPropagation()方法

e.stopPropagation()

事件委托(代理、委派)

ul中有很多li,点击每个 li 都会弹出对话框,以前需要给每个 li 注册事件,是非常辛苦的,而且访问 DOM 的次数越多,这就会延长整个页面的交互就绪时间。

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

以上案例:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li,事件会冒泡到 ul 上, ul 有注册事件,就会触发事件监听器。

常用的鼠标事件

image.png

  1. 禁止鼠标右键菜单
    contextmenu主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单
    1. document.addEventListener('contextmenu', function(e) {
    2. e.preventDefault();
    3. })
  1. 禁止鼠标选中(selectstart 开始选中)
    1. document.addEventListener('selectstart', function(e) {
    2. e.preventDefault();
    3. })

鼠标事件对象

event对象代表事件的状态,跟事件相关的一系列信息的集合。现阶段我们主要是用鼠标事件对象 MouseEvent 和键盘事件对象 KeyboardEvent。

image.png

图片跟随鼠标移动

鼠标不断的移动,使用鼠标移动事件: mousemove
在页面中移动,给document注册事件
图片要移动距离,而且不占位置,我们使用绝对定位即可
核心原理: 每次鼠标移动,我们都会获得最新的鼠标坐标, 把这个x和y坐标做为图片的top和left 值就可以移动图片

  1. var pic = document.querySelector('img');
  2. document.addEventListener('mousemove', function(e) {
  3. var x = e.pageX;
  4. var y = e.pageY;
  5. pic.style.top = y - 40 + 'px';
  6. pic.style.left = x - 50 + 'px';
  7. })

常用键盘事件

image.png

注意:

  1. 如果使用addEventListener 不需要加 on
  2. onkeypress 和前面2个的区别是,它不识别功能键,比如左右箭头,shift 等。
  3. 三个事件的执行顺序是: keydown — keypress —- keyup

image.png

注意: onkeydown 和 onkeyup 不区分字母大小写,onkeypress 区分字母大小写。
在我们实际开发中,我们更多的使用keydown和keyup, 它能识别所有的键(包括功能键)
Keypress 不识别功能键,但是keyCode属性能区分大小写,返回不同的ASCII值

当我们按下 s 键, 光标就定位到搜索框:

  1. var search = document.querySelector('input');
  2. document.addEventListener('keyup', function(e) {
  3. // console.log(e.keyCode);
  4. if (e.keyCode === 83) {
  5. search.focus();
  6. }
  7. })

BOM

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

BOM的构成:

image.png

window 对象是浏览器的顶级对象,它具有双重角色。
它是 JS 访问浏览器窗口的一个接口。
它是一个全局对象。定义在全局作用域中的变量、函数都会变成 window 对象的属性和方法。
在调用的时候可以省略 window,前面学习的对话框都属于 window 对象方法,如 alert()、prompt() 等。
注意:window下的一个特殊属性 window.name

window 对象的常见事件

窗口加载事件

  1. window.onload = function(){}
  2. 或者
  3. window.addEventListener("load",function(){});

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

  1. 有了 window.onload 就可以把 JS 代码写到页面元素的上方,因为 onload 是等页面内容全部加载完毕,再去执行处理函数。
  2. window.onload 传统注册事件方式 只能写一次,如果有多个,会以最后一个 window.onload 为准。
  3. 如果使用 addEventListener 则没有限制
  • document.addEventListener(‘DOMContentLoaded’,function(){})

DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash等等。
Ie9以上才支持
如果页面的图片很多的话, 从用户访问到onload触发可能需要较长的时间, 交互效果就不能实现,必然影响用户的体验,此时用 DOMContentLoaded 事件比较合适。

调整窗口大小事件

  1. window.onresize = function(){}
  2. window.addEventListener("resize",function(){});

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

  1. 只要窗口大小发生像素变化,就会触发这个事件。
  2. 我们经常利用这个事件完成响应式布局。 window.innerWidth 当前屏幕的宽度

定时器

setTimeout() 定时器

  • window.setTimeout(调用函数, [延迟的毫秒数]);
  • window.clearTimeout(timeoutID) 停止定时器
    setTimeout() 方法用于设置一个定时器,该定时器在定时器到期后执行调用函数。
    注意:
    1. window 可以省略。
    2. 这个调用函数可以直接写函数,或者写函数名或者采取字符串‘函数名()’三种形式。第三种不推荐
    3. 延迟的毫秒数省略默认是 0,如果写,必须是毫秒。
    4. 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。

setTimeout() 这个调用函数我们也称为回调函数 callback
普通函数是按照代码顺序直接调用。
而这个函数,需要等待时间,时间到了才去调用这个函数,因此称为回调函数。
简单理解: 回调,就是回头调用的意思。上一件事干完,再回头再调用这个函数。
以前我们讲的 element.onclick = function(){} 或者 element.addEventListener(“click”, fn); 里面的函数也是回调函数。

setInterval() 定时器

  • window.setInterval(回调函数, [间隔的毫秒数]);
    setInterval() 方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。
    注意:
    1. window 可以省略。
    2. 这个调用函数可以直接写函数,或者写函数名或者采取字符串 ‘函数名()’ 三种形式。
    3. 间隔的毫秒数省略默认是 0,如果写,必须是毫秒,表示每隔多少毫秒就自动调用这个函数。
    4. 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。
    5. 第一次执行也是间隔毫秒数之后执行,之后每隔毫秒数就执行一次。

this

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

  1. 全局作用域或者普通函数中this指向全局对象window(注意定时器里面的this指向window)
  2. 方法调用中谁调用this指向谁
  3. 构造函数中this指向构造函数的实例

JS 执行机制

JS 是单线程

JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这是因为 Javascript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作 DOM 而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是: 如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

同步和异步

为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程。于是,JS 中出现了同步和异步。

同步:前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。

异步:你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情。比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。

同步任务:同步任务都在主线程上执行,形成一个执行栈。

异步任务:JS 的异步是通过回调函数实现的。

一般而言,异步任务有以下三种类型:

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

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

JS 执行机制

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

image.png

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

location 对象

window 对象给我们提供了一个 location 属性用于获取或设置窗体的 URL,并且可以用于解析 URL 。 因为这个属性返回的是一个对象,所以我们将这个属性也称为 location 对象。

URL

统一资源定位符 (Uniform Resource Locator, URL) 是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
URL 的一般语法格式为:

  1. protocol://host[:port]/path/[?query]#fragment
  2. http://www.itcast.cn/index.html?name=andy&age=18#link

image.png

location 对象的属性

image.png

location 对象的方法

image.png

navigator 对象

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

history 对象

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

image.png

PC 端网页特效

元素偏移量 offset

offset 翻译过来就是偏移量, 我们使用 offset 系列相关属性可以动态的得到该元素的位置(偏移)、大小等。

  • 获得元素距离带有定位父元素的位置
  • 获得元素自身的大小(宽度高度)

注意: 返回的数值都不带单位

image.png

offset 与 style 区别

offset 可以得到任意样式表中的样式值
offset 系列获得的数值是没有单位的
offsetWidth 包含padding+border+width
offsetWidth 等属性是只读属性,只能获取不能赋值
所以,我们想要获取元素大小位置,用offset更合适

style 只能得到行内样式表中的样式值
style.width 获得的是带有单位的字符串
style.width 获得不包含padding和border 的值
style.width 是可读写属性,可以获取也可以赋值
所以,我们想要给元素更改值,则需要用style改变

image.png

获取鼠标在盒子内的坐标

我们在盒子内点击,想要得到鼠标距离盒子左右的距离。
首先得到鼠标在页面中的坐标(e.pageX, e.pageY)
其次得到盒子在页面中的距离 ( box.offsetLeft, box.offsetTop)
用鼠标距离页面的坐标减去盒子在页面中的距离,得到 鼠标在盒子内的坐标
如果想要移动一下鼠标,就要获取最新的坐标,使用鼠标移动事件 mousemove

  1. var box = document.querySelector('.box');
  2. box.addEventListener('mousemove', function(e) {
  3. var x = e.pageX - this.offsetLeft;
  4. var y = e.pageY - this.offsetTop;
  5. this.innerHTML = 'x坐标是' + x + ' y坐标是' + y;
  6. })

拖动的模态框

image.png

  1. <head lang="en">
  2. <meta charset="UTF-8">
  3. <title></title>
  4. <style>
  5. .login-header {
  6. width: 100%;
  7. text-align: center;
  8. height: 30px;
  9. font-size: 24px;
  10. line-height: 30px;
  11. }
  12. ul,li,ol,dl,dt,dd,div,p,span,h1,h2,h3,h4,h5,h6,a {
  13. padding: 0px;
  14. margin: 0px;
  15. }
  16. .login {
  17. display: none;
  18. width: 512px;
  19. height: 280px;
  20. position: fixed;
  21. border: #ebebeb solid 1px;
  22. left: 50%;
  23. top: 50%;
  24. background: #ffffff;
  25. box-shadow: 0px 0px 20px #ddd;
  26. z-index: 9999;
  27. transform: translate(-50%, -50%);
  28. }
  29. .login-title {
  30. width: 100%;
  31. margin: 10px 0px 0px 0px;
  32. text-align: center;
  33. line-height: 40px;
  34. height: 40px;
  35. font-size: 18px;
  36. position: relative;
  37. cursor: move;
  38. }
  39. .login-input-content {
  40. margin-top: 20px;
  41. }
  42. .login-button {
  43. width: 50%;
  44. margin: 30px auto 0px auto;
  45. line-height: 40px;
  46. font-size: 14px;
  47. border: #ebebeb 1px solid;
  48. text-align: center;
  49. }
  50. .login-bg {
  51. display: none;
  52. width: 100%;
  53. height: 100%;
  54. position: fixed;
  55. top: 0px;
  56. left: 0px;
  57. background: rgba(0, 0, 0, .3);
  58. }
  59. a {
  60. text-decoration: none;
  61. color: #000000;
  62. }
  63. .login-button a {
  64. display: block;
  65. }
  66. .login-input input.list-input {
  67. float: left;
  68. line-height: 35px;
  69. height: 35px;
  70. width: 350px;
  71. border: #ebebeb 1px solid;
  72. text-indent: 5px;
  73. }
  74. .login-input {
  75. overflow: hidden;
  76. margin: 0px 0px 20px 0px;
  77. }
  78. .login-input label {
  79. float: left;
  80. width: 90px;
  81. padding-right: 10px;
  82. text-align: right;
  83. line-height: 35px;
  84. height: 35px;
  85. font-size: 14px;
  86. }
  87. .login-title span {
  88. position: absolute;
  89. font-size: 12px;
  90. right: -20px;
  91. top: -30px;
  92. background: #ffffff;
  93. border: #ebebeb solid 1px;
  94. width: 40px;
  95. height: 40px;
  96. border-radius: 20px;
  97. }
  98. </style>
  99. </head>
  100. <body>
  101. <div class="login-header"><a id="link" href="javascript:;">点击,弹出登录框</a></div>
  102. <div id="login" class="login">
  103. <div id="title" class="login-title">登录会员
  104. <span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span>
  105. </div>
  106. <div class="login-input-content">
  107. <div class="login-input">
  108. <label>用户名:</label>
  109. <input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="list-input">
  110. </div>
  111. <div class="login-input">
  112. <label>登录密码:</label>
  113. <input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="list-input">
  114. </div>
  115. </div>
  116. <div id="loginBtn" class="login-button"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div>
  117. </div>
  118. <!-- 遮盖层 -->
  119. <div id="bg" class="login-bg"></div>
  120. <script>
  121. // 1. 获取元素
  122. var login = document.querySelector('.login');
  123. var mask = document.querySelector('.login-bg');
  124. var link = document.querySelector('#link');
  125. var closeBtn = document.querySelector('#closeBtn');
  126. var title = document.querySelector('#title');
  127. // 2. 点击弹出层这个链接 link 让mask 和login 显示出来
  128. link.addEventListener('click', function() {
  129. mask.style.display = 'block';
  130. login.style.display = 'block';
  131. })
  132. // 3. 点击 closeBtn 就隐藏 mask 和 login
  133. closeBtn.addEventListener('click', function() {
  134. mask.style.display = 'none';
  135. login.style.display = 'none';
  136. })
  137. // 4. 开始拖拽
  138. // (1) 当我们鼠标按下, 就获得鼠标在盒子内的坐标
  139. title.addEventListener('mousedown', function(e) {
  140. var x = e.pageX - login.offsetLeft;
  141. var y = e.pageY - login.offsetTop;
  142. // (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
  143. document.addEventListener('mousemove', move)
  144. function move(e) {
  145. login.style.left = e.pageX - x + 'px';
  146. login.style.top = e.pageY - y + 'px';
  147. }
  148. // (3) 鼠标弹起,就让鼠标移动事件移除
  149. document.addEventListener('mouseup', function() {
  150. document.removeEventListener('mousemove', move);
  151. })
  152. })
  153. </script>
  154. </body>

元素可视区 client

client 翻译过来就是客户端,我们使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到该元素的边框大小、元素大小等。

image.png

元素滚动 scroll

scroll 翻译过来就是滚动的,我们使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。

image.png

image.png

如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发 onscroll 事件。

三大系列总结

他们主要用法:
offset系列 经常用于获得元素位置 offsetLeft offsetTop
client 经常用于获取元素大小 clientWidth clientHeight
scroll 经常用于获取滚动距离 scrollTop scrollLeft
注意页面滚动的距离通过 window.pageXOffset 获得

mouseenter 和mouseover的区别

当鼠标移动到元素上时就会触发 mouseenter 事件
类似 mouseover,它们两者之间的差别是
mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发。 mouseenter 只会经过自身盒子触发
之所以这样,就是因为mouseenter不会冒泡
跟mouseenter搭配 鼠标离开 mouseleave 同样不会冒泡

动画函数封装

动画实现原理

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

实现步骤:

  1. 获得盒子当前位置
  2. 让盒子在当前位置加上1个移动距离
  3. 利用定时器不断重复这个操作
  4. 加一个结束定时器的条件
  5. 注意此元素需要添加定位,才能使用element.style.left

注意函数需要传递2个参数,动画对象和移动到的距离。

  1. <head>
  2. <meta charset="UTF-8">
  3. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  4. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  5. <title>Document</title>
  6. <style>
  7. div {
  8. position: absolute;
  9. left: 0;
  10. width: 100px;
  11. height: 100px;
  12. background-color: pink;
  13. }
  14. span {
  15. position: absolute;
  16. left: 0;
  17. top: 200px;
  18. display: block;
  19. width: 150px;
  20. height: 150px;
  21. background-color: purple;
  22. }
  23. </style>
  24. </head>
  25. <body>
  26. <div></div>
  27. <span>夏雨荷</span>
  28. <script>
  29. // 简单动画函数封装obj目标对象 target 目标位置
  30. function animate(obj, target) {
  31. var timer = setInterval(function() {
  32. if (obj.offsetLeft >= target) {
  33. // 停止动画 本质是停止定时器
  34. clearInterval(timer);
  35. }
  36. obj.style.left = obj.offsetLeft + 1 + 'px';
  37. }, 30);
  38. }
  39. var div = document.querySelector('div');
  40. var span = document.querySelector('span');
  41. // 调用函数
  42. animate(div, 300);
  43. animate(span, 200);
  44. </script>
  45. </body>

动画函数给不同元素记录不同定时器

如果多个元素都使用这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。
核心原理:利用 JS 是一门动态语言,可以很方便的给当前对象添加属性。

  1. <head>
  2. <meta charset="UTF-8">
  3. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  4. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  5. <title>Document</title>
  6. <style>
  7. div {
  8. position: absolute;
  9. left: 0;
  10. width: 100px;
  11. height: 100px;
  12. background-color: pink;
  13. }
  14. span {
  15. position: absolute;
  16. left: 0;
  17. top: 200px;
  18. display: block;
  19. width: 150px;
  20. height: 150px;
  21. background-color: purple;
  22. }
  23. </style>
  24. </head>
  25. <body>
  26. <button>点击夏雨荷才走</button>
  27. <div></div>
  28. <span>夏雨荷</span>
  29. <script>
  30. // var obj = {};
  31. // obj.name = 'andy';
  32. // 简单动画函数封装obj目标对象 target 目标位置
  33. // 给不同的元素指定了不同的定时器
  34. function animate(obj, target) {
  35. // 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
  36. // 解决方案就是 让我们元素只有一个定时器执行
  37. // 先清除以前的定时器,只保留当前的一个定时器执行
  38. clearInterval(obj.timer);
  39. obj.timer = setInterval(function() {
  40. if (obj.offsetLeft >= target) {
  41. // 停止动画 本质是停止定时器
  42. clearInterval(obj.timer);
  43. }
  44. obj.style.left = obj.offsetLeft + 1 + 'px';
  45. }, 30);
  46. }
  47. var div = document.querySelector('div');
  48. var span = document.querySelector('span');
  49. var btn = document.querySelector('button');
  50. // 调用函数
  51. animate(div, 300);
  52. btn.addEventListener('click', function() {
  53. animate(span, 200);
  54. })
  55. </script>
  56. </body>

缓动效果原理

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

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
  2. 核心算法: (目标值 - 现在的位置 ) / 10 做为每次移动的距离 步长
  3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
  4. 注意步长值需要取整
  1. <script>
  2. // 缓动动画函数封装obj目标对象 target 目标位置
  3. // 思路:
  4. // 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
  5. // 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
  6. // 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
  7. function animate(obj, target) {
  8. // 先清除以前的定时器,只保留当前的一个定时器执行
  9. clearInterval(obj.timer);
  10. obj.timer = setInterval(function() {
  11. // 步长值写到定时器的里面
  12. var step = (target - obj.offsetLeft) / 10;
  13. if (obj.offsetLeft == target) {
  14. // 停止动画 本质是停止定时器
  15. clearInterval(obj.timer);
  16. }
  17. // 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
  18. obj.style.left = obj.offsetLeft + step + 'px';
  19. }, 15);
  20. }
  21. var span = document.querySelector('span');
  22. var btn = document.querySelector('button');
  23. btn.addEventListener('click', function() {
  24. // 调用函数
  25. animate(span, 500);
  26. })
  27. // 匀速动画 就是 盒子是当前的位置 + 固定的值 10
  28. // 缓动动画就是 盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
  29. </script>

动画函数多个目标值之间移动

可以让动画函数从 800 移动到 500。
当我们点击按钮时候,判断步长是正值还是负值
如果是正值,则步长 往大了取整
如果是负值,则步长 向小了取整

  1. <script>
  2. // 缓动动画函数封装obj目标对象 target 目标位置
  3. // 思路:
  4. // 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
  5. // 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
  6. // 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
  7. function animate(obj, target) {
  8. // 先清除以前的定时器,只保留当前的一个定时器执行
  9. clearInterval(obj.timer);
  10. obj.timer = setInterval(function() {
  11. // 步长值写到定时器的里面
  12. // 把我们步长值改为整数 不要出现小数的问题
  13. // var step = Math.ceil((target - obj.offsetLeft) / 10);
  14. var step = (target - obj.offsetLeft) / 10;
  15. step = step > 0 ? Math.ceil(step) : Math.floor(step);
  16. if (obj.offsetLeft == target) {
  17. // 停止动画 本质是停止定时器
  18. clearInterval(obj.timer);
  19. }
  20. // 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
  21. obj.style.left = obj.offsetLeft + step + 'px';
  22. }, 15);
  23. }
  24. var span = document.querySelector('span');
  25. var btn500 = document.querySelector('.btn500');
  26. var btn800 = document.querySelector('.btn800');
  27. btn500.addEventListener('click', function() {
  28. // 调用函数
  29. animate(span, 500);
  30. })
  31. btn800.addEventListener('click', function() {
  32. // 调用函数
  33. animate(span, 800);
  34. })
  35. // 匀速动画 就是 盒子是当前的位置 + 固定的值 10
  36. // 缓动动画就是 盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
  37. </script>

动画函数添加回调函数

回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。
回调函数写的位置:定时器结束的位置。

  1. sliderbar.addEventListener('mouseenter', function() {
  2. // animate(obj, target, callback);
  3. animate(con, -160, function() {
  4. // 当我们动画执行完毕,就把 ← 改为 →
  5. sliderbar.children[0].innerHTML = '→';
  6. });

动画函数封装到单独JS文件里面

因为以后经常使用这个动画函数,可以单独封装到一个JS文件里面,使用的时候引用这个JS文件即可。
单独新建一个JS文件。
HTML文件引入 JS 文件。

网页轮播图

轮播图也称为焦点图,是网页中比较常见的网页特效。
功能需求:

  1. 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
  2. 点击右侧按钮一次,图片往左播放一张,以此类推, 左侧按钮同理。
  3. 图片播放的同时,下面小圆圈模块跟随一起变化。
  4. 点击小圆圈,可以播放相应图片。
  5. 鼠标不经过轮播图, 轮播图也会自动播放图片。
  6. 鼠标经过,轮播图模块, 自动播放停止。

案例分析:

因为js较多,我们单独新建js文件夹,再新建js文件, 引入页面中。
此时需要添加 load 事件。
鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
显示隐藏 display 按钮。

动态生成小圆圈
核心思路:小圆圈的个数要跟图片张数一致
所以首先先得到ul里面图片的张数(图片放入li里面,所以就是li的个数)
利用循环动态生成小圆圈(这个小圆圈要放入ol里面)
创建节点 createElement(‘li’)
插入节点 ol. appendChild(li)
第一个小圆圈需要添加 current 类

小圆圈的排他思想
点击当前小圆圈,就添加current类
其余的小圆圈就移除这个current类
注意: 我们在刚才生成小圆圈的同时,就可以直接绑定这个点击事件了。

点击小圆圈滚动图片
此时用到animate动画函数,将js文件引入(注意,因为index.js 依赖 animate.js 所以,animate.js 要写到 index.js 上面)
使用动画函数的前提,该元素必须有定位
注意是ul 移动 而不是小li
滚动图片的核心算法: 点击某个小圆圈 , 就让图片滚动 小圆圈的索引号乘以图片的宽度做为ul移动距离
此时需要知道小圆圈的索引号, 我们可以在生成小圆圈的时候,给它设置一个自定义属性,点击的时候获取这个自定义属性即可。

点击右侧按钮一次,就让图片滚动一张。
声明一个变量num, 点击一次,自增1, 让这个变量乘以图片宽度,就是 ul 的滚动距离。
图片无缝滚动原理
把ul 第一个li 复制一份,放到ul 的最后面
当图片滚动到克隆的最后一张图片时, 让ul 快速的、不做动画的跳到最左侧: left 为0
同时num 赋值为0,可以从新开始滚动图片了

克隆第一张图片
克隆ul 第一个li cloneNode() 加true 深克隆 复制里面的子节点 false 浅克隆
添加到 ul 最后面 appendChild

点击右侧按钮, 小圆圈跟随变化
最简单的做法是再声明一个变量circle,每次点击自增1,注意,左侧按钮也需要这个变量,因此要声明全局变量。
但是图片有5张,我们小圆圈只有4个少一个,必须加一个判断条件
如果circle == 4 就 从新复原为 0

自动播放功能
添加一个定时器
自动播放轮播图,实际就类似于点击了右侧按钮
此时我们使用手动调用右侧按钮点击事件 arrow_r.click()
鼠标经过focus 就停止定时器
鼠标离开focus 就开启定时器

节流阀

防止轮播图按钮连续点击造成播放过快。
节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
开始设置一个变量 var flag = true;
If(flag) {flag = false; do something} 关闭水龙头
利用回调函数 动画执行完毕, flag = true 打开水龙头

移动端网页特效

触屏事件

移动端浏览器兼容性较好,我们不需要考虑以前 JS 的兼容性问题,可以放心的使用原生 JS 书写效果,但是移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android 和 IOS 都有。
touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。
常见的触屏事件如下:

image.png

触摸事件对象(TouchEvent)

TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各自有事件对象。
触摸事件对象重点我们看三个常见对象列表:

image.png

移动端拖动元素

  1. touchstart、touchmove、touchend 可以实现拖动元素
  2. 但是拖动元素需要当前手指的坐标值 我们可以使用 targetTouches[0] 里面的pageX 和 pageY
  3. 移动端拖动的原理:手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
  4. 手指移动的距离:手指滑动中的位置 减去 手指刚开始触摸的位置
    拖动元素三步曲:
    (1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
    (2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
    (3) 离开手指 touchend:
    注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();

移动端常用开发插件

移动端常见插件:iScroll、Swiper、SuperSlider

click 延时解决方案

移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) 页面。
解决方案:

  1. 禁用缩放。 浏览器禁用默认的双击缩放行为并且去掉 300ms 的点击延迟。
    1. <meta name="viewport" content="user-scalable=no">
  1. 使用插件。 fastclick 插件解决 300ms 延迟。
    1. document.addEventListener('DOMContentLoaded',function () {
    2. /*等页面文档加载完成 不需要等所有的资源*/
    3. FastClick.attach(document.body);
    4. });

本地存储

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()