2022.10.26
一.案例-表单验证
<style>
* {
margin: 0;
padding: 0;
}
form {
width: 500px;
padding: 30px;
border: 5px solid skyblue;
border-radius: 15px;
margin: 50px auto;
display: flex;
flex-direction: column;
font-size: 20px;
}
form > label {
height: 60px;
position: relative;
}
form > label > span {
position: absolute;
left: 15px;
bottom: 5px;
color: red;
display: none;
}
form > label > input {
font-size: 20px;
padding-left: 20px;
}
</style>
<form>
<label>
用户名 : <input type="text" autocomplete="off" class="username" name="username">
<span class="err">请按照规则填写用户名</span>
</label>
<label>
密码 : <input type="text" autocomplete="off" name="password">
<span>请按照规则填写密码</span>
</label>
<label>
手机号 : <input type="text" name="phone">
<span>请按照规则填写手机号</span>
</label>
</form>
/*
案例 - 表单验证
*/
// 整体书写的方式
// 0. 获取元素, 获取到 form 标签下所有的 文本框
const formEle = document.querySelector('form')
const inps = formEle.querySelectorAll('input[name]')
console.log(inps)
// 0. 准备正则
// 因为我是批量在操作表单验证
// 所以正则也需要批量书写
// 需要正则 和 页面上需要验证的文本框 有一一的对应关系
// 使用 对象 的方式来书写正则
// 需要有 key 和 value
// value 肯定是正则表达式
// key 就可以书写标签上 name 属性的值
// 页面上 name=username 的标签输入内容的时候, 使用 regObj 内 username 这个正则来验证
// 页面上 name=password 的标签输入内容的时候, 使用 regObj 内 password 这个正则来验证
const regObj = {
username: /^[0-9a-zA-Z]\w{5,11}$/,
password: /^\d{5,10}$/,
phone: /^\d{11}$/
}
// 1. 循环 inps 来依次绑定 input 事件
inps.forEach(item => item.oninput = handler)
function handler() {
// 2. 拿到你正在输入的那个元素的文本
const text = this.value
// 3. 拿到该文本框对应的正则表达式
// 要用该标签的 name 属性去 regObj 内读取内容
const reg = regObj[this.name]
// 4. 拿到该文本框对应的错误提示标签
const errBox = this.nextElementSibling
// 5. 判断
if (reg.test(text)) {
errBox.style.display = 'none'
} else {
errBox.style.display = 'block'
}
}
二.案例-密码强度
<style>
* {
margin: 0;
padding: 0;
}
form {
width: 500px;
padding: 30px;
border: 5px solid skyblue;
border-radius: 15px;
margin: 50px auto;
display: flex;
flex-direction: column;
font-size: 20px;
}
form > label > input {
font-size: 20px;
padding-left: 20px;
}
form > p {
width: 100%;
height: 30px;
margin: 10px 0 0 0;
display: flex;
justify-content: space-between;
}
form > p > span {
width: 30%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #ccc;
color: #fff;
}
form > p > span:nth-child(1).active {
background-color: red;
}
form > p > span:nth-child(2).active {
background-color: orange;
}
form > p > span:nth-child(3).active {
background-color: green;
}
</style>
<form>
<label>
密码 : <input type="text"class="password">
</label>
<p>
<span>弱</span>
<span>中</span>
<span>强</span>
</p>
</form>
<script>
/*
案例 - 密码强度
需求:
=> 随着输入, 实时判断密码强度的级别, 来给不同过得 span 添加 active 类名
=> 核心: 随着输入, 实时判断密码强度
=> 规则:
-> 数字 字母(不区分大小写) 符号(!@#%&)
-> 包含任意一种, 就是 弱
-> 包含任意两种, 就是 中
-> 三种全部包含, 就是 强
*/
// 0. 获取元素
const pwdInp = document.querySelector('.password')
const levelsBox = document.querySelectorAll('p > span')
// 0. 准备正则
const r1 = /\d/
const r2 = /[a-zA-Z]/
const r3 = /[!@#$%^&*]/
// 1. 绑定事件
pwdInp.oninput = function () {
// 2. 拿到用户输入的内容
const text = pwdInp.value
// 3. 想办法确定 text 的强度级别
let level = 0 // 1 2 3
// 你能通过一个正则, level++
if (r1.test(text)) level++
if (r2.test(text)) level++
if (r3.test(text)) level++
// 4. 根据确定好的级别来决定给那个或者哪些 span 添加 active 类名
// 如果 level === 0
// 如果 level === 1, 只有 levelsBox[0] 的元素有 active 类名
// 如果 level === 2, 只有 levelsBox[0 和 1] 的元素有 active 类名
// 如果 level === 3, 只有 levelsBox[0 和 1 和 2] 的元素有 active 类名
// 有 active 类名的元素的索引 必然 小于 level
for (let i = 0; i < levelsBox.length; i++) {
levelsBox[i].classList.remove('active')
if (i < level) levelsBox[i].classList.add('active')
}
}
</script>
三.案例-左右切换轮播图
* {
margin: 0;
padding: 0;
}
img {
width: 100%;
height: 100%;
display: block;
}
ul, ol, li {
list-style: none;
}
.banner {
width: 600px;
height: 350px;
border: 2px solid #333;
margin: 50px auto;
position: relative;
overflow: hidden;
}
.banner > ul {
width: 500%;
height: 100%;
display: flex;
position: absolute;
left: 0;
top: 0;
}
.banner > ul > li {
flex: 1;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 100px;
font-weight: 700;
color: #fff;
}
.banner > ol {
width: 200px;
height: 30px;
background-color: rgba(0, 0, 0, .6);
border-radius: 15px;
position: absolute;
left: 50%;
bottom: 30px;
transform: translateX(-50%);
display: flex;
justify-content: space-evenly;
align-items: center;
}
.banner > ol > li {
width: 20px;
height: 20px;
background-color: rgb(255, 255, 255);
border-radius: 50%;
cursor: pointer;
}
.banner > ol > li.active {
background-color: red;
}
.banner > div {
width: 40px;
height: 60px;
background-color: rgba(0, 0, 0, .3);
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
color: #fff;
cursor: pointer;
}
.banner > div.prev {
left: 0;
}
.banner > div.next {
right: 0;
}
.banner > div:hover {
background-color: rgba(0, 0, 0, .6);
}
<body>
<!-- 整个轮播图可视区 -->
<div class="banner">
<!-- 承载所有图片的大盒子 -->
<ul class="imgBox">
<li style="background-color: orange;">1</li>
<li style="background-color: skyblue;">2</li>
<li style="background-color: green;">3</li>
<li style="background-color: yellow;">4</li>
<li style="background-color: purple;">5</li>
<li style="background-color: pink;">6</li>
</ul>
<!-- 承载所有焦点的大盒子 -->
<ol class="pointBox"></ol>
<!-- 左右切换按钮 -->
<div class="prev"><</div>
<div class="next">></div>
</div>
<script src="../utils.js"></script>
<script src="./index.js"></script>
</body>
// 轮播图的逻辑代码
// 0. 获取元素
// 整个轮播图可视区域
const banner = document.querySelector('.banner')
// 承载图片的 ul 标签
const imgBox = banner.querySelector('.imgBox')
// 承载焦点的 ol 标签
const pointBox = banner.querySelector('.pointBox')
// 0. 准备变量
// 表示当前是第几个图片在展示, 表示一个 索引
let index = 1
// 表示可视窗口的宽度
// 语法: 元素.clientWidth
// 得到: 该元素 内容 + padding 区域的尺寸
const banner_width = banner.clientWidth
// 表示定时器返回值
let timer = 0
// 表示开关
let flag = true
/*
1. 调整本身的元素状态
+ 复制元素
+ 方案1: 手动调整 html 结构 + css 样式
+ 方案2: 使用 JS 来实现复制元素的行为
*/
copyEle()
function copyEle() {
// 1. 拿到 ul 内的第一个 li 和 最后一个 li 复制一份一模一样的
// 语法: 元素.firstElementChild
// 获取到该元素下的第一个子元素节点
// 语法: 元素.cloneNode(参数)
// 作用: 复制一份一模一样的该元素
// 参数:
// false 表示不克隆后代元素
// true 表示克隆后代元素
const first = imgBox.firstElementChild.cloneNode(true)
const last = imgBox.lastElementChild.cloneNode(true)
// 2. 把对应的元素插入到对应的位置
// 2-1. first 元素应该插入到 imgBox 内的最后一个位置
// 语法: 元素.appendChild(子元素)
// 作用: 把该子元素追加到该元素内, 放在最后一个的位置
imgBox.appendChild(first)
// 2-2. last 元素应该插入到 imgBox 内本身的第一个元素的前面
// 语法: 父元素.insertBefore(要插入的元素, 谁的前面)
imgBox.insertBefore(last, imgBox.firstElementChild)
// 3. 调整一下 imgBox 的宽度
// 因为插入了两个元素以后, 宽度不够了
// 宽度应该设置为多少
// 本身是 5 个, 插入完设置为 700%
// 本身是 9 个, 插入完设置为 1100%
// 当前子元素的数量 * 100 + '%'
// 语法: 元素.children
// 得到: 伪数组, 该元素内所有的子元素
// 语法: 元素.children.length
// 得到: 该元素内所有的子元素的数量
imgBox.style.width = imgBox.children.length * 100 + '%'
// 4. 调整一下 imgBox 的定位位置
// 当我们调整完毕图片以后, 第一个的位置是 假的最后一张
// 需要让 imgBox 向左移动一个可视窗口的尺寸
// 注意: 详见解释1
imgBox.style.left = -index * banner_width + 'px'
}
/*
2. 设置焦点
+ 根据 图片的数量 设置焦点
+ 方案1: 手动调整 html 结构 + css 样式, 保持焦点个数和图片个数一样
+ 方案2: 使用 JS 来实现焦点调整
*/
setPoint()
function setPoint() {
// 1. 拿到你需要设置的焦点的数量
// imgBox 内有多少个元素, 就需要设置多少个-2焦点
// 注意: 如果是先设置焦点, 后复制元素, 不需要 -2
const pointNum = imgBox.children.length - 2
// 2. 根据 pointNum 的值, 生成对应的 li 插入到 pointBox
for (let i = 0; i < pointNum; i++) {
// 2-1. 生成 li 标签
// 语法: document.createElement('标签名')
// 作用: 创建一个元素节点
const liEle = document.createElement('li')
// 2-2. 给某一个 li 添加 active 类名
// 注意: 详见解释2
if (i === index - 1) liEle.classList.add('active')
// 2-3. 给每一个 li 添加一个 item 类名
liEle.classList.add('item')
// 2-4. 给每一个 li 设置一个 data-point 的自定义属性, 值设置为索引
liEle.dataset.point = i
// 2-5. 插入到 pointBox 标签内
pointBox.appendChild(liEle)
}
// 3. 设置一下 pointBox 的宽度(选做)
// 根据焦点的个数来设置宽度
pointBox.style.width = pointNum * (20 + 10) + 'px'
}
/*
3. 自动轮播
+ 每间隔一段时间, 改变一张图片
=> 每间隔一段时间: setInterval()
=> 改变一张图片: 切换到下一张, 就是在改变 imgBox 的 left 位置
+ 注意: 详见解释3
*/
autoPlay()
function autoPlay() {
// 1. 开启定时器
timer = setInterval(() => {
// 2. 修改 index
index++
// 3. 使用 move 函数来修改 imgBox 的 left 值
move(imgBox, { left: -index * banner_width }, moveEnd)
}, 2000)
}
/*
4. 运动结束
+ 在每一张图片运动结束以后, 都需要进行一些判断
+ 调整一些内容
*/
function moveEnd() {
// 1. 根据 index 的值, 来判断是否要拉回来
// 要判断当最后一张(假的第一张) 运动结束的时候, 拉回来
// 最后一张(假的第一张) 的索引是多少 ? imgBox.children.length - 1
if (index === imgBox.children.length - 1) {
// 定到什么位置, index = 1
index = 1
// 修改 imgBox 的定位位置
// 不能用 move 运动回去, 需要瞬间定位回去
imgBox.style.left = -index * banner_width + 'px'
}
// 2. 根据 index 的值, 来判断是否要拉回去
// 判断当第一张(假的最后一张) 运动结束的时候, 拉回去
// 第一张(假的最后一张) 的索引是多少 ? 0
if (index === 0) {
index = imgBox.children.length - 2
imgBox.style.left = -index * banner_width + 'px'
}
// 3. 调整焦点跟随
// 把所有焦点的类名干掉
for (let i = 0; i < pointBox.children.length; i++) {
pointBox.children[i].classList.remove('active')
}
// 只让 [index - 1] 的焦点有 active 类名
pointBox.children[index - 1].classList.add('active')
// 4. 把开关开启
flag = true
}
/*
5. 移入移出
+ 当光标移入 轮播图可视区域 的时候, 停止自动轮播
+ 当光标移出 轮播图可视区域 的时候, 再次开启自动轮播
*/
overOut()
function overOut() {
// 1. 移入, 停止自动轮播(关闭定时器)
banner.addEventListener('mouseover', () => clearInterval(timer))
// 2. 移出, 再次开启自动轮播
banner.addEventListener('mouseout', () => autoPlay())
}
/*
6. 绑定点击事件
+ 左按钮点击事件
+ 右按钮点击事件
+ 每一个焦点的点击事件
+ 共同点:
=> 都是点击事件
=> 都是 banner 的后代元素
+ 事件委托
*/
bindEvent()
function bindEvent() {
// 1. 给banner 绑定点击事件
banner.addEventListener('click', e => {
// 2. 判断点击的是 左
if (e.target.className === 'prev') {
// 判断开关
if (flag === false) return
flag = false
index--
move(imgBox, { left: -index * banner_width }, moveEnd)
}
// 3. 判断点击的是 右
if (e.target.className === 'next') {
// 判断开关
if (flag === false) return
// 代码来到这里, 说明开关是 开启 的
// 关闭开关
// 注意: 详见解释5
flag = false
index++
move(imgBox, { left: -index * banner_width }, moveEnd)
}
// 4. 判断点击的是 焦点
// 因为 焦点 和 承载图片的小盒子 都是 li 标签, 不能严格区分
// 给每一个焦点 li 设置一个类名
if (e.target.className === 'item') {
// 判断开关
if (flag === false) return
flag = false
// 你点击的是哪一个 li, 就让索引对应的图片展示
// 核心: 你当前点击的这个 li 的索引是多少 ?
// 注意: 详见解释4
// console.log(e.target)
// 4-1. 拿到元素身上记录的 data-point 自定义属性
const point = e.target.dataset.point - 0
// 4-2. 把拿到的索引进行设置
index = point + 1
// 4-3. 使用 move 函数运动到指定位置即可
move(imgBox, { left: -index * banner_width }, moveEnd)
}
})
}
/*
7. 切换标签页(离开当前页面)
+ 再回来的时候会发生抖动现象
+ 原因:
=> 当你来开一个页面的时候, 定时器是不会停止的
=> 但是 DOM 不会动
+ 解决:
=> 当离开当前页面的时候, 让定时器停止(停止自动轮播)
=> 当回到当前页面的时候, 让自动轮播再次开启
*/
tabChange()
function tabChange() {
// 1. 写一个事件, 监控当前页面的离开和回来
// 事件类型: visibilitychange
// 事件源: document
// 注意: 该事件只有绑定在 document 身上的时候会触发
document.addEventListener('visibilitychange', () => {
// 2. 拿到可视状态
// 语法: document.visibilityState
// hidden: 表示隐藏, 从 可视 变为 不可视
// visible: 表示显示, 从 不可视 变为 可视
// 离开当前页面, 停止自动轮播
if (document.visibilityState === 'hidden') clearInterval(timer)
// 回到页面以后, 开启自动轮播
if (document.visibilityState === 'visible') autoPlay()
})
}
/* 解释1
假设: 我想让 [1] 的图片展示在可视窗口内
+ index = 1
+ imgBox 的 left 值是 -600px -index * 600
假设: 我想让 [2] 的图片展示在可视窗口内
+ index = 2
+ imgBox 的 left 值是 -1200px -index * 600
假设: 我想让 [3] 的图片展示在可视窗口内
+ index = 3
+ imgBox 的 left 值是 -1800px -index * 600
假设: 我想让 [0] 的图片展示在可视窗口内
+ index = 0
+ imgBox 的 left 值是 0px -index * 600
*/
/* 解释2
[1] 的 图片对应的焦点索引应该是 0
[2] 的 图片对应的焦点索引应该是 1
[3] 的 图片对应的焦点索引应该是 2
图片的索引 和 焦点的索引 是 -1 的关系
index 这个变量表示的是 图片的索引
*/
/* 解释3
目前:
=> 显示的是真实第一张
=> index = 1
=> imgBox 的 left 位置是 -600px -index * banner_width
2000ms 以后
=> 显示的是真实第二张
=> index = 2
=> imgBox 的 left 位置是 -1200px -index * banner_width
2000ms 以后
=> 显示的是真实第三张
=> index = 3
=> imgBox 的 left 位置是 -1800px -index * banner_width
修改完毕 index 以后, 使用 move 函数让他运动到指定位置
*/
/* 解释4
假设:
+ 你点击的是 [0] 的 li
+ 给 index 设置为 1
假设:
+ 你点击的是 [1] 的 li
+ 给 index 设置为 2
假设:
+ 你点击的是 [4] 的 li
+ 给 index 设置为 5
然后用 move 函数运动过去即可
你如何知道你点击的这个 li 的索引是多少 ?
+ 在渲染 li 的时候, 把该 li 的索引以自定义属性 data-point 的形式记录在标签身上
+ 当你点击 li 的时候, 只要读取到你点击的这个 li 的 data-point 自定义属性
+ 就知道你点击的 li 的索引是多少了
问题: 你能直到你点击的这个 li 是谁, 但是不知道他的索引 ?
+ 你点击的这个 li 是谁 ? e.target
+ 这些 li 是渲染到页面上的, 如果我在插入页面之前, 把每一个 li 的索引 "贴在" 标签身上
+ 你点击这个 li 的时候, e.target 身上的 "贴纸" 就记录着当前 li 的索引
问题: 如何把 索引 "贴在" 标签上 ?
+ 以自定义属性的形式记录在标签身上
+ 语法: 元素.dataset.xxx = yyy
=> 作用: 在标签上身上设置一个叫做 data-xxx="yyy" 的属性
问题: 如何读取元素身上的 "贴纸" ?
+ 语法: 元素.dataset.xxx
+ 得到: 该元素身上一个叫做 data-xxx 的属性对应的值
问题: 什么时候设置, 什么时候读取 ?
+ 在渲染该 li 的时候就要设置, 先设置好自定义属性, 再插入页面
=> 在 setPoint 函数内进行的 li 的生成和插入
+ 在你点击该 li 的时候读取, 因为这个时候需要使用了
=> 在点击事件内需要拿到索引去设置 index 并调整 imgBox 的 left 值
*/
/* 解释5
当你点击的时候, 判断如果开关是关闭的, 不去切换下一张
如果开关是开启的, 切换下一张的同时, 关闭开关
=> move(xxx, xxx, moveEnd) 表示开始运动(开始切换下一张)
=> moveEnd 会在当前这一张切换完毕后执行
=> moveEnd 函数内的最后一行代码执行完毕, 表示本次运动完整的结束了
=> 在 moveEnd 的最后面, 开启开关
*/
补充新知识点:
tabChange()
function tabChange() {
// 1. 写一个事件, 监控当前页面的离开和回来
// 事件类型: visibilitychange
// 事件源: document
// 注意: 该事件只有绑定在 document 身上的时候会触发
document.addEventListener('visibilitychange', () => {
// 2. 拿到可视状态
// 语法: document.visibilityState
// hidden: 表示隐藏, 从 可视 变为 不可视
// visible: 表示显示, 从 不可视 变为 可视
// 离开当前页面, 停止自动轮播
if (document.visibilityState === 'hidden') clearInterval(timer)
// 回到页面以后, 开启自动轮播
if (document.visibilityState === 'visible') autoPlay()
})
}
2022.10.27
一.运动函数
运动函数第一版 以下用到的样式以及 html为同一个
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 100px;
height: 100px;
background-color: skyblue;
position: absolute;
}
p {
width: 100px;
height: 100px;
background-color: orange;
position: absolute;
left: 0;
top: 200px;
}
</style>
<div></div>
<p></p>
运动函数第一版
<script>
/*
运动函数
+ 意义: 封装一个函数, 当调用该函数的时候, 可以给元素设置样式
=> 设置样式: 随着时间的变化, 逐步设置样式
*/
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
move(ele, 'top', 500)
}
const eleP = document.querySelector('p')
eleP.onclick = function () {
move(eleP, 'left', 300)
}
// 准备把运动的过程封装起来
// 书写一个函数需要几个参数
// 1. 谁在运动, 要运动的元素
// 2. 运动哪一个样式
// 3. 目标位置
function move(element, type, target) {
// 每间隔一段时间, 走一点点距离
// 准备一个变量表示初始值
let duration = 0
// 需要定时器
const timer = setInterval(() => {
// 随着定时器修改 duration
duration += 5
// 只要使用 duration 当做定位位置就可以了
element.style[type] = duration + 'px'
// 判断, 到位了需要停止
if (duration >= target) clearInterval(timer)
}, 20)
}
</script>
运动函数第二版
<script>
/*
问题: 如果元素一开始没在 0 位置, 运动过程会出现变化 ?
原因: 因为我封装的代码, 初始值设定的为 0
=> 不管你元素初始值在哪里, 我运动的初始值就是 0
解决: 把元素的初始值设置给运动的初始值
*/
function move(element, type, target) {
// 准备一个变量表示初始值
// type 表示的是 要运动的样式(详见解释1)
// 获取到元素的对应样式, 和 type 变量对应的
// 注意: 我们拿到的值是带有 'px' 为单位的
let duration = parseInt(window.getComputedStyle(element)[type])
// 需要定时器
const timer = setInterval(() => {
// 随着定时器修改 duration
duration += 5
// 只要使用 duration 当做定位位置就可以了
element.style[type] = duration + 'px'
// 判断, 到位了需要停止
if (duration >= target) clearInterval(timer)
}, 20)
}
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
move(ele, 'left', 500)
}
/* 解释1
假设: type === 'left'
+ 表示你要运动的就是 left 样式
+ 你最终要设置的是 left 样式
+ 你关心的初始值是元素 left 样式本身的值
假设: type === 'width'
+ 表示你要运动的就是 width 样式
+ 你最终要设置的是 width 样式
+ 你关心的初始值是元素 width 样式本身的值
你要拿到什么值来当做初始值
+ 元素对应样式的值
语法: 元素.style.样式名
得到: 该元素的行内样式的值
注意: 只能获取到元素的行内样式
你的元素初始值不一定是行内样式
语法: window.getComputedStyle(元素).样式名
得到: 该元素的某一个样式的值(行内样式或者非行内样式都能拿到)
*/
</script>
运动函数第三版
<script>
/*
问题: 初始值和目标值之间不是 5 的倍数, 就会出现问题 ?
原因: 初始值 和 目标值 的 差值, 不是 5 的倍数
=> 但是我们每次的增长都是 5
解决:
=> 方案1: 每次 +1
=> 方案2:
-> 每次运动剩余距离的 1/10
*/
function move(element, type, target) {
const timer = setInterval(() => {
// 1. 拿到当前值
const current = parseInt(window.getComputedStyle(element)[type])
// 2. 计算本次运动距离
const duration = (target - current) / 10
// 3. 判断是否到位
if (current === target) {
clearInterval(timer)
} else {
// 元素进行赋值
element.style[type] = current + duration + 'px'
}
}, 20)
}
/* 解释1
假设: type === 'left'
+ 表示你要运动的就是 left 样式
+ 你最终要设置的是 left 样式
+ 你关心的初始值是元素 left 样式本身的值
假设: type === 'width'
+ 表示你要运动的就是 width 样式
+ 你最终要设置的是 width 样式
+ 你关心的初始值是元素 width 样式本身的值
你要拿到什么值来当做初始值
+ 元素对应样式的值
语法: 元素.style.样式名
得到: 该元素的行内样式的值
注意: 只能获取到元素的行内样式
你的元素初始值不一定是行内样式
语法: window.getComputedStyle(元素).样式名
得到: 该元素的某一个样式的值(行内样式或者非行内样式都能拿到)
*/
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
move(ele, 'left', 500)
move(ele, 'top', 800)
}
</script>
运动函数第四版
<script>
/*
问题: 无法到达目标位置 ?
原因: 因为每次拿到 1/10 的时候, 会有小数
+ 假设: 上一次运动到了 90px 的位置, 我们的目标位置是 100px
=> 本次: 运动距离是 (100 - 90) / 10 得到 1
=> 本次把元素设置到了 91px 的位置
+ 下一次: 元素在 91px 的位置, 目标位置是 100px
=> 本次: 运动距离是 (100 - 91) / 10 得到 0.9
=> 本次把元素设置到了 91.9px 的位置
=> 浏览器能描述的最小像素是 1px
=> 虽然你设置的是 91.9px, 但是不足 1px 的浏览器不进行描述
=> 依旧把元素摆在 91px 的位置
+ 下一次: 元素在 91px 的位置, 目标位置是 100px
=> 本次: 运动距离是 (100 - 91) / 10 得到 0.9
=> 本次把元素设置到了 91.9px 的位置
=> 浏览器能描述的最小像素是 1px
=> 虽然你设置的是 91.9px, 但是不足 1px 的浏览器不进行描述
=> 依旧把元素摆在 91px 的位置
解决:
+ 向上取整, 永远不会有小于 1 的情况
+ 唯一一次拿到 0, 表示到位了
问题: 一旦向负方向运动, 无法到达执行位置 ?
原因: 向上取整
+ 因为每次都是向上取整
+ 因为是负方向, 得到的数字是负数
+ 一定会有 -0.x 的时候, 表示还没有到位
+ 向上取整以后得到 0
解决: 向下取整
问题: 只能考虑一个方向 ?
解决: 判断
+ 如果大于 0 , 向上取整
+ 如果小于 0 , 向下取整
*/
function move(element, type, target) {
const timer = setInterval(() => {
// 1. 拿到当前值
const current = parseInt(window.getComputedStyle(element)[type])
// 2. 计算本次运动距离
let duration = (target - current) / 10
duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
// 3. 判断是否到位
if (current === target) {
clearInterval(timer)
} else {
// 元素进行赋值
element.style[type] = current + duration + 'px'
}
}, 20)
}
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
move(ele, 'left', 500)
// move(ele, 'top', 800)
}
</script>
运动函数第五版
<script>
/*
问题: 无法识别 opacity ?
原因: 你设置元素值的时候带了一个 'px' 作为单位
+ 但是 opacity 这个样式是没有单位的
解决:
+ 判断一下, 如果你设置的是 opacty 那么不加 px 为单位
+ 如果你设置的不是 opacity, 加上 px 为单位
问题: 没有运动过程 ?
原因: 取值范围是 0 ~ 1 之间
+ 我们的 duration 进行了一个取整的操作
+ 我们的计算逻辑是按照 0 ~ 100 的取值逻辑进行计算
+ 但是 opacity 的取值范围是 0 ~ 1
解决:
+ 如果是 opacity 获取的时候, 放大 100 倍
+ 在赋值的时候, 再次缩小 100 倍
问题: 只能运动 1 ~ 0 ?
原因:
+ 根据之前的调整, 我们把 opacity 的计算范围也是按照 0 ~ 100 的范围计算
+ 但是你设置的时候, 是按照 0 ~ 1 的范围给出的目标
解决:
+ 方案1: 你在设置的时候, 也按照 0 ~ 100 的范围进行设置
+ 方案2: 你设置的时候, 还是按照 0 ~ 1 的范围设置
=> 我在封装的函数内, 先判断一下, 如果你是 opacity , 我来帮你放大 100倍
*/
function move(element, type, target) {
// 先判断一下, 你的 type 是不是 opacity, 如果是, 我先帮你放大 100 倍
if (type === 'opacity') target *= 100
// 开始运动
const timer = setInterval(() => {
// 1. 拿到当前值
// 判断一下是否是 opacity
let current
if (type === 'opacity') {
current = window.getComputedStyle(element)[type] * 100
} else {
current = parseInt(window.getComputedStyle(element)[type])
}
// 2. 计算本次运动距离
let duration = (target - current) / 10
duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
// 3. 判断是否到位
if (current === target) {
clearInterval(timer)
} else {
// 元素进行赋值 - 判断你设置的是否是 opacity
if (type === 'opacity') {
// 赋值的时候, 缩小 100 倍
element.style[type] = (current + duration) / 100
} else {
element.style[type] = current + duration + 'px'
}
}
}, 20)
}
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
move(ele, 'opacity', 0.12)
move(ele, 'left', 500)
move(ele, 'top', 800)
move(ele, 'width', 800)
move(ele, 'height', 600)
}
</script>
运动函数第六版
<script>
/*
问题: 一个函数只能运动一个样式 ?
原因: 没有参数位置 ?
解决:
=> 方案1: 增加参数位置
-> function move(element, type, target, type2, target2, type3, target3) {}
-> 用的时候, 必须传递满参数
=> 方案2: 改变传递参数的方式
-> 以一个数据结合的方式, 传递我要运动的多个样式
-> function move(element, target) {}
-> target 是一个 对象数据结构
{ left: 500 }
{ left: 500, top: 300 }
-> 封装的时候, 对象内有多少个成员, 就开启多少个运动的定时器
*/
// option 配置
function move(element, options) {
// console.log(options)
// 开启多少多少个运动, 取决于 options 内有多少个成员
// for in 循环遍历
for (let k in options) {
// 随着循环, k 分别是对象内的每一个 键名, 也就是我要运动的每一个 样式名
// 随着循环, options[k] 分别是对象内的每一个 值, 也就是我们要运动的每一个 样式值
// 先判断一下, 你的 type 是不是 opacity, 如果是, 我先帮你放大 100 倍
const type = k
let target = options[k]
if (type === 'opacity') target *= 100
// 开始运动
const timer = setInterval(() => {
// 1. 拿到当前值
// 判断一下是否是 opacity
let current
if (type === 'opacity') {
current = window.getComputedStyle(element)[type] * 100
} else {
current = parseInt(window.getComputedStyle(element)[type])
}
// 2. 计算本次运动距离
let duration = (target - current) / 10
duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
// 3. 判断是否到位
if (current === target) {
clearInterval(timer)
} else {
// 元素进行赋值 - 判断你设置的是否是 opacity
if (type === 'opacity') {
// 赋值的时候, 缩小 100 倍
element.style[type] = (current + duration) / 100
} else {
element.style[type] = current + duration + 'px'
}
}
}, 20)
}
}
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
// move(ele, 'opacity', 0.12)
// move(ele, 'left', 500)
// move(ele, 'top', 800)
// move(ele, 'width', 800)
// move(ele, 'height', 600)
move(ele, {
opacity: 0.12,
left: 500,
top: 800,
width: 900,
height: 600
})
}
</script>
运动函数第七版
<script>
/*
需求: 在运动结束以后, 在控制台打印一个信息
问题: 运动结束的代码会执行多次 ?
原因:
+ 因为你运动的样式多了, 会开启多个定时器
+ 定时器关闭的代码也会执行多个
解决:
+ 最后一个定时器结束了, 在执行这段结束的代码
+ 换成计数器的方式
+ 在定时器开始之前, 统计一共会开启多少个定时器
=> 每关闭一个定时器, 计数器--
=> 当计数器到 0 的时候, 表示所有定时器关闭了
=> 所有要运动的样式结束了
*/
// option 配置
function move(element, options) {
// console.log(options)
let count = 0
// 开启多少多少个运动, 取决于 options 内有多少个成员
for (let k in options) {
const type = k
let target = options[k]
if (type === 'opacity') target *= 100
// 开始运动
const timer = setInterval(() => {
// 1. 拿到当前值
// 判断一下是否是 opacity
let current
if (type === 'opacity') {
current = window.getComputedStyle(element)[type] * 100
} else {
current = parseInt(window.getComputedStyle(element)[type])
}
// 2. 计算本次运动距离
let duration = (target - current) / 10
duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
// 3. 判断是否到位
if (current === target) {
clearInterval(timer)
// 内关闭一个定时器, 计数器--
count--
// 当 count 为 0 的时候, 表示所有样式运动结束了
if (count === 0) console.log('结束')
} else {
// 元素进行赋值 - 判断你设置的是否是 opacity
if (type === 'opacity') {
// 赋值的时候, 缩小 100 倍
element.style[type] = (current + duration) / 100
} else {
element.style[type] = current + duration + 'px'
}
}
}, 20)
// 计数器++
count++
}
}
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
move(ele, {
opacity: 0.12,
left: 500,
top: 800,
width: 900,
height: 600
})
}
</script>
运动函数最终版
<script>
/*
需求: div 运动结束以后, 在控制台打印结束, p 运动结束以后, 以 alert 形式打印结束
解决:
+ 你把你想要在结束后做的事情, 放在一个 "盒子" 里面
+ 把这个 "盒子" 给到 move
+ 告诉 move, 当运动结束以后, 把 "盒子" 内的代码执行掉
+ 对这个 "盒子" 的要求: 可以承载一段代码
+ 承载一段代码的 "盒子" 是 函数
*/
// option 配置
function move(element, options, fn = () => {}) {
// fn 接受到的就是一个函数
let count = 0
// 开启多少多少个运动, 取决于 options 内有多少个成员
for (let k in options) {
const type = k
let target = options[k]
if (type === 'opacity') target *= 100
// 开始运动
const timer = setInterval(() => {
// 1. 拿到当前值
// 判断一下是否是 opacity
let current
if (type === 'opacity') {
current = window.getComputedStyle(element)[type] * 100
} else {
current = parseInt(window.getComputedStyle(element)[type])
}
// 2. 计算本次运动距离
let duration = (target - current) / 10
duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration)
// 3. 判断是否到位
if (current === target) {
clearInterval(timer)
// 内关闭一个定时器, 计数器--
count--
// 当 count 为 0 的时候, 表示所有样式运动结束了
if (count === 0) fn()
} else {
// 元素进行赋值 - 判断你设置的是否是 opacity
if (type === 'opacity') {
// 赋值的时候, 缩小 100 倍
element.style[type] = (current + duration) / 100
} else {
element.style[type] = current + duration + 'px'
}
}
}, 20)
// 计数器++
count++
}
}
// 需求: 点击 div 可以让 div 的 left 变成 500
// 需求: 需要你运动到目标位置
const ele = document.querySelector('div')
ele.onclick = function () {
move(
ele,
{
opacity: 0.12,
left: 500,
top: 800,
width: 900,
height: 600
},
function () {
ele.style.backgroundColor = 'purple'
}
)
}
const eleP = document.querySelector('p')
eleP.onclick = function () {
move(eleP, { left: 300, top: 200 }, () => {
eleP.innerText = '结束了'
})
}
</script>
二.认识面向对象
面向对象
+ 是一个开发方式, 是一个你完整功能时候的思考方式
1. 自定义构造函数创建对象
4-1. 自己书写一个自定义构造函数
4-2. 用自己自定义构造函数创建对象
构造函数:
+ 关键: 在如何调用函数, 调用时候必须要和 new 关键字连用
new 关键字: (熟读并背诵全文, 重点, 考试要考)
+ 当 new 关键字和一个函数连用的时候, 我们叫做构造方式调用
+ 会在该函数内 自动创建一个对象, 并且自动返回这个对象
+ 会让函数内的 this 指向这个自动创建出来的对象
// 4-1. 自己书写一个自定义构造函数
function createObj(name, age) {
// 1. 自动创建一个对象
// 2. 手动向这个对象插入内容
// 在该函数内, this 指向这个被自动创建出来的 对象
this.name = name
this.age = age
// 3. 自动把创建的这个对象返回
}
// 4-2. 用自己自定义构造函数创建对象
const o1 = new createObj('Jack', 18)
console.log(o1)
const o2 = new createObj('Rose', 20)
console.log(o2)
2.构造函数的书写
/*
构造函数的书写
1. 构造函数名首字母大写
+ 只是为了和其他函数做一个区分
2. 调用的时候必须要和 new 连用
+ 因为我们之所以书写 构造函数, 为了批量生产对象
+ 只有和 new 关键字连用的时候, 该函数才会有 自动创建对象 和 自动返回对象, 并让函数内的 this 指向这个对象的能力
+ 如果不和 new 连用, 没有这些能力, 就不是构造函数了
3. 构造函数内不要写 return
+ 因为构造函数和 new 连用以后, 会自动返回创建好的对象
+ 如果你在 构造函数 内 return 了一个基本数据类型, 写了白写
+ 如果你在 构造函数 内 return 了一个复杂数据类型, 构造函数白写
4. 构造函数内的 this
+ 因为该函数和 new 关键字连用了
+ 该函数内的 this 指向的就是这个被自动创建出来的对象
+ 我们管这个自动创建出来的对象叫做 实例对象
+ 我们管这个构造函数创建 实例对象 的过程, 叫做 实例化 的过程
*/
2022.10.28
一.认识面向对象
认识面向对象
+ 是一个开发思想, 是一种我们写代码的方式(方法)
解释名词:
+ 面向过程(关注过程式的开发)
=> 在开发过程中, 开率到每一个顺序步骤细节
=> 每一次的任务完成都需要从头开始, 考虑顺序步骤细节
+ 面向对象(关注对象式的开发)
=> 在开发过程中, 只考虑有没有一个对象能帮我完成该功能
=> 如果有, 直接用
-> 如果没有, 造一个
=> 既然选择了造一个
-> 干脆造一个机器, 批量生产
二.原型 prototype
原型 prototype(熟读并背诵全文)
+ 原型: 专门由构造函数向内添加成员, 提供给所有的实例对象使用
+ 注意: 原型添加的内容是由 构造函数添加, 但是不是为了给 构造函数使用
概念1: 每一个函数天生自带一个属性叫做 prototype(原型), 是一个对象数据类型
=> 私人: 每一个 "袋鼠" 天生自带一个 "口袋"
=> 构造函数的本质也是函数, 构造函数也会有 prototype
概念2: 每一个对象天生自带一个属性叫做 __proto__(原型), 指向所属构造函数的 prototype
=> 私人: 每一个 "小袋鼠" 天生自带一个 "家", 是所属 "大袋鼠" 的 "口袋"
概念3:
=> 当你需要访问一个对象成员(属性和方法)的时候
=> 首先在对象自己身上查找, 如果有直接使用, 停止查找
=> 如果没有, 自动去到自己的 __proto__ 查找, 如果有, 直接使用
=> 未完待续...
=> 私人: "小袋鼠" 需要找零食的时候, 先在自己身上查找, 如果没有就自动去自己 "家" 查找
解决构造函数的不合理
+ 当我们书写构造函数的时候
+ 属性直接书写在 构造函数体内
+ 方法直接书写在 构造函数原型上
// 书写一个合理的构造函数
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('你好 世界') }
// 准备实例化对象
const p1 = new Person('张三', 1)
console.log(p1)
const p2 = new Person('李四', 2)
console.log(p2)
p1.sayHi()
p2.sayHi()
console.log(p1.sayHi === p2.sayHi) ---> ture
三.ES6 的类
ES6 的类
+ 私人: ES6 书写构造函数的语法
语法:
class 类名 {
// 类的构造器(等价于构造函数的函数体)
constroctor () {
}
// 直接书写方法, 就是原型上的方法
sayHi () {}
}
注意: 类 不能当做 普通函数 调用
原因:
+ 我们之所以书写构造函数, 就是为了拿他当个 "机器" 来创造对象
+ 所以 构造函数 必须要和 new 连用
+ 但是, 构造函数 的本质 是一个函数, 是函数就可以直接被调用, 只是没有了创造对象的能力
=> 构造的能力失去了
+ 所以: 在 ES6 的类的时候
=> 专门当做一个 "机器", 为了创造对象使用的
=> 必须和 new 连用
// ES6 的类
class Person {
constructor (age) {
this.age = age
this.sayHi()
}
sayHi () {
console.log('你好 世界')
}
}
console.log(new Person(18));
四.案例-选项卡
<div class="box" id="tab1">
<ul>
<li class="active">1</li>
<li>2</li>
<li>3</li>
</ul>
<ol>
<li class="active">1</li>
<li>2</li>
<li>3</li>
</ol>
</div>
<div class="box" id="tab2">
<ul>
<li class="active">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<ol>
<li class="active">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ol>
</div>
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 500px;
height: 350px;
border: 5px solid #333;
margin: 50px auto;
display: flex;
flex-direction: column;
}
.box > ul {
height: 60px;
display: flex;
}
.box > ul > li {
font-size: 30px;
display: flex;
justify-content: center;
align-items: center;
flex: 1;
background-color: skyblue;
cursor: pointer;
color: #fff;
}
.box > ul > li.active {
background-color: orange;
}
.box > ol {
flex: 1;
position: relative;
}
.box > ol > li {
width: 100%;
height: 100%;
position: absolute;
background-color: purple;
font-size: 100px;
color: #fff;
display: none;
}
.box > ol > li.active {
display: block;
}
</style>
// 写一个构造函数 批量生产
function Tabs(select, options = {}) {
// 需要将来的对象内有一个 ele 元素
this.ele = document.querySelector(select)
this.btns = this.ele.querySelectorAll('ul > li')
this.boxs = this.ele.querySelectorAll('ol > li')
this.options = options
// 这里的 this 就是本次创建的实例对象
// 被自动返回以后赋值给了 t1
this.change()
}
// 需要一个 change 方法, 书写 Tabs 的原型(prototype)上
Tabs.prototype.change = function () {
// 将来这个 change 函数会被怎么被调用 ?
// 将来这个 change 函数, 一定会 实例对象.change()
// 该函数内的 this 是谁 ? 实例对象
for (let i = 0; i < this.btns.length; i++) {
this.btns[i].addEventListener(this.options.type || 'click', () => {
// 1. 去掉所有类名
// 去掉 当前实例 内 btns 和 boxs 的所有类名
// 这里的 this 是谁 ? 事件源
for (let j = 0; j < this.btns.length; j++) {
this.btns[j].classList.remove('active')
this.boxs[j].classList.remove('active')
}
// 2. 给 [i] 的加上
this.btns[i].classList.add('active')
this.boxs[i].classList.add('active')
})
}
}
// 实现选项卡
new Tabs('#tab1', { type: 'mouseover' })
new Tabs('#tab2')
五.案例-放大镜
/*
说明:
show 展示图片的盒子(img 和 mask)
mask 图片上的遮罩层
list 切换列表(p, p 的子级是 img)
enlarge 放大的图片的盒子(img)
需求:
1. 调整比例
+ 调整一下 mask 或者 enlarge 盒子的大小
mask盒子 enlarge盒子
------- = ------------
show盒子 enlarge图片
2. 移入移出
1-1. 移入 show, mask 和 enlarge(隐乐儿至, 放大的图片) 显示
1-2. 移出 show, mask 和 enlarge 隐藏
3. 点击事件
+ 点击 list 内的每一个 p 的时候
=> 切换 show 的子级 img 的 图片
=> 切换 enlarge 的子级 img 的 图片
=> 切换 p 的 active 类名
4. 移动事件
+ 在 show 内移动的时候
=> 让 mask 跟随鼠标移动
=> 让 enlarge 内的 img 按照比例移动
*/
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 450px;
height: 600px;
border: 1px solid #333;
margin: 50px;
display: flex;
flex-direction: column;
position: relative;
}
.box > .show {
height: 450px;
border-bottom: 1px solid #333;
position: relative;
}
.box > .show > .mask {
width: 200px;
height: 200px;
background-color: yellow;
opacity: 0.5;
position: absolute;
left: 100px;
top: 100px;
pointer-events: none;
display: none;
}
.box > .list {
flex: 1;
box-sizing: border-box;
display: flex;
align-items: center;
}
.box > .list > p {
border: 1px solid #333;
margin-left: 30px;
cursor: pointer;
}
.box > .list > p.active {
border-color: orange;
}
.box > .list > p > img {
pointer-events: none;
}
.box > .enlarge {
width: 400px;
height: 400px;
position: absolute;
left: 110%;
top: 0;
overflow: hidden;
display: none;
}
.box > .enlarge > img {
width: 800px;
height: 800px;
position: absolute;
left: 0;
top: 0;
}
</style>
<div class="box">
<div class="show">
<img src="./imgs/1.jpg" alt="">
<div class="mask"></div>
</div>
<div class="list">
<p data-show="./imgs/1.jpg" data-enlarge="./imgs/1.big.jpg" class="active"><img src="./imgs/1.small.jpg" alt=""></p>
<p data-show="./imgs/2.jpg" data-enlarge="./imgs/2.big.jpg"><img src="./imgs/2.small.jpg" alt=""></p>
</div>
<div class="enlarge">
<img src="./imgs/1.big.jpg" alt="">
</div>
</div>
<script>
new Enlarge('.box')
</script>
class Enlarge {
constructor (select) {
this.ele = document.querySelector(select)
// 需要用到的属性
this.show = this.ele.querySelector('.show')
this.mask = this.ele.querySelector('.mask')
this.list = this.ele.querySelector('.list')
this.enlarge = this.ele.querySelector('.enlarge')
// 拿到一些元素的尺寸
this.mask_width = parseInt(window.getComputedStyle(this.mask).width)
this.mask_height = parseInt(window.getComputedStyle(this.mask).height)
this.show_width = this.show.clientWidth
this.show_height = this.show.clientHeight
this.img_width = parseInt(window.getComputedStyle(this.enlarge.firstElementChild).width)
this.img_height = parseInt(window.getComputedStyle(this.enlarge.firstElementChild).height)
this.enlarge_width = 0
this.enlarge_height = 0
// 调用对应的方法
this.setScale()
this.overOut()
this.listChange()
this.move()
}
// 需要用到的方法
/*
调整 enlarge 的大小
mask盒子 enlarge盒子
------- = ------------
show盒子 enlarge图片
enlarge盒子 * show盒子 = mask盒子 * enlarge图片
enlarge盒子 = mask盒子 * enlarge图片 / show盒子
*/
setScale () {
// 计算比例
this.enlarge_width = this.mask_width * this.img_width / this.show_width
this.enlarge_height = this.mask_height * this.img_height / this.show_height
// 设置比例
this.enlarge.style.width = this.enlarge_width + 'px'
this.enlarge.style.height = this.enlarge_height + 'px'
}
overOut () {
this.show.addEventListener('mouseover', () => {
this.mask.style.display = 'block'
this.enlarge.style.display = 'block'
})
this.show.addEventListener('mouseout', () => {
this.mask.style.display = 'none'
this.enlarge.style.display = 'none'
})
}
/*
列表切换
*/
listChange () {
this.list.addEventListener('click', e => {
if (e.target.tagName === 'P') {
// 开始切换
// 1. 切换类名
for (let i = 0; i < this.list.children.length; i++) {
this.list.children[i].classList.remove('active')
}
e.target.classList.add('active')
// 2. 切换图片
// 当你点击 p 标签的时候
// 该标签身上自定义属性 data-show 的值, 就是 show 内 img 标签应该显示的图片地址
// 该标签身上自定义属性 data-enlarge 的值, 就是 enlarge 内 img 标签应该显示的图片地址
this.show.firstElementChild.src = e.target.dataset.show
this.enlarge.firstElementChild.src = e.target.dataset.enlarge
}
})
// 把 show 里面的图片换掉
// 如何更换 img 标签显示的图片
// 修改该 img 标签的 src 地址
// this.show.firstElementChild.src = './imgs/2.jpg'
}
/*
移动联动
+ 需要鼠标移动事件
*/
move () {
this.show.addEventListener('mousemove', e => {
// 1. 拿到光标坐标点
let x = e.offsetX - this.mask_width / 2
let y = e.offsetY - this.mask_height / 2
// 2. 边界值判断
if (x <= 0) x = 0
if (y <= 0) y = 0
if (x >= this.show_width - this.mask_width) x = this.show_width - this.mask_width
if (y >= this.show_height - this.mask_height) y = this.show_height - this.mask_height
// 3. 给 mask 标签的 left 和 top 赋值
this.mask.style.left = x + 'px'
this.mask.style.top = y + 'px'
// 4. 计算 enlarge 盒子内图片移动的尺寸
/*
mask尺寸 mask移动距离
---------- = ------------
enlarge尺寸 图片移动距离
mask尺寸 * 图片移动距离? = mask移动距离 * enlarge尺寸
图片移动距离? = mask移动距离 * enlarge尺寸 / mask尺寸
*/
const img_x = x * this.enlarge_width / this.mask_width * -1
const img_y = y * this.enlarge_height / this.mask_height * -1
// 5. 给 enlarge 内 img 的 left 和 top 赋值
this.enlarge.firstElementChild.style.left = img_x + 'px'
this.enlarge.firstElementChild.style.top = img_y + 'px'
})
}
}
2022.10.31
一.万物皆对象
/*
万物皆对象
+ 私人: 任何非空数据结构都可以看做一个 对象数据结构
回忆:
+ 对象 / 数组 / 函数 / 时间 / 正则 / ...
=> 本身就是一个复杂数据类型, 是一个 "盒子"
+ 数值 / 字符串 / 布尔
=> 是基本数据类型, 但是同时也是包装数据数据类型
=> 在使用的时候, 会瞬间转换成复杂数据类型的样子让你使用
=> 使用完毕会转换回基本数据类型的样子存储
+ 任何非空数据结构都可以把它当做一个 对象数据结构 使用
=> 任何一个数据, 都是一类内容中一个真实的个体
=> 例子:
{} 对象 这一类数据的一个真实个体
[] 数组 这一类数据的一个真实个体
10 数字 这一类数据的一个真实个体
'1' 字符串 这一类数据的一个真实个体
true 布尔 这一类数据的一个真实个体
function () {} 函数 这一类数据的一个真实个体
/^a$/ 正则 这一类数据的一个真实个体
每一个数据都可以被叫做 实例对象
-> 每一个数据都满足对象的特点
+ 特殊的数据结构(函数, 一等公民)
=> 普通函数, 可以封装一段代码, 执行一段代码
=> 构造函数, 主要关心书写的 this.xxx 的代码, 使用的时候 和 new 关键字连用, 可以创建对象
=> 对象结构, 可以当做一个对象使用, 存储一些数据
+ 每个数据所属的类
=> 数值 所属的类 Number
=> {} 所属的类 Object
=> [] 所属的类 Array
=> /^$/ 所属的类 RegExp
=> 时间对象 所属的类 Date
=> 函数 所属的类 Function
=> '' 所属的类 String
=> true 所属的类 Boolean
*/
二.原型链
/*
原型链
+ 面试题: 你给我解释一下什么是 原型 和 原型链 ?
+ 原型: 每一个构造函数天生自带的 prototype, 是一个对象数据结构, 讲方法书写在这里, 提供给所有实例对象使用
+ 原型链: 使用 __proto__ 串联起来的对象链状结构, 为了是对象访问机制服务
概念:
1. 每一个函数天生自带一个属性, 叫做 prototype, 是一个对象数据结构
2. 每一个对象天生自带一个属性, 叫做 __proto__, 指向所属构造函数的 prototype
3. 每一个没有构造函数的对象数据结构, 所属的构造函数是, 内置构造函数 Object
对象访问机制:
+ 当你需要访问一个对象的成员的时候
+ 首先在自己身上查找, 如果有, 直接使用, 停止查找
+ 如果没有, 会自动去自己的 __proto__ 查找, 如果有, 直接使用, 停止查找
+ 如果还没有, 就再去 __proto__ 查找
+ 直到 Object.prototype 都没有, 那么返回 undefined
问题1: 实例对象 p1 的 __proto__ 指向谁 ?
+ 因为 p1 是由 Person 创造出来的对象
+ 所以 p1 所属的构造函数是 Person
+ 所以 p1.__proto__ === Person.prototype
问题2: 自定义构造函数 Person 的 __proto__ 指向谁 ?
+ 自定义构造函数也是一个构造函数, 构造函数也是一个函数
+ 在 JS 内, 只要是函数, 就是属于 Function 类
+ Person 所属的构造函数就是 内置构造函数 Function
+ Person.__proto__ === Function.prototype
问题3: Person.prototype 的 __proto__ 指向谁 ?
+ 因为 Person.prototype 是一个标准的对象数据结构
+ 所以 Person.prototype 所属的构造函数就是内置构造函数 Object
+ Person.prototype.__proto__ === Object.prototype
问题4: 内置构造函数 Function 的 __proto__ 指向谁 ?
+ 内置构造函数也是一个构造函数, 构造函数也是一个函数
+ 在 JS 内, 只要是函数, 就是属于 Function 类
+ 内置构造函数 Function, 是 JS 内的顶级函数
=> 自己是自己的构造函数, 自己是自己的实例对象
+ 内置构造函数所属的构造函数就是 Function
+ Function.__proto__ === Function.prototype
问题5: Function.prototype 的 __proto__ 指向谁 ?
+ 因为 Function.prototype 是一个标准的对象数据结构
+ 所以 Function.prototype 所属的构造函数就是内置构造函数 Object
+ Function.prototype.__proto__ === Object.prototype
问题6: 内置构造函数 Object 的 __proto__ 指向谁 ?
+ 内置构造函数也是一个构造函数, 构造函数也是一个函数
+ 在 JS 内, 只要是函数, 就是属于 Function 类
+ 内置构造函数 Object 所属的构造函数 Function
+ Object.__proto__ === Function.prototype
问题7: Object.prototype 的 __proto__ 指向谁 ?
+ Object.prototype.__proto__ === null
+ 因为 JS 内 Object.prototype 叫做 顶级原型
*/
三.检测数据类型
检测数据类型
1. typeof
+ 语法:
=> typeof 要检测的数据结构
=> typeof(要检测的数据结构)
+ 特点: 只能准确的检测基本数据类型
2. constructor
+ 语法: 数据结构.constructor
+ 返回: 该数据结构所属的构造函数
+ 缺点: 不能判断 null 和 undefined 类型
=> 因为我们只有 非空 数据类型才可以使用 点语法
=> null 和 undefined 不能用
3. instanceof
+ 语法: 数据结构 instanceof 类
+ 返回值是一个布尔值
=> true: 说明 该数据结构是从属于该 类
=> false: 说明 该数据结构不从属于该 类
+ 特点: 不能判断 null 和 undefined 类型
4. Object.prototype.toString.call()
+ 语法: Object.prototype.toString.call(你要判断的数据结构)
+ 返回值: '[object 数据类型]'
+ 特点: 可以判断所有数据结构
+ 详见解释1
/* 解释1:
+ 在 Object.prototype 上有一个叫做 toString 的方法
=> 目的是为了给所有对象使用的
=> 作用: 只要是对象数据结构, 就转换成字符串数据类型
=> 结果: {} -> '[object Object]'
+ 为了避免所有的数据结构都能使用这个方法
=> 给每一个数据结构上都添加了 toString 方法
=> Array.prototype.toString = function () {} // 数组的就是去括号
=> String.prototype.toString = function () {} // 数值 字符串 布尔的可以转换进制转字符串
=> Object.prototype.toString = function () {} // 还原数据结构
+ 就想直接调用 Object 原型上存储的 toString 方法
=> 书写 Object.prototype.toString()
=> 原理就是把 this 还原数据结构
=> 如果这个函数内的 this 指向对象, 还原对象
=> 如果这个函数内的 this 指向数组, 还原数组
=> 如果这个函数内的 this 指向字符串, 还原字符串
=> Object.prototype.toString() 调用的时候
-> toString 函数内的 this 是谁 ? Object.prototype
=> 利用 call 方法改变函数内的 this 指向
-> Object.prototype.toString.call({}) 调用的时候
-> toString 函数内的 this 指向 {}
-> Object.prototype.toString.call([]) 调用的时候
-> toString 函数内的 this 指向 []
*/
四.对象的 toString 方法
/*
对象的 toString 方法
+ 在 Object.prototype 上是有这个方法的
+ 任何一个对象数据结构都可以调用
+ 当发生隐式转换的时候, 会自动调用该方法
=> 例子:
-> 用一个对象和 字符串 进行加法运算
-> '1' + {}
-> 以上表达式会进行字符串拼接
-> 左边是一个字符串, 但是右边不是
-> 会自动把右边的 {} 转换成 字符串类型, 在进行运算
-> 就触发了隐式转换, 就会自动调用 toString 方法
*/
// 改写一下 toString 方法
// Object.prototype.toString = function () {
// console.log('hello world')
// return '123'
// }
// const res = '1' + {}
// 左边是 : '1'
// 右边是 : '[object Object]'
// console.log(res)
// 任何一个对象数据结构, 访问成员的时候
// 在自己身上查找, 如果有直接使用
// 如果没有, 自动去到自己的 __proto__ 查找
// const o = {}
// 访问 o 的 toString 成员
// 自己没有, 去到自己的 __proto__ 查找
// o.__proto__ 是 Object.protoytype
// 访问并执行的就是 Object.prototype 上的 toString 方法
// o.toString()
// 面试题: 让下面等式成立
// const o = ?
// console.log(o == 1 && o == 2 && o == 3)
// ? 位置应该书写什么 ?
// o 的三次比较都是和 数值类型进行比较
// 在三次比较的时候
// 如果 o 本身就是一个数值类型, 那么没有关系
// 只要 o 不是数值类型, 就会触发三次 隐式转换, 转换成数值类型去比较
// o 会触发三次隐式转换
// 因为只要触发隐式转换就会触发 toString 方法
// 要求 o 必须是一个可以调用 toString 方法的数据
// 把 o 设计为一个对象数据结构
// const o = {
// }
// console.log(o == 1 && o == 2 && o == 3)
// 思考:
// 如果我让 o 可以调用 toString 方法
// 方案1: 修改 Object.prototype.toString 方法
// 方案2: 给 o 自己加一个 toString 方法
// const o = {
// toString () {
// console.log('我是给 o 自己家的一个 toString 方法')
// return 1
// }
// }
// console.log(o == 1 && o == 2 && o == 3)
// 因为这个 toString 方法会被调用三次
// 我需要第一次返回的是 1
// 我需要第二次返回的是 2
// 我需要第三次返回的是 3
const o = {
count: 0,
toString () {
// this 是谁 ? o
// this.count ? 0
// ++this.count ? 1, 并且把 o 内的 count 修改为 1
return ++this.count
}
}
console.log(o == 1 && o == 2 && o == 3 && o == 4)
/*
o == 1 && o == 2 && o == 3
第一步: o == 1
=> 发现 o 不是数值类型, 需要把 o 转换成数值类型
1-1. o.toString()
-> 会把 o.count 修改为 1
-> return 1
1-2. 用执行完毕的结果和 1 进行比较
-> 1 == 1
-> true
第二步: o == 2
=> 发现 o 不是数值类型, 需要把 o 转换成数值类型
2-1. o.toString()
-> 会把 o.count 修改为 2
-> return 2
2-2. 用执行完毕的结果和 2 进行比较
-> 2 == 2
-> true
第三部: o == 3
=> 发现 o 不是数值类型, 需要把 o 转换成数值类型
3-1. o.toString()
-> 会把 o.count 修改为 3
-> return 3
3-2. 用执行完毕的结果和 3 进行比较
-> 3 == 3
-> true
*/
2022.11.01
一.复习函数
/*
函数
+ 是一个 JS 内的数据结构, 是一个复杂数据类型
+ 私人: 一个承载一段代码的 "盒子"
+ 函数必然有两个阶段
=> 函数定义阶段: 把代码装进 "盒子" 里面
=> 函数调用阶段: 把 "盒子" 内的代码执行一遍
函数两个阶段做的事情(熟读并背诵全文)
+ 函数定义阶段
1. 在堆内存开辟一段函数存储空间
2. 把函数体内的代码一模一样的存储在该空间内(不进行函数体内的变量解析)
3. 把空间地址赋值给变量
+ 函数调用阶段
1. 按照变量内的地址去查找一个函数存储空间
=> 如果变量不存在, 直接报错: xxx is not defined
=> 如果变量保存的不是函数地址, 直接报错: xxx is not a function
2. 在 调用栈 内开辟一段函数执行空间
3. 在 执行空间 内进行形参赋值
4. 在 执行空间 内进行预解析
5. 在 执行空间 内把函数体内的代码复制过来从头到尾执行一遍
6. 代码执行完毕, 执行空间销毁
*/
// 函数定义阶段
// let num = 100
// function fn() {
// // 假设在定义函数的时候就已经解析了变量, 那么应该存储起来的是 console.log(100)
// // 假设没有在定义函数的时候解析变量, 那么存储起来的是 console.log(num)
// console.log(num)
// }
// // 修改 num 的值
// num = 200
// 调用函数
// fn()
/*
先修改的 num 才调用的 fn 函数
+ 如果定义阶段就已经解析了变量, 那么 后期 num = 200 不会影响结果, 打印出来依旧是 100
+ 如果定义阶段没有解析变量, 那么后期 num = 200 会影响结果, 打印出来就是 200
*/
// 函数调用阶段
let num = 100
// 函数调用阶段
/*
如果函数调用的时候,
先进行形参赋值, 后预解析
1. 把 a 赋值为 10
2. 预解析的时候, 解析 a 变量发现已经存在, 但是要赋值为一个函数
打印 a 变量的时候, 得到的应该是一个函数
先预解析, 后进行形参赋值
1. 声明 a 变量, 并且赋值为一个函数
2. 给 a 变量赋值为 10
打印 a 变量的时候, 得到的应该是一个 10
*/
function fn(a) {
console.log(a)
function a() {
console.log('你好 世界')
}
}
fn(10)
二.不会销毁的空间
/*
不会销毁的空间
+ 当函数内返回了一个复杂数据类型
+ 并且外部有变量接受该数据的时候
+ 该空间不会被销毁
再次想让他销毁
+ 只要让外部变量不在存储该地址即可
*/
function fn() {
const obj = {
name: 'Jack'
}
return obj
}
let res = fn()
console.log(res)
三.初识闭包
初识闭包
+ 概念: 函数里的函数
+ 私人:
1. 一个不会销毁的空间
2. 在函数内 直接 或者 间接 返回一个函数
3. 内部函数需要使用着外部函数的私有数据
特点:
1. 可以在函数外部利用闭包来操作函数的私有变量
2. 延长了私有变量的生命周期
// 我们管 inner 叫做 outer 的闭包函数
function outer() {
// num 是 outer 的私有数据
const num = 100
function inner() {
return num
}
return inner
}
// res 接受的就是 outer 函数内定义的那个 inner 函数
// 我们就说 inner 是 outer 的闭包函数
// 因为 inner 被返回以后赋值给了 res
// 所以也说 res 是 outer 的闭包函数
const res = outer()
// 在调用 res 函数, 其实也就是在调用 outer 内的 inner 函数
// n 得到的是 inner 函数内 return num 结果
// inner 函数自己有 num 变量吗 ? 没有, 要去到外部作用域查找
// 也就是 outer 函数的私有变量 num
const n = res()
console.log(n)
四.间接返回一个函数( 沙箱模式 )
/*
间接返回一个函数( 沙箱模式 )
*/
function outer() {
let num = 100
let str = 'hello world'
let boo = true
const obj = {
getNum: function () { return num },
getStr: function () { return str },
setNum: function (val) { num = val },
}
return obj
}
// res 接受到的就是 outer 函数内的 obj 对象
// 内部存储了两个函数
const res = outer()
// 当我需要使用 outer 函数内的 num 变量的值的时候
// 代用的 getNum 函数, return 的是 outer 的私有变量 num 的值
console.log(res.getNum())
res.setNum(200)
// 再一次获取 outer 内的私有变量 num 的值
console.log(res.getNum())
const res2 = outer()
console.log(res2.getNum()) // 100
五.闭包语法糖
/*
res === {
getNum: 函数,
setNum: 函数
}
得到这个对象的目的:
+ 对该 fn 函数形成一个 "包裹"
+ 为了 获取 和 设置 fn 函数内的 私有变量 num
+ 一旦 "包裹" 内的 getNum 执行, 就会获取到 私有变量num
+ 一旦 "包裹" 内的 setNum 执行, 就会设置 私有变量num 的值
针对以上目的:
+ 考虑该语法, 发现不那么符合语境
+ 研究了一个 getter 获取器 和 setter 设置器的语法
+ 目的: 只是为了让 闭包 的语法更贴合语境
思考:
+ res 对象
+ 如果我把 res 真的想象成一个对象
+ 对于对象的操作语法
=> 对象名.xxx 这样来操作
*/
function fn() {
let num = 100
const obj = {
// 原先的 getNum 目的获取 num 的值, 我们语法糖使用 getter 获取器来书写
// 语法: get 名字 () {}
// getter 获取器会把 getNum 制作为一个 obj 对象内的成员, 值就是该函数的返回值
get getNum () { return num },
// 原先的 setNum 目的是设置 num 的值, 我们语法糖使用 setter 设置器来书写
// 语法: set 名字 () {}
// 当你按照对象的语法, 给 setNum 赋值的时候, 就会触发该函数
set setNum (val) { num = val }
}
return obj
}
// res 得到的就是 fn 函数内的 obj 对象
const res = fn()
console.log(res)
// getter 获取器的使用
// 当 getter 获取器把该内容直接制作成对象内的成员了
// 我们就按照对象的语法操作即可
console.log(res.getNum)
// setter 设置器的使用
res.setNum = 200
六.函数 柯理化
函数柯理化:一种对封装函数的处理方式
// 封装方案3: 利用闭包
// 步骤1: 书写一个 验证函数的 制造函数
function createRegTest(reg) {
function inner(str) {
return reg.test(str)
}
return inner
}
// 利用 createRegTest 函数来制造出正则验证函数
// testUsername 得到的是 inner 函数
const testUsername = createRegTest(/\w{5,11}/)
const testPassword = createRegTest(/\d{6,12}/)
// console.log(testUsername);
// console.log(testPassword);
// 开始验证
// 验证用户名
const r1 = testUsername('admin')
console.log(r1)
// 验证用户名第二次
const r2 = testUsername('guoxiang')
console.log(r2)
// 验证密码第一次
const r3 = testPassword('12345a')
console.log(r3)
// 验证用户名第三次
const r4 = testUsername('aaa')
console.log(r4)
七.复杂一些的柯理化函数 - 01
// 负责记录初次保留的内容
function create() {
// 需要把你传递的参数都保留下来
// 利用一个信息, arguments
// 函数内天生自带的信息, 表示该函数有多少个实参
// console.log(arguments)
// 创建一个数组, 把信息保留下来
const arr = [ ...arguments ]
// inner 函数需要做的事情, 就是接受四个参数, 把它们组装起来
function inner() {
// 把本次的参数传递到 arr 内继续保留下来
const data = [ ...arr, ...arguments ]
console.log(data)
// 判断, 如果 data 内不够 4 个, 我需要把目前的 若干个(2, 3, 1) 保留下来
// 如果 data 内够 4 个, 处理逻辑
if (data.length < 4) {
// console.log('应该继续收集')
// 把目前的 data 内的内容作为初始内容保留, 继续去收集
return create(...data)
} else {
// 把 data 内的数据求和, 给出结果
const res = data.reduce((prev, item) => prev + item, 0)
return res
}
}
return inner
}
// f1 接受了一个闭包. 闭包里面存储了一个 arr 数组, 里面有一个数据
// f1 得到了一个 闭包函数, 保留了一个数据结合, 里面有一个数据 [10]
const f1 = create(10)
// r 得到了一个 闭包函数, 保留了一个数据集合, 里面有三个数据 [10, 20, 30]
const r = f1(20, 30)
const res = r(40)
console.log(res)
八.复杂一些的柯理化函数 - 02
/*
复杂一些的柯理化函数
https://www.xhl.com:8080/index.html
https://www.xhl.com:8080/login.html
https://www.xhl.com:8081/index.html
https://www.gx.com:8080/index.html
http://www.xhl.com:8080/index.html
+ 以上内容包含了 四段 基本信息
=> 传输协议 http 或者 https
=> 域名 www.xhl.com 或者 www.gx.com 或者 ...
=> 端口号 8080 8081 0 ~ 65535
=> 地址 /index.html 或者 /login.html
*/
// 函数的柯理化处理
// 把一个正常逻辑的函数, 改变成 柯理化逻辑的 函数
// 正常逻辑的函数
// function resolveUrl(a, b, c, d) {
// return a + '://' + b + ':' + c + d
// }
// // 将来使用的时候
// const url1 = resolveUrl('http', 'www.xhl.com', 8080, '/index.html')
// const url2 = resolveUrl('http', 'www.xhl.com', 8080, '/login.html')
// console.log(url1)
// console.log(url2)
function resolveUrl(a, b, c, d) {
return a + '://' + b + ':' + c + d
}
// https://www.xhl.com:8080/index.html
// 书写柯理化函数
// ... 用在函数形参的位置, 表示合并
// fn 接受的是第一个实参
// 剩下的所有实参, 放在一个数组内放在 arr 里面了
function currying(fn, ...arr) {
// arr 就是我们收集的参数
// 确认我一共要多少个参数(功能函数有多少个形参, 就是需要多少个参数)
// 功能函数是 fn
// 功能函数有多少个形参: 函数名.length
const max = fn.length
function inner() {
const data = [ ...arr, ...arguments ]
console.log(data)
// 判断是否足够
if (data.length < max) {
return currying(fn, ...data)
} else {
// 利用 fn 函数来把四个参数拼装起来
// 返回的就是拼装结果
return fn(...data)
}
}
return inner
}
const f1 = currying(resolveUrl, 'http')
const f2 = f1('www.xhl.com')
const url1 = f2(8080, '/index.html')
console.log(url1)
const url2 = f2(8081, '/login.html')
console.log(url2)
const f3 = f2(8080)
const url3 = f3('/list.html')
console.log(url3)
九.复杂一些的柯理化函数 - 03
/*
复杂一些的柯理化函数
https://www.xhl.com:8080/index.html
https://www.xhl.com:8080/login.html
https://www.xhl.com:8081/index.html
https://www.gx.com:8080/index.html
http://www.xhl.com:8080/index.html
+ 以上内容包含了 四段 基本信息
=> 传输协议 http 或者 https
=> 域名 www.xhl.com 或者 www.gx.com 或者 ...
=> 端口号 8080 8081 0 ~ 65535
=> 地址 /index.html 或者 /login.html
*/
// 函数的柯理化处理
// 把一个正常逻辑的函数, 改变成 柯理化逻辑的 函数
// 正常逻辑的函数
function resolveUrl(a, b, c, d) {
return a + '://' + b + ':' + c + d
}
// 将来使用的时候
const url1 = resolveUrl('http', 'www.xhl.com', 8080, '/index.html')
const url2 = resolveUrl('http', 'www.xhl.com', 8080, '/login.html')
console.log(url1)
console.log(url2)
// 把我们的正常逻辑函数, 处理成柯理化形式
const resolveUrl = currying((a, b, c, d) => a + '://' + b + ':' + c + d)
// // 在我的开发过程中, 前三个都是固定的, 只有最后一个是变化的
const join3 = resolveUrl('http', 'www.xhl.com', 8080)
const join2 = resolveUrl('http', 'www.gx.com')
// // 开始拼接路径
const url1 = join3('/index.html')
const url2 = join3('/login.html')
console.log(url1)
console.log(url2)
const url3 = join2(8081, '/aaa.html')
const url4 = join2(8082, '/aaa.html')
console.log(url3)
console.log(url4)
// 在我的开发过程中, 前三个都是固定的, 只有最后一个是变化的
const join3 = currying((a, b, c, d) => a + '://' + b + ':' + c + d)('http', 'www.xhl.com', 8080)
const join2 = currying((a, b, c, d) => a + '://' + b + ':' + c + d)('http', 'www.gx.com')
// 开始拼接路径
const url1 = join3('/index.html')
const url2 = join3('/login.html')
console.log(url1)
console.log(url2)
const url3 = join2(8081, '/aaa.html')
const url4 = join2(8082, '/aaa.html')
console.log(url3)
console.log(url4)