JavaScript 之元编程

本文介绍 JavaScript 元编程中常使用的一些语法和简单应用. 有兴趣的可以参考文末给的链接了解更多的关于 JavaScript 元编程方便的知识.

1. Symbol

class MyClass {
    static [Symbol.hasInstance](lho) {
        return Array.isArray(lho);
    }
}
assert([] instanceof MyClass);

class Collection {
  *[Symbol.iterator]() {
    var i = 0;
    while(this[i] !== undefined) {
      yield this[i];
      ++i;
    }
  }

}
var myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(var value of myCollection) {
    console.log(value); // 1, then 2
}

Symbol 是 ES6 引入的新的数据类型, 应用范围十分广泛.

2. Proxy

一次关于 Proxy 的尝试.

规定要读取环境变量的值用形如 ENV:[A-Z0-9._]+ 的形式表示

let config = {
  "host": "127.0.0.1",
  "name": "admin",
  "pass": "ENV:DB_PASS" // 要求转为环境变量返回
}
config = new Proxy(config, {
  get (target, property) {
    if (property in target) {
      if (/^ENV:[A-Z0-9._]+$/.test(target[property])) {
        return process.env[target[property].slice(4)]
      } else {
        return target[property]
      }
    } else {
      throw new ReferenceError()
    }
  }
})

类似的可以自己约定并使用 Proxy 实现一些特性, 如私有属性规定 _ 开头, 使用 Proxy 拦截请求返回 undefined. 也可以使用 Proxy 实现访问日志.

Proxy 方法有很多, 而且和 Reflect 一一对应.

proxy = new Proxy({}, {
  apply: Reflect.apply,
  construct: Reflect.construct,
  defineProperty: Reflect.defineProperty,
  getOwnPropertyDescriptor: Reflect.getOwnPropertyDescriptor,
  deleteProperty: Reflect.deleteProperty,
  getPrototypeOf: Reflect.getPrototypeOf,
  setPrototypeOf: Reflect.setPrototypeOf,
  isExtensible: Reflect.isExtensible,
  preventExtensions: Reflect.preventExtensions,
  get: Reflect.get,
  set: Reflect.set,
  has: Reflect.has,
  ownKeys: Reflect.ownKeys,
})

另一个例子:

function Foo() {
  return new Proxy(this, {
    get: function (object, property) {
      if (Reflect.has(object, property)) {
        return Reflect.get(object, property);
      } else {
        return function methodMissing() {
          console.log('you called ' + property + ' but it doesn\'t exist!');
        }
      }
    }
  });
}

Foo.prototype.bar = function () {
  console.log('you called bar. Good job!');
}

foo = new Foo();
foo.bar();
//=> you called bar. Good job!
foo.this_method_does_not_exist()
//=> you called this_method_does_not_exist but it doesn't exist

- 阅读剩余部分 -

小结 2016

2016 年, 要用一个词来形容我的话, 那可能是"技术宅". 终日沉迷代码不能自拔, 不过正因如此, 技术上成长特别快. 马上要 2017 年, 稍微总结下自己即将过去的 2016 年吧~

技术篇

2016 年, 学习使用了非常多的东西, 包括但不限于:

AngularJS, React, Redux, Vue2, Vuex, Underscore, Electron, Koa2, PEG.js, D3, jQuery, ESLint, Swig, Mongoose, Sass, PostCSS, Gulp, Bower, Webpack, NPM, Git, PHP, Laravel, Vagrant, Bootstrap, Python, BeautifulSoap.

有的技术是花了很多时间, 例如 AngularJS 几乎做了一整年项目, Vue2 则是目前主要使用的前端框架, Koa2 是目前主要使用的后端框架. 而有些则是做一下小应用, 例如 React 和 Redux 结合这做了一个问卷设计页面, PEG.js 写了一个 XML 解析器, Python 和 BeautifulSoap 撸爬虫. 另有一些其实都快忘了, PHP 和 Laravel 主要是去年年底和年初在搞, 自从把精力放在前端之后就再也没用, 大概已经忘光了.

项目篇

2016年, 做了很多项目

  • 使用 PHP 和 laravel 自己搭建了一个博客框架
  • 使用 Python 写了豆瓣爬虫
  • 使用 AngularJS 写了一个书籍推荐和购买平台
  • 使用 jQuery 写了一个飞机大战游戏
  • 使用 AngularJS 和 ELectron 写了一个类似 Windows 资源管理器的软件
  • 使用 AngularJS 和 Electron 写了一个磁盘分析软件
  • 使用 React 和 Redux 写了一个问卷设计小应用
  • 发布了 node-wmic 模块
  • 使用 PEG.js 写了一个 XML 解析器
  • 使用 Vue2, AngularJS, Koa2 写了一个 RSS 订阅器
  • 使用 Node 写了一个静态博客框架
  • 使用 JAVA 写了一个 YACC

- 阅读剩余部分 -

浅谈 co 库

最近在写一个静态博客框架. 但不同于 hexo 之类的, 框架本身提供了博客书写和管理功能. 后端同样使用了 Koa2 来做,

归功于 Arch, 我已经在使用 Node 7.2 了, 不过好像默认还是不能支持 asyncawait , 经查发现是要输入参数开启的=.=. 不过当时第一时间并没有想说去加参数解决, 而是使用了 co 这个库... 虽然也可以用 babel , 但是通过 babel 运行代码很难调试.

node.png

co 用起来其实和 async 差不多, 甚至一些地方更简洁, 比如他可以 yield 一个 Promise 数组. 而 async 则只能 await 一个 Promise.all. 其实归根就是 co 进行了一个隐性的转换. co 内可以直接 yeild 一个数组或对象, co 会自动把数组或对象里面的所有值尝试转为 Promise 并包装在 Promise.all 中返回.

yield [promise1, promise2, ..., promisen]

async 要处理多个异步的并行操作, 只能把这些操作手动放入 Promise.all 返回. 即:

await Promise.all([promise1, promise2, ..., promisen])

草案还有一个 await* , 这个也是最近才注意到的, 使用 await* 就可以这样写了:

await* [promise1, promise2, ..., promisen]

然而草案并不推荐 await* , 目前 Node 7.2 也不支持 await* , 不过 babel 支持 ~

- 阅读剩余部分 -

正确使用 JS 的 sort 方法

sort() 方法对数组的元素做原地的排序, 并返回这个数组. 默认按照字符串的 Unicode 编码排序. sort 排序可能是不稳定的. 但其实 sort 方法可能并没有你想象的那么简单, 不信的话你耐心往下看看.

默认排序

sort 方法支持传入一个比较函数, 如果不传入则默认按照其字符串的 Unicode 编码排序, 因此默认情况下会出现以下情况.

[5, 1, 4, 11, 42].sort()
// [1, 11, 4, 42, 5]

解决方法就是:

[5, 1, 4, 11, 42].sort((pre, curr) => pre - curr)
// [1, 4, 5, 11, 42]

pre - curr 的值有三种情况

  • 小于 0 时, curr 排在 pre 后面
  • 大于 0 时, curr 排在 pre 前面
  • 等于 0 时, curr 和 pre 的位置不变

另外, 我们也可以对字符串进行排序:

['d', 'c', 'b', 'a'].sort()
// [a, b, c, d]

可能?是不稳定?

因为 ECMAScript 只是制定了 sort 这个方法, 但并没有给出具体的实现方式以及是否需要稳定的要求, sort 的实现就和其他大多数的方法一样由浏览器自行制定. 不同的浏览器或者同个浏览器不同版本上 sort 方法可能是稳定的, 也可能是不稳定的.

这里结合 V8 源码分析下这个 sort 方法的内部调优过程.

在 V8 中, 会通过以下函数方法进入 innerArraySort

function ArraySort(comparefn) {
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.sort");

  var array = TO_OBJECT(this);
  var length = TO_LENGTH(array.length);
  return InnerArraySort(array, length, comparefn);
}

源码有点长, 可以自己看下 这里

注释已经点明了:

- 阅读剩余部分 -

聊聊 Webpack 使用

老早的时候就听说了 Webpack 这个工具, 当时大概的印象就是类似 Gulp 这样的东西, 并且看起来好像挺复杂的. 直到学习 React 的时候才开始接触 Webpack, 才知道 Webpack 更多的是做模块化的工作. 不过当时也是乱配置一通能用就行=.=.

现在 Vue 标配也是用 Webpack 了. Webpack 其实并没有想象中的那么复杂, 其实最核心的还是 loader 那一块. 这次就主要聊一聊 Webpack. 我用的是 Webpack 最新版本 2.1.0-beta.27.

what-is-webpack.png

Loader

Loader 是 Webpack 的核心, 它会自动查找项目中的我们指定的文件类型, 然后使用我们指定的 Loader 进行处理. 例如:

module: {
  rules: [{
    test:    /\.vue$/,
    loader:  'vue-loader',
    options: {
      loaders: {
        css: ExtractTextPlugin.extract({
          loader:         ['css-loader?minimize', 'postcss-loader'],
          fallbackLoader: 'vue-style-loader'
        })
      }
    }
  }, {
    test:    /\.js$/,
    loader:  'babel-loader',
    exclude: /node_modules/
  }, {
    test:   /\.css$/,
    loader: ExtractTextPlugin.extract({
      loader: ['css-loader?minimize', 'postcss-loader']
    })
  }, {
    test:   /\.(eot|woff|woff2|ttf)([\?]?.*)$/,
    loader: 'file-loader'
  }, {
    test:   /\.(png|jpg|gif|svg|ico)$/,
    loader: 'url-loader?limit=8192',
  }]
},

对于 Vue 文件, 我们要让 vue-loader 来处理, 这里可以先忽略 ExtractTextPlugin 部分, 它作用是提取 CSS 这个在后面会提. 对于 .js 文件, 我们使用 babel-loader 来处理, 我们可以在项目配置一个 .babelrc 文件来指定我们使用的 presets 和 plugins.

- 阅读剩余部分 -

使用 Koa2 开发小结

RSS 订阅器项目是我最近花时间比较多的一个项目了. 在这个项目中我使用了大量的新技术, 很多技术都是我第一次使用. 后端是基于 Koa2 和 Mongoose 的 RESTful API.

在这个项目开展前, 我已经有半年多没接触后端了. 上一次后端还是用 PHP 以及 Laravel 框架开发的 LNMP 架构. 在动工前, 我也没有正式的使用过 Node 以及其部署, 对于 Koa2 的 async await 的异步书写方式也只是久仰大名而已.

这篇博客主要想说一说自己在使用 Node.js 和 Koa2 开发后端过程中的一些总结和收获.

中间件

Koa2 本身是一个非常轻的框架, 我们需要使用大量的中间件去完善它, 例如 koa-bodyparser , koa-etag, koa-router, koa-sslify 等等.

同时, 肯定免不了自己写中间件, 例如我自己就写了 7 个中间件, 分别是处理缓存, 处理 cookies, 强制 www, 配合前端 HTML5Mode, 错误处理, UA 判断, JWT 和 XSRF 处理.

module.exports = function () {
    return async(ctx, next) => {
        if (/^\/(mark|square|feed|feeds|post|posts|me|search)/.test(ctx.request.url)) {
            if (ctx.mobile) {
                await send(ctx, './public/index.html')
            }
            else {
                await send(ctx, './public/pc.html')
            }
        }
        await next()
    }
}

上述就是配合前端 HTML5Mode 以及根据 UA 指向不同入口文件的中间件.

- 阅读剩余部分 -