前言:好久以前讀過一遍該書,近日得閒,重拾該書,詳細研究一方,歡迎討論指正。node
目錄:web
一、精華正則表達式
二、語法編程
三、對象數組
四、函數瀏覽器
五、繼承安全
六、數組服務器
七、正則表達式閉包
八、方法app
九、代碼風格
十、優美的特性
附錄A 毒瘤
附錄B 糟粕
附錄C JSLint
附錄D 語法圖
附錄E JSON
正文:
第1章 精華
本書的目的就是要揭示JavaScript中的精華,讓你們知道它是一門傑出的動態編程語言。
JavaScript是創建在一些很是優秀的想法和少數很是糟糕的想法之上。
那些優秀的想法包括函數、弱類型、動態對象和富有表現力的對象字面量表示法。
那些糟糕的想法包括基於全局變量的編程模型。
JavaScript的函數是基於詞法做用域的頂級對象。
JavaScript是1門弱類型語言。
JavaScript有很是強大的對象字面量表示法。
JavaScript的繼承是原型繼承。
JavaScript依賴於全局變量來進行鏈接。
全部編譯單元的全部頂級對象被撮合到一個被稱爲全局對象的命名空間中。
本文貫徹始終都會用到一個method方法去定義新方法:
Function.prototype.method = function (name, func){ this.prototype[name] = func; return this; }
第2章 語法
本章介紹JavaScript的精華部分的語法,並簡要地概述其語言結構。
空白
空白可能表現爲被格式化的字符或註釋的形式。
如:
var that = this;
var 和 that 之間的空格不能移除,可是其餘的空格能夠移除。
JavaScript提供兩種註釋形式,一種是用/* */包圍的塊註釋。一種是以//開頭的行註釋。註釋應該優先被用來提升程序的可讀性。其中,塊註釋遇到正則表達式的時候,對於被註釋的代碼來講是不安全的。
標識符
標識符由一個字符開頭,其後可選擇性地加上一個或多個字母、數字或下劃線。標識符不能使用保留字。
JavaScript不容許使用保留字來命名變量或參數,同時,不容許在對象字面量中,或者用點運算符提取對象屬性時,使用保留字做爲對象的屬性名。
數字
JavaScript只有一個數字類型。它在內部被表示爲64位的浮點數,和Java的double數字類型同樣。
它沒有分離出整數類型,因此:
1 === 1.0 // true 100 === 1e2 // true
NaN是一個數值,它表示不能產生正常結果的運算結果。NaN不等於任何值,包括它自己。能夠用isNaN(number)檢測NaN。
Infinity表示全部大於1.79769313486231570e+308的值。
數字擁有方法。JavaScript有一個對象Math,它包含一套做用於數字的方法。例如,能夠用Math.floor(number)方法把一個數字轉換成一個整數。
字符串
字符串字面量能夠被包在一對單引號或雙引號中,它可能包含0個或多個字符。
\(反斜線符號)是轉義字符。JavaScript在被建立的時候,Unicode是一個16位的字符集,因此JavaScript中的全部字符都是16位的。
JavaScript沒有字符類型。要表示一個字符,只需建立一個包含一個字符的字符串便可。
轉義字符用來把那些正常狀況下不被容許的字符插入到字符串中,好比反斜線、引號和控制字符。\u約定用來指定數字字符編碼。
"A" === "\u0041" // true
字符串有一個length屬性。例如,"seven".length是5。
字符串是不可變的。
語句
在web瀏覽器中,每一個<script>標籤提供一個被編譯且當即執行的編譯單元。由於缺乏連接器,JavaScript把它們拋到一個公共的全局命名空間中。
當var語句被用在函數內部時,它定義的是這個函數的私有變量。
語句一般按照從上到下的順序被執行。JavaScript能夠經過條件語句(if 和 switch),循環語句(while\for\do)、強制跳轉語句(break\return\throw)和函數調用來改變執行序列。
代碼塊是包在一對花括號中的一組語句。JavaScript中的代碼塊不會建立新的做用域,所以變量應該被定義在函數的頭部,而不是在代碼塊中。
下面的值被當作假(6個):
false
null
undefined
空字符串''
數字0
數字NaN
其它全部的值都被當作真,包括true、字符串"false",以及全部的對象。
case從句防止繼續執行下一個case,case從句後應該加一個強制跳轉語句,break。
for...in語句:
會枚舉一個對象的全部屬性名(或鍵名)
var x; var mycars = {}; mycars["name"] = "BMW"; mycars["color"] = "green"; Object.prototype.size = "6meter"; for (x in mycars){ console.log(mycars[x]); //BMW green 6meter
}
一般須要檢查object.hasOwnProperty(variable)來肯定這個屬性是不是該對象的成員,仍是來自於原型鏈。
for (x in mycars){ if(mycars.hasOwnProperty(x)){ console.log(mycars[x]); //BMW green
}
}
return語句會致使從函數提早返回。它也能夠指定要返回的值,若是沒有指定返回表達式,那麼返回值是undefined。
typeof運算符產生的值有number、string、boolean、undefined、function、object。
若是運算數是一個數組或null,那麼typeof的結果是object,實際上是錯的。
函數調用運算符是跟隨在函數名後面的一對圓括號。
字面量
對象字面量是一種能夠方便地按指定規格建立新對象的表示法。屬性名能夠是標識符或字符串。
這些名字被當作字面量名而不是變量名來對待,因此對象的屬性名在編譯時才能知道。
函數
函數字面量定義了函數值,它能夠有一個可選的名字,用於遞歸地調用本身。它能夠指定一個參數列表,這些參數就像變量同樣,在調用時由傳遞的實際參數初始化。
函數的主體包括變量定義和語句。
第3章 對象
JavaScript的簡單數據類型(5種)包括數字、字符串、布爾值(true和false)、null和undefined。其餘全部的值都是對象。
JavaScript中的對象是可變的鍵控集合。
數組是對象,函數是對象,正則表達式是對象,對象也是對象。
對象是屬性的容器,其中每一個屬性都擁有名字和值。屬性的名字是能夠包括空字符串在內的任意字符串。屬性值能夠是除undefined值以外的任何值。
var obj = { "": 'aa' ; } obj[""]; // aa
JavaScript裏的對象是無類型的。它對新屬性的名字和屬性的值沒有限制。對象適合用於聚集和管理數據。對象能夠包含其餘對象,因此他們能夠很容易地表示成樹狀或圖形結構。
JavaScript包含一種原型鏈的特性,容許對象繼承另外一個對象的屬性。正確地使用它,能減小對象初始化時消耗的時間和內存。
對象字面量
一個對象字面量就是包圍在一對花括號中的零個或多個"名/值"對。對象字面量能夠出如今任何容許表達式出現的地方。
var empty_object = {}; var stooge = { "first-name": "Jerome", "last-name": "Howard" }
在對象字面量中,若是屬性名是一個合法的JavaScript標識符且不是保留字,則並不強求要用引號括住屬性名。如:"first-name"是必須的,first_name是可選的。用逗號來分隔多個"名/值"對。
屬性的值能夠從包括另外一個對象字面量在內的任意表達式中得到。對象是能夠嵌套的。
var flight = { airline: "Oceanic", number: 815, arrival: { IATA: "LAZ", time: "2004-09-23 10:42", city: "Los Angeles" } }
檢索
要檢索對象裏面的值,能夠採用在[]後綴中括住一個字符串表達式的方式。
若是字符串表達式是一個字符串字面量,並且它是一個合法的JavaScript標識符且不是保留字,那麼也能夠用.表示法代替。優先考慮使用.表示法,由於它更緊湊且可讀性更好。
stooge["first-time"]; // "Jerome" flight.departure.IATA; // "LAZ"
若是你嘗試檢索一個並不存在的成員屬性的值,將返回undefined。
stooge["middle-name"]; //undefined flight.status; // undefined stooge["FIRST-NAME"]; //undefined
||運算符能夠用來填充默認值:
var middle = stooge["middle-name"] || "(none)" var status = flight.status || "unknown";
嘗試從undefined的成員屬性中取值將會致使TypeError異常。這時能夠經過&&運算符來避免錯誤。
var stooge = { first_name: 'bally' } stooge.last_name; // undefined stooge.last_name.name; // throw "TypeError" stooge.last_name && stooge.last_name.name; // undefined
更新
對象裏的值能夠經過賦值語句來更新。若是屬性名已經存在於對象裏,那麼這個屬性的值就會被替換。
stooge['first-name'] = 'Jome';
若是對象以前沒有擁有那個屬性名,那麼該屬性就會被擴充到對象中。
stooge['middle-name'] = 'Lester'; stooge.nickname = 'Curly'; flight.equipment = { model: 'Boeing 777' }; flight.status = 'overdue';
引用
對象經過引用來傳遞,它們永遠不會被複制。
var stooge = { first_name: 'bally' } var x = stooge; // undefined x.nickname = 'Curly' // "Curly" stooge.nickname; "Curly" var a = {}, b = {}, c = {}; a = b = c = {}; //a、b和c都引用同一個空對象
原型
每一個對象都鏈接到一個原型對象,而且它能夠從中繼承屬性。全部經過對象字面量建立的對象都鏈接到Object.prototype,它是JavaScript中的標配對象。
當建立一個對象時,能夠選擇某個對象做爲它的原型。
給Object增長一個create方法。這個方法建立一個使用原對象做爲其原型的新對象。
if(typeof Object.create !== 'function'){ Object.create = function (o){ var F = function () {}; F.prototype = o; return new F(); } } var another_stooge = Object.create(stooge); another_stooge.nickname; // 'Curly'
原型鏈接只有在檢索值時才被用到,若是咱們嘗試去獲取對象的某個屬性值,但該對象沒有此屬性名,那麼JavaScript會試着從原型對象中獲取屬性值。若是該原型對象也沒有此屬性,再從它的原型中尋找,依次類推,直到該過程最後到達終點Object.prototype。
原型關係是一種動態的關係。若是咱們添加一個新的屬性到原型中,該屬性會當即對全部基於該原型建立的對象可見。
stooge.profession = 'actor'; another_stooge.profession; // 'actor'
反射
檢查對象並肯定對象有什麼屬性。
typeof flight.number; // 'number' typeof flight.status; // 'string' typeof flight.arrival; // 'object' typeof flight.manifest; // 'undefined'
請注意原型鏈中的任何屬性都會產生值。
typeof flight.toString; // 'function' typeof flight.constructor; // 'function'
使用hasOwnProperty方法,若是對象擁有獨有的屬性,將返回true。hasOwnProperty方法不會檢查原型鏈。
flight.hasOwnProperty('number'); // true flight.hasOwnProperty('constructor'); // false
枚舉
for...in語句可用來遍歷一個對象中的全部屬性名。該枚舉過程將會列出全部的屬性,包括函數和你可能不關心的原型中的屬性。
最經常使用的過濾方法就是hasOwnProperty方法,以及使用typeof來排成函數。
var Stooge = function(){} Stooge.prototype.nick_name = 'nick'; Stooge.prototype.getName = function(){ console.log(this.nick_name) } for(name in another_stooge){ if(typeof another_stooge[name] !== 'function'){ //過濾函數 console.log(name) } }
刪除
delete運算符能夠用來刪除對象的屬性。若是對象包含該屬性,那麼該屬性就會被刪除。它不會觸及原型鏈中的任何對象。
刪除對象的屬性可能會讓原型鏈中的屬性透現出來。
another_stooge.nickname; // 'Moe'; delete another_stooge.nickname; another_stooge.nickname; // 'Curly'
減小全局變量污染
JavaScript能夠很隨意地定義全局變量來容納你的應用的全部資源。遺憾的是,全局變量削弱了程序的靈活性,應該避免使用。
最小化使用全局變量的方法之一是爲你的應用只建立一個惟一的全局變量。
var MYAPP = {};
此時,該變量變成了你的應用的容器:
MYAPP.stooge = { "first-name": "Joe", "last-name": "Howard" } MYAPP.flight = { airline: "Oceanic", number: 815, arrival: { IATA: "LAX", time: "2004-09-23 10:42", city: "Los Angeles" } }
只要把全局性的資源都歸入一個名稱空間之下,你的程序與其餘應用程序、組件、類庫之間發生衝突的可能性就會顯著下降。很明顯,MYAPP.stooge指向的是頂層結構。
第4章 函數
JavaScript設計地最出色的就是它的函數的實現。函數用於指定對象的行爲。
函數對象
JavaScript中的函數就是對象。
對象字面量產生的對象鏈接到Object.prototype。
函數對象鏈接到Function.prototype(該原型對象自己鏈接到Object.prototype)
每一個函數在建立時都會附加兩個隱藏屬性:函數的上下文和實現函數行爲的代碼。
每一個函數對象在建立時都配有一個prototype屬性。它的值是一個擁有constructor屬性且值爲該函數的對象。
constructor指向該對象的構造函數。
function abc(){} abc.constructor; //function Function() { [native code] } var bbc = new abc(); bbc.constructor; //function abc(){}
由於函數是對象,因此他們能夠像任何其餘的值同樣被使用。函數能夠保存在變量、對象和數組中。
函數能夠被當作參數傳遞給其餘函數,函數也能夠再返回函數。並且,由於函數是對象,因此函數也能夠擁有方法。
函數的不同凡響在於他們能夠被調用。
函數字面量
函數對象經過函數字面量來建立:
//建立一個名爲add的變量,並用來把兩個數字相加的函數賦值給它 var add = function (a, b){ return a + b; }
函數字面量包括4個部分:
第1部分:保留字function。
第2部分:函數名,能夠被省略(匿名函數)。
第3部分:包圍在圓括號中的一組參數,在該函數被調用時,初始化爲實際提供的值。
第4部分:包圍在花括號中的一組語句。這些語句是函數的主體,在函數被調用時執行。
經過函數字面量建立的函數對象包含一個連到外部上下文的鏈接。這被稱爲閉包。
調用
調用一個函數會暫停當前函數的執行,傳遞控制權和參數給新函數。
除了聲明時定義的形式參數,每一個函數還接收兩個附加的參數:this和arguments。
在JavaScript中一共有4種調用模式:
方法調用模式、函數調用模式、構造器調用模式、apply調用模式。
若是實際參數值過多,超出的參數值會被忽略。
若是實際參數值過少,超出的參數值會被替換爲undefined。
對參數值不會進行類型檢查:任何類型的值均可以被傳遞給任何參數。
方法調用模式
當一個函數被保存爲對象的一個屬性時,咱們稱它爲一個方法。當一個方法被調用時,this被綁定到該對象。
//建立myObject對象,它有一個value屬性和一個increment方法 //increment方法接受一個可選的參數,若是參數不是數字,默認使用1 var myObject = { value: 0, increment: function(inc){ this.value += typeof inc === 'number' ? inc : 1; } } myObject.increment(); myObject.value; // 1 myObject.increment(2); myObject.value; // 2
方法可使用this訪問本身所屬的對象,因此它能從對象中取值或對對象進行修改。
經過this可取得它們所屬對象的上下文的方法稱爲公共方法。
函數調用模式
當一個函數並不是一個對象的屬性時,那麼它就是被當作一個函數來調用的。
以此模式調用函數時,this被綁定到全局對象。
var myObject = { value: 0, increment: function(inc){ this.value += typeof inc === 'number' ? inc : 1; } } myObject.double = function(){ var that = this; var helper = function(){ that.value = add(that.value, that.value); //this指向全局對象 } helper(); } function add(a, b){ return a + b; } myObject.increment(2) myObject.double(); myObject.value;
構造器調用模式
JavaScript是一門基於原型繼承的語言。這意味着對象能夠直接從其餘對象繼承屬性。該語言是無類型的。
若是在一個函數前面帶上new來調用,那麼背地裏將會建立一個鏈接到該函數的prototype成員的新對象,同時this會被綁定到那個新對象上。
var Quo = function(string){ this.status = string; } Quo.prototype.get_status = function(){ return this.status; } var myQuo = new Quo('confused'); myQuo.get_status(); // "confused"
一個函數,若是建立的目的就是但願結合new前綴來調用,那它就被稱爲構造器函數。
不推薦使用這種形式的構造器函數。
Apply調用模式
apply方法讓咱們構建一個參數數組傳遞給調用函數。容許咱們選擇this的值。
apply方法接收兩個參數。第1個是要綁定給this的值,第2個就是一個參數數組。
var array = [3,4]; function add(a,b){ return a + b; } var sum = add.apply(null, array); sum; // 7 //構造一個包含status成員的對象 var statusObject = { status: 'A-OK' } var status = Quo.prototype.get_status.apply(statusObject);
參數
當函數被調用時,會獲得一個免費配送的參數,那就是arguments數組。
函數能夠經過此參數訪問全部它被調用時傳遞給它的參數列表,包括那些沒有被分配給函數聲明時定義的形式參數的多餘參數。這使得編寫一個無需指定參數個數的函數成爲可能。
var sum = function(){ var i, sum = 0; for(i = 0; i < arguments.length; i++){ sum += arguments[i] } return sum; } sum(2,4,6,8); // 20
返回
當一個函數被調用時,它從第一個語句開始執行,並在遇到關閉函數體的}時結束。而後函數把控制權交還給調用該函數的程序。
return語句可用來使函數提早返回。當return語句被執行時,函數當即返回而再也不執行餘下的語句。
一個函數老是會返回一個值。若是沒有指定返回值,則返回undefined。
若是函數調用時在前面加上了new前綴,且返回值不是一個對象,則返回this(該新對象)。
異常
var add = function(a, b){ if(typeof a !== 'number' || typeof b !== 'number'){ throw { name : 'TypeError', message: 'add needs numbers' } } return a + b; } var try_it = function(){ try { add('seven'); } catch(e){ console.log(e.name + ": " + e.message) } } try_it(); // TypeError: add needs numbers
一個try語句只會有一個捕獲全部異常的catch代碼塊。若是你的處理手段取決於異常的類型,那麼異常處理器必須檢查異常對象的name屬性來肯定異常的類型。
擴充類型的功能
給Function.prototype增長方法來使得該方法對全部函數可用:
Function.prototype.method = function(name, func){ this.prototype[name] = func; return this; }
經過給Function.prototype增長了一個method方法,下次給對象增長方法的時候就沒必要鍵入prototype了。
經過給Number.prototype增長一個integer方法來取整:
Number.method('integer', function(){ return Math[this < 0 ? 'ceil' : 'floor'](this); }); (-10 / 3).integer();
增長一個移除字符串首尾空白的方法:
String.method('trim', function(){ return this.replace(/^\s+|\s+$/g, ''); //\s匹配任何不可見字符,包括空格、製表符、換頁符等 })
由於JavaScript原型繼承的動態本質,新的方法馬上被賦予到全部的對象實例上。
基本類型的原型是公共結構,因此在類庫混用時務必當心。一個保險的作法就是隻在肯定沒有該方法時才添加它。
Function.prototype.method = function(name, func){ if(!this.prototype[name]){ this.prototype[name] = func; } return this; }
遞歸
遞歸函數能夠很是高效地操做樹形結構,好比瀏覽器端的文檔對象模型(DOM),每次遞歸調用時處理指定的樹的一小段。
//定義walk_the_DOM函數,它從某個指定的節點開始,按HTML源碼中的順序訪問該樹中的每一個節點 //它會調用一個函數,並依次傳遞每一個節點給它 //walk_the_DOM調用自身去處理每個子節點 var walk_the_DOM = function walk(node, func){ func(node); node = node.firstChild; while(node){ walk(node, func); node = node.nextSibling; } } //定義一個getElementsByAttribute函數它以一個屬性名稱字符串和一個可選的匹配值做爲參數 //它調用walk_the_DOM,傳遞一個用來查找節點屬性名的函數做爲參數 //匹配的節點會累加到一個結果數組中 var getElementsByAttribute = function (att, value) { var results = []; walk_the_DOM(document.body, function(node){ var actual = node.nodeType === 1 && node.getAttribute(att); if(typeof actual === 'string' && (actual === value || typeof value !== 'string')){ results.push(node); } }) } getElementsByAttribute('class','body')
做用域
JavaScript不支持塊級做用域,支持函數做用域。
閉包
做用域的好處是內部函數能夠訪問定義它們的外部函數的參數和變量(除了this和arguments)。
一個更有趣的情形是內部函數擁有比它的外部函數更長的生命週期
構造一個myObject對象,擁有一個value屬性和一個increment方法,假定咱們但願保護該值不會被非法更改。
經過調用一個函數的形式去初始化myObject,該函數會返回一個對象字面量。
函數裏定義了一個value變量。該變量對increment和getValue來講是可用的,但函數的做用域使得它對其餘的程序來講老是不可見的。
var myObject = (function(){ var value = 0; return { increment: function(inc){ value += typeof inc === 'number' ? inc : 1; }, getValue: function(){ return value; } } }())
status是私有屬性,定義另外一種形式的Quo函數:
var quo = function(status){ return { get_status: function(){ return status; } } } //構造一個quo實例 var myQuo = quo('amazed'); myQuo.get_status();
備註:這個quo函數被設計成無須在前面加上new來調用,因此名字也沒有大寫。
來看一個更有趣的例子:
//定義一個函數,它設置一個DOM節點爲黃色,而後把它漸變爲白色 var fade = function(node){ var level = 1; var step = function(){ var hex = level.toString(16); node.style.backgroundColor = '#FFFF' + hex + hex; if(level < 15){ level += 1; setTimeout(step, 100); } } setTimeout(step, 100); } fade(document.body);
理解內部函數訪問外部函數的實際變量而無須複製是很重要的。
var add_the_handlers = function(nodes){ var i; for(i = 0; i < nodes.length; i++){ nodes[i].onclick = function(e){ alert(i) } } }
add_the_handlers函數的本意是想傳遞給每一個事件處理器一個惟一值,但它未能達到目的,由於事件處理器函數綁定了變量i自己,而不是函數在構造時的變量i的值。
var add_the_handlers = function(nodes){ var helper = function(i){ return function(e){ alert(i) } } var i; for(i = 0;i < nodes.length; i++){ nodes[i].onclick = helper(i); } }
避免在循環中建立函數,在循環以外建立一個輔助函數,讓這個輔助函數再返回一個綁定了當前i值的函數。
回調
發起異步請求,提供一個當服務器的響應到達時隨即觸發的回調函數。
異步函數當即返回,這樣客戶端就不會被阻塞。
request = prepare_the_request(); send_request_asynchronously(request, function(response){ display(resposne); })
模塊
咱們可使用函數和閉包來構造模塊。
模塊是一個提供接口卻隱藏狀態與實現的函數或對象。
經過使用函數產生模塊,幾乎能夠徹底摒棄全局變量的使用。
Function.prototype.method = function(name, func){ if(!this.prototype[name]){ this.prototype[name] = func; } return this; } String.method('deentityfy', function(){ var entity = { quot: '"', lt: '<', gt: '>' }; return function(){ return this.replace(/&([^&;]+);/g, function(a,b){ var r = entity[b]; return typeof r === 'string' ? r : a; } ) } }()) '<">'.deentityfy();
模塊模式的通常形式是:一個定義了私有變量和函數的函數,利用閉包建立能夠訪問私有變量和函數的特權函數,最後返回這個特權函數,或者把它們保存到一個能夠訪問到的地方。
使用模塊模式能夠徹底摒棄全局變量的使用,它促進了信息隱藏和其餘優秀的設計實踐。
對於應用程序的封裝,或者構造其餘單例對象,模塊模式很是有效。
模塊模式能夠用來產生安全的對象,假定咱們想要構造一個用來產生序列號的對象:
var serial_maker = function(){ //返回一個用來產生惟一字符串的對象 //惟一字符串由兩部分組成,前綴+序列號 //該對象包含一個設置前綴的方法,一個設置序列號的方法 // 和一個產生惟一字符串的gensym方法 var prefix = ''; var seq = 0; return { set_prefix: function(p){ prefix = String(p); }, set_seq: function(s){ seq = s; }, gensym: function(){ var result = prefix + seq; seq += 1; return result; } } } var seqer = serial_maker(); seqer.set_prefix('Q'); seqer.set_seq(1000); var unique = seqer.gensym();
seqer包含的方法沒有用到this或that,所以沒有辦法損害seqer。
級聯
有一些方法沒有返回值,若是讓這些方法返回this而不是undefined,就能夠啓用級聯。
如:
getElement('myBoxDiv') .move(350,150) .width(100) .height(100)
柯里化
函數也是值,從而咱們能夠用更有趣的方式操做函數值。柯里化容許咱們把函數與傳遞給它的函數相結合,,產生一個新的函數。
Function.method('curry', function(){ var slice = Array.prototype.slice, args = slice.apply(arguments), that = this; return function(){ return that.apply(null, args.concat(slice.apply(arguments))) } }) function add(a,b){ return a + b; } var add1 = add.curry(1); add1(6); // 7
第5章
JavaScript是一門基於原型的語言,這意味着對象直接從其餘對象繼承。
僞類
JavaScript不直接讓對象從其餘對象繼承,反而插入了一個多餘的間接層:經過構造器函數產生對象。
當一個函數對象被建立時,Function構造器產生的函數對象會運行相似這樣的一些代碼:
this.prototype = {constructor: this};
新函數對象被賦予一個prototype屬性,它的值是一個包含constructor屬性且屬性值爲該函數的對象。
這個prototype對象是存放繼承特徵的地方。由於JavaScript語言沒有提供一種方法去肯定哪一個杉樹是打算用來作構造器的,因此每一個函數都會獲得一個prototype對象。
採用構造器調用模式,用new前綴去調用一個函數,函數執行的方式會被修改。
若是new運算符是一個方法而不是一個運算符,可能會像這樣執行:
Function.method('new',function(){ //建立一個新對象,它繼承自構造器函數的原型對象 var that = Object.create(this.prototype); //調用構造器函數,綁定this到新對象上 var other = this.apply(that, arguments); //若是它的返回值不是一個對象,就返回該對象 return (typeof other === 'object' && other) || that; })
定義一個構造器並擴充它的原型:
var Mammal = function(name){ this.name = name; } Mammal.prototype.get_name = function(){ return this.name; } Mammal.prototype.says = function(){ return this.sayings || ''; }
如今能夠構造一個實例:
var myMammal = new Mammal('Herb the Mammal'); var name = myMammal.get_name();
構造另外一個僞類來繼承Mamal,這是經過定義它的constructor函數並替換它的prototype爲一個Mammal的實例。
var Cat = function(name){ this.name = name; this.saying = 'meow'; } //替換Cat.prototype爲一個新的Mammal實例 Cat.prototype = new Mammal(); //擴充新原型對象,增長purr和get_name方法 Cat.prototype.purr = function(n){ var i, s = ''; for(i = 0; i < n; i++){ if(s){ s += '-'; } s += 'r'; } return s; } Cat.prototype.get_name = function(){ return this.says() + ' ' + this.name + ' ' + this.says(); } var myCat = new Cat('Herny'); var says = myCat.says(); var purr = myCat.purr(5); var name = myCat.get_name();
僞類模式本意是想向面向對象靠攏,但它看起來格格不入。咱們能夠隱藏一些醜陋的細節,經過使用method方法來定義一個inherits方法實現。
Function.method('inherits', function(Parent){ this.prototype = new Parent(); return this; })
這裏,inherits和method方法都返回this,能夠採用級聯的形式編程。
如今能夠只用一行語句構造咱們的cat對象。
var Cat = function(name){ this.name = name; this.saying = 'memo' } .inherits(Mammal) .method('purr', function(n){ var i, s = ''; for(i = 0; i < n; i++) if(s){ s += '-'; } s += 'r'; } return s; }) .method('get_name',function(){ return this.says() + ' ' + this.name + ' ' + this.says(); })
原型
先用對象字面量去構造一個有用的對象:
var myMammal = { name: 'Herb the Mammal', get_name: function(){ return this.name; }, says: function(){ return this.saying || ''; } }
一旦有了一個想要的對象,就能夠利用Object.create方法構造出更多的實例。
var myCat = Object.create(myMammal); myCat.get_name
函數化
應用模塊模式來保護私有變量。
咱們從構造一個生成對象開始,以小寫字母開頭來命名它,由於它並不須要使用new前綴。
該函數包括4個步驟:
一、建立一個新對象
二、有選擇地定義私有實例變量和方法
三、給這個新對象擴充方法
四、返回那個新對象
var constructor = function(spec , my){ var that,其餘的私有變量; my = my || {}; 把共享的變量和函數添加到my中 that = 一個新對象 添加給that特權方法 return that; }
把這個模式應用到mammal中。
var mamal = funtion(spec){ var that = {}; that.get_name = function(){ return spec.name; }; that.says = function(){ return spec.saying || ''; } return that; } var myMammal = mammal({name: 'Herb'})
函數化模式還給咱們提供了一個處理父類的方法。
咱們會構造一個superior方法,它取得一個方法名並返回調用那個方法的函數。該函數會調用原來的方法,儘管屬性已經變化了。
Object.method('superior', function(){ var that = this,method = that[name]; return function(){ return method.apply(that, arguments); } })
第6章
數組字面量
var empty= []; var numbers = [ 'zero','one' ]
對象字面量:
var numbers_object = { '0': 'zero', '1': 'one' }
明顯的差別:
numbers繼承自Array.prototype
numbers_object繼承自Object.prototype
長度
每一個數組都有一個length屬性。
若是用大於或等於當前length的數字做爲下標來存儲一個元素,那麼length值會被增大,不會數組越界。
刪除
因爲JavaScript的數組其實就是對象,因此delete運算符能夠用來從數組中移除元素。
delete numbers[2].//殺出以後就是undefined
JavaScript數組有一個splice方法。它能夠對數組作個手術,刪除一些元素並將它們替換爲其餘的元素。
numbers.splice(2,1); //第1個參數是數組中的1個序號,第2個參數是要刪除的元素個數,任何額外的參數會在序號那個點的位置被插入到數組中。
枚舉
for能夠避免for..in帶來的問題
var i; for(i = 0; i < myArray.length; i++){ myArray[i] }
容易混淆的地方
當屬性名是小而連續的整數時,應該使用數組,不然,使用對象。
判斷一個對象是不是數組:
var is_array = function(value){ return Object.prototype.toString.apply(value) === '[object Array]' }
方法
JavaScript提供了一套可用的方法,是被存儲在Array.prototype中的函數。
給array增長一個方法,容許咱們對數組進行計算。
Array.method('reduce', function(f,value){ var i; for(i = 0; i < this.length; i++){ value = f(this[i], value) } return value; }) //建立一個數組 var data = [4,8,15,16]; //定義兩個簡單的函數 var add = fucntion(a, b){ return a+b; } var mult = function(a, b){ return a * b; } var sum = data.reduce(add, 0); var product = data.reduce(mult, 1);
由於數組自己就是對象,因此能夠直接給一個單獨的數組添加方法:
//給data數組添加一個total方法 data.total = function(){ return this.reduce(add, 0) } total = data.total;
指定初始值
Array.dim(a,b)建立a個b元素
Array.dim = function(dimension, initial){ var a = [],i; for(i = 0; i < dimension; i++){ a[i] = initial; } return a; } var myArray = Array.dim(10,0);
構造一個矩陣
Array.matrix = function(m, n, initial){ var a, i, j, mat = []; for(i = 0;i < m; i++){ a = []; for(j = 0;j < n; j++){ a[j] = initial; } mat[i] = a; } return mat; }
構造一個單位矩陣
Array.identity = function(n){ var i,mat = Array.matrix(n,n,0); for(i = 0;i < n; i++){ mat[i][i] = 1; } return mat; } myMatrix = Array.identity(4); myMatrix[3][3]; // 1
第7章 正則表達式