小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

近期前端学习二三事

最近还是在前端上投入了算是较多的学习吧,有些沉淀,今天来稍微总结一下。

一. ES6

ES6标准不久前已经发布正式版了,里面涉及到了很多新特性,是值得关注的。前端技术日新月异,等到被迫去学就已经太晚了。下面我说说自己感兴趣的新特性。

Generator

这个家伙写出来和函数外表上差不多,主要是函数多了个*号,函数主题引入了关键字"yield"。但做的事情就很丰富了。 首先定义一个GeneratorFunction

function *Gen(){
    console.log('start');
    var a = yield [1,2,3];
    console.log(a);
    var b = yield 2;
    console.log(b);
}

这样就定义了一个GeneratorFunction,此时Gen是个GeneratorFunction而不是Generator。可以用GeneratorFunction实例化出Generator

var gen = Gen();

这样就知道GeneratorFunctionGenerator的区别了。但是这里是不用new关键字的。

直接用gen()运行会报错的,因为它不是函数。而真正的驱动运行的调用它的next()方法。

gen.next(); //log出了”start“,返回对象{value:[1,2,3],done:false}
gen.next(5);//log出了”2“,返回对象{value:2,done:false}
gen.next(7);//log出了"7",返回对象{value:undefined,done:true}
gen.next(); //只返回对象{value:undefined,done:true}

从这里可以看到几点Generator的特性:

  1. 它是用next驱动的,每次会在yield关键字停住,等待下一次next驱动继续执行
  2. 每次返回的是个对象,中间有两个key:value,done。value的值为yield关键字后面的值,done用来指示Geneator是否执行完成。
  3. 每次next方法调用可以传值,而这个值会被赋予到yield关键字左侧的变量,对于例子就是赋值给了a和b.

或许就看Generator的这些特性还不能体会它的优点,那么一会儿会提到的co函数将会让你大吃一惊,先hold住。

下面给出GeneratorFunctionGenerator的判断方法(来自co):

function isGenerator(obj) {
    return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}

function isGeneratorFunction(obj) {
    var constructor = obj.constructor;
    if (!constructor) return false;
    if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' ===         constructor.displayName) return true;
    return isGenerator(constructor.prototype);
}

Promise

`Promise`是用来解决异步回调的优雅的方式,已经存在很多优雅的`Promise`库,现在ES6将其标准化了,node4也默认支持ES6,这将使得`Promise`会有更多的应用。

最开始`Promise`都是配合`Deferred`一起用的,不过现在基本都被包装到了`Promise`的构造函数中,所以使用方式都变成如下这样了:

var fs = require('fs');
new Promise(function(resolve,reject){
  fs.stat('index.html',function(err,data){
      if(err){
          reject(err);
      }
      resolve(data.size);
  });
})
.then(function(data){
    console.log("demo.txt的文件大小为:"+data)
})
.catch(function(err){
    console.log("错误:"+err);
});

要想用好Promise,还是得明白其原理,否则就会出现各种不懂,特别是对于then方法里面的传值,以及then里面的回调函数的返回的情况,还包含并行等高阶运用,看看这篇文章,你就知道会有多少坑等着你去踩:We have a problem with promises

如果想知道Promise提供了什么接口,来来看看这篇文章:Javascript 中的神器——Promise

如果想了解其原理,那就去读朴灵大神的《深入浅出Node.js》吧

箭头函数(array function)

提供了类似lamda表达式的语法糖,好处有两个:

  1. 可以减少代码量的编写,让代码更规整
  2. 箭头函数里面的作用域不会改变,就不会被那个this搞得晕头转向了(其实这this蛮好玩的,比java这种好玩多了。)

看个例子:jquery的ajax其实已经promise化了,所以是可以这样用的:

$.ajax({ type: 'POST', url: '/api', data: data })
.done(function(data){
    console.log('Data:'+ data);
    this.setState({data:data});
}.bind(this))
.fail(function(err){
    console.log('Error:'+err);
    this.setState({err:err});
}.bind(this))

用了伟大的array function,我们就可以这样玩了:

$.get("http://example.com")
.done(data =>{
    console.log('Data'+data);
    this.setState({data:data});
})
.fail(err => {
    console.log('Error:'+err);
    this.setState({err:err});
})

不用担心上下文this改变带来的好处还是不错的。在map/reduce等方法的好处更明显了:

