原理:通过创建一个虚拟的 window 对象,将子应用的代码运行在这个虚拟环境中,从而实现与主应用和其他子应用的隔离。
示例代码:
function createSandbox() {
const sandbox = {
window: {},
document: {},
};
const code = `
// 子应用代码
function init() {
window.myVariable = 'Hello from subapp';
console.log(window.myVariable);
}
init();
`;
const script = new Function('window', 'document', code);
script(sandbox.window, sandbox.document);
return sandbox;
}
const subappSandbox = createSandbox();
console.log(subappSandbox.window.myVariable); // 输出: Hello from subapp
console.log(window.myVariable); // 输出: undefined
在 JavaScript 里,内存泄漏和垃圾回收是十分关键的概念,它们和程序的性能与稳定性紧密相连。下面为你详细介绍这两个概念。
JavaScript 是一门采用自动内存管理的语言,也就是垃圾回收机制(Garbage Collection,简称 GC)。该机制的主要功能是自动找出那些不再被使用的对象,然后释放它们所占用的内存。
- 标记清除算法(Mark and Sweep)
这是最基础的垃圾回收算法。它的工作流程如下:
- 标记阶段:垃圾回收器会从根对象(像全局对象、函数调用栈等)开始,对所有可达对象进行标记。
- 清除阶段:把那些未被标记的对象视为垃圾,释放它们所占用的内存。
- 标记整理算法(Mark and Compact) 该算法是在标记清除算法的基础上发展而来的,它在清除阶段之后增加了整理内存的步骤,也就是把存活的对象往内存的一端移动,以此来减少内存碎片。
- 引用计数算法(Reference Counting) 为每个对象维护一个引用计数,当有新的引用指向该对象时,引用计数加 1;当引用被移除时,引用计数减 1。一旦引用计数变为 0,就表明该对象不再被使用,其内存会被释放。不过,这种算法无法处理循环引用的问题。
// 创建一个对象
let obj = { name: 'John' };
// 此时 obj 指向的对象有一个引用
// 移除引用
obj = null;
// 此时 obj 指向的对象没有引用了,垃圾回收器会在合适的时候回收该对象的内存
JavaScript 的垃圾回收器会在以下几种情况下执行标记清除算法:
- 内存不足:当可用内存不足时,垃圾回收器会开始执行。
- 手动触发:你可以通过调用
global.gc()
来手动触发垃圾回收。 - 定时触发:垃圾回收器会在后台定期执行,这是一种自动的方式。
内存泄漏指的是程序在运行过程中,一些不再使用的内存没有被释放,从而导致内存占用持续增加,最终可能引发程序性能下降甚至崩溃。
- 全局变量
在全局作用域中声明变量,如果不手动释放,这些变量会一直存在于内存中。
// 全局变量
window.globalVar = { data: [1, 2, 3, 4, 5] };
// 若后续不再使用 globalVar,但未将其置为 null,它会一直占用内存
- 未清理的定时器
如果定时器在不再需要时没有被清除,它会持续占用内存。
// 创建一个定时器
const intervalId = setInterval(() => {
console.log('This is a timer');
}, 1000);
// 如果后续不再需要这个定时器,却没有清除它
// clearInterval(intervalId);
- 闭包
闭包会引用其外部函数的变量,若闭包一直存在,这些变量就不会被释放。
function outer() {
let data = [1, 2, 3];
return function inner() {
return data;
};
}
const closure = outer();
// 由于 closure 这个闭包一直存在,它引用的 data 数组不会被释放
- DOM 元素引用
如果对 DOM 元素的引用没有被清除,即使该元素已经从 DOM 树中移除,它仍然会占用内存。
const element = document.getElementById('myElement');
// 假设后续将该元素从 DOM 树中移除
document.body.removeChild(element);
// 但由于 element 变量仍然引用该元素,它不会被释放
- 避免使用全局变量:尽可能在局部作用域中声明变量。
- 及时清理定时器:在不需要定时器时,使用
clearInterval
或clearTimeout
清除它们。 - 合理使用闭包:在闭包不再使用时,手动解除对外部变量的引用。
- 清除 DOM 元素引用:在 DOM 元素从 DOM 树中移除后,将对该元素的引用置为
null
。
React 是一个用于构建用户界面的 JavaScript 库,其渲染原理主要涉及虚拟 DOM、协调(Reconciliation)和渲染三个核心过程。下面为你详细介绍:
虚拟 DOM 是一种轻量级的 JavaScript 对象,它是真实 DOM 的抽象表示。在 React 中,每个组件都可以看作是一个函数,该函数接收一些输入(props),并返回一个描述 UI 结构的虚拟 DOM 树。
- 提高性能:直接操作真实 DOM 的代价比较高,因为每次操作都会引发浏览器的重排和重绘。而虚拟 DOM 的操作是在内存中进行的,相对来说代价较低。React 通过比较新旧虚拟 DOM 树的差异,只更新需要更新的真实 DOM 节点,从而减少了对真实 DOM 的操作次数。
- 跨平台:虚拟 DOM 是一个抽象的概念,不依赖于具体的平台。这使得 React 可以在不同的环境中使用,如浏览器、服务器端(SSR)、移动端(React Native)等。
// 一个简单的 React 组件
import React from 'react';
const App = () => {
return (
<div>
<h1>Hello, React!</h1>
</div>
);
};
// 这里的 JSX 代码会被转换为虚拟 DOM 对象
// 类似于下面的形式
const virtualDOM = React.createElement('div', null,
React.createElement('h1', null, 'Hello, React!')
);
协调是 React 比较新旧虚拟 DOM 树的过程,目的是找出两者之间的差异,从而确定需要对真实 DOM 进行哪些更新操作。这个过程也被称为“Diffing 算法”。
- 树比较:React 会逐层比较新旧虚拟 DOM 树的节点。如果节点的类型不同(如从
<div>
变成<p>
),React 会直接替换整个子树。 - 组件比较:对于组件节点,如果组件的类型相同,React 会复用该组件实例,并更新其 props;如果组件的类型不同,React 会销毁旧组件,创建新组件。
- 列表比较:当处理列表时,React 会使用
key
属性来帮助识别每个元素。如果列表中的元素没有key
,React 会默认按照索引来比较,这可能会导致性能问题。而使用唯一的key
可以帮助 React 更高效地识别哪些元素被添加、删除或移动。
import React, { useState } from 'react';
const List = () => {
const [items, setItems] = useState([1, 2, 3]);
const addItem = () => {
setItems([...items, items.length + 1]);
};
return (
<div>
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<button onClick={addItem}>Add Item</button>
</div>
);
};
export default List;
在这个例子中,当点击按钮添加新元素时,React 会根据 key
属性来比较新旧虚拟 DOM 树,从而确定只需要在列表末尾添加一个新的 <li>
元素,而不是重新渲染整个列表。
渲染是将协调过程中确定的差异应用到真实 DOM 上的过程。React 会根据协调结果,对真实 DOM 进行插入、更新或删除等操作,从而实现 UI 的更新。
- 初始渲染:当组件首次渲染时,React 会根据组件返回的虚拟 DOM 树创建对应的真实 DOM 节点,并将其插入到页面中。
- 更新渲染:当组件的状态(state)或属性(props)发生变化时,React 会重新调用组件函数,生成新的虚拟 DOM 树。然后通过协调过程找出新旧虚拟 DOM 树的差异,最后将这些差异应用到真实 DOM 上。
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);
在这个例子中,当点击按钮调用 increment
函数时,count
状态会发生变化,React 会重新渲染 Counter
组件,生成新的虚拟 DOM 树。然后通过协调过程找出差异,最后将更新后的 count
值反映到真实 DOM 上。
React 的渲染原理主要包括虚拟 DOM、协调和渲染三个核心过程。虚拟 DOM 作为真实 DOM 的抽象表示,在内存中进行操作,提高了性能和跨平台性;协调过程通过 Diffing 算法比较新旧虚拟 DOM 树的差异;渲染过程将这些差异应用到真实 DOM 上,实现 UI 的更新。通过这些机制,React 能够高效地管理和更新用户界面。
Monorepo 通过符号链接、虚拟包和依赖管理工具实现包共享