|快乐十分计划群李成银的博客 http://hlj35.com 关注前端开发、Node.js zh-cn Thu, 15 Aug 2019 12:46:17 GMT |pc蛋蛋老群火爆农村的贝店是个什么鬼? http://hlj35.com/post/neidian-intro.html

前几天,老家的一个亲戚在微信问我,贝店可靠么?是不是骗人或者传销什么的,说现在有很多人在开贝店。她也想开一个店,但不知道靠不靠谱,怕是传销之类的。

说实话,之前还真没听过这个名字,第一反应应该是跟微店或者拼多多之类的电商平台差不多吧。我平时购物基本上都是京东自营,连淘宝都极少用。

为了给亲戚一个答复,我准备了解了这个平台。

关于贝店

从百科中了解到,贝店 创立于2017年8月,是专注于家庭消费的社交电商平台,贝店采用自营+品牌直供的模式,与源头品牌直接合作,店主无需囤货、发货,由贝店统一采购、统一发货、统一服务。贝店是贝贝集团基于“无社群不电商”的理念,全新推出的社交电商APP。

由演员刘涛代言,个人可以申请开店,帮助贝店卖东西,贝店提供佣金。

如何开店

下载贝店的 APP 后,通过升级权益的方式成为店主。首先需要输入别人的推荐码(如:19665797),然后需要话费 ¥398/¥439 购买一件物品,这个物品会返还 100 成长值,然后利用这个成长值可以成为店长。

大致浏览了下,售价¥398 的物品京东上只卖一百多。其实就是花¥398开店,然后送你件便宜的商品。亲戚说前段时间有个活动,不用花钱可以开店。

开店完成后,可以为每件商品生成截图然后在微信群或者朋友圈里宣传。这个时候其实跟之前的微商一样,通过熟人关系来推广。

总结

  • 贝店是一个电商平台,通过拉人的方式(推荐码),将商品通过微信群/朋友圈来卖给熟人。然后店主获取一些佣金
  • 由于 App 里看不到店长相关的信息,店长想要卖东西的话必须经常性的把商品生成截图,通过熟人宣传,做的始终是熟人的生意。这个方式实际上就是店主帮助贝店宣传,导入到贝店的 APP 里
  • 对于商品,目前看到的还都是贝店自营的,感觉上质量应该比拼多多之类的好一些。买了个小商品,到时候看看效果。
  • 这种通过拉人头的方式除了有真实的商品来卖,感觉有一点点传销的性质。所以这个模式后面会不会被政府监管,还不知道
  • 至于能不能赚钱,就看有多大的推广渠道了(也就是能坑多少熟人了,哈哈)
  • 这个模式主要靠店主来推广,购买者没什么推广意愿,所以裂变效应没有拼多多来的大
  • 这个平台和拼多多一样,都是先从农村火起来的,反而我们这群人后面才知道[捂脸]。

可以通过 https://m.beidian.com/shop/shopkeeper.html?shop_id=2116217 看效果。

]]>
Tue, 30 Oct 2018 09:00:00 GMT http://hlj35.com/post/neidian-intro.html
|南京彩票群香港买保险及游记 http://hlj35.com/post/buy-insurance-in-hongkong.html

这几天去香港买了一份友邦的重疾险,也和那边的亲戚朋友聊了下,对香港的感知记录如下:

关于保险

  • 友邦的重疾险包括 53 种重大疾病以及身故,我买的是 $150000 的额度,保费交 18 年,一年费用大概是 $3900 多
  • 该保险是终身的,前十年赠送 50%,并且后续会有一些分红
  • 小孩买比较便宜,大概是大人的一半左右
  • 买保险需要去个人医务所做一些简单的体检(身高、血压、验血、之前有没有动过手术之类的)
  • 需要办一张香港那边的银行卡,方便以后续交保费(我办的时中国银行,当然这个卡也可以拿到炒港股)

其他

  • 香港的最低工资 9600 港币,中位数(在 50% 左右时候的工资)为 2W 多港币,上班的话工资增长较为缓慢
  • 以家庭为单位缴税,有很多的减税额(亲戚家有小孩,一年有 30W 的减税额,他老婆还在读博士不用缴税,所以他们家交的税非常少)
  • 香港山比较多,感觉跟重庆有点像,所以高架很多
  • 有很多公园,开发的地方很少,导致房子价格都很高,并且几乎也没楼间距,居住条件比较差。(当年香港回归的时候协议里规定了很多地方都不让开发,并且香港那边建房子比较流程很复杂,导致很缓慢)
  • 房屋产权有 999 年,也有 99 年的,有公摊。但到了 2047 年后就不知道是什么样了(回归 50 年)?
  • 房子很贵,不管是租还是买,导致房子非常小(30平米可以做一室一厅一厨一卫,房间可能是三面靠墙), 60 平米的房子算是很大的了(亲戚租的房子都快到深圳了,60 多平还 1.1W 港币/月,市区的话至少得 3W 多)。买的话便宜的也得十多万一平,好的大概二三十万一平。
  • 公共交通做的非常好(地铁、巴士很多),私家车并不多,虽然路不宽,但堵车的情况较少。靠左行使,车开的很快。巴士要提前按铃,不然不停。道路没有台湾干净。
  • 屈臣氏/7-ELEVEN 等便利店非常多,可以在屈臣氏里买奶粉。
  • 朋友对香港的未来不太看好,以目前的发展速度,感觉大陆部分城市 5-10 年应该就超越了香港,所以他的小孩还是在广州生的。
  • 由于比较明主,导致很多事情很难推进,或者周期很长(港珠澳大桥香港段因为一个老奶奶对两份环评报告提起司法复核导致延期了二年,扩建机场要填海部分居民反对导致搁浅)
  • 看病很麻烦,公立医院很便宜但要排队很久(治疗白内障可能要排队 1-2 年),私立医院非常贵(相对的大陆看病真的非常便宜),导致很多人都会自己买保险,这也是为什么香港保险业很发达的一个原因。
  • 医生一般会医院干几年,然后就会自己开个医务所,所以个人的医务所非常多。普通的病一般都会在医务所看。医生的收入很高。

alt

alt

alt

alt

alt

alt

]]>
Wed, 27 Sep 2017 09:53:00 GMT http://hlj35.com/post/buy-insurance-in-hongkong.html
|北京赛车群二维码ThinkJS 3 正式版发布! http://hlj35.com/post/thinkjs-release-3.html

ThinkJS 是一款拥抱未来的 Node.js Web 框架,致力于集成项目最佳实践,规范项目让企业级团队开发变得更加简单,更加高效。我们的 Github 地址是:https://github.com/thinkjs/thinkjs 欢迎大家 star~

ThinkJS 2.x 发布的时候,虽然我们带来了很多非常棒的功能,但我们也意识到本身的不足。针对这些问题,开发团队经过几个月的紧张开发,我们很高兴的宣布,ThinkJS 迎来了新的大版本 3.0。目前该版本已经在线上多个项目中使用,简单一个命令就可以让你下载使用它:

npm install -g think-cli

有哪些更新?

正如之前所说,为了解决之前遗留的不足问题我们开发了新版本,下图为我们新版的架构图。

除了引进了新的架构,对框架进行高度解构外,我们还增加了很多新的功能,优化了老版为人诟病的用法,下面就让我们来具体看看有哪些更新吧。

基于 Koa 重构框架架构

相较于之前使用自身集成架构,3.0 最大的改变就是将底层架构基于 Koa 重构。重构之后的 ThinkJS 3.0 版本能完全兼容 Koa 的中间件。使用 Koa 重构主要有下面几个原因:

  • Koa 现在已经比较稳定,洋葱模型的中间件更能满足多维度的需求,同时中间件生态已经慢慢成熟。这些都是 ThinkJS 之前所不具备的,基于 Koa 后就可以站在巨人的肩膀上,可以做的更好。

  • Koa 2.0 中使用更优雅的 async/await 解决异步问题,这个理念和 ThinkJS 是完全一致的。借助 Babel 在 ThinkJS 2.x 版本中就可以使用这些 ES next 语法功能。随着 Node 8.x LTS 的即将发布, async/await 被原生支持后我们就可以不用借助 Babel 转译直接在生产环境中使用了。

精简核心,支持扩展和适配器

ThinkJS 2.x 版本内置了很多功能,这些功能基本上是很多项目的最佳实践,但并不一是每个项目都需要这些所有的功能,但基于 2.x 的架构要实现这个需求就非常困难。所以 ThinkJS 3.0 版本基于 Koa 重构后在这块做了精简,不再提供丰富的功能,而是提供一个最基本的核心,然后通过扩展满足更多的需求。

多进程模型

ThinkJS 2.x 单进程是可选配置,且框架设计较为简单,很多功能需要开发者手动处理。我们在 3.0 中开发了 think-cluster 模块直接内置了多进程模型,最大限度的利用多核 CPU 提高响应速度。 同时,提供一套进程之间通信的方式,如::通知其他进程执行某个任务,代码在线更新后重启所有的子进程。

智能的错误提示

Node.js 开发中一个比较头疼的问题就是错误处理问题,遇到问题后查错和定位问题后比较麻烦,尤其是使用了 Babel 转译之后,为此我们专门开发了 think-trace 模块用来跟踪错误问题,能够更加友好的捕捉显示错误堆栈信息。

还有!

除了刚才说的一些功能,我们还有一些其它的更新要告诉大家:

感谢

最后感谢所有使用 ThinkJS 框架的用户,感谢你们多年来的支持,是你们的存在让我们有了更进一步的勇气。当然还需要感谢所有为 ThinkJS 项目做过贡献的贡献者,包括代码贡献和文档贡献,你们的努力让 ThinkJS 变的越来越棒!如果没时间帮助贡献代码想用其它方式支持我们也可以通过捐赠的方式支持我们,你们的捐赠会用来帮助 ThinkJS 推广。感谢你们所有人,让我们一起拥抱 ThinkJS,期待更美好的未来吧!

]]>
Tue, 05 Sep 2017 01:07:22 GMT http://hlj35.com/post/thinkjs-release-3.html
|极速快三计划群台湾 JSDC 之旅 http://hlj35.com/post/jsdc-2016-taiwan.html 这个月参加了台湾的 JSDC 大会,顺便玩了下台湾,有一些认知在此记录下。

  • 台湾由于当时城市规划不好,现在房屋比较杂乱,并且很多地方也比较旧。
  • 环境很好,没有雾霾。上下班高峰期也不怎么拥堵。
  • 是允许燃油助力车的,需要有驾照,燃油助力车和机动车跑在同样的车道。必须带头盔,哪怕是坐在后面的人。
  • 民众素质很高,经常会说谢谢。
  • 道路很干净,连烟头也很少看到,垃圾分类做的很好。
  • 捷运(地铁)有博爱(老弱病残)坐,即使车厢内有很多年轻人站着,也不敢坐博爱座。
  • 因为九二共识政治因素,大陆去台湾的游客少了 60%-70%(和宾馆前台聊天得知),他们对蔡英文的意见也很大。
  • 对数字 4 很避讳,楼层没有 4 层,房间号码也不会有 4。但对数字 250 并不避讳,如:一碗面 250 块。
  • 说话就是像台剧里那样,很嗲,哪怕男生也是这样。
  • 软体(软件)行业发展并不怎么好,行业薪水也没大陆高。
  • 南港软件园(类似:西二旗软件园)的房价是 33 坪(一坪大概 3.3 平方米)是 2500W(人名币为 530 多万)
  • 哪怕个人开的饭馆也会给自己放假,景点也会放假,并且休息时间点不定。
  • 整体节奏比较慢,压力小。
  • 电视台里说大陆的时候也比较客气,没有看到像之前说大陆吃不起茶叶蛋的情况。

这次玩的地方有:故宫博物院、中正纪念堂、西门町、101 大厦、野柳地质公园、花莲等,整体来说去台湾旅游还是不错的,值得第二次去。

]]>
Sun, 30 Oct 2016 00:01:33 GMT http://hlj35.com/post/jsdc-2016-taiwan.html
|网赚彩票群【译】通过开发 Babel 插件理解抽象语法树(AST) http://hlj35.com/post/understanding-asts-by-building-your-own-babel-plugin.html

原文:http://www.zcfy.cc/article/347

每天数以千计的 JavaScript 开发者使用的语言版本现在浏览器都还没有完全实现,许多他们使用的语言特性仅仅还只是建议,并没有保证说一定会纳入规范。因为有 Babel 项目使现在就能使用这些特性变成了可能。

Babel 是我们知道的将 ES6 代码转译为 ES5 代码且能安全稳定运行最好的工具,同时它允许开发者开发插件,能够在编译时期转换 JavaScript 的结构。

现在,我们来看看如何开发一个给 JavaScript 添加默认不可变数据的 Babel 插件,代码可以从 GitHub repo 下载。

语言概述

我们想设计一个通过 Mori 将普通对象和数组字面量转换为持久数据结构的插件。

我们希望写出来的代码类似这样:

var foo = { a: 1 };
var baz = foo.a = 2;
foo.a === 1;
baz.a === 2;

然后将代码转换为:

var foo = mori.hashMap("a", 1);
var baz = mori.assoc(foo, "a", 2);
mori.get(foo, "a") === 1;
mori.get(baz, "a") === 2;

我们借助 MoriScript 来开始吧。

Babel 概述

如果我们想更深入的了解 Babel,我们需要知道 3 个处理流程中很重要的工具。

Babel Process

解析

Babylon 是一个解析器,它可以将 JavaScript 字符串转换为对计算机来说更加友好的表现形式,称之为抽象语法树(AST)。

转换

babel-traverse 模块允许你浏览、分析和修改抽象语法树(AST)。

生成

最后,babel-generator 模块用来将转换后的抽象语法树(AST)转换为 JavaScript 字符串。

什么是抽象语法树(AST)

在继续本教程之前,我们有必要先了解抽象语法树(AST)的用途,所以先来看看它是什么以及我们为什么需要它。

JavaScript 程序通常是由一系列的字符组成的,每一个在我们的大脑中都有一些可视的含义。这对我们来说非常方便,可以让我们使用匹配的字符([], {}, ()),成对的字符('', "")和缩进让程序解析起来更加简单。

然而,这对计算机来说并不是很有用,这些字符在内存中仅仅是个数值,而且计算机也不能询问一些像『在这个申明中有多少个变量』这种高级的问题。所以我们需要一些妥协,寻找一种可以让我们编程同时让计算机也能理解的方式。

我们来看看下面的代码:

var a = 3;
a + 5

当我们把这个代码转换成抽象语法树(AST)的时候,会得到一个类似下面的结构图:

AST Example

所有的抽象语法树(AST)根节点都是 Program 节点,这个节点包含了所有的最顶层语句。这个例子中,包含了 2 部分:

  1. 一个变量声明,将标识符 a 赋值为数值 3

  2. 一个二元表达式语句,描述为标志符为 a,操作符 + 和数值 5

尽管它们看上去只是由一些简单的元素组成的,对应的抽象语法树(AST)通常情况下也比较复杂,尤其是一些复杂的程序。我们不要试图自己去分析抽象语法树(AST),可以通过 astexplorer.net 网站帮助我们来完成,它允许我们在左边输入 JavaScript 代码,右侧会出可浏览的抽象语法树(AST),我们可以通过这个工具辅助理解和试验一些代码。

为了保持使用 Babel 的一致性,确保选择的解析器为 "babylon6"

当我们开发 Babel 插件时,我们的任务就是插入/移动/替换/删除一些节点,然后创建一个新的抽象语法树(AST)用来生成代码。

安装

开始之前确保你安装了 nodenpm,然后创建一个项目目录,添加文件 package.json,并安装开发环境下的依赖。

mkdir moriscript && cd moriscript
npm init -y
npm install --save-dev babel-core

此时,我们为插件创建一个文件,内容为导出一个默认的函数:

// moriscript.js
module.exports = function(babel) {
  var t = babel.types;
  return {
    visitor: {

    }
  };
};

这个函数给 visitor pattern 导出了一个接口,我们后续再来介绍它。

最后,我们创建一个测试插件的运行器。

// run.js
var fs = require("fs");
var babel = require("babel-core");
var moriscript = require("./moriscript");

// read the filename from the command line arguments
var fileName = process.argv[2];

// read the code from this file
fs.readFile(fileName, function(err, data) {
  if(err) throw err;

  // convert from a buffer to a string
  var src = data.toString();

  // use our plugin to transform the source
  var out = babel.transform(src, {
    plugins: [moriscript]
  });

  // print the generated code to screen
  console.log(out.code);
});

