当前位置: 首页 > news >正文

面试题小结(真实面试)

面试题

        • 1.call与apply的区别
        • 2.vue3的响应式原理
        • 3.js的垃圾回收机制
        • 4.说说原型链
        • 5.什么是防抖和节流
        • 6.说一下作用域链
        • 7.在一个页面加载数据时(还没加载完成),切换到另一个页面,怎么暂停之前页面的数据加载。
      • 浏览器自动中止机制

这些都是本人之前亲自遇到过的面试问题,本文是某次面试的记录

1.call与apply的区别

在 JavaScript 中,applybindcall 都是用来改变函数执行时的上下文,即改变函数运行时的 this 指向。它们的主要区别在于参数的传递方式和函数的执行时机。

call(this指向(原型对象),…arg)

function fn(...args) {console.log(this, args);
}
let obj = { myname: "张三" };
fn.call(obj, 1, 2); // this 指向 obj,参数以列表形式传入

apply(this指向(原型对象),[…arg])

function fn(...args) {console.log(this, args);
}
let obj = { myname: "张三" };
fn.apply(obj, [1, 2]); // this 指向 obj,参数以数组形式传入

bind与call类型,不同的是会返回一个改变this指向的新方法

function fn(...args) {console.log(this, args);
}
let obj = { myname: "张三" };
const bindFn = fn.bind(obj); // 返回一个新的函数,this 指向 obj
bindFn(1, 2); // 执行新函数,this 指向 obj
2.vue3的响应式原理

Vue3的响应式系统是基于ES6的ProxyReflect实现的,相较于Vue2使用的Object.defineProperty,Vue3在性能和功能上都有显著提升

在Vue2中,响应式系统是通过Object.defineProperty来实现的。它通过拦截对象属性的读取和设置操作来实现响应式。

function reactive(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
console.log(`访问了${key}属性`);
return value;
},
set(val) {
console.log(`${key}由->${value}->设置成->${val}`);
if (value !== val) {
value = val;
}
}
});
}const data = { name: '林三心', age: 22 };
Object.keys(data).forEach(key => reactive(data, key, data[key]));console.log(data.name); // 访问了name属性 // 林三心
data.name = 'sunshine_lin'; // 将name由->林三心->设置成->sunshine_lin
console.log(data.name); // 访问了name属性 // sunshine_lin

Object.defineProperty有一个显著的缺陷:它无法监听对象新增或删除的属性

Vue3通过Proxy来实现响应式,解决了Vue2的缺陷。Proxy可以拦截并重新定义基本操作(例如属性查找、赋值、枚举、函数调用等)

function reactive(target) {
const handler = {get(target, key, receiver) {console.log(`访问了${key}属性`);return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {console.log(`${key}由->${target[key]}->设置成->${value}`);return Reflect.set(target, key, value, receiver);}
};return new Proxy(target, handler);
}const data = { name: '林三心', age: 22 };
const proxyData = reactive(data);console.log(proxyData.name); // 访问了name属性 // 林三心
proxyData.name = 'sunshine_lin'; // 将name由->林三心->设置成->sunshine_lin
console.log(proxyData.name); // 访问了name属性 // sunshine_lin

Proxy不仅可以监听对象的新增和删除属性,还可以直接监听数组的变化

Vue3的响应式系统还包括依赖收集触发更新的机制。通过track函数收集依赖,trigger函数触发更新

const targetMap = new WeakMap();function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {targetMap.set(target, depsMap = new Map());
}
let dep = depsMap.get(key);
if (!dep) {depsMap.set(key, dep = new Set());
}
dep.add(activeEffect);
}function trigger(target, key) {
let depsMap = targetMap.get(target);
if (depsMap) {const dep = depsMap.get(key);if (dep) {dep.forEach(effect => effect());}
}
}function reactive(target) {
const handler = {get(target, key, receiver) {track(receiver, key);return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {Reflect.set(target, key, value, receiver);trigger(receiver, key);}
};
return new Proxy(target, handler);
}
3.js的垃圾回收机制

浏览器的 Javascript 具有自动垃圾回收机制(GCGarbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

对于寻找到那些没有使用的变量的方法,通常情况下有两种实现方式:标记清除引用计数

标记清除(主要)

这是JS最常用的垃圾回收方式。垃圾回收器会定期执行以下流程:

  1. 标记阶段:从根对象(全局对象、当前调用栈等)开始递归遍历,将所有可访问的对象标记为可达

  2. 清除阶段:回收所有未被标记的对象内存

以函数为例,在此函数内定义两个变量,如下

function demo(){const a = 'a';  // 标记可达const b = 1;    // 标记可达
}demo();  // 函数调用后,执行上下文消失

函数执行时:a 和 b 被调用栈引用,在GC标记阶段会被标记为可达
函数结束后:执行上下文销毁,a 和 b 失去所有引用
下一次GC运行时:

  1. 标记阶段:从根对象无法访问 a 和 b → 不被标记
  2. 清除阶段:回收内存

闭包情况

function ex() {const a = 'a';return () => console.log(a); // 闭包引用a
}const closure = ex(); // 保存返回的函数

函数执行结束后:变量 a 被返回的闭包函数引用,只要 closure 变量可达(如全局引用),aGC标记阶段就始终被标记为可达

总结

当上下文消失时,其中不再被任何可达对象引用的变量,会在下一次垃圾回收的标记阶段未被标记,随后在清除阶段被回收

注意点:

  1. 标记是周期性行为:不是声明时立即标记,而是在GC的标记阶段统一处理
  2. 没有"取消标记":变量因失去引用在下一次GC时不被标记,而非主动取消
  3. 闭包的关键是引用链:必须保持闭包函数本身可达,内部变量才会保留
  4. 回收非即时:从变量失去引用到实际回收可能有延迟(取决于GC运行时机)

引用计数法

引用计数的含义是跟踪记录每个值被引用的次数。
例如,定义一个变量a值为1,值1的引用次数为1,后面有b=a引用次数+1,c=a引用次数+1,b=''引用次数-1,如下:

const a = 1;      // 值`1`的引用计数 = 1(由`a`引用)
const b = a;      // 值`1`的引用计数 = 2(`a`和`b`都引用)
const c = a;      // 值`1`的引用计数 = 3(`a`, `b`, `c`)
b = '';           // `b`不再引用`1`,计数减1 → 变为2

当变量的最终引用次数为0时,则会被GC清除。
只有当a和c都不再引用1时,计数才会归零并被回收。

这种方法遇到循环引用就会出问题

function createObjects() {const objA = { name: 'A' }; // 引用计数 = 1const objB = { name: 'B' }; // 引用计数 = 1objA.ref = objB; // objB计数 = 2objB.ref = objA; // objA计数 = 2
}
createObjects(); // 函数执行结束

函数结束后:objA和objB的局部引用消失 → 计数各减1
但objA.ref和objB.ref互相引用 → 计数保持1
结果:两个对象永远不会被回收 → 内存泄漏
所以不会怎么使用这种方法。

4.说说原型链

js是基于原型生成对象的方式

所有对象都有它的原型对象

使用class创建一个Object类,Object本质是一个构造函数,可以使用 const obj = new Object(…)创建一个新的实例对象,而所有对象都有原型对象,所以obj._proto_ 会指向原型对象,构造函数的prototype也会指向同一个原型对象,他们基于同一个原型对象产生,如下图:

image-20240507170307699

这就是最常见的原型链,整个的原型链还包括函数对象以及其他方式创建的对象

原型链的实际作用

之所以要挂在原型对象上面,是因为由构造函数实例化出来的每一个实例对象,属性值是不相同的,所以需要每个对象独立有一份

但是对于方法而言,所有对象都是相同的,因此我们不需要每个对象拥有一份,直接挂在原型对象上面共用一份即可

function Computer(name, price) {
this.name = name;
this.price = price;
}
Computer.prototype.showPrice = function () {
console.log(`${this.name}的电脑价格为${this.price}`);
};const huawei = new Computer("华为", 5000);
const apple = new Computer("苹果", 8000);
5.什么是防抖和节流

防抖目的是防止事件过于频繁的触发,让函数如同电梯一样,在规定时间内有人按电梯就重新计时。常见场景于百度输入框,连续输入时设置一个定时时间,在此时间内继续输入就更新定时时间,直到超出定时时间调用搜索事件。

节流也是为了防止事件过于频繁的触发,不过思想不一样,它是让事件每隔一个固定时间触发一次。常见场景于窗口缩放时间,一般缩放窗口时会触发特别多的resize事件,节流就是定时每隔一段时间触发一次,以此优化性能。

6.说一下作用域链

作用域

作用域相当于地盘,有全局作用域,函数作用域,块级作用域,作用域可以很好的隔绝不同作用域的变量,外层作用域就访问不到内层作用域的内容。

作用域链

所有相邻的作用域会形成一条链路,就是作用域链,内层作用域可以沿着作用域链找到外层作用域的变量进行使用。

7.在一个页面加载数据时(还没加载完成),切换到另一个页面,怎么暂停之前页面的数据加载。

在单页应用(SPA)中切换页面时,暂停或取消之前页面的数据加载是优化性能和避免资源浪费的关键。以下是具体实现方案:

核心解决方案使用 AbortController

现代浏览器提供了 AbortController API,可优雅地取消网络请求。

import { useEffect, useState } from 'react';function PageA() {
const [data, setData] = useState(null);useEffect(() => {
// 1. 创建控制器
const abortController = new AbortController();// 2. 发起带取消信号的请求
fetch('https://api.example.com/data', {
signal: abortController.signal // 关键:绑定取消信号
})
.then(response => response.json())
.then(setData)
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求被取消'); // 正常取消不报错
} else {
console.error('请求出错', err);
}
});// 3. 组件卸载时取消请求
return () => abortController.abort();
}, []);return <div>{data ? data : 'Loading...'}</div>;
}// 页面切换时,React 自动触发清理函数取消请求

Vue的解决方法也是如此

<script setup>
import { ref, onBeforeUnmount } from 'vue';const data = ref(null);
let controller = null; // 存储 AbortControllerconst fetchData = async () => {
// 如果已有请求进行中,先取消
if (controller) controller.abort();controller = new AbortController();try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal
});
data.value = await response.json();
} catch (err) {
if (err.name !== 'AbortError') {
console.error('请求错误', err);
}
}
};// 组件挂载时获取数据
fetchData();// 组件卸载前取消请求
onBeforeUnmount(() => {
if (controller) controller.abort();
});
</script>

在非单页应用(传统多页应用)中,当用户切换页面时,浏览器会自动中止所有未完成的网络请求。这是浏览器内置行为,无需开发者手动处理。

浏览器自动中止机制

页面卸载时自动终止

  • 当用户点击链接/提交表单/刷新页面时
  • 浏览器触发 beforeunloadunload 事件
  • 所有进行中的网络请求会被自动取消
  • 包括:XHR、Fetch、图片加载、CSS/JS文件下载等
http://www.xdnf.cn/news/874891.html

相关文章:

  • Java编程常见错误与最佳实践
  • machine_env_loader must have been assigned before creating ssh child instance
  • hadoop集群启动没有datanode解决
  • PyCharm项目和文件运行时使用conda环境的教程
  • Python趣学篇:用数学方程绘制浪漫爱心
  • SpringBoot+Mybatisplus配置多数据源(超级简单!!!!)
  • #Java篇:学习node后端之sql常用操作
  • BBU 电源市场报告:深入剖析与未来展望​
  • 洛谷P1591阶乘数码
  • GO语言---函数命名返回值
  • 嵌入式系统中常用的开源协议
  • 41、响应处理-【源码分析】-自定义MessageConverter
  • [C]深入解析条件式日志宏的设计原理
  • Deepfashion2 数据集使用笔记
  • 2025年五一数学建模竞赛A题-支路车流量推测问题详细建模与源代码编写(一)
  • 洛谷 单源最短路径 Dijkstra算法+优先队列
  • 点云数据去噪(Point Cloud Processing Toolbox)
  • C++——智能指针 shared_ptr
  • 小黑黑日常积累:dataclass的简单使用
  • AtCoder解析大全
  • 在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
  • 基于 qiankun + vite + vue3 构建微前端应用实践
  • 长参考帧LTR
  • 前端八股之JS的原型链
  • 20-项目部署(Docker)
  • 【人工智能】大模型的创造力:从训练到应用的灵感火花
  • 如何配置deepseek + ida-pro-mcp
  • 让AI看见世界:MCP协议与服务器的工作原理
  • [AI Claude] 软件测试2
  • JS利用原型链实现继承