PHP 学习之路:第十二天—— js 事件与传递机制

166 阅读2分钟

一、事件添加和删除

<!-- 1. 添加到元素事件属性上 -->
<button class="btn" onclick="console.log(this.textContent);">Button1</button>
<button>Button2</button>
<button>Button3</button>
<button>Button4</button>

<script>
  const btn2 = document.querySelector("button:nth-of-type(2)");
  // 2. 使用js脚本动态添加事件属性的方式
  btn2.onclick = function () {
    console.log(this.textContent);
  };
  btn2.onclick = function () {
    this.style.color = "red";
    // 控制台没有输出,说明第一个点击事件没有执行
  };
  // onclick这样的事件属性不允许重复定义同一个事件到同一个元素上
  // 通过移除事件属性的方式来删除事件
  btn2.onclick = null;

  // 3. 事件监听器/注册器
  const btn3 = document.querySelector("button:nth-of-type(3)");
  // btn3.addEventListener(事件类型,事件回调(异步回调));
  // click事件名称,不能有on,带on的叫事件属性
  btn3.addEventListener("click", (ev) => console.log(ev.target.textContent, "第1次"));
  btn3.addEventListener("click", (ev) => console.log(ev.target.textContent, "第2次"));

  // 4. 移除事件监听器
  // 通过回调函数添加的事件方法的事件是不能被移除的
  const handle = () => console.log(btn3.textContent, "第3次");
  btn3.addEventListener("click", handle);
  btn3.removeEventListener("click", handle);

  // 5. 事件派发
  const btn4 = document.querySelector("button:last-of-type");
  // 将一个自定义的事件派发给指定的元素

  let i = 0;
  btn4.addEventListener("click", function () {
    console.log("点击了广告,你已赚了: " + i + "元");
    i += 0.5;
  });

  const ev = new Event("click");
  // 每派发一次click,就会自动点击了这个按钮
  // btn4.dispatchEvent(ev);
  // setInterval("btn4.dispatchEvent(ev)", 1000);
  setInterval(() => btn4.dispatchEvent(ev), 1000);
</script>

二、事件传递

  1. 捕获: 从最外层元素逐级向内直到事件的绑定者
  2. 目标: 到达事件目标
  3. 冒泡: 从目标再由内向外逐级向上直到最外层元素

1.事件捕获

 <ul>
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
  <li>item4</li>
  <li>item5</li>
</ul>
<script>
  const items = document.querySelectorAll("li");

  items.forEach(function (item) {
    item.onclick = function (ev) {
      // ev: 事件对象,保存了当前事件的所有细节
      // console.log(ev.type);
      // console.log(ev.target.textContent);
      // ev.target: 事件的触发者
      // console.log(ev.target);
      // ev.currentTarget: 事件的绑定者
      console.log(ev.currentTarget);
      // 当前ev.target === ev.currentTarget
      // console.log(ev.target === ev.currentTarget);
      // console.log(ev.target === this);
    };
  });

  // 1.捕获, window, document,html.....
  // 第三个参数是事件的传递机制: true, 捕获阶段, false, 冒泡阶段(默认值)
  window.addEventListener("click", ev => console.log(ev.currentTarget), true);
  // document
  document.addEventListener("click", ev => console.log(ev.currentTarget), true);
  // html
  document.documentElement.addEventListener("click", ev => console.log(ev.currentTarget), true);
  // body
  document.body.addEventListener("click", ev => console.log(ev.currentTarget), true);
  // ul
  document.querySelector("ul").addEventListener("click", ev => console.log(ev.currentTarget), true);
</script>

2.事件冒泡

<ul>
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
  <li>item4</li>
  <li>item5</li>
</ul>
<script>
  const items = document.querySelectorAll("li");

  items.forEach(function (item) {
    item.onclick = function (ev) {
      // ev: 事件对象,保存了当前事件的所有细节
      // console.log(ev.type);
      // console.log(ev.target.textContent);
      // ev.target: 事件的触发者
      // console.log(ev.target);
      // ev.currentTarget: 事件的绑定者
      console.log(ev.currentTarget);
      // 当前ev.target === ev.currentTarget
      // console.log(ev.target === ev.currentTarget);
      // console.log(ev.target === this);
    };
  });

  // 2.冒泡阶段,将第三个参数设置为false
  window.addEventListener("click", ev => console.log(ev.currentTarget), false);
  //   document
  document.addEventListener("click", ev => console.log(ev.currentTarget), false);
  //   html
  document.documentElement.addEventListener("click", ev => console.log(ev.currentTarget));
  //   body
  document.body.addEventListener("click", ev => console.log(ev.currentTarget));
  // ul
  document.querySelector("ul").addEventListener("click", ev => console.log(ev.currentTarget));
</script>

三、事件冒泡的应用:事件委托

<ul>
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
  <li>item4</li>
  <li>item5</li>
</ul>
<script>
  // 给所有的li添加点击事件
  // 第一步先获取所有的li
  // const items = document.querySelectorAll("li");
  // 循环的给所有li添加点击事件
  // items.forEach((items) => (items.onclick = (ev) => console.log(ev.currentTarget)));

  // 根据事件冒泡的原理,子元素上的同名事件会自动传递到父元素上的同名事件
  // 如果将事件直接绑定到父元素上,那么在子元素上点击时,会自动触发父元素上的同名事件

  // 事件的绑定者与事件的触发者不相同
  // 事件绑定者: 父元素 ul
  // 事件触发者: 子元素 li

  document.querySelector("ul").onclick = (ev) => {
    // 事件绑定者: 父元素 ul
    console.log(ev.currentTarget);
    // 事件触发者: 子元素 li
    console.log(ev.target);
  };
</script>

四、表单事件

<style>
  body {
    background-color: mediumturquoise;
    color: #444;
    font-weight: lighter;
  }
  #login {
    width: 20em;
    border-radius: 0.3em;
    box-shadow: 0 0 1em #888;
    box-sizing: border-box;
    padding: 1em 2em 1em;
    margin: 2em auto;
    background-color: paleturquoise;
    display: grid;
    grid-template-columns: 3em 1fr;
    gap: 1em 0;
  }
  #login .title {
    grid-area: auto / span 2;
    place-self: center;
  }
  #login input {
    border-radius: 0.3em;
    border: none;
    padding-left: 0.3em;
  }
  #login input:focus {
    outline: none;
    box-shadow: 0 0 5px seagreen;
    border-radius: 0.2em;
    transition: 0.5s;
  }
  #login button {
    grid-area: auto / 2 / auto;
    outline: none;
    background-color: lightseagreen;
    border: none;
    height: 2em;
    color: #fff;
  }
  #login button:hover {
    cursor: pointer;
    background-color: seagreen;
    transition: 0.5s;
  }
</style>
<!-- 表单 -->
<form action="" method="POST" id="login">
  <label class="title">用户登录</label>
  <label for="email">邮箱:</label>
  <input type="email" id="email" name="email" value="" autofocus />
  <label for="password">密码:</label>
  <input type="password" id="password" name="password" />
  <!-- <button name="submit" type="button">登录</button> -->
  <button name="submit">登录</button>
</form>

<script>
  // 获取表单
  const login = document.forms.login;

  // login.onsubmit = () => alert("提交了");
  // ev.preventDefault();专用于禁用元素的默认行为,例如跳转,提交
  // login.onsubmit = (ev) => ev.preventDefault();

  // console.log(login.submit);
  login.submit.onclick = (ev) => {
    // 禁止默认行为
    ev.preventDefault();
    // 禁止冒泡
    ev.stopPropagation();
    // console.log(ev.currentTarget);
    // form属性返回控件所属的表单对象
    // console.log(ev.currentTarget.form);
    // 非空验证
    isEmpty(ev.currentTarget.form);
  };

  function isEmpty(form) {
    console.log(form.email.value.length);
    console.log(form.password.value.length);

    if (form.email.value.length === 0) {
      alert("邮箱不能为空");
      // 将输入焦点设置到邮箱的输入框中
      form.email.focus();
      return false;
    } else if (form.password.value.length === 0) {
      alert("密码不能为空");
      // 将输入焦点设置到密码的输入框中
      form.email.focus();
      return false;
    } else {
      alert("验证通过");
    }
  }
