JavaScript实用技巧汇总

JavaScript实用技巧汇总

JavaScript开发中有很多实用的技巧和最佳实践。本文汇总了开发中常用的技巧,包括函数防抖节流、页面加载事件、网页打印和正则表达式等。

1. 函数防抖与节流

防抖和节流是优化高频触发事件的常用技巧。

1.1 核心概念

  • 防抖(Debounce):在指定的时间内只响应最后一次调用
  • 节流(Throttle):在指定的时间内只响应第一次调用

1.2 防抖实现

应用场景:用户在直播间内,超过一段时间没有任何操作时弹出提示框,有操作则重新计时。

// 防抖函数实现
function debounce(fn, time) {
  let timeout = null; // 创建一个标记用来存放定时器的返回值

  return function () {
    clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉

    timeout = setTimeout(() => {
      // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内
      // 如果还有字符输入的话,就不会执行 fn 函数
      fn.apply(this, arguments); // arguments是传入的参数
    }, time);
  };
}

// 使用示例
let sendMessage = function () {
  console.log('执行操作');
};

// 立即开始计时
setTimeout(debounce(sendMessage, 5000), 0);

// 每次点击都会重新计时
document.addEventListener("click", debounce(sendMessage, 5000), true);

常用场景

  • 搜索框输入联想
  • 窗口resize事件
  • 滚动事件优化

1.3 节流实现

// 节流函数实现
function throttle(fn, time) {
  let canRun = true; // 通过闭包保存一个标记

  return function () {
    if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return

    canRun = false; // 立即设置为false

    setTimeout(() => {
      fn.apply(this, arguments);
      canRun = true; // 执行完事件后,重新将这个标记设置为true
    }, time);
  };
}

// 使用示例
window.addEventListener('scroll', throttle(function() {
  console.log('滚动事件');
}, 1000));

常用场景

  • 滚动事件
  • 鼠标移动事件
  • 按钮点击防止重复提交

1.4 防抖与节流对比

特性 防抖 节流
触发时机 停止触发后延迟执行 按固定频率执行
执行次数 最后一次触发时执行 第一次触发时执行
典型场景 搜索框、表单验证 滚动、resize
时机描述 “等会儿再说” “按节奏执行”

2. 页面加载事件

2.1 window.onload vs $(document).ready()

对比项 window.onload $(document).ready()
执行时间 必须等到页面内包括图片的所有元素加载完毕后才能执行 DOM结构绘制完毕后就执行,不必等到加载完毕
编写个数 不能同时编写多个,如果有多个window.onload方法,只会执行一个 可以同时编写多个,并且都可以得到执行
简化写法 无简化写法 可以简写成 $(function(){});
// JavaScript原生写法
window.onload = function() {
  console.log('页面所有资源加载完成');
};

// jQuery写法
$(document).ready(function() {
  console.log('DOM加载完成');
});

// jQuery简化写法
$(function() {
  console.log('DOM加载完成(简化写法)');
});

2.2 DOMContentLoaded事件

现代JavaScript推荐使用DOMContentLoaded事件:

document.addEventListener('DOMContentLoaded', function() {
  console.log('DOM加载完成');
});

2.3 执行顺序示例

<!DOCTYPE html>
<html>
<head>
  <script>
    // 1. 立即执行
    console.log('1. script标签执行');

    // 2. DOM加载完成执行
    document.addEventListener('DOMContentLoaded', function() {
      console.log('2. DOMContentLoaded');
    });

    // 3. jQuery ready
    $(document).ready(function() {
      console.log('3. jQuery ready');
    });

    // 4. 所有资源加载完成执行
    window.onload = function() {
      console.log('4. window.onload');
    };
  </script>
</head>
<body>
  <img src="large-image.jpg" alt="大图片">
</body>
</html>

// 输出顺序:
// 1. script标签执行
// 2. DOMContentLoaded
// 3. jQuery ready
// 4. window.onload

3. 网页打印

3.1 打印整个网页

// 打印当前页面
window.print();

3.2 打印部分页面

通过CSS媒体查询控制打印内容:

<style>
  /* 默认隐藏打印区域 */
  .print-area {
    display: none;
  }

  /* 打印时的样式 */
  @media print {
    /* 隐藏不需要打印的元素 */
    .no-print {
      display: none !important;
    }

    /* 显示打印区域 */
    .print-area {
      display: block !important;
    }

    /* 自定义打印样式 */
    body {
      font-size: 12pt;
      line-height: 1.5;
    }

    h1 {
      page-break-before: always;
    }

    .page-break {
      page-break-after: always;
    }
  }
</style>

<div class="no-print">
  <button onclick="window.print()">打印</button>
  <p>这部分不会打印</p>
</div>

<div class="print-area">
  <h1>打印内容</h1>
  <p>这部分会打印</p>
  <div class="page-break"></div>
  <p>第二页内容</p>
</div>

3.3 打印设置技巧

