一、前言

今天地铁上,看到很多拖着行李箱的路人,想回家了。

在上篇博客结尾,记录到了函数的几种创建方式,简单说了下创建差异,以及不同浏览器对于name属性的支持,这篇博客将从第四章函数的回调模式说起。我想了想,还是把一篇博客的知识点控制在五个以内,太长了我自己都懒得看,而且显得特别混杂。标题还是简要说下介绍了哪些知识,也方便自己以后查阅,那么开始。

二、函数的回调模式

1.什么是函数回调模式?

当调用函数时,我们可以将函数作为参数传入到需要调用的函数中,例如我们为函数A传入一个函数B,当函数A执行时调用了函数B,那么我们可以说函数B是一个回调函数,简称回调。

  1. function A(data){
  2. data();
  3. };
  4. function B(){
  5. console.log(1);
  6. };
  7. A(B);//将函数B作为参数传入到A函数中进行调用

当B函数作为参数传入A函数时,此时的B函数是不带括号的,因为函数名带括号时表示立即执行,这点大家应该都知道。

回调函数可以是一个匿名函数,其实这种写法在编程中反而更为常见。

  1. function A(data){
  2. data();
  3. };
  4.  
  5. A(function (){
  6. console.log(2);
  7. });//

 2.回调函数作为对象的方法时this指向问题

回调函数通常的写法是callback(parameters),通常parameters是一个匿名函数,或者一个可调用的全局函数。但当函数是某个对象的方法时,常规的回调执行会出现问题。

  1. //回调函数sayName是obj的一个方法
  2. let obj = {
  3. name : 'echo',
  4. sayName : function () {
  5. console.log(this.name);
  6. }
  7. };
  8. let func = function (callback) {
  9. callback()
  10. };
  11. func(obj.sayName);//并不会输出echo

我们原本预期是输出echo,但实际执行时this指向了全局window而非obj,所以不能拿到name属性,如何解决呢,在传递回调函数时,我们也可以把回调函数所属对象也作为参数传进去。调用方式改为如何即可,通过call或者apply改变this指向。

  1. //回调函数sayName是obj的一个方法
  2. let obj = {
  3. name : 'echo',
  4. sayName : function () {
  5. console.log(this.name);
  6. }
  7. };
  8. let func = function (callback, obj) {
  9. callback.call(obj)
  10. };
  11. func(obj.sayName, obj);//echo

在上述代码中,回调函数作为参数的写法是obj.sayName,其实也可以直接传一个字符串sayName进去,通过obj[sayName]执行,这样做的好处是,函数执行时this直接指向了调用的obj,所以就不需要call额外修改this指向了。

  1. //回调函数sayName是obj的一个方法
  2. let obj = {
  3. name : 'echo',
  4. sayName : function () {
  5. console.log(this.name);
  6. }
  7. };
  8. let func = function (callback, obj) {
  9. obj[callback]();
  10. };
  11. func('sayName', obj);//echo

3.回调模式的使用价值

在浏览器中大部分编程都是事件驱动的,例如页面加载完成触发load事件,用户点击触发click事件,也真是因为回调模式的灵活性,才让JS事件驱动编程如此灵活。回调模式能让程序'异步'执行,也就是不按代码顺序执行。

我们可以在程序中定义多个回调函数,但它们并不是在代码加载就会执行,等到时间成熟,例如用户点击了某个元素,回调函数才会根据开始执行。

  1. document.addEventListener("click", console.log, false);

除了事件驱动,另一个常用回调函数的情景就是结合定时器,setTimeout()与setInterval(),这两个方法的参数都是回调模式。

  1. let func = function () {
  2. console.log(1);
  3. };
  4. setTimeout(func, 500);//500ms后执行一次func函数
  5. setInterval(func, 500)//每隔500ms执行一次func函数

再次强调的是,定时器中回调函数的写法是func,并未带括号,如果带了括号就是立即执行。另一种写法是setTimeout('func()', 500),但这是一种反模式,并不推荐。

 三、返回函数作为返回值

函数是对象,除了可以作为参数同样也能作为返回值,也就是说函数的返回值也能是一个函数。

  1. function demo () {
  2. console.log(1);
  3. return function () {
  4. console.log(2);
  5. };
  6. };
  7. let func1 = demo();//
  8. func1()//

在上述代码中函数demo将返回的函数包裹了起来,创建了一个闭包,我们可以利用闭包存储一些私有属性,而私有属性可以通过返回的函数操作,且外部函数不能直接读取这些私有属性。

  1. const setup = function () {
  2. let count = 0;
  3. return function () {
  4. return (count += 1);
  5. };
  6. };
  7. let next = setup();
  8. next();//
  9. next();//
  10. next();//

四、函数重写