//旧的书写形式
[1,2,3].map(function(i){
    return i*2;
}); //返回[2,4,6]

//array function
[1,2,3].map(i=>{return i*2})

结果相同,代码减少很多且可以写得更美观和易读

其它还有诸如引入新的基本数据类型Symbol,类,let和var,解析异构,参数默认值等,都是JS称霸全球的勇士啊。具体学习可以去看infoq上翻译”深入浅出ES6“系列。

二.co函数

如果问现在前端有什么优雅的解决异步回调地狱的方式,那么绝对是co函数了(快去github上搜搜),这是TJ大神基于Generator写出来的一个函数,对,就是个函数,一个功能异常强大的函数。下面来简要的分析分析,不过首先要知道高阶函数和偏函数的概念

高阶函数与偏函数

高阶函数是指函数可以作为参数也可以作为返回值的函数,如:

var fs = require('fs');
function size(filename){
    return function(done){
        fs.stat(filename,function(err,data){
            if(err) done(err);
            done(null,data.size);
        })
    }
}

上面那个也叫做偏函数,是指创建一个调用"一部分参数或变量已经预置的函数"的函数。这个很有用,比如在做类型判断时:

var isType = function(type){
    var toString = Object.prototype.toString;
    return function(obj){
        return toString.call(obj) == '[object '+type +']';
    }
}
var isString = isType('String');
var isFunction = isType('Function');

通过偏函数可以很好的利用闭包抽取代码的重复部分,否则分别取定义isString,isFunction会产生很多重复的代码量。

下面再来看看高阶函数中来把函数作为参数的传递的用法,这个运用就很多了,想es5的forEach(),map(),reduce()等都是它的运用,下面看reduce的兼容写法和用法:

Array.prototype.reduce = function(func,initValue){
  var previous = initValue,k=0,length = this.length;
  if(initValue ===void 0){
    previous = this[0];
    k=1;
  }
  if(typeof func === 'function'){
    for(k;k<length;k++){
      this.hasOwnProperty(k) && (previous = func(previous,this[k],k,this));
    }
  }
  return previous;
}

//用法
[1,2,3,4].reduce(function(a,b){
    return a+b;
}); //返回10

co函数的用法

co(function *(){
    var a = yield size('./demo1.txt');
    console.log('demo1.txt的文件大小:'+a);
    var b = yield size('./demo2.txt');
    console.log('demo2.txt的文件大小:'+b);
    return [a,b];
})(function(err,done){
    console.log('=====end=====')
    console.log(done);
});

输出结果:
//demo1.txt的文件大小:12
//demo2.txt的文件大小:57
//=====end=====
//[12,57]

在写同步的代码,有木有觉得?这就是co函数的魅力

co函数的简单实现

function co(GenFun){
  var gen = GenFun();
  var next = null;
  return function(done){
      function _next(err,res){
          next = gen.next(res);
          if(next.done){
              done(null,next.value)
          }else{
              next.value(_next);
          }
      }
      _next();
  }
}

这段代码还是不好理解的,要慢慢琢磨,多看几遍,多写几遍,就熟悉了。下面也尝试理下思路:

  • co函数本质上是一个高阶函数;
  • co传递的参数是一个GeneratorFunction,当然实际上不止如此。然后yield住的又是另外一个高阶函数(实际上也不止如此)。
  • 上面高阶函数的返回的函数的传参是一个函数,且函数的参数一般下有两个,第一个代表错误err,第二个是返回的数据。
  • co函数的关键点就是在利用next函数执行Generator的next方法,然后执行next中的value函数,并又把next作为参数传递进去,这样形成以_next为媒介不断的调用Generator的next方法,直到结束。
  • 实际上的co函数支持各种传参的处理,并且4.0版本较之前版本的变化比较大,4.0版本更多的用了promise,详细的分析可以去看竹隐大神写的koa源码分析系列(二)co的实现koa源码分析系列(四)co-4.0新变化

三.面向函数编程

这家伙在各个领域目前都火的一塌糊涂,Android界的RxJava框架,iOS界的ReactiveCocoa框架,只要你花时间学习它,了解它,那机会爱上它,去吻它,去拥抱它。

先来感受下面向函数编程的理念:它强调的是让函数形成一个个的可组装,可复用的管道,然后根据需要组装起来,让数据如水一般的流过。

