前兩天看了篇不錯的關於javascript寫法的文章。在評論里,不少人表示看不懂最后一種方法,作者也沒詳細說明,於是我順勢解說,算是沾點人氣。
代碼如上圖,整體結構模仿jQuery,代碼不長,知識點較多,信息量很大,我們分兩部分,逐行解釋。
第一部分
第一部分中,我們把extend相關的方法抽離,剩余代碼如下:
(function () { var yQuery = (function () { var yQuery = function () { return yQuery.fn.init(); }; yQuery.fn = yQuery.prototype = { init: function () { return this; } }; return yQuery; })(); window.yQuery = window.$ = yQuery(); })();
知識點1:無引用的匿名函數調用
(function() {
})();
這種寫法作用是聲明並執行一個方法,等同於:
function Test() {
}
Test();
知識點2:屬性變量無需聲明
var obj = new Object(); obj.name = "abc";
obj並沒有name屬性,但無需聲明就可以使用,初始值為undefined。上例中yQuery.fn 就沒有聲明。
知識點3:{}二義性,相當於創建一個對象。
var obj = new Object(); var obj = { };
在js中,{}除了可作為復合語句邊界以外,還有創建一個空對象的作用,因此上面這兩句相同。而在例子中用到的情況如下:
{ init: function () { return this; } };
這段代碼猛一看像方法,實質是一個包含了init方法的對象,而方法中的this指向這個對象本身。
知識點4:連等表達式
var a = b = 1;
這個比較好理解,相當於對他們分別賦同一個值。在上例中,yQuery.fn就用到了這個寫法。
知識點5:原型繼承prototype
yQuery.fn = yQuery.prototype = { init: function () { return this; } };
例子中,通過連等分別對yQuery.fn和yQuery.prototype賦值給了同一個對象。而fn的作用只是一個別名,只為書寫方便,重點是給prototype賦值,為什么要給它賦值?在本例中無法解釋。
在jQuery中,init方法會返回不同對象,而本例中永遠返回同一個對象,因此這里prototype沒有多大意義。至於jQuery為什么要用prototype,算是題外話了,有興趣的可點這里。
第一部分代碼含義
這段代碼第二行yQuery和第三行的yQuery是兩個變量,因名字相同,所有很有迷惑性。整段代碼意思就是:yQuery.prototype指向了一個包含init方法的對象,prototype有個別名fn,可通過yQuery.fn.init()返回這個對象,最后把這個對象賦值給window.$和window.yQuery屬性。
第二部分
看完了第一部分,第二部分就相對簡單了,代碼如下:
yQuery.extend = yQuery.fn.extend = function () { var options, name, src, copy, target = arguments[0] || {}, i = 1, length = arguments.length; if (length === i) { target = this; --i; } for (; i < length; i++) { if ((options = arguments[i]) != null) { for (name in options) { src = target[name]; copy = options[name]; if (src === copy) { continue; } if (copy!==undefined) { target[name] = copy; } } } } return target; };
知識點1:函數中的arguments變量
函數內部會自帶一個arguments變量,該變量記錄傳入的參數,從左至右分別是arguments[0],arguments[1]等,js奇怪的地方在於,你聲明了一個無參函數,在調用的時候依然可以傳入參數,比如:
function NoArg() { console.log(arguments[0]); console.log(arguments[1]); console.log(arguments[2]); } NoArg(1, 2, "a");
該例NoArg調用時,會正常顯示傳入參數。
知識點2:結果類型不確定的邏輯運算
js中,所有類型都可以進行邏輯判斷(true或false),js會將值轉化為布爾值,但並不改變原值。如:
var str = "a"; if(str) { }
此處的str為true,該特性與邏輯運算符”||”和”&&”結合形成了js一大特點,代碼如下:
var str = "a"; var num = 1; var x = str || num; //x="a" var y = str && num; //y=1;
js支持“邏輯短路”,所謂邏輯短路是指:
- 在”||” 運算中,第一個條件符合就結束判斷。
- 在”&&”運算中,第一個條件不符合就結束判斷。
因此,”str || num”的str為true,則結束判斷,返回str。”str&&num”的str為true,則繼續判斷num,num為true,則返回num。在本文案例中,有幾個地方用到了這個特性:
target = arguments[0] || {}
容易看出,如果arguments[0]有值則返回該值,不然就通過{}返回一個空的對象。還有一處在圖片中有,我文中沒有打出來的代碼:
$.ui = $.ui || { };
知識點3:數組方式訪問對象屬性
var obj = new Object(); obj.name = "a"; obj["name"] = "a";
最后兩行代碼等效。
第二部分代碼含義
這部分代碼可簡單描述為:定義一個方法,將參數1之外的所有參數的屬性成員賦值給參數1。我把循環部分修改一下,能更容易看懂,代碼如下:
for (; i < length; i++) { if ((options = arguments[i]) != null) { for (var name in options) { if (options[name] !== undefined) { target[name] = options[name]; } } } }
結語
js中有不少語法和運算符存在二義性,這導致js代碼顯的混亂難懂,所以不少人罵js是一個2B的語言,我覺得也不無道理。不過隨着js的應用范圍越來越廣泛,終究避不開它,因此罵歸罵,學還是要學的。
我是看了周愛民老師的《JavaScript語言精髓與編程實踐(第2版)》后才算對js真正入門,飲水思源,推薦此書。