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

作业帮前端面试(准备)

手撕

原地修改链表

在这里插入图片描述

// 定义链表节点类
class ListNode {constructor(val, next = null) {this.val = val;this.next = next;}
}/*** 重排链表函数* @param {ListNode} head 链表头节点* @return {void} 不返回任何值,直接修改原链表*/
const reorderList = function(head) {// 处理空链表或只有一个节点的情况,无需调整if (!head || !head.next) return;// 步骤1:使用快慢指针找到链表的中间节点let slow = head;let fast = head;while (fast.next && fast.next.next) {slow = slow.next;fast = fast.next.next;}// 步骤2:反转链表的后半部分let prev = null;let curr = slow.next;slow.next = null; // 将链表分为前后两部分while (curr) {let nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}// 此时 prev 是反转后后半部分链表的头节点// 步骤3:合并前半部分链表和反转后的后半部分链表let first = head;let second = prev;while (second) {let temp1 = first.next; // 保存 first 的下一个节点let temp2 = second.next; // 保存 second 的下一个节点first.next = second; // first 指向 secondsecond.next = temp1; // second 指向 first 原来的下一个节点first = temp1; // first 移动至原下一个节点second = temp2; // second 移动至原下一个节点}let first=head;let second=prev;while(second){let temp1=first.next;let temp2=second.next;first.next=second;second.next=temp1;first=temp1;second=temp2;}
};// 辅助函数:根据数组创建链表
function createList(arr) {if (arr.length === 0) return null;let head = new ListNode(arr[0]);let current = head;for (let i = 1; i < arr.length; i++) {current.next = new ListNode(arr[i]);current = current.next;}return head;
}// 辅助函数:将链表转换为数组(用于输出)
function listToArray(head) {let arr = [];let current = head;while (current) {arr.push(current.val);current = current.next;}return arr;
}// 测试示例
// 示例1: 链表 0->1->2->3
let head1 = createList([0, 1, 2, 3]);
reorderList(head1);
console.log(listToArray(head1)); // 输出: [0, 3, 1, 2]// 示例2: 链表 0->1->2->3->4
let head2 = createList([0, 1, 2, 3, 4]);
reorderList(head2);
console.log(listToArray(head2)); // 输出: [0, 4, 1, 3, 2]// 示例3: 链表 0->1->2->3->4->5
let head3 = createList([0, 1, 2, 3, 4, 5]);
reorderList(head3);
console.log(listToArray(head3)); // 输出: [0, 5, 1, 4, 2, 3]

串行执行Promise

在这里插入图片描述

  • 1.async/await
async function runPromiseInSeries(promiseArray){const results=[];for(const promiseFun of promiseArray){try{const result=await promiseFun();results.push(result);}catch(err){console.log("promise执行失败",err);throw err;}}return results;
}const promiseFunctions=[()=>new Promise(resolve=>setTimeout(console.log('Promise 1');resolve('Result1'),1000)),()=>new Promise(resolve=>setTimeout(console.log('Promise 2');resolve('Result2'),1000))
];runPromisesInSeries(promiseFunctions).then(finalResults => console.log('全部完成:', finalResults)).catch(error => console.error('链中发生错误:', error));
    1. Array.reduce()
function runPromiseInSeriesWithReduce(promiseArray){return promiseArray.reduce((promiseChain,currentPromiseFunc)=>{return promiseChain.then(chianResult=>{return [...chainResults,currentResult];})},Promise.resolve([])) 
}// 示例用法
const promiseFunctions = [() => new Promise(resolve => setTimeout(() => { console.log('Promise 1'); resolve('Result 1'); }, 1000)),() => new Promise(resolve => setTimeout(() => { console.log('Promise 2'); resolve('Result 2'); }, 500)),() => new Promise(resolve => setTimeout(() => { console.log('Promise 3'); resolve('Result 3'); }, 800))
];runPromisesInSeriesWithReduce(promiseFunctions).then(finalResults => console.log('全部完成:', finalResults)).catch(error => console.error('链中发生错误:', error));
    1. 递归
function runPromiseInSeriesRecusively(promiseArray,index=0,results=[]){if(index>=promiseArray.length){return Promise.resolve(results);}return promiseArray[index]().then(currentResult=>{results.push(currentResult);return runPromiseInSeriesRecursively(promiseArray,index=1,results);})
}

千位格式化

/* 
1)将数字转换为字符串,以便使用字符串方法进行处理。
2)使用正则表达式匹配字符串中的位置,在每三个数字前插入一个逗号。
3)返回格式化后的字符串。 
*/
function formatNumberWithCommasCustom(number) {  // 将数字转换为字符串,并【去掉可能的小数点!】let str = Math.floor(number).toString();  // 初始化结果字符串和一个计数器  let result = '';  let count = 0;  // 从字符串的【最后一个】字符开始遍历for (let i = str.length - 1; i >= 0; i--) {  // 将当前字符添加到结果字符串的前面  result = str[i] + result;  // 每添加一个字符,计数器加1  count++;  // 如果计数器达到3(意味着已经添加了3个字符),则插入一个逗号,并重置计数器  if (count === 3 && i !== 0) {  result = ',' + result;  count = 0;  }  }  // 如果原始数字有小数部分,则将其添加到结果字符串的后面  if (number % 1 !== 0) {  result += '.' + (number - Math.floor(number)).toFixed(2).slice(2); // 保留两位小数  }  return result;  
}  

找出不在指定区间内的数字

在这里插入图片描述

/*** 找出所有不在任何已使用区间内的数字* @param {number[][]} usedRanges - 二维数组,每个内层数组表示一个区间 [start, end](包含两端)* @param {number[]} checkNums - 需要检查的数字数组* @returns {number[]} - 所有不在任何 usedRanges 区间内的数字组成的数组*/
function findUnusedNumbers(usedRanges, checkNums) {// 如果 usedRanges 或 checkNums 为空,直接返回 checkNums 的副本if (usedRanges.length === 0) {return [...checkNums];}if (checkNums.length === 0) {return [];}const result = []; // 存储最终结果的数组// 遍历需要检查的每一个数字for (const num of checkNums) {let isUsed = false; // 标记当前数字是否落在任何一个区间内// 遍历所有已使用的区间for (const range of usedRanges) {const [start, end] = range; // 解构赋值,获取当前区间的起始和结束值// 判断当前数字 num 是否在当前区间 [start, end] 内(包含边界)if (num >= start && num <= end) {isUsed = true; // 如果在区间内,标记为已使用break; // 已经找到一个区间包含它,无需检查剩余区间,跳出内层循环}}// 如果遍历完所有区间,isUsed 仍为 false,说明该数字不在任何区间内if (!isUsed) {result.push(num); // 将其加入结果数组}}return result;
}// --- 示例测试 ---
const used = [[1, 20], [23, 40]];
const checknum = [-20, 80];console.log(findUnusedNumbers(used, checknum)); // 输出: [-20, 80]
// 解释:-20 小于1,80大于40,都不在[1,20]和[23,40]区间内。// 再测试一个更复杂的例子
const used2 = [[5, 10], [15, 25], [30, 35]];
const checknum2 = [3, 8, 12, 20, 28, 40];
console.log(findUnusedNumbers(used2, checknum2)); // 输出: [3, 12, 28, 40]
// 解释:
// 3: 小于5 -> 未被使用
// 8: 在 [5,10] 内 -> 被使用,跳过
// 12: 在5-10和15-25之间 -> 未被使用
// 20: 在 [15,25] 内 -> 被使用,跳过
// 28: 在15-25和30-35之间 -> 未被使用
// 40: 大于35 -> 未被使用

排序算法

在这里插入图片描述

  • 快速排序​​的思路
    在这里插入图片描述
function quickSort(arr,low,high){if(low>=high)return;const pivot=arr[high];let left=low;//i=lowfor(let i=low;i<high;i++){//pivotif(arr[i]<=pivot){[arr[i],arr[left]]=[arr[left],arr[i]];left++}}[arr[left],arr[high]]=[arr[high],arr[left]];quickSort(arr,low,left-1);quickSort(arr,left+1,high);
}
  • 归并排序​​的​​稳定性​​很重要
    在这里插入图片描述
function mergeSort(arr){if(arr.length<=1)return arr;const mid=Math.floor(arr.length/2);const left=arr.slice(0,mid);const right=arr.slice(mid);const merge=(left,right)=>{//const result=[];//while(left.length>0&&right.length>0){//		if(left[0]<right[0]){//				result.push(left.shift());//		}else{//				result.push(right.shift());//		}//}//return result.concat(left,right);let result=[];let leftIndex=0;let rightIndex=0;while(leftIndex<left.length&&rightIndex<rightIndex){if(left[leftIndex]<right[rightIndex]){result.push(left[leftIndex]);leftIndex++;}else{result.push(right[rightIndex];rightIndex++;}}return result.concat(left.slice(leftIndex),right.slice(rightIndex));}//返回return merge(left,right);
}

在这里插入图片描述


🟢 Vue2 与 Vue3 的核心区别

1. 响应式系统重构

Vue3 使用 Proxy 替代了 Vue2 中的 Object.defineProperty 来实现响应式数据。

  • Vue2 (Object.defineProperty): 只能拦截对象已有属性的读取和写入,对新增属性、数组索引修改及Map, Set等数据结构支持不足,需借助Vue.setVue.delete
  • Vue3 (Proxy): 代理整个对象,可监听各种操作(包括属性增删、数组索引变化、Map/Set操作等),提供了更全面的响应式能力。

2. 组合式 API (Composition API) vs 选项式 API (Options API)

Vue3 引入了 Composition API,提供了比 Vue2 的 Options API 更灵活的组织逻辑的方式。

  • Options API (Vue2): 将代码按选项组织,如 data, methods, computed, watch, 生命周期钩子。不利于逻辑复用,大型组件易变得臃肿且逻辑关注点分散。
  • Composition API (Vue3): 允许在 setup (或 <script setup>) 中按功能逻辑组织代码,相关响应式数据、计算属性、方法和生命周期钩子可以放在一起,极大改善了代码的组织性和可复用性。

3. 性能优化

Vue3 在性能方面有多项改进:

  • Tree-shaking 支持更优: Vue3 的全局 API 和组件 API 都设计为可 tree-shaking 的,未使用的功能不会打包到最终产物中。
  • 虚拟 DOM 重写: 优化了 diff 算法,引入了静态提升、事件缓存等编译时优化,减少了运行时开销。
  • 更小的体积: 尽管增加了许多新特性,但 Vue3 的整体体积比 Vue2 更小。

4. 片段 (Fragments)

  • Vue2: 组件模板必须有一个根元素。
  • Vue3: 组件模板支持多个根元素(Fragment)。

5. 生命周期钩子变化

Vue3 的生命周期钩子名称有变化,并移除了 beforeCreatecreated(因为在 setup 中,它们的行为已被涵盖)。

Vue2 Options APIVue3 Composition API (inside setup)
beforeCreateNot needed (use setup instead)
createdNot needed (use setup instead)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
errorCapturedonErrorCaptured

6. TypeScript 支持

Vue3 对 TypeScript 的支持更加友好,提供了更好的类型推断。

7. 组件注册:为何 Vue3 中组件无需显式注册?

在使用 <script setup> 的单文件组件中,导入的组件模板中可直接使用,无需通过 components 选项显式注册。这是因为 <script setup> 是一种编译时语法糖,编译器会自动识别导入的组件并使其在模板中可用,极大地简化了代码。
在这里插入图片描述

🟢 Vue3 组件通信

1. 父子组件通信

父传子 (Props)

父组件通过属性绑定传递数据,子组件通过 defineProps 接收。

<!-- ParentComponent.vue -->
<template><ChildComponent :message="parentMessage" :count="count" />
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const parentMessage = ref('Hello from Parent!');
const count = ref(0);
</script>
<!-- ChildComponent.vue -->
<template><div>{{ message }} - {{ count }}</div>
</template><script setup>
// defineProps 是一个编译宏,无需导入
const props = defineProps({message: {type: String,required: true},count: Number
});
</script>

子传父 (自定义事件)

子组件通过 defineEmits 定义事件,然后通过 emit 触发。父组件监听该事件。

<!-- ChildComponent.vue -->
<template><button @click="sendMessage">Click Me</button>
</template><script setup>
const emit = defineEmits(['messageSent']);const sendMessage = () => {emit('messageSent', 'Data from child!');
};
</script>
<!-- ParentComponent.vue -->
<template><ChildComponent @message-sent="handleMessage" /><p>Received: {{ childMessage }}</p>
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const childMessage = ref('');const handleMessage = (msg) => {childMessage.value = msg;
};
</script>

2. 兄弟组件通信

兄弟组件通信通常需要通过一个共同的父组件(“状态提升”)或使用全局事件总线/状态管理库。

通过共同的父组件中转

父组件管理状态,通过 props 传递给一个子组件,并监听另一个子组件的事件来更新状态。

<!-- ParentComponent.vue -->
<template><ChildA :value="sharedValue" @update="updateValue" /><ChildB :value="sharedValue" />
</template><script setup>
import { ref } from 'vue';
import ChildA from './ChildA.vue';
import ChildB from './ChildB.vue';const sharedValue = ref('');const updateValue = (newValue) => {sharedValue.value = newValue;
};
</script>

使用 Mitt 等事件总线库

Mitt 是一个小巧的发布订阅库,可用于组件间事件通信。

  1. 创建事件总线 (eventBus.js):

    // eventBus.js
    import mitt from 'mitt';
    const emitter = mitt();
    export default emitter;
    
  2. 兄弟组件 A (发送事件):

    <!-- ComponentA.vue -->
    <template><button @click="sendData">Send to B</button>
    </template><script setup>
    import emitter from '../eventBus.js';const sendData = () => {emitter.emit('data-from-A', { message: 'Hello from A!' });
    };
    </script>
    
  3. 兄弟组件 B (接收事件):

    <!-- ComponentB.vue -->
    <template><div>Received: {{ receivedData }}</div>
    </template><script setup>
    import { ref, onMounted, onUnmounted } from 'vue';
    import emitter from '../eventBus.js';const receivedData = ref('');const handleData = (data) => {receivedData.value = data.message;
    };onMounted(() => {emitter.on('data-from-A', handleData);
    });onUnmounted(() => {emitter.off('data-from-A', handleData); // 记得移除监听以防内存泄漏
    });
    </script>
    

3. 跨层级组件通信 (provide/inject)

provideinject 可以实现深层嵌套组件之间的数据传递,无需通过层层 props。

<!-- AncestorComponent.vue -->
<template><ChildComponent />
</template><script setup>
import { provide, ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const theme = ref('dark');provide('app-theme', theme); // 提供键值 'app-theme'
</script>
<!-- DeepDescendantComponent.vue (任何后代组件) -->
<template><div :class="theme">Themed content</div>
</template><script setup>
import { inject } from 'vue';// 注入祖先提供的 'app-theme',并提供默认值 'light'
const theme = inject('app-theme', 'light');
</script>

🟢 Vuex 与 Pinia 的区别与用法

Pinia 是 Vue 官方推荐的新一代状态管理库,可看作是 Vuex 的进化版。

核心区别

特性VuexPinia
理念更强调规范性,适合大型严格项目更灵活轻量,API 设计更简洁直观
API 风格Options APIComposition API
核心概念state, getters, mutations (同步), actions (异步)state, getters, actions (可同步也可异步) 移除了 mutations
TypeScript 支持支持,但需要一些配置原生支持优秀,类型推断好
模块化通过 modules 划分,需设置 namespaced: true每个 store 天然是模块化的,通过不同文件定义多个 store
使用方式在组件中通过 this.$storemapState/mapGetters/mapActions 辅助函数在组件中直接导入并使用定义的 store 函数

具体用法

Vuex

  1. 创建 Store:

    // store/index.js
    import { createStore } from 'vuex';export default createStore({state: {count: 0},mutations: { // 同步修改状态increment(state) {state.count++;},setCount(state, value) {state.count = value;}},actions: { // 异步操作,提交 mutationincrementAsync({ commit }) {setTimeout(() => {commit('increment');}, 1000);}},getters: { // 计算属性doubleCount(state) {return state.count * 2;}}
    });
    
  2. 在组件中使用:

    <template><div>{{ count }}</div><div>{{ doubleCount }}</div><button @click="increment">Increment</button><button @click="incrementAsync">Increment Async</button>
    </template><script>
    import { mapState, mapGetters, mapActions } from 'vuex';export default {computed: {...mapState(['count']),...mapGetters(['doubleCount'])},methods: {...mapActions(['incrementAsync']),increment() {this.$store.commit('increment'); // 直接提交 mutation}}
    };
    </script>
    

Pinia

  1. 创建 Store:

    // stores/counter.js
    import { defineStore } from 'pinia';// 'counter' 是 store 的唯一 ID
    export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),actions: { // 可同步也可异步increment() {this.count++;},async incrementAsync() {setTimeout(() => {this.increment();}, 1000);}},getters: {doubleCount: (state) => state.count * 2}
    });
    
  2. 在组件中使用:

    <template><div>{{ counterStore.count }}</div><div>{{ counterStore.doubleCount }}</div><button @click="counterStore.increment()">Increment</button><button @click="counterStore.incrementAsync()">Increment Async</button><!-- 或者使用解构保持响应性 --><div>{{ count }}</div><button @click="increment">Increment</button>
    </template><script setup>
    import { useCounterStore } from '@/stores/counter';
    import { storeToRefs } from 'pinia'; // 用于解构保持响应性const counterStore = useCounterStore();// 直接修改 state (Pinia 也允许)
    // counterStore.count++;// 如果需要解构,使用 storeToRefs 保持响应性
    const { count } = storeToRefs(counterStore);
    const { increment } = counterStore; // 解构 action
    </script>
    

总结建议:对于新项目,尤其是 Vue3 项目,优先推荐使用 Pinia。它更简单,类型支持更好,去除了 Vuex 中一些繁琐的概念(如 mutations)。

🟢 CSS 选择器权重

CSS 选择器的权重决定了当多条规则应用于同一元素时,哪条规则会生效。

权重由四个分量组成,通常表示为 (a, b, c, d)0,0,0,0

  • a (千位): 内联样式 (style attribute) - 权重 1,0,0,0
  • b (百位): ID 选择器 - 权重 0,1,0,0
  • c (十位): 类选择器 (class)、属性选择器 ([type=“text”])、伪类 (:hover) - 权重 0,0,1,0
  • d (个位): 元素选择器 (div)、伪元素 (::before) - 权重 0,0,0,1
  • 通配符*组合器>+~:where() 权重为 0,0,0,0,不影响 specificity。
  • !important 是最高优先级,但强烈建议谨慎使用。

比较规则: 从 a 到 d 依次比较,权重高的样式生效。注意:权重不进位,1000个类选择器(c=1000)的权重也低于1个ID选择器(b=1, c=0)。

权重示例表

选择器示例权重分量具体权重值 (a,b,c,d)
style="..." (内联样式)a=11,0,0,0
#headerb=10,1,0,0
#header #nav (2个ID)b=20,2,0,0
.menu .item (2个类)c=20,0,2,0
ul li a (3个元素)d=30,0,0,3
button.primary (1元素1类)c=1, d=10,0,1,1
#submit-btn.active (1ID1类)b=1, c=10,1,1,0
* (通配符)00,0,0,0

应用案例

假设HTML为:<button id="submit-btn" class="btn primary" style="color: red;">Submit</button>

button { color: black; }        /* 权重: 0,0,0,1 -> 0,0,0,1 */
.btn { color: blue; }           /* 权重: 0,0,1,0 -> 0,0,1,0 */
#submit-btn { color: green; }   /* 权重: 0,1,0,0 -> 0,1,0,0 */
.primary { color: yellow; }     /* 权重: 0,0,1,0 -> 但后声明的相同权重规则可能被覆盖 */

最终生效的是 style="color: red;" (权重 1,0,0,0) 或 #submit-btn { color: green; } (权重 0,1,0,0),内联样式权重更高。如果没有内联样式,则 #submit-btn 的绿色生效。

🟢 三个 Span 标签垂直居中

让行内元素如 <span> 垂直居中,需要根据其父容器的布局方式选择方法。

方法 1:Flexbox 布局 (推荐)

Flexbox 是现代布局的首选方式,非常简单可靠。

<div class="container"><span>Span 1</span><span>Span 2</span><span>Span 3</span>
</div>
.container {display: flex;align-items: center; /* 垂直居中 */justify-content: center; /* 水平居中 (如果需要) */height: 200px; /* 必须给容器一个高度 */border: 1px solid #ccc;
}

align-items: center 会使 flex 容器内的所有项目(包括 span)在交叉轴上(默认是垂直方向)居中。

方法 2:Grid 布局

Grid 布局同样能轻松实现居中。

.container {display: grid;place-items: center; /* 同时实现水平和垂直居中 */height: 200px;border: 1px solid #ccc;
}

place-itemsalign-items (垂直) 和 justify-items (水平) 的简写。

方法 3:行高 (Line-height) - 适用于单行文本

如果容器高度固定且内容只有一行文本,可以设置 line-height 等于容器高度。

.container {height: 200px;border: 1px solid #ccc;
}
.container span {line-height: 200px; /* 关键:与容器高度相同 */
}

注意: 此方法要求 span 内容不换行,且容器内无其他影响行高的元素。

重复请求控制

在这里插入图片描述


低代码

在这里插入图片描述
前端开发中经常会遇到一些特定的技术和问题,下面我将为你梳理这些知识点,希望能帮助你更好地理解和应对。

🟢 HTTP OPTIONS 请求

1. 触发时机

OPTIONS 请求主要用于“预检”,即在发送某些可能涉及安全风险的请求之前,先询问服务器是否允许。浏览器会自动处理这个过程,以下情况会触发:

  • 跨域请求且非简单请求:这是最常见的触发场景。浏览器会先发送 OPTIONS 请求进行“预检”。
    • 简单请求需同时满足:方法是 GET、HEAD 或 POST;Content-Type 是 text/plainmultipart/form-dataapplication/x-www-form-urlencoded 之一;没有使用自定义头部。
    • 不符合上述条件的即为非简单请求,例如:
      • 使用了 PUT、DELETE 等方法。
      • 设置了 自定义头部(如 AuthorizationX-Custom-Header)。
      • Content-Typeapplication/json
  • 显式查询服务器能力:开发者可以主动发送 OPTIONS 请求,查询服务器对某个资源支持哪些 HTTP 方法(响应头中的 Allow: GET, POST, OPTIONS)。

2. 预检流程

其预检机制是为了保障安全,具体流程可参考下图:

允许
拒绝
浏览器发起非简单跨域请求
自动发送OPTIONS预检请求
服务器检查请求头
Access-Control-Request-Method
Access-Control-Request-Headers
服务器返回CORS响应头
浏览器检查响应头
是否符合实际请求要求
发送实际请求
阻塞实际请求
并在控制台报错

3. 优化建议

频繁的预检请求可能影响性能,可通过设置 Access-Control-Max-Age 头部来指定预检响应的缓存时间(单位秒),在此时间内,同一请求无需再次预检。

🟢 1px 边框问题

1. 问题根源

这个问题源于 CSS 像素设备物理像素之间的差异。在 DPR(设备像素比) 大于 1 的高清屏(如 Retina 屏)上,1个 CSS 像素会由多个物理像素来渲染。例如 DPR=2 时,1px 在屏幕上实际占据了 2x2 的物理像素,导致视觉上变粗。

2. 解决方案对比

解决方案核心原理优点缺点适用场景
伪元素 + Transform用伪元素生成边框,通过 scale 缩放至所需粗细兼容性好,控制灵活,支持圆角需额外元素或伪元素,代码稍多各类边框,尤其是带圆角的边框
动态 Viewport通过 JS 动态缩放视口,使 CSS 像素与物理像素 1:1 对应一劳永逸,无需为每个边框单独处理会影响页面所有布局,需使用 REM 等单位适配全新项目
0.5px (媒体查询)针对高 DPR 设备直接设置 border: 0.5px代码简单安卓兼容性差,仅 iOS 8+ 支持仅需适配 iOS 的场景
Border-Image使用图片模拟细边框可实现复杂边框样式修改颜色不便,不支持圆角特殊样式的边框
SVG使用 SVG 矢量图绘制边框显示精准,不受 DPR 影响需要熟悉 SVG高保真 UI,复杂边框

3. 常用方案代码示例

伪元素 + Transform (推荐)
这是最常用且兼容性较好的方案,利用伪元素和 CSS Transform 进行缩放。

/* 下边框 */
.scale-border {position: relative;border: none;
}.scale-border::after {content: "";position: absolute;left: 0;bottom: 0;width: 100%;height: 1px; /* 创建原始边框 */background-color: #000;transform: scaleY(0.5); /* Y轴缩放至0.5倍 */transform-origin: 0 0; /* 设置缩放原点 */
}/* 适配不同DPR */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {.scale-border::after {transform: scaleY(0.5);}
}@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) {.scale-border::after {transform: scaleY(0.333);}
}

动态 Viewport (适用于新项目)
此方案通过 JavaScript 动态调整 viewport 的缩放比例,强制让 CSS 像素与物理像素等值。

<!-- HTML 中的 viewport 标签 -->
<meta name="viewport" id="viewportMeta" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
// JavaScript 动态调整
const dpr = window.devicePixelRatio || 1;
const scale = 1 / dpr; // 计算缩放比例const metaEl = document.querySelector('meta[name="viewport"]');
metaEl.setAttribute('content', `width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`);// 通常还需配合 REM 布局,根据缩放后的视口宽度动态设置根字体大小
document.documentElement.style.fontSize = `${100 * (window.innerWidth / 750)}px`; // 以750px设计稿为例

🟢 资源预加载 (Preload)

1. 基本原理

Preload 是一种 资源提示,通过 <link rel="preload"> 告诉浏览器提前获取并缓存某个重要资源(如字体、关键 CSS/JS、图片等)。

<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero-image.webp" as="image">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
  • as 属性:指定资源类型,帮助浏览器设置正确的优先级和策略。
  • crossorigin:加载字体等 CORS 资源时必须设置,即使同源。

2. 为何不阻塞渲染

Preload 的关键特性在于其异步非阻塞的加载方式:

  • 优先级分配:浏览器会根据 as 类型为预加载资源分配高优先级,但不会阻塞 HTML 解析和页面渲染。
  • 分离加载与执行:Preload 只加载并缓存资源,并不立即执行(如JS代码、CSS应用)。执行时机仍由资源在文档中的位置或代码逻辑决定。
  • 优化用户体验:通过提前加载关键资源,浏览器能更快地获取它们,从而减少后续渲染过程中的等待时间,提升页面加载性能,而不阻塞当前页面的渲染。

🟢 React Suspense 原理

1. 核心机制

Suspense 的核心是让组件能够“等待”某些异步操作(如代码加载、数据获取)完成,在等待期间显示一个降级 UI(如 loading 状态)。

其底层原理依赖于 “抛出 Promise” 的机制:

  • 挂起(Suspend):当一个异步操作(如 React.lazy() 动态导入组件或自定义异步数据获取)正在进行时,相关的 React 组件会抛出一个 Promise 对象,而不是正常渲染。这不是真正的 JS 错误,而是 React 的一种特殊通信机制。
  • 捕获与处理(Catch):上层的 <Suspense> 边界 会捕获这个被抛出的 Promise。
  • 显示降级 UI<Suspense> 会立即渲染其 fallback 属性指定的内容(如一个旋转的加载器)。
  • 解决与恢复(Resolve):当抛出的 Promise 被解决(resolve)后,React 会自动重新尝试渲染之前被挂起的组件树。此时异步操作已完成,组件便能成功渲染并显示最终内容。

2. 与并发模式(Concurrent Mode)的结合

在 React 的并发模式下,Suspense 的能力得到增强:

  • 可中断渲染与优先级调度:高优先级的更新可以中断正在进行的、较低优先级的异步渲染(如一个已部分渲染的懒组件),确保用户交互能得到及时响应。
  • 流畅的过渡体验:使用 startTransitionuseTransition 钩子,可以告诉 React 某个切换(如路由选项卡)是“过渡性”的,从而在准备新内容时保持旧 UI 的交互性,并优雅地显示加载状态,避免隐藏当前内容直到新内容加载完成带来的突兀感。

基础

知识领域核心概念/问题关键原理/机制关联技术/解决方案
网络协议TCP协议面向连接、可靠传输、流量控制、拥塞控制三次握手、四次挥手、滑动窗口、慢启动
HTTPS协议HTTP + SSL/TLS,加密传输、身份认证非对称加密交换会话密钥、对称加密通信内容、数字证书
七层网络模型 (OSI模型)物理层、数据链路层、网络层、传输层、会话层、表示层、应用层TCP/IP协议族(对应网络层、传输层、应用层等)
浏览器渲染与缓存页面从点击到显示的过程DNS解析 → TCP连接 → HTTP请求 → 服务器处理 → 浏览器渲染优化DNS查询、减少HTTP请求、利用缓存
页面解析过程解析HTML构建DOM树 → 解析CSS构建CSSOM树 → 合并成渲染树 → 布局 (Layout) → 绘制 (Paint)避免同步JS、使用defer/async、优化CSS选择器
浏览器缓存强缓存 (Cache-Control, Expires)、协商缓存 (Last-Modified/If-Modified-Since, ETag/If-None-Match)合理设置缓存策略提升性能
CDN (内容分发网络)将资源缓存到离用户更近的边缘节点,减少网络延迟和源站压力加速静态资源(图片、CSS、JS)加载
前端安全跨域 (Cross-Origin)浏览器同源策略 (协议、域名、端口任一不同即为跨域) 的限制CORS (设置Access-Control-Allow-Origin等响应头)、JSONP (利用<script>标签跨域)、反向代理 (服务器端转发请求)
CSRF (跨站请求伪造)攻击者诱导用户在已登录的Web应用中执行非本意的操作验证请求来源 (Referer检查)、使用Token验证 (Anti-CSRF Token)、设置SameSite Cookie属性
其他前端常见攻击 (如XSS)XSS: 攻击者向页面注入恶意脚本XSS: 对用户输入进行转义、使用CSP (内容安全策略)
前端框架与工具Vue双向数据绑定原理 (Vue 2)通过数据劫持 (Object.defineProperty)+ 发布-订阅模式 实现。Object.defineProperty 定义所有属性的 getter/setter,在 getter 中收集依赖,在 setter 中通知更新。Vue 3改用Proxy实现,性能更优且能监听动态新增属性。
Vue源码 & 打包过程Vue源码包含编译器、响应式系统、虚拟DOM、组件系统等。打包过程通常使用Webpack或Vite,将众多模块(.vue, .js, .css)打包成少量优化后的静态资源文件(如JS Bundle)。Tree-shaking、代码分割、压缩混淆等优化手段。
Webpack原理核心概念:入口(Entry)输出(Output)加载器(Loaders)(处理非JS文件)、插件(Plugins)(执行更广的任务)、模式(Mode)(开发/生产)。模块化、依赖分析、代码转换和打包。
项目与部署实习项目部署常见方式:CI/CD流水线(如Jenkins, GitLab CI)、手动部署(SCP/FTP上传文件)。流程:构建 → 打包(生成静态文件)→ 上传至服务器(如Nginx目录)→ 配置服务器(如Nginx反向代理)。自动化部署提升效率,利用Docker容器化部署增强环境一致性。
JS编程与算法深拷贝 (Deep Clone)完整复制对象/数组及其嵌套引用,新老对象完全独立。JSON.parse(JSON.stringify(obj))(有局限)、递归实现(处理对象、数组、循环引用)、使用第三方库(如Lodash的_.cloneDeep)。
遍历树的时间复杂度通常为 O(n),其中 n 为树中节点的总数。因为每个节点都会访问一次。深度优先搜索 (DFS)、广度优先搜索 (BFS)。
快速排序的时间复杂度平均情况:O(n log n);最坏情况(已排序):O(n²)分治思想,选取基准元素分区。
判断数据类型typeof(基本类型,null"object")、instanceof(检测构造函数的prototype是否在对象原型链上)、Object.prototype.toString.call(obj)(返回[object Type])。Array.isArray()(判断是否为数组)。
编程题:最长连续相同元素遍历数组,计数当前连续相同元素,更新最大计数和对应元素。时间复杂度 O(n),空间复杂度 O(1)
其他毕业设计 & 爬虫原理 & 模型改进点需根据你的实际项目情况补充。爬虫原理:模拟HTTP请求获取网页内容 → 解析提取数据(正则、CSS选择器、XPath)→ 存储数据(数据库、文件)。模型改进点常指机器学习模型调参、优化算法、特征工程等。

🔧 编程题:寻找最长连续相同元素

题目:给定一个数组,找出连续出现次数最多的元素及其长度。
例如:输入 [1, 2, 2, 3, 3, 3, 2],应返回 { element: 3, length: 3 }

🧠 思路
  1. 初始化:我们需要变量来记录当前连续元素当前连续长度最大连续元素最大连续长度
  2. 遍历数组:逐个检查数组中的元素。
  3. 判断连续性
    • 如果当前元素上一个元素相同,当前连续长度加1。
    • 如果不相同,则说明一段连续结束了。此时比较当前连续长度最大连续长度,如果更长,就更新最大连续元素最大连续长度。然后重置当前连续元素当前连续长度为新的元素。
  4. 处理最后一段:遍历结束后,最后一段连续序列可能还未比较,需要再判断一次。
📜 JavaScript 代码实现
function findLongestConsecutiveSequence(arr) {if (arr.length === 0) {return { element: undefined, length: 0 };}let currentElement = arr[0];let currentLength = 1;let maxElement = arr[0];let maxLength = 1;for (let i = 1; i < arr.length; i++) {if (arr[i] === currentElement) {// 当前元素与之前连续的元素相同,长度加1currentLength++;} else {// 当前元素发生变化,比较并更新最大记录if (currentLength > maxLength) {maxElement = currentElement;maxLength = currentLength;}// 重置当前记录为新的元素currentElement = arr[i];currentLength = 1;}}// 循环结束后,再次检查最后一段序列if (currentLength > maxLength) {maxElement = currentElement;maxLength = currentLength;}return { element: maxElement, length: maxLength };
}// 测试示例
console.log(findLongestConsecutiveSequence([1, 2, 2, 3, 3, 3, 2])); // { element: 3, length: 3 }
console.log(findLongestConsecutiveSequence(['a', 'b', 'b', 'b', 'a'])); // { element: 'b', length: 3 }
console.log(findLongestConsecutiveSequence([5])); // { element: 5, length: 1 }
console.log(findLongestConsecutiveSequence([])); // { element: undefined, length: 0 }function findLongestConsutiveSequence(arr){let currentElement=arr[0];let currentLength=1;let maxElement=arr[0];let maxLen=1;for(let i=1;i<arr.length;i++){if(arr[i]===currentElement){currentLength++;}else{if(currentLen>maxLen){maxElement=currentElement;maxLength=currentLength;}//重置当前记录为新的元素currentElement=arr[i];currentLength=1;}}if(currentLength>maxLength){maxElement=currentElement;maxLength=currentLength;}return {element:maxElement,length:maxLength};
}
⏱ 时间复杂度分析
  • 时间复杂度:O(n)
    算法使用了一个简单的 for 循环,从头到尾遍历了输入数组一次。循环内的所有操作(比较、赋值)都是常数时间 O(1)。因此,总的时间复杂度是线性的 O(n),其中 n 是数组的长度。
  • 空间复杂度:O(1)
    算法只使用了几个固定的变量(currentElement, currentLength, maxElement, maxLength)来存储中间状态和结果。这些变量所占用的空间不随输入数组的大小 n 而变化。因此,空间复杂度是常数级的 O(1)
http://www.dtcms.com/a/389658.html

相关文章:

  • 51单片机-使用单总线通信协议驱动DS18B20模块教程
  • 全文单侧引号的替换方式
  • NVIDIA RTX4090 在Ubuntu系统中开启P2P peer access 直连访问
  • 再次深入学习深度学习|花书笔记2
  • 中移物联ML307C模组OPENCPU笔记1
  • 计算机视觉
  • VScode实现uniapp小程序开发(含小程序运行、热重载等)
  • Redis的各种key问题
  • 元宇宙与医疗产业:数字孪生赋能医疗全链路革新
  • 为你的数据选择合适的分布:8个实用的概率分布应用场景和选择指南
  • 掌握Stable Diffusion WebUI:模型选择、扩展管理与部署优化
  • LVGL拼音输入法优化(无bug)
  • 多层感知机:从感知机到深度学习的关键一步
  • PostgreSQL绿色版整合PostGIS插件,以Windows 64位系统为例
  • GEO优化推荐案例:2025年上海源易信息科技的全链路实践
  • 时空预测论文分享:多模态融合 空间索引结构 超图 时演化因果关系
  • 智能手机产量增长4%
  • MySQL高可用MHA实战指南
  • Coze源码分析-资源库-创建工作流-后端源码-核心技术/总结
  • 《棒球团建》国家级运动健将·棒球1号位
  • 基于STM32单片机生理监控心率脉搏TFT彩屏波形曲线加体温测量
  • Selenium 浏览器自动化完全指南:从环境搭建到实战应用
  • C51单片机——开发学习:中断
  • 树与二叉树【数据结构】
  • RPM包版本号系统解析:设计哲学、比较规则与实践指南
  • IDEA启动异常
  • vite使用vue2项目
  • 前端性能优化实用方案(一):减少50%首屏资源体积的Webpack配置
  • SQL 条件函数 IF、CASE WHEN 用法速查
  • 【深度学习新浪潮】如何估算大模型的训练和推理内存需求?