我们通过指定 MoriScript 文件来运行这个脚本,用来检查生成的 JavaScript 代码是不是我们期望的。如:node run.js example.ms

数组

MoriScript 首要和最重要的目标就是将普通对象和数组字面量转换为 Mori 对应的部分:HashMaps and Vectors。我们先来看看数组,它会相对简单一些:

var bar = [1, 2, 3];
// should become
var bar = mori.vector(1, 2, 3);

拷贝上面的代码到 astexplorer,选中数组字面量 [1, 2, 3],查看对应的抽象语法树(AST)节点。

为了更好的可读性,我们忽略了元数据字段,因为我们不用关心它

{
  "type": "ArrayExpression",
  "elements": [
    {
      "type": "NumericLiteral",
      "value": 1
    },
    {
      "type": "NumericLiteral",
      "value": 2
    },
    {
      "type": "NumericLiteral",
      "value": 3
    }
  ]
}

现在我们对 mori.vector(1, 2, 3) 进行相同的操作。

{
  "type": "CallExpression",
  "callee": {
    "type": "MemberExpression",
    "object": {
      "type": "Identifier",
      "name": "mori"
    },
    "property": {
      "type": "Identifier",
      "name": "vector"
    }
  },
  "arguments": [
    {
      "type": "NumericLiteral",
      "value": 1
    },
    {
      "type": "NumericLiteral",
      "value": 2
    },
    {
      "type": "NumericLiteral",
      "value": 3
    }
  ]
}

如果我们通过可视化方式看的话,可以更好的看到 2 个语法树之间需要修改的部分。

Array AST

现在我们可以非常清晰的看到我们需要替换顶层的表达式,同时要保留 2 个语法数之间共同的数值字面量。

我们给 visitor 对象添加 ArrayExpression 方法。

module.exports = function(babel) {
  var t = babel.types;
  return {
    visitor: {
      ArrayExpression: function(path) {

      }
    }
  };
};

当 Babel 遍历抽象语法树(AST)时,它会查看每一个节点。当发现插件里 visitor 对象上有相应的方法时,会调用这个方法,并将上下文传递进去。所以我们可以进行分析或者替换操作。

ArrayExpression: function(path) {
  path.replaceWith(
    t.callExpression(
      t.memberExpression(t.identifier("mori"), t.identifier("vector")),
      path.node.elements
    )
  );
}

我们可以通过 babel-types 模块的文档 documentation 查看每个表达式的类型。在这个例子下,我们可需要将 ArrayExpression 替换为 CallExpression,它可以通过 t.callExpression(callee, arguments) 来生成。实际上需要调用的是 MemberExpression,后者可以通过 t.memberExpression(object, property) 来生成。

同样地,你可以在网站 astexplorer 上运行,点击 “transform” 下拉菜单,然后选择 “babelv6”

对象

接下来我们来看看对象:

var foo = { bar: 1 };
// should become
var foo = mori.hashMap("bar", 1);

对象字面量和前面的 ArrayExpression 含有类似的结构。

{
  "type": "ObjectExpression",
  "properties": [
    {
      "type": "ObjectProperty",
      "key": {
        "type": "Identifier",
        "name": "bar"
      },
      "value": {
        "type": "NumericLiteral",
        "value": 1
      }
    }
  ]
}

这个相当直白,properties 是个数组,每个元素都包含一个 key 和 一个 value。现在选中 mori.hashMap('bar', 1) 然后看相对应生成的结构是如何构成的。

{
  "type": "CallExpression",
  "callee": {
    "type": "MemberExpression",
    "object": {
      "type": "Identifier",
      "name": "mori"
    },
    "property": {
      "type": "Identifier",
      "name": "hashMap"
    }
  },
  "arguments": [
    {
      "type": "StringLiteral",
      "value": "bar"
    },
    {
      "type": "NumericLiteral",
      "value": 1
    }
  ]
}

同样地,我们通过可视化的方式看看抽象语法树(AST)。

Object AST

像之前一样,CallExpression 里面包含了 MemberExpression,这个可以从上面数组的代码里借用。但是为了将属性和值放在一个扁平化的数组里我们需要做一点复杂的事情。

ObjectExpression: function(path) {
  var props = [];

  path.node.properties.forEach(function(prop) {
    props.push(
      t.stringLiteral(prop.key.name),
      prop.value.value
    );
  });

  path.replaceWith(
    t.callExpression(
      t.memberExpression(t.identifier("mori"), t.identifier("hashMap")),
      props
    )
  );
}

这个实现跟数组的实现方式非常类似,除了我们不得不将 Identifier 转换为 StringLiteral 以防止最终得到的代码类似于:

// before
var foo = { bar: 1 };
// after
var foo = mori.hashMap(bar, 1);

最后,我们创建一个助手函数迁去创建 Mori MemberExpressions 好让我们能继续使用。

function moriMethod(name) {
  return t.memberExpression(
    t.identifier("mori"),
    t.identifier(name)
  );
}

// now rewrite
t.memberExpression(t.identifier("mori"), t.identifier("methodName"));
// as
moriMethod("methodName");

现在我们可以创建并运行一些测试用例,看看插件是否正常工作:

mkdir test
echo -e "var foo = { a: 1 };\nvar baz = foo.a = 2;" > test/case.ms
node run.js test/case.ms

你可以在终端下看到类似下面的信息:

var foo = mori.hashMap("a", 1);
var baz = foo.a = 2;

赋值

为了让新的 Mori 数据结构有效,我们需要将新的属性覆盖到原生的语法结构上。

foo.bar = 3;
// needs to become
mori.assoc(foo, "bar", 3);

