npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

node-oojs

v1.4.0

Published

Object Oriented JavaScript

Downloads

72

Readme

OOJS - make codes easy!

简单, 是一种美、一种哲学、一种信仰. oojs 提供了最佳的 javascript 编程方式, 让代码更加简单的编写、阅读和维护.

oojs 代码示例:

//创建cookie类
oojs.define({
    name: 'cookie',                 //类名
    namespace: 'oojs.utility',      //命名空间
    prefix: 'prefix-',              //属性
    getCookie:function(key){...}    //函数
});

//使用cookie类
var cookie = oojs.using('oojs.utility.cookie');
var id = cookie.getCookie('id');

oojs 是 编程框架 而非 类库 , 适用于所有的js项目, 可以和各种规范如ADM,CDM等一起使用. oojs中的oo即面向对象(Object Oriented), 对象是组织代码的最小单位.

oojs 核心理念:

万物皆对象 对象皆JSON

oojs 主要功能:

使用JSON结构描述类 使用命名空间组织类 兼容全版本node环境 兼容全部浏览器环境


#传统的JS编程方式

首先, 让我们了解为何传统js编程方式会导致代码的可读性下降. 因为js的灵活性, 在开发中经常会出现孤零的变量和函数, 比如:

var a = function(){return b};
var b = 'hello';
var c = a();
function d(){
    //...
}

所以我们常常见到这样组织代码的:

var property1 = 'a';
function method1(){...};
var property2 = 'b';
function method2(){...};
var property3 = '';
if(property1==='a'){
    property3 = 'c';
    var method3 = method1(property3);
}

再加上匿名函数和闭包的乱入:

//匿名函数和闭包举例
var property4 = 'd';
var method4 = method1(function(){
    function(){
        return property4;
    }
})

即使是有名的js项目或者大神们编写的代码已经尽量清晰的去组织变量和代码, 当代码量过多时也必然会导致可读性下降. 本质原因是传统的js开发 将变量作为了组织代码的最小单位 .

oojs的思想是 将对象作为组织代码的最小单位 . 实际上, 已经有很多的开发者意识到了这一问题.比如在最新版本的jQuery中源码, 已经通过使用JSON对象提高了代码可读性:

jQuery.extend({
	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},
    ...
	noop: function() {}
});

由此可见, 使用JSON字面量创建对象, 是最自然的面向对象编程方式.

基于此, oojs诞生了.


oojs入门

名词解释-全限定性名: 命名空间+类名.比如类C, 所在的命名空间是A.B, 则C的全限定性名是A.B.C

简单示例

下面是一个简单的node程序示例.

//node环境下引用oojs
require('node-oojs');

//定义cookie类
oojs.define({
    name: 'cookie',
    namespace: 'oojs.utility',
    getCookie:function(key){...},
    setCookie:function(key, value, option){...}
});

//使用cookie类, 因为cookie是静态类所以不需要实例化
var cookie = oojs.using('oojs.utility.cookie');

//使用cookie类的getCookie函数
var id = cookie.getCookie('id');

在oojs中, 使用JSON结构声明一个类. 通过oojs.define完成类的定义. 通过oojs.using获取到类的引用.

开始使用

下面详细的讲解使用oojs的步骤.

1. 引用oojs

oojs支持浏览器和node环境. 使用oojs的第一步就是引入oojs.

node 环境

使用npm或者cnpm安装最新版本的oojs. 注意oojs的npm模块名称叫做"node-oojs"(因为npm上的oojs名字已经被占用). 在项目根目录运行npm命令:

npm install node-oojs

在程序的入口处引用:

require('node-oojs');

整个程序进程只需要引用一次.

浏览器环境

在浏览器环境下, 需要手动下载oojs文件. 项目地址:

https://github.com/zhangziqiu/oojs

oojs项目的bin目录下面, 有两个js文件:

  • oojs.core.js: 仅包括核心的面向对象编程功能.
  • oojs.js: 除了核心功能, 还包括loader类用于加载依赖类. 以及event类用于处理事件编程. 通常都加载此文件.

将oojs.js下载到项目中后, 直接引用即可:

<script type="text/javascript" src="./bin/oojs.js"></script>

2. 创建类

