惰性函数
知识点: 函数进阶
, 函数重写
,预编译
,堆栈内存
1. 概念:
官方说法:惰性函数 表示 函数执行的分支 只会在函数第一次调用的时候执行,在第一次调用的过程中,该函数会被 覆盖 为 另一个按照书写好的函数,之后,再次调用该函数时,就不再经过之前的分支了,执行的函数, 也不是之前的函数了,而是 经过修改后的函数;
总结说:就是在执行函数时,函数他 改变 函数自己;(自己理解)。
哈哈哈,听不懂,那看一下例子吧 。
2. 背景:
为了解决浏览器的兼容问题,我们会使用 if
判断,来判断 api 是否存在,从而来执行不同的代码 。
通常以 给元素添加绑定事件 来举例子:
function addEvent(type, element, fnu) {
if (element.addEventListener) {
element.addEventListener(type, fun, false)
} else if (element.attachEvent) {
element.attachEvent('on' + type, fun)
} else {
element['on' + type] = fun
}
}
解释:
addEventListener
:兼容 谷歌,火狐等
attachEvent
:兼容 IE7, 8
都是给元素绑定事件的方法 ;判断是否存在,然后给传入的元素绑定方法,和以下的方法一样:
<body>
<button id="btn">点击</button>
</body>
<script>
const btn = document.getElementById('btn')
// 兼容 谷歌,火狐等
btn.addEventListener('click', function() {
console.log(2)
}, false)
// 不兼容 谷歌等,所以使用谷歌测试时,会报错
btn.attachEvent('onclick', function() {
console.log(3)
})
// 不太懂,好像都兼容
btn['onclick'] = function() {
console.log(21)
}
</script>
下一个,我们使用 addEvent
函数来给 元素绑定事件:
<body>
<button id="btn">点击</button>
<script>
const btn = document.getElementById('btn')
function addEvent(type, element, fun) {
if (element.addEventListener) {
element.addEventListener(type, fun, false)
} else if (element.attachEvent) {
element.attachEvent('on' + type, fun)
} else {
element['on' + type] = fun
}
}
// 我们这样调用
addEvent('click', btn, function() {
console.log(1)
})
</script>
</body>
我们这样 每次调用 都会执行 if判断
,调用一千次,就会进行一千次的判断;为了不进行那么多的判断,我们可以使用 懒惰函数 来完善这个方法;
在那之前先理解一下,函数重写
3. 函数重写:
就是在函数里面,对原函数本函数进行重新赋值,执行一次函数后,以后再调用这个函数时,调用的都是 重新赋值的函数 。
function fn () {
console.log('我是函数fn')
fn = function () {
console.log('我是重写的fn')
}
}
fn() // => 我是函数fn
fn() // => 我是重写的fn
fn() // => 我是重写的fn
- 执行函数
fn()
; - 输出
我是函数fn
- 把
fn
重新赋值一个新的函数; - 下一次调用
fn
,fn
就是我们重新赋值的这个函数了(输出 “我是重新的fn”), - 而不是之前的那个(”我是函数fn“)!
4. 惰性函数:
惰性函数没有一种具体的写法,以下有2种写法:
第一种 :
<body>
<button id="btn">点击</button>
<script>
const btn = document.getElementById('btn')
function addEvent(type, element, fun) {
if (element.addEventListener) {
console.log(1)
addEvent = function(type, element, fun) {
element.addEventListener(type, fun, false)
}
}
else if (element.attachEvent) {
console.log(2)
addEvent = function(type, element, fun) {
element.attachEvent('on' + type, fun)
}
}
else {
console.log(3)
addEvent = function(type, element, fun) {
element['on' + type] = fun
}
}
console.log(100)
return addEvent(type, element, fun)
}
// 使用函数,为元素绑定事件
addEvent('click', btn, function() {
console.log(1)
})
</script>
</body>
可以实现按钮的点击,以上的实现和 函数重写 的原理是一样的,在第一次调用 函数时,就确定了 之后函数的内容,之后调用函数,就不用执行判断,来判断当前浏览器所兼容的 语法了。只需调用一次,就明白了 。
我们在函数中,输出值一开始输出:
> 1
> 100
// 继续点击按钮,后面输出的都是 1, 就说明了 之后调用函数,都是 另一个新函数,不然会输出 100 的;
> 1 n
但如果之后想要改变 函数的名字,就要一个一个改,比较麻烦;
第二种:
使用 立即执行函数,在一开始,就进行 兼容性的判断,然后返回不同的方法函数,然后使用 变量接收返回的函数,使用变量来调用函数就可以了 。
const addEvent = (function() {
if (document.addEventListener) {
return function(type, element, fun) {
element.addEventListener(type, fun, false)
}
}
else if (document.attachEvent) {
return function (type, element, fun) {
element.attachEvent('on' + type, fun)
}
}
else {
return function (type, element, fun) {
element['on' + type] = fun
}
}
})();
addEvent('click', btn, function() {
console.log(1)
})
注意,里面的 document
哦 。
5. 自己加点菜:
var getTimeStamp = (function() {
var timeStamp = new Date().getTime()
console.log(1)
return function() {
console.log(100)
return timeStamp
}
})();
console.log(getTimeStamp())
console.log(getTimeStamp())
console.log(getTimeStamp())
其实,只输出 1 一次,然后 100 三次;
页面执行时:会形成一个全局作用域 window,
变量提升: var getTimeStamp
和 自执行函数执行(注意,会执行哦,然后把函数的返回值,返回给 变量;
代码执行: console.log(getTimeStamp())
时执行的是 自执行函数返回的 函数,不是自执行函数 。
看看控制台输出的:
是不是和我们想实现的功能有点相似。
6. 一点练习:
如何让以下的函数 只执行一遍 判断 呢?
let timeStamp = null
function getTimeStamp () {
if (timeStamp) {
console.log('进入判断')
return timeStamp
}
console.log('没有进入判断')
timeStamp = new Date().getTime()
return timeStamp
}
console.log(getTimeStamp())
console.log(getTimeStamp())
console.log(getTimeStamp())
console.log(getTimeStamp())
console.log(getTimeStamp())
输出的都是一样的时间戳,具体的执行顺序是
1,变量提升 timeStamp = undefined; getTimeStamp = 函数堆内存,存储 代码字符串
2,赋值: timeStamp = null,执行函数 getTimeStamp();
3,执行函数,判断 timeStamp 是否存在值!
是: 返回值;
否: 给他赋值一个时间戳;
4, 第一次 timeStamp 为 null,所以(不进入if)执行
timeStamp = new Date().getTime() 然后返回值;
第二次 timeStamp 为之前 赋值 的时间戳,进入 if判断,返回值;
第三次 timeStamp 为之前的 时间戳,进入 if判断,返回值;
第n次 也和以上的一样,都是 进入 if判断,返回值;
如何证明呢?
你看控制台的输出结果就知道了!
只执行一次 没有进入 if判断,其余的调用都进入到了 if判断中了 。
5, 但是,有人提出了,调用函数 getTimeStamp(),都会执行一次 if判断,想要在 时间戳 存在,下一次就不进行 if 判断了,这该怎么做?
可以用 惰性函数 试试:
var timeStamp = null
function getTimeStamp () {
timeStamp = new Date().getTime();
getTimeStamp = function () {
console.log(2)
return timeStamp
}
console.log(1)
return timeStamp
}
console.log(getTimeStamp())
console.log(getTimeStamp())
console.log(getTimeStamp())
- 第一次:
getTimeStamp()
时,给timeStamp
赋值了当前时间戳,并改变了getTimeStamp
的指向,指向了一个新的函数,输出 1,并返回timeStamp
值; - 第二次:再次调用
getTimeStamp()
时,执行了一个新函数,输出 2, 并返回之前赋值的 时间戳; - 第三次:还是和第二次一样,输出2,返回 时间戳;
- 如果有点听不懂,想想 函数重写的这个原理 。
最后有一个学习视频和网站推荐给大家看看: