JS - 手写代码(面试)

238 阅读3分钟

原理都是利用闭包保存变量

防抖

  • 防抖是任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行,一般用于输入框实时搜索
// 防抖
function debounce(fn,time){
    let timer = null;
    return function(){
        if(timer){
            clearTimeout(timer)
        }
        timer = setTimeout(()=>{
            fn.apply(this,arguments)
        },time)
    }
}

节流

  • 节流是规定函数在指定的时间间隔内只执行一次,一般用于scroll事件
// 节流
function throttle(fn,time){
    let canRun = true;
    return function(){
        if(!canRun){
            return
        }
        canRun = false;
        setTimeout(() => {
            fn.apply(this,arguments);
            canRun = true;
        },time)
    }
}

深拷贝

// 深拷贝(deepClone) 对象深拷贝
const deepClone = function (obj) {
    var result = Array.isArray(obj) ? [] : {};
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                result[key] = deepClone(obj[key])
            } else {
                result[key] = obj[key];
            }
        }
    }

    return result;

}
// 数组深拷贝 
const deepArrClone = function (arr) {
    if (Array.isArray(arr)) {
        return JSON.parse(JSON.stringify(arr));
    }
}

数组乱序

const mixArr = function(arr){
    return arr.sort(() => {
        return Math.random() - 0.5;
    })
}

数组去重

const removeDup = function(arr){
    return Array.from(new Set(arr));
}

数组flat(展平)

const flattenByDeep = function (array, deep) {
    var result = [];
    for (let i = 0; i < array.length; i++) {
        if(Array.isArray(array[i]) && deep >= 1){
            result.concat(flattenByDeep(array[i]), deep-1)
        }else{
            result.push(array[i])
        }
    }

    return result;
}

call && apply && bind

call实现
Function.prototype.call = function(context){
    if(typeof this !== 'function'){
        throw new TypeError(`${this} is not a function`);
    }

    context = context || window;
    context.fn = this; 
    const args = [...arguments].slice(1);
    const result = context.fn(...args);
    
    delete context.fn;
    return result;
}
apply实现
Function.prototype.apply = function(context){
    if(typeof this !== 'function'){
        throw new TypeError(`${this} is not a function`);
    }

    context = context || window;
    context.fn = this; 
    var result;
    if(arguments[1].length){
        result = context.fn(...arguments[1])
    }else{
        result = context.fn()
    }

    return result;
}
bind实现
Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    const _this = this
    const args = [...arguments].slice(1)
    // 返回函数
    return function F() {
        // 1 判断是否用作构造函数
        if (this instanceof F) {
            return new _this(...args, ...arguments)
        }
        // 2 用作普通函数
        return _this.apply(context, args.concat(...arguments))
    }
}

观察者(发布-订阅)模式

  • 作用: 当一个对象的状态发生改变时,能够自动通知其他关联对象,自动刷新对象状态
  • 也就是提供给关联对象一种同步通信的手段,使其状态与依赖它的其他对象保持同步通信
/**
 * 发布订阅模式(观察者模式)
 * handles: 事件处理函数集合
 * on: 订阅事件
 * emit: 发布事件
 * off: 删除事件
**/

class PubSub {
    constructor() {
        this.handles = {};
    }

    // 订阅事件
    on(eventType, handle) {
        if (!this.handles.hasOwnProperty(eventType)) {
            this.handles[eventType] = [];
        }
        if (typeof handle == 'function') {
            this.handles[eventType].push(handle);
        } else {
            throw new Error('缺少回调函数');
        }
        return this; // 实现链式调用
    }

    // 发布事件
    emit(eventType, ...args) {
        if (this.handles.hasOwnProperty(eventType)) {
            this.handles[eventType].forEach((item, key, arr) => {
                item.apply(null, args);
            })
        } else {
            throw new Error(`"${eventType}"事件未注册`);
        }
        return this;
    }

    // 删除事件
    off(eventType, handle) {
        if (!this.handles.hasOwnProperty(eventType)) {
            throw new Error(`"${eventType}"事件未注册`);
        } else if (typeof handle != 'function') {
            throw new Error('缺少回调函数');
        } else {
            this.handles[eventType].forEach((item, key, arr) => {
                if (item == handle) {
                    arr.splice(key, 1);
                }
            })
        }
        return this; // 实现链式操作
    }
}

实现instanceof

  • instanceof功能: instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
// instanceof
const myInstanceOf = function(left, right){
    let proto = left.__proto__;
    let protoType = right.prototype;
    while(true){
        if(proto === null){
            return false;
        }
        if(proto === protoType){
            return true;
        }

        proto = proto.__proto__;
    }
}

new的过程

  1. 创建一个空的简单 JavaScript 对象(即{});
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  3. 将步骤1新创建的对象作为 this 的上下文 ;
  4. 如果该函数没有返回对象,则返回 this。

以 var child = new Parent()为例:

function newParent(){
    var obj = {}; // 首先创建一个对象
    obj.__proto__ = Parent.prototype; // 然后将该对象的__proto__属性指向构造函数的protoType
    var result = Parent.call(obj) // 执行构造函数的方法,将obj作为this传入
    return typeof(result) == 'object' ?  result : obj
}

链式调用、任务队列、流程控制

// 链式调用、任务队列、流程控制
function _LazyMan(name) {
    this.name = name;
    this.queue = [];
    this.queue.push(() => {
        console.log(`Hi! This is ${this.name}!`);
        this.next()
    })
}

_LazyMan.prototype.sleep = function (time) {
    setTimeout(() => {
        console.log(`Wake up after ${time}`);
        this.next();
    }, time)
    return this;
}

_LazyMan.prototype.eat = function (someFood) {
    console.log(`Eat ${someFood}~`);
    this.next();
    return this;
}

_LazyMan.prototype.sleepFirst = function (time) {
    this.queue.unshift(() => {
        setTimeout(() => {
            console.log(`Wake up after ${time}`);
            this.next();
        }, time)
    })
    return this;
}

_LazyMan.prototype.next() = function () {
    let fn = this.queue.shift();
    fn && fn();
}

function LazyMan(name) {
    _LazyMan(name);
}

函数柯里化

函数柯里化: 把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术,是高阶函数的一种用法

// 函数柯里化
function currying(fn,...args){
    console.log(fn.length)
    if(fn.length <= args.length){
        return fn(...args)
    }
    return function(...args1){
        console.log(...args,...args1, '---------')
        return currying(fn,...args,...args1)
    }
}

curry的性能:

  • 存取arguments对象通常要比存取命名参数要慢一点
  • 一些老版本的浏览器在arguments.length的实现上是相当慢的
  • 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上