99 js实战
loyalvi Lv7

06 JS实战

在js中,如何解决递归导致栈溢出问题

在 JavaScript 中,递归函数如果调用层次过深,会导致调用栈溢出(Stack Overflow)。为了解决这个问题,可以采用以下几种方法:

1. 尾调用优化(Tail Call Optimization)

  • 原理:尾调用优化是一种优化技术,当递归函数的调用是函数体中的最后一个操作时,编译器或解释器可以优化掉当前的调用栈帧,从而避免栈溢出。
  • 实现条件:递归调用必须是函数体中的最后一个操作,不能有其他操作跟随。
  • 示例代码
    1
    2
    3
    4
    5
    function factorial(n, acc = 1) {
    if (n === 1) return acc;
    return factorial(n - 1, n * acc);
    }
    console.log(factorial(1000)); // 尾调用优化的递归函数
  • 注意:尾调用优化在严格模式下('use strict';)效果更好,且不是所有 JavaScript 引擎都支持尾调用优化。

2. 使用循环代替递归

  • 原理:将递归逻辑转换为循环逻辑,通过迭代的方式实现相同的功能,避免递归调用。
  • 示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    function factorial(n) {
    let result = 1;
    for (let i = 2; i <= n; i++) {
    result *= i;
    }
    return result;
    }
    console.log(factorial(1000)); // 使用循环代替递归

3. 使用栈模拟递归

  • 原理:手动使用栈(数组)来模拟递归调用的过程,将递归参数和状态存储在栈中,然后通过循环来处理栈中的数据。
  • 示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function factorial(n) {
    const stack = [];
    let result = 1;
    while (n > 1) {
    stack.push(n);
    n--;
    }
    while (stack.length > 0) {
    result *= stack.pop();
    }
    return result;
    }
    console.log(factorial(1000)); // 使用栈模拟递归

4. 使用 Trampoline 函数

  • 原理:Trampoline 函数是一种用于处理递归调用的技术,它通过将递归调用转换为迭代调用,避免了递归调用导致的栈溢出。
  • 实现方法:创建一个 Trampoline 函数,该函数接受一个递归函数作为参数,并返回一个迭代函数。
  • 示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function trampoline(fn) {
    return function(...args) {
    let result = fn.apply(this, args);
    while (typeof result === 'function') {
    result = result();
    }
    return result;
    };
    }
    function factorial(n, acc = 1) {
    if (n === 1) return acc;
    return () => factorial(n - 1, n * acc);
    }
    const trampolinedFactorial = trampoline(factorial);
    console.log(trampolinedFactorial(1000)); // 使用 Trampoline 函数

5. 增加调用栈大小

  • 原理:通过增加 JavaScript 调用栈的大小来允许更深层次的递归调用。
  • 方法:在 Node.js 中,可以通过命令行参数 --stack-size 来增加调用栈大小,例如 node --stack-size=10000 script.js
  • 注意:这种方法仅适用于 Node.js 环境,且增加调用栈大小可能会导致其他问题,如内存溢出等。
    通过以上方法,可以有效地解决 JavaScript 中递归导致的栈溢出问题,选择合适的方法取决于具体的场景和需求。

script标签上有那些属性,分别什么作用

<script> 标签用于在 HTML 文档中嵌入或引用 JavaScript 代码。它有多个属性,每个属性都有特定的作用,以下是一些常见的 <script> 标签属性及其作用:

常用属性

  • src
    • 作用:指定外部 JavaScript 文件的路径。如果设置了 src 属性,浏览器会加载并执行该文件中的 JavaScript 代码。
    • 示例
      1
      <script src="script.js"></script>
  • type
    • 作用:指定脚本的 MIME 类型。对于 JavaScript,默认值为 text/javascript,通常可以省略。
    • 示例
      1
      <script type="text/javascript"></script>
  • async
    • 作用:指定脚本是否异步加载和执行。如果设置了 async 属性,脚本会在下载时不会阻塞页面的渲染,下载完成后立即执行。
    • 特点
      • 适用于不依赖其他脚本的独立模块。
      • 脚本执行顺序不确定,可能在 DOMContentLoaded 事件触发之前或之后执行。
    • 示例
      1
      <script src="script.js" async></script>
  • defer
    • 作用:指定脚本是否延迟执行。如果设置了 defer 属性,脚本会在文档解析完成后、DOMContentLoaded 事件触发前执行。
    • 特点
      • 脚本会按照在页面中出现的顺序执行。
      • 适用于依赖 DOM 的脚本。
    • 示例
      1
      <script src="script.js" defer></script>
  • charset
    • 作用:指定脚本文件的字符编码。如果未指定,默认使用页面的字符编码。
    • 示例
      1
      <script src="script.js" charset="utf-8"></script>
  • crossorigin
    • 作用:设置跨源资源共享(CORS)请求的模式。当加载外部脚本时,可以指定是否需要 CORS 验证。
      • anonymous:不发送凭据信息(如 cookies 和 HTTP 认证信息)。
      • use-credentials:发送凭据信息。
    • 示例
      1
      <script src="https://example.com/script.js" crossorigin="anonymous"></script>

其他属性

  • integrity
    • 作用:指定一个加密哈希值,用于验证加载的脚本文件是否被篡改。
    • 示例
      1
      <script src="script.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9UcV6wFojX8Io"></script>
  • nomodule
    • 作用:指定脚本是否在不支持 ES 模块的浏览器中执行。如果设置了 nomodule 属性,支持 ES 模块的浏览器会忽略该脚本。
    • 示例
      1
      <script src="legacy-script.js" nomodule></script>
  • referrerpolicy
    • 作用:指定引用者策略,控制在加载脚本时发送的 Referer 头信息。
      • no-referrer:不发送 Referer 头。
      • origin:只发送源信息。
      • unsafe-url:发送完整的 URL。
    • 示例
      1
      <script src="script.js" referrerpolicy="no-referrer"></script>
      通过合理使用这些属性,可以更好地控制脚本的加载和执行行为,提高页面的性能和安全性。

网页加载进度条

在网页中实现加载进度条可以提升用户体验,尤其是在加载大量资源或数据时。以下是几种常见的实现方法:

1. 使用 HTML5 的 <progress> 元素

HTML5 提供了 <progress> 元素,可以用来显示任务的进度。虽然它本身不直接显示网页加载进度,但可以结合 JavaScript 来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Progress Bar Example</title>
<style>
progress {
width: 100%;
height: 20px;
}
</style>
</head>
<body>
<progress id="progressBar" value="0" max="100"></progress>
<script>
window.addEventListener('load', function() {
const progressBar = document.getElementById('progressBar');
progressBar.value = 100;
});
// 模拟加载过程
const totalResources = 5;
let loadedResources = 0;
function updateProgress() {
loadedResources++;
const progressBar = document.getElementById('progressBar');
progressBar.value = (loadedResources / totalResources) * 100;
if (loadedResources < totalResources) {
setTimeout(updateProgress, 1000);
}
}
updateProgress();
</script>
</body>
</html>

2. 使用 JavaScript 监听资源加载事件

可以通过监听各种资源加载事件(如图片、脚本等)来更新进度条:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Progress Bar Example</title>
<style>
#progressBar {
width: 100%;
height: 20px;
background-color: #f3f3f3;
border: 1px solid #ccc;
}
#progressBar div {
height: 100%;
width: 0;
background-color: #4caf50;
}
</style>
</head>
<body>
<div id="progressBar"><div></div></div>
<img src="image1.jpg" alt="Image 1">
<img src="image2.jpg" alt="Image 2">
<img src="image3.jpg" alt="Image 3">
<script>
const progressBar = document.getElementById('progressBar').firstChild;
const images = document.getElementsByTagName('img');
const totalImages = images.length;
let loadedImages = 0;
function updateProgress() {
loadedImages++;
const percent = (loadedImages / totalImages) * 100;
progressBar.style.width = percent + '%';
}
for (let i = 0; i < images.length; i++) {
images[i].addEventListener('load', updateProgress);
}
</script>
</body>
</html>

3. 使用第三方库

有一些第三方库可以帮助实现更复杂的进度条效果,例如 nprogress

1
npm install nprogress

然后在你的项目中引入并使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Progress Bar Example</title>
<link rel="stylesheet" href="nprogress.css">
</head>
<body>
<script src="nprogress.js"></script>
<script>
NProgress.start();
// 模拟加载过程
setTimeout(() => {
NProgress.done();
}, 3000);
</script>
</body>
</html>

这些方法可以根据你的具体需求和项目结构来选择和调整。

4. window.performance

监听静态资源加载情况
可以通过10 BOM window对象对象来监听页面资源加载进度。该对象提供了各种⽅法来获取资源加载的详细信息。 可以使用performance.getEntries()⽅法获取页面上所有的资源加载信息。可以使用该⽅法来监测每个资源的加载状态,计算加载时间,并据此来实现⼀个资源加载进度条。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const resources = window.performance.getEntriesByType('resource'); 
const totalResources = resources.length;
let loadedResources = 0;
resources.forEach((resource) => {
// 排除 AJAX 请求
if (resource.initiatorType !== 'xmlhttprequest') {
resource.onload = () => {
loadedResources++;
const progress = Math.round((loadedResources / totalResources) * 100);
updateProgress(progress);
};
}
});
// 更新进度条
function updateProgress(progress) {
}

该代码会遍历所有资源,并注册⼀个onload事件处理函数。当每个资源加载完成后,会更新loadedResources变量,并计算当前的进度百分比,然后调⽤updateProgress()函数来更新进度条。需要注意的是,这里排除了AJAX请求,因为它们不属于页面资源。当所有资源加载完成后,页面就会完全加载。

站点一键换肤

在Vue中实现站点一键换肤可以通过多种方式来实现,以下是几种常见的方法:

方法一:使用CSS变量

  1. 定义CSS变量:在全局样式文件中定义CSS变量,用于存储主题相关的颜色、字体等样式信息。
    1
    2
    3
    4
    :root {
    --background-color: #ffffff;
    --text-color: #333333;
    }
  2. 使用CSS变量:在CSS中使用这些变量来设置样式。
    1
    2
    3
    4
    body {
    background-color: var(--background-color);
    color: var(--text-color);
    }
  3. 动态切换主题:通过JavaScript动态修改CSS变量的值来切换主题。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function changeTheme(themeName) {
    const root = document.documentElement;
    if (themeName === 'dark') {
    root.style.setProperty('--background-color', '#333333');
    root.style.setProperty('--text-color', '#ffffff');
    } else {
    root.style.setProperty('--background-color', '#ffffff');
    root.style.setProperty('--text-color', '#333333');
    }
    }
  4. 创建切换按钮:在Vue组件中创建一个按钮,用于触发主题切换.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <button @click="handleThemeChange">切换主题</button>
    </template>
    <script>
    export default {
    methods: {
    handleThemeChange() {
    const currentTheme = document.documentElement.style.getPropertyValue('--background-color') === '#333333' ? 'light' : 'dark';
    changeTheme(currentTheme);
    }
    }
    }
    </script>

