防抖、节流、发布订阅、apply、call、bind

防抖(dobeunce)

适用场景:

  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
  • 实时搜索,只执行最后一次输入的。

单位时间内执行最后一次

const debounce = (fn,delay) =>{
    let timer = null;
    return (...args)=>{
        clearTimeout(timer);
        timer = setTimeout(()=>{
            fn.apply(this,args);
        },delay)
    }
}
const debounce = (fn,delay) =>{
    let timer = null;
    return (...args)=>{
        clearTimeout(timer);
        timer = setTimeout(()=>{
            fn.apply(this,args);
        },delay)
    }
}

节流 (throttle)

使用场景:

  • 拖拽:固定时间内只执行一次,防止高频次出发位置变动
  • 缩放:监控浏览器的resize
  • 动画: 避免短时间内多次出发动画引起性能问题
const throttle = (fn,delay=200) => {
    let flag = true;
    return (...args)=>{
        if(!flag) return false;
        flag = false;
        setTimeout(()=>{
            fn.apply(this,args);
            flag = true;
        },delay);
    }
}
const throttle = (fn,delay=200) => {
    let flag = true;
    return (...args)=>{
        if(!flag) return false;
        flag = false;
        setTimeout(()=>{
            fn.apply(this,args);
            flag = true;
        },delay);
    }
}

单位时间内只执行第一次

Event

Event bus 是node中各个模块的基石,又是组件间通信的依赖手段之一,同时涉及到了订阅发布的设计模式

class EventEmeitter {
    constructor(){
        this.events = new Map();
        this._maxListeners = this._maxListenners || 10;
    }
    emit(type,...args){
       let handler;
           handler = this.events.get(type);
           if(Array.isArray(handler)){
               for(let i = 0; i < handler.length; i++){
                   if(args.length>0){
                       handler[i].apply(this,args);
                   }else{
                       handler[i].apply(this);
                   }
               }
           }else{
               if(args.length>0){
                   handler.apply(this,args);
               }else{
                   handler.apply(this);
               }
           }
           return true;
    }
    
    addEventListener(type,fn){
        const handler = this.events.get(type); // 获取对应事件名称的函数
            if(!handler){
                this.events.set(type,fn);
            }else if(handler && typeof handler === 'function'){
                // 如果handler是函数说明当前只有一个监听函数
                this.events.set(type,[handler,fn]);
            }else{
                if(handler.length <= this._maxListenners) return handler.push(fn); // 添加多个监听有上限
        }
    }
    
    removeListener(type, fn){
        const handler = this.events.get(type); // 获取对应事件名称的函数
            // 如果是函数,说明只被监听了一次
            if(handler && typeof handler === 'function'){
                this.events.delete(type,fn);
            }else{
                // 如果handler是数组,说明被多次监听,需要找到对应的函数的位置
                let position;
                for(let i = 0 ; i< handler.length; i++){
                    if(handler[i] === fn){
                        position = i
                    }else{
                        position = -1;
                    }
                }
                // 如果找到匹配的函数
                if(position !== -1){
                    // 直接删除
                    handler.splice(position,1);
                    // 如果清除后只有一个函数,那么取消数组,以函数形式保存
                    if(handler.length ===1){
                        this.events.set(type,handler[0]);
                    }
                } else {
                    return this;
                }
            }
    }
}
class EventEmeitter {
    constructor(){
        this.events = new Map();
        this._maxListeners = this._maxListenners || 10;
    }
    emit(type,...args){
       let handler;
           handler = this.events.get(type);
           if(Array.isArray(handler)){
               for(let i = 0; i < handler.length; i++){
                   if(args.length>0){
                       handler[i].apply(this,args);
                   }else{
                       handler[i].apply(this);
                   }
               }
           }else{
               if(args.length>0){
                   handler.apply(this,args);
               }else{
                   handler.apply(this);
               }
           }
           return true;
    }
    
    addEventListener(type,fn){
        const handler = this.events.get(type); // 获取对应事件名称的函数
            if(!handler){
                this.events.set(type,fn);
            }else if(handler && typeof handler === 'function'){
                // 如果handler是函数说明当前只有一个监听函数
                this.events.set(type,[handler,fn]);
            }else{
                if(handler.length <= this._maxListenners) return handler.push(fn); // 添加多个监听有上限
        }
    }
    
    removeListener(type, fn){
        const handler = this.events.get(type); // 获取对应事件名称的函数
            // 如果是函数,说明只被监听了一次
            if(handler && typeof handler === 'function'){
                this.events.delete(type,fn);
            }else{
                // 如果handler是数组,说明被多次监听,需要找到对应的函数的位置
                let position;
                for(let i = 0 ; i< handler.length; i++){
                    if(handler[i] === fn){
                        position = i
                    }else{
                        position = -1;
                    }
                }
                // 如果找到匹配的函数
                if(position !== -1){
                    // 直接删除
                    handler.splice(position,1);
                    // 如果清除后只有一个函数,那么取消数组,以函数形式保存
                    if(handler.length ===1){
                        this.events.set(type,handler[0]);
                    }
                } else {
                    return this;
                }
            }
    }
}

call简单实现

call的作用

  • 将函数设为对象的属性
  • 执行并且删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数、默认指向window
  • 如果传入一个原始类数据,这个只会被转换为它对应的对象形式
Function.prototype.myCall = function(ctx, ...args) {
  // 兼容浏览器和node中的 this; 需要对传进来的上下文 进行处理
  // 如果是  null or undefined 需要指定默认this;
  ctx = ctx === null || ctx === undefined ? globalThis : Object(ctx)
  // Symbol 创建的key是唯一的、不重复的key. 防止与对象的key重复
  const key = Symbol('fn')
  // 定义这个 给 ctx 新增一个全局唯一的属性 key. 并设置 不能被枚举
  Object.defineProperty(ctx, key, {
    enumerable: false,
    value: this
  })
  // 把参数传入执行
  const result = ctx[key](...args)
  // 删除ctx上新创建的 属性
  delete ctx[key]
  return result
}

function myMethod(a, b) {
  console.log(this, a, b)
  return a + b
}

console.log(myMethod.myCall({name: 2}, 2, 3))
Function.prototype.myCall = function(ctx, ...args) {
  // 兼容浏览器和node中的 this; 需要对传进来的上下文 进行处理
  // 如果是  null or undefined 需要指定默认this;
  ctx = ctx === null || ctx === undefined ? globalThis : Object(ctx)
  // Symbol 创建的key是唯一的、不重复的key. 防止与对象的key重复
  const key = Symbol('fn')
  // 定义这个 给 ctx 新增一个全局唯一的属性 key. 并设置 不能被枚举
  Object.defineProperty(ctx, key, {
    enumerable: false,
    value: this
  })
  // 把参数传入执行
  const result = ctx[key](...args)
  // 删除ctx上新创建的 属性
  delete ctx[key]
  return result
}

function myMethod(a, b) {
  console.log(this, a, b)
  return a + b
}

console.log(myMethod.myCall({name: 2}, 2, 3))

apply 简单实现

apply原理与call很相似

Function.prototype.myApply = function(context,arr){
    var context = Object(context) || window;
    context.fn = this;
    var result ;
    if(!arr){
        result = context.fn()
    }else{
        var args = [];
        for(var i=0,len=arr.length;i<len; i++){
            args.push("arr["+i+"]");
        }
        result = eval("context.fn(" + args + ")");
    }
    delete context.fn;
    return result;
}
Function.prototype.myApply = function(context,arr){
    var context = Object(context) || window;
    context.fn = this;
    var result ;
    if(!arr){
        result = context.fn()
    }else{
        var args = [];
        for(var i=0,len=arr.length;i<len; i++){
            args.push("arr["+i+"]");
        }
        result = eval("context.fn(" + args + ")");
    }
    delete context.fn;
    return result;
}

bind 简单实现

实现bind分为两步

  • 返回一个函数,绑定this,传递预置参数
  • bind返回的函数可以作为构造函数使用,作为构造函数时应使得this失效,但是传入的参数依然有效
// mdn的实现
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}
// mdn的实现
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

参考链接