[项目根目录]/src/utility 目录下创建template.js文件, 内容如下:

    //template类提供一个render方法用于模板和数据的拼接
	oojs.define({
		name: 'template',
		namespace: 'utility',
		render: function (source, data) {
			var regexp = /{(.*?)}/g;
			return source.replace(regexp, function (match, subMatch, index, s) {
				return data[subMatch] || "";
			});
		}
	});

上面就是一个完整类的代码. 在oojs中, js类文件都是以oojs.define开始, 里面包括一个JSON格式的对象. 开发者可以自由的添加属性和方法, 但是要注意 此JSON对象的以下属性是oojs框架使用的:

  • name: 类名, 比如上面的 template
  • namespace: 类所在的命名空间. 比如template类放在了 utility 命名空间下. 我们可以将所有的工具类都放在utility命名空间下,实现类的组织管理.
  • 名字为 "类名" 的函数(比如template类的template函数): 类的构造函数. 使用 oojs.create 创建一个类实例时, 会执行一次构造函数.
  • 名字为 "$+类名" 的函数(比如template类的$template函数): 类的静态构造函数. 当使用oojs.define将一个类引入时, 会执行一次静态构造函数. 创建实例的时候不会执行. 多次执行oojs.define只有第一次引入时会调用一次.
  • deps: 依赖的类. 使用object描述. key为引用变量名, value为类的全限定性名. 后续可以通过this.key引用到这个依赖类. 在构造函数和静态构造函数执行时,所有的deps依赖类都已经加载完毕.可以安全的使用依赖类. 在构造函数或者静态构造函数中可以放心的使用this.key的方式使用依赖类. 有关加载依赖的详细说明后面会有单独的章节讲解.

3. 使用类

现在我们已经有了一个template类. 下面介绍如何在不同的环境下使用template类.

3.1 创建入口类

通常程序都有一个main函数作为程序入口, 在oojs中稍有不同, 借助oojs的依赖管理和静态构造函数, 我们可以构造一个入口类main.js:

    oojs.define({
        name: 'main',
        deps: { template: 'utility.template' },
        $main: function(){
            var result = this.template.render('My name is {name}', {name:'ZZQ'});
            console.log(result);
        }
    });

main.js可以放置在任意目录,而且也没有命名空间. main的静态构造函数$main作为程序的入口, 在静态构造函数中通过"this.template"的引用到template类.

3.2 node环境运行

在node环境中运行main.js, 需要在main.js顶部添加引用oojs库的代码:

    //引用oojs库
    require('node-oojs');
    //后面就是main类的完整代码.
    oojs.define({
        name: 'main',
        deps: { template: 'utility.template' },
        $main: function(){
            var result = this.template.render('My name is {name}', {name:'ZZQ'});
            console.log(result);
        }
    });

运行:

    node main.js

即可看到输出结果:

    My name is ZZQ

在node环境下, 当加载 utility.template 类时, 默认会从如下路径加载类文件:

[node运行目录]/src/utility/template.js

即将 [node运行根目录]/src 目录作为代码存放的根目录, 每一个命名空间就是一个文件夹.

3.3 浏览器环境

假设项目目录就是网站的根目录, 并且网站名称是 localhost. 在根目录下创建main.html, 编写如下代码:

<!DOCTYPE html>
<html>
    <body>
        <!-- 引入oojs, 假设将oojs.js下载到了src目录中 -->
        <script type="text/javascript" src="./src/oojs.js"></script>
        <!-- 设置类文件根目录 -->
        <script>
            oojs.setPath('http://localhost/src/');
        </script>
        <script>
                //下面是main.js的内容, 可以将main直接写在页面里
                oojs.define({
                    name: 'main',
                    deps: { template: 'utility.template' },
                    $main: function(){
                        var result = this.template.render('My name is {name}', {name:'ZZQ'});
                        console.log(result);
                    }
                });
        </script>
    </body>
</html>

打开 http://localhost/main.html页面, 即可在console控制台中看到:

    My name is ZZQ

在浏览器中使用时, 需要设置类文件的根目录.oojs框架将从指定的根目录, 使用异步的方式加载类. 比如上面的例子加载 utility.template 类的地址是:

    http://localhost/src/utility/template.js

如果main有多个依赖类, 会同时并行异步加载, 并且在所有的类都加载完毕后在运行$main静态构造函数.

通过上面实例的详细讲解, 已经可以使用oojs编写简单可维护的js代码了. 下面的篇幅会介绍一些oojs细节和高级用法.


类的加载路径

使用oojs的项目代码是用类组织的.在入门示例中, template.js 加载时, 默认使用如下路径:

node: [node运行的目录]/src/utility/template.js

浏览器: [页面当前域名]/src/utility/template.js

即无论是node还是浏览器, 在项目的根目录里创建src文件夹, src内根据命名空间来组织文件夹.

oojs提供了setPath函数, 可以为特定的类或者命名空间指定自己特殊的加载路径.

//设置全局根目录
oojs.setPath('./src2/');
oojs.setPath('http://localhost/asset/js/');

//为 A 和 A.B 命名空间设置特殊的加载路径
oojs.setPath({
    'A':'./src1/A/',
    'A.B':'./src2/A/B/'
});

oojs的路径查找使用的是树状结构. 以"A.B.C"这个类的查找规则为例:

  • 首先从root根目录查找. 此时path为默认的src目录.
  • 查找命名空间A, 发现通过setPath设置了A的路径, 此时path为上面设置的 './src1/A'
  • 查找命名空间A.B, 发现通过setPath设置了A.B的路径, 此时path为上面设置的 './src2/A/B/'
  • 查找命名空间A.B.C, 未发现A.B.C的路径, 于是使用上一步的path作为结果返回

通过上面的查找行为可以看出, 优先使用更加精确的类的加载路径, 否则使用父路径.


oojs的git源码结构

bin目录

bin目录中, 用于存放编译后的文件. bin目录下有以下两个js文件:

  • oojs.core.js: 压缩后的文件. 仅包括核心的面向对象编程功能.
  • oojs.js: 压缩后的文件. 除了核心功能, 还包括loader类用于加载依赖类.以及event类用于处理事件编程. 通常都加载此文件.

在bin的根目录下, 放置的是代码压缩后的js文件. 通常在项目中引用的就是代码压缩后的js文件. 同时为了应对一些比如调试等场景, bin目录下还包括如下文件夹:

  • format文件夹: 格式化,但是无注释的编译结果
  • gzip文件夹: gzip后的编译结果
  • source文件夹: 包含注释的编译结果

上面每一个文件夹中, 也都包含 oojs.core.js和oojs.js两个文件(gzip目录下是.gz后缀), 可以根据需要使用.

src目录

src目录存放oojs的源码文件. oojs框架自身也是按照oojs的编程方式实现的, 即oojs是自解析的.

  • oojs文件夹 : oojs命名空间
    • event.js : oojs.event类实现了事件编程和promise编程模式. 这是异步编程时必不可少的类.
    • loader.js : oojs.loader类提供了浏览器环境加载依赖类的加载器.
  • oojs.js : oojs的核心编程框架. oojs.core.js就是直接从这个文件编译出来的.

项目根目录

bin和src两个目录是最主要的文件夹. 除了这两个文件夹, oojs的项目根目录还包括如下文件:

  • .gitignore: git配置文件, 里面写明了git不需要管理的文件. 比如 node_modules 文件夹.

  • README.md: 说明文档. 即您正在阅读的这个页面的内容.

  • make.js: 编译文件. 使用 node make.js 命令, 可以从src目录生成bin目录下面的所有文件.

  • package.json: 包描述文件.


事件函数与this指针

假设我们声明了一个类:

    oojs.define({
        name:'myClass',
        word: 'Hello World',
        say: function(){
            alert(this.word);
        }
    })

myClss类有一个say函数, 输出myClass类的word属性. say函数中通过this引用myClass类自身. 这在通常情况下都是正确的.

但是在事件中, 比如常见的按钮单击事件, 或者一个定时器函数, this指针并不是总指向myClass自身的:

    window.word = 'I am window';
    var myClass = oojs.using('myClass');
    setTimeout(myClass.say, 1000);

上面的代码会输出"I am window"而不是myClass类中的"Hello World". 因为在setTimeout中的this指向了window对象而不是myClass.

oojs提供了proxy函数用于解决this指针问题. 默认情况下为了使用方便, 会为function的原型添加proxy函数. 如果不希望对原型造成污染也可以通过配置取消此功能.

proxy函数用来修改事件的this指针. 比如上面的代码可以这样修改:

    var myClass = oojs.using('myClass');
    setTimeout(myClass.say.proxy(myClass), 1000);

使用了proxy之后, 可以正常的输出"Hello World".

proxy函数的第一个参数表示this指针需要指向的对象.

proxy函数还可以 修改事件处理函数的签名 , 下面举一个复杂的例子.

在nodejs中, 系统提供了socket对象, 用于网络编程.

var net = require('net');
var client = net.connect({port: 8124},
    function() { //'connect' listener
          console.log('client connected');
          client.write('world!\r\n');
});

调用 net.connect函数时, 需要传递一个回调函数, 并且回调函数是无参数的. 通常, 使用上面的例子, 我们传递了一个匿名的回调函数, 并且在这个回调函数中使用 client变量, 此时会生成一个闭包, 以便在回调函数执行时, 可以正确访问到client变量.

使用proxy函数, 可以用一种 显式闭包 的方式, 将client作为参数传递, 让其看起来是通过参数传递的而不是使用闭包:

var net = require('net');
var client = net.connect({port: 8124},
    function(mySocket) { //'connect' listener
          console.log('client connected');
          mySocket.write('world!\r\n');
}.proxy(this, client));

注意, 这里通过proxy除了传递this对象外, 还传递了client变量. connect原本的回调函数是没有签名的, 但是你会发现在回调函数执行时, mySocket可以被正常访问. 此时我们将原本无参数的事件处理函数, 变成了一个有参数的事件处理函数.

proxy函数看似神奇,其实内部还是使用闭包实现. 所以我在这里称其为 显式闭包 . 使用显示闭包极大的增加了代码的可读性和可维护性. 可以说显示闭包让邪恶的闭包从良了.

另外, 显示闭包还可以解决循环中使用闭包的常见错误, 看下面的例子: (参见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures)

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

数组 helpText 中定义了三个有用的提示信息,每一个都关联于对应的文档中的输入域的 ID。通过循环这三项定义,依次为每一个输入域添加了一个 onfocus 事件处理函数,以便显示帮助信息。

运行这段代码后,您会发现它没有达到想要的效果。无论焦点在哪个输入域上,显示的都是关于年龄的消息。

该问题的原因在于赋给 onfocus 是闭包(showHelp)中的匿名函数而不是闭包对象;在闭包(showHelp)中一共创建了三个匿名函数,但是它们都共享同一个环境(item)。在 onfocus 的回调被执行时,循环早已经完成,且此时 item 变量(由所有三个闭包所共享)已经指向了 helpText 列表中的最后一项。

使用proxy函数就不会出现上面的问题:

    document.getElementById(item.id).onfocus = function(ev, item) {
      showHelp(item.help);
    }.proxy(this, item);

特别要注意, proxy函数只能在原有的事件处理函数后面新增参数. onfocus事件原本是包括一个事件对象参数的. 即上面的ev. 所以需要将item作为第二个函数参数使用.

相对于传统的node变成, 下面来看看使用oojs实现的完整的socket服务器的例子:

require('node-oojs');


oojs.define({
    name: 'socketServer',    
    /**
     * 静态构造函数
     */
    $socketServer: function () {
        var net = require('net');
		
        //启动服务
        this.server = net.createServer();
        this.server.on('connection', this.onConnection.proxy(this));
        this.server.on('error', this.onError.proxy(this));
        this.server.listen(8088, function () {
            base.log.info('server bound');
        });
    },

    /**
     * 服务器连接事件处理函数. 
     * @param {Object} socket 本次连接的socket对象
     */
    onConnection: function (socket) {
        socket.on('data', this.onData.proxy(this, socket));
        socket.on('end', this.onEnd.proxy(this, socket));
        socket.on('error', this.onError.proxy(this, socket));
    },

     /**
     * socket接收数据事件处理函数. 
     * @param {Object} data 本次介绍到的数据对象buffer
     * @param {Object} socket 本次连接的socket对象
     */
    onData: function (data, socket) {
        //do something...
    },

    /**
     * socket 关闭事件处理函数. 
     * @param {Object} socket 本次连接的socket对象
     */
    onEnd: function (socket) {
        //do something...
    },

    /**
     * socket 异常事件处理函数. 
     * @param {Object} err 异常对象
     * @param {Object} socket 本次连接的socket对象
     */
    onError: function (err, socket) {
        //do something...
    }
});

#oojs的原型继承和快速克隆 oojs中使用特有的快速克隆方法实现高效的对象创建. 主要用在内部的oojs.create函数中, 此函数用于创建一个类实例.

假设a是classA的一个实例. 此时的原型链情况如下:

a.contructor.prototype->classA

当访问a的一个属性时, 有以下几种情况:

  1. 属性是值类型: 访问: 通过原型链获取到classA的属性 赋值: 在a对象上设置新的属性值, 再次访问时获取到的是a对象上的新值
  2. 属性是引用类型(比如object类型): 访问: 通过原型链获取到classA的属性 赋值: 因为是引用类型, 所以实际上是对classA上的引用对象赋值. 即classA被修改, 所有实例的此属性都被修改

为了解决此问题, oojs在创建classA的实例时, 会遍历classA的属性, 如果发现属性的类型是引用类型, 则对其进行快速克隆:

        /**
         * 快速克隆方法
         * @public
         * @method fastClone
         * @param {Object} source 带克隆的对象. 使用此方法克隆出来的对象, 如果source对象被修改, 则所有克隆对象也会被修改
         * @return {Object} 克隆出来的对象.
         */
        fastClone: function (source) {
            var temp = function () {};
            temp.prototype = source;
            var result = new temp();
        }

传统的克隆对象是十分消耗性能的, oojs的最初也是用了传统的克隆方法. 最后改进成使用快速克隆方法.

假设这个属性为A, 此时相当于将属性A作为类, 创建了一个属性A的实例, 即关系是:

a.A.constructor.prototype -> classA.A 

此时, 如果A的所有属性都不是引用类型, 则可以解决上面的赋值问题. 但是如果属性A本身, 又含有引用类型, 则同样会出现赋值是修改原形的问题. 假设: A.B为object 则通过 a.A.B 获取到的对象与 classA.A.B 获取到的对象是同一个对象. 对于a.A.B的修改同样会影响到classA.A.B 通过递归的快速克隆可以解决此问题, 但是因为性能开销太大, 所以oojs最后不支持多层的对象嵌套.

实际上, 我们可以通过编程方式来解决这个问题.

  • 在类声明时赋值的属性, 即JSON中直接描述的属性值, 应该是静态static类型. 不应在运行时修改.
  • 如果一个属性是实例属性, 则应该在动态构造函数中赋值.比如:
oojs.define({
    name: 'classA'
    A: null
    classA:function(){
        this.A = { B:1 }
    }
});

所以一定要注意, 如果类的属性是对象, 并且是实例属性(运行时会被修改),则必须在动态构造函数中创建.

另改一个问题就是a对象的遍历. 同样因为使用了原型继承, 不能够通过hasOwnProperty来判断一个属性是否是实例a的. 可以通过遍历classA来解决:

for(var key in a){
    if(key && typeof a[key] !== 'undefined' && classA.hasOwnProperty(key)){
        //do something...
    }
}

#事件编程 js中常常使用事件和异步, 在浏览器端的Ajax是异步, 在nodejs中更是到处都是异步事件.

在异步事件的编程中, 常常会遇到多层嵌套的情况. 比如:

var render = function (template, data, l10n) {
  //do something...
};

$.get("template", function (template) {
  // something
  $.get("data", function (data) {
    // something
    $.get("l10n", function (l10n) {
      // something
      render(template, data, l10n);
    });
  });
});

在异步的世界里, 需要在回调函数中获取调用结果, 然后再进行接下来的处理流程, 所以导致了回调函数的多层嵌套, 并且只能串行处理.

oojs提供了oojs.event, 来解决此问题. 比如要实现上面的功能, 可以进行如下改造:

var render = function (template, data, l10n) {
  //do something...
};

var ev = oojs.create(oojs.event);
ev.bind('l10n', function(data){
    ev.emit('l10n', data);
});
ev.bind('data', function(data){
    ev.emit('data', data);
});
ev.bind('template', function(data){
    ev.emit('template', data);
});
//并行执行template, data和l10n事件, 都执行完毕后会触发group中的回调函数
ev.group('myGroup', ['template','data','l10n'], function(data){
    render(data.template, data.data, data.l10n);
});

oojs.event的group可以将事件打包成一组. 在group的回调函数中, 会传递一个参数data, 这是一个object对象, 其中key为group中绑定的每一个事件名, value为事件的返回值. 所以可以通过data[事件名]获取到某一个事件的返回值.

oojs.event中的group还可以动态添加新的事件. 比如:

ev.group('myGroup', ['template','data','l10n'], function(data){
    render(data.template, data.data, data.l10n);
});

ev.group('myGroup', ['another'], function(data){
    anotherData = data.another;
});

注意上面的代码, 虽然为myGroup又添加了一个another事件. 但是此时mygroup绑定了两个事件处理函数, 这两个函数都会在所有事件完成时执行, 但是不一定哪个在前. 所以oojs.event还提供了afterGroup事件, 此事件会在所有group绑定的callback执行完毕后再执行:

ev.group('myGroup', ['template','data','l10n']);

ev.group('myGroup', ['another']);

ev.afterGroup('myGroup', function(data){
    render(data.template, data.data, data.l10n, data.another);
});

oojs.event使用oo的思想实现. node中本身自带EventEmmiter也实现了部分功能.


Promise 编程

使用event事件编程, 可以解决回调函数嵌套的问题. 但是是否有更好的解决办法呢? 答案是使用Promise.

通过举例, 来快速的了解什么是Promise.

传统的回调函数方式编程, 在这个例子中, step1,step2,step3 三个函数都是异步顺序执行的:

var step1,step2,step3 = function(data, callback){
    var result = data + 1;
    callback(result);
}

// step1开始执行
var data1 = 1;
step1(data1, function(data2){
    // step2开始执行
    step2(data2, function(data3){
        // step3开始执行
        step3(data3, function(data4){
            // 输出最终执行结果
            console.log(data4);
        })
    })
})

使用event事件编程, 绑定了一个事件序列:

var ev = oojs.create(oojs.event);
ev.bind('step1', function(data1){
    // step1开始执行
    var data2 = data1 + 1;
    ev.emit('step2', data2);
});
ev.bind('step2', function(data2){
    // step2开始执行
    var data3 = data2 + 1;
    ev.emit('step3', data3);
});
ev.bind('step3', function(data3){
    // step3开始执行
    var data4 = data3 + 1;
    // 输出最终执行结果
    console.log(data4);
});

// 从step1开始
ev.emit('step1', 1);

使用Promise编程, 使用then函数将事件串联起来:

// 创建了一个完成态的promise对象,下一个then会立刻执行,并且传递参数 1.
var promise = oojs.promise.resolve(1);
promise.then(function(data1){
     // step1开始执行
     var data2 = data1 + 1;
     return data2;
}).then(function(data2){
     // step2开始执行
     var data3 = data2 + 1;
     return data3;
}).then(function(data3){
     // step3开始执行
     var data4 = data3 + 1;
     // 输出最终执行结果
    console.log(data4);
})

从上面三个实例可见, event和promise都可以解决回调函数嵌套的问题.

不同的是, event需要创建三个事件, 以step1事件为例,需要在step1的事件处理函数中,显示的调用step2.即事件序列的执行迅 速分散在了每一个事件处理函数中.

而使用promise时, 是由一个promise对象通过调用then函数, 将step1-3串联起来, 整个事件的执行顺序集中在了一起管理.

有关promise的深入学习,推荐以下学习资料: 《JavaScript Promise迷你书(中文版)》 http://liubin.org/promises-book/#ch2-promise-all


为什么要用面向对象的思想写js?

oo不仅仅是一种编程方法, 而是组织代码的最小单位.

看几个使用AMD规范的例子就会明白, AMD中最后一个参数factory虽然美其名曰构造函数, 但是在这个函数中, 你可以做任何事情:创建局部function, function中再嵌套function, 使用闭包, 处理一些业务逻辑. 最后的结果是这个factory不易阅读和维护.

究其原因, js编程很容易陷入面向过程编程的方式中. 而AMD等规范只注重"模块"的开发, 却忽视了一个模块内部的代码如何组织和管理.

js中让代码不易管理的几个杀手包括: 闭包, 零散的函数对象, 异步机制(node中尤其重要).

oojs使用oo的思想, 减少闭包的使用, 让每一个函数对象都挂靠在类对象上, 减少孤零的函数对象的存在. 再配合oojs.event的事件机制, 解决异步编程中的事件嵌套噩梦.

可以说oojs为js的大规模开发提供了有效地基础保障.


加入我们

oojs还在发展中, 我们尽量不在核心的oojs.js中加入过多的功能, 保持核心精简. 同时通过oojs团队成员的努力, 让oojs适用于更多的场景.

欢迎有志之士加入到oojs的开发中来!