雖然已經有很長時間沒寫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: }
參考鏈接:

