为了更加高效的准备面试,所以面试题都来源于牛客网真题,让面试题更符合实际。
1 cookie,sessionStorage,localStorage的区别
(1)考察点分析
- 存储机制:了解每种存储方式的实现原理和数据存储方式。
- 生命周期:掌握每种存储方式的数据存储时长和生命周期管理。
- 作用域:理解每种存储方式的数据访问范围和作用域限制。
(2)最终答案
-
Cookie:
- 存储机制:以文本形式存储在客户端浏览器中,通过 HTTP 头部在客户端和服务器之间传递。
- 生命周期:可以设置过期时间,在有效期内持久保存,过期后会自动删除。
- 作用域:可以设置路径和域名限制访问,同源策略下会自动发送到服务器端。
-
sessionStorage:
- 存储机制:HTML5 提供的会话存储机制,仅在当前会话(浏览器标签页)下有效。
- 生命周期:数据在页面关闭后自动清除,不同页面或标签页间数据不共享。
- 作用域:每个页面拥有独立的 sessionStorage,不同页面之间无法共享数据。
-
localStorage:
- 存储机制:HTML5 提供的持久化本地存储机制,数据永久保存在客户端浏览器上。
- 生命周期:除非被显式清除,否则数据永久保存,即使浏览器关闭也不会被清除。
- 作用域:每个域名拥有独立的 localStorage,不同页面和标签页间可以共享数据。
2 position 有哪些属性值?
(1)考察点分析
- 基础知识:考察候选人是否理解CSS中的position属性及其各种可能的值。
- 应用能力:考察候选人能否正确选择和使用不同的position属性值来实现所需的布局效果。
- 问题解决能力:考察候选人是否能通过对position属性的理解,解决实际开发中的布局问题。
(2)最终答案
CSS 的 position 属性有以下几种属性值:
- static:默认值。元素按照正常的文档流进行定位,不受 top、right、bottom、left 等属性影响。
示例:
<div style="position: static;">This is static</div>
- relative:相对定位。元素相对于其正常位置进行偏移,但仍保留在文档流中。
<div style="position: relative; top: 10px; left: 20px;">This is relative</div>
- absolute:绝对定位。元素相对于最近的定位祖先(非 static)进行定位,如果没有定位祖先,则相对于初始包含块(通常是浏览器窗口)。
<div style="position: relative;">
<div style="position: absolute; top: 10px; left: 20px;">This is absolute</div>
</div>
- fixed:固定定位。元素相对于浏览器窗口进行定位,即使页面滚动也不会移动。
示例:
<div style="position: fixed; top: 10px; left: 20px;">This is fixed</div>
- sticky:粘性定位。元素在相对定位和固定定位之间切换,依赖于用户的滚动位置。在元素的滚动范围内表现为相对定位,超出范围后表现为固定定位。
<div style="position: sticky; top: 0;">This is sticky</div>
3 叙述一下不同情境下的 This 指向
(1)考察点分析:
- 基础知识掌握:考察候选人是否理解 JavaScript 中 this 的工作原理和不同的指向情况。
- 语言和执行上下文理解:考察候选人对 JavaScript 执行上下文的理解,包括全局执行上下文、函数执行上下文和对象方法执行上下文。
- 调试和问题解决能力:考察候选人是否能够理解和解释代码中 this 的行为,以及解决 this 指向不明确导致的问题。
(2)最终答案
// 1. 全局上下文中的 this
console.log(this); // 输出全局对象,如 window(浏览器环境)
// 2. 函数调用中的默认绑定
function foo() {
console.log(this);
}
foo(); // 输出全局对象,如 window(非严格模式下)
// 3. 对象方法调用中的 this
const obj = {
name: 'John',
greet: function() {
console.log(this.name);
}
};
obj.greet(); // 输出 'John'
// 4. 构造函数中的 this
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
console.log(person1.name); // 输出 'Alice'
// 5. 使用 call、apply、bind 改变 this 的指向
const obj2 = {
name: 'Emily'
};
function introduce() {
console.log(`My name is ${this.name}`);
}
introduce.call(obj2); // 输出 'My name is Emily'
// 6. 箭头函数中的 this
const obj3 = {
name: 'Tom',
greet: () => {
console.log(this.name); // this 指向全局对象,因为箭头函数没有自己的 this 绑定
}
};
obj3.greet(); // 输出全局对象的 name(如果定义了)
4 TS 泛型函数是什么?
(1)考察点分析
- 泛型概念理解:考察候选人是否理解泛型在编程语言中的作用和基本概念。
- 泛型函数的使用场景:考察候选人能否识别并利用适合使用泛型的场景,如处理集合类型、返回值类型不确定等。
- 类型安全性和代码复用性:考察候选人能否通过泛型函数提高代码的可维护性、扩展性和类型安全性。
(2)最终答案
1.定义泛型函数:
泛型函数使用 或其他字母来表示类型变量,这些类型变量可以在函数体内用作参数类型或返回类型的占位符。
function identity<T>(arg: T): T {
return arg;
}
在上面的示例中, 表示这是一个泛型函数,T 是类型变量,它表示任意类型。函数 identity 接受一个参数 arg,类型为 T,并返回一个 T 类型的值。
2. 使用泛型函数:
// 调用泛型函数,并传入具体类型为 number
let result1 = identity<number>(5); // result1 的类型为 number
console.log(result1); // 输出 5
// 调用泛型函数,并传入具体类型为 string
let result2 = identity<string>("Hello"); // result2 的类型为 string
console.log(result2); // 输出 "Hello"
在这个示例中,identity(5) 和 identity(“Hello”) 分别传入了 number 类型和 string 类型的参数,TypeScript 根据传入的参数类型推导出 result1 和 result2 的具体类型。
3. 泛型函数处理数组示例:
function reverse<T>(items: T[]): T[] {
return items.reverse();
}
let numbers = [1, 2, 3, 4, 5];
let reversedNumbers = reverse(numbers); // reversedNumbers 的类型为 number[]
console.log(reversedNumbers); // 输出 [5, 4, 3, 2, 1]
let names = ["Alice", "Bob", "Charlie"];
let reversedNames = reverse(names); // reversedNames 的类型为 string[]
console.log(reversedNames); // 输出 ["Charlie", "Bob", "Alice"]
在这个示例中,reverse 函数是一个泛型函数,接受一个数组 items,其中的元素类型为 T。根据传入的 numbers 和 names 数组,分别推导出 reversedNumbers 和 reversedNames 的具体类型。
5 比较 Vue 2 和 Vue 3 的响应式系统有什么区别?
(1)考察点分析
- 深入了解 Vue 框架:考察候选人对 Vue.js 框架的理解程度,特别是其核心的响应式系统。
- 版本差异的掌握:考察候选人是否能够区分和理解 Vue 2 和 Vue 3 在响应式系统实现上的主要区别。
- 性能优化和技术升级:考察候选人对新技术(如 Proxy)的接受和掌握程度,以及这些技术在框架升级中的应用。
(2)最终答案
- 性能优化:Vue 3 的 Proxy 相较于 Vue 2 的 Object.defineProperty 有更好的性能,因为 Proxy 的代理操作更高效。
- 功能增强:Vue 3 的 Proxy 支持更多的操作捕获,使得框架能够更精确地追踪响应式依赖。
- 适应新特性:Vue 3 的响应式系统更适应现代 JavaScript 和浏览器的发展趋势,如 ES6+ 的语法特性和新的浏览器API。
6 请解释一下 CSS 盒模型及其组成部分
(1)考察点分析
- 基本概念理解:考察候选人对 CSS 盒模型的基本理解和应用能力。
- 属性应用能力:考察候选人是否能清晰地解释盒模型的各个组成部分及其对页面布局的影响。
- 问题解决能力:考察候选人在实际开发中如何应用盒模型进行页面设计和调试。
(2)最终答案
-
盒模型概述:
CSS 盒模型是用来描述每个 HTML 元素在页面布局中所占空间的模型。它包括了元素的内容区域、内边距、边框和外边距四个部分。
-
组成部分详解:
- 内容区域(Content):显示元素实际内容的区域,可以通过设置
width
和height
属性控制大小。 - 内边距(Padding):内容区域与边框之间的空白区域,可以使用
padding
属性设置,如padding: 10px;
。 - 边框(Border):内边距外的边框,可以使用
border
属性定义边框的样式、宽度和颜色,如border: 1px solid #000;
。 - 外边距(Margin):元素与相邻元素之间的空白区域,可以使用
margin
属性设置,如margin: 10px;
。
- 内容区域(Content):显示元素实际内容的区域,可以通过设置
7 请解释一下动画在前端开发中的应用及其实现方式
(1)考察点分析
- 基本概念理解:考察候选人对动画的基本概念和在前端开发中的重要性理解。
- 技术实现能力:考察候选人是否了解和熟悉常见的动画实现方式,如 CSS 动画和 JavaScript 动画。
- 性能优化和交互设计:考察候选人在动画设计中如何考虑性能优化和用户交互体验。
(2)最终答案
-
动画概述:
动画在前端开发中广泛应用,用于增强用户体验和提升网页交互性。它能够吸引用户的注意力,改善页面流畅性,并提供反馈和状态转换的视觉效果。
-
实现方式:
-
CSS 动画:使用
@keyframes
定义动画的关键帧,结合transition
属性实现简单的过渡效果和动画效果。例如:.box { width: 100px; height: 100px; background-color: red; transition: width 0.3s ease-in-out; } .box:hover { width: 150px; }
-
JavaScript 动画:通过 JavaScript 可以实现更复杂的动画效果和交互。常用的库如 GSAP(GreenSock Animation Platform)提供了丰富的动画 API,可以实现复杂的序列动画和交互效果。
// 使用 GSAP 库实现动画 gsap.to(".box", { duration: 1, x: 100, rotation: 360, ease: "power2.inOut" });
-
-
性能优化和交互设计:
- 动画性能优化包括减少动画数量和复杂度、使用硬件加速、避免频繁的布局重绘和重排。
- 在设计动画时,应考虑用户交互体验和无障碍访问,确保动画不会干扰用户的操作和浏览体验。
8 如何使用 Flex实现水平和垂直居中
(1)考察点分析
- Flexbox 基础理解:考察候选人对 Flexbox 布局模型的基本理解和应用能力。
- 居中实现能力:考察候选人是否能清晰地解释如何使用 Flexbox 实现元素的水平和垂直居中。
- 应对不同情况的能力:考察候选人在面对各种布局需求时,如何灵活应用 Flexbox 进行布局和居中。
(2)最终答案
-
Flexbox 概述:
Flexbox 是一种用于页面布局的弹性盒子模型,通过设置父容器的
display: flex;
属性,可以控制子项目的布局和排列方式。 -
水平居中实现:
.container { display: flex; justify-content: center; /* 水平居中 */ }
上述代码中,通过设置 .container 容器为 flex 布局,并使用 justify-content: center; 属性将子项目水平居中。
9 请解释一下 JavaScript 中的闭包(Closure)及其应用场景
(1)考察点分析
- 基本概念理解:考察候选人对闭包的基本定义和理解能力。
- 闭包的特性和作用:考察候选人是否能清晰地说明闭包的特性以及在实际开发中的作用。
- 应用场景:考察候选人能否举例说明闭包在实际开发中的常见应用场景。
(2)最终答案
-
闭包概述:
闭包是指在函数内部创建另一个函数,并且内部函数可以访问外部函数的变量和参数,即使外部函数已经执行完毕。闭包通过保存了外部函数的执行环境,使得内部函数能够继续访问和操作外部函数的变量。
-
特性和作用:
- 保护变量:内部函数可以访问外部函数的局部变量,但外部函数无法直接访问内部函数的变量,从而实现了数据的私有化和保护。
- 延长变量的生命周期:外部函数执行完毕后,其作用域中的变量在闭包的作用下仍然可以被内部函数访问和操作,延长了变量的生命周期。
- 模块化开发:利用闭包可以实现模块化开发,封装私有变量和方法,并提供接口供外部调用,实现了模块的封装和重用。
-
应用场景示例:
-
事件处理函数:在事件监听中使用闭包可以保存事件触发时的状态或数据。
function addClickHandler() { var count = 0; document.getElementById('btn').addEventListener('click', function() { count++; console.log('Button clicked ' + count + ' times'); }); }
-
定时器:利用闭包可以保存定时器中的变量状态,实现循环或延时操作。
function delayedMessage() { var count = 0; var interval = setInterval(function() { count++; console.log('Delayed message ' + count); if (count >= 5) { clearInterval(interval); } }, 1000); }
-
10 请解释一下 JavaScript 中的深拷贝与浅拷贝及其区别
(1)考察点分析
- 基本概念理解:考察候选人对深拷贝和浅拷贝的基本概念和区别的理解。
- 实际操作能力:考察候选人是否能清晰地解释和实现深拷贝和浅拷贝的方法。
- 问题解决能力:考察候选人在面对不同的对象复制需求时,如何选择和应用合适的方法进行拷贝。
(2)最终答案
-
深拷贝与浅拷贝概述:
- 浅拷贝:浅拷贝是指创建一个新对象,这个新对象的属性是原对象属性的引用。浅拷贝只复制对象的第一层属性,如果属性是对象,则只复制其引用,原对象和新对象共享同一块内存空间。
- 深拷贝:深拷贝是指创建一个新对象,并递归地复制所有嵌套对象的属性,使得新对象和原对象完全独立,互不影响。
-
浅拷贝的实现方法:
-
使用
Object.assign()
实现浅拷贝:let obj1 = { a: 1, b: { c: 2 } }; let shallowCopy = Object.assign({}, obj1);
-
使用展开运算符
...
实现浅拷贝:let shallowCopy = { ...obj1 };
-
-
深拷贝的实现方法:
-
使用递归函数手动实现深拷贝:
function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } let copy = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepClone(obj[key]); } } return copy; }
-
使用
JSON.parse(JSON.stringify())
实现深拷贝(注意局限性):let deepCopy = JSON.parse(JSON.stringify(obj1));
-
使用 Lodash 的
_.cloneDeep
方法实现深拷贝:let _ = require('lodash'); let deepCopy = _.cloneDeep(obj1);
-
-
实际应用和注意事项:
- 在实际开发中,选择深拷贝还是浅拷贝取决于具体需求。如果只需要复制对象的第一层属性,浅拷贝通常更高效;如果需要完全独立的副本,深拷贝是必需的。
- 使用
JSON.parse(JSON.stringify())
方法时需要注意它的局限性,如不能复制函数、不可枚举属性、以及处理循环引用的能力。 - 处理复杂对象结构或需要高性能时,可以考虑使用第三方库如 Lodash 来实现深拷贝。
11 请解释一下 ES6(ECMAScript 2015)有哪些新特性,并举例说明其应用。
(1)考察点分析
- 基础知识掌握:考察候选人对 ES6 的基本新特性是否熟悉。
- 理解和应用能力:考察候选人是否能够理解每个新特性及其应用场景。
- 实际编码能力:考察候选人是否能够通过代码示例展示对新特性的理解和应用。
(2)最终答案
-
简要概述 ES6:
ES6 是 ECMAScript 的第六版,也是 JavaScript 的重大更新,带来了许多新特性和改进,提升了开发效率和代码可读性。
-
主要新特性及应用:
-
let
和const
声明:块级作用域变量声明。let x = 10; const y = 20;
-
箭头函数:更简洁的函数语法和词法作用域的
this
绑定。const add = (a, b) => a + b; console.log(add(5, 3)); // 8
-
模板字符串:嵌入变量的字符串模板,更加简洁易读。
const name = 'Alice'; const greeting = `Hello, ${name}!`; console.log(greeting); // Hello, Alice!
-
解构赋值:从数组或对象中提取值到变量中。
const [a, b] = [1, 2]; const {x, y} = {x: 3, y: 4}; console.log(a, b); // 1, 2 console.log(x, y); // 3, 4
-
默认参数:为函数参数设置默认值。
function greet(name = 'Guest') { return `Hello, ${name}!`; } console.log(greet()); // Hello, Guest! console.log(greet('Bob')); // Hello, Bob!
-
展开运算符(
...
):用于数组和对象的展开。const arr1 = [1, 2, 3]; const arr2 = [...arr1, 4, 5]; console.log(arr2); // [1, 2, 3, 4, 5]
-
for...of
循环:遍历可迭代对象的值。const numbers = [10, 20, 30]; for (const num of numbers) { console.log(num); }
-
Map
和Set
数据结构:提供更灵活的数据存储方式。const map = new Map(); map.set('key1', 'value1'); console.log(map.get('key1')); // value1 const set = new Set([1, 2, 3]); set.add(4); console.log(set.has(2)); // true
-
模块化:使用
import
和export
进行模块化开发。// module.js export const greet = (name) => `Hello, ${name}!`; // main.js import { greet } from './module.js'; console.log(greet('Alice')); // Hello, Alice!
-
类(Class):更接近面向对象编程的语法糖。
class Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; } } const alice = new Person('Alice'); console.log(alice.greet()); // Hello, Alice!
-
Promise:异步编程的新方式,取代回调函数。
const fetchData = () => { return new Promise((resolve, reject) => { setTimeout(() => resolve('Data fetched'), 1000); }); }; fetchData().then((data) => console.log(data)); // Data fetched
-
12 请解释一下 JavaScript 中的箭头函数及其应用场景
(1)考察点分析
- 基本概念理解:考察候选人对箭头函数基本语法和特点的理解。
- 特性和使用场景:考察候选人是否能够清晰地说明箭头函数的特性及其在实际开发中的应用场景。
- 比较和选择能力:考察候选人能否理解箭头函数与传统函数的区别,并在适当的场景中做出选择。
(2)最终答案
-
箭头函数概述:
箭头函数是一种更简洁的函数书写方式,引入了新的语法,减少了冗余代码。箭头函数不会创建自己的
this
,而是从外层作用域继承this
。 -
箭头函数的语法:
- 基本语法:使用箭头
=>
定义函数。 - 单个参数时,可以省略参数括号:
const add = x => x + 10; console.log(add(5)); // 15
- 多个参数时,需要使用括号:
const add = (x, y) => x + y; console.log(add(5, 3)); // 8
- 单行语句隐式返回结果:
const square = x => x * x; console.log(square(4)); // 16
- 多行语句需使用花括号和
return
语句:const sum = (x, y) => { const result = x + y; return result; }; console.log(sum(5, 3)); // 8
- 基本语法:使用箭头
-
箭头函数的特性:
- 没有
this
绑定:箭头函数不会创建自己的this
,而是继承自外层作用域:function Timer() { this.seconds = 0; setInterval(() => { this.seconds++; console.log(this.seconds); }, 1000); } const timer = new Timer(); // 每秒输出递增的数字
- 没有
arguments
对象:箭头函数没有自己的arguments
对象,可使用展开运算符...args
代替:const sum = (...args) => args.reduce((acc, val) => acc + val, 0); console.log(sum(1, 2, 3, 4)); // 10
- 不能用作构造函数:箭头函数不能使用
new
关键字实例化对象。 - 没有
prototype
属性:箭头函数没有prototype
属性。
- 没有
-
应用场景:
- 简化回调函数:箭头函数在回调函数中使用简洁明了:
const numbers = [1, 2, 3, 4]; const doubled = numbers.map(n => n * 2); console.log(doubled); // [2, 4, 6, 8]
- 保持
this
上下文:在需要保持当前this
上下文的场景中使用,如事件处理器、定时器等:class MyComponent { constructor() { this.name = 'MyComponent'; document.getElementById('myButton').addEventListener('click', () => { console.log(this.name); // 输出 'MyComponent' }); } } const component = new MyComponent();
- 简化回调函数:箭头函数在回调函数中使用简洁明了:
13 请解释一下 JavaScript 中 Map 和 Object 的区别
(1)考察点分析
- 基础知识理解:考察候选人对 Map 和 Object 基本概念的理解。
- 特性和使用场景:考察候选人是否能清晰地说明 Map 和 Object 各自的特性及其在实际开发中的适用场景。
- 实际操作能力:考察候选人是否能够通过代码示例展示对 Map 和 Object 的理解和应用。
(2)最终答案
-
基本概念:
Object
:用于存储键值对,键名只能是字符串或符号(Symbol
)。Map
:一种新的键值对存储结构,键名可以是任意类型的值(包括对象)。
-
主要区别:
-
键的类型:
Object
的键名只能是字符串或符号。Map
的键名可以是任意类型。
-
键值对的迭代顺序:
Object
的键值对没有固定的迭代顺序。Map
的键值对按照插入顺序进行迭代。
-
性能:
Map
在频繁增删键值对时性能较优。Object
在简单的、静态键值对存储时较为高效。
-
常用操作:
Map
提供了更丰富的操作方法,如set
、get
、has
、delete
、clear
。Object
需要使用更多的原生方法来实现类似功能。
-
大小:
Map
有size
属性,可以直接获取键值对的数量。Object
没有直接获取大小的属性,需要手动计算。
-
原型链:
Object
有原型链,会继承一些默认属性和方法。Map
不会继承任何默认属性和方法。
-
-
代码示例:
-
创建和操作
Map
:const map = new Map(); map.set('key1', 'value1'); map.set(2, 'value2'); console.log(map.get('key1')); // 'value1' console.log(map.get(2)); // 'value2' console.log(map.size); // 2 map.delete('key1'); console.log(map.has('key1')); // false
-
创建和操作
Object
:const obj = {}; obj['key1'] = 'value1'; obj[2] = 'value2'; console.log(obj['key1']); // 'value1' console.log(obj[2]); // 'value2' console.log(Object.keys(obj).length); // 2 delete obj['key1']; console.log('key1' in obj); // false
-
14 请解释一下 MVVM 和 MVC 的区别
(1)考察点分析
- 基础概念理解:考察候选人对 MVVM 和 MVC 两种架构模式的基本理解。
- 特性和结构:考察候选人是否能清晰地说明两者的结构及其特性。
- 应用场景和选择能力:考察候选人能否理解 MVVM 和 MVC 的适用场景,并在实际开发中做出选择。
(2)最终答案
-
MVVM 和 MVC 概述:
- MVC(Model-View-Controller):一种经典的软件架构模式,将应用分为三个部分:模型(Model)、视图(View)和控制器(Controller)。
- MVVM(Model-View-ViewModel):一种现代的软件架构模式,将应用分为三个部分:模型(Model)、视图(View)和视图模型(ViewModel)。
-
结构和特性:
-
MVC:
- Model:负责数据和业务逻辑。
- View:负责用户界面展示。
- Controller:处理用户输入,更新模型和视图。
-
MVVM:
- Model:负责数据和业务逻辑。
- View:负责用户界面展示。
- ViewModel:处理视图的行为和状态,通过双向数据绑定将视图和模型连接起来。
-
-
主要区别:
-
数据绑定:
- MVC:数据绑定通常是手动的,通过控制器来更新视图。
- MVVM:采用双向数据绑定,视图和模型之间的同步由框架自动处理。
-
关注点分离:
- MVC:控制器直接处理用户输入,关注点相对集中。
- MVVM:视图模型解耦了视图和模型,使得视图和业务逻辑的关注点更加清晰。
-
复杂度和灵活性:
- MVC:结构较为简单,适用于中小型应用。
- MVVM:适用于需要复杂状态管理和动态更新的大型应用。
-
框架支持:
- MVC:常见于服务器端框架,如 Ruby on Rails、ASP.NET MVC。
- MVVM:常见于前端框架,如 Angular、Vue.js。
-
-
代码示例:
-
MVC:
// Model class Model { constructor() { this.data = ''; } setData(newData) { this.data = newData; } getData() { return this.data; } } // View class View { constructor(controller) { this.controller = controller; this.init(); } init() { document.getElementById('button').addEventListener('click', () => { this.controller.updateModel(); }); } render(data) { document.getElementById('output').innerText = data; } } // Controller class Controller { constructor(model, view) { this.model = model; this.view = view; } updateModel() { this.model.setData('Hello MVC'); this.view.render(this.model.getData()); } } const model = new Model(); const controller = new Controller(model); const view = new View(controller);
-
MVVM(以 Vue.js 为例):
<!-- View --> <div id="app"> <input v-model="message" /> <p>{{ message }}</p> </div> <script> // ViewModel new Vue({ el: '#app', data: { message: 'Hello MVVM' } }); </script>
-
15 请解释一下 Vue 的生命周期
(1)考察点分析
- 基础概念理解:考察候选人对 Vue 生命周期的基本理解。
- 各个生命周期钩子函数的作用和用法:考察候选人是否能清晰地说明各个生命周期钩子的作用和具体使用场景。
- 实际操作能力:考察候选人是否能够通过代码示例展示对生命周期钩子函数的理解和应用。
(2)最终答案
-
Vue 生命周期概述:
Vue 实例在创建过程中会经历一系列的初始化过程,从创建、挂载、更新到销毁,这一过程称为生命周期。
-
各个生命周期钩子函数的介绍:
- beforeCreate:实例初始化之后,数据观测和事件配置之前调用。
- created:实例创建完成,数据观测和事件配置已完成,但还未挂载到 DOM。
- beforeMount:在挂载开始之前调用,相关的 render 函数首次被调用。
- mounted:实例挂载到 DOM 后调用,DOM 操作可以在此进行。
- beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
- updated:由于数据更改导致虚拟 DOM 重新渲染和打补丁后调用。
- beforeDestroy:实例销毁之前调用,此时实例仍然完全可用。
- destroyed:实例销毁后调用,组件的所有事件监听器被移除,所有子实例也被销毁。
-
代码示例:
展示一个简单的 Vue 组件,使用所有生命周期钩子函数:
<template> <div> <p>{{ message }}</p> <button @click="updateMessage">Update Message</button> </div> </template> <script> export default { data() { return { message: 'Hello Vue!' }; }, beforeCreate() { console.log('beforeCreate: 实例初始化之后,数据观测和事件配置之前调用'); }, created() { console.log('created: 实例创建完成,数据观测和事件配置已完成,但还未挂载到 DOM'); }, beforeMount() { console.log('beforeMount: 在挂载开始之前调用,相关的 render 函数首次被调用'); }, mounted() { console.log('mounted: 实例挂载到 DOM 后调用,DOM 操作可以在此进行'); }, beforeUpdate() { console.log('beforeUpdate: 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前'); }, updated() { console.log('updated: 由于数据更改导致虚拟 DOM 重新渲染和打补丁后调用'); }, beforeDestroy() { console.log('beforeDestroy: 实例销毁之前调用,此时实例仍然完全可用'); }, destroyed() { console.log('destroyed: 实例销毁后调用,组件的所有事件监听器被移除,所有子实例也被销毁'); }, methods: { updateMessage() { this.message = 'Message Updated!'; } } }; </script>
16 请解释一下 Vue 父子组件的生命周期顺序
(1)考察点分析
- 生命周期钩子的理解:考察候选人对 Vue 组件生命周期钩子的理解。
- 父子组件的生命周期顺序:考察候选人是否清楚父子组件在创建和销毁时生命周期钩子的触发顺序。
- 实际操作能力:考察候选人是否能够通过代码示例展示对父子组件生命周期顺序的理解和应用。
(2)最终答案
-
父子组件生命周期钩子的基本概念:
了解 Vue 组件的生命周期钩子:
beforeCreate
、created
、beforeMount
、mounted
、beforeUpdate
、updated
、beforeDestroy
和destroyed
。 -
父子组件生命周期的触发顺序:
-
创建阶段:
- 父组件的
beforeCreate
- 父组件的
created
- 父组件的
beforeMount
- 子组件的
beforeCreate
- 子组件的
created
- 子组件的
beforeMount
- 子组件的
mounted
- 父组件的
mounted
- 父组件的
-
更新阶段:
- 父组件的
beforeUpdate
- 子组件的
beforeUpdate
- 子组件的
updated
- 父组件的
updated
- 父组件的
-
销毁阶段:
- 父组件的
beforeDestroy
- 子组件的
beforeDestroy
- 子组件的
destroyed
- 父组件的
destroyed
- 父组件的
-
-
代码示例:
展示一个简单的父子组件,打印出生命周期钩子的触发顺序:
父组件:
<template>
<div>
<p>Parent Component</p>
<child-component />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
beforeCreate() {
console.log('Parent beforeCreate');
},
created() {
console.log('Parent created');
},
beforeMount() {
console.log('Parent beforeMount');
},
mounted() {
console.log('Parent mounted');
},
beforeUpdate() {
console.log('Parent beforeUpdate');
},
updated() {
console.log('Parent updated');
},
beforeDestroy() {
console.log('Parent beforeDestroy');
},
destroyed() {
console.log('Parent destroyed');
}
};
</script>
子组件:
<template>
<div>
<p>Child Component</p>
</div>
</template>
<script>
export default {
beforeCreate() {
console.log('Child beforeCreate');
},
created() {
console.log('Child created');
},
beforeMount() {
console.log('Child beforeMount');
},
mounted() {
console.log('Child mounted');
},
beforeUpdate() {
console.log('Child beforeUpdate');
},
updated() {
console.log('Child updated');
},
beforeDestroy() {
console.log('Child beforeDestroy');
},
destroyed() {
console.log('Child destroyed');
}
};
</script>
17 Vue双向数据绑定的原理
(1)考察点分析
- 理解数据绑定的机制:考察候选人对 Vue 双向数据绑定的底层实现原理的理解。
- 数据劫持与发布订阅模式:考察候选人是否了解 Vue 是如何利用数据劫持和发布订阅模式来实现双向数据绑定的。
- Vue 的响应式系统:考察候选人对 Vue 响应式系统的基本了解,包括依赖追踪和更新策略。
(2)最终答案
-
双向数据绑定的基本概念:
Vue 的双向数据绑定是指数据模型层和视图层之间的自动同步,数据的变化会实时更新到视图,而视图中的变化也会反映到数据模型中。
-
数据劫持(Object.defineProperty):
Vue 使用
Object.defineProperty
方法来劫持对象属性的访问和修改。通过这种方式,Vue 能够监听到每个属性的变化,从而实现数据的响应式更新。 -
发布订阅模式:
在 Vue 中,每个数据都有对应的依赖收集器(Dep),而视图中的指令(比如
v-model
)会创建一个监听器(Watcher)。当数据变化时,会触发对应属性的依赖收集器,通知所有相关的监听器更新视图。 -
响应式系统的运作机制:
- 初始化阶段:
- Vue 在组件实例化时,对数据对象进行递归遍历,对每个属性使用
Object.defineProperty
进行数据劫持。 - 同时,为每个属性初始化一个依赖收集器(Dep),用来存储依赖该属性的监听器(Watcher)。
- Vue 在组件实例化时,对数据对象进行递归遍历,对每个属性使用
- 依赖追踪:
- 当模板中使用了某个属性时,会创建一个对应的监听器(Watcher),并将其加入到属性的依赖收集器中。
- 当属性被修改时,会触发该属性的依赖收集器,通知所有相关的监听器更新视图。
- 更新策略:
- Vue 使用虚拟 DOM 和差异化比较算法,找出需要更新的部分,并高效地更新到视图上,从而实现了快速响应和高效的渲染性能。
- 初始化阶段:
18 vue组件的通信方式
(1)考察点分析
- 组件通信方式:考察候选人是否了解 Vue 中组件之间通信的多种方式,如 props、事件、Vuex 等。
- 适用场景和优缺点:考察候选人是否能根据不同的场景选择合适的通信方式,并理解各种方式的优缺点。
- 实际应用能力:考察候选人是否能够通过示例或场景来展示组件通信的实际应用能力。
(2)最终答案
-
组件通信的基本方式:
-
props / $emit:父子组件通信的基础方式。父组件通过 props 向子组件传递数据,子组件通过 $emit 触发事件向父组件传递数据。
-
事件总线:利用 Vue 实例作为中央事件总线,可以在任意关系的组件之间传递事件或数据,适合于兄弟组件通信。
-
Vuex:用于大型应用中集中管理共享状态。通过 store 存储和管理状态,各组件通过 getters 和 mutations 修改和获取状态。
-
$attrs / $listeners:透传父组件的属性和监听器到子组件,简化了属性的传递和事件的监听,适合于需要传递大量属性的情况。
-
provide / inject:祖先组件通过 provide 提供数据,后代组件通过 inject 注入数据,适合于跨层级组件通信的场景。
-
-
场景和优缺点分析:
-
props / $emit:简单直观,适合于父子组件通信,但对于复杂嵌套或多层级组件通信可能会显得繁琐。
-
事件总线:适合于非直接关系的组件通信,但组件之间的关系不明确,不易于追踪和调试。
-
Vuex:适合于状态复杂或需要多个组件共享状态的场景,但对于小型应用可能会显得过于复杂。
-
$attrs / $listeners:适合于需要将父组件的属性和事件传递给子组件的情况,但不适合于多层级嵌套的场景。
-
provide / inject:适合于祖先组件向所有后代组件传递依赖,但当跨级关系不清晰时会导致组件之间的耦合性增加。
-
-
示例代码:
父子组件通信示例(props / $emit):
ParentComponent.vue:
<template>
<div>
<p>Parent Component</p>
<ChildComponent :message="parentMessage" @update="handleUpdate" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: 'Message from parent'
};
},
methods: {
handleUpdate(newMessage) {
this.parentMessage = newMessage;
}
}
};
</script>
ChildComponent.vue:
<template>
<div>
<p>Child Component</p>
<input type="text" v-model="message">
<button @click="updateParent">Update Parent</button>
</div>
</template>
<script>
export default {
props: ['message'],
methods: {
updateParent() {
this.$emit('update', this.message);
}
}
};
</script>
这个示例展示了父组件通过 props 向子组件传递数据,并通过 $emit 触发事件向父组件传递数据的过程。
19 如何解决跨域问题?
(1)考察点分析
- 理解跨域原理:考察候选人对跨域问题的基本理解,包括同源策略及其限制。
- 解决跨域的方法:考察候选人是否了解常见的跨域解决方案,如 CORS、JSONP、代理服务器等。
- 安全性考虑:考察候选人在解决跨域问题时是否考虑到安全性和最佳实践。
(2)最终答案
-
同源策略:
浏览器的同源策略限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
-
解决跨域的常见方法:
-
CORS(跨域资源共享):通过服务端设置响应头部,如
Access-Control-Allow-Origin
,允许指定的源访问资源。 -
JSONP(JSON with Padding):利用
<script>
标签的跨域特性,动态创建<script>
标签请求跨域数据,并通过回调函数处理返回数据。 -
代理服务器:前端通过向同域名下的代理服务器发起请求,再由代理服务器转发到目标服务器,绕过浏览器的同源策略限制。
-
WebSocket:使用 WebSocket 协议进行通信,支持跨域,但需要服务器端支持 WebSocket。
-
跨域资源嵌入:在响应中设置合适的跨域资源嵌入标记,如
<link rel="stylesheet" crossorigin="anonymous">
和<img crossorigin="anonymous">
。
-
-
安全性考虑:
-
使用 CORS 时,确保服务器设置合适的响应头,避免开放过多权限,防止恶意请求。
-
JSONP 存在安全隐患,必须信任响应的内容,可能容易受到 XSS 攻击的影响。
-
使用代理服务器时,需要进行严格的安全审查和过滤,防止恶意请求通过代理访问目标服务器。
-
20 请你谈谈你对堆和栈的理解
(1)考察点分析
- 存储结构:了解堆和栈在内存中的具体存储结构和组织方式。
- 数据存取:理解在堆和栈中数据的存储和访问方式。
- 使用场景:掌握在不同情况下应该选择使用堆还是栈的原则。
(2)最终答案
-
栈(Stack):
- 栈是一种后进先出(LIFO)的数据结构,数据的存取速度快,存储在连续的内存空间中。
- 栈的大小有限,由编译器或运行时系统进行管理,存储函数调用、局部变量以及函数执行上下文。
- 函数调用时,会创建栈帧,函数执行完毕后,栈帧被销毁,释放栈空间。
-
堆(Heap):
- 堆是一种不连续的内存区域,用于动态分配内存空间。
- 堆的存取速度相对较慢,因为数据存储在不同的内存块中,需要通过指针进行间接访问。
- 堆的大小通常比栈大,用于存储动态分配的对象、全局变量等,需要手动管理内存的分配和释放,否则可能导致内存泄漏或碎片化问题。
-
使用场景:
- 栈适合存储函数调用、局部变量等生命周期短暂且大小确定的数据。
- 堆适合存储动态分配的数据、全局变量等生命周期较长或大小不确定的数据。
21 TCP 和 UDP 的区别
(1)考察点分析
- 协议类型:理解 TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)的基本特点和用途。
- 特点对比:对比它们在可靠性、连接性、数据传输方式等方面的不同。
- 适用场景:了解在不同的应用场景下,选择 TCP 还是 UDP 的考量。
(2)最终答案
-
TCP(传输控制协议):
- 连接性:面向连接,通信前需要建立连接,形成可靠的数据传输通道。
- 可靠性:提供数据传输的可靠性保证,通过确认和重传机制确保数据的正确性和顺序性。
- 流量控制:使用滑动窗口协议进行流量控制,防止数据丢失和网络拥塞。
- 应用场景:适用于需要可靠数据传输、数据顺序性和不允许丢包的应用,如文件传输、网页访问等。
-
UDP(用户数据报协议):
- 连接性:无连接,发送数据前不需要建立连接。
- 可靠性:不提供数据传输的可靠性保证,数据可能丢失或者乱序到达。
- 传输方式:面向无连接的简单数据传输方式,快速传输数据,不保证数据顺序和到达。
- 应用场景:适用于实时性要求高、允许丢包的应用,如音频/视频流传输、在线游戏数据传输等。
22 直播使用TCP还是UDP?
(1)考察点分析
- 协议特性:理解TCP和UDP协议的基本特性和差异。
- 直播需求:了解直播场景对网络传输的具体需求。
- 技术选择:根据直播的特点,评估和选择适合的传输协议。
(2)最终答案
直播场景下TCP和UDP的比较:
-
TCP(Transmission Control Protocol):
- 可靠性:TCP提供数据包的确认和重传机制,确保数据可靠传输。
- 有序性:TCP保证数据包按顺序到达。
- 拥塞控制:TCP具有流量控制和拥塞控制机制。
- 适用性:适用于对数据完整性要求高的应用,如文件传输、网页浏览等。
-
UDP(User Datagram Protocol):
- 低延迟:UDP没有建立连接的过程,数据传输速度快。
- 无状态:UDP不维护连接状态,不保证数据包的顺序、可靠性或完整性。
- 简单高效:UDP协议简单,头部开销小,适合快速传输。
- 适用性:适用于对实时性要求高的应用,如在线游戏、实时视频会议等。
直播场景分析:
- 实时性:直播对实时性要求极高,观众希望看到尽可能无延迟的画面。
- 流畅性:直播过程中,轻微的数据丢失可能不会显著影响观看体验,但长时间的卡顿或重连会极大影响用户体验。
- 并发性:直播可能面临大量用户同时观看,需要高效处理高并发的数据传输。
结论:
对于直播应用,UDP 通常是更好的选择,因为它提供了更低的延迟和更高的传输效率,适合实时传输。然而,完全使用UDP可能会遇到数据丢失的问题,特别是在不稳定的网络环境下。因此,实际应用中可能会采用以下策略:
- UDP为主:主要使用UDP来保证直播的实时性和流畅性。
- FEC(Forward Error Correction):使用前向纠错技术来减少数据丢失的影响,提高传输的可靠性。
- HLS/DASH:采用HTTP Live Streaming或Dynamic Adaptive Streaming over HTTP等自适应比特率流技术,允许客户端根据网络状况选择不同质量的视频流。
最终,选择哪种协议或技术,需要根据具体的应用需求和网络环境进行权衡。
23 为什么TCP需要三次握手?
(1)考察点分析
- TCP连接建立:理解TCP连接建立的基本原理。
- 三次握手过程:了解三次握手的每个步骤及其目的。
- 安全性和可靠性:认识到三次握手在确保连接双方准备就绪和防止重复连接中的作用。
(2)最终答案
TCP三次握手的步骤和原因:
-
第一次握手 - SYN:
- 客户端选择一个初始序列号
x
,向服务器发送一个SYN报文段(SYN=1),请求建立连接。 - 客户端进入
SYN_SENT
状态,等待服务器确认。
- 客户端选择一个初始序列号
-
第二次握手 - SYN-ACK:
- 服务器收到客户端的SYN报文后,如果同意建立连接,会分配TCP资源,并发送一个SYN-ACK报文段(SYN=1, ACK=1)。
- 服务器的ACK确认号为
x+1
,表示期望收到从x+1
开始的数据。 - 服务器进入
SYN_RCVD
状态。
-
第三次握手 - ACK:
- 客户端收到服务器的SYN-ACK报文后,会发送一个ACK报文段(ACK=1),确认收到服务器的SYN。
- 客户端的ACK确认号为服务器的初始序列号加1,即
y+1
。 - 客户端和服务器此时都进入了
ESTABLISHED
状态,连接建立成功。
为什么需要三次握手:
- 防止失效的连接请求突然传送到了服务端:三次握手可以防止服务器端打开一个不存在的连接。
- 确保双方的接收和发送能力都是正常的:三次握手可以确认客户端和服务器都能够接收和发送数据。
- 同步初始序列号:三次握手过程中,双方交换各自的初始序列号,为后续的数据传输建立基准。
结论:
三次握手是TCP连接建立过程中的一个关键步骤,它确保了连接的可靠性和数据传输的稳定性。通过这个机制,TCP协议能够在不可靠的IP网络层之上提供可靠的数据传输服务。
24 为什么TCP需要四次挥手?
(1)考察点分析
- TCP连接终止:理解TCP连接终止的基本原理。
- 四次挥手过程:了解四次挥手的每个步骤及其目的。
- 数据传输完成:认识到TCP需要确保双方数据传输都完成后才能安全关闭连接。
(2)最终答案
TCP四次挥手的步骤和原因:
-
第一次挥手 - FIN:
- 当一方(假设为客户端)完成数据发送任务后,发送一个FIN报文段(FIN=1),用来关闭主动方到被动方的数据传输。
- 客户端进入
FIN-WAIT-1
状态。
-
第二次挥手 - ACK:
- 接收方(服务器)收到FIN报文后,发送一个ACK报文段确认收到FIN,并告知对方可以关闭其到发送方的数据传输。
- 客户端收到ACK后,进入
FIN-WAIT-2
状态。
-
第三次挥手 - FIN:
- 接收方(服务器)完成数据发送任务后,发送一个FIN报文段关闭其到发送方的数据传输。
- 服务器进入
LAST-ACK
状态。
-
第四次挥手 - ACK:
- 发送方(客户端)收到这个FIN报文后,发送一个ACK报文段作为回应,并进入
TIME-WAIT
状态。 - 经过一段时间(称为2MSL,即最大报文段生存时间的两倍)后,客户端关闭连接。
- 发送方(客户端)收到这个FIN报文后,发送一个ACK报文段作为回应,并进入
为什么需要四次挥手:
- 全双工通信:TCP连接是全双工的,意味着双方都可以独立关闭各自的发送和接收通道。
- 确保数据传输完成:四次挥手确保了即使在数据传输完成后,双方也能够独立地关闭发送通道,而不是同时关闭。
- 防止数据丢失:确保所有数据都被接收和确认,防止在连接关闭过程中数据丢失。
- 有序关闭连接:四次挥手允许双方分别确认对方的关闭请求,确保连接能够有序地关闭。
结论:
四次挥手是TCP连接终止过程中的一个关键步骤,它确保了连接的有序和安全关闭。通过这个机制,TCP协议能够在保证数据传输完整性的同时,安全地结束通信会话。
25 标准盒模型(Standard Box Model)和怪异盒模型(IE Box Model)的区别
(1)考察点分析
- 盒模型理解:理解CSS盒模型的基本概念,包括元素的宽度、高度、内边距、边框和外边距。
- 盒模型差异:了解标准盒模型和怪异盒模型在计算元素宽度和高度时的区别。
- 兼容性考虑:认识到不同盒模型对网页布局的影响,以及在不同浏览器中的表现差异。
(2)最终答案
标准盒模型和怪异盒模型的区别:
-
标准盒模型(W3C标准):
- 在标准盒模型中,元素的
width
和height
指的是内容区域的宽度和高度。 - 元素的总宽度是
width
加上左右两侧的padding
和border
。 - 元素的总高度同理。
- 在标准盒模型中,元素的
-
怪异盒模型(IE盒模型):
- 在怪异盒模型中,元素的
width
和height
包括了内容区域、内边距、边框,但不包括外边距。 - 这意味着如果你设置了一个元素的
width
,这个值实际上已经包含了padding
和border
。
- 在怪异盒模型中,元素的
-
示例比较:
- 假设有一个元素,内容区域宽
100px
,内边距10px
,边框5px
。 - 在标准盒模型中,如果你设置
width: 100px;
,元素的总宽将是100px + 10px*2 + 5px*2 = 130px
。 - 在怪异盒模型中,如果同样设置
width: 100px;
,元素的总宽仍然是100px
,因为这里的100px
已经包括了padding
和border
。
- 假设有一个元素,内容区域宽
为什么存在两种盒模型:
- 历史原因:怪异盒模型是早期IE浏览器的实现方式,而标准盒模型是后来W3C制定的标准。
- 兼容性:开发者需要根据目标用户的浏览器情况选择合适的盒模型,或者使用CSS的
box-sizing
属性来统一盒模型的行为。
结论:
了解两种盒模型的差异对于跨浏览器开发非常重要。开发者可以通过设置 box-sizing: border-box;
来让元素的 width
和 height
按照标准盒模型来计算,这样可以简化布局并提高代码的可维护性。
26 上下两个元素的margin都是20,那他们的间距是多少?
(1)考察点分析
- 外边距折叠:理解CSS中外边距折叠的基本概念。
- 间距计算:掌握如何计算两个元素相遇时的实际间距。
(2)最终答案
上下两个元素的外边距间距问题:
假设我们有两个元素,每个元素的 margin-bottom
和 margin-top
都是20px。
外边距折叠现象:
在CSS中,当两个垂直方向相邻的元素相遇时,它们的外边距不会累加。相反,它们会折叠成一个单一的外边距,这个外边距的值等于两个相遇外边距中较大的那个。
间距计算规则:
- 如果两个元素的外边距值相同(在这个例子中都是20px),那么它们之间的间距将是这个值,即20px。
- 如果外边距值不同,比如一个元素的
margin-bottom
是10px,另一个元素的margin-top
是20px,那么间距将是20px,因为20px是两者中较大的值。
结论:
在这种情况下,两个元素的外边距都是20px,所以它们之间的间距是20px,而不是40px。这是因为发生了外边距折叠,只保留了较大的外边距值。
27 css垂直水平居中方法
(1)考察点分析
- 布局技术:理解不同的CSS布局技术,如Flexbox、Grid等。
- 居中策略:掌握使用CSS实现元素垂直和水平居中的方法。
- 兼容性:了解不同居中方法的浏览器兼容性。
(2)最终答案
CSS垂直水平居中的方法:
-
使用
margin
属性(传统方法):- 对于一个已知宽高的元素,可以通过设置
margin
属性为auto
来实现水平居中。 - 垂直居中则较难实现,通常需要额外的HTML元素或使用
line-height
属性。
- 对于一个已知宽高的元素,可以通过设置
-
使用Flexbox:
- Flexbox提供了一个简单的方式来居中子元素。
- 容器设置为
display: flex;
,然后使用justify-content: center;
和align-items: center;
实现水平和垂直居中。
.container { display: flex; justify-content: center; align-items: center; }
-
使用Grid:
- CSS Grid布局同样提供了居中的方法。
- 容器设置为display: grid;,并使用place-items: center;实现居中。
.container {
display: grid;
place-items: center;
}
-
使用绝对定位和transform属性:
- 将元素绝对定位到一个父容器的中心,然后使用transform: translate(-50%, -50%);来调整位置。
.container {
position: relative;
}
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
-
使用text-align和line-height(仅限于内联或内联块元素):
- 对于内联或内联块元素,可以使用text-align: center;实现水平居中。
- 垂直居中可以通过设置line-height等于元素的高度来实现。
28 前端路由及其在单页应用中的作用
(1)考察点分析:
- 前端路由概念:理解前端路由的基本原理和实现方式。
- SPA架构:了解单页应用的特点和为何需要前端路由。
- 路由库使用:掌握至少一种前端路由库的使用。
(2)最终结果:
- 前端路由定义:前端路由是一种在不重新加载整个页面的情况下,通过改变URL来实现页面内容更新的技术。
- SPA与前端路由:
- 单页应用(SPA)通过动态加载资源在用户与应用程序交互时不需要重新加载整个页面。
- 前端路由在SPA中用于管理URL和视图的映射,使得用户可以通过浏览器的前进和后退按钮导航历史。
- 前端路由实现:
- 使用前端路由库,如React Router或Vue Router,可以轻松定义路由规则和组件映射。
- 路由库监听URL的变化,并根据配置的路由规则动态加载相应的组件或视图。
假设我们使用React Router来实现前端路由:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
</Switch>
</Router>
);
}
29 如何实现一个Promise?
(1)考察点分析
- 理解Promise概念:掌握Promise对象的基本概念,包括其作用和工作流程。
- 状态管理:理解Promise的三种状态:Pending(等待)、Fulfilled(已成功)、Rejected(已失败)。
- 执行器函数:理解在Promise构造函数中执行的异步操作,以及如何通过resolve和reject函数改变Promise的状态。
- 链式调用:掌握
then
和catch
方法的使用,以及Promise链的构建。 - 错误处理:理解如何在Promise中进行错误捕获和处理。
(2)最终答案
实现一个基本的Promise可以按照以下步骤进行:
- 定义Promise类:创建一个类,其构造函数接收一个执行器函数作为参数。
- 初始化状态:在构造函数中初始化状态为Pending。
- 定义resolve和reject方法:这两个方法用于改变Promise的状态,并允许链式调用。
- 实现then和catch方法:允许用户添加回调函数,以处理Promise的成功或失败状态。
以下是使用JavaScript实现的简单Promise示例:
class MyPromise {
constructor(executor) {
// 初始化状态为Pending
this.state = 'Pending';
// 成功或失败的结果值
this.value = undefined;
// 存储then方法的回调队列
this.onFulfilled = [];
this.onRejected = [];
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
resolve(value) {
// 状态改变为Fulfilled,并存储值
if (this.state === 'Pending') {
this.state = 'Fulfilled';
this.value = value;
this.onFulfilled.forEach(fn => fn(value));
}
}
reject(error) {
// 状态改变为Rejected,并存储错误
if (this.state === 'Pending') {
this.state = 'Rejected';
this.value = error;
this.onRejected.forEach(fn => fn(error));
}
}
then(onFulfilled, onRejected) {
// 处理Fulfilled状态
if (this.state === 'Fulfilled') {
onFulfilled(this.value);
} else if (this.state === 'Rejected' && typeof onRejected === 'function') {
onRejected(this.value);
}
// 将回调加入队列,等待状态改变时调用
return new MyPromise((resolve, reject) => {
if (this.state === 'Pending') {
this.onFulfilled.push(value => {
// 提供对 then 的链式调用支持
resolve(onFulfilled ? onFulfilled(value) : value);
});
this.onRejected.push(error => {
reject(onRejected ? onRejected(error) : error);
});
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
30 JS 类型有哪些?
(1)考察点分析
- 基本数据类型:理解JavaScript的五种基本数据类型及其用途。
- 引用数据类型:掌握对象(Object)、数组(Array)、函数(Function)等引用类型的结构和行为。
- 特殊值:识别并区分
NaN
和undefined
这两种特殊值。 - 类型转换:了解JavaScript中类型转换的规则,包括强制类型转换和隐式类型转换。
- 类型检测:掌握如何使用
typeof
、instanceof
等操作符进行类型检测。
(2)最终答案
JavaScript的类型可以分为两大类:基本数据类型和引用数据类型。
- 基本数据类型
// Number
let age = 25;
// String
let name = 'John Doe';
// Boolean
let isApproved = false;
// Undefined
let x;
// Null
let y = null;
// Symbol (ES6 新增)
let mySymbol = Symbol('mySymbol');
- 引用数据类型
// Object
let person = {
name: 'Alice',
age: 30
};
// Array
let numbers = [1, 2, 3, 4, 5];
// Function
function sayHello() {
console.log('Hello, World!');
}
- 特殊值
NaN:不是一个数字(Not a Number),是一个代表错误数值的特殊值。
undefined:表示变量已声明但未初始化。 - 类型转换
JavaScript在运算或操作中可能会进行类型转换,例如:
'5' + 3; // "53",字符串和数字相加时,数字会被转换为字符串
- 类型检测
typeof:返回一个变量类型的字符串值。
instanceof:检测一个实例是否是特定构造函数的实例。
Object.prototype.toString.call():返回对象的类型字符串,是一种更准确的方法。
31 js的几种作用域,有什么区别?
(1)考察点分析
- 理解作用域的概念:考察候选人是否理解作用域及其在 JavaScript 中的重要性。
- 识别不同类型的作用域:考察候选人是否能够识别并区分不同类型的作用域,包括全局作用域、函数作用域和块级作用域。
- 应用场景与区别:考察候选人是否能够理解并解释不同作用域在实际应用中的区别和使用场景。
(2)最终答案
-
全局作用域:
- 在全局作用域中声明的变量可以在任何地方访问。
- 例如:
var globalVar = "I am a global variable"; function showGlobalVar() { console.log(globalVar); // 可以访问到 globalVar } showGlobalVar(); // 输出:I am a global variable
- 未使用
var
、let
或const
声明的变量会自动成为全局变量:function createGlobalVar() { undeclaredVar = "I am also a global variable"; } createGlobalVar(); console.log(undeclaredVar); // 输出:I am also a global variable
-
函数作用域:
- 在函数作用域中声明的变量只能在函数内部访问。
- 例如:
function localScope() { var localVar = "I am a local variable"; console.log(localVar); // 可以访问到 localVar } localScope(); console.log(localVar); // 报错:localVar 未定义
-
块级作用域:
-
在块级作用域中声明的变量只能在块内部访问。
-
例如:
if (true) { let blockVar = "I am a block variable"; console.log(blockVar); // 可以访问到 blockVar } console.log(blockVar); // 报错:blockVar 未定义
-
const
也具有块级作用域:for (const i = 0; i < 3; i++) { console.log(i); // 输出 0, 1, 2 } console.log(i); // 报错:i 未定义
-
32 let,var,const是哪种作用域下定义的?
(1)考察点分析
- 理解关键字的作用域:考察候选人是否理解 let、var 和 const 的作用域规则。
- 变量声明和作用域控制:考察候选人是否能够正确使用这些关键字来控制变量的作用域。
- 区别和最佳实践:考察候选人是否能够解释它们之间的区别,并在实际开发中选择适当的关键字。
(2)最终答案
-
var
的作用域:var
声明的变量具有函数作用域。- 在函数内部声明的变量只能在函数内部访问。
- 变量声明会被提升到函数或全局作用域的顶部,但初始化仍在原来的位置。
例如:
function varExample() { console.log(varVar); // 输出 undefined(变量提升) var varVar = "I am a var variable"; console.log(varVar); // 输出 "I am a var variable" } varExample(); console.log(varVar); // 报错:varVar 未定义
-
let 的作用域:
- let 声明的变量具有块级作用域。
- 变量只能在块 {} 内部访问,不会被提升到块的顶部。
例如:
if (true) {
let letVar = "I am a let variable";
console.log(letVar); // 输出 "I am a let variable"
}
console.log(letVar); // 报错:letVar 未定义
- const 的作用域:
- const 声明的变量也具有块级作用域。
- 与 let 类似,const 声明的变量只能在块 {} 内部访问。
- 声明时必须初始化,且声明后不能重新赋值。
例如:
if (true) {
const constVar = "I am a const variable";
console.log(constVar); // 输出 "I am a const variable"
}
console.log(constVar); // 报错:constVar 未定义
// 声明后不能重新赋值
const anotherConstVar = "Initial value";
anotherConstVar = "New value"; // 报错:Assignment to constant variable.
33 定位有哪几种?
(1)考察点分析
- 理解不同定位方式的概念:考察候选人是否理解 CSS 中不同定位方式的基本概念。
- 识别定位方式的区别和应用场景:考察候选人是否能够识别并解释不同定位方式的区别和适用场景。
- 实际应用:考察候选人是否能够将定位方式应用于实际布局和样式设计中。
(2)最终答案
-
静态定位(
static
):- 默认定位方式。
- 元素按照文档流的正常顺序进行布局,不受
top
、right
、bottom
、left
和z-index
属性的影响。
-
相对定位(
relative
):- 相对于元素自身的正常位置进行偏移。
- 元素仍然占据原来的空间,但可以通过
top
、right
、bottom
、left
属性进行偏移。
-
绝对定位(
absolute
):- 相对于最近的定位祖先元素(非
static
)进行定位。 - 如果没有定位祖先,则相对于初始包含块(通常是视口)进行定位。
- 元素脱离文档流,不占据空间。
- 相对于最近的定位祖先元素(非
-
固定定位(
fixed
):- 相对于视口进行定位。
- 元素脱离文档流,不占据空间。
- 在滚动页面时,元素保持相对于视口的位置不变。
-
粘性定位(
sticky
):- 元素根据用户的滚动位置进行定位。
- 在跨越特定阈值前表现为相对定位,之后表现为固定定位。
- 常用于制作粘性导航栏等效果。
34 HTTP和HTTPS的区别
(1)考察点分析
- 基础知识:了解HTTP和HTTPS协议的基本原理和区别。
- 安全性:理解HTTPS的安全性特性以及如何实现。
- 性能:知道HTTPS对性能的影响及其优化方法。
- 应用场景:能够解释何时应使用HTTP,何时应使用HTTPS。
(2)最终答案
-
协议概述:
- HTTP是超文本传输协议,数据以明文方式传输。
- HTTPS是在HTTP的基础上加入SSL/TLS层,用于加密数据传输,保证数据的安全性。
-
安全性:
- HTTP的数据传输是明文的,容易被中间人攻击、窃听和篡改。
- HTTPS通过SSL/TLS加密数据,确保数据在传输过程中的保密性、完整性和真实性。
-
端口:
- HTTP默认使用端口80。
- HTTPS默认使用端口443。
-
性能:
- HTTP无加密开销,速度相对较快。
- HTTPS由于加解密过程,初次握手时会有一定的性能开销,但可以通过硬件加速、HTTP/2等技术优化。
-
证书:
- HTTP不需要证书。
- HTTPS需要SSL/TLS证书,证书可以从证书颁发机构(CA)获取。
-
SEO和用户信任:
- HTTPS对SEO友好,搜索引擎更倾向于排名HTTPS站点。同时,浏览器会标记不安全的HTTP站点,影响用户信任。
35 Vue 3 中 ref 和 reactive 底层实现的区别
(1)考察点分析
- Vue 3 的响应式系统:了解 Vue 3 的响应式系统的基本原理。
- ref 和 reactive 的使用场景:理解 ref 和 reactive 的使用场景及其区别。
- 底层实现细节:掌握 ref 和 reactive 在底层实现上的不同点。
- 优化和性能:理解两者的性能影响和优化方法。
(2)最终答案
在 Vue 3 中,ref
和 reactive
都是用于创建响应式数据的工具,但它们在底层实现上有所不同。
-
基本概念:
ref
用于创建包含单个值的响应式引用,适用于基本类型和需要单独引用的场景。reactive
用于创建包含多个属性的响应式对象,适用于复杂对象。
-
使用场景:
ref
适合处理基本类型(如字符串、数字)或需要对整个对象进行引用变更的场景。例如:const count = ref(0); count.value++; // 通过 .value 访问和修改值
reactive
适合处理复杂对象和嵌套结构。例如:const state = reactive({ count: 0, nested: { foo: 'bar' } }); state.count++; // 直接访问和修改属性 state.nested.foo = 'baz'; // 深层次属性的响应式更新
-
底层实现:
- ref:
ref
使用RefImpl
类来实现。其核心是一个对象,用于存储内部值,并通过getter
和setter
来拦截读取和修改操作。- 当
ref
包装的值变化时,dep
依赖收集机制会通知所有依赖此ref
的副作用进行更新。
- reactive:
reactive
使用Proxy
来代理对象的所有属性访问和变更。- 代理对象的
getter
和setter
触发依赖收集和派发更新,确保对象内部的任何属性变化都能引起响应式更新。
- ref:
-
性能和优化:
- 对于单一值或简单对象,
ref
的性能较优,因为其包装开销较小。 - 对于复杂对象或嵌套对象,
reactive
提供了更全面的响应式支持,但在频繁的深层次属性访问和变更时可能有较高的性能开销。
- 对于单一值或简单对象,
36 伪类和伪元素有哪些?
(1)考察点分析
- 基础知识:了解伪类和伪元素的定义和用法。
- 常见伪类和伪元素:熟悉常见的伪类和伪元素,并能正确区分它们。
- 实际应用:理解伪类和伪元素在实际项目中的应用场景。
(2)最终答案
在 CSS 中,伪类和伪元素用于增强选择器的功能和样式的控制。以下是常见的伪类和伪元素的介绍。
-
伪类:
-
伪类用于选择元素的特定状态,常见伪类包括:
-
动态伪类:
:hover
:鼠标悬停时应用的样式。:active
:元素被激活(通常是被点击)时应用的样式。:focus
:元素获得焦点时应用的样式。
-
结构性伪类:
:first-child
:选择父元素的第一个子元素。:last-child
:选择父元素的最后一个子元素。:nth-child(n)
:选择父元素的第 n 个子元素。:nth-of-type(n)
:选择父元素中特定类型的第 n 个子元素。
-
其他伪类:
:disabled
:选择被禁用的表单元素。:checked
:选择被选中的表单元素。:not(selector)
:选择不匹配指定选择器的元素。
示例:
a:hover { color: red; } input:focus { border-color: blue; } li:nth-child(odd) { background-color: lightgray; }
-
-
-
伪元素:
-
伪元素用于创建元素的特定部分,常见伪元素包括:
::before
:在元素内容之前插入内容。::after
:在元素内容之后插入内容。::first-line
:选择元素的第一行文本。::first-letter
:选择元素的第一个字母。::placeholder
:选择输入字段的占位符文本。
示例:
p::first-line { font-weight: bold; } p::first-letter { font-size: 2em; color: red; } .element::before { content: "Prefix"; color: gray; } .element::after { content: "Suffix"; color: gray; } input::placeholder { color: darkgray; }
-
-
实际应用:
- 伪类常用于实现交互效果和状态变化。例如,
:hover
用于改变按钮在鼠标悬停时的样式,:focus
用于改变输入框在获得焦点时的样式。 - 伪元素常用于插入装饰性内容而不需要额外的 HTML 标签。例如,使用
::before
和::after
插入图标或额外的文本装饰。
- 伪类常用于实现交互效果和状态变化。例如,
37 大文件上传是如何实现的?
(1)考察点分析
- 基础知识:了解大文件上传的基本概念和挑战。
- 分块上传:理解分块上传的原理和实现步骤。
- 续传和错误处理:掌握断点续传和错误处理的机制。
- 性能优化:知道如何优化大文件上传的性能。
(2)最终答案
实现大文件上传的常见方法是使用分块上传,主要步骤和原理如下:
-
基本概念:
大文件上传面临的主要挑战包括网络不稳定、上传时间长、服务器内存占用高等。分块上传通过将大文件分成小块逐个上传,可以提高上传的可靠性和效率。 -
分块上传的实现步骤:
-
前端:
- 将大文件分成若干小块。例如,一个 100MB 的文件可以分成 100 个 1MB 的小块。
- 使用
File
对象的slice
方法切割文件:function sliceFile(file, chunkSize) { const chunks = []; for (let start = 0; start < file.size; start += chunkSize) { const chunk = file.slice(start, start + chunkSize); chunks.push(chunk); } return chunks; }
- 对每个小块进行上传,记录每块的上传状态:
async function uploadChunk(chunk, index) { const formData = new FormData(); formData.append('chunk', chunk); formData.append('index', index); await fetch('/upload', { method: 'POST', body: formData }); } const file = document.getElementById('file').files[0]; const chunks = sliceFile(file, 1024 * 1024); // 1MB per chunk chunks.forEach((chunk, index) => uploadChunk(chunk, index));
-
后端:
- 接收并存储每个上传的小块,记录已接收的小块信息:
from flask import Flask, request app = Flask(__name__) @app.route('/upload', methods=['POST']) def upload(): chunk = request.files['chunk'] index = request.form['index'] with open(f'/tmp/upload_{index}', 'wb') as f: f.write(chunk.read()) return 'OK'
- 当所有小块上传完成后,按照顺序合并成完整文件:
import os def merge_chunks(file_path, total_chunks): with open(file_path, 'wb') as merged_file: for i in range(total_chunks): chunk_path = f'/tmp/upload_{i}' with open(chunk_path, 'rb') as chunk_file: merged_file.write(chunk_file.read()) os.remove(chunk_path) # 假设前端通知合并所有块 merge_chunks('/tmp/final_file', 100)
- 接收并存储每个上传的小块,记录已接收的小块信息:
-
-
续传和错误处理:
- 断点续传:记录上传的进度信息(如已成功上传的小块),在上传中断后继续从断点上传。
// 简单的续传示例 async function resumeUpload(chunks) { const uploadedChunks = await getUploadedChunks(); for (let i = 0; i < chunks.length; i++) { if (!uploadedChunks.includes(i)) { await uploadChunk(chunks[i], i); } } }
- 错误处理:对于上传失败的小块,可以重试上传,或者提供用户手动重试的选项。
- 断点续传:记录上传的进度信息(如已成功上传的小块),在上传中断后继续从断点上传。
-
性能优化:
- 并行上传:同时上传多个小块以提高上传速度。
- 压缩传输:在网络带宽受限时,先对文件或小块进行压缩再上传。
- CDN 加速:使用 CDN 节点进行上传加速。
综上所述,大文件上传可以通过分块上传来实现,并结合续传和错误处理机制,进一步优化性能以提高上传效率和用户体验。
38 图片懒加载具体是怎么做的?
(1)考察点分析
- 基础知识:了解图片懒加载的概念及其重要性。
- 实现方式:掌握常见的图片懒加载实现方式,如原生 JavaScript 和使用库。
- 性能优化:理解懒加载对性能优化的影响,特别是对网页加载速度和用户体验的改善。
(2)最终答案
图片懒加载是一种延迟加载图片的方法,只有当图片即将出现在视口内时才进行加载,从而优化页面加载性能。下面介绍两种实现方式:原生 JavaScript 和使用库。
-
基本概念:
图片懒加载通过延迟加载图片,减少初始页面加载时间,降低服务器负载,提高用户体验。 -
原生 JavaScript 实现:
可以使用IntersectionObserver
API 来检测图片是否进入视口,并在图片进入视口时加载图片。示例代码:
<img data-src="path/to/image.jpg" alt="Lazy Loaded Image" class="lazy">
document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove("lazy");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
} else {
// Fallback for browsers that do not support IntersectionObserver
let lazyLoad = function() {
let scrollTop = window.pageYOffset;
lazyImages.forEach(function(img) {
if (img.offsetTop < (window.innerHeight + scrollTop)) {
img.src = img.dataset.src;
img.classList.remove("lazy");
}
});
if (lazyImages.length == 0) {
document.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationChange", lazyLoad);
}
};
document.addEventListener("scroll", lazyLoad);
window.addEventListener("resize", lazyLoad);
window.addEventListener("orientationChange", lazyLoad);
}
});
- 使用库实现:现有的懒加载库封装了懒加载的实现,可以更方便地使用。
- 使用 lazysizes:
# 引入 lazysizes 库:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.2.2/lazysizes.min.js" async></script>
# 使用 lazysizes 类:
<img data-src="path/to/image.jpg" class="lazyload" alt="Lazy Loaded Image">
- 使用 Lozad.js:
# 引入 Lozad.js 库:
<script src="https://cdn.jsdelivr.net/npm/lozad"></script>
# 使用 Lozad.js:
<img data-src="path/to/image.jpg" class="lozad" alt="Lazy Loaded Image">
<script>
document.addEventListener("DOMContentLoaded", function() {
const observer = lozad();
observer.observe();
});
</script>
- 性能优化:
图片懒加载可以显著减少初始页面的加载时间,特别是在图片较多的情况下。通过减少不必要的图片加载,懒加载降低了服务器带宽消耗,优化了用户体验,尤其是对长列表或移动设备上的用户体验改善显著。
综上所述,图片懒加载是通过延迟加载图片来优化页面性能的一种技术,可以使用原生 JavaScript 或第三方库实现。
39 说一下原型和原型链
(1)考察点分析
- 基础概念:了解 JavaScript 中的原型和原型链的基本概念。
- 作用与机制:理解原型和原型链在对象继承中的作用与机制。
- 实际应用:掌握如何在实际开发中使用和调试原型链。
(2)最终答案
在 JavaScript 中,原型和原型链是理解对象继承和属性查找机制的关键概念。以下是对原型和原型链的详细解释。
-
基本概念:
-
原型:
每个 JavaScript 对象都有一个内部属性[[Prototype]]
,指向另一个对象,这个对象被称为原型。原型对象可以包含该对象共享的属性和方法。
在现代 JavaScript 中,可以通过__proto__
访问原型(不推荐),或者使用Object.getPrototypeOf
和Object.setPrototypeOf
方法。示例:
function Person(name) { this.name = name; } Person.prototype.greet = function() { console.log(`Hello, my name is ${this.name}`); }; const alice = new Person('Alice'); alice.greet(); // 输出:Hello, my name is Alice
-
原型链:
原型链是由对象及其原型组成的链式结构。当访问一个对象的属性时,如果该属性不存在于对象本身,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端。示例:
console.log(alice.__proto__ === Person.prototype); // true console.log(Person.prototype.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__ === null); // true
-
-
作用与机制:
-
属性查找:
当访问一个对象的属性时,JavaScript 会先在对象自身的属性中查找。如果没有找到,则会沿着原型链向上查找,直到找到属性或到达原型链的末端(即null
)。示例:
console.log(alice.hasOwnProperty('name')); // true console.log(alice.hasOwnProperty('greet')); // false, greet 在原型链上 console.log('greet' in alice); // true, 因为 greet 在原型链上
-
继承机制:
通过原型链,可以实现对象的继承,使得一个对象可以共享另一个对象的属性和方法。构造函数和原型是常见的实现方式。示例:
function Employee(name, jobTitle) { Person.call(this, name); // 调用父构造函数 this.jobTitle = jobTitle; } Employee.prototype = Object.create(Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.work = function() { console.log(`${this.name} is working as ${this.jobTitle}`); }; const bob = new Employee('Bob', 'Engineer'); bob.greet(); // 输出:Hello, my name is Bob bob.work(); // 输出:Bob is working as Engineer
-
-
实际应用:
-
使用构造函数和原型实现对象的继承,如上例所示。
-
通过
Object.create
创建具有特定原型的对象:const personPrototype = { greet() { console.log(`Hello, my name is ${this.name}`); } }; const charlie = Object.create(personPrototype); charlie.name = 'Charlie'; charlie.greet(); // 输出:Hello, my name is Charlie
-
理解原型链有助于调试和优化代码。例如,避免在对象本身和原型链上存在重名的属性,以减少不必要的查找开销。
-
综上所述,原型和原型链是 JavaScript 中对象继承和属性查找的基础。理解这些概念可以帮助你更好地掌握 JavaScript 的对象机制和编程技巧。
40 类和对象的区别
(1)考察点分析
- 基础概念:了解类和对象的定义及其在编程中的作用。
- 实例化:理解类是如何被实例化为对象的。
- 应用场景:掌握类和对象在实际编程中的使用场景和区别。
(2)最终答案
-
基本概念:
-
类:
类是一个模板或蓝图,用于定义对象的属性和方法。在类中,可以定义属性(成员变量)和方法(成员函数),这些属性和方法描述了对象的状态和行为。示例:
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`); } }
-
对象:
对象是类的实例。对象包含类定义的实际数据和行为(方法)。每个对象都有自己独立的属性,但它们共享类定义的结构和行为。示例:
const alice = new Person('Alice', 30); alice.greet(); // 输出:Hello, my name is Alice and I am 30 years old. const bob = new Person('Bob', 25); bob.greet(); // 输出:Hello, my name is Bob and I am 25 years old.
-
-
实例化:
- 类通过实例化来创建对象。实例化是创建类的实例(对象)的过程。在上面的示例中,通过
new Person('Alice', 30)
和new Person('Bob', 25)
实例化了两个Person
对象。
- 类通过实例化来创建对象。实例化是创建类的实例(对象)的过程。在上面的示例中,通过
-
应用场景:
-
类:
类用于定义和创建具有相同属性和行为的多个对象。例如,定义一类学生,每个学生都有姓名、年龄、学号等属性和方法。示例:
class Student { constructor(name, id) { this.name = name; this.id = id; } study() { console.log(`${this.name} is studying.`); } }
-
对象:
对象是实际使用类创建的实例,用于存储和操作数据。例如,创建具体的学生对象来表示具体的学生。示例:
const student1 = new Student('John', 'S001'); const student2 = new Student('Jane', 'S002'); student1.study(); // 输出:John is studying. student2.study(); // 输出:Jane is studying.
-
综上所述,类是用于定义对象的模板或蓝图,而对象是类的具体实例。类定义了对象的属性和方法,而对象则包含实际的数据和行为。通过类的实例化,可以创建多个具有相同结构和行为的对象,从而实现面向对象编程的抽象和复用。
41 说一下浏览器的事件循环
(1)考察点分析
- 基础概念:了解浏览器事件循环的基本原理。
- 执行顺序:理解同步任务和异步任务的执行顺序。
- 实际应用:掌握事件循环在实际编程中的应用和调试。
(2)最终答案
- 基本概念:
- 事件循环:
事件循环是浏览器和 Node.js 处理异步操作的核心机制。它是一个不断检查和执行任务的循环过程,确保异步操作按正确顺序执行。 - 调用栈:
调用栈是一个栈数据结构,用于存储代码执行的上下文。当函数调用时,函数的执行上下文被压入调用栈,当函数执行完毕时,执行上下文被弹出调用栈。 - 任务队列:
任务队列用于存储待执行的异步任务。当调用栈为空时,事件循环从任务队列中取出任务并执行。
- 执行顺序:
-
同步任务:
同步任务在调用栈中按顺序执行,执行完毕后调用栈清空。 -
异步任务:
异步任务分为宏任务(macro task)和微任务(micro task)。宏任务包括setTimeout
、setInterval
、I/O 操作等。微任务包括Promise
的then
、catch
、finally
回调和MutationObserver
。 -
事件循环执行过程:
- 执行所有同步任务,直到调用栈为空。
- 执行所有微任务(如果有),例如
Promise
的回调。 - 执行一个宏任务,例如
setTimeout
回调。 - 重复上述过程。
示例:
console.log('Start'); setTimeout(() => { console.log('setTimeout'); }, 0); Promise.resolve().then(() => { console.log('Promise'); }); console.log('End');
解析:
1.console.log(‘Start’) 是同步任务,立即执行。
2.setTimeout 是宏任务,回调被放入宏任务队列。
3.Promise.resolve().then 是微任务,回调被放入微任务队列。
4.console.log(‘End’) 是同步任务,立即执行。
5.调用栈为空,执行微任务队列中的回调,输出 Promise。
6.执行宏任务队列中的回调,输出 setTimeout。
输出顺序为:
Start
End
Promise
setTimeout
- 实际应用:
- 使用 setTimeout 和 Promise 进行异步编程:
使用 setTimeout 来延迟执行代码,使用 Promise 来处理异步操作并链式调用。 - 理解和调试异步代码的执行顺序:
理解事件循环的执行顺序,有助于避免常见的异步编程问题,例如回调地狱和竞态条件。
示例:
function asyncOperation() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Operation Complete');
}, 1000);
});
}
asyncOperation().then((message) => {
console.log(message); // 输出:Operation Complete
});
console.log('Async operation started');
解析:
1、asyncOperation 返回一个 Promise,其回调被放入宏任务队列。
2、console.log(‘Async operation started’) 是同步任务,立即执行。
3、1 秒后,宏任务队列中的回调执行,输出 Operation Complete。
输出顺序为:
Async operation started
Operation Complete
综上所述,浏览器的事件循环通过调用栈和任务队列管理同步和异步任务的执行顺序,确保代码按照预期顺序运行。理解事件循环有助于编写和调试异步代码,提高代码的执行效率和可维护性。
42 cookie的作用是什么?
(1)考察点分析
- 理解 Cookie 的基本概念:考察候选人是否理解 Cookie 的定义和基本用途。
- 了解 Cookie 的作用:考察候选人是否能具体解释 Cookie 在 Web 应用中的具体作用和应用场景。
- 安全性和限制:考察候选人是否了解 Cookie 的安全性问题及其使用中的限制。
(2)最终答案
-
Cookie 的基本概念:
- Cookie 是存储在用户浏览器中的小型文本文件。
- 由服务器发送并由客户端存储,每次客户端请求时发送回服务器。
-
Cookie 的作用:
- 状态管理:跟踪和存储用户会话状态,如登录状态、购物车内容等。
- 个性化设置:存储用户的偏好设置,如语言选择、主题设置等。
- 跟踪和分析:用于网站分析和广告跟踪,帮助网站了解用户行为和优化体验。
-
安全性和限制:
- 安全性:Cookie 存在安全风险,如被窃取、劫持等。通过
HttpOnly
、Secure
、SameSite
属性可以提高安全性。 - 大小限制:单个 Cookie 的大小通常不能超过 4KB,一个域名下的 Cookie 总数也有限制(一般为 20 个)。
- 生命周期:Cookie 可以设置过期时间或在浏览器会话结束时删除。
- 安全性:Cookie 存在安全风险,如被窃取、劫持等。通过
示例代码
// 设置 Cookie
document.cookie = "username=JohnDoe; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/; Secure; HttpOnly; SameSite=Strict";
// 读取 Cookie
const getCookie = (name) => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
};
console.log(getCookie("username")); // 输出: JohnDoe
43 cookie的作用是什么?
(1)考察点分析
- 理解 Cookie 过期时间的设置机制:考察候选人是否了解如何设置 Cookie 的过期时间及其背后的原理。
- 理解 Cookie 的携带机制:考察候选人是否了解 Cookie 在客户端和服务器之间的传递过程。
(2)最终答案
-
Cookie 过期时间的设置:
- 由服务器设置:服务器在发送 Set-Cookie 头时,可以指定 Cookie 的过期时间。
- 由客户端设置:客户端也可以通过 JavaScript 设置 Cookie 的过期时间。
-
Cookie 的携带机制:
- 由浏览器自动携带:浏览器会在发送请求时自动携带符合条件的 Cookie。
- 条件:只有与请求的域名和路径匹配的 Cookie 才会被携带,并且要考虑安全属性(如 Secure 和 HttpOnly)。
-
示例代码:
设置 Cookie 过期时间
服务器端设置:
HTTP/1.1 200 OK
Set-Cookie: username=JohnDoe; Expires=Wed, 21 Oct 2024 07:28:00 GMT; Path=/;
客户端 JavaScript 设置:
document.cookie = "username=JohnDoe; expires=Wed, 21 Oct 2024 07:28:00 GMT; path=/;";
浏览器自动携带 Cookie
当用户访问匹配域名和路径的页面时,浏览器会自动携带相关的 Cookie:
GET /path/to/resource HTTP/1.1
Host: www.example.com
Cookie: username=JohnDoe
总结
Cookie 过期时间的设置:可以由服务器在 Set-Cookie 头中设置,也可以由客户端通过 JavaScript 设置。
Cookie 的携带:由浏览器在请求时自动完成,浏览器会根据请求的域名和路径,自动携带符合条件的 Cookie。
44 undefine和null的区别
(1)考察点分析
- 理解基本概念:考察候选人是否清楚 undefined 和 null 各自的定义和用途。
- 了解使用场景:考察候选人是否能正确区分和使用 undefined 和 null。
- 掌握类型和相等性判断:考察候选人是否了解两者在类型判断和相等性判断中的区别。
(2)最终答案
-
基本概念:
undefined
:表示变量声明了但未赋值,或对象属性不存在。null
:表示一个变量被明确赋值为空,没有对象值。
-
使用场景:
undefined
:通常由 JavaScript 引擎自动分配,表示缺少值。- 变量声明但未赋值。
- 对象属性不存在。
- 函数没有返回值。
null
:通常由开发者手动赋值,表示没有对象。- 明确表示变量没有对象值。
- 重置变量或对象属性。
-
类型和相等性判断:
- 类型:
typeof undefined
返回"undefined"
。typeof null
返回"object"
。
- 相等性判断:
undefined == null
返回true
(抽象相等)。undefined === null
返回false
(严格相等)。
- 类型:
-
示例代码:
// undefined 示例
let a;
console.log(a); // 输出: undefined
let obj = {};
console.log(obj.prop); // 输出: undefined
function foo() {}
console.log(foo()); // 输出: undefined
// null 示例
let b = null;
console.log(b); // 输出: null
let obj2 = { prop: null };
console.log(obj2.prop); // 输出: null
// 类型判断
console.log(typeof undefined); // 输出: "undefined"
console.log(typeof null); // 输出: "object"
// 相等性判断
console.log(undefined == null); // 输出: true
console.log(undefined === null); // 输出: false
45 watch和computed的区别
(1)考察点分析
- 响应式数据管理:考察候选人对Vue.js中响应式数据的处理方式。
- 实时性要求:考察候选人对数据变化的实时监控和处理能力。
- 计算属性与副作用:考察候选人对计算属性和副作用函数的理解和区别。
(2)最终答案
-
watch
的特点和使用场景:- 特点:可以监视数据的变化,当数据发生变化时执行自定义的函数。
- 使用场景:
- 监听单个数据的变化,执行异步操作或复杂逻辑。
- 监听对象或数组的变化,执行深层次的操作。
- 监听路由变化,执行页面跳转前的处理。
-
computed
的特点和使用场景:- 特点:基于响应式依赖进行缓存,只有依赖发生改变才会重新计算。
- 使用场景:
- 计算和返回一个新的衍生数据,例如根据原始数据计算过滤后的列表。
- 依赖多个响应式数据,只有当这些数据发生变化时才重新计算。
- 在模板中使用,自动追踪依赖,确保最小化计算次数。
-
区别:
- 触发时机:
watch
在数据变化时立即触发相应的处理函数。computed
会根据其依赖的数据是否发生变化来决定是否重新计算。
- 返回值:
watch
不关心返回值,可以执行任何副作用操作。computed
必须有返回值,并且返回值会被缓存,多次访问时直接返回缓存的结果。
- 触发时机:
-
区别:
// watch 的使用示例
watch: {
// 监听单个数据变化
counter(newValue, oldValue) {
console.log(`Counter changed from ${oldValue} to ${newValue}`);
},
// 监听对象深层次数据变化
'user.name': {
handler(newValue, oldValue) {
console.log(`User name changed from ${oldValue} to ${newValue}`);
},
deep: true
}
}
// computed 的使用示例
computed: {
// 根据原始数据计算过滤后的列表
filteredList() {
return this.list.filter(item => item.active);
},
// 计算和返回一个新的衍生数据
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
46 节流防抖的概念和应用场景
(1)考察点分析
- 基础概念:了解节流(Throttle)和防抖(Debounce)的基本原理和区别。
- 应用场景:理解节流和防抖在实际开发中的应用场景。
- 实现方式:掌握如何在代码中实现节流和防抖。
(2)最终答案
-
基本概念:
-
节流(Throttle):
节流是在一定时间内,只允许一个事件处理函数被调用。例如,如果设置的时间间隔是 100ms,则在这 100ms 内,无论事件触发多少次,事件处理函数只会执行一次。实现代码:
function throttle(func, limit) { let inThrottle; return function() { const context = this; const args = arguments; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } } }
-
防抖(Debounce):
防抖是在一定时间内,如果事件被再次触发,则重新计时。只有当事件在设定的时间内没有再次触发,事件处理函数才会被调用。例如,如果设置的时间间隔是 300ms,则只有当事件触发后 300ms 内没有再次触发,事件处理函数才会执行。实现代码:
function debounce(func, delay) { let timeout; return function() { const context = this; const args = arguments; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), delay); } }
-
-
应用场景:
-
节流:
-
窗口滚动:
在用户滚动窗口时,频繁触发滚动事件会导致性能问题。使用节流技术可以限制滚动事件的触发频率,减少性能开销。示例:
window.addEventListener('scroll', throttle(() => { console.log('Scrolled!'); }, 100));
-
按钮点击:
防止用户快速多次点击按钮,导致多次请求发送。示例:
const button = document.getElementById('myButton'); button.addEventListener('click', throttle(() => { console.log('Button clicked!'); }, 2000));
-
鼠标移动:
在拖拽或绘制操作中,控制鼠标移动事件的触发频率。示例:
document.addEventListener('mousemove', throttle((event) => { console.log(`Mouse position: ${event.clientX}, ${event.clientY}`); }, 50));
-
-
防抖:
-
输入框搜索:
在用户输入时,防止每次输入都触发搜索请求。只有用户停止输入一段时间后,才发送搜索请求。示例:
const searchInput = document.getElementById('search'); searchInput.addEventListener('input', debounce(() => { console.log('Search:', searchInput.value); }, 300));
-
窗口调整:
在用户调整窗口大小时,频繁触发调整事件会导致性能问题。使用防抖技术可以确保调整事件在用户停止调整窗口一段时间后才触发。示例:
window.addEventListener('resize', debounce(() => { console.log('Window resized!'); }, 500));
-
表单验证:
在表单字段输入时,防止频繁触发验证请求。只有用户停止输入一段时间后,才进行表单验证。示例:
const formInput = document.getElementById('formInput'); formInput.addEventListener('input', debounce(() => { console.log('Validating form input:', formInput.value); }, 300));
-
-
综上所述,节流和防抖是控制事件触发频率的有效方法。节流用于在一定时间内限制事件处理函数的执行次数,而防抖用于在事件频繁触发时,确保在指定时间内只执行一次事件处理函数。理解和合理使用这两种技术,可以显著提升应用的性能和用户体验。
47 Vite 为什么这么快?在开发环境和生产环境有什么优化?
(1)考察点分析
- 基本概念:了解 Vite 的基本原理和工作机制。
- 性能优化:理解 Vite 在开发环境和生产环境的优化策略。
- 实际应用:掌握如何利用 Vite 的优势提升开发效率和构建性能。
(2)最终答案
-
Vite 为什么这么快:
-
即时热更新(HMR):
Vite 利用浏览器的原生 ES 模块支持,实现了快速的模块热更新(HMR)。当源代码发生变化时,Vite 只需要更新变化的模块,而无需重新打包整个应用,大幅减少了重新编译的时间。 -
按需编译:
在传统的构建工具中,每次修改代码后都需要重新打包整个项目。而 Vite 在开发环境中采用按需编译的方式,只有当模块被请求时才进行编译,这减少了项目启动时间和重新编译的时间。 -
依赖预构建:
Vite 在启动时会使用 esbuild 对第三方依赖进行预构建,这使得这些依赖可以快速加载和执行。esbuild 是一个高速构建工具,相比传统的 JavaScript 打包工具,构建速度非常快。
-
-
开发环境的优化:
-
即时热更新(HMR):
Vite 实现了高效的模块热更新,修改代码后可以立即看到变化,无需刷新整个页面。 -
按需编译:
Vite 在开发环境中不需要预先打包整个项目,只有在模块被请求时才进行编译,从而减少启动时间和重新编译的时间。 -
依赖预构建:
Vite 使用 esbuild 对第三方依赖进行预构建,提高了依赖的加载速度。
-
-
生产环境的优化:
-
静态资源处理:
在生产环境中,Vite 会对静态资源进行处理和优化,包括压缩、代码分割和缓存等,从而生成高性能的构建结果。 -
基于 Rollup 的构建:
Vite 在生产环境中使用 Rollup 进行构建。Rollup 是一个高效的 JavaScript 模块打包工具,具有强大的代码优化和打包能力。通过 Rollup 的 Tree Shaking 特性,Vite 可以去除未使用的代码,减少打包后的文件体积。 -
Tree Shaking:
Vite 利用 Rollup 的 Tree Shaking 特性,自动去除未使用的代码,从而减少打包后的文件体积,提升加载速度和运行效率。
-
-
实际应用:
-
在开发环境中,使用 Vite 可以显著提高开发效率。通过即时热更新和按需编译,开发者可以立即看到代码修改的效果,减少等待时间。
-
在生产环境中,通过 Vite 的优化策略,可以生成高性能的构建结果。静态资源的优化处理和基于 Rollup 的构建方式,确保了应用的快速加载和高效运行,提升了用户体验。
-
综上所述,Vite 通过即时热更新、按需编译和依赖预构建等技术,在开发环境中提供了快速的开发体验。同时,在生产环境中,Vite 利用静态资源优化、Rollup 构建和 Tree Shaking 等策略,生成高性能的构建结果,提升了应用的运行效率和用户体验。
48 对响应式布局的理解
(1)考察点分析
- 基础概念:了解响应式布局的基本原理和特点。
- 实现方式:掌握实现响应式布局的常用技术和方法。
- 实际应用:理解响应式布局在实际项目中的应用场景和设计策略。
(2)最终答案
-
基础概念:
-
响应式布局:
响应式布局是一种网页设计方法,使网页能够在不同设备和屏幕尺寸上自适应地调整布局和样式。其核心思想是通过流动网格、灵活图像和 CSS 媒体查询等技术,使网页在各种设备上显示效果一致,用户体验良好。 -
特点:
响应式布局具有以下特点:- 自适应:页面内容能够根据不同设备的屏幕尺寸自动调整,确保在各种设备上都能良好展示。
- 统一性:避免为不同设备单独设计和开发多个版本的网页,减少开发和维护成本。
- 灵活性:通过灵活的布局和样式,使网页能够适应未来的设备变化。
-
-
实现方式:
-
流动网格(Fluid Grid):
使用百分比而不是固定像素来定义布局的宽度,使布局能够根据屏幕大小进行调整。例如:.container { width: 100%; max-width: 1200px; margin: 0 auto; } .column { float: left; width: 50%; /* 使用百分比定义宽度 */ }
-
灵活图像(Flexible Images):
使用 CSS 设置图像的最大宽度为 100%,使图像能够根据容器的宽度自适应调整。例如:img { max-width: 100%; height: auto; }
-
媒体查询(Media Queries):
使用 CSS3 媒体查询,根据设备的不同特性(如宽度、高度、分辨率等)应用不同的样式。例如:@media (max-width: 768px) { .column { width: 100%; /* 在小屏幕设备上,列宽度为 100% */ } } @media (min-width: 769px) { .column { width: 50%; /* 在大屏幕设备上,列宽度为 50% */ } }
-
-
实际应用:
-
在开发过程中,通过响应式布局可以确保网页在各种设备(如手机、平板、桌面电脑)上都能提供良好的用户体验。例如,使用流行的前端框架(如 Bootstrap、Tailwind CSS 等),可以快速实现响应式布局,提升开发效率和代码复用性。
-
实际项目中,响应式布局的应用场景包括但不限于:
- 响应式导航栏:根据屏幕宽度调整导航栏的布局和样式,使其在小屏设备上变为汉堡菜单。
- 响应式图片和视频:确保图片和视频在不同设备上按比例缩放,提供最佳观看体验。
- 响应式表格:在小屏设备上,将表格内容调整为更适合阅读的格式,避免横向滚动。
-
综上所述,响应式布局是一种重要的网页设计技术,通过流动网格、灵活图像和 CSS 媒体查询等方法,实现网页在不同设备上的自适应布局。理解和掌握响应式布局技术,可以显著提升网页的用户体验和跨设备兼容性。
49 媒体查询media的概念和作用
(1)考察点分析
- 基础概念:了解媒体查询的基本原理和语法。
- 应用场景:理解媒体查询在响应式设计中的应用场景和作用。
- 实际应用:掌握如何在项目中使用媒体查询实现响应式布局。
(2)最终答案
-
基本概念:
-
媒体查询:
媒体查询是 CSS3 的一个特性,通过@media
规则定义,根据设备的不同特性应用不同的样式,从而实现响应式设计。 -
语法:
媒体查询的基本语法包括媒体类型和一个或多个媒体特性。例如,根据屏幕宽度调整样式:@media screen and (max-width: 768px) { /* 在屏幕宽度不超过 768px 时应用的样式 */ .container { width: 100%; } }
-
-
应用场景:
-
响应式布局:
使用媒体查询根据设备的屏幕大小调整布局,使网页在各种设备上都能良好展示。例如,在大屏幕设备上使用多列布局,在小屏幕设备上使用单列布局:@media (min-width: 769px) { .column { width: 50%; } } @media (max-width: 768px) { .column { width: 100%; } }
-
按需加载样式:
在不同设备上加载不同的样式文件,减少不必要的资源加载。例如,为移动设备加载轻量级的样式:<link rel="stylesheet" media="screen and (max-width: 768px)" href="mobile.css"> <link rel="stylesheet" media="screen and (min-width: 769px)" href="desktop.css">
-
设备特性优化:
针对高分辨率屏幕、触摸屏等设备特性优化用户体验。例如,为 Retina 屏幕加载高分辨率的图片:@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .logo { background-image: url('logo@2x.png'); } }
-
-
实际应用:
- 在项目中使用媒体查询调整布局和样式,以实现响应式设计。以下是一个完整的示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Responsive Design</title> <style> body { font-family: Arial, sans-serif; } .container { width: 80%; margin: 0 auto; } .header, .footer { background-color: #f1f1f1; text-align: center; padding: 1em 0; } .main { display: flex; flex-wrap: wrap; } .column { flex: 1; padding: 1em; } /* 大屏幕设备样式 */ @media (min-width: 769px) { .column { width: 50%; } } /* 小屏幕设备样式 */ @media (max-width: 768px) { .column { width: 100%; } } </style> </head> <body> <div class="container"> <div class="header">Header</div> <div class="main"> <div class="column">Column 1</div> <div class="column">Column 2</div> </div> <div class="footer">Footer</div> </div> </body> </html>
- 在项目中使用媒体查询调整布局和样式,以实现响应式设计。以下是一个完整的示例:
在以上示例中,我们通过媒体查询实现了响应式布局:
- 在大屏幕设备(宽度大于 768px)上,页面采用两列布局,每列宽度为 50%。
- 在小屏幕设备(宽度不超过 768px)上,页面采用单列布局,每列宽度为 100%。
通过媒体查询,可以根据不同设备的特性调整样式,使网页在各种设备上都能提供良好的用户体验。结合其他响应式技术(如流动网格、灵活图像等),可以进一步提升页面的跨设备兼容性和用户体验。
50 React 和 Vue 的区别?
(1)考察点分析
- 框架基础:理解 React 和 Vue 的基本概念和设计哲学。
- 技术细节:掌握 React 和 Vue 在技术实现和功能上的主要区别。
- 实际应用:了解 React 和 Vue 在实际项目中的应用场景和优势。
(2)最终答案
- 框架基础
-
React:
- 由 Facebook 开发和维护,是一个用于构建用户界面的 JavaScript 库。
- 采用组件化和单向数据流,主要用于构建复杂、动态的单页应用(SPA)。
- 使用 JSX(JavaScript XML)来编写组件,使得 HTML 和 JavaScript 紧密结合。
-
Vue:
- 由尤雨溪开发,是一个用于构建用户界面的渐进式框架。
- 设计简洁,易于上手,适用于各种规模的项目。
- 使用基于 HTML 的模板语法,支持 Vue 的指令(如
v-bind
、v-if
)来绑定数据和控制视图。
- 技术细节
-
模板语法:
- React:使用 JSX,允许在 JavaScript 中直接编写 HTML 结构。
const element = <h1>Hello, world!</h1>;
- Vue:使用基于 HTML 的模板语法,支持 Vue 指令。
<template> <h1>{{ message }}</h1> </template> <script> export default { data() { return { message: 'Hello, world!' }; } }; </script>
- React:使用 JSX,允许在 JavaScript 中直接编写 HTML 结构。
-
数据绑定:
- React:单向数据流,数据从父组件传递到子组件,通过 props 进行传递和更新。
- Vue:双向数据绑定,通过
v-model
指令可以实现数据和视图的双向绑定。
-
组件体系:
- React:一切皆组件,组件是函数或类,状态管理和生命周期方法在组件内部定义。
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return <div>{this.state.count}</div>; } }
- Vue:组件化结构清晰,组件是对象,支持模板、脚本和样式的单文件组件(
.vue
文件)。<template> <div>{{ count }}</div> </template> <script> export default { data() { return { count: 0 }; } }; </script> <style scoped> div { color: red; } </style>
- React:一切皆组件,组件是函数或类,状态管理和生命周期方法在组件内部定义。
-
状态管理:
- React:使用内置的
useState
和useReducer
等 Hooks 管理状态。对于复杂的状态管理,可以使用外部库(如 Redux、MobX)。const [count, setCount] = useState(0);
- Vue:使用内置的
reactive
和ref
等响应式 API。Vuex 是 Vue 官方的状态管理库,用于集中式管理应用状态。const state = reactive({ count: 0 });
- React:使用内置的
-
路由和生态系统:
- React:React Router 是社区常用的路由解决方案。React 生态系统丰富,有大量第三方库和工具。
- Vue:Vue Router 是官方提供的路由解决方案,与 Vue 深度集成。Vue 生态系统包含 Vuex、Vue CLI 等工具和插件,易于搭建和扩展项目。
- 实际应用
-
React:
- 适用于大型复杂应用,需要灵活高效的状态管理和自定义逻辑。
- 由于其广泛使用和成熟生态,适合团队开发和长期维护的项目。
-
Vue:
- 适用于中小型项目和快速原型开发,易于上手,开发效率高。
- 由于其渐进式架构,适合逐步引入到现有项目中。
综上所述,React 和 Vue 各有优点,选择合适的框架应根据项目需求和团队技术栈进行决定。
51 进程和线程的区别
(1)考察点分析
- 基础概念:了解进程和线程的基本定义及其关系。
- 技术细节:掌握进程和线程在操作系统中的主要区别,包括资源管理、通信方式等。
- 实际应用:理解进程和线程在实际应用中的优缺点和适用场景。
(2)最终答案
- 基础概念
- 进程:
- 进程是操作系统中的一个执行单元,每个进程拥有独立的内存空间、代码和数据,是程序的一次执行。
- 线程:
- 线程是进程中的一个执行单元,一个进程可以包含多个线程,线程共享进程的地址空间和系统资源,但拥有独立的执行栈和程序计数器。
- 资源管理
- 进程:
- 拥有独立的内存空间和资源,进程之间的通信需要通过进程间通信(IPC)机制。
- 进程切换的开销较大,包括保存和恢复进程上下文等操作。
- 线程:
- 共享进程的内存空间和资源,线程之间可以直接读写共享数据。
- 线程切换的开销较小,因为线程共享进程的资源,切换时只需保存和恢复线程的上下文。
- 并发编程
- 进程:
- 适用于需要高度隔离和独立性的场景,如多进程的服务器模型,每个进程负责处理一个请求。
- 进程之间的通信相对复杂,但可以通过操作系统提供的 IPC 机制实现。
- 线程:
- 适用于需要高效并发和共享资源的场景,如多线程的 Web 服务器、图形界面程序等。
- 线程之间共享进程的资源,因此通信和数据共享更加简单和高效,但需要注意线程安全问题。
通过以上解释,可以清晰地理解进程和线程的区别,以及它们在操作系统和并发编程中的应用场景和特点。
51 http 状态码有哪些?
(1)考察点分析
- 操作系统基础:理解计算机操作系统中进程和线程的基本概念。
- 资源管理:区分进程和线程在资源管理上的差异。
- 并发编程:理解进程和线程在并发编程中的应用场景和特点。
(2)最终答案
- 基础概念
- 进程:
- 进程是操作系统中的一个执行单元,每个进程拥有独立的内存空间、代码和数据,是程序的一次执行。
- 线程:
- 线程是进程中的一个执行单元,一个进程可以包含多个线程,线程共享进程的地址空间和系统资源,但拥有独立的执行栈和程序计数器。
- 资源管理
- 进程:
- 拥有独立的内存空间和资源,进程之间的通信需要通过进程间通信(IPC)机制。
- 进程切换的开销较大,包括保存和恢复进程上下文等操作。
- 线程:
- 共享进程的内存空间和资源,线程之间可以直接读写共享数据。
- 线程切换的开销较小,因为线程共享进程的资源,切换时只需保存和恢复线程的上下文。
- 并发编程
- 进程:
- 适用于需要高度隔离和独立性的场景,如多进程的服务器模型,每个进程负责处理一个请求。
- 进程之间的通信相对复杂,但可以通过操作系统提供的 IPC 机制实现。
- 线程:
- 适用于需要高效并发和共享资源的场景,如多线程的 Web 服务器、图形界面程序等。
- 线程之间共享进程的资源,因此通信和数据共享更加简单和高效,但需要注意线程安全问题。
通过以上解释,可以清晰地理解进程和线程的区别,以及它们在操作系统和并发编程中的应用场景和特点。
52 快速排序算法
(1)考察点分析
- 排序算法:了解快速排序算法的基本思想和实现方式。
- 时间复杂度:理解快速排序算法的时间复杂度。
- 代码实现:掌握快速排序算法的实现步骤和关键代码。
(2)最终答案
-
基本概念
快速排序是一种常见的排序算法,基于分治法的思想。其基本思想是选择一个基准元素,将数组分成两个子数组,小于基准元素的放在左边,大于基准元素的放在右边,然后对子数组进行递归排序。 -
算法步骤
- 选择基准元素:从数组中选择一个元素作为基准元素。
- 分区操作:将数组中小于基准元素的放在左边,大于基准元素的放在右边,基准元素放在中间。
- 递归排序:对左右两个子数组进行递归排序。
- 代码实现
下面是快速排序算法的 JavaScript 实现示例:
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
const pivot = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
const arr = [5, 3, 7, 2, 8, 4, 1];
const sortedArr = quickSort(arr);
console.log(sortedArr); // 输出:[1, 2, 3, 4, 5, 7, 8]
53 二分查找算法
(1)考察点分析
- 查找算法:了解二分查找算法的基本思想和实现方式。
- 时间复杂度:理解二分查找算法的时间复杂度。
- 应用场景:掌握二分查找算法在有序数组中的应用场景。
(2)最终答案
1. 基本概念
二分查找算法是一种高效的查找算法,适用于有序数组。其基本思想是通过每一次比较将查找范围缩小一半,从而快速定位目标值。
2. 算法步骤
- 初始化左右边界:将左边界设为数组起始位置,右边界设为数组末尾位置。
- 循环查找:在左右边界之间循环,计算中间位置 mid,比较目标值和中间值:
- 如果目标值等于中间值,则返回 mid。
- 如果目标值小于中间值,则更新右边界为 mid - 1。
- 如果目标值大于中间值,则更新左边界为 mid + 1。
- 如果左边界大于右边界,则表示查找失败,返回 -1。
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const midValue = arr[mid];
if (midValue === target) {
return mid; // 找到目标值,返回索引
} else if (midValue < target) {
left = mid + 1; // 目标值在右半边
} else {
right = mid - 1; // 目标值在左半边
}
}
return -1; // 未找到目标值
}
// 示例
const array = [1, 3, 5, 7, 9, 11, 13, 15];
const target = 9;
const index = binarySearch(array, target);
console.log(index); // 输出:4
3. 应用场景
二分查找算法适用于有序数组,特别是在静态数据集中查找目标值的场景。例如,在排序好的数组中查找某个元素的位置,或者判断某个元素是否存在于数组中。