方法二:切换CSS文件

  1. 准备多个CSS文件:为每个主题准备一个CSS文件,定义不同的样式.
  2. 动态加载CSS文件:通过JavaScript动态切换加载不同的CSS文件来实现主题切换.
    1
    2
    3
    4
    function changeTheme(theme) {
    const link = document.getElementById('theme');
    link.href = `./css/${theme}.css`;
    }

方法三:使用组件库的主题支持

如果你使用的是支持主题切换的UI组件库(如Element Plus),可以直接利用其提供的主题切换功能.

  1. 引入组件库的主题文件:在项目中引入组件库的主题文件.
    1
    2
    import 'element-plus/dist/index.css';
    import './styles/theme.scss';
  2. 切换主题:通过修改组件库提供的主题变量来切换主题.
    1
    2
    3
    4
    5
    6
    html {
    --v-bg-color: #cfcccc;
    }
    html.dark {
    --v-bg-color: #141414;
    }

方法四:使用Vuex管理主题状态

  1. 定义状态:在Vuex中定义一个状态来存储当前的主题.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const store = new Vuex.Store({
    state: {
    theme: 'light'
    },
    mutations: {
    changeTheme(state, theme) {
    state.theme = theme;
    }
    }
    });
  2. 动态应用主题:在组件中根据Vuex中的状态动态应用不同的主题样式.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <div :class="theme">
    <!-- 组件内容 -->
    </div>
    </template>
    <script>
    import { mapState, mapMutations } from 'vuex';
    export default {
    computed: {
    ...mapState(['theme'])
    },
    methods: {
    ...mapMutations(['changeTheme'])
    }
    }
    </script>

方法五:使用Less/Sass

使用Less/Sass等CSS预处理器:通过预处理器提供的变量、函数等功能来实现主题切换。
这些方法各有优缺点,可以根据项目的具体需求和使用场景选择合适的方法来实现站点的一键换肤功能.

大文件上传

前端大文件上传是一个常见的需求,尤其是在需要处理视频、图片、文档等大型文件的应用中。以下是实现大文件上传的一些关键点和技术方案:

  1. 分片上传
    • 将大文件分割成多个小片段(chunk),然后逐一上传这些小片段。
    • 客户端可以并行上传多个片段,提高上传速度。
    • 服务端接收到所有片段后,再将它们重新组合成原始文件。
  2. 断点续传
    • 如果上传过程中断,可以从中断的地方继续上传,而不是从头开始。
    • 这通常通过记录每个片段的上传状态来实现。
  3. 进度反馈
    • 提供实时的上传进度反馈给用户,增强用户体验。
    • 可以通过JavaScript定时器或者事件监听来实现。
  4. 错误处理和重试机制
    • 对上传过程中可能出现的错误进行处理,比如网络问题导致的上传失败。
    • 实现自动重试机制,对于失败的片段进行重新上传。
  5. 安全性
    • 确保上传过程中的数据安全,使用HTTPS等加密传输协议。
    • 服务端验证上传的文件类型和大小,防止恶意文件上传。
  6. 服务端支持
    • 服务端需要能够处理分片上传的逻辑,包括接收片段、存储和重组文件。
    • 可以使用现有的云存储服务,如AWS S3、阿里云OSS等,它们通常支持分片上传。
  7. 前端技术栈
    • 使用HTML5的<input type="file">来选择文件。
    • 使用JavaScript(可能结合框架如React、Vue等)来处理文件的读取和上传逻辑。
    • 使用XMLHttpRequestFetch API来发送网络请求。
  8. 库和工具
    • 使用如Resumable.jsPluploadDropzone.js等第三方库来简化大文件上传的实现。
    • 这些库通常提供了分片上传、断点续传、进度条等功能。
  9. 用户体验
    • 提供取消上传的选项。
    • 在文件上传完成后提供反馈,比如上传成功或失败的通知。
  10. 测试
    • 在不同的网络条件下测试上传功能,确保在慢速或不稳定的网络环境下也能正常工作。
      实现大文件上传时,需要综合考虑上述因素,以确保上传过程既高效又稳定。

      太大的文件可以将文件分成2M左右的大小,blob表示原始数据,也就是二进制数据,同事提供了对数的截取方法slice,而File继承blob功能


