目录
[2. 闭包](#2. 闭包)
[3. 原型与原型链](#3. 原型与原型链)
[5. 事件循环](#5. 事件循环)
前言
分为三大部分:"H5/JS/CSS篇 "、"Vue篇 "、"手写代码篇"
题目来自Gemini3 Pro,本文汇总题目后整理出一份简单易懂的答案,方便同学们理解
如有错误,欢迎大家评论区留言或私信我,我看到后第一时间回复,谢谢
JS篇
1.数据类型与堆栈内存
JavaScript的数据类型分为两大类:"基本数据类型 "和"引用数据类型(对象数据类型)"
- 基本数据类型:Int、String、Number、Boolean、null、undefined、Symbol
- 引用数据类型:Object(Function、Array、Object、Date、RegExpJS引擎对不同数据类型的数据存储有不同的处理方式,总结来说:"基本数据类型用栈存储,引用数据类型用栈和堆共同存储 "
如果你学过C语言的指针那么这里将非常容易理解,变量在初始化后,将被存储在内存单元中。而内存单元主要有两部分:"内存单元地址 "和"内存单元中的存储内容"
以基本数据类型存储来说,执行以下变量定义:
javascript
const test = "This is a test variable";
此时在内存单元中的存储如下图所示:
基本数据类型定义比较简单,当定义一个基本数据类型时,JS引擎向栈顶单元存储该数据类型的值,再开辟一块新的内存单元作为栈顶
而引用类型则不同,它就像是C语言里的指针,当定义一个引用类型时,JS引擎会先在堆中分配多块内存来存储该引用类型变量的值,再将该块内存的首地址存储到栈顶单元中
例如如下代码:
javascript
const myObject = {
'name': 'This is a test object variable'
}
此时在内存单元中的存储如下图所示:

下表是堆与栈的比较:
| 特性 | 栈 | 堆 |
| 存储内容 | 基本类型的值、引用类型的内存地址 | 引用类型的具体对象内容 |
| 分配空间 | 自动分配、空间固定且连续 | 动态分配、空间大且不连续 |
| 执行效率 | 高、后进先出 | 相对较低,需要查找地址 |
|---|
Gemini said
复习 JavaScript 核心概念是夯实基础的关键。这里为你梳理了这五个重点,帮你查漏补缺。
2. 闭包
闭包:可以访问另一个函数作用域中变量的函数。
作用:
-
数据私有化: 模拟私有变量,防止全局变量污染。
-
缓存存储: 让变量的值始终保持在内存中。
问题:
-
内存泄漏: 如果闭包长期存在且未被释放,会导致关联的变量无法被垃圾回收,占据内存。
-
性能损耗: 闭包涉及跨作用域访问,处理速度比局部变量稍慢。
一个闭包示例代码(计时器):
javascript
function createCounter() {
let count = 0;
return function add() {
count++;
console.log(`当前计数: ${count}`);
};
}
const myCounter = createCounter();
myCounter(); // 输出: 当前计数: 1
myCounter(); // 输出: 当前计数: 2
myCounter(); // 输出: 当前计数: 3
const anotherCounter = createCounter();
anotherCounter(); // 输出: 当前计数: 1
在上面代码中,myCounter和anotherCounter是两个不同的对象,各自有专属的作用域,所以计时器之间不会相互污染
除此之外,闭包常用于"防抖"和"节流",这个后面会讲
3. 原型与原型链
在 JavaScript 中,每个对象都有一个隐藏属性__proto__,指向它的原型对象Prototype。
-
原型: 每一个函数在创建时都会关联一个Prototype属性,用于存放实例共享的方法和属性。
-
原型链: 当访问一个对象的属性时,如果对象本身没有,JS 引擎会顺着__proto__往上找,直到找到该属性或到达null,这种链式指向关系就是原型链。
什么时候用原型:
当某个业务需要创建多个对象变量,并且这些对象变量都有相同的函数:
javascript
function createFactory(name) {
this.name = name;
this.say = function() {
console.log("My name is:", this.name);
}
}
const variable1 = new createFactory('1');
const variable2 = new createFactory('2');
// ...
所有Factory创建的对象实例都有相同的函数,这无疑是很占内存的,此时就可以使用原型方法来节省内存:
javascript
function createFactory(name) {
this.name = name;
}
// 方法一:直接在构造函数中定义方法
createFactory.prototype.say = function() {
console.log("Factory name is: " + this.name);
}
const variable1 = new createFactory("1");
const variable2 = new createFactory("2");
// 方法二:在实例的__proto__上直接定义方法
// variable1.__proto__.say = function() {
// console.log("Factory name is: " + this.name);
// }
// Object.getPrototypeOf(variable).say = function() {
// console.log("Factory name is: " + this.name);
// }
// ....
variable1.say(); // Factory name is: 1
variable2.say(); // Factory name is: 2
下面是一个原型链完整结构图,看明白这张图(反人类图),相信原型链对你来说已经不在话下了:
(双向箭头表示相同、相等)

更具体的原型与原型链讲解可以参考另一篇文章:
JavaScript---原型和原型链_js原型和原型链的概念-CSDN博客
4.this指向问题
this指向取决于函数在哪里被调用,而不是函数在哪里定义
普通函数:
- 全局环境:指向window(非严格模式)或Global(Node.js环境下)或undefined(严格模式)
- 对象调用:谁调用指向谁
- 构造函数:指向new出来的新实例
- 显式绑定:call、apply、bind手动改变this指向
箭头函数:
- 没有自己的this,它会捕获定义时所处的上层作用域的this值
- 无法改变:call、apply对它无效
- 不能作为构造函数:没有this,无法new
具体的this指向可以参考另一篇文章:
【二】JavaScript能力提升---this对象_this撖寡情-CSDN博客
5. 事件循环
JS是单线程的,它通过循环处理异步操作
异步操作会进入一个任务队列中,任务队列分为:"宏任务"和"微任务"
- 宏任务:setTimeout、setInterval、I/O操作、UI刷新
- 微任务:Promise.then/catch/finally、vue里的nextTick
任务队列执行顺序:
- 执行第一个宏任务
- 执行过程中遇到异步操作,分别放入宏任务/微任务队列
- 宏任务执行完毕后,执行所有的微任务
- 微任务执行完毕后,取出一个新的宏任务执行,循环往复
PS:微任务的优先级高于下一个宏任务,也就是说,如果在微任务任务中又出现了微任务,那么该微任务会直接放在微任务队列,本次执行也会执行这个微任务
值得注意的是,执行同步代码的时候,也算是一次宏任务:
javascript
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务
Promise.resolve().then(() => {
console.log('3'); // 宏任务里的微任务
});
}, 0);
Promise.resolve().then(() => {
console.log('4'); // 微任务
});
console.log('5'); // 同步
最终打印结果:1, 5, 4, 2, 3
javascript
async function test() {
console.log('a');
await console.log('b'); // 这里也是同步执行的
console.log('c');
}
test();
console.log('d');
// 输出顺序:a -> b -> d -> c
await之前的代码是同步的,await之后的代码全部被塞进Promise.then 微任务里,await语句也算同步的
具体的事件循环可以参考另一篇文章:
Vue中nextTick()的理解_vue nexttick-CSDN博客
Vue篇
6.Vue3 的生命周期
在Vue3的组合式API(使用setup语法糖的语法)中,选项式API中的beforeCreate()、created()被setup()取代,具体生命周期如下表所示:
| 阶段 | setup语法糖 | 描述 |
|---|---|---|
| 创建 | setup() | Vue实例初始化完成,数据观测已就绪 |
| 挂载 | onBeforeMount | 模板编译完成,准备挂载DOM |
| 挂载 | onMounted | DOM已挂载(最常用) |
| 更新 | onBeforeUpdate | 数据变了,DOM还没变 |
| 更新 | onUpdated | DOM已经更新完毕 |
| 卸载 | onBeforeMount | 组件卸载前(清理定时器 / 事件监听) |
| 卸载 | onMounted | 组件已卸载 |
综上所述,即分为四个阶段:"创建 " -> "挂载 " -> "更新 " -> "卸载"
值得注意的是,创建阶段的生命周期代码即setup()内你编写的代码
7. v-if和v-show的区别
v-if 和v-show的区别在于性能不同:
| 特性 | v-if | v-show |
|---|---|---|
| 原理 | 真正的条件渲染,DOM元素会被销毁和重建 | CSS切换,DOM元素始终存在,只是切换display:none |
| 初始渲染开销 | 低,初始条件为假就不渲染 | 高,无论条件真假,都会渲染DOM |
| 切换开销 | 高,设计DOM的插入和删除 | 低,只修改CSS属性 |
| 适用场景 | 条件很少改变,或者初始就不展示 | 需要频繁切换显示/隐藏(Tab页、弹窗、开关) |
注意:v-if和v-for不推荐在同一个元素上使用,原因有两点:
1.若v-if的条件依赖于v-for的变量,由于v-if的优先级高于v-for,此时会直接报错,一个代码示例如下:
html
<ul>
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
</ul>
编译之后的伪代码:
javascript
if (user.isActive) { // 报错!此时 user 还是 undefined
this.users.map(function (user) { ... })
}
2.当v-if的条件不依赖于v-for的变量时,虽然此时将v-for和v-if应用于同一个元素上,虽然没有性能损耗,但仍然不建议这样做,因为会导致逻辑魂落,代码不好阅读。
8.Vue 组件通信有哪些方式
父传子:
父组件绑定属性,子组件使用defineProps接收
子传父:
子组件使用defineEmits声明事件
双向绑定:
使用预编译:父组件使用v-model绑定,子组件使用defineModel声明双向绑定变量
不使用预编译:子组件接收modelValu prop,再手动触发update:modelValue事件
跨层级/依赖注入:
祖先组件provie('key', value),后代组件inject('key'),适合深层嵌套插件或组件库开发
全局状态管理:
使用Pinia状态库管理
引用:
父组件使用ref获取子组件实例,子组件使用defineExpose暴露方法和变量
透传:
父组件传递的非Props属性,如class,style,id会自动透视给子组件的根元素
9.nextTick
Vue3中的DOM更新设计非常巧妙,当你修改一个ref响应式变量后,DOM的更新操作会被塞入到事件循环的"微任务"中,原因在于Vue中的数据更新非常频繁,如果频繁更新DOM会很卡,所以Vue只会记录多次修改DOM的最后一次,并在此时将DOM更新操作塞入到微任务中
nextTick是一个全局API,它接受一个回调函数,在下一次DOM更新循环结束之后延迟回调(即把nextTick内的回调塞入微任务)
nextTick的使用场景:
当你修改了ref后,DOM此时还没有更新,但是你立马获取DOM元素,此时还是旧的值,所以需要用到nextTick
更多nextTick讲解可以参考另一篇文章:
Vue中nextTick()的理解_vue nexttick-CSDN博客
手写代码篇
1.防抖函数
java
function debounce(func, delay) {
let timer = null;
const debounced = function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
}, delay)
}
return debounced;
}
2.节流函数
javascript
function throttle(func, delay) {
let timer = null;
const throttled = function(...args) {
if (timer) {
return;
}
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
}
return throttled;
}
3.new关键字实现
javascript
function createNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype);
const res = Constructor.apply(obj, args);
return res instanceof Object ? res : obj;
}