当我们希望一个函数做一些初始化操作,并且初始化的操作只执行一次时,面对这种情况我们就需要使用函数重写

  1. let handsomeMan = function () {
  2. console.log('echo is handsome man!');
  3. handsomeMan = function () {
  4. console.log('Yes,echo is handsome man!');
  5. };
  6. };
  7. handsomeMan()//echo is handsome man!
  8. handsomeMan()//Yes,echo is handsome man!

上方代码中,第一次调用的输出只会执行一次,这是因为新的函数覆盖掉了旧函数,虽然一直都是调用handsomeMan,但前后执行函数完全不同。

这种模式的另一名字是函数的懒惰定义,因为函数是执行一次后才重新定义,相比分开两个函数来写,这样的执行效率更为高效。

此模式有个明显的问题就是,一旦重新函数被重写,最初函数的所有方法属性都将丢失。

  1. let handsomeMan = function () {
  2. console.log('echo is handsome man!');
  3. handsomeMan = function () {
  4. console.log('Yes,echo is handsome man!');
  5. };
  6. };
  7. handsomeMan.property = "properly";
  8.  
  9. let boy = handsomeMan;
  10. boy();//echo is handsome man!
  11. boy();//echo is handsome man!
  12. console.log(boy.property);//properly
  13. //property属性已丢失
  14. handsomeMan()//echo is handsome man!
  15. handsomeMan()//Yes,echo is handsome man!
  16. console.log(handsomeMan.property);//undefined

 五、立即执行函数

所谓立即执行函数就是一个在创建后就会被立即执行的函数表达式,也可以叫自调用函数(IIFE)。

  1. //调用括号在里面
  2. (function () {
  3. console.log(1);
  4. }());
  5. //调用括号在外面
  6. (function () {
  7. console.log(2);
  8. })();

它主要由三部分组成,一对括号(),里面包裹着一个函数表达式,以及一个自调的括号(),这个括号可紧跟函数,也可以写在外面。我个人常用第二种写法,但JSLint更倾向于第一种写法。

立即执行函数长用于处理代码初始化的工作,因为它提供了一个独立的作用域,所有初始化中存在的的变量都不会污染到全局环境。

立即执行函数也可以传递参数,像这样

  1. (function (data) {
  2. console.log(data);
  3. })(1);

除了传参,立即执行函数也可以返回值,并且这些返回值可以赋值给变量。

  1. var result = (function () {
  2. return 2 + 2;
  3. })();
  4. console.log(result);//

在对应一个对象的属性时,也可以使用立即执行函数,假设对象的某个属性是一个待确定的值,那我们就可以使用此模式来初始化该值。

  1. let o = {
  2. message: (function () {
  3. return 2+2
  4. })(),
  5. getMsg: function () {
  6. return this.message;
  7. }
  8. };
  9. o.message;//
  10. o.getMsg()//

需要注意的是,此时的o.message是一个字符串,并非一个函数。

但是伴随着ES6中块级作用域的出现,利用自执行函数保护全局作用域免受初始化变量污染的做法已经没有必要了。

  1. {
  2. let a = 1;
  3. }
  4. console.log(a);//无权访问

六、代码初始化的意义

在函数重写和自调用函数模式中多次提到了代码初始化,为什么要做代码初始化,简单举例说下。

JS的函数监听大家都不会陌生,而早期IE与大部分浏览器提供的监听绑定方法不同,如果不使用初始化,可能是这样

  1. let o = {
  2. addListener : function (el, type ,fn) {
  3. if(typeof window.addEventListener === 'function') {
  4. el.addEventListener(type, fn, false);
  5. }else if (typeof document.attachEvent === 'function') {
  6. el.attachEvent('on' + type, fn);
  7. }else{
  8. el['on' + type] = fn;
  9. }
  10. }
  11. };
  12. o.addListener();

当我们调用o.addListener()方法时,很明显效率不高,每次调用都要把各浏览器判断走一遍,才能确定最终的监听绑定方式;我们初始化监听方式。

  1. let o = {
  2. addListener: null
  3. };
  4. if (typeof window.addEventListener === 'function') {
  5. o.addListener = function (el, type, fn) {
  6. el.addEventListener(type, fn, false);
  7. };
  8. }else if (typeof document.attachEvent === 'function') {
  9. o.addListener = function (el, type, fn){
  10. el.attachEvent('on' + type, fn);
  11. }
  12. }else{
  13. o.addListener = function () {
  14. el['on' + type] = fn;
  15. }
  16. };

在当我们调用o.addListener()方法时,此时addListener已经初始化过了,不用反反复复走监听绑定判断,这就是代码初始化的意义,把那些你能确定下来,但需要繁琐执行的逻辑一次性确定好,之后就是直接使用的操作了,就是这么个意思。