这次我们就不再给出抽象语法树(AST),只看图示和插件代码,但你自己可以通过 astexplorer 来查看相应的抽象语法树(AST)。

Assignment AST

我们需要展开并转换节点,将每一个 AssignmentExpression 变成想要的 CallExpression

AssignmentExpression: function(path) {
  var lhs = path.node.left;
  var rhs = path.node.right;

  if(t.isMemberExpression(lhs)) {
    if(t.isIdentifier(lhs.property)) {
      lhs.property = t.stringLiteral(lhs.property.name);
    }

    path.replaceWith(
      t.callExpression(
        moriMethod("assoc"),
        [lhs.object, lhs.property, rhs]
      )
    );
  }
}

AssignmentExpressions 处理方法先是初步检测表达式左侧是否有 MemberExpression(因为我们不希望类似 var a = 3 混淆进去),然后替换为 CallExpression,它通过 Mori assoc 方法创建的。

像之前一样,我们处理使用了 Identifier 的地方,将其转换为 StringLiteral

现在创建另一个测试用例来检查代码是否正常工作:

echo -e "foo.bar = 3;" >> test/case.ms
node run.js test/case.ms

$ mori.assoc(foo, "bar", 3);

成员

最后,我们需要替换访问一个对象的原生语法:

foo.bar;
// needs to become
mori.get(foo, "bar");

这里是 2 个可视化的抽象语法树(AST)。

Member AST

我们差不多可以直接使用 MemberExpression 属性,然后当属性是个 Identifier 的时候,需要将其转换。

MemberExpression: function(path) {
  if(t.isAssignmentExpression(path.parent)) return;

  if(t.isIdentifier(path.node.property)) {
    path.node.property = t.stringLiteral(path.node.property.name);
  }

  path.replaceWith(
    t.callExpression(
      moriMethod("get"),
      [path.node.object, path.node.property]
    )
  );
}

首先最重要的一个不同点是当父级节点是 AssignmentExpression 时需要尽早的结束函数执行,这是因为我们希望 AssignmentExpression 访问器处理这些情况。

这看起来比较美好,当你运行这个代码的时候,你会发现出现堆栈溢出错误。这是因为使用 mori.get 替换 MemberExpression(foo.bar) 时,Babel 会遍历这个新的节点,并且重新递归的访问 MemberExpression 这个方法。

为了避免出现这个情况,我们可以对 moriMethod 返回值进行标记,然后在 MemberExpression 方法里根据标记进行忽略。

function moriMethod(name) {
  var expr = t.memberExpression(
    t.identifier("mori"),
    t.identifier(name)
  );

  expr.isClean = true;
  return expr;
}

一旦被标记后,函数内部可以添加另一个返回判断条件。

MemberExpression: function(path) {
  if(path.node.isClean) return;
  if(t.isAssignmentExpression(path.parent)) return;

  // ...
}

创建最后一个测试用例,编译代码并且检查是否正常工作。

echo -e "foo.bar" >> test/case.ms
node run.js test/case.ms

$ mori.get(foo, "bar");

如果一切顺利的话,现在你会了一门类似于 JavaScript 的语言。不同的是,在不影响原来语法的情况下,默认就有了不可变的数据结构。

结尾

这是一篇偏重代码的文章,但是覆盖到了所有设计和构建 Babel 插件的基础,可以作为一种有效的方式用来转换 JavaScript 文件。你可以通过 REPL here 的方式来使用 MoriScript,完整的代码可以在 on GitHub 找到。

如果你想更深入的了解 Babel 插件,你可以检出 GitHub 上仓库代码 Babel Handbookbabel-plugin-hello-world,还可以通过阅读更多的插件源代码 700+ Babel plugins already on npm。同样地还有创建插件的脚手架 Yeoman generator

希望这篇文章可以激发你去创建 Babel 插件。在你去实施下一个转译语言之前,这里有一些比较接地气的规则需要关注。Babel 是一个 JavaScript 到 JavaScript 的编译器,这意味着不能将 CoffeeScript 写成一个 Babel 插件。我们只能转换 Babel’s parser 可以理解的 JavaScript 超集

建议你通过 novel 插件来开始,你可以使用二进制操作符 | 创建一个函数式管道,类似于 F#, Elm 和 LiveScript 语言里的功能。

2 | double | square

// would become

square(double(2))

或者内置一个箭头函数:

const doubleAndSquare = x => x | double | square

// would become

const doubleAndSquare = x => square(double(x));

// then use babel-preset-es2015

var doubleAndSquare = function doubleAndSquare(x) {
  return square(double(x));
};

一旦你理解了这些规则,唯一的限制就是解析器和你的想象力。

你有 Babel 插件需要分享的么,请在评论里告知我。

英文原文:https://www.sitepoint.com/understanding-asts-building-babel-plugin/

]]>
Fri, 02 Sep 2016 06:31:01 GMT http://hlj35.com/post/understanding-asts-by-building-your-own-babel-plugin.html
|加拿大28赌群开发和维护 ThinkJS 是一种什么样的体验? http://hlj35.com/post/experience-of-thinkjs.html 受邀于在知乎上回答维护一个大型开源项目是怎样的体验,博客里做个备份。

先介绍下项目:ThinkJS,一个借助 Babel 编译,可以直接用 ES2015+ 特性开发 Node.js 项目的框架,为企业级 Node.js 项目开发提供巨大的便利。目前 GitHub 上 star 数为 1620,issue + pr 有 300 多,有 1700+ 单元测试用例和非常完善的中英文文档,已经有不少公司在使用。

项目事迹

提到 ThinkJS,可能有些人第一想到是不是和国内的 PHP 框架 ThinkPHP 有一些关系,你没猜错,刚开始 ThinkJS 就是借鉴 ThinkPHP 来开发的。这个想法是在 2013 年下半年的时候开始有的,那个时候 Node.js 框架主要还是 Express,但用 Callback 处理异步的方式非常让人头疼,而有另一种比较好的方案就是用 Promise,所以慢慢就有了借鉴 ThinkPHP,使用 Promise 机制开发一个 Node.js 框架的想法。

借鉴 ThinkPHP 框架有几个原因: 1:ThinkPHP 代码比较简单,借鉴成本较低。 2:ThinkPHP 在国内有一定的用户量,如果这部分用户想用 Node.js 开发项目肯定是希望有个和 ThinkPHP 相似的框架。 3:最重要的一点是,ThinkPHP 在国内持续维护了 7 年多的时间,这在国内是一个非常不容易的事情。我也是希望借助这个激励自己要把 ThinkJS 持续维护下去。

我们在 2014.09.22 发布了 ThinkJS 1.0 版本,在公司内部有较多的项目在使用,外部慢慢的也有一些人在使用。

随着越来越复杂的项目使用 ThinkJS,Promise 也暴露了一些弊端,如:不能很好的跳过一些中间环节和数据传递。随着 ES2015 规范的发布和 React 的火爆,虽然运行环境还不支持这些新的特性,但这里借助 Babel 编译可以提前使用这些新的特性。而对于异步处理方式有了更好的方式,Generator Function 或者 Async Function。

所以我们在 2015.03 完成了全新版本的设计,定位为可以在项目里直接使用 Es2015+ 特性开发,框架会进行自动编译和自动更新,大大方便 Node.js 项目的开发,同时优化 1.0 里不合理的架构和设计,脱离对 ThinkPHP 的依赖。于是我们在 2015.10.30 发布了 2.0 版本,这天也是 Babel 发布 6.0 的日子。

随着 2.0 的发布,ThinkJS 成为世界上第一个全面支持使用 ES2015+ 特性开发 Node.js 项目的框架,后续的版本又支持了 TypeScript,断点调试等功能。

和其他框架的对比

很多人一看到 ThinkJS,就觉得是个大而全的框架,估计就直接放弃了,他们更喜欢 Koa 这种小而美的框架。

其实 ThinkJS 并不是大而全,只是封装了一些常用的功能,为企业级开发而定制的。提供的 Middleware 和 Adapter 机制更适合企业级项目开发。

Koa 虽然本身很小,但实际开发项目时,需要自己寻找各种中间件,这些中间件质量层次不齐,有些功能模块接口不统一,这给团队开发带来很大的麻烦。同时团队还要基于 Koa 做自己做项目规范,经过时间才能积累最佳实践。而 ThinkJS 将这些直接提供了,省去了很多时间。

一些好玩的事情

自从有了英文文档后,慢慢的有些老外也在用 ThinkJS。发现这些老外真是积极,他们会主动给一些 awesome-* 项目发 pr,让其添加 ThinkJS,也会主动修改文档中的一些拼写错误,然后发 pr。

更好玩的是有个老外 EunseokEom 觉得 ThinkJS 的官网不太好看,所以他自己设计了个,并且给实现了,https://github.com/75team/www.thinkjs.org/pull/60,虽然这个网站我们觉得也不是很好看,最后没有合并,但老外这种贡献的精神真的非常赞。不过这也让我们有了优化官网的想法。

]]>
Mon, 06 Jun 2016 06:37:00 GMT http://hlj35.com/post/experience-of-thinkjs.html
|彩票投注群如何把 Callback 接口包装成 Promise 接口 http://hlj35.com/post/how-to-convert-callback-to-promise.html 前端开发尤其 Node.js 开发中,经常要调用一些异步接口,如:文件操作、网络数据读取。而这些接口默认情况下往往是通过 Callback 方式提供的,即:最后一个参数传入一个回调函数,当出现异常时,将错误信息作为第一个参数传给回调函数,如果正常,第一个参数为 null,后面的参数为对应其他的值。

var fs = require("fs");
fs.readFile("foo.json", "utf8", function(err, content){
    if(err){
        //异常情况
    }else{
        //正常情况
    }
})

当这种写法遇上比较复杂的逻辑时,就很容易出现 callback hell 的问题。为此,开发者也积极寻找对应的解决方案,如:Promise、ES6 Generator + co + Promise、ES2016 草案里的 async functions 等。

这几种方案也是慢慢的在进化,试图更好的处理 callback hell 的问题。但这几种方案一致的依赖基础方式都是 Promise,这也是为什么 Promise 并没有引入新的语法但也写进了 ES6 规范的一个大的原因。甚至现在一些新的接口(如:Fetch)直接返回 Promise。

然后对异步接口的处理方式都依赖 Promise,那么下面就来说下如何将 Callback 接口变成 Promise 接口。

Callback 接口变成 Promise 接口

其实 Callback 接口变成 Promise 接口非常简单,包括现在也有很多库都有类似的方法可以转换,如:

  • bluebird 模块里有 promisify 方法
  • es6-promisify 模块
  • ThinkJS 里的 promisify 方法

由于 Callback 接口的参数方式是固定的,所以很容易变成 Promise 接口,如:

let promisify = (fn, receiver) => {
  return (...args) => {
    return new Promise((resolve, reject) => {
      fn.apply(receiver, [...args, (err, res) => {
        return err ? reject(err) : resolve(res);
      }]);
    });
  };
};

几行代码基本就搞定了对 Callback 接口对 Promise 的转换,当然上面的代码是用 ES6 代码写的。用 ES5 写的话可以类似下面这样:

var promisify = function promisify(fn, receiver) {
  return function () {
    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    return new Promise(function (resolve, reject) {
      fn.apply(receiver, [].concat(args, [function (err, res) {
        return err ? reject(err) : resolve(res);
      }]));
    });
  };
};

有了 promisify 这样一个函数,那么把 Callback 接口变成 Promise 接口就非常简单了,如:

var fs = require("fs");
var readFilePromise = promisify(fs.readFile, fs); //包装为 Promise 接口
readFilePromise("foo.json", "utf8").then(function(content){
    //正常情况
}).catch(function(err){
    //异常情况
})

有了快速转换的方法后,就不用去找模块对应的 Promise 版本的模块了。

特殊情况

有些设计不合理的接口可能会传递多个值给回调函数,如:

var fn = function(foo, callback){
    if(success){
        callback(null, content1, content2);
    }else{
        callback(err);
    }
}

上面的代码在正常情况下会传递 2 个参数给回调函数,由于 Promise resolve 的时候只能传入一个值,所以这种接口变成 Promise 接口后是无法获取到 content2 数据的。

对于这种情况只能手工来包装了,同时顺便鄙视下设计这个接口的人。

担心性能

有些人担心大量使用 Promise 会引起性能的下降,这个事情在当初 Node.js 设计接口时也争吵了很久,有时候易用性和性能本来就是有些互斥的。

其实可以使用高性能的 Promise 库来提高性能,如:bluebird。简单对比测试发现,blurbird 的性能是 V8 里内置的 Promise 3 倍左右(bluebird 的优化方式见 https://github.com/petkaantonov/bluebird/wiki/Optimization-killers )。

可以通过下面的方式替换调内置的 Promise:

global.Promise = require("bluebird");

如果项目里用了 Babel 编译 ES6 代码的话,可以用下面的方式替换:

//Babel 编译时会把 Promise 编译为 Babel 依赖的 Promise
require("babel-runtime/core-js/promise").default = require("bluebird");
global.Promise = require("bluebird");
]]>
Tue, 19 Apr 2016 10:02:58 GMT http://hlj35.com/post/how-to-convert-callback-to-promise.html
|加拿大28下注群ThinkJS 项目里如何使用 Mongoose http://hlj35.com/post/use-mongoose-in-thinkjs.html ThinkJS 里内置的 ORM 可以很方便的操作关系型数据库和文档型数据库,支持:Mysql,SQLite,PostgreSQL,Mongo 等。如果内置的 ORM 不能满足项目的需求,那么也可以集成第三方的 ORM,如:Mongoose,Waterline 等。本文就来聊聊如何在 ThinkJS 里集成 Mongoose。

关于 Mongoose

Mongoose 是一个专门用来操作 Mongo 的 ORM,提供了很多非常有用的方法。可以通过 npm install mongoose 命令来安装 Mongoose,Mongoose 详细文档请见: http://mongoosejs.com/docs/guide.html

项目里可以直接引入 Mongoose,然后根据官方文档来使用,但这种方式下开发会比较麻烦,因为没法使用 ThinkJS 里内置的模型类、自动实例化和自动传入模型配置等功能了。

ThinkJS 模型与 Mongoose 的差异

ThinkJS 的模型是一个类,实例化的时候会传入模型名称和连接 Mongo 的配置,基类大致代码如下:

export default class {
    // name 为传入的模型名称,没有传入的话会自动分析当前的文件名作为模型名
   // config 为连接 Mongo 的配置
    constructor(name, config){
        this.name = name;
        this.config = config;
    }
}

而连接 Mongo 是通过 Mongo Socket 这个 Adapter 来完成的,后续操作都是等拿到 Socket Connection 后进行。

Mongoose 里是创建连接,然后定义 schema 和创建模型,使用时实例化模型。如果有多个配置连接的话,通过 createConnection 方法来创建连接。

var mongoose = require("mongoose")
  , Schema = mongoose.Schema

var personSchema = Schema({
  _id     : Number,
  name    : String,
  age     : Number,
  stories : [{ type: Schema.Types.ObjectId, ref: "Story" }]
});

var storySchema = Schema({
  _creator : { type: Number, ref: "Person" },
  title    : String,
  fans     : [{ type: Number, ref: "Person" }]
});

所以可以通过包装将 Mongoose 的格式转成 ThinkJS 的格式。

如何包装

"use strict";

import mongoose from "mongoose";

/**
 * model
 */
export default class extends think.base {
  /**
   * schema
   * @type {Object}
   */
  schema = {};
  /**
   * model instance
   * @type {[type]}
   */
  _model = null;
  /**
   * constructor
   * @param  {[type]} name   [description]
   * @param  {Object} config [description]
   * @return {[type]}        [description]
   */
  constructor(name, config = {}){
    super();
    if(think.isObject(name)){
      config = name;
      name = "";
    }
    this.name = name;
    this.config = think.parseConfig(config);
  }
  /**
   * 创建连接
   * @return {[type]} [description]
   */
  getConnection(){
    let user = "";
    if(this.config.user){
      user = this.config.user + ":" + this.config.password + "@";
    }
    let host = this.config.host || "127.0.0.1";
    let port = this.config.port || 27017;
    let str = `mongodb://${user}${host}:${port}/${this.config.database}`;

    return mongoose.createConnection(str);
  }
  /**
   * 获取 Mongoose 里的 Model
   * @return {[type]} [description]
   */
  getModel(){
    if(!this._model){
      let connection = this.getConnection();
      this._model = connection.model(this.name, new mongoose.Schema(this.schema));
    }
    return this._model;
  }
}

通过上面包装的方式后,可以通过 getModel 方法获取 Mongoose 里的模型,然后就可以对其实例化进行操作了。如:

export default class extends think.controller.base {
  /**
   * index action
   * @return {Promise} []
   */
  async indexAction(){
    let instance = this.model("user");
    let model = instance.getModel();
    let ret = await model.create({
      username: "welefen"
    });

    let data = await model.find({}).exec();
    console.log(data)

    //auto render template file index_index.html
    return this.display();
  }
}

实际项目中,建议将上面的包装放在 model/base.js 里,其他模型文件继承该类。 控制器里通过 getModel 方法获取模型,然后实例化并调用方法,也可以将这些逻辑封装在模型中。如:

// model/use.js

import Base from "./base";

export default class extends Base {
    schema = {
        username: String
    };
    findData(where = {}){
        let Model = this.getModel();
        return Model.find(where).exec();
    }
    addData(data = {}){
        let Model = this.getModel();
        return model.create(data);
    }
}

这样就可以通过 findData 方法来查询数据,addData 方法来添加数据了。

]]>
Mon, 11 Apr 2016 09:50:46 GMT http://hlj35.com/post/use-mongoose-in-thinkjs.html
|极速赛车计划群使用 node-inspector 断点调试 ThinkJS ES2015+ 项目 http://hlj35.com/post/use-node-inspector-debug-thinkjs-es2015-project.html ThinkJS 是第一个全面支持使用 ES2015+ 特性开发的 Node.js 框架,使用 ES2015 里的 */yield 或者 ES2016 草案里的 async/await 可以很好的解决异步嵌套的问题,借助 Babel 编译,可以稳定运行在 Node.js 各个主流版本中。同时 ThinkJS 提供了自动编译和自动更新的机制,免去了文件修改后重启 Node.js 服务的麻烦。

使用 ES2015+ 特性可以更好的开发 Node.js 项目,但由于项目需要编译,给断点调试带来了麻烦。

ThinkJS 从 2.2.0 版本开始支持断点调试 ES2015+ 的代码,本文就聊聊如何使用 node-inspector 来断点调试 ThinkJS 里的 ES2015+ 项目。如果项目里的 ThinkJS 版本小于 2.2.0,请先升级 ThinkJS,否则看不到效果。

安装 node-inspector

可以通过 npm install -g node-inspector 来全局安装 node-inspector,如果是在 *unix 系统下,需要在命令前面添加 sudo 执行。

启动 node-inspector 服务

通过命令 node-inspector & 来启动 node-inspector 服务。

启动 Node.js 服务

使用 node --debug www/production.js 来启动 Node.js 服务。

这里跟之前启动服务有些区别,由于启动时需要添加 --debug 参数,所以不能用 npm start 来执行启动了。

调试

访问 http://127.0.0.1:8080/debug?port=5858,会出现调试页面。

然后在 app 目录下找到对应的编译后的文件,在对应的地方加上断点(这里一定要是在 app/ 目录,不能是源代码 src/ 目录),如:

alt

然后新建标签页,访问对应的接口。这时候页面会一直卡在那里。这时候返回 node-inspector 的标签页,会看到内容已经跳到 ES2015+ 的代码,如:

alt

然后就可以利用后侧的断点工具进行调试了。

alt

总结

如果无法断点调试,请确认断点是否打在编译后的目录下(app/ 目录)。

]]>
Wed, 30 Mar 2016 02:19:25 GMT http://hlj35.com/post/use-node-inspector-debug-thinkjs-es2015-project.html
|百家乐计划群在 VS Code 下断点调试 ThinkJS ES2015+ 项目 http://hlj35.com/post/debug-thinkjs-2015-project-in-vscode.html ThinkJS 是第一个全面支持使用 ES2015+ 特性开发的 Node.js 框架,使用 ES2015 里的 */yield 或者 ES2016 草案里的 async/await 可以很好的解决异步嵌套的问题,借助 Babel 编译,可以稳定运行在 Node.js 各个主流版本中。同时 ThinkJS 提供了自动编译和自动更新的机制,免去了文件修改后重启 Node.js 服务的麻烦。

使用 ES2015+ 特性可以更好的开发 Node.js 项目,但由于项目需要编译,给断点调试带来了麻烦。

ThinkJS 从 2.2.0 版本开始支持断点调试 ES2015+ 的代码,本文就聊聊如何在 VS Code 里断点调试 ThinkJS ES2015+ 项目。如果项目里的 ThinkJS 版本小于 2.2.0,请先升级 ThinkJS,否则看不到效果。

打开项目

通过 VS Code 菜单 File -> Open 来打开 ThinkJS 2015+ 项目,如:

alt

设置调试配置

点击左侧的调试菜单,点击上面的调试按钮,会调试选择的环境,选择 Node.js。如:

alt

选择 Node.js 后,会生成一个 launch.json 文件。修改里面的配置,将 sourceMaps 值改为 true(注意:有 2 个 sourceMaps key,都修改)。

alt

启动服务

点击上面的调试按钮来启动服务。如果已经在命令行启动了 Node.js 服务,需要关掉,否则会因为端口被占用导致错误。

alt

开始调试

回到代码模式,在 app/ 目录下的文件里加上断点(一定要是在 app/ 目录下的文件,不能是 src/ 下的文件)。如:

alt

访问对应的页面,就可以看到代码显示的已经是源代码了,然后利用顶部的调试按钮就可以调试了。如:

alt

这样就可以很好的在 VS Code 下调试 ES2015+ 代码了。

总结

如果不能调试,请确认断点是否打在 app/ 目录下。

]]>
Wed, 30 Mar 2016 02:19:21 GMT http://hlj35.com/post/debug-thinkjs-2015-project-in-vscode.html