JavaScript编程基础(九)完结散花:闭包原理、实战之投票系统与购物车功能

原创 阁主  2026-03-16 16:50:02  阅读 6001 次 评论 0 条
摘要:

继续上次的JavaScript编程基础(八)继续学习,简单记录学习PHP中文网23期JavaScript基础知识,内容包括:闭包原理、实战之投票系统与购物车功能

闭包作用

  • 函数调用一旦结束,它内部创建的所有成员全部销毁
  • 但是,由于返回一个子函数,而这个子函数又引用了父函数中的成员,会将它访问到的
  • 所有成员,保存到它的”作用域链上”,只要记录到了作用域链上的成员,它的环境就不能被销毁
  • 通过子函数将父函数中的成员, 返回或带出函数给外部
  • 通过闭包,顺利的访问到了函数的内部成员
  • 闭包的作用域是独立的,相当于其它语言中的”静态变量”的效果
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>闭包</title>
  </head>
  <body>
    <script>
      // 全局
      let a = 100;

      // 函数
      function f1() {
        let a = 200;
        console.log(a);
      }

      f1();

      /**
       * js 与其它语言相比有一个重要区别
       * 1. 可以不需要任何声明,直接访问外部成员,如变量
       * 2. 因此,js需要维护一个"作用域链"对象,来保存(记录)全部可访问的变量
       */

      // ================================

      // 父函数
      function outer() {
        let a = 100;

        // 子函数
        function f1() {
          // 子函数中,是可以访问到父函数中的(父作用域中)成员,如变量
          // 就像: 单向玻璃
          return a;
        }

        return f1;
      }

      // 调用 outer
      const f2 = outer();
      //   console.log(f2)

      // f2 仍然是一个函数
      // 调用 f2()
      console.log(f2());
      // 当我们调用f2(),已经产生了闭包

      let result = f2();

      /**
       * 闭包的作用
       * 1. 函数调用一旦结束,它内部创建的所有成员全部销毁
       * 2. 但是,由于返回一个子函数,而这个子函数又引用了父函数中的成员,会将它访问到的
       * 所有成员,保存到它的"作用域链上",只要记录到了作用域链上的成员,它的环境就不能被销毁
       * 3. 通过子函数将父函数中的成员, 返回或带出函数给外部
       * 4. 通过闭包,顺利的访问到了函数的内部成员
       * 5. 闭包的作用域是独立的,相当于其它语言中的"静态变量"的效果
       */

      // =========================================

      let count = 300;
      function fun() {
        let count = 0;

        return function () {
          return ++count;
        };
      }

      const f3 = fun();

      // 只要执行一次f3(),就是创建一个独立的闭包
      console.log(f3());
      console.log(f3());
      console.log(f3());
    </script>
  </body>
</html>

投票系统

投票系统

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>投票系统</title>
    <style>
      .box {
        width: 16em;
        padding: 1em;
        display: flex;
        border: 1px solid #ddd;
        background-color: #eee;
        box-shadow: 5px 5px 5px #888;
        margin: auto;
        color: #444;
        border-radius: 0.5em;
      }
      .box .item {
        flex: 1;
        text-align: center;
      }
      .box .item img {
        width: 4em;
        border-radius: 50%;
        box-shadow: 2px 2px 2px #aaa;
      }
      .box .item .vote {
        padding: 0.1em 1em;
        background-color: #fff;
        border: none;
        background-color: lightseagreen;
        border-radius: 0.5em;
        box-shadow: 2px 2px 2px #aaa;
      }
      .box .item .vote:hover {
        background-color: lightseagreen;
        color: white;
        cursor: pointer;
        transition: 0.3s;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item">
        <img src="images/xz.jpg" alt="" />
        <h3>肖战</h3>
        <button class="vote"></button>
      </div>
      <div class="item">
        <img src="images/zsw.jpg" alt="" />
        <h3>张颂文</h3>
        <button class="vote"></button>
      </div>
      <div class="item">
        <img src="images/yz.jpg" alt="" />
        <h3>杨紫</h3>
        <button class="vote"></button>
      </div>
    </div>

    <script>
      // 获取相关的DOM元素
      // 抽票按钮
      const voteBtns = document.querySelectorAll(".vote");
      // 数组: 保存每个人的投票结果
      const total = [0, 0, 0];

      // 今晚就不用箭头函数了,全部使用经典传统函数语法
      voteBtns.forEach(function (btn, index) {
        // 初始化起始票数
        btn.textContent = total[index] + " 票";
        // 为每个投票键添加点击事件
        btn.onclick = (function () {
          // 创建一个临时的闭包(静态)变量
          let count = 0;
          return function () {
            this.textContent = ++count + " 票";
            // count : 闭包中的变量
            total[index] = count;
            console.log(total);
          };
        })();
      });

      //=======================================

      // 相对于demo2(),demo1()就是它的外部环境,父函数
      function demo1() {
        let a = 500;
        // 子函数: 相对于demo1()
        return function () {
          // 此时,如果子函数引用了父函数中的成员
          // 就会创建一个闭包对象,内部保存着对父函数中的成员的引用 a
          // 尽管 a 定义在父函数 demo1中, 但是在demo2是可访问的
          console.log(a);
        };
      }

      const demo3 = demo1(); // 返回的还是一个函数
      // 外部调用 demo3(),相当于执行了子函数 demo2()
      // 创建了一个闭包对象
      demo3();
    </script>
  </body>
</html>

购物车

这个购物车示例大概就如下图所示,里面原本PHP中文网是使用json数据替换静态的html商品俩表,这边我修改成了根据商品数据data.js里的商品总数进行列出到前端,我也保留了中文网原来的html,学习提升最重要。

carts.html:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>购物车</title>
    <link rel="stylesheet" href="carts.css" />
  </head>
  <body>
    <table class="carts">
      <caption class="carts-title">
        我的购物车
      </caption>
      <!-- 1. 购物车头部 -->
      <thead class="carts-head">
        <tr>
          <!-- 全选按钮 -->
          <td>
            <input
              type="checkbox"
              name=""
              class="check-all"
              aria-label="checkbox"
              checked
            />
          </td>
          <td>编号</td>
          <td>品名</td>
          <td>单位</td>
          <td>单价</td>
          <td>数量</td>
          <td>金额(元)</td>
        </tr>
      </thead>
      <!-- 2. 购物车主体 -->
      <tbody class="carts-body">
        <!-- 下面是静态的数据模板,后续用js动态生成 -->
        <!-- <tr class="item">
          <td><input type="checkbox" name="" class="check" checked /></td>
          <td class="id">xxx</td>
          <td class="name">xxx</td>
          <td class="unit">xxx</td>
          <td class="price">xxx</td>
          <td><input type="number" name="" class="num" value="1" min="1" /></td>
          <td class="money">xxx</td>
        </tr>
        <tr class="item">
          <td><input type="checkbox" name="" class="check" checked /></td>
          <td class="id">xxx</td>
          <td class="name">xxx</td>
          <td class="unit">xxx</td>
          <td class="price">xxx</td>
          <td><input type="number" name="" class="num" value="1" min="1" /></td>
          <td class="money">xxx</td>
        </tr>
        <tr class="item">
          <td><input type="checkbox" name="" class="check" checked /></td>
          <td class="id">xxx</td>
          <td class="name">xxx</td>
          <td class="unit">xxx</td>
          <td class="price">xxx</td>
          <td><input type="number" name="" class="num" value="1" min="1" /></td>
          <td class="money">xxx</td>
        </tr>
        <tr class="item">
          <td><input type="checkbox" name="" class="check" checked /></td>
          <td class="id">xxx</td>
          <td class="name">xxx</td>
          <td class="unit">xxx</td>
          <td class="price">xxx</td>
          <td><input type="number" name="" class="num" value="1" min="1" /></td>
          <td class="money">xxx</td>
        </tr>
        <tr class="item">
          <td><input type="checkbox" name="" class="check" checked /></td>
          <td class="id">xxx</td>
          <td class="name">xxx</td>
          <td class="unit">xxx</td>
          <td class="price">xxx</td>
          <td><input type="number" name="" class="num" value="1" min="1" /></td>
          <td class="money">xxx</td>
        </tr> -->
      </tbody>

      <!-- 3. 购物车尾部 -->
      <tfoot class="carts-foot">
        <tr>
          <td colspan="5">总计:</td>
          <td class="total">xxx</td>
          <td class="total-money">xxx</td>
        </tr>
      </tfoot>
    </table>

    <script type="module">
      // (一) 从购物车模块中获取数据
      import carts from "./carts.js";
      // 水果商品列表,json格式
      const fruit_data = carts.data;
      console.log(fruit_data);
      console.log(carts.total);
      console.log(carts.totalMoney);

      // (二) 获取相关的dom元素
      const items = document.querySelector(".carts-body");
      const total = document.querySelector(".carts-foot .total");
      const totalMoney = document.querySelector(".carts-foot .total-money");

      console.log(items, total, totalMoney);

      // (三) 将以上模块中的数据,渲染到页面中
      // 1. 替换静态购物车的全部商品
      // items.forEach(function (item, index) {
      //   item.querySelector(".id").textContent = carts.data[index].id;
      //   item.querySelector(".name").textContent = carts.data[index].name;
      //   item.querySelector(".unit").textContent = carts.data[index].unit;
      //   item.querySelector(".price").textContent = carts.data[index].price;
      //   item.querySelector(".num").value = carts.data[index].num;
      //   item.querySelector(".money").textContent = carts.data[index].money;
      // });

      // 1.将fruit_data水果商品的json列表循环渲染到carts-body里,动态生成tr列表和td内容
      // 创建文档片断对象,防止页面抖动
      const frag = new DocumentFragment();
      for (let i = 0; i < fruit_data.length; i++) {
        let temp_data = fruit_data[i];
        let tr = document.createElement("tr");
        let td_str = `
              <td><input type="checkbox" name="" class="check" checked /></td>
              <td class="id">${temp_data.id}</td>
              <td class="name">${temp_data.name}</td>
              <td class="unit">${temp_data.unit}</td>
              <td class="price">${temp_data.price}</td>
              <td><input type="number" name="" class="num" value="${temp_data.num}" min="1" /></td>
              <td class="money">${temp_data.money}</td>`;
        // 将td_str字符串转换为dom对象
        tr.innerHTML = td_str;
        // console.log(tr);
        frag.append(tr);
      }
      // console.log(frag);
      // 将文档片断对象frag添加到items中,并删除原本的tr静态数据
      items.append(frag);

      // 2. 总数量
      total.textContent = carts.total;
      // 3. 总金额
      totalMoney.textContent = carts.totalMoney;
      // ===========================================

      //   (四) 获取购物车商品的数量,单价,金额构成的数组
      // 单价数组
      const prices = document.querySelectorAll(".carts-body .price");
      // 金额数组
      const monies = document.querySelectorAll(".carts-body .money");
      // console.log(monies);
      // 数量数组
      const nums = document.querySelectorAll(".carts-body .num");
      // 是否选中: check数组
      const checks = document.querySelectorAll(".carts-body .check");
      /**
       * 分析
       * 1. 需要更新的数据: 每个商品金额,总数量,总金额
       * 2. 以上的数据更新,全部依赖于商品数量的变化,进行动态计算
       */

      // (五) 遍历所有的数量控件,为每个数量控件添加 change 事件,(num为当前的数量控件, index为当前的索引, arr为所有的数量控件)
      nums.forEach(function (num, index, arr) {
        num.onchange = function () {
          // 检查当前商品有没有被选中,如果没有,就不要计算。
          // 选中check的状态是true,未选中的状态是false
          if (!checks[index].checked) {
            return false;
          }
          // 1. 计算商品金额 = 数量 * 单价
          monies[index].textContent = num.value * prices[index].textContent;
          // 2. 计算总数量
          // (1) 将每个数量控件中的数值取出来,但是value默认是字符,需要转换数值类型
          const numArr = [...arr].map(function (item) {
            //字符串转换为整数值
            return parseInt(item.value);
          });
          // (2) 求和得到总数量
          total.textContent = numArr.reduce(function (pre, cur) {
            //累加器
            return pre + cur;
          });

          // 3. 计算总金额
          // (1) 将每个数量控件中的数值取出来,但是默认是字符,需要转换数值类型
          const moneyArr = [...monies].map(function (money) {
            //字符串转换为浮点数值
            return parseFloat(money.textContent);
          });
          // (2) 求和得到总数量
          totalMoney.textContent = moneyArr.reduce(function (pre, cur) {
            //累加器
            return pre + cur;
          });
        };
      });

      // (六) 全选 / 全不选

      // 全选按钮
      const checkAll = document.querySelector(".check-all");
      // 缓存总数量和总金额
      let totalTmp = total.textContent;
      let totalMoneyTmp = totalMoney.textContent;

      // 为全选添加change
      checkAll.onchange = function () {
        // 遍历所有商品的状态,并将当前全选按钮的状态赋值给它
        checks.forEach(function (check) {
          // 选中状态修改为全选按钮的状态
          check.checked = checkAll.checked;
        });

        // 根据所有商品的状态,动态设置全选按钮的状态(如果所有商品都选中,全选按钮选中,否则不选中)
        // this : 全选按钮
        if (this.checked === false) {
          // 检测每个商品的选中状态,只有全部未选中才返回 true
          // every : 检测每个元素是否都符合条件,只有全部符合条件才返回 true
          let itemStatus = [...checks].every(function (check) {
            return check.checked === false;
          });

          if (itemStatus === true) {
            total.textContent = 0;
            totalMoney.textContent = 0;
          }
        } else {
          // 用缓存的数组来填充
          total.textContent = totalTmp;
          totalMoney.textContent = totalMoneyTmp;
        }
      };

      // 遍历每个商品的复选框,并添加change,动态的计算相关数据
      checks.forEach(function (check, index) {
        // 添加change
        check.onchange = function () {
          // 根据所有商品的复选框的状态,来动态的设置全选
          // every : 检测每个元素是否都符合条件,只有全部符合条件才返回 true
          checkAll.checked = [...checks].every(function (check) {
            return check.checked;
          });

          // 如果商品未选中,应该减去相对应的总数量和总金额
          if (check.checked === false) {
            // 1. 总数量
            total.textContent = total.textContent - nums[index].value;
            // 2. 总金额
            totalMoney.textContent =
              totalMoney.textContent - monies[index].textContent;
          } else {
            // 如果商品选中,应该加上相对应的总数量和总金额
            // 1. 总数量
            total.textContent =
              parseInt(total.textContent) + parseInt(nums[index].value);
            // // 2. 总金额
            totalMoney.textContent =
              parseFloat(totalMoney.textContent) +
              parseFloat(monies[index].textContent);
            // ===================================================
            // JS中, "+"用在字符串运算时,是字符串拼接,用在数值运算时,是数值相加
            // 上面写法也可以写成,string->number的简便转换方法: 利用任何数乘以1, 都不变
            total.textContent = total.textContent * 1 + nums[index].value * 1;
            totalMoney.textContent =
              totalMoney.textContent * 1 + monies[index].textContent * 1;
          }
        };
      });
    </script>
  </body>
</html>

样式表carts.css:

/* 购物车 */
.carts {
  width: 450px;

  border-collapse: collapse;
  text-align: center;
  font-weight: lighter;
}
.carts th,
.carts td {
  padding: 5px;
}

/* 购物车头部 */
.carts .carts-head {
  background-color: lightseagreen;
  color: white;
}

.carts .carts-body tr {
  border-bottom: thin solid #ccc;
}
.carts input[type="number"] {
  width: 3em;
  color: seagreen;
  border: none;
}
.carts input[type="number"]:focus {
  outline: thin solid lightseagreen;
}
/* 购物车底部 */
.carts .carts-foot {
  background-color: lightcyan;
}

.carts .pay {
  display: grid;
  grid: 1fr / auto-flow;
  place-content: end;
  gap: 10px;
}

商品数据data.js:

// 模块: 商品数据

export default [
  {
    id: 20,
    name: "苹果",
    unit: "Kg",
    price: 10,
    num: 5,
    // 金额 = 数量 * 单价
    // 适合用: 访问器属性来创建
    get money() {
      return this.num * this.price;
    },
  },
  {
    id: 22,
    name: "草莓",
    unit: "箱",
    price: 10,
    num: 3,
    // 金额 = 数量 * 单价
    // 适合用: 访问器属性来创建
    get money() {
      return this.num * this.price;
    },
  },
  {
    id: 66,
    name: "香梨",
    unit: "件",
    price: 30,
    num: 1,
    get money() {
      return this.price * this.num;
    },
  },
  {
    id: 85,
    name: "西瓜",
    unit: "个",
    price: 40,
    num: 5,
    get money() {
      return this.price * this.num;
    },
  },
  {
    id: 99,
    name: "榴莲",
    unit: "千克",
    price: 50,
    num: 2,
    get money() {
      return this.price * this.num;
    },
  },
];

购物车模块carts.js:

// 模块: 购物车
import data from "./data.js";

export default {
  // 1. 商品数据
  data,

  // 2. 总数量(访问器属性)
  get total() {
    // (1) 获取商品数组中的数量橡构成的数组
    const nums = data.map(function (item) {
      return item.num;
    });
    //箭头函数的写法是
    // const nums = data.map(item => item.num);

    // (2) 对商品数量数组求和
    return nums.reduce(function (pre, cur) {
      return pre + cur;
    });
  },

  // 3. 总金额(访问器属性)
  get totalMoney() {
    // (1) 获取商品金额数组
    const monies = data.map(function (item) {
      return item.money;
    });
    // (2) 对商品金额数组求和
    return monies.reduce(function (pre, cur) {
      return pre + cur;
    });
  },
};

附件

本文核心代码附件如下,自行下载。

0404.zip大小:46.05KB
已经过安全软件检测无毒,请您放心下载。
本文地址:https://www.mainblog.cn/334.html
版权声明:本文为原创文章,版权归 阁主 所有,欢迎分享本文,转载请保留出处!
免责申明:有些内容源于网络,没能联系到作者。如侵犯到你的权益请告知,我们会尽快删除相关内容。
NEXT:已经是最新一篇了

评论已关闭!