• Posts tagged "Javascript"

Blog Archives

新一代Node.js的Web开发框架Koa2

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者:

  • 张丹(Conan), 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-koa2

前言

Nodejs是一个年轻的编程框架,充满了活力和无限激情,一直都在保持着快速更新,甚至你都追不上他的更新速度。我写的“从零开始nodejs系列文章”,基本把Nodejs的应用领域都涵盖了,但是放下了1年半后,再重新用Nodejs做开发,发现都变了,还要再重新再学一遍。我花了3周的时间,重学了ES6, KOA2, VUE2, VUX, Angular2, Webpack等的框架,终于找回了之前用Nodejs的时候的一些感觉,所以就有了接来的一系列的文章,希望能把我的踩坑经验分享给大家。

本文标题为“新一代Node.js的Web开发框架Koa2”,那么上一代是什么呢?就是Express,我写过2篇文章来介绍Express的,请参考文章:Node.js开发框架Express4.xNodejs开发框架Express3.0开发手记–从零开始。同时,Koa也从Koa的1.x升级了Koa的2.x,一切都在加速,希望大家能够体会到加速世界,给开发带来的快感,这就是风口。

程序代码已经上传到github有需要的同学,自行下载。
https://github.com/bsspirit/koa2-sample

目录

  1. 安装Node环境
  2. 建立项目
  3. 通过koa-generator来建立项目
  4. 目录结构
  5. app.js 文件分析
  6. 路由管理
  7. 页面渲染
  8. 日志分析

1. 安装Node环境

让我们从头开始 Koa2 的安装和使用吧,第一步,就是安装Node和NPM环境。在Window上面安装,就直接从官方网站下载一个可执行安装文件,执行安装即可。

在Linux Ubuntu上安装过程也是类似,本机的系统环境为:Linux Ubuntu 16.04 LTS,然后下载Node工程的源文件,编译,安装。


# 切换到root用户
~ sudo -i      

# 下载nodejs最新版本,源代码       
~ wget https://nodejs.org/dist/v8.4.0/node-v8.4.0.tar.gz
~ tar xvzf node-v8.4.0.tar.gz
~ cd node-v8.4.0

 # 编译、安装
~ ./configure
~ make
~ make install

运行node命令和npm命令


~ node -v
v8.4.0

~ npm -v
5.4.1

2. 建立项目

下面我们就开始创建Koa2的项目,接下来的操作在window与linux通用,只是编辑器不同而已。

进入window的开发目录,新建项目koa2-demo1。


# 进入开发目录
~ cd d:\workspace\js

# 新建项目、并进入项目目录
~ mkdir koa2-demo1 && cd koa2-demo1

创建一个node项目


~ d:\workspace\js\koa2-demo1>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (koa2-demo1)
version: (1.0.0)
description: koa2 demo
entry point: (index.js)
test command:
git repository:
keywords:
author: DanZhang
license: (ISC) MIT
About to write to d:\workspace\js\koa2-demo1\package.json:

{
  "name": "koa2-demo1",
  "version": "1.0.0",
  "description": "koa2 demo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "DanZhang ",
  "license": "MIT"
}

Is this ok? (yes) yes

这样我们就完成了一个最简单的nodejs项目,在当前的目录下面生成了package.json的文件。

接下来,我们来安装koa2的库,到当前的项目中。


~ npm install koa -s
+ koa@2.3.0
added 37 packages in 12.462s

查看当前目录,发现生成了一个node_modules的目录,这个目录存放了koa包所有的依赖的库。

然后,我们新建一个启动文件:index.js,用来启动Koa的项目。


~ notepad index.js

var koa = require('koa');
var app = new koa();

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

启动项目


~ node index.js

打开浏览器: http://localhost:3000/

看到这界面,那么恭喜你,第一步成功了。

3. 通过koa-generator来建立项目

Koa和Express项目一样,都提供了一种脚手架的方式来创建项目,使用这么方式会极大地提高开发的效率,但不建议初学者直接使用,高度自动化的生成器,可能会让你不理解实现的原理。

接下来,我们用koa-generator来生成一个koa的项目。

安装koa-generator,这是一个命令工具,所以需要全局安装。


~ npm install -g koa-generator

然后,我们创建koa2的项目时,就可以直接使用koa2的命令了。


# 进入开发目录
~ cd d:\workspace\js

# 生成koa2项目
~ koa2 koa2-demo2

   create : koa2-demo2
   create : koa2-demo2/package.json
   create : koa2-demo2/app.js
   create : koa2-demo2/public
   create : koa2-demo2/public/stylesheets
   create : koa2-demo2/public/stylesheets/style.css
   create : koa2-demo2/public/javascripts
   create : koa2-demo2/public/images
   create : koa2-demo2/routes
   create : koa2-demo2/routes/index.js
   create : koa2-demo2/routes/users.js
   create : koa2-demo2/views
   create : koa2-demo2/views/index.pug
   create : koa2-demo2/views/layout.pug
   create : koa2-demo2/views/error.pug
   create : koa2-demo2/bin
   create : koa2-demo2/bin/www

   install dependencies:
     > cd koa2-demo2 && npm install

   run the app:
     > SET DEBUG=koa* & npm start koa2-demo2

进入项目,并安装依赖库


~ cd koa2-demo2 && npm install

启动项目


~ npm run start

> koa2-demo2@0.1.0 start d:\workspace\js\koa2-demo2
> node bin/www

打开浏览器

如果你看到的界面,和上面的一样,那么恭喜你,用koa-generator生成的koa2的项目,也能正常工作了。这里你可以发现,用koa-generator时,只要一条命令就够了,程序会给你生成很多文件,直接就构建好了一个项目的基础。

4. 目录结构

接下来,我们就基于koa2-demo2的项目来分析,看一下生成出来的项目目录结构。我们用一个文本编辑器来打开这个项目,这样可以看得更清楚一些。

  • bin, 存放启动项目的脚本文件
  • node_modules, 存放所有的项目依赖库。
  • public,静态文件(css,js,img)
  • routes,路由文件(MVC中的C,controller)
  • views,页面文件(pug模板)
  • package.json,项目依赖配置及开发者信息
  • app.js,应用核心配置文件
  • package.json,node项目配置文件
  • package-lock.json,node项目锁定的配置文件

5. app.js 文件分析

app.js 是应用核心配置文件,我们把这个文件能够看明白,整理koa就非常容易了。

app.js文件,我们可以分割为X个部分进行解读:依赖包加载、错误处理、中间件加载、web界面渲染模板、自定义日志、自己定义路由、外部调用接口。


// 依赖包加载
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

const index = require('./routes/index')
const users = require('./routes/users')

// 错误处理
onerror(app)

// 中间件加载
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

// web界面渲染模板
app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// 自定义日志
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// 自己定义路由
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

// 外部调用接口
module.exports = app

麻雀虽小,五脏俱全!通过app.js的配置,就可以把一个web项目给组装起来了。

6. 路由管理

普通的web应用,通常都是多个页面组成,每个页面会对应一个URL的地址,用户在浏览器输入URL可以打开不同的页面。路由管理,就是把URL和页面的关系对应起来的。

在 app.js 中,路由来提取到 ./routes/index.js 和 ./routes/users.js 两个文件中进行实现了。

以 ./routes/index.js 文件来举例说明。


~ notepad ./routes/index.js

const router = require('koa-router')()

// 解析'/'
router.get('/', async (ctx, next) => {  
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})

// 解析 '/string'
router.get('/string', async (ctx, next) => {
  ctx.body = 'koa2 string'
})

// 解析 '/json'
router.get('/json', async (ctx, next) => {
  ctx.body = {
    title: 'koa2 json'
  }
})

module.exports = router

从代码中分析,当路径为’/’时,网页会返回Hello Koa 2!;当路径为’/string’时,网页会返回koa2 string;哪路径是/json时,网页会返回koa2 json。

7. 页面渲染

从上面路由中,我们看到路径’/’的输出,是一个被渲染为网页输出,而’/string’和’/json’的输出是直接字符的输出。传统的web应用,大都是后台渲染的机制。新型的单面应用来说,前后端分离的设计,根本不需要后端来渲染,直接输出json就可以了。

对后台渲染的实现,我们可以参考’/’的实现。


router.get('/', async (ctx, next) => {
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})

上面代码中,通过 await ctx.render(‘index’,{}) 这样的语法,就可以加载pug的模板引擎。

  • await是ES6的关键字,用于把异步代码同步化,就不再写回调函数了(callback)。
  • ctx.render()函数,用于加载渲染引擎。

然后,我们找到views中的index.pug文件。


~ notepad ./views/index.pug

extends layout

block content
  h1= title
  p Welcome to #{title}

在index.pug文件中,有一个参数是后台传过来的,就是title,同时index.pug继承了layout.pug。

再打开layout.pug文件。


doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content

layout.pug文件,是整个html的骨架,这样就可以通过后台要渲染为一个HTML的网页了。打开chrome浏览品质开发人员工具,看到HTML的代码的结构,与layout.pug和index.pug结构是一致的。

8. 日志分析

最后要说的就是服务器日志了,每一次的浏览行为,都会产生一条服务器日志,用于记录用户的访问情况。我们后台通过命令行启动后,后台的服务器就会一直存活着,接收浏览器的请求,同时产生日志。

日志中,200表示正常访问,404是没有对应的URL错误。你会看到每次访问的路径都被记录了,包括后台的路径和css的文件路径,还包括了访问协议,响应时间,页面大小等。

我们可以自定义日志格式,记录更多的信息,也可以记录对自己有用的信息。这样我们就构建出一个最小化的web应用了。

Nodejs的发展速度,远远超越了其他语言的发展,我看好Nodejs,希望能给入门Nodejs的朋友一些帮助。加油!!

程序代码已经上传到github有需要的同学,自行下载。
https://github.com/bsspirit/koa2-sample

转载请注明出处:
http://blog.fens.me/nodejs-koa2

打赏作者

Angular2新的体验

AngularJS体验式编程系列文章,将介绍如何用angularjs构建一个强大的web前端系统。angularjs是由Google团队开发的一款非常优秀web前端框架。在当前如此多的web框架下,angularjs能脱颖而出,从架构设计上就高人一等,双向数据绑定,依赖注入,指令,MVC,模板。Angular.js创新地把后台技术融入前端开发,扫去jQuery一度的光芒。用angularjs就像写后台代码,更规范,更结构化,更可控。

从 Angular 2.x 开始新的体验。

关于作者

  • 张丹(Conan), 程序员R,Java,Nodejs
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/angular2-init/

前言

随着Angular2的升级,从1.x升级2.x,但我却迟迟没有动手。原因了2.x对1.x完全不兼容,而且从原生Javascript变成了Typescript,增加很多的生成模块,依赖的类库一下子变得很多。变化实在太大了,确实让人难以接受。我特别对于Typescript的不解,喜欢javascript的灵活,而静态化意味代码量的增加。

前几天,看到Angular2的release,想来想去还是要试试。从官方文档中,看到对中文支持的非常好,开发团队真是用心了。开发团队用心的改变,作为使用者来说是切身可以感受的,带着敬意和信心,决定把Angular2学起来。

目录

  1. Angular2介绍
  2. Anguar2的快速启动
  3. 从命令行构建Anguar2

1. Angular2介绍

Angular是由Google开发的一套前端应用开发框架,可以快速帮你构建复杂的大型前端单页应用(Single Page Application)。我非常喜欢Angular 1.x 给我带来的前端开发体验,当前花了2周时间时间学习,几百行代码就实现了,超乎想象的应用效果。我放下了所有原来对于前端Javascript的认知,直接转向了Angular。

官方网站:https://angular.cn/

Angular2的新特性包括:

  • 渐进式Web应用:借助现代化Web平台的力量,交付app式体验。高性能、离线化、零安装。
  • 原生:借助来自Ionic、NativeScript和React Native中的技术与思想,构建原生移动应用。
  • 桌面:借助你已经在Web开发中学过的能力,结合访问原生操作系统API的能力,创造能在桌面环境下安装的应用,横跨Mac、Windows和Linux平台。
  • 代码生成:Angular会把你的模板转换成代码,针对现代JavaScript虚拟机进行高度优化,轻松获得框架提供的高生产率,同时又能保留所有手写代码的优点。
  • 统一:在服务端渲染应用的首屏,像只有HTML和CSS的页面那样几乎瞬间展现,支持node.js、.NET、PHP,以及其它服务器,为通过SEO来优化站点铺平了道路。
  • 代码拆分:Angular应用通过新的组件路由(Component Router)模块实现快速加载,提供了自动拆分代码的功能,为用户单独加载它们请求的视图中需要的那部分代码。
  • 模板:通过简单而强大的模板语法,快速创建UI视图。
  • Angular命令行工具:命令行工具:快速进入构建环节、添加组件和测试,然后立即部署。
  • 各种IDE:在常用IDE和编辑器中获得智能代码补全、实时错误反馈及其它反馈等特性。
  • 测试:使用Karma进行单元测试,让你在每次存盘时都能立即知道是否弄坏了什么。Protractor则让你的场景测试运行得又快又稳定。
  • 动画:通过Angular中直观简便的API创建高性能复杂编排和动画时间线 —— 只要非常少的代码。
  • 可访问性:通过支持ARIA的组件、开发者指南和内置的一体化测试基础设施,创建具有完备可访问性的应用。

接下来,就让我们来体验Angular2的新颖之处的。

2. Anguar2的快速启动

阅读官方文档,我们可以跟着文档进行项目的快速启动,快速启动项目quickstart,是基于github,node,npm的,我们按照文档执行就可以了。关于Node的介绍,可以参考系列文章从零开始nodejs系列文章

系统环境:

  • Win10 64bit
  • node v6.9.4
  • npm 3.10.10
  • git version 2.7.0.windows.1

2.1 构建项目

进入开发目录,下载quickstart项目。


~ cd D:\\workspace\js
~ git clone https://github.com/angular/quickstart.git quickstart
~ cd quickstart
~ npm install   # 执行npm安装和配置
~ npm start     # 启动项目

通过npm start命令,就直接启动了angular2的程序,默认会自动打开浏览器,就可以看到效果了。如果浏览没有自动打开,自己手动在浏览器打开localhost:3000,也可以打开。

2.2 目录结构

接下来,我们查看项目目录结构。

由于这个quickstart的模板项目,包含了太多的文件。一下子看到这么多东西,肯定要晕一会的。图中用红色部分标记的几个文件,是我们应该要知道的,其他的文件对于首次执行来说并不是太重要。

文件说明

  • app/app.components.ts, 一个自定义组件,Typescript代码。
  • app/app.module.ts, 模块文件,用于组合管理组件,Typescript代码。
  • app/main.ts, 项目启动的入口,加载ts文件,Typescript代码。
  • app/app.components.spec.ts, 单测试文件
  • .gitignore, git的配置文件
  • index.html, 单页应用的html入口文件
  • package.json, Node项目的工程配置文件
  • tsconfig.json, Typescript语言的配置文件

2.3 4个核心文件

把几个核心的文件中的代码,进行解释。

app/app.components.ts文件


import { Component } from '@angular/core';        //引用系统类库

@Component({                                      //定义组件
  selector: 'my-app',                             //对应该index.html的<my-app>标签
  template: `<h1>Hello {{name}}</h1>`,            //替换<my-app>标签的内容
})
export class AppComponent  { name = 'Angular'; }  //输出自定义组件 

app/app.module.ts文件


import { NgModule }      from '@angular/core';                  //引用系统类库
import { BrowserModule } from '@angular/platform-browser';      //引用系统类库
import { AppComponent }  from './app.component';                //引用app.components.ts文件,定义的AppComponent组件
 
@NgModule({                                                     //定义模块
  imports:      [ BrowserModule ],                              //引用浏览器模块
  declarations: [ AppComponent ],                               //声明组件
  bootstrap:    [ AppComponent ]                                //启动组件
})
export class AppModule { }                                      //输出自定义模块 

app/main.ts文件


import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';  //引用系统类库

import { AppModule } from './app.module';                                    //引用app.module.ts文件的AppModule 

platformBrowserDynamic().bootstrapModule(AppModule);                         //启动程序,加载模块

index.html文件


<!DOCTYPE html>
<html>
  <head>
    <title>Angular QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">

    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app>Loading AppComponent content here ...</my-app>
  </body>
</html>

index.html中$lt;my-app&gtl的标签,是会被app.components.ts文件定义的selector发现并处理。

这4个文件,其实就是程序源代码的核心文件。那么项目中的其他文件,都是各种工具的配置文件。比如,我要编译Typescript文件到Javascript文件,就是需要在tsconfig.json进行配置;对类库做动态加载就要用到systemjs;执行单元测试需要用到karma;上传到github需要用到.gitignore;对node项目的管理用package.json配置。

2.4 扩展工具

当然,还有一些扩展工具。

单独编译Typescrip到Javascript。


~ D:\workspace\js\quickstart>npm run tsc

单独编译Typescrip,并监控文件状态,发现文件变化重新变异。


~ D:\workspace\js\quickstart>npm run tsc:w

启动Server,在浏览器中查看效果。


~ D:\workspace\js\quickstart>npm run lite 

单元测试


~ D:\workspace\js\quickstart>npm test

E2E测试End-to-end


~ D:\workspace\js\quickstart>npm run e2e 

除了从github上面下载quickstart模板,还是另外一种初始构建项目的方式,就是用Anguar2的命令行工具。

3. 从命令行构建Anguar2

从上文的Angular2的特性中,就有一项是命令工具,用这种方式构建一个新项目,更符合开发人员“脚手架”的思路。首先,我们需要安装这个命令工具anguar-cli。请大家务必升级Nodejs到v6.x以上,我之前用v4.4,花了大量时间调试都没有通过。

3.1 安装anguar-cli


~ D:\workspace\js>npm install -g angular-cli

安装好后,我们可以通过help命令查看命令行有哪个功能。


D:\workspace\js>ng help
ember build 
  Builds your app and places it into the output path (dist/ by default).
  aliases: b
  --target (String) (Default: development)
    aliases: -t , -dev (--target=development), -prod (--target=production)
  --environment (String) (Default: )
    aliases: -e 
  --output-path (Path) (Default: null)
    aliases: -o 
  --watch (Boolean) (Default: false)
    aliases: -w
  --watcher (String)
  --suppress-sizes (Boolean) (Default: false)
  --base-href (String) (Default: null)
    aliases: -bh 
  --aot (Boolean) (Default: false)
  --sourcemap (Boolean) (Default: true)
    aliases: -sm
  --vendor-chunk (Boolean) (Default: true)
  --verbose (Boolean) (Default: false)
  --progress (Boolean) (Default: true)
  --i18n-file (String) (Default: null)
  --i18n-format (String) (Default: null)
  --locale (String) (Default: null)

  // 省略....

查看ng工具的版本


