大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章

第二十一章:数学

原文:21. Math

译者:飞龙

协议:CC BY-NC-SA 4.0

Math对象用作多个数学函数的命名空间。本章提供了一个概述。

数学属性

Math的属性如下:

Math.E

欧拉常数(e)

Math.LN2

2 的自然对数

Math.LN10

10 的自然对数

Math.LOG2E

e 的底数 2 对数

Math.LOG10E

e 的十进制对数

Math.PI

圆的周长与直径的比值(3.14159 …),π

Math.SQRT1_2

一半的平方根,大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章_第1张图片

Math.SQRT2

二的平方根,大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章_第2张图片

数值函数

Math的数值函数包括以下内容:

Math.abs(x)

返回x的绝对值。

Math.ceil(x)

返回大于等于x的最小整数:

> Math.ceil(3.999)
4
> Math.ceil(3.001)
4
> Math.ceil(-3.001)
-3
> Math.ceil(3.000)
3

有关将浮点数转换为整数的更多信息,请参阅转换为整数。

Math.exp(x)

返回 e^x,其中 e 是欧拉常数(Math.E)。这是Math.log()的反函数。

Math.floor(x)

返回小于等于x的最大整数:

> Math.floor(3.999)
3
> Math.floor(3.001)
3
> Math.floor(-3.001)
-4
> Math.floor(3.000)
3

有关将浮点数转换为整数的更多信息,请参阅转换为整数。

Math.log(x)

返回x的自然(以 e 为底)对数 ln(x)。这是Math.exp()的反函数。

Math.pow(x, y)

返回 x^y,xy次幂:

> Math.pow(9, 2)
81
> Math.pow(36, 0.5)
6

Math.round(x)

返回x四舍五入到最接近的整数(如果在两个整数之间,则为较大的整数):

> Math.round(3.999)
4
> Math.round(3.001)
3
> Math.round(3.5)
4
> Math.round(-3.5)
-3

有关将浮点数转换为整数的更多信息,请参阅转换为整数。

Math.sqrt(x)

返回大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章_第3张图片x的平方根:

> Math.sqrt(256)
16

三角函数

三角函数方法接受弧度作为角度并返回。以下函数向您展示了如何实现转换,如果需要的话:

  • 从度到弧度:

    function toRadians(degrees) {
        return degrees / 180 * Math.PI;
    }
    

以下是交互:

    > toRadians(180)
    3.141592653589793
    > toRadians(90)
    1.5707963267948966
    ```

+   从弧度到度:

    ```js
    function toDegrees(radians) {
        return radians / Math.PI * 180;
    }
    ```

以下是交互:

```js
    > toDegrees(Math.PI * 2)
    360
    > toDegrees(Math.PI)
    180
    ```

三角函数方法如下:

`Math.acos(x)`

返回`x`的反余弦值。

`Math.asin(x)`

返回`x`的反正弦值。

`Math.atan(x)`

返回`x`的反正切值。

`Math.atan2(y, x)`

返回商的反正切值![](inleq_2104.png)。

`Math.cos(x)`

返回`x`的余弦值。

`Math.sin(x)`

返回`x`的正弦值。

`Math.tan(x)`

返回`x`的正切值。

## 其他函数

以下是剩余的`Math`函数:

`min(x1?, x2?, ...)`

返回参数中的最小数:

```js
> Math.min()
Infinity
> Math.min(27)
27
> Math.min(27, -38)
-38
> Math.min(27, -38, -43)
-43

通过apply()在数组上使用它(参见func.apply(thisValue, argArray)):

> Math.min.apply(null, [27, -38, -43])
-43

max(x1?, x2?, ...)

返回参数中的最大数:

> Math.max()
-Infinity
> Math.max(7)
7
> Math.max(7, 10)
10
> Math.max(7, 10, -333)
10

通过apply()在数组上使用它(参见func.apply(thisValue, argArray)):

> Math.max.apply(null, [7, 10, -333])
10

Math.random()

/**
 * Compute a random integer within the given range.
 *
 * @param [lower] Optional lower bound. Default: zero.
 * @returns A random integer i, lower ≤ i < upper
 */
function getRandomInteger(lower, upper) {
    if (arguments.length === 1) {
        upper = lower;
        lower = 0;
    }
    return Math.floor(Math.random() * (upper - lower)) + lower;
}

第二十二章:JSON

原文:22. JSON

译者:飞龙

协议:CC BY-NC-SA 4.0

JSON(JavaScript 对象表示)是一种用于数据存储的纯文本格式。它已经成为 Web 服务、配置文件等数据交换格式的一种流行选择。ECMAScript 5 有一个 API,用于将 JSON 格式的字符串转换为 JavaScript 值(解析)以及反之(字符串化)。

背景

本节解释了 JSON 是什么以及它是如何创建的。

数据格式

JSON 将数据存储为纯文本。它的语法是 JavaScript 表达式语法的子集。例如:

{
    "first": "Jane",
    "last": "Porter",
    "married": true,
    "born": 1890,
    "friends": [ "Tarzan", "Cheeta" ]
}

JSON 使用 JavaScript 表达式中的以下结构:

复合

JSON 数据的对象和 JSON 数据的数组

原子

字符串、数字、布尔值和空值

它遵循以下规则:

  • 字符串必须始终用双引号括起来;例如,像'mystr'这样的字符串字面量是非法的。

  • 属性键必须用双引号括起来

