前端开发中常用的JavaScript方法

工作中常用的JavaScript方法整理汇总

1、实现全屏

该方法通过兼容不同浏览器的全屏API(如requestFullScreenwebkitRequestFullScreen等),将页面根元素(document.documentElement)设置为全屏状态,适配主流浏览器(Chrome、Firefox、Safari、Edge)。

javascript 复制代码
function fullScreen() {
  const el = document.documentElement
  // 兼容不同浏览器的全屏请求方法
  const rfs = 
    el.requestFullScreen || 
    el.webkitRequestFullScreen || 
    el.mozRequestFullScreen || 
    el.msRequestFullscreen
  if (typeof rfs !== "undefined" && rfs) {
    rfs.call(el) // 调用全屏方法
  }
}
// 调用函数触发全屏
fullScreen()

Demo

点击按钮触发全屏,可在浏览器中直接测试(需注意部分浏览器需用户主动交互才能触发全屏,如点击事件内调用):

html 复制代码
<button onclick="fullScreen()">开启全屏</button>
<script>
function fullScreen() {
  const el = document.documentElement
  const rfs = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullscreen
  if (typeof rfs !== "undefined" && rfs) {
    rfs.call(el)
  }
}
</script>

2、退出全屏

与"实现全屏"对应,该方法兼容不同浏览器的退出全屏API(如exitFullscreenmozCancelFullScreen等),关闭当前页面的全屏状态,恢复正常视图。

javascript 复制代码
function exitScreen() {
  // 兼容不同浏览器的退出全屏方法
  if (document.exitFullscreen) {
    document.exitFullscreen()
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen()
  } else if (document.webkitCancelFullScreen) {
    document.webkitCancelFullScreen()
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen()
  }
  // 注:原代码中"cfs"未定义,此处保留原逻辑,实际使用时可删除该分支
  // if (typeof cfs !== "undefined" && cfs) {
  //   cfs.call(el)
  // }
}
// 调用函数退出全屏
exitScreen()

Demo

配合"开启全屏"按钮,添加退出全屏功能,需在全屏状态下点击才生效:

html 复制代码
<button onclick="fullScreen()">开启全屏</button>
<button onclick="exitScreen()">退出全屏</button>
<script>
function fullScreen() {
  const el = document.documentElement
  const rfs = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullscreen
  if (typeof rfs !== "undefined" && rfs) {
    rfs.call(el)
  }
}
function exitScreen() {
  if (document.exitFullscreen) {
    document.exitFullscreen()
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen()
  } else if (document.webkitCancelFullScreen) {
    document.webkitCancelFullScreen()
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen()
  }
}
</script>

3、页面打印

直接调用浏览器原生的window.print()方法,触发页面打印对话框,可选择打印机、打印范围等配置,适用于需要快速打印页面内容的场景(如订单、报表)。

javascript 复制代码
// 触发页面打印
window.print()

Demo

添加打印按钮,点击后弹出浏览器打印窗口,可预览并打印当前页面:

html 复制代码
<div>
  <h1>打印测试内容</h1>
  <p>这是一段需要打印的文本</p>
  <button onclick="window.print()">打印页面</button>
</div>

4、打印内容样式更改

通过CSS的@media print媒体查询,自定义打印时的页面样式(如隐藏不需要打印的元素、调整字体/颜色等),实现"屏幕显示"与"打印效果"的差异化。

css 复制代码
<style>
/* 打印时的专属样式:隐藏class为noprint的元素 */
@media print {
  .noprint {
    display: none; 
  }
  /* 可选:调整打印内容的字体和颜色 */
  .print {
    font-size: 16px;
    color: #333;
  }
}
</style>
<!-- 打印时显示的内容 -->
<div class="print">这部分内容会被打印</div>
<!-- 打印时隐藏的内容(如按钮、导航) -->
<div class="noprint">
  <button onclick="window.print()">打印按钮(打印时隐藏)</button>
</div>

Demo

测试打印效果时,"打印按钮"会被隐藏,仅保留核心内容:

html 复制代码
<style>
@media print {
  .noprint { display: none; }
  .print { font-size: 18px; color: #000; }
}
</style>
<div class="print">
  <h2>用户订单</h2>
  <p>订单号:20240508001</p>
  <p>商品:JavaScript教程</p>
</div>
<div class="noprint">
  <button onclick="window.print()">打印订单</button>
  <button>返回首页</button>
</div>

5、阻止关闭事件

通过监听window.onbeforeunload事件,在用户刷新页面、关闭标签页或导航离开时,弹出确认提示框(部分浏览器不支持自定义提示文本,会显示默认文案),适用于防止用户误操作导致数据丢失(如表单未提交)。

javascript 复制代码
// 监听页面卸载前事件
window.onbeforeunload = function() {
  // 自定义提示文本(部分浏览器可能忽略,显示默认内容)
  return '您确定要离开吗?当前页面数据可能未保存!';
};

Demo

在表单页面添加该事件,用户未提交表单时离开会触发提示:

html 复制代码
<form id="myForm">
  <input type="text" name="username" placeholder="请输入用户名">
  <button type="submit">提交</button>
</form>
<script>
let isSubmitted = false; // 标记表单是否已提交
document.getElementById("myForm").addEventListener("submit", function() {
  isSubmitted = true; // 提交后取消阻止
});

window.onbeforeunload = function() {
  if (!isSubmitted) { // 未提交时才触发提示
    return '您确定要离开吗?表单数据尚未提交!';
  }
};
</script>

6、屏幕录制

通过navigator.mediaDevices.getDisplayMedia()获取屏幕流,结合MediaRecorder API录制屏幕内容,录制完成后将视频以webm格式下载到本地(也可修改代码将视频流上传到后端),适用于在线演示、教程录制等场景。

javascript 复制代码
// 初始化屏幕录制
const startRecord = async function() {
  try {
    // 获取屏幕流(需用户授权)
    const stream = await navigator.mediaDevices.getDisplayMedia({
      video: true, // 录制视频
      audio: true  // 可选:录制音频(需额外授权)
    });
    const recordedChunks = []; // 存储录制的视频片段
    // 配置录制参数(webm格式,vp9编码,兼容性较好)
    const options = { mimeType: "video/webm; codecs=vp9" };
    const mediaRecorder = new MediaRecorder(stream, options);

    // 监听视频片段数据(每段数据可用时触发)
    mediaRecorder.ondataavailable = function(event) {
      if (event.data.size > 0) {
        recordedChunks.push(event.data); // 保存片段
      }
    };

    // 监听录制结束(用户停止共享屏幕时触发)
    mediaRecorder.onstop = function() {
      downloadVideo(recordedChunks); // 下载视频
      stream.getTracks().forEach(track => track.stop()); // 停止流
    };

    // 开始录制
    mediaRecorder.start();
    console.log("录制已开始,点击停止共享结束录制");
  } catch (err) {
    console.error("录制失败:", err); // 捕获授权失败等错误
  }
};

// 下载录制的视频
function downloadVideo(chunks) {
  // 合并片段为Blob对象
  const blob = new Blob(chunks, { type: "video/webm" });
  const url = URL.createObjectURL(blob); // 创建临时URL
  const a = document.createElement("a"); // 创建下载链接
  a.href = url;
  a.download = `screen-record-${new Date().getTime()}.webm`; // 文件名(带时间戳)
  document.body.appendChild(a);
  a.click(); // 触发下载
  document.body.removeChild(a);
  URL.revokeObjectURL(url); // 释放临时URL
}

// 调用函数开始录制(需用户主动触发,如点击按钮)
// startRecord();

Demo

添加开始录制按钮,用户授权后即可录制屏幕,停止共享时自动下载视频:

html 复制代码
<button onclick="startRecord()">开始屏幕录制</button>
<script>
const startRecord = async function() {
  try {
    const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
    const recordedChunks = [];
    const options = { mimeType: "video/webm; codecs=vp9" };
    const mediaRecorder = new MediaRecorder(stream, options);

    mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) recordedChunks.push(event.data);
    };

    mediaRecorder.onstop = () => {
      const blob = new Blob(recordedChunks, { type: "video/webm" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `screen-record-${Date.now()}.webm`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      stream.getTracks().forEach(track => track.stop());
    };

    mediaRecorder.start();
    alert("录制已开始,点击浏览器的「停止共享」结束录制,结束后自动下载视频");
  } catch (err) {
    alert("录制失败:" + err.message);
  }
};
</script>

7、判断横竖屏

通过window.orientation获取屏幕旋转角度(0/180为竖屏,90/-90为横屏),结合orientationchange(或resize)事件监听屏幕方向变化,实时判断当前是横屏还是竖屏,适用于移动端适配场景(如游戏、视频播放)。

javascript 复制代码
// 判断横竖屏的函数
function hengshuping() {
  const orientation = window.orientation;
  if (orientation === 180 || orientation === 0) {
    alert("竖屏状态!");
    // 可选:更新页面提示文本,替代alert
    // document.getElementById("orient-tip").textContent = "当前为竖屏";
  }
  if (orientation === 90 || orientation === -90) {
    alert("横屏状态!");
    // document.getElementById("orient-tip").textContent = "当前为横屏";
  }
}

// 监听屏幕方向变化事件(兼容部分不支持orientationchange的浏览器,用resize兜底)
const eventName = "onorientationchange" in window ? "orientationchange" : "resize";
window.addEventListener(eventName, hengshuping, false);

// 初始加载时执行一次,获取当前方向
hengshuping();

Demo

在移动端页面显示当前横竖屏状态,避免频繁弹出alert:

html 复制代码
<div id="orient-tip" style="font-size: 20px; text-align: center; margin-top: 50px;"></div>
<script>
function hengshuping() {
  const orientation = window.orientation;
  const tipEl = document.getElementById("orient-tip");
  if (orientation === 180 || orientation === 0) {
    tipEl.textContent = "当前为竖屏状态";
    tipEl.style.color = "#0088ff";
  } else if (orientation === 90 || orientation === -90) {
    tipEl.textContent = "当前为横屏状态";
    tipEl.style.color = "#ff6600";
  }
}

const eventName = "onorientationchange" in window ? "orientationchange" : "resize";
window.addEventListener(eventName, hengshuping, false);
hengshuping();
</script>

8、横竖屏样式变更

通过CSS的@media all and (orientation: landscape/portrait)媒体查询,分别定义横屏(landscape)和竖屏(portrait)时的页面样式(如背景色、布局),实现屏幕方向变化时的样式自动切换,无需JavaScript干预。

css 复制代码
<style>
/* 横屏时的样式:背景色为红色,文本居中 */
@media all and (orientation: landscape) {
  body {
    background-color: #ff0000;
    color: #fff;
    text-align: center;
    font-size: 24px;
  }
}

/* 竖屏时的样式:背景色为绿色,文本左对齐 */
@media all and (orientation: portrait) {
  body {
    background-color: #00ff00;
    color: #333;
    text-align: left;
    font-size: 18px;
  }
}
</style>
<div>旋转屏幕查看样式变化</div>

Demo

在移动端旋转屏幕,背景色和文本样式会自动切换,无需额外JS:

html 复制代码
<style>
@media all and (orientation: landscape) {
  .container {
    background-color: #ff0000;
    color: white;
    padding: 20px;
    border-radius: 8px;
  }
}
@media all and (orientation: portrait) {
  .container {
    background-color: #00ff00;
    color: #333;
    padding: 15px;
    border-radius: 4px;
  }
}
</style>
<div class="container">
  <h3>横竖屏样式测试</h3>
  <p>横屏时背景为红色,竖屏时为绿色</p>
</div>

9、标签页显隐

通过document.hidden(及兼容属性msHiddenwebkitHidden)判断当前标签页是否处于隐藏状态,结合visibilitychange(及兼容事件msvisibilitychangewebkitvisibilitychange)监听标签页显隐变化,适用于暂停/恢复视频播放、停止/启动定时器等场景。

javascript 复制代码
// 兼容不同浏览器的标签页隐显属性和事件名
const { hidden, visibilityChange } = (() => {
  let hiddenProp, eventName;
  if (typeof document.hidden !== "undefined") {
    // 标准属性(Chrome、Firefox、Edge)
    hiddenProp = "hidden";
    eventName = "visibilitychange";
  } else if (typeof document.msHidden !== "undefined") {
    // IE浏览器
    hiddenProp = "msHidden";
    eventName = "msvisibilitychange";
  } else if (typeof document.webkitHidden !== "undefined") {
    // Safari浏览器
    hiddenProp = "webkitHidden";
    eventName = "webkitvisibilitychange";
  }
  return {
    hidden: hiddenProp,
    visibilityChange: eventName
  };
})();

// 标签页显隐变化时的回调函数
const handleVisibilityChange = () => {
  const isHidden = document[hidden];
  if (isHidden) {
    console.log("当前标签页已隐藏(如切换到其他标签)");
    // 示例:暂停视频播放
    // document.getElementById("myVideo").pause();
  } else {
    console.log("当前标签页已显示(如切回当前标签)");
    // 示例:恢复视频播放
    // document.getElementById("myVideo").play();
  }
};

// 监听标签页显隐事件
if (hidden && visibilityChange) {
  document.addEventListener(visibilityChange, handleVisibilityChange, false);
}

Demo

显示标签页的实时状态,切换标签时会更新提示:

html 复制代码
<div id="tab-status" style="font-size: 20px; text-align: center; margin-top: 50px;"></div>
<script>
const { hidden, visibilityChange } = (() => {
  let hiddenProp, eventName;
  if (typeof document.hidden !== "undefined") {
    hiddenProp = "hidden";
    eventName = "visibilitychange";
  } else if (typeof document.msHidden !== "undefined") {
    hiddenProp = "msHidden";
    eventName = "msvisibilitychange";
  } else if (typeof document.webkitHidden !== "undefined") {
    hiddenProp = "webkitHidden";
    eventName = "webkitvisibilitychange";
  }
  return { hidden: hiddenProp, visibilityChange: eventName };
})();

const statusEl = document.getElementById("tab-status");
const updateStatus = () => {
  statusEl.textContent = document[hidden] ? "标签页已隐藏" : "标签页已显示";
  statusEl.style.color = document[hidden] ? "#ff0000" : "#0088ff";
};

if (hidden && visibilityChange) {
  document.addEventListener(visibilityChange, updateStatus, false);
  updateStatus(); // 初始状态
}
</script>

10、本地图片预览

通过window.createObjectURL()(或兼容的window.URL.createObjectURL())为本地选择的图片文件创建临时URL,将该URL赋值给img标签的src属性,实现无需上传服务器即可预览本地图片,适用于头像上传、图片表单等场景。

html 复制代码
<!-- 页面结构:文件选择框 + 预览图片 -->
<div class="test">
  <input type="file" id="imgInput" accept="image/*"> <!-- accept="image/*"仅允许选择图片 -->
  <br>
  <img id="previewImg" src="" alt="图片预览" style="margin-top: 20px; max-width: 300px; max-height: 300px;">
</div>

<script>
// 为文件创建临时URL的工具函数(兼容不同浏览器)
const getObjectURL = (file) => {
  let url = null;
  if (window.createObjectURL !== undefined) {
    // 标准方法
    url = window.createObjectURL(file);
  } else if (window.URL !== undefined) {
    // Chrome、Safari
    url = window.URL.createObjectURL(file);
  } else if (window.webkitURL !== undefined) {
    // 旧版Safari
    url = window.webkitURL.createObjectURL(file);
  }
  return url;
};

// 监听文件选择框的变化事件
document.getElementById("imgInput").addEventListener("change", (event) => {
  const file = event.target.files[0]; // 获取选择的第一个文件
  if (file && file.type.startsWith("image/")) { // 验证是否为图片文件
    const previewUrl = getObjectURL(file);
    document.getElementById("previewImg").src = previewUrl;
    // 可选:释放URL(避免内存泄漏,可在图片上传成功后调用)
    // URL.revokeObjectURL(previewUrl);
  } else {
    alert("请选择有效的图片文件!");
    document.getElementById("previewImg").src = ""; // 清空预览
  }
});
</script>

Demo

选择本地图片后,实时在页面上预览,支持常见图片格式(jpg、png、gif等):

html 复制代码
<div>
  <label>选择图片:</label>
  <input type="file" id="imgInput" accept="image/jpeg,image/png,image/gif">
  <div style="margin-top: 20px;">
    <img id="previewImg" src="" alt="预览图" style="max-width: 400px; max-height: 400px; border: 1px solid #eee; padding: 5px;">
  </div>
</div>
<script>
const getObjectURL = (file) => {
  let url = null;
  if (window.createObjectURL || window.URL || window.webkitURL) {
    url = (window.createObjectURL || window.URL.createObjectURL || window.webkitURL.createObjectURL).call(window, file);
  }
  return url;
};

document.getElementById("imgInput").addEventListener("change", (e) => {
  const file = e.target.files[0];
  if (!file) return;
  if (!file.type.match("image/")) {
    alert("请选择图片文件(jpg、png、gif)!");
    return;
  }
  const previewUrl = getObjectURL(file);
  document.getElementById("previewImg").src = previewUrl;
});
</script>

11、图片预加载

通过创建Image对象,将需要预加载的图片URL赋值给Imagesrc属性,提前加载图片资源到浏览器缓存中,避免后续使用时出现"白屏"或"加载中"状态,适用于图片轮播、多图展示页面。

javascript 复制代码
// 存储预加载的Image对象(避免被垃圾回收,确保缓存有效)
const preloadedImages = [];

/**
 * 图片预加载函数
 * @param {Array<string>} imgUrls - 需要预加载的图片URL数组
 * @param {Function} callback - 所有图片加载完成后的回调函数(可选)
 */
function preloader(imgUrls, callback) {
  let loadedCount = 0; // 已加载完成的图片数量
  const totalCount = imgUrls.length; // 总需要加载的图片数量

  if (totalCount === 0) {
    callback && callback(); // 无图片需加载,直接执行回调
    return;
  }

  for (let i = 0; i < totalCount; i++) {
    const img = new Image(); // 创建Image对象
    preloadedImages.push(img); // 存入数组

    // 图片加载完成事件
    img.onload = function() {
      loadedCount++;
      console.log(`已预加载图片 ${i+1}/${totalCount}:${imgUrls[i]}`);
      // 所有图片加载完成后执行回调
      if (loadedCount === totalCount) {
        callback && callback();
      }
    };

    // 图片加载失败事件
    img.onerror = function() {
      console.error(`预加载图片失败:${imgUrls[i]}`);
      loadedCount++;
      if (loadedCount === totalCount) {
        callback && callback();
      }
    };

    // 赋值src,开始加载图片
    img.src = imgUrls[i];
  }
}

// 示例:预加载2张图片,加载完成后执行回调
const imgList = [
  "https://example.com/image1.jpg", // 替换为实际图片URL
  "https://example.com/image2.png"
];

preloader(imgList, () => {
  console.log("所有图片预加载完成!");
  // 后续操作:如显示图片轮播
  // document.getElementById("carousel").style.display = "block";
});

Demo

预加载图片后,点击按钮显示图片,避免加载延迟:

html 复制代码
<div>
  <div id="load-status" style="font-size: 18px; margin-bottom: 20px;">正在预加载图片...</div>
  <button id="showBtn" style="display: none;" onclick="showImages()">显示图片</button>
  <div id="image-container" style="margin-top: 20px; display: flex; gap: 20px;"></div>
</div>

<script>
const preloadedImages = [];

function preloader(imgUrls, callback) {
  let loadedCount = 0;
  const totalCount = imgUrls.length;
  const statusEl = document.getElementById("load-status");

  if (totalCount === 0) {
    callback && callback();
    return;
  }

  for (let i = 0; i < totalCount; i++) {
    const img = new Image();
    preloadedImages.push(img);

    img.onload = function() {
      loadedCount++;
      statusEl.textContent = `预加载进度:${loadedCount}/${totalCount}`;
      if (loadedCount === totalCount) {
        statusEl.textContent = "所有图片预加载完成!";
        document.getElementById("showBtn").style.display = "inline-block"; // 显示按钮
        callback && callback();
      }
    };

    img.onerror = function() {
      statusEl.textContent = `预加载失败:第${i+1}张图片`;
      loadedCount++;
      if (loadedCount === totalCount) {
        document.getElementById("showBtn").style.display = "inline-block";
        callback && callback();
      }
    };

    img.src = imgUrls[i];
  }
}

// 预加载示例图片(替换为实际URL)
const imgUrls = [
  "https://picsum.photos/400/300?random=1",
  "https://picsum.photos/400/300?random=2",
  "https://picsum.photos/400/300?random=3"
];

preloader(imgUrls);

// 显示预加载的图片
function showImages() {
  const container = document.getElementById("image-container");
  preloadedImages.forEach(img => {
    img.style.maxWidth = "400px";
    container.appendChild(img);
  });
  document.getElementById("showBtn").disabled = true; // 禁用按钮,避免重复添加
}
</script>

12、字符串脚本化

通过eval()函数将字符串形式的JavaScript代码解析并执行,可将字符串转换为对象、变量或函数。注意eval()存在XSS漏洞风险(若字符串来源不可信),且影响代码可读性和性能,慎用。

javascript 复制代码
// 1. 将字符串转换为对象
const objStr = '({ name: "jack", age: 20 })'; // 注意:对象需用()包裹,否则会被解析为代码块
const user = eval(objStr);
console.log("转换后的对象:", user); // 输出:{ name: "jack", age: 20 }

// 2. 将字符串转换为变量引用
const varName = "user";
const userRef = eval(varName); // 等同于直接使用 user 变量
console.log("变量引用:", userRef.name); // 输出:jack

// 3. 执行字符串中的函数代码
const funcStr = 'function add(a, b) { return a + b; }';
eval(funcStr); // 定义 add 函数
const sum = add(3, 5);
console.log("函数执行结果:", sum); // 输出:8

Demo

演示eval()的基本用法(仅用于学习,实际开发优先使用JSON.parse()等安全方法):

html 复制代码
<div>
  <p>eval() 字符串转对象示例:</p>
  <button onclick="testEval()">执行eval()</button>
  <pre id="result" style="margin-top: 10px; padding: 10px; background: #f5f5f5;"></pre>
</div>

<script>
function testEval() {
  const resultEl = document.getElementById("result");
  let output = "";

  // 安全场景:字符串为固定值(非用户输入)
  const safeStr = '({ title: "JavaScript教程", author: "web前端开发" })';
  const book = eval(safeStr);
  output += "1. 转换后的对象:\n" + JSON.stringify(book, null, 2) + "\n\n";

  // 危险场景:若字符串含用户输入,可能注入恶意代码(示例,勿在生产环境使用)
  // const userInput = 'alert("XSS攻击!");'; // 恶意输入
  // eval(userInput); // 会执行 alert

  resultEl.textContent = output;
}
</script>

安全替代方案

  • 若需转换JSON字符串,优先使用JSON.parse()(无XSS风险):

    javascript 复制代码
    const jsonStr = '{"name": "jack", "age": 20}';
    const user = JSON.parse(jsonStr); // 安全可靠

13、递归函数名解耦

通过arguments.callee引用当前递归函数(arguments是函数内部的类数组对象,callee属性指向函数本身),避免递归函数内部硬编码函数名,解决"修改函数名时需同步修改内部引用"的问题。注意arguments.callee在严格模式(use strict)下禁用,需谨慎使用。

javascript 复制代码
// 示例1:斐波那契数列(使用arguments.callee解耦函数名)
function fibonacci(n) {
  const currentFunc = arguments.callee; // 引用当前函数(fibonacci)
  if (n <= 1) return 1; // 基线条件:n=0或1时返回1
  return currentFunc(n - 1) + currentFunc(n - 2); // 递归调用,无需硬编码函数名
}

// 测试斐波那契数列
console.log("斐波那契数列第5项:", fibonacci(5)); // 输出:8(1,1,2,3,5,8...)

// 示例2:修改函数名后仍正常工作(无需修改内部代码)
function fib(n) { // 函数名从fibonacci改为fib
  const currentFunc = arguments.callee;
  if (n <= 1) return 1;
  return currentFunc(n - 1) + currentFunc(n - 2); // 内部引用无需修改
}
console.log("斐波那契数列第6项:", fib(6)); // 输出:13

Demo

对比"硬编码函数名"和"使用arguments.callee"的差异,修改函数名后前者会报错,后者正常运行:

html 复制代码
<div>
  <button onclick="testRecursion()">测试递归解耦</button>
  <pre id="recursion-result" style="margin-top: 10px; padding: 10px; background: #f5f5f5;"></pre>
</div>

<script>
function testRecursion() {
  const resultEl = document.getElementById("recursion-result");
  let output = "";

  // 1. 硬编码函数名的递归(修改函数名后会报错)
  function factorialHard(n) {
    if (n <= 1) return 1;
    return n * factorialHard(n - 1); // 硬编码函数名
  }
  output += "1. 硬编码函数名的阶乘(n=5):" + factorialHard(5) + "\n";

  // 修改函数名后,原内部引用失效(报错)
  const factHardNew = factorialHard;
  try {
    factHardNew(6); // 内部仍调用factorialHard,已不存在
  } catch (err) {
    output += "   修改函数名后报错:" + err.message + "\n\n";
  }

  // 2. 使用arguments.callee的递归(修改函数名后正常)
  function factorialDecoupled(n) {
    const currentFunc = arguments.callee;
    if (n <= 1) return 1;
    return n * currentFunc(n - 1); // 引用当前函数,不依赖函数名
  }
  output += "2. 解耦的阶乘(n=5):" + factorialDecoupled(5) + "\n";

  // 修改函数名后仍正常运行
  const factDecoupledNew = factorialDecoupled;
  output += "   修改函数名后的阶乘(n=6):" + factDecoupledNew(6) + "\n";

  resultEl.textContent = output;
}
</script>

严格模式替代方案

若需使用严格模式,可通过"函数表达式"或"命名函数表达式"实现解耦:

javascript 复制代码
'use strict'; // 严格模式下arguments.callee禁用

// 命名函数表达式(推荐)
const fibonacci = function fib(n) {
  if (n <= 1) return 1;
  return fib(n - 1) + fib(n - 2); // 内部使用表达式名fib,与外部变量名fibonacci解耦
};
console.log(fibonacci(5)); // 输出:8

14、隐显判断

通过IntersectionObserver API监听DOM元素与视口(或指定根元素)的交叉状态(是否进入/离开视口),判断元素是否在页面视图内可见,支持配置可见比例阈值(如完全可见时触发),适用于懒加载、滚动动画、曝光统计等场景。

html 复制代码
<!-- 页面结构:多个高350px的元素,模拟滚动场景 -->
<style>
.container {
  height: 1000px; /* 父容器高度,允许滚动 */
  padding-top: 20px;
}
.item {
  height: 350px;
  margin: 20px auto;
  border: 2px solid #eee;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  width: 80%;
}
</style>

<div class="container">
  <div class="item" data-id="1">元素1(初始不可见)</div>
  <div class="item" data-id="2">元素2(初始不可见)</div>
  <div class="item" data-id="3">元素3(初始不可见)</div>
</div>

<script>
// 检查浏览器是否支持IntersectionObserver
if (window?.IntersectionObserver) {
  // 获取所有需要监听的元素(转换为数组)
  const items = [...document.getElementsByClassName("item")];
  
  // 配置IntersectionObserver选项
  const observerOptions = {
    root: null, // 根元素:null表示视口
    rootMargin: "0px 0px", // 根元素的边距(扩展/缩小交叉检测区域)
    threshold: 1 // 可见比例阈值:1表示元素完全进入视口时触发回调
  };

  // 创建IntersectionObserver实例
  const observer = new IntersectionObserver((entries) => {
    // entries:所有被监听元素的交叉状态数组
    entries.forEach((entry) => {
      const itemEl = entry.target;
      // entry.intersectionRatio:元素可见比例(0=完全不可见,1=完全可见)
      if (entry.intersectionRatio === 1) {
        itemEl.textContent = `元素${itemEl.dataset.id}(完全可见)`;
        itemEl.style.backgroundColor = "#e6f7ff"; // 完全可见时变色
      } else {
        itemEl.textContent = `元素${itemEl.dataset.id}(部分/完全不可见)`;
        itemEl.style.backgroundColor = "#fff"; // 不可见时恢复
      }
    });
  }, observerOptions);

  // 开始监听所有元素
  items.forEach((item) => observer.observe(item));

  // 可选:停止监听(如页面销毁时)
  // setTimeout(() => {
  //   observer.disconnect();
  // }, 10000);
} else {
  // 兼容不支持的浏览器(如IE),使用滚动事件 fallback
  alert("您的浏览器不支持IntersectionObserver,请升级浏览器!");
}
</script>

Demo

滚动页面,元素完全进入视口时会更新文本和背景色,直观查看可见状态:

html 复制代码
<style>
body { margin: 0; }
.container {
  height: 1200px;
  padding-top: 20px;
  background-color: #f9f9f9;
}
.item {
  height: 350px;
  margin: 30px auto;
  border: 2px solid #ddd;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  width: 70%;
  transition: background-color 0.3s;
}
</style>

<div class="container">
  <div class="item" data-id="1">元素1(滚动查看可见性)</div>
  <div class="item" data-id="2">元素2(滚动查看可见性)</div>
  <div class="item" data-id="3">元素3(滚动查看可见性)</div>
</div>

<script>
if (window?.IntersectionObserver) {
  const items = [...document.getElementsByClassName("item")];
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      const el = entry.target;
      if (entry.isIntersecting) { // entry.isIntersecting:是否进入视口
        const ratio = entry.intersectionRatio.toFixed(2);
        el.textContent = `元素${el.dataset.id}(可见比例:${ratio})`;
        el.style.backgroundColor = ratio === "1.00" ? "#d4edda" : "#fff3cd";
      } else {
        el.textContent = `元素${el.dataset.id}(已离开视口)`;
        el.style.backgroundColor = "#fff";
      }
    });
  }, { root: null, rootMargin: "0px", threshold: [0, 0.5, 1] }); // 多个阈值:0(进入)、0.5(半可见)、1(完全可见)

  items.forEach(item => observer.observe(item));
} else {
  alert("浏览器不支持IntersectionObserver,无法查看可见状态!");
}
</script>

15、元素可编辑

通过为DOM元素添加contenteditable="true"属性,使元素变为可编辑状态(类似textareainput),支持用户直接在页面上修改文本内容,适用于富文本编辑、可修改的标题/描述等场景。

html 复制代码
<!-- 基础用法:div元素可编辑 -->
<div contenteditable="true" style="padding: 10px; border: 1px solid #ddd; min-height: 50px; max-width: 500px;">
  点击此处编辑内容...(支持换行、粘贴文本)
</div>

<!-- 进阶用法:限制仅文本编辑 + 实时显示内容长度 -->
<div style="margin-top: 30px;">
  <p>可编辑标题(仅文本,最大长度20):</p>
  <h3 
    contenteditable="true" 
    style="padding: 5px; border: 1px solid #eee; display: inline-block;"
    oninput="updateLength(this)"
    onpaste="return false" <!-- 禁止粘贴(可选) -->
  >
    编辑我
  </h3>
  <span id="length-tip" style="margin-left: 10px; color: #666;">长度:3/20</span>
</div>

<script>
// 实时更新可编辑元素的内容长度
function updateLength(el) {
  const content = el.innerText.trim(); // 获取纯文本内容(排除HTML标签)
  const maxLength = 20;
  const currentLength = content.length;

  // 限制最大长度
  if (currentLength > maxLength) {
    el.innerText = content.substring(0, maxLength);
    // 光标定位到末尾(避免截断后光标错位)
    const range = document.createRange();
    const sel = window.getSelection();
    range.selectNodeContents(el);
    range.collapse(false);
    sel.removeAllRanges();
    sel.addRange(range);
  }

  // 更新长度提示
  const tipEl = document.getElementById("length-tip");
  const finalLength = el.innerText.length;
  tipEl.textContent = `长度:${finalLength}/${maxLength}`;
  tipEl.style.color = finalLength >= maxLength ? "#ff0000" : "#666";
}
</script>

