# 前端技术

前言

工欲善其事,必先利其器

# 深拷贝的另一种选择

简单易用的深拷贝方法:

const clone = JSON.parse(JSON.stringify(obj));

另外一种原生方法:

const clone = structuredClone(obj);

# 省市区数据

已汇总到我的 Gitee 代码仓库 area (opens new window)中的 dist 文件夹

# replace 高级用法

let str = "aaabbbcccddd";

// match是当前匹配的子串,
// $1是指第1个括号匹配的字符串,
// 以此类推,参数还有$2,$3...,如果正则表达式里有多个小括号
let newStr = str.replace(/(\w{3})/g, (match, $1) => {
  return $1 + "-";
});

console.log(newStr);

# 前端下载文件流

this.$axios.get("downloadUrl").then((res) => {
  // 在内存中创建一个下载链接
  let url = window.URL.createObjectURL(new Blob([res]));
  let link = document.createElement("a");
  link.style.display = "none";
  link.href = url;
  link.setAttribute("download", "文件名.xlsx");
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  window.URL.revokeObjectURL(url);
});

# 自己关闭自己

// 这个方法只能关闭由 JavaScript 打开的窗口。
// 如果当前页面是由用户手动打开的或者是浏览器的默认首页,那么这个方法将无效。
window.close();

// 以下方法不好使!不用再试了!
// window.open("about:blank", "_self").close();

# 自己删除自己

let element = document.getElementById("myElement");
// 方法一:通过父节点删除自身
element.parentNode.removeChild(element);
// 方式二:直接删除
element.remove();

# 前端 base64 转换

base64 的转化是双向的,md5 是单向的

let base64Str = window.btoa("good luck"); // Z29vZCBsdWNr
let originStr = window.atob("Z29vZCBsdWNr"); // good luck

// 兼容中文的base64转换
let base64Str = window.btoa(unescape(encodeURIComponent("你好")));
let originStr = decodeURIComponent(escape(window.atob("5L2g5aW9")));

# 复制内容的两种方法

WARNING

clipboard 仅能在 https 协议或 localhost 下使用!

// 推荐方式
navigator.clipboard.writeText("Hello, Friday").then(() => alert("复制成功"));

// 传统方式
function copyText(text) {
  const el = document.createElement("input");
  el.value = text;
  document.body.appendChild(el);
  el.select();
  document.execCommand("copy");
  document.body.removeChild(el);
  alert("复制成功");
}

# 将文件变成内存地址,实现本地预览

  • createObjectURL 返回一段带 hash 的 url,并且一直存储在内存中,直到 document 触发了 unload 事件(例如:document close)或者执行 revokeObjectURL 来释放。
  • FileReader.readAsDataURL 则返回包含很多字符的 base64,并会比 blob url 消耗更多内存,但是在不用的时候会自动从内存中清除(通过垃圾回收机制)
  • 使用 createObjectURL 可以节省性能并更快速,只不过需要在不使用的情况下手动释放内存
  • 如果不太在意设备性能问题,并想获取图片的 base64,则推荐使用 FileReader.readAsDataURL
// 首先通过饿了么upload组件,取消自动上传,上传文件,change方法里拿到file对象

// 通过URL.createObjectURL(blob)可以获取当前文件的一个内存URL,并且是同步的
let _url = URL.createObjectURL(file.raw);
image.url = _url;

# object 标签的父子窗口通信

借助postMessage()方法,我们可以实现 object 或 iframe 与父级窗口之间的通信。

<!-- 父级窗口中使用<object>标签引入子级窗口 -->
<body>
  <!-- 子级窗口 -->
  <object
    data="http://127.0.0.1:5501/index.html"
    width="100%"
    height="100%"
    id="childWin"
  ></object>
  <script>
    // 监听子窗口传递过来的数据
    window.addEventListener("message", (e) => {
      console.log("I am from Children: ", e.data);
    });
    // 向子窗口发送数据
    setInterval(() => {
      // 获取到子窗口的window对象
      document
        .querySelector("#childWin")
        .contentWindow.postMessage("Hello from Father", "*");
    }, 3000);
  </script>
</body>

注意

window.parent 获取上一级的窗口
window.top 获取最顶级的窗口

<!-- 子级窗口 -->
<body>
  <div>child page</div>
  <script>
    // 在子窗口中监听从父级中传过来的数据
    window.addEventListener("message", (e) => {
      console.log(e.data);
    });
    // 向父级窗口中发送数据
    setInterval(() => {
      // 通过window.parent获得父级窗口的window对象
      window.parent.postMessage("Hi from Children", "*");
    }, 3000);
  </script>
