以下是问题总览:不会的点击右侧目录对应的问题即可跳转到答案
chunkHash:
chunkHash
是基于整个代码块(chunk)计算的。在Webpack中,一个chunk通常包含多个模块的集合。chunkHash
也会改变。这意味着即使只有一个小的组件或模块发生变化,整个chunk的hash都会更新,导致所有依赖这个chunk的文件也必须重新下载。适用于那些不经常变动的库文件
。contentHash:
contentHash
是根据文件内容计算的,特别适用于样式文件(如CSS)。contentHash
会更新,而与此文件相关的其他文件不会受到影响。(频繁)
才需要重新下载文件。这种区分有助于有效管理缓存,因为它允许客户端只在文件内容改变时才重新下载文件,而不是因为一小部分更改而下载整个bundle。
Loader:
babel-loader
帮助我们将ES6以上的代码转换成向后兼容的JavaScript版本,而style-loader
和css-loader
则用于将CSS文件转换并嵌入到JavaScript中,这样可以通过JavaScript将样式注入到DOM中。Plugin:
下面是一个简单的 loader 示例,该 loader 会将文件内容全部转换为大写形式:
// file: upper-case-loader.js
module.exports = function(source) {
return source.toUpperCase();
};
要在 webpack 配置中使用这个 loader,你需要将它添加到 module.rules
部分。将它应用于 .txt
文件:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: [
{
loader: path.resolve('./upper-case-loader.js')
}
]
}
]
}
};
这样配置后,所有 .txt
文件在打包时都会通过 upper-case-loader.js
进行处理,即将其内容转换为大写。
Webpack plugin 可以在打包流程的不同阶段接入,执行更复杂的任务。下面是一个简单的 plugin 示例,它会在打包完成后输出一个消息:
// file: log-done-plugin.js
class LogDonePlugin {
apply(compiler) {
compiler.hooks.done.tap('LogDonePlugin', (stats) => {
console.log('Build is done!');
});
}
}
module.exports = LogDonePlugin;
要在 webpack 配置中使用这个 plugin,你需要将其实例添加到配置的 plugins
数组中:
// webpack.config.js
const LogDonePlugin = require('./log-done-plugin.js');
module.exports = {
// 其他配置...
plugins: [
new LogDonePlugin()
]
};
这个 plugin 使用了 Webpack 的钩子系统(具体使用 compiler.hooks.done
钩子),在每次成功构建后打印一条消息。
处理图片的Loader:
file-loader
:可以将图片文件输出到构建目录,并返回公共URL。url-loader
:功能类似于file-loader
,但是如果文件大小小于限制,url-loader
会返回一个DataURL(即Base64编码的字符串),这可以减少请求次数。限制文件大小:
url-loader
来限制图片大小。在url-loader
的配置选项中,可以指定limit
属性(单位为字节)。如果文件大小小于这个限制,它将返回DataURL,否则它将回退到file-loader
行为,即将文件移动到输出目录并返回URL。在Webpack中,将多个CSS文件合并成一个通常使用MiniCssExtractPlugin
来完成。这个插件将CSS从主应用程序中分离出来,生成一个单独的CSS文件,而不是将CSS直接嵌入到JavaScript bundle中,这样做可以更有效地利用缓存,因为CSS通常比JavaScript更少更改。使用该插件还可以将多个CSS文件合并为一个文件,减少请求次数,提升页面加载速度。
配置示例:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles.css'
})
]
};
这样配置后,Webpack会把所有的CSS文件抽取并合并成一个名为styles.css
的文件。
Tree Shaking
是一个术语,通常用于描述移除
JavaScript上下文中未引用代码的过程。它依赖于ES6模块系统中的静态结构特性,这使得Webpack可以在打包时静态分析代码,并剔除那些未被使用的模块。
对ES6模块的影响:
对CommonJS的影响:
以下是一个简单的 JavaScript 函数,用于实现基本的模板字符串功能,该函数接受一个模板字符串和一个上下文对象,然后返回替换了变量的字符串:
function templateString(template, context) {
return template.replace(/\$\{(\w+)\}/g, (match, key) => {
return context.hasOwnProperty(key) ? context[key] : '';
});
}
// 使用示例
const template = 'Hello, ${name}! Today is ${day}.';
const context = { name: 'Alice', day: 'Wednesday' };
console.log(templateString(template, context));
// 输出: "Hello, Alice! Today is Wednesday."
在这个实现中,函数templateString
接受两个参数:template
是包含变量占位符的字符串,context
是一个对象,其属性值用于替换模板中的占位符。
这个函数使用正则表达式\$\{(\w+)\}
来匹配模板中的所有形如${variable}
的表达式,并将其替换为context
对象中对应的值。如果在context
中找不到相应的键,则替换为空字符串。
这种方法可以模拟基本的模板字符串功能,但它不支持复杂表达式(如运算或方法调用)。如果需要支持更复杂的表达式,可能需要实现一个更复杂的解析器或使用现有的模板引擎库,如Handlebars或Mustache。
Promise.all
是一个非常有用的功能,它允许同时处理多个 Promise 对象,并在它们全部成功解决后返回一个新的 Promise 对象。如果传入的任何一个 Promise 失败,返回的 Promise 将立即以该 Promise 的拒绝理由拒绝。下面是一个简单的实现:
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
reject(new TypeError('arguments must be an array'));
return;
}
let resolvedCounter = 0;
let promiseNum = promises.length;
let resolvedResults = new Array(promiseNum);
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(value => {
resolvedCounter++;
resolvedResults[i] = value;
if (resolvedCounter === promiseNum) {
resolve(resolvedResults);
}
}, reason => {
reject(reason);
});
}
});
}
这个实现考虑到了几个关键点:
promises
参数是一个数组。resolvedResults
中相应的位置。resolve
将会用这些结果作为数组调用。reject
会立即被调用。实现响应式布局的方法有几种,常用的技术包括:
例如,使用媒体查询来改变容器的布局:
.container {
width: 100%;
display: flex;
flex-direction: column;
}
@media (min-width: 600px) {
.container {
flex-direction: row;
}
}
这个例子中,.container
在屏幕宽度至少为600px时将其子元素从垂直排列转为水平排列。
Flexbox 提供了多种属性来控制布局:
row
, row-reverse
, column
, column-reverse
。nowrap
, wrap
, wrap-reverse
。flex-start
, flex-end
, center
, space-between
, space-around
。flex-start
, flex-end
, center
, baseline
, stretch
。stretch
, center
, flex-start
, flex-end
, space-between
, space-around
。flex-grow
, flex-shrink
和flex-basis
的简写。在CSS中使用动画时,可以通过animation
属性来控制动画的各种方面。这些属性包括:
@keyframes
规则中定义的动画序列。linear
, ease
, ease-in
, ease-out
, 和 ease-in-out
。infinite
表示无限循环。normal
, reverse
, alternate
, 和 alternate-reverse
。none
, forwards
, backwards
, 和 both
。paused
和running
。例如,创建一个简单的淡入淡出效果的动画:
@keyframes fadeInOut {
from { opacity: 0; }
to { opacity: 1; }
}
.element {
animation-name: fadeInOut;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-direction: alternate;
}
要同时让一个元素旋转并沿水平方向移动,可以使用transform
属性结合rotate
和translateX
函数:
.element {
transform: rotate(45deg) translateX(100px);
}
这里,.element
将沿X轴移动100像素并旋转45度。这种组合允许在单一的属性中执行多重变形。
LESS和SASS都是流行的CSS预处理器,它们提供变量、混入(mixins)、函数等高级功能来编写更可维护、更强大的样式表。
LESS:
.less
扩展名。SASS:
在技术选型时,可以考虑以下因素:
在 ES6 中,Symbol
是一种新的原始数据类型,它被用来创建唯一的标识符。Symbol
的主要用途是作为对象的属性键,这些属性是唯一的,可以防止属性名的冲突。这对于大型项目或者多个库和框架一起使用时尤其有用,因为它可以减少不同代码库间命名冲突的可能性。
创建一个 Symbol 非常简单,使用 Symbol()
函数即可:
let sym = Symbol();
let obj = {
[sym]: "Symbol Value"
};
console.log(obj[sym]); // 输出: "Symbol Value"
在这个例子中,sym
是一个 Symbol,并且被用作对象 obj
的属性键。通过这种方式,sym
属性不会与对象上的其他属性冲突,即使它们有相同的名称。
私有属性:
Symbols 可以用来模拟私有属性,因为直接通过对象属性名无法访问到使用 Symbol 作为键的属性(除非你拥有这个 Symbol):
let age = Symbol();
class Person {
constructor(name, ageValue) {
this.name = name;
this[age] = ageValue;
}
displayAge() {
console.log(this[age]);
}
}
let person = new Person("Alice", 30);
person.displayAge(); // 输出: 30
console.log(person.age); // 输出: undefined
这里的 age
属性在类的外部是无法直接访问的,从而实现了一种简单的封装。
消除魔法字符串:
在代码中直接使用字符串或数字往往会造成所谓的“魔法字符串”问题,即这些值的含义不清晰且易出错。使用 Symbol 可以增强代码的可读性和可维护性:
const COLOR_RED = Symbol("Red");
const COLOR_BLUE = Symbol("Blue");
function getColor(color) {
switch (color) {
case COLOR_RED:
return "Red";
case COLOR_BLUE:
return "Blue";
default:
throw new Error("Unknown color");
}
}
使用 Symbol 实现迭代器:
使用 Symbol.iterator 可以自定义对象的迭代行为:
let collection = {
items: [1, 2, 3],
[Symbol.iterator]: function* () {
for (let item of this.items) {
yield item;
}
}
};
for (let item of collection) {
console.log(item); // 输出 1, 2, 3
}
防止属性覆盖:
当使用第三方库或者在大型项目中,可能会有多个模块操作同一个对象。使用 Symbol 作为属性键可以防止无意中覆盖已有的属性:
let id = Symbol("id");
obj[id] = "Module1";
console.log(obj[id]); // 输出: "Module1"
通过以上例子,我们可以看到,Symbol
提供了一种方式来增加代码的健壮性和封装性,是在实际工作中非常有用的工具。
Proxy 是 ES6 引入的一个功能强大的特性,允许你定义一个对象的各种操作的自定义行为(如属性查找、赋值、枚举等)。它常用于拦截和自定义对象的基本操作。
使用方法:
let handler = {
get: function(target, name) {
return name in target ? target[name] : 42;
}
};
let p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 输出:1, 42
在这个例子中,handler
对象定义了一个get
陷阱,它拦截对未定义属性的访问,并返回42
。
使用场景:
Reflect 是与 Proxy 配套的一个内置对象,它提供了拦截 JavaScript 操作的方法。这些方法与 Proxy 的handler对象的方法相对应。Reflect
不是一个函数对象,所以它是不可构造的。
Reflect的用途包括:
Reflect.apply(Math.floor, undefined, [1.75])
等同于 Math.floor(1.75)
。Generator 是一个可以返回多次的特殊函数,它能在执行中暂停,后面又可以从停下来的地方继续执行。
语法:
function* myGenerator() {
yield 'hello';
yield 'world';
}
const generator = myGenerator();
console.log(generator.next().value); // 输出 'hello'
console.log(generator.next().value); // 输出 'world'
应用场景:
yield
语句暂停和恢复代码执行,如通过 yield
暂停执行,等待异步操作完成。next()
方法时切换到不同的内部状态。async
和 await
是基于 Promise 的语法糖,使得异步代码的书写和阅读更接近同步代码。
实现原理:
await
表达式在 async 函数中被调用时,它会暂停函数的执行并等待 Promise 解决。git reset 和 git revert 都是用来撤销之前的更改,但它们的方式和影响不同。
git reset:
git reset
主要用于在本地撤销更改。它可以将HEAD(当前分支的指针)移动到指定的状态,无论是回到过去的某个提交,还是撤销工作目录中的未提交更改。git reset
有三种模式:--soft
(只改变提交历史,但不改变工作目录和索引)、--mixed
(默认模式,改变索引(暂存区)但不改变工作目录)和 --hard
(改变工作目录和索引,完全恢复到某个提交的状态)。git reset
,尤其是 --hard
选项,要小心,因为它会永久删除提交记录和工作中的更改。git revert:
git revert
用于在公共历史中安全地撤销更改。它通过创建一个新的提交来“反转”一个或多个旧提交的效果,而不是删除或修改现有提交。对于处理多个 merge request 的撤销:
git revert
是更安全的做法。你可以分别对每个 merge commit 执行 git revert
,Git 会为每个撤销操作创建一个新的反向提交。撤回使用 git add
添加到暂存区的内容,可以使用 git reset
命令:
git reset HEAD <file>
这将从暂存区中移除指定的文件,但保留其在工作目录中的更改。如果你想从暂存区中移除所有文件,可以省略
参数:
git reset HEAD
这不会影响文件的当前更改(即工作目录中的更改仍然存在),只是将它们从暂存区中撤回。
HTTP/2 相较于 HTTP/1.1,引入了多项改进:
HTTP/3:
阻塞问题,即一个丢失的数据包阻塞后续所有数据包的问题。
TCP(传输控制协议)和 UDP(用户数据报协议)是两种常用的互联网协议:
TCP:
UDP:
当谈到“多端”开发时,通常是指创建可以在多种设备和平台上运行的应用程序,包括桌面、手机和平板电脑。多端开发的核心原理涉及代码的复用、适配不同平台的界面和功能、以及确保在所有目标设备上提供良好的用户体验。以下是一些实现多端应用的主要技术和原理:
使用跨平台开发框架是实现多端应用的常见方式。这些框架允许开发者编写一次代码,然后部署到多个平台。一些流行的跨平台框架包括:
响应式设计是多端开发中确保应用界面在不同屏幕尺寸和分辨率上都能正确显示的关键。使用CSS媒体查询和灵活的布局系统(如Flexbox和CSS Grid),可以创建适应不同设备的界面。
不同平台可能支持不同的功能和API。多端应用开发中,需要根据平台的能力来适配或替换某些功能。例如,某些API在桌面上可用而在移动设备上不可用,或者行为略有不同。
性能是多端开发中的重要考虑因素,尤其是对于那些同时运行在高性能设备和性能较低设备的应用。优化可能涉及减少资源消耗、改进数据处理方式和优化网络使用。
尽管平台之间可能存在差异,但提供一致的用户体验是多端开发的重要目标。这包括确保应用的界面和交互在所有平台上都保持一致性。
在多端项目中,持续集成(CI)和持续部署(CD)可以帮助自动化测试和发布过程,确保在所有支持的平台上都能及时更新应用,并保持应用的质量和稳定性。
标签,提前加载用户肯定会用到的资源,如字体文件、CSS、核心JavaScript等。async
或defer
属性)和非阻塞CSS(使用media
属性或动态加载技术)。通过综合应用这些策略,可以显著改善网站的加载时间和用户体验。每种策略都应根据具体情况和网站的独特需求来选择和调整。这样的优化过程需要持续监控和迭代改进,以确保最优的性能表现。