历史

Douglas Crockford 于 2001 年发现了 JSON。他给它起了个名字,并在json.org上发布了一个规范:

我发现了 JSON。我不主张发明 JSON,因为它已经存在于自然界中。我所做的是发现它,我给它起了名字,我描述了它的有用之处。我不主张自己是第一个发现它的人;我知道至少有其他人在我之前至少一年发现了它。我发现的最早的情况是,Netscape 有人在至少 1996 年就开始使用 JavaScript 数组文字进行数据通信,而这至少比我想到这个想法早了五年。

最初,Crockford 希望 JSON 有一个名字叫JavaScript 标记语言,但是 JSML 的首字母缩写已经被JSpeech 标记语言使用了。

JSON 规范已经被翻译成许多人类语言,现在有许多编程语言的库支持解析和生成 JSON。

语法

Douglas Crockford 创建了一张 JSON 名片,正面有一个徽标(参见图 22-1),背面有完整的语法(参见图 22-2)。这使得 JSON 的简单性在视觉上显而易见。

大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章_第4张图片图 22-1:JSON 名片的正面显示了一个徽标(来源:Eric Miraglia)。

大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章_第5张图片图 22-2:JSON 名片的背面包含完整的语法(来源:Eric Miraglia)。

语法可以转录如下:

object
    { }
    { members }

members
    pair
    pair , members

pair
    string : value
    array
    [ ]
    [ elements ]

elements
    value
    value , elements

value
    string
    number
    object
    array
    true
    false
    null

string
    ""
    " chars "

chars
    char
    char chars

char
    any-Unicode-character-except-"-or-\-or-control-character
    \" \\ \/ \b \f \n \r \t
    \u four-hex-digits

number
    int
    int frac
    int exp
    int frac exp

int
    digit
    digit1-9 digits
    - digit
    - digit1-9 digits

frac
    . digits
    exp
    e digits

digits
    digit
    digit digits

e
    e e+ e-
    E E+ E-

全局变量JSON用作生成和解析带有 JSON 数据的字符串的函数的命名空间。

JSON.stringify(value, replacer?, space?)

JSON.stringify(value, replacer?, space?)将 JavaScript 值value转换为 JSON 格式的字符串。它有两个可选参数。

可选参数replacer用于在对其进行字符串化之前更改value。它可以是:

  • 一个节点访问者(参见通过节点访问者转换数据)在将其字符串化之前转换值树。例如:

    function replacer(key, value) {
        if (typeof value === 'number') {
            value = 2 * value;
        }
        return value;
    }
    

使用 replacer:

    > JSON.stringify({ a: 5, b: [ 2, 8 ] }, replacer)
    '{"a":10,"b":[4,16]}'
    ```

+   隐藏所有不在列表中的属性键(非数组对象的属性)的属性白名单。例如:

    ```js
    > JSON.stringify({foo: 1, bar: {foo: 1, bar: 1}}, ['bar'])
    '{"bar":{"bar":1}}'
    ```

白名单对数组没有影响:

```js
    > JSON.stringify(['a', 'b'], ['0'])
    '["a","b"]'
    ```

可选参数`space`影响输出的格式。如果没有这个参数,`stringify`的结果将是单行文本:

```js
> console.log(JSON.stringify({a: 0, b: ['\n']}))
{"a":0,"b":["\n"]}

使用它,可以插入换行符,并且通过数组和对象的每个嵌套级别增加缩进。有两种指定缩进方式的方法:

一个数字

将数字乘以缩进级别并将行缩进为相同数量的空格。小于 0 的数字被解释为 0;大于 10 的数字被解释为 10:

> console.log(JSON.stringify({a: 0, b: ['\n']}, null, 2))
{
  "a": 0,
  "b": [
    "\n"
  ]
}

一个字符串

要缩进,重复给定的字符串以表示每个缩进级别。只使用字符串的前 10 个字符:

> console.log(JSON.stringify({a: 0, b: ['\n']}, null, '|--'))
{
|--"a": 0,
|--"b": [
|--|--"\n"
|--]
}

因此,以下对JSON.stringify()的调用会将对象打印为一个格式良好的树:

JSON.stringify(data, null, 4)

JSON.stringify()忽略的数据

在对象中,JSON.stringify()只考虑可枚举的自有属性(参见属性特性和属性描述符)。以下示例演示了忽略了不可枚举的自有属性obj.foo

> var obj = Object.defineProperty({}, 'foo', { enumerable: false, value: 7 });
> Object.getOwnPropertyNames(obj)
[ 'foo' ]
> obj.foo
7
> JSON.stringify(obj)
'{}'

JSON.stringify()处理不受 JSON 支持的值(例如函数和undefined)的方式取决于它们遇到的位置。不支持的值本身导致stringify()返回undefined而不是字符串:

> JSON.stringify(function () {})
undefined

其值不受支持的属性将被简单地忽略:

> JSON.stringify({ foo: function () {} })
'{}'

数组中不支持的值将被字符串化为null

> JSON.stringify([ function () {} ])
'[null]'

toJSON()方法

如果JSON.stringify()遇到具有toJSON方法的对象,则使用该方法获取要字符串化的值。例如:

> JSON.stringify({ toJSON: function () { return 'Cool' } })
'"Cool"'

日期已经有一个产生 ISO 8601 日期字符串的toJSON方法:

> JSON.stringify(new Date('2011-07-29'))
'"2011-07-28T22:00:00.000Z"'

toJSON方法的完整签名如下:

function (key)

key参数允许您根据上下文以不同方式进行字符串化。它始终是一个字符串,并指示在父对象中找到您的对象的位置:

根位置

空字符串

属性值

属性键

数组元素

元素的索引作为字符串

我将通过以下对象演示toJSON()

var obj = {
    toJSON: function (key) {
        // Use JSON.stringify for nicer-looking output
        console.log(JSON.stringify(key));
        return 0;
    }
};

如果使用JSON.stringify(),则每次出现obj都会被替换为0。通知toJSON()方法在属性键'foo'和数组索引 0 处遇到了obj

> JSON.stringify({ foo: obj, bar: [ obj ]})
"foo"
"0"
'{"foo":0,"bar":[0]}'

内置的toJSON()方法如下:

  • Boolean.prototype.toJSON()

  • Number.prototype.toJSON()

  • String.prototype.toJSON()

  • Date.prototype.toJSON()

JSON.parse(text, reviver?)

JSON.parse(text, reviver?)解析text中的 JSON 数据并返回 JavaScript 值。以下是一些示例:

> JSON.parse("'String'") // illegal quotes
SyntaxError: Unexpected token ILLEGAL
> JSON.parse('"String"')
'String'
> JSON.parse('123')
123
> JSON.parse('[1, 2, 3]')
[ 1, 2, 3 ]
> JSON.parse('{ "hello": 123, "world": 456 }')
{ hello: 123, world: 456 }

可选参数reviver是一个节点访问者(参见通过节点访问者转换数据),可用于转换解析后的数据。在此示例中,我们将日期字符串转换为日期对象:

function dateReviver(key, value) {
    if (typeof value === 'string') {
        var x = Date.parse(value);
        if (!isNaN(x)) { // valid date string?
            return new Date(x);
        }
    }
    return value;
}

以下是交互:

> var str = '{ "name": "John", "birth": "2011-07-28T22:00:00.000Z" }';
> JSON.parse(str, dateReviver)
{ name: 'John', birth: Thu, 28 Jul 2011 22:00:00 GMT }

通过节点访问者转换数据

JSON.stringify()JSON.parse()都允许您通过传递函数来转换 JavaScript 数据:

  • JSON.stringify()允许您在将其转换为 JSON 之前更改 JavaScript 数据。

  • JSON.parse()解析 JSON,然后让您对生成的 JavaScript 数据进行后处理。

JavaScript 数据是一个树,其复合节点是数组和对象,其叶子是原始值(布尔值,数字,字符串,null)。让我们将传递的转换函数称为节点访问者。这些方法遍历树并为每个节点调用访问者。然后可以选择替换或删除节点。节点访问者的签名如下:

function nodeVisitor(key, value)

参数是:

this

当前节点的父节点。

key

当前节点位于其父节点内的键。key 总是一个字符串。

当前节点。

根节点 root 没有父节点。当访问 root 时,为其创建了一个伪父节点,并且参数具有以下值:

  • this{ '': root }

  • key''

  • valueroot

节点访问者有三种返回值的选项:

  • 返回 value,然后不执行任何更改。

  • 返回不同的值。然后当前节点被替换。

  • 返回 undefined。然后移除节点。

以下是节点访问者的示例。它记录了传递给它的值。

function nodeVisitor(key, value) {
    console.log([
        // Use JSON.stringify for nicer-looking output
        JSON.stringify(this), // parent
        JSON.stringify(key),
        JSON.stringify(value)
    ].join(' # '));
    return value; // don't change node
}

让我们使用此函数来检查 JSON 方法如何迭代 JavaScript 数据。

JSON.stringify()

特殊的根节点首先出现在前缀迭代中(父节点在子节点之前)。访问的第一个节点始终是伪根。在每次调用后显示的最后一行是 stringify() 返回的字符串:

> JSON.stringify(['a','b'], nodeVisitor)
{"":["a","b"]} # "" # ["a","b"]
["a","b"] # "0" # "a"
["a","b"] # "1" # "b"
'["a","b"]'

> JSON.stringify({a:1, b:2}, nodeVisitor)
{"":{"a":1,"b":2}} # "" # {"a":1,"b":2}
{"a":1,"b":2} # "a" # 1
{"a":1,"b":2} # "b" # 2
'{"a":1,"b":2}'

> JSON.stringify('abc', nodeVisitor)
{"":"abc"} # "" # "abc"
'"abc"'

JSON.parse()

首先是叶子节点,在后缀迭代中(子节点在父节点之前)。访问的最后一个节点始终是伪根。在每次调用后显示的最后一行是 parse() 返回的 JavaScript 值:

> JSON.parse('["a","b"]', nodeVisitor)
["a","b"] # "0" # "a"
["a","b"] # "1" # "b"
{"":["a","b"]} # "" # ["a","b"]
[ 'a', 'b' ]

> JSON.parse('{"a":1, "b":2}', nodeVisitor)
{"a":1,"b":2} # "a" # 1
{"a":1,"b":2} # "b" # 2
{"":{"a":1,"b":2}} # "" # {"a":1,"b":2}
{ a: 1, b: 2 }

> JSON.parse('"hello"', nodeVisitor)
{"":"hello"} # "" # "hello"
'hello'

第二十三章:标准全局变量

原文:23. Standard Global Variables

译者:飞龙

协议:CC BY-NC-SA 4.0

本章是 ECMAScript 规范标准化的全局变量的参考。Web 浏览器有更多全局变量,这些变量在 MDN 上列出。所有全局变量都是全局对象的(自有或继承的)属性(在浏览器中是 window;参见 全局对象)。

构造函数

有关以下构造函数的详细信息,请参见括号中指示的部分:

  • Array([数组构造函数](ch18.html#array_constructor “数组构造函数”))

  • Boolean([原始值的包装对象](ch08.html#wrapper_objects “原始值的包装对象”))

  • Date([日期构造函数](ch20.html#date_constructors “日期构造函数”))

  • Function([使用 new Function() 评估代码](ch23.html#function_constructor “使用 new Function() 评估代码”))

  • Number([原始值的包装对象](ch08.html#wrapper_objects “原始值的包装对象”))

  • 对象([将任何值转换为对象](ch17_split_000.html#toobject “将任何值转换为对象”))

  • RegExp([创建正则表达式](ch19.html#creating_regexps “创建正则表达式”))

  • String([原始值的包装对象](ch08.html#wrapper_objects “原始值的包装对象”))

错误构造函数

有关这些构造函数的详细信息,请参见 [错误构造函数](ch14.html#error_constructors “错误构造函数”):

  • Error

  • EvalError

  • RangeError

  • ReferenceError

  • SyntaxError

  • TypeError

  • URIError

非构造函数

有几个全局函数不是构造函数。它们在本节中列出。

编码和解码文本

以下函数处理 URI 编码和解码的几种方式:

encodeURI(uri)

uri 中对特殊字符进行百分比编码。特殊字符是除以下字符外的所有 Unicode 字符:

URI 字符: ; , / ? : @ & = + $ #
未编码: a-z A-Z 0-9 - _ . ! ~ * ' ( )

例如:

> encodeURI('http://example.com/Für Elise/')
'http://example.com/F%C3%BCr%20Elise/'

encodeURIComponent(uriComponent)

uriComponent 中对所有字符进行百分比编码,除了:

| 未编码: | a-z A-Z 0-9 - _ . ! ~ * ' ( ) |

encodeURI 相反,URL 和文件名中有意义的字符也被编码了。因此,您可以使用此函数将任何文本转换为合法的文件名或 URL 路径段。例如:

> encodeURIComponent('http://example.com/Für Elise/')
'http%3A%2F%2Fexample.com%2FF%C3%BCr%20Elise%2F'

decodeURI(encodedURI)

解码由 encodeURI 生成的百分比编码的 URI:

> decodeURI('http://example.com/F%C3%BCr%20Elise/')
'http://example.com/Für Elise/'

encodeURI 不会对 URI 字符进行编码,decodeURI 也不会对其进行解码,即使它们已经被正确编码:

> decodeURI('%2F')
'%2F'
> decodeURIComponent('%2F')
'/'

decodeURIComponent(encodedURIComponent)

解码由 encodeURIComponent 生成的百分比编码的 URI 组件。与 decodeURI 相反,所有百分比编码的字符都被解码:

> decodeURIComponent('http%3A%2F%2Fexample.com%2FF%C3%BCr%20Elise%2F')
'http://example.com/Für Elise/'

以下内容已被弃用:

  • escape(str)str进行百分比编码。它已被弃用,因为它不能正确处理非 ASCII 字符。请改用encodeURIComponent()

  • unescape(str)str进行百分比解码。它已被弃用,因为它不能正确处理非 ASCII 字符。请改用decodeURIComponent()

对数字进行分类和解析

以下方法有助于对数字进行分类和解析:

  • isFinite(number) (检查是否为无穷大)

  • isNaN(value) (陷阱:检查值是否为 NaN)

  • parseFloat(string) (parseFloat())

  • parseInt(string, radix) (通过 parseInt()获取整数)

通过 eval()和 new Function()动态评估 JavaScript 代码

本节将介绍如何在 JavaScript 中动态评估代码。

使用 eval()评估代码

函数调用:

eval(str)

评估str中的 JavaScript 代码。例如:

> var a = 12;
> eval('a + 5')
17

请注意,eval()在语句上下文中解析(参见表达式与语句):

> eval('{ foo: 123 }')  // code block
123
> eval('({ foo: 123 })')  // object literal
{ foo: 123 }
在严格模式下使用 eval()

对于eval(),您确实应该使用严格模式(参见严格模式)。在松散模式下,评估的代码可以在周围范围内创建局部变量:

function sloppyFunc() {
    eval('var foo = 123');  // added to the scope of sloppyFunc
    console.log(foo);  // 123
}

在严格模式下无法发生:

function strictFunc() {
    'use strict';
    eval('var foo = 123');
    console.log(foo);  // ReferenceError: foo is not defined
}

然而,即使在严格模式下,评估的代码仍然可以读取和写入周围范围内的变量。要防止这种访问,您需要间接调用eval()

间接 eval()在全局范围内进行评估

有两种调用eval()的方法:

  • 直接。通过直接调用名称为“eval”的函数。

  • 间接调用。以其他方式(通过call(),作为window的方法,通过在不同名称下存储它并在那里调用等)。

正如我们已经看到的,直接eval()在当前范围内执行代码:

var x = 'global';

function directEval() {
    'use strict';
    var x = 'local';

    console.log(eval('x')); // local
}

相反,间接eval()在全局范围内执行它:

var x = 'global';

function indirectEval() {
    'use strict';
    var x = 'local';

    // Don’t call eval directly
    console.log(eval.call(null, 'x')); // global
    console.log(window.eval('x')); // global
    console.log((1, eval)('x')); // global (1)

    // Change the name of eval
    var xeval = eval;
    console.log(xeval('x')); // global

    // Turn eval into a method
    var obj = { eval: eval };
    console.log(obj.eval('x')); // global
}

(1)的解释:当您通过名称引用变量时,初始结果是所谓的引用,一个具有两个主要字段的数据结构:

  • base指向环境,即变量值存储的数据结构。

  • referencedName是变量的名称。

eval()函数调用期间,函数调用运算符(括号)遇到对eval的引用,并且可以确定要调用的函数的名称。因此,这样的函数调用触发了直接的eval()。但是,您可以通过不给出调用运算符的引用来强制间接eval()。这是通过在应用运算符之前检索引用的值来实现的。逗号运算符在第(1)行为我们执行此操作。此运算符评估第一个操作数并返回评估第二个操作数的结果。评估始终产生值,这意味着引用被解析并丢失了函数名称。

间接评估的代码总是松散的。这是代码独立于其当前环境进行评估的结果:

function strictFunc() {
    'use strict';

    var code = '(function () { return this }())';
    var result = eval.call(null, code);
    console.log(result !== undefined); // true, sloppy mode
}

使用 new Function()评估代码

构造函数Function()的签名为:

new Function(param1, ..., paramN, funcBody)

它创建一个函数,其零个或多个参数的名称为param1parem2等,其主体为funcBody;也就是说,创建的函数如下所示:

function («param1», ..., «paramN») {
    «funcBody»
}

让我们使用new Function()创建一个函数f,它返回其参数的总和:

> var f = new Function('x', 'y', 'return x+y');
> f(3, 4)
7

类似于间接eval()new Function()创建其作用域为全局的函数:¹⁶

var x = 'global';

function strictFunc() {
    'use strict';
    var x = 'local';

    var f = new Function('return x');
    console.log(f()); // global
}

这样的函数默认情况下也是松散的:

function strictFunc() {
    'use strict';

    var sl = new Function('return this');
    console.log(sl() !== undefined); // true, sloppy mode

    var st = new Function('"use strict"; return this');
    console.log(st() === undefined); // true, strict mode
}

eval()与 new Function()

通常,最好使用new Function()而不是eval()来评估代码:函数参数为评估的代码提供了清晰的接口,而且你不需要间接eval()的略显笨拙的语法来确保评估的代码只能访问全局变量(除了它自己的变量)。

最佳实践

你不应该使用eval()new Function()。动态评估代码很慢,而且存在潜在的安全风险。它还会阻止大多数使用静态分析的工具(如 IDE)考虑代码。

通常有更好的替代方案。例如,Brendan Eich 最近在推特上发推文指出了程序员们使用的反模式,他们想要访问存储在变量propName中的属性:

var value = eval('obj.'+propName);

这个想法是有道理的:点运算符只支持固定的,静态提供的属性键。在这种情况下,属性键只在运行时知道,这就是为什么需要eval()来使用该运算符。幸运的是,JavaScript 还有方括号运算符,它接受动态属性键。因此,以下是前面代码的更好版本:

var value = obj[propName];

你也不应该使用eval()new Function()来解析 JSON 数据。这是不安全的。要么依赖 ECMAScript 5 对 JSON 的内置支持(参见第二十二章),要么使用一个库。

合法的用例

eval()new Function()有一些合法的,尽管是高级的用例:带有函数的配置数据(JSON 不允许),模板库,解释器,命令行和模块系统。

结论

这是 JavaScript 动态评估代码的一个相对高级的概述。如果你想深入了解,可以查看 kangax 的文章“全局 eval。有哪些选项?”。

控制台 API

在大多数 JavaScript 引擎中,有一个全局对象console,其中包含用于记录和调试的方法。该对象不是语言本身的一部分,但已成为事实上的标准。由于它们的主要目的是调试,console方法在开发过程中最常用,而在部署的代码中很少使用。

本节概述了控制台 API。它记录了 Chrome 32、Firebug 1.12、Firefox 25、Internet Explorer 11、Node.js 0.10.22 和 Safari 7.0 的现状。

控制台 API 在各种引擎之间的标准化程度如何?

控制台 API 的实现差异很大,而且不断变化。如果你想要权威的文档,你有两个选择。首先,你可以查看 API 的标准概述:

  • Firebug 首先实现了控制台 API,其在其维基中的文档是目前最接近标准的东西。

  • 此外,Brian Kardell 和 Paul Irish 正在制定API 规范,这应该会导致更一致的行为。

其次,你可以查看各种引擎的文档:

  • Chrome

  • Firebug

  • Firefox

  • Internet Explorer

  • Node.js

  • Safari

警告

在 Internet Explorer 9 中存在一个错误。在该浏览器中,只有开发者工具至少打开过一次,console对象才存在。这意味着如果在工具打开之前引用console,你会得到一个ReferenceError。作为一种解决方法,你可以检查console是否存在,如果不存在则创建一个虚拟实现。

简单的日志记录

控制台 API 包括以下记录方法:

console.clear()

清除控制台。

console.debug(object1, object2?, ...)

最好使用console.log(),它与此方法相同。

console.error(object1, object2?, ...)

将参数记录到控制台。在浏览器中,记录的内容可能会被“错误”图标标记,和/或包括堆栈跟踪或代码链接。

console.exception(errorObject, object1?, ...]) [仅限 Firebug]

记录object1等,并显示交互式堆栈跟踪。

console.info(object1?, object2?, ...)

将参数记录到控制台。在浏览器中,记录的内容可能会被“信息”图标标记,和/或包括堆栈跟踪或代码链接。

console.log(object1?, object2?, ...)

将参数记录到控制台。如果第一个参数是printf风格的格式字符串,则使用它来打印其余的参数。例如(Node.js REPL):

> console.log('%s', { foo: 'bar' })
[object Object]
> console.log('%j', { foo: 'bar' })
{"foo":"bar"}

唯一可靠的跨平台格式化指令是%s。Node.js 支持%j以将数据格式化为 JSON;浏览器倾向于支持记录交互内容的指令。

console.trace()

记录堆栈跟踪(在许多浏览器中是交互式的)。

console.warn(object1?, object2?, ...)

将参数记录到控制台。在浏览器中,记录的内容可能会被“警告”图标标记,和/或包括堆栈跟踪或代码链接。

在以下表中指出了各种平台的支持:

Chrome Firebug Firefox IE Node.js Safari
clear
debug
error
exception
info
log
trace
warn

exception以斜体排版,因为它只在单个平台上受支持。

检查和计数

控制台 API 包括以下检查和计数方法:

console.assert(expr, obj?)

如果exprfalse,则将obj记录到控制台并抛出异常。如果为true,则什么也不做。

console.count(label?)

计算带有此语句的行被执行的次数。

在以下表中指出了各种平台的支持:

Chrome Firebug Firefox IE Node.js Safari
assert
count

格式化日志

控制台 API 包括以下格式化日志的方法:

console.dir(object)

将对象的表示打印到控制台。在浏览器中,该表示可以交互地进行探索。

console.dirxml(object)

打印 HTML 或 XML 元素的 XML 源树。

console.group(object1?, object2?, ...)

将对象记录到控制台并打开一个包含所有未来记录内容的嵌套块。通过调用console.groupEnd()来关闭该块。该块最初是展开的,但可以折叠。

console.groupCollapsed(object1?, object2?, ...)

类似于console.group(),但是该块最初是折叠的。

console.groupEnd()

关闭由console.group()console.groupCollapsed()打开的组。

console.table(data, columns?)

将数组打印为表格,每行一个元素。可选参数columns指定在列中显示哪些属性/数组索引。如果缺少该参数,则所有属性键都将用作表格列。缺少的属性和数组元素显示为列中的undefined

var persons = [
    { firstName: 'Jane', lastName: 'Bond' },
    { firstName: 'Lars', lastName: 'Croft', age: 72 }
];
// Equivalent:
console.table(persons);
console.table(persons, ['firstName', 'lastName', 'age']);

结果表如下:

(索引) 名字 姓氏 年龄
0 “Jane” “Bond” undefined
1 “Lars” “Croft” 72

在以下表中指出了各种平台的支持:

Chrome Firebug Firefox IE Node.js Safari
dir
dirxml
group
groupCollapsed
groupEnd
table

分析和计时

控制台 API 包括以下用于分析和计时的方法:

控制台.标记时间线(标签) [仅限 Safari]

console.timeStamp相同。

控制台.性能(标题?)

打开分析。可选的title用于分析报告。

控制台.分析结束()

停止分析并打印分析报告。

控制台.时间(标签)

启动标签为label的计时器。

控制台.时间结束(标签)

停止标签为label的计时器并打印自启动以来经过的时间。

控制台.时间戳(标签?)

记录具有给定label的时间戳。可以记录到控制台或时间轴。

以下表格显示了各种平台上的支持:

Chrome Firebug Firefox IE Node.js Safari
markTimeline
profile (devtools)
profileEnd (devtools)
time
timeEnd
timeStamp

markTimeline以斜体排版,因为它仅在单个平台上受支持。 (devtools)表示必须打开开发人员工具才能使该方法起作用。¹⁷

命名空间和特殊值

以下全局变量用作函数的命名空间。有关详细信息,请参阅括号中指示的材料:

JSON

JSON API 功能([第二十二章](ch22.html “第二十二章.JSON”))

数学

数学 API 功能([第二十一章](ch21.html “第二十一章.数学”))

对象

元编程功能([对象操作小抄:使用对象](ch17_split_001.html#oop_cheat_sheet “对象操作小抄:使用对象”))

以下全局变量包含特殊值。有关更多信息,请查看括号中指示的材料:

未定义

表示某物不存在的值([未定义和 null](ch08.html#undefined_null “未定义和 null”):

> ({}.foo) === undefined
true

NaN

一个表示某物是“非数字”([NaN](ch11.html#nan “NaN”)的值:

> 1 / 'abc'
NaN

无穷大

表示数值无穷大∞的值([无穷大](ch11.html#infinity “无穷大”):

> 1 / 0
Infinity

¹⁶ Mariusz Nowak(@medikoo)告诉我,由Function评估的代码默认情况下在任何地方都是松散的。

¹⁷ 感谢 Matthias Reuter(@gweax)和 Philipp Kyeck(@pkyeck)对本节的贡献。

第二十四章: Unicode 和 JavaScript

原文:24. Unicode and JavaScript

译者:飞龙

协议:CC BY-NC-SA 4.0

本章是对 Unicode 及其在 JavaScript 中的处理的简要介绍。

Unicode 历史

Unicode 始于 1987 年,由 Joe Becker(施乐),Lee Collins(苹果)和 Mark Davis(苹果)发起。其想法是创建一个通用字符集,因为当时对于编码纯文本存在许多不兼容的标准:许多变体的 8 位 ASCII,Big Five(繁体中文),GB 2312(简体中文)等。在 Unicode 之前,没有多语言纯文本的标准,但有丰富的文本系统(例如苹果的 WorldScript),允许您组合多个编码。

第一份 Unicode 草案提案于 1988 年发布。此后继续工作并扩大工作组。Unicode 联盟于 1991 年 1 月 3 日成立:

Unicode 联盟是一家致力于开发、维护和推广软件国际化标准和数据的非营利性公司,特别是 Unicode 标准[…]

Unicode 1.0 标准的第一卷于 1991 年 10 月出版,第二卷于 1992 年 6 月出版。

重要的 Unicode 概念

字符的概念可能看起来很简单,但它有许多方面。这就是为什么 Unicode 是一个如此复杂的标准。以下是重要的基本概念:

字符和字形

这两个术语的意思相似。字符是数字实体,而字形是书面语言的原子单位(字母、印刷连字、中文字符、标点符号等)。程序员以字符为思考单位,而用户以字形为思考单位。有时需要使用多个字符来表示单个字形。例如,我们可以通过组合字符o和字符^(抑扬符)来产生单个字形ô。

字形

这是一种显示字形的具体方式。有时,相同的字形在不同的上下文或其他因素下显示方式不同。例如,字形fi可以呈现为字形f和字形i,通过连字字形连接,或者没有连字。

代码点

Unicode 通过称为代码点的数字来表示它支持的字符。代码点的十六进制范围是 0x0 到 0x10FFFF(17 倍 16 位)。

代码单元

为了存储或传输代码点,我们将它们编码为代码单元,这是具有固定长度的数据片段。长度以位为单位,并由编码方案确定,Unicode 有几种编码方案,例如 UTF-8 和 UTF-16。名称中的数字表示代码单元的长度,以位为单位。如果一个代码点太大而无法适应单个代码单元,它必须被分解为多个单元;也就是说,表示单个代码点所需的代码单元数量可能会有所不同。

BOM(字节顺序标记)

如果一个代码单元大于一个字节,字节顺序很重要。BOM 是文本开头的一个伪字符(可能被编码为多个代码单元),指示代码单元是大端(最重要的字节在前)还是小端(最不重要的字节在前)。没有 BOM 的文本的默认值是大端。BOM 还指示所使用的编码;对于 UTF-8、UTF-16 等编码是不同的。此外,如果 Web 浏览器没有关于文本编码的其他信息,它还可以作为 Unicode 的标记。然而,由于几个原因,BOM 并不经常使用:

  • UTF-8 是迄今为止最流行的 Unicode 编码,不需要 BOM,因为只有一种字节排序方式。

  • 几种字符编码规定了固定的字节顺序。那么就不应该使用 BOM。例如 UTF-16BE(UTF-16 大端)、UTF-16LE、UTF-32BE 和 UTF-32LE。这是处理字节顺序的更安全的方式,因为元数据和数据保持分开,不会混淆。

规范化

有时相同的字形可以用几种方式表示。例如,字形ö可以表示为单个代码点,也可以表示为一个o后跟一个组合字符¨(分音符,双点)。规范化是将文本转换为规范表示的过程;等效的代码点和代码点序列都被转换为相同的代码点(或代码点序列)。这对于文本处理(例如搜索文本)很有用。Unicode 规定了几种规范化。

字符属性

规范指定了规范的几个属性,其中一些列在这里:

  • 名称。一个由大写字母 A-Z,数字 0-9,连字符(-)和<空格>组成的英文名称。两个例子:

  • “λ”的名称是“希腊小写字母λ”。

  • “!”的名称是“感叹号”。

  • 一般类别。将字符分成字母、大写字母、数字和标点等类别。

  • 年龄。该字符是在哪个版本的 Unicode 中引入的(1.0、1.1、2.0 等)?

  • 已弃用。是否不鼓励使用该字符?

  • 以及更多

代码点

代码点的范围最初是 16 位。随着 Unicode 版本 2.0(1996 年 7 月)的扩展,它现在被分成了 17 个平面,编号从 0 到 16。每个平面包括 16 位(十六进制表示法:0x0000–0xFFFF)。因此,在接下来的十六进制范围中,四个底部以外的数字包含了平面的编号。

  • 第 0 平面,基本多文种平面(BMP):0x0000–0xFFFF

  • 第 1 平面,补充多语种平面(SMP):0x10000–0x1FFFF

  • 第 2 平面,补充表意文字平面(SIP):0x20000–0x2FFFF

  • 第 3–13 平面,未分配

  • 第 14 平面,补充特殊用途平面(SSP):0xE0000–0xEFFFF

  • 第 15–16 平面,补充专用区域(S PUA A/B):0x0F0000–0x10FFFF

第 1–16 平面称为补充平面星际平面

Unicode 编码

UTF-32(Unicode 转换格式 32)是一种具有 32 位代码单元的格式。任何代码点都可以由单个代码单元编码,使得这是唯一的固定长度编码;对于其他编码,编码一个点所需的单元数量是变化的。

UTF-16是一种具有 16 位代码单元的格式,需要一个到两个单元来表示一个代码点。BMP 代码点可以由单个代码单元表示。高代码点是 20 位(16 乘以 16 位),在减去 0x10000(BMP 的范围)后。这些位被编码为两个代码单元(所谓的代理对):

领先代理

最重要的 10 位:存储在范围 0xD800–0xDBFF 中。也称为高代理代码单元

尾随代理

最不重要的 10 位:存储在范围 0xDC00–0xDFFF 中。也称为低代理代码单元

以下表格(改编自 Unicode 标准 6.2.0,表 3-5)可视化了位的分布:

代码点 UTF-16 代码单元
xxxxxxxxxxxxxxxx(16 位) xxxxxxxxxxxxxxxx
pppppxxxxxxyyyyyyyyyy(21 位=5+6+10 位) 110110qqqqxxxxxx 110111yyyyyyyyyy(qqqq = ppppp − 1)

为了启用这种编码方案,BMP 有一个未使用的代码点范围为 0xD800–0xDFFF 的空隙。因此,领先代理、尾随代理和 BMP 代码点的范围是不相交的,使得在面对错误时解码更加健壮。以下函数将代码点编码为 UTF-16(稍后我们将看到一个使用它的示例):

function toUTF16(codePoint) {
    var TEN_BITS = parseInt('1111111111', 2);
    function u(codeUnit) {
        return '\\u'+codeUnit.toString(16).toUpperCase();
    }

    if (codePoint <= 0xFFFF) {
        return u(codePoint);
    }
    codePoint -= 0x10000;

    // Shift right to get to most significant 10 bits
    var leadingSurrogate = 0xD800 | (codePoint >> 10);

    // Mask to get least significant 10 bits
    var trailingSurrogate = 0xDC00 | (codePoint & TEN_BITS);

    return u(leadingSurrogate) + u(trailingSurrogate);
}

UCS-2,一种已弃用的格式,使用 16 位代码单元来表示(仅!)BMP 的代码点。当 Unicode 代码点的范围扩展到 16 位之外时,UTF-16 取代了 UCS-2。

UTF-8具有 8 位代码单元。它在传统 ASCII 编码和 Unicode 之间架起了一座桥梁。ASCII 只有 128 个字符,其编号与前 128 个 Unicode 代码点相同。UTF-8 是向后兼容的,因为所有 ASCII 代码都是有效的代码单元。换句话说,在范围 0–127 的单个代码单元中编码了相同范围内的单个代码点。这些代码单元的最高位为零。另一方面,如果最高位为 1,则会跟随更多的单元,以为更高的代码点提供额外的位。这导致了以下编码方案:

  • 0000–007F:0xxxxxxx(7 位,存储在 1 字节中)

  • 0080–07FF:110xxxxx,10xxxxxx(5+6 位=11 位,存储在 2 字节中)

  • 0800–FFFF:1110xxxx,10xxxxxx,10xxxxxx(4+6+6 位=16 位,存储在 3 字节中)

  • 10000–1FFFFF:11110xxx,10xxxxxx,10xxxxxx,10xxxxxx(3+6+6+6 位=21 位,存储在 4 字节中)。最高代码点是 10FFFF,因此 UTF-8 有一些额外的空间。

如果最高位不为 0,则零之前的 1 的数量表示序列中有多少个代码单元。初始单元之后的所有单元都具有位前缀 10。因此,初始代码单元和后续代码单元的范围是不相交的,这有助于从编码错误中恢复。

UTF-8 已成为最流行的 Unicode 格式。最初,它之所以受欢迎,是因为它与 ASCII 的向后兼容性。后来,它因其在操作系统、编程环境和应用程序中的广泛和一致的支持而受到青睐。

JavaScript 源代码和 Unicode

JavaScript 处理 Unicode 源代码有两种方式:内部(在解析期间)和外部(在加载文件时)。

内部源代码

在内部,JavaScript 源代码被视为一系列 UTF-16 代码单元。根据第 6 节的 EMCAScript 规范:

ECMAScript 源文本以 Unicode 字符编码的形式表示,版本为 3.0 或更高。[…] ECMAScript 源文本被假定为本规范的目的是一系列 16 位代码单元。[…] 如果实际源文本以除 16 位代码单元以外的形式编码,必须处理为首先转换为 UTF-16。

在标识符、字符串文字和正则表达式文字中,任何代码单元也可以通过 Unicode 转义序列\uHHHH来表示,其中HHHH是四个十六进制数字。例如:

> var f\u006F\u006F = 'abc';
> foo
'abc'

> var λ = 123;
> \u03BB
123

这意味着您可以在源代码中使用 Unicode 字符的文字和变量名,而不会离开 ASCII 范围。

在字符串文字中,还有一种额外的转义可用:用两位十六进制数字表示的十六进制转义序列,表示范围在 0x00-0xFF 的代码单元。例如:

> '\xF6' === 'ö'
true
> '\xF6' === '\u00F6'
true

外部源代码

虽然内部使用 UTF-16,但 JavaScript 源代码通常不以该格式存储。当 Web 浏览器通过

你可能感兴趣的:(javascript,javascript,开发语言,ecmascript)