面试系列——手写代码实现(一)

1,471 阅读5分钟

前言

本文是面试系列篇的实现篇。笔者整理了面试过程中可能会遇到的手写实现,以及它的原理。这可以帮助面试者在笔试环节获得良好的加分。

其他文章系列,欢迎关注我文末的公众号

正文

apply和call

apply和call的实现中,主要是利用了两个点:

  • 根据传入的thisArgs去创建对象

  • 在新创建的对象上定义当前函数,然后进行调用

apply和call的区别:

  • apply在指定对象后只接受一个参数
  • call在指定对象后可以接受多个参数

代码实现如下:

/**
 * apply模拟实现
 */
Function.prototype.myApply = function(thisArg, arrArg) {
    //#1 类型检测
    if (typeof this !== 'function') {
        throw new TypeError(`${this}.apply is not a function`);
    }
    //#2 arrArg和thisArg参数判断
    if (arrArg === undefined || arrArg === null) {
        arrArg = [];
    }
    if (thisArg === undefined || thisArg === null) {
        thisArg = _self;
    }
​
    //#3 创建对象,函数赋值,调用对象方法
    const obj = new Object(thisArg);
    obj['_fn_'] = this;
    var result = obj['_fn_'](...arrArg);
    delete obj['_fn_'];
    return result;
}
​
/**
 * call模拟实现
 */
Function.prototype.myCall = function(thisArg) {
    //#1 类型检测
    if (typeof this !== 'function') {
        throw new TypeError(`${this}.call is not a function`);
    }
​
    //#2 参数调整
    const argumentsArr = [];
    for (let i = 1; i < arguments.length; i++) {
        argumentsArr.push(arguments[i]);
    }
    if (thisArg === undefined || thisArg === null) {
        thisArg = _self;
    }
​
    //#3 创建对象,函数赋值,调用对象函数
    const obj = new Object(thisArg);
    var _fn = '_fn_';
    obj[_fn] = this;
    var result = obj[_fn](...argumentsArr);
    delete obj[_fn];
    return result;
}

bind和softBind实现

bind和softBind的区别:

  • bind的绑定对象是固定的,softBind相当于是一种软绑定【如果this为空,或者指向全局对象时,才改变函数的绑定】

bind和apply & call的区别:

  • bind返回一个函数;apply和call是调用时执行的

代码实现如下:

/**
 * bind实现
 */
Function.prototype.myBind = function(obj) {
    //#1 类型校验
    if (typeof this === 'function') {
        throw new TypeError(`${this}.bind is not a function`);
    }
​
    //#2 取参
    const args = Array.prototype.shift.call(arguments) || [];
    const fn = this;
​
    //#3 定义函数,调用apply改变this指向,同时改变新函数的原型
    var bindFn = function() {
        return this.apply(obj, [].concat(args, arguments));
    }
    bindFn.prototype = fn.prototype;
    return bindFn;
}
​
/**
 * softBind的实现
 */
Function.prototype.softBind = function(obj) {
    //#1 类型校验
    if (typeof this === 'function') {
        throw new TypeError(`${this}.softBind is not a function`);
    }
​
    //#2 取参
    const args = Array.prototype.shift.call(arguments) || [];
    const fn = this;
​
    //#3 定义函数,调用apply,此处注意对this的判断,改变新函数的原型
    var softBindFn = function() {
        return fn.apply((!this || this === (window || global) ? obj : this), [].concat(args, arguments));
    }
    softBindFn.prototype = fn.prototype;
    return softBindFn;
}

new的实现

new实现的过程:

  • 创建一个新的对象
  • 将新对象的隐式引用(proto)指向构造函数的原型
  • 通过apply调用构造函数,给这个对象赋值
  • 最后判断返回的对象是否是一个对象,然后结果

代码实现如下:

/**
 * new模拟实现
 */
function myNew() {
    //#1 创建一个空对象
    let obj = Object.create({});
    
    //#2 取出构造函数
    let Constructor = Array.prototype.unshift.call(arguments);
​
    //#3 改变新对象的隐式指向
    obj._proto_ = Constructor.prototype;
​
    //#4 调用构造函数,返回结果
    ret = Constructor.apply(obj, arguments);
​
    //#5 对结果进行判断
    return ret instanceof Object ? ret : obj;
}

instanceof的实现

instance的实现:

  • 取出隐式引用(proto),然后和原型进行比较
  • 依次往上循环,直到__proto__为null时

代码实现如下:

/**
 * 实现instanceof
 */
function instanceFn(L, R) {
    //#1 取出R的原型
    const O = R.prototype;
​
    //#2 取L的隐式指向,依次在原型链上判断
    L = L._proto_;
    while(true) {
        if (L === null) {
            //#3 当L为空时,返回false
            return false;
        } else {
            //#4 当O与L相等是,说明继承于R
            if (O === L) {
                return true;
            }
            L = L._proto_;
        }
    }
}

ES6的extend实现

ES6中的extend实现的原理:

  • 将子构造函数的原型指向父构造函数的实例
  • 然后改变原型中的构造函数(constructor)指向子构造函数
  • 然后将子构造函数的隐式指向(proto)指向父构造函数

代码实现如下:

/**
 * extend模拟实现
 */
function _inherits(subType, superType) {
    //#1 创建对象,增强对象,指定对象
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: {
            value: subType,
            writable: true,
            configurable: true,
            enumerable: false
        }
    });
​
    //#2 给子构造函数的隐式指向父构造函数
    if (superType) {
        Object.setPrototypeOf ? Object.setPrototypeOf(subType, superType) : subType._proto_ = superType;
    }
}

深拷贝和浅拷贝的实现

浅拷贝,就是只进行一层拷贝:

  • 把对象的key取出来,然通过hasOwnProperty判断,赋值

深拷贝,就是无限层级的拷贝:

  • 通过Map的形式,解决循环引用,如果对象存在,则直接返回
  • 通过getOwnPropertySymbols的方法,解决Symbol值的拷贝
  • 循环递归,拷贝对象属性

代码实现如下:

/**
 * 浅拷贝
 */
function shallowCopy(obj) {
    //#1 类型校验
    if (obj !== 'object') return obj;
​
    const target = {};
​
    //#2 遍历对象的key
    for (let key in obj) {
        //#3 判断key是否在对象上
        if (obj.hasOwnProperty(key)) {
            target[key] === obj[key];
        }
    }
    return target;
}
/**
 * 深拷贝
 */
function isObject(obj) {
    return typeof obj === 'object' && obj !== null;
}
function deepCopy(source, hash = new WeakMap()) {
    //#1 类型校验
    if (!isObject(source)) return source;
    //#2 解决循环引用
    if (hash.get(source)) return hash.get(source);
    
    //#3 创建新对象,存入hash中
    const target = Array.isArray(source) ? [] : {};
    hash.set(source, target);
​
    //#4 Symbol拷贝
    const symbolArr = Object.getOwnPropertySymbols(source);
    if (symbolArr.length) {
        symbolArr.forEach(key => {
            if (isObject(source[key])) {
                target[key] = deepCopy(source[key], hash);
            } else {
                target[key] = source[key]
            }
        })
    }
    
    //#5 遍历对象key值
    for (let key in source) {
        //#6 判断是否是对象的key
        if (source.hasOwnProperty(key)) {
            //#7 判断属性类型
            if (isObject(source[key])) {
                target[key] = deepCopy(source[key], hash);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

防抖和节流的实现

节流:事件在一段时间内只会被触发一次,多次触发,只有一次生效

防抖:在事件触发n秒后再被调用,如果在这n秒内又被触发,则重新计时

代码实现如下:

/**
 * 节流
 */
function throttle(fn, delay) {
    //#1 定义上次触发时间
    let last = 0;
    return function() {
        //#2 取参
        const args = arguments;
        const now = +new Date();
​
        //#3 判断是否超过之前的时间
        if (now < last + delay) {
            last = now;
            fn.apply(this, args);
        }
    };
}
/**
 * 防抖
 */
function debounce(fn, delay) {
    let timer;
    return function(...args) {
        //#1 判断定时器是否存在,清除定时器
        if (timer) clearTimeout(timer);
​
        //#2 重新调用setTimeout
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

Promise的all和race实现

Promise.all:谁跑得慢,以谁为准,如果有一个失败,就返回失败结果

Promise.race:谁跑得快,以谁为准

代码实现如下:

/**
 * all模拟实现
 */
Promise1.all = function (promises) {
    //#1 计时器 & 结果数组
    let count = 0;
    const res = [];
​
    //#2 传值函数,将结果放入结果数组
    function processData (index, data, resolve) {
        res[index] = data;
        count++;
        if (count === promises.length) {
            resolve(res);
        }
    }
​
    //#3 返回一个Promise
    return new Promise((resolve, reject) => {
        for (let j = 0; j < promises.length; j++) {
            //#4 遍历promises数组,调用每个promise,将结果放入结果数组
            promises[j].then(data => {
                processData(j, data, resolve);
            }, reject);
        }
    });
}
/**
 * race模拟实现
 */
Promise1.race = function (promises) {
    //#1 返回一个Promise
    return new Promise((resolve, reject) => {
        //#2 遍历promises数组,谁先返回,就用谁
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    });
}

欢迎关注博主的微信公众号,我们一起做沟通和交流