这篇就记录这么多吧,还有五天回家过年了!

精读JavaScript模式(五),函数的回调、闭包与重写模式的更多相关文章

  1. javascript基础(五)函数

    原文http://pij.robinqu.me/ 通过call和apply间接调用函数(改变this) call 和 apply带有多个参数,call和apply把当前函数的this指向第一个参数给定 ...

  2. javascript基础:函数参数与闭包问题

    今天在写东西的时候,对函数参数的概念有些模糊,查阅相关资料后,在博客上记点笔记,方便日后复习. 首先,在js中函数参数并没有强语言中那么要求严格,他不介意传递进来多少个参数,也不在乎传进来的参数是什么 ...

  3. JavaScript 基础(五) 函数 变量和作用域

    函数定义和调用 定义函数,在JavaScript中,定义函数的方式如下: function abs(x){ if(x >=0){ return x; }else{ return -x; } } ...

  4. JavaScript函数表达式、闭包、模仿块级作用域、私有变量

    函数表达式是一种非常有用的技术,使用函数表达式可以无需对函数命名,从而实现动态编程.匿名函数,是一种强大的方式,一下总结了函数表达式的特点: 1.函数表达式不同于函数声明,函数声明要求有名字,但函数表 ...

  5. JavaScript高级程序设计--函数小记

    执行环境和作用域链   每个函数都有自己的执行环境.当执行流进入一个函数时,函数 的环境就会被推入一个环境栈中.而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境.   当代码在一个环境中 ...

  6. JavaScript(五):函数(闭包,eval)

    1.函数的申明:三种方法: function命令 函数表达式:变量赋值 Function构造函数 //method 1: function命令 function test(){ console.log ...

  7. 精读JavaScript模式(六),Memoization模式与函数柯里化的应用

    假期就这么结束了!十天假就有三天在路上,真的难受!想想假期除了看了两场电影貌似也没做什么深刻印象的事情.流浪地球,特效还是很赞,不过对于感情的描写还是逃不掉拖沓和尴尬的通病,对于国产科幻还是抱有支持的 ...

  8. 前端基本知识(四):JS的异步模式:1、回调函数;2、事件监听;3、观察者模式;4、promise对象

    JavaScript语言将任务的执行模式可以分成两种:同步(Synchronous)和异步(Asychronous). “同步模式”就是一个任务完成之后,后边跟着一个任务接着执行:程序的执行顺序和排列 ...

  9. JavaScript学习总结(一)——闭包、对象、函数

    一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...

随机推荐

  1. EPEL 源

    EPEL/zh-cn   Page Discussion View View source History   < EPEL In other languages: English (en) e ...

  2. FTP服务与配置

    FTP简介 网络文件共享服务主流的主要有三种,分别是ftp.nfs.samba. FTP是File Transfer Protocol(文件传输协议)的简称,用于internet上的控制文件的双向传输 ...

  3. 【NIFI】 Apache NiFI 之 ExecuteScript处理(二)

    本例介绍NiFI ExecuteScript处理器的使用,使用的脚本引擎ECMScript 接上一篇[NIFI] Apache NiFI 之 ExecuteScript处理(一) ExecuteScr ...

  4. 《Linux就该这么学》第六天课程

    每个人都有梦想,同时也有理想,当一个人的梦想与理想相同时会发生什么? 搜集了一些对新手有用的表格 原创地址:https://www.linuxprobe.com/chapter-05.html use ...

  5. 73.解决Xcode10 library not found for -lstdc++ 找不到问题

    Xcode10 彻底废除了libstdc++,相关文件libstdc++.6.0.9.dylib.libstdc++.6.dylib.libstdc++.dylib.libstdc++.6.0.9.t ...

  6. java的poi 读取exc 文件

    package lizikj.bigwheel.shop.util.excel; import java.io.File; import java.io.FileInputStream; import ...

  7. valgrind内存检测工具

    valgrind 那点事 ---------------------------------------内存检测工具 valgrind要使用此工具,可以使用--tool=memcheck 在Valgr ...

  8. Java面试集合(一)

    前言 大家好,给大家带来Java面试集合(一)的概述,希望你们喜欢 一 1.Java按应用范围可划分几个版本? 答:Java按应用范围有三个版本,分别是JavaSE,JavaEE,JavaME. 2. ...

  9. C语言判断大小端的几种方法

    在操作系统中,经常会用到判断大小端,很多面试题中也会经常遇到,以前的时候没有总结过,这里总结一下. 以后用到了就直接可以用了. 所谓的大小端,大致的解释意思就是: [大端模式] CPU对操作数的存放方 ...

  10. numpy 介绍

    NumPy系统是Python的一种开源的数值计算扩展.这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nested list structure)结构要高效的多(该结构也可以用来表示矩 ...