Visitar URL original
JavaScript专题之惰性函数 · Issue #44 · mqyqingfeng/Blog · GitHub
Skip to content

JavaScript专题之惰性函数 #44

Description

@mqyqingfeng

需求

我们现在需要写一个 foo 函数,这个函数返回首次调用时的 Date 对象,注意是首次。

解决一:普通方法

var t;
function foo() {
    if (t) return t;
    t = new Date()
    return t;
}

问题有两个,一是污染了全局变量,二是每次调用 foo 的时候都需要进行一次判断。

解决二:闭包

我们很容易想到用闭包避免污染全局变量。

var foo = (function() {
    var t;
    return function() {
        if (t) return t;
        t = new Date();
        return t;
    }
})();

然而还是没有解决调用时都必须进行一次判断的问题。

解决三:函数对象

函数也是一种对象,利用这个特性,我们也可以解决这个问题。

function foo() {
    if (foo.t) return foo.t;
    foo.t = new Date();
    return foo.t;
}

依旧没有解决调用时都必须进行一次判断的问题。

解决四:惰性函数

不错,惰性函数就是解决每次都要进行判断的这个问题,解决原理很简单,重写函数。

var foo = function() {
    var t = new Date();
    foo = function() {
        return t;
    };
    return foo();
};

更多应用

DOM 事件添加中,为了兼容现代浏览器和 IE 浏览器,我们需要对浏览器环境进行一次判断:

// 简化写法
function addEvent (type, el, fn) {
    if (window.addEventListener) {
        el.addEventListener(type, fn, false);
    }
    else if(window.attachEvent){
        el.attachEvent('on' + type, fn);
    }
}

问题在于我们每当使用一次 addEvent 时都会进行一次判断。

利用惰性函数,我们可以这样做:

function addEvent (type, el, fn) {
    if (window.addEventListener) {
        addEvent = function (type, el, fn) {
            el.addEventListener(type, fn, false);
        }
    }
    else if(window.attachEvent){
        addEvent = function (type, el, fn) {
            el.attachEvent('on' + type, fn);
        }
    }
}

当然我们也可以使用闭包的形式:

var addEvent = (function(){
    if (window.addEventListener) {
        return function (type, el, fn) {
            el.addEventListener(type, fn, false);
        }
    }
    else if(window.attachEvent){
        return function (type, el, fn) {
            el.attachEvent('on' + type, fn);
        }
    }
})();

当我们每次都需要进行条件判断,其实只需要判断一次,接下来的使用方式都不会发生改变的时候,想想是否可以考虑使用惰性函数。

重要参考

Lazy Function Definition Pattern

专题系列

JavaScript专题系列目录地址:https://github.com/mqyqingfeng/Blog。

JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

Activity

  1. fi3ework commented on Nov 3, 2017

    @fi3ework
    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            addEvent = function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            addEvent = function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
    }

    如果我没理解错,这段函数,在第一次执行的时候,addEvent并不会绑定事件,只是对addEvent重新赋值了一次,这样修改如何?

    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            el.addEventListener(type, fn, false);
            addEvent = function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            el.attachEvent('on' + type, fn);
            addEvent = function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
    }

    或者立即执行它

  2. mqyqingfeng commented on Nov 8, 2017

    @mqyqingfeng
    OwnerAuthor

    @fi3ework 感谢补充哈~ 确实是这样的,第一次并会不绑定事件,所以其实还需要先执行一次:

    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            addEvent = function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            addEvent = function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
    }
    
    addEvent();

    然后再使用 addEvent 绑定事件

    不过你补充的这种方法非常好,就不用再执行一次了~ o( ̄▽ ̄)d

  3. forzalianjunting commented on Jul 12, 2018

    @forzalianjunting
    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            addEvent = function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            addEvent = function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
        addEvent(type, el, fn) 
    }

    其实最后直接执行一次就好了

  4. lishihong commented on Dec 6, 2018

    @lishihong
    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            addEvent = function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            addEvent = function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
        addEvent(type, el, fn) 
    }

    其实最后直接执行一次就好了

    怎么这么多人赞同的啊,这样写不是死循环了吗。。。

  5. forzalianjunting commented on Dec 6, 2018

    @forzalianjunting

    @lishihong addEvent经过条件判定后已经被重写了

  6. lishihong commented on Dec 6, 2018

    @lishihong

    @lishihong addEvent经过条件判定后已经被重写了

    哦 是啊 明白了

  7. DarkoPeng commented on Dec 18, 2018

    @DarkoPeng

    感觉用这个例子来引入惰性函数不是很适合

    我们现在需要写一个 foo 函数,这个函数返回首次调用时的 Date 对象,注意是首次。

    这个需求应该是用 once 函数实现比较好,不过 addEvent 的例子是适合惰性函数的。

    function once(fn) {
      var fire, ret
      return function() {
        var self = this
        if (!fire) {
          fire = true
          ret = fn.apply(self, arguments)
        }
        return ret
      }
    }
  8. panyanbin commented on Apr 4, 2019

    @panyanbin
    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            addEvent = function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            addEvent = function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
        addEvent(type, el, fn) 
    }

    其实最后直接执行一次就好了

    怎么这么多人赞同的啊,这样写不是死循环了吗。。。


    如果存在这么一款浏览器

    window.addEventListener和window.attachEvent都不存在时,就是死循环了吧

  9. Mcqueengit commented on May 17, 2019

    @Mcqueengit
    var foo = function() {  //假设这个是匿名函数A
        var t = new Date();
        foo = function() {   //假设这个是匿名函数B
            return t;
        };
        return foo();
    };
    foo();   //一个时间
    foo();   //同样的事件
    

    如上代码,前后两次执行 foo() 返回同样的时间,我认为应该是利用了闭包的特性。
    如果按照您在JavaScript深入之执行上下文中的讲解,那此处的执行过程应该怎么描述呢(注:以下描述只描述到了执行第一个 foo() )。
    1.进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
    2.全局执行上下文初始化
    接下来就有点不清楚了,是?
    3.执行 foo 指向的匿名函数 A ,创建 匿名函数 A 执行上下文,匿名函数 A 执行上下文被压入执行上下文栈
    4.匿名函数 A 执行上下文初始化,创建变量对象、作用域链、this等

     anonymousAContext = {
            AO: {
                arguments: {
                    length: 0
                },
                t: undefined,
            },
            Scope: [AO,  globalContext.VO],
            this: undefined
    }
    

    5.执行 foo 指向的匿名函数 B ,创建 匿名函数 B 执行上下文,匿名函数 B 上下文被压入执行上下文栈
    6.匿名函数 B 执行上下文初始化,创建变量对象、作用域链、this等

     anonymousBContext = {
            AO: {
                arguments: {
                    length: 0
                },
            },
            Scope: [AO, anonymousAContext.AO, globalContext.VO],
            this: undefined
     }
    

    7.匿名函数 B 执行,沿着作用域链查找 t 值,返回 t 值
    8.匿名函数 B 函数执行完毕,匿名函数 B 函数上下文从执行上下文栈中弹出
    9.匿名函数 A 函数执行完毕,匿名函数 A 执行上下文从执行上下文栈中弹出

    而之所以形成闭包,是因为步骤 4 至步骤 5 中,对 foo 进行了重新赋值,从而让 foo 所指向函数的 [[Scope]] 值为 [ anonymousAContext.AO, globalContext.VO] 。
    不知道这样的解释是否正确,对于 foo 是指向匿名函数的变量,之前的教程貌似没有介绍。

  10. gtandsn commented on Jan 13, 2020

    @gtandsn

    其实就是foo的指向发生了变化
    最开始初始化的时候
    foo指向
    function () {
    var t = new Date()
    foo = function () {
    console.log(the same time: ${t});
    }
    return foo();
    }
    运行完成以后
    foo指向
    foo = function () {
    console.log(the same time: ${t});
    }

    还有就是一些闭包在起作用

  11. wubianluoye commented on May 11, 2020

    @wubianluoye

    妙啊

  12. ferrinweb commented on May 17, 2020

    @ferrinweb

    妙,之前就这样写过,不过就与 const 无缘了。

  13. anjina commented on Dec 13, 2020

    @anjina

    妙,之前就这样写过,不过就与 const 无缘了。

    改成自执行函数就可以把。

    const addEvent = (function(type, el, fn) {
      if(window.addEventListener) {
        return function(type, el, fn) {
          el.addEventListener(type, fn, false);
        }
      }
    
      if(window.attachEvent) {
        return function(type, el, fn) {
          el.attachEvent('on' + type, fn);
        }
      }
    })();
  14. anjina commented on Dec 13, 2020

    @anjina

    感觉用这个例子来引入惰性函数不是很适合

    我们现在需要写一个 foo 函数,这个函数返回首次调用时的 Date 对象,注意是首次。

    这个需求应该是用 once 函数实现比较好,不过 addEvent 的例子是适合惰性函数的。

    function once(fn) {
      var fire, ret
      return function() {
        var self = this
        if (!fire) {
          fire = true
          ret = fn.apply(self, arguments)
        }
        return ret
      }
    }

    个人觉得惰性函数是 通过改写函数来 避免多次做不必要的判断, once 函数 还是需要每次执行都进行判断

  15. cw84973570 commented on Dec 30, 2020

    @cw84973570

    学习了,这两篇都简单了好多,之前看柯里化看得头大

  16. dongxiaosun commented on May 10, 2021

    @dongxiaosun

    element-ui 处理 dom 事件的源码就是这样写的。

    export const on = (function() {
      if (!isServer && document.addEventListener) {
        return function(element, event, handler) {
          if (element && event && handler) {
            element.addEventListener(event, handler, false);
          }
        };
      } else {
        return function(element, event, handler) {
          if (element && event && handler) {
            element.attachEvent('on' + event, handler);
          }
        };
      }
    })();
  17. BlueStoneQ commented on Jun 14, 2022

    @BlueStoneQ

    @fi3ework @mqyqingfeng
    我是这样改了一下:

    function addEvent (type, el, fn) {
      if (window.addEventListener) {
        // 在第一次调用时 根据特征进行重新定义 
        addEvent = function(type, el, fn) {
          el.addEventListener(type, fn, false);
        }
      }
    
      if (window.attachEvent) {
        addEvent = function(type, el, fn) {
          el.attachEvent('on' + type, fn);
        }
      }
    
      // me: 我觉得应该有调用这一句,此时调用的已经是重新定义过的addEvent 不会无限递归
      addEvent(type, el, fn);
    }
  18. ferrinweb commented on Jun 15, 2022

    @ferrinweb

    妙,之前就这样写过,不过就与 const 无缘了。

    改成自执行函数就可以把。

    const addEvent = (function(type, el, fn) {
      if(window.addEventListener) {
        return function(type, el, fn) {
          el.addEventListener(type, fn, false);
        }
      }
    
      if(window.attachEvent) {
        return function(type, el, fn) {
          el.attachEvent('on' + type, fn);
        }
      }
    })();

    这样就不是惰性函数了。

  19. YuFengjie97 commented on Dec 18, 2024

    @YuFengjie97

    妙妙妙

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions