正如標題所說,這是我第三次翻開紅寶書也就是《 JavaScript 高級程序設計第三版》,不得不說,雖然書有一些年份,不少知識點也不適合現代的前端開發,可是對於想要掌握 JavaScript 基礎的前端新手,亦或是像我同樣想找回曾經遺忘在記憶角落的那些碎片知識,這本書依舊很是的適合,不愧被成爲 "JavaScript 聖經"javascript
本文是讀書筆記,之因此又一次選擇讀這本書還有一個理由,以前都是記的紙質筆記,此次想把它做爲電子版,也算是對以前知識的整理css
本文篇幅較長,目的是做爲個人電子版學習筆記,我會盡量去其糟粕,取其精華,同時我會添加一些書上未記載但很重要的知識點補充
html
上篇在這裏前端
引用類型是一種數據結構,它在別的語言中被稱爲類,可是在 JavaScript 中實際上並無類,雖然提供了不少相似「類」的語法(class 關鍵字),但不少書籍都認爲這不是一個好的事情java
let date = new Date()
console.dir(date)
複製代碼
這裏經過 new
關鍵字來生成一個 Date 引用類型的實例,能夠發現它的原型上有不少公有的方法,引用類型的實例也被成爲引用類型的值,是一個對象node
ECMAScript 原生的引用類型有jquery
接下來我會介紹幾個比較經常使用的引用類型來詳細分析git
Object 是最多見的引用類型,原生的引用類型都繼承自 Object 類型,能夠經過點表示法和方括號表示法來訪問對象,前者書寫更加簡單,後者能夠支持一些特殊語法和表達式,可是書寫比較複雜(同時因爲要解析表達式,性能也稍慢)github
let obj = {} // 經過字面量表示法來建立對象,比 new 更加簡便
obj.a = 1
obj["b"] = 2
obj["c d"] = 3 // 方括號能夠支持點表示法不支持的語法
// 方括號也能夠支持表達式
// ps:對象中含有數字的屬性會被轉爲字符串
obj[1 + 2] = 4
obj[obj] = 5 // 若是屬性是一個對象,會轉爲原始類型
console.log(obj) // {"3": 4, "a": 1, "b": 2, "c d": 3, "[object Object]": 5}
複製代碼
可使用 new 關鍵字來動態生成一個對象,另外它不只限於普通的對象,還能夠生成包裝類型的對象web
let str = "abc"
let wrappedStr = new Object("abc") // 等同於 new String("abc")
console.log(typeof str) // 'string'
console.log(typeof wrappedStr) // 'object'
console.log(wrappedStr instanceof String) // true 注意 String 首字母是大寫,表明是 String 包裝類型而非 string 基本類型
複製代碼
wrappedStr 這個包裝對象看上去和基本類型類似,但它是一個對象,進一步說是 String 包裝類型的實例,這個咱們放到以後講
Array 類型能夠經過 new 關鍵字調用或者字面量表達式來生成實例,當使用 new 並傳入一個參數時,會建立參數長度的稀疏數組(由空單元組成的數組),當傳入兩個以上參數時,會建立由參數組成的數組
let arr1 = new Array(10)
let arr2 = new Array(10,20)
console.log(arr1) //[ <10 empty items> ]
console.log(arr2) // [ 10, 20 ]
複製代碼
數組有一個 length 的內部屬性,它是能夠被修改的,能夠利用這個特色快速清空數組和增長數組長度
let arr = [1,2,3] // 使用字面量建立數組
arr.length = 0
console.log(arr) // []
arr.length = 100
console.log(arr) // [ <100 empty items> ] 建立的都是空單元的稀疏數組,不推薦直接使用
複製代碼
當嘗試將 Array 類型轉換爲字符串時(這可能會存在於隱式轉換中),默認會調用數組原型上的 toString 方法,它會依次調用數組中每一個元素的 toString 方法 (null 和 undefined 是例外,它們會直接轉爲空字符串)
let arr = [{a:1},123,()=>{},undefined]
console.log(arr.toString()) // "[object Object],123,()=>{}," 最後以逗號結尾,由於 undefined 變成空字符串了
let obj = {}
obj[arr] = ""
console.log(obj) // { '[object Object],123,()=>{},': "" } 當對象的屬性是一個對象,JS 會將其轉爲字符串再做爲其屬性,這裏就發生了隱式轉換
複製代碼
JavaScript 中的數組既能夠表示棧也能夠表示隊列,當使用 pop
方法時會視爲棧,將棧頂也就是數組最後一個元素彈出,當使用 shift
方法時會視爲隊列,將隊首也就是數組第一個元素出列
而添加數組元素的方法 push,unshift
後會返回數組的長度
let arr = [1,2,3]
console.log(arr.push(4)) // 4 表示數組長度爲4
console.log(arr.unshift(0)) // 5 表示數組長度爲5
console.log(arr.pop()) // 4 返回最後一個元素4
console.log(arr.shift()) // 0 返回第一個元素0
複製代碼
經過調用 sort
方法能夠給數組元素進行排序,可是這個方法有點特殊,sort 會調用每一個數組元素的 toString 方法,這會致使排序的結果和預期的有出入
let arr = [1,2,10,20,100,200]
console.log(arr.sort()) // [ 1, 10, 100, 2, 20, 200 ]
複製代碼
調用 sort 會將每一個元素都轉爲字符串,最終會給 "1", "2", "10", "20""10", "20", "100", "200" 排序,而在上一章中我介紹到,JavaScript 中兩個字符串比較,會逐個比較每一個字符的字符串的編碼,若是當前字符編碼相同,則依次比較下一位,一旦某個字符大於另外一個字符則直接返回結果,不會再日後比較
這裏之因此 10 排在 2 的前面,是由於在比較 "10" 和 "2" 時,先比較第一位也就是字符串 1 和字符串 2,由於字符串 2 的編碼大於字符串 1,因此就會直接退出比較,就會產生 "10" < "2" 的結果
這並非一個奇怪的 BUG,而是 sort 默認使用字典序進行排序,維基百科中是這麼解釋字典序的
設想一本英語字典裏的單詞,哪一個在前哪一個在後?
顯然的作法是先按照第一個字母、以 a、b、c……z 的順序排列;若是第一個字母同樣,那麼比較第二個、第三個乃至後面的字母。若是比到最後兩個單詞不同長(好比,sigh 和 sight),那麼把短者排在前。
經過這種方法,咱們能夠給原本不相關的單詞強行規定出一個順序。「單詞」能夠看做是「字母」的字符串,而把這一點推而廣之就能夠認爲是給對應位置元素所屬集合分別相同的各個有序多元組規定順序:下面用形式化的語言說明。
若是不用默認的排序方式,能夠經過給 sort 方法傳入一個比較函數
let arr = [1,2,10,20,100,200]
console.log(arr.sort((a,b) => a - b)) // [ 1, 2, 10, 20, 100, 200 ]
複製代碼
sort 是一個高階函數,即支持傳入一個函數做爲參數,每次比較時都會調用傳入的參數,其中參數 a 和 b 就是兩個準備進行比較的字符串,在上一章還提到過,若是兩個字符串相減會轉爲 Number 類型再進行計算,這樣就能夠避免字符串類型比較形成的問題
同時若是傳入的函數返回值大於 0 ,則 b 會排在 a 前面,小於 0 則 a 會排在 b 前面,等於 0 則不變,根據這個特色能夠控制返回數組是順序仍是倒序
let arr = [1,2,10,20,100,200]
// [ 200, 100, 20, 10, 2, 1 ] 倒序數組
// 第一次比較時 b 爲 2,a 爲 1,因爲返回值大於 0,因此 b 將排在 a 的前面,變成 [2,1],以此類推
// 注意是插入排序
console.log(arr.sort((a,b) => b - a))
複製代碼
sort 它會修改原數組,而不是新生成一個數組做爲返回值,使用前請考慮清楚是否須要對原數組進行拷貝
let arr = [1,2,10,20,100,200]
console.log(arr.sort((a,b) => b - a)) // [ 200, 100, 20, 10, 2, 1 ]
console.log(arr) // [ 200, 100, 20, 10, 2, 1 ] 原數組被修改了!
複製代碼
題外話:上述做爲參數的函數中,能夠經過 Math.random 隨機返回大於小於 0 的數字能夠實現數組亂序,可是並非真正的亂序,而洗牌算法能夠解決這個問題
concat 會將參數添加到數組末尾,不像 sort 會修改原數組,它會建立一個當前數組的副本(淺拷貝),因此相對比較安全
另外若是參數包含數組,會給數組進行一層降維
let arr = [1, 2, 3]
console.log(arr.concat(4, [5, 6], [7, [8, 9]])) // [ 1, 2, 3, 4, 5, 6, 7, [ 8, 9 ] ]
console.log(arr) // [1,2,3]
複製代碼
固然如今更推薦使用 ES6 的擴展運算符,寫法更加簡潔,和 concat 實現的功能相似,一樣也會淺拷貝數組
let arr = [1, 2, 3]
console.log([...arr, 4, ...[5, 6], ...[7, [8, 9]]]) // [ 1, 2, 3, 4, 5, 6, 7, [ 8, 9 ] ]
console.log(arr) // [1,2,3]
複製代碼
slice 會基於參數來切割數組,它一樣會淺拷貝數組,當傳入不一樣參數會有不一樣功能
slice 方法能夠將類數組轉爲真正的數組
let arr = [1, 2, 3, 4, 5]
console.log(arr.slice()) // [1, 2, 3, 4, 5] 淺拷貝原數組
console.log(arr.slice(2)) // [3, 4, 5]
console.log(arr.slice(2, 3)) // [3]
console.log(arr.slice(2, 1)) // []
console.log(arr.slice(2, -1)) // [3, 4] 等同於 arr.slice(2, -1 + 5)
console.log(Array.prototype.slice.call({0: "a", length: 1})) // ["a"]
複製代碼
splice 能夠認爲是 push, pop, unshift, shift 的結合,而且可以指定插入/刪除的位置,很是強大,但它傳入但參數也更爲複雜,因此通常只有在操做數組具體下標元素的時候纔會使用,同時它也會修改原數組,使用時請注意
同時 splice 會返回一個數組,若是是使用它的刪除功能,則返回的數組中會包含被刪除的元素,來看一些比較特殊的例子
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(0, 1)) // [1]
console.log(arr) // [2, 3, 4, 5]
console.log(arr.splice(1)) // [3,4,5]
console.log(arr) // [2]
複製代碼
第一次調用 splice 會在數組下標爲 0 的位置刪除一個元素,它的返回值就是被刪除的元素 1,同時打印數組,會發現 splice 修改了原來的數組,原數組的第一個元素被刪除了
第二次調用 splice 只傳了一個參數,表示刪除從數組下標爲 1 的位置至數組最後一個元素,由於此時數組爲 [2,3,4,5],因此刪除下標從 1 到最後的元素 3,4,5,並做爲 splice 的返回值,最後原數組就只包含元素 2 了
indexOf 方法會返回參數在數組中的下標,不存在則返回 -1,一個特殊狀況就是 NaN,若是使用 indexOf 判斷 NaN 是否在數組中,永遠會返回 -1
let arr = [1,2,3,4,5,NaN]
console.log(NaN) // -1
複製代碼
解決這個問題可使用 ES6 的 includes
方法,它會返回一個布爾值,而非目標元素下標,同時它能夠判斷 NaN 是否存在與目標數組中
let arr = [1,2,3,4,5,NaN]
console.log(arr.indexOf(NaN)) // -1
console.log(arr.includes(NaN)) // true
複製代碼
我我的習慣使用 includes 這個 api,由於平常中不少狀況只須要知道參數是否存在於數組中,因此返回布爾值就足夠了,若是遇到須要返回下標,或者從數組的指定位置搜索參數可使用 indexOf
reverse 和 sort 以及 splice 同樣會修改原數組
let arr = [1,2,3,4,5]
console.log(arr.reverse()) // [5,4,3,2,1]
console.log(arr) // [5,4,3,2,1]
複製代碼
ES5 爲數組提供了 5 個迭代方法,它們都是高階函數,第一個參數是一個函數,第二個參數是函數的 this 指向
這些 api 平常用的很是多就不贅述了,只說一個小細節
對一個空數組不管參數中的函數返回什麼,調用 some 都會返回 false, 調用 every 都會返回 true
let arr = []
console.log(arr.some(()=>{})) // false
console.log(arr.every(()=>{})) // true
複製代碼
reduce 也就是歸併方法,我的認爲是數組中最高級的使用方法,用的好能夠實現一些很是強大的功能,這裏舉個的例子:多維數組扁平化
const flat = function (depth = 1) {
let arr = Array.prototype.slice.call(this)
if(depth === 0 ) return arr
return arr.reduce((pre, cur) => {
if (Array.isArray(cur)) {
// 須要用 call 綁定 this 值,不然會指向 window
return [...pre, ...flat.call(cur,depth-1)]
} else {
return [...pre, cur]
}
}, [])
}
複製代碼
關於 reduce 還有一個關於下標的注意點,當 reduce 只傳一個參數時,index 的下標是從 1 也就是數組第二個元素開始的(若是此時數組爲空會報錯),當 reduce 傳入第二個參數,會做爲遍歷的起始值,此時 index 的下標就從 0 也就是數組第一個元素開始
let arr = ["b", "c", "d", "e"]
arr.reduce((pre, cur, index) => {
console.log(index)
return pre + cur
})
// 1
// 2
// 3
arr.reduce((pre, cur, index) => {
console.log(index)
return pre + cur
}, "a")
// 0
// 1
// 2
// 3
複製代碼
正則表達式可使用構造函數動態生成,也可使用字面量快速生成,因爲正則表達式實例也是對象,因此也會有屬性,經常使用的屬性有
.
能夠匹配任何單個字符,能夠理解爲 [\s\S](默認 .
不會匹配換行符,回車符,分隔符等)let reg = /\[abc]/ig
console.dir(reg)
let reg2 = new RegExp('\\[abc]', "ig") // RegExp 第二個參數爲正則標誌
console.dir(reg2)
console.log(reg.test("[abc]"), reg2.test("[abc]"))
複製代碼
打印結果以下
reg 和 reg2 實現的功能是相同的,第一種更簡便,第二種更靈活,須要結合實際狀況靈活使用
另外還有一個比較重要的點,能夠看到 2 個正則對象的 lastIndex 都爲 5,此時若是繼續用 test 方法匹配會返回 false
let reg = /\[abc]/ig
console.dir(reg)
let reg2 = new RegExp('\\[abc]', "ig") // RegExp 第二個參數爲正則標誌
console.dir(reg2)
console.log(reg.test("[abc]"), reg2.test("[abc]"))
console.log(reg.test("[abc]"), reg2.test("[abc]"))
複製代碼
之因此出現這樣的狀況是由於正則對象內部保存的 lastIndex 屬性會決定下次正則匹配的位置,第一次用 test 方法匹配成功後,lastIndex 從 0 變成 5,同時下次會從參數的第 6 個元素開始匹配,此時發現匹配不到任何元素,因此會返回 false ,並將 lastIndex 重置爲默認值 0
關於正則其實很是複雜,深刻會涉及到狀態機,回溯,貪婪匹配等知識點,寫的很差會影響系統的性能,可是若是能搞懂其中的奧祕,寫出優質的正則,可以很大程度上解放勞動力,例如給整個項目替換部分代碼,另外 Vue 的模版字符串編譯也是依賴正則的匹配來提取屬性,自定義指令等
函數也是對象,而函數的函數名只是做爲一個指向函數的指針,這個我在上一章也提到過,JavaScript 不容許直接操做對象的內存空間,開發中操做的都是指向堆內存對象的指針
能夠經過 new 關鍵字來動態生成一個函數
let func = new Function("a","console.log(a)")
console.log(func(123)) // 123
複製代碼
Function 函數做爲構造函數時,至少接受一個參數,當只傳入一個參數時,會直接將參數做爲函數體,當傳入超過一個函數時,會將最後一個參數做爲函數體,以前的全部參數會做爲生成的函數的參數
經過 new 動態生成函數優勢在於更加靈活,例如 Vue 的編譯器最終會將字符串經過 new Function 傳入 code 代碼並執行
// src/compiler/to-function.js:11
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}
複製代碼
而通常狀況咱們是不須要使用這種方式的,直接使用字面量的形式建立函數,由於前者雖然更加靈活,可是性能並非很是理想,同時還有可能存在安全隱患(相似 eval,可能會被惡意用戶注入惡意代碼運行,造成 XSS 攻擊)
JavaScript 中的函數沒有重載,可是能夠實現必定程度上的參數重載
import $ from 'jquery'
$("p").css("background-color"); // color
$("p").css("background-color",'red');
複製代碼
這裏是一個 jquery 的代碼,當調用 css 方法傳入一個參數時,會返回 p 節點當前的背景顏色,若是傳入 2 個參數,會設置背景顏色,css 方法的功能取決於傳入參數的個數,原理是利用函數參數個數來判斷返回屬性仍是設置屬性
當 JavaScript 在加載數據時(也能夠理解爲進入一個新的環境或者說上下文),會優先讀取環境中的全部函數聲明,而且這是在加載代碼以前執行的操做,而當全部準備工做完成後,纔會逐行執行代碼,也就是說當代碼執行到函數表達式時,函數纔會被真正執行
func()
// 函數表達式
const func2 = function () {
console.log(2)
}
func2()
// 函數聲明
function func() {
console.log(1)
}
複製代碼
執行順序:func 聲明 -> func 執行 -> func2 聲明 -> func2 執行
在進入全局環境以前,func 因爲"函數聲明提高"被提高到最前面執行,隨後再是執行整個代碼,因此上面代碼並不會報錯,fun2 是函數表達式,若是將 func2 執行的代碼放到 func2 聲明以前,就會發生錯誤,由於函數表達式不會被提高
關於函數聲明和函數表達式還有一些小細節:
var sum = function sum() {
//...
sum() // 在函數內部 sum 指向的是右邊的詞法名稱,而非左邊的 sum 變量
}
複製代碼
右邊的 sum 會做爲匿名函數的「詞法名稱」,這種狀況經常使用於自身的遞歸,若是沒有這個詞法名稱函數只能使用 arguments.callee
方法來實現遞歸(沒法使用左邊的 sum 變量),而函數內部的 arguments 對象因爲性能問題已不推薦使用,因此若是有遞歸的需求,推薦給匿名函數添加一個詞法名稱,另外須要注意的是,詞法名稱是常量,函數內部沒法修改
每日一題中就有該知識點的考察,如下是某個解題者的解釋
var b = 10;
(function b() {
// 內部做用域,會先去查找是有已有變量b的聲明,有就直接賦值20,確實有了呀。發現了具名函數 function b(){},拿此b作賦值;
// IIFE的函數沒法進行賦值(內部機制,相似const定義的常量),因此無效。
// (這裏說的「內部機制」,想搞清楚,須要去查閱一些資料,弄明白IIFE在JS引擎的工做方式,堆棧存儲IIFE的方式等)
b = 20;
console.log(b); // [Function b]
console.log(window.b); // 10,不是20
})();
複製代碼
函數還有一個 length
屬性,它表示的是函數但願接受的形參個數
形參的數量不包括剩餘參數個數,僅包括第一個具備默認值以前的參數個數
function func(a,b,...c) {}
console.log(func.length) // 2
function func2(a = 1,b,c) {}
console.log(func2.length) // 0
function func2(a,b = 2,c) {}
console.log(func2.length) // 1
複製代碼
能夠看到第一個函數的 length 屬性爲 2,由於後面是剩餘參數,不計算在 length 長度中,而第二個由於參數 a 含有默認值,因此會返回 a 以前的參數,同時由於 a 以前沒有參數因此最終返回 0,第三個例子中參數 b 有默認值,因此返回 b 以前的參數個數,也就是 1
函數在運行時還會生成一個 this 對象,它能夠理解爲指針,在通常狀況下,this 指向的是調用該函數的對象,而使用 函數 apply / call 方法能夠改變 this 的指向(再次強調,由於函數也是對象,因此也會有屬性和方法)
function func(a,b,c) {
console.log(a,b,c)
console.log(this)
}
func.call({a:1},1,2,3) // 1,2,3 {a:1}
func.call('123',1,2,3) // String{"123"} {a:1}
func.apply({a:1},[1,2,3]) // 1,2,3 {a:1}
複製代碼
apply 和 call 的區別在於 apply 的第一個參數爲即將執行的函數的 this 值,第二個參數爲數組或者類數組,表明即將執行的函數的參數,而 call 第一個參數相同,第二個至最後的參數表明即將執行的參數
即 apply 會用數組保存函數的參數,call 則會平鋪,除此之外沒有區別(雖然 call 方法必需要將函數參數平鋪,可是可使用 ES6 的擴展運算符將其寫爲數組的形式,如今更推薦使用 call )
另外 apply 和 call 都會讓第一個參數進行裝箱操做,即若是傳入一個基本類型且基本類型有包裝類型(下文會詳細解釋包裝類型),則 this 的值爲傳入的基本類型的包裝類型,例子中將 string 類型變成了 String 的包裝類型
在非嚴格模式下,若是 this 的值爲 null / undefined,則自動會指向全局的 window 對象,而嚴格模式則不會有這個行爲(值得注意的是這個並不是 apply / call 的行爲)
function func() {
console.log(this)
}
func.call(undefined) // window 對象
function func2() {
"use strict"
console.log(this)
}
func2.call(undefined) // undefined
複製代碼
bind 和 apply / call 方法相似,也是一個用來改變函數 this 指向的方法,區別在於 bind 會返回一個被綁定 this 指向函數,而 apply / call 則直接會運行它,若是須要綁定 this 指向,又不想當即執行的話,可使用 bind 方法,等須要使用時再調用綁定後的函數
bind 第一個參數爲 this 指向,第二個至之後的參數爲給綁定的函數預先傳入的參數,預置參數的函數一般也被稱爲偏函數
function func(a,b,c,d) {
console.log(this)
console.log(a,b,c,d)
}
let boundFunc = func.bind({a:1},1,2)
boundFunc(3,4) // {a:1} 1,2,3,4
複製代碼
經過 bind 預先傳入了參數 1,2,當調用綁定後的函數時,它會預先傳入 1,2 做爲第一第二個參數,此時再給函數傳入參數,會做爲第三第四的參數,最終打印 1,2,3,4
爲了便於操做基本類型的值,ECMAScript 提供了 3 個特殊的引用類型:
它們有別於 boolean,number,string ,能夠發現它們首字母是大寫,意味着它們是引用類型,也就是對象
JS 高級程序設計中說到:
每當讀取一個基本類型值的時候,後臺就會建立一個對應的基本包裝類型對象,從而讓咱們可以調用一些方法來操做這些數據
var s1 = "some text"
var s2 = s1.substring(2)
複製代碼
咱們要知道,基本類型是沒有任何方法的,也就是說 "some text" 這個字符串是沒有 substring 這個方法的,那爲何第二行代碼不會報錯呢?
緣由在於,當第二行代碼訪問保存着字符串的變量 s1 時,會處於一種讀取模式,嘗試從內存中讀取這個字符串的值,而在讀取模式中訪問字符串時,會有如下操做
能夠這樣理解
var s1 = "some text"
// 讀取模式
s1 = new String("some text")
var s2 = s1.substring(2)
s1 = "some text" // 還原成基本類型
複製代碼
String 包裝類型做爲引用類型,它是含有 substring 方法的,因此須要將基本類型轉換爲包裝類型才能執行方法,並將返回的結果賦值給變量 s2,最後再將 s1 還原成一開始的基本類型
另外自動建立 (並不是主動調用 new 建立的對象) 的包裝類型只會存在於代碼執行瞬間,而後當即銷燬
直接調用 String 函數生成的是基本類型,而使用 new 關鍵字將 String 做爲構造函數使用,則生成的是包裝類型
let str = String("abc") // "abc" string 基本類型
let wrappedStr = new String("abc") // String {"abc"} String 包裝類型
let boolean = Boolean(true) // true boolean 基本類型
let wrappedBoolean = new Boolean(true) // Boolean {true} Boolean 包裝類型
let number = Number(123) // 123 number 基本類型
let wrappedNumber = new Number(123) // Number {123} Number 包裝類型
複製代碼
通常狀況下不推薦主動生成包裝類型,由於容易和基本類型搞混
toFixed
是 Number 包裝類型下的一個方法,用於按照指定小數位返回數值的字符串表示,當數值小於傳入當參數,會四捨五入
let num = 10
console.log(num.toFixed(2)) // "10.00"
let num2 = 1.68
console.log(num.toFixed(1)) // "1.7"
複製代碼
可是使用 toFixed 時須要考慮到 Javascript 的小數的精度問題
let num = 1.335
console.log(num.toFixed(2)) // "1.33"
console.log(num.toFixed(50)) // "1.3349999999999999644..."
複製代碼
事實上數字 1.335 在語言底層並非真的以 1.335 來存儲的,經過 toFixed 方法返回小數點後 50 位能夠發現,1.335 真正的值爲 1.3349999... 以後就是無限的循環,因爲 JS 最多能表示的精度的長度是 16,全部的小數都只會精確到小數點後 16 位同時自動湊整,因此就進位以後就獲得了 1.335
因爲表明的真實數字是 1.3349999...,因此 toFixed 四捨五入後的結果也就是 1.33 了,由於下一位是 4 被捨去了
slice 方法同時可使用於數組類型和 String 包裝類型,具體的特色能夠看上面的 Array 章節
indexOf 方法同時可使用於數組類型和 String 包裝類型,它會從第一個位置開始遍歷,尋找參數在字符串(數組)中的位置並返回下標,同時它還接受第二個參數用於在指定的位置以後開始尋找
let str = "hello world"
// 從下標 6 的位置開始尋找字符串 o
// 但返回值仍相對於整個字符串的位置
console.log(str.indexOf('o',6)) // 7
複製代碼
儘管 ES6 有 includes 和 find 之類更強大的方法去尋找元素,可是若是須要從某個指定位置開始尋找元素,使用 indexOf 會更加的方便,如下例子會返回字符串中單詞 e 的全部下標
let str = ` React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes `
let arr = []
let pos = str.indexOf('e')
while (pos > -1) {
arr.push(pos)
pos = str.indexOf('e',pos + 1)
}
console.log(arr) // [6,14,25,34,37,42,49,62,73,77,85,94,122,132,138,149,156,159,169,183,190,208]
複製代碼
trim 方法能夠去除字符串首尾的空格,對於一些表單的輸入是不容許有空格的,可使用 trim 來去除,同時在 ES10 中,還有 trimStart 和 trimEnd 這兩種方法,分別去除字符串前面和後面的空格
replace 方法能夠用來根據參數替換字符串,第一個參數能夠是字符串也能夠是正則,第二個參數能夠是字符串也能夠是函數
str.replace(regexp|substr, newSubStr|function)
當參數都是字符串時,只是簡單的在 str 中找到第一個參數第一次出現的位置,並替換成第二個參數
當第一個參數是正則時,會替換和正則匹配的字符串,同時若是正則中含有 g 標誌,會進行全局搜索,當第一次匹配到對應字符串後,再也不中止匹配,而是繼續日後搜索是否仍有可替換的字符串
同時第二個參數還能夠傳入函數,匹配到的字符串會替換爲函數的返回值,函數的引入使得 replace 方法更加的靈活
let str = "hello world"
console.log(str.replace('l','x')) // "hexlo world" 只將第一個 l 替換爲 x
console.log(str.replace(/l/,'x')) // "hexlo world" 同上
console.log(str.replace(/l/g,'x')) // "hexxo worxd" 經過給正則添加全局搜索的標誌符,能夠實現全局替換
console.log(str.replace(/l/, (match,index,str) => {
console.log(match) // 'l' 匹配的子串
console.log(index) // 2 匹配到的子字符串在原字符串中的偏移量
console.log(str) // 'hello world' 被匹配的原字符串
return 'q' // 返回字符串 q 表明將第一個匹配到的字符串 l 替換爲 q
})) // "heqlo world"
複製代碼
另外若是第一個正則含有捕獲組,那麼第二個參數還能夠拿到前面正則中的捕獲組,更多詳情能夠查看 MDN
關於 split 方法用來分割字符串,除了咱們經常使用的傳入一個字符串外,其實 split 還支持傳入一個正則做爲參數,若是是一個包含捕獲組的正則表達式,會將捕獲組也放入最終返回的數組中
let str = "hello world"
console.log(str.split(/(l)/)) // [ 'he', 'l', '', 'l', 'o wor', 'l', 'd' ]
複製代碼
能夠看到這裏不只經過分隔符 'l' 將字符串分割,還將分隔符也保存在了數組中
事實上,並無所謂的全局變量或全局函數,全部在全局做用域中定義的屬性和函數,都是 Global 對象的屬性,包括原生的引用類型都是 Global 對象的屬性, Global 對象通常不容許被直接訪問
之因此是 Global 對象而不是 window 對象,是由於 window 對象只在瀏覽器中存在,在 node 環境下,全局對象爲 global,而在 webworker 中全局對象爲 self,它們雖然都不是 Global 對象,可是都做爲承擔它的對象而存在
// 在 node 環境運行如下代碼
console.log(global) // node 中的全局對象 global
console.log(window) // 報錯 window 對象不存在
// 在 webworker 中不容許操做 DOM,也沒有 window 對象
複製代碼
在本系列第一篇中我提到過,在瀏覽器端,JavaScript 有三部分組成
也就是說有一部分的方法並不屬於 ECMAScript 的範疇,好比 BOM 提供的 alert,console 方法,或者 DOM 提供的 createElement,appendChild,換句話說,不僅是 JavaScript,別的語言也能操做 DOM ,操做控制檯等,瀏覽器只提供了訪問它們的接口
JavaScript Weekly 這周正好給我推薦了一篇不錯的文章,介紹 ECMAScript 最新的提案 globalThis
,它存在與全部 JavaScript 運行的平臺,並指向全局的 Global 對象
Explaining the globalThis ES Proposal
《JavaScript 高級程序設計第三版》
《你不知道的JavaScript》