JavaScript 专题之 惰性函数

发布于 2022-05-16 12:38:03 字数 2056 浏览 1256 评论 16

需求

我们现在需要写一个 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

亣腦蒛氧 2022-05-04 13:50:21

其实就是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});
}

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

一生独一 2022-05-04 13:50:21

妙啊

太傻旳人生 2022-05-04 13:50:21

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

荒芜了季节。 2022-05-04 13:50:21

妙,之前就这样写过,不过就与 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);
    }
  }
})();
过潦 2022-05-04 13:50:21

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

我们现在需要写一个 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 函数 还是需要每次执行都进行判断

舟遥客 2022-05-04 13:50:21

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

半边脸i 2022-05-04 13:50:21

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);
      }
    };
  }
})();
烂柯人 2022-05-04 13:49:43
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 是指向匿名函数的变量,之前的教程貌似没有介绍。

倾城°AllureLove 2022-05-04 13:49:32
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.addEventListenerwindow.attachEvent都不存在时,就是死循环了吧

小情绪 2022-05-04 13:48:43

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

我们现在需要写一个 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
  }
}
吖咩 2022-05-04 13:47:46

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

哦 是啊 明白了

ぃ弥猫深巷。 2022-05-04 13:02:43

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

临走之时 2022-05-04 11:20:30
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) 
}

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

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

烦人精 2022-05-04 08:28:19
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) 
}

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

2022-05-04 03:30:09

@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

野の 2022-05-04 03:21:43
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);
        }
    }
}

或者立即执行它

~没有更多了~

关于作者

半透明的墙

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文