来自课程:黑马程序员JavaScript核心教程,前端基础教程,JS的DOM BOM操作教程 -pink老师
Web APIs 简介
JS 基础阶段与 Web APIs 阶段
JS 基础学习 ECMAScript 基础语法为后面作铺垫, Web APIs 是 JS 的应用,大量使用 JS 基础语法做交互效果
作用和分类
- 作用: 就是使用 JS 去操作 html 和浏览器
- 分类:DOM (文档对象模型)、BOM(浏览器对象模型)
API 和 Web API
1. API
API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。
简单理解: API 是给程序员提供的一种工具,以便能更轻松的实现想要完成的功能。
2. Web API
Web API 是浏览器提供的一套操作浏览器功能和页面元素的 API ( BOM 和 DOM )。
现阶段我们主要针对于浏览器讲解常用的 API , 主要针对浏览器做交互效果。
比如我们想要浏览器弹出一个警示框, 直接使用 alert(‘弹出’)
MDN 详细 API : developer.mozilla.org/zh-CN/docs/…
因为 Web API 很多,所以我们将这个阶段称为 Web APIs
3. API 和 Web API 总结
- API 是为我们程序员提供的一个接口,帮助我们实现某种功能,我们会使用就可以了,不必纠结内部如何实现
- Web API 主要是针对于浏览器提供的接口,主要针对于浏览器做交互效果。
- Web API 一般都有输入和输出(函数的传参和返回值),Web API 很多都是方法(函数)
- 学习 Web API 可以结合前面学习内置对象方法的思路学习
DOM
DOM 简介
1. 什么是 DOM
-
文档对象模型(Document Object Model,简称DOM),是用来呈现以及与任意 HTML 或 XML文档交互的API。 -
白话文:DOM 是浏览器提供的一套专门用来
操作网页内容的功能 -
DOM作用
操作网页内容,可以开发网页内容特效和实现用户交互
-
DOM 是 W3C 组织推荐的处理可扩展标记语言(HTML或者XML)的标准
编程接口。 -
W3C 已经定义了一系列的 DOM 接口,通过这些 DOM 接口可以改变网页的内容、结构和样式。
2. DOM 树
- DOM树是什么
- 将 HTML 文档以树状结构直观的表现出来,我们称之为文档树或 DOM 树
- 描述网页内容关系的名词
- 作用:
文档树直观的体现了标签与标签之间的关系
- 文档:一个页面就是一个文档,DOM 中使用 document 表示
- 元素:页面中的所有标签都是元素,DOM 中使用 element 表示
- 节点:网页中的所有内容都是节点(标签、属性、文本、注释等),DOM 中使用 node 表示
DOM 把以上内容都看做是对象
3. DOM对象(重要)
- DOM对象:浏览器根据html标签生成的 JS
对象- 所有的标签属性都可以在这个对象上面找到
- 修改这个对象的属性会自动映射到标签身上
- DOM的核心思想
- 把网页内容当做对象来处理
- document 对象
-
DOM 里提供的一个对象
-
所以它提供的属性和方法都是用来访问和操作网页内容的
例:document.write()
-
网页所有内容都在document里面
-
总结
- DOM 树是什么?
- 将 HTML 文档以树状结构直观的表现出来,我们称之为文档树或 DOM树
- 作用:文档树直观的体现了标签与标签之间的关系
- DOM对象怎么创建的?
- 浏览器根据html标签生成的 JS对象(DOM对象)
- DOM的核心就是把内容当对象来处理
- document 是什么?
- 是 DOM 里提供的一个对象
- 网页所有内容都在document里面
获取DOM元素
- 提问:我们想要操作某个标签首先做什么?
- 答:肯定首先选中这个标签,跟 CSS选择器类似,选中标签才能操作
查找元素DOM元素就是利用 JS 选择页面中标签元素
1. (重点)根据CSS选择器来获取DOM元素
选择匹配的第一个元素
-
语法:
document.querySelector('css选择器') -
参数:包含一个或多个有效的CSS选择器 字符串
-
返回值:CSS选择器匹配的第一个元素,一个HTMLElement对象。如果没有匹配到,则返回null。
选择匹配的多个元素
-
语法:
document.querySelectorAll('css选择器') -
参数: 包含一个或多个有效的CSS选择器 字符串
-
返回值:CSS选择器匹配的 NodeList 对象集合,得到的是一个伪数组:
- 有长度有索引号的数组
- 但是没有 pop() push() 等数组方法
- 想要得到里面的每一个对象,则需要遍历(for)的方式获得。
- 哪怕只有一个元素,通过querySelectAll() 获取过来的也是一个伪数组,里面只有一个元素而已
两者的区别是什么?
- querySelector() 只能选择一个元素, 可以直接操作
- querySelectorAll() 可以选择多个元素,得到的是伪数组,需要遍历得到每一个元素
2. (了解)其他获取DOM元素方法
DOM在我们实际开发中主要用来操作元素。
我们如何来获取页面中的元素呢?
获取页面中的元素可以使用以下几种方式:
- 根据 ID 获取
- 根据标签名获取
- 通过 HTML5 新增的方法获取
- 特殊元素获取
1.根据 ID 获取
document.getElementById('id'); 使用 getElementById() 方法可以获取带有 ID 的元素对象。返回的是一个元素对象
使用 console.dir() 可以打印我们获取的元素对象,更好的查看对象里面的属性和方法。
2. 根据标签名获取
document.getElementsByTagName('标签名'); 使用 getElementsByTagName() 方法可以返回带有指定标签名的对象的集合。
注意:
-
因为得到的是一个对象的集合,以伪数组的形式存储,所以我们想要操作里面的元素就需要遍历。
-
得到元素对象是动态的
-
如果获取不到元素,则返回为空的伪数组(因为获取不到对象)
-
还可以获取某个元素(父元素)内部所有指定标签名的子元素element.getElementsByTagName('标签名');注意:父元素必须是
单个对象(必须指明是哪一个元素对象). 获取的时候不包括父元素自己。
3. 通过 HTML5 新增的方法获取
-
document.getElementsByClassName('类名');// 根据类名返回元素对象集合 -
document.querySelector('选择器');// 根据指定选择器返回第一个元素对象 -
document.querySelectorAll('选择器');// 根据指定选择器返回所有元素对象集合
注意:
querySelector 和 querySelectorAll里面的选择器需要加符号,比如:document.querySelector('#nav')
获取特殊元素(body,html)
获取body元素 doucumnet.body // 返回body元素对象
获取html元素 document.documentElement // 返回html元素对象
事件基础
1. 事件概述
JavaScript 使我们有能力创建动态页面,而事件是可以被 JavaScript 侦测到的行为。
简单理解: 触发--- 响应机制。
网页中的每个元素都可以产生某些可以触发 JavaScript 的事件,例如,我们可以在用户点击某按钮时产生一个 事件,然后去执行某些操作。
2. 事件三要素
- 事件源 (谁)
- 事件类型 (什么事件)
- 事件处理程序 (做啥)
<body>
<button id="btn">唐伯虎</button>
<script>
//1. 事件源 事件被触发的对象 谁 按钮
var btn = document.getElementById('btn');
//2. 事件类型 如何触发 什么事件 比如鼠标点击(onClick)还是鼠标经过 或者键盘按下 btn.onClick
//3. 事件处理程序 通过一个函数赋值的方式 完成
btn.onclick = function() {
alert('点秋香')
}
</script>
</body>
3. 执行事件的步骤
- 获取事件源
- 注册事件(绑定事件)
- 添加事件处理程序(采取函数赋值形式)
4. 常见的鼠标事件
操作元素
-
DOM对象都是根据标签生成的,所以操作标签,本质上就是操作DOM对象。
-
就是操作对象使用的点语法。
JavaScript 的 DOM 操作可以改变网页内容、结构和样式,我们可以利用 DOM 操作元素来改变元素里面的内容、属性等。
1. 操作(改变)元素内容
- 设置/修改DOM元素内容有哪2钟方式?
element.innerText- 将文本内容添加/更新到任意标签位置
- 显示纯文本,不解析标签
- 从起始位置到终止位置的内容, 但它不识别 html 标签, 同时空格和换行也会去掉(非标准)
element.innerHTML- 将文本内容添加/更新到任意标签位置
- 会解析标签,多标签建议使用模板字符
- 起始位置到终止位置的全部内容,包括 html 标签,同时保留空格和换行(W3C)
-
三者的区别是什么?
- 元素.innerText 属性 只识别文本,不能解析标签
- 元素.innerHTML 属性 能识别文本,能够解析标签
- 如果还在纠结到底用谁,你可以选择innerHTML
//年会抽奖案例
//需求:从数组随机抽取一等奖、二等奖和三等奖,显示到对应的标签里面
<body>
<div>
<strong>年会抽奖名单</strong>
<h1>一等奖:<span id="one">??</span></h1>
<h2>二等奖:<span id="two">??</span></h1>
<h3>三等奖:<span id="three">??</span></h1>
</div>
<script>
//1. 声明数组
const personArr = ["周杰伦", "刘德华", "周星驰", "Pink老师", "张学友"];
//2.1 随机数 数组的下标
const oneIndex = Math.floor(Math.random() * personArr.length);
//2.2 获取one元素
const one = document.querySelector("#one");
//2.3 把名字给one
one.innerHTML = personArr[oneIndex];
//2.4 删除数组这个名字 去掉一等奖
personArr.splice(oneIndex, 1);
//3. 二等奖
const twoIndex = Math.floor(Math.random() * personArr.length);
const two = document.querySelector("#two");
two.innerHTML = personArr[twoIndex];
//去掉二等奖
personArr.splice(twoIndex, 1);
//4. 三等奖
const threeIndex = Math.floor(Math.random() * personArr.length);
const three = document.querySelector("#three");
three.innerHTML = personArr[threeIndex];
</script>
</body>
2. 操作元素属性
1. 操作元素常用属性
-
还可以通过 JS 设置/修改标签元素属性,比如通过 src更换 图片
-
最常见的属性比如: href、title、src 等
-
语法:
对象.属性 = 值
<body>
<img src="./images/1.webp" alt="">
<script>
// 1. 获取图片元素
const img = document.querySelector('img')
// 2. 修改图片对象的属性 对象.属性 = 值
img.src = './images/2.webp'
img.title = 'pink老师的艺术照'
</script>
</body>
2. 操作元素样式属性
我们可以通过 JS 设置/修改标签元素的样式属性,比如元素的大小、颜色、位置等样式。
-
通过 style 属性操作CSS
- 语法:
对象.style.样式属性 = 值 - eg. box.style.width = '300px'
- 如果属性有-连接符,需要转换为小驼峰命名法
- 赋值的时候,需要的时候不要忘记加css单位
- 语法:
-
操作类名(className) 操作CSS
- 如果修改的样式比较多,直接通过style属性修改比较繁琐,我们可以通过借助于css类名的形式
- 语法:
元素.className = 'active' - 由于class是关键字, 所以使用className去代替
- className是使用新值换旧值, 如果需要添加一个类,需要保留之前的类名
-
通过 classList 操作类控制CSS
-
H5 新增
-
为了解决className 容易覆盖以前的类名,我们可以通过classList方式追加和删除类名
-
语法
追加类 元素.classList.add('类名')
删除类 元素.classList.remove('类名')
切换类 元素.classList.toggle('类名') 是否包含类 元素.classList.contains('类名') 看看有没有包含某个类,如果有则返回true,么有则返回false
-
总结:
-
通过style方式操作属性时,修改盒子的样式,比如 padding-left, 如何写?
element.style.paddingLeft = ‘300px’
小驼峰命名法
-
使用 className 有什么好处?
可以同时修改多个样式
-
使用 className 有什么注意事项?
直接使用 className 赋值会覆盖以前的类名
-
使用 className 和classList的区别?
修改大量样式的更方便
修改不多样式的时候方便
classList 是追加和删除不影响以前类名
-
注意:JS 修改 style 样式操作,产生的是行内样式,CSS 权重比较高,因为我们是样式属性,一定别忘记,大部分数字后面都需要加单位
3. 操作表单元素属性
- 表单很多情况,也需要修改属性,比如点击眼睛,可以看到密码,本质是把表单类型转换为文本框
- 正常的有属性有取值的 跟其他的标签属性没有任何区别
- 获取:
DOM对象.属性名 - 设置:
DOM对象.属性名 = 新值 - 表单属性中添加就有效果,移除就没有效果,一律使用布尔值表示 如果为true 代表添加了该属性 如果是false 代表移除了该属性,比如: disabled、checked、selected
<body>
<button id="btn">按钮</button>
<input type="text" value="请输入内容" />
<script>
//1. 获取元素
let btn = document.querySelector('button');
let input = document.querySelector('input')
//2. 注册事件 处理程序
//3. 事件处理程序 通过一个函数赋值的方式 完成
btn.onclick = function() {
input.value = '被点击了';
//想要input被禁用
input.disabled = true;
//this指向的是事件函数的调用者 btn
this.disabled = true;
}
</script>
</body>
4. 自定义属性
-
标准属性
- 标签天生自带的属性 比如class id title等, 可以直接使用点语法操作,比如:disabled、checked、selected
-
自定义属性:
- 在html5中推出来了专门的data-自定义属性
- 在标签上一律以data-开头
- 在DOM对象上一律以dataset对象方式获取
- 比如data-id="10"属性,取值box.dataset.id
1. 自定义属性的操作
1. 获取属性值
element.属性获取属性值。element.getAttribute('属性')获取属性值
区别:
- element.属性 获取内置属性值(元素本身自带的属性,比如class、id、style等等),不可获取程序员自定义的属性
- element.getAttribute('属性') 主要获得自定义的属性(标准) 我们程序员自定义的属性(比如data-index等等),也可获取内置属性
2. 设置属性值
element.属性 = '值'设置内置属性值。element.setAttribute('属性', '值'); 设置属性值(内置和自定义的都可以设置)
区别:
- element.属性 设置内置属性值
- element.setAttribute('属性'); 主要设置自定义的属性(标准),也可设置内置属性值
注意:使用 element.setAttribute('属性', '值') 设置class时,属性是class
//使用element.属性 设置class属性值时,用的是className
div.className = 'header'
//使用 element.setAttribute 设置class属性值时,用的是class
div.setAttribute('class', 'footer')
3. 移除属性
element.removeAttribute('属性');
2. H5自定义属性
自定义属性目的:是为了保存并使用数据。有些数据可以保存到页面中而不用保存到数据库中。
自定义属性获取是通过getAttribute('属性') 获取。
但是有些自定义属性很容易引起歧义,不容易判断是元素的内置属性还是自定义属性。
H5给我们新增了自定义属性:
1. 设置H5自定义属性
H5规定自定义属性data-开头做为属性名并且赋值。
比如 <div data-index="1"></div>
或者使用 JS 设置 element.setAttribute('data-index', 2)
2. 获取H5自定义属性
- 兼容性获取
element.getAttribute(‘data-index’); - H5新增
element.dataset.index或者element.dataset['index']ie11才开始支持
//获取自定义属性data-list-name的值
div.getAttribute('data-list-name');
div.dataset.listName;
div.dataset['listName'];
3. 排他思想
如果有同一组元素,我们想要某一个元素实现某种样式, 需要用到循环的排他思想算法:
- 所有元素全部清除样式(干掉其他人)
- 给当前元素设置样式 (留下我自己)
- 注意顺序不能颠倒,首先干掉其他人,再设置自己
4. 总结
操作元素是 DOM 核心内容
节点操作
1. 节点概述
-
DOM节点:DOM树里每一个内容都称之为节点。
-
节点类型
-
元素节点所有的标签 比如 body、 div
html 是根节点
-
属性节点
所有的属性 比如 class href
-
文本节点
所有的文本,比如标签里面的文字
-
其他
-
网页中的所有内容都是节点(标签、属性、文本、注释等),在DOM 中,节点使用 node 来表示。
HTML DOM 树中的所有节点均可通过 JavaScript 进行访问,所有 HTML 元素(节点)均可被修改,也可以创建或删除。
一般地,节点至少拥有nodeType(节点类型)、nodeName(节点名称)和nodeValue(节点值)这三个基本属性。
- 元素节点 nodeType 为 1
- 属性节点 nodeType 为 2
- 文本节点 nodeType 为 3 (文本节点包含文字、空格、换行等)
我们在实际开发中,节点操作主要操作的是元素节点
2. 为什么学节点操作
获取元素通常使用两种方式:
这两种方式都可以获取元素节点,我们后面都会使用,但是节点操作更简单
3. 查找节点
节点关系:针对的找亲戚返回的都是对象
节点关系:
-
父节点
-
子节点
-
兄弟节点
利用 DOM 树可以把节点划分为不同的层级关系,常见的是父子兄层级关系。
1. 父节点查找
子元素.parentNode
- 返回最近一级的父节点 找不到返回为null
2. 子节点
-
(了解)
parentNode.childNodes(标准)获得所有子节点、包括文本节点(空格、换行)、注释节点、元素节点等。如果只想要获得里面的元素节点,则需要专门处理。 所以我们
一般不提倡使用childNodes。
let ul = document.querySelector('ul');
for(let i = 0; i < ul.childNodes.length;i++) {
if (ul.childNodes[i].nodeType == 1) { // ul.childNodes[i] 是元素节点
console.log(ul.childNodes[i])
}
}
-
(重点)
parentNode.children(非标准)仅获得所有元素节点,其余节点不返回,返回的还是一个伪数组,是一个只读属性。(这个是我们重点掌握的)。虽然children 是一个非标准,但是得到了各个浏览器的支持,因此我们可以放心使用。
-
(了解)
parentNode.firstChildfirstChild 返回第一个子节点(不管是文本节点还是元素节点),找不到则返回null。同样,也是包含所有的节点。 -
(了解)
parentNode.lastChildlastChild 返回最后一个子节点(不管是文本节点还是元素节点),找不到则返回null。同样,也是包含所有的节点。 -
(了解)
parentNode.firstElementChildfirstElementChild 返回第一个子元素节点,找不到则返回null。 注意:这个方法有兼容性问题,IE9 以上才支持。 -
(了解)
parentNode.lastElementChildlastElementChild 返回最后一个子元素节点,找不到则返回null。 注意:这个方法有兼容性问题,IE9 以上才支持。
总结:
实际开发中,firstChild 和 lastChild 包含其他节点,操作不方便,而 firstElementChild 和 lastElementChild 又有兼容性问题,那么我们如何获取第一个子元素节点或最后一个子元素节点呢?
解决方案:
- 如果想要第一个子元素节点,可以使用 parentNode.chilren[0]
- 如果想要最后一个子元素节点,可以使用 parentNode.chilren[parentNode.chilren.length - 1]
3. 兄弟关系查找
-
(了解)
node.nextSiblingnextSibling 返回当前元素的下一个兄弟元素节点,找不到则返回null。同样,也是包含所有的节点。 -
(了解)
node.previousSiblingpreviousSibling 返回当前元素上一个兄弟元素节点,找不到则返回null。同样,也是包含所有的节点。 -
(掌握,注意兼容)
node.nextElementSiblingnextElementSibling 返回当前元素下一个兄弟元素节点,找不到则返回null。注意:这个方法有兼容性问题, IE9 以上才支持。 -
(掌握,注意兼容)
node.previousElementSiblingpreviousElementSibling 返回当前元素上一个兄弟节点,找不到则返回null。注意:这个方法有兼容性问题, IE9 以上才支持。
问:如何解决兼容性问题 ?
答:自己封装一个兼容性的函数
function getNextElementSibling(element) {
var el = element;
while (el = el.nextSibling) {
if (el.nodeType === 1) {
return el;
}
}
return null;
}
4. 增加节点
增加节点
很多情况下,我们需要在页面中增加元素,比如,点击发布按钮,可以新增一条信息
一般情况下,我们新增节点,按照如下操作:
- 创建一个新的节点
- 把创建的新的节点放入到指定的元素内部
-
创建节点
即创造出一个新的网页元素,再添加到网页内,一般先创建节点,然后插入节点
创建元素节点方法:document.createElement('标签名')
-
追加节点
要想在界面看到,还得插入到某个父元素中
- 插入到父元素的最后一个子元素:
父元素.appendChild(要插入的元素) - 插入到父元素中某个子元素的前面:
父元素.insertBefore(要插入的元素, 在哪个元素前面)
- 插入到父元素的最后一个子元素:
document.createElement() 方法创建由 tagName 指定的 HTML 元素。因为这些元素原先不存在,是根据我们的需求动态生成的,所以我们也称为动态创建元素节点。
//实现简单版发布留言案例
<body>
<textarea id="text">123</textarea>
<button>发布</button>
<ul></ul>
<script>
//获取元素
let text = document.getElementById('text')
let btn = document.querySelector('button')
let ul = document.querySelector('ul')
btn.onclick = function() {
if (text.value == '') {
alert('您没有输入内容')
return false
} else {
//创建元素
let li = document.createElement('li')
li.innerHTML = text.value
//添加元素
ul.insertBefore(li, ul.children[0])
}
}
</script>
</body>
复制节点(克隆节点)
-
特殊情况下,我们新增节点,按照如下操作:
- 复制一个原有的节点
- 把复制的节点放入到指定的元素内部
-
克隆节点
元素.cloneNode(布尔值)
cloneNode会克隆出一个跟原标签一样的元素,括号内传入布尔值
- 若为 false 或 为空,则代表克隆时不包含后代节点。是浅拷贝,即只克隆复制节点本身,不克隆里面的子节点。
- 若为 true ,则代表克隆时会包含后代节点一起克隆。是深度拷贝,会复制节点本身以及里面所有的子节点。
- 默认为false
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
const ul = document.querySelector('ul')
// 1 克隆节点 元素.cloneNode(true)
// const li1 = ul.children[0].cloneNode(true)
// console.log(li1)
// 2. 追加
ul.appendChild(ul.children[0].cloneNode(true))
</script>
</body>
5. 删除节点
-
若一个节点在页面中已不需要时,可以删除它
-
在 JavaScript 原生DOM操作中,要删除元素必须通过父元素删除
-
语法:
父元素.removeChild(要删除的元素) -
注:
- 如不存在父子关系则删除不成功
- 返回删除的节点
- 删除节点和隐藏节点(display:none)有区别的: 隐藏节点还是存在的,但是删除,则从html中删除节点
6. 三种动态创建元素区别
- document.write()
- element.innerHTML
- document.createElement()
区别:
- document.write 是直接将内容写入页面的内容流,但是文档流执行完毕,则它会导致页面全部重绘
- innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘
- innerHTML 创建多个元素效率更高(不要拼接字符串,采取数组形式拼接),结构稍微复杂
- createElement() 创建多个元素效率稍低一点点,但是结构更清晰
//document.write 不建议使用
document.write('<div>1111</div>')
//innerHTML 拼接字符串,效率低
var d1 = +new Date();
for (var i = 0; i <= 10000; i++) {
document.body.innerHTML += '<a href="javascript:;">百度</a>'
}
var d2 = +new Date();
console.log(d2-d1);
//innerHTML 采取数组形式拼接,效率更高,注意测试的时候尽量创建多个元素效果更好
var d1 = +new Date();
var arr = [];
for (var i = 0; i <= 1000; i++) {
arr.push('<a href="javascript:;">百度</a>')
}
document.body.innerHTML = arr.join('')
var d2 = +new Date();
console.log(d2-d1);
//createElement() 创建多个元素效率稍低一点点,注意测试的时候尽量创建多个元素效果更好
var d1 = +new Date();
for (var i = 0; i <= 1000; i++) {
var a = document.createElement('a');
document.body.appendChild(a)
}
var d2 = +new Date();
console.log(d2-d1);
总结:不同浏览器下,innerHTML 效率要比 creatElement 高
DOM 重点核心(总结)
文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。
W3C 已经定义了一系列的 DOM 接口,通过这些 DOM 接口可以改变网页的内容、结构和样式。
- 对于JavaScript,为了能够使JavaScript操作HTML,JavaScript就有了一套自己的dom编程接口。
- 对于HTML,dom使得html形成一棵dom树. 包含 文档、元素、节点
关于dom操作,我们主要针对于元素的操作。主要有创建、增、删、改、查、属性操作、事件操作。
1. 创建
- document.write
- innerHTML
- createElement
2. 增
- appendChild
- insertBefore
3. 删
- removeChild
4. 改
主要修改dom的元素属性,dom元素的内容、属性, 表单的值等
- 修改元素属性: src、href、title等
- 修改普通元素内容: innerHTML 、innerText
- 修改表单元素: value、type、disabled等
- 修改元素样式: style、className
5. 查
主要获取查询dom的元素
- DOM提供的API 方法: getElementById、getElementsByTagName 古老用法 不太推荐
- H5提供的新方法: querySelector、querySelectorAll 提倡
- 利用节点操作获取元素: 父(parentNode)、子(children)、兄(previousElementSibling、nextElementSibling) 提倡
6. 属性操作
主要针对于自定义属性。
- setAttribute:设置dom的属性值
- getAttribute:得到dom的属性值
- removeAttribute移除属性
7. 事件操作
给元素注册事件,采取 事件源.事件类型 = 事件处理程序
事件高级
1. 事件监听(注册/绑定)
1. 注册事件概述
-
什么是事件?
事件是在编程时系统内发生的动作或者发生的事情,比如用户在网页上单击一个按钮
-
什么是事件监听?
就是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为 绑定事件或者注册事件。
比如鼠标经过显示下拉菜单,比如点击可以播放轮播图等等
-
事件监听语法
元素对象.addEventListener('事件类型', 要执行的函数) -
事件监听三要素:
- 事件源:谁被触发了,哪个dom元素被事件触发了,要获取dom元素
- 事件类型:用什么方式触发,比如鼠标单击 click、鼠标经过 mouseover 等
- 事件调用的函数:要做什么事
-
举例说明
<body> <button>点击</button> <script> // 需求: 点击了按钮,弹出一个对话框 // 1. 事件源 按钮 // 2.事件类型 点击鼠标 click 字符串 // 3. 事件处理程序 弹出对话框 const btn = document.querySelector('button') btn.addEventListener('click', function () { alert('你早呀~') }) </script> </body> -
注意:
- 事件类型要加引号
- 函数是点击之后再去执行,每次点击都会执行一次
2. 事件监听版本/方式
-
注册事件有两种方式:
传统方式和方法监听注册方式 -
DOM L0 传统on注册
事件源.on事件 = function() { }
-
DOM L2 事件监听注册
事件源.addEventListener(事件, 事件处理函数)
-
区别:
on方式会被覆盖,addEventListener方式可绑定多次,拥有事件更多特性,推荐使用(更多详细区别内容见 DOM事件流->解绑事件)
-
发展史:
- DOM L0 :是 DOM 的发展的第一个版本; L:level
- DOM L1:DOM级别1 于1998年10月1日成为W3C推荐标准
- DOM L2:使用addEventListener注册事件
- DOM L3: DOM3级事件模块在DOM2级事件的基础上重新定义了这些事件,也添加了一些新事件类型
3. addEventListener 事件监听方式
eventTarget.addEventListener(type, listener[, useCapture]) 将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。
该方法接收三个参数:
type:事件类型字符串,比如 click 、mouseover ,注意这里不要带 onlistener:事件处理函数,事件发生时,会调用该监听函数useCapture:可选参数,是一个布尔值,默认是 false。学完 DOM 事件流后,我们再进一步学习
4. attachEvent 事件监听方式
eventTarget.attachEvent(eventNameWithOn, callback) 将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,指定的回调函数就会被执行。
该方法接收两个参数:
eventNameWithOn:事件类型字符串,比如 onclick 、onmouseover ,这里要带 oncallback: 事件处理函数,当目标触发事件时回调函数被调用
注意:IE8 及早期版本支持
5. 注册事件兼容性解决方案
function addEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 addEventListener 方法
if (element.addEventListener) {
element.addEventListener(eventName, fn); // 第三个参数 默认是false
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, fn);
} else {
// 相当于 element.onclick = fn;
element['on' + eventName] = fn;
}
兼容性处理的原则: 首先照顾大多数浏览器,再处理特殊浏览器
2. 事件类型
-
鼠标事件
- 鼠标触发
- click 鼠标点击
- (推荐)mouseenter 鼠标经过 mouseleave 鼠标离开(没有冒泡效果)
- (不推荐)mouseover 鼠标经过 和 mouseout 鼠标离开 会有冒泡效果
-
焦点事件
- 表单获得光标
- focus 获得焦点
- blur 失去焦点
-
键盘事件
- 键盘触发
- Keydown 键盘按下触发
- Keyup 键盘抬起触发
-
文本事件
- 表单输入触发
- input 用户输入事件
3. 事件对象
1. 什么是事件对象
-
什么是事件对象
- 也是个对象,这个对象里有事件触发时的相关信息
- 例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息
-
使用场景
- 可以判断用户按下哪个键,比如按下回车键可以发布新闻
- 可以判断鼠标点击了哪个元素,从而做相应的操作
-
如何获取事件对象
- 在事件绑定的回调函数的第一个参数就是事件对象
- 一般命名为event、ev、e
元素.addEventListener('click', function(e) {})
eventTarget.onclick = function(event) {}
eventTarget.addEventListener('click', function(event) {})
// 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
event就是一个事件对象,写到我们侦听函数的小括号里面,当形参来看。
事件对象只有有了事件才会存在,它是系统给我们自动创建的,不需要我们传递参数。
事件对象是我们事件的一系列相关数据的集合,跟事件相关,比如鼠标点击里面就包含了鼠标的相关信息,鼠标坐标等,如果是键盘事件里面包含的键盘事件的信息比如判断用户按下了哪个键。
这个事件对象我们可以自己命名,比如event、evt、e。
事件对象也有兼容性问题ie678,通过window.event
官方解释:event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态。
简单理解:事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象 event,它有很多属性和方法。
比如:
- 谁绑定了这个事件。
- 鼠标触发事件的话,会得到鼠标的相关信息,如鼠标位置。
- 键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。
2. 事件对象的使用语法
eventTarget.onclick = function(event) {
// 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
}
eventTarget.addEventListener('click', function(event) {
// 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
})
这个 event 是个形参,系统帮我们设定为事件对象,不需要传递实参过去。
当我们注册事件时, event 对象就会被系统自动创建,并依次传递给事件监听器(事件处理函数)。
3. 事件对象的兼容性方案
事件对象本身的获取存在兼容问题:
- 标准浏览器中是浏览器给方法传递的参数,只需要定义形参 e 就可以获取到。
- 在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。
解决: e = e || window.event;
4. 事件对象常见属性和方法
部分常用属性
-
type 获取当前的事件类型
-
clientX/clientY 获取光标相对于浏览器可见窗口左上角的位置
-
offsetX/offsetY 获取光标相对于当前DOM元素左上角的位置
-
key 用户按下的键盘键的值,现在不提倡使用keyCode
5. 环境对象 this
-
环境对象:指的是函数内部特殊的
变量 this,它代表着当前函数运行时所处的环境 -
作用:弄清楚this的指向,可以让我们代码更简洁
- 函数的调用方式不同,this 指代的对象也不同
- 【
谁调用, this 就是谁】 是判断 this 指向的粗略规则 - 直接调用函数,其实相当于是 window.函数,所以 this 指代 window
//每个函数里面都有this 环境对象
//普通函数里面this指向的是window
function fn() {
console.log(this) //window
}
fn()//相当于window.fn()
btn.addEventListener("click", function () {
console.log(this) //btn对象
})
6. e.target 和 this 的区别
this 是事件绑定的元素, 这个函数的调用者(绑定这个事件的元素)
e.target 是事件触发的元素。
7. 回调函数
-
如果将函数 A 做为参数传递给函数 B 时,我们称函数 A 为
回调函数 -
简单理解:当一个函数当做参数来传递给另外一个函数的时候,这个函数就是
回调函数 -
常见的使用场景:
//eg1 function fn() { console.log('我是回调函数') } setInterval(fn, 1000) //eg2 box.addEventListener('click', function () { alert('我也是回调函数') }) -
总结
- 把函数当做另外一个函数的参数传递,这个函数就叫回调函数
- 回调函数本质还是函数,只不过把它当成参数使用
- 使用匿名函数做为回调函数比较常见
4. DOM事件流
事件流指的是事件完整执行过程中的流动路径。
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM 事件流。
说明:假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段
简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父
实际工作都是使用事件冒泡为主
我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点( 最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。
事件捕获
-
事件捕获概念:从DOM的根元素开始去执行对应的事件 (从外到里)
-
代码
DOM.addEventListener(事件类型, 事件处理函数, 是否使用捕获机制) -
说明:
- addEventListener第三个参数传入 true 代表是捕获阶段触发(很少使用)
- 若传入false代表冒泡阶段触发,默认就是false
- 若是用 L0 事件监听,则只有冒泡阶段,没有捕获
-
事件捕获: 网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到到最具体的元素接收的过程。
事件冒泡
-
事件冒泡概念
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡。
-
简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的 同名事件(同类型事件,比如都是click)。
-
事件冒泡是默认存在的
-
L2事件监听第三个参数是 false,或者默认都是冒泡
-
事件冒泡: IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到到 DOM 最顶层节点的过程。
注意:
- JS 代码中只能执行捕获或者冒泡其中的一个阶段
- onclick 和 attachEvent 只能得到冒泡阶段。
- addEventListener(type, listener[, useCapture])第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是 false(不写默认就是false),表示在事件冒泡阶段调用事件处理程序。
实际开发中我们很少使用事件捕获,我们更关注事件冒泡。有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事件,我们后面讲解。
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
//dom事件流三个阶段
//捕获阶段: addEventListener第三个参数是true document->html->body->father->son
// var son = document.querySelector('.son');
// son.addEventListener('click', function() {
// alert('son')
// },true)
// var father = document.querySelector('.father');
// father.addEventListener('click', function() {
// alert('father')
// },true)
//冒泡阶段:addEventListener第三个参数是false或省略 son->father->body->html->document
var son = document.querySelector('.son');
son.addEventListener('click', function() {
alert('son')
})
var father = document.querySelector('.father');
father.addEventListener('click', function() {
alert('father')
})
</script>
</body>
阻止事件冒泡
-
问题:因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
-
需求:若想把事件就限制在当前元素内,就需要阻止事件冒泡
-
前提:阻止事件冒泡需要拿到事件对象
-
语法:
事件对象.stopPropagation() -
注意:此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效
1. 阻止事件冒泡的两种方式
事件冒泡:开始时由最具体的元素接收,然后逐级向上传播到到 DOM 最顶层节点。
事件冒泡本身的特性,会带来的坏处,也会带来的好处,需要我们灵活掌握。
阻止事件冒泡
e.stopPropagation()标准写法:利用事件对象里面的 stopPropagation()方法e.cancelBubble = true;非标准写法:IE 6-8 利用事件对象 cancelBubble 属性
2. 阻止事件冒泡的兼容性解决方案
if(e && e.stopPropagation){
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}
解绑事件(删除事件)
1. 删除事件的方式
- 传统注册方式(on事件方式/L0):
eventTarget.onclick = null; - 方法监听注册方式(addEventListener方式/L2)
- eventTarget.removeEventListener(事件类型type, 事件处理函数listener[, useCapture获取捕获或者冒泡阶段]);
- eventTarget.detachEvent(eventNameWithOn, callback);
- 注意:匿名函数无法被解绑
<body>
<div>11</div>
<div>22</div>
<div>33</div>
<script>
//传统方式
var divs = document.querySelectorAll('div')
divs[0].onclick = function() {
alert(1111);
divs[0].onclick = null;
}
//addEventListener
divs[1].addEventListener('click', fn)
function fn() {
alert(222);
divs[1].removeEventListener('click', fn)
}
// detachEvent
divs[2].attachEvent('onclick', fn1)
function fn1() {
alert(333);
btn[2].detachEvent('click', fn1)
}
</script>
</body>
2. 删除事件兼容性解决方案
function removeEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 removeEventListener 方法
if (element.removeEventListener) {
element.removeEventListener(eventName, fn); // 第三个参数 默认是false
} else if (element.detachEvent) {
element.detachEvent('on' + eventName, fn);
} else {
element['on' + eventName] = null;
}
3. 两种注册事件区别
-
传统on注册(L0)
- 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
- 直接使用null覆盖偶就可以实现事件的解绑
- 都是冒泡阶段执行的
-
事件监听注册(L2)
- 语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
- 后面注册的事件不会覆盖前面注册的事件(同一个事件)
- 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
- 必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
- 匿名函数无法被解绑
阻止默认行为
我们某些情况下需要阻止默认行为的发生,比如 阻止 链接的跳转,表单域跳转
语法:事件对象e.preventDefault()
5. 事件委托(代理、委派)
事件冒泡本身的特性,会带来的坏处,也会带来的好处,需要我们灵活掌握。生活中有如下场景:
咱们班有100个学生, 快递员有100个快递, 如果一个个的送花费时间较长。同时每个学生领取的时候,也需要排队领取,也花费时间较长,何如?
解决方案:快递员把100个快递,委托给班主任,班主任把这些快递放到办公室,同学们下课自行领取即可。
优势:快递员省事,委托给班主任就可以走了。 同学们领取也方便,因为相信班主任。
-
思考:如果同时给多个元素注册事件,我们怎么做的?
for循环注册事件
<ul>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
</ul>
点击每个 li 都会弹出对话框,以前需要给每个 li 注册事件,是非常辛苦的,而且访问 DOM 的次数越多,这就会延长整个页面的交互就绪时间。
有没有一种技巧 注册一次事件就能完成以上效果呢?
事件委托的原理
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
以上案例:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li,事件会冒泡到 ul 上, ul 有注册事件,就会触发事件监听器。
事件委托
事件委托也称为事件代理, 在 jQuery 里面称为事件委派。
-
事件委托是利用事件流的特征解决一些开发需求的知识技巧
-
优点:减少注册次数,可以提高程序性能
-
原理:事件委托其实是利用事件冒泡的特点。
给
父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件 -
事件对象.target是触发的对象
-
实现:事件对象.target.tagName 可以获得真正触发事件的元素,比如点击的是div,事件对象.target.tagName是DIV
事件委托的作用
我们只操作了一次 DOM ,提高了程序的性能。
6. 其他事件
页面加载事件
load
-
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
-
为什么要学?
- 有些时候需要等页面资源全部处理完了做一些事情
- 老代码喜欢把 script 写在 head 中,这时候直接找 dom 元素找不到
-
事件名:load
-
监听页面所有资源加载完毕:
给 window 添加 load 事件
window.onload = function(){} 或者 window.addEventListener("load",function(){}); -
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件
DOMContentLoaded
-
当初始的HTML文档被完全加载和解析完成之后,(仅当DOM加载完成后)DOMContentLoaded 事件被触发,而无需等待样式表、图像等完全加载。
-
事件名:DOMContentLoaded
-
监听页面DOM加载完毕:
给 document 添加 DOMContentLoaded 事件
document.addEventListener('DOMContentLoaded',function(){}) -
Ie9以上才支持
-
如果页面的图片很多的话, 从用户访问到onload触发可能需要较长的时间, 交互效果就不能实现,必然影响用户的体验,此时用 DOMContentLoaded 事件比较合适。
总结
页面加载事件有哪两个?如何添加?
-
load 事件
监听整个页面资源给 window 加
-
DOMContentLoaded
给 document 加,无需等待样式表、图像等完全加载
页面滚动事件 scroll
-
页面滚动事件是滚动条在滚动的时候持续触发的事件
-
为什么要学?
很多网页需要检测用户把页面滚动到某个区域后做一些处理, 比如固定导航栏,比如返回顶部
-
事件名:scroll
-
监听整个页面滚动:
window.addEventListener("scroll",function(){})给 window 或 document 添加 scroll 事件 -
监听哪个元素的内部滚动直接给哪个元素加即可
-
使用场景:
我们想要页面滚动一段距离,比如100px,就让某些元素显示隐藏,那我们怎么知道,页面滚动了100像素呢?
-
就可以使用scroll 来检测滚动的距离~~~
获取位置
-
scrollLeft和scrollTop (属性)
- 获取被卷去的大小
- 获取元素内容往左、往上滚出去看不到的距离
- 这两个值是可读写的
-
尽量在scroll事件里面获取被卷去的距离
-
开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素
滚动到指定的坐标
-
scrollTo() 方法可把内容滚动到指定的坐标
-
语法:
元素.scrollTo(x, y) -
例如:window.scrollTo(0, 1000)
页面被卷去的头部
如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发 onscroll 事件。
页面被卷去的头部兼容性解决方案
需要注意的是,页面被卷去的头部,有兼容性问题,因此被卷去的头部通常有如下几种写法:
- 声明了 DTD(文档类型定义,比如HTML5中使用),使用 document.documentElement.scrollTop
- 未声明 DTD,使用 document.body.scrollTop
- 新方法 window.pageYOffset 和 window.pageXOffset,IE9 开始支持
function getScroll() {
return {
left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft||0,
top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
};
}
//使用的时候 getScroll().left
页面尺寸事件
resize
-
会在窗口尺寸改变的时候触发事件
resizewindow.onresize = function(){} window.addEventListener("resize",function(){}) -
检测屏幕宽度:
window.addEventListener("resize",function(){ let w = document.documentElemet.clientWidth })
注意:
- 只要窗口大小发生像素变化,就会触发这个事件。
- 我们经常利用这个事件完成响应式布局。 window.innerWidth 当前屏幕的宽度
获取元素宽高
-
获取宽高:
- 获取元素的可见部分宽高(不包含边框,margin,滚动条等)
- clientWidth和clientHeight
7. 元素尺寸与位置
-
使用场景:
- 前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事。
- 简单说,就是通过js的方式,得到元素在页面中的位置
- 这样我们可以做,页面滚动到这个位置,就可以做某些操作,省去计算了
-
获取宽高:
- 获取元素的自身宽高、包含元素自身设置的宽高、padding、border
- offsetWidth和offsetHeight
- 获取出来的是数值,方便计算
- 注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0
-
获取位置:
-
offsetLeft和offsetTop 注意是只读属性
获取元素距离自己定位父级元素(最近一级带有定位的祖先元素)的左、上距离,如果都没有定位则以 文档左上角 为准
-
element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置
-
offest、client、scroll区别
他们主要用法:
- offset系列 经常用于获得元素位置 offsetLeft offsetTop
- client 经常用于获取元素大小 clientWidth clientHeight
- scroll 经常用于获取滚动距离(元素滚动距离) scrollTop scrollLeft
- 注意页面滚动的距离通过 window.pageXOffset 获得
9. 常用的鼠标事件
1. 常用的鼠标事件
-
禁止鼠标右键菜单
contextmenu 主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
- 禁止鼠标选中(selectstart 开始选中)
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
2. 鼠标事件对象
event对象代表事件的状态,跟事件相关的一系列信息的集合。
现阶段我们主要是用鼠标事件对象 MouseEvent 和键盘事件对象 KeyboardEvent。
//案例: 跟随鼠标的天使
//天使图片一直跟随鼠标移动
//分析:鼠标不断的移动,使用鼠标移动事件: mousemove
//在页面中移动,给document注册事件
//图片要移动距离,而且不占位置,我们使用绝对定位即可
//核心原理: 每次鼠标移动,我们都会获得最新的鼠标坐标, 把这个x和y坐标做为图片的top和left 值就可以移动图片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
img {
width: 50px;
height: 50px;
position: absolute;
}
</style>
</head>
<body>
<img src="../xts.jpg" />
<script>
var img = document.querySelector('img');
document.addEventListener('mousemove', function(e) {
//注意单位,减去的25是图片的宽/高的一半,这样鼠标就在图片的中心位置
img.style.left = e.pageX - 25 + 'px';
img.style.top = e.pageY -25 + 'px';
})
</script>
</body>
</html>
10. 常用的键盘事件
1. 常用键盘事件
事件除了使用鼠标触发,还可以使用键盘触发。
注意:
如果使用addEventListener 不需要加 on- onkeypress 和前面2个的区别是,它不识别功能键,比如左右箭头,shift 等。
- 三个事件的执行顺序是: keydown -- keypress --- keyup
键盘事件对象
注意:
- onkeydown 和 onkeyup 不区分字母大小写,a和A得到的都是65
- onkeypress 区分字母大小写。a:97,A:65
- 在我们实际开发中,我们更多的使用keydown和keyup, 它能识别所有的键(包括功能键)
- Keypress 不识别功能键,但是keyCode属性能区分大小写,返回不同的ASCII值
- 在文本框里使用 keydown 和 keypress 的特点:他们两个事件触发的时候,文字还没有落入文本框中。但是 keyup 事件触发的时候,文字已经落入文本框里面了
3. ASCII 表
Window对象
BOM 浏览器对象模型
1. 什么是 BOM
BOM(Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window。
-
window对象是一个全局对象,也可以说是JavaScript中的顶级对象
-
像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的。
-
所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
-
window对象下的属性和方法调用的时候可以省略window。
BOM 由一系列相关的对象构成,并且每个对象都提供了很多方法与属性。
BOM 缺乏标准,JavaScript 语法的标准化组织是 ECMA,DOM 的标准化组织是 W3C,BOM 最初是Netscape 浏览器标准的一部分。
DOM
- 文档对象模型
- DOM 就是把「文档」当做一个「对象」来看待
- DOM 的顶级对象是 document
- DOM 主要学习的是操作页面元素
- DOM 是 W3C 标准规范
BOM
- 浏览器对象模型
- 把「浏览器」当做一个「对象」来看待
- BOM 的顶级对象是 window
- BOM 学习的是浏览器窗口交互的一些对象
- BOM 是浏览器厂商在各自浏览器上定义的,兼容性较差
2. BOM 的构成
BOM 比 DOM 更大,它包含 DOM。
window 对象是浏览器的顶级对象,它具有双重角色。
- 它是 JS 访问浏览器窗口的一个接口。
- 它是一个全局对象。定义在全局作用域中的变量、函数都会变成 window 对象的属性和方法。
在调用的时候可以省略 window,前面学习的对话框都属于 window 对象方法,如 alert()、prompt() 等。 注意:window下的一个特殊属性 window.name。
console.dir(window)
定时器
window 对象给我们提供了 2 个非常好用的方法-定时器。
- setTimeout()
- setInterval()
定时器-延时函数
-
JavaScript 内置的一个用来让代码延迟执行的函数,叫 setTimeout
-
语法:
setTimeout(调用函数, [延迟的毫秒数]) -
setTimeout 仅仅只执行一次,所以可以理解为就是把一段代码延迟执行, 平时省略window
-
注意点
- 延时器需要等待,所以后面的代码先执行
- 每一次调用延时器都会产生一个新的延时器
-
清除延时函数
let timer = setTimeout(调用函数, [延迟的毫秒数])
clearTimeout(timer)
注意:
- window 可以省略。
- 这个调用函数可以
直接写函数,或者写函数名或者采取字符串‘函数名()'三种形式。第三种不推荐 - 延迟的毫秒数省略默认是 0,如果写,必须是毫秒。
- 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符(名字)。
- setTimeout() 这个调用函数我们也称为
回调函数 callback - 普通函数是按照代码顺序直接调用,而这个函数,需要等待时间,时间到了才去调用这个函数,因此称为回调函数。
简单理解:回调,就是回头调用的意思。上一件事干完,再回头再调用这个函数。
以前我们讲的 element.onclick = function(){} 或者 element.addEventListener(“click”, fn); 里面的 函数也是回调函数。
定时器-间歇函数
window.setInterval(回调函数, [间隔的毫秒数]); setInterval() 方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。
function fn() {
console.log('一秒执行一次')
}
// setInterval(函数名, 间隔时间) 函数名不要加小括号
//n是一个id数字
let n = setInterval(fn, 1000)
注意:
- window 可以省略。
- 这个调用函数可以直接写函数,或者写函数名或者采取字符串 '函数名()' 三种形式。
- 间隔的毫秒数省略默认是 0,如果写,必须是毫秒,表示每隔多少毫秒就自动调用这个函数。
- 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。
- 第一次执行也是间隔毫秒数之后执行,之后每隔毫秒数就执行一次。
- 定时器返回的是一个id数字
停止 setInterval() 定时器
window.clearInterval(intervalID); clearInterval()方法取消了先前通过调用 setInterval()建立的定时器。
let 变量名 = setInterval(函数名, 间隔时间)
// 关闭定时器
clearInterval(变量名)
注意:
- window 可以省略。
- 里面的参数就是定时器的标识符。
<body>
<button class="start">开启定时器</button>
<button class="stop">停止定时器</button>
<script>
var start = document.querySelector('.start');
var stop = document.querySelector('.stop');
//注意:这里的timer要定义在全局作用域下,否则清除定时器的时候获取不到局部变量,并且timer初始值最好为null,如果为undefine时,清除定时器会出问题
var timer = null;
start.addEventListener('click', function() {
timer = setInterval(function() {
console.log('哈哈哈');
}, 1000)
})
stop.addEventListener('click', function() {
clearInterval(timer)
})
</script>
</body>
两种定时器对比和注意
执行的次数
-
延时函数: 执行一次
-
间歇函数:每隔一段时间就执行一次,除非手动清除
注意:
- 在定时器里面是无法删除定时器的,因为定时器还在运作,所以使用 timer = null 而不是clearTimeouet(timer)
- 清除定时器如果不是在定时器里面,clearTimeouet(timer)没有问题
let timer = null;
//无法删除定时器
timer = setTimeout(() => {
clearTimeout(timer);
console.log(timer); //timer为非空的数字标识
}, 1000);
//这种写法才可以
timer = setTimeout(() => {
timer = null
console.log(timer); //timer为非空的数字标识
}, 1000);
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 等
异步任务相关
回调函数添加到任务队列中(任务队列也称为消息队列)。
console.log(1);
setTimeout(function () {
console.log(3);
}, 0);
console.log(2);
JS 执行机制
- 先执行
执行栈中的同步任务。 - 异步任务(回调函数)放入任务队列中。
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取
任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
console.log(1);
document.onclick = function() {
console.log('click');
}
console.log(2);
setTimeout(function() {
console.log(3)
}, 3000)
js执行机制小结:js中会把我们的任务分为同步任务和异步任务,把同步任务放在执行栈中,当有异步任务时,异步任务会提交给对应的异步进程处理(浏览器),它等着你点击啊、等着你时间到啊,当你触发完毕后,才会把回调函数异步任务放到任务队列中去。主线程中的同步任务执行完毕之后,再到任务队列中看看有没有异步任务,如果有,就取过来,再到执行栈中去执行,然后再回头看有没有异步任务,如果有再取过来再去执行,这种循环的过程,我们称之为事件循环。
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环(event loop)。
location 对象
什么是 location 对象
-
location 的数据类型是对象,它拆分并保存了 URL 地址的各个组成部分。
-
常用属性和方法:
- href 属性获取完整的 URL 地址,对其赋值时用于地址的跳转
- search 属性获取地址中携带的参数,符号 ? 后面部分(包括?)
- hash 属性获取地址中的啥希值,符号 # 后面部分(包括#,后期vue路由的铺垫,经常用于不刷新页面,显示不同页面,比如 网易云音乐)
- reload 方法用来刷新当前页面,传入参数 true 时表示强制刷新
window 对象给我们提供了一个 location 属性用于获取或设置窗体的 URL,并且可以用于解析 URL 。 因为这个属性返回的是一个对象,所以我们将这个属性也称为 location 对象。
URL
统一资源定位符 (Uniform Resource Locator, URL) 是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
URL 的一般语法格式为:
protocol://host[:port]/path/[?query]#fragment
http://www.itcast.cn/index.html?name=andy&age=18#link
location 对象的属性
重点记住: href 和 search
//第一个登录页面,里面有提交表单, action 提交到 index.html页面
//第二个页面,可以使用第一个页面的参数,这样实现了一个数据不同页面之间的传递效果
//第二个页面之所以可以使用第一个页面的数据,是利用了URL 里的 location.search参数
//在第二个页面中,需要把这个参数提取。
//第一步去掉? 利用 substr
//第二步 利用=号分割 键 和 值 split(‘=‘)
//第一个数组就是键 第二个数组就是值
//login.html
<body>
<form action="index.html">
用户名:<input type="text" name="uname">
<input type="submit" value="登录">
</form>
</body>
//输入用户名,点击登录,在index.html地址栏中
http://127.0.0.1:5500/Dom/location/index.html?uname=andy
//index.html
<body>
<div></div>
<script>
var params = location.search.substr(1);
var arr = params.split('=');
console.log(location.search,params,arr);
var div = document.querySelector('div');
div.innerHTML = arr[1] + ',欢迎您'
</script>
</body>
location 对象的方法
navigator 对象
-
navigator的数据类型是对象,该对象下记录了浏览器自身的相关信息
-
常用属性和方法:
- 通过 userAgent 检测浏览器的版本及平台
// 检测 userAgent(浏览器信息) !(function () { const userAgent = navigator.userAgent // 验证是否为Android或iPhone const android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/) const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/) // 如果是Android或iPhone,则跳转至移动站点 if (android || iphone) { location.href = 'http://m.itcast.cn' } })()
navigator 对象包含有关浏览器的信息,它有很多属性,我们最常用的是 userAgent,该属性可以返回由客户机发送服务器的 user-agent 头部的值。
下面前端代码可以判断用户那个终端打开页面,实现跳转
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))) {
window.location.href = ""; //手机
} else {
window.location.href = ""; //电脑
}
history 对象
- history 的数据类型是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退、历史记录等(该对象包含用户(在浏览器窗口中)访问过的 URL。)
- 常用属性和方法
history 对象一般在实际开发中比较少用,但是会在一些 OA 办公系统中见到。
本地存储
本地存储介绍
以前我们页面写的数据一刷新页面就没有了,是不是?
随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,HTML5规范提出了相关解决方案。
本地存储特性
- 数据存储在用户浏览器中
- 设置、读取方便、甚至页面刷新不丢失数据
- 容量较大,sessionStorage约5M、localStorage约20M
- 只能存储字符串,可以将对象JSON.stringify() 编码后存储
- 常见的使用场景:
todomvc.com/examples/va… 页面刷新数据不丢失
本地存储分类
sessionStorage
特征:
- 生命周期为关闭浏览器窗口
- 在同一个窗口(页面)下数据可以共享
- 以键值对的形式存储使用
语法:
- 存储数据:sessionStorage.setItem(key, value)
- 获取数据:sessionStorage.getItem(key)
- 删除数据:sessionStorage.removeItem(key)
- 删除所有数据:sessionStorage.clear()
localStorage
-
作用: 可以将数据永久存储在本地(用户的电脑), 除非手动删除,否则关闭页面也会存在
-
特性:
- 可以多窗口(页面)共享(同一浏览器可以共享)
- 以键值对的形式存储使用
- 只能存储字符串数据类型
-
语法:
- 存储数据:localStorage.setItem(key, value)
- 获取数据:localStorage.getItem(key)
- 删除数据:localStorage.removeItem(key)
- 删除所有数据:localStorage.clear()
-
浏览器查看本地数据
存储复杂数据类型
-
本地只能存储字符串,无法存储复杂数据类型。
-
解决:需要将复杂数据类型转换成JSON字符串,在存储到本地
-
语法:
JSON.stringify(复杂数据类型) -
问题:因为本地存储里面取出来的是字符串,不是对象,无法直接使用
-
解决:把取出来的字符串转换为对象,语法:
JSON.parse(JSON字符串)
PC 端网页特效
元素偏移量 offset 系列
offset 概述
ffset 翻译过来就是偏移量, 我们使用 offset 系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
- 获得元素距离带有定位父元素的位置
- 获得元素自身的大小(宽度高度)
- 它以带有定位的父亲为准,如果没有父亲或者父亲没有定位,则以body为准
注意: 返回的数值都不带单位
offset 与 style 区别
offset
- offset 可以得到任意样式表中的样式值
- offset 系列获得的数值是没有单位的
- offsetWidth 包含padding+border+width
- offsetWidth 等属性是只读属性,只能获取不能赋值
所以,我们想要获取元素大小位置,用offset更合适
style
- style 只能得到行内样式表中的样式值
- style.width 获得的是带有单位的字符串
- style.width 获得不包含padding和border 的值
- style.width 是可读写属性,可以获取也可以赋值
所以,我们想要给元素更改值,则需要用style改变
//获取鼠标在盒子内的坐标
//我们在盒子内点击,想要得到鼠标距离盒子左右的距离。
//首先得到鼠标在页面中的坐标(e.pageX, e.pageY)
//其次得到盒子在页面中的距离 ( box.offsetLeft, box.offsetTop)
//用鼠标距离页面的坐标减去盒子在页面中的距离,得到 鼠标在盒子内的坐标
//如果想要移动一下鼠标,就要获取最新的坐标,使用鼠标移动事件 mousemove
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
background-color: pink;
margin: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
var box = document.querySelector('.box');
box.addEventListener('mousemove', function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
this.innerHTML = 'x坐标是:'+x+' y坐标是:'+y
})
</script>
</body>
</html>
<script>
//模态框拖拽
//弹出框,我们也称为模态框。
//点击弹出层, 会弹出模态框, 并且显示灰色半透明的遮挡层。
//点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层。
//鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
//鼠标松开,可以停止拖动模态框移动。
// 1. 获取元素
var login = document.querySelector('.login');
var mask = document.querySelector('.login-bg');
var link = document.querySelector('#link');
var closeBtn = document.querySelector('#closeBtn');
var title = document.querySelector('#title');
// 2. 点击弹出层这个链接 link 让mask 和login 显示出来
link.addEventListener('click', function() {
mask.style.display = 'block';
login.style.display = 'block';
})
// 3. 点击 closeBtn 就隐藏 mask 和 login
closeBtn.addEventListener('click', function() {
mask.style.display = 'none';
login.style.display = 'none';
})
// 4. 开始拖拽
// (1) 当我们鼠标按下, 就获得鼠标在盒子内的坐标
title.addEventListener('mousedown', function(e) {
var x = e.pageX - login.offsetLeft;
var y = e.pageY - login.offsetTop;
// (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
document.addEventListener('mousemove', move)
function move(e) {
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
// (3) 鼠标弹起,就让鼠标移动事件移除
document.addEventListener('mouseup', function() {
document.removeEventListener('mousemove', move);
})
})
</script>
元素可视区 client 系列
client 翻译过来就是客户端,我们使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到该元素的边框大小、元素大小等。
立即执行函数
不需要调用,立马能够自己执行的函数。
写法:
- (function() {})(); (function(a,b) {})(1,2)
- (function() {}()); (function(a,b) {}(1,2))
立即执行函数最大的作用:创建一个独立的作用域,避免了命名冲突问题。
load和pageshow
下面三种情况都会刷新页面都会触发 load 事件。
- a标签的超链接
- F5或者刷新按钮(强制刷新)
- 前进后退按钮
但是 火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。
所以此时后退按钮不能刷新页面。
此时可以使用 pageshow事件来触发,这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中,pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件,注意这个事件给window添加。
//pageshow是我们重新加载页面触发的事件(不管点了刷新按钮、后退、链接都可以重新加载页面)
window.addEventListener('pageshow', function() {
//e.persisted 返回的是true 就是说如果这个页面是从缓存取出来的页面,也需要xxxxx
if (e.persisted) {
xxxxx
}
})
动画函数封装
动画实现原理
核心原理:通过定时器 setInterval() 不断移动盒子位置。
实现步骤:
- 获得盒子当前位置
- 让盒子在当前位置加上1个移动距离
- 利用定时器不断重复这个操作
- 加一个结束定时器的条件
- 注意此元素需要添加定位,才能使用element.style.left
//动画原理
// 1. 获得盒子当前位置
// 2. 让盒子在当前位置加上1个移动距离
// 3. 利用定时器不断重复这个操作
// 4. 加一个结束定时器的条件
// 5. 注意此元素需要添加定位,才能使用element.style.left
var div = document.querySelector('div');
var timer = setInterval(function() {
if (div.offsetLeft >= 400) {
clearInterval(timer)
}
div.style.left = div.offsetLeft + 1 + 'px';
}, 30)
动画函数简单封装
注意函数需要传递2个参数,动画对象和移动到的距离。
function animtae(obj, target) {
var timer = setInterval(function() {
if (obj.offsetLeft >= target) {
clearInterval(timer)
}
obj.style.left = obj.offsetLeft + 1 + 'px';
}, 30)
}
var div = document.querySelector('div');
animtae(div, 300)
动画函数给不同元素记录不同定时器
如果多个元素都使用这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。
核心原理:利用 JS 是一门动态语言,可以很方便的给当前对象添加属性。
<script>
//给不同的元素指定了不同的定时器
function animtae(obj, target) {
//当我们不断的点击按钮,这个元素的速度会越来越快
//解决方案:让我们元素只有一个定时器执行
//先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
if (obj.offsetLeft >= target) {
clearInterval(obj.timer)
}
obj.style.left = obj.offsetLeft + 1 + 'px';
}, 30)
}
var btn = document.querySelector('button')
var div = document.querySelector('div');
var span = document.querySelector('span');
btn.addEventListener('click', function() {
animtae(div, 300)
})
animtae(span, 400)
</script>
缓动效果原理
缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来 思路:
- 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
- 核心算法: (目标值 - 现在的位置 ) / 10 做为每次移动的距离 步长
- 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
- 注意步长值需要取整
动画函数多个目标值之间移动
以让动画函数从 800 移动到 500。
当我们点击按钮时候,判断步长是正值还是负值
- 如果是正值,则步长 往大了取整
- 如果是负值,则步长 向小了取整
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动画</title>
<style>
div {
position: absolute;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<button class="btn500">点击按钮到500</button>
<button class="btn800">点击按钮到800</button>
<div>div</div>
<script>
//让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
// 核心算法: (目标值 - 现在的位置 ) / 10 做为每次移动的距离 步长
// 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
//给不同的元素指定了不同的定时器
function animate(obj, target) {
//当我们不断的点击按钮,这个元素的速度会越来越快
//解决方案:让我们元素只有一个定时器执行
//先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
//步长值写道定时器里面
//步长值改为整数,不要出现小数问题
var step = (target - obj.offsetLeft) / 10;
//动画可以前进也可以后退,如果后退,应该是负数,向下取整
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval(obj.timer)
}
//把每次加1 这个步长值改为一个慢慢变小的值,步长公式:(目标值 - 现在的位置 ) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 30)
}
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800')
var div = document.querySelector('div');
btn500.addEventListener('click', function() {
animate(div, 500)
})
btn800.addEventListener('click', function() {
animate(div, 800)
})
</script>
</body>
</html>
动画函数添加回调函数
回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。
回调函数写的位置:定时器结束的位置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动画</title>
<style>
div {
position: absolute;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<button class="btn500">点击按钮到500</button>
<button class="btn800">点击按钮到800</button>
<div>div</div>
<script>
//让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
// 核心算法: (目标值 - 现在的位置 ) / 10 做为每次移动的距离 步长
// 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
//给不同的元素指定了不同的定时器
function animate(obj, target, callback) {
//当我们不断的点击按钮,这个元素的速度会越来越快
//解决方案:让我们元素只有一个定时器执行
//先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
//步长值写道定时器里面
//步长值改为整数,不要出现小数问题
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval(obj.timer);
//回调函数写到定时器里面
if (callback) {
callback();
}
}
//把每次加1 这个步长值改为一个慢慢变小的值,步长公式:(目标值 - 现在的位置 ) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 30)
}
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800')
var div = document.querySelector('div');
btn500.addEventListener('click', function() {
animate(div, 500)
})
btn800.addEventListener('click', function() {
animate(div, 800, function() {
div.style.backgroundColor = 'red';
})
})
</script>
</body>
</html>
动画函数封装到单独JS文件里面
因为以后经常使用这个动画函数,可以单独封装到一个JS文件里面,使用的时候引用这个JS文件即可。
- 单独新建一个JS文件。
- HTML文件引入 JS 文件。
M端(移动端)事件
触屏事件
触屏事件概述
移动端浏览器兼容性较好,我们不需要考虑以前 JS 的兼容性问题,可以放心的使用原生 JS 书写效果。
但是移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android 和 IOS 都有。
touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。
常见的触屏事件如下:
触摸事件对象(TouchEvent)
TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各自有事件对象。
触摸事件对象重点我们看三个常见对象列表:
注意:当我们手指离开屏幕的时候,就没有了 touches 和 targetTouches 列表,但是会有 changedTouches。
因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
targetTouches[0] 就可以得到正在触摸dom元素的第一个手指的相关信息比如 手指的坐标等等
移动端拖动元素
-
touchstart、touchmove、touchend 可以实现拖动元素
-
但是拖动元素需要当前手指的坐标值 我们可以使用 targetTouches[0] 里面的pageX 和 pageY
-
移动端拖动的原理:手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
-
手指移动的距离:手指滑动中的位置 减去 手指刚开始触摸的位置 拖动元素三步曲:
- 触摸元素 touchstart:获取手指初始坐标,同时获得盒子原来的位置
- 移动手指 touchmove:计算手指的滑动距离,并且移动盒子
- 离开手指 touchend:
注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
div {
position: absolute;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<script>
// (1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
// (2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
// (3) 离开手指 touchend:
var div = document.querySelector('div');
var startX = 0; //获取手指初始坐标
var startY = 0;
var x = 0; //获得盒子原来的位置
var y = 0;
div.addEventListener('touchstart', function(e) {
// 获取手指初始坐标
startX = e.targetTouches[0].pageX;
startY = e.targetTouches[0].pageY;
x = this.offsetLeft;
y = this.offsetTop;
});
div.addEventListener('touchmove', function(e) {
// 计算手指的移动距离: 手指移动之后的坐标减去手指初始的坐标
var moveX = e.targetTouches[0].pageX - startX;
var moveY = e.targetTouches[0].pageY - startY;
// 移动我们的盒子 盒子原来的位置 + 手指移动的距离
this.style.left = x + moveX + 'px';
this.style.top = y + moveY + 'px';
e.preventDefault(); // 阻止屏幕滚动的默认行为
});
</script>
</body>
</html>
移动端常见特效
classList 属性
classList属性是HTML5新增的一个属性,返回元素的类名。但是ie10以上版本支持。
该属性用于在元素中添加,移除及切换 CSS 类。有以下方法
- 添加类:element.classList.add(’类名’);
- 移除类:element.classList.remove(’类名’);
- 切换类(有这个类删除没有这个类添加):element.classList.toggle(’类名’);
注意以上方法里面,所有类名都不带点
click 延时解决方案
移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) 页面。
解决方案:
-
禁用缩放。 浏览器禁用默认的双击缩放行为并且去掉 300ms 的点击延迟。
<meta name="viewport" content="user-scalable=no"> -
利用touch事件自己封装这个事件解决 300ms 延迟。
原理就是:
- 当我们手指触摸屏幕,记录当前触摸时间
- 当我们手指离开屏幕, 用离开的时间减去触摸的时间
- 如果时间小于150ms,并且没有滑动过屏幕, 那么我们就定义为点击
//封装tap,解决click 300ms 延时
function tap (obj, callback) {
var isMove = false;
var startTime = 0; // 记录触摸时候的时间变量
obj.addEventListener('touchstart', function (e) {
startTime = Date.now(); // 记录触摸时间
});
obj.addEventListener('touchmove', function (e) {
isMove = true; // 看看是否有滑动,有滑动算拖拽,不算点击
});
obj.addEventListener('touchend', function (e) {
if (!isMove && (Date.now() - startTime) < 150) { // 如果手指触摸和离开时间小于150ms 算点击
callback && callback(); // 执行回调函数
}
isMove = false; // 取反 重置
startTime = 0;
});
}
//调用
tap(div, function(){ // 执行代码 });
解决方案:
- 使用插件。 fastclick 插件解决 300ms 延迟。
移动端常用开发框架
框架概述
框架,顾名思义就是一套架构,它会基于自身的特点向用户提供一套较为完整的解决方案。框架的控制权在框架本身,使用者要按照框架所规定的某种规范进行开发。
插件一般是为了解决某个问题而专门存在,其功能单一,并且比较小。
前端常用的框架有 Bootstrap、Vue、Angular、React 等。既能开发PC端,也能开发移动端
前端常用的移动端插件有 swiper、superslide、iscroll等。
框架: 大而全,一整套解决方案
插件: 小而专一,某个功能的解决方案
Bootstrap
Bootstrap 是一个简洁、直观、强悍的前端开发框架,它让 web 开发更迅速、简单。 它能开发PC端,也能开发移动端 Bootstrap JS插件使用步骤:
- 引入相关js 文件
- 复制HTML 结构
- 修改对应样式
- 修改相应JS 参数
插件
-
插件: 就是别人写好的一些代码,我们只需要复制对应的代码,就可以直接实现对应的效果
-
学习插件的基本过程
- 熟悉官网,了解这个插件可以完成什么需求 www.swiper.com.cn/
- 看在线演示,找到符合自己需求的demo www.swiper.com.cn/demo/index.…
- 查看基本使用流程 www.swiper.com.cn/usage/index…
- 查看APi文档,去配置自己的插件 www.swiper.com.cn/api/index.h…
- 注意: 多个swiper同时使用的时候, 类名需要注意区分
插件简述
JS 插件是 js 文件,它遵循一定规范编写,方便程序展示效果,拥有特定功能且方便调用。如轮播图和瀑布流插件。
特点:它一般是为了解决某个问题而专门存在,其功能单一,并且比较小。 我们以前写的animate.js 也算一个最简单的插件
fastclick 插件解决 300ms 延迟。 使用延时
GitHub官网地址: github.com/ftlabs/fast…
插件的使用
- 引入 js 插件文件。
- 按照规定语法使用。
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
Swiper 插件的使用
中文官网地址: www.swiper.com.cn/
- 引入插件相关文件。
- 按照规定语法使用
其他移动端常见插件
- superslide: www.superslide2.com/
- iscroll: github.com/cubiq/iscro…
插件的使用总结
- 确认插件实现的功能
- 去官网查看使用说明
- 下载插件
- 打开demo实例文件,查看需要引入的相关文件,并且引入
- 复制demo实例文件中的结构html,样式css以及js代码
练习-移动端视频插件 zy.media.js
H5 给我们提供了 video 标签,但是浏览器的支持情况不同。
不同的视频格式文件,我们可以通过source 解决。
但是外观样式,还有暂停,播放,全屏等功能我们只能自己写代码解决。
这个时候我们可以使用插件方式来制作。
正则表达式
介绍
-
什么是正则表达式?
正则表达式(Regular Expression)是用于
匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。通常用来查找、替换那些符合正则表达式的文本,许多语言都支持正则表达式。
-
请在下图中找出【戴帽子和眼镜的男人】
戴帽子、戴眼镜、男人都是描述信息,通过这些信息能够在人群中查找到确定的某个人,那么这些用于查找的描述信息编写一个模式,对应到计算机中就是所谓的正则表达式。
-
正则表达式在 JavaScript中的使用场景:
-
例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)
比如用户名: /^[a-z0-9_-]{3,16}$/
-
过滤掉页面内容中的一些敏感词(
替换),或从字符串中获取我们想要的特定部分(提取)等
-
总结
- 正则表达式是什么?
是用于匹配字符串中字符组合的模式
- 正则表达式有什么作用?
表单验证(匹配)
过滤敏感词(替换)
字符串中提取我们想要的部分(提取)
语法
-
我们想要查找一群人中是否有戴眼镜的人, 怎么做呢?
-
定义规则: 戴眼镜的
-
根据规则去查找:找到则返回
-
-
正则同样道理,我们分为两步:
-
定义规则
-
查找
-
-
比如:查找下面文本中是否包含字符串 '前端'
- const str = '我们在学习前端,希望学习前端能高薪毕业'
-
JavaScript 中定义正则表达式的语法有两种,我们先学习其中比较简单的方法
-
步骤
-
定义正则表达式语法:
const 变量名 = /表达式/其中 // 是正则表达式字面量
比如
const reg = /前端/ -
检测(查找)是否匹配(判断是否有符合规则的字符串)
-
方式一.
test()方法 用来查看正则表达式与指定的字符串是否匹配,如果正则表达式与指定的字符串匹配,返回true,否则false语法:regObj.test(被检测的字符串)
比如 reg.test(str)
-
方式二.
exec()方法 在一个指定字符串中执行一个搜索匹配,如果匹配成功,exec() 方法返回一个数组,否则返回null语法:regObj.exec(被检测的字符串)
比如 reg.exec(str)
<script> const str = '我们在学习前端,希望学习前端能高薪毕业' // 正则表达式使用: // 1. 定义规则 const reg = /前端/ // 2. 是否匹配 test() // console.log(reg.test(str)) // true // 3. 是否匹配 exec() console.log(reg.exec(str)) // 返回数组 </script> -
- 总结
-
正则表达式检测查找 test方法和exec方法有什么区别?
- test方法 用于判断是否有符合规则的字符串,返回的是布尔值 找到返回true,否则false
- exec方法用于检索(查找)符合规则的字符串,找到返回数组,否则为 null
元字符
-
普通字符:
大多数的字符仅能够描述它们本身,这些字符称作普通字符,例如所有的字母和数字。
也就是说普通字符只能够匹配字符串中与它们相同的字符。
-
元字符(特殊字符)
是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能。
比如,规定用户只能输入英文26个英文字母,普通字符的话 abcdefghijklm…..
但是换成元字符写法: [a-z]
-
参考文档:
为了方便记忆和学习,我们对众多的元字符进行了分类:
- 边界符(表示位置,开头和结尾,必须用什么开头,用什么结尾)
- 量词 (表示重复次数)
- 字符类 (比如 \d 表示 0~9)
边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
如果 ^ 和 $ 在一起,表示必须是精确匹配。
console.log(/^哈/.test('哈')) // true
console.log(/^哈/.test('哈哈')) // true
console.log(/^哈/.test('二哈')) // flase
console.log(/^哈$/.test('哈')) // true 只有这种情况为true 否则全是false
console.log(/^哈$/.test('哈哈')) // false
console.log(/^哈$/.test('二哈')) // false
量词
量词用来 设定某个模式出现的次数
注意: 逗号左右两侧千万不要出现空格!!!
// 量词 * 类似 >=0 次
console.log(/^哈$/.test('哈')) // true
console.log(/^哈*$/.test('')) // true
console.log(/^哈*$/.test('哈')) // true
console.log(/^哈*$/.test('哈哈')) // true
console.log(/^哈*$/.test('二哈很傻')) // false
console.log(/^哈*$/.test('哈很傻')) // false
console.log(/^哈*$/.test('哈很哈')) // false
// 量词 + 类似 >=1 次
console.log(/^哈$/.test('哈')) // true
console.log(/^哈+$/.test('')) // false
console.log(/^哈+$/.test('哈')) // true
console.log(/^哈+$/.test('哈哈')) // true
console.log(/^哈+$/.test('二哈很傻')) // false
console.log(/^哈+$/.test('哈很傻')) // false
console.log(/^哈+$/.test('哈很哈')) // false
console.log('------------------')
console.log('------------------')
// 量词 ? 类似 0 || 1
console.log(/^哈?$/.test('')) // true
console.log(/^哈?$/.test('哈')) // true
console.log(/^哈?$/.test('哈哈')) // true
console.log(/^哈?$/.test('二哈很傻')) // false
console.log(/^哈?$/.test('哈很傻')) // false
console.log(/^哈?$/.test('哈很哈')) // false
// 量词 {n} 写几,就必须出现几次
console.log(/^哈{4}$/.test('哈')) // false
console.log(/^哈{4}$/.test('哈哈')) // false
console.log(/^哈{4}$/.test('哈哈哈')) // false
console.log(/^哈{4}$/.test('哈哈哈哈')) // true
console.log(/^哈{4}$/.test('哈哈哈哈哈')) // false
console.log(/^哈{4}$/.test('哈哈哈哈哈哈')) // false
console.log('------------------')
// 量词 {n,} >=n
console.log(/^哈{4,}$/.test('哈')) // false
console.log(/^哈{4,}$/.test('哈哈')) // false
console.log(/^哈{4,}$/.test('哈哈哈')) // false
console.log(/^哈{4,}$/.test('哈哈哈哈')) // true
console.log(/^哈{4,}$/.test('哈哈哈哈哈')) // true
console.log(/^哈{4,}$/.test('哈哈哈哈哈哈')) // true
console.log('------------------')
// 量词 {n,m} 逗号左右两侧千万不能有空格 >=n && <= m
console.log(/^哈{4,6}$/.test('哈')) // false
console.log(/^哈{4,6}$/.test('哈哈')) // false
console.log(/^哈{4,6}$/.test('哈哈哈')) // false
console.log(/^哈{4,6}$/.test('哈哈哈哈')) // true
console.log(/^哈{4,6}$/.test('哈哈哈哈哈')) // true
console.log(/^哈{4,6}$/.test('哈哈哈哈哈哈')) // true
console.log(/^哈{4,6}$/.test('哈哈哈哈哈哈哈')) // false
- 总结
- + 表示重复至少 1 次
- ? 表示重复 0 次或1次
- * 表示重复 0 次或多次
- {m, n} 表示重复 m 到 n 次
字符类
-
[ ] 匹配字符集合
- 后面的字符串只要包含 abc 中任意
一个字符,都返回 true 。
- 后面的字符串只要包含 abc 中任意
-
[ ] 里面加上 - 连字符
-
使用连字符 - 表示一个范围
-
比如:
[a-z] 表示 a 到 z 26个英文字母都可以
[a-zA-Z] 表示大小写都可以
[0-9] 表示 0~9 的数字都可以
-
-
[ ] 里面加上 ^ 取反符号
-
比如:
[^a-z] 匹配除了小写字母以外的字符
注意要写到中括号里面
-
-
. 匹配除换行符之外的任何单个字符
-
预定义:指的是 某些常见模式的简写方式。
总结:
[abc] 匹配abc其中的任何单个字符
[a-z] 匹配26个小写英文字母其中的任何单个字符
[^a-z] 匹配除了26个小写英文字母之外的其他任何单个字符
修饰符
-
修饰符约束正则执行的某些细节行为,如是否区分大小写、是否支持多行匹配等
-
语法:/表达式/修饰符
- i 是单词 ignore 的缩写,正则匹配时字母不区分大小写 /^JAVA$/i.test('java')
- g 是单词 global 的缩写,匹配所有满足正则表达式的结果
- eg. console.log(/a/i.test('a')) //true
-
替换 replace 替换
-
语法:字符串.replace(/正则表达式/, '替换的文本')
console.log(/^java$/.test('java')) // true
console.log(/^java$/i.test('JAVA')) // true
console.log(/^java$/i.test('Java')) // true
const str = 'java是一门编程语言, 学完JAVA工资很高'
// const re = str.replace(/java|JAVA/g, '前端')
const re = str.replace(/java/ig, '前端')
console.log(re) // 前端是一门编程语言, 学完前端工资很高
扩展阅读
重绘和回流
浏览器是如何进行界面渲染的?
- 解析(Parser)HTML,生成DOM树(DOM Tree)
- 同时解析(Parser) CSS,生成样式规则 (Style Rules)
- 根据DOM树和样式规则,生成渲染树(Render Tree)
- 进行布局 Layout(回流/重排):根据生成的渲染树,得到节点的几何信息(位置,大小)
- 进行绘制 Painting(重绘): 根据计算和获取的信息进行整个页面的绘制
- Display: 展示在页面上
-
回流(重排)
当 Render Tree 中部分或者全部元素的尺寸、结构、布局等发生改变时,浏览器就会重新渲染部分或全部文档的过程称为 回流。
-
重绘
由于节点(元素)的样式的改变并不影响它在文档流中的位置和文档布局时(比如:color、background-color、outline等), 称为重绘。
-
注意:
重绘不一定引起回流,而回流一定会引起重绘。 -
会导致回流(重排)的操作:
- 页面的首次刷新
- 浏览器的窗口大小发生改变
- 元素的大小或位置发生改变
- 改变字体的大小
- 内容的变化(如:input框的输入,图片的大小)
- 激活css伪类 (如::hover)
- 脚本操作DOM(添加或者删除可见的DOM元素)
简单理解影响到布局了,就会有回流
this
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,一般情况下this的最终指向的是那个调用它的对象
现阶段,我们先了解一下几个this指向
- 全局作用域或者普通函数中this指向全局对象window(注意定时器里面的this指向window)
- 方法调用中谁调用this指向谁
- 构造函数中this指向构造函数的实例
事件监听里面写const多次点击为啥没问题
const btn = document.querySelector("button");
btn.addEventListener("click", function () {
const num = Math.random()
});
多次点击,num重新赋值了,为啥没有报错? 因为用到了垃圾回收机制 简单来说,在一个函数里面,当我们执行完函数,变量不再使用了,它会进行销毁掉,或者叫做回收掉
滚动条/页面丝滑的滚动
html {
/* 让滚动条丝滑的滚动 */
scroll-behavior: smooth;
}
属性选择器
/*将input里面有type属性的颜色改为红色*/
input[type] {
color: red
}
/*将input里面有type属性为password的颜色改为红色*/
input[type=password] {
color: red
}
倒计时计算思路与公式
需求:计算到下课还有多少时间
分析:
① 用将来时间减去现在时间就是剩余的时间
② 核心: 使用将来的时间戳减去现在的时间戳
③ 把剩余的时间转换为 天 时 分 秒
注意:
-
通过时间戳得到是毫秒,需要转换为秒再计算
-
转换公式:
-
d = parseInt(总秒数/ 60/60 /24); // 计算天数
-
h = parseInt(总秒数/ 60/60 %24) // 计算小时
-
m = parseInt(总秒数 /60 %60 ); // 计算分数
-
s = parseInt(总秒数%60); // 计算当前秒数
form.reset()
清空表单 重置
数组 map join
数组中map方法 迭代数组
- 作用:map 迭代数组
- 语法:
arr.map() - 使用场景:map 可以处理数据,并且返回新的数组
数组中join方法
-
作用:join() 方法用于把数组中的所有元素转换一个字符串
-
语法:
arr.join() -
参数:数组元素是通过参数里面指定的分隔符进行分隔的