首發(fā)于:segmentfault《JavaScript語言精粹 修訂版》 讀書筆記
之前看到這篇文章,前端網(wǎng)老姚淺談:怎么學JavaScript?最筒,說到怎么學習JavaScript绸栅,那就是看書劫哼、分析源碼。
10本書讀2遍的好處,應該大于一本書讀20遍。
看書主動學習樱溉,看視頻是被動學習。
看書和分析源碼的時機纬凤。但已經(jīng)工作一年半載時福贞,正是提高的好時候,此時可以去看書了停士。全面系統(tǒng)的梳理知識點挖帘,掃清自己的盲區(qū)。如果只是靠項目經(jīng)驗是不夠的恋技,通過項目來學習拇舀,那>肯定是必須的,工作本身就是一個學習的過程蜻底。
怎么把一本書看完呢骄崩?很簡單,敲。文字加代碼都敲刁赖。
比較認同老姚的說法搁痛。去年畢業(yè)到現(xiàn)在长搀,我也算是工作一年了宇弛,是時候看書查缺補漏了。
于是我就先把這本薄的經(jīng)典書《JavaScript語言精粹 修訂版》豆瓣讀書本書簡介(總共10章源请,除去附錄枪芒,才100頁),讀完并記錄了一些筆記谁尸【俗伲基本算是摘抄書本的,自己聯(lián)想到了一些知識和資料也擴展了一下良蛮〕槁担總體寫下來近一萬字。讀書筆記還可以分享給別人看决瞳』踽悖回顧時,書不在身邊還可以看看自己的筆記皮胡。想想這類經(jīng)典書記一遍動手敲一遍也是很值得的痴颊。不過這讀書筆記中可能會有一些錯別字,閱讀時如果發(fā)現(xiàn)歡迎指正屡贺。
第1章 精華
大多數(shù)語言都有精華和糟粕蠢棱。JavaScript
令人詭異的事情是,在對這門語言沒有的太多了解甩栈,甚至對編程都沒有太多了解的情況下泻仙,你也能用它來完成工作。
看到這里不禁想起:
張鑫旭大牛在《我對知乎前端相關(guān)問題的十問十答》
非計算機專業(yè)背景學習JS要點有這一條:
所有繼承和原型相關(guān)內(nèi)容跳過量没,注意玉转,是跳過,不要看允蜈!沒有這些JS一樣可以活得很好冤吨,你的日常工作一樣玩得飛起,當然饶套,你沒忍住看了相關(guān)知識也沒關(guān)系漩蟆,因為你會發(fā)現(xiàn)自己看不懂的;
JavaScript
的函數(shù)是(主要)基于詞法作用域的頂級對象妓蛮。
譯注:
JavaScript
中的函數(shù)是根據(jù)詞法來劃分作用域的怠李,而不是動態(tài)劃分作用域的。具體內(nèi)容參見《JavaScript
權(quán)威指南》中譯第5版相關(guān)章節(jié)“8.8.1 詞法作用域”。
JavaScript有非常強大的對象字面量表示法捺癞。這種表示法是JSON的靈感來源夷蚊。
原型繼承是JavaScript中一個有爭議的特性。
《ECMAScript
編程語言》第3版定義了JavaScript
的標準髓介。
ES3標準
擴展:顏海鏡大牛整理的ES3中文版
顏海鏡大牛整理的ES5中文版
W3c ES5中文版
阮一峰大牛的書籍《ES6標準入門2》
更多內(nèi)容可參見這篇文章:ECMAScript 2018 標準導讀
一個簡單的例子:
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
}
書中貫徹始終都會用到這個method
方案惕鼓,作者將會在第4章解釋它。
第2章 語法
本章主要用鐵路圖(語法圖)表示語法唐础。
主要有:空白箱歧、標識符、數(shù)字一膨、字符串呀邢、語句、表達式豹绪、字面量价淌、函數(shù)。
typeof
運算符產(chǎn)生的值有'number'
, 'string'
,'boolean'
,'undefined'
,'function'
,'object'
瞒津。如果運算數(shù)是一個數(shù)組或者是null
,那么結(jié)果是'object'
,這其實是不對的蝉衣。
第3章 對象
JavaScript
簡單數(shù)據(jù)類型包括數(shù)字、字符串仲智、布爾值买乃,null
值和undefined
值。其他所有值都是對象钓辆。
數(shù)組剪验、字符串和布爾值“貌似”對象,因為它們擁有方法(包裝對象)前联,但它們是不可變的功戚。
對象是屬性的容器,其中每個屬性都擁有名字和值似嗤。屬性名可以是包括空字符串在內(nèi)的所有字符串啸臀,屬性值可以是除了undefined
值之外的任何值。
JavaScript
包含一種原型鏈的特性烁落,允許對象繼承到另一個對象的屬性乘粒。正確地使用它能減少對象初始化時的消耗的時間和內(nèi)存。
檢索
.
,[]
兩種檢索方式伤塌,推薦點.
表示法灯萍。
嘗試重undefined
的成員屬性中取值將會導致TypeError
異常,這時可以通過&&
來避免錯誤每聪。
更新
如果屬性名已經(jīng)存在對象里旦棉。那么屬性的值會被替換齿风。如果之前沒有擁有那個屬性名,那么該屬性將被擴充到對象中绑洛。
引用
對象通過引用來傳遞救斑。它們永遠不會被復制。
原型
所有通過對象字面量創(chuàng)建的對象都鏈接到Object.prototype
真屯。
創(chuàng)建新對象時脸候,可以選擇某個對象作為它的原型。
if (typeof Object.create !== 'function') {
Object.create = function(o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
原型連接只有在檢索值的時候才被用到讨跟。如果嘗試去獲取對象的某個屬性值纪他,但對象沒有此屬性名鄙煤,那么JavaScript
會試著從原型對象中獲取屬性值晾匠。如果那個原型對象也沒有該屬性,那么再從它的原型中尋找梯刚,依此類推凉馆,直到該過程最后達到終點Object.prototype
。如果想要的屬性完全不存在原型鏈中亡资,那么結(jié)果就是 undefined
值澜共。這個過程稱為委托。
原型關(guān)系是一種動態(tài)的關(guān)系锥腻。
反射
原型鏈上的所有屬性都會產(chǎn)生值嗦董。有兩種方案可以處理掉對象上不需要的屬性。
①程序檢查時丟棄值為函數(shù)的屬性瘦黑。但有可能有些值確實是函數(shù)京革,所以該方法不可靠。
②使用hasOwnProperty
方法幸斥,如果是對象擁有獨有的屬性匹摇,則返回true
。該方法不會檢查原型鏈甲葬。
枚舉
① for in
可以遍歷一個對象中所有的屬性名廊勃。但包含函數(shù)和一些不關(guān)心的原型中屬性。而且順序不確定经窖,可以用 hasOwnProperty
方法和typeof
排除函數(shù)坡垫。
②for
循環(huán)不會出現(xiàn)for in
那些情況。
刪除
delete運算符可以用來刪除對象的屬性画侣。
減少全局變量的污染
可以把全局性的資源納入一個名稱空間之下冰悠。這樣做能減少沖突。
第4章 函數(shù)
函數(shù)用于①代碼復用②信息隱藏③組合調(diào)用棉钧。一般來說屿脐,所謂編程涕蚤,就是將一組需求分節(jié)成一組函數(shù)與數(shù)據(jù)結(jié)構(gòu)的技能。
JavaScript
的函數(shù)就是對象的诵。
函數(shù)對象連接到Function.prototype
(該原型對象本身連接到Object.prototype
)万栅。
每個函數(shù)在創(chuàng)建時會附加兩個隱藏屬性,函數(shù)的上下文和實現(xiàn)函數(shù)行為的代碼西疤。
每個函數(shù)對象在創(chuàng)建時也隨配有一個prototype
屬性烦粒。它的值是一個擁有constructor
屬性且值為該函數(shù)的對象。
函數(shù)字面量
函數(shù)字面量包括4個部分代赁。①保留字function
②函數(shù)名扰她,可以省略,③一組參數(shù)④一組語句芭碍。
函數(shù)字面量可以出現(xiàn)在任何允許表達式出現(xiàn)的地方徒役。一個內(nèi)部函數(shù)除了可以訪問自己的參數(shù)和變量,同時也可以自由訪問把它嵌套在其中的父函數(shù)的參數(shù)和變量窖壕。通過函數(shù)字面量創(chuàng)建的函數(shù)對象包含一個連接到外部上下文的連接忧勿。這被稱為閉包。
調(diào)用
除了聲明時定義的形式參數(shù)瞻讽,每一個函數(shù)還接收兩個附加的參數(shù):this
和argument
鸳吸。在JavaScript
中一共有四種調(diào)用模式。①方法調(diào)用模式速勇,②函數(shù)調(diào)用模式③構(gòu)造器調(diào)用模式④apply
調(diào)用模式晌砾。
(this
指向問題一直困擾很多人。我一般是這樣記的烦磁,誰調(diào)用this
就指向誰。)
方法調(diào)用模式
對象的方法執(zhí)行,this
指向該對象个初。比如:
var myObj = {
value: 0,
showValue: function() {
console.log('value:', this.value);
}
}
myObj.showValue(); // value: 0
函數(shù)調(diào)用模式
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,
構(gòu)造器調(diào)用模式
JavaScript
是一門基于原型繼承的語言珍逸。
如果在一個函數(shù)前面帶上new
來調(diào)用逐虚。那么背地利將會創(chuàng)建一個連接到該函數(shù)的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'
一個函數(shù),如果創(chuàng)建的目的就是希望結(jié)合new
前綴來調(diào)用漱病。那么它就被稱為構(gòu)造器函數(shù)买雾。按照約定把曼,它們保存在以大寫函數(shù)命名的變量里。如果調(diào)用構(gòu)造器函數(shù)時沒有在前面加上new
,可能會發(fā)生非常糟糕的事情漓穿,既沒有編譯時的警告嗤军,也沒有運行時廣告,所以大寫約定非常重要晃危。
作者不推薦這種形式的構(gòu)造器函數(shù)叙赚。有更好的替代方式。
Apply調(diào)用模式
JavaScript
是一門函數(shù)式的面向?qū)ο缶幊陶Z言僚饭,所以對象可以擁有方法震叮。
apply
方法讓我們構(gòu)建一個參數(shù)數(shù)組傳遞給調(diào)用函數(shù),它也允許我們選擇this
的值鳍鸵。
參數(shù)
arguments
苇瓣,雖然擁有length
屬性,但不是真正的數(shù)組权纤。而是類似數(shù)組(array-like
)的對象钓简。
返回
return
可用來是函數(shù)提前返回。當return
被執(zhí)行時汹想,函數(shù)立即返回而不再執(zhí)行余下的語句。
一個函數(shù)總會返回一個值撤蚊,如果沒指定古掏,那就是返回undefined
值。
如果函數(shù)調(diào)用時在前面加上了new
前綴侦啸,且返回值不是一個對象槽唾,則返回this
(該新對象)。
異常
JavaScript
提供了一套異常處理機制光涂。
throw
語句和try catch
,try catch
中finally
是可選的庞萍。
擴展類型的功能
JavaScript
允許給語言的基本類型擴充功能。在第3章中我們已經(jīng)看到忘闻,可以通過Object.prototype
添加方法钝计,可以讓該方法對所有對象都可用。這樣的方式對函數(shù)齐佳、數(shù)組私恬、字符串、數(shù)字炼吴、正則表達式和布爾值同樣適用本鸣。
例如:
Function.prototype.method = function () {
this.prototype[name] = func;
return this;
}
基本類型的原型是公用結(jié)構(gòu),所以在類庫混用時務必小心硅蹦。一個保險的做法就是只在確認沒有該方法時才添加它荣德。
Function.prototype.methods = function(name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
遞歸
遞歸函數(shù)就是會直接或間接地調(diào)用自身的一種函數(shù)闷煤。遞歸是一種強大的編程技術(shù),遞歸是用一般的方式去解決每一個子問題涮瞻。書中舉了一個漢諾塔的例子曹傀,是程序設計中經(jīng)典遞歸問題。詳細說明可以參見 百度百科“漢諾塔”詞條饲宛。
一些語言提供了尾遞歸優(yōu)化皆愉。尾遞歸是一種在函數(shù)的最后執(zhí)行調(diào)用語句的特殊形式的遞歸。參見Tail call艇抠。 ES6版本擴展了尾遞歸幕庐。參見阮一峰老師的《ES6標準入門》中的尾調(diào)用優(yōu)化
作用域
在編程語言中,作用域控制著變量與參數(shù)的可見性和聲明周期家淤。
書中指出當前JavaScript
沒有塊級作用域异剥。因為沒有塊級作用域,所以最好的做法是在函數(shù)體的頂部聲明函數(shù)中可能用到的所有變量絮重。不過ES6
擴展了有塊級作用域冤寿。
閉包
作用域的好處是內(nèi)部函數(shù)可以訪問定義它們的外部函數(shù)的參數(shù)和變量(除了this
和arguments
)。
例子:
<ul class="list">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
// 點擊相應節(jié)點時青伤,顯示對應的序號督怜。可以使用閉包來解決狠角。
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類數(shù)組轉(zhuǎn)成數(shù)組等方案實現(xiàn)。
// 閉包特性:1丰歌、函數(shù)內(nèi)再嵌套函數(shù)姨蟋,2、內(nèi)部函數(shù)可以調(diào)用外層的參數(shù)和變量立帖,3眼溶、參數(shù)和變量不會被垃圾回收機制回收。
// 閉包優(yōu)點 靈活和方便晓勇,便于封裝堂飞。缺點:空間浪費、內(nèi)存泄露宵蕉、性能消耗酝静。
回調(diào)
發(fā)起異步請求,提供一個當服務器響應到達時隨即出發(fā)的回調(diào)函數(shù)羡玛。異步函數(shù)立即返回别智,這樣客戶端就不會被阻塞。
模塊
我們可以使用函數(shù)和閉包來構(gòu)造模塊稼稿。模塊是一個提供接口卻隱藏狀態(tài)與實現(xiàn)的函數(shù)或?qū)ο蟆?br>
舉例:給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;
}
};
}());
模塊模式利用了函數(shù)作用域和閉包來創(chuàng)建被綁定對象與私有成員的關(guān)聯(lián)丽啡,在上面例子中,只有deentityify
方法有權(quán)訪問字符實體表這個數(shù)據(jù)對象硬猫。
模塊模式的一般形式是:一個定義了私有變量和函數(shù)的函數(shù)补箍;利用閉包創(chuàng)建可以訪問私有變量和函數(shù)的特權(quán)函數(shù);最后返回這個特權(quán)函數(shù)啸蜜,或者把它們保存到一個可以訪問的地方坑雅。
使用模塊模式就可以摒棄全局變量的使用。它促進了信息隱藏和其他優(yōu)秀的設計實踐衬横。對于應用程序的封裝裹粤,或者構(gòu)造其他單例對象,模塊模式非常有效蜂林。
單例譯注
模塊模式通常結(jié)合單例模式使用遥诉。JavaScript
的單例就是用對象字面量表示法創(chuàng)建的對象,對象的屬性值可以是數(shù)值或函數(shù)噪叙,并且屬性值在該對象的生命周期中不會發(fā)生變化矮锈。更多內(nèi)容參見:單例模式
級聯(lián)
有一些方法沒有返回值。如果我們讓這些方法返回this
而不是undefined
构眯,就可以啟用級聯(lián)愕难。
在一個級聯(lián)中,我們可以在單獨一條語句中依次調(diào)用同一個對象的很多方法惫霸。比如jQuery
獲取元素、操作樣式葱弟、添加事件壹店、添加動畫等。
柯里化
柯里化芝加,是把多參數(shù)函數(shù)轉(zhuǎn)換為一系列單參數(shù)函數(shù)并進行調(diào)用的技術(shù)硅卢。更多詳情可參見:柯里化
函數(shù)也是值〔卣龋柯里化允許我們把函數(shù)與傳遞給它的參數(shù)相結(jié)合将塑,產(chǎn)生一個新的函數(shù)。
var add1 = add.curry(1);
document.writeln(add1(6));
JavaScript并沒有curry方法蝌麸,但可以擴展該功能点寥。
arguments不是真正的數(shù)組,所以使用了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)));
}
});
記憶
函數(shù)可以將先前操作的結(jié)果記錄在某個對象里敢辩,從而避免無謂的重復運算蔽莱。這種優(yōu)化稱作記憶。
比如說戚长,我們想要一個遞歸函數(shù)來計算Fibonacci
(斐波那契)數(shù)列盗冷,它的特點是,前面相鄰兩項之和等于后一項的值同廉。更多參考:斐波那契仪糖。最前面兩個數(shù)字是0和1。
var fibonacci = function() {
return n < 2? n : fibonacci(n-1) + fibonacci(n-2);
}
這樣雖然能完成工作迫肖,但它做了很多無謂的工作锅劝。
構(gòu)造一個帶有記憶功能的函數(shù):
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
函數(shù)來定義fibonacci
函數(shù),提供其初始的memo
數(shù)組和formula
函數(shù)咒程。
var fibonacci = memoizer([0,1],function(recur, n){
return recur(n-1) + recur (n-2);
})
極大的減少了我們的工作量鸠天。例如要產(chǎn)生一個記憶的階乘函數(shù),只需要提供基本的階乘公式即可:
var factorial = meoizer([1,1], function(recur, n){
return n * recur(n-1);
});
第5章 繼承
偽類
JavaScript
的原型存在諸多矛盾帐姻。它不直接讓對象從其他對象繼承稠集,反而插入了一個多余的間接層:通過構(gòu)造器函數(shù)產(chǎn)生對象。
Function
構(gòu)造器產(chǎn)生的函數(shù)對象會運行類似這樣的一些代碼:
this.prototype = {constructor:this}
新函數(shù)對象被賦予一個prototype
屬性饥瓷,這個prototype
對象是存放繼承特征的地方剥纷。
當采用構(gòu)造器調(diào)用模式,即用new
前綴去調(diào)用一個函數(shù)時呢铆,函數(shù)執(zhí)行的方式會被修改晦鞋。如果new
運算符是一個方法而不是一個運算符,它可能像這樣執(zhí)行:
Function.method('new',function(){
// 創(chuàng)建一個新對象棺克,它繼承自構(gòu)造器函數(shù)的原型對象悠垛。
var that = Object.create(this.prototype);
// 調(diào)用構(gòu)造器函數(shù),綁定 -this- 到新對象上娜谊。
var other = this.apply(that,arguments);
// 如果它的返回值不是一個對象确买,就返回該新對象。
return (typeof other === 'object' && other) || that;
});
所有構(gòu)造器函數(shù)都約定命名成大寫字母纱皆。一種更好的備選方案就是根本不使用new
湾趾。
對象說明符
就是指傳多個參數(shù)時,可以直接傳遞一個對象派草。
原型
可以用Object.create
方法構(gòu)造出更多實例來搀缠。
函數(shù)化
迄今為止,我們所看到的繼承模式的一個弱點就是沒法保護隱私近迁。對象的所有屬性都是可見的艺普。我們無法得到私有變量和私有函數(shù)。
幸運的是独泞,我們有一個更好的選擇府适,那就是應用模塊模式。
我們從構(gòu)造一個生成對象的函數(shù)開始官份。我們以小寫字母開頭來命名缴罗。
該函數(shù)包括以下四個步驟
1助琐、創(chuàng)建一個新對象。
2面氓、有選擇地私有實例變量和方法兵钮。
3、給這個新對象擴充方法舌界。
4掘譬、返回那個新對象。
以下是一個函數(shù)化構(gòu)造器的偽代碼模板
var constructor = function (spec, my) {
var that, 其他的私有實例變量呻拌;
my = my || {};
把共享的變量和函數(shù)添加到my中
that = 一個新對象
添加給 that 的特權(quán)方法
return that;
}
函數(shù)化模式有很大的靈活性葱轩。它相比偽類模式不僅帶來的工作更少,還讓我們更好的封裝和信息隱藏藐握,以及訪問父類方法的能力靴拱。
部件
我們可以從一套部件中把對象組裝出來。
第6章 數(shù)組
數(shù)組是一段線性分配的內(nèi)存猾普,它通過整數(shù)計算偏移并訪問其中的元素袜炕。
數(shù)組是一種性能出色的數(shù)據(jù)結(jié)構(gòu)。不幸的是初家,JavaScript
沒有像此類數(shù)組一樣的數(shù)據(jù)結(jié)構(gòu)偎窘。
數(shù)組字面量
對象字面量
數(shù)組繼承了Array.prototype
大量有用的方法。而對象字面量是繼承自Object.prototype
溜在。
數(shù)組有length
屬性陌知,而對象沒有。
長度
每個數(shù)組都有一個length
屬性掖肋。
可以直接設置length
的值纵诞。設置更大的length不會給數(shù)組分配更多的空間,而設小導致所有下標大于等于新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
的數(shù)組也是對象登刺,所以delete
運算符可以用來從數(shù)組中移出元素籽腕。移除后,長度不變纸俭,原位置上變成了undefined
皇耗。
可以使用Array.prototype.splice
方法刪除數(shù)組。
枚舉
JS數(shù)組就是對象揍很,所以for in
語句可以用來遍歷數(shù)據(jù)的所有屬性郎楼。
不過万伤,for in
無法保證屬性順序。并且可能從原型鏈中得到意外的屬性呜袁。
for
循環(huán)可以避免以上問題敌买。
容易混淆的地方
typeof [] === "object"; // true
typeof {} === "object"; // true
識別是否是數(shù)組。
// 方法一阶界、
var is_array = function (value) {
return value && typeof value === 'object' && value.constructor === Array;
};
但它在識別從不同窗口(window)或幀(frame)里的構(gòu)造的數(shù)組時會失敗虹钮。
有一個更好的方式:
// 方法二、
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 運算符用來測試一個對象在其原型鏈中是否存在一個構(gòu)造函數(shù)的 prototype 屬性律姨。
方法五、isPrototypeOf() 方法用于測試一個對象是否存在于另一個對象的原型鏈上疗绣。
方法六线召、Object.getPrototypeOf() 方法返回指定對象的原型(即, 內(nèi)部[[Prototype]]屬性的值)。
小結(jié):除了方法二多矮、三外缓淹,面對復雜的環(huán)境,其他的都不能準確的判斷是否是數(shù)組塔逃。
方法
JavaScript
提供了一套數(shù)組可用的方法讯壶,這些方法是被存儲在Array.prototype中的函數(shù)。
Object.prototype
是可以擴充的湾盗。
Array.prototype
也是可以擴充的伏蚊。
ES5
中提供的Object.create
方法。這方法用在數(shù)組是沒有意義的格粪,因為它產(chǎn)生的是一個對象躏吊,而不是一個數(shù)組,產(chǎn)生的對象將繼承這個數(shù)組的值和方法帐萎,但它沒有length
特殊屬性比伏。
指定初始值
JavaScript
的數(shù)組通常不會預設值。書中寫了一個循環(huán)來擴展疆导,生成初始值赁项。
擴展:ES6
中提供了fill
來填充。比如:
['a','b','c'].fill(0); // [0,0,0]
new Array(3).fill(0); // [0,0,0]
// fill方法還可以接受第二、第三個參數(shù)悠菜,用于指定填充的起始位置和結(jié)束位置(不包含)舰攒。
new Array(3).fill(0,1,2); // [ ,0, ,] 空位不是undefined』诖祝空位沒有任何值摩窃。ES6則是明確將空位轉(zhuǎn)為undefined。
第7章 正則表達式
正則表達式對字符串中的信息實現(xiàn)查找篙顺、替換和提取操作偶芍。
可處理正則表達式的方法有regexp.exec
、regexp.test
德玫、string.match
匪蟀、string.search
和string.split
。通常來說宰僧,正則相較于等效的字符串處理有著顯著的性能優(yōu)勢材彪。
一個例子
// 正則表達式必須寫在一行中
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'
個人擴展:這里推薦 在線測試正則表達式的網(wǎng)站regex101,默認是PHP語言琴儿,選擇JavaScript語言段化。
在線圖形化RegExp工具
MDN RegExp.prototype.exec()
大概解釋下這個正則,
這里的^
起始位置造成,$
結(jié)束位置
()
分組捕獲 ?:
不捕獲
.
表示除換行以外的任意單個字符显熏,對于碼點大于0xFFFF
的Unicode
字符,點(.
)不能識別(ES6
中加u
修飾符才可識別)晒屎,+
表示一個或多個喘蟆,*
表示零個或多個,?
表示0
個或一個鼓鲁。[]表示或者蕴轨,里面符合一個即可。
\d
表示數(shù)字0-9
骇吭。
不嚴謹?shù)恼齽t表達式是一個常見的安全漏洞的發(fā)源地橙弱。在執(zhí)行某些匹配時,嵌套的正則表達式也能導致極其惡劣的性能問題燥狰。因此簡單是最好的策略棘脐。
再看一個 匹配數(shù)字的例子。
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
結(jié)構(gòu)
有兩個方法來創(chuàng)建一個RegExp
對象龙致。優(yōu)先考慮的是正則表達式字面量荆残,還有一種方式是new RegExp('','g')
。
正則表達式標識:g
全局(匹配多次净当,不同的方法對g
標識的處理防范各不相同),i
忽略大小寫。m
多行
元素
正則表達式分支
|
表示或像啼,也表示分支 比如:
'info'.match(/in|int/) // 匹配成功俘闯,["in", index: 0, input: "info"]
正則表達式序列
一個正則表達式序列飽和一個或多個正則表達式因子。每一個因子能選擇是否跟隨一個量詞忽冻,這個量詞決定著這個因子被允許出現(xiàn)的次數(shù)真朗,若沒指定,這個因子則只匹配一次僧诚。
正則表達式因子
\ / [ ] () { } ? + * | . ^ $
正則表達式轉(zhuǎn)義
\ 表轉(zhuǎn)義 \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的引用慈省,以此類推臀防。
正則表達式分組
捕獲型()
非捕獲型?:
向前正向匹配?=
有一個(?=
前綴。它類似于非捕獲類型分組边败,但在這個組匹配后袱衷,文本會倒回到它它開始的地方,實際上并不匹配任何東西笑窜。也可以理解為匹配位置致燥。
向后負向匹配
有一個(?!
前綴。它類似于向前正向匹配分組排截,但只有當它匹配失敗時它才繼續(xù)向前進行匹配嫌蚤。這不是一個好的特性。
正則表達式字符集
正則表達式字符集是一種指定一組字符的便利方式匾寝。例如搬葬,要匹配一個元音字母,(?:a|e|i|o|u)
,可以方便的寫成[aeiou]
艳悔。
類提供另外兩個便利:①指定字符范圍
所以急凰,一組由32
個ASCII
的特殊組合,可以寫成[!-\/:-@\[-
{-~]`
②類的取反
取反
[^!-\/:-@\[-`{-~]
正則表達式字符轉(zhuǎn)義
字符類內(nèi)部的轉(zhuǎn)義規(guī)則和正則表達式因子的相比稍有不同猜年。下面是在字符類中需要被轉(zhuǎn)義的特殊字符抡锈。
- / [ \ ]
正則表達式量詞
量詞后綴決定正則表達式因子應該被匹配的次數(shù)。
{3}
三次
{3,6}
3乔外、4床三、5、6次
{3,}
3次或更多次
?
等同于{0,1}
杨幼,*
等同于{0,}
撇簿,+
等同于{1,}
聂渊。
第8章 方法
Array
array.concat(item...)
concat
方法產(chǎn)生一個新數(shù)組,它包含一份array
的淺復制并把一個或多個參數(shù)item
附加在其后四瘫。如果item
是數(shù)組汉嗽,那么每個元素分別被添加。后面有和它功能類似的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
構(gòu)造成一個字符串饼暑。
separator
默認值就是逗號','
。
如果你想把大量的字符串片段組裝成一個字符串洗做,把這些片段放在一個數(shù)組中弓叛,并用join
方法連接起來通常比用+
元素運算符連接起來要快。
譯注:對于
IE6/7
诚纸,使用join連接大量字符串效率確實優(yōu)于加號運算符撰筷。但目前主流瀏覽器,包括IE8
以后的版本咬清,都對+
元素運算符連接字符串做了優(yōu)化闭专,性能已經(jīng)顯著高于Array.join()
。所以目前大多數(shù)情況下旧烧,建議首選使用+ 連接字符串影钉。更多參看《高性能網(wǎng)站建設進階指南》中字符串優(yōu)化相關(guān)章節(jié)。
array.pop()
pop
方法移除array
中的最后一個元素掘剪,并返回這個元素平委。如果array
為空,則返回undefined
夺谁。
var a = ['a','b','c'];
var c = a.pop(); // a 是 ['a','b'] c是 'c'
// pop 可以像這樣實現(xiàn)廉赔。
// 這里的 Array.method()在第一章例子中已經(jīng)定義了,并且貫穿全書匾鸥。其實就是相當于Array.prototype
Array.method('pop', function () {
return this.splice(this.length-1,1)[0];
});
array.push(item...)
與concat
不同的是蜡塌,它會修改array,如果參數(shù)item
是數(shù)組勿负,它會把參數(shù)數(shù)組作為單個元素整個添加到數(shù)組中馏艾。并返回這個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
可以像這樣實現(xiàn):
Array.method('push', function () {
this.splice.apply(
this,
[this.length,0].
concat(Array.prototype.slice.apply(arguments)));
return this.length;
});
array.reverse()
reverse
反轉(zhuǎn)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
可以這樣實現(xiàn):
Array.method('shift', function(){
return this.splice(0,1)[0];
});
array.slice(start[, end])
slice
是對array
中的一段做淺復制檀头。end
是可選的轰异。默認是array.length
,如果兩個參數(shù)任何一個是負數(shù)岖沛,array.length
會和相加。如果start
大于array.length
,獲得一個[]
,字符串也有Sting.slice
這個同名方法溉浙。
array.sort
默認不能給一組數(shù)字排序烫止。默認把要被排序的元素都視為字符串。
幸運的是戳稽,可以使用自己的比較函數(shù)替換默認的比較函數(shù)。
比較函數(shù)應該接受兩個參數(shù)期升,并且如果這兩個參數(shù)相等則返回0惊奇,如果第1個參數(shù)應該排列在前面,則返回一個負數(shù)播赁,如果第二個參數(shù)應該排列在前面颂郎,則返回一個正數(shù)。
sort
方法是不穩(wěn)定的容为。JavaScript
的sort
方法的穩(wěn)定性根據(jù)不同瀏覽器的實現(xiàn)而不一致乓序。
可參見MDN sort
array.splice(start, deleteCount,item...)
splice
方法從array中移除一個或多個元素,并用新的item
替換它們坎背。
// splice 可以像這樣實現(xiàn)
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
方法一樣替劈,不過是用于把元素添加到數(shù)組的開始部分,返回新array
的length
得滤。
// unshift 可以像這樣實現(xiàn)
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
方法調(diào)用function
,傳遞一個會被綁定到this
上的對象和一個可選的數(shù)組作為參數(shù)陨献。
Number
number.toExponential(fractionDigits)
toExponential
方法 把這個number
轉(zhuǎn)換成一個指數(shù)形式的字符串《可選參數(shù)控制其小數(shù)點后的數(shù)字位數(shù)眨业。它的值必須在0~20
。
number.toFixed(fractionDigits)
toFixed
方法把這個number轉(zhuǎn)換成一個十進制數(shù)形式的字符串沮协×浼瘢可選參數(shù)控制其小數(shù)點后的數(shù)字位數(shù)。它的值必須在0~20慷暂。
number.toPrecision(precision)
toPrecision
方法把這個number
轉(zhuǎn)換成一個十進制數(shù)形式的字符串聘殖。可選參數(shù)控制數(shù)字的精度呜呐。它的值必須在0~21
就斤。
number.toString(radix)
把number
轉(zhuǎn)換成字符串∧⒓可選參數(shù)控制基數(shù)洋机。它的值必須是2~36
。默認的radix
是以10
為基數(shù)的洋魂。radix
參數(shù)最常用的是整數(shù)绷旗,但是它可以用任意的數(shù)字喜鼓。
Object
object.hasOwnProperty(name)
如果這個object
包含名為name
的屬性,那么返回true
衔肢。原型鏈中的同名方法不會被檢測庄岖。這個方法對name
就是“hasOwnProperty”
時不起作用。
RegExp
regexp.exec(string)
exec
是正則中最強大(和最慢)的方法角骤。
如果成功匹配隅忿,它會返回一個數(shù)組。下標為0 的元素包含正則匹配的子字符串邦尊。下標為1的則是分組1捕獲的文本背桐。下標為2的則是分組2捕獲的文本。以此類推蝉揍。如果匹配失敗則返回null
链峭。
regexp.test(string)
test
是最簡單(和最快)的方法。匹配成功又沾,返回true
,否則返回false
弊仪。不要對這個方法使用g
標識。
比如:
var reg = /\w+/g;
reg.test('ab'); // true
// 再執(zhí)行一遍就是false了杖刷。
reg.test('ab'); // false
// 再執(zhí)行一遍就是true了励饵。
reg.test('ab'); // true
// 再執(zhí)行一遍又是false了,如此反復挺勿,所以用g標識后曲横,看起來很詭異。應該每次匹配開始位置變了不瓶。
reg.test('ab'); // false
test
可以像這樣實現(xiàn):
RegExp.method('test', function(string){
return this.exec(string) !== null;
});
String
string.charAt(pos)
返回在string
中的pos
位置處的字符禾嫉。
string.charCodeAt(pos)
與charAt
一樣,不過返回整數(shù)形式表示字符碼位蚊丐。
string.concat(string)
很少用熙参,用+
號運算符更方便。
string.indexOf(searchString,position)
在string
中查找第一個參數(shù)麦备,如果被找到返回該字符的位置孽椰,否則返回-1
。position
可設置指定位置開始查找凛篙。
string.lastIndexOf(searchString,position)
lastIndexOf
方法和indexOf
方法類似黍匾,不過它是從末尾開始查找,不是從頭開始呛梆。
string.localeCompare(that)
比較兩個字符串锐涯。類似于array.sort
。
string.match(regexp)
如果沒有g
標識填物,那么調(diào)用string.match(regexp)
和調(diào)用regexp.exec(string)
結(jié)果相同纹腌。如果帶有g
標識霎终,那么它生成一個包含所有匹配(除捕獲分組之外)的數(shù)組。
string.replace(searchValue,replaceValue)
對string
進行查找和替換操作升薯,并返回一個新的字符串莱褒。參數(shù)searchvalue
可以是一個字符串也可以是一個正則表達式對象。參數(shù)replaceValue
可以是一個字符串或一個函數(shù)涎劈。
string.search(regexp)
和indexOf
類似广凸,不過它接收正則為參數(shù)。
string.slice(start, end)
slice
方法復制string
的一部分來構(gòu)造一個新的字符串蛛枚。如果start
參數(shù)是負數(shù)炮障,它將與string.length
相加。end
參數(shù)是可選的坤候。
string.split(separator,limit)
把string
分割成片段來創(chuàng)建一個字符串數(shù)組∑蟛洌可選參數(shù)limit
可以限制分割的片段數(shù)量白筹。separator
參數(shù)可以是字符串或者正則。
string.substring(start,end)
與slice
方法一樣谅摄,不過它不能處理負數(shù)參數(shù)徒河。
string.toLocaleLowerCase()
它使用本地化的規(guī)則把這個string
中的字母轉(zhuǎn)換成小寫格式。這個方法主要用在土耳其語上送漠。
string.toLocaleUpperCase()
它使用本地化的規(guī)則把這個string
中的字母轉(zhuǎn)換成大寫格式顽照。這個方法主要用在土耳其語上。
string.toLowerCase()
返回新字符串闽寡,所有字母轉(zhuǎn)成小寫格式代兵。
string.toUpperCase()
返回新字符串,所有字母轉(zhuǎn)成大寫格式爷狈。
String.fromCharCode(char...)
根據(jù)一串數(shù)字編碼返回一個字符串植影。
var a = String.fromCharCode(67,97,116) // a是'Cat'
第9章 代碼風格
這一章中,簡短的說了一些代碼風格涎永。事實證明代碼風格在編程中是很重要的思币。
第10章 優(yōu)美的特性
精簡的JavaScript
里都是好東西。
包括:1羡微、函數(shù)是頂級對象谷饿;2、基于原型繼承的動態(tài)作用域妈倔;3博投、對象字面量和數(shù)組字面量。
到此启涯,讀書筆記已完結(jié)贬堵。