虽然已经有很长时间没写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: }
参考链接: