《JavaScript 闖關記》之單體內建物件

劼哥stone發表於2016-11-17

ECMA-262 對內建物件的定義是「由 JavaScript 實現提供的、不依賴於宿主環境的物件,這些物件在 JavaScript 程式執行之前就已經存在了」。意思就是說,開發人員不必顯式地例項化內建物件,因為它們已經例項化了。前面我們已經介紹了大多數內建物件,例如 ObjectArrayString。ECMA-262 還定義了兩個單體內建物件:GlobalMath

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 物件還包含一些屬性,其中一部分屬性已經在本書前面介紹過了。例如,特殊的值 undefinedNaN 以及 Infinity 都是 Global 物件的屬性。此外,所有原生引用型別的建構函式,像 ObjectFunction,也都是 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 明確禁止給 undefinedNaNInfinity 賦值,這樣做即使在非嚴格模式下也會導致錯誤。

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…,獲取最新動態。

相關文章