Demo

可直接编辑页面元素,标题部分限制长度且禁止粘贴,实时显示内容长度:

html 复制代码
<style>
.editable-card {
  width: 600px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.editable-title {
  font-size: 22px;
  margin: 0 0 15px 0;
  padding: 5px;
  border-bottom: 1px dashed #ddd;
  outline: none; /* 取消聚焦时的默认边框 */
}
.editable-content {
  min-height: 100px;
  padding: 10px;
  border: 1px solid #eee;
  border-radius: 4px;
  outline: none;
  white-space: pre-wrap; /* 保留换行符 */
}
.length-tip {
  display: block;
  margin-top: 10px;
  color: #666;
  font-size: 14px;
}
</style>

<div class="editable-card">
  <h2 class="editable-title" contenteditable="true" oninput="updateTitleLength(this)">
    文章标题(可编辑,最大30字)
  </h2>
  <div class="editable-content" contenteditable="true">
    请输入文章内容...
    支持换行、段落格式。
    可直接粘贴纯文本(粘贴HTML会保留标签,需谨慎)。
  </div>
  <span id="title-length" class="length-tip">长度:12/30</span>
</div>

<script>
function updateTitleLength(el) {
  const content = el.innerText;
  const max = 30;
  let current = content.length;

  if (current > max) {
    el.innerText = content.substring(0, max);
    // 光标定位到末尾
    const range = document.createRange();
    const sel = window.getSelection();
    range.selectNodeContents(el);
    range.collapse(false);
    sel.removeAllRanges();
    sel.addRange(range);
    current = max;
  }

  document.getElementById("title-length").textContent = `长度:${current}/${max}`;
}
</script>

16、元素属性监听

通过MutationObserver API监听DOM元素的属性变化(如styledata-*class等),当属性被修改时触发回调函数,可获取修改前/后的值,适用于动态跟踪元素状态、响应属性变更等场景。

html 复制代码
<!-- 页面结构:待监听的元素 + 触发属性修改的按钮 -->
<div id="test" style="padding: 20px; border: 1px solid #ddd; display: inline-block;">
  测试元素
</div>
<button onclick="handleClick()" style="margin-left: 20px; padding: 8px 16px;">
  修改元素属性
</button>

<script>
// 获取待监听的元素
const el = document.getElementById("test");
let attrCount = 1; // 用于生成唯一的data-name属性值

// 配置MutationObserver选项:监听属性变化
const observerOptions = {
  attributes: true, // 启用属性监听
  attributeOldValue: true, // 记录属性修改前的值(可选,便于对比)
  attributeFilter: ["style", "data-name"] // 仅监听指定属性(可选,优化性能)
};

// 创建MutationObserver实例
const observer = new MutationObserver((mutations) => {
  // mutations:所有被修改的属性数组
  mutations.forEach((mutation) => {
    console.log("属性变化详情:");
    console.log(" - 被修改的属性:", mutation.attributeName);
    console.log(" - 修改前的值:", mutation.oldValue); // 需开启attributeOldValue: true
    console.log(" - 修改后的值:", el.getAttribute(mutation.attributeName));
    alert(`属性「${mutation.attributeName}」已修改!`);
  });
});

// 开始监听元素的属性变化
observer.observe(el, observerOptions);

// 按钮点击事件:修改元素的style和data-name属性
function handleClick() {
  // 修改style属性(颜色)
  el.setAttribute("style", `padding: 20px; border: 1px solid #ddd; color: ${getRandomColor()};`);
  // 修改data-name属性(自定义属性)
  el.setAttribute("data-name", `test-${attrCount++}`);
}

// 辅助函数:生成随机颜色
function getRandomColor() {
  const letters = "0123456789ABCDEF";
  let color = "#";
  for (let i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}

// 5秒后停止监听(示例,可根据需求调整)
setTimeout(() => {
  observer.disconnect();
  console.log("5秒后已停止属性监听");
  alert("已停止监听元素属性变化!");
}, 5000);
</script>

Demo

点击按钮修改元素颜色和自定义属性,控制台会打印属性修改前后的值,5秒后停止监听:

html 复制代码
<style>
#target {
  padding: 25px;
  border: 2px solid #eee;
  border-radius: 8px;
  font-size: 18px;
  margin: 20px 0;
}
.log-container {
  margin-top: 20px;
  padding: 15px;
  background-color: #f5f5f5;
  border-radius: 4px;
  max-height: 200px;
  overflow-y: auto;
  font-family: monospace;
  font-size: 14px;
}
</style>

<div id="target">被监听的元素</div>
<button onclick="modifyAttrs()">修改属性(颜色+自定义属性)</button>
<div class="log-container" id="log">
  日志:等待属性变化...<br>
</div>

<script>
const target = document.getElementById("target");
const logEl = document.getElementById("log");
let count = 1;

// 添加日志
function addLog(text) {
  logEl.innerHTML += `[${new Date().toLocaleTimeString()}] ${text}<br>`;
  logEl.scrollTop = logEl.scrollHeight; // 滚动到底部
}

// 创建观察者
const observer = new MutationObserver((mutations) => {
  mutations.forEach(mut => {
    const attr = mut.attributeName;
    const oldVal = mut.oldValue || "无";
    const newVal = target.getAttribute(attr);
    addLog(`属性「${attr}」变化:旧值=${oldVal} → 新值=${newVal}`);
  });
});

// 开始监听
observer.observe(target, {
  attributes: true,
  attributeOldValue: true,
  attributeFilter: ["style", "data-id"]
});
addLog("已开始监听style和data-id属性变化");

// 修改属性的函数
function modifyAttrs() {
  const color = `rgb(${Math.random()*255|0}, ${Math.random()*255|0}, ${Math.random()*255|0})`;
  target.setAttribute("style", `padding:25px; border:2px solid #eee; border-radius:8px; font-size:18px; color: ${color};`);
  target.setAttribute("data-id", `id-${count++}`);
}

// 8秒后停止监听
setTimeout(() => {
  observer.disconnect();
  addLog("8秒后停止监听属性变化");
}, 8000);
</script>

17、打印DOM元素

通过console.dir()方法打印DOM元素的详细属性和方法,与console.log()(仅打印元素标签结构)不同,console.dir()会以对象形式展示DOM元素的所有属性(如styleclassListdataset等),便于开发调试时查看元素的内部状态。

javascript 复制代码
// 1. 打印body元素的详细属性
console.log("=== console.log(document.body):仅打印标签结构 ===");
console.log(document.body);

console.log("\n=== console.dir(document.body):打印详细属性 ===");
console.dir(document.body);

// 2. 打印指定元素的属性(如id为"test"的元素)
const testEl = document.getElementById("test");
if (testEl) {
  console.log("\n=== console.dir(testEl):指定元素的详细属性 ===");
  console.dir(testEl);
  // 示例:查看元素的style属性(通过dir可快速定位)
  console.log("\n=== testEl.style的详细属性 ===");
  console.dir(testEl.style);
}

Demo

打开浏览器控制台(F12),查看console.logconsole.dir的差异:

html 复制代码
<div id="test" class="demo-element" data-type="example" style="color: #ff0000; font-size: 16px;">
  调试用元素
</div>

<button onclick="logElement()">点击打印DOM元素到控制台</button>

<script>
function logElement() {
  const el = document.getElementById("test");
  if (!el) return;

  // 1. console.log:打印元素标签结构
  console.log("1. console.log(el) → 标签结构:");
  console.log(el);

  // 2. console.dir:打印元素详细属性(对象形式)
  console.log("\n2. console.dir(el) → 详细属性:");
  console.dir(el);

  // 3. 打印元素的特定属性(如classList、dataset)
  console.log("\n3. 元素的classList:");
  console.dir(el.classList); // 查看所有类名
  console.log("\n4. 元素的dataset(自定义属性):");
  console.dir(el.dataset); // 查看data-*属性
}
</script>

调试场景示例

当需要查看元素的offsetTop(距离顶部的距离)、clientWidth(可视宽度)等布局属性时,console.dir(el)可快速定位这些属性,无需手动书写el.offsetTop等代码。

18、激活应用

在移动端开发中,通过<a>标签的href属性指定特定协议(如telsmswx),点击链接可唤起设备上的对应应用(如电话、短信、微信),适用于移动端的快捷操作场景(如"一键拨打客服电话""一键发送短信")。

html 复制代码
<!-- 1. 唤起电话应用:拨打指定号码 -->
<a href="tel:12345678910" style="display: inline-block; margin: 10px; padding: 8px 16px; background: #0088ff; color: #fff; text-decoration: none; border-radius: 4px;">
  拨打客服电话:123-4567-8910
</a>

<!-- 2. 唤起短信应用(Android):指定收件人和默认内容 -->
<a href="sms:12345678910,12345678911?body=您好,我想咨询产品问题" style="display: inline-block; margin: 10px; padding: 8px 16px; background: #00cc99; color: #fff; text-decoration: none; border-radius: 4px;">
  给客服发短信(Android)
</a>

<!-- 3. 唤起短信应用(iOS):指定收件人和默认内容(协议格式略有差异) -->
<a href="sms:/open?addresses=12345678910,12345678911&body=您好,我想咨询产品问题" style="display: inline-block; margin: 10px; padding: 8px 16px; background: #00cc99; color: #fff; text-decoration: none; border-radius: 4px;">
  给客服发短信(iOS)
</a>

<!-- 4. 唤起微信(iOS):通过wx://协议(需微信已安装,兼容性依赖系统版本) -->
<a href="wx://" style="display: inline-block; margin: 10px; padding: 8px 16px; background: #7bb32e; color: #fff; text-decoration: none; border-radius: 4px;">
  打开微信(iOS,需已安装)
</a>

<!-- 5. 唤起邮件应用:指定收件人、主题、内容 -->
<a href="mailto:service@example.com?subject=咨询问题&body=我的问题是:" style="display: inline-block; margin: 10px; padding: 8px 16px; background: #ff6600; color: #fff; text-decoration: none; border-radius: 4px;">
  发送邮件给客服
</a>

<script>
// 可选:判断设备类型,显示对应平台的短信链接
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const smsLinkAndroid = document.querySelector('a[href^="sms:12345678910,12345678911"]');
const smsLinkIOS = document.querySelector('a[href^="sms:/open"]');

if (isIOS) {
  smsLinkAndroid.style.display = "none"; // iOS隐藏Android短信链接
} else {
  smsLinkIOS.style.display = "none"; // Android隐藏iOS短信链接
}
</script>

Demo

在移动端打开页面,点击链接会唤起对应应用;在PC端点击会提示"无法打开链接"(需实际移动端测试):

html 复制代码
<style>
.app-link {
  display: inline-block;
  margin: 15px;
  padding: 10px 20px;
  color: #fff;
  text-decoration: none;
  border-radius: 6px;
  font-size: 16px;
  transition: opacity 0.3s;
}
.app-link:hover {
  opacity: 0.9;
}
.tel-link { background-color: #0088ff; }
.sms-link { background-color: #00cc99; }
.wechat-link { background-color: #7bb32e; }
.mail-link { background-color: #ff6600; }
</style>

<div style="text-align: center; margin-top: 50px;">
  <h3>移动端应用唤起测试</h3>
  <p>请在手机上打开此页面测试</p>
  
  <a href="tel:4008123456" class="app-link tel-link">
    拨打400-812-3456
  </a>
  
  <a href="sms:4008123456?body=您好,我想咨询订单问题" class="app-link sms-link" id="smsLink">
    给4008123456发短信
  </a>
  
  <a href="wx://" class="app-link wechat-link">
    打开微信(仅iOS,需已安装)
  </a>
  
  <a href="mailto:support@example.com?subject=反馈&body=我的反馈:" class="app-link mail-link">
    发送反馈邮件
  </a>
</div>

<script>
// 适配iOS和Android的短信链接
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const smsLink = document.getElementById("smsLink");

if (isIOS) {
  // iOS短信协议格式
  smsLink.href = "sms:/open?addresses=4008123456&body=您好,我想咨询订单问题";
} else {
  // Android短信协议格式
  smsLink.href = "sms:4008123456?body=您好,我想咨询订单问题";
}
</script>

注意事项

  • 不同平台(iOS/Android)的协议格式可能存在差异,需针对性适配;
  • 应用唤起依赖设备已安装对应应用,若未安装,链接可能无效;
  • 部分浏览器(如Chrome)可能限制非用户主动交互(如点击)的应用唤起,需确保链接通过点击触发。
相关推荐
龙在天几秒前
npm run dev 做了什么❓小白也能看懂
前端
hellokai44 分钟前
React Native新架构源码分析
android·前端·react native
li理1 小时前
鸿蒙应用开发完全指南:深度解析UIAbility、页面与导航的生命周期
前端·harmonyos
去伪存真1 小时前
因为rolldown-vite比vite打包速度快, 所以必须把rolldown-vite在项目中用起来🤺
前端
KubeSphere1 小时前
Kubernetes v1.34 重磅发布:调度更快,安全更强,AI 资源管理全面进化
前端
码出极致2 小时前
支付平台资金强一致实践:基于 Seata TCC+DB 模式的余额扣减与渠道支付落地案例
后端·面试
wifi歪f2 小时前
🎉 Stenciljs,一个Web Components框架新体验
前端·javascript
1024小神2 小时前
如何快速copy复制一个网站,或是将网站本地静态化访问
前端
掘金一周2 小时前
DeepSeek删豆包冲上热搜,大模型世子之争演都不演了 | 掘金一周 8.28
前端·人工智能·后端
moyu842 小时前
前端存储三剑客:Cookie、LocalStorage 与 SessionStorage 全方位解析
前端