前端面试基础知识整理【Day-1】

目录

前言

1.数据类型与堆栈内存

[2. 闭包](#2. 闭包)

[3. 原型与原型链](#3. 原型与原型链)

4.this指向问题

[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指向取决于函数在哪里被调用,而不是函数在哪里定义

普通函数:

  1. 全局环境:指向window(非严格模式)或Global(Node.js环境下)或undefined(严格模式)
  2. 对象调用:谁调用指向谁
  3. 构造函数:指向new出来的新实例
  4. 显式绑定:call、apply、bind手动改变this指向

箭头函数:

  1. 没有自己的this,它会捕获定义时所处的上层作用域的this值
  2. 无法改变:call、apply对它无效
  3. 不能作为构造函数:没有this,无法new

具体的this指向可以参考另一篇文章:

【二】JavaScript能力提升---this对象_this撖寡情-CSDN博客

5. 事件循环

JS是单线程的,它通过循环处理异步操作

异步操作会进入一个任务队列中,任务队列分为:"宏任务"和"微任务"

  1. 宏任务:setTimeout、setInterval、I/O操作、UI刷新
  2. 微任务:Promise.then/catch/finally、vue里的nextTick

任务队列执行顺序:

  1. 执行第一个宏任务
  2. 执行过程中遇到异步操作,分别放入宏任务/微任务队列
  3. 宏任务执行完毕后,执行所有的微任务
  4. 微任务执行完毕后,取出一个新的宏任务执行,循环往复

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-ifv-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;
}
相关推荐
mCell5 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell6 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭6 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清7 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木7 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076607 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声7 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易7 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得07 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion7 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计