当然函数式编程就会设计到一堆的概念,来看看你知道几个:

  • immutable data 不可变数据,
  • first class functions,
  • 尾递归优化,
  • composition 可组装性,
  • pipelining 管道,
  • higher order functions 高阶函数
  • map 和 reduce
  • 柯里化currying
  • Monad等

对于JS技术而言,上面得诸如map,reduce和currying本质都是高阶函数的运用。对于其它语言如swift,会有自动柯里化,让人欢喜不易,例子和教程网上已经有非常多了,下面来给出一些链接:

四.面向切面编程(AOP)

这里写出来就是为了装装逼,自己目前也不是很懂,不过这个理念在新一代node后端框架koa中有了很深的运用。

面向切面编程(AOP)是一种增强对象和方法的行为的技术,可以无侵入地增加功能。AOP 允许你从“外部”增加新行为,以及合并和修改既有的行为

具体一点讲就是对于每一个函数,然后我可以去规定开始和结束,然后把中间的权限交出去,因此对于一个函数而言,它并不能确定自己内部会发生什么事情。我们来看下koa框架的使用

var koa = require('koa');
var app = koa();
//中间件1
app.use(function *(next){
    var start = new Date;
    console.log("===start中间件1===");
    yield next;
    console.log("===end中间件1===");
    console.log(this.method);
});
//中间件2
app.use(function *(){
    console.log("===start中间件2===");
    yield next;
    console.log("===end中间件2===");
});
//中间件3
app.use(function *(){
    console.log("===start中间件3===");
    this.body = 'Hello World';
    console.log("===end中间件3===");
});

app.listen(3000);

//打印结果
//===start中间件1===
//===start中间件2===
//===start中间件3===
//===end中间件3===
//===end中间件2===
//===start中间件1===
//GET

这就和传统的编程范式不一样了吧。AOP在iOS和Android上都有很多的用法,绝不会像上面那么简单,人傻,还要多看书才行。

五.React

React是facebook发布的前端view层面的框架,但也引入了很多新的技术和理念,稍微浏览一下:

同构Javascript

这指的是同一份代码,运行在服务端也行,运行在客户端也行,当然与浏览器BOM和DOM无关的纯javascript语言层面的东西是很简单的,node早就统一了前后端,但是如果加上DOM呢?也就是很难做服务端预渲染技术(不是服务端直出技术)。但为什么需要预渲染技术呢?

我们先来看看ajax技术:用户首次访问的时候,会先加载个外壳,然后慢慢填充数据,界面可能会是一块一块出来的,体验不算很好,但加载完了就感觉很不错了。再来看看后台页面直出技术:第一次访问,页面一次加载完成,但后面即使一个小的跳动,也都要整个页面刷新,体验也就不会很好了。

这个时候服务端预渲染技术就来帮你了,采用了Virtual DOM虚拟DOM技术,就可以在服务端先把整个虚拟树构建好,然后推送到客户端,实现首屏类直出的渲染,然后后续由客户端继续维护虚拟DOM,实现ajax拉取数据的方式。这样就感觉棒棒的了。

单向数据流Flux

这是facebook提出的新的模型,用来配合React完成服务端交互以及用户交互。理解起来不是很难:

不管是服务端交互,还是用户交互,都会产生Action,然后Action,然后Dispatcher分发Action,Store收到Action后更新数据,最后更新到view上。

Flux

逻辑很简单,代码量会增多,但维护会方便很多,具体实现可以去玩玩alt。

diff算法

diff算法是把virtual dom更新到真实DOM所用到的算法,它会差异化比较然后只更新变动的部分,而不是像Angular一样全部更新,这也是React高效的真实原因了,有空去膜拜一下。

六.Webpack

又一个功能很强的前端构建框架,迎合新的时代,他可以去做的事情:

  • ES6已经开始流行,可以借助babel-loader将es6代码转为es5代码
  • css已经很流行预处理了,所以有sass-loader,less-loader,现在比较流行css直接用style内嵌了,所以还有style-loader,loader合一串联
  • 小的图标可以转换为base64,ttf,eot,svg,woff等的加载
  • 打包,可以方便的打包成多个文件,还可以借助插件提取公共代码部分
  • 一大堆插件,一半都是按需去找

这是个最好的时代,也是个最坏的时代,持续学习,持续奋斗。

←支付宝← →微信 →
comments powered by Disqus