JavaScript编程基础(五)细说事件与实战之经典计算器

原创 阁主  2026-02-22 14:11:18  阅读 2407 次 评论 0 条
摘要:

继续上次的JavaScript编程基础(四)继续学习,简单记录学习PHP中文网23期JavaScript基础知识,内容包括:dataset自定义属性、class属性、getComputedStyle计算样式、事件添加与派发、事件冒泡、事件冒泡: 事件代理/委托

dataset 对象

  1. 预定义属性: id,class,style, title...
  2. 自定义属性: data-前缀
  3. 注: data-不要写,蛇形->驼峰
<!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>dataset: 自定义属性</title>
  </head>
  <body>
    <!--
        属性
        1. 内置属性: id, class, style,...
        2. 自定义属性: data-前缀
           data-uname, data-my-email
     -->
    <div
      id="user_id"
      class="user_class"
      style="color: red"
      data-uname="朱老师"
      data-my-email="zhu@qq.com"
    >
      用户信息
    </div>

    <script>
      const div = document.querySelector("div");

      // 1. 访问内置属性: obj.prop
      console.log(div.id);
      // class是关键字, className -> class
      console.log(div.className);
      // 返回CSSStyleDeclaration对象
      console.log(div.style);
      console.log(div.style.color);

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

      // 2. 自定义属性: dataset.prop
      //  data-uname="朱老师"
      // data-: 省略
      console.log(div.dataset["uname"]);
      // 合法属性可以直接用点语法访问
      console.log(div.dataset.uname);
      //   data-my-email="zhu@qq.com"
      // 蛇形 -> 小驼峰, user_name  -> userName
      console.log(div.dataset["my-email"]);
      console.log(div.dataset["myEmail"]);
      console.log(div.dataset.myEmail);
    </script>
  </body>
</html>

getComputedStyle对象

  • 计算样式: 元素上的全样样式,包括行内,文档,外部等
  • 只读

style1.css代码:

h2 {
  background-color: yellow;
}

完整示例代码:

<!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>getComputedStyle: 计算样式</title>
    <link rel="stylesheet" href="style1.css" />
    <!-- 文档样式 -->
    <style>
      h2 {
        border: 1px solid #000;
        width: 200px;
        height: 50px;
      }
    </style>
  </head>
  <body>
    <!-- 行内样式 -->
    <h2 style="color: red">Hello world</h2>

    <script>
      const h2 = document.querySelector("h2");

      // 行内样式style=""
      console.log(h2.style.color);

      // 文档样式<style>
      console.log(h2.style.width);
      console.log(h2.style.height);
      console.log(h2.style.backgroundColor);

      // 一个元素最终样式,由行内,文档,外部css共同作用的结果

      //   全局函数, 计算属性,可以获取元素上的任何css属性
      // console.log(window.getComputedStyle(h2));
      console.log(window.getComputedStyle(h2).width);
      console.log(window.getComputedStyle(h2).height);
      console.log(typeof window.getComputedStyle(h2).height);
      console.log(window.getComputedStyle(h2).backgroundColor);

      // 宽度
      let width = window.getComputedStyle(h2).width;
      console.log(typeof width);
      // 转换为整数
      console.log(typeof parseInt(width), parseInt(width));
      // 根据 任何数乘以1 都不变,但是一个数字字符串乘以1, 会发生类型的自动转换
      console.log(typeof ("123" * 1), "123" * 1);

      width = parseInt(width);
      h2.style.width = "300px";
      h2.style.width = width + 100 + "px";
    </script>
  </body>
</html>

classList

这是专用于操作元素的class属性

  1. 添加: classList.add()
  2. 判断: classList.contains()
  3. 替换: classList.replace()
  4. 移除: classList.remove()
  5. 切换: classList.toggle()
<!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>classList: class属性</title>
    <style>
      .red {
        color: red;
      }
      .blue {
        color: blue;
      }
      .bgc {
        background-color: yellow;
      }
    </style>
  </head>
  <body>
    <h2 class="red">Hello world</h2>
    <script>
      const h2 = document.querySelector("h2");
      // 1. 传统: className
      // 添加 <h2 class="red">...</h2>
      h2.className = "red";
      h2.className = "red bgc";
      // 删除
      h2.className = "";

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

      // 2. classList
      // 添加
      h2.classList.add("red");
      h2.classList.add("bgc");
      h2.classList.add("red", "bgc");
      /// 替换
      h2.classList.replace("red", "blue");
      // 删除
      h2.classList.remove("bgc", "blue");
      // 切换: 自动在 add() , remove() 之间自动切换
      h2.classList.toggle("red");
    </script>
  </body>
</html>

事件基础

  • 事件 3 要素
  1. 事件名称: 字符串, click, keydown, scroll
  2. 事件主体: 元素, <button>,<div>,<form>...
  3. 事件方法: 函数, function(ev){}, ()=>{}
  • 事件增删
  1. 事件添加: 事件属性, addEventListener()
  2. 事件删除: null,removeEventListener()
  3. 事件派发:dispatchEvent()
  • 定时器
  1. 一次性: setTimeout()
  2. 间歇式: setInterval()
<!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>
    <!-- 1. 标签属性: 事件属性 tag.on+事件名称 -->
    <button onclick="alert('登陆成功')">登录</button>

    <!-- =================================== -->

    <!-- 2. html对象属性: obj.onclick  -->
    <button class="save">保存</button>
    <script>
      let saveBtn = document.querySelector(".save");
      saveBtn.onclick = function () {
        alert("保存成功");
      };
      saveBtn.onclick = function () {
        alert("保存失败");
      };

      // 删除
      saveBtn.onclick = null;

      //   onclick: 不能重复添加同名事件
    </script>

    <!-- =================================== -->

    <!-- 3. 事件监听器: obj.addEventListener() -->
    <button class="submit">提交</button>
    <script>
      let submitBtn = document.querySelector(".submit");
      // submitBtn.addEventListener(事件名称,事件方法,是否冒泡false)
      submitBtn.addEventListener("click", function () {
        alert("提交成功");
      });

      // 简写
      submitBtn.addEventListener("click", () => alert("提交成功"));

      submitBtn.addEventListener("click", function () {
        alert("提交失败");
      });

      //   submitBtn.removeEventListener('click', 不能用匿名函数)

      // addEventListener():注册的同名事件方法会依次触发
    </script>

    <!-- =================================== -->

    <!-- 4. (自定义)事件派发 -->
    <button class="ads">广告位</button>
    <script>
      const adsBtn = document.querySelector(".ads");

      let money = 0;

      // 点击一次赚10元
      adsBtn.addEventListener("click", function () {
        console.log((money += 10) + " 元");
      });

      // 定时器: 自动累加赚钱
      /**
       * 定时器
       * 1. 一次性: setTimeout(function(){}, time)
       * 2. 间歇式: setInterval(function(){}, time)
       */

      setTimeout(function () {
        console.log("大家晚上好");
      }, 2000);

      setInterval(function () {
        console.log("大家晚上好");
      }, 2000);

      setInterval(function () {
        console.log((money += 10) + " 元");
      }, 1000);

      // 创建一个机器人, 帮我去点击
      // 机器人 -> 自定义事件去模拟它

      const myClick = new Event("click");

      setInterval(function () {
        adsBtn.dispatchEvent(myClick);
      }, 1000);

      // 赚够250元, 就停止

      let timer = setInterval(function () {
        adsBtn.dispatchEvent(myClick);
        // 如果money >= 250 ,就不要点击
        if (money >= 250) {
          // 清除定时器
          // 如果是setTimeout(),用 clearTimeout()来清除
          // 如果是setInterval(), 用clearInterval()
          clearInterval(timer);
          console.warn(`今天已经赚了${money}元,明天再来吧`);
        }
      }, 1000);
    </script>
  </body>
</html>

事件冒泡

  1. 事件由内向外,逐级向上传递,直到根元素
  2. 事件冒泡可以被禁用:ev.stopPropagation()
<!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 {
        border: 1px solid;
        padding: 15px;
      }
    </style>
  </head>
  <body>
    <div class="box box1">
      box1
      <div class="box box2">
        box2
        <div class="box box3">box3</div>
      </div>
    </div>

    <script>
      const boxes = document.querySelectorAll(".box");
      // 为每个div添加一个点击事件
      boxes.forEach(function (box) {
        box.addEventListener(
          "click",
          function () {
            console.log(this.className.replace("box", ""));

            // 取消冒泡
            // 事件对象 event, 总是可用
            console.log(event.stopPropagation());
          },
          false
        );
      });

      /**
       * 冒泡前提
       * 1. 元素之间存在层次关系
       * 2. 祖先元素也定义与之同名的事件
       */

      /**
       * addEventListener(event,callback,是否冒泡false)
       * 1. false: 默认值,冒泡(从内向外)
       * 2. true: 捕获 ( 从外向内 )
       */
    </script>
  </body>
</html>

事件代理

  1. 利用事件冒泡,将子元素事件委托到父级上触发
  2. 事件代理机制,可以极大的简化事件添加操作
  3. 事件绑定主体ev.currentTarge
  4. 事件触发主体: ev.target
<!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>
    <ul class="list1">
      <li class="item">电脑</li>
      <li class="item">手机</li>
      <li class="item">外套</li>
    </ul>
    <hr />
    <ul class="list2">
      <li class="item">西瓜</li>
      <li class="item">苹果</li>
      <li class="item">草莓</li>
    </ul>

    <script>
      // 任务: 点击任何一个<li>,显示内容

      // 1. 传统
      const items = document.querySelectorAll(".list1 > .item");
      items.forEach((item) => {
        item.onclick = function (ev) {
          // ev: 事件对象
          console.log(ev);

          // 事件绑定者: ev.currentTarget
          console.log(ev.currentTarget);

          // 事件触发者: ev.target
          console.log(ev.target);

          // this
          console.log(this);

          console.log(this === ev.target);
          console.log(ev.currentTarget === ev.target);

          // 因为三者完全相同所以用哪个都可以获取内容
          console.log(ev.currentTarget.textContent);
        };
      });

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

      //   事件冒泡: 子元素上的同名事件,会自动冒泡到父元素上面去触发

      // 2. 事件代理

      const list = document.querySelector(".list2");

      list.onclick = function (ev) {
        // 事件绑定者: ev.currentTarget, 父元素
        console.log(ev.currentTarget);

        // 事件触发者: ev.target, 子元素
        console.log(ev.target);

        // this
        console.log(this);
        console.log(this === ev.currentTarget);
        console.log(this === ev.target);

        // 需要用时间触发者来获取元素文本
        console.log(ev.target.textContent);
      };
    </script>
  </body>
</html>

实战计算器

index.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="style.css" />
  </head>
  <body>
    <!-- 事件代理, eval() -->

    <div class="calculator">
      <!-- 结果区 -->
      <input type="text" class="result" value="0" readonly />
      <!-- 按键区 -->
      <div class="btns" onclick="calculator(event)">
        <button>F1</button>
        <button>F2</button>
        <button>CE</button>
        <button>AC</button>
        <button>7</button>
        <button>8</button>
        <button>9</button>
        <button>/</button>
        <button>4</button>
        <button>5</button>
        <button>6</button>
        <button>X</button>
        <button>1</button>
        <button>2</button>
        <button>3</button>
        <button>-</button>
        <button>0</button>
        <button>.</button>
        <button>=</button>
        <button>+</button>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

style.css:

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
body {
  background-color: #eee;
}

.calculator {
  min-width: 360px;
  max-width: 460px;
  border-radius: 8px;
  background-color: #666;
  padding: 15px;
  display: grid;
  grid-template-rows: 60px 1fr;
  gap: 20px;
  margin: 40px auto;
}
.calculator .result {
  font-size: 32px;
  font-weight: bolder;
  text-align: right;
  padding: 6px;
  border: none;
  outline: none;
  border-radius: 8px;
  background-color: #cecece;
  box-shadow: 2px 2px 2px #333 inset;
}
.calculator .btns {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 50px;
  gap: 15px;
}
.calculator .btns > * {
  font-size: 32px;
  font-weight: bolder;
  border-radius: 8px;
  color: #888;
  border: none;
  outline: none;
  background-color: #444;
  box-shadow: 2px 2px 2px #000;
}

.calculator .btns *:nth-child(-n + 4) {
  background-color: #222;
}
.calculator .btns *:nth-child(-n + 4):hover,
.calculator .btns > *:hover {
  cursor: pointer;
  opacity: 0.7;
  color: white;
  transition: 0.3s;
}

script.js:

// 计算函数
function calculator(ev) {
  // 显示结果区
  const result = document.querySelector('.result')

  // 当前按钮(事件代理)
  if (ev.target.tagName !== 'BUTTON') return false

  // 当前按钮
  const curBtn = ev.target

  // 按钮内容
  let content = curBtn.textContent

  //   eval('字符串表达式'): 将一个字符串表达式进行计算并返回结果

  // 将内容显示到结果区
  // 根据用户点击的按钮内容确定要执行的操作
  switch (content) {
    // AC 清零
    case 'AC':
      result.value = 0
      break

    // CE: 退格
    case 'CE':
      // 如果结果区有内容
      if (result.value.length == 1 || result.value == '错误') {
        result.value = 0
      } else {
        // 删除最后一个字符
        // slice(startIndex,endIndex),结果中不包含结束索引的值
        result.value = result.value.slice(0, -1)
      }
      break

    // F1
    case 'F1':
      break
    // F2
    case 'F2':
      break

    // = : 计算结果
    case '=':
      // 缓存结果
      let tmpResult = 0

      try {
        //   如果是乘法,将 "X" 换成 "*"
        result.value = result.value.replace('X', '*')
        // 计算字符串表达式
        tmpResult = eval(result.value)

        // 如果结果是小数,仅保留5位就可以了
        if (tmpResult.toString().includes('.')) {
          tmpResult = tmpResult.toFixed(5)
          // 如果小数部分出现了多余的0,应该去掉(对结果精度没影响)
          tmpResult = parseFloat(tmpResult)
        }

        // 显示出结果
        result.value = tmpResult
      } catch {
        result.value = '错误'
      }

      break

    default:
      // 如果当前结果区显示有前导0,先清空, 防止出现前导0
      if (result.value == 0 || result.value == '错误') result.value = ''
      result.value += content
  }
}

/**
 * 知识点总结
 * 1. 事件代理
 * 2. eval()
 * 3. try-catch
 * 4. toFixed()
 */

/**
 * 作业
 * 1. 处理除零错误
 * 2. 处理操作符位于表达式首位的错误
 */

完整代码附件:

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

评论已关闭!