</script>

与表单相关的事件:

  • focus: 获取焦点事件
  • blur: 失去焦点事件
  • input: 只要值发生变化时连续触发,不等失去焦点
  • change: 值发生了改变且失去焦了点时触发,
  • select: 选中文本时触发,
  • invalid: 提交时表单元素值不满足验证条件时触发
  • reset: 将表单值全部重置到默认值(并非清空)
  • submit: 提交表单时触发,注意触发对象是,提交的是表单不是按钮
  • keydown: 按下键盘时
  • keyup:松开键盘时
  • keypress: 按过了键盘时, 按下有值键时(除Ctrl/Alt/Shift/Meta),先触发keydown
  • 按下一直不放手的触发顺序: keydown,keypress,重复这二个事件,直到keyup
  • load,error: 在后面xhr时细说

五、实战:留言板/评论

image.png

html 文件:

<!DOCTYPE html>
<html lang="en">
<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>
  <form class="comment">
    <label for="content">请留言:</label>
    <!-- maxlength="100" 设置文本可输入的最大长度 -->
    <textarea name="content" id="content" placeholder="不要超过100个字符" maxlength="100" cols="30" rows="5""></textarea>
    <span class="notice">还可以输入100字</span>
    <button class="submit" type="button" name="submit">提交</button>
  </form>
  <ul class="list"></ul>
  <script>
    // 获取元素
    const comment = document.querySelector(".comment");
    const content = comment.content;
    const submitBtn = comment.submit;
    const commentList = document.querySelector(".list");
    const notice = document.querySelector(".notice");
    
    // 提交按钮的点击事件
    submitBtn.onclick = (ev) => {
      // 获取用户的留言内容
      let value = content.value;
      // 创建留言元素样式
      const newComment = document.createElement("li");
      newComment.textContent = value;
      newComment.style.borderBottom = "1px solid white";
      newComment.style.minHeight = "3em";

      // 添加删除留言的功能,为每条留言添加删除按钮
      const deleteBtn = document.createElement("button");
      deleteBtn.textContent = "删除";
      deleteBtn.style.float = "right";
      deleteBtn.classList.add("del-btn");
      // 用户点击删除留言后的操作
      deleteBtn.onclick = function(){
        if(confirm('是否删除?')){
          // 确定:true,取消: false 
          this.parentNode.remove();
          alert("删除成功!")
          content.focus();
          return false;
        }  
      }

      // 将留言控制在 0~100 字
      if(value.length > 0 && value.length <= 100){
        // 将删除按钮追加到新留言的后面
        newComment.append(deleteBtn);
        // 将留言追加到页面中显示
        commentList.prepend(newComment);
        alert('留言成功');
        content.value=null;
         notice.textContent = `还可以输入100字`;
        content.focus();
      }else{
        alert("没有内容或内容超出规定长度");
        content.focus();
        return false;
      } 
    }

    // oninput 监控输入, 提示信息
    content.oninput=function(){
      let textLength = content.value.trim().length;
      notice.textContent = `还可以输入${100 - textLength}字`;
    }
  </script>
</body>
</html>

css 文件:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
li {
  list-style: none;
}
body {
  background-color: rgb(174, 236, 236);
  color: #555;
}
.comment {
  width: 85%;
  margin: 1em auto;
  display: grid;
  gap: 0.5em;
}
.comment #content {
  resize: none;
  border: none;
  padding: 0.5em;
  outline: none;
}
.comment #content:focus,
.comment #content:hover {
  box-shadow: 0 0 8px steelblue;
  transition: 0.6s;
}
.comment .submit {
  width: 30%;
  margin-left: auto;
  background-color: lightseagreen;
  border: none;
  outline: none;
  color: white;
  height: 2.5em;
}
.comment .submit:hover {
  background-color: seagreen;
  transition: 0.6s;
  cursor: pointer;
}
.list {
  width: 80%;
  /* background-color: yellow; */
  margin: auto;
  padding: 1em;
}

.del-btn {
  background-color: wheat;
  color: #000;
  padding: 0.5em 1em;
  /* height: 2.2em; */
  border: none;
  outline: none;
}
.del-btn:hover {
  cursor: pointer;
  background-color: rgb(31, 230, 163);
}

六、字符串方法

1.concat()

// 1. concat()拼装
let str = "html".concat("css", "js", "php", "!", 888);
console.log(str);

2.slice(start, end)

// 2. slice(start, end)取子串
str = "hello php.cn";
let res = str.slice(0, 5);
console.log(res);
res = str.slice(0);
console.log(res);
res = str.slice(6);
console.log(res); // php.cn
//   -1是反向的第一个字符, n
res = str.slice(-6);
console.log(res); // php.cn
res = str.slice(-2);
console.log(res); // cn

3.substr(start, size)

// 3. substr(start, size): 取子串
res = str.substr(0, 5);
console.log(res); // hello
res = str.substr(-6, 3);
console.log(res); // php

4.trim()

// 4. trim(): 删除字符串二边的空白字符
let psw = "    root888   ";
console.log(psw.length);
console.log(psw.trim().length);

5. split()

// 5. 将字符串打散成数组
res = str.split("");
console.log(res);

// 从一个邮箱中解析出用户名和邮箱地址
let email = "admin@php.cn";
res = email.split("@");
console.log(res);
console.log("userName = ", res[0]);
console.log("emailAddress = ", res[1]);

七、数组常用方法

1.栈方法

// 1.栈方法
// 栈: 只允许在数组一端进行增删的操作
let arr = [];
//   push():尾部添加,入栈
console.log(arr.push(1, 2, 3));
console.log(arr);
console.log(arr.push(4));
console.log(arr);
// pop():尾部删除:出栈
console.log(arr.pop());
console.log(arr);
console.log(arr.pop());
console.log(arr);

// 还有一对是头部进行元素的增删
// unsift(): 在头部添加
// shift(): 从头部删除
arr = [];
arr.unshift("a", "b", "c");
console.log(arr);
arr.unshift("d");
console.log(arr);
console.log(arr.shift());
console.log(arr);
console.log(arr.shift());
console.log(arr);

2.join()

// 2. join(): 与字符串 split()相反,将数组转为字符串
arr = ["电脑", "西瓜", "外套"];
// 默认转为逗号连接的字符串
let res = arr.join();
let res = arr.join("</li><li>");
console.log(`<li>${res}</li>`);

3.concat()

// 3. concat()
console.log("hello".concat("php.cn"));
console.log(["hello"].concat(["php.cn"]));

4.slice()

// 4. slice(): 取部分成员
arr = [1, 2, 3, 4, 5];
res = arr.slice(0, 3);
res = arr.slice(-2);
res = arr.slice(-3);
console.log(res);

5.splice()

// 5. splice(): 增删改
arr = [1, 2, 3, 4, 5];
// 删除,1个或2个参数
res = arr.splice(2, 2);
console.log(res);
console.log(arr);

// 新增
arr = [1, 2, 3, 4, 5];
// 不删除元素,将第2个参数设为0,但是要传入第三个参数
res = arr.splice(1, 0, 88, 99);
res = arr.splice(1, 0, ...[88, 99, 66]);
console.log(arr);
// 更新

// 更新元素,将第2个参数设为删除数量,但是要传入第三个参数,用来替换掉被删除的元素
arr = [1, 2, 3, 4, 5];
res = arr.splice(1, 3, ...[88, 99, 66]);
console.log(arr);

6.forEach()

// forEach功能一样,但有一个返回值 map()

7.filter()

// 7. filter()对每个成员应用回调函数进行处理返回true的成员组成的数组
arr = [1, 2, 3, 4, 5];
//   res = arr.filter((item) => item % 2);
res = arr.filter(item => !(item % 2));
console.log(res);

8.reduce()

// 8. reduce():归内操作,将多成员进行统计后,单值返回
res = [1, 2, 3, 4, 5];
//   res = arr.reduce(function (prev, curr) {
//     return (prev += curr);
//   });

// 使用箭头函数,简化写法
res = arr.reduce((prev, curr) => (prev += curr), 20);
console.log(res);