ECMA-262 對內建物件的定義是「由 JavaScript 實現提供的、不依賴於宿主環境的物件,這些物件在 JavaScript 程式執行之前就已經存在了」。意思就是說,開發人員不必顯式地例項化內建物件,因為它們已經例項化了。前面我們已經介紹了大多數內建物件,例如 Object
、Array
和 String
。ECMA-262 還定義了兩個單體內建物件:Global
和 Math
。
Global
物件
Global
物件可以說是 JavaScript 中最特別的一個物件了,因為不管你從什麼角度上看,這個物件都是不存在的。Global
物件在某種意義上是作為一個終極的「兜底兒物件」來定義的。換句話說,不屬於任何其他物件的屬性和方法,最終都是它的屬性和方法。所有在全域性作用域中定義的屬性和函式,都是 Global
物件的屬性。本書前面介紹過的那些函式,諸如 isNaN()
、isFinite()
、parseInt()
以及 parseFloat()
,實際上全都是 Global
物件的方法。除此之外,Global
物件還包含其他一些方法。
URI 編碼方法
Global
物件的 encodeURI()
和 encodeURIComponent()
方法可以對 URI(Uniform Resource Identifiers,通用資源識別符號)進行編碼,以便傳送給瀏覽器。有效的 URI 中不能包含某些字元,例如空格。而這兩個 URI 編碼方法就可以對 URI 進行編碼,它們用特殊的 UTF-8 編碼替換所有無效的字元,從而讓瀏覽器能夠接受和理解。
其中,encodeURI()
主要用於整個 URI,而 encodeURIComponent()
主要用於對 URI 中的某一段進行編碼。它們的主要區別在於,encodeURI()
不會對本身屬於 URI 的特殊字元進行編碼,例如冒號、正斜槓、問號和井字號;而 encodeURIComponent()
則會對它發現的任何非標準字元進行編碼。來看下面的例子。
var uri = "http://shijiajie.com/illegal value.htm#start";
console.log(encodeURI(uri));
// "http://shijiajie.com/illegal%20value.htm#start"
console.log(encodeURIComponent(uri));
// "http%3A%2F%2Fshijiajie.com%2Fillegal%20value.htm%23start"複製程式碼
使用 encodeURI()
編碼後的結果是除了空格之外的其他字元都原封不動,只有空格被替換成了 %20
。而 encodeURIComponent()
方法則會使用對應的編碼替換所有非字母數字字元。這也正是可以對整個 URI 使用 encodeURI()
,而只能對附加在現有 URI 後面的字串使用 encodeURIComponent()
的原因所在。
一般來說,我們使用 encodeURIComponent()
方法的時候要比使用 encodeURI()
更多,因為在實踐中更常見的是對查詢字串引數而不是對基礎 URI 進行編碼。
與 encodeURI()
和 encodeURIComponent()
方法對應的兩個方法分別是 decodeURI()
和 decodeURIComponent()
。其中,decodeURI()
只能對使用 encodeURI()
替換的字元進行解碼。例如,它可將 %20
替換成一個空格,但不會對 %23
作任何處理,因為 %23
表示井字號 #
,而井字號不是使用 encodeURI()
替換的。同樣地,decodeURIComponent()
能夠解碼使用 encodeURIComponent()
編碼的所有字元,即它可以解碼任何特殊字元的編碼。來看下面的例子:
var uri = "http%3A%2F%2Fshijiajie.com%2Fillegal%20value.htm%23start";
console.log(decodeURI(uri));
// http%3A%2F%2Fshijiajie.com%2Fillegal value.htm%23start
console.log(decodeURIComponent(uri));
// http://shijiajie.com/illegal value.htm#start複製程式碼
這裡,變數 uri
包含著一個由 encodeURIComponent()
編碼的字串。在第一次呼叫 decodeURI()
輸出的結果中,只有 %20
被替換成了空格。而在第二次呼叫 decodeURIComponent()
輸出的結果中,所有特殊字元的編碼都被替換成了原來的字元,得到了一個未經轉義的字串(但這個字串並不是一個有效的 URI)。
eval()
方法
eval()
方法就像是一個完整的 JavaScript 解析器,它只接受一個引數,即要執行的 JavaScript 字串。看下面的例子:
eval("console.log('hi')");複製程式碼
這行程式碼的作用等價於下面這行程式碼:
console.log("hi");複製程式碼
當解析器發現程式碼中呼叫 eval()
方法時,它會將傳入的引數當作實際的 JavaScript 語句來解析,然後把執行結果插入到原位置。通過 eval()
執行的程式碼被認為是包含該次呼叫的執行環境的一部分,因此被執行的程式碼具有與該執行環境相同的作用域鏈。這意味著通過 eval()
執行的程式碼可以引用在包含環境中定義的變數,舉個例子:
var msg = "hello world";
eval("console.log(msg)"); // "hello world"複製程式碼
可見,變數 msg
是在 eval()
呼叫的環境之外定義的,但其中呼叫的 console.log()
仍然能夠顯示 "hello world"
。這是因為上面第二行程式碼最終被替換成了一行真正的程式碼。同樣地,我們也可以在 eval()
呼叫中定義一個函式,然後再在該呼叫的外部程式碼中引用這個函式:
eval("function sayHi() { console.log('hi'); }");
sayHi(); // "hi"複製程式碼
顯然,函式 sayHi()
是在 eval()
內部定義的。但由於對 eval()
的呼叫最終會被替換成定義函式的實際程式碼,因此可以在下一行呼叫 sayHi()
。對於變數也一樣:
eval("var msg = 'hello world';");
console.log(msg); // "hello world"複製程式碼
在 eval()
中建立的任何變數或函式都不會被提升,因為在解析程式碼的時候,它們被包含在一個字串中;它們只在 eval()
執行的時候建立。
嚴格模式下,在外部訪問不到 eval()
中建立的任何變數或函式,因此前面兩個例子都會導致錯誤。同樣,在嚴格模式下,為 eval
賦值也會導致錯誤:
"use strict";
eval = "hi"; // causes error複製程式碼
能夠解釋程式碼字串的能力非常強大,但也非常危險。因此在使用 eval()
時必須極為謹慎,特別是在用它執行使用者輸入資料的情況下。否則,可能會有惡意使用者輸入威脅你的站點或應用程式安全的程式碼(即所謂的程式碼注入)。
Global
物件的屬性
Global
物件還包含一些屬性,其中一部分屬性已經在本書前面介紹過了。例如,特殊的值 undefined
、NaN
以及 Infinity
都是 Global
物件的屬性。此外,所有原生引用型別的建構函式,像 Object
和 Function
,也都是 Global
物件的屬性。下表列出了 Global
物件的所有屬性。
屬性 | 說明 | 屬性 | 說明 |
---|---|---|---|
undefined | 特殊值undefined | Date | 建構函式Date |
NaN | 特殊值NaN | RegExp | 建構函式RegExp |
Infinity | 特殊值Infinity | Error | 建構函式Error |
Object | 建構函式Object | EvalError | 建構函式EvalError |
Array | 建構函式Array | RangeError | 建構函式RangeError |
Function | 建構函式Function | ReferenceError | 建構函式ReferenceError |
Boolean | 建構函式Boolean | SyntaxError | 建構函式SyntaxError |
String | 建構函式String | TypeError | 建構函式TypeError |
Number | 建構函式Number | URIError | 建構函式URIError |
ECMAScript 5 明確禁止給 undefined
、NaN
和 Infinity
賦值,這樣做即使在非嚴格模式下也會導致錯誤。
window
物件
JavaScript 雖然沒有指出如何直接訪問 Global
物件,但 Web 瀏覽器都是將這個全域性物件作為 window
物件的一部分加以實現的。因此,在全域性作用域中宣告的所有變數和函式,就都成為了 window
物件的屬性。來看下面的例子。
var color = "red";
function sayColor(){
console.log(window.color);
}
window.sayColor(); // "red"複製程式碼
JavaScript 中的 window
物件除了扮演規定的 Global
物件的角色外,還承擔了很多別的任務。
## Math
物件
JavaScript 還為儲存數學公式和資訊提供了一個公共位置,即 Math
物件。與我們在 JavaScript
直接編寫的計算功能相比,Math
物件提供的計算功能執行起來要快得多。Math
物件中還提供了輔助完成這些計算的屬性和方法。
Math
物件的屬性
Math
物件包含的屬性大都是數學計算中可能會用到的一些特殊值。下表列出了這些屬性。
屬性 | 說明 |
---|---|
Math.E | 自然對數的底數,即常量e的值 |
Math.LN10 | 10的自然對數 |
Math.LN2 | 2的自然對數 |
Math.LOG2E | 以2為底e的對數 |
Math.LOG10E | 以10為底e的對數 |
Math.PI | π的值 |
Math.SQRT1_2 | 1/2的平方根(即2的平方根的倒數) |
Math.SQRT2 | 2的平方根 |
min()
和 max()
方法
Math
物件還包含許多方法,用於輔助完成簡單和複雜的數學計算。其中,min()
和 max()
方法用於確定一組數值中的最小值和最大值。這兩個方法都可以接收任意多個數值引數,如下面的例子所示。
var max = Math.max(3, 54, 32, 16);
console.log(max); // 54
var min = Math.min(3, 54, 32, 16);
console.log(min); // 3複製程式碼
要找到陣列中的最大或最小值,可以像下面這樣使用 apply()
方法。
var values = [1, 2, 3, 4, 5, 6, 7, 8];
var max = Math.max.apply(Math, values);
console.log(max); // 8複製程式碼
這個技巧的關鍵是把 Math
物件作為 apply()
的第一個引數,從而正確地設定 this
值。然後,可以將任何陣列作為第二個引數。
舍入方法
下面來介紹將小數值舍入為整數的幾個方法:Math.ceil()
、Math.floor()
和 Math.round()
。這三個方法分別遵循下列舍入規則:
Math.ceil()
執行向上舍入,即它總是將數值向上舍入為最接近的整數;Math.floor()
執行向下舍入,即它總是將數值向下舍入為最接近的整數;Math.round()
執行標準舍入,即它總是將數值四捨五入為最接近的整數。
下面是使用這些方法的示例:
console.log(Math.ceil(25.9)); // 26
console.log(Math.ceil(25.5)); // 26
console.log(Math.ceil(25.1)); // 26
console.log(Math.round(25.9)); // 26
console.log(Math.round(25.5)); // 26
console.log(Math.round(25.1)); // 25
console.log(Math.floor(25.9)); // 25
console.log(Math.floor(25.5)); // 25
console.log(Math.floor(25.1)); // 25複製程式碼
random()
方法
Math.random()
方法返回介於0和1之間一個隨機數,包括0而不包括1。對於某些站點來說,這個方法非常實用,因為可以利用它來隨機顯示一些名人名言和新聞事件。套用下面的公式,就可以利用 Math.random()
從某個整數範圍內隨機選擇一個值。
值 = Math.floor(Math.random() * 可能值的總數 + 第一個可能的值)複製程式碼
公式中用到了 Math.floor()
方法,這是因為 Math.random()
總返回一個小數值。而用這個小數值乘以一個整數,然後再加上一個整數,最終結果仍然還是一個小數。舉例來說,如果你想選擇一個1到10之間的數值,可以像下面這樣編寫程式碼:
var num = Math.floor(Math.random() * 10 + 1);複製程式碼
總共有10個可能的值(1到10),而第一個可能的值是1。而如果想要選擇一個介於2到10之間的值,就應該將上面的程式碼改成這樣:
var num = Math.floor(Math.random() * 9 + 2);複製程式碼
從2數到10要數9個數,因此可能值的總數就是9,而第一個可能的值就是2。多數情況下,其實都可以通過一個函式來計算可能值的總數和第一個可能的值,例如:
function selectFrom(lowerValue, upperValue) {
var choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}
var num = selectFrom(2, 10);
console.log(num); // 介於2和10之間(包括2和10)的一個數值複製程式碼
函式 selectFrom()
接受兩個引數:應該返回的最小值和最大值。而用最大值減最小值再加1得到了可能值的總數,然後它又把這些數值套用到了前面的公式中。這樣,通過呼叫 selectFrom(2,10)
就可以得到一個介於2和10之間(包括2和10)的數值了。利用這個函式,可以方便地從陣列中隨機取出一項,例如:
var colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
var color = colors[selectFrom(0, colors.length-1)];
console.log(color); // 可能是陣列中包含的任何一個字串複製程式碼
其他方法
Math
物件中還包含其他一些與完成各種簡單或複雜計算有關的方法,但詳細討論其中每一個方法的細節及適用情形超出了本書的範圍。下面我們就給出一個表格,其中列出了這些沒有介紹到的 Math
物件的方法。
方法 | 說明 |
---|---|
Math.abs(num) | 返回num的絕對值 |
Math.asin(x) | 返回x的反正弦值 |
Math.exp(num) | 返回Math.E的num次冪 |
Math.atan(x) | 返回x的反正切值 |
Math.log(num) | 返回num的自然對數 |
Math.atan2(y,x) | 返回y/x的反正切值 |
Math.pow(num,power) | 返回num的power次冪 |
Math.cos(x) | 返回x的餘弦值 |
Math.sqrt(num) | 返回num的平方根 |
Math.sin(x) | 返回x的正弦值 |
Math.acos(x) | 返回x的反餘弦值 |
Math.tan(x) | 返回x的正切值 |
雖然 ECMA-262 規定了這些方法,但不同實現可能會對這些方法採用不同的演算法。畢竟,計算某個值的正弦、餘弦和正切的方式多種多樣。也正因為如此,這些方法在不同的實現中可能會有不同的精度。
關卡
// 如何高效產生m個n範圍內的不重複隨機數(m<=n)< span="">
var getRandomNumber = function(n, m){
// 待實現方法體
}
console.log(getRandomNumber(20, 3)); // 8,4,19=n)<>複製程式碼
更多
關注微信公眾號「劼哥舍」回覆「答案」,獲取關卡詳解。
關注 github.com/stone0090/j…,獲取最新動態。