分片上传的具体操作步骤如下:

  1. 分片上传整体流程
    • 开始上传:前端启动文件分片上传,后端返回唯一标识。
    • 分片上传:前端获取文件,设置固定分片大小,将文件切成多个小片,并计算每个分片的MD5值(32位)。将每个分片的内容和MD5标识符一同上传至服务器。服务端接收每个分片及相关信息后,通过对每个分片进行校验,来确保分片的完整性。
    • 结束上传:当分片上传完毕或者前端取消上传时,调用结束上传接口结束此次文件上传操作。服务端根据是正常结束或取消上传来决定后续操作。
  2. 前端具体流程
    • 开始上传:发送开始上传请求,向服务器传递文件名、文件总大小、分片总数和切片大小,获取并保存文件上传的唯一标识符。同时在发送请求前,对上传的文件名进行校验。
    • 分片上传:将文件进行切片,根据文件的大小决定每个分片的大小并切分成多个片段,同时计算出总切片数,并为每个切片添加从0开始的顺序索引。对每个切片计算出它们的MD5值,并将这些分片的MD5值和顺序索引保存在浏览器内存中。然后发送上传数据请求,向服务器发送唯一标识符、分片的顺序索引、分片数据,MD5值和当前分片的大小。在每个分片发送请求后,如果发送成功,则将其对应的信息从浏览器内存中删除,并计算出此时的上传进度,然后发送下一个片段直至最后。如果请求发生错误,则对该分片再次发送一次上传请求,如果仍然错误,不再上传,调用结束请求并提示错误原因。
    • 结束上传:如果文件分片全部成功上传,向服务器发送结束请求,传递正常结束状态码,清空浏览器内存。如果主动取消上传则传递取消请求状态码,同时清空浏览器内存,不再继续上传。
  3. 部分代码实现
    • 创建分片:createChunk函数用于将文件分割成多个分片。
      1
      2
      3
      4
      5
      6
      7
      function createChunk(file, chunkSize) {
      const result = [];
      for (let i = 0; i < file.size; i += chunkSize) {
      result.push(file.slice(i, i + chunkSize));
      }
      return result;
      }
    • 根据文件内容创建hash值:hash函数用于计算分片的MD5值。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      function hash(chunks) {
      return new Promise((resolve) => {
      var spark = new SparkMD5();
      function _read(i) {
      if (i >= chunks.length) {
      resolve(spark.end());
      return;
      }
      var blob = chunks[i];
      var reader = new FileReader();
      reader.onload = e => {
      var bytes = e.target.result;
      spark.append(bytes);
      _read(i + 1);
      };
      reader.readAsArrayBuffer(blob);
      }
      _read(0);
      });
      }
    • 分片上传:uploadChunk函数用于上传每个分片。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      function uploadChunk(chunks, hash, fileName) {
      var taskArr = [];
      chunks.forEach((chunk, index) => {
      var formdata = new FormData();
      formdata.append('chunk', chunk);
      formdata.append('chunkName', `${hash}-${index}-${fileName}`);
      formdata.append('fileName', fileName);
      var task = axios.post('http://127.0.0.1:3000/upload', formdata, {
      headers: {
      'Content-Type': 'multipart/form-data'
      }
      });
      taskArr.push(task);
      });
      Promise.all(taskArr).then(() => {
      console.log('通知后端');
      });
      }
    • 上传测试:可以看到最终上传的文件会存在target目录下,而chunkCache_开头的临时文件夹是用于存储切片(在合并完后会被删除)。
      通过上述步骤和代码示例,可以实现大文件的分片上传。

Js超过 Number 最⼤值的数怎么处理

在 JavaScript 中,超过 Number.MAX_VALUE 的数值被认为是 Infinity (正⽆穷⼤)。如果要处理超过 Number.MAX_VALUE 的数值,可以使⽤第三⽅的 JavaScript 库,如 big.js 或bignumber.js ,这些库可以处理任意精度的数值。
例如,使⽤ big.js 库可以将两个超过 Number.MAX_VALUE 的数相加:

1
2
3
4
5
const big = require('big.js');
const x = new big('9007199254740993');
const y = new big('100000000000000000');
const result = x.plus(y);
console.log(result.toString()); // 输出:100009007194925474093

这⾥创建了两个 big.js 对象 x 和 y ,分别存储超过 Number.MAX_VALUE 的数值。通过plus⽅法将它们相加,得到了正确的结果。最后,通过 toString ⽅法将结果转换为字符串。
如果不依赖外部库,JavaScript 中,数值超过了 Number 最⼤值时,可以使⽤ BigInt 类型来处理,它可以表⽰任意精度的整数。
使⽤ BigInt 类型时,需要在数值后⾯添加⼀个 n 后缀来表⽰ BigInt 类型。例如:

1
const bigNum = 9007199254740993n; // 注意:数字后⾯添加了 'n' 后缀

注意,BigInt 类型是 ECMAScript 2020 新增的特性,因此在某些浏览器中可能不被⽀持。如果需要在
不⽀持 BigInt 的环境中使⽤ BigInt,可以使⽤ polyfill 或者第三⽅库来实现。

js实现输入两个版本号,返回哪个版本号大,哪个版本号小

为了比较两个版本号的大小,我们可以将版本号按照点(.)分割成数组,然后逐个比较每个部分的大小。以下是一个JavaScript函数,用于比较两个版本号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function compareVersions(v1, v2) {
// 将版本号按照点分割成数组
var v1Parts = v1.split('.');
var v2Parts = v2.split('.');
// 比较每个部分的大小
for (var i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
// 如果v1的某个部分不存在,则认为该部分为0
var v1Part = v1Parts[i] || 0;
// 如果v2的某个部分不存在,则认为该部分为0
var v2Part = v2Parts[i] || 0;
// 将部分转换为数字进行比较
v1Part = parseInt(v1Part, 10);
v2Part = parseInt(v2Part, 10);
// 比较大小
if (v1Part > v2Part) {
return v1 + ' > ' + v2;
} else if (v1Part < v2Part) {
return v1 + ' < ' + v2;
}
}
// 如果所有部分都相等,则两个版本号相等
return v1 + ' = ' + v2;
}

使用这个函数,你可以传入两个版本号,它会返回哪个版本号大,哪个版本号小,或者它们是否相等。例如:

1
2
3
console.log(compareVersions('1.2.3', '1.2.4')); // 输出:1.2.3 < 1.2.4
console.log(compareVersions('1.2.3', '1.2.2')); // 输出:1.2.3 > 1.2.2
console.log(compareVersions('1.2.3', '1.2.3')); // 输出:1.2.3 = 1.2.3

这个函数假设版本号的每个部分都是数字,并且按照从左到右的顺序比较每个部分的大小。如果版本号的某个部分不存在,则认为该部分为0。

JS实现某一年的某一个月的最后一天是星期几

在JavaScript中,你可以使用Date对象来计算某年某月的最后一天是星期几。下面是一个简单的函数,它接受年份和月份作为参数,并返回该月最后一天是星期几:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function getLastDayOfWeek(year, month) {
// 月份是从0开始的,所以需要减1
month = month - 1;
// 计算该月最后一天的日期
var lastDay = new Date(year, month + 1, 0);
// 获取星期几,getDay()返回0是周日,1是周一,以此类推,需要根据需要调整
var dayOfWeek = lastDay.getDay();
// 将数字转换为星期的名称
var days = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
// 返回结果
return days[dayOfWeek];
}
// 使用示例
var year = 2024; // 年份
var month = 12; // 月份
console.log(getLastDayOfWeek(year, month)); // 输出2024年12月的最后一天是星期几

请注意,getDay()方法返回的数字代表星期几,其中0代表星期日,1代表星期一,以此类推。如果你希望星期日为1,星期一为2,等等,你需要对返回的数字进行适当的调整。在上面的代码中,我直接使用了0-6的数字索引来从数组中获取对应的星期名称。

箭头函数和普通函数的区别

在JavaScript中,箭头函数(Arrow Function)和普通函数(也称为函数声明或函数表达式)是两种不同的函数定义方式,它们之间有几个关键的区别:

  1. 语法简洁性
    • 箭头函数:语法更简洁,不需要使用function关键字,也没有自己的arguments对象,没有superthis绑定,不能用作构造函数。
    • 普通函数:使用function关键字,可以有更复杂的函数体,有自己的arguments对象,可以作为构造函数使用。
  2. this
    • 箭头函数:不绑定自己的this值,它会捕获其所在上下文的this值作为自己的this值,这在回调函数中非常有用。
    • 普通函数:有自己的this值,这个值取决于函数的调用方式(例如,作为对象的方法调用、作为普通函数调用、使用callapply调用等)。
  3. arguments 对象
    • 箭头函数:没有自己的arguments对象,但可以通过剩余参数(...args)来访问其参数。
    • 普通函数:有自己的arguments对象,可以用来访问函数参数。
  4. 构造函数
    • 箭头函数:不能用作构造函数,尝试这样做会抛出错误。
    • 普通函数:可以用作构造函数,使用new关键字调用时会创建一个新对象。
  5. 原型链
    • 箭头函数:不创建自己的prototype属性。
    • 普通函数:创建自己的prototype属性,可以用来添加方法或属性,使得通过构造函数创建的对象可以共享这些方法和属性。
  6. 词法作用域
    • 箭头函数:不绑定自己的词法作用域,它们捕获其所在上下文的词法作用域。
    • 普通函数:创建自己的词法作用域。
  7. new.target
    • 箭头函数:没有new.target,因为它们不能作为构造函数使用。
    • 普通函数:作为构造函数调用时,new.target指向构造函数本身。
  8. yield 关键字
    • 箭头函数:不能直接包含yield关键字,因为它们不能用作Generator函数。
    • 普通函数:可以包含yield关键字,可以作为Generator函数。
  9. 函数体内变量
    • 箭头函数:不能在箭头函数内部使用var声明变量,只能使用letconst
    • 普通函数:可以在函数体内使用varletconst声明变量。
      箭头函数通常用于简短的回调函数,而普通函数则适用于需要更多控制和功能的场景。
      js箭头函数

浅拷贝和深拷贝

在JavaScript中,浅拷贝和深拷贝是两种常见的数据复制方法,它们的区别在于复制数据时是否递归复制对象的属性值。以下是一些实现浅拷贝和深拷贝的方法:

浅拷贝(Shallow Copy)

  1. Object.assign()
    • 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将源对象的属性复制到目标对象的顶层。
    1
    2
    3
    const obj1 = { a: 1 };
    const obj2 = { b: 2 };
    const shallowCopied = Object.assign({}, obj1, obj2);
  2. 扩展运算符(Spread Operator)
    • 可以用来复制数组和对象。
    1
    2
    3
    4
    const arr = [1, 2, 3];
    const shallowArrCopy = [...arr];
    const obj = { a: 1 };
    const shallowObjCopy = { ...obj };
  3. Array.prototype.slice()
    • 可以用于数组的浅拷贝。
    1
    2
    const arr = [1, 2, 3];
    const shallowArrCopy = arr.slice();

深拷贝(Deep Copy)

  1. JSON方法
    • 使用JSON.stringify()将对象转换成字符串,然后使用JSON.parse()将字符串转换回对象。
    1
    2
    const obj = { a: 1, b: { c: 2 } };
    const deepCopied = JSON.parse(JSON.stringify(obj));

    注意:这种方法不能复制函数、undefined、Symbol值、循环引用等。

  2. 递归复制
    • 手动编写函数递归地复制对象的属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function deepCopy(obj) {
    if (obj === null || typeof obj !== 'object') {
    return obj;
    }
    let copiedObj = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
    copiedObj[key] = deepCopy(obj[key]);
    }
    }
    return copiedObj;
    }
  3. 结构化克隆算法
    • 使用structuredClone()方法,这是一个浏览器API,可以深拷贝大多数类型的数据。
    1
    2
    const obj = { a: 1, b: { c: 2 } };
    const deepCopied = structuredClone(obj);
  4. 使用库
    • 使用第三方库,如Lodash的_.cloneDeep()方法。
    1
    2
    3
    4
    // 需要引入Lodash库
    const _ = require('lodash');
    const obj = { a: 1, b: { c: 2 } };
    const deepCopied = _.cloneDeep(obj);
  5. Map 和 Set 的深拷贝
    • 对于包含Map和Set的数据结构,需要特别处理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function deepCopy(obj) {
    if (obj === null || typeof obj !== 'object') {
    return obj;
    }
    if (obj instanceof Map) {
    return new Map(obj);
    }
    if (obj instanceof Set) {
    return new Set(obj);
    }
    let copiedObj = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
    copiedObj[key] = deepCopy(obj[key]);
    }
    }
    return copiedObj;
    }

    每种方法都有其适用场景和限制,选择哪种方法取决于具体的需求和数据结构。

localstorage,sessionstorage和cookie

localStoragesessionStoragecookie 是客户端存储的三种主要方式,它们都用于在用户的浏览器上存储数据,但每种方式都有其特点和用途。

  1. 存储大小:每个cookie可以存储的数据量较小,一般不超过4KB。
  2. 有效期:可以设置过期时间,如果未设置,则为浏览器会话结束时。
  3. 作用域:默认情况下,cookie 对于同一域名下的多个路径都是可见的,但可以通过设置路径来限制其作用域。
  4. 发送到服务器:每次HTTP请求都会携带cookie,因此对于不需要在客户端存储的数据,使用cookie可能会增加服务器请求的开销。
  5. 安全:cookie可以设置为HttpOnly,这样JavaScript就无法访问cookie,增加了安全性。

localStorage

  1. 存储大小:可以存储大约5MB的数据。
  2. 有效期:数据没有过期时间,即使关闭浏览器也会保留。
  3. 作用域:数据存储在特定的协议和域名下,对所有页面都是可见的。
  4. 发送到服务器:数据仅存储在客户端,不会自动发送到服务器。
  5. API:提供了一组API,如setItemgetItemremoveItemclear等,用于操作存储的数据。

sessionStorage

  1. 存储大小:与localStorage相同,可以存储大约5MB的数据。
  2. 有效期:数据仅在浏览器会话期间有效,关闭标签页或浏览器后数据会被清除。
  3. 作用域:数据存储在特定的标签页或窗口中,同一窗口下的不同页面可以共享sessionStorage中的数据。
  4. 发送到服务器:与localStorage一样,数据仅存储在客户端,不会自动发送到服务器。
  5. API:同样提供了setItemgetItemremoveItemclear等API。

比较

  • 存储大小localStoragesessionStorage的存储容量远大于cookie
  • 有效期localStorage是永久存储,sessionStorage是会话存储,而cookie可以设置过期时间。
  • 作用域localStoragesessionStorage的作用域是域名级别的,而cookie可以设置为路径级别。
  • 安全性cookie可以设置为HttpOnly,增加安全性,而localStoragesessionStorage没有这种机制。
  • 性能:由于cookie每次HTTP请求都会发送,对于不需要频繁验证用户身份的大量数据,使用localStoragesessionStorage可能更合适。
    根据应用场景的不同,开发者可以选择最适合的数据存储方式。例如,对于需要跨会话持久化的数据,可以使用localStorage;对于会话级别的数据,可以使用sessionStorage;而对于需要在服务器端验证的用户身份信息,可以使用cookie

IndexedDB

IndexedDB 是一种在浏览器中存储大量结构化数据的方式,它具有以下几个特点:

  1. 存储空间大:IndexedDB 没有固定的存储上限,通常不小于 250MB,远超过 localStorage 的 5MB 限制。
  2. 存储格式多样:IndexedDB 支持字符串、二进制数据(ArrayBuffer 对象和 Blob 对象)以及 JSON 键值对存储。一个对象相当于关系型数据库中的数据表,称为 对象仓库 (object store)
  3. 异步操作:IndexedDB 的操作是异步的,这意味着在进行大量数据读写时,不会阻塞网页,提高性能。
  4. 同源限制:IndexedDB 只能访问自身域名下的数据库,不能跨域访问,与 localStoragesessionStorage 一样。
  5. 支持事务:IndexedDB 支持事务处理,保证了数据操作的原子性和一致性,避免数据不一致的情况。
  6. 复杂查询能力:IndexedDB 支持使用索引和游标进行复杂查询,能够高效地检索和操作数据。
  7. 结构化存储:IndexedDB 允许存储结构化数据,包括对象和文件,使数据管理更加灵活和方便。
    应用场景
  • IndexedDB 适用于存储大量结构化数据,例如离线应用的数据存储。
  • 对于简单的数据,可以使用 localStorage;而对于大量结构化数据,IndexedDB 更为适合。
  • IndexedDB 也适用于实现后退上一个页面不刷新页面的场景,通过将数据缓存到本地,下次打开列表后,如果 URL 中的 ID 和缓存的数据 ID 一致,可以直接使用缓存数据,不再进行请求。
    localStoragesessionStoragecookie 的比较
  • IndexedDB 与 localStorage 类似,都是通过 key-value 方式存储,但 IndexedDB 可以直接存储对象数组等,不需要像 localStorage 那样必须转为字符串。
  • IndexedDB 支持异步调用,不会因为写入数据慢而导致页面阻塞,而 localStorage 的操作是同步的。
  • IndexedDB 的存储空间远大于 localStoragesessionStorage,适合存储大量数据。
  • IndexedDB 支持二进制存储,而 localStoragesessionStorage 主要用于存储字符串。
    总的来说,IndexedDB 是一种强大的浏览器存储机制,特别适合于需要存储大量数据和复杂数据结构的Web应用。

Service Worker

Service Worker 是一种在浏览器后台运行的脚本,它作为网络代理,可以拦截和管理网络请求,实现资源的缓存、更新和推送等功能。以下是Service Worker的一些关键点:

定义

Service Worker 是一个后台运行的脚本,充当一个代理服务器,拦截用户发出的网络请求,比如加载脚本和图片。Service Worker 可以修改用户的请求,或者直接向用户发出回应,不用联系服务器,这使得用户可以在离线情况下使用网络应用。

工作原理

Service Worker 是事件驱动的,它独立于网页文档之外运行,不能直接操作DOM。当浏览器启动时,Service Worker 会自动加载并执行,之后它会一直运行在后台,等待处理来自页面的消息。Service Worker 的主要作用是对网络请求进行拦截和重定向,通过拦截请求,可以实现缓存资源、离线体验、请求拦截和修改以及推送通知等功能。

应用场景

Service Worker 适用于以下场景:

  1. 离线缓存:通过Service Worker,可以实现Web应用的离线访问。当用户首次访问网页时,Service Worker会拦截并缓存网络请求,使得用户在离线状态下依然可以访问这些资源。
  2. 推送通知:即使在用户没有打开网页的情况下,Service Worker也可以接收来自服务器的推送消息,并通过浏览器显示通知。这使得Web应用可以像原生应用一样,及时向用户传达重要信息。
  3. 背景数据处理:Service Worker可以在用户与网页交互的间隙,处理一些耗时的后台任务。例如,我们可以使用Service Worker来同步用户数据,或者构建网站内容的索引,从而提高后续访问的速度。

最新进展

Service Worker 技术仍在不断发展中,浏览器厂商持续对其进行优化和改进。例如,Webpack5引入了对Service Worker的支持,可以更好地处理Service Worker的更新和缓存策略。随着PWA(Progressive Web Apps)的兴起,Service Worker作为构建PWA的关键技术之一,其应用和优化也在不断扩展。

从输入 URL 到页面渲染的全过程

从输入URL到页面渲染的全过程涉及多个步骤,包括网络请求、浏览器处理、DOM构建、样式计算、布局、绘制等。以下是这个过程的详细步骤:

  1. 地址解析
    • 用户在浏览器地址栏输入URL,浏览器首先解析URL以确定要访问的资源。
  2. DNS解析
    • 浏览器通过DNS解析将域名转换为IP地址。
  3. 建立连接
    • 浏览器使用解析得到的IP地址,通过TCP协议与服务器建立连接(通常使用HTTP/HTTPS协议)。
  4. 发送HTTP请求
    • 浏览器构建HTTP请求,包括请求行(方法、URL、HTTP版本)、请求头(User-Agent、Accept等)、空行和请求体(POST请求时)。
  5. 服务器处理请求
    • 服务器接收到请求后,根据请求类型(GET、POST等)处理请求,查找资源。
  6. 服务器响应
    • 服务器将处理结果以HTTP响应的形式发送给浏览器,包括状态行、响应头(Content-Type、Content-Length等)和响应体。
  7. 浏览器接收响应
    • 浏览器接收到服务器的响应,解析状态码和响应头,根据状态码处理响应内容(200表示成功,404表示未找到等)。
  8. 内容解析
    • 浏览器开始解析响应内容,如果是HTML文档,则解析HTML构建DOM树。
  9. CSS解析与应用
    • 浏览器解析CSS样式表,构建CSSOM树(CSS Object Model Tree),并将CSSOM树与DOM树结合,生成渲染树。
  10. JavaScript解析与执行
    • 浏览器解析JavaScript代码,并执行。JavaScript可以操作DOM和CSSOM,可能会改变渲染树。
  11. 布局(Reflow)
    • 浏览器根据渲染树和CSS样式计算每个节点的几何信息(位置和尺寸),这个过程称为布局或重排。
  12. 绘制(Painting)
    • 浏览器使用布局信息对每个节点进行绘制,生成页面的像素数据。
  13. 合成(Compositing)
    • 浏览器将各个层的内容合并到屏幕上显示,这个过程称为合成。现代浏览器使用GPU加速合成,提高渲染效率。
  14. 页面渲染完成
    • 浏览器完成所有上述步骤后,用户可以看到完整的页面内容。
  15. Service Worker
    • 如果页面使用了Service Worker,它可以在上述过程中的多个点介入,如缓存资源、拦截请求等。
      这个过程可能会因为浏览器的优化和缓存机制而有所简化,例如,浏览器可能会缓存DNS解析结果、HTTP响应等,减少重复请求的处理时间。此外,现代浏览器会使用异步加载、预加载、预渲染等技术来提高性能和用户体验。

