设计模式(1):只执行一次的函数
概述
最近最近做项目的时候总会思考一些大的应用设计模式相关的问题,我把自己的思考记录下来,供以后开发时参考,相信对其他人也有用。
只执行一次的函数
我们经常会遇到这种情况,就是希望某个函数只执行一次,以后就不执行了。一般情况下,我们会这么写:
<script>
export default {
data() {
return {
runOnce: true,
};
},
methods: {
func() {
console.log('hello world', this);
},
funcRunOnce() {
if (this.runOnce) {
this.func();
this.runOnce = false;
}
},
},
};
</script>
但是这样并不优雅,不仅污染了data,还用2个方法进行实现,实在难看。
用闭包改进
于是我们考虑用闭包,把data里面的runOnce这个变量放到闭包里面去,这样就不会污染data了。代码如下:
<script>
export default {
methods: {
func() {
console.log('hello world', this);
},
funcRunOnce(params) {
let runOnce = true;
return () => {
if (runOnce) {
this.func();
runOnce = false;
}
}();
},
},
};
</script>
但是这么写显然是错了,因为每次调用funcRunOnce都会构造一次闭包,里面的runOnce这个变量根本不会共享。所以继续改写如下:
// 方法1
<script>
export default {
created() {
this.funcRunOnce = this.runOnce(this.func);
},
methods: {
func() {
console.log('hello world', this);
},
runOnce(func) {
let runOnce = true;
return (params) => {
if (runOnce) {
func(params);
runOnce = false;
}
};
},
},
};
</script>
// 方法2
<script>
export default {
methods: {
func() {
console.log('hello world', this);
},
runOnce(func) {
let runOnce = true;
return (params) => {
if (runOnce) {
func(params);
runOnce = false;
}
};
},
funcRunOnce: this.runOnce(this.func),
},
};
</script>
使用utils
可以看到,上面的方法仍然很不优雅,要么用一个created和2个方法实现,要么用三个方法实现。而都用了一个公共的方法runOnce。所以我们考虑把runOnce放到utils.js里面去。
// utils.js
export function runOnce(func) {
let runOnce = true;
return (params) => {
if (runOnce) {
func(params);
runOnce = false;
}
};
}
//example.vue
import { runOnce } from '@/utils';
<script>
export default {
methods: {
funcRunOnce: runOnce(() => {
console.log('hello world', this);
}),
},
};
</script>
上面的写法看起来非常简洁,但是实际上是不行的,因为this的指向错了。由于runOnce返回的函数并不是vue实例的方法,所以里面的this指向的是undefined。
注意:即使看起来我们好像在funcRunOnce方法中用箭头函数捕获了外面实例的this,但是实际上它捕获的并不是外面的实例的this,而是runOnce返回的函数里面的this。
捕获this
能用箭头函数的地方我们都用了,但是为什么我们还是捕获不了this呢?如此一来是不是完成不了这个任务了?
并不是,方法还是有的,方法是不用箭头函数捕获this。代码如下:
// utils.js
export function runOnce(func) {
let runOnce = true;
return function(params) {
if (runOnce) {
func.apply(this, params);
runOnce = false;
}
};
}
//example.vue
import { runOnce } from '@/utils';
<script>
export default {
methods: {
funcRunOnce: runOnce(function h() {
console.log('hello world', this);
}),
},
};
</script>
通过查看代码可以看出,2个地方的箭头函数都被改写成了function,并且还用到了apply函数来强制施加this。
理由很简单,由于runOnce函数里面没有用箭头函数,所以它返回的函数是属于vue实例的,所以它返回的函数的this,是指向vue实例的;又因为funcRunOnce里面没有用箭头函数,所以我们可以用apply把这个this强制附加到func里面去!
同理我们还可以写出第一次不执行,后续才执行的函数:
// utils.js
// 第一次不执行,后续再执行
export function notRunOnce(func) {
let once = false;
return function(params) {
if (once) {
func.apply(this, params);
}
once = true;
};
}
学到了什么
- 在vue里面可以用赋值的形式初始化方法,或者在created里面初始化方法。
- 箭头函数虽然能捕获this,但不是万能的;有时候我们需要用function和apply结合来捕获this。