H5W3
当前位置:H5W3 > 其他技术问题 > 正文

【前端技术】面试系列之JavaScript手写代码篇

加深对 JavaScript 的理解,学习并手写常见高频题目,理解实现原理
欢迎大家指点,有不足之处会及时改进

点击此处传送至代码仓库

this

1. bind

/**
 * 1. bind() 方法创建一个新的函数
 * 2. 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数
 * 3. new 情况下忽略第一个参数
 * 4. 其余参数将作为新函数的参数,供调用时使用
 * @param {object} ctx
 * @param  {...any} args
 * @returns {function} 返回一个原函数的拷贝,并拥有指定 this 值和初始参数
 */
Function.prototype.__bind = function (ctx, ...args) {
    // 判断 this 是否为 function 类型
    if (typeof this !== 'function') throw new TypeError('Error');

    // 保存当前 this
    const __this = this;

    return function F() {
        return this instanceof F
            ? new __this(...args, ...arguments) // new 
            : __this.apply(ctx, [...args, ...arguments]); // 直接调用时绑定 this
    };
};

// ------------------------------ 测试 ------------------------------

function print() {
    console.log(this.name, ...arguments);
}

const obj = {
    name: 'mxin',
};

// Function.prototype.__bind()
console.log('Function.prototype.__bind()');

// 直接调用,返回原函数拷贝,this 指向 obj
const F = print.__bind(obj, 26);
F(178); // mxin, 26, 178

// new 情况
const _obj = new F(145); // undefined, 26, 145
console.log(_obj); // print {}

// Function.prototype.bind()
console.log('Function.prototype.bind()');

const Fn = print.bind(obj, 26);
Fn(178); // mxin, 26, 178

const __obj = new Fn(145); // undefined, 26, 145
console.log(__obj); // print {}

2. call

/**
 * 模拟 call
 * 使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
 * @param {object} ctx
 * @param  {...any} args
 * @returns {any} 调用 this 的返回值,若无有返回值,则返回 undefined
 */
Function.prototype.__call = function (ctx, ...args) {
    if (typeof this !== 'function') throw new TypeError('Error');

    // 考虑 null 情况,参数默认赋值会无效
    if (!ctx) ctx = window;

    // 将 this 函数保存在 ctx 上
    ctx.fn = this;

    // 传参执行并保存返回值
    const res = ctx.fn(...args);

    // 删除 ctx 上的 fn
    delete ctx.fn;
  
    return res;
};

// ------------------------------ 测试 ------------------------------

function Product(name, price) {
    this.name = name;
    this.price = price;
}

// Function.prototype.__call()
console.log('Function.prototype.__call()');

function Food(name, price) {
    Product.__call(this, name, price);
    this.category = 'food';
}
const food = new Food('cheese', 5);
console.log(food);
// Food {name: "cheese", price: 5, category: "food"}
//   category: "food"
//   name: "cheese"
//   price: 5
//   __proto__:
//     constructor: ƒ Food(name, price)
//     __proto__: Object

// Function.prototype.call()
console.log('Function.prototype.call()');

function Toy(name, price) {
    Product.call(this, name, price);
    this.category = 'toy';
}
const toy = new Toy('car', 10);
console.log(toy);
// Toy {name: "car", price: 10, category: "toy"}
//   category: "toy"
//   name: "car"
//   price: 10
//   __proto__:
//     constructor: ƒ Toy(name, price)
//     __proto__: Object

3. apply

/**
 * 模拟 apply
 * 调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数
 * @param {object} ctx
 * @param {} args
 */
Function.prototype.__apply = function (ctx, args) {
    if (typeof this !== 'function') throw new TypeError('Error');

    // 考虑 null 情况,参数默认赋值会无效
    if (!ctx) ctx = window;

    // 将 this 函数保存在 ctx 上
    ctx.fn = this;

    // 传参执行并保存返回值
    const result = ctx.fn(...args);

    // 删除 ctx 上的 fn
    delete ctx.fn;
  
    return result;
};

// ------------------------------ 测试 ------------------------------

const numbers = [5, 6, 2, 3, 7];

// Function.prototype.__apply()
console.log('Function.prototype.__apply()');

const max = Math.max.__apply(null, numbers);
console.log(max); // 7

// Function.prototype.apply()
console.log('Function.prototype.apply()');
const min = Math.min.apply(null, numbers);
console.log(min); // 2

原型

1. 继承

例举几种比较常用的继承方式

/**
 * 使用 extends 继承
 */

// 继承类
class Vehicle {}
class Bus extends Vehicle {}

let b = new Bus();
console.log(b instanceof Bus); // true
console.log(b instanceof Vehicle); // true


// 继承普通构造函数
function Person() {}
class Engineer extends Person {}

let e = new Engineer();
console.log(e instanceof Engineer); // true
console.log(e instanceof Person); // true


/**
 * 寄生式组合继承
 */
function Person(name) {
    this.name = name;
}
function Man(name, age) {
    Person.call(this, name, age);
    this.age = age;
}
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;

const man = new Man('mxin', 18);
console.log(man instanceof Man); // true
console.log(man instanceof Person); // true

2. instanceof

/**
 * 模拟 instanceof
 * 判断 obj.__proto__ 和 __constructor.prototype 是否相等
 * @param {object} obj 实例对象
 * @param {function} __constructor 构造函数
 */
function __instanceof(obj, __constructor) {
    const prototype = __constructor.prototype;
    obj = Object.getPrototypeOf(obj);

    while (true) {
        if (obj === null) return false;
        if (obj === prototype) return true;
        obj = Object.getPrototypeOf(obj);
    }
}

// ------------------------------ 测试 ------------------------------

function C() {}
function D() {}

const o = new C();

// __instanceof()
console.log('__instanceof()');

console.log(__instanceof(o, C));
console.log(__instanceof(o, D));
console.log(__instanceof(o, Object));

// instanceof
console.log('instanceof');

console.log(o instanceof C);
console.log(o instanceof D);
console.log(o instanceof Object);

对象

1. Objcet.create

/**
 * 模拟 Object.create
 * 创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
 * @param {object} prototype 新创建对象的原型对象,为 null 时 只能使用 Object.create()
 * @param {object} properties 访问器描述符,同 Object.defineProperties 第二个参数
 * @returns {object}
 */
function __create(prototype, properties) {
    if (typeof prototype !== 'object') throw new TypeError('Error');

    function Constructor() {}
    Constructor.prototype = prototype;

    const obj = new Constructor();

    if (prototype) obj.constructor = Constructor;

    // 设置访问器描述符
    if (properties) {
        if (typeof properties !== 'object') throw TypeError('Error');
        Object.defineProperties(obj, properties);
    }

    return obj;
}

// ------------------------------ 测试 ------------------------------

const person = {
    isHuman: false,
    printIntroduction: function () {
        console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
    },
};

// __create()
console.log('__create()');

const __me = __create(person);
__me.name = '__mxin';
__me.isHuman = true;
__me.printIntroduction();

// Object.create()
console.log('Object.create()');

const me = Object.create(person);
me.name = 'mxin';
me.isHuman = true;
me.printIntroduction();

// 目前创建纯净空对象只有 Object.create(null) 可行,无法模拟
const emptyObj = Object.create(null);
console.log(emptyObj);
// {}
//    No properties

2. Object.is

  • == 运算不同,== 运算符在判断相等前对两边的变量(如果它们不是同一类型) 进行强制转换 (这种行为的结果会将 "" == false 判断为 true), 而 Object.is不会强制转换两边的值
  • === 运算也不相同, === 运算符 (也包括 == 运算符) 将数字 -0+0 视为相等 ,而将Number.NaNNaN 视为不相等
/**
 * 模拟 Object.is
 * 判断两个值是否为同一个值
 * 1. 都是 undefined
 * 2. 都是 null
 * 3. 都是 true 或 false
 * 4. 都是相同长度的字符串且相同字符按相同顺序排列
 * 5. 都是相同对象(意味着每个对象有同一个引用)
 * 6. 都是数字且
 *    a. 都是 +0
 *    b. 都是 -0
 *    c. 都是 NaN
 *    d. 或都是非零而且非 NaN 且为同一个值
 * @param {*} x
 * @param {*} y
 */
function __is(x, y) {
    if (x === y) {
        return x !== 0 || 1 / x === 1 / y;
    } else {
        return x !== x && y !== y;
    }
}

// ------------------------------ 测试 ------------------------------

// __is()
console.log('__is()');

console.log(`__is('foo', 'foo'): ${__is('foo', 'foo')}`); // true
console.log(`__is('foo', 'bar'): ${__is('foo', 'bar')}`); // false

const __foo = { a: 1 };
const __bar = { a: 1 };
console.log(`__is(__foo, __foo): ${__is(__foo, __foo)}`); // true
console.log(`__is(__foo, __bar): ${__is(__foo, __bar)}`); // false
console.log(`__is(window, window): ${__is(window, window)}`); // true
console.log(`__is([], []): ${__is([], [])}`); // false
console.log(`__is(null, null): ${__is(null, null)}`); // true

// 特例
console.log(`__is(0, -0): ${__is(0, -0)}`); // false
console.log(`__is(0, +0): ${__is(0, +0)}`); // true
console.log(`__is(-0, -0): ${__is(-0, -0)}`); // true
// console.log(`__is(NaN, 0 / 0): ${__is(NaN, 0 / 0)}`); // true


// Object.is()
console.log('Object.is()');

console.log(`Object.is('foo', 'foo'): ${Object.is('foo', 'foo')}`); // true
console.log(`Object.is('foo', 'bar'): ${Object.is('foo', 'bar')}`); // false

const foo = { a: 1 };
const bar = { a: 1 };
console.log(`Object.is(foo, foo): ${Object.is(foo, foo)}`); // true
console.log(`Object.is(foo, bar): ${Object.is(foo, bar)}`); // false
console.log(`Object.is(window, window): ${Object.is(window, window)}`); // true
console.log(`Object.is([], []): ${Object.is([], [])}`); // false
console.log(`Object.is(null, null): ${Object.is(null, null)}`); // true

// 特例
console.log(`Object.is(0, -0): ${Object.is(0, -0)}`); // false
console.log(`Object.is(0, +0): ${Object.is(0, +0)}`); // true
console.log(`Object.is(-0, -0): ${Object.is(-0, -0)}`); // true
console.log(`Object.is(NaN, 0 / 0): ${Object.is(NaN, 0 / 0)}`); // true

3. new

/**
 * 模拟 new
 * 1. 创建原型为 constructor.prototype 的新对象 obj
 * 2. 执行构造函数,this 指向 obj
 * 3. 判断构造函数返回值是否为对象,是就返回此对象
 * 4. 构造函数无返回值返回 obj
 * @param {function} constructor
 * @param  {...any} args
 * @returns {object}
 */
function __new(constructor, ...args) {
    if (typeof constructor !== 'function') throw new TypeError('Error');

    // 创建一个空对象,指定原型为constructor.prototype
    const obj = Object.create(constructor.prototype);

    // 执行构造函数,绑定this
    const result = constructor.apply(obj, args);

    // 如果构造函数返回值是一个对象,那么返回该对象, 如果没有就返回 obj
    return result && result instanceof Object ? result : obj;
}

// ------------------------------ 测试 ------------------------------

function Person(name, age) {
    this.name = name;
    this.age = age;
}

// __new
console.log('__new');
const __mxin = __new(Person, '__mxin', 18);
console.log(__mxin);
// Person {name: "__mxin", age: "18"}
//     age: "18"
//     name: "__mxin"
//     __proto__:
//         constructor: ƒ Person(name, age)
//         __proto__: Object

// new
console.log('new');
const mxin = new Person('mxin', 18);
console.log(mxin);
// Person {name: "mxin", age: "18"}
//     age: "18"
//     name: "mxin"
//     __proto__:
//         constructor: ƒ Person(name, age)
//         __proto__: Object

4. 浅拷贝

几种常用方式:

  1. 自定义循环
  2. 展开运算符
  3. Object.assign()
/**
 * 浅拷贝,无脑循环
 * @param {*} targetObj
 */
function shallowClone(targetObj) {
    const resObj = {};
    for (let key in targetObj) {
        resObj[key] = targetObj[key];
    }
    return resObj;
}

// ------------------------------ 测试 ------------------------------

console.log('shallowClone()');

const shallowObj = {
    name: 'mxin',
    age: 18,
};

/**
 * 自定义方法
 */
const a = shallowClone(shallowObj);
a.name = '__mxin';
a.age = 20;

console.log('a', a);
// {name: "__mxin", age: 20}
//   age: 20
//   name: "__mxin"

/**
 * 拓展运算符
 */
const b = { ...a };
b.name = '____mxin';
b.age = 22;

console.log('b', b);
// {name: "____mxin", age: 22}
//   age: 22
//   name: "____mxin"

