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 在多个方面存在区别&#…...

JavaScript【5】DOM模型
1.概述: DOM (Document Object Model):当页面被加载时,浏览器会创建页面的文档对象模型,即dom对象;dom对象会被结构化为对象树,如一个HTML文档会被分为head,body等部分,而每个部分又…...

Cloudflare防火墙拦截谷歌爬虫|导致收录失败怎么解决?
许多站长发现网站突然从谷歌搜索结果中“消失”,背后很可能是Cloudflare防火墙误拦截了谷歌爬虫(Googlebot),导致搜索引擎无法正常抓取页面。 由于Cloudflare默认的防护规则较为严格,尤其是针对高频访问的爬虫IP&…...
鸿蒙OSUniApp 实现的表单验证与提交功能#三方框架 #Uniapp
UniApp 实现的表单验证与提交功能 前言 在移动端应用开发中,表单是用户与应用交互的重要媒介。一个好的表单不仅布局合理、使用方便,还应该具备完善的验证与提交功能,以确保用户输入的数据准确无误。本文将分享如何在 UniApp 中实现表单验证…...

如何在 Windows 11 或 10 的 CMD 中检查固件
检查 Windows 11 或 10 中现有设备的硬件固件版本,可以帮助用户安装和更新准确的驱动程序,进行故障排除活动,确保兼容性以及维护系统性能。因此,在本教程中,我们将讨论如何在命令提示符(CMD)中使用一些命令查找 Windows 服务器或桌面中硬件固件版本的方法。由于本教程将…...

进阶-数据结构部分:3、常用查找算法
飞书文档https://x509p6c8to.feishu.cn/wiki/LRdnwfhNgihKeXka7DfcGuRPnZt 顺序查找 查找算法是指:从一些数据之中,找到一个特殊的数据的实现方法。查找算法与遍历有极高的相似性,唯一的不同就是查找算法可能并不一定会将每一个数据都进行访…...
Oracle 11.2.0.4 pre PSU Oct18 设置SSL连接
Oracle 11.2.0.4 pre PSU Oct18 设置SSL连接 1 说明2 客户端配置jdk环境3服务器检查oracle数据库补丁4设置ssla 服务器配置walletb 上传测试脚本和配置文件到客户端c 服务器修改数据库侦听和sqlnet.orad 修改客户端的sqlnet.ora和tnsnames.ora的连接符e 修改java代码的数据连接…...
服务器连接多客户端
一、epoll 核心函数详解 1. epoll_create/epoll_create1 - 创建 epoll 实例 c #include <sys/epoll.h> int epoll_create(int size); // Linux 2.6.8前需指定size(>1),后续版本可忽略 int epoll_create1(int flags); // 推荐使用…...

基于QT和FFmpeg实现自己的视频播放器FFMediaPlayer(一)——项目总览
在音视频开发的学习过程中,开发一款视频播放器是FFmpeg进阶的最好实战方法。本文将基于 QT 和 FFmpeg 着手实现自定义视频播放器 FFMediaPlayer,作为系列文章的开篇,我们先来整体了解项目的设计思路、架构与配置。 一、软件设计五大原则 …...
服务器死机了需要检查哪些问题
在这个数字化的时代,服务器就像是我们信息世界的“大管家”,可要是它突然死机了,那可真是让人头疼。今天咱们就来聊聊,服务器死机了,到底需要检查哪些问题。 一、硬件问题 电源供应:检查电源是否稳定&…...

【HCIA】浮动路由
前言 我们通常会在出口路由器配置静态路由去规定流量进入互联网默认应该去往哪里。那么,如果有两个运营商的路由器都能为我们提供上网服务,我们应该如何配置默认路由呢?浮动路由又是怎么一回事呢? 文章目录 前言1. 网络拓扑图2. …...

使用instance着色
本节我们学习使用instance着色器进行着色 //拾取var handler new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction(function(movement){console.log(movement);var pickedObject viewer.scene.pick(movement.position);if(Cesium.defined(picke…...

【NLP 72、Prompt、Agent、MCP、function calling】
命运把我们带到哪里,就是哪里 —— 25.5.13 一、Prompt 1.User Prompt 用户提示词 当我们与大模型进行对话时,我们向大模型发送的消息,称作User Prompt,也就是用户提示词,一般就是我们提出的问题或者想说的话 但是我们…...

Mysql数据库之集群进阶
一、日志管理 5.7版本自定义路径时的文件需要自己提前创建好文件,不会自动创建,否则启动mysql会报错 错误日志 rpm包(yum) /var/log/mysql.log 默认错误日志 ###查询日志路径 [rootdb01 ~]# mysqladmin -uroot -pEgon123 variables | grep -w log_e…...

