語法和語義
可選的分號
標識符(Identifier)
基礎數據類型和字面量(Literal)
變量的作用域
關於 ==, !=, ===, !==
關於 this, new, apply, call
關於 arguments
成員訪問, prototype鏈, 繼承
變量訪問, call object, Closure
總結
慣用法和技巧
編碼規范和JS書寫模板
頁面功能的普通功能
模塊需要提供給其他模塊使用(單例)
模塊需要提供給其他模塊使用(多實例)
為繼承而設計的類
繼承其他類
BOM和DOM簡介
BOM
DOM
jQuery學習
設計理念
Utility
選擇 與 CSS3選擇器
構造jQuery對象
jquery對象結構
基本操作
事件
操作
數據
檢測
前端基礎庫 fdevlib
style目錄結構簡要說明
fdev-v4現有組件
JS學習
本文目的在於為Java或有語言基礎的同學參於前端開發提供指導
語法和語義
可選的分號
語句如果它們在單獨一行,那麼結尾的分號可以省略。
註:為瞭一致性和避免錯誤(如壓縮或merge帶來的錯誤),我們要求所有語句都要加分號(塊語句和函數申明語句後不需要分號)
var a = 1;
a += 1;
var callback = function() {
return 'hi';
};
// 函數申明語句
function other() {
}
for (var i = 0; i < 10; i++) {
}
if (a === 123) {
}
[註] 我們不能寫出這樣的句子
function abc() {
return // 返回123
123;
}
abc(); // -> undefined
標識符(Identifier)
和Java相比,Javascript允許在標識符中使用$,所以很多庫都用它來定義特殊作用的全局對象
像jQuery, Prototype, Mootools等框架使用$操作頁面節點, 它僅是一個普通的函數
// 在jQuery中
$('#myp a').css('color', '#f00'); // 設置鏈接顏色
// 在舊版本的Mootools中
if ($type(abc) === 'string') { // 新版本使用typeOf代替
}
// 在中文站JS基礎庫FDEV3中
var elm = $('myp'); // 相當於document.getElementById('myp');
所以$可以用作有"特殊"含義的變量名
// 在有些同學的代碼中, 用於在內部函數中訪問外圍對象
var Test = {
hi: function() {
var $this = this; // 我見過的常用的名稱還有 that, self, me 等,
// 使用self有一個隱患,就是忘記瞭定義self時,
// 會造成難以排錯,因為有一個全局的self會產生幹憂
$.ajax(url, {
success: function(o) {
o.success && $this.render(o.data)
}
});
},
render: function() {
}
};
基礎數據類型和字面量(Literal)
Number
123, 0xf000, 0377(8進制)
3.14, .222, 1.2e12, 1.4e-12
可以使用Math對象進行常見的科學計算,見 asp”>http://w3schools.com/js/js_obj_math.asp
此外Javascript也定義瞭一些特殊的Number用於合適的需要, 如NaN, Number.MAX_VALUE, Number.MIN_VALUE,
參考《Javascript權威指頁》3.1.6
註:當使用parseInt將字符串轉成整型時, 請帶上進制, 否則如果有前綴是0,會被當成8進制處理可能不符合預期需求
var num = parseInt(inputText, 10); // 如在文本框輸入頁碼等
String
可以使用單引號或雙引號兩種形式
var str1 = '我是一個字符串,包含著雙引號"'; // 單引號中的雙引號可以不用轉義,當然使用轉義也木有問題
var str2 = "我是另一個字符串, 包含著單引號'";
var template =
'<p>\
<dl>\
<dt>示例1</dt>\
<dd></dd>\
<dt></dt>\
<dd></dd>\
</dl>\
</p>';
String和Number的轉化技巧
parseInt('123', 10); // 註: 請加上第二個參數
'123' – 0 // 註: 不能使用+
456 + ''
123456 + '元' // -> '123456元';
[String對象的方法請參考]
http://w3schools.com/jsref/jsref_obj_string.asp
Boolean
false, null, undefined, 0, NaN, '' 在邏輯判斷中都被當成 false 處理
var keyword = $('#keyword').val();
if (!$.trim(keyword)) {
// 用戶輸入為空
}
var num = $('#num').val();
num = parseInt(num, 10);
if (!num) { // NaN
// 用戶輸入非法數字
}
Function
// 申明式
function a() {
}
console.debug(a.name) // -> 'a'
// 匿名函數
var b = function() {
};
console.debug(b.name) // -> ''
申明式和語句式區別
fun(123); // 在這裡就可以使用
function fun(value) {
console.debug(value);
}
b(123); // b is undefined;
var b = function() {
};
Object Literal
var album = {
name: 'pipixiuxiu', // 註,這裡的name不是變量, 隻是省略瞭單引號
‘count’: 100,
'private': true, // 當屬性名稱是保留字時,必須使用這種方式
234: '較少用',
12.34: '更少用'
};
album.name;
album['count'];
album[234];
jQuery對象是怎麼造就的?
var o = {
0: elm0,
1: elm1,
2: elm2,
3: elm3,
length: 4,
splice: function() {} // firebug看到這個會以為它是個數組,所以顯示方式和數組一樣
…
}
註: 使用Literal語法構造對象,而不是new Object()
var o = {}; // 而不是 new Object();
Array Literal
var ary = [];
ary[0] = 'one';
var list = [99, 'some string', function() {}];
[Array的方法請參考]
http://www.w3schools.com/jsref/jsref_obj_array.asp
註: **總是使用Literal語法構造數組, 而不是使用 new Array() **
var array = []; // 而不是new Array();
拼接字符串, 在Java中使用StringBuilder或StringBuffer拼接字符串,以減少臨時對象的產生提高GC效率。
在JS中使用數據組達到這一目的。
var html = [];
elms.each(function() {
html.push('some string …');
});
html = html.join('');
[註] 在Object/Array最後一個元素後面不能加逗號,盡管這在標準上允許,但是在IE6 7 瀏覽器下會報語法錯誤
所以,如果發現在FF下工作正常,在IE下工作不正常,請檢查是否object或array最後多瞭個逗號
var o = {
name: 'pipi',
desc: 'er er er',
// price: 12 // 在調試中把這句註釋瞭,造成上面一句最後多瞭個逗號
};
所以可能在某些代碼中發現以下寫法以避免上述錯誤
var o = {
name: 'pipi',
desc: 'er er er',
// price: 12,
_: 0 // 多瞭一個這樣的元素
};
不過我不推薦這樣的形式,註意點就好,因為上述形式可能會帶來其他隱患,並且可讀性也不好。
可以借助工具來掃描這類錯誤, 如JSLint: http://www.jslint.com/
變量的作用域
變量在JS中是函數作用域的,而不是塊作用域(C, Java, C#等是塊作用域)
function hello() {
var a = 123;
for (var i = 0; i < 10; i++) {
var a = 234;
//…
}
console.debug(a); // -> 234
}
在函數作用域內定義的var都相當於在一開始定義一樣
即一個函數執行時,引擎分兩步操作
1. 處理所有var語句(和函數申明語句), 準備好執行環境, 相當於這些var和function申明語句寫在函數的最前面
2. 執行函數內的語句
所以上述代碼相當於這樣:
function hello() {
var a,
i;
a = 123;
for (i = 0; i < 10; i++) {
a = 234;
//…
}
console.debug(a); // -> 234
}
這會讓javascript虛擬機實現closure更方便,讓內存模型更簡潔
function hello() {
alert(a); // alert(undefined);
if (false) {
var a;
}
}
等效於
function hello() {
var a;
alert(a);
if (false) {
}
}
function hello() {
alert(a); // ReferenceError: a is not defined
}
[最佳實踐]
一個函數內最好隻有一個var, 並且盡可能靠前(最好是在函數一開始) 但這與塊作用域的實踐, 變量最好在用到的時候定義不一致
所以 函數盡可能得短小, 以滿足前面兩個條件
關於 ==, !=, ===, !==
一句話: 總是使用 === 和 !==進行比較
因為 == 和 != 在比較時如有必要,會進行類型轉換
null == undefined // -> true
'0' == false // -> true
'1' == true // -> true
null == false // -> false
undefined == false // -> false
[具體的比較規則請參考] ECMA262 11.9.3
如果不想看, 可能參考一條規則: null == undefined, 字符串和boolean都轉成數字進行比較
關於 this, new, apply, call
當調用一個方法(函數時),函數內部的this指向一個對象, 在規范中被稱為: ThisBinding, 我喜歡叫它BindObject
一般情況下, this指向調用它的那個對象或者 GlobalObject, 在瀏覽器中為window
var A = {
hello: function() {
console.debug(this);
}
};
A.hello(); // -> A
var b = A.hello;
b(); //-> Global Object, 在瀏覽器中即是window
所以函數中的this指向和如何調用它有關系,和在哪裡定義這個函數關系不大
我們可以使用call或apply方法參數化this
var A = {
name: 'A',
function hello() {
console.debug(this.anme);
}
};
A.hello(); // this === A
var B = { name: 'B' };
A.hello.call(B); // this === B
至於call和apply的區別, 隻是方法支持參數的區別, 功能一樣
fun.apply(o, [arg1, arg2, arg3]);
function.call(o, arg1, arg2, arg3);
new 操作符會構造一個對象, 並且讓this指向它
function A() {
console.debug(this);
}
A(); // -> window
new A(); // -> object, object !== window
function People(name) {
this.name = name
}
var p1 = new People('p1');
var p2 = new People('p2');
assert(p1 !== p2);
assert(p1.name === 'p1');
這裡我們似乎看到瞭js面向對象的影子
關於new的返回值
如果function返回object, 則new 返回那個object, 否則返回引擎構造的那個object
function A() {
}
new A(); //-> object
function B() {
var o = {
};
return o;
}
new B(); //-> o
function C() {
return 1; // typeof 1 === 'number', 1 is not an object
}
new C(); // -> object, not 1
關於 arguments
函數內部除this外,還有一個對象就是arguments, 它是一個類數組,保存著函數的調用參數
function a() {
var args = arguments,
a = args[0],
b = args[3],
len = args.length;
}
我們可以對arguments進行迭代, 但如果我們要使用數組的方法操作它,就需要把它轉化成數組
var ary = $.makeArray(arguments); // $ is jQuery
var ary = [].slice.call(arguments, 0); // 如果沒有庫支持
成員訪問, prototype鏈, 繼承
var A = {
name: 'A',
hello: function() {
console.debug('hello ' + this.name);
}
};
A.name; // 'A'
A.hello(); // 'hello A'
var CA = function() {
};
CA.prototype = A;
var o = new CA();
// 讀取時, 如果必要, 則訪問 prototype
o.name; // 'A'
o.hello(); // 'hello A'
// 設置
o.name = 'o';
o.name; // 'o' // 找到瞭, 就不需要訪問 prototype瞭
o.hello(); // 'hello o'
// A未曾改變
A.name // 'A'
A.hello // 'hello A'
讀取一個對象成員時, 會走prototype鏈, 設置一個對象成員時, 對prototype無影響
比如像這樣:
var A = function() {};
A.prototype = { propPA: 'PA' };
var a = new A();
a.propA = 'A';
var B = function() {};
B.prototype = a;
var b = new B();
b.propB = 'B';
var C = function() {};
C.prototype = b;
var c = new C();
c.propC = 'C';
console.debug(c.propC); // C
console.debug(c.propB); // B
console.debug(c.propA); // A
console.debug(c.propPA); // PA
console.debug(c.toString()); // [object Object]
所以我們可以用prototype對象來實現類, 以及用prototype鏈來實現繼承
我們使用如下模板書寫一個簡單的類
var A = function() {
this.init.apply(this, arguments); // 代理給prototype.init
};
A.prototype = {
/**
* 構造函數
*/
init: function(name) {
this.name = name; // 對象屬性
},
/**
* 實例方法
*/
hello: function() {
console.debug('hello ' + this.name);
}
};
JS是動態的語言, 可以很方面地實現對象的功能擴展, 所以比較少用繼承
如果使用繼承, 我會使用以下方法來實現, 因為代碼較原型鏈方式來得更簡單
var B = function() {
this.init.apply(this, arguments);
};
B.prototype = $.extendIf({
init: function(name, age) {
A.prototype.init.call(this, name); // super(name)
this.age = age;
},
say: function() {
}
}, A.prototype);
變量訪問, call object, Closure
在js中,變量是函數作用域的,這一點上面已講過
這裡的訪問主要考慮嵌套函數的調用, 即一個非常重要的特性Closure
var welcome = 'welcome ~~~';
function hello(name) {
var prefix = 'hello';
alert(welcome);
function say(postfix) {
var my = 'string my';
alert(prefix + name);
alert(my)
alert(postfix)
}
return say;
}
var say = hello('alibaba');
say('good bye');
每次調用一個函數時, 執行函數代碼前, Js執行引擎要做些準備工作, 其中會創建一個稱為 Call Object的對象
而調用方法的形式參數, 局部變量, arguments等就成為這個對象的屬性.
所以訪問形式參數, 局部變量, arguments就相當於訪問這個對象的屬性
總結
JS的語言本身非常精簡, 整個ECMA262規范隻有200頁, 其中有一半用來介紹瞭核心庫的內容,
語言語法,語義,引擎的實現全部加起來也才100頁. 所以真的是非常地精巧, 但描述能力卻很強大.
引擎核心內容總結如下:
this會動態地指向BindObject, 所以又稱為動態作用域. 我們對this屬性的訪問就是訪問這個對象的屬性.
變量的訪問,其實是訪問的是Call Object的屬性, CallObject鏈根據詞法結構就已確定, 所以又稱為靜態作用域,或“詞法作用域”
由於這個鏈在js中是不能訪問的, 所以又稱"暗鏈"
對象屬性(成員)的訪問,遵循的是prototype鏈, 由於這個鏈在js中是可以直接訪問的,所以又稱為"明鏈"
[如果對上述內容詳細內容感興趣可以參看]
* 《Javascript權威指》: 4.6 4.7
* ECMA 262 10.3 ~ 10.6
* http://jibbering.com/faq/notes/closures/
* 我的文章: http://b2b-doc.alibaba-inc.com/pages/viewpage.action?pageId=47748204
慣用法和技巧
// 檢測空串
if (!$.trim(str)) {
}
// 字符串/數字類型轉換
var str = '100';
var num = str – 0;
var str2 = num + '';
// 空檢則
if (!value) { // undefined, null, false, 0, '', NaN 等為 '空'
}
// 邏輯或 ||
if (!a) {
a = 100;
}
a = a || 100; // 註意 不能寫成 a ||= 100, 雖然ruby支持
if (!this.elm) {
this.elm = $('#myp');
}
this.elm = this.elm || $('#myp');
var pageSize = 10;
if (data && data.pageSize) {
pageSize = data.pageSize;
}
pageSize = (data || {}).pageSize || 10;
// 邏輯與&&
function(success) {
if (success) {
success('abc');
}
success && success('abc');
}
註意: 不能寫成 options.delay && this.delay = true; 賦值運算附優先級低
可以是 options.delay && (this.delay = true);
// 默認參數
function hello(options) {
options = $.extend({
time: 1000,
color: '#f00',
'font-size': '12px'
}, options);
…
}
// 字符串拼接
var html = [];
for (…) {
html.push('…');
}
html = html.join('');
// 字符串模板
var template =
'<p>\
<dl>\
<dt>示例1</dt>\
<dd></dd>\
<dt></dt>\
<dd></dd>\
</dl>\
</p>';
編碼規范和JS書寫模板
JS是一門動態靈活的語言, 不同的同學可能寫出風格完全不同的代碼.
所以參考或使用此模板目的在於提高js代碼一致性和可讀性.
首先需要參考: 前端CodeReview列表
頁面功能的普通功能
如 Diy皮膚功能, Js文件名為 diy-skins.js
我們把文件中模塊代碼包含在一個自執行匿名函數中, 以避免全局名字空間沖突
原則上一個文件隻包含一個模塊(一般是一個主類)
因為我們的merge腳本不夠強, 為瞭按需加載和減少請求數, 有可能會把一個組件所有代碼放在一個文件中
但是也需要把每個模塊放在各自的自執行匿名函數中
/**
* 旺鋪Diy後臺皮膚選擇
* @author qijun.weiqj
*/
(function($, WP) {
[1]
var Util = WP.Util,
UI = WP.UI,
PageSwitcher = WP.widget.PageSwitcher
…
[2]
var DiySkins = {
[3]
init: function() {
},
initCatsPanel: function() {
},
initSkinPanel: function() {
}
};
[4]
WP.PageContext.register(…)
// 相當於
// 頁面載入後就執行
$(function() {
Diy.Skin.init();
})
})(jQuery, Platform.winport);
[1]. 這個區域相當於java的import區, 根據需要可以alias一些用到的類
當然現在我們還需要在merge文件中包含需要的庫文件.
後續我們會期望引入腳本來自動merge導入的類
像這樣:
var Util = require('widget/util'),
UI = require('widget/ui'),
ModBox = require('diy/mod-box');
[2]. 主模塊(類/對象)名要求和js文件名盡量保持一致
[3]. 初始化方法或構造函數名稱為init
[4]. 最後當頁面domready時應該執行初始化方法, 所以調用jquery.ready方法 (在旺鋪中進行一些封裝,所以調用PageContext.register進入註冊)
模塊需要提供給其他模塊使用(單例)
(function($, WP) {
var UI = {
[1]
/**
* 縮放圖片
* @param {jquery} selector …
* @param {int} size …
*/
resizeImage: function(selector, size) {
},
[2]
_resize: function() {
}
/**
* IE6 position fixed
*/
positionFixed: function(selector) {
}
};
[3]
WP.widget.UI = UI;
})(jQuery, Platform.winport);
相當於方法, 可由其他模塊調用
下劃線帶頭的方法, 不可由其他模塊調用
掛接到相應的名字空間下, 以便由外界可以使用
模塊需要提供給其他模塊使用(多實例)
(function($, WP) {
var Tabs = function() {
this.init.apply(this, arguments);
};
Tabs.prototype = {
init: function(tabs, bodies) {
},
_create: function() {
}
};
WP.widget.Tabs = Tabs;
})(jQuery, Platform.winport);
為繼承而設計的類
(function($, WP) {
var Dialog = function() {
this.init.apply(this, arguments);
};
Dialog.prototype = {
[1]
init: function(options) {
..
render(this);
},
[2]
close: function() {
},
[3]
_createButtons: function() {
}
};
[4]
function render(self) {
self._createButtons();
}
WP.widget.Dialog = Dialog;
})(jQuery, Platform.winport);
[1]. 構造函數
[2]. public方法
[3]. protected方法, 可由子類重寫
[4]. private方法
繼承其他類
(function($, WP) {
var Dialog = WP.widget.Dialog;
var FormDialog = function() {
this.init.apply(this, arguments);
};
FormDialog.prototype = $.extendIf({
init: function(options) {
[1]
Dialog.prototype.init.call(this, options);
},
[2]
_createButtons: function() {
Dialog.prototype._createButtons.call(this, …);
…
},
[3]
__createForm: function() {
},
[4]
getData: function() {
}
}, Dialog.prototype);
[5]
function createForm2(self) {
}
WP.diy.FormDialog = FormDialog;
})(jQuery, Platform.winport);
[1]. 調用父類構造函數
[2]. 重寫父類方法, 調用父類方法
[3]. private方法
[4]. public方法
[5]. private(和3選擇一種形式即可)
關於文檔註釋可參考: Google Javascript Gude
BOM和DOM簡介
BOM
BOM就是瀏覽器提供給我們可以操作瀏覽器的API
我們常用的有:
打開新窗口, 查看當前URL信息, 檢測瀏覽器類型和版本號, 返回前一頁, 查詢屏幕分辨率等
[參考]
http://w3schools.com/jsref/
《Javascript高級程序設計》第8章,第9章
DOM
DOM提供用於訪問HTML/XML文檔的API
瀏覽器之間有差異性,所以一般使用js類庫來間接訪問DOM API
有時候頁面較簡單,或者沒有JS庫引入, 那可以直接使用
一般文檔節點操作: http://w3schools.com/jsref/
樣式/樣式表處理(如旺鋪DIY): 《Javascript高級程序設計》10.3.2, 11.2
jQuery學習
設計理念
// jQuery是一個function, 返回一個對象(稱為jQuery對象)
var jQuery = function() {
return new jQuery.fn.init(…);
};
// jQuery對象的實例方法由 jQuery.fn決定
jQuery.prototype = jQuery.fn = { … };
jQuery.fn.init.prototype = jQuery.fn;
// 采用這種方法擴展jQuery
jQuery.fn.bind = function() { … };
// 采用這種方法擴展靜態方法
jQuery.extend = function() {…};
jQuery.each = function() {…};
所以jQuery包含兩種操作:
一種是和DOM結點無關, 直接調用靜態方法即可, 如$.ajax, $.each等
一種是和DOM結點相關, 需要首先構造jQuery對象, 再使用它的實例方法, 如事件綁定jQuery#bind, 樣式設置jQuery#css等
Utility
<script src="http://style.china.alibaba.com/js/lib/fdev-v4/core/fdev-min.js"></script>
(function($) {
// 這裡$就是jQuery
})(jQuery);
$.ready 初始化
dom ready後,表示可以使用js操作頁面dom節點, 它比onload要早, 因為onload要等圖片全部加載完
$.ready(function() {
});
// 可以簡寫
$(function() {
});
$.each 迭代
var array = ['one', 'two', 'three'];
$.each(array, function(index, item) {
// 當item類型為object時, 可使用this訪問item, 否則請使用參數方式訪問item
});
var o = { name: …, value: … };
$.each(o, function(key, value) {
});
$.extend 擴展
// 默認參數
function hello(options) {
options = $.extend({
title: '測試',
delay: 1000
}, options);
…
}
// 擴展功能
var A = {
a: function() {
}
};
$.extend(A, {
b: function() {
}
});
// clone
var o = {…};
var other = $.extend({}, o);
我們可以擴展jQuery功能, 但在應用中我們不允許這麼做, 維護fdevlib的同學可以根據需要擴展庫
$.extend({
add: function() {
},
use: function() {
}
});
$.fn.extend({
tabs: function() {
}
});
$.extendIf (由fdevlib實現), 類似於extend, 但如果存在相同key的項則不添加, 我常常使用它來實現"繼承"
var Base = {
a: function() {
},
b: function() {
},
c: function() {
}
};
var A = $.extendIf({
b: function() {
},
d: function() {
}
}, Base);
var Base = function() {
this.init.apply(this, arguments);
};
Base.prototype = {
};
var A = function() {
this.init.apply(this, arguments);
};
A.prototype = $.extendIf({
}, Base.prototype);
ajax
跨域jsonp請求
$.ajax(url, {
dateType: 'jsonp',
success: function(o) {
o.success && self.render(o.data);
}
});
瀏覽器會產生一次url類似 url?callback=jQuery23522552_24342344的調用
期望瀏覽器返回這樣的文本
jQuery23522552_24342344({"success": true, data: …})
此段代碼將會作為js代碼在客戶端執行,即調用一個由jquery隨機生成的方法, 這個方法會調用我們的success方法,
於是我們就得到瞭數據
跨域調用要求使用jsonp方式,從避免造成全局名字空間污染和覆蓋, 也方便瀏覽器對數據進行垃圾回收
返回的數據格式要求如下:
{ "success": true | false, data: … }
動態載入script文件
$.ajax(url, {
dateType: 'script',
success: function() {
// 在這裡script文件就載入完畢瞭
}
});
註, 由於IE實現上的原因,造成造成script文件返回302也會進入success邏輯
所以如果如果采用古老的get script方式來跨域請求數據的話, 需要在success裡進行判斷.
$.ajax(url, {
dateType: 'script',
success: function() {
if (window.myData) {
…
}
}
});
// 以上服務器反回這樣的串串: var myData = …
當然我們也可以使用這個方法發起xhr請求
$.ajax(url, {
type: 'post',
data: { … }
success: function(html) {
p.html(html);
}
});
註: 在IE下,xhr請求, 服務器如果返回302跳轉(如另一頁面退出後,旺鋪DIY後臺發起xhr post請求,服務端會返回302), 將會進入success邏輯
默認情況下, 如果你ajax請求(包括xhr, jsonp, getScript), 如果有參數data, 並且類型為object, 則jQuery使用
$.param來編碼你的數據, 內部會使用 encodeURIComponent, 這將導致中文編碼成utf8.
如果後端木有按要求解碼,這可能會產生問題. 有兩個解決方法:
1.前端不編碼
自己使用$.paramSpecial– 這個方法由fdev-v4提供, 用於object -> string, 並且不編碼, 隻轉義幾個uri中不允許的字符
2. 要求後端解碼(可能需要加參數input_charset參數), 這一條優先采用
關於ajax或編輯相關的jquery api有:
$.param, $.paramSpecial, $.serialize, $.serializeArray
額外閱讀:
關於跨域請求數據,除瞭使用getscript jsonp還可以使用window.name機制, work平臺就使用瞭這個機制
具體請參考: http://www.planabc.net/2008/09/01/window_name_transport/
關於script執行和瀏覽器渲染阻塞問題,請參看
《高性能網站建設進階指南》第4章或《高性能Javascript》第一章
動態加載 $.add, $.use
這兩個方法由fdev-v4提供, 使得我們可以按需加載組件
動態加載的方式使用我們的組件
$.use('web-alitalk', function() {
FE.util.alitalk($('a[data-alitalk]'));
});
在我們的應用中使用這種機制
// 在應用配置文件中註冊
$.add('wp-dialog', {
js: ['http://….dialog.js']
});
// 在需要的地方使用
$.use('wp-dialog', function() {
new WP.widget.Dialog({
…
});
});
類型檢測
根據我的經驗,最有用的隻有兩個
$.isArray - 判斷是否為數組
$.isPlainObject - 判斷是否為"簡單"對象
if (typeof a === 'string') {
}
if (typeof a === 'function') {
}
if ($.isArray(a)) {
}
$.trim
如果引入瞭fdev-min.js, 則可以使用String#trimg() 或 $.trim()
如果僅僅使用jQuery, 則可以使用$.trim(), 因為有些瀏覽器(例IE6) 沒有實現trim方法
$.namespace
此方法由fdev-v4提供
namespace用於方便創建一個多層嵌套的對象結構用於代表名字空間(類似於java的package)
在應用中,我們不允許污染全局名字空間,一個應用往往隻分配到一個名字空間
比如旺鋪是 Platform.winport, offer發佈是在 Platform.postoffer
那我們旺鋪中的其他類都在這個名字空間下, 初始化時會分配瞭如下幾個名字空間
前後臺都需要,在global/base.js中
jQuery.namespace(
'Platform.winport',
'Platform.winport.widget', // 業務無關組件
'Platform.winport.unit' // TODO 後續去掉mod.unit名字空間
'Platform.winport.mod', // 板塊
);
後臺, 在page/diy/diy.js中
jQuery.namespace(
'Platform.winport.diy',
'Platform.winport.diy.form' // 板塊編輯
);
註: 上述為何有的文件在global中,有的文件在 page/diy中, 請參看 style目錄結構規劃,
直接看文檔 http://fd.aliued.cn/doc/page/regulations/dir-structure
$.proxy
var Page {
init: function(config) {
var self = this;
this.config = config;
$.use('ui-dialog', function() {
self.initDialog();
});
},
initDialog: function() {
// assert(this === Page);
node.dialog({
center: this.config.center
});
}
};
// 使用proxy
var Page = {
init: function() {
$.use('ui-dialog', $.proxy(this, 'initDialog'));
},
initDialog: function() {
}
};
下面這樣寫達不到目的
var Page = {
init: function(config) {
this.config = this.config;
$.use('ui-dialog', this.initDialog);
},
initDialog: functoin() {
assert(this !== Page);
this.config // undefined
}
};
所以proxy用於方便創建一個代理function, 讓被代理的function具有指定的context
1.6版本的proxy還可以包裝額外的參數,但文檔還沒有更新,具體可參看源碼 1.6.2 第804行
提外話:
一個看源碼技巧: 在頁面中引入jquery非壓縮版.
在firebug script tab的watch中輸入jQuery, 展開對象找到相應的方法單擊即可看到相應的源碼
var Page = {
init: function() {
var arg1 = 'arg1',
arg2 = 'arg2';
$.ajax(url, {
dateType: 'jsonp',
success: $.proxy(this, 'ajaxSuccess', arg1, arg2);
});
},
ajaxSuccess: function(arg1, arg2, ret) {
assert(this === Page);
assert(arg1 === 'arg1');
assert(arg2 === 'arg2');
if (ret.success) {
…
}
}
};
為瞭提高JS代碼可讀性, 我曾做過分享, 其中有一條是:
一個功能性方法,代碼行數不超過40行(正常情況下會在一屏之內)
如果要達到上述要求,方法裡面將不可能包含很深的嵌套, 即代碼嵌套層次不超過3
如:
var Page = {
initPartA: function() {
var self = this;
// level 1
$.ajax(url, {
dateType: 'jsonp',
// level 2
success: function(ret) {
// level 3
ret.success && self.render(ret.data);
};
});
},
render: function(data) {
}
};
其他常用方法
$.map, $.makeArray
function test() {
var args = $.makeArray(arguments);
}
// 參數可以是數組或非數組
function test(args) {
args = $.makeArray(args);
}
$.map 相當於 CollectionUtils.transform
var links = $('a'),
urls = null;
urls = $.map(links, function(index, link) {
return $(link).attr('href');
});
選擇 與 CSS3選擇器
認識選擇器
簡單選擇器(simple selecotr)
$('*'); // universal selector 星號選擇器 選擇所有節點
$('p'); // type selector/element selector 類型選擇器(我習慣叫它tag選擇器)
$('#header'); // id selector id選擇器
$('.input-text'); // class selector class選擇器
$(':input'); // pseudo selector 偽類選擇器 選擇所有表單輸入域
// a:hover, a:linked, a:visited 兩個冒號帶頭的就是 偽類選擇器
$('li:first') // 選擇第一個li節點
$('[name="alibaba"]') // attribute selector 屬性選擇器 選擇所有name屬性=alibaba的節點
$('[name]') // 存在…
combinator
~~~js
$('#header a') // descendant selector 後代選擇器
$('#header ul li')
$('#header>p') // child selector 子代選擇器
// 下面兩個鄰代選擇器應該不常用, 我從來沒有用過
$(':input+span') // next adjacent selector 在input後面的, 緊挨著的那個span
$(':input~span') // next sibling selector 在input後面的, 所有同代span
$('p,a,:input') // multiple Selector
$('a.close:acitve') // and
使用選擇器
盡量使用簡單的選擇器, 如
$('#id')
$('a.close', container) // class需要帶tag限制,
// 由於在ie6等瀏覽器對class沒有原生api支持,使得單純的class selector比較慢
$('ul>li', container) // 有context限制
優先級
非常簡單, 一句話:
id > 非(id,tag) > tag, 相同等級看數量
參看 http://www.w3.org/TR/css3-selectors/ 第9節
[額外閱讀]
CSS3選擇器詳細內容請參考W3C文檔: http://www.w3.org/TR/css3-selectors/
jQuery支持的選擇器請參考: http://api.jquery.com/category/selectors/
構造jQuery對象
// from 選擇器
$('#publish-dialog a.close');
// from dom節點
var dom = $('#doc')[0];
$(dom);
// from jquery對象
var chooser = $('p.offer-chooser');
var panel = chooser.find('ul.chooser-panel');
或者
var panel = $('ul.chooser-panel', chooser); // 內部調用 find來完成
jquery對象結構
如果針對本文檔,在firefox script watch中輸入 jQuery('ul'), 將創建一個jQuery對象,
用於操作文檔中所有ul節點, 展開它會看到如下對象結構
{
0: ul, // HTMLUListElement, 即原生dom節點對象
1: ul,
2: ul,
…
7: ul,
length: 8,
selector: 'ul',
context: document,
prevObject: document
…
}
jQuery對象是一個普通對象,有數組相似的接口,所以可以像數組一樣參於迭代
構造一個jQuery對象成本不大,因為隻包含一些引用字段,沒有大數據字段
瞭解這個是為瞭讓我們更好地使用jQuery,而不是把$(美元符號)當作一種奇怪的語法來用.
見下面這段jQuery初學者常見的代碼
$('a.close', p).click(function() {
$(this).addClass('abc');
if (…) {
$(this).attr(…)
} else {
$(this).data(…);
}
$(this)…
});
為瞭增加可讀性,提高效率和減少不必要的內存消耗, 請記住 $(elm) 等於 new jQuery(elm);
隻構造必要的jQuery對象, 上述代碼可以這麼寫
elms.click(function() {
var elm = $(this); // 保存引用
…
});
基本操作
var lis = $('ul.offer-list li');
if (!lislength) { // 判斷節點是否存在
return;
}
lis.eq(0).addClass('first'); // 隻對第一個節點處理, 相當於 $(lis[0]).addClass('first');
var firstLi = lis[0]; // 取得第1個節點(html dom元素)
lis.each(function(index) {
var li = $(this); // each中的this是原生的dom對象
li.data('offer', offers[index]);
});
事件
普通事件
$('a.open', panel).click(function(e) {
e.preventDefault();
new Dialog({
header: '設置'
width: '700',
height: '350',
confirm: function() {
…
}
});
});
上述事件掛接,也可以使用bind
button.bind('click', function(e) {
…
});
click方法隻是bind方法的一個包裝, 相似的還有其他事件: change, resize, dblclick, keypress 等
幾乎所有常用的瀏覽器事件都有直接對應的方法用於掛接或觸發事件
關於其他事件請參考: http://api.jquery.com/category/events/
關於事件函數有幾點說明:
關於this
this指向觸發事件的元素(html dom節點)
$('a.delete', list).click(function(e) {
e.prevent();
var link = $(this), // here, is a html element
li = link.closest('li'),
postId = li.data('postId');
Post.delete(postId);
});
關於參數event
我們可以從中獲取一些關於事件的參數, 如鼠標位置,鍵盤輸入等等,
或者取消瀏覽器默認事件, 阻止冒泡
$('a.close', dialog).click(function(e) {
e.preventDefault(); // 阻止鏈接正常行為,
// 因為這個鏈接是作為功能按扭來使用的, 而不需要跳轉或重新定位瞄點
…
});
$('p.canvas').mousemove(function(e) {
var x = e.pageX, // 鼠標位置
y = e.pageY;
});
$('input.username').keydown(function(e) {
var self = this;
if (e.keyCode === 13) { // 回車
self.submitForm();
}
});
關於event object請參考: http://api.jquery.com/category/events/event-object/
觸發事件
有時候需要人為地觸發一個瀏覽器事件, 如提交表單
或者打開浮層登錄框框後需要默認選擇第二個tab, 就相當於"按"一下那個tab
form.submit();
tabs.eq(2).click();
或者
form.trigger('submit');
tabs.eq(2).trigger('click');
同bind, 無參數的click或submit隻是trigger的一個包裝
trigger它會執行瀏覽器默認行為, 比如當你click一個checkbox的時候, 界面中的checkbox也會勾選或取消勾選
同時trigger還會冒泡(關於事件冒泡,下面會有)
如果隻想執行事件,而不觸發默認行為, 請使用triggerHandler
同時trigger和triggerHandler還支持額外的參數,具體可參考文檔
http://api.jquery.com/trigger/
http://api.jquery.com/triggerHandler/
事件冒泡
body
p#doc
p#header
p.search-bar
input.search-btn[type=button]
當我們點擊input.search-btn時, input.search-btn, p.search-bar, p.#header, p#doc, body 依次會觸發click事件.
這個過程叫事件冒泡
關於事件冒泡更細致的內容可參考: http://api.jquery.com/category/events/
所以如果我們所事件函數掛接在外圍節點, 將能接收到裡面子節點的事件.
容器內部節點動態創建的事件綁定
p.click(function(e) {
if ($(e.target).is('a.delete')) {
// ..
}
});
以上代碼可以這樣寫:
p.delegate('a.delete', 'click', function() {
});
如果有很多個節點需要綁定事件, 此時可以使用冒泡綁定在外圍容器上, 很大地提高效率
// bigtable為大表格
$('table.bigtable td').dblclick(function() {
});
// 請使用事件代理綁定事件
$('table.bigtable').delegate('td', 'dblclick', function() {
});
自定義事件
// move.js
saveLayout: function() {
…
$('window').trigger('diychanged', { type: 'layout' });
}
// diy-skins
saveSkin: function() {
$('window').trigger('diychanged', { type: 'diyskins' });
}
// header.js
showTips: function() {
$('window').bind('diychanged', function(e, data) {
new Tips({
//…
message: '點擊發佈可應用您的修改到旺鋪'
});
});
}
namespace事件
$('a.mylink').bind('click', function() {
});
$('a.mylink').bind('click.mylink', function() {
});
// 移除事件
$('a.mylink').unbind('click.mylink');
// 觸發指定namespace事件
$('a.mylink').triggerHandler('click.mylink');
關於namespace的參考鏈接, 官網上我一時找不到瞭, 不好意思,以前記得有的,現在隻有一點點:
http://api.jquery.com/bind/
http://api.jquery.com/unbind/
或者直接參看源碼可能會更清晰: jquery 1.6.2, 第2619行, 第2726行, 第2810行
操作
節點操作的api可直接參考 http://api.jquery.com/category/manipulation/
有幾點需要說明, dom節點操作是所有操作裡最慢的, 往往頁面的效率瓶頸就在dom操作,所以要特別註意
改變屬性的操作 如: addClass, removeClass, css等需要瀏覽器重繪節點
而html, append, prepand,等節點操作需要瀏覽器重排節點並且重繪
具體請參考: 《High Performance Javascript》chapter 3
可以像這樣:
render: function(offers) {
var self = this,
html = [];
$.each(offers, function(index, offer) {
html.push(self.createItemHtml(offer));
});
p.html('<ul>' + html.join('') + '</ul>');
}
或者
render: function(offers) {
var self = this,
ul = $('<ul>');
$.each(offers, function(index, offer) {
ul.append(self.createItem(offer));
});
p.append(ul);
}
數據
jQuery#data 用於在節點上保存節點相關的數據, 這樣在需要的時候就可以根據節點取得相應數據
render: function(offers) {
var self = this,
ul = $('<ul>');
$.each(offers, function(index, offer) {
var li = self.createItem(offer);
li.data('offer', offer); // here
…
});
…
},
handleP4p: function() {
var self = this;
p.delegate('a.title', 'click', function() {
var li = $(this).closest('li'),
offer = li.data('offer');
self.p4pclick(offer);
});
}
html5data標簽和data方法
<p class="mod wp-supplier-info" data-mod-config='{"requestUrl":"http…"}'>…</p>
我們使用html5 data標簽代替以往的input:hidden域,來保存後端需要傳遞給前端的頁面參數
var mod = $('p.wp-supplier-info'),
config = mod.data('modConfig'); // 省略前綴data, 後面的轉化成camel形式,
// 返回的是一個json object(如果確實是的話)
<p class="offers-container" data-request-url="http://….">
</p>
var container = $(…),
url = container.data('requestUrl'); // typeof url === 'string'
jQuery會足夠智能地解析出需要的結構,大概規則是:
1. 如果是true/false/null/number就轉化成相應的數據
2. 如果是json串,就轉化成object
3. 當作string處理
感興趣的可以查看源碼: 1.6.2版 1689行,dataAttr方法
檢測
可以使用 $.util.ua (由fdev-v4包裝) 來檢測是否IE瀏覽器和版本號
可以使用 $.browser 來檢測瀏覽器的信息
可以使用 $.support 進行瀏覽器特性檢測
// 瀏覽器檢測
if ($.util.ua.ie6) {
}
// 瀏覽器檢測
if ($.browser.webkit) {
}
// 特性檢測
if ($.support.opacity) { // 是否支持透明, 現在IE不支持
}
使用瀏覽器兼容的解決方法
如果辦不到1, 請先使用特性檢測
最後一條路,才使用瀏覽器檢測
具體請參考: http://api.jquery.com/jQuery.support/
其他api可以直接看文檔就可以,而且官方文檔都有相關例子,不需要再做介紹
前端基礎庫 fdevlib
style目錄結構簡要說明
lib – 基礎庫
fdev-v4
core
fdev-min.js
widget
ui
dialog.js
util
cookie.js
json2.js
web
alitalk.js
sys – 公共業務組件
head
ibank
popsigin
app
platform
company
member
postoffer
work
winport
global
winport-merge.js
module
page
diy
diy-merge.js
前端基礎庫請參考: http://fd.aliued.cn/fdevlib/#home
應用stle目錄結構說明請參考: http://fd.aliued.cn/doc/page/regulations/dir-structure
fdev-v4現有組件
fdev-v4
core
fdev-min.js 我們使用的時候引入這個文件
config.js 動態加載的組件配置信息
gears.js 定義瞭一些基本方法,如String#trim,
還有一些方法移自fdev3, 還有就是動態加載$.add, $.use等方法
jquery1.6.2.js
web.js 可以使用FE.util.lastLoginId獲得用戶登錄信息
所以用戶登錄等cookie信息的函數由這個文件定義
widget
ui
autocomplete 自動補全,像我們的搜索
colorbox
colorpicker 顏色選取
combobox 自定義樣式下拉框(原生下拉框比較醜)
datapicker 日歷選擇
dialog 對話框
draggable 拖動
droppable 拖放
sortable 拖動排序(來自jquery ui)
portlets 拖動排序,目前用於旺鋪diy後臺
flash-chart flash圖片組件(YID)
flash-storage flash本地存儲
flash-uploader 上傳文件
flash-websocket flash長鏈接
flash flash顯示
menu
progressbar 進度條
scrollto 頁面滾動
tab tab組件
timer
util
cookie cookie操作(寫cookie時需要引入)
date
debug 調試
history 歷史記錄 work平臺有使用
json2 json
storage 本地存儲
web
alitalk 阿裡旺旺
browser
datalazyload 延遲加載
pca
stylesheet 操作樣式表
sweet js模板
valid 前端驗證
websocket 長鏈接
作者“夢工廠”