关于seajs

虽然已经有很长时间没写JavaScript,但很多时候看到一些应用还是会带着好奇心去研究一下。之前是看腾讯的朋友网,它的webchat做的很不错(虽然ff下有bug,也有消息丢失的情况,但总体的设计和体验上还是很不错的),抓包大致看了看请求和部分代码。

它的聊天框使用div + contenteditable属性实现了一个简单的“富”文本编辑器,但好像仅支持表情(源码里有正则,对指定的img节点的src进行过滤)。我分别在chrome和ie下使用了它的聊天框,发现IE下在粘贴会先出现粘贴的内容,然后所粘贴的内容被全部转换为文本。

一般情况下,如果仅使用一个textarea来实现ubb这样的输入,插入的表情不能立即显示出来,这个体验其实不好,但如果支持富文本输入,则可能导致页面因为节点的问题而发生错位等等…所以要实现的功能就是,支持插入特定的表情和特定的节点,除此之外的所有的节点都将被替换/删除掉。

朋友网前台页面使用Seajs装载jQuery来进行开发的,大概查看了一下编写的代码,写的比较优雅。上面表情插入的聊天框只要监听它的onpaste(低版本的opera监听它的oninput)进行处理。

由用户选择的表情,将不会被替换掉。

下面有一张关于各浏览器字符输入与剪贴板的事件的支持图:

而关于Seajs,很早就看过,但从来没用过,诸如CommonJS、RequireJS这些东东,都没有太多精力去使用和研究过。不过在查看朋友网的部分JS代码后,对于seajs就很好奇,想看看源码写几个例子,权当学习一下JavaScript的模块开发方式。在08年团队开发页面时,前台JavaScript会采用类似这样的写法:xx.include(‘…/dom.js’);xx.include([‘a.js’, ‘b.js’, …]);也算是有了JavaScript模块化开发的雏形,但还不是很成熟。仔细查看了一下seajs的源码,确实写的挺赞的。

我在写例子之前是参考过这篇文章的:《JavaScript模块化开发库之seajs》

Seajs适用于分模块化开发的应用,比如类似QZone,点击“装扮空间”时需要动态加载CSS、JS,在资源加载完成后渲染装扮,监听事件、回调…使用seajs来开发的话会很方便,主体的逻辑结构也将十分清晰。

Seajs的世界里,一个文件就是一个模块,而如果模块过多,那文件同样也将比较多,那随之而来的JS请求也将变多。而如果你在一个页面就定义了很多模块,那么有几个问题要注意…

假设直接在html页面里定义了一个名为domx的模块

1: define('./domx', [], function(require, exports, module) {

       2:     var mod;

       3:  

       4:     return mod = {

       5:         'a' : function() {

       6:             alert('a');

       7:         },

       8:         'b' : function() {

       9:             alert('b');

      10:         }

      11:     };

      12: })

一个文件只能书写一个模块(可定义多个模块),书写多个模块时,最后一个会替换之前的定义方法<在加载资源时发生>。

1: define(function(require, exports, module) {

       2:     alert('start-1');

       3: });

如果html仅有上述代码,上面的alert(‘start-1’)并不会执行,这个可以从源码那里找到答案<详见define函数的定义>:

1: (function(util, data, fn) {

       2:  

       3:   /**

       4:    * Defines a module.

       5:    * @param {string=} id The module id.

       6:    * @param {Array.<string>|string=} deps The module dependencies.

       7:    * @param {function()|Object} factory The module factory function.

       8:    */

       9:   function define(id, deps, factory) {

      10:     var argsLen = arguments.length;

      11:  

      12:     // define(factory)

      13:     if (argsLen === 1) {

      14:       factory = id;

      15:       id = undefined;

      16:     }

      17:     // define(id || deps, factory)

      18:     else if (argsLen === 2) {

      19:       factory = deps;

      20:       deps = undefined;

      21:  

      22:       // define(deps, factory)

      23:       if (util.isArray(id)) {

      24:         deps = id;

      25:         id = undefined;

      26:       }

      27:     }

      28:  

      29:     // Parse dependencies

      30:     if (!util.isArray(deps) && util.isFunction(factory)) {

      31:       deps = parseDependencies(factory.toString());

      32:     }

      33:  

      34:     // Get url directly for specific modules.

      35:     if (id) {

      36:       var url = util.id2Uri(id);

      37:     }

      38:     // Try to derive url in IE6-9 for anonymous modules.

      39:     else if (document.attachEvent && !util.isOpera) {

      40:  

      41:       // Try to get the current script

      42:       var script = util.getCurrentScript();

      43:       if (script) {

      44:         url = util.unParseMap(util.getScriptAbsoluteSrc(script));

      45:       }

      46:  

      47:       if (!url) {

      48:         util.log('Failed to derive URL from interactive script for:',

      49:             factory.toString());

      50:  

      51:         // NOTE: If the id-deriving methods above is failed, then falls back

      52:         // to use onload event to get the url.

      53:       }

      54:     }

      55:  

      56:     var mod = new fn.Module(id, deps, factory);

      57:  

      58:     if (url) {

      59:       util.memoize(url, mod);

      60:       data.packageMods.push(mod);

      61:     }

      62:     else {

      63:       // Saves information for "memoizing" work in the onload event.

      64:       data.anonymousMod = mod;

      65:     }

      66:  

      67:   }

      68:  

      69:  

      70:   function parseDependencies(code) {

      71:     // Parse these `requires`:

      72:     //   var a = require('a');

      73:     //   someMethod(require('b'));

      74:     //   require('c');

      75:     //   ...

      76:     // Doesn't parse:

      77:     //   someInstance.require(...);

      78:     var pattern = /(?:^|[^.])\brequire\s*\(\s*(["'])([^"'\s\)]+)\1\s*\)/g;

      79:     var ret = [], match;

      80:  

      81:     code = removeComments(code);

      82:     while ((match = pattern.exec(code))) {

      83:       if (match[2]) {

      84:         ret.push(match[2]);

      85:       }

      86:     }

      87:  

      88:     return util.unique(ret);

      89:   }

      90:  

      91:  

      92:   // http://lifesinger.github.com/lab/2011/remove-comments-safely/

      93:   function removeComments(code) {

      94:     return code

      95:         .replace(/(?:^|\n|\r)\s*\/\*[\s\S]*?\*\/\s*(?:\r|\n|$)/g, '\n')

      96:         .replace(/(?:^|\n|\r)\s*\/\/.*(?:\r|\n|$)/g, '\n');

      97:   }

      98:  

      99:  

     100:   fn.define = define;

     101:  

     102: })(seajs._util, seajs._data, seajs._fn);

当书写模块define(fn);时,define的实参长度为1,即argsLen===1,而id等于undefined时,url的值也将为undefined,所以此时的这个匿名函数将会被绑定到seajs._data.anonymousMod ==> 一个fn.Module的实例({id:undefined, dependencies:[], factory:xxx});它是不会被执行的…

假设现在加载一个dom模块==>seajs.use(‘dom’),它将会请求一个dom.js,如果dom.js未定义名为dom的模块或是未使用define(fn)时,当文件被加载后,调用seajs在内部调用fetch方法时,之前那个mod(id为undefined)的id将会被替换为http://yourdomain.com/dom.js。代码function(require, exports, module) { alert('start-1');}将绑定到dom模块上,实际开发中defind(fn)这样的代码应当放在单独的JS文件中,让它作为模块的定义方法。

Seajs的简单使用示例:

1、定义模块<在JS中定义>

1: define('./dom', [], function(require, exports, module) {

       2:     var mod;

       3:  

       4:     return mod = {

       5:         'a' : function() {

       6:             alert('a');

       7:         },

       8:         'b' : function() {

       9:             alert('b');

      10:         }

      11:     };

      12: })

或者

1:  

       2: define(function(require, exports, module) {//or define([], function(require, exports, module) {

       3:     

       4:     exports.a = function() {

       5:         alert('dom-a');

       6:     };

       7:  

       8:     exports.b = function() {

       9:         alert('dom-b');

      10:     };

      11:  

      12: });

2、使用模块

1: seajs.use('./dom', function(dom) {

       2:     dom.a();

       3: });

注:第一种方式,其它在调用的时候也会转换为第二种方式。多个模块调用如下

1: define('./domx', [], function(require, exports, module) {

       2:     var mod;

       3:  

       4:     return mod = {

       5:         'a' : function() {

       6:             alert('a');

       7:         },

       8:         'b' : function() {

       9:             alert('b');

      10:         }

      11:     };

      12: });

      13:  

      14: define('./events', [], function(require, exports, module) {

      15:     var mod;

      16:  

      17:     return mod = {

      18:         'c' : function() {

      19:             alert('d');

      20:         },

      21:         'd' : function() {

      22:             alert('d');

      23:         }

      24:     };

      25: });

      26:  

      27: seajs.use(['./domx', './events'], function(domx, events) {

      28:     domx.a();

      29:     events.c();

      30: });

回调函数里的参数与依赖的模块是一一对应的关系

Seajs内部实现了依赖关系,让开发者可以将更多的精力放到应用的业务中去。假设test.html页面引用了dom.js,而dom.js 依赖event.js 而event.js依赖cache.js,那么加载顺序将是

dom.js –> event.js –> cache.js

而各模块的执行顺序是cache.js –> event.js –> dom.js

在dom下定义如下两种写法,差异较大。

方式一:

1: define('./dom', function(require, exports, module) {

       2:     

       3:     var cache = require('event');

       4:  

       5:     exports.a = function() {

       6:         alert('dom-a');

       7:     };

       8:  

       9:     exports.b = function() {

      10:         alert('dom-b');

      11:     };

      12:  

      13:     alert('dom');

      14:  

      15: });

方式二:

1: define('./dom', [], function(require, exports, module) {

       2:     

       3:     var cache = require('event');

       4:  

       5:     exports.a = function() {

       6:         alert('dom-a');

       7:     };

       8:  

       9:     exports.b = function() {

      10:         alert('dom-b');

      11:     };

      12:  

      13:     alert('dom');

      14:  

      15: });

使用方式二,event.js并不会被请求,而使用方式一,seajs会调用parseDependencies方法,解析出模块的依赖关系。

1: function parseDependencies(code) {

       2:   // Parse these `requires`:

       3:   //   var a = require('a');

       4:   //   someMethod(require('b'));

       5:   //   require('c');

       6:   //   ...

       7:   // Doesn't parse:

       8:   //   someInstance.require(...);

       9:   var pattern = /(?:^|[^.])\brequire\s*\(\s*(["'])([^"'\s\)]+)\1\s*\)/g;

      10:   var ret = [], match;

      11:  

      12:   code = removeComments(code);

      13:   while ((match = pattern.exec(code))) {

      14:     if (match[2]) {

      15:       ret.push(match[2]);

      16:     }

      17:   }

      18:  

      19:   return util.unique(ret);

      20: }

参考链接:

使用Seajs实现模块化JavaScript开发>>

JavaScript模块化开发库之Seajs>>

Sea.js手册与文档>>

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微信公众号:Java团长

Java后端程序员1年工作经验总结

  毕业已经一年有余,这一年里特别感谢技术管理人员的器重,以及同事的帮忙,学到了不少东西。这一年里走过一些弯路,也碰到一些难题,也受到过做为一名开发却经常为系统...

32120
来自专栏阮一峰的网络日志

处理Apache日志的Bash脚本

去年一年,我写了将近100篇网络日志。 现在这一年结束了,我要统计"访问量排名",看看哪些文章最受欢迎。(隆重预告:本文结尾处将揭晓前5名。) ? 以往,我用的...

37550
来自专栏互联网技术栈

Dubbo作者聊 设计原则

转于自己在公司的Blog: http://pt.alibaba-inc.com/wp/experience_1301/code-detail.html

34240
来自专栏Golang语言社区

聊一聊goroutine stack

推送在外卖订餐中扮演着重要的角色,为商家实时接单、骑手实时派单提供基础的数据通道。早期推送是由第三方服务商提供的, 随着业务复杂度的提升、订单量和用户数的持续增...

67250
来自专栏Java 源码分析

Netty 入门

1. 粘包问题 一 .长连接与短连接: 1.长连接:Client方与Server方先建立通讯连接,连接建立后不断开, 然后再进行报文发送和接收。长连接在 net...

31750
来自专栏Java帮帮-微信公众号-技术文章全总结

大文件拆分方案的Java实践【面试+工作】

大文件拆分问题涉及到io处理、并发编程、生产者/消费者模式的理解,是一个很好的综合应用场景,为此,花点时间做一些实践,对相关的知识做一次梳理和集成,总结一些共性...

39240
来自专栏技巅

Jvm(jdk8)源码分析1-java命令启动流程详解

28430
来自专栏腾讯移动品质中心TMQ的专栏

测试人员代码分析5步法

前言 随着测试行业的发展,测试攻城狮也越来越关注产品的源码了。在很多团队中,读懂代码的实现是做好测试的重要一步,但是读懂代码这件事非常依赖代码能力,没有什么捷径...

306100
来自专栏芋道源码1024

从客户端的角度设计后端的接口

2.请求Path,http://www.online.com/api/ [path]

21430
来自专栏Golang语言社区

[go语言]吐槽:怎么样实现支持并发访问的数据集合更好?

在go语言里,提倡用信道通讯的方式来替代显式的同步机制。但是我发现有的时候用信道通讯方式实现的似乎也不是很好(暂不考虑效率问题)。 假设有一个帐号的集合,需要在...

43770

扫码关注云+社区

领取腾讯云代金券