手写题

142 阅读8分钟

new

// 使用[`Object.__proto__`]
Function.prototype.construct = function (aArgs) {
  var oNew = {};
  oNew.__proto__ = this.prototype;
  this.apply(oNew, aArgs);
  return oNew;
};

//使用`Object.create()`方法
Function.prototype.construct = function (aArgs) {
  var oNew = Object.create(this.prototype);
  this.apply(oNew, aArgs);
  return oNew;
};

// 使用闭包
Function.prototype.construct = function(aArgs) {
  var fConstructor = this, fNewConstr = function() {
    fConstructor.apply(this, aArgs);
  };
  fNewConstr.prototype = fConstructor.prototype;
  return new fNewConstr();
}

// 使用 Function 构造器:
Function.prototype.construct = function (aArgs) {
  var fNewConstr = new Function("");
  fNewConstr.prototype = this.prototype;
  var oNew = new fNewConstr();
  this.apply(oNew, aArgs);
  return oNew;
};


手写call、apply、bind

Function.prototype.call()

  • call()方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。
Function.prototype.myCall = function(context, args){
    if(typeof context === 'undefined' || context === null){
        context = 'undefined' !== typeof window? window : globalThis
    }
    let sb = Symbol();
    context[sb] = this
     //如果context是一个对象,则对象调用对象上的方法this指向该对象
    let fn = context[sb](args)
    return fn
}
fun.myCall(obj,1,2)

Function.prototype.apply()

  • apply()方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
  • 注意:call()方法的作用和apply()方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
  • 手写apply()
Function.prototype.myApply = function(context, args){
    if(typeof context === 'undefined' || context === null){
        context = 'undefined' !== typeof window? window : globalThis
    }
    let sb = Symbol();
    context[sb] = this
     //如果context是一个对象,则对象调用对象上的方法this指向该对象
    let fn = context[sb](...args)
    return fn
}

Function.prototype.bind()

  • bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
 Function.prototype.myBind = function(context, ...args){
    if(typeof context === 'undefined' || context === null){
        context = 'undefined' !== typeof window? window : globalThis
    }
    let self = this 
    return function(){
        return self.apply(context, args.concat(Array.prototype.slice.call(arguments)))
    }
 }
 fun.myBind(obj,1,2)(3,4)

详解bind实现步骤

  1. bind是绑在原型上的方法 由于function XXX 的原型链 指向的是Function.prototype,因此我们在调用XXX.bind的时候,调用的是Function.prototype上的方法。
Function.prototype._bind = function(){}

这样,我们就可以在一个构造函数上直接调用我们的_bind方法

function myFun(){}
myFun._bind();
  1. 改变this的指向 bind最核心的特性就是改变this的指向,并且返回一个函数;改变this的指向我们可以通过apply和call实现。
  • 首先通过self来保存当前的this,也就是传入的函数,因为我们知道this具有 隐式绑定 的规则。
Function.prototype._bind = function(thisObj){
    const self = this //这里的this是调用_bind的函数
    return function(){
        self.apply(thisObj);
    }
}
function myFun(){
    console.log(this.a)
}
var obj = {a:1}
myFun._bind(obj)() //1
  1. 支持柯里化
function fn(x) {
    return function(y) {
        return x + y
    }
}
var fn1 = fn(1)
fn1(2) //3
  • 柯里化使用了闭包,当我们执行fn(1)时候,函数内使用了外层函数的x,从而形成了闭包。
  • bind函数类似,首先获取外部函数的arguments,去除绑定的对象,其他的参数保存为变量args;然后return方法内再一次获取当前函数的arguments,最终用合并外层和内存函数的参数。
Function.prototype._bind = function(thisObj){
    const self = this 
    const args = [...arguments].slice(1)
    return function(){
        const finalArgs = [...args, ...arguments]
        self.apply(thisObj, finalArgs)
    }
}
var obj = {i:1}
function myfun(a,b,c){
    console.log(this.i + a + b + c)
}
var myFun1 = myFun._bind(obj, 1, 2)
myFun1(3) //7
  1. 考虑new的调用 通过bind绑定之后的方法,依然可以通过new来实现实例化,new的优先级高于bind
  • 对比原始bind和上叙第三层_bind
// 原生
var obj = {i:1}
function myFun(a, b, c){
    // 此处用new方法,this指向的是当前函数myFun
    console.log(this.i + a + b + c);
}
var myFun1 = myFun.bind(obj,1,2);
new myFun1(3); // NAN
// 第三层的_bind
var obj = {i:1}
function myFun(a, b, c){
    console.log(this.i + a + b + c);
}
var myfun1 = myFun._bind(obj, 1, 2);
new myFun1(3); // 7
  • 因此,我们需要在bind内部,对new的进行处理,而new.target属性,正好可以用来检测构造函数方法是否是通过new运算符来被调用的。
  • 实现一个new
function _create() {
    // 1.  创建一个空的简单对象(即{});
    let obj = {}
    // 2.  链接到该对象(设置该对象的constructor)到另一个对象;
    let Constructor = [].shift.call(arguments) //获取第一个参数
    obj.__proto__ = Construtor.prototype
    // 3.  将步骤1新创建的对象作为this的上下文;
    let result = Construtor.apply(obj, arguments)
    // 4.  如果该函数没有返回对象,则返回this
    return typeof result === "object" ? retult : obj;
}
Function.prototype._bind(){
    var self = this 
    // 将参数转为数组,再剔除第一个参数
    var args = [...arguments].slice(1);
    return function(){
        var finalArgs = [...args, ...arguments];
        if(new.target !== undefined){
            var result = self.apply(this,finalArgs);
            if(result instanceof Object){
                return result;
            }
            return this;
        }else{
            // apply接收的第二个参数是类数组对象
            return self.apply(args, finalArgs); 
        }
    }
}
  1. 保留函数原型
  • 当我们的构造函数有prototype属性,我们需要将prototype属性补上;
  • 调用对象必须是函数
Function.prototype._bind(){
    // 1. 判断是否为函数调用
    if(typeof this !== 'function' || Object.prototype.toString.call(this) !== '[object Function]'){
        return new TypeError(this + 'must be a function');
    }
    // 2. 获取外层参数
    var args = [].slice(1).call([...arguments]);
    // 3. 保留this即调用_bind的函数
    var self = this 
    var bound = function(){
        // 4. 合并外层和内部的参数
        var finalArgs = [...args, ...arguments];
        // 5. 判断是否是new的形式
        if(new.target !== undefined){
            var result = self.apply(this, finalArgs);
            if(result instanceof Object){
                return result;
            }
            return this;
        }else{
            return self.apply(args, finalArgs);
        }
    }
    // 判断构造函数是否有原型
    if(self.prototype) {
        // 为什么使用了Object.create?因为我们要防止bound.prototype的修改而导致self.prototype被修改
        bound.prototype = Object.create(self.prototype);
        bound.prototype.constructor = self;
    }
    return bound;
}

订阅/发布

// 事件总线 | 发布订阅
class EventEmitter {
    constructor() {
        this.cache = {}
    }

    on(name, fn){
        if(this.cache[name]){
            this.cache[name].push(fn)
        }else{
            this.cache[name] = [fn]
        }
    }

    off(name, fn){
        const tasks = this.cache[name]
        if(tasks){
            const index = tasks.findIndex((f) => f === fn || f.callback === fn)
            if(index >= 0){
                tasks.splice(index, 1)
            }
        }
    }

    emit(name, once = false){
        // 创建副本,如果回调函数内解析注册相同事件,会造成四循环
        const tasks = this.cache[name].slice()
        for(let fn of tasks){
            fn()
        }
        if(once){
            delete this.cache[name]
        }
    }
}

// 测试
const eventEmitter = new EventEmitter()
eventEmitter.on("beforeRun", function(){
    console.log("注册1")
})

eventEmitter.on("beforeRun", function(){
    console.log("注册2")
})

eventEmitter.emit("beforeRun")


实现Promise

juejin.cn/post/695345…

juejin.cn/post/703837…

Promise.resolve

Promise.resolve2 = (value) =>{
    //是Promise实例,直接返回即可
    if(value && typeof value === 'object' && value instanceof Promise){
        return value
    }

    // 否则其他情况一律再通过Promise包装一下 
    return new Promise((resolve) => {
        resolve(value)
    })
}
// 测试1
Promise.resolve(111).then(res=>{
    console.log(res)
})

Promise.resolve2(111).then(res=>{
    console.log(res)
})

// 测试2:参数是一个Promise

const p = new Promise((resolve) =>{
    resolve(222)
})
Promise.resolve(p).then(res=>{
    console.log(res)
})
Promise.resolve2(p).then(res=>{
    console.log(res)
})

Promise.reject

Promise.reject2 = (err) =>{
    return new Promise((_, reject)=>{
        reject(err)
    })
}

// 测试1
Promise.reject(111).catch(res=>{
    console.log(res)
})
Promise.reject2(111).catch(res=>{
    console.log(res)
})


// 测试2:参数是一个Promise

const p = new Promise((resolve) =>{
    resolve(222)
})
Promise.reject(p).catch(res=>{
    console.log(res)
})
Promise.reject2(p).catch(res=>{
    console.log(res)
})


Promise.all

所有成功则成功和1个失败即失败

Promise.all2 = (promises) => {
    return new Promise((resolve, reject)=>{
        let len = promises.length
        const results = new Array(len)
        let count = 1
        // index 用于保证结果顺序
        promises.forEach((promise, index) => {
        // 对p进行一次包装,防止非Promise对象
            Promise.resolve(promise).then(res => {
                    results[index] = res
                    if(count === len) resolve(results)
                    count += 1
                }).catch(err => {
                    reject(err)
                })
        });
      
    })
}

// 测试
const p1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve(1)
    }, 1000)
})
const p2 = Promise.resolve(2)
const p3 = 3
const p4 = new Date()
const promises = [p1, p2, p3, p4]

const pp = Promise.all(promises)
pp.then(res=>{
    console.log(res)
})

const pp2 = Promise.all2(promises)
pp2.then(res=>{
    console.log(res)
}).catch(err => {
    console.log(err)
})

Promise.allSettled

Promise.allSettled2 = (promises) => {
            return new Promise((resolve, reject) => {
                let len = promises.length
                let count = 1
                const results = new Array(len)
                promises.forEach((promise, index) => {
                    // 对p进行一次包装,防止非Promise对象
                    Promise.resolve(promise).then(res => {
                        results[index] = {
                            status: 'fulfilled',
                            value: res
                        }
                        if (count++ === len) resolve(results)
                    }).catch(err => {
                        results[index] = {
                            status: 'rejected',
                            value: err
                        }
                        if (count++ === len) resolve(results)
                    })

                });
            })
        }
        
        // 测试
        const p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(1)
            }, 1000)
        })
        const p2 = Promise.resolve(2)
        const p3 = 3
        const p4 = new Date()
        const p5 = Promise.reject(111)
        const promises = [p1, p2, p3, p4, p5]

        const pp = Promise.allSettled(promises).then(res => {
            console.log(res)
        })


        const pp2 = Promise.allSettled2(promises).then(res => {
            console.log(res)
        })

Promise.race

Promise.race2 = (promises) => {
    return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
            // 对p进行一次包装,防止非Promise对象
            Promise.resolve(promise).then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        });
    })
}

// 测试
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 1000)
})
const p2 = Promise.resolve(2)
const p3 = 3
const p4 = new Date()
const p5 = Promise.reject(111)
const promises = [p1, p2, p3, p4, p5]

Promise.race(promises).then(res => {
    console.log(res)
})

Promise.race2(promises).then(res => {
    console.log(res)
})

实现Async/Await

function asyncFunc(gen){
    // async函数返回的是一个promise
    return new Promise((resolve, reject)=>{
        // 执行传入的generator函数,generator函数返回的是一个迭代器
        const iter = gen()
        // 递归迭代器
        function next(value){
            let result
            try {
                result = iter.next(value)
            } catch (error) {
                reject(error)
            }
            if(result.done) return resolve(result.value)
            Promise.resolve(result.value).then(data=>{
                next(data)
            }).catch(err => {
                reject(err)
            })
        }
        next()
    })
}

实现一个可以控制请求并发数的最高效的发送请求功能。

参考链接:

juejin.cn/post/697602…

github.com/rxaviers/as…

function limitRequest(urls, limit){
    return new Promise((reslove, reject)=>{
        let len = urls.length
        const results = new Array(len)
        while(limit > 0 && urls.length > 0 ){
            send(urls.shift(), len - urls.length - 1)
            limit -= 1
        }

        function send(url, index){
            fetch(url)
                .then(response => response.json())
                .then(res=>{
                results[index] = res
                if(urls.length > 0){
                    send(urls.shift(), len - urls.length - 1)
                }else{
                    reslove(results)
                }
            })
        }
    })
}

// 服务端
const express = require('express')
const app = express()
app.use(express.static(__dirname))

app.get('/a', function(req, res){
    let id = req.query.id
    setTimeout(()=>{
        res.setHeader("Access-Control-Allow-Origin", "*")
        res.json({code:200, message: "成功接收到" + id})
    }, id * 1000)
})
app.listen(91)

// 测试
const urls = [
    'http://127.0.0.1:91/a?id=1',
    'http://127.0.0.1:91/a?id=2',
    'http://127.0.0.1:91/a?id=3',
    'http://127.0.0.1:91/a?id=4',
    'http://127.0.0.1:91/a?id=5',
    'http://127.0.0.1:91/a?id=6',
]


limitRequest(urls, 3).then(res=>{
    console.log(res)
})

防抖节流

函数防抖和节流,都是控制事件触发频率的方法。应用场景有很多,如:输入框持续输入、多次触发点击事件、onScroll等等。

  • 防抖:指定的时间内执行多次,只执行最后一次
function debounce(fn, delay = 300){
    let timer //闭包引用的外界变量
    return function () {
        const args = arguments
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, delay);
        
    }
}
  • 节流:指定的时间内执行多次,只执行第一次
function throttle(fn, delay){
    let run = true 
    return function(){
        const args = arguments
        if(!run){
            return
        }
        run = false
        setTimeout(() => {
            fn.apply(this, args)
            run = true
        }, delay)
    }
}

浅拷贝和深拷贝

JSON.stringify()实现深拷贝

const old_obj = {
    a: 1,
    b: 'bb',
    c: new Date(),
    d: function(){},
    e: [1,2,3]
}

const new_obj = JSON.parse(JSON.stringify(old_obj))
console.log(new_obj) //{ a: 1, b: 'bb', c: '2022-02-15T09:27:14.694Z', e: [ 1, 2, 3 ] }

问题: 无法实现函数的拷贝

Web Worker实现深拷贝

// index.html
 const old_obj = {
        a: 1,
        b: 'bb',
        c: new Date(),
        // d: function d(){},
        e: [1,2,3]
    }

    const worker = new Worker('work.js')
    worker.postMessage(old_obj)
    worker.onmessage = function(message){
        const new_obj = message.data
        new_obj.a = 111
        console.log(old_obj)
        console.log(new_obj)
    }
    
    // work.js
    self.addEventListener('message', function (e) {
        self.postMessage(e.data)
    })

问题:无法实现函数的复制。old_obj对象中有函数会报错,报错如下:

Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': function d(){} could not be cloned.

lodash库实现深拷贝

const _ = require('lodash')
_.cloneDeep(old_obj)

递归遍历实现拷贝

// 使用Object.prototype.toString().call实现类型判断;其他还有typeof/instanceof
function deepClone(object){
   const type = Object.prototype.toString.call(object)
   console.log(type)
    if(type === '[object Object]'){
        const new_obj = new Object()
        for (const key in object) {
            if (Object.hasOwnProperty.call(object, key)) {
                new_obj[key] = deepClone(object[key])
            }
        }
        return new_obj
    }else if(type === '[object Array]'){
        const new_arr = new Array()
        object.forEach((element) => {
            new_arr.push(deepClone(element))
        });
        return new_arr
    }else{
        return object
    }
}

ES5实现继承

高阶函数

组合函数

柯里化(异步求和)

偏函数

惰性函数

缓存函数

数组——扁平化

使用Array.prototype.flat()

const old_arr = [1,[2,3,[4,5,[6,7]]]]
const new_arr = old_arr.flat(4)
console.log(new_arr) //[1,2,3,4,5,6,7]

数组-去重

通过转换为Set去重

const old_arr = [1,2,2,2,3,4,4]
const new_set = new Set(old_arr)
const new_arr = [...new_set]
console.log(new_arr) //[ 1, 2, 3, 4 ]

解析URL Params

用 setTimeout 实现 setInterval

输入两个数组 [1,2,3,2,1], [3,2,1,4,7]  返回公共的并且长度最长子数组的长度