</body>

# 遥控隔壁标签页刷新

在页面 A 中,打开页面 B,借助postMessage()控制页面 B 刷新

// 页面A
let opener = window.open(pageBUrl);
opener.postMessage("refreshPage", "*");

// 页面B,在打开的瞬间监听message事件
window.addEventListener("message", (e) => {
  if (e.data === "refreshPage") {
    location.reload();
  }
});

# 正则匹配 script 标签

  • .匹配除换行符以外的字符
  • (.|\n)*表示匹配任意数量的任意字符
  • 量词*后面的?,表示非贪婪匹配
  • [^>]表示除>意外的任意字符
`<div id="myPage">
  {{ message }}
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<script>
  var app = new Vue({
    el: '#myPage',
    data: {
      message: 'Hello Free Page!'
    }
  })
</script>`.match(/<script[^>]*>(.|\n)*?<\/script>/g);

# 数组随机排序

const randomArr = (arr) => arr.sort(() => Math.random() - 0.5);

# 生成随机颜色

const getRandomColor = () =>
  `#${Math.floor(Math.random() * 0xffffff).toString(16)}`;

# 滚动到顶部/底部

// 整个页面滚动到顶部
document.body.scrollIntoView({ behavior: "smooth", block: "start" });
// 整个页面滚动到底部
document.body.scrollIntoView({ behavior: "smooth", block: "end" });

# 获取 url 中的参数

// 在地址栏里拿到 '?someKey=someValue'
let queryStr = new URL(location.href).search;
let params = new URLSearchParams(queryStr);
// 获取其中一个参数
params.get("someKey");
// 显示键/值对
for (var pair of params.entries()) {
  console.log(pair[0] + ", " + pair[1]);
}

# 使用原生 fetch 请求数据

借助fetch,我们可以摆脱对axios的依赖

fetch("http://example.com/movies.json")
  .then((response) => response.json())
  .then((data) => console.log(data));

一个更全面的示例:

// fetch(url,options) 返回promise对象
fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
    },
    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  }).then((response) => response.json());
}

# DOM 节点转数组




 



// 获取节点集合
let domCollection = document.getElementByTagName("script");
// 将集合转为数组
let domArr = Array.from(domCollection);
// 这样就可以遍历这些节点了
domArr.forEach((node) => console.log(node.src));

# 前端换肤的方案探索

关于前端换肤,有两种需求场景:

  1. 只可以切换固定几套主题色
  2. 用户可以随便选择色值,更改主题色

可以参考掘金上的这篇文章:前端主题切换方案 (opens new window)

针对第二种场景,自由换肤,可以参考我的个人开源项目:theme-starter (opens new window) 的 README

# JS 拦截 Ctrl+S 事件

//js监听键盘ctrl+s快捷键保存
document.addEventListener("keydown", (e) => {
  // 判断:S键被按下,并且之前也按下了Ctrl键
  if (
    e.keyCode == 83 &&
    (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)
  ) {
    e.preventDefault();
    alert("saved");
  }
});

# 循环中调用异步函数

  1. 需要使用朴实无华的 for 循环和 async 新秀
function func(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(i);
      resolve(i);
    }, 2000);
  });
}

// for循环放到async函数内
async function circle() {
  for (let index = 0; index < 3; index++) {
    await func(index);
  }
}

circle();

# sleep 函数的两种实现方式

  1. 页面可以正常操作,渲染,不会卡死!
// 异步版,返回一个promise,在指定时间后resolve
const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));

// 使用示例:需要在async函数中使用
async function myFunc() {
  console.log(11);
  await sleep(4000);
  console.log(22);
}
myFunc();
  1. 注意:页面直接卡死!!!
// 同步版,借助while循环实现线程阻塞
const sleep = (time) => {
  let start = new Date();
  while (new Date() - start < time) {
    continue;
  }
};
// 使用示例:
function testSleep() {
  console.log(11);
  sleep(4000);
  console.log(22);
}

testSleep();

# 生成随机字符串

Date.now().toString(36);

# 获取数组最后一个值

// IE浏览器和安卓的Opera不支持!但是Edge浏览器支持
array.at(-1);

// 传统方法
array.slice(-1)[0];

# reduce 方法使用示例

// array.reduce(累加函数,初始值)
[1, 2, 3].reduce((pre, cur) => pre + cur, 0); // 6

// 1个元素
[1].reduce((pre, cur) => pre + cur, 0); // 1

// 0个元素时,必须传入初始值
[].reduce((pre, cur) => pre + cur, 0); // 0

// 复杂示例:pre不加.val,cur加
[
  { name: "a", val: 3 },
  { name: "b", val: 4 },
].reduce((pre, cur) => pre + cur.val, 0); // 7

# 控制台获取上次执行结果

1 + 1;
// 2

$_ * 10;
// 20

# 移动端调试

  1. 手机与电脑连接同一个 wifi,表示在同一局域网下
  2. 通过 ipconfig 查看电脑本机 ipv4 地址 (xxx.xxx.xx.xxx)
  3. 设置开发环境的 host 为 4 个 0,表示访问你电脑上的任何一个 Ip,都能访问的到你的前端服务
  4. 在手机上访问 xxx.xxx.xx.xxx:port,直接访问正在开发的页面
devServer: {
  host: "0.0.0.0"; // 表示前端服务在所有本机IP上监听
}

# 让你的网页飘花瓣

<!-- 直接引入 -->
<script src="https://cdn.jsdelivr.net/gh/yremp/yremp-js@1.5/sakura.js"></script>

<!-- 动态引入 -->
<script>
  let _js = document.createElement("script");
  _js.src = "https://cdn.jsdelivr.net/gh/yremp/yremp-js@1.5/sakura.js";
  document.head.appendChild(_js);
</script>

# Npm 不常用操作

# 批量升级项目依赖包

# 首先,全局安装 npm-check-updates
npm install -g npm-check-updates

# 检查所有依赖的版本
ncu

# 一键更新所有依赖
ncu -u

# 单独升级vue
ncu

# 清空 npm 缓存

npm cache clean -f

# 更改 npm 镜像源

# 安装依赖时,临时使用淘宝镜像源【非常有用】
npm install --registry https://registry.npm.taobao.org
# 改成淘宝镜像源
npm config set registry https://registry.npm.taobao.org
# 改回官方镜像源
npm config set registry https://registry.npmjs.org

# 查看模块版本列表

# 查看版本列表
npm view vue versions
# 查看某个模块的信息
npm view vue

# 安装 node-sass 模块

# 因为 node-sass 模块被墙掉了
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# 再安装别的依赖
npm i

# 设置 electron 镜像源

# npm
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
# yarn
yarn config set electron_mirror https://npm.taobao.org/mirrors/electron/

# Git 不常用操作

# 临时缓存本地修改的文件

本地文件修改了一半儿,要切到别的分支修改代码。这时候可以:

# 缓存开发了一半儿的本地文件,-u是指包括新建的文件
git stash -u
# 分支切换回来后,再弹出缓存的文件到工作区
git stash pop

# 撤销(反做)以前的某次提交记录

# 撤销以前的某次提交<HashA>,反做某次提交<HashA>
git revert <HashA>
# 提交
git push

# 版本回退到某次提交记录

# 版本回退到某次提交<HashA>
git reset --hard <HashA>
# 强制提交,远程仓库的<HashA>之后的几次提交记录就消失了
git push -f

# 挑选别的分支中的某次提交到当前分支

# 首先,切换到master分支
git checkout master

# 挑选dev分支上的<HashA>,到master分支上
git cherry-pick <HashA>

# 挑选多个提交到master
git cherry-pick <HashA> <HashB>

# 如果要摘取的提交是个合并节点,需要添加m参数,值设为1
# 通常1号父分支是接受变动的分支,2号父分支是作为变动来源的分支
git cherry-pick -m 1 <HashA>

# 在提交信息的末尾追加一行(cherry picked from commit ...),方便以后查到这个提交是如何产生的。
git cherry-pick -x <HashA>

# 撤销最近一次cherry-pick操作
git cherry-pick --abort

# 提交日志筛选

# 筛选出作者是Jason,3月1日之后的提交记录
git log --oneline --author="Jason" --since=2023-03-01

# 筛选出3月1日之后的提交记录,过滤掉merge记录,并设置格式为:hash值;提交title;作者;绝对时间
# 例如:92c64cc7d;fix: 解决/和查询字符串的各种场景;Jason Bai;Wed Apr 26 11:51:03 2023 +0800
git log --oneline --no-merges --since=2023-03-01 --pretty=format:"%h;%s;%an;%ad"
Last Updated: 11/12/2023, 9:23:02 AM