V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
real_newbie
V2EX  ›  JavaScript

关于CommonJS中Module实现的问题

  •  
  •   real_newbie · 2011-04-28 12:22:06 +08:00 · 8786 次点击
    这是一个创建于 4956 天前的主题,其中的信息可能已经有所发展或是发生改变。
    原生的JavaScript中可以通过Closure来实现模块化. 比如像这里所做的这样: http://yuiblog.com/blog/2007/06/12/module-pattern/

    在CommonJS的实现中, 是这样做的: http://wiki.commonjs.org/wiki/Modules/1.1#Sample_Code

    CommonJS中的这种module的方式是怎么实现的呢?
    31 条回复    1970-01-01 08:00:00 +08:00
    real_newbie
        1
    real_newbie  
    OP
       2011-04-28 13:12:57 +08:00
    是不是CommonJS中的所谓Module和Yuiblog里所说的Module其实不是同一个概念?

    前者只是单纯的把不同的function放到了不同的文件里, 加入了require的机制; 后者可以进行一些类似数据封装的工作?
    kuno
        2
    kuno  
       2011-04-28 19:38:12 +08:00
    commonJS的module只是社区的一个proposal, 目前只有node.js部分实现了它。
    不过,目前看起来下一个版本的ecmascript的module系统会不太一样,
    兰州可以参考这个视频里的内容,
    kuno
        3
    kuno  
       2011-04-28 19:42:55 +08:00
    刚看了commonJS的wiki,原来还有很多的其他系统也实现了。
    更正一下, :(
    real_newbie
        4
    real_newbie  
    OP
       2011-04-28 19:46:49 +08:00
    @kuno,

    感谢回复, CommonJS的实现并不是只有node.js, 像SproutCore, CouchDB之类的也有实现. (CommonJS的Spec有实现的列表)

    你给出的那个视频好像是介绍下一代JavaScript中如何实现module的吧?(抱歉, 没仔细看视频, 只看了标题和开头). 我目前在做的东西是基于CouchDB的, 所以我想知道是关于CommonJS的, 而不是下一代JavaScript的实现.

    不管怎么样, 还是非常感谢你~
    aligo
        5
    aligo  
       2011-04-28 19:47:06 +08:00
    数据封装是什么意思?
    一般控制访问的话,return返回的是一个匿名函数或者对象,作为闭包,可以访问scope的东西,但是调用那个module的时候就用不了了

    yui的module就是基于这个基本原理包装起来的,这样就不会因为随便操作一般对象的prototype产生问题
    real_newbie
        6
    real_newbie  
    OP
       2011-04-28 19:52:13 +08:00
    @aligo,

    对于yui的module我是理解的, 就和你说的一样.
    aligo
        7
    aligo  
       2011-04-28 19:56:22 +08:00
    CommonJS的那个require应该就是
    function(jsfile){
    var exports = {};
    然后这里执行js文件里的代码
    return exports;
    }
    real_newbie
        8
    real_newbie  
    OP
       2011-04-28 20:00:34 +08:00
    @aligo,

    所以其实和yui的module是两个不同的概念, 是这样子吗?
    aligo
        9
    aligo  
       2011-04-28 20:08:05 +08:00
    @real_newbie 一样是闭包,不过如果那玩意要在客户端使用require,必须是像jsonp一样,完整的代码是:
    do_export(function(){
    var exports = {};
    然后这里执行js文件里的代码
    return exports;
    });

    不过这样的动态载入无故增加http请求数量,实在多余,一般是把所有js文件打包成一个,压缩之后要求客户端缓存
    real_newbie
        10
    real_newbie  
    OP
       2011-04-28 20:17:02 +08:00
    @aligo,

    我觉得不一样, 就拿主帖子中CommonJS Spec里的例子来讲. 我觉得如果是和yuiblog里一样的话, math.js应该改成下面这样样子, 才是一样的:

    exports.add = fuction() { return function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    };
    };

    然后再require的时候要这个样子:

    var add = require('math').add();
    aligo
        11
    aligo  
       2011-04-28 20:28:32 +08:00
    恩,你这个意思我明白,是要在exports.add有似有变量吧,不过你这个也不对,应该是:
    exports.add = fuction() {
    var args = arguments;
    return (function(arguments) {
    var sum = 0, i = 0, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    })(arguments);
    };

    但是我上面说的是,你要给前端使用的话,只有在上面这段代码的东西的基础上,再包裹
    do_export(function(){
    var exports = {};
    上面的代码
    return exports;
    });

    同时预先定义好do_export,用以接受require发出的http jsonp请求,但是这里有一个问题,浏览器的js貌似要blocking i/o等待require发起的异步请求返回是一件相当麻烦的事情了

    不知道你看懂了我说的没有。。。
    aligo
        12
    aligo  
       2011-04-28 20:29:11 +08:00
    订正:
    exports.add = fuction() {
    var args = arguments;
    return (function(args) {
    var sum = 0, i = 0, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    })(args);
    };
    real_newbie
        13
    real_newbie  
    OP
       2011-04-28 20:32:21 +08:00
    @aligo,

    抱歉, 我一开始就没讲明白, 我并不是要在前端使用的. 是用在Server端的.

    exports.add = fuction() {
    var args = arguments;
    return (function(arguments) {
    var sum = 0, i = 0, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    })(arguments);
    };

    这个不和我的require('math').add(); 只是你这一版本的应该还是require('math).add, 不用加"()"进行调用, 对吧?
    aligo
        14
    aligo  
       2011-04-28 20:42:03 +08:00
    @real_newbie 恩,我只是和你地址里的那个代码实现一样的东西var add = require('math').add;而不是var add = require('math').add();

    不过扯远了,我要说的是module这东西,用来实现通过scope进行访问控制是可以的,就像yui那样

    然后如果前端使用它是为了分开文件就没有必要了,而这里的require实现的包括exports,对于每个文件是相互独立的,实现原理应该就是#7的那样,对于不同环境当然有不同实现,不过你这样去理解就够了
    real_newbie
        15
    real_newbie  
    OP
       2011-04-28 20:46:38 +08:00
    @aligo,

    呵呵, 事实上你#11里讲的关于前端的那一段我的确没有看的太明白. 不过因为我是用在Server端的, 所以就不关心啦;)
    aligo
        16
    aligo  
       2011-04-28 20:52:50 +08:00
    @real_newbie 恩,其实就是前端要实现require动态载入js的话,就是动态插入script标签,必然事先得提供一个do_export函数用来接受闭包
    aligo
        17
    aligo  
       2011-04-28 20:56:04 +08:00
    @real_newbie 主要是你提到了yui,我以为你要在前端实现这个require,于是劝你放弃
    real_newbie
        18
    real_newbie  
    OP
       2011-04-28 21:04:19 +08:00
    @aligo,

    恩, 提到yui, 主要是因为我理解yui的这种方式, 所以想进行对比下.

    另外off topic, 前端的话, 好像有这个玩意: https://github.com/jrburke/requirejs

    不过似乎压缩以后还是显的有点过大了.
    aligo
        19
    aligo  
       2011-04-28 21:13:25 +08:00
    @real_newbie 恩,这个东西well-scoped module就是用define()包裹新加入的js,类似我刚才说的do_export()

    不过一般用用直接插入script标签就够了,不过意义不大,因为这增加了http请求次数,一般需求都是把js合并后各种压缩成一个
    kuno
        20
    kuno  
       2011-04-29 10:14:46 +08:00
    根据我使用node.js的经验,commonJS的实现并没有使用closure.
    在node里面,module是一个特别的全局对象,有一个exports的属性。所有能够被调用的对象又都是exports的成员。当一个这个module通过require('MODULE_NAME')被调用之后,exports包含的成员就被载入了当前客户端global scope, 成为一个全局对象了。


    function require(jsfile) {
    var module = eval(do_some_io(jsfile));

    return moduel.exports;
    }
    aligo
        21
    aligo  
       2011-04-29 11:53:03 +08:00
    @kuno 我试了一下
    bar.js:
    var foo = 'bar'

    exports.bar = function () {
    return foo
    }

    foo.js:
    require.paths.unshift('.')

    var bar = require('bar')

    console.log(typeof bar.foo == 'undefined')
    console.log(typeof bar.bar == 'function')

    console.log(bar.foo)
    console.log(bar.bar())


    node foo.js结果是:
    true
    true
    undefined
    bar

    这里新require进来的js并不是一个global scope,如果拿来和ruby比较的话
    module Bar
    BAR = 'bar'
    def ...
    end
    然后在别的地方是可以Bar::BAR的
    real_newbie
        22
    real_newbie  
    OP
       2011-04-29 11:56:18 +08:00
    @kuno,

    恩, 我也是这么理解的. 只是单纯的把原来放在一个文件里的东西分散到了不同的文件, 便于管理而已.
    aligo
        23
    aligo  
       2011-04-29 11:57:51 +08:00
    @kuno 并且如果再加入一个rab.js如下
    exports.rab = function () {
    return exports.bar()
    }

    然后在原来的的foo.js
    var rab = require('rab')
    console.log(rab.rab())

    这样是会出错的
    aligo
        24
    aligo  
       2011-04-29 12:02:53 +08:00
    @kuno 最后,再创建一个oof.js:
    exports.oof = function () {
    return oof_text
    }
    exports.test_oof = function () {
    return typeof oof_text == 'undefined'
    }

    foo.js改为:
    var oof_text = 'oof!'
    require.paths.unshift('.')
    var oof = require('oof')

    console.log(oof.test_oof())
    console.log(oof.oof())

    你会看到true然后出错
    real_newbie
        25
    real_newbie  
    OP
       2011-04-29 12:04:09 +08:00
    我不明白这个所谓global scope是什么意思.

    如果要复用bar()似乎应该是这样子做:

    function bar() {}

    exports.bar = function() {
    return bar();
    }

    exports.rab = function() {
    return bar();
    }
    aligo
        26
    aligo  
       2011-04-29 12:06:17 +08:00
    总之我要说的是这里require进来的东西就是module,每个js都是自己的scope,只有通过exports.的东西才会被require返回,但是这个exports在次require之间也是互相独立的
    aligo
        27
    aligo  
       2011-04-29 12:13:13 +08:00
    然后再来玩个新的bar.js:
    var foo = 'b'
    var_foo = 'ar'

    Bar = function () {
    return foo + var_foo
    }


    然后这是新foo.js

    require.paths.unshift('.')

    require('bar')

    console.log(typeof bar == 'undefined')
    console.log(typeof var_bar == 'undefined')
    console.log(typeof Bar == 'function')

    console.log(Bar)
    console.log(Bar())
    console.log(bar)
    console.log(var_bar)

    结果是
    true
    true
    true
    [Function]
    bar
    到最后2个就出错了
    n2n3
        28
    n2n3  
       2011-04-29 12:25:47 +08:00
    real_newbie
        29
    real_newbie  
    OP
       2011-04-29 12:33:30 +08:00
    我总算是搞明白了.

    @kuno 的require实现其实没有错. 但是return module.exports时候, 那些在module里却没有被exports的东西还是在的. 所以return的还是一个Closure.

    @aligo的理解是正确的.
    real_newbie
        30
    real_newbie  
    OP
       2011-04-29 12:42:30 +08:00
    @n2n3, 果然还是源代码最高啊. module.js里的"Module._load"很清楚了.
    aligo
        31
    aligo  
       2011-04-29 15:06:03 +08:00
    恩,所以继续离题一下,如果要在前端里实现这样的效果,就需要像你上面提到的那个requirejs那样,在每个js中用一个define()包裹,像#9那样
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3101 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 13:46 · PVG 21:46 · LAX 05:46 · JFK 08:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.