1. 闭包的原理与应用
问题:什么是闭包?它的原理是什么?有哪些应用场景?
答案:
闭包是指 函数与其词法环境的组合。 当一个函数在其词法作用域外被调用时,它仍然可以访问其词法作用域内的变量。 闭包的原理是 JavaScript 的 作用域链机制,函数在定义时会保存其所在的作用域链, 即使函数在其他地方执行,也能通过作用域链访问到定义时的变量。
应用场景:
- 封装私有变量:通过闭包实现模块化,隐藏内部变量。
function createCounter() { let count = 0; return function() { count++; return count; }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2 - 函数柯里化:将多参数函数转换为单参数函数。
function add(a) { return function(b) { return a + b; }; } const add5 = add(5); console.log(add5(3)); // 8
2. 事件循环与异步机制
问题:解释 JavaScript 的事件循环机制,宏任务和微任务的区别是什么?
答案:
事件循环是 JavaScript 处理异步任务的机制,分为以下几个步骤:
-
执行同步代码:所有同步任务在主线程上依次执行。
-
处理微任务:执行所有微任务(如
Promise.then、MutationObserver)。 -
渲染页面:如果需要,浏览器会进行页面渲染。
-
处理宏任务:执行一个宏任务(如
setTimeout、setInterval、I/O操作)。 -
重复循环:重复上述步骤。
宏任务与微任务的区别:
-
宏任务:由浏览器发起,如
setTimeout、setInterval、I/O操作。 -
微任务:由 JavaScript 引擎发起,如
Promise.then、MutationObserver。
示例:
console.log('1'); // 同步任务
setTimeout(() => console.log('2'), 0); // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
console.log('4'); // 同步任务
// 输出顺序:1 -> 4 -> 3 -> 2
3. Promise 的实现原理
问题:如何手动实现一个 Promise?
Promise 的核心原理是通过 状态机 和 回调队列 实现。
Promise 有三种状态:pending、fulfilled、rejected。当状态改变时,会执行相应的回调函数。
实现代码:
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(cb => cb());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = reason;
this.onRejectedCallbacks.forEach(cb => cb());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
} else if (this.state === 'rejected') {
onRejected(this.value);
} else {
this.onFulfilledCallbacks.push(() => onFulfilled(this.value));
this.onRejectedCallbacks.push(() => onRejected(this.value));
}
}
}
二、Vue 深入原理
1. Vue 的响应式原理
问题:Vue 是如何实现数据响应式的?
答案:
Vue 的响应式原理基于 数据劫持 和 依赖收集。具体步骤如下:
-
数据劫持:通过
Object.defineProperty或Proxy监听对象属性的读写操作。 -
依赖收集:在属性被访问时,收集依赖(Watcher);在属性被修改时,通知依赖更新。
-
虚拟 DOM:当数据变化时,生成新的虚拟 DOM,与旧的虚拟 DOM 进行对比,更新真实 DOM。
示例:
function defineReactive(obj, key, val) {
const dep = new Dep(); // 依赖管理器
Object.defineProperty(obj, key, {
get() {
dep.depend(); // 收集依赖
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify(); // 通知依赖更新
}
});
}
2. Vue 的虚拟 DOM 与 Diff 算法
虚拟 DOM 是真实 DOM 的轻量级副本,用于提高渲染性能。 Diff 算法是虚拟 DOM 的核心,通过对比新旧虚拟 DOM 的差异,最小化真实 DOM 的操作。
Diff 算法的步骤:
-
同层比较:只比较同一层级的节点。
-
Key 优化:通过
key属性识别节点的唯一性,减少不必要的更新。 -
节点复用
function patch(oldVNode, newVNode) {
if (oldVNode === newVNode) return;
if (isSameVNode(oldVNode, newVNode)) {
patchVNode(oldVNode, newVNode);
} else {
replaceVNode(oldVNode, newVNode);
}
}
1. 排序算法
问题:实现快速排序算法。
答案:
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const right = arr.filter(x => x > pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
}
2. 链表操作
问题:如何反转一个单链表? 答案:
function reverseList(head) {
let prev = null;
let curr = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
3. 动态规划
问题:如何解决背包问题?
答案:
function knapsack(weights, values, capacity) {
const n = weights.length;
const dp = Array.from({ length: n + 1 }, () => Array(capacity + 1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 0; j <= capacity; j++) {
if (weights[i - 1] <= j) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
return dp[n][capacity];
}
防抖
防抖函数的目的是在一段时间内多次触发某个操作时,只执行最后一次。它通常用于减少频繁触发的事件(如窗口调整大小、输入框输入等)带来的性能问题。
function debounce(func, delay) {
let timer = null;
return function(...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args)
}, delay);
}
}
节流
- 在一定时间内,如果多次触发,只执行一次
- 节流函数的目的是在一段时间内多次触发某个操作时,
- 只执行一次。它通常用于限制某些操作的执行频率(如滚动事件、鼠标移动事件等)。
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = new Date();
const context = this;
if (now - lastTime > delay) {
lastTime = now;
fn.apply(context, args);
}
}
}
实现new
function creatObject(Constructor, ...args) {
// 创建一个新的空对象
const obj = {};
// 设置对象的原型为构造函数的prototype
Object.setPrototypeOf(obj, Constructor.prototype);
// 绑定this到新的对象上
const result = Constructor.apply(obj, args);
// 如果构造函数返回一个新对象,则返回该对象,否则返回创建的对象
return (typeof result == 'object' && result !== null) ? result : obj
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(`Hello my name is${this.name} and I am ${this.age} year`);
}
const person = creatObject(Person, 'Alice', 30);
person.say()
实现Bind
Function.prototype.myBind = function(context, ...args) {
if (typeof this !== 'function') {
throw new Error('Error: myBind is must be function');
}
const func = this;
return function(...newArgs) {
return func.apply(context, args.concat(newArgs));
}
}
function greet (greeting, pun) {
console.log(`${greeting} my name is ${this.name} ${pun}`) ;
}
const person2 = {name: 'litianyang'};
const boundGreet = greet.myBind(person2, 'Hello');
boundGreet('!')
深拷贝
function deepClone(obj) {
if (typeof obj != 'object' || obj == null) {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
for(let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]);
}
}
return copy;
}
var obj1 = {
name: "Alice",
age: 25,
id: [1,2,3],
info: {
city: "New York"
}
};
deepClone(obj1);
冒泡排序
var arr1 = [5,4,3,1];
for (let i = 0; i < arr1.length - 1; i++) {
// -1是因为在循环一次之后最大的肯定最后面
for (let j = 0; j < arr1.length - 1 - i; j++) {
// -i是因为每次循环比较完之后 最大的肯定都在后面
// 前后两两比较,把小的放前面 交换一下位置
if (arr1[j] > arr1[j + 1]) {
var temp = arr1[j];
arr1[j] = arr1[j + 1];
arr1[j+1] = temp;
}
}
}
// console.log(arr1)
选择排序
假设一个第一个元素的下标为最小,在数组中一次找出最小数的下标,依次放到最前面
var arr2 = [5,7,1,3,2,6];
for (let i = 0; i < arr2.length - 1; i++) {
var minIndex = i;
for (let j = i + 1; j < arr2.length; j++) {
if (arr2[j] < arr2[minIndex]) {
minIndex = j;
console.log(minIndex);
}
}
var temp = arr2[i]
arr2[i] = arr2[minIndex];
arr2[minIndex] = temp;
}
快速排序
var arr3 = [6,5,3,7,8,1,9]
function quickSort (arr) {
if (arr.length <= 1 ) {
return arr;
}
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr.splice(pivotIndex, 1)[0];
const left = [];
const right = [];
console.log(arr);
for (let i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i])
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)]
}
// console.log(quickSort(arr3))
quickSort(arr3)