临床决策支持系统的提示工程优化路径深度解析
引言 随着人工智能技术在医疗领域的迅猛发展,临床决策支持系统(CDSS)正经历从传统规则引擎向智能提示工程的范式转变。在这一背景下,如何构建既符合循证医学原则又能适应个体化医疗需求的CDSS成为医学人工智能领域的核心挑战。本报告深入剖析了临床决策支持系统中提示工程的…...
精益数据分析(64/126):移情阶段的用户触达策略——从社交平台到精准访谈
精益数据分析(64/126):移情阶段的用户触达策略——从社交平台到精准访谈 在创业的移情阶段,精准找到目标用户并开展深度访谈是验证需求的关键。今天,我们结合《精益数据分析》中的方法论,探讨如何利用Twit…...

苹果新一代车载系统CarPlay Ultra来袭,全屏接管+ChatGPT助力,智能驾驶要“起飞”
AITOP100获悉,苹果又搞出大动作啦!正式推出了新一代车载系统——CarPlay Ultra。这次,苹果可是下了狠功夫,把iPhone和汽车的所有显示屏深度整合到了一起,还首次把ChatGPT引入到了驾驶体验当中。这系统可不简单…...

无线信道的噪声与干扰
目录 1. 无线信道(wireless channel)与电磁波 2.1 电磁波的传输(无线信道传输) 2.2 视线(line of sight)传播与天线高度 2. 信道的数学模型 2.1 调制信道模型 2.1.1 加性噪声/加性干扰 2.1.2 乘性噪声/乘性干扰 2.1.3 随参信道/恒参信道 2.2 编码信道模型 2.3 小结 …...

MySQL 8.0 OCP 1Z0-908 101-110题
Q101.which two queries are examples of successful SQL injection attacks? A.SELECT id, name FROM backup_before WHERE name‘; DROP TABLE injection; --’; B. SELECT id, name FROM user WHERE id23 oR id32 OR 11; C. SELECT id, name FROM user WHERE user.id (SEL…...

BBR 的 buffer 动力学观感
这周很忙,今天还加了一天班,但还是抽空实现了五一在安徽泾县山区喝着一壶酒写的 BBR ProbeRTT 的想法,没多少行代码,它真就消除了带宽锯齿,皮了个鞋👞,昨天我还在群里说了今天再说说 BBR 的&…...

Spring之Bean的初始化 Bean的生命周期 全站式解析
目录 导图 步骤 第一步 实例化 第二步 属性赋值 第三步 初始化 aware 接口 BeanPostProcessor 接口 InitializingBean 和 init-method 第四步使用 第五步使用后销毁 描述一下 Bean 的 生命周期 导图 步骤 总体上可以分为五步 首先是 Bean 的实例化Bean 在进行实例…...

FreeCAD源码分析: Transaction实现原理
本文阐述FreeCAD中Transaction的实现原理。 注1:限于研究水平,分析难免不当,欢迎批评指正。 注2:文章内容会不定期更新。 一、概念 Ref. from What is a Transaction? A transaction is a group of operations that have the f…...

flutter缓存网络视频到本地,可离线观看
记录一下解决问题的过程,希望自己以后可以参考看看,解决更多的问题。 需求:flutter 缓存网络视频文件,可离线观看。 解决: 1,flutter APP视频播放组件调整; 2,找到视频播放组件&a…...

Kotlin 中 infix 关键字的原理和使用场景
在 Kotlin 中,使用 infix 关键字修饰的函数称为中缀函数,使用是可以省略 . 和 (),允许以更自然(类似自然语言)的语法调用函数,这种特性可以使代码更具可读性。 1 infix 的原理 中缀函数必须满足以下条件&…...

c++从入门到精通(五)--异常处理,命名空间,多继承与虚继承
异常处理 栈展开过程: 栈展开过程沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子句为止;也可能一直没找到匹配的catch,则退出主函数后查找过程终止。栈展开过程中的对象被自动销毁。 在栈展开的过程中,…...
mock 数据( json-server )
json-server 实现数据 mock 实现步骤: 1. 在项目中安装 json-server npm install -D json-server 2. 准备一个 json 文件 server/data.json {"posts": [{ "id": "1", "title": "a title", "views"…...
Java多线程编程中的常见问题与陷阱汇总
线程安全问题 多线程环境下,多个线程同时访问共享资源时,可能会导致数据不一致或程序行为异常。常见的线程安全问题包括竞态条件、死锁、活锁等。 public class Counter {private int count 0;public void increment() {count;}public int getCount()…...
ARP Detection MAC-Address Static
一、ARP Detection(ARP检测) ✅ 定义: ARP检测是一种防止ARP欺骗攻击的安全机制。它通过监控或验证网络中的ARP报文,来判断是否存在伪造的ARP信息。 🔍 工作原理: 网络设备(如交换机…...

gcc/g++常用参数
1.介绍 gcc用于编译c语言,g用于编译c 源代码生成可执行文件过程,预处理-编译-汇编-链接。https://zhuanlan.zhihu.com/p/476697014 2.常用参数说明 2.1编译过程控制 参数作用-oOutput,指定输出名字-cCompile,编译源文件生成对…...

nginx配置之负载均衡
版权声明:原创作品,请勿转载! 1.实验环境准备 准备3台linux服务器(ubuntu和centos均可,本文使用centos7.9),两台web和一台负载均衡服务器,均安装nginx服务 主机名IP软件lb0110.0.0…...