/**
 * Object.assign()
 */
const c = Object.assign({}, shallowObj)
c.name = '______mxin';
c.age = 24;

console.log('c', c);
// {name: "______mxin", age: 24}
//   age: 24
//   name: "______mxin"

// 不影响原有对象
console.log('shallowObj', shallowObj);
// {name: "mxin", age: 18}
//   age: 18
//   name: "mxin"

5. 深拷贝

/**
 * 深拷贝
 * 深层克隆对象结构
 * @param {object} target
 * @returns {object}
 */
function deepClone(target) {
    // 如果不是对象,直接返回本身
    if (!isObject(target) || target === null) return target;

    // 参数类型校验情况还有很多,没有覆盖全面,可以后期拓展
    if (target instanceof Date) return new Date(target);
    if (target instanceof RegExp) return new RegExp(target);

    const obj = {};
    const stack = [
        {
            parent: obj,
            key: null,
            data: target,
        },
    ];

    while (stack.length) {
        const node = stack.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        let res = key ? (parent[key] = {}) : parent;

        for (const k in data) {
            if (data.hasOwnProperty(k)) {
                if (isObject(data[k])) {
                    stack.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return obj;
}

/**
 * 判断 target 是否为对象
 * @param {*} target
 */
function isObject(target) {
    return Object.prototype.toString.call(target) === '[object Object]';
}
// ------------------------------ 测试 ------------------------------

console.log('deepClone()');

const deepObj = {
    e: {
        f: {
            g: {
                h: 1,
            },
        },
    },
    i: {
        j: {
            k: {
                l: 2,
            },
        },
    },
};

const d = deepClone(deepObj);
d.e.f.g.h = 2;
d.i.j.k.l = 4;

console.log('d', d);

// 不影响原有对象
console.log('deepObj', deepObj);

6. 对象扁平化

/**
 * 对象扁平化
 * 将多层嵌套的 key 合并
 * @param {object} target
 * @param {string} tempKey
 * @param {object} res
 * @returns {object}
 */
function flattenObject(target, tempKey = '', res = {}) {
    // 使用 Object.entries() 将键值对转换成数组,确保 key 与 val 的对应关系
    for (const [key, val] of Object.entries(target)) {
        // 如果 val 是对象,保存合并后的 key 进行递归
        if (isObject(val)) {
            const tmp = tempKey + key + '.';
            flattenObject(val, tmp, res);
        } else {
            // 当 val 不是对象,合并 key 并对结果对象赋值
            const tmp = tempKey + key;
            res[tmp] = val;
        }
    }
    return res;
}

/**
 * 判断 target 是否为对象
 * @param {*} target
 */
function isObject(target) {
    return Object.prototype.toString.call(target) === '[object Object]';
}

// ------------------------------ 测试 ------------------------------

console.log('flattenObject()');

const object = {
    d: {
        e: {
            f: {
                g: {
                    h: 1,
                },
            },
        },
        i: {
            j: {
                k: {
                    l: 2,
                },
            },
        },
    },
};

console.log(flattenObject(object));
// {
//   d.e.f.g.h: 1
//   d.i.j.k.l: 2
// }

数组

1. 数组扁平化

几种常用方式:

  1. 递归
  2. Array.prototype.flat()
  3. Array.prototype.reduce()
/**
 * 数组扁平化
 * 判断数组中元素类型,如果是数组类型就递归,否则直接 push 到 res 中
 * @param {array} target
 * @param {array} res
 * @returns {array}
 */
function flattenArray(target, res = []) {
    for (const val of target) {
        if (Array.isArray(val)) {
            flattenArray(val, res);
        } else {
            res.push(val);
        }
    }
    return res;
}

/**
 * 使用 Array.prototype.reduce()
 * @param {array} target
 */
function flattenArrayByReduce(target) {
    const initPre = [];
    return target.reduce(
        (pre, current) =>
            pre.concat(
                Array.isArray(current) ? flattenArrayByReduce(current) : current
            ),
        initPre
    );
}

// ------------------------------ 测试 ------------------------------

console.log('flattenArray()');

const array = [[0], 1, [2, [3, [4, [5, [6]]]]], [7, [8]]];

/**
 * 递归
 */
console.log(flattenArray(array));
// [0, 1, 2, 3, 4, 5, 6, 7, 8]

/**
 * Array.prototype.flat()
 */
console.log(array.flat(Number.MAX_SAFE_INTEGER));
// [0, 1, 2, 3, 4, 5, 6, 7, 8]

/**
 * Array.prototype.reduce()
 */
console.log(flattenArrayByReduce(array));
// [0, 1, 2, 3, 4, 5, 6, 7, 8]

2. 数组去重

  • 使用 set 
console.log([...new Set(array)]);
  • 使用对象,或者将对象换成 map ,需要注意数组中元素的类型
/**
 * 数组去重
 * 基于对象实现,也可以使用 Map
 * @param {array} target
 * @returns {array}
 */
function removeDuplicate(target) {
    const temp = {};
    for (let i = 0; i < target.length; i++) {
        const item = target[i];
        if (
            Object.prototype.toString.call(item) !== '[object Object]' &&
            Object.prototype.toString.call(item) !== '[object Function]' &&
            Object.prototype.toString.call(item) !== '[object Symbol]' &&
            Object.prototype.toString.call(item) !== '[object Array]'
        ) {
            if (temp.hasOwnProperty(item)) {
                target[i] = target[target.length - 1];
                target.length--;
                i--;
            }
        }
        temp[item] = item;
    }
    return target;
}

// ------------------------------ 测试 ------------------------------

console.log('removeDuplicate()');

const array = [
    1,
    1,
    '2',
    '2',
    true,
    true,
    false,
    false,
    undefined,
    undefined,
    null,
    null,
    Symbol('3'),
    Symbol('3'),
    {},
    {},
    [],
    [],
];

console.log(removeDuplicate(array));

异步编程

1. Promise

  • Promise

    • resolve
    • reject
    • then
    • catch
    • finally
  • Promise.resolve
  • Promise.reject
  • Promise.all
  • promise.race
const isFunction = variable => typeof variable === 'function';

// 定义Promise的三种状态常量
const PENDING = 'pending';
const RESOLVE = 'resolved';
const REJECTED = 'rejected';

class __Promise {
    constructor(fn) {
        this.__status = PENDING;
        // 储存 value,用于 __then 返回
        this.__value = null;
        // 失败队列,在 __then 时注入,resolve 时触发
        this.__rejectedQueues = [];
        // 成功队列,在 __then 时注入,resolve 时触发
        this.__resolvedQueues = [];

        try {
            fn(this.__resolve, this.__reject);
        } catch (err) {
            this.__reject(err);
        }
    }

    __resolve = val => {
        const run = () => {
            if (this.__status !== PENDING) return;
            this.__status = RESOLVE;

            // 依次执行成功队列中的函数,并清空队列
            const runResolved = value => {
                let cb;
                while ((cb = this.__resolvedQueues.shift())) {
                    cb(value);
                }
            };

            // 依次执行失败队列中的函数,并清空队列
            const runRejected = error => {
                let cb;
                while ((cb = this.__rejectedQueues.shift())) {
                    cb(error);
                }
            };

            /*
             * 如果 resolve 的参数为 Promise 对象,
             * 则必须等待该 Promise 对象状态改变后当前 Promsie 的状态才会改变
             * 且状态取决于参数 Promsie 对象的状态
             */
            if (val instanceof __Promise) {
                val.__then(
                    value => {
                        this.__value = value;
                        runResolved(value);
                    },
                    err => {
                        this.__value = err;
                        runRejected(err);
                    }
                );
            } else {
                this.__value = val;
                runResolved(val);
            }
        };

        // 异步调用
        setTimeout(run);
    };

    __reject = err => {
        if (this.__status !== PENDING) return;

        const run = () => {
            this.__status = REJECTED;
            this.__value = err;
            let cb;
            while ((cb = this.__rejectedQueues.shift())) {
                cb(err);
            }
        };

        setTimeout(run);
    };

    __then(onResolved, onRejected) {
        const { __value, __status } = this;

        return new __Promise((onResolvedNext, onRejectedNext) => {
            const resolved = value => {
                try {
                    if (!isFunction(onResolved)) {
                        onResolvedNext(value);
                    } else {
                        const res = onResolved(value);

                        if (res instanceof __Promise) {
                            // 如果当前回调函数返回__Promise对象,必须等待其状态改变后在执行下一个回调
                            res.__then(onResolvedNext, onRejectedNext);
                        } else {
                            // 否则会将返回结果直接作为参数,传入下一个 __then 的回调函数,并立即执行下一个 __then 的回调函数
                            onResolvedNext(res);
                        }
                    }
                } catch (err) {
                    onRejectedNext(err);
                }
            };

            const rejected = error => {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(error);
                    } else {
                        const res = onRejected(error);

                        if (res instanceof __Promise) {
                            res.__then(onResolvedNext, onRejectedNext);
                        } else {
                            onResolvedNext(res);
                        }
                    }
                } catch (err) {
                    onRejectedNext(err);
                }
            };

            if (__status === PENDING) {
                this.__resolvedQueues.push(resolved);
                this.__rejectedQueues.push(rejected);
            }

            if (__status === RESOLVE) resolved(__value);

            if (__status === REJECTED) rejected(__value);
        });
    }

    __catch(onRejected) {
        return this.__then(null, onRejected);
    }

    __finally(cb) {
        return this.__then(
            value => __Promise.resolve(cb()).__then(() => value),
            reason =>
                __Promise.resolve(cb()).__then(() => {
                    throw new Error(reason);
                })
        );
    }

    static resolve(value) {
        // 如果参数是 __Promise 实例,直接返回这个实例
        if (value instanceof __Promise) return value;
        return new __Promise(resolve => resolve(value));
    }

    static reject(value) {
        return new __Promise((resolve, reject) => reject(value));
    }

    static all(list) {
        return new __Promise((resolve, reject) => {
            const values = [];
            let count = 0;

            for (const [i, p] of list.entries()) {
                // 数组参数如果不是 __Promise 实例,先调用 __Promise.resolve
                this.resolve(p).__then(
                    res => {
                        values[i] = res;
                        count++;
                        // 所有状态都变成 resolved 时返回的 __Promise 状态就变成 resolved
                        if (count === list.length) resolve(values);
                    },
                    err => {
                        // 有一个被 rejected 时返回的 __Promise 状态就变成 rejected
                        reject(err);
                    }
                );
            }
        });
    }

    static race(list) {
        return new __Promise((resolve, reject) => {
            list.forEach(p => {
                this.resolve(p).__then(
                    res => {
                        resolve(res);
                    },
                    err => {
                        reject(err);
                    }
                );
            });
        });
    }
}

// ------------------------------ 测试 ------------------------------

console.log('class __Promise {}');

const p1 = new __Promise((resolve, reject) =>
    setTimeout(() => {
        resolve('mxin');
    }, 500)
);
const p2 = new __Promise((resolve, reject) =>
    setTimeout(() => {
        resolve('__mxin');
    }, 200)
);
const p3 = new __Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('mxin3'));
    }, 100);
});

// 测试 __resolve __then __finally
new __Promise((resolve, reject) => {
    resolve('mxin');
})
    .__then(res => {
        console.log('__resolve:', res);
    })
    .__finally(() => {
        console.log('__resolve finally');
    });

// 测试 __reject __catch __finally
new __Promise((resolve, reject) => {
    reject(new Error());
})
    .__catch(e => {
        console.log('__reject:', e);
    })
    .__finally(() => {
        console.log('__reject finally');
    });

// 测试 static resolve
__Promise
    .resolve('mxin')
    .__then(res => console.log('static resolve:', res))
    .__finally(() => console.log('static resolve finally'));

// 测试 static reject
__Promise
    .reject(new Error())
    .__catch(res => console.log('static reject:', res))
    .__finally(() => console.log('static reject finally'));

// 测试 all,可添加 p3 测试 rejected 状态
__Promise
    .all([p1, p2])
    .__then(res => console.log('all resolve:', res))
    .__catch(e => console.log('all reject', e))
    .__finally(() => console.log('all finally'));

// 测试 race,速度快的优先返回并结束, 添加 p3 优先 reject
__Promise
    .race([p1, p2])
    .__then(res => console.log('race resolve:', res))
    .__catch(e => console.log('race reject', e))
    .__finally(() => console.log('race finally'));

2. async/await

const NEXT = 'next';
const THROW = 'throw';
/**
 * 模拟 async 函数
 * 1.generator 分割代码片段
 * 2.使用一个函数让其自迭代
 * 3.使用 promise 将 yield 包裹起来
 * 4.执行下一步的时机由 promise 来控制
 * @param {*} fn
 */
function __async(fn) {
    return function () {
        // 获取迭代器实例
        const gen = fn.apply(this, arguments);

        return new Promise((resolve, reject) => {
            // 执行下一步
            function _next(value) {
                __step(gen, resolve, reject, _next, _throw, NEXT, value);
            }
            // 抛异常
            function _throw(err) {
                __step(gen, resolve, reject, _next, _throw, THROW, err);
            }
            // 首次触发
            _next(void 0);
        });
    };
}

/**
 * 执行迭代步骤,处理下次迭代结果
 * 1.将所有值promise化
 * 2.当 promise 执行完之后再执行下一步
 * 3.递归调用 next 函数,直到 done == true
 */
function __step(gen, resolve, reject, _next, _throw, key, arg) {
    try {
        var info = gen[key](arg);
        var value = info.value;
    } catch (error) {
        return reject(error);
    }
    // 迭代完成
    if (info.done) {
        resolve(value);
    } else {
        Promise.resolve(value).then(_next, _throw);
    }
}

// ------------------------------ 测试 ------------------------------
console.log('async');

__async(function* () {
    const e = yield new Promise(resolve =>
        setTimeout(() => {
            resolve('e');
        }, 1000)
    );
    const a = yield Promise.resolve('a');
    const d = yield 'd';
    const b = yield Promise.resolve('b');
    const c = yield Promise.resolve('c');
    return [a, b, c, d, e];
})().then(
    res => console.log(res) // ['a', 'b', 'c', 'd', 'e']
);

3. 并发

/**
 * 异步分片处理并发
 * 1.通过 limitNum 限制并发的 promise 数量
 * 2.临时结果保存到 resArr 中
 * 3.start 返回 promise,全部执行完毕 finally 中 resolve 最终结果
 */
class Limit {
    constructor(limitNum, promiseList) {
        this.resArr = [];
        this.handling = 0;
        this.resolvedNum = 0;
        this.limitNum = limitNum;
        this.promiseList = promiseList;
        this.runTime = this.promiseList.length;
    }

    handle(promise) {
        console.log(promise, this.handling);
        return new Promise((resolve, reject) => {
            promise.then(res => resolve(res)).catch(e => reject(e));
        });
    }

    start() {
        const __this = this;
        return new Promise(resolve => {
            const run = () => {
                if (!__this.promiseList.length) return;
                __this.handling += 1;
                __this
                    .handle(__this.promiseList.shift())
                    .then(res => {
                        __this.resArr.push(res);
                    })
                    .catch(e => {
                        const error = new Error(e);
                        __this.resArr.push(error);
                    })
                    .finally(() => {
                        __this.handling -= 1;
                        __this.resolvedNum += 1;
                        if (__this.resolvedNum === __this.runTime) {
                            resolve(__this.resArr);
                        }
                        run();
                    });
            };

            for (let i = 1; i <= __this.limitNum; i++) {
                run();
            }
        });
    }
}

// ------------------------------ 测试 ------------------------------
console.log('Limit');

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000);
});
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2);
    }, 1000);
});
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(3);
    }, 2000);
});
const p4 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(4);
    }, 2000);
});
const p5 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(5);
    }, 3000);
});
const p6 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(6);
    }, 3000);
});
const promiseList = [p1, p2, p3, p4, p5, p6];

const limit = new Limit(2, promiseList);

limit.start().then(res => {
    console.log(res);
});

4. 发布/订阅

/**
 * 事件订阅/发布
 * 1.on 收集 key 对应的回调函数依赖关系,存入 eventList
 * 2.emit 根据第一个参数判断 key 值,并执行其函数依赖
 * 3.remove 根据 key 值清空依赖
 */
class __Event {
    constructor() {
        this.eventList = [];
    }

    on(key, fn) {
        if (!this.eventList[key]) this.eventList[key] = [];
        this.eventList[key].push(fn);
    }

    emit() {
        const key = [].shift.call(arguments);
        const fns = this.eventList[key];

        if (!fns || fns.length === 0) return false;

        for (const fn of fns) {
            fn.apply(this, arguments);
        }
    }

    remove(key) {
        if (!this.eventList[key]) return false;
        this.eventList[key] = null;
        delete this.eventList[key];
    }
}

// ------------------------------ 测试 ------------------------------
// Event
console.log('Event');

const __event = new __Event();

__event.on('name', val => {
    console.log(`info: ${val}`);
    // info: mxin
});

__event.on('name', val => {
    console.log(`info2: ${val}`);
    // info2: mxin
});