~ D:\workspace\js>ng version
                             _                           _  _
  __ _  _ __    __ _  _   _ | |  __ _  _ __         ___ | |(_)
 / _` || '_ \  / _` || | | || | / _` || '__|_____  / __|| || |
| (_| || | | || (_| || |_| || || (_| || |  |_____|| (__ | || |
 \__,_||_| |_| \__, | \__,_||_| \__,_||_|          \___||_||_|
               |___/

angular-cli: 1.0.0-beta.26
node: 6.9.4
os: win32 x64

当然,我们最需要的就是构建项目,启动服务,生成组件,指令,服务等功能。

3.2 新建项目

新建一个项目


~ D:\workspace\js>ng new conan1      // 新建项目
installing ng2
  create .editorconfig
  create README.md
  create src\app\app.component.css
  create src\app\app.component.html
  create src\app\app.component.spec.ts
  create src\app\app.component.ts
  create src\app\app.module.ts
  create src\assets\.gitkeep
  create src\environments\environment.prod.ts
  create src\environments\environment.ts
  create src\favicon.ico
  create src\index.html
  create src\main.ts
  create src\polyfills.ts
  create src\styles.css
  create src\test.ts
  create src\tsconfig.json
  create angular-cli.json
  create e2e\app.e2e-spec.ts
  create e2e\app.po.ts
  create e2e\tsconfig.json
  create .gitignore
  create karma.conf.js
  create package.json
  create protractor.conf.js
  create tslint.json
Successfully initialized git.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Project 'conan1' successfully created.

等了大概有10分钟,下载了有200mb的文件,希望大家有耐心。进入安装目录。


~ D:\workspace\js\conan1>cd conan1      // 进入项目目录
~ D:\workspace\js\conan1>ng serve       // 启动项目

打开浏览器,查看Web网页。

如果你想修改启动端口为4201,可以用下面的命令。


ng serve --host 0.0.0.0 --port 4201 --live-reload-port 49153

用命令工具构建的项目目录。

目录结构有一个变化,就是嵌套了一层src目录,然后才是app目录。另外,多了angular-cli.json的配置文件。

3.3 模块生成器

接下来,我们可以用命令工具,进行项目模块和组件的开发了。

命令使用说明
组件Componentng generate component my-new-component
指令Directiveng generate directive my-new-directive
服务Serviceng generate pipe my-new-service
管道Pipeng generate pipe my-new-pipe
类Classng generate class my-new-class
接口Interfaceng generate interface my-new-interface
枚举对象Enumng generate enum my-new-enum
模块Moduleng generate module my-module

3.4 生成一个新组件

用命令生成一个hello的新组件,文件生成如下。


~ D:\workspace\js\conan1>ng generate component hello
installing component
  create src\app\hello\hello.component.css
  create src\app\hello\hello.component.html
  create src\app\hello\hello.component.spec.ts
  create src\app\hello\hello.component.ts
  update src\app\app.module.ts

在app目录下生成了一个hello的子目录,包括了4个组件的文件,同时在app.module.ts模块文件中,注册了这个新组件。

我们要修改2个地方,让这个新生成的hello组件可以运行。

1. 修改index.html文件,增加<app-hello>标签。


<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Conan1</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root>Loading...</app-root>
  
  <app-hello>Loading...</app-hello>
</body>
</html>

2. 修改app.module.ts文件,在@NgModule.bootstrap中,增加HelloComponent的启动项。


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello/hello.component';

@NgModule({
  declarations: [
    AppComponent,
    HelloComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent,HelloComponent]
})
export class AppModule { }

查看浏览器,我们可以看到hello works!显示到界面上。

3.5 生成一个新指令

属性型指令用于改变一个 DOM 元素的外观或行为。用命令生成一个link的新指令,操作如下。


~ D:\workspace\js\conan1>ng generate directive link
installing directive
  create src\app\link.directive.spec.ts
  create src\app\link.directive.ts
  update src\app\app.module.ts

主要生成了新文件link.directive.ts。


import { Directive } from '@angular/core';
@Directive({
  selector: '[appLink]'
})
export class LinkDirective {
  constructor() { }
}

接下来,用appLink指令来做一个样式的修饰,需要修改2个文件。

1. 修改hello.component.html文件,增加一段span的HTML标签,并配置appLink属性。


<p>hello works!</p>

<span appLink>Directive me!</span>

2. 修改link.directive.ts文件,在LinkDirective类的构造器constructor函数中,增加样式的修改属性。同时,通过@HostListener,监听鼠标动作。


import { Directive, ElementRef, HostListener, Renderer } from '@angular/core';
@Directive({
  selector: '[appLink]'
})
export class LinkDirective {
    constructor(el: ElementRef, renderer: Renderer) {
       renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'yellow');
    }

    @HostListener('mouseenter') onMouseEnter() {
      this.highlight('blue');
    }
    @HostListener('mouseleave') onMouseLeave() {
      this.highlight('yellow');
    }
    
    private highlight(color: string) {
      this.renderer.setElementStyle(this.el.nativeElement, 'backgroundColor', color);
    }
}

修改后,我们在浏览器中查看运行的效果。

3.6 生成一个新服务

服务,可以封装代码,比如我们可以对数据访问代码单独隔离,封装到一个独立的服务中。用命令生成一个meta的新服务,操作如下。


~ D:\workspace\js\conan1>ng generate service meta
installing service
  create src\app\meta.service.spec.ts
  create src\app\meta.service.ts
  WARNING Service is generated but not provided, it must be provided to be used

生成了2个service的文件,同时提供了Service必须被provided,才可以被应用。我们如果想要启用meta的这个服务,需要在3个文件中进行修改。

1. 修改meta.service.ts文件,增加getNames()函数,用于提供数据访问。


import { Injectable } from '@angular/core';
@Injectable()
export class MetaService {
  constructor() { }
  getNames():any[]{
    return [
        {id: 1, name: 'AAA'},
        {id: 2, name: 'BBB'},
        {id: 3, name: 'CCC'},
        {id: 4, name: 'DDD'}
      ];
  }
}

2. 修改hello.component.ts文件,把MetaService注入到HelloComponent中,通过providers进行依赖注入的体现,同时调用MetaService 类的getNames()函数获取数据。


import { Component, OnInit } from '@angular/core';
import { MetaService } from '../meta.service';
@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
  providers: [MetaService]
})
export class HelloComponent implements OnInit {
  meta = [];
  constructor(private metaService: MetaService) { }
  ngOnInit() {
    this.meta = this.metaService.getNames();
  }
}

3. 修改hello.component.html文件,用于数据的html输出,以列表形式输出。


<p>hello works!</p>

<span appLink>Directive me!</span>

<ul>
  <li *ngFor="let m of meta">
    {{m.id}}, {{m.name}}
  </li>
</ul>

修改后,我们在浏览器中查看运行的效果。

3.7 生成一个新管道

管道,可以用于数据的连续处理操作,比如可以把日期在界面端进行格式化,把20170101格式转型2017-01-01格式。用命令生成一个datef的新管道,操作如下。


~ D:\workspace\js\conan1>ng generate pipe datef
installing pipe
  create src\app\datef.pipe.spec.ts
  create src\app\datef.pipe.ts
  update src\app\app.module.ts

生成了2个pipe的文件。如果我们想使用这个功能,需要在2个文件中进行修改。

1. 修改datef.pipe.ts文件,对日期进行格式化,把20161222转化为2016-12-22。


import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
  name: 'datef'
})
export class DatefPipe implements PipeTransform {
  transform(value: string) {
    var dat1 = value.toString();
    return dat1.substring(0,4)+ "-"+ parseInt(dat1.substring(4,6)) +  "-"+dat1.substring(6,8);
  }
}

2. 修改hello.component.html文件,进行HTML的输出。


<p>hello works!</p>

<span appLink>Directive me!</span>

<ul>
  <li *ngFor="let m of meta">
    {{m.id}}, {{m.name}}
  </li>
</ul>

<p>{{day}} ==> {{ day | datef}}</p>

修改后,我们在浏览器中查看运行的效果。

其他的命令,我们在后文再继续说明。本文只是一篇对Angular2的新体验介绍,由于Angular2整个框架还是变化挺大的,同时我对Typescript也不熟悉,对webapck也需要花时间学习。整个过程花了不少时间,也走了不少的弯路。

目前,网上Angular2框架介绍的文章本来就不多,虽然官网文档比较详细,但是上手来说,还是有一定的难度的,希望本文对于新手来说有一些帮助,也希望Angualr2尽快成熟,我准备正在项目中用起来!!

转载请注明出处:
http://blog.fens.me/angularjs-yeoman-project/

打赏作者

图片延迟加载库Layzr

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-layzr/

js-layzr

前言

延迟加载技术,普遍应用于现在大部分的互联网的Web应用上。通过对图像的延迟加载,等到用户浏览区域接近时才把图片进行加载,可以有效地提升网页打开的速度,进一步改进用户的体验。

Layzr.js库就可以完成这个功能,150多行代码,很小很快很方便。

目录

  1. layzr介绍
  2. layzr的基本使用
  3. Layzr的配置

1. layzr介绍

layzr.js 是一个很小、速度快、无依赖的,用于浏览器图片延迟加载的库。

我们找到Layzr.js官方的Github上面,dist目录发布的 layzr.min.js 仅有 2.2 KB。同时,发现 package.json 文件,没有任何的dependencies依赖。

用layzr.js进行图片延迟加载,是非常方便的。通过配置选项,实现最大化的加载速度。layzr.js对于滚动事件已去抖,以尽量减少对浏览器的压力,以确保最佳的渲染。

项目官方网站:http://callmecavs.github.io/layzr.js/

2. layzr的基本使用

layzr.js是在浏览器端运行的Javascript库,但是他是用于NPM管理的,还在没有bower中发布。关于NPM和Bower的介绍,请分别查看文章:快速创建基于npm的nodejs库bower解决js的依赖管理

下面我们用npm的方式,下载layzr库。

首先,创建项目目录。


~  D:\workspace\javascript> mkdir js-layzr && cd js-layzr 

新建NPM项目文件package.json。


~ D:\workspace\javascript\js-layzr> vi package.json
{
    "name": "js-layzr",
    "version": "0.0.1",
    "description": "a demo for layzr",
    "dependencies": {}
}

通过NPM下载layzr.js包


~ D:\workspace\javascript\js-layzr> npm install layzr.js --save
npm WARN package.json js-layzr@0.0.1 No repository field.
npm WARN package.json js-layzr@0.0.1 No README data
layzr.js@1.1.4 node_modules\layzr.js

layzr1

接下来,我们打开layzr.js项目提供的Demo的文件,在layzr.js/demo.html。


<!DOCTYPE html>

<!--[if IE 9]>         <html class="ie9" lang="en-US"> <![endif]-->
<!--[if gt IE 9]><!--> <html lang="en-US"> <!--<![endif]-->
  <head>
    <title></title>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
      .demo-images {
        width: 300px;
        margin-right: auto;
        margin-left: auto;
        padding-top: 50vh;
      }

      .demo-image,
      iframe {
        display: block;
        margin-bottom: 100px;
      }
    </style>
  </head>
  <body>
    <div class="wrapper">

      <div class="demo-images">
        <img class="demo-image" src="http://placehold.it/300">
        <img class="demo-image" data-layzr="http://placehold.it/300">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina" data-layzr-bg>
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">
        <iframe width="300" height="169" data-layzr="https://www.youtube.com/embed/2a01Rg2g2Z8"></iframe>
      </div>

    </div><!-- END wrapper -->

    <script src="dist/layzr.js"></script>
    <script>
      // create new instance, defaults shown below
      var layzr = new Layzr({
        selector: '[data-layzr]',
        attr: 'data-layzr',
        retinaAttr: 'data-layzr-retina',
        bgAttr: 'data-layzr-bg',
        threshold: 0,
        callback: null
      });
    </script>
  </body>
</html>

在浏览型预览效果,如下图所示。

layzr2

左边为浏览器页面,右边为Chrome的开发者工具的Elements界面,红色直线为显示和代码的对应关系。当页面向下滚动时候,图片会一张一张的加载。同时代码中,原代码为


<img class="demo-image" data-layzr="http://placehold.it/300" data-layzr-retina="http://placehold.it/300&text=retina">

的部分,会被重新翻译为


<img class="demo-image" src="http://placehold.it/300">

从本Demo中,我们可以猜出layzr库应该有2个组成部分,监听页面事件 和 更新img标签的DOM元素。

3. Layzr的配置选择

在HTML中,图片是用img标签在控制的,要用到Layzr.js库,需要在img标签中增加属性。


<img src="optional/placeholder" data-layzr="normal/image" data-layzr-retina="optional/retina/image" data-layzr-bg >
  • src: 用于定义图像占位符,如果没有定义图像占位符,那么在图像载入前,可能会显示异常。
  • data-layzr: 用于显示的图像
  • data-layzr-retina: 用于图像的延迟加载,测试发现没效果。
  • data-layzr-bg: 图像用于做成背景,测试发现直接被隐藏了。

对应地,我们还需要定义Javascript的实例,用来启动layzr的事件。


var layzr = new Layzr({ 
  selector: '[data-layzr]', 
  attr: 'data-layzr', 
  retinaAttr: 'data-layzr-retina', 
  bgAttr: 'data-layzr-bg', 
  threshold: 0, 
  callback: null 
});

Javascript实例属性:

  • selector: 用于选定图像标签。
  • attr: 用于指定data-layzr的属性
  • retinaAttr: 用于指定data-layzr-retina属性
  • bgAttr: 用于指定data-layzr-bg的属性
  • threshold: 用于定义图像加载参数,通过屏幕高度来控制。
  • callback: 当加载完成,触发事件回调。

接下来,我们新建一个layzr.html的文件,用于测试 layzr 的库各种功能,会比官方提供的Demo更易于理解。

新建文件layzr.html。


<!DOCTYPE html>
<html>
<head>
    <title>Layzr Demo</title>
    <script src="node_modules/layzr.js/dist/layzr.js"></script>
    <style>
        .row {
            margin: 50px 0;
            height: 500px;
        }
        .border {
            padding: 10px;
            border-style: solid;
            border-width: 3px;
            border-color: blue;
        }
    </style>
</head>
<body>
<div class="wrapper">
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/02/marked.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/buffer.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/02/express4.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/child_process.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2015/03/crypto.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr="http://blog.fens.me/wp-content/uploads/2014/02/architect.png">
        </div>
        <div class="row">
            <img src="./bg.png" data-layzr-retina="./bg2.png" data-layzr="http://blog.fens.me/wp-content/uploads/2013/08/seo-title.png">
        </div>
</div>

<script>
    var layzr = new Layzr({
        selector: '[data-layzr]',
        attr: 'data-layzr',
        retinaAttr: 'data-layzr-retina',
        threshold: 10,
        callback: function () {
            console.log(this);
            this.classList.add('border');
        }
    });
</script>

</body>
</html>

用浏览器找开页面,我们可以看到展示效果。

layzr3

当浏览器向下滑动的过程中,图片由原来黑色的占位图,会自动替换为我们需要显示的图片。

layzr4

本文测试的代码,已上传到github:https://github.com/bsspirit/js-layzr

这样,我们就实现图像的延迟加载技术。对于有大量图片的网站来说,尝试一下图像的延迟加载技术,肯定会给网站带来大幅度的性能改进的。

转载请注明出处:
http://blog.fens.me/nodejs-layzr/

打赏作者

Node.js进程通信模块child_process

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-child-process/

child_process

前言

Node.js是一种单线程的编程模型,对Node.js的赞美和诟病的也都是因为它的单线程模型,所有的任务都在一个线程中完成(I/O等例外)。单线程模型,不仅让代码非常简洁,更是直接避免了线程调度的复杂性;同样也是因为单线程,让CPU密集型计算应用,完全不适用。

在Node.js的内核中,给了我们一种新的选择,通过child_process模块创建新进程,从而实现多核并行计算。

目录

  1. child_process介绍
  2. child_process的基本使用:spawn, exec, execFile, fork

1. child_process介绍

child_process是Node.js的一个十分重要的模块,通过它可以实现创建多进程,以利用单机的多核计算资源。虽然,Nodejs天生是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信,等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

本文仅从使用上对child_process模块进行介绍,对于深入的内容请参考官方文档,https://nodejs.org/api/child_process.html

关于Node.js线程不错的文章:https://cnodejs.org/topic/518b679763e9f8a5424406e9

2. child_process的基本使用

child_process模块,在v0.12.0版本主要包括4个异步进程函数(spawn,exec,execFile,fork)和3个同步进程函数(spawnSync,execFileSync,execSync)。以异步函数中spawn是最基本的创建子进程的函数,其他三个异步函数都是对spawn不同程度的封装。spawn只能运行指定的程序,参数需要在列表中给出,而exec可以直接运行复杂的命令。

比如要运行 du -sh /disk1 命令, 使用spawn函数需要写成spawn(‘du ‘, [‘-sh ‘, ‘/disk1’]),而使用exec函数时,可以直接写成exec(‘du -sh /disk1’)。exec是会先进行Shell语法解析,因此用exec函数可以更方便的使用复杂的Shell命令,包括管道、重定向等。下面我们就针对每个异步函数,进行测试一下。

系统环境

  • Linux Ubuntu 14.04.1 LTS 64bit
  • Nodejs:v0.13.0-pre
  • Npm:1.4.28

创建项目


~ cd /disk1/demo
~ mkdir nodejs-childprocess && cd nodejs-childprocess

2.1 spawn函数

spawn从定义来看,有3个参数。

child_process.spawn(command[, args][, options])
  • command: 只执行的命令
  • args: 参数列表,可输入多的参数
  • options: 环境变量对象

其中环境变量对象包括7个属性:

  • cwd: 子进程的当前工作目录
  • env: 环境变量键值对
  • stdio: 子进程 stdio 配置
  • customFds: 作为子进程 stdio 使用的文件标示符
  • detached: 进程组的主控制
  • uid: 用户进程的ID.
  • gid: 进程组的ID.

首先,我们运行一下,上文提到的du的命令。直接在命令行,运行的结果。


~ du -sh /disk1
582M    /disk1

新建文件spawn.js


~ vi spawn.js

var child = require('child_process');
var du = child.spawn('du', ['-sh', '/disk1']);
du.stdout.on('data', function (data) {
    console.log('stdout: ' + data);
});
du.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
du.on('exit', function (code) {
    console.log('child process exited with code ' + code);
});

通过node运行的结果


~ node spawn.js

stdout: 582M    /disk1
child process exited with code 0

输出的结果是一样的,这样我们就可以很方便地以异步的方式调用系统命令了,spawn是不支持callback函数的,通过stream的方式发数据传给主进程,从而实现了多进程之间的数据交换。

这个功能的直接用应用场景就是“系统监控”。在Linux下,我们有很多命令行工具,可以实时监控CPU,内存,IO,网络等数据,那么用Node的child_process模块可以很容易地把这些数据采集的我们自己的应用中。

比如,我们用mpstat命令,监控用户CPU的使用情况。先看看mpstat命令直接使用的效果。


~ mpstat 1
Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
11:45:56 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
11:45:57 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:45:58 AM  all   96.50    0.00    3.25    0.00    0.25    0.00    0.00    0.00    0.00    0.00
11:45:59 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:00 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:01 AM  all   96.26    0.00    3.24    0.00    0.25    0.00    0.25    0.00    0.00    0.00
11:46:02 AM  all   96.75    0.00    3.25    0.00    0.00    0.00    0.00    0.00    0.00    0.00
11:46:03 AM  all   96.51    0.00    3.24    0.00    0.25    0.00    0.00    0.00    0.00    0.00
^C
Average:     all   96.50    0.00    3.35    0.00    0.11    0.00    0.04    0.00    0.00    0.00

我们新建文件mpstat.js,读取mpstat命令的数据,然后只输出%usr的数据。


~ vi mpstat.js

var child = require('child_process');
var mpstat = child.spawn('mpstat', ['1']);

//Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
//11:27:12 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
//11:27:13 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00

var line = 0;
var cols = ["time","day","CPU","%usr","%nice","%sys","%iowait","%irq","%soft","%steal","%guest","%gnice","%idle"];
mpstat.stdout.on('data', function (data) {
    var str = data.toString();
    if(line > 2) {
        var arr = str.split(/\s+/);
        console.log(arr[0]+" "+cols[3]+" "+arr[3]);
    }else{
       line++;
    }
});
mpstat.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
mpstat.on('exit', function (code) {
    console.log('child process exited with code ' + code);
});

运行程序:


~ node mpstat.js
11:47:57 %usr 96.75
11:47:58 %usr 96.52
11:47:59 %usr 96.75
11:48:00 %usr 96.25
11:48:01 %usr 96.74
11:48:02 %usr 96.51
11:48:03 %usr 96.74
11:48:04 %usr 96.51

这样就完成系统数据的采集,我们可以把采集到的数据存储到数据库中,通过websocket等协议直接输出的浏览器前端进行数据的展示。监控系统的例子,可以参考我之前写过的一篇文章:websocket服务器监控

2.2 exec函数

exec函数是对spawn的一种友好封装,增加Shell命令解析,可以直接嵌入复杂的命令,比如,管道用法 cat spawn.js exec.js | wc。

我先试用wc命令来统计一下当前目录的文件字数,分别对应3列为 字节数、字数、行数。


~ wc *
   9   29  275 exec.js
  25   95  878 mpstat.js
  16   41  343 spawn.js
  50  165 1496 total

~ cat *.js | wc
     50     165    1496

接下来,我们使用exec函数,来使用Linux管道命令。


~ vi exec.js

var childProcess = require('child_process');

var ls = childProcess.exec('cat *.js | wc', function (error, stdout, stderr) {
   if (error) {
     console.log(error.stack);
     console.log('Error code: '+error.code);
   }
   console.log('Child Process STDOUT: '+stdout);
});

运行程序:


~ node exec.js
Child Process STDOUT:      50     165    1496

输出结果与Linux命令类似,而且用exec时,命令可以写成一个完整的字符串了,不用像spawn函数时分开写成多个参数数组的形式。最后通过一个callback函数来返回,更符合JavaScript的函数调用习惯,通常情况我们可以用exec来替换spawn的使用。

2.3 execFile函数

execFile函数会直接执行特定的程序,参数作为数组传入,不会被bash解释,因此具有较高的安全性。execFile与spawn的参数相似,也需要分别指定执行的命令和参数,但可以接受一个回调函数,与exec的回调函数相同。

我们看一个execFile函数的例子。


~ vi execFile.js

var childProcess = require('child_process');
var path = ".";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    console.log(result)
});

运行程序


~ node execFile.js
total 16
-rw-r--r-- 1 root root 527 Mar 20 13:23 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js

那么,什么时候使用exec,什么时候使用execFile呢?

如果命令参数是由用户来输入的,对于exec函数来说是有安全性风险的,因为Shell会运行多行命令,比如’ls -l .;pwd,如逗号分隔,之后的命令也会被系统运行。但使用exeFile命令时,命令和参数分来,防止了参数注入的安全风险。

我们用程序测试一下。


~ vi execFile.js

// exec
var cmd = 'ls -l .;pwd'
var ls = childProcess.exec(cmd, function (error, stdout, stderr) {
   if (error) {
     console.log(error.stack);
     console.log('Error code: '+error.code);
   }
   console.log('Child Process STDOUT: '+stdout);
});

// execFile
var path = ".;pwd";
childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    console.log(result)
});

运行程序


~  node execFile.js

{ [Error: Command failed: /bin/ls -l .;pwd
/bin/ls: cannot access .;pwd: No such file or directory
] killed: false, code: 2, signal: null, cmd: '/bin/ls -l .;pwd' }

Child Process STDOUT: total 16
-rw-r--r-- 1 root root 547 Mar 20 13:31 execFile.js
-rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
-rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
-rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js
/disk1/demo/nodejs-childprocess

从输出结果看到,exec函数被正常执行,而execFile函数,则提示参数错误。

2.4 fork

fork函数,用于在子进程中运行的模块,如 fork(‘./son.js’) 相当于 spawn(‘node’, [‘./son.js’]) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

我们一个主进程和子进程通信的例子,主进程文件main.js和子进程文件son.js。

新建主进程文件。


~ main.js

var childProcess = require('child_process');
var n = childProcess.fork('./son.js');

n.on('message', function(m) {
  console.log('Main Listen: ', m);
});
n.send({ hello: 'son' });

新建子进程文件。


~ vi son.js

process.on('message', function(m) {
  console.log('Son Listen:', m);
});
process.send({ Hello: 'conan' });

运行程序:


~ node main.js
Main Listen:  { Hello: 'conan' }
Son Listen: { hello: 'son' }

通过main.js启动子进程son.js,通过process在两个进程之间传递数据。我们对系统进程进行检查,看看是否确实有两个进程。


~ ps -aux|grep node
root     22777  0.2  0.1 603240 13252 pts/3    Sl+  14:25   0:00 node main.js
root     22782  0.2  0.1 603540 13516 pts/3    Sl+  14:25   0:00 /usr/local/bin/node ./son.js

有2个node进程分别是node main.js和node ./son.js。

掌握了进程之间的通信,我们可以做的事情就比较多了,比如自己做一个Node.js多进程管理器,调度器之类。相对于Java的多线程管理或者进程调度来说,Node程序是如此简单明了的。我已经非常明显的感觉到了编程语言在进步!!

转载请注明出处:
http://blog.fens.me/nodejs-child-process/

打赏作者

Node.js缓冲模块Buffer

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-buffer/

buffer

前言

Javascript是为浏览器而设计的,能很好的处理unicode编码的字符串,但对于二进制或非unicode编码的数据就显得无能为力。Node.js继承Javascript的语言特性,同时又扩展了Javascript语言,为二进制的数据处理提供了Buffer类,让Node.js可以像其他程序语言一样,能处理各种类型的数据了。

网上有很多讲Buffer的文章,大都讲的是原理,怎么使用几乎找不到,文章将重点介绍Buffer的使用。

目录

  1. Buffer介绍
  2. Buffer的基本使用
  3. Buffer的性能测试

1. Buffer介绍

在Node.js中,Buffer类是随Node内核一起发布的核心库。Buffer库为Node.js带来了一种存储原始数据的方法,可以让Nodejs处理二进制数据,每当需要在Nodejs中处理I/O操作中移动的数据时,就有可能使用Buffer库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。

Buffer 和 Javascript 字符串对象之间的转换需要显式地调用编码方法来完成。以下是几种不同的字符串编码:

  • ‘ascii’ – 仅用于 7 位 ASCII 字符。这种编码方法非常快,并且会丢弃高位数据。
  • ‘utf8’ – 多字节编码的 Unicode 字符。许多网页和其他文件格式使用 UTF-8。
  • ‘ucs2’ – 两个字节,以小尾字节序(little-endian)编码的 Unicode 字符。它只能对 BMP(基本多文种平面,U+0000 – U+FFFF) 范围内的字符编码。
  • ‘base64’ – Base64 字符串编码。
  • ‘binary’ – 一种将原始二进制数据转换成字符串的编码方式,仅使用每个字符的前 8 位。这种编码方法已经过时,应当尽可能地使用 Buffer 对象。Node 的后续版本将会删除这种编码。

Buffer官方文档:http://nodejs.org/api/buffer.html

2. Buffer的基本使用

Buffer的基本使用,主要就是API所提供的操作,主要包括3个部分 创建Buffer类、读Buffer、写Buffer。由于基本操作在官方文档中详细的使用介绍,我只是简单列举一下。

系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

创建项目


~ cd D:\workspace\javascript>
~ D:\workspace\javascript>mkdir nodejs-buffer && cd nodejs-buffer

2.1 创建Buffer类

要创建一个Buffer的实例,我们要通过new Buffer来创建。新建文件buffer_new.js。


~ vi buffer_new.js

// 长度为0的Buffer实例
var a = new Buffer(0);
console.log(a);
> <Buffer >

// 长度为0的Buffer实例相同,a1,a2是一个实例
var a2 = new Buffer(0);
console.log(a2);
> <Buffer >

// 长度为10的Buffer实例
var a10 = new Buffer(10);
console.log(a10);
> <Buffer 22 37 02 00 00 00 00 04 00 00>

// 数组
var b = new Buffer(['a','b',12])
console.log(b);
> <Buffer 00 00 0c>

// 字符编码
var b2 = new Buffer('你好','utf-8');
console.log(b2);
> <Buffer e4 bd a0 e5 a5 bd>

Buffer类有5个类方法,用于Buffer类的辅助操作。

1) 编码检查,上文中提到Buffer和Javascript字符串转换时,需要显式的设置编码,那么这几种编码类型是Buffer所支持的。像中文处理只能使用utf-8编码,对于几年前常用的gbk,gb2312等编码是无法解析的。


// 支持的编码
console.log(Buffer.isEncoding('utf-8'))
console.log(Buffer.isEncoding('binary'))
console.log(Buffer.isEncoding('ascii'))
console.log(Buffer.isEncoding('ucs2'))
console.log(Buffer.isEncoding('base64'))
console.log(Buffer.isEncoding('hex'))  # 16制进
> true

//不支持的编码
console.log(Buffer.isEncoding('gbk'))
console.log(Buffer.isEncoding('gb2312'))
> false

2) Buffer检查,很多时候我们需要判断数据的类型,对应后续的操作。


// 是Buffer类
console.log(Buffer.isBuffer(new Buffer('a')))
> true

// 不是Buffer
console.log(Buffer.isBuffer('adfd'))
console.log(Buffer.isBuffer('\u00bd\u00bd'))
> false

3) 字符串的字节长度,由于字符串编码不同,所以字符串长度和字节长度有时是不一样的。比如,1个中文字符是3个字节,通过utf-8编码输出就是4个中文字符,占12个字节。


var str2 = '粉丝日志';
console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'utf8') + " bytes");
> 粉丝日志: 4 characters, 12 bytes
console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'ascii') + " bytes");
> 粉丝日志: 4 characters, 4 bytes

4) Buffer的连接,用于连接Buffer的数组。我们可以手动分配Buffer对象合并后的Buffer空间大小,如果Buffer空间不够了,则数据会被截断。


var b1 = new Buffer("abcd");
var b2 = new Buffer("1234");
var b3 = Buffer.concat([b1,b2],8);
console.log(b3.toString());
> abcd1234

var b4 = Buffer.concat([b1,b2],32);
console.log(b4.toString());
console.log(b4.toString('hex'));//16进制输出
> abcd1234 乱码....
> 616263643132333404000000000000000000000000000000082a330200000000

var b5 = Buffer.concat([b1,b2],4);
console.log(b5.toString());
> abcd

程序运行的截图
buffer_concat

5) Buffer的比较,用于Buffer的内容排序,按字符串的顺序。


var a1 = new Buffer('10');
var a2 = new Buffer('50');
var a3 = new Buffer('123');

// a1小于a2
console.log(Buffer.compare(a1,a2));
> -1

// a2小于a3
console.log(Buffer.compare(a2,a3));
> 1

// a1,a2,a3排序输出
console.log([a1,a2,a3].sort(Buffer.compare));
> [ <Buffer 31 30>, <Buffer 31 32 33>, <Buffer 35 30> ]

// a1,a2,a3排序输出,以utf-8的编码输出
console.log([a1,a2,a3].sort(Buffer.compare).toString());
> 10,123,50

2.2 写入Buffer

把数据写入到Buffer的操作,新建文件buffer_write.js。


~ vi buffer_write.js

//////////////////////////////
// Buffer写入
//////////////////////////////

// 创建空间大小为64字节的Buffer
var buf = new Buffer(64);

// 从开始写入Buffer,偏移0
var len1 = buf.write('从开始写入');

// 打印数据的长度,打印Buffer的0到len1位置的数据
console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));

// 重新写入Buffer,偏移0,将覆盖之前的Buffer内存
len1 = buf.write('重新写入');
console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));

// 继续写入Buffer,偏移len1,写入unicode的字符串
var len2 = buf.write('\u00bd + \u00bc = \u00be',len1);
console.log(len2 + " bytes: " + buf.toString('utf8', 0, len1+len2));

// 继续写入Buffer,偏移30
var len3 = buf.write('从第30位写入', 30);
console.log(len3 + " bytes: " + buf.toString('utf8', 0, 30+len3));

// Buffer总长度和数据
console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));

// 继续写入Buffer,偏移30+len3
var len4 = buf.write('写入的数据长度超过Buffer的总长度!',30+len3);

// 超过Buffer空间的数据,没有被写入到Buffer中
console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));

buffer_write

Node.js的节点的缓冲区,根据读写整数的范围,提供了不同宽度的支持,使从1到8个字节(8位、16位、32位)的整数、浮点数(float)、双精度浮点数(double)可以被访问,分别对应不同的writeXXX()函数,使用方法与buf.write()类似。


buf.write(string[, offset][, length][, encoding])
buf.writeUIntLE(value, offset, byteLength[, noAssert])
buf.writeUIntBE(value, offset, byteLength[, noAssert])
buf.writeIntLE(value, offset, byteLength[, noAssert])
buf.writeIntBE(value, offset, byteLength[, noAssert])
buf.writeUInt8(value, offset[, noAssert])
buf.writeUInt16LE(value, offset[, noAssert])
buf.writeUInt16BE(value, offset[, noAssert])
buf.writeUInt32LE(value, offset[, noAssert])
buf.writeUInt32BE(value, offset[, noAssert])
buf.writeInt8(value, offset[, noAssert])
buf.writeInt16LE(value, offset[, noAssert])
buf.writeInt16BE(value, offset[, noAssert])
buf.writeInt32LE(value, offset[, noAssert])
buf.writeInt32BE(value, offset[, noAssert])
buf.writeFloatLE(value, offset[, noAssert])
buf.writeFloatBE(value, offset[, noAssert])
buf.writeDoubleLE(value, offset[, noAssert])
buf.writeDoubleBE(value, offset[, noAssert])

另外,关于Buffer写入操作,还有一些Buffer类的原型函数可以操作。

Buffer复制函数 buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])。


// 新建两个Buffer实例
var buf1 = new Buffer(26);
var buf2 = new Buffer(26);

// 分别向2个实例中写入数据
for (var i = 0 ; i < 26 ; i++) {
    buf1[i] = i + 97; // 97是ASCII的a
    buf2[i] = 50; // 50是ASCII的2
}

// 把buf1的内存复制给buf2
buf1.copy(buf2, 5, 0, 10); // 从buf2的第5个字节位置开始插入,复制buf1的从0-10字节的数据到buf2中
console.log(buf2.toString('ascii', 0, 25)); // 输入buf2的0-25字节
> 22222abcdefghij2222222222

Buffer填充函数 buf.fill(value[, offset][, end])。


// 新建Buffer实例,长度20节节
var buf = new Buffer(20);

// 向Buffer中填充数据
buf.fill("h");
console.log(buf)
> <Buffer 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68>
console.log("buf:"+buf.toString())
> buf:hhhhhhhhhhhhhhhhhhhh
// 清空Buffer中的数据
buf.fill();
console.log("buf:"+buf.toString())
> buf:

Buffer裁剪,buf.slice([start][, end])。返回一个新的缓冲区,它和旧缓冲区指向同一块内存,但是从索引 start 到 end 的位置剪裁。


var buf1 = new Buffer(26);
for (var i = 0 ; i < 26 ; i++) {
    buf1[i] = i + 97;
}

// 从剪切buf1中的0-3的位置的字节,新生成的buf2是buf1的一个切片。
var buf2 = buf1.slice(0, 3);
console.log(buf2.toString('ascii', 0, buf2.length));
> abc

// 当修改buf1时,buf2同时也会发生改变
buf1[0] = 33;
console.log(buf2.toString('ascii', 0, buf2.length));
> !bc

2.3 读取Buffer

我们把数据写入Buffer后,我们还需要把数据从Buffer中读出来,新建文件buffer_read.js。我们可以通过readXXX()函数获得对应该写入时编码的索引值,再转换原始值取出,有这种方法操作中文字符就会变得麻烦,最常用的读取Buffer的方法,其实就是toString()。


~ vi buffer_read.js

//////////////////////////////
// Buffer 读取
//////////////////////////////

var buf = new Buffer(10);
for (var i = 0 ; i < 10 ; i++) {
    buf[i] = i + 97;
}
console.log(buf.length + " bytes: " + buf.toString('utf-8'));
> 10 bytes: abcdefghij

// 读取数据
for (ii = 0; ii < buf.length; ii++) {
    var ch = buf.readUInt8(ii); // 获得ASCII索引
    console.log(ch + ":"+ String.fromCharCode(ch));
}
> 97:a
98:b
99:c
100:d
101:e
102:f
103:g
104:h
105:i
106:j

写入中文数据,以readXXX进行读取,会3个字节来表示一个中文字。


var buf = new Buffer(10);
buf.write('abcd')
buf.write('数据',4)
for (var i = 0; i < buf.length; i++) {
    console.log(buf.readUInt8(i));
}

>97
98
99
100
230  // 230,149,176 代表“数”
149
176
230  // 230,141,174 代表“据”
141
174 

如果想输出正确的中文,那么我们可以用toString(‘utf-8’)的函数来操作。


console.log("buffer :"+buf); // 默认调用了toString()的函数
> buffer :abcd数据
console.log("utf-8  :"+buf.toString('utf-8'));
> utf-8  :abcd数据
console.log("ascii  :"+buf.toString('ascii'));//有乱码,中文不能被正确解析
> ascii  :abcdf0f
.
console.log("hex    :"+buf.toString('hex')); //16进制
> hex    :61626364e695b0e68dae

对于Buffer的输出,我们用的最多的操作就是toString(),按照存入的编码进行读取。除了toString()函数,还可以用toJSON()直接Buffer解析成JSON对象。


var buf = new Buffer('test');
console.log(buf.toJSON());
> { type: 'Buffer', data: [ 116, 101, 115, 116 ] }

3. Buffer的性能测试

通过上文中对Buffer的介绍,我们已经了解了Buffer的基本使用,接下来,我们要开始做Buffer做一些测试。

3.1 8K的创建测试

每次我们创建一个新的Buffer实例时,都会检查当前Buffer的内存池是否已经满,当前内存池对于新建的Buffer实例是共享的,内存池的大小为8K。

如果新创建的Buffer实例大于8K时,就把Buffer交给SlowBuffer实例存储;如果新创建的Buffer实例小于8K,同时小于当前内存池的剩余空间,那么这个Buffer存入当前的内存池;如果Buffer实例不大0,则统一返回默认的zerobuffer实例。

下面我们创建2个Buffer实例,第一个是以4k为空间,第二个以4.001k为空间,循环创建10万次。


var num = 100*1000;
console.time("test1");
for(var i=0;i<num;i++){
    new Buffer(1024*4);
}
console.timeEnd("test1");
> test1: 132ms

console.time("test2");
for(var j=0;j<num;j++){
    new Buffer(1024*4+1);
}
console.timeEnd("test2");
> test2: 163ms

第二个以4.001k为空间的耗时多23%,这就意味着第二个,每二次循环就要重新申请一次内存池的空间。这是需要我们非常注意的。

3.2 多Buffer还是单一Buffer

当我们需要对数据进行缓存时,创建多个小的Buffer实例好,还是创建一个大的Buffer实例好?比如我们要创建1万个长度在1-2048之间不等的字符串。


var max = 2048;     //最大长度
var time = 10*1000; //循环1万次

// 根据长度创建字符串
function getString(size){
    var ret = ""
    for(var i=0;i<size;i++) ret += "a";
    return ret;
}

// 生成字符串数组,1万条记录
var arr1=[];
for(var i=0;i<time;i++){
    var size = Math.ceil(Math.random()*max)
    arr1.push(getString(size));
}
//console.log(arr1);

// 创建1万个小Buffer实例
console.time('test3');
var arr_3 = [];
for(var i=0;i<time;i++){
    arr_3.push(new Buffer(arr1[i]));
}
console.timeEnd('test3');
> test3: 217ms

// 创建一个大实例,和一个offset数组用于读取数据。
console.time('test4');
var buf = new Buffer(time*max);
var offset=0;
var arr_4=[];
for(var i=0;i<time;i++){
    arr_4[i]=offset;
    buf.write(arr1[i],offset,arr1[i].length);
    offset=offset+arr1[i].length;
}
console.timeEnd('test4');
> test4: 12ms

读取索引为2的数据。


console.log("src:[2]="+arr1[2]);
console.log("test3:[2]="+arr_3[2].toString());
console.log("test4:[2]="+buf.toString('utf-8',arr_4[2],arr_4[3]));

运行结果如图所示。
buffer_test

对于这类的需求来说,提前生成一个大的Buffer实例进行存储,要比每次生成小的Buffer实例高效的多,能提升一个数量级的计算效率。所以,理解并用好Buffer是非常重要的!!

3.3 string VS Buffer

有了Buffer我们是否需求把所有String的连接,都换成Buffer的连接?那么我们就需要测试一下,String和Buffer做字符串连接时,哪个更快一点?

下面我们进行字符串连接,循环30万次。


//测试三,Buffer VS string
var time = 300*1000;
var txt = "aaa"

var str = "";
console.time('test5')
for(var i=0;i<time;i++){
    str += txt;
}
console.timeEnd('test5')
> test5: 24ms

console.time('test6')
var buf = new Buffer(time * txt.length)
var offset = 0;
for(var i=0;i<time;i++){
    var end = offset + txt.length;
    buf.write(txt,offset,end);
    offset=end;
}
console.timeEnd('test6')
> test6: 85ms

从测试结果,我们可以明显的看到,String对字符串的连接操作,要远快于Buffer的连接操作。所以我们在保存字符串的时候,该用string还是要用string。那么只有在保存非utf-8的字符串以及二进制数据的情况,我们才用Buffer。

6. 程序代码

本文的程序代码,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学习buffer,下载地址:https://github.com/bsspirit/nodejs-buffer

也可以直接用github命令行来下载:


~ git clone git@github.com:bsspirit/nodejs-buffer.git   # 下载github项目
~ cd nodejs-buffer                                      # 进入下载目录

关于Node.js的底层,本人接触并不多,未能从V8(C++)的做更深入的研究,仅仅在使用层次上,写出我的总结。对于文中的错误或描述不清楚的地方,还请大牛予以指正!!

参考文章:

浅析nodejs的buffer类

转载请注明出处:
http://blog.fens.me/nodejs-buffer/

打赏作者