模拟实现 JavaScript 常用内置函数

By bobo 2021-12-15 19:58:50 文章分类:JavaScript

防抖

function debounce(fn, delay) {
    let timer = null

    return function () {
        let context = this
        let args = arguments

        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(function () {
            fn.apply(context, args)
        }, delay)
    }
}

节流

function throttle(fn, interval) {
    let last = 0

    return function () {
        let args = arguments
        let context = this
        let now = Date.now()

        if (now - last > interval) {
            last = now
            fn.apply(context, args)
        }
    }
}

call

/**
 * call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
 *
 * @param context
 * @returns {any}
 */
Function.prototype.myCall = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }

    const args = [...arguments].slice(1)
    context = context || window

    const symbol = Symbol()
    context[symbol] = this

    const res = context[symbol](...args)
    delete context[symbol]

    return res
}

apply

/**
 * apply() 方法调用一个具有给定this值的函数,
 * 以及以一个数组(或类数组对象)的形式提供的参数。
 *
 * @param context
 * @returns {any}
 */
Function.prototype.myApply = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }

    context = context || window
    const symbol = Symbol()
    context[symbol] = this

    let res = arguments[1] ? context[symbol](...arguments[1]) : context[symbol]()
    delete context[symbol]

    return res
}

bind

/**
 * bind() 方法创建一个新的函数,在 bind() 被调用时,
 * 这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
 * @param context
 * @returns {(function(): (*))|*}
 */
Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }

    const _this = this
    const args = [...arguments].slice(1)

    return function F() {
        if (this instanceof F) {
            return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
    }
}

new

/**
 * new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
 *
 * @param F
 * @param args
 * @returns {*}
 */
function myNew(F, ...args) {
    let o = {}
    Object.setPrototypeOf(o, F.prototype)
    let res = F.apply(o, args)

    return res instanceof Object ? res : o
}

Object.create()

关于调用构造函数时会执行的操作,规范明确提到了这一点:

If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.

/**
 /**
 * Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__
 *
 * @param proto
 * @param properties
 * @returns {F}
 */
function myObjectCreate(proto, properties) {
    if (typeof proto !== 'object' && proto !== null) {
        throw new TypeError('Object prototype may only be an Object or null')
    }

    function F() {}
    F.prototype = proto
    const f = new F()

    // If Type(proto) is not Object,
    // set the [[Prototype]] internal property of obj to the standard
    // built-in Object prototype object as described in 15.2.4.
    if (proto === null) {
        Object.setPrototypeOf(f, proto)
    }

    const isObject = o => Object.prototype.toString.call(o) === '[object Object]'
    if (isObject(properties)) {
        for (let key in properties) {
            if (Object.prototype.hasOwnProperty.call(properties, key) && !isObject(properties[key])) {
                throw new TypeError('Property description must be an object')
            }
        }
        // Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
        Object.defineProperties(f, properties)
    }

    return f
}


instanceof

/**
 * 模拟实现 instanceof 操作符
 * instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
 *
 * @param L 实例对象
 * @param R 构造函数
 * @return L 实例对象的的原型链是否出现在 R 构造函数的 prototype 上
 * @private
 */
function _instanceof(L, R) {
    // 传入是 undefined 或 null 直接 返回 false
    if (L == null) return false
    // 取左边对象的 __proto__
    L = L.__proto__
    // 取右边构造函数的 prototype
    R = R.prototype

    while (true) {
        if (L == null) return false
        if (L === R) return true

        L = L.__proto__
    }
}

Object.assign

if (typeof Object.assign != 'function') {
    // Must be writable: true, enumerable: false, configurable: true
    Object.defineProperty(Object, "assign", {
        value: function assign(target, varArgs) { // .length of function is 2
            'use strict';
            if (target == null) { // TypeError if undefined or null
                throw new TypeError('Cannot convert undefined or null to object');
            }

            let to = Object(target);

            for (var index = 1; index < arguments.length; index++) {
                var nextSource = arguments[index];

                if (nextSource != null) { // Skip over if undefined or null
                    for (let nextKey in nextSource) {
                        // Avoid bugs when hasOwnProperty is shadowed
                        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                            to[nextKey] = nextSource[nextKey];
                        }
                    }
                }
            }
            return to;
        },
        writable: true,
        configurable: true
    });
}