创建XMLHttpRequest
对象发送请求,通过调用其abort()
方法来取消请求。示例代码如下:
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.github.com/");
xhr.send();
// 在需要取消请求的地方
xhr.abort();
使用$.ajax()
或$.get()
等方法发送请求,将返回的jqXHR
对象保存起来,再调用abort()
方法取消请求。示例代码如下:
// 用$.ajax发送请求
var jq = $.ajax({
type: "get",
url: "https://api.github.com/",
dataType: "json",
success: function(data) {
console.log(data);
},
error: function(err) {
console.log(err);
}
});
// 取消请求
jq.abort();
// 用$.get发送请求
var request = $.get(url, function(data) {
// 请求成功的回调函数
});
// 停止请求
request.abort();
通过axios.CancelToken.source()
方法创建取消令牌source
,将source.token
添加到请求配置中,在需要取消请求时调用source.cancel()
方法。示例代码如下:
import axios from 'axios';
// 创建取消标记
const source = axios.CancelToken.source();
axios.get('/api/data', { cancelToken: source.token })
.then(response => {
// 请求成功处理
})
.catch(error => {
if (axios.isCancel(error)) {
console.log('请求被取消', error.message);
} else {
console.log('请求出错', error.message);
}
});
// 取消请求
source.cancel('请求取消的原因');
使用AbortController
和AbortSignal
来中止请求。创建AbortController
实例,将其signal
属性传递给fetch
的配置对象,在需要取消请求时调用controller.abort()
方法。示例代码如下:
const controller = new AbortController();
const { signal } = controller;
fetch('https://jsonplaceholder.typicode.com/todos/1', { signal })
.catch(error => {
if (error.name === 'AbortError') {
console.log('fetch aborted');
} else {
console.error('fetch error:', error);
}
});
// 取消请求
setTimeout(() => {
controller.abort();
}, 1000);
让我详细解释移动端 rem 的处理方法:
rem 是相对于根元素(html)的字体大小的单位。例如,如果根元素的 font-size 是 16px,那么 1rem = 16px。
// 方案1:基础版本
function setRem() {
// 基准大小
const baseSize = 16;
// 设计稿宽度
const designWidth = 375;
// 获取当前视窗宽度
const width = document.documentElement.clientWidth;
// 计算缩放比例
const scale = width / designWidth;
// 设置根元素font-size
document.documentElement.style.fontSize = baseSize * scale + "px";
}
// 初始化
setRem();
// 改变窗口大小时重新设置
window.addEventListener("resize", setRem);
// 方案2:完整版本
(function flexible(window, document) {
const docEl = document.documentElement;
const dpr = window.devicePixelRatio || 1;
// 调整body字体大小
function setBodyFontSize() {
if (document.body) {
document.body.style.fontSize = "12px";
} else {
document.addEventListener("DOMContentLoaded", setBodyFontSize);
}
}
setBodyFontSize();
// 设置rem函数
function setRemUnit() {
const rem = docEl.clientWidth / 10;
docEl.style.fontSize = rem + "px";
}
setRemUnit();
// 监听resize事件
window.addEventListener("resize", setRemUnit);
window.addEventListener("pageshow", function (e) {
if (e.persisted) {
setRemUnit();
}
});
// 设置data-dpr属性
if (dpr >= 2) {
const fakeBody = document.createElement("body");
const testElement = document.createElement("div");
testElement.style.border = ".5px solid transparent";
fakeBody.appendChild(testElement);
docEl.appendChild(fakeBody);
if (testElement.offsetHeight === 1) {
docEl.classList.add("hairlines");
}
docEl.removeChild(fakeBody);
}
})(window, document);
// _rem.scss
@function rem($px) {
$rem: 37.5px; // 基准值,根据设计稿来定
@return ($px / $rem) + rem;
}
// 使用示例
.container {
width: rem(375); // 10rem
height: rem(100); // 2.67rem
font-size: rem(16); // 0.427rem
margin: rem(10); // 0.267rem
}
// postcss.config.js
module.exports = {
plugins: {
"postcss-pxtorem": {
rootValue: 37.5, // 设计稿宽度的1/10
propList: ["*"], // 需要转换的属性
selectorBlackList: [], // 不需要转换的选择器
replace: true,
mediaQuery: false,
minPixelValue: 0,
},
},
};
// vite.config.js
import postcssPxtorem from "postcss-pxtorem";
export default {
css: {
postcss: {
plugins: [
postcssPxtorem({
rootValue: 37.5,
propList: ["*"],
selectorBlackList: [".norem"],
}),
],
},
},
};
DOCTYPE html>
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<script src="./flexible.js">script>
head>
<body>
body>
html>
内容
// 常见设计稿宽度
const designWidth = 375; // iPhone 6/7/8
const designWidth = 750; // 2倍图
// 解决1px边框问题
.border-1px {
position: relative;
&::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: #000;
transform: scaleY(0.5);
}
}
// 字体使用px
.text {
font-size: 14px; // 不转换为rem
}
这样的 rem 方案可以很好地处理移动端的适配问题,使页面在不同设备上都能保持良好的显示效果。
terser-webpack-plugin
等。ts-loader
或 babel-loader
来处理 TypeScript 文件,并且需要仔细调整配置以确保类型检查和构建的顺利进行。综上所述,Vite 在开发时的启动速度、HMR 性能、配置的简洁性、对现代前端框架的支持、插件生态的易用性以及对 TypeScript 的支持等方面都有一定的优势,尤其是对于开发体验和开发效率有更高要求的项目,Vite 是一个很好的选择。然而,Webpack 仍然是一个强大的工具,对于一些复杂的、需要高度定制化的项目,Webpack 的丰富插件和强大的配置能力可以更好地满足需求。在选择时,可以根据项目的具体情况和团队的经验来决定使用哪种工具。
在面试中回答这个问题时,可以结合实际的项目经验,例如:“在我之前的项目中,使用 Vite 开发一个 Vue 3 项目,开发服务器的启动速度非常快,几乎是瞬间完成,而之前使用 Webpack 时,启动时间会随着项目规模的增加而显著增加。而且 Vite 的 HMR 性能很好,修改代码后可以立即看到效果,无需长时间等待,相比之下,Webpack 的 HMR 有时会出现整个页面刷新的情况,影响开发体验。Vite 的配置也更加简洁,对于 TypeScript 的处理也很方便,而在使用 Webpack 时,需要更多的配置来处理 TypeScript 模块和实现类似的开发体验。不过,如果是一个需要高度定制化的大型项目,Webpack 可以通过其丰富的插件和复杂的配置来满足需求,但这也需要更多的时间和精力去配置和维护。”
通过这样的回答,可以向面试官展示你对两种打包工具的深入了解和在实际项目中的应用经验。
设计模式即 Software Design Pattern,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。
让我为您详细讲解JavaScript中的原型链(Prototype Chain):
原型链是JavaScript实现继承的主要方式。每个对象都有一个内部属性 [[Prototype]]
(可以通过__proto__
访问),指向其原型对象。当查找一个对象的属性时,如果对象本身没有这个属性,就会沿着原型链向上查找。
// 基本示例
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
return this.name;
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 建立继承关系
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const myDog = new Dog('旺财', '柴犬');
console.log(myDog.sayName()); // "旺财"
__proto__
(即其构造函数的prototype)__proto__
的__proto__
// 原型链结构示例
Object.prototype.__proto__ === null
Function.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype
myDog.__proto__ === Dog.prototype
Dog.prototype.__proto__ === Animal.prototype
Animal.prototype.__proto__ === Object.prototype
// 使用原型链实现继承
function Vehicle() {
this.isVehicle = true;
}
Vehicle.prototype.move = function() {
return '移动中...';
};
function Car(brand) {
Vehicle.call(this);
this.brand = brand;
}
// 设置原型链
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
// 添加Car特有的方法
Car.prototype.honk = function() {
return '嘟嘟!';
};
const myCar = new Car('Toyota');
console.log(myCar.move()); // "移动中..."
console.log(myCar.honk()); // "嘟嘟!"
console.log(myCar.isVehicle); // true
// ES6 class语法其实是原型链的语法糖
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
return this.name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
}
__proto__
Object.create()
设置原型这样的回答既有理论深度,又有实践经验,同时还可以引申讨论JavaScript面向对象编程的其他方面。
闭包是指一个函数能够访问并记住其词法作用域中的变量,即使该函数在其原始作用域之外执行时也是如此。简单来说,闭包就是一个函数和其周围状态(词法环境)的引用的组合。
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.getCount()); // 输出: 0
console.log(counter.increment()); // 输出: 1
console.log(counter.increment()); // 输出: 2
优点:
缺点:
// 实现一个简单的模块模式
const modulePattern = (function() {
// 私有变量
let privateVar = 'I am private';
// 私有方法
function privateMethod() {
return 'This is private';
}
return {
// 公共方法
publicMethod: function() {
return privateVar;
},
anotherPublicMethod: function() {
return privateMethod();
}
};
})();
这样的回答既展示了对闭包的理解,又体现了实践经验,同时还可以引申出其他相关的重要概念。
实现一个简单的 EventBus 可以帮助你在不同的组件或模块之间进行通信。EventBus 通常用于发布-订阅模式,允许组件之间发送和接收事件,而不需要直接引用彼此。
以下是一个用 JavaScript 实现的简单 EventBus 示例:
class EventBus {
constructor() {
this.events = {};
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 发布事件
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
}
// 移除事件监听
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
}
}
}
// 使用示例
const eventBus = new EventBus();
// 订阅事件
eventBus.on('customEvent', (data) => {
console.log('Received data:', data);
});
// 发布事件
eventBus.emit('customEvent', { message: 'Hello, EventBus!' });
// 移除事件监听
eventBus.off('customEvent', (data) => {
console.log('Received data:', data);
});
constructor
): 初始化一个空的事件对象,用于存储事件名称和对应的回调函数数组。on
): 将回调函数添加到指定事件名称的数组中。emit
): 遍历指定事件名称的回调函数数组,并调用每个回调函数,传递数据。off
): 从指定事件名称的回调函数数组中移除指定的回调函数。这个简单的 EventBus 实现可以满足大多数前端项目中的基本需求。如果你需要更复杂的功能,比如异步处理、错误处理等,可以在此基础上进行扩展。
事件循环是 JavaScript 处理异步操作的核心机制,确保 JavaScript 在单线程环境下能够高效地处理各种任务,避免阻塞。以下是事件循环的详细解释:
执行栈(Call Stack):
function first() {
second();
}
function second() {
third();
}
function third() {
console.log('Hello, World!');
}
first();
first()
时,first
函数会被压入执行栈;first
函数调用 second()
,second
函数会被压入执行栈;second
函数调用 third()
,third
函数会被压入执行栈;third
函数执行并打印 Hello, World!
,然后 third
函数从栈中弹出,接着 second
函数弹出,最后 first
函数弹出。任务队列(Task Queue):
宏任务(Macrotasks):
setTimeout
、setInterval
、setImmediate
(Node.js)、I/O 操作、UI 渲染等。setTimeout
函数会将其回调函数添加到宏任务队列中,当达到设定的延迟时间后,该回调函数会等待被执行。微任务(Microtasks):
Promise.then()
、Promise.catch()
、process.nextTick
(Node.js)、queueMicrotask
等。Promise.resolve().then()
会将其回调函数添加到微任务队列中,该回调函数会在当前宏任务完成后立即执行,而不是等待下一个宏任务。console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
Promise.resolve().then(() => {
console.log('Promise inside Timeout 1');
});
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
setTimeout(() => {
console.log('Timeout inside Promise 1');
}, 0);
});
console.log('End');
console.log('Start')
是同步代码,直接执行,输出 Start
。setTimeout(() => {...}, 0)
是宏任务,其回调函数被添加到宏任务队列中。Promise.resolve().then(() => {...})
是微任务,其回调函数被添加到微任务队列中。console.log('End')
是同步代码,直接执行,输出 End
。Promise.resolve().then(() => {...})
的回调函数,执行该微任务,输出 Promise 1
,并将另一个 setTimeout
回调添加到宏任务队列。setTimeout(() => {...})
的回调函数,执行该宏任务,输出 Timeout 1
,同时将内部的 Promise.then()
微任务添加到微任务队列。Promise.then()
微任务,输出 Promise inside Timeout 1
。setTimeout(() => {...})
回调函数,输出 Timeout inside Promise 1
。重要性:
应用场景:
fetch
或 XMLHttpRequest
进行网络请求时,请求完成后的回调函数会被添加到任务队列中,等待执行。setTimeout
、setInterval
等定时器,其回调函数会在设定的时间后添加到任务队列中。在面试中,可以这样回答:“JavaScript 事件循环是一种处理异步操作的机制,它基于单线程执行模型。核心组件包括执行栈和任务队列,任务队列又分为宏任务队列和微任务队列。宏任务如 setTimeout
、setInterval
等,微任务如 Promise.then()
等。事件循环的执行流程是先检查执行栈是否为空,若为空,检查微任务队列,若微任务队列不为空,执行微任务直到为空,再从宏任务队列取一个任务执行,不断重复这个过程。这一机制使 JavaScript 可以在等待异步操作时继续执行其他代码,避免阻塞,同时保证了执行顺序。例如在处理网络请求、用户交互和定时器操作等场景中,事件循环能确保这些异步操作的回调函数在适当的时间得到执行,同时避免因等待而影响程序的流畅性。”
通过这样的详细解释和示例,可以清晰地阐述 JavaScript 事件循环的概念、流程、重要性和应用场景,让面试官了解你对该知识点的深入理解和掌握程度。
项目发布后进行优化是一个持续的过程,旨在提高性能、提升用户体验和减少资源消耗。以下是一些常见的优化策略:
loading="lazy"
属性)延迟加载图片,减少初始加载时间。Cache-Control
、Expires
),利用浏览器缓存减少重复请求。offsetHeight
、clientWidth
等属性读取后立即修改样式。transform
、opacity
)实现流畅动画。通过以上优化策略,可以显著提高项目的性能和用户体验,确保项目在发布后能够稳定高效地运行。在实际操作中,可以根据项目的具体情况进行针对性的优化。
Vue2 和 Vue3 的 diff 算法主要有三个区别:
Vue2 的双端比较会同时从新旧子节点的两端开始比较,会进行以下四种比较:
如果四种都未命中,才会进行遍历查找。
Vue3 的 diff 算法步骤:
PatchFlag 是 Vue3 新增的一个标记,用于标识节点的动态内容类型:
等等…这样在 diff 的时候可以只关注动态内容,提高性能。
这些优化在实际应用中的效果:
如果想要在面试中脱颖而出,可以补充:
Vue3 diff 算法借鉴了 inferno 的算法实现
Vue3 还有其他性能优化:
记住: