Skip to content

写给 Jscex 的一些建议 #51

Open
lifesinger opened this Issue Aug 8, 2012 · 26 comments
@lifesinger

抱歉呀,今天才仔细阅读了一遍 Jscex 的文档,按照阅读顺序,提供一些建议:

首页

  1. jscex.info 这域名,是因为 jscex.org 被人抢占了么?如果 org 在老赵手上,建议用 org,这是开源组织的标配。另外,建议首页默认是中文,右上角给个英文版的链接就好。毕竟 Jscex 目前主要是想在大陆推广吧?国际化可以缓一缓。我们可以先尝试下先国内后国外的战略,发展中国家包围发达国家。能成的话,也是一种新气象,不然国人老是以为国外的月亮更圆,我们得让同胞们看看我们自身的实力,扭转大众的一些误区。

  2. Jscex 专项拨款挺好的,但缺乏明确的游戏规则。建议可以将参与方式、奖品规则写一篇博客详细介绍下,这样能更让诚意更具诚意,说不定哪位心头一热,就会开始给社区做贡献了。虽然奖品不是目的,但明确的游戏规则可以让运营更实际有效。

  3. “能够在任意支持ECMAScript 3的执行引擎里使用”,这句话建议去掉,容易给普通用户造成误解,比如:什么是 ECMAScript 3?怎么只支持 ECMAScript 3,难道不支持 ECMAScript 5 吗?可以考虑改成:能够在任意主流 JavaScript 引擎中使用。至于什么是主流 JS 引擎,知者自知,不必去解释,除非真有人问题(一般不会有人问)。

  4. 冒泡排序,对于计算机科班出身的程序员来说,不是问题。但 Jscex 如果想推广给更多前端程序员用,则写一小段文字或给出参考链接来解释下什么是冒泡排序还是有必要的,比如 NCZ 写过一篇 Computer science in JavaScript: Bubble sort。把用户当白痴,很重要很重要。国内技术圈,比我们想象中的浮躁很多。

  5. 冒泡排序这个例子,对于 Jscex 来说,核心还是动画的实现。我建议直接换成一个简单的 “Hello, World” 的文字飘入动画就好。今晚有时间的话,我来基于 seajs 的 plugin-jscex 做一个。这样应该能更亲民一些。冒泡排序可以作为进阶中的案例。

  6. “增加交换和比较操作的耗时,因为排序算法的性能主要取决于交换和比较操作的次数多少。” 这个说得不清楚,在这个例子里,增加耗时的目的,我觉得跟性能无关,而是为了让动画看起来不那么太快。建议改成:“增加交换和比较操作的耗时,这样在动画演示时能比较容易看清楚。”

  7. eval(Jscex.compile("async", function (x, y) { 这种写法,对入门者来说还是比较逆天的。打算在 seajs/plugin-jscex 里,把这技术实现细节隐藏掉。老赵等我的插件。

  8. $await(Jscex.Async.sleep(10)) 的写法,感觉也不够简洁。感觉也是将内部的实现暴露了出来。建议可以为外部用户提供一个 shortcut: $await(10). 其实我还有一个疑问:为什么要用 await ? 而不是更直白的 wait 或 sleep ?

开发指南

  1. require("jscex-parser").init() 建议简化成 require("jscex-parser"),init 内置掉。我还是期望对使用者来说,不要去理解什么是核心组件,什么是 parser,只要简简单单 var Jscex = require(jscex) 就万事大吉了。jit 可以让用户按需引入。或者变成:
require('jscex')  // 用于开发时,含 core、buildbase, async, parser 和 jit
require('jscex-runtime') // 用户上线后,只含 core, buildbase, async

这样也方便用户部署,而且大部分用户,用开发时的就满足了需求,runtime 版本交给那些对性能有追求的高级使用者去玩。

  1. 构造器基础模块究竟是干嘛的?文档里没有解释什么是构造器,以及什么时候需要自己构建构造器?看得不是很明白。这应该是高级用户才会去玩的吧?估计要看源码才知道如何去写。在文档里,如果某个内容说不清楚,建议不如先隐藏掉。

  2. 包引入这文档,动态依赖和静态依赖貌似写反了。静态依赖是需要手动自己去写依赖,动态依赖是指可以动态自动加载。还是我理解错了?

  3. async 居然还有一个 powerpack, 建议如果不是真的 power 到几十K的体积,建议就放到默认的 async 里。

  4. 感觉太多内部细节暴露给了使用者,感觉不是很妥当。是否可以在现有 API 的基础上,再包装出一层更傻瓜的高级 API ?比如上面提到了一些:

require('jscex') // <-- 80% 的用户,只要这一句就好了,然后全局就有了 Jscex 和 $await 等变量
$await(10) // <-- 等价 $await(Jscex.Async.sleep(10))

异步模块

这个文档入口,在开发指南里居然没有。我花了好长时间才从首页找到一个入口,有点囧。

$await指令的使用形式便是普通的方法调用,但事实上在上下文中并没有这个方法。它的作用与eval(Jscex.compile("async", …))一样,仅仅是个占位符,让Jscex知道在这个地方需要进行“特殊处理”。

这段话是否有误?能理解 $await 是个占位符,eval(Jscex.compile("async", …)) 怎么也成了占位符?我没看 jscex 的源码,直觉上,Jscex.compile("async", fn) 是一个真实的普通函数,这个函数执行时,会分析第二个参数 fn 中的 $await 等占位符,然后进行一系列操作,结果是一段编译后的代码,最后交给 eval 去在当前 context 上执行。我理解是否有误?

这个并发的例子:

    var queryUserTask = queryUserAsync(userId);
    // 手动启动queryUserAsync任务,start方法调用将立即返回。
    queryUserTask.start();

    var items = $await(queryItemsAsync(userId));
    var user = $await(queryUserTask); // 等待之前的任务完成

感觉理解起来还是有点绕。不如 async 等类库提供的 api 方便,比如类似:

async. parallel([queryUserAsync(userId), queryItems(userId)],  function(users, items) {
  // do sth
})

async 类库不反对异步回调,只是将回调套回调的写法变得更简单清晰。文档读到这里,Jscex 对我来说,貌似只有 $await 让我觉得非常诱人,难以舍弃-.-

任务模型

Event 这个例子让人有点小兴奋了,居然还可以这样用。我前面有些地方误解 Jscex 了,误解的文字也就不去修改了,保留一份阅读文档过程中的真实记录。

我喜欢这个:

return $await(Task.whenAll({
        user: queryUserAsync(userId),
        items: queryItemsAsync(userId)
    }));

比 async 类库强太多了。依旧期待是否在包装出一个高级 API,比如:

return $await.parallel({
        user: queryUserAsync(userId),
        items: queryItemsAsync(userId)
    }));

CancellationToken 又是高级用户内容了,这被普通用户看见,会有心理障碍的-.-

这取消模型还是得赞下,设计得挺棒的。

下面的貌似都是高级内容,我决定需要的时候再看,这份建议就写到这。

最后说个总体感觉,$await 魅力无穷,但需要通过 eval(Jscex.compile...)Task.create 来创建 AsyncTask 给 $await 调用,感觉对普通用户有难度,需要理解的东西比较多,写的时候要换一些思维去想。

$await 是更自然的异步流程控制,但感觉 task 不是很自然,没有传统的 callback 容易理解。

是否可以将 Task 隐藏掉?比如首页的例子如果能像下面这样写就帅呆啦:

function compare(x, y) {
    $await(10); // 暂停10毫秒
    return x - y; 
}

function swap(a, i, j) {
    $await(20); // 暂停20毫秒
    var t = a[i]; a[i] = a[j]; a[j] = t;
    paint(a); // 重绘数组
}

function bubbleSortAsync(array) {
    for (var x = 0; x < array.length; x++) {
        for (var y = 0; y < array.length - x; y++) {
            // 异步比较元素
            var r = $await(compare(array[y], array[y + 1])); // 如果这里也可以把 $await 省略掉就更帅啦
            // 异步交换元素
            if (r > 0) $await(swap(array, y, y + 1));
        }
    }
}

上面的代码,是在某一个文件里,比如 xx.js,这在 node 或 commonjs 里,都是一个模块,意味着我们可以在模块编译时,做点手脚,比如对于 NodeJS 环境,可以在 Module._compile 调用时,自动将 eval(Jscex.compile...) 加上。

SeaJS 的 plugin-jscex 我就是想这么干的,让开发时用户可以直接写成:

define(function(require, exports, module) {
  function compare(x, y) {
    $await(10); // 暂停10毫秒
    return x - y; 
  }

  function swap(a, i, j) {
    $await(20); // 暂停20毫秒
    var t = a[i]; a[i] = a[j]; a[j] = t;
    paint(a); // 重绘数组
  }

  function bubbleSortAsync(array) {
    for (var x = 0; x < array.length; x++) {
        for (var y = 0; y < array.length - x; y++) {
            // 异步比较元素
            var r = $await(compare(array[y], array[y + 1])); // 如果这里也可以把 $await 省略掉就更帅啦
            // 异步交换元素
            if (r > 0) $await(swap(array, y, y + 1));
        }
    }
  }
})

然后我在模块编译时,通过 plugin-jscex, 将上面的代价等价成:

define(eval(Jscex.compile('async', function(require, exports, module) {
  function compare(x, y) {
    $await(10); // 暂停10毫秒
    return x - y; 
  }

  function swap(a, i, j) {
    $await(20); // 暂停20毫秒
    var t = a[i]; a[i] = a[j]; a[j] = t;
    paint(a); // 重绘数组
  }

  function bubbleSortAsync(array) {
    for (var x = 0; x < array.length; x++) {
        for (var y = 0; y < array.length - x; y++) {
            // 异步比较元素
            var r = $await(compare(array[y], array[y + 1])); // 如果这里也可以把 $await 省略掉就更帅啦
            // 异步交换元素
            if (r > 0) $await(swap(array, y, y + 1));
        }
    }
  }
})))

老赵看看这样是否可行?等你确定后我再来折腾 plugin-jscex. 如果可行就太好了。

@lifesinger

plugin-jscex 的出发点,是想让 Jscex 对普通用户来说,看起就就只增加了一个 $await 关键字,而无需关心其他。如果能做到这一步,那至少我是肯定离不开 Jscex 了。

@lifesinger

与 seajs 的 issue 关联下: seajs/seajs#316

@sumory
sumory commented Aug 8, 2012
  • 不能再同意了
  • 也觉得compile太扎眼,诱人的await又有限制(形式上必须在compile内,而且还明晃晃的被用户可见)
@yaniswang

太赞了,支持玉柏。

jscex的语法学习成本太高了,各类约定和新语法。

特别是那个compile和eval,就已经让我感觉非常恐怖了。

var compareAsync = eval(Jscex.compile("async", function (x, y) {
    $await(Jscex.Async.sleep(10)); // 暂停10毫秒
    return x - y; 
}));

为何为把初始化的过程变成类似这样?

var compareAsync = Jscex("async", function (x, y) {
    $await(Jscex.Async.sleep(10)); // 暂停10毫秒
    return x - y; 
});

把实现细节隐藏在内部,这样感觉就舒服多了,看不见心不烦。

期待玉伯的seajs插件,那样的语法太爽了,用户不需要关心内部实现。

@JeffreyZhao
Owner

@lifesinger @yaniswang @sumory

说实话,这个evalcomplie是真心去除不掉的,而且这个恐怖完全是心理上的,说实话我也不是很愿意去掉,这方面也有很多思路可以说说。

具体能不能做到plugin-jscex这个我再看下哪!总之还是多谢捧场!

@lamb
lamb commented Aug 8, 2012

感觉引入dev模式的比较好。
require('jscex') // 用户上线后,只含 core, buildbase, async
require('jscex-dev') // 用于开发时,含 core、buildbase, async, parser 和 jit
这样给别人看到上线的代码是jscex,就是这一个;如果第一次看到jscex-runtime会有可能疑惑,除了runtime还有其他东西没?

@JeffreyZhao
Owner

@lifesinger

关于$await(10),其实$await后面带的是一种“对象”,假如为10做一个特例,那么为什么把这个10当做是一个“等待”,而不是别的含义呢?我不想特例化任何一个东西,所以是$await(sleep(10)sleep返回的是一个对象。

目前玉伯的提议如果是要封装eval的话,是要考虑到eval时的上下文,像下方那样define(eval(Jscex.compile()))是可行的,不过Jscex目前还没不支持这么做。不过很巧的是我正打算支持这点,可以看到这里的compileBlock,下个版本就会支持了。

@JeffreyZhao
Owner

@lambgao require是给Node.js用的,dev和prod也好都是为浏览器缩小体积用的,Node.js不需要区分这个……

@bastengao

支持 玉伯。

@JeffreyZhao JeffreyZhao was assigned Aug 8, 2012
@lifesinger

期待 compileBlock, 这样就真心方便写插件了,还可以给 node 也写一个插件模块,这样普通用户都不用知道 eval(Jscex.compile...)

$await(10) 可以认为就是一个 shortcut

我觉得 Jscex 现在提供的是一套面向高级用户的中级 API,可以考虑在这层 API 的基础上,再封装出一套更亲民的高级 API. SeaJS 内部也有一套类似的中级 API,目前只有高级用户以及插件开发者需要了解,普通用户则只需要靠直觉去使用 definerequire 等高级 API 就好。

@mengmomo
mengmomo commented Aug 8, 2012

老赵的思想要激荡下了

@JeffreyZhao
Owner

@lifesinger 假如没有第二个shortcut的话,我觉得暂时还是不支持了。其实现在只是$await(10)$await(sleep(10))的区别,而且“暂停”似乎更多是演示时需要,实际情况下用的不多?我还是想更多强调“模型”。

包数量过多的确可能是个问题,我考虑下个版本就缩减为jscex-compiler和jscex-async了,不区分parser、jit、async、async-powerpack那么多,该合并就合并掉!

@lifesinger

@JeffreyZhao 同意 $await(num) 的确没必要,貌似真实代码里不会这么写-.-

$await.parallel$await.series 呢?我用 async 库,这两个方法是用得最多的。Jscex 看能否考虑下?

@JeffreyZhao
Owner

@lifesinger Jscex的理念是$await是唯一的扩展点,其他都是类库,把各种调度统一成Task模型。

series对Jscex来说没有什么意义,因为就是:

$await(op1());
$await(op2());
$await(op3());

parallel其实也只是:

var resultArray = $await(whenAll(op1(), op2(), op3()));

Jscex最大的特点就是JavaScript语句本身的语义来进行流程控制。

@lifesinger

突然好奇了下,下面这种写法支不支持:

function sleep(n) {
    $await(Jscex.Async.sleep(n)); // 暂停 n 毫秒
}

var compareAsync = eval(Jscex.compile("async", function (x, y) {
    sleep(n);
    return x - y; 
}));

PS: 中文文档首页没有加载 Jscex.compile 相关的模块,导致想快速试验一些 idea 时,不方便。

前端有一个习惯,比如验证 jquery 支不支持某个特性,会打开 jquery.com ,在官方首页上通过浏览器的调试工具来测试,方便快捷。zepto, handlebars 等等类库都如此。建议 Jscex 首页加载完整版 Jscex,以方便用户快速尝试。

@JeffreyZhao
Owner

@lifesinger

这是不支持的,$await只能在eval(Jscex.compile())里面使用,除非这样:

var sleep = eval(Jscex.compile("async", function (n) {
    $await(Jscex.Async.sleep(n)); // 暂停 n 毫秒
}));

但你的意图只要这样就能满足:

var sleep = Jscex.Async.sleep;

或是:

function sleep(n) {
    return Jscex.Async.sleep(n);
}

其实这些就是最普通的JavaScript编程了,只要记住$await等待的是一个异步对象,至于这个对象是哪里来的都无所谓,Jscex.Async.sleep返回的是一个异步对象,你为它取得别名或者封装当然也可以,只要让它返回异步对象就行。

@JeffreyZhao
Owner

@lifesinger

其实eval(Jscex.compile())是一个标识符,用于清晰地区分普通函数和Jscex函数——并且,它可以正确执行。

不过以后有了compileBlock之后我也打算简化这块,因为它的职责纯粹只是一个标识符了,无需执行,这样就可以使用一个更易写的标示符,例如:

eval(Jscex.compileBlock(function () {    

    "async" << function compare(x, y) {
        $await(10); // 暂停10毫秒
        return x - y; 
    }

    "async" << function swap(a, i, j) {
        $await(20); // 暂停20毫秒
        var t = a[i]; a[i] = a[j]; a[j] = t;
        paint(a); // 重绘数组
    }

    "async" << function bubbleSortAsync(array) {
        for (var x = 0; x < array.length; x++) {
            for (var y = 0; y < array.length - x; y++) {
                // 异步比较元素
                var r = $await(compare(array[y], array[y + 1]));
                // 异步交换元素
                if (r > 0) $await(swap(array, y, y + 1));
            }
        }
    }

}));

至于$await绝对不能省略,不能默认运用“等待”语义,我就是希望一切都让人明白在做什么,有意而为之,否则各种问题更多……

@lamb
lamb commented Aug 9, 2012

有了compileBlock,运行的时候是不是就需要像mocha那样变成jscex [options] [files]?

@JeffreyZhao
Owner

@lambgao 当然不需要,compileBlock外面不还包着eval吗?还是动态编译执行,没有额外步骤。

@raindylong

作为jscex的初学者,在我阅读开发指南的时候,开始部分都是模模糊糊,唯有读到这一句的时候才恍然大悟:
$await指令的确切语义是:“等待该Task对象结束(返回结果或抛出错误);如果它尚未启动,则启动该任务;如果已经完成,则立即返回结果(或抛出错误)”。

我觉得作为一个需要让更多人来了解和使用的新类库来说,更多这样的总结性的警句、妙句,会更加有利于新手的了解和使用。而且,这样的妙句让新手越早阅读到心里会越舒坦,不然对于一些耐性不好的新手来说,读了几段就被吓住了,人也早跑了。

@raindylong

补充一条,作为javascript类库,使用此类库开发的小游戏是增加推广的比较有效的渠道,samples/async/bullet.html中这个例子感觉就不错,大可美化一下并标题为“是男人就坚持20秒”,邀请各位大神来比拼一下。 :)

@guang384

第一点非常赞同,先在国内推广吧。

@Jeffzhao360

very good.

@fantasybei

为了看懂老赵的wind,哥拾起了龙书,-_-!!!!!

@faylai
@loogn
loogn commented Oct 11, 2013

wind-core.js里的
var isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
可以优化成
var isArray = function (obj) {
return obj && obj.constructor===Array.prototype.constructor;
};
从函数调用+字符串比较简化成引用的比较

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.