2024-08-14
前端
00

目录

splice
filter
Math: round, floor, ceil
1. Math.round()
2. Math.floor()
3. Math.ceil()
总结
提取URL的参数
方法 1: 使用 URLSearchParams
方法 2: 手动解析 URL
方法 3: 使用正则表达式
总结
深拷贝和浅拷贝
虚拟DOM
什么是虚拟DOM?
为什么需要虚拟DOM?
PWA
PWA 的核心特性:
PWA 的优势:
PWA 的实际应用场景:
Promise 简介
GET与POST
缺点
Promise的值穿透
示例说明:
解释:
为什么会有值穿透?
实际应用场景:
JavaScript脚本延迟加载
1. defer 属性
2. async 属性
3. 动态创建脚本元素
4. 放置脚本标签在页面底部
5. 懒加载脚本
异步编程方式
1. 回调函数(Callbacks)
2. Promise
3. Async/Await
4. 生成器(Generators)
pnpm
函数式编程
核心概念
函数式编程的缺点
CSR和SSR
1. CSR(Client-Side Rendering,客户端渲染)
什么是 CSR?
工作流程:
优点:
缺点:
适用场景:
2. SSR(Server-Side Rendering,服务器端渲染)
什么是 SSR?
工作流程:
优点:
缺点:
适用场景:
3. CSR 和 SSR 的混合使用
闭包
闭包的定义
闭包的形成
闭包的示例
闭包的应用场景
闭包的优缺点
优点:
缺点:
总结

splice

splice 方法是 JavaScript 中数组操作的一个强大工具,用于对数组的内容进行增删改查。它可以从数组中删除元素、添加新元素,或者同时进行删除和添加操作。

语法:

javascript
array.splice(start, deleteCount, item1, item2, ...);

参数:

  1. start:表示要修改的起始位置(从 0 开始计数)。如果 start 大于数组长度,则从数组末尾开始添加元素。如果是负数,则表示从数组末尾向前计算的位置。

  2. deleteCount(可选):表示要删除的元素个数。如果为 0,则不删除任何元素。如果未指定,则删除从 start 开始的所有元素。

  3. item1, item2, ...(可选):表示要添加到数组中的新元素。如果不指定,则只删除元素,不添加新元素。

返回值:

返回一个包含被删除元素的新数组。如果没有删除元素,则返回一个空数组。

例子:

  1. 删除元素:
javascript
let arr = [1, 2, 3, 4, 5]; let removed = arr.splice(2, 2); // 从索引 2 开始删除 2 个元素 console.log(arr); // 输出 [1, 2, 5] console.log(removed); // 输出 [3, 4]
  1. 添加元素:
javascript
let arr = [1, 2, 3, 4, 5]; arr.splice(2, 0, 'a', 'b'); // 在索引 2 处不删除任何元素,添加 'a' 和 'b' console.log(arr); // 输出 [1, 2, 'a', 'b', 3, 4, 5]
  1. 替换元素:
javascript
let arr = [1, 2, 3, 4, 5]; arr.splice(2, 2, 'a', 'b'); // 从索引 2 开始删除 2 个元素,并添加 'a' 和 'b' console.log(arr); // 输出 [1, 2, 'a', 'b', 5]

通过 splice 方法,你可以灵活地对数组进行操作,包括删除、添加或替换元素。

filter

filter 方法是 JavaScript 中用于筛选数组元素的一个常用方法。它会创建一个新数组,其中包含通过提供的测试函数的所有元素。

语法:

javascript
array.filter(callback(element, index, array), thisArg);

参数:

  1. callback:一个用来测试每个元素的函数。返回 true 的元素会被包含在返回的新数组中,返回 false 的则会被过滤掉。这个函数接收以下三个参数:

    • element:正在处理的当前元素。
    • index(可选):正在处理的当前元素的索引。
    • array(可选):调用 filter 的数组本身。
  2. thisArg(可选):可选的参数。当执行回调函数时,用作 this 的值。

返回值:

返回一个新的数组,包含所有通过测试的元素。如果没有元素通过测试,则返回一个空数组。

例子:

  1. 过滤数组中的偶数:
javascript
let arr = [1, 2, 3, 4, 5, 6]; let evenNumbers = arr.filter(function(element) { return element % 2 === 0; }); console.log(evenNumbers); // 输出 [2, 4, 6]
  1. 过滤数组中长度大于 3 的字符串:
javascript
let words = ['apple', 'pear', 'banana', 'kiwi']; let longWords = words.filter(function(word) { return word.length > 3; }); console.log(longWords); // 输出 ['apple', 'pear', 'banana']
  1. 使用箭头函数:
javascript
let arr = [10, 20, 30, 40]; let filtered = arr.filter(num => num > 25); console.log(filtered); // 输出 [30, 40]

使用场景:

  • 从数组中筛选符合特定条件的元素。
  • 清理数组中的无效数据(如 nullundefined 或空字符串)。
  • 提取符合特定标准的数据子集。

filter 方法不会修改原数组,而是返回一个新的数组,因此它非常适合用于保持数据的不可变性(immutability)。

Math: round, floor, ceil

在 JavaScript 中,Math.roundMath.floorMath.ceil 是用于对数字进行不同方式的取整操作的三个常用方法。它们各自的功能如下:

1. Math.round()

Math.round() 方法用于将数字四舍五入到最接近的整数。

语法:

javascript
Math.round(number);

参数:

  • number:要四舍五入的数字。

返回值:

返回最接近的整数。如果小数部分等于或大于 0.5,则向上舍入;如果小数部分小于 0.5,则向下舍入。

例子:

javascript
Math.round(4.7); // 返回 5 Math.round(4.4); // 返回 4 Math.round(4.5); // 返回 5

2. Math.floor()

Math.floor() 方法用于向下取整,即返回小于或等于给定数字的最大整数。

语法:

javascript
Math.floor(number);

参数:

  • number:要向下取整的数字。

返回值:

返回小于或等于给定数字的最大整数。

例子:

javascript
Math.floor(4.7); // 返回 4 Math.floor(4.4); // 返回 4 Math.floor(-4.7); // 返回 -5

3. Math.ceil()

Math.ceil() 方法用于向上取整,即返回大于或等于给定数字的最小整数。

语法:

javascript
Math.ceil(number);

参数:

  • number:要向上取整的数字。

返回值:

返回大于或等于给定数字的最小整数。

例子:

javascript
Math.ceil(4.1); // 返回 5 Math.ceil(4.9); // 返回 5 Math.ceil(-4.1); // 返回 -4

总结

  • Math.round():四舍五入,返回最接近的整数。
  • Math.floor():向下取整,返回小于或等于给定数字的最大整数。
  • Math.ceil():向上取整,返回大于或等于给定数字的最小整数。

提取URL的参数

在 JavaScript 中,可以通过多种方式从 URL 中提取参数。以下是几种常用的方法:

方法 1: 使用 URLSearchParams

URLSearchParams 是一个用于处理 URL 查询参数的内置对象,非常方便提取和操作 URL 参数。

示例:

假设有以下 URL:

javascript
let url = "https://example.com?page=2&sort=asc&filter=active";

要提取 URL 中的参数,可以这样做:

javascript
let params = new URLSearchParams(new URL(url).search); // 获取特定参数值 let page = params.get('page'); // "2" let sort = params.get('sort'); // "asc" let filter = params.get('filter'); // "active" // 遍历所有参数 for (let [key, value] of params) { console.log(`${key}: ${value}`); }

方法 2: 手动解析 URL

如果不想使用 URLSearchParams,可以手动解析 URL 查询字符串:

示例:

javascript
let url = "https://example.com?page=2&sort=asc&filter=active"; function getQueryParams(url) { let queryString = url.split('?')[1]; if (!queryString) { return {}; } return queryString.split('&').reduce((acc, param) => { let [key, value] = param.split('='); acc[decodeURIComponent(key)] = decodeURIComponent(value); return acc; }, {}); } let params = getQueryParams(url); // 获取特定参数值 console.log(params.page); // "2" console.log(params.sort); // "asc" console.log(params.filter); // "active"

方法 3: 使用正则表达式

你还可以使用正则表达式来提取特定的 URL 参数。这种方法比较灵活,但可能需要更复杂的正则表达式来处理不同的情况。

示例:

javascript
let url = "https://example.com?page=2&sort=asc&filter=active"; function getParamByName(name, url) { name = name.replace(/[\[\]]/g, "\\$&"); let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, " ")); } let page = getParamByName('page', url); // "2" let sort = getParamByName('sort', url); // "asc" let filter = getParamByName('filter', url); // "active"

总结

  • 推荐使用 URLSearchParams,因为它是现代浏览器中处理 URL 参数的标准方法,简单且强大。
  • 手动解析 URL 适用于不依赖现代 API 的场景,代码相对灵活。
  • 正则表达式 可以用于处理更复杂或特定的 URL 格式,但一般不如前两种方法直观。

深拷贝和浅拷贝

一般有两种数据类型:

  1. 基本类型(存在栈内存中)
  2. 引用类型(存在堆内存中)
  • 浅拷贝:只复制对象或数组的第一层属性,嵌套对象的引用不变。常用方法:Object.assign() 和展开运算符(...)。引用类型只会拷贝它的内存地址。

  • 深拷贝:递归复制所有层级的属性,生成一个完全独立的新对象。常用方法:JSON.parse(JSON.stringify()) 或使用 lodash_.cloneDeep()。拷贝后的对象与原始对象互不影响。

浅拷贝适用于简单结构,深拷贝适用于需要完全独立副本的复杂结构。

虚拟DOM

虚拟DOM(Virtual DOM)是JavaScript对象的一个抽象表示,类似于实际的DOM结构。它是UI的轻量级副本,存在于内存中。虚拟DOM的主要目的是优化实际DOM的操作,从而提高性能。

什么是虚拟DOM?

  • 虚拟DOM是对真实DOM的抽象表示,它使用JavaScript对象树来表示UI的结构。
  • 当UI发生变化时,框架(如React)首先在虚拟DOM上应用这些变化,而不是直接操作真实DOM。
  • 虚拟DOM更新后,会通过算法(如React中的“diffing”算法)将虚拟DOM和真实DOM进行对比,找到最小的差异,然后只更新实际DOM中发生变化的部分。

为什么需要虚拟DOM?

  1. 提高性能

    • 直接操作真实DOM的开销较大,尤其是频繁操作DOM时,性能会受到影响。
    • 虚拟DOM通过批量更新和最小化操作次数,减少了真实DOM的操作,从而提高了性能。
  2. 跨平台兼容性

    • 虚拟DOM是一种抽象概念,框架可以根据不同的平台(如浏览器、服务器、移动设备)生成相应的UI,确保跨平台的一致性。
  3. 简化编程模型

    • 虚拟DOM提供了一种声明式编程模型,开发者可以更专注于描述UI的状态,而不需要手动管理DOM更新和优化,这简化了开发过程。

PWA

PWA(Progressive Web App,渐进式Web应用)是一种结合了网页和原生应用优点的Web应用程序。PWA旨在通过现代Web技术为用户提供与原生应用类似的体验,同时保留网页的灵活性和可访问性。

PWA 的核心特性:

  1. 渐进式

    • PWA 可以逐步增强,即使在老旧的浏览器中也能运行,随着浏览器的更新,功能会逐步增强。
  2. 响应式

    • PWA 能够在各种设备上流畅运行,无论是桌面、手机还是平板,用户都能获得良好的体验。
  3. 离线可用

    • 通过 Service Worker(一个独立于网页运行的脚本),PWA 可以缓存资源,使应用即使在没有网络连接时也能运行。
  4. 类似原生应用的体验

    • PWA 可以被添加到设备的主屏幕,用户可以像使用原生应用一样直接打开它,启动时没有浏览器的地址栏,并且支持全屏模式。
  5. 安全性

    • PWA 必须在 HTTPS 环境下运行,以确保数据传输的安全性。
  6. 推送通知

    • PWA 支持 Web 推送通知功能,即使应用未打开,用户也能接收到通知。
  7. 自动更新

    • PWA 可以自动更新,而不需要用户进行手动操作,确保应用始终保持最新状态。
  8. 可发现性

    • PWA 可以通过搜索引擎被索引,因此它们能够被用户通过普通的Web搜索引擎发现。

PWA 的优势:

  • 跨平台:一个 PWA 应用可以在所有支持现代浏览器的平台上运行,减少了开发和维护多个平台应用的成本。
  • 安装简便:用户无需通过应用商店下载和安装,PWA 可以直接通过浏览器添加到主屏幕,体验接近原生应用。
  • 占用资源少:相比原生应用,PWA 占用设备存储空间较少,运行时资源消耗也较低。
  • 离线能力:PWA 能够在网络连接较差甚至没有网络的情况下提供基础功能。

PWA 的实际应用场景:

  • 内容发布平台:如新闻网站、博客、杂志等,用户可以在没有网络时仍然访问已加载的内容。
  • 电商平台:购物网站可以通过PWA提升用户体验,增加用户粘性,尤其在移动设备上。
  • 社交应用:如消息类应用,PWA 可以提供即时推送和离线消息查看功能。

Promise 简介

是异步编程解决方案,避免回调地狱。是一个构造函数,接受一个函数作为参数,返回promise实例。构建promise时,内部代码立刻执行。

它是个对象,也是个容器,保存着未来才会结束的事件。对象状态不受外界影响,只有异步操作的结果可以决定当前是哪种状态。

三种状态:

  • pending:进行中
  • resolved:已完成
  • rejected:已拒绝

状态都是不可逆的。

  1. pending -> fulfilled: resolved 已完成
  2. pending -> rejected: rejected 已拒绝

GET与POST

JS
function getData(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function () { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { reject(`Error: ${xhr.status}`); } }; xhr.onerror = function () { reject('Network Error'); }; xhr.send(); }); } // 使用示例 getData('https://api.example.com/data') .then(data => console.log(data)) .catch(error => console.error(error));
JS
function postData(url, data) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); xhr.onload = function () { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { reject(`Error: ${xhr.status}`); } }; xhr.onerror = function () { reject('Network Error'); }; xhr.send(JSON.stringify(data)); }); } // 使用示例 postData('https://api.example.com/data', { key: 'value' }) .then(response => console.log(response)) .catch(error => console.error(error));

缺点

  1. 无法取消promise

Promise的值穿透

Promise 值穿透(Promise Value Throttling)是指在 Promise 链中,即使某个 thencatch 回调函数中没有显式地返回值,Promise 也会将前一个 then 中的结果值“穿透”到下一个 thencatch 回调函数中。

示例说明:

javascript
const promise = new Promise((resolve, reject) => { resolve('Initial Value'); }); promise .then(value => { console.log(value); // 输出: 'Initial Value' // 没有返回值,相当于返回 undefined }) .then(value => { console.log(value); // 输出: 'Initial Value' });

解释:

  1. 第一个 then

    • resolve('Initial Value') 使得 Promise 进入 fulfilled 状态,并将 'Initial Value' 作为参数传递给第一个 then
    • 第一个 then 中虽然没有显式地返回值,但默认返回了 undefined
  2. 值穿透

    • 由于第一个 then 没有显式返回值(即没有返回 Promise 或其他值),Promise 会将前一个 then 的值(即 'Initial Value')继续传递给下一个 then,形成值的“穿透”。
  3. 第二个 then

    • 第二个 then 收到了第一个 then 中的值 'Initial Value',并继续处理这个值。

为什么会有值穿透?

值穿透的行为是 JavaScript Promise 的一种默认机制,目的是简化链式调用。它确保即使中间某些 thencatch 没有返回值或未对值进行处理,整个链中的后续操作仍然可以接收到上一个有效的返回值。

实际应用场景:

值穿透在一些特定的应用场景中很有用,例如,你希望在中间处理某些副作用(如日志记录),但不改变或干扰 Promise 链中传递的值。

javascript
promise .then(value => { console.log('Logging:', value); // 记录日志 // 不改变值的传递 }) .then(value => { console.log('Received:', value); // 依然能接收到原始值 });

在这个例子中,虽然第一个 then 中没有返回值,第二个 then 依然能够接收到最初的 'Initial Value',这就是值穿透的作用。

JavaScript脚本延迟加载

延迟加载:等页面加载完成后再加载JS,有助于提高页面加载速度。

1. defer 属性

defer 属性用于将脚本的执行延迟到整个页面解析完成之后。具有 defer 属性的脚本会按照它们在文档中出现的顺序加载并执行。它适用于不会依赖于 DOM 结构的脚本。

用法:

html
<script src="script.js" defer></script>

特点:

  • 脚本会与 HTML 并行加载,但在 HTML 解析完成后才会执行。
  • 按照脚本在文档中的顺序执行,适合依赖顺序的脚本。
  • 适用于需要在 DOM 完全构建后执行的脚本。

2. async 属性

async 属性用于异步加载脚本。脚本会在加载完成后立即执行,而不必等待 HTML 的解析完成。这种方式适合不依赖其他脚本或 DOM 结构的独立脚本。

用法:

html
<script src="script.js" async></script>

特点:

  • 脚本与 HTML 并行加载,并在加载完成后立即执行。
  • 执行顺序不一定按照它们在文档中的顺序,因此不适用于依赖顺序的脚本。
  • 适合用于独立的小脚本或分析、广告等不依赖于其他脚本的功能。

3. 动态创建脚本元素

通过 JavaScript 动态创建 <script> 元素,并将其添加到 DOM 中,可以实现延迟加载脚本。这种方法通常用于条件加载脚本或在用户触发某个事件后再加载脚本。

用法:

javascript
function loadScript(url) { const script = document.createElement('script'); script.src = url; document.body.appendChild(script); } // 在某个事件发生时加载脚本 loadScript('script.js');

特点:

  • 可以在任意时间点或事件触发时加载脚本。
  • 适合按需加载脚本,减少初始加载的开销。

4. 放置脚本标签在页面底部

<script> 标签放在 HTML 文档的底部(即 </body> 之前),可以确保页面的主要内容(HTML 和 CSS)在 JavaScript 加载和执行之前先行加载和显示。

用法:

html
<!-- HTML 内容 --> <script src="script.js"></script> </body>

特点:

  • 确保页面内容优先加载,避免脚本阻塞渲染。
  • 适合在需要确保页面内容优先加载的情况下使用。

5. 懒加载脚本

懒加载是指只有在需要时才加载脚本,比如当用户滚动到页面的某个部分时或在某个特定事件触发时加载。

用法:

懒加载可以通过动态创建 <script> 标签或使用第三方库(如 IntersectionObserver)来实现。

javascript
document.addEventListener('scroll', function() { if (document.documentElement.scrollTop > 200) { loadScript('lazyload.js'); } });

特点:

  • 仅在必要时加载脚本,进一步减少页面初始加载时间。
  • 适合加载较大、影响性能的脚本,如复杂的动画、图表等。

异步编程方式

在 JavaScript 中,异步编程是处理长时间运行任务(如网络请求、文件读取、计时器等)而不阻塞主线程的关键技术。异步编程可以通过多种方式实现,以下是常见的几种异步编程实现方式:

1. 回调函数(Callbacks)

回调函数是最基础的异步编程方式。一个函数在执行异步操作时,将另一个函数(回调函数)作为参数传递进去,待异步操作完成后,回调函数会被调用。

示例:

javascript
function fetchData(callback) { setTimeout(() => { const data = "Some data"; callback(data); }, 1000); } fetchData(function(result) { console.log(result); // 输出: Some data });

优点:

  • 简单直观,适合简单的异步操作。

缺点:

  • 如果有多个异步操作嵌套,容易出现“回调地狱”(Callback Hell),代码难以维护。

2. Promise

Promise 是 ES6 引入的一种更现代的异步编程方式。Promise 对象代表一个异步操作的最终完成或失败,并返回一个值。

示例:

javascript
function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = "Some data"; resolve(data); }, 1000); }); } fetchData() .then(result => { console.log(result); // 输出: Some data }) .catch(error => { console.error(error); });

优点:

  • 消除回调地狱,通过链式调用使代码更具可读性。
  • 提供 thencatchfinally 方法,方便处理异步操作的不同状态。

缺点:

  • 仍然可能导致复杂的链式结构,尤其是多个异步操作依赖时。

3. Async/Await

async/await 是基于 Promise 的语法糖,提供了更简洁和同步风格的异步代码编写方式,使代码更易读。

示例:

javascript
async function fetchData() { return new Promise((resolve) => { setTimeout(() => { resolve("Some data"); }, 1000); }); } async function main() { try { const result = await fetchData(); console.log(result); // 输出: Some data } catch (error) { console.error(error); } } main();

优点:

  • 代码结构更接近同步操作,易读易维护。
  • 错误处理更直观,可以使用 try/catch

缺点:

  • 需要注意 await 的位置,可能导致等待非必要的异步操作完成。

4. 生成器(Generators)

生成器是另一种控制异步流程的方式,通过 function* 定义生成器函数,并使用 yield 暂停和恢复执行。与异步函数结合使用时,可以通过外部库(如 co)来实现异步操作。

示例:

javascript
function* fetchData() { const data = yield new Promise((resolve) => { setTimeout(() => resolve("Some data"), 1000); }); console.log(data); // 输出: Some data } const generator = fetchData(); const promise = generator.next().value; promise.then(result => { generator.next(result); });

优点:

  • 生成器与 yield 可以让你在异步代码中间暂停执行,写出看起来像同步的代码。
  • 提供了更强大的流程控制。

缺点:

  • 生成器本身不是异步的,需要结合其他工具(如 co)实现异步操作,且不如 async/await 直观。

pnpm

跟npm一样是一个包管理器,但是具有如下优势:

  1. 包安装速度很快
  2. 磁盘利用高效

函数式编程

函数式编程(Functional Programming, FP)是一种编程范式,它强调使用纯函数和不可变数据来构建程序,避免使用共享状态和副作用。函数式编程通常被认为是一种更加声明式的编程方式,重点在于“做什么”而不是“怎么做”。

核心概念

  1. 纯函数(Pure Functions)

    • 定义:纯函数是指给定相同的输入,永远会得到相同的输出,并且在执行过程中不产生任何副作用。
    • 无副作用:纯函数不会修改全局状态,也不会依赖外部状态(例如,全局变量、I/O 操作等)。

    示例

    javascript
    function add(a, b) { return a + b; }

    add 函数是一个纯函数,它总是返回相同的结果,不依赖或改变外部状态。

  2. 不可变性(Immutability)

    • 定义:不可变性是指数据一旦创建就不能被改变。所有对数据的操作都会返回一个新的数据副本,而不是直接修改原数据。
    • 优点:不可变性使得数据状态更加可预测,避免了因共享状态和副作用导致的错误。

    示例

    javascript
    const arr = [1, 2, 3]; const newArr = arr.concat(4); // 不修改原数组,返回新数组 [1, 2, 3, 4]
  3. 函数作为一等公民(First-class Functions)

    • 定义:在函数式编程中,函数被视为一等公民。函数可以像其他数据类型一样被传递、返回或赋值给变量。
    • 高阶函数:高阶函数是指接受一个或多个函数作为参数,或者返回一个函数作为结果的函数。

    示例

    javascript
    function double(x) { return x * 2; } function applyFunction(fn, value) { return fn(value); } console.log(applyFunction(double, 5)); // 输出: 10
  4. 函数组合(Function Composition)

    • 定义:函数组合是将多个函数组合成一个新函数,其中每个函数的输出作为下一个函数的输入。
    • 好处:通过函数组合,复杂的操作可以被拆分为多个简单的函数,并以一种清晰的方式组合在一起。

    示例

    javascript
    const add = x => x + 1; const multiply = x => x * 2; const composedFunction = x => multiply(add(x)); console.log(composedFunction(5)); // 输出: 12
  5. 声明式编程

    • 定义:在函数式编程中,编程风格通常是声明式的。开发者更多地描述“做什么”而不是“怎么做”,通过组合函数来处理数据,而不是通过循环、条件语句等控制流来操控数据。

    示例

    javascript
    const numbers = [1, 2, 3, 4]; const squared = numbers.map(x => x * 2); console.log(squared); // 输出: [2, 4, 6, 8]

函数式编程的优点

  1. 代码简洁和可读性高

    • 通过使用纯函数和不可变数据,函数式编程使代码更加简洁、易于理解和维护。
  2. 容易测试和调试

    • 由于纯函数不依赖外部状态,也不改变外部状态,因此测试函数变得更容易,调试过程也更加简单。
  3. 并发和并行计算的优势

    • 不可变数据和无副作用的纯函数使得并发编程更加安全,减少了竞态条件和死锁的可能性。
  4. 避免共享状态和副作用

    • 函数式编程鼓励使用不可变数据和纯函数,减少了由于共享状态和副作用导致的错误。

函数式编程的缺点

  1. 学习曲线

    • 对于习惯了命令式编程的开发者来说,函数式编程可能有一定的学习曲线,尤其是在理解纯函数、不可变性和函数组合等概念时。
  2. 性能开销

    • 由于函数式编程强调不可变性,频繁创建新数据对象可能会导致性能开销,尤其是在处理大量数据时。
  3. 复杂度

    • 在某些情况下,过度使用函数组合和高阶函数可能会导致代码难以理解和维护。

CSR和SSR

CSR(Client-Side Rendering,客户端渲染)和 SSR(Server-Side Rendering,服务器端渲染)是两种常见的网页渲染技术,分别在客户端和服务器端处理 HTML 的生成。它们在处理网页的渲染方式、性能和用户体验上有不同的特点和适用场景。

1. CSR(Client-Side Rendering,客户端渲染)

什么是 CSR?

在客户端渲染中,初始 HTML 文件是由服务器发送到客户端的一个基础模板,通常包含很少的内容(可能只是一个空的 div 容器和脚本标签)。然后,JavaScript 框架(如 React、Vue 或 Angular)在客户端(浏览器)上加载和执行,将数据填充到模板中,生成最终的页面内容。

工作流程:

  1. 浏览器向服务器请求页面。
  2. 服务器返回一个基础 HTML 文件和相关的 JavaScript 文件。
  3. 浏览器加载 HTML,并通过 JavaScript 获取数据并动态生成页面内容。
  4. 页面内容在浏览器中渲染和显示。

优点:

  • 更好的用户交互:CSR 允许更丰富的用户交互体验,因为页面内容是由客户端动态生成和更新的,通常不需要重新加载整个页面。
  • 前端分离:前端和后端可以完全分离,前端只负责渲染和用户交互,后端提供数据接口。

缺点:

  • 首次加载时间较长:由于需要加载 JavaScript 并执行后生成内容,首次页面加载时间可能较长,尤其是在网络较慢或设备性能较低时。
  • SEO(搜索引擎优化)挑战:因为初始 HTML 内容非常少,搜索引擎爬虫可能无法获取完整的页面内容,导致 SEO 效果较差。不过,可以通过服务端渲染(SSR)或预渲染(Pre-rendering)来解决这个问题。

适用场景:

  • 适合需要复杂用户交互和动态更新内容的应用,例如单页应用(SPA)。
  • 在内容不依赖于 SEO 或 SEO 不那么重要的场景下。

2. SSR(Server-Side Rendering,服务器端渲染)

什么是 SSR?

在服务器端渲染中,HTML 是在服务器上生成的,然后完整的 HTML 页面发送到客户端。客户端收到完整的 HTML 文件后,直接渲染内容。这种方式通常用于传统的多页应用(MPA),以及通过 React、Vue 等框架进行 SSR 的现代应用。

工作流程:

  1. 浏览器向服务器请求页面。
  2. 服务器处理请求,生成完整的 HTML 页面。
  3. 服务器返回完整的 HTML 文件,浏览器直接渲染显示。
  4. 页面在浏览器中显示后,JavaScript 代码可能会接管页面,以实现更丰富的交互(通常称为“水合”过程,Hydration)。

优点:

  • 更快的首次加载:由于 HTML 是在服务器端生成的,浏览器可以更快地显示页面内容,改善用户体验,尤其是在首次访问时。
  • 更好的 SEO 支持:因为服务器生成完整的 HTML 页面,搜索引擎可以轻松地爬取和索引内容,从而改善 SEO。

缺点:

  • 服务器压力大:每次请求都需要服务器生成完整的 HTML 页面,增加了服务器的负载,尤其是在高并发情况下。
  • 复杂的缓存策略:由于每次请求都生成新的 HTML 内容,缓存变得更复杂,尤其是在需要处理大量动态数据时。
  • 前后端紧耦合:SSR 需要前端和后端紧密协作,增加了开发和维护的复杂性。

适用场景:

  • 适合需要快速加载和良好 SEO 的内容驱动型网站,如新闻网站、博客等。
  • 适合初次加载速度非常重要的场景,例如营销页面或首页展示。

3. CSR 和 SSR 的混合使用

在实际开发中,许多现代应用会结合 CSR 和 SSR 的优点,采用混合渲染策略。例如,使用 SSR 提供快速的初次加载和良好的 SEO,之后在客户端使用 CSR 处理动态交互和页面更新。

闭包

闭包(Closure)是 JavaScript 中一个非常重要的概念,也是理解 JavaScript 作用域和函数行为的关键。闭包使得函数可以访问它外部的变量,即使这个函数在它的外部环境中被调用。

闭包的定义

闭包是指一个函数可以访问它在声明时所在的词法作用域,即使这个函数是在其词法作用域之外执行。换句话说,闭包是指函数与其词法环境的组合

闭包的形成

闭包形成的条件是:

  1. 函数嵌套:函数在另一个函数内部定义。
  2. 函数内部引用了外部函数的变量:内部函数访问了外部函数中的变量。

闭包的示例

以下是一个简单的闭包示例:

javascript
function outerFunction() { let outerVariable = 'I am from outer scope'; function innerFunction() { console.log(outerVariable); // 访问外部函数的变量 } return innerFunction; } const closure = outerFunction(); // 执行外部函数,返回内部函数 closure(); // 调用内部函数,输出:I am from outer scope

在这个示例中:

  • innerFunctionouterFunction 内部定义的一个函数,因此它可以访问 outerFunction 的变量 outerVariable
  • 即使 outerFunction 已经执行完毕并退出了,但通过闭包,innerFunction 依然保持对 outerVariable 的访问权。

闭包的应用场景

  1. 数据私有化

    • 闭包可以用于创建私有变量,防止变量被全局访问或修改。这在模块模式中非常常见。

    示例

    javascript
    function counter() { let count = 0; return function() { count += 1; return count; }; } const increment = counter(); console.log(increment()); // 输出: 1 console.log(increment()); // 输出: 2

    在这个例子中,count 是一个私有变量,只有通过闭包函数 increment 才能访问和修改它。

  2. 回调函数和事件处理

    • 闭包在回调函数和事件处理程序中非常常用,它允许回调函数访问外部函数中的变量。

    示例

    javascript
    function setupEventHandlers() { let message = 'Hello, World!'; document.getElementById('myButton').addEventListener('click', function() { alert(message); }); } setupEventHandlers();

    在这个例子中,事件处理函数访问了外部函数中的变量 message

  3. 函数工厂

    • 闭包可以用于创建一组相关的函数,例如函数工厂。

    示例

    javascript
    function createMultiplier(multiplier) { return function(value) { return value * multiplier; }; } const double = createMultiplier(2); const triple = createMultiplier(3); console.log(double(5)); // 输出: 10 console.log(triple(5)); // 输出: 15

    在这个例子中,createMultiplier 函数返回一个闭包,每个闭包都有自己的 multiplier 参数,允许创建不同的乘数函数。

闭包的优缺点

优点:

  • 数据封装:闭包可以帮助创建私有变量,增强数据的封装性。
  • 延迟执行:通过闭包,可以保存外部函数中的变量,直到内部函数被调用时才执行。
  • 模块化:闭包是实现模块模式的基础,可以帮助将代码组织得更为模块化和清晰。

缺点:

  • 内存泄漏:由于闭包引用了外部函数的变量,可能导致这些变量在不再需要时仍然被保存在内存中,从而引发内存泄漏。
  • 调试复杂性:闭包有时会使代码逻辑变得复杂,特别是在嵌套过深或闭包数量较多的情况下,可能导致调试变得困难。

总结

闭包是 JavaScript 中的一个强大特性,它允许函数“记住”并访问其词法作用域,即使函数在该作用域之外执行。理解闭包对于编写更健壮、模块化的 JavaScript 代码至关重要,同时也需要注意它可能带来的内存管理和调试问题。

本文作者:Jeff Wu

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!