面试题 记录一下

93 阅读6分钟

1. 闭包的原理与应用

问题:什么是闭包?它的原理是什么?有哪些应用场景?

答案:

闭包是指 函数与其词法环境的组合。 当一个函数在其词法作用域外被调用时,它仍然可以访问其词法作用域内的变量。 闭包的原理是 JavaScript 的 作用域链机制,函数在定义时会保存其所在的作用域链, 即使函数在其他地方执行,也能通过作用域链访问到定义时的变量。

应用场景:

  1. 封装私有变量:通过闭包实现模块化,隐藏内部变量。
    function createCounter() {
        let count = 0;
        return function() {
            count++;
            return count;
        };
    }
    const counter = createCounter();
    console.log(counter()); // 1
    console.log(counter()); // 2
    
  2. 函数柯里化:将多参数函数转换为单参数函数。
    function add(a) {
        return function(b) {
            return a + b;
        };
    }
    const add5 = add(5);
    console.log(add5(3)); // 8
    

2. 事件循环与异步机制

问题:解释 JavaScript 的事件循环机制,宏任务和微任务的区别是什么?

答案:

事件循环是 JavaScript 处理异步任务的机制,分为以下几个步骤:

  1. 执行同步代码:所有同步任务在主线程上依次执行。

  2. 处理微任务:执行所有微任务(如 Promise.thenMutationObserver)。

  3. 渲染页面:如果需要,浏览器会进行页面渲染。

  4. 处理宏任务:执行一个宏任务(如 setTimeoutsetIntervalI/O 操作)。

  5. 重复循环:重复上述步骤。

宏任务与微任务的区别:

  • 宏任务:由浏览器发起,如 setTimeoutsetIntervalI/O 操作。

  • 微任务:由 JavaScript 引擎发起,如 Promise.thenMutationObserver

示例:

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 有三种状态:pendingfulfilledrejected。当状态改变时,会执行相应的回调函数。

实现代码:

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 的响应式原理基于 数据劫持依赖收集。具体步骤如下:

  1. 数据劫持:通过 Object.definePropertyProxy 监听对象属性的读写操作。

  2. 依赖收集:在属性被访问时,收集依赖(Watcher);在属性被修改时,通知依赖更新。

  3. 虚拟 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 算法的步骤:

  1. 同层比较:只比较同一层级的节点。

  2. Key 优化:通过 key 属性识别节点的唯一性,减少不必要的更新。

  3. 节点复用

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);
    }
}

节流

  1. 在一定时间内,如果多次触发,只执行一次
  2. 节流函数的目的是在一段时间内多次触发某个操作时,
  3. 只执行一次。它通常用于限制某些操作的执行频率(如滚动事件、鼠标移动事件等)。
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)