首發于:segmentfault《JavaScript語言精粹 修訂版》 讀書筆記
之前看到這篇文章,前端網老姚淺談:怎么學JavaScript?,說到怎么學習JavaScript,那就是看書、分析源碼。
10本書讀2遍的好處,應該大于一本書讀20遍。
看書主動學習,看視頻是被動學習。
看書和分析源碼的時機。但已經工作一年半載時,正是提高的好時候,此時可以去看書了。全面系統的梳理知識點,掃清自己的盲區。如果只是靠項目經驗是不夠的,通過項目來學習,那>肯定是必須的,工作本身就是一個學習的過程。
怎么把一本書看完呢?很簡單,敲。文字加代碼都敲。
比較認同老姚的說法。去年畢業到現在,我也算是工作一年了,是時候看書查缺補漏了。
于是我就先把這本薄的經典書《JavaScript語言精粹 修訂版》豆瓣讀書本書簡介(總共10章,除去附錄,才100頁),讀完并記錄了一些筆記。基本算是摘抄書本的,自己聯想到了一些知識和資料也擴展了一下。總體寫下來近一萬字。讀書筆記還可以分享給別人看。回顧時,書不在身邊還可以看看自己的筆記。想想這類經典書記一遍動手敲一遍也是很值得的。不過這讀書筆記中可能會有一些錯別字,閱讀時如果發現歡迎指正。
第1章 精華
大多數語言都有精華和糟粕。JavaScript
令人詭異的事情是,在對這門語言沒有的太多了解,甚至對編程都沒有太多了解的情況下,你也能用它來完成工作。
看到這里不禁想起:
張鑫旭大牛在《我對知乎前端相關問題的十問十答》
非計算機專業背景學習JS要點有這一條:
所有繼承和原型相關內容跳過,注意,是跳過,不要看!沒有這些JS一樣可以活得很好,你的日常工作一樣玩得飛起,當然,你沒忍住看了相關知識也沒關系,因為你會發現自己看不懂的;
JavaScript
的函數是(主要)基于詞法作用域的頂級對象。
譯注:
JavaScript
中的函數是根據詞法來劃分作用域的,而不是動態劃分作用域的。具體內容參見《JavaScript
權威指南》中譯第5版相關章節“8.8.1 詞法作用域”。
JavaScript有非常強大的對象字面量表示法。這種表示法是JSON的靈感來源。
原型繼承是JavaScript中一個有爭議的特性。
《ECMAScript
編程語言》第3版定義了JavaScript
的標準。
ES3標準
擴展:顏海鏡大牛整理的ES3中文版
顏海鏡大牛整理的ES5中文版
W3c ES5中文版
阮一峰大牛的書籍《ES6標準入門2》
更多內容可參見這篇文章:ECMAScript 2018 標準導讀
一個簡單的例子:
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
}
書中貫徹始終都會用到這個method
方案,作者將會在第4章解釋它。
第2章 語法
本章主要用鐵路圖(語法圖)表示語法。
主要有:空白、標識符、數字、字符串、語句、表達式、字面量、函數。
typeof
運算符產生的值有'number'
, 'string'
,'boolean'
,'undefined'
,'function'
,'object'
。如果運算數是一個數組或者是null
,那么結果是'object'
,這其實是不對的。
第3章 對象
JavaScript
簡單數據類型包括數字、字符串、布爾值,null
值和undefined
值。其他所有值都是對象。
數組、字符串和布爾值“貌似”對象,因為它們擁有方法(包裝對象),但它們是不可變的。
對象是屬性的容器,其中每個屬性都擁有名字和值。屬性名可以是包括空字符串在內的所有字符串,屬性值可以是除了undefined
值之外的任何值。
JavaScript
包含一種原型鏈的特性,允許對象繼承到另一個對象的屬性。正確地使用它能減少對象初始化時的消耗的時間和內存。
檢索
.
,[]
兩種檢索方式,推薦點.
表示法。
嘗試重undefined
的成員屬性中取值將會導致TypeError
異常,這時可以通過&&
來避免錯誤。
更新
如果屬性名已經存在對象里。那么屬性的值會被替換。如果之前沒有擁有那個屬性名,那么該屬性將被擴充到對象中。
引用
對象通過引用來傳遞。它們永遠不會被復制。
原型
所有通過對象字面量創建的對象都鏈接到Object.prototype
。
創建新對象時,可以選擇某個對象作為它的原型。
if (typeof Object.create !== 'function') {
Object.create = function(o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
原型連接只有在檢索值的時候才被用到。如果嘗試去獲取對象的某個屬性值,但對象沒有此屬性名,那么JavaScript
會試著從原型對象中獲取屬性值。如果那個原型對象也沒有該屬性,那么再從它的原型中尋找,依此類推,直到該過程最后達到終點Object.prototype
。如果想要的屬性完全不存在原型鏈中,那么結果就是 undefined
值。這個過程稱為委托。
原型關系是一種動態的關系。
反射
原型鏈上的所有屬性都會產生值。有兩種方案可以處理掉對象上不需要的屬性。
①程序檢查時丟棄值為函數的屬性。但有可能有些值確實是函數,所以該方法不可靠。
②使用hasOwnProperty
方法,如果是對象擁有獨有的屬性,則返回true
。該方法不會檢查原型鏈。
枚舉
① for in
可以遍歷一個對象中所有的屬性名。但包含函數和一些不關心的原型中屬性。而且順序不確定,可以用 hasOwnProperty
方法和typeof
排除函數。
②for
循環不會出現for in
那些情況。
刪除
delete運算符可以用來刪除對象的屬性。
減少全局變量的污染
可以把全局性的資源納入一個名稱空間之下。這樣做能減少沖突。
第4章 函數
函數用于①代碼復用②信息隱藏③組合調用。一般來說,所謂編程,就是將一組需求分節成一組函數與數據結構的技能。
JavaScript
的函數就是對象。
函數對象連接到Function.prototype
(該原型對象本身連接到Object.prototype
)。
每個函數在創建時會附加兩個隱藏屬性,函數的上下文和實現函數行為的代碼。
每個函數對象在創建時也隨配有一個prototype
屬性。它的值是一個擁有constructor
屬性且值為該函數的對象。
函數字面量
函數字面量包括4個部分。①保留字function
②函數名,可以省略,③一組參數④一組語句。
函數字面量可以出現在任何允許表達式出現的地方。一個內部函數除了可以訪問自己的參數和變量,同時也可以自由訪問把它嵌套在其中的父函數的參數和變量。通過函數字面量創建的函數對象包含一個連接到外部上下文的連接。這被稱為閉包。
調用
除了聲明時定義的形式參數,每一個函數還接收兩個附加的參數:this
和argument
。在JavaScript
中一共有四種調用模式。①方法調用模式,②函數調用模式③構造器調用模式④apply
調用模式。
(this
指向問題一直困擾很多人。我一般是這樣記的,誰調用this
就指向誰。)
方法調用模式
對象的方法執行,this
指向該對象。比如:
var myObj = {
value: 0,
showValue: function() {
console.log('value:', this.value);
}
}
myObj.showValue(); // value: 0
函數調用模式
var add = function(a,b) {
return a + b;
}
add(3,4); //7
window.add(3,4); //7
// 這種this被綁定到全局對象(window)。
// 可以理解是window.add(3,4);
有種簡單的辦法就是var that = this;
把this
存儲下。
例:
var myObj = {
value: 0,
age: 20,
showValue: function() {
console.log('value:',this.value);
var that = this;
var showAge = function() {
// window上沒有age,所以是undefined
console.log('這里的this是window ---age:', this.age); // undefined
console.log('age:', that.age); // 20
}
showAge();
}
}
myObj.showValue(); // 0, undefined,
構造器調用模式
JavaScript
是一門基于原型繼承的語言。
如果在一個函數前面帶上new
來調用。那么背地利將會創建一個連接到該函數的prototype
成員的新對象,同時this會被綁定到那個新對象上。
new
前綴也會改變return
語句的行為。
例:
var Quo = function (string) {
this.status = string;
}
Quo.prototype.get_status = function () {
return this.status;
}
var myQuo = new Quo('confused'); // 'confused'
一個函數,如果創建的目的就是希望結合new
前綴來調用。那么它就被稱為構造器函數。按照約定,它們保存在以大寫函數命名的變量里。如果調用構造器函數時沒有在前面加上new
,可能會發生非常糟糕的事情,既沒有編譯時的警告,也沒有運行時廣告,所以大寫約定非常重要。
作者不推薦這種形式的構造器函數。有更好的替代方式。
Apply調用模式
JavaScript
是一門函數式的面向對象編程語言,所以對象可以擁有方法。
apply
方法讓我們構建一個參數數組傳遞給調用函數,它也允許我們選擇this
的值。
參數
arguments
,雖然擁有length
屬性,但不是真正的數組。而是類似數組(array-like
)的對象。
返回
return
可用來是函數提前返回。當return
被執行時,函數立即返回而不再執行余下的語句。
一個函數總會返回一個值,如果沒指定,那就是返回undefined
值。
如果函數調用時在前面加上了new
前綴,且返回值不是一個對象,則返回this
(該新對象)。
異常
JavaScript
提供了一套異常處理機制。
throw
語句和try catch
,try catch
中finally
是可選的。
擴展類型的功能
JavaScript
允許給語言的基本類型擴充功能。在第3章中我們已經看到,可以通過Object.prototype
添加方法,可以讓該方法對所有對象都可用。這樣的方式對函數、數組、字符串、數字、正則表達式和布爾值同樣適用。
例如:
Function.prototype.method = function () {
this.prototype[name] = func;
return this;
}
基本類型的原型是公用結構,所以在類庫混用時務必小心。一個保險的做法就是只在確認沒有該方法時才添加它。
Function.prototype.methods = function(name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
遞歸
遞歸函數就是會直接或間接地調用自身的一種函數。遞歸是一種強大的編程技術,遞歸是用一般的方式去解決每一個子問題。書中舉了一個漢諾塔的例子,是程序設計中經典遞歸問題。詳細說明可以參見 百度百科“漢諾塔”詞條。
一些語言提供了尾遞歸優化。尾遞歸是一種在函數的最后執行調用語句的特殊形式的遞歸。參見Tail call。 ES6版本擴展了尾遞歸。參見阮一峰老師的《ES6標準入門》中的尾調用優化
作用域
在編程語言中,作用域控制著變量與參數的可見性和聲明周期。
書中指出當前JavaScript
沒有塊級作用域。因為沒有塊級作用域,所以最好的做法是在函數體的頂部聲明函數中可能用到的所有變量。不過ES6
擴展了有塊級作用域。
閉包
作用域的好處是內部函數可以訪問定義它們的外部函數的參數和變量(除了this
和arguments
)。
例子:
<ul class="list">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
// 點擊相應節點時,顯示對應的序號。可以使用閉包來解決。
var add_the_handlers = function() {
var helper = function(i) {
return function(e) {
alert(i);
}
}
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = helper(i);
}
}
// 擴展 另外可以用let i = 0,或者把nodes類數組轉成數組等方案實現。
// 閉包特性:1、函數內再嵌套函數,2、內部函數可以調用外層的參數和變量,3、參數和變量不會被垃圾回收機制回收。
// 閉包優點 靈活和方便,便于封裝。缺點:空間浪費、內存泄露、性能消耗。
回調
發起異步請求,提供一個當服務器響應到達時隨即出發的回調函數。異步函數立即返回,這樣客戶端就不會被阻塞。
模塊
我們可以使用函數和閉包來構造模塊。模塊是一個提供接口卻隱藏狀態與實現的函數或對象。
舉例:給String
添加一個deentityify
方法。它的任務是尋找字符串中的HTML
字符實體并把它們替換成對應的字符。
String.method('deentityify', function () {
// 字符實體表。它映射字符實體的名字到對應的字符。
var entity = {
quot: '"',
lt: '<',
gt: '>'
};
// 返回 deentityify方法
return function () {
return this.replace(/&([^&;]+);)/g,
function (a,b) {
var r = entity[b];
return typeof r === 'string'? r : a;
}
};
}());
模塊模式利用了函數作用域和閉包來創建被綁定對象與私有成員的關聯,在上面例子中,只有deentityify
方法有權訪問字符實體表這個數據對象。
模塊模式的一般形式是:一個定義了私有變量和函數的函數;利用閉包創建可以訪問私有變量和函數的特權函數;最后返回這個特權函數,或者把它們保存到一個可以訪問的地方。
使用模塊模式就可以摒棄全局變量的使用。它促進了信息隱藏和其他優秀的設計實踐。對于應用程序的封裝,或者構造其他單例對象,模塊模式非常有效。
單例譯注
模塊模式通常結合單例模式使用。JavaScript
的單例就是用對象字面量表示法創建的對象,對象的屬性值可以是數值或函數,并且屬性值在該對象的生命周期中不會發生變化。更多內容參見:單例模式
級聯
有一些方法沒有返回值。如果我們讓這些方法返回this
而不是undefined
,就可以啟用級聯。
在一個級聯中,我們可以在單獨一條語句中依次調用同一個對象的很多方法。比如jQuery
獲取元素、操作樣式、添加事件、添加動畫等。
柯里化
柯里化,是把多參數函數轉換為一系列單參數函數并進行調用的技術。更多詳情可參見:柯里化
函數也是值。柯里化允許我們把函數與傳遞給它的參數相結合,產生一個新的函數。
var add1 = add.curry(1);
document.writeln(add1(6));
JavaScript并沒有curry方法,但可以擴展該功能。
arguments不是真正的數組,所以使用了Array.prototype.slice方法。
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)));
}
});
記憶
函數可以將先前操作的結果記錄在某個對象里,從而避免無謂的重復運算。這種優化稱作記憶。
比如說,我們想要一個遞歸函數來計算Fibonacci
(斐波那契)數列,它的特點是,前面相鄰兩項之和等于后一項的值。更多參考:斐波那契。最前面兩個數字是0和1。
var fibonacci = function() {
return n < 2? n : fibonacci(n-1) + fibonacci(n-2);
}
這樣雖然能完成工作,但它做了很多無謂的工作。
構造一個帶有記憶功能的函數:
var memoizer = function(mome, formula) {
var recur = function(n) {
var result = meno[n];
if (typeof result !== 'number') {
result = formula(recur, n);
meno[n] = result;
}
return result;
};
return recur;
}
再用這個memoizer
函數來定義fibonacci
函數,提供其初始的memo
數組和formula
函數。
var fibonacci = memoizer([0,1],function(recur, n){
return recur(n-1) + recur (n-2);
})
極大的減少了我們的工作量。例如要產生一個記憶的階乘函數,只需要提供基本的階乘公式即可:
var factorial = meoizer([1,1], function(recur, n){
return n * recur(n-1);
});
第5章 繼承
偽類
JavaScript
的原型存在諸多矛盾。它不直接讓對象從其他對象繼承,反而插入了一個多余的間接層:通過構造器函數產生對象。
Function
構造器產生的函數對象會運行類似這樣的一些代碼:
this.prototype = {constructor:this}
新函數對象被賦予一個prototype
屬性,這個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;
});
所有構造器函數都約定命名成大寫字母。一種更好的備選方案就是根本不使用new
。
對象說明符
就是指傳多個參數時,可以直接傳遞一個對象。
原型
可以用Object.create
方法構造出更多實例來。
函數化
迄今為止,我們所看到的繼承模式的一個弱點就是沒法保護隱私。對象的所有屬性都是可見的。我們無法得到私有變量和私有函數。
幸運的是,我們有一個更好的選擇,那就是應用模塊模式。
我們從構造一個生成對象的函數開始。我們以小寫字母開頭來命名。
該函數包括以下四個步驟
1、創建一個新對象。
2、有選擇地私有實例變量和方法。
3、給這個新對象擴充方法。
4、返回那個新對象。
以下是一個函數化構造器的偽代碼模板
var constructor = function (spec, my) {
var that, 其他的私有實例變量;
my = my || {};
把共享的變量和函數添加到my中
that = 一個新對象
添加給 that 的特權方法
return that;
}
函數化模式有很大的靈活性。它相比偽類模式不僅帶來的工作更少,還讓我們更好的封裝和信息隱藏,以及訪問父類方法的能力。
部件
我們可以從一套部件中把對象組裝出來。
第6章 數組
數組是一段線性分配的內存,它通過整數計算偏移并訪問其中的元素。
數組是一種性能出色的數據結構。不幸的是,JavaScript
沒有像此類數組一樣的數據結構。
數組字面量
對象字面量
數組繼承了Array.prototype
大量有用的方法。而對象字面量是繼承自Object.prototype
。
數組有length
屬性,而對象沒有。
長度
每個數組都有一個length
屬性。
可以直接設置length
的值。設置更大的length不會給數組分配更多的空間,而設小導致所有下標大于等于新length的屬性被刪除。
var arr = [1,2,3];
arr.length = 1;
console.log(arr) // [1]
也可以通過length
來通過添加值
var arr = [1,2,3];
arr[arr.length] = 4;
console.log(arr) // [1,2,3,4]
有時用push
方法更方便。
刪除
由于JavaScript
的數組也是對象,所以delete
運算符可以用來從數組中移出元素。移除后,長度不變,原位置上變成了undefined
。
可以使用Array.prototype.splice
方法刪除數組。
枚舉
JS數組就是對象,所以for in
語句可以用來遍歷數據的所有屬性。
不過,for in
無法保證屬性順序。并且可能從原型鏈中得到意外的屬性。
for
循環可以避免以上問題。
容易混淆的地方
typeof [] === "object"; // true
typeof {} === "object"; // true
識別是否是數組。
// 方法一、
var is_array = function (value) {
return value && typeof value === 'object' && value.constructor === Array;
};
但它在識別從不同窗口(window)或幀(frame)里的構造的數組時會失敗。
有一個更好的方式:
// 方法二、
var is_array = function (value) {
return Object.prototype.toString.apply(value) === '[object Array]';
}
擴展:
ES5 提供了Array.isArray()的方法。不過兼容性是IE9+。
要做到兼容,可以用如下方法。MDN上提供的。MDN Array.isArray
// 方法三、
if (!Array.isArray){
Array.isArray = function(arg){
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
var arr = [];
// 方法四
arr instanceof Array;
// 方法五
Array.prototype.isPrototypeOf(arr);
// 方法六
Object.getPrototypeOf(arr) === Array.prototype;
方法四、instanceof 運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性。
方法五、isPrototypeOf() 方法用于測試一個對象是否存在于另一個對象的原型鏈上。
方法六、Object.getPrototypeOf() 方法返回指定對象的原型(即, 內部[[Prototype]]屬性的值)。
小結:除了方法二、三外,面對復雜的環境,其他的都不能準確的判斷是否是數組。
方法
JavaScript
提供了一套數組可用的方法,這些方法是被存儲在Array.prototype中的函數。
Object.prototype
是可以擴充的。
Array.prototype
也是可以擴充的。
ES5
中提供的Object.create
方法。這方法用在數組是沒有意義的,因為它產生的是一個對象,而不是一個數組,產生的對象將繼承這個數組的值和方法,但它沒有length
特殊屬性。
指定初始值
JavaScript
的數組通常不會預設值。書中寫了一個循環來擴展,生成初始值。
擴展:ES6
中提供了fill
來填充。比如:
['a','b','c'].fill(0); // [0,0,0]
new Array(3).fill(0); // [0,0,0]
// fill方法還可以接受第二、第三個參數,用于指定填充的起始位置和結束位置(不包含)。
new Array(3).fill(0,1,2); // [ ,0, ,] 空位不是undefined。空位沒有任何值。ES6則是明確將空位轉為undefined。
第7章 正則表達式
正則表達式對字符串中的信息實現查找、替換和提取操作。
可處理正則表達式的方法有regexp.exec
、regexp.test
、string.match
、string.search
和string.split
。通常來說,正則相較于等效的字符串處理有著顯著的性能優勢。
一個例子
// 正則表達式必須寫在一行中
var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
var url = "http://www.ora.com:80/goodparts?q#fragment";
var result = parse_url.exec(url);
// ……
依次匹配到的是:
url: 'http://www.ora.com:80/goodparts?q#fragment',
scheme: 'http',
slash: '//'
host: 'www.ora.com'
port:'80'
path:'goodparts'
query: 'q'
hash: 'fragment'
個人擴展:這里推薦 在線測試正則表達式的網站regex101,默認是PHP語言,選擇JavaScript語言。
在線圖形化RegExp工具
MDN RegExp.prototype.exec()
大概解釋下這個正則,
這里的^
起始位置,$
結束位置
()
分組捕獲 ?:
不捕獲
.
表示除換行以外的任意單個字符,對于碼點大于0xFFFF
的Unicode
字符,點(.
)不能識別(ES6
中加u
修飾符才可識別),+
表示一個或多個,*
表示零個或多個,?
表示0
個或一個。[]表示或者,里面符合一個即可。
\d
表示數字0-9
。
不嚴謹的正則表達式是一個常見的安全漏洞的發源地。在執行某些匹配時,嵌套的正則表達式也能導致極其惡劣的性能問題。因此簡單是最好的策略。
再看一個 匹配數字的例子。
var parse_number = /^-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i;
parse_number.test('1'); // true
parse_number.test('number'); // false
parse_number.test('98.6'); // true
parse_number.test('132.21.86.100'); // false
parse_number.test('123.45E-67'); // true
parse_number.test('123.45D-67'); // false
結構
有兩個方法來創建一個RegExp
對象。優先考慮的是正則表達式字面量,還有一種方式是new RegExp('','g')
。
正則表達式標識:g
全局(匹配多次,不同的方法對g
標識的處理防范各不相同),i
忽略大小寫。m
多行
元素
正則表達式分支
|
表示或,也表示分支 比如:
'info'.match(/in|int/) // 匹配成功,["in", index: 0, input: "info"]
正則表達式序列
一個正則表達式序列飽和一個或多個正則表達式因子。每一個因子能選擇是否跟隨一個量詞,這個量詞決定著這個因子被允許出現的次數,若沒指定,這個因子則只匹配一次。
正則表達式因子
\ / [ ] () { } ? + * | . ^ $
正則表達式轉義
\ 表轉義 \f
分頁 \n
換行 \r
回車 \t
制表
\u
允許制定一個 Unicode 字符來表示一個十六進制的常量。
\d
等同于[0-9] \D 取反等同于 [^0-9]
\s
Unicode
空白符一個不完全子集。 \S 與\s相反
\w
[0-9A-Z_a-z]
\W 與其相反 [^0-9A-Z_a-z]
\b
表示 字邊界
\1
表示 分組1所捕獲的文本的一個引用,所以它能被再次匹配。
\2
表示 指向分組2的引用,\3
是表示分組3的引用,以此類推。
正則表達式分組
捕獲型()
非捕獲型?:
向前正向匹配?=
有一個(?=
前綴。它類似于非捕獲類型分組,但在這個組匹配后,文本會倒回到它它開始的地方,實際上并不匹配任何東西。也可以理解為匹配位置。
向后負向匹配
有一個(?!
前綴。它類似于向前正向匹配分組,但只有當它匹配失敗時它才繼續向前進行匹配。這不是一個好的特性。
正則表達式字符集
正則表達式字符集是一種指定一組字符的便利方式。例如,要匹配一個元音字母,(?:a|e|i|o|u)
,可以方便的寫成[aeiou]
。
類提供另外兩個便利:①指定字符范圍
所以,一組由32
個ASCII
的特殊組合,可以寫成[!-\/:-@\[-
{-~]`
②類的取反
取反
[^!-\/:-@\[-`{-~]
正則表達式字符轉義
字符類內部的轉義規則和正則表達式因子的相比稍有不同。下面是在字符類中需要被轉義的特殊字符。
- / [ \ ]
正則表達式量詞
量詞后綴決定正則表達式因子應該被匹配的次數。
{3}
三次
{3,6}
3、4、5、6次
{3,}
3次或更多次
?
等同于{0,1}
,*
等同于{0,}
,+
等同于{1,}
。
第8章 方法
Array
array.concat(item...)
concat
方法產生一個新數組,它包含一份array
的淺復制并把一個或多個參數item
附加在其后。如果item
是數組,那么每個元素分別被添加。后面有和它功能類似的array.push(item...)
方法。
var a = ['a','b','c'];
var b = ['x','y','z'];
var c = a.concat(b, true);
// c => ['a','b','c','x','y','z',true]
擴展: ES6
有更便捷的擴展運算符...
var a = ['a','b','c'];
var b = ['x','y','z'];
var c = [...a,true,...b]; // ["a", "b", "c", true, "x", "y", "z"]
array.join(separator)
join
方法把一個array
構造成一個字符串。
separator
默認值就是逗號','
。
如果你想把大量的字符串片段組裝成一個字符串,把這些片段放在一個數組中,并用join
方法連接起來通常比用+
元素運算符連接起來要快。
譯注:對于
IE6/7
,使用join連接大量字符串效率確實優于加號運算符。但目前主流瀏覽器,包括IE8
以后的版本,都對+
元素運算符連接字符串做了優化,性能已經顯著高于Array.join()
。所以目前大多數情況下,建議首選使用+ 連接字符串。更多參看《高性能網站建設進階指南》中字符串優化相關章節。
array.pop()
pop
方法移除array
中的最后一個元素,并返回這個元素。如果array
為空,則返回undefined
。
var a = ['a','b','c'];
var c = a.pop(); // a 是 ['a','b'] c是 'c'
// pop 可以像這樣實現。
// 這里的 Array.method()在第一章例子中已經定義了,并且貫穿全書。其實就是相當于Array.prototype
Array.method('pop', function () {
return this.splice(this.length-1,1)[0];
});
array.push(item...)
與concat
不同的是,它會修改array,如果參數item
是數組,它會把參數數組作為單個元素整個添加到數組中。并返回這個array
的新長度值。
var a = [1,2,3];
var b = [4,5,6];
var c = a.push(b,true);
// a 是 [1,2,3,[4,5,6],true]
// c 是 5
push
可以像這樣實現:
Array.method('push', function () {
this.splice.apply(
this,
[this.length,0].
concat(Array.prototype.slice.apply(arguments)));
return this.length;
});
array.reverse()
reverse
反轉array
元素順序,并返回array
本身。
var a = [1,2,3];
var b = a.reverse();
// a 和 b都是 [3,2,1]
array.shift()
shift
移除array
的第一個元素并返回這個元素。如果array
為空,則返回undefined
。shift
通常比pop
慢的多。
var a = [1,2,3];
var c = a.shift(); // a 是[2,3] , c 是1
shift
可以這樣實現:
Array.method('shift', function(){
return this.splice(0,1)[0];
});
array.slice(start[, end])
slice
是對array
中的一段做淺復制。end
是可選的。默認是array.length
,如果兩個參數任何一個是負數,array.length
會和相加。如果start
大于array.length
,獲得一個[]
,字符串也有Sting.slice
這個同名方法。
array.sort
默認不能給一組數字排序。默認把要被排序的元素都視為字符串。
幸運的是,可以使用自己的比較函數替換默認的比較函數。
比較函數應該接受兩個參數,并且如果這兩個參數相等則返回0,如果第1個參數應該排列在前面,則返回一個負數,如果第二個參數應該排列在前面,則返回一個正數。
sort
方法是不穩定的。JavaScript
的sort
方法的穩定性根據不同瀏覽器的實現而不一致。
可參見MDN sort
array.splice(start, deleteCount,item...)
splice
方法從array中移除一個或多個元素,并用新的item
替換它們。
// splice 可以像這樣實現
Array.method('splice',function (start, deleteCount) {
var max = Math.max,
min = Math.min,
delta,
element,
insertCount = max(arguments.length - 2, 0),
k = 0,
len = this.length,
new_len,
result = [],
shift_count;
start = start || 0;
if (start < 0) {
start += len;
}
start = max(min(start, len), 0);
deleteCount = max(min(typeof deleteCount === 'number' ? deleteCount : len, len - start), 0);
delta = insertCount - deleteCount;
new_len = len + delta;
while (k < deleteCount) {
element = this[start + k];
if (element !== undefined) {
result[k] = element;
}
k += 1;
}
shift_count = len - start - deleteCount;
if (delta < 0) {
k = start + insertCount;
while (shift_count) {
this[k] = this[k - delta];
k += 1;
shift_count -= 1;
}
this.length = new_len;
} else if (delta > 0) {
k = 1;
while (shift_count) {
this[new_len - k] = this[len - k];
k += 1;
shift_count -= 1;
}
this.length = new_len;
}
for (k = 0; k < insertCount; k += 1) {
this[start + k] = arguments[k + 2];
}
return result;
});
array.unshift(item...)
unshift
方法像push
方法一樣,不過是用于把元素添加到數組的開始部分,返回新array
的length
。
// unshift 可以像這樣實現
Array.method('unshift', function(){
this.splice.apply(this,
[0,0].concat(Array.prototype.slice.apply(arguments)));
return this.length;
});
Function
function.apply(thisArg,argArray)
apply
方法調用function
,傳遞一個會被綁定到this
上的對象和一個可選的數組作為參數。
Number
number.toExponential(fractionDigits)
toExponential
方法 把這個number
轉換成一個指數形式的字符串。可選參數控制其小數點后的數字位數。它的值必須在0~20
。
number.toFixed(fractionDigits)
toFixed
方法把這個number轉換成一個十進制數形式的字符串。可選參數控制其小數點后的數字位數。它的值必須在0~20。
number.toPrecision(precision)
toPrecision
方法把這個number
轉換成一個十進制數形式的字符串。可選參數控制數字的精度。它的值必須在0~21
。
number.toString(radix)
把number
轉換成字符串。可選參數控制基數。它的值必須是2~36
。默認的radix
是以10
為基數的。radix
參數最常用的是整數,但是它可以用任意的數字。
Object
object.hasOwnProperty(name)
如果這個object
包含名為name
的屬性,那么返回true
。原型鏈中的同名方法不會被檢測。這個方法對name
就是“hasOwnProperty”
時不起作用。
RegExp
regexp.exec(string)
exec
是正則中最強大(和最慢)的方法。
如果成功匹配,它會返回一個數組。下標為0 的元素包含正則匹配的子字符串。下標為1的則是分組1捕獲的文本。下標為2的則是分組2捕獲的文本。以此類推。如果匹配失敗則返回null
。
regexp.test(string)
test
是最簡單(和最快)的方法。匹配成功,返回true
,否則返回false
。不要對這個方法使用g
標識。
比如:
var reg = /\w+/g;
reg.test('ab'); // true
// 再執行一遍就是false了。
reg.test('ab'); // false
// 再執行一遍就是true了。
reg.test('ab'); // true
// 再執行一遍又是false了,如此反復,所以用g標識后,看起來很詭異。應該每次匹配開始位置變了。
reg.test('ab'); // false
test
可以像這樣實現:
RegExp.method('test', function(string){
return this.exec(string) !== null;
});
String
string.charAt(pos)
返回在string
中的pos
位置處的字符。
string.charCodeAt(pos)
與charAt
一樣,不過返回整數形式表示字符碼位。
string.concat(string)
很少用,用+
號運算符更方便。
string.indexOf(searchString,position)
在string
中查找第一個參數,如果被找到返回該字符的位置,否則返回-1
。position
可設置指定位置開始查找。
string.lastIndexOf(searchString,position)
lastIndexOf
方法和indexOf
方法類似,不過它是從末尾開始查找,不是從頭開始。
string.localeCompare(that)
比較兩個字符串。類似于array.sort
。
string.match(regexp)
如果沒有g
標識,那么調用string.match(regexp)
和調用regexp.exec(string)
結果相同。如果帶有g
標識,那么它生成一個包含所有匹配(除捕獲分組之外)的數組。
string.replace(searchValue,replaceValue)
對string
進行查找和替換操作,并返回一個新的字符串。參數searchvalue
可以是一個字符串也可以是一個正則表達式對象。參數replaceValue
可以是一個字符串或一個函數。
string.search(regexp)
和indexOf
類似,不過它接收正則為參數。
string.slice(start, end)
slice
方法復制string
的一部分來構造一個新的字符串。如果start
參數是負數,它將與string.length
相加。end
參數是可選的。
string.split(separator,limit)
把string
分割成片段來創建一個字符串數組。可選參數limit
可以限制分割的片段數量。separator
參數可以是字符串或者正則。
string.substring(start,end)
與slice
方法一樣,不過它不能處理負數參數。
string.toLocaleLowerCase()
它使用本地化的規則把這個string
中的字母轉換成小寫格式。這個方法主要用在土耳其語上。
string.toLocaleUpperCase()
它使用本地化的規則把這個string
中的字母轉換成大寫格式。這個方法主要用在土耳其語上。
string.toLowerCase()
返回新字符串,所有字母轉成小寫格式。
string.toUpperCase()
返回新字符串,所有字母轉成大寫格式。
String.fromCharCode(char...)
根據一串數字編碼返回一個字符串。
var a = String.fromCharCode(67,97,116) // a是'Cat'
第9章 代碼風格
這一章中,簡短的說了一些代碼風格。事實證明代碼風格在編程中是很重要的。
第10章 優美的特性
精簡的JavaScript
里都是好東西。
包括:1、函數是頂級對象;2、基于原型繼承的動態作用域;3、對象字面量和數組字面量。
到此,讀書筆記已完結。