JavaScript

事件循环

js 引擎在执行任务时,不会等待异步任务的结果返回,而是将其挂起,等结果返回了再将异步任务的回调加入事件队列。

事件队列分为微任务队列和宏任务队列,引擎会优先执行微任务,再执行宏任务。
宏任务包括:setTimeoutsetInterval
微任务包括:PromiseIntersectionObserverqueueMicrotask

当 js 引擎完成当前的同步任务后,会清空 所有 微任务,然后执行 一条 宏任务,再清空微任务,不断循环。

console.log(1);
setTimeout(() => {
    console.log(2);
    queueMicrotask(() => console.log(3));
});

new Promise(resolve => {
    console.log(4);
    resolve();
}).then(() => {
    console.log(5);
    setTimeout(() => {
        console.log(6); 
    });
});

选中区域查看打印循序:1 4 5 2 3 6

原型链

所有除 null 外的对象都有一个属性 __proto__,指向该对象的原型。
所有除箭头函数外的函数都有一个属性 prototype,指向该函数的原型。
对象的 __proto__ 等价于该对象的构造函数的 prototype

访问对象的一个属性时:

  1. 对象上存在该属性,直接返回对应属性的值
  2. 对象上不存在该属性,则访问对象的原型。如果原型上存在该属性,返回原型中对应属性的值,否则继续访问原型的原型,直到遇到 null,返回 undefined

几个例子:

const f = function() {}
console.log(f.prototype === f.constructor.__proto__);  // true

const f2 = () => {}
console.log(f.prototype);  // undefined

const o = new f();
console.log(o.__proto__ === f.prototype);  // true
f.prototype.a = 1;
console.log(o.a);  // 1

正则

正则的一个坑

上代码:

const regex = /1/g;
for (let i = 0; i < 4; i++) {
    console.log(regex.test('123'));  // true false true false
}

test 返回值不同是因为单个实例 + 全局匹配。

regex 匹配过一次后,会记录索引值到 lastIndex,再次匹配会从 lastIndex 开始。

要从头匹配的话,可以设置 lastIndex = 0

const regex = /1/g;
for (let i = 0; i < 4; i++) {
    console.log(regex.test('123'));  // true true true true
    regex.lastIndex = 0;
}

编程题

克隆

// 浅克隆
const clone = target => {
    if (target === null) {
        return null;
    }

    return typeof target === 'object' ? { ...target } : target;
}

// 深克隆
const deepClone = (target) => {

    // 克隆对象
    const cloneObject = (obj) => {
        const clonedObj = {};
        cloned.set(obj, clonedObj);

        for (const [key, value] of Object.entries(obj)) {
            clonedObj[key] = getClonedValue(value);
        }

        return clonedObj;
    }

    // 克隆数组
    const cloneArray = (array) => array.map(item => getClonedValue(item));

    // 克隆 Set
    const cloneSet = (set) => {
        const s = new Set();
        for (const value of set) {
            const newValue = getClonedValue(value);
            s.add(newValue);
        }
        return s;
    }

    // 克隆 Map
    const cloneMap = (map) => {
        const m = new Map();
        for (const [key, value] of map.entries()) {
            const newKey = getClonedValue(key);
            const newValue = getClonedValue(value);
            m.set(newKey, newValue);
        }
        return m;
    }

    // 克隆正则
    const cloneRegExp = (regex) => {
        const regexStr = regex.toString();
        const flags = regexStr.slice(regexStr.lastIndexOf('/') + 1);
        return new RegExp(regex.source, flags);
    }

    // 获取克隆后的值
    const getClonedValue = (value) => {
        const type = Object.prototype.toString.call(value);
        switch (type) {
            case '[object Number]':
            case '[object Boolean]':
            case '[object String]':
            case '[object Null]':
            case '[object Undefined]':
            case '[object Function]':  // 这 6 种类型直接返回
                return value;
            case '[object Array]':  // 数组
                return cloneArray(value);
            case '[object Object]':  // 对象
                return cloned.get(value) || cloneObject(value);
            case '[object Set]':  // Set
                return cloneSet(value);
            case '[object Map]':  // Map
                return cloneMap(value);
            case '[object RegExp]':  // 正则表达式
                return cloneRegExp(value);
        }
    }

    const cloned = new Map();  // 克隆过的对象,处理嵌套调用
    return getClonedValue(target);
}

函数防抖

多次调用函数,在最后一次调用的 n 秒后再执行。

const debounce = (fn, time) => {
    let timer = null;
    return function() {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.call(this, ...arguments);
        }, time);
    }
}

函数节流

在 n 秒内,不管多少次调用,只执行一次函数。

const throttle = (fn, time) => {
    let canRun = true;
    return function () {
        if (canRun) {
            canRun = false;
            fn.call(this, ...arguments);
            setTimeout(() => {
                canRun = true;
            }, time);
        }
    }
}

实现 add(1, 2)(3)(4)

// add(1, 2)(3)() --> 6
// add(1)(2)(3, 4, 5)() --> 15
const add = (...args) => {
    const sum = args.reduce((prev, curr) => prev + curr);
    return (...nextArgs) => nextArgs.length > 0 ? add(sum, ...nextArgs) : sum;
}
最后更新时间: