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

2024-01-12 09:20:09 浏览数 (1)

第二十一章:数学

原文: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

一半的平方根,

Math.SQRT2

二的平方根,

数值函数

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

Math.abs(x)

返回x的绝对值。

Math.ceil(x)

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

代码语言:javascript复制
> 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的最大整数:

代码语言:javascript复制
> 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次幂:

代码语言:javascript复制
> Math.pow(9, 2)
81
> Math.pow(36, 0.5)
6

Math.round(x)

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

代码语言:javascript复制
> Math.round(3.999)
4
> Math.round(3.001)
3
> Math.round(3.5)
4
> Math.round(-3.5)
-3

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

Math.sqrt(x)

返回

x的平方根:

代码语言:javascript复制
> Math.sqrt(256)
16

三角函数

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

从度到弧度:

代码语言:javascript复制
function toRadians(degrees) {
    return degrees / 180 * Math.PI;
}

以下是交互:

代码语言:javascript复制
    > 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)):

代码语言:javascript复制
> Math.min.apply(null, [27, -38, -43])
-43

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

返回参数中的最大数:

代码语言:javascript复制
> Math.max()
-Infinity
> Math.max(7)
7
> Math.max(7, 10)
10
> Math.max(7, 10, -333)
10

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

代码语言:javascript复制
> Math.max.apply(null, [7, 10, -333])
10

Math.random()

代码语言:javascript复制
/**
 * 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 表达式语法的子集。例如:

代码语言: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 的简单性在视觉上显而易见。

图 22-1:JSON 名片的正面显示了一个徽标(来源:Eric Miraglia)。

图 22-2:JSON 名片的背面包含完整的语法(来源:Eric Miraglia)。

语法可以转录如下:

代码语言:javascript复制
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。它可以是:

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

代码语言:javascript复制
function replacer(key, value) {
    if (typeof value === 'number') {
        value = 2 * value;
    }
    return value;
}

使用 replacer:

代码语言:javascript复制
    > 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:

代码语言:javascript复制
> console.log(JSON.stringify({a: 0, b: ['n']}, null, 2))
{
  "a": 0,
  "b": [
    "n"
  ]
}

一个字符串

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

代码语言:javascript复制
> console.log(JSON.stringify({a: 0, b: ['n']}, null, '|--'))
{
|--"a": 0,
|--"b": [
|--|--"n"
|--]
}

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

代码语言:javascript复制
JSON.stringify(data, null, 4)
JSON.stringify()忽略的数据

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

代码语言:javascript复制
> 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而不是字符串:

代码语言:javascript复制
> JSON.stringify(function () {})
undefined

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

代码语言:javascript复制
> JSON.stringify({ foo: function () {} })
'{}'

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

代码语言:javascript复制
> JSON.stringify([ function () {} ])
'[null]'
toJSON()方法

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

代码语言:javascript复制
> JSON.stringify({ toJSON: function () { return 'Cool' } })
'"Cool"'

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

代码语言:javascript复制
> JSON.stringify(new Date('2011-07-29'))
'"2011-07-28T22:00:00.000Z"'

toJSON方法的完整签名如下:

代码语言:javascript复制
function (key)

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

根位置

空字符串

属性值

属性键

数组元素

元素的索引作为字符串

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

代码语言:javascript复制
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

代码语言:javascript复制
> 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 值。以下是一些示例:

代码语言: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是一个节点访问者(参见通过节点访问者转换数据),可用于转换解析后的数据。在此示例中,我们将日期字符串转换为日期对象:

代码语言:javascript复制
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;
}

以下是交互:

代码语言:javascript复制
> 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)。让我们将传递的转换函数称为节点访问者。这些方法遍历树并为每个节点调用访问者。然后可以选择替换或删除节点。节点访问者的签名如下:

代码语言:javascript复制
function nodeVisitor(key, value)

参数是:

this

当前节点的父节点。

key

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

当前节点。

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

  • this{ '': root }
  • key''
  • valueroot

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

  • 返回 value,然后不执行任何更改。
  • 返回不同的值。然后当前节点被替换。
  • 返回 undefined。然后移除节点。

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

代码语言:javascript复制
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() 返回的字符串:

代码语言:javascript复制
> 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 值:

代码语言: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 - _ . ! ~ * ' ( )

例如:

代码语言:javascript复制
> encodeURI('http://example.com/Für Elise/')
'http://example.com/Für Elise/'

encodeURIComponent(uriComponent)

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

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

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

代码语言:javascript复制
> encodeURIComponent('http://example.com/Für Elise/')
'http://example.com/Für Elise/'

decodeURI(encodedURI)

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

代码语言:javascript复制
> decodeURI('http://example.com/Für Elise/')
'http://example.com/Für Elise/'

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

代码语言:javascript复制
> decodeURI('/')
'/'
> decodeURIComponent('/')
'/'

decodeURIComponent(encodedURIComponent)

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

代码语言:javascript复制
> decodeURIComponent('http://example.com/Für Elise/')
'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()评估代码

函数调用:

代码语言:javascript复制
eval(str)

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

代码语言:javascript复制
> var a = 12;
> eval('a   5')
17

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

代码语言:javascript复制
> eval('{ foo: 123 }')  // code block
123
> eval('({ foo: 123 })')  // object literal
{ foo: 123 }
在严格模式下使用 eval()

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

代码语言:javascript复制
function sloppyFunc() {
    eval('var foo = 123');  // added to the scope of sloppyFunc
    console.log(foo);  // 123
}

在严格模式下无法发生:

代码语言:javascript复制
function strictFunc() {
    'use strict';
    eval('var foo = 123');
    console.log(foo);  // ReferenceError: foo is not defined
}

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

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

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

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

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

代码语言:javascript复制
var x = 'global';

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

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

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

代码语言:javascript复制
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)行为我们执行此操作。此运算符评估第一个操作数并返回评估第二个操作数的结果。评估始终产生值,这意味着引用被解析并丢失了函数名称。

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

代码语言:javascript复制
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()的签名为:

代码语言:javascript复制
new Function(param1, ..., paramN, funcBody)

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

代码语言:javascript复制
function («param1», ..., «paramN») {
    «funcBody»
}

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

代码语言:javascript复制
> var f = new Function('x', 'y', 'return x y');
> f(3, 4)
7

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

代码语言:javascript复制
var x = 'global';

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

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

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

代码语言:javascript复制
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中的属性:

代码语言:javascript复制
var value = eval('obj.' propName);

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

代码语言: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):

代码语言:javascript复制
> 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

代码语言:javascript复制
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”):

代码语言:javascript复制
> ({}.foo) === undefined
true

NaN

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

代码语言:javascript复制
> 1 / 'abc'
NaN

无穷大

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

代码语言:javascript复制
> 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(稍后我们将看到一个使用它的示例):

代码语言:javascript复制
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是四个十六进制数字。例如:

代码语言:javascript复制
> var fu006Fu006F = 'abc';
> foo
'abc'

> var λ = 123;
> u03BB
123

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

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

代码语言:javascript复制
> 'xF6' === 'ö'
true
> 'xF6' === 'u00F6'
true
外部源代码

虽然内部使用 UTF-16,但 JavaScript 源代码通常不以该格式存储。当 Web 浏览器通过<script>标签加载源文件时,它会确定编码如下:

如果文件以 BOM 开头,则编码是 UTF 变体,取决于使用的 BOM。

否则,如果文件是通过 HTTP(S)加载的,那么Content-Type头可以通过charset参数指定编码。例如:

代码语言:javascript复制
Content-Type: application/javascript; charset=utf-8
提示

JavaScript 文件的正确媒体类型(以前称为MIME 类型)是application/javascript。但是,较旧的浏览器(例如 Internet Explorer 8 及更早版本)最可靠地使用text/javascript。不幸的是,<script>标签的type属性的默认值是text/javascript。至少对于 JavaScript,您可以省略该属性;包含它没有好处。

否则,如果<script>标签具有charset属性,则将使用该编码。即使属性type包含有效的媒体类型,该类型也不得具有参数charset(就像前述的Content-Type头)。这确保了charsettype的值不会冲突。

否则,使用包含<script>标签的文档的编码。例如,这是 HTML5 文档的开头,其中<meta>标签声明文档编码为 UTF-8:

代码语言:javascript复制
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
...

强烈建议您始终指定编码。如果不指定,将使用特定于区域设置的默认编码。换句话说,在不同国家,人们将以不同方式看待文件。只有最低的 7 位在各个区域设置中相对稳定。

我的建议可以总结如下:

  • 对于您自己的应用程序,您可以使用 Unicode。但必须将应用程序的 HTML 页面的编码指定为 UTF-8。
  • 对于库,最安全的做法是发布 ASCII(7 位)代码。

一些缩小工具可以将具有超出 7 位的 Unicode 代码点的源代码转换为“7 位干净”的源代码。它们通过用 Unicode 转义替换非 ASCII 字符来实现。例如,以下调用UglifyJS将文件test.js翻译为:

代码语言:javascript复制
uglifyjs -b beautify=false,ascii-only=true test.js

文件test.js如下所示:

代码语言:javascript复制
var σ = 'Köln';

UglifyJS 的输出如下:

代码语言:javascript复制
var u03c3="Kxf6ln";

考虑以下负面示例。有一段时间,库 D3.js 以 UTF-8 发布。这导致了一个错误,因为当它从编码不是 UTF-8 的页面加载时,代码包含了诸如以下语句:

代码语言:javascript复制
var π = Math.PI, ε = 1e-6;

标识符π和ε没有被正确解码,也没有被识别为有效的变量名。此外,一些超出 7 位的代码点的字符串文字也没有被正确解码。作为一种解决方法,您可以通过向<script>标签添加适当的charset属性来加载代码:

代码语言:javascript复制
<script charset="utf-8" src="d3.js"></script>

JavaScript 字符串和 Unicode

JavaScript 字符串是一系列 UTF-16 代码单元。根据 ECMAScript 规范,第 8.4 节:

当一个字符串包含实际文本数据时,每个元素被认为是单个 UTF-16 代码单元。

转义序列

如前所述,您可以在字符串文字中使用 Unicode 转义序列和十六进制转义序列。例如,您可以通过将o与重音符(代码点 0x0308)组合来产生字符ö:

代码语言:javascript复制
> console.log('ou0308')
ö

这适用于 JavaScript 命令行,例如 Web 浏览器控制台和 Node.js REPL。您还可以将这种类型的字符串插入到 Web 页面的 DOM 中。

通过转义引用星际飞机字符

网络上有许多不错的 Unicode 符号表。看看 Tim Whitlock 的“Emoji Unicode Tables”,并对现代 Unicode 字体中有多少符号感到惊讶。表中的符号都不是图像;它们都是字体字形。假设您想通过 JavaScript 显示一个星际飞机中的 Unicode 字符(显然,这样做存在风险:并非所有字体都支持所有这些字符)。例如,考虑一头奶牛,代码点为 0x1F404:!。

您可以复制字符并直接粘贴到您的 Unicode 编码 JavaScript 源代码中:

JavaScript 引擎将解码源代码(通常为 UTF-8)并创建一个具有两个 UTF-16 代码单元的字符串。或者,您可以自己计算两个代码单元并使用 Unicode 转义序列。有一些网络应用程序可以执行这种计算,例如:

  • UTF 转换器
  • Mathias Bynens 的“JavaScript 转义”

先前定义的函数toUTF16也执行了它:

代码语言:javascript复制
> toUTF16(0x1F404)
'\uD83D\uDC04'

UTF-16 代理对(0xD83D,0xDC04)确实编码了奶牛:

计数字符

如果字符串包含代理对(两个编码单元编码一个代码点),那么length属性不再计算图形元素。它计算编码单元:

这可以通过库来修复,例如 Mathias Bynens 的Punycode.js,它与 Node.js 捆绑在一起:

代码语言:javascript复制
> var puny = require('punycode');
> puny.ucs2.decode(str).length
1
Unicode 规范化

如果您想在字符串中搜索或比较它们,那么您需要进行规范化,例如通过库unorm(由 Bjarke Walling)。

JavaScript 正则表达式和 Unicode

JavaScript 正则表达式中的 Unicode 支持(请参阅第十九章)非常有限。例如,没有办法匹配“大写字母”等 Unicode 类别。

行终止符影响匹配。行终止符是下表中指定的四个字符之一:

代码单元

名称

字符转义序列

u000A

换行符

n

u000D

回车

r

u2028

行分隔符

u2029

段落分隔符

以下正则表达式构造基于 Unicode:

s S(空白,非空白)具有基于 Unicode 的定义:

代码语言:javascript复制
> /^s$/.test('uFEFF')
true

.(点)匹配所有代码单元(不是代码点!)除了行终止符。请参阅下一节,了解如何匹配任何代码点。

多行模式/m:在多行模式下,断言^匹配输入的开头和行终止符之后。断言$匹配行终止符之前和输入的结尾。在非多行模式下,它们只在输入的开头或结尾匹配。

其他重要的字符类是基于 ASCII 而不是 Unicode 定义的:

d D(数字,非数字):数字等同于[0-9]

w W(单词字符,非单词字符):单词字符等同于[A-Za-z0-9_]

b B(在单词边界,单词内):单词是由单词字符([A-Za-z0-9_])组成的序列。例如,在字符串'über'中,字符类转义b将字符b视为单词的开始:

代码语言:javascript复制
> /bb/.test('über')
true
匹配任何代码单元和任何代码点

要匹配任何代码单元,您可以使用[sS];请参见原子:通用。

要匹配任何代码点,您需要使用:¹⁸

代码语言:javascript复制
([-uD7FFuE000-uFFFF]|[uD800-uDBFF][uDC00-uDFFF])

前面的模式工作原理如下:

代码语言:javascript复制
([BMP code point]|[leading surrogate][trailing surrogate])

由于所有这些范围都是不相交的,该模式将正确匹配 UTF-16 字符串中的代码点。

一些库可帮助处理 JavaScript 中的 Unicode:

Regenerate有助于生成像前面那样的范围,以匹配任何代码单元。它旨在用作构建工具的一部分,但也可以动态工作,用于尝试各种功能。

XRegExp是一个正则表达式库,它有一个官方附加组件,可以通过以下三种构造之一匹配 Unicode 类别、脚本、块和属性:

代码语言:javascript复制
p{...} p{^...} P{...}

例如,p{Letter}匹配各种字母表中的字母,而p{^Letter}P{Letter}都匹配所有其他代码点。第三十章包含了对 XRegExp 的简要概述。

  • ECMAScript 国际化 API(请参见ECMAScript 国际化 API)提供了对 Unicode 的排序和搜索等功能。
推荐阅读和章节来源

有关 Unicode 的更多信息,请参见以下内容:

  • 维基百科上有几篇关于Unicode及其术语的好文章。
  • Unicode.org,Unicode 联盟的官方网站,以及其FAQ也是很好的资源。
  • Joel Spolsky 的介绍性文章“每个软件开发人员绝对必须了解的有关 Unicode 和字符集的绝对最低限度(没有借口!)”很有帮助。

有关 JavaScript 中的 Unicode 支持的信息,请参见:

  • Mathias Bynens 的文章“JavaScript 的内部字符编码:UCS-2 还是 UTF-16?”
  • 《JavaScript,正则表达式和 Unicode》由 Steven Levithan
致谢

以下人员为本章做出了贡献:Mathias Bynens(@mathias),Anne van Kesteren(@annevk)和 Calvin Metcalf(@CWMma)。

----

¹⁸ 严格来说,任何Unicode 标量值。

第二十五章:ECMAScript 5 中的新功能

原文:25. New in ECMAScript 5 译者:飞龙 协议:CC BY-NC-SA 4.0

本章列出了仅在 ECMAScript 5 中可用的功能。如果您必须使用旧版 JavaScript 引擎,您应该避免使用这些功能,或者通过库启用其中一些功能(稍后将进行描述)。请注意,通常情况下,本书假定您正在使用完全支持 ECMAScript 5 的现代引擎。

ECMAScript 5 规范包含了对其范围的以下描述:

ECMAScript 的第五版(作为 ECMA-262 第 5 版发布)

  • 对已成为浏览器实现中常见的语言规范的实际解释进行了编码
  • 增加了对自第三版出版以来出现的新功能的支持。这些功能包括
    • 访问器属性,
    • 反射创建和检查对象,
    • 程序控制属性属性,
    • 附加数组操作函数,
    • 对 JSON 对象编码格式的支持,以及 x
    • 提供增强的错误检查和程序安全性的严格模式。

新功能

ECMAScript 5 中包含的新功能如下:

严格模式(参见严格模式)

将以下行放在文件或函数的开头可以打开所谓的严格模式,使 JavaScript 成为一个更干净的语言,禁止一些功能,执行更多检查,并抛出更多异常:

代码语言:javascript复制
'use strict';

访问器(参见访问器(Getter 和 Setter))

Getter 和 setter 允许您通过方法实现属性的获取和设置。例如,以下对象obj包含属性foo的 getter:

代码语言:javascript复制
> var obj = { get foo() { return 'abc' } };
> obj.foo
'abc'

语法更改

ECMAScript 5 包括以下语法更改:

保留字作为属性键

您可以在点运算符之后使用保留字(例如newfunction)并且在对象文字中作为非引用的属性键:

代码语言:javascript复制
> var obj = { new: 'abc' };
> obj.new
'abc'

合法的尾随逗号

对象文字和数组文字中的尾随逗号是合法的。

多行字符串文字

如果通过反斜杠转义行尾,字符串文字可以跨多行。

标准库中的新功能

ECMAScript 5 为 JavaScript 的标准库带来了几个新增功能。本节按类别列出了它们。

元编程

获取和设置原型(参见获取和设置原型):

  • Object.create()
  • Object.getPrototypeOf()

通过属性描述符管理属性属性(参见属性描述符):

  • Object.defineProperty()
  • Object.defineProperties()
  • Object.create()
  • Object.getOwnPropertyDescriptor()

列出属性(参见迭代和属性检测):

  • Object.keys()
  • Object.getOwnPropertyNames()

保护对象(参见保护对象):

  • Object.preventExtensions()
  • Object.isExtensible()
  • Object.seal()
  • Object.isSealed()
  • Object.freeze()
  • Object.isFrozen()

新的Function方法(参见Function.prototype.bind(thisValue, arg1?, …, argN?)):

  • Function.prototype.bind()
新方法

字符串(参见第十二章):

  • 新方法String.prototype.trim()
  • 通过方括号操作符[...]访问字符

新的Array方法(参见[Array Prototype Methods](ch18.html#array_prototype_methods “Array Prototype Methods”):

  • Array.isArray()
  • Array.prototype.every()
  • Array.prototype.filter()
  • Array.prototype.forEach()
  • Array.prototype.indexOf()
  • Array.prototype.lastIndexOf()
  • Array.prototype.map()
  • Array.prototype.reduce()
  • Array.prototype.some()

新的Date方法(参见Date Prototype Methods):

  • Date.now()
  • Date.prototype.toISOString()
JSON

对 JSON 的支持(参见第二十二章):

  • JSON.parse()(参见JSON.parse(text, reviver?))
  • JSON.stringify()(参见JSON.stringify(value, replacer?, space?))
  • 一些内置对象具有特殊的toJSON()方法:
  • Boolean.prototype.toJSON()
  • Number.prototype.toJSON()
  • String.prototype.toJSON()
  • Date.prototype.toJSON()

与旧版浏览器一起工作的提示

如果您需要与旧版浏览器一起工作,以下资源将非常有用:

  • Juriy Zaytsev(“kangax”)的兼容性表显示了各种浏览器的各个版本支持 ECMAScript 5 的程度。
  • es5-shim 将 ECMAScript 5 的大部分(但不是全部)功能带到只支持 ECMAScript 3 的浏览器中。

0 人点赞