标签:异步 dem interval 匿名 dso 为什么 逻辑 sage tac
一、前言
今天地铁上,看到很多拖着行李箱的路人,想回家了。
在上篇博客结尾,记录到了函数的几种创建方式,简单说了下创建差异,以及不同浏览器对于name属性的支持,这篇博客将从第四章函数的回调模式说起。我想了想,还是把一篇博客的知识点控制在五个以内,太长了我自己都懒得看,而且显得特别混杂。标题还是简要说下介绍了哪些知识,也方便自己以后查阅,那么开始。
二、函数的回调模式
1.什么是函数回调模式?
当调用函数时,我们可以将函数作为参数传入到需要调用的函数中,例如我们为函数A传入一个函数B,当函数A执行时调用了函数B,那么我们可以说函数B是一个回调函数,简称回调。
function A(data){ data(); }; function B(){ console.log(1); }; A(B);//将函数B作为参数传入到A函数中进行调用
当B函数作为参数传入A函数时,此时的B函数是不带括号的,因为函数名带括号时表示立即执行,这点大家应该都知道。
回调函数可以是一个匿名函数,其实这种写法在编程中反而更为常见。
function A(data){ data(); }; A(function (){ console.log(2); });//2
2.回调函数作为对象的方法时this指向问题
回调函数通常的写法是callback(parameters),通常parameters是一个匿名函数,或者一个可调用的全局函数。但当函数是某个对象的方法时,常规的回调执行会出现问题。
//回调函数sayName是obj的一个方法 let obj = { name : ‘echo‘, sayName : function () { console.log(this.name); } }; let func = function (callback) { callback() }; func(obj.sayName);//并不会输出echo
我们原本预期是输出echo,但实际执行时this指向了全局window而非obj,所以不能拿到name属性,如何解决呢,在传递回调函数时,我们也可以把回调函数所属对象也作为参数传进去。调用方式改为如何即可,通过call或者apply改变this指向。
//回调函数sayName是obj的一个方法 let obj = { name : ‘echo‘, sayName : function () { console.log(this.name); } }; let func = function (callback, obj) { callback.call(obj) }; func(obj.sayName, obj);//echo
在上述代码中,回调函数作为参数的写法是obj.sayName,其实也可以直接传一个字符串sayName进去,通过obj[sayName]执行,这样做的好处是,函数执行时this直接指向了调用的obj,所以就不需要call额外修改this指向了。
//回调函数sayName是obj的一个方法 let obj = { name : ‘echo‘, sayName : function () { console.log(this.name); } }; let func = function (callback, obj) { obj[callback](); }; func(‘sayName‘, obj);//echo
3.回调模式的使用价值
在浏览器中大部分编程都是事件驱动的,例如页面加载完成触发load事件,用户点击触发click事件,也真是因为回调模式的灵活性,才让JS事件驱动编程如此灵活。回调模式能让程序‘异步‘执行,也就是不按代码顺序执行。
我们可以在程序中定义多个回调函数,但它们并不是在代码加载就会执行,等到时间成熟,例如用户点击了某个元素,回调函数才会根据开始执行。
document.addEventListener("click", console.log, false);
除了事件驱动,另一个常用回调函数的情景就是结合定时器,setTimeout()与setInterval(),这两个方法的参数都是回调模式。
let func = function () { console.log(1); }; setTimeout(func, 500);//500ms后执行一次func函数 setInterval(func, 500)//每隔500ms执行一次func函数
再次强调的是,定时器中回调函数的写法是func,并未带括号,如果带了括号就是立即执行。另一种写法是setTimeout(‘func()‘, 500),但这是一种反模式,并不推荐。
三、返回函数作为返回值
函数是对象,除了可以作为参数同样也能作为返回值,也就是说函数的返回值也能是一个函数。
function demo () { console.log(1); return function () { console.log(2); }; }; let func1 = demo();//1 func1()//2
在上述代码中函数demo将返回的函数包裹了起来,创建了一个闭包,我们可以利用闭包存储一些私有属性,而私有属性可以通过返回的函数操作,且外部函数不能直接读取这些私有属性。
const setup = function () { let count = 0; return function () { return (count += 1); }; }; let next = setup(); next();//1 next();//2 next();//3
四、函数重写
当我们希望一个函数做一些初始化操作,并且初始化的操作只执行一次时,面对这种情况我们就需要使用函数重写。
let handsomeMan = function () { console.log(‘echo is handsome man!‘); handsomeMan = function () { console.log(‘Yes,echo is handsome man!‘); }; }; handsomeMan()//echo is handsome man! handsomeMan()//Yes,echo is handsome man!
上方代码中,第一次调用的输出只会执行一次,这是因为新的函数覆盖掉了旧函数,虽然一直都是调用handsomeMan,但前后执行函数完全不同。
这种模式的另一名字是函数的懒惰定义,因为函数是执行一次后才重新定义,相比分开两个函数来写,这样的执行效率更为高效。
此模式有个明显的问题就是,一旦重新函数被重写,最初函数的所有方法属性都将丢失。
let handsomeMan = function () { console.log(‘echo is handsome man!‘); handsomeMan = function () { console.log(‘Yes,echo is handsome man!‘); }; }; handsomeMan.property = "properly"; let boy = handsomeMan; boy();//echo is handsome man! boy();//echo is handsome man! console.log(boy.property);//properly //property属性已丢失 handsomeMan()//echo is handsome man! handsomeMan()//Yes,echo is handsome man! console.log(handsomeMan.property);//undefined
五、立即执行函数
所谓立即执行函数就是一个在创建后就会被立即执行的函数表达式,也可以叫自调用函数。
//调用括号在里面 (function () { console.log(1); }()); //调用括号在外面 (function () { console.log(2); })();
它主要由三部分组成,一对括号(),里面包裹着一个函数表达式,以及一个自调的括号(),这个括号可紧跟函数,也可以写在外面。我个人常用第二种写法,但JSLint更倾向于第一种写法。
立即执行函数长用于处理代码初始化的工作,因为它提供了一个独立的作用域,所有初始化中存在的的变量都不会污染到全局环境。
立即执行函数也可以传递参数,像这样
(function (data) { console.log(data); })(1);
除了传参,立即执行函数也可以返回值,并且这些返回值可以赋值给变量。
var result = (function () { return 2 + 2; })(); console.log(result);//4
在对应一个对象的属性时,也可以使用立即执行函数,假设对象的某个属性是一个待确定的值,那我们就可以使用此模式来初始化该值。
let o = { message: (function () { return 2+2 })(), getMsg: function () { return this.message; } }; o.message;//4 o.getMsg()//4
需要注意的是,此时的o.message是一个字符串,并非一个函数。
六、代码初始化的意义
在函数重写和自调用函数模式中多次提到了代码初始化,为什么要做代码初始化,简单举例说下。
JS的函数监听大家都不会陌生,而早期IE与大部分浏览器提供的监听绑定方法不同,如果不使用初始化,可能是这样
let o = { addListener : function (el, type ,fn) { if(typeof window.addEventListener === ‘function‘) { el.addEventListener(type, fn, false); }else if (typeof document.attachEvent === ‘function‘) { el.attachEvent(‘on‘ + type, fn); }else{ el[‘on‘ + type] = fn; } } }; o.addListener();
当我们调用o.addListener()方法时,很明显效率不高,每次调用都要把各浏览器判断走一遍,才能确定最终的监听绑定方式;我们初始化监听方式。
let o = { addListener: null }; if (typeof window.addEventListener === ‘function‘) { o.addListener = function (el, type, fn) { el.addEventListener(type, fn, false); }; }else if (typeof document.attachEvent === ‘function‘) { o.addListener = function (el, type, fn){ el.attachEvent(‘on‘ + type, fn); } }else{ o.addListener = function () { el[‘on‘ + type] = fn; } };
在当我们调用o.addListener()方法时,此时addListener已经初始化过了,不用反反复复走监听绑定判断,这就是代码初始化的意义,把那些你能确定下来,但需要繁琐执行的逻辑一次性确定好,之后就是直接使用的操作了,就是这么个意思。
这篇就记录这么多吧,还有五天回家过年了!
精读JavaScript模式(五),函数的回调、闭包与重写模式
标签:异步 dem interval 匿名 dso 为什么 逻辑 sage tac
原文地址:https://www.cnblogs.com/echolun/p/10285380.html