2015年12月

萌新脱坑指南 - JavaScript回调函数&Node.js异步回调

这两天要给任狗解释什么是回调函数,想了想应该整理成文章,方便看,也当理清自己的思路了。

博客主题偷懒到现在没写完,代码看着可能有点不舒服,还请将就着看。

什么是回调函数

顾名思义,回调,回头再调用它(什么鬼 。
JS中的回调函数,作为萌新,我们会在这样的地方看见它:

app.get('/',function(req,res){
    res.send("hello world");
})

在这里面,作为 app.get方法(此处感谢老姐夫@faceair纠正)的第二个参数的,是一个函数,而这个,我们可以叫它回调函数。

回调函数是咋用的

我们来假设一个应用场景。

  1. 我们有两个初始参数
  2. 我们为二者求和
  3. 求和结果翻倍
  4. 调教
// 普通柴犬会先写个函数,返回其求和
function sum(a,b){
    var sum = a+b;
    return sum;
    console.log("sum函数结束");
}
// 然后用一个变量获取函数的返回值
var doge = sum(116,117);
// 数值翻倍
doge *= 2;
// 然后再随意地调教调教它
console.log(doge);

以上的代码,根据执行顺序,会在var doge = sum(116,117)的时候输出sum函数结束,最后才输出doge的最终值
这是一种普通的处理方式,顺序执行。然后我们用回调的方法嵌套执行:

// 普通柴犬会先写个函数,其中callback就是你需要写进参数的回调函数
function sum(a,b,callback){
    var sum = a+b;
    // 进行处理之后,调用回调函数,并把求得的和作为参数传进了它
    callback(sum);
    console.log("sum函数结束");
}
// 然后搞搞搞,前两个是参数,后面的就是回调函数
sum(233,233,function(data){
    // 然后用一个变量获取函数的返回值
    var doge = data;
    // 数值翻倍
    doge *= 2;
    // 然后再随意地调教调教它
    console.log(doge);
});

这里会先输出doge的值,然后才输出“sum函数结束”。

这就是回调函数最基本的形态和原理。

node.js的异步回调

我们使用node.js的时候,经常会用到回调函数,它在形态上和我们刚刚写的类似,但是它有着异步的特性。
这个特性,我们可以理解为“点菜”特性。
我们先扔出一个express中很常见的例子。

var express = require("express");
var fs = require("fs");// fs是node的文件系统模块
var app = express();

app.get('/',function(req,res){
    var doge = 0;
    // fs.readFile方法是一种读取文件的方法,未出错会把文件的内容赋值到data这个位置
    fs.readFile("./test.md",function(err,data){
        if(err){
            console.log("error : "+err)
        }else{
            doge = data.length;
            console.log("hello,doge : "+doge);
        }
    });
    // res.send的内容会输出到页面中
    res.send("hello,doge : "+doge);
});

app.listen(23333);

这里我们使用express进行了一个简单的服务构建,当启动的时候,访问localhost:23333/,我们会得到res.send的参数。下面我们忽略杂项只看正片内容:

    var doge = 0;
    fs.readFile("./test.md",function(err,data){
        if(err){
            console.log("error : "+err)
        }else{
            doge = data.length;//根据我的测试文件,这里假设文件内容的长度是30
            console.log("hello,doge : "+doge);
        }
    });
    res.send("hello,doge : "+doge);

按照正常的执行顺序,应该是这样运行的:

  1. 定义doge为0;
  2. 进行读文件操作,读取文件内容,读取成功后把文件内容的长度赋值给doge;
  3. 在控制台输出hello,doge : 30
  4. 在浏览器的页面里输出hello,doge : 30

抱着这样的信心,测试了一下,会被光速打脸。
真正的输出结果是:

  1. 在控制台输出hello,doge : 30
  2. 在浏览器的页面里输出hello,doge : 0

这是为什么呢?这正是刚刚我们提到的“node.js的'点菜'特性”。
在node.js中,我们能见到的很多模块的、需要写回调函数的方法,都具有这个点菜特性,比如刚刚的fs.readFile()
我们在餐馆中,我们进行了一次点菜,但是这不影响我们继续点其他的菜,或者结束点菜开始聊天、等到菜上来再开吃,对吧?
所以,我们的餐馆行为,是这样的:

进入餐馆 -> 点菜 -> 谈笑风生 ->(菜做好之后)吃菜

刚刚的行为,影射到我们的node代码中,其实是这样的:

//进入餐馆;
点菜("冷吃兔",function(菜){
    吃(菜);
})
谈笑风生();

也就是说,我们提前定义了一个回调函数,菜点好了,在做菜完成之后才去执行function(菜){吃(菜);},而我们会开始谈笑风生。
重新审视我们刚刚的代码:

    var doge = 0;
    // 我们现在点了个菜
    fs.readFile("./test.md",function(err,data){
        // 这里面,是菜做好了,产生了err和data值之后执行的内容
        if(err){
            console.log("error : "+err)
        }else{
            doge = data.length;
            console.log("hello,doge : "+doge);
        }
    });
    // 这是点菜之后 执行的内容
    res.send("hello,doge : "+doge);

这就是node的“点菜”特性,我们通常匪夷所思地叫它“异步回调”,其实理解为点菜,会更简单一些。正是因为有这样的特性,所以在基础的node写法中,我们总是会嵌套起很多层的回调函数,用以防止逻辑顺序不能正确执行。
在前人的不懈努力后,我们已经有了很多的对于异步回调嵌套问题的解决方案,比如co,Promise,yield,async等等等等,具体内容还请异步baidu/google。

总结

以上是关于JS的回调函数和Node.js的异步回调的一些脱坑帮助,一点拙见,如果哪里出现错误请邮箱或者评论告知。

感谢在创作过程中提供灵魂帮助的@名侦探羊神,事实上“点菜”这个生动的比喻正是羊神提出的。
同样感谢帮我纠错的老姐夫@faceair,上文中数处错误已在老姐夫指导下进行了修改。
参考:
· 文件系统 (fs)-Node.js中文网
· Node.js机制及原理理解初步(具体为什么会有点菜特性可以参考这篇文章)

整理11月

11月已逝,月常反思。

关于Canvas游戏的编写

10月末和11月上旬,一直在编写两个移动端的canvas休闲小游戏。
依旧用的熟悉的CreateJS,之前用过这个去实现动画,这次直接进行游戏开发,难免力不从心。
开发过程中遇见的几个坑:

  • 低级错误:在移动端的触碰兼容事件中,很难处理触碰事件点击事件滑动事件等。
    • 体现:拖动事件
    • 处理:使用框架hammer.js
  • 低级错误:在整体游戏的图形显示中,图片的层级错乱。
    • 体现:后添加的游戏元素理所应当会显示在之前的上面,影响视觉效果
    • 处理:分图层,在图层内添加新的游戏元素
  • 低级错误:不能很好的复用代码
    • 体现:相似的代码出现了多次
    • 处理:封装函数,复用代码,返回对象
  • 低级错误:游戏效率低下
    • 体现:逻辑错乱,导致游戏计算量过大
    • 处理:重构游戏逻辑,减少刷新和遍历操作
  • 常见问题:多次需求修改
    • 体现:需求多次修改,成本偏高
    • 处理:尽量降低部分与部分之间的耦合性,增强可修改性,不过重构代价低的时候尽量重构得更清晰好用

关于前端工程化的新认识

之前使用jade的过程中,只是简单作为了一个工程化的一部分,没有合理利用它。
在这个月的开发中,感受到了一个很重要的点就是,要把jade的template做得尽量的灵活。
之前,一个jade的基础template中,我们会留出这样几个代码块:

  • title - 页面标题
  • content - 页面内容
  • ??? - 页面内容中的特殊模块,如登录模块、nav

而现在,为了让一个template能应对更多特殊情况,我是这样划分的:

  • title - 页面标题
  • special-src - 页面特殊引用,这部分主要针对特殊的页面,应用场景举例:
    1. 页面中包含一个大型的js用来处理一个图标
    2. 页面中需要使用一个特殊的css文件来显示某一部分的样式
    3. 等等
  • content - 页面内容
  • footer-script - 页面特殊脚本,通常这个的下面只是一个script标签,针对小型的js引用

以及在目录划分上,现在我采用了分文件夹的内容处理方式,对于页面整体的组成有了更明显的展示,也更加便于引用。

关于物联网工程的“实习”(校方所组织的)

并不是什么正规的实习,但是有幸听到了杨正辉先生很好的一次讲座,有很多感受,会在之后的文章中详细叙述自己对于讲座内容等的理解和认识,对于先生所说的“互联网 与 互联网+”、“感知 - 传输 - 应用”等的一个个人理解。

关于IDE

自己比较懒,不喜欢折腾插件,所以最终放弃了Sublime Text。现在使用的是WebStorm。
在使用IDE上,之前一直坚持的一个理论是一直使用默认主题(懒),近期发现自己对于主题进行部分DIY是很有必要的,因为不同的人对于颜色的感知都有不同,所以要尽量去选择一个自己看着顺眼的配色,至少也要去官方主题库去斟酌一番。

关于后端

之前挖坑打算写后端,对GitHub项目 - Node_blog_V2进行了一个抄袭和研究循序并进的过程。

这个项目使用了一个标准的MVC结构,在学习过程中,发现了其中几个优缺点,优点:

  • 项目结构清晰
  • 数据处理方式很有借鉴意义

缺点:

  • 可修改之处很多
  • 没留出路由中间件的可行入口

后端的结构对于我一个一直在做前端的人来说是很新鲜的,这个坑基本是要弃了,打算带着这份新的理解去完成一个实际的后端项目用作自己的博客的一部分。

月常瞎扯

这个月,感谢玉酱带来的正能量,感谢强哥推荐的项目让我的毛爷爷越来越多,感谢Bilibili重新带给我的乐趣。
以及,费玉清的嘿嘿嘿的梗已经把我的室友洗脑了!!!
卸载了手机和电脑的炉石,更多的时间要用来休息和学习。
口琴方面,可以吹完千本樱了,换气又强了不少。
人生漫漫,仍需早睡早起,尚要不忘初心,如此。