前端
Web前端开发是从网页制作演变而来的,名称上有很明显的时代特征。在互联网的演化进程中,网页制作是Web 1.0时代的产物,那时网站的主要内容都是静态的,用户使用网站的行为也以浏览为主。2005年以后,互联网进入Web 2.0时代,各种类似桌面软件的Web应用大量涌现,网站的前端由此发生了翻天覆地的变化。网页不再只是承载单一的文字和图片,各种富媒体让网页的内容更加生动,网页上软件化的交互形式为用户提供了更好的使用体验,这些都是基于前端技术实现的。
查看更多相关内容
prototype 和proto区别
在JavaScript中,`prototype`属性和`__proto__`属性(通常读作"proto")是有关于对象原型链的概念,但它们在使用和目的上有所不同。
### `prototype`属性
`prototype`是函数对象(Function objects)的一个属性。当你使用构造函数创建一个新对象时,这个新对象的内部`[[Prototype]]`(也就是它的`__proto__`属性)会被赋值为构造函数的`prototype`属性。这意味着,使用同一个构造函数创建的所有对象都会共享同一个`prototype`对象。
举个例子,如果我们有一个构造函数:
```javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
```
当我们创建一个`Person`实例时:
```javascript
var person1 = new Person("Alice");
```
`person1`对象的`[[Prototype]]`(即`__proto__`)会指向`Person.prototype`,这使得`person1`能够访问到`sayHello`方法。
### `__proto__`属性
`__proto__`是每个JavaScript对象都拥有的一个内部属性,它指向该对象的原型。这是一个从对象指向其构造函数的`prototype`属性的链接。根据ECMAScript标准,`__proto__`是`[[Prototype]]`的实现,而`[[Prototype]]`是对象的内部属性。在现代JavaScript开发中,通常推荐使用`Object.getPrototypeOf(obj)`来获取对象的原型,而不是直接使用`__proto__`,因为`__proto__`并不是所有JavaScript环境中都得到支持。
再次拿刚才的例子,`person1.__proto__`会指向`Person.prototype`,因为`person1`是由`Person`构造函数创建的。
### 小结
- `prototype`是函数特有的属性,用于当作构造函数时为实例对象指定原型。
- `__proto__`是每个对象都有的属性,指向该对象的原型。
在实践中,`prototype`用来实现基于原型的继承和共享属性/方法,而`__proto__`提供了一种访问和操作对象原型链的方式。然而,直接操作`__proto__`被视为不太安全的做法,尤其是在现代JavaScript编程中,应该利用`Object.getPrototypeOf()`和`Object.setPrototypeOf()`等方法来替代`__proto__`的直接使用。
前端 · 8月9日 17:42
script 标签的 defer 和 async 有什么区别?
当您在 HTML 文档中使用 `<script>` 标签引入 JavaScript 时,`defer` 和 `async` 属性可以控制脚本的加载和执行方式,它们之间的区别主要在于脚本加载的时间以及执行的时机。
### defer 属性
使用 `defer` 属性的 `<script>` 标签会让脚本在文档解析期间异步下载,但是会延迟到整个文档解析完毕之后、DOMContentLoaded 事件触发之前执行。这意味着带有 `defer` 的脚本总是在文档解析完成之后执行,保证了执行时 DOM 已经完全构建好。
#### 例子:
```html
<script src="example.js" defer></script>
```
如果您有多个带有 `defer` 属性的脚本,它们将按照在文档中出现的顺序执行,即便有些脚本可能会比其他脚本更早下载完成。
### async 属性
而 `async` 属性也允许脚本在文档解析时异步下载,但是它一旦下载完成就会立即执行,这可能会在文档的其余部分尚未解析完毕时发生。因此,使用 `async` 的脚本不能保证按照在页面中出现的顺序执行,也无法保证 DOM 完全构建完成。
#### 例子:
```html
<script src="example.js" async></script>
```
`async` 适用于那些不依赖于其他脚本且不依赖于 DOM 的脚本,例如,广告加载或者埋点脚本。
### 总结
- `defer` 确保脚本在文档完全解析和 DOM 构建完成后,但在 DOMContentLoaded 事件之前执行。
- `async` 确保脚本在下载完成后尽快执行,但可能会打断文档的解析过程。
- 没有这两个属性的 `<script>` 标签会立即下载并阻塞文档解析直到脚本执行完成。
在实际应用中,选择 `defer` 或 `async` 取决于脚本对文档解析的依赖性,以及脚本之间的依赖关系。如果您需要确保脚本按照顺序执行,并且在 DOM 完全构建后执行,那么 `defer` 是更好的选择。如果脚本的执行顺序不重要,并且想尽快获取并执行脚本,可以使用 `async`。
前端 · 8月5日 12:52
javascript 中垃圾回收的方法有哪些?
JavaScript中的垃圾回收(garbage collection)是一种自动内存管理机制,它帮助开发者不需要手动释放分配的内存。在JavaScript中,垃圾回收主要采用了以下几种方法:
### 1. 标记清除(Mark and Sweep)
这是最常见的垃圾回收算法。当变量进入环境时,就“标记”这个变量为“进入环境”。当变量离开环境时,则“标记”这个变量为“离开环境”。垃圾收集器会定期运行,它会检查所有的变量,以及它们引用的其他变量是否还在环境中。如果一个变量已经不再环境中,且没有任何其他变量引用它,那么这个变量占用的内存就会被回收。
#### 例子:
```javascript
function processData() {
var data = { /* 大量数据 */ };
// 使用data进行处理
}
processData();
// processData执行完毕后,data变量离开环境,变成无法访问的状态,会被标记为可回收。
```
### 2. 引用计数(Reference Counting)
引用计数是另一种垃圾回收机制。在这个系统中,每一个值都有一个“引用数”,表示有多少变量或资源引用这个值。如果引用数变为0,则表示该值不再需要,其占用的内存可以被回收。这种方法的一个问题是循环引用:如果两个对象互相引用,即便它们已经不再需要,它们的引用数也不会降到0,导致内存无法被回收。
#### 例子:
```javascript
function referenceCycle() {
var objectA = {};
var objectB = {};
objectA.other = objectB;
objectB.other = objectA;
}
referenceCycle();
// 即使referenceCycle函数执行结束,objectA和objectB因为相互引用,它们的引用数都不为0,造成内存泄漏。
```
### 3. 分代收集(Generational Collection)
分代收集是基于对象存活时间的假设,将对象分为两组:“新生代”和“老生代”。新创建的对象属于新生代,对象如果存活足够长的时间,就会被移动到老生代。通常新生代使用标记-复制(mark-copy)算法,老生代使用标记-清除(mark-sweep)或标记-整理(mark-compact)算法。
### 4. 标记-整理(Mark-Compact)
这种方法是对标记-清除的改进。在标记阶段,标记所有活动对象,然后在整理阶段,将所有活动的对象移动到内存的一端,然后清理掉边界之外的内存。
### 5. 增量收集(Incremental Collection)
增量收集是将垃圾回收分成小片段执行,每次只处理一部分对象,然后暂停,让程序执行。这种方式可以减少垃圾收集过程中的停顿时间。
### 6. 闲时收集(Idle-time Collection)
某些JavaScript引擎会利用CPU空闲时间来执行垃圾回收的工作,以避免影响到程序的执行效率。
前端 · 8月5日 12:52
React 如何做性能优化?有哪些常见手段?
React 在性能优化方面提供了多种策略和工具,以确保用户界面高效、平滑且响应迅速。以下是一些常用的性能优化手段:
### 1. 使用 `shouldComponentUpdate` 和 `React.PureComponent`
在类组件中,通过实现 `shouldComponentUpdate` 方法可以控制组件是否需要更新。当组件的状态或属性改变时,此方法会被调用,并根据返回的布尔值决定是否进行渲染。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">shouldComponentUpdate</span><span class="token">(</span><span class="token parameter">nextProps</span><span class="token parameter">,</span><span class="token parameter"> nextState</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">// 只有当特定的属性或状态改变时才更新组件</span><span>
</span><span> </span><span class="token control-flow">return</span><span> nextProps</span><span class="token">.</span><span class="token property-access">id</span><span> </span><span class="token">!==</span><span> </span><span class="token">this</span><span class="token">.</span><span class="token property-access">props</span><span class="token">.</span><span class="token property-access">id</span><span> </span><span class="token">||</span><span> nextState</span><span class="token">.</span><span class="token property-access">count</span><span> </span><span class="token">!==</span><span> </span><span class="token">this</span><span class="token">.</span><span class="token property-access">state</span><span class="token">.</span><span class="token property-access">count</span><span class="token">;</span><span>
</span><span></span><span class="token">}</span></code></div></section></pre>
对于那些拥有不可变的属性和状态的组件,可以使用 `React.PureComponent`,它通过浅比较 props 和 state 来减少不必要的渲染。
### 2. 使用 Hooks(如 `React.memo` 和 `useMemo`)
对于函数组件,`React.memo` 是一个高阶组件,它仅在组件的 props 发生变化时才会重新渲染组件。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> </span><span class="token maybe-class-name">MyComponent</span><span> </span><span class="token">=</span><span> </span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token method property-access">memo</span><span class="token">(</span><span class="token">function</span><span> </span><span class="token maybe-class-name">MyComponent</span><span class="token">(</span><span class="token parameter">props</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">/* 只有props改变时,组件才会重新渲染 */</span><span>
</span><span></span><span class="token">}</span><span class="token">)</span><span class="token">;</span></code></div></section></pre>
`useMemo` 和 `useCallback` 钩子可以用来缓存复杂计算的结果和回调函数,避免在每次渲染时都重新计算和创建新的函数实例。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> memoizedValue </span><span class="token">=</span><span> </span><span class="token">useMemo</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token">computeExpensiveValue</span><span class="token">(</span><span>a</span><span class="token">,</span><span> b</span><span class="token">)</span><span class="token">,</span><span> </span><span class="token">[</span><span>a</span><span class="token">,</span><span> b</span><span class="token">]</span><span class="token">)</span><span class="token">;</span><span>
</span><span></span><span class="token">const</span><span> memoizedCallback </span><span class="token">=</span><span> </span><span class="token">useCallback</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">// 一个依赖特定props的回调函数</span><span>
</span><span></span><span class="token">}</span><span class="token">,</span><span> </span><span class="token">[</span><span>props</span><span class="token">]</span><span class="token">)</span><span class="token">;</span></code></div></section></pre>
### 3. 避免不必要的 DOM 更新
当操作 DOM 时,应尽量减少更新次数和范围。可以使用虚拟列表(比如 `react-window` 或 `react-virtualized`)来仅渲染可视区域的元素,从而提高长列表的性能。
### 4. 懒加载组件和路由
使用 `React.lazy` 可以实现组件级别的代码拆分,这样可以将不同的组件打包成单独的代码块,并在需要时才加载它们。
同时,结合 `React Router` 的 `Suspense` 组件,可以实现路由级别的懒加载,仅当路由被访问时才加载对应的组件。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> </span><span class="token maybe-class-name">OtherComponent</span><span> </span><span class="token">=</span><span> </span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token method property-access">lazy</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token module">import</span><span class="token">(</span><span class="token">'./OtherComponent'</span><span class="token">)</span><span class="token">)</span><span class="token">;</span><span>
</span>
<span></span><span class="token">function</span><span> </span><span class="token maybe-class-name">MyComponent</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token control-flow">return</span><span> </span><span class="token">(</span><span>
</span><span> </span><span class="token"><</span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token property-access maybe-class-name">Suspense</span><span> fallback</span><span class="token">=</span><span class="token">{</span><span class="token"><</span><span>div</span><span class="token">></span><span class="token maybe-class-name">Loading</span><span class="token spread">...</span><span class="token"><</span><span class="token">/</span><span>div</span><span class="token">></span><span class="token">}</span><span class="token">></span><span>
</span><span> </span><span class="token"><</span><span class="token maybe-class-name">OtherComponent</span><span> </span><span class="token">/</span><span class="token">></span><span>
</span><span> </span><span class="token"><</span><span class="token">/</span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token property-access maybe-class-name">Suspense</span><span class="token">></span><span>
</span><span> </span><span class="token">)</span><span class="token">;</span><span>
</span><span></span><span class="token">}</span></code></div></section></pre>
### 5. 使用 Web Workers
对于复杂或计算密集型任务,可以使用 Web Workers 在后台线程中执行,避免阻塞主线程导致用户界面卡顿。
### 6. 优化条件渲染
避免不必要的渲染,例如,可以将条件渲染逻辑移到可能更改状态的事件处理函数中,而不是在渲染方法中进行。
### 7. 状态升级
将子组件的本地状态提升到父组件中,这样可以减少不必要的子组件渲染,因为状态的变化会集中处理。
### 8. 使用不可变数据结构
使用不可变数据可以更容易地检测到状态和属性的变化,这使得组件的更新检查更高效。库如 `Immutable.js` 可以用来帮助创建不可变数据。
### 9. 使用生产版本的 React
开发中通常使用的是开发版本的 React,它包含了许多有用的警告和错误信息。但在生产中,应该使用经过压
缩和优化的生产版本,它删除了这些额外的警告和检查,以减少库的大小并提升性能。
### 10. 分析和监控
使用性能分析工具,如 React DevTools 中的 Profiler,可以帮助识别渲染性能瓶颈。它可以记录组件的渲染时间,并帮助你找到可以优化的部分。
### 11. 避免内联对象和数组的传递
对于那些接收对象或数组作为 props 的组件,应避免在渲染方法中直接创建新的内联对象或数组,因为这会导致 props 始终不相等,从而触发不必要的渲染。
```javascript
<MyComponent items={[1, 2, 3]} /> // 每次渲染都会创建一个新的数组,不推荐这样做
// 更好的做法是在组件外部定义这个数组
const items = [1, 2, 3];
<MyComponent items={items} />
```
### 12. 使用键(keys)来优化列表渲染
当渲染列表时,应该为每个列表项指定一个唯一的 key。这有助于 React 确定哪些项已更改、添加或删除,从而提高列表渲染的效率。
```javascript
data.map((item) => <ListItem key={item.id} {...item} />)
```
### 13. 使用 Context 时的优化
当使用 React Context API 时,应该注意其可能对性能的影响。Context 的变动会导致所有消费该 Context 的组件重新渲染。为了避免不必要的渲染,可以分割 Context 或是使用 `useMemo` 跟 `useCallback` 来传递稳定的上下文值。
### 14. 避免过度渲染和过度传递 props
审视组件间的 props 传递,确保不会传递额外的 props。如果一个组件不需要某个 prop,那么就不应该传递它,因为这可能会导致不必要的组件渲染。
### 15. 服务器端渲染 (SSR)
服务器端渲染可以加快首次页面加载时间,并提升搜索引擎优化(SEO)。通过在服务器上生成 HTML,可以减少客户端的工作量,从而提升性能。
### 实施示例
假设我们有一个用户列表组件,其中包含大量用户数据。我们可以应用以下优化:
- 使用 `React.memo` 封装用户列表项组件,仅在 props 变化时重新渲染。
- 通过 `useMemo` 缓存用户列表计算,避免在每次渲染时重新计算。
- 使用虚拟列表库,如 `react-window`,仅渲染可视区域内的用户,以优化长列表性能。
- 如果用户列表是通过路由导航到达的,可以使用 `React.lazy` 和 `Suspense` 实现路由懒加载。
- 通过 React DevTools 的 Profiler 分析用户列表的渲染性能,找出任何潜在的性能瓶颈进行优化。
通过应用这些优化技巧,我们可以显著提高大型 React 应用的性能和用户体验。
前端 · 8月5日 12:52
什么是闭包?什么场景需要使用闭包?
### 什么是闭包?
在计算机科学中,闭包(Closure)是指一个函数绑定了其外部作用域的变量,因此这个函数可以在其定义环境之外被调用时仍能访问到那些绑定的变量。简单来说,闭包让你可以从一个函数内部访问到其外部函数作用域的变量。
闭包的特点是:
1. **函数嵌套**:通常闭包包含一个函数内定义的另一个函数。
2. **环境捕获**:内部函数会捕获定义它的外部函数的作用域中的变量。
3. **作用域链**:内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
在 JavaScript 中,闭包是一种非常常见和强大的特性,因为 JavaScript 是词法作用域的语言,函数的作用域在函数定义时就已经确定了。
### 什么场景需要使用闭包?
闭包通常用于以下几种场景:
1. **数据封装和私有化**:
使用闭包可以创建私有变量,这些变量只能被特定的函数访问和修改,从而模拟出类似私有属性的效果。这在模块模式中尤其常见。
**例子**:一个简单的计数器函数,利用闭包可以隐藏计数器的值,只能通过特定的函数来操作。
```javascript
function createCounter() {
let count = 0;
return {
increment: function() { count += 1; },
decrement: function() { count -= 1; },
getCount: function() { return count; }
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1
```
2. **回调函数**:
在异步编程中,闭包常用于回调函数中,以确保异步操作完成时能够访问到定义回调时的环境状态。
**例子**:在一个异步请求中使用闭包记住请求开始时的状态。
```javascript
function fetchData(url, callback) {
// 假设这里发起了一个异步请求
setTimeout(() => { // 模拟异步操作
const data = 'fetched data'; // 假设这是响应数据
callback(data);
}, 1000);
}
function requestData() {
const requestTimestamp = Date.now();
fetchData('https://example.com/data', function(data) {
console.log(`Request took ${Date.now() - requestTimestamp} ms`);
console.log(`Data received: ${data}`);
});
}
requestData();
```
3. **函数工厂**:
闭包可以用来创建可以记住和操作环境状态的函数,这些函数根据不同的参数创建出来,具有不同的行为。
**例子**:根据不同的倍数创建乘法函数。
```javascript
function createMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 输出 10
console.log(triple(5)); // 输出 15
```
4. **节流和防抖**:
在 JavaScript 的 DOM 事件处理中,为了优化性能,防止过多的事件处理函数被频繁触发,会使用闭包来实现函数节流(throttle)和防抖(debounce)。
**例子**:使用防抖确保事件处理函数在特定时间内只执行一次。
前端 · 8月5日 12:52
HTTP 协议 1.0 和 1.1 和 2.0 有什么区别?
HTTP(超文本传输协议)是 Web 上交换数据的基础协议,随着 Web 技术的发展,HTTP 也经历了多个版本的迭代。下面我会详细介绍 HTTP 1.0、1.1 和 2.0 这三个版本的区别:
### HTTP 1.0
- **无状态连接**:HTTP 1.0 是无状态的,也就是说每次请求都需要建立一个新的TCP连接,完成数据传输后连接就会关闭。这种方式在每次请求都需要经历 TCP 连接的建立和断开过程,导致性能上的不足。
- **限制性能**:由于每次请求都要建立新的连接,所以并发多个请求会导致大量的延迟和性能问题。
- **无宿主名(Host)字段**:HTTP 1.0 不支持 Host 头部。这意味着同一个物理服务器上无法托管多个域名的网站。
### HTTP 1.1
- **持久连接**:HTTP 1.1 默认采用持久连接(也称为“keep-alive”),允许在一个TCP连接上发送和接收多个HTTP请求/响应,从而减少了TCP连接的开销。
- **管线化**:HTTP 1.1 引入了请求的管线化,理论上客户端可以在收到前一个响应之前发送下一个请求,减少了请求的延迟。但实际上,由于某些浏览器和服务器的实现问题,这个特性并未广泛使用。
- **新增头部字段**:例如 Host(它允许在同一物理服务器上虚拟托管多个域名)、Etag(实体标签,可以协助缓存验证)、Accept-Encoding(指定客户端可以接收的内容编码类型)等。
- **缓存控制**:更复杂和灵活的缓存控制机制,使得客户端和服务器可以更有效地协商数据的缓存,减少不必要的数据传输。
- **分块传输编码**:允许服务器开始发送响应而不需要先知道全部内容的总大小。
### HTTP 2.0
- **二进制协议**:HTTP/1.x 是文本协议,而 HTTP/2 是二进制协议,提供了更高效的解析和网络传输。
- **多路复用**:在同一个连接上并行交错地发送多个请求和响应,而不会互相影响,极大地提高了传输效率和减少了延迟。
- **流优先级**:可以为 HTTP/2 连接上的流设置优先级,允许更重要的资源先被发送。
- **服务器推送**:服务器可以对一个客户端请求发送多个响应,允许服务器主动推送资源给客户端,进一步提升页面加载效率。
- **头部压缩**:HTTP/2 引入了 HPACK 压缩格式,用于减小头部大小,以减少传输延迟。
举例来说,一个明显的性能改进是在使用HTTP/2时浏览一个网站:由于多路复用和头部压缩等特性,相比于 HTTP/1.1,网页的加载时间可以显著减少,尤其是在网络条件较差或加载资源较多的场景下。此外,HTTP/2 的服务器推送功能允许服务器预先推送静态资源,比如 CSS 或 JavaScript 文件,这可以进一步提高加载速度,因为浏览器不必等待解析 HTML 再去请求这些资源。
计算机基础 · 8月5日 12:51
浏览器有哪些缓存策略?
浏览器缓存策略主要是用于提高网页加载速度,减少服务器压力以及节省带宽。以下是几种主要的浏览器缓存策略:
1. **强缓存(Strong Cache)**
- `Expires`:这是HTTP/1.0中使用的头信息,用来指定资源到期的时间。如果请求的时间小于Expires的时间,浏览器会直接使用缓存中的资源,而不会向服务器发起请求。
- `Cache-Control`:在HTTP/1.1中引入,比Expires更灵活。常用的指令包括`max-age`(资源最大有效时间)、`no-cache`(每次都要向服务器确认)、`no-store`(完全不缓存),等等。若设置了`max-age`,且缓存时间未过期,则浏览器会直接使用本地缓存。
2. **协商缓存(Negotiation Cache)**
- `Last-Modified`和`If-Modified-Since`:服务器在响应中加入`Last-Modified`标头指明资源最后修改时间,浏览器再请求时通过`If-Modified-Since`将这个值发送给服务器,由服务器判断资源是否有更新。
- `ETag`和`If-None-Match`:ETag是资源的唯一标识符,当资源有变动时ETag也会变。浏览器存储资源的ETag,并在下次请求时通过`If-None-Match`发送给服务器,以检查资源是否有更新。
若在协商缓存中服务器确认内容没有更新,则服务器会返回304状态码,浏览器就会使用本地缓存;如果内容更新了,则会返回200状态码和新的资源内容。
3. **预缓存(Pre-Caching)**
- Service Workers:通过Service Workers可以拦截网络请求,并动态地缓存或者恢复资源。这允许创建有效的离线体验,并且可以精细控制缓存策略。
4. **内存和硬盘缓存**
浏览器通常将资源缓存在内存或硬盘中:
- 内存缓存:缓存存储在内存中,访问速度快,但只在浏览器会话期间有效。
- 硬盘缓存:缓存存储在硬盘上,访问速度慢一些,但即使关闭浏览器后依然可以使用。
举个例子,假设您访问了一个前端的网站,网站的CSS文件设置了强缓存,`Cache-Control`设置为`max-age=3600`,这意味着在接下来的一个小时内,如果您再次访问该网站,浏览器就会直接使用本地缓存的CSS文件,而不需要再次请求服务器,这样就能加快页面的加载速度。
而对于网站的新闻部分,可能会使用协商缓存,每次访问时通过ETag或者Last-Modified信息检查内容是否有更新,以确保用户总是看到最新的内容,同时在内容没有更新的情况下减少不必要的资源传输。
前端 · 8月5日 12:50
Web 端应用如何做移动的适配
为了确保Web应用能够在移动设备上良好运行,我们需要关注几个关键点:
### 1. 响应式设计(Responsive Design)
响应式设计是适配移动端的核心。通过使用媒体查询(Media Queries)和相对单位(如百分比,em,rem等),我们可以确保网页布局和元素能够根据不同设备屏幕尺寸和分辨率自动调整。例如,Bootstrap框架提供了一系列预定义的响应式类,可以帮助开发者更快地实现响应式设计。
**例子:**
```css
@media screen and (max-width: 768px) {
.container {
width: 100%;
}
}
```
上面的媒体查询表明,当屏幕宽度小于或等于768像素时,`.container` 类的宽度将被设置为100%。
### 2. 触控优化(Touch Optimization)
移动设备通常是通过触控进行操作的,因此需要确保所有的交互元素(如按钮、链接、表单控件等)都足够大,以便手指点击,并有足够的间距,以防误触。HTML5为表单元素提供了适用于移动端的类型,如 `<input type="email">`,这会调用适合输入电子邮件的虚拟键盘。
**例子:**
```html
<button class="touch-optimized-button">Submit</button>
```
```css
.touch-optimized-button {
padding: 15px;
margin: 10px;
font-size: 18px;
}
```
### 3. 视口配置(Viewport Configuration)
通过设置 `<meta>`标签中的 `viewport`属性,我们可以控制视口的尺寸和比例。这告诉浏览器如何控制页面的尺寸和比例,以匹配不同设备的屏幕。
**例子:**
```html
<meta name="viewport" content="width=device-width, initial-scale=1.0">
```
### 4. 性能优化(Performance Optimization)
移动设备相比于桌面设备,其网络连接可能更不稳定,处理能力可能更弱。因此,要特别注意减少代码体积、图片大小,以及优化加载时间。使用压缩工具、图片懒加载、缓存策略等,都是性能优化的常见做法。
**例子:**
使用WebP格式的图片,它比传统的JPEG或PNG格式具有更好的压缩率,可以更快地加载。
### 5. 使用框架和工具(Frameworks and Tools)
现代前端框架和库,如React、Vue、Angular等,通常都有考虑到移动设备的适配。使用这些框架和它们的UI组件库(如Material-UI, Vuetify等),可以更快地开发出适合移动设备的界面。
**例子:**
```javascript
import { Button } from '@material-ui/core';
<Button variant="contained" color="primary">
Click me
</Button>
```
综上所述,要做好移动端的Web应用适配,需要综合考虑布局、交互、视口设置、性能优化以及合适的开发工具。通过以上这些方法,我们可以提供更好的移动用户体验,并确保应用在不同设备上都能够良好地运行。
前端 · 8月5日 12:50
React hook 使用需要注意哪些?
在使用 React Hooks 时需要遵循一些最佳实践和注意事项,以确保代码的可维护性与功能的正确性。以下是一些关键点:
### 1. 遵守Hooks规则
#### 不要在循环、条件或嵌套函数中调用Hooks
Hooks 应该始终在组件的顶层被调用,这样可以保证 Hooks 在每次渲染时都以相同的顺序被调用,这对于 React 的内部状态追踪机制非常重要。
#### 只在React函数中调用Hooks
应该仅在React的函数组件或自定义 Hooks 中调用 Hooks。不要在普通的 JavaScript 函数中调用。
### 2. 使用 `useState`时的注意事项
#### 初始化状态
对于复杂的状态逻辑,可以通过传递一个函数给 `useState` 来惰性初始化,这样可以避免在每次渲染时重新创建初始状态。
```jsx
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
```
#### 状态更新函数的身份稳定
`setState` 函数是身份稳定的,这意味着你可以在其他 Hooks 中安全地引用它,而不用担心它会在重新渲染时改变。
### 3. 使用 `useEffect`时的注意事项
#### 清理副作用
在 `useEffect` 中创建的订阅、定时器、监听事件等副作用,应该在返回的清理函数中进行清除,以避免内存泄漏。
```jsx
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清理订阅
subscription.unsubscribe();
};
}, [props.source]);
```
#### 依赖列表的完整性
确保依赖列表包含了所有外部作用域中被 `useEffect` 使用到的值,这样才能正确响应这些值的变化。如果忽略了依赖,可能会导致旧的闭包中的值被捕获,从而引发错误。
```jsx
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // 确保所有使用到的变量都被包含在依赖列表中
```
### 4. 避免在 `useEffect`中进行不必要的操作
#### 节流和防抖
如果 `useEffect` 中的操作非常昂贵,考虑使用节流(throttling)或防抖(debouncing)技术来减少操作的频率。
### 5. 自定义Hooks
#### 代码复用
当你发现需要在不同组件之间复用状态逻辑时,可以将其抽离成自定义 Hooks。这有助于减少代码冗余并增强逻辑的可维护性。
例如,使用自定义 `useForm` Hook 来处理表单:
```jsx
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const handleChange = (event) => {
setValues({
...values,
[event.target.name]: event.target.value,
});
};
return [values, handleChange];
}
```
### 6. 性能优化
#### `useMemo` 和 `useCallback`
在有必要的情况下,使用 `useMemo` 和 `useCallback` 来避免不必要的渲染或计算。`useMemo` 可以用来缓存复杂计算的结果,`useCallback` 可以用来缓存函数,这在将函数传递给子组件时特别有用,可以避免不必要的子组件重渲染。
前端 · 8月5日 12:50
封装一个可以设置过期时间的localStorage存储函数
实现一个具有过期时间功能的localStorage存储函数,需要定义一个函数,它会将数据和过期时间一起存储在localStorage中。
下面是一个简单的实现示例:
```javascript
/**
* 设置带过期时间的localStorage
* @param {string} key - 存储的键名
* @param {*} value - 要存储的值,可以是任何可序列化的数据
* @param {number} ttl - 过期时间(毫秒)
*/
function setLocalStorageWithExpiry(key, value, ttl) {
const now = new Date();
// 创建一个包含数据和过期时间的对象
const item = {
value: value,
expiry: now.getTime() + ttl,
};
// 将对象序列化之后存储到localStorage中
localStorage.setItem(key, JSON.stringify(item));
}
/**
* 获取localStorage存储的值
* @param {string} key - 存储的键名
* @returns {*} 存储的值或者当值不存在或过期时返回null
*/
function getLocalStorageWithExpiry(key) {
const itemStr = localStorage.getItem(key);
// 如果没有找到对应的存储项
if (!itemStr) {
return null;
}
const item = JSON.parse(itemStr);
const now = new Date();
// 检查过期时间
if (now.getTime() > item.expiry) {
// 如果已过期,删除存储并返回null
localStorage.removeItem(key);
return null;
}
// 如果未过期,返回存储的值
return item.value;
}
// 示例使用
// 存储一个名为 'myData' 的数据,过期时间为1小时(3600000毫秒)
setLocalStorageWithExpiry('myData', { a: 1, b: 2 }, 3600000);
// 获取存储的数据
const myData = getLocalStorageWithExpiry('myData');
console.log(myData); // 如果还未过期,则会打印出存储的对象 { a: 1, b: 2 }
```
在这个封装的函数中,我们通过 `setLocalStorageWithExpiry`函数存储数据的时候,会额外添加一个过期时间戳到对象中,并将该对象序列化后保存在localStorage里。当通过 `getLocalStorageWithExpiry`函数获取数据的时候,我们会先检查当前时间是否已经超过了存储时设置的过期时间戳,如果已经过期,则从localStorage中删除该项,并返回 `null`;如果未过期,则返回保存的值。
前端 · 8月5日 12:50