2025年01月10日浙江鑫越系统科技前端面试
目录
- vue2 和 vue3 的区别
- vue 怎么封装组件
- js 怎么把一个数组置空
- 怎么组件自己调用自己的组件
- v-bind:attribute 和 v-bind=“{attribute}” 的区别
- var let const 的区别
- this 指向
- 作用域链
- 闭包
- 原型链
- 事件循环
1. vue2 和 vue3 的区别
Vue 2 和 Vue 3 在多个方面存在区别,以下从架构设计、语法与 API、性能、生态系统等方面进行详细介绍:
架构设计
- 响应式系统
- Vue 2:基于
Object.defineProperty()
实现响应式。这种方式有一定局限性,例如无法检测对象属性的添加和删除,对于数组,部分方法(如通过索引修改元素)也不能触发响应式更新。 - Vue 3:采用 Proxy 对象实现响应式系统。Proxy 可以劫持整个对象,并能拦截更多操作,解决了 Vue 2 中响应式的一些限制,能更好地检测对象属性的变化,包括属性的添加、删除以及数组元素的修改等。
- Vue 2:基于
- 代码组织
- Vue 2:主要使用选项式 API(Options API),将不同的逻辑(如数据、方法、生命周期钩子等)分散在不同的选项中,在处理复杂组件时,可能会导致代码碎片化,逻辑分散难以维护。
- Vue 3:引入了组合式 API(Composition API),允许开发者根据逻辑关注点来组织代码,将相关的逻辑封装在一起,提高了代码的复用性和可维护性,尤其适合大型项目。
语法与 API
- 组件定义
- Vue 2:使用
Vue.extend()
或单文件组件(SFC)来定义组件,通过export default
导出一个包含各种选项的对象。 - Vue 3:仍然支持单文件组件,但在组合式 API 中,可以使用
<script setup>
语法糖来简化组件的定义,减少样板代码。
- Vue 2:使用
<!-- Vue 2 组件定义 -->
<template><div>{{ message }}</div>
</template><script>
export default {data() {return {message: 'Hello, Vue 2!'};}
};
</script><!-- Vue 3 组件定义(<script setup>) -->
<template><div>{{ message }}</div>
</template><script setup>
import { ref } from 'vue';
const message = ref('Hello, Vue 3!');
</script>
- 生命周期钩子
- Vue 2:有
beforeCreate
、created
、beforeMount
、mounted
、beforeUpdate
、updated
、beforeDestroy
、destroyed
等生命周期钩子。 - Vue 3:部分钩子名称发生了变化,
beforeDestroy
改为beforeUnmount
,destroyed
改为unmounted
,并且在组合式 API 中可以使用onBeforeMount
、onMounted
等函数来注册生命周期钩子。
- Vue 2:有
// Vue 2 生命周期钩子
export default {created() {console.log('Vue 2: Component created');}
};// Vue 3 组合式 API 生命周期钩子
import { onMounted } from 'vue';export default {setup() {onMounted(() => {console.log('Vue 3: Component mounted');});}
};
- 响应式数据定义
- Vue 2:在
data
选项中定义响应式数据,使用this
来访问。 - Vue 3:使用
ref()
和reactive()
函数来创建响应式数据。ref()
用于创建单个值的响应式数据,reactive()
用于创建对象的响应式数据。
- Vue 2:在
// Vue 2 响应式数据定义
export default {data() {return {count: 0};},methods: {increment() {this.count++;}}
};// Vue 3 响应式数据定义
import { ref } from 'vue';export default {setup() {const count = ref(0);const increment = () => {count.value++;};return {count,increment};}
};
性能
- 渲染性能
- Vue 2:渲染器在更新 DOM 时,使用虚拟 DOM 进行比较和更新,在处理大型组件树时,可能会有一定的性能开销。
- Vue 3:重写了渲染器,采用了静态提升、PatchFlag 等优化技术,减少了虚拟 DOM 的比较范围,提高了渲染性能,尤其是在处理大型组件和频繁更新的场景下表现更优。
- 内存占用
- Vue 2:由于响应式系统的实现方式,在创建大量响应式对象时,可能会占用较多的内存。
- Vue 3:Proxy 实现的响应式系统在内存使用上更加高效,减少了不必要的内存开销。
生态系统
- 插件兼容性
- Vue 2:拥有丰富的插件生态系统,但部分插件可能需要进行适配才能在 Vue 3 中使用。
- Vue 3:随着时间的推移,越来越多的插件开始支持 Vue 3,但在过渡期间,可能会面临一些插件兼容性问题。
- 工具链支持
- Vue 2:与之配套的工具链(如 Vue CLI)已经非常成熟。
- Vue 3:官方推出了 Vite 作为构建工具,它具有更快的冷启动和热更新速度,更适合现代前端开发。
2. vue 怎么封装组件
在 Vue 里封装组件可以提升代码复用性与可维护性。下面为你详细介绍封装组件的步骤和示例。
步骤
- 创建组件文件:在项目里创建一个新的
.vue
文件,此文件即为组件。 - 定义组件结构:在
<template>
标签里定义组件的 HTML 结构。 - 编写组件逻辑:在
<script>
标签里编写组件的 JavaScript 逻辑。 - 添加样式:在
<style>
标签里添加组件的 CSS 样式。 - 使用组件:在需要使用该组件的地方引入并注册它。
示例
下面是一个简单的按钮组件封装示例:
1. 创建组件文件 ButtonComponent.vue
<template><button :class="classes" @click="handleClick">{{ label }}</button>
</template><script>
export default {name: 'ButtonComponent',props: {label: {type: String,default: 'Click me'},primary: {type: Boolean,default: false}},computed: {classes() {return {'bg-blue-500 text-white py-2 px-4 rounded': this.primary,'bg-gray-500 text-white py-2 px-4 rounded': !this.primary};}},methods: {handleClick() {this.$emit('click');}}
};
</script><style scoped>
button {cursor: pointer;
}
</style>
2. 使用组件
<template><div><ButtonComponent label="Primary Button" primary @click="onPrimaryClick" /><ButtonComponent label="Secondary Button" @click="onSecondaryClick" /></div>
</template><script>
import ButtonComponent from './ButtonComponent.vue';export default {name: 'App',components: {ButtonComponent},methods: {onPrimaryClick() {console.log('Primary button clicked');},onSecondaryClick() {console.log('Secondary button clicked');}}
};
</script><style scoped>
/* 全局样式 */
</style>
解释
<template>
:定义了组件的 HTML 结构,这里是一个按钮元素。<script>
:定义了组件的 JavaScript 逻辑,包含props
(接收外部传入的数据)、computed
(计算属性)和methods
(方法)。<style>
:定义了组件的 CSS 样式,scoped
属性保证样式仅应用于当前组件。- 使用组件:在需要使用组件的地方引入并注册它,然后在
<template>
中使用组件标签。
通过以上步骤,你就可以在 Vue 项目中封装并使用组件了。
3. js 怎么把一个数组置空
在 JavaScript 里,有多种方法能将数组置空,下面为你详细介绍常见的几种方式及其示例代码。
方法一:将数组的长度设为 0
通过把数组的 length
属性设置成 0,能够直接清空数组,此操作会移除数组里的所有元素。
let arr = [1, 2, 3, 4, 5];
arr.length = 0;
console.log(arr); // 输出: []
方法二:重新赋值为空数组
直接把数组变量重新赋值为空数组 []
,不过要留意这种方法会让原数组失去引用,若有其他变量也引用了原数组,这些变量不会受到影响。
let arr = [1, 2, 3, 4, 5];
let anotherArr = arr;
arr = [];
console.log(arr); // 输出: []
console.log(anotherArr); // 输出: [1, 2, 3, 4, 5]
方法三:使用 splice
方法
splice
方法可用于从数组里添加或删除元素。当你把起始位置设为 0,删除数量设为数组的长度时,就能清空数组。
let arr = [1, 2, 3, 4, 5];
arr.splice(0, arr.length);
console.log(arr); // 输出: []
综上所述,若你想直接清空原数组且让所有引用该数组的变量都变为空数组,推荐使用将 length
属性设为 0 或者 splice
方法;若你仅想让当前变量指向一个新的空数组,可使用重新赋值为空数组的方法。
4. 怎么组件自己调用自己的组件
在前端开发里,组件自己调用自己也就是实现组件的递归调用,这种方式常用于处理树形结构的数据,像菜单、文件目录这类场景。下面分别以 Vue 和 React 为例,介绍如何实现组件的递归调用。
Vue 中实现组件递归调用
在 Vue 里实现组件递归调用,需确保组件自身可以在模板里引用自己。以下是一个简单的树形菜单组件示例:
<template><ul><li v-for="item in menuItems" :key="item.id">{{ item.label }}<!-- 若存在子菜单,递归调用组件 --><TreeMenu v-if="item.children && item.children.length > 0" :menuItems="item.children" /></li></ul>
</template><script>
export default {name: 'TreeMenu',props: {menuItems: {type: Array,required: true}}
};
</script><style scoped>
ul {list-style-type: none;padding-left: 20px;
}
</style>
<template><div id="app"><TreeMenu :menuItems="menuData" /></div>
</template><script>
import TreeMenu from './TreeMenu.vue';export default {name: 'App',components: {TreeMenu},data() {return {menuData: [{id: 1,label: 'Menu Item 1',children: [{id: 2,label: 'Submenu Item 1',children: [{id: 3,label: 'Sub - submenu Item 1'}]}]},{id: 4,label: 'Menu Item 2'}]};}
};
</script>
React 中实现组件递归调用
在 React 里实现组件递归调用,同样是在组件的渲染函数中调用自身。以下是一个对应的树形菜单组件示例:
import React from 'react';const TreeMenu = ({ menuItems }) => {return (<ul>{menuItems.map((item) => (<li key={item.id}>{item.label}{/* 若存在子菜单,递归调用组件 */}{item.children && item.children.length > 0 && (<TreeMenu menuItems={item.children} />)}</li>))}</ul>);
};export default TreeMenu;
import React from 'react';
import TreeMenu from './TreeMenu';const App = () => {const menuData = [{id: 1,label: 'Menu Item 1',children: [{id: 2,label: 'Submenu Item 1',children: [{id: 3,label: 'Sub - submenu Item 1'}]}]},{id: 4,label: 'Menu Item 2'}];return (<div><TreeMenu menuItems={menuData} /></div>);
};export default App;
上述代码展示了在 Vue 和 React 中实现组件递归调用的方法,你可以依据实际需求对代码进行修改和扩展。
5. v-bind:attribute 和 v-bind=“{attribute}” 的区别
在Vue.js里,v-bind:attribute
和 v-bind="{attribute}"
这两种语法存在明显差异,下面为你详细介绍:
1. v-bind:attribute
(简写为 :attribute
)
这种语法用于把单个表达式的值绑定到某个特定的属性上。就像下面这样:
<template><div><!-- 把isActive变量的值绑定到disabled属性 --><button :disabled="isActive">提交</button><!-- 把title变量的值绑定到title属性 --><img :src="imageUrl" :title="imageTitle" /></div>
</template>
- 要是
isActive
的值为true
,按钮就会被禁用。 imageUrl
和imageTitle
分别对应图片的src
和title
属性。
2. v-bind="{attribute}"
(对象语法)
该语法会对一个对象进行解构操作,然后把对象里的所有属性都绑定到元素上。示例如下:
<template><div><!-- 假设userData = { name: 'John', age: 30 } --><UserProfile v-bind="userData" /><!-- 等同于 --><UserProfile :name="userData.name" :age="userData.age" /></div>
</template>
- 当使用
v-bind="userData"
时,userData
对象中的所有属性(像name
和age
)都会被当作props传递给UserProfile
组件。 - 这一语法常用于批量传递props或者动态绑定多个属性。
主要区别
特性 | v-bind:attribute | v-bind=“{…}” |
---|---|---|
绑定数量 | 一次只能绑定一个属性 | 可以同时绑定多个属性 |
表达式类型 | 支持任意类型的表达式 | 要求必须是对象类型 |
应用场景 | 适用于绑定单个属性 | 适合批量绑定多个属性 |
实际应用示例
<template><div><!-- 情况1:绑定单个属性 --><input :value="message" /><!-- 情况2:批量绑定多个属性 --><input v-bind="inputConfig" /></div>
</template><script>
export default {data() {return {message: 'Hello',inputConfig: {type: 'text',placeholder: '请输入内容',disabled: false}}}
}
</script>
总结
- 若你需要绑定单个属性,就使用
:attribute="expression"
。 - 若你要批量绑定多个属性,建议使用
v-bind="object"
。
合理运用这两种语法,能够让你的Vue代码变得更加简洁和具有可读性。
6. var let const 的区别
在 JavaScript 中,var
、let
和 const
是用于声明变量的关键字,它们的主要区别体现在作用域、变量提升、可变性和暂时性死区等方面。以下是详细对比:
1. 作用域规则
var
:函数作用域(或全局作用域)- 在函数内部声明的变量只能在函数内部访问,在全局作用域声明的变量会成为全局对象(如浏览器中的
window
)的属性。
- 在函数内部声明的变量只能在函数内部访问,在全局作用域声明的变量会成为全局对象(如浏览器中的
let
和const
:块级作用域({}
内有效)- 在
if
、for
、while
等代码块中声明的变量,外部无法访问。
- 在
示例对比:
function testScope() {if (true) {var x = 10; // 函数作用域let y = 20; // 块级作用域const z = 30; // 块级作用域}console.log(x); // ✅ 输出 10console.log(y); // ❌ ReferenceErrorconsole.log(z); // ❌ ReferenceError
}
2. 变量提升(Hoisting)
var
:存在变量提升,可在声明前访问(值为undefined
)let
和const
:存在暂时性死区(TDZ),声明前访问会报错
示例对比:
console.log(a); // ✅ undefined(var 提升但未赋值)
console.log(b); // ❌ ReferenceError(TDZ)
console.log(c); // ❌ ReferenceError(TDZ)var a = 1;
let b = 2;
const c = 3;
3. 可变性
var
和let
:变量值可修改const
:常量值不可修改(必须在声明时赋值,且不能重新赋值)
示例对比:
var a = 1;
a = 2; // ✅ 允许修改let b = 3;
b = 4; // ✅ 允许修改const c = 5;
c = 6; // ❌ TypeError(不能重新赋值)// 注意:const 声明对象/数组时,对象属性或数组元素可修改
const obj = { name: 'Alice' };
obj.name = 'Bob'; // ✅ 允许修改属性
obj = {}; // ❌ TypeError(不能重新赋值对象)
4. 重复声明
var
:允许在同一作用域重复声明同名变量(后面的会覆盖前面的)let
和const
:不允许在同一作用域重复声明同名变量
示例对比:
var a = 1;
var a = 2; // ✅ 允许,a 变为 2let b = 3;
let b = 4; // ❌ SyntaxError(重复声明)const c = 5;
const c = 6; // ❌ SyntaxError(重复声明)
5. 全局作用域行为
var
:在全局作用域声明的变量会成为全局对象的属性let
和const
:在全局作用域声明的变量不会成为全局对象的属性
示例对比:
var x = 10;
console.log(window.x); // ✅ 输出 10let y = 20;
const z = 30;
console.log(window.y); // ❌ undefined
console.log(window.z); // ❌ undefined
推荐使用场景
let
:需要重新赋值的变量,尤其是在块级作用域中(如循环、条件语句)。const
:不需要重新赋值的变量(默认优先使用),如常量、对象、函数引用等。var
:尽量避免使用,除非需要兼容旧代码或特殊场景(如函数作用域)。
总结表格
特性 | var | let | const |
---|---|---|---|
作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
变量提升 | 存在(值为 undefined ) | 存在(TDZ) | 存在(TDZ) |
可变性 | 可变 | 可变 | 不可变(常量) |
重复声明 | 允许 | 不允许 | 不允许 |
全局对象属性 | 是 | 否 | 否 |
通过合理使用 let
和 const
,可以减少变量污染和提升代码的健壮性,这也是现代 JavaScript 开发的最佳实践。
7. this 指向
在 JavaScript 中,this
关键字的指向是动态的,它取决于函数的调用方式。这一点与其他语言有很大不同,也是 JavaScript 的一个难点。下面将详细介绍 this
的指向规则及其应用场景。
1. 全局作用域中的 this
在全局作用域中,this
指向全局对象(在浏览器中是 window
对象)。
console.log(this === window); // true(在浏览器环境中)var globalVar = 'global';
console.log(this.globalVar); // 'global'(全局变量是全局对象的属性)// 严格模式下,全局作用域中的 this 仍为全局对象
function test() {'use strict';console.log(this === window); // true
}
test();
2. 函数调用中的 this
普通函数调用时,this
指向全局对象(非严格模式)或 undefined
(严格模式)。
function showThis() {console.log(this);
}showThis(); // window(非严格模式)或 undefined(严格模式)// 严格模式示例
function strictThis() {'use strict';console.log(this); // undefined
}
strictThis();
3. 方法调用中的 this
当函数作为对象的方法调用时,this
指向调用该方法的对象。
const person = {name: 'Alice',greet() {console.log(`Hello, ${this.name}`);}
};person.greet(); // 'Hello, Alice'(this 指向 person 对象)// 嵌套对象示例
const obj = {outer: {inner: {method() {console.log(this); // 指向 inner 对象}}}
};obj.outer.inner.method(); // 输出 inner 对象
4. 构造函数中的 this
使用 new
调用构造函数时,this
指向新创建的实例对象。
function Car(color) {this.color = color;this.showColor = function() {console.log(this.color);};
}const redCar = new Car('red');
redCar.showColor(); // 'red'(this 指向 redCar 实例)
5. 箭头函数中的 this
箭头函数不绑定自己的 this
,而是捕获其所在上下文的 this
值。
const obj = {name: 'Bob',regular() {console.log(this.name); // 'Bob'},arrow: () => {console.log(this.name); // undefined(箭头函数的 this 继承自全局作用域)}
};obj.regular(); // 'Bob'
obj.arrow(); // undefined// 常见应用:在回调函数中保持 this 指向
const timer = {seconds: 10,start() {setInterval(() => {this.seconds--; // 箭头函数的 this 指向 timer 对象console.log(this.seconds);}, 1000);}
};timer.start();
6. call
、apply
和 bind
方法
这三个方法可以显式地绑定函数的 this
值。
function greet(message) {console.log(`${message}, ${this.name}`);
}const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };// call():直接调用并指定 this
greet.call(person1, 'Hi'); // 'Hi, Alice'
greet.call(person2, 'Hello'); // 'Hello, Bob'// apply():类似 call,但参数以数组形式传递
greet.apply(person1, ['Hi']); // 'Hi, Alice'// bind():创建一个新函数,永久绑定 this
const greetAlice = greet.bind(person1);
greetAlice('Hey'); // 'Hey, Alice'
7. DOM 事件处理中的 this
在 DOM 事件处理函数中,this
通常指向触发事件的元素。
<button id="myButton">Click me</button>
<script>const button = document.getElementById('myButton');button.addEventListener('click', function() {this.style.backgroundColor = 'red'; // this 指向 button 元素});
</script>
8. 类中的 this
在 ES6 类中,this
的指向与构造函数和方法的调用方式一致。
class Counter {constructor() {this.count = 0;}increment() {this.count++;console.log(this.count);}
}const counter = new Counter();
counter.increment(); // 1(this 指向 counter 实例)
总结:this
指向的判断规则
- 全局作用域:
this
指向全局对象(如window
)。 - 函数调用:非严格模式下指向全局对象,严格模式下为
undefined
。 - 方法调用:
this
指向调用该方法的对象。 - 构造函数:
this
指向新创建的实例对象。 - 箭头函数:
this
继承自父级上下文。 - 显式绑定:使用
call
、apply
、bind
方法指定this
。 - DOM 事件:
this
指向触发事件的元素。
常见问题与注意事项
- 箭头函数与普通函数的区别:箭头函数没有自己的
this
,适合用于需要保留上下文的场景(如回调函数);普通函数的this
取决于调用方式。 - 丢失
this
绑定:
解决方法:使用const obj = {name: 'Alice',greet() {console.log(`Hello, ${this.name}`);} };const greetFunc = obj.greet; greetFunc(); // 错误:this 指向全局对象或 undefined
bind()
绑定this
,或使用箭头函数。
通过理解 this
的指向规则,你可以更准确地编写 JavaScript 代码,避免因 this
指向不明导致的错误。
9. 闭包
在 JavaScript 里,闭包是一个强大且重要的概念。下面为你详细解释 JavaScript 中的闭包。
定义
闭包是指有权访问另一个函数作用域中变量的函数。简单来说,即使外部函数执行完毕,其作用域内的变量也不会被销毁,而是会被闭包“捕获”并保留,使得这些变量能在外部函数之外被访问和修改。
形成条件
闭包的形成需要满足以下两个关键条件:
- 函数嵌套:必须存在一个外部函数和至少一个内部函数。
- 内部函数引用外部函数的变量:内部函数使用了外部函数作用域内的变量。
作用
闭包在 JavaScript 中有多种重要作用:
- 读取函数内部的变量:外部函数执行结束后,其内部变量会被闭包保存,可通过闭包在外部访问这些变量。
- 让这些变量的值始终保持在内存中:变量不会因外部函数执行完毕而被销毁,而是持续存在于内存里,方便后续使用。
- 封装私有变量和方法:可以使用闭包来创建私有变量和方法,避免全局作用域的污染。
示例
function outerFunction() {// 外部函数的变量let counter = 0;// 内部函数,形成闭包function innerFunction() {counter++;return counter;}return innerFunction;
}// 创建闭包实例
const closure = outerFunction();// 调用闭包
console.log(closure()); // 输出: 1
console.log(closure()); // 输出: 2
console.log(closure()); // 输出: 3
在这个示例中,outerFunction
是外部函数,innerFunction
是内部函数。innerFunction
引用了 outerFunction
作用域内的 counter
变量,从而形成了闭包。当 outerFunction
执行完毕后,counter
变量不会被销毁,而是被 innerFunction
捕获并保留。每次调用 closure
函数时,counter
变量的值都会增加。
闭包的潜在问题
虽然闭包功能强大,但也可能带来一些问题,比如内存泄漏。由于闭包会让变量一直存在于内存中,如果闭包使用不当,可能会导致内存占用过高。因此,在使用闭包时,需要注意内存的使用情况,避免不必要的内存消耗。
10. 原型链
原型链是JavaScript中实现继承和对象属性查找的一种机制。以下是关于原型链的详细介绍:
原型的概念
在JavaScript中,每个对象都有一个原型(prototype
)。原型也是一个对象,它可以包含一些属性和方法。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript引擎就会去它的原型对象中查找。
原型链的形成
- 所有的对象都默认从
Object.prototype
继承属性和方法。例如,toString()
、valueOf()
等方法就是从Object.prototype
继承来的。 - 当创建一个函数时,JavaScript会自动为这个函数添加一个
prototype
属性,这个属性指向一个对象,称为该函数的原型对象。当使用构造函数创建一个新对象时,新对象的__proto__
属性(也称为原型链指针)会指向构造函数的原型对象。这样就形成了一条链,从新对象开始,通过__proto__
不断指向它的原型对象,直到Object.prototype
,这条链就是原型链。
原型链的作用
- 实现继承:通过原型链,一个对象可以继承另一个对象的属性和方法。例如,定义一个
Animal
构造函数,再定义一个Dog
构造函数,让Dog
的原型指向Animal
的实例,这样Dog
的实例就可以继承Animal
的属性和方法。 - 属性和方法的共享:多个对象可以共享原型对象上的属性和方法,节省内存空间。比如,所有数组对象都共享
Array.prototype
上的push()
、pop()
等方法。
示例代码
// 定义一个构造函数
function Person(name) {this.name = name;
}// 在构造函数的原型上添加方法
Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name}`);
};// 创建一个Person的实例
const person1 = new Person('John');// 访问实例的属性和方法,先在实例本身查找,找不到就去原型上查找
person1.sayHello(); // 输出 "Hello, my name is John"
console.log(person1.__proto__ === Person.prototype); // 输出 true
在这个例子中,person1
是 Person
构造函数的实例,它的 __proto__
属性指向 Person.prototype
。当调用 person1.sayHello()
时,由于 person1
本身没有 sayHello
方法,JavaScript会沿着原型链在 Person.prototype
上找到该方法并执行。
11. 事件循环
以下是对 JavaScript 事件循环的更深入解释:
基本概念
- 单线程执行模型:JavaScript 是单线程的,即在同一时间内只能执行一个任务。这意味着 JavaScript 代码按顺序执行,不会出现多个任务同时执行的情况。但为了处理异步操作,JavaScript 引入了事件循环机制,使它可以在等待某些操作完成时继续执行其他代码。
核心组件
- 执行栈(Call Stack):
- 执行栈是一个后进先出(LIFO)的数据结构,用于存储当前正在执行的函数调用。
- 当一个函数被调用时,它会被压入执行栈;当函数执行完成,它会从栈中弹出。
- 例如:
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):
- 任务队列存储着等待执行的任务,主要是异步操作的回调函数。
- 任务队列可以分为宏任务队列(Macrotask Queue)和微任务队列(Microtask 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
。
- 首先,
事件循环的重要性和应用场景
-
重要性:
- 事件循环使 JavaScript 能够高效处理异步操作,避免因等待某些操作(如网络请求、文件读取等)而阻塞代码执行,保证程序的流畅性。
- 理解事件循环有助于避免一些常见的异步编程错误,如竞态条件、回调地狱等。
-
应用场景:
- 网络请求:当使用
fetch
或XMLHttpRequest
进行网络请求时,请求完成后的回调函数会被添加到任务队列中,等待执行。 - 用户交互:点击事件、输入事件等用户交互的处理函数会被添加到任务队列中,在用户触发事件后等待执行。
- 定时器操作:使用
setTimeout
、setInterval
等定时器,其回调函数会在设定的时间后添加到任务队列中。
- 网络请求:当使用
在面试中,可以这样回答:“JavaScript 事件循环是一种处理异步操作的机制,它基于单线程执行模型。核心组件包括执行栈和任务队列,任务队列又分为宏任务队列和微任务队列。宏任务如 setTimeout
、setInterval
等,微任务如 Promise.then()
等。事件循环的执行流程是先检查执行栈是否为空,若为空,检查微任务队列,若微任务队列不为空,执行微任务直到为空,再从宏任务队列取一个任务执行,不断重复这个过程。这一机制使 JavaScript 可以在等待异步操作时继续执行其他代码,避免阻塞,同时保证了执行顺序。例如在处理网络请求、用户交互和定时器操作等场景中,事件循环能确保这些异步操作的回调函数在适当的时间得到执行,同时避免因等待而影响程序的流畅性。”
通过这样的详细解释和示例,可以清晰地阐述 JavaScript 事件循环的概念、流程、重要性和应用场景,让面试官了解你对该知识点的深入理解和掌握程度。
相关文章:
2025年01月10日浙江鑫越系统科技前端面试
目录 vue2 和 vue3 的区别vue 怎么封装组件js 怎么把一个数组置空怎么组件自己调用自己的组件v-bind:attribute 和 v-bind“{attribute}” 的区别var let const 的区别this 指向作用域链闭包原型链事件循环 1. vue2 和 vue3 的区别 Vue 2 和 Vue 3 在多个方面存在区别&#…...
2025.05.11阿里云机考真题算法岗-第三题
📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 镜像追踪游戏 问题描述 A先生正在玩一款名为「镜像追踪」的游戏。游戏在一个 n n n...
实景三维建模软件应用场景(众趣科技实景三维建模)
实景三维建模软件应用场景概述 实景三维建模软件,作为数字化时代的重要工具,不仅能够真实、立体、时序化地反映和表达物理世界,还为国家的基础设施建设和数字化发展提供了有力的支撑。 在测绘与地理信息领域,实景三维建模软件是构…...
centos中libc.so.6No such file的解决方式
你在运行安装程序时遇到了以下错误: Configuring the installer for this systems environment...strings: /lib/libc.so.6: No such file 这个错误通常是由于系统中缺少 glibc(GNU C Library)或其相关文件导致的。glibc 是 Linux 系统中的…...
MapReduce打包运行
1. 编写 MapReduce 程序 首先需要编写 MapReduce 程序,通常包含 Mapper、Reducer 和 Driver 类。例如,一个简单的 WordCount 程序: java import java.io.IOException; import java.util.StringTokenizer;import org.apache.hadoop.conf.Con…...
RustDesk:开源电脑远程控制软件
RustDesk:开源电脑远程控制软件 RustDesk:开源电脑远程控制软件一、RustDesk 简介二、下载教程2.1 桌面版下载2.2 Android 版下载 三、安装教程3.1 桌面版安装 四、功能讲解4.1 远程控制4.2 文件传输4.3 安全可靠4.4 自定义服务器 五、RustDesk技术架构解…...
【Unity】WebGL开发问题汇总
1 前言 主要记录下WebGL开发过程中遇到的各种问题。 2 问题 2.1 中文字体不显示 问题: 经典问题了。将项目打包在WebGL平台后,运行发现所用中文字体都不现实了。 解决办法: 替换Text组件的“字体”。可以将电脑字体复制到项目当中然后替换组…...
华为海思系列----昇腾张量编译器(ATC)模型转换工具----入门级使用指南(LINUX版)
由于官方SDK比较冗余且经常跨文档讲解且SDK整理的乱七八糟,对于新手来说全部看完上手成本较高,本文旨在以简短的方式介绍 CAFFE / ONNX 模型转 om 模型,并进行推理的全流程。希望能够帮助到第一次接触华为海思框架的道友们。大佬们就没必要看这种基础文章啦! 注:本…...
c++STL-list的模拟实现
cSTL-list的模拟实现 list源码剖析list模拟实现list构造函数拷贝构造函数赋值重载迭代器 iterator访问结点数size和判空尾插 push_back头插 push_front尾删pop_back头删pop_front插入 insert删除 erase清空clear和析构函数访问结点 参考程序 list源码剖析 建议先看cSTL-list的…...
RabbitMQ 核心概念与消息模型深度解析(二)
四、代码实战 了解了 RabbitMQ 的核心概念和消息模型后,接下来我们通过代码实战来进一步加深对它们的理解和掌握。下面将以 Java 和 Spring AMQP 为例,展示如何使用 RabbitMQ 进行消息的发送和接收。 4.1 环境准备 在开始编写代码之前,需要…...
JAVA研发+前后端分离,ZKmall开源商城B2C商城如何保障系统性能?
在电商行业竞争白热化的当下,B2C 商城系统的性能表现成为决定用户留存与商业成败的关键因素。ZKmall 开源商城凭借 Java 研发与前后端分离架构的深度融合,构建起一套高效、稳定且具备强大扩展性的系统架构,从底层技术到上层应用全方位保障性能…...
【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】
1. 背景 我们在 gd_shim_module 介绍章节中,看到 我们将 StorageModule 模块加入到了 modules 中。 // system/main/shim/stack.cc modules.add<storage::StorageModule>();在 ModuleRegistry::Start 函数中我们对 加入的所有 module 挨个初始化。 而在该函…...
Datawhale 5月llm-universe 第1次笔记
课程地址:GitHub - datawhalechina/llm-universe: 本项目是一个面向小白开发者的大模型应用开发教程,在线阅读地址:https://datawhalechina.github.io/llm-universe/ 难点:配置conda环境变量 我用的vscode github方法 目录 重要…...
Linux架构篇、第五章git2.49.0部署与使用
Linux_架构篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:git2.49.0部署与使用 版本号: 1.0,0 作者: 老王要学习 日期: 2025.05.13 适用环境: Centos7 文档说明 这份文档聚焦于在 CentOS 7 环境下部署和…...
南方科技大学Science! 自由基不对称催化新突破 | 乐研试剂
近日,南方科技大学刘心元教授团队联合浙江大学洪鑫教授团队在自由基不对称催化领域取得新进展。课题组开发了一系列大位阻阴离子 N,N,P-配体,用于铜催化未活化外消旋仲烷基碘与亚砜亚胺的不对称胺化反应。该反应表现出广泛的底物兼容性,涵盖具…...
手机换IP真的有用吗?可以干什么?
在当今数字化时代,网络安全和个人隐私保护日益受到重视。手机作为我们日常生活中不可或缺的工具,其网络活动痕迹往往通过IP地址被记录和追踪。那么,手机换IP真的有用吗?它能为我们带来哪些实际好处?本文将为你一一解答…...
【C++详解】类和对象(上)类的定义、实例化、this指针
文章目录 一、类的定义1、类定义格式2、访问限定符3、类域 二、实例化1、实例化概念2、对象大小 三、this指针 一、类的定义 1、类定义格式 class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中…...
C语言—再学习(数据的存储类别)
在c语言中,每个变量和函数都有两个属性:数据类型和数据的存储类别 C的存储类别包括4种:自动挡(auto)、静态的(static)、寄存器的(register)、外部的(extern&…...
软考软件评测师——计算机组成与体系结构(分级存储架构)
一、虚拟存储技术 虚拟存储系统通过软硬件协同实现内存扩展,其核心特征包括: 逻辑容量扩展能力:实际物理内存与外存结合,呈现远大于物理内存的连续地址空间动态加载机制:程序运行时仅加载必要部分到内存,…...
需求跟踪矩阵准确性的5大策略
需求跟踪矩阵的准确性可显著提升软件项目质量,确保需求的全面覆盖、减少遗漏和偏差,有利于优化变更管理,降低返工风险,最终保障产品符合用户预期和业务目标。如果不能保证跟踪矩阵的准确性,可能会导致需求遗漏、测试覆…...
【调度算法】MAPF多智能体路径规划问题
参考链接:https://blog.csdn.net/qq_43353179/article/details/129396325 在这篇博客的基础上对一些省略的部分进行补充。 网站:https://mapf.info/ 可行性判断 1. k-鲁棒性(k-robust MAPF) 在经典 MAPF 中,只要所有…...
迅龙3号基于兆讯MH22D3适配CST328多点触摸驱动开发笔记
MH22D3芯片是兆讯公司新推出的基于cortex-M3内核的新一代芯片,专注于显示应用,其主频高达216Mhz,64KB SRAM,512KB Flash,开发UI应用游刃有余。详细介绍请看:MH22D3新一代显控应用性价比之王 新龙微基于MH22…...
推荐算法工程化:ZKmall模板商城的B2C 商城的用户分层推荐策略
在 B2C 电商竞争激烈的市场环境中,精准推荐已成为提升用户体验、促进商品销售的关键。ZKmall 模板商城通过推荐算法工程化手段,深度挖掘用户数据价值,制定科学的用户分层推荐策略,实现 “千人千面” 的个性化推荐,帮助…...
你对于JVM底层的理解
JVM(Java虚拟机)是一个执行Java字节码的虚拟机,负责将Java程序的代码转化为能够在不同操作系统上运行的机器码。为了深入理解JVM的底层工作原理,可以从以下几个方面入手: 1. 类加载机制 JVM的类加载机制是其核心之一…...
深入探讨 Java 性能术语与优化实践
在 Java 开发中,性能优化是确保应用程序高效运行的关键。无论是构建实时处理系统还是大规模分布式服务,理解性能术语和分析方法都至关重要。本文将详细介绍 Java 性能中的核心术语,包括延迟(Latency)、吞吐量(Throughput)、利用率(Utilization)、效率(Efficiency)、…...
简单介绍Qt的属性子系统
深入理解Qt的属性系统 笔者最近正在大规模的开发Qt的项目和工程,这里笔者需要指出的是,这个玩意在最常规的Qt开发中是相对比较少用的,笔者也只是在Qt的QPropertyAnimation需要动画感知笔者设置的一个属性的时候方才知道这个东西的。因此&…...
【PmHub后端篇】PmHub中基于自定义注解和AOP的服务接口鉴权与内部认证实现
1 引言 在现代软件开发中,尤其是在微服务架构下,服务接口的鉴权和内部认证是保障系统安全的重要环节。本文将详细介绍PmHub中如何利用自定义注解和AOP(面向切面编程)实现服务接口的鉴权和内部认证,所涉及的技术知识点…...
消息~组件(群聊类型)ConcurrentHashMap发送
为什么选择ConcurrentHashMap? 在开发聊天应用时,我们需要存储和管理大量的聊天消息数据,这些数据会被多个线程频繁访问和修改。比如,当多个用户同时发送消息时,服务端需要同时处理这些消息的存储和查询。如果用普通的…...
掌控随心 - 服务网格的流量管理艺术 (Istio 实例)
掌控随心 - 服务网格的流量管理艺术 (Istio 实例) 想象一下,没有服务网格的时候,我们要实现像“将 1% 的用户流量导入到新版本应用”、“根据用户设备类型访问不同后端”、“模拟下游服务故障”这类高级流量策略,通常需要在代码、负载均衡器、API 网关等多个地方进行复杂且分…...
Github 2025-05-13 Python开源项目日报 Top10
根据Github Trendings的统计,今日(2025-05-13统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10TypeScript项目1 ComfyUI:强大而模块化的稳定扩散GUI 创建周期:399 天开…...
Spring Boot 自动装配原理详解
Spring Boot 的自动装配(Auto-Configuration)是其核心特性之一,它极大地简化了 Spring 应用的配置过程。通过自动装配,Spring Boot 能够根据项目中的依赖(例如,添加了 Spring Data JPA 依赖后自动配置数据库…...
Python核心数据类型全解析:字符串、列表、元组、字典与集合
导读: Python 是一门功能强大且灵活的编程语言,而其核心数据类型是构建高效程序的基础。本文深入剖析了 Python 的五大核心数据类型——字符串、列表、元组、字典和集合,结合实际应用场景与最佳实践,帮助读者全面掌握这些数据类型…...
索尼(sony)摄像机格式化后mp4的恢复方法
索尼(sony)的Alpha 7 Ⅳ系列绝对称的上是索尼的“全画幅标杆机型”,A7M4配备了3300万像素的CMOS,以及全新研发的全画幅背照式Exmor R™CMOS影像传感器,搭载BIONZ XR™影像处理器,与旗舰微单™Alpha 1如出一辙。下面我们来看看A7M4…...
Kubernetes容器运行时:Containerd vs Docker
Containerd 和 Docker 是容器技术领域的两个核心组件,它们在功能定位、架构设计、性能特点及适用场景上有显著差异。以下是两者的详细对比分析: 一、定位与功能 特性DockerContainerd核心定位完整的容器平台,包含构建、运行、编排等全生命周…...
免费专业级 PDF 处理!SolidPDF OCR 识别 + 精准转换批量处理
各位办公小能手们!今天咱来聊聊一款超牛的软件——SolidConverterPDF。这可是个专业的多功能PDF处理工具,啥格式转换、文档编辑、扫描识别,它都能搞定!下面我就给大伙详细唠唠它的厉害之处。 先说说它的核心功能。 一是PDF格式转换…...
电子电器架构 --- 区域计算架构(Zonal Compute)备战下一代电子电气架构
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...
API的学习总结(上)
在 Java 中,API 指的是 Java 提供的一系列类、接口、方法和工具,用于开发 Java 应用程序。Java API 是 Java 平台的核心组成部分,它提供了丰富的功能,包括基础数据类型、集合框架、输入输出、网络编程、多线程、数据库连接等。 核…...
Spring Boot之Web服务器的启动流程分析
如何判断创建哪种web容器:servlet?reactive? 我们在启动Spring Boot程序的时候,会使用SpringApplication.run方法来启动,在启动流程中首先要判断的就是需要启动什么类型的服务器,是servlet?或者…...
代码随想录算法训练营第六十三天| 图论9—卡码网47. 参加科学大会,94. 城市间货物运输 I
每日被新算法方式轰炸的一天,今天是dijkstra(堆优化版)以及Bellman_ford ,尝试理解中,属于是只能照着代码大概说一下在干嘛。 47. 参加科学大会 https://kamacoder.com/problempage.php?pid1047 dijkstra(…...
RAG之大规模解析 PDF 文档全流程实战
PDF 文档在商业、学术和政府领域无处不在,蕴含着大量宝贵信息。然而,从 PDF 中提取结构化数据却面临着独特的挑战,尤其是在处理数千甚至数百万个文档时。本指南探讨了大规模解析 PDF 的策略和工具。 PDF解析挑战 PDF 的设计初衷是为了提供一致的视觉呈现,而非数据提取。这…...
uart16550详细说明
一、介绍 uart16550 ip core异步串行通信IP连接高性能的微控制器总线AXI,并为异步串行通信提供了 控制接口。软核设计连接了axilite接口。 二、特性 1.axilite接口用于寄存器访问和数据传输 2.16650串口和16450串口的软件和硬件寄存器都是兼容的 3.默认的core配置参数…...
Docker 环境安装(2025最新版)
Docker在主流的操作系统和云平台上都可以使用,包括Linux操作 系统(如Ubuntu、 Debian、Rocky、Redhat等)、MacOS操作系统和 Windows操作系统,以及AWS等云平 台。 Docker官网: https://docs.docker.com/ 配置宿主机网…...
Comparator不满足自反性错误,Comparison method violates its general contract
APP运行退出,跟踪信息 java.lang.IllegalArgumentException: Comparison method violates its general contract! Collections.sort(idxsList);//按score升序排列 查看idxs类 public int compareTo(Idxs o) { //重写compareTo方法 return (int) (this.g…...
[Java实战]Spring Boot 3 整合 Apache Shiro(二十一)
[Java实战]Spring Boot 3 整合 Apache Shiro(二十一) 引言 在复杂的业务系统中,安全控制(认证、授权、加密)是核心需求。相比于 Spring Security 的重量级设计,Apache Shiro 凭借其简洁的 API 和灵活的扩…...
如何界定合法收集数据?
首席数据官高鹏律师团队 在当今数字化时代,数据的价值日益凸显,而合法收集数据成为了企业、机构以及各类组织必须严守的关键准则。作为律师,深入理解并准确界定合法收集数据的范畴,对于保障各方权益、维护法律秩序至关重要。 一…...
Flask+HTML+Jquery 文件上传下载
HTML 代码: <div id"loadingIndicator" style"display:none;"><div class"spinner"></div> </div> <!-- 请求过程中转圈圈 --> <form action"" method"post" enctype"m…...
MapReduce打包运行
(一)maven打包 MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序(例如:jar…...
国产化Word处理控件Spire.Doc教程:如何使用 C# 从 Word 中提取图片
通过编程方式从 Word 文档中提取图片,可以用于自动化文档处理任务。E-iceblue旗下Spire系列产品是国产文档处理领域的优秀产品,支持国产化,帮助企业高效构建文档处理的应用程序。本文将演示如何使用 C# 和 Spire.Doc for .NET 库从 Word 文件…...
07 mysql之DQL
一、什么是DQL DQL 是 SQL 的一部分,专门用于查询数据。核心命令是 SELECT,是最常用的命令,支持: 简单查询条件过滤排序与分页多表连接聚合统计子查询与复杂逻辑二、基础查询语法 SELECT 字段1, 字段2, ... FROM 表名 WHERE 条件表达式 GROUP BY 分组字段 HAVING 分组条件…...
spark-standalone
一、定义:Standalone 模式是一种独立的集群部署模式,自带完整服务,可单独部署到一个集群中,无需依赖任何其他资源管理系统。 二、配置步骤 1.和前面一样拉到hadoop101的/opt/module这个目录里面。 2.压缩 3.重命名为spark-sta…...