@media print {
  /* 设置纸张大小 */
  @page {
    size: A4;
    margin: 2cm;
  }

  /* 设置页眉页脚 */
  @page {
    @top-center {
      content: "页眉内容";
    }
    @bottom-center {
      content: "第 " counter(page) " 页";
    }
  }

  /* 避免元素被分页截断 */
  .no-break {
    page-break-inside: avoid;
  }

  /* 打印背景颜色和图像 */
  * {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

3.4 打印指定区域

function printArea(areaId) {
  // 创建新窗口
  const printWindow = window.open('', '_blank');

  // 获取打印内容
  const printContent = document.getElementById(areaId).innerHTML;

  // 写入新窗口
  printWindow.document.write(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>打印</title>
      <style>
        body { font-family: Arial, sans-serif; }
        @media print {
          @page { margin: 1cm; }
        }
      </style>
    </head>
    <body>
      ${printContent}
    </body>
    </html>
  `);

  printWindow.document.close();

  // 等待内容加载完成后打印
  printWindow.onload = function() {
    printWindow.print();
    printWindow.close();
  };
}

// 使用
printArea('print-area');

4. 正则表达式技巧

4.1 捕获分组与替换

使用正则表达式的捕获分组功能,可以保存匹配的内容并在替换时引用。

// 示例:将15vh内的数字替换成calc(var(--heightsize) * 数字,并把vh去掉
// (\d+\.\d+|\d+) 就是将匹配到的数字做暂存,在表达式内用$1进行引用

let data = "margin-top: 15vh; margin-top: 5vh; margin-top: 1111.555vh;";
console.log(data, "原始数据");

// 正则替换
let reg = data.replace(/(\d+\.\d+|\d+)vh/g, "calc(var(--heightsize) * $1)");
console.log(reg, "替换后");

// 输出:
// "margin-top: calc(var(--heightsize) * 15);
//  margin-top: calc(var(--heightsize) * 5);
//  margin-top: calc(var(--heightsize) * 1111.555);"

4.2 常用正则表达式

// 手机号验证
const phoneReg = /^1[3-9]\d{9}$/;

// 邮箱验证
const emailReg = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;

// 身份证号验证
const idCardReg = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;

// URL验证
const urlReg = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/;

// IP地址验证
const ipReg = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;

// 密码强度验证(至少8位,包含大小写字母和数字)
const passwordReg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/;

// 中文验证
const chineseReg = /^[\u4e00-\u9fa5]+$/;

// 用户名验证(4-16位字母、数字、下划线)
const usernameReg = /^[a-zA-Z0-9_]{4,16}$/;

4.3 正则表达式常用方法

// test() - 测试字符串是否匹配
const pattern = /\d+/;
console.log(pattern.test('abc123')); // true

// match() - 返回匹配的数组
const str = 'abc123def456';
console.log(str.match(/\d+/g)); // ['123', '456']

// replace() - 替换匹配的内容
const text = 'Hello World';
console.log(text.replace(/World/, 'JavaScript')); // 'Hello JavaScript'

// split() - 使用正则分割字符串
const csv = 'apple,banana;orange:grape';
console.log(csv.split(/[,:;]/)); // ['apple', 'banana', 'orange', 'grape']

// search() - 查找匹配的位置
const sentence = 'Hello World';
console.log(sentence.search(/World/)); // 6

4.4 正则表达式标志

标志 描述 示例
g 全局匹配 /a/g 匹配所有a
i 忽略大小写 /a/i 匹配a和A
m 多行匹配 /^a/m 匹配每行开头的a
s dotAll模式 /a./s .匹配换行符
u Unicode模式 /^\u{20BB7}/u 匹配Unicode字符
y 粘连模式 从lastIndex开始匹配
// 全局替换
const str = 'Hello hello HELLO';
console.log(str.replace(/hello/gi, 'Hi'));
// 'Hi Hi Hi'

// 多行匹配
const text = 'line1\nline2\nline3';
console.log(text.match(/^line/gm));
// ['line1', 'line2', 'line3']

5. 其他实用技巧

5.1 数组去重

// 使用Set
const arr = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(arr)];

// 使用filter
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);

// 使用reduce
const unique3 = arr.reduce((acc, cur) => {
  return acc.includes(cur) ? acc : [...acc, cur];
}, []);

5.2 数组排序

// 数字排序
const nums = [3, 1, 4, 1, 5, 9];
nums.sort((a, b) => a - b); // 升序
nums.sort((a, b) => b - a); // 降序

// 对象数组排序
const users = [
  { name: 'Tom', age: 20 },
  { name: 'Jerry', age: 18 }
];
users.sort((a, b) => a.age - b.age);

5.3 深拷贝

// 使用JSON(不能处理函数和undefined)
const deepCopy = JSON.parse(JSON.stringify(obj));

// 使用structuredClone(现代浏览器)
const deepCopy2 = structuredClone(obj);

// 手动实现
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;

  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof Array) return obj.map(item => deepClone(item));

  const clonedObj = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clonedObj[key] = deepClone(obj[key]);
    }
  }
  return clonedObj;
}

5.4 类型判断

// 精确判断类型
function getType(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1);
}

console.log(getType([])); // 'Array'
console.log(getType({})); // 'Object'
console.log(getType(null)); // 'Null'
console.log(getType(undefined)); // 'Undefined'
console.log(getType(() => {})); // 'Function'

6. 总结

本文汇总了JavaScript开发中的实用技巧:

  • 防抖与节流:优化高频事件
  • 页面加载事件:理解不同加载阶段的区别
  • 网页打印:使用媒体查询控制打印内容
  • 正则表达式:强大的文本处理工具

掌握这些技巧可以让你的JavaScript代码更高效、更优雅。