页面白屏问题

页面白屏问题是一个常见的前端问题,它严重影响用户体验。以下是一些可能导致页面白屏的原因以及相应的解决方案:

  1. 资源加载失败
    • 页面依赖的关键资源(CSS、JS、图片等)加载失败,导致页面无法正常渲染。例如,在React项目中引入了不存在的资源或者资源加载失败,就会造成页面无法正常渲染,导致白屏。解决方案包括检查资源文件的路径是否正确,确保资源文件的服务器响应状态码为200,以及使用网络监控工具来跟踪资源加载情况。
  2. 代码错误
    • 代码中存在语法错误、逻辑错误或运行时错误,导致页面无法正常加载。解决方案是使用浏览器的开发者工具(如Chrome DevTools)来检查控制台中的错误信息,并修复相应的代码问题。
  3. 兼容性问题
    • 页面在某些浏览器或设备上无法正常显示,导致白屏。解决方案是进行跨浏览器测试,并使用polyfills或者特定的CSS/JS代码来解决兼容性问题。
  4. 网络问题
    • 用户的网络连接出现问题,导致页面无法正常加载。解决方案是优化网络请求,使用CDN、压缩技术和缓存策略优化网络请求,减少页面加载时间。
  5. 服务器故障
    • 服务器出现故障,导致页面无法正常加载。解决方案是检查服务器状态,确保服务器正常运行,并且资源文件可以被正确访问。
  6. Service Worker缓存问题
    • 如果使用了Service Worker进行缓存管理,错误的缓存策略可能导致页面白屏。解决方案是检查Service Worker的缓存策略,确保正确地缓存了资源,并且在需要时更新缓存。
  7. 优化页面大小和性能
    • 压缩图像、移除不必要的脚本和样式,减小页面大小。合理使用脚本,避免在页面加载时执行大量脚本,并考虑使用延迟加载或异步加载策略。
  8. 浏览器渲染优化
    • 简化HTML和CSS代码,减少HTML和CSS代码中的复杂度,提高浏览器对这些代码的解析效率。减少页面上加载的资源数量,尤其是图片和视频等资源。优化JavaScript代码的执行效率,减少JavaScript代码对CPU资源的占用。
      通过上述解决方案,可以有效地排查和解决页面白屏问题,提升网页性能和用户体验。

判断两个域名是否一样

判断两个域名是否一样,通常需要考虑以下几个因素:

  1. 域名本身
    • 直接比较两个域名的字符串是否完全相同。例如,example.comexample.com 是相同的,而 example.comwww.example.com 是不同的。
  2. 子域名
    • 子域名会影响域名的判断。例如,sub.example.comexample.com 是不同的,因为它们指向不同的主机。
  3. 顶级域名(TLD)
    • 顶级域名(如.com、.org、.net)也会影响域名的判断。
  4. 端口号
    • 如果域名包含端口号,那么端口号的不同也会导致域名不同。例如,example.com:80example.com:443 是不同的。
  5. 协议
    • 协议(如HTTP和HTTPS)的不同也会影响域名的判断。http://example.comhttps://example.com 是不同的。

在JavaScript中判断域名是否相同

在JavaScript中,你可以使用URL对象来解析和比较域名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function areDomainsTheSame(url1, url2) {
const parsedUrl1 = new URL(url1);
const parsedUrl2 = new URL(url2);
// 比较协议、域名和端口
return (
parsedUrl1.protocol === parsedUrl2.protocol &&
parsedUrl1.hostname === parsedUrl2.hostname &&
parsedUrl1.port === parsedUrl2.port
);
}
// 示例
const url1 = 'https://example.com';
const url2 = 'http://example.com';
console.log(areDomainsTheSame(url1, url2)); // 输出:false
const url3 = 'https://example.com';
const url4 = 'https://example.com';
console.log(areDomainsTheSame(url3, url4)); // 输出:true

注意事项

  • 子域名:如果你需要判断两个域名是否属于同一个顶级域名,但不考虑子域名的差异,你可能需要手动去除子域名的部分,只比较顶级域名和二级域名。
  • 国际化域名:对于包含非ASCII字符的国际化域名,比较之前可能需要进行Punycode转换。
  • 大小写:域名本身是不区分大小写的,但在实际比较时,需要确保两个域名的大小写完全一致。
    通过这些方法,你可以准确地判断两个域名是否相同。
由 Hexo 驱动 & 主题 Keep
访客数 访问量