Be JS PUA (3)

84 阅读32分钟

2022.10.26

一.案例-表单验证

微信图片_20221026205235.png

<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'
  }
}

二.案例-密码强度

微信图片_20221026205523.png

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

三.案例-左右切换轮播图

1666789049312.png

   * {
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">&lt;</div>
<div class="next">&gt;</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')

五.案例-放大镜

1666960184773.png

 /*
说明:
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 叫做 顶级原型
*/

001.png

三.检测数据类型

 检测数据类型

  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)


二.不会销毁的空间

001.png

002.png

 /*
  不会销毁的空间
    + 当函数内返回了一个复杂数据类型
    + 并且外部有变量接受该数据的时候
    + 该空间不会被销毁

  再次想让他销毁
    + 只要让外部变量不在存储该地址即可
*/

function fn() {
  const obj = {
    name: 'Jack'
  }
  return obj
}

let res = fn()

console.log(res)

三.初识闭包

003.png 初识闭包
+ 概念: 函数里的函数
+ 私人:
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)

四.间接返回一个函数( 沙箱模式 )

004.png

/*
  间接返回一个函数( 沙箱模式 )
*/

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

六.函数 柯理化

函数柯理化:一种对封装函数的处理方式

005.png

 // 封装方案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)