// 触发事件,上面两个回调执行对应代码
__event.emit('name', 'mxin');

// 移除事件
__event.remove('name');

// 事件被移除,不再触发
__event.emit('name', 'mxin');

技巧

1. 防抖

/**
 * 防抖
 * 事件高频触发,间隔 wait 时长执行回调
 * @param {*} fn
 * @param {*} wait
 */
function debounce(fn, wait) {
    let timeout;
    return function () {
        let __this = this,
            args = arguments;
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(__this, args);
        }, wait);
    };
}

// ------------------------------ 测试 ------------------------------

// debounce()
console.log('debounce()');

window.onresize = debounce(function () {
    console.log('改变窗口大小完毕 1000ms 后触执行');
}, 1000);

2. 节流

/**
 * 节流
 * 高频事件触发,间隔 delay 时间执行一次回调
 * @param {*} fn
 * @param {*} delay
 */
function throttle(fn, delay) {
    const prevTime = Date.now();
    return function () {
        const curTime = Date.now();
        if (curTime - prevTime > delay) {
            fn.apply(this, arguments);
            prevTime = curTime;
        }
    };
}

// ------------------------------ 测试 ------------------------------

// throttle()
console.log('throttle()');

window.onresize = throttle(function () {
    console.log('间隔 1000ms 执行一次');
}, 1000);

3. 柯里化

/**
 * 柯里化
 * 把接受多个参数的函数变换成接受一个单一参数的函数
 * 并返回接受余下的参数且返回结果的新函数
 */
function curry() {
    const args = [...arguments];

    const fn = function () {
        args.push(...arguments);
        return fn;
    };

    fn.toString = () => {
        return args.reduce((pre, current) => pre + current);
    };
    return fn;
}

// ------------------------------ 测试 ------------------------------

// curry
console.log('curry()');

console.log(curry(1)(2)(3)); // 6
console.log(curry(1, 2, 3)(4)); // 10
console.log(curry(1)(2)(3)(4)(5)); // 15
console.log(curry(2, 6)(1)); // 9

Vue

1. Reactive

参考 Vue 3.0 的实现方式

  • reactive 创建响应式对象
  • effect 副作用
  • computed 计算属性

具体实现思路及演示可以看之前写过的一篇文章,点击传送

const reactiveMap = new WeakMap();
const targetMap = new WeakMap();
const effectStack = [];

/**
 * 副作用函数
 * @param {*} fn
 */
function effect(fn) {
    try {
        // 将需要执行的effect入栈
        effectStack.push(fn);

        // 执行该effect,进入proxy的get拦截
        return fn();
    } finally {
        // 依赖收集完毕及所有get流程走完,当前effect出栈
        effectStack.pop();
    }
}

/**
 * 依赖收集
 * @param {*} target
 * @param {*} key
 */
function track(target, key) {
    // 初始化依赖Map
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }

    // 第二层依赖使用Set存放key对应的effect
    let dep = depsMap.get(key);
    if (!dep) {
        targetMap.get(target).set(key, (dep = new Set()));
    }

    // 取当前栈中的effect存入第二层依赖中
    const activeEffect = effectStack[effectStack.length - 1];
    activeEffect && dep.add(activeEffect);
}

/**
 * 触发响应,执行effect
 * @param {*} target
 * @param {*} key
 */
function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (depsMap) {
        const effects = depsMap.get(key);
        effects && effects.forEach(run => run());
    }
}

/**
 * 定义响应式对象,返回proxy代理对象
 * @param {*} object
 */
function reactive(object) {
    if (reactiveMap.has(object)) return reactiveMap.get(object);

    const proxy = new Proxy(object, handlers);

    reactiveMap.set(object, proxy);
    return proxy;
}

/**
 * 处理器对象,定义捕获器
 */
const handlers = {
    set(target, key) {
        Reflect.set(...arguments);
        trigger(target, key);
    },
    get(target, key) {
        track(target, key);
        return typeof target[key] === 'object'
            ? reactive(target[key])
            : Reflect.get(...arguments);
    },
};

/**
 * 计算属性
 * @param {*} fn
 */
function computed(fn) {
    return {
        get value() {
            return effect(fn);
        },
    };
}

module.exports = {
    effect,
    reactive,
    computed,
};

参考资料

本文地址:H5W3 » 【前端技术】面试系列之JavaScript手写代码篇

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址