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

简介: 大话 JavaScript(Speaking JavaScript):第一章到第五章

第一部分:JavaScript 快速入门


这部分是 JavaScript 的一个独立快速介绍。你可以在不阅读本书中的其他内容的情况下理解它,本书的其他部分也不依赖于它的内容。然而,阅读本书的提示在阅读本书的提示中适用。

第一章:基本 JavaScript


本章是关于“基本 JavaScript”,这是我为 JavaScript 的一个子集选择的名称,尽可能简洁,同时仍然能让你高效地工作。当你开始学习 JavaScript 时,我建议你在学习其他语言之前先在其中编程一段时间。这样,你就不必一次学习所有内容,这可能会让人困惑。

背景

本节简要介绍了 JavaScript 的背景,以帮助你理解它为什么是这样的。

JavaScript 与 ECMAScript

ECMAScript是 JavaScript 的官方名称。之所以需要一个新名称,是因为Java有商标(最初由 Sun 持有,现在由 Oracle 持有)。目前,Mozilla 是少数几家被允许正式使用JavaScript名称的公司之一,因为它很久以前就获得了许可证。对于常见用法,以下规则适用:

  • JavaScript意味着编程语言。
  • ECMAScript是语言规范的官方名称。因此,每当提到语言的版本时,人们都说ECMAScript。JavaScript 的当前版本是 ECMAScript 5;ECMAScript 6 目前正在开发中。

影响和语言的性质

JavaScript 的创造者 Brendan Eich 别无选择,只能很快地创建这种语言(否则,Netscape 可能会采用其他更糟糕的技术)。他从几种编程语言中借鉴了一些东西:Java(语法,原始值与对象),Scheme 和 AWK(一级函数),Self(原型继承),以及 Perl 和 Python(字符串,数组和正则表达式)。

JavaScript 在 ECMAScript 3 之前没有异常处理,这就解释了为什么语言经常自动转换值并经常悄悄失败:最初它无法抛出异常。

一方面,JavaScript 有一些怪癖,缺少相当多的功能(块作用域变量,模块,支持子类等)。另一方面,它有几个强大的功能,可以让你解决这些问题。在其他语言中,你学习语言特性。在 JavaScript 中,你经常学习模式而不是语言特性。

鉴于它的影响,毫不奇怪 JavaScript 可以实现一种混合了函数式编程(高阶函数;内置的mapreduce等)和面向对象编程(对象,继承)的编程风格。

语法

本节解释了 JavaScript 的基本语法原则。

语法概述

一些语法的例子:

// Two slashes start single-line comments
var x;  // declaring a variable
x = 3 + y;  // assigning a value to the variable `x`
foo(x, y);  // calling function `foo` with parameters `x` and `y`
obj.bar(3);  // calling method `bar` of object `obj`
// A conditional statement
if (x === 0) {  // Is `x` equal to zero?
    x = 123;
}
// Defining function `baz` with parameters `a` and `b`
function baz(a, b) {
    return a + b;
}

注意等号的两种不同用法:

  • 单个等号(=)用于将值赋给变量。
  • 三个等号(===)用于比较两个值(参见相等运算符)。

语句与表达式

要理解 JavaScript 的语法,你应该知道它有两个主要的语法类别:语句和表达式:

  • 语句“做事情”。程序是一系列语句。这是一个语句的例子,它声明(创建)一个变量foo
var foo;
  • 表达式产生值。它们是函数参数,赋值的右侧等等。这是一个表达式的例子:
3 * 7

语句和表达式之间的区别最好通过 JavaScript 有两种不同的if-then-else的方式来说明——作为语句:

var x;
if (y >= 0) {
    x = y;
} else {
    x = -y;
}

或作为一个表达式:

var x = y >= 0 ? y : -y;

你可以将后者用作函数参数(但不能使用前者):

myFunction(y >= 0 ? y : -y)

最后,无论 JavaScript 在哪里期望一个语句,你也可以使用一个表达式;例如:

foo(7, 1);

整行是一个语句(所谓的表达式语句),但函数调用foo(7, 1)是一个表达式。

分号

在 JavaScript 中,分号是可选的。但是,我建议始终包括它们,因为否则 JavaScript 可能会错误猜测语句的结束。详细信息请参见自动分号插入

分号终止语句,但不终止块。有一种情况下,您会在块后看到一个分号:函数表达式是以块结尾的表达式。如果这样的表达式出现在语句的最后,它后面会跟着一个分号:

// Pattern: var _ = ___;
var x = 3 * 7;
var f = function () { };  // function expr. inside var decl.

注释

JavaScript 有两种注释:单行注释和多行注释。单行注释以//开头,并在行尾终止:

x++; // single-line comment

多行注释由/**/界定:

/* This is
 a multiline
 comment.
 */

变量和赋值

在 JavaScript 中,变量在使用之前被声明:

var foo;  // declare variable `foo`

赋值

您可以声明一个变量并同时赋值:

var foo = 6;

您也可以给现有变量赋值:

foo = 4;  // change variable `foo`

复合赋值运算符

有复合赋值运算符,比如+=。以下两个赋值是等价的:

x += 1;
x = x + 1;

标识符和变量名

标识符是在 JavaScript 中扮演各种语法角色的名称。例如,变量的名称是标识符。标识符区分大小写。

大致而言,标识符的第一个字符可以是任何 Unicode 字母、美元符号($)或下划线(_)。随后的字符还可以是任何 Unicode 数字。因此,以下都是合法的标识符:

arg0
_tmp
$elem
π

以下标识符是保留字——它们是语法的一部分,不能用作变量名(包括函数名和参数名):

arguments break case catch
class const continue debugger
default delete do else
enum export extends false
finally for function if
implements import in instanceof
interface let new null
package private protected public
return static super switch
this throw true try
typeof var void while

以下三个标识符不是保留字,但您应该将它们视为保留字:

| Infinity |

| NaN |

| undefined |

最后,您还应该避免使用标准全局变量的名称(参见第二十三章)。您可以将它们用于局部变量而不会破坏任何东西,但您的代码仍然会变得混乱。

JavaScript 有许多我们从编程语言中期望的值:布尔值、数字、字符串、数组等等。JavaScript 中的所有值都有属性。每个属性都有一个(或名称)和一个。您可以将属性视为记录的字段。您可以使用点(.)运算符来读取属性:

value.propKey

例如,字符串'abc'具有属性length

> var str = 'abc';
> str.length
3

前面的也可以写成:

> 'abc'.length
3

点运算符也用于给属性赋值:

> var obj = {};  // empty object
> obj.foo = 123; // create property `foo`, set it to 123
123
> obj.foo
123

您也可以用它来调用方法:

> 'hello'.toUpperCase()
'HELLO'

在上面的例子中,我们已经在值'hello'上调用了方法toUpperCase()

原始值与对象

JavaScript 在值之间做了一个相当武断的区分:

  • 原始值是布尔值、数字、字符串、nullundefined
  • 所有其他值都是对象

两者之间的一个主要区别是它们的比较方式;每个对象都有唯一的标识,并且只有(严格)等于自身:

> var obj1 = {};  // an empty object
> var obj2 = {};  // another empty object
> obj1 === obj2
false
> obj1 === obj1
true

相反,所有编码相同值的原始值都被视为相同:

> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true

接下来的两节将更详细地解释原始值和对象。

原始值

以下是所有原始值(或简称原始值):

原始值具有以下特征:

按值比较

“内容”进行比较:

> 3 === 3
true
> 'abc' === 'abc'
true

始终不可变

属性无法更改,添加或删除:

> var str = 'abc';
> str.length = 1; // try to change property `length`
> str.length      // ⇒ no effect
3
> str.foo = 3; // try to create property `foo`
> str.foo      // ⇒ no effect, unknown property
undefined

(读取未知属性始终返回undefined。)

对象

所有非原始值都是对象。最常见的对象类型是:

  • 普通对象,可以通过对象字面量创建(参见单个对象):
{
    firstName: 'Jane',
    lastName: 'Doe'
}

前面的对象有两个属性:属性firstName的值为'Jane',属性lastName的值为'Doe'

  • 数组,可以通过数组字面量创建(参见数组):
[ 'apple', 'banana', 'cherry' ]

前面的数组有三个元素,可以通过数字索引访问。例如,'apple’的索引是 0。

  • 正则表达式,可以通过正则表达式字面量创建(参见正则表达式):
/^a+b+$/

对象具有以下特征:

按引用比较

进行身份比较;每个值都有自己的身份:

> {} === {}  // two different empty objects
false
> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true

默认可变

通常可以自由更改,添加和删除属性(参见单个对象):

> var obj = {};
> obj.foo = 123; // add property `foo`
> obj.foo
123

undefined 和 null

大多数编程语言都有表示缺少信息的值。JavaScript 有两个这样的“非值”,undefinednull

  • undefined表示“没有值”。未初始化的变量是undefined
> var foo;
> foo
undefined

缺少参数是undefined

> function f(x) { return x }
    > f()
    undefined
    ```
如果读取不存在的属性,将得到`undefined`:
```js
    > var obj = {}; // empty object
    > obj.foo
    undefined
    ```
+   `null`表示“没有对象”。每当期望对象时(参数,对象链中的最后一个等),它被用作非值。
### 警告
`undefined`和`null`没有属性,甚至没有标准方法,如`toString()`。
#### 检查 undefined 或 null
函数通常允许您通过`undefined`或`null`指示缺少值。您可以通过显式检查来做相同的事情:
```js
if (x === undefined || x === null) {
    ...
}

您还可以利用undefinednull都被视为false的事实:

if (!x) {
    ...
}

警告

false0NaN''也被视为false(参见真值和假值)。

使用 typeof 和 instanceof 对值进行分类

有两个用于对值进行分类的运算符:typeof主要用于原始值,而instanceof用于对象。

typeof看起来像这样:

typeof value

它返回描述value“类型”的字符串。以下是一些示例:

> typeof true
'boolean'
> typeof 'abc'
'string'
> typeof {} // empty object literal
'object'
> typeof [] // empty array literal
'object'

以下表列出了typeof的所有结果:

操作数 结果
undefined 'undefined'
null 'object'
布尔值 'boolean'
数字值 'number'
字符串值 'string'
函数 'function'
所有其他正常值 'object'
(引擎创建的值) JavaScript 引擎允许创建值,其typeof返回任意字符串(与此表中列出的所有结果都不同)。

typeof null返回'object'是一个无法修复的错误,因为这会破坏现有的代码。这并不意味着null是一个对象。

instanceof看起来像这样:

value instanceof Constr

如果value是由构造函数Constr创建的对象,则返回true(参见构造函数:对象的工厂)。以下是一些示例:

> var b = new Bar();  // object created by constructor Bar
> b instanceof Bar
true
> {} instanceof Object
true
> [] instanceof Array
true
> [] instanceof Object  // Array is a subconstructor of Object
true
> undefined instanceof Object
false
> null instanceof Object
false

布尔值

原始布尔类型包括值truefalse。以下运算符产生布尔值:

  • 二进制逻辑运算符:&&(与),||(或)
  • 前缀逻辑运算符:!(非)
  • 比较运算符:
  • 相等运算符:===!====!=
  • 排序运算符(用于字符串和数字):>, >=, <, <=

真值和假值

每当 JavaScript 期望布尔值(例如if语句的条件)时,可以使用任何值。它将被解释为truefalse。以下值被解释为false

  • undefinednull
  • 布尔值:false
  • 数字:-0NaN
  • 字符串:''

所有其他值(包括所有对象!)都被认为是true。被解释为false的值称为假值,被解释为true的值称为真值Boolean()作为函数调用,将其参数转换为布尔值。您可以使用它来测试值的解释方式:

> Boolean(undefined)
false
> Boolean(0)
false
> Boolean(3)
true
> Boolean({}) // empty object
true
> Boolean([]) // empty array
true

二进制逻辑运算符

JavaScript 中的二进制逻辑运算符是短路的。也就是说,如果第一个操作数足以确定结果,第二个操作数将不会被评估。例如,在以下表达式中,函数foo()永远不会被调用:

false && foo()
true  || foo()

此外,二进制逻辑运算符返回它们的操作数之一,这些操作数可能是布尔值也可能不是。使用真值检查来确定哪一个:

和(&&)

如果第一个操作数为假值,则返回它。否则,返回第二个操作数:

> NaN && 'abc'
NaN
> 123 && 'abc'
'abc'

或(||)

如果第一个操作数为真值,则返回它。否则,返回第二个操作数:

> 'abc' || 123
'abc'
> '' || 123
123

相等运算符

JavaScript 有两种相等性:

  • 普通,或“宽松”,(不)相等:==!=
  • 严格(不)相等:===!==

普通相等性认为太多的值是相等的(详细内容在普通(宽松)相等性(==,!=)中有解释),这可能会隐藏错误。因此,建议始终使用严格相等性。

数字

JavaScript 中的所有数字都是浮点数:

> 1 === 1.0
true

特殊数字包括以下内容:

NaN(“不是一个数字”)

一个错误值:

> Number('xyz')  // 'xyz' can’t be converted to a number
NaN

Infinity

也是大多数错误值:

> 3 / 0
Infinity
> Math.pow(2, 1024)  // number too large
Infinity

Infinity大于任何其他数字(除了NaN)。同样,-Infinity小于任何其他数字(除了NaN)。这使得这些数字在作为默认值时非常有用(例如,当你正在寻找最小值或最大值时)。

运算符

JavaScript 有以下算术运算符(参见算术运算符):

  • 加法:number1 + number2
  • 减法:number1 - number2
  • 乘法:number1 * number2
  • 除法:number1 / number2
  • 余数:number1 % number2
  • 增量:++variable, variable++
  • 递减:--variable, variable--
  • 否定:-value
  • 转换为数字:+value

全局对象Math(参见Math)通过函数提供更多的算术运算。

JavaScript 还有位操作的运算符(例如,位与;参见位运算符)。

字符串

字符串可以直接通过字符串字面量创建。这些字面量由单引号或双引号括起来。反斜杠(\)转义字符并产生一些控制字符。以下是一些例子:

'abc'
"abc"
'Did she say "Hello"?'
"Did she say \"Hello\"?"
'That\'s nice!'
"That's nice!"
'Line 1\nLine 2'  // newline
'Backlash: \\'

单个字符通过方括号访问:

> var str = 'abc';
> str[1]
'b'

属性length计算字符串中的字符数:

> 'abc'.length
3

与所有原始值一样,字符串是不可变的;如果要更改现有字符串,需要创建一个新字符串。

字符串运算符

字符串通过加号(+)操作符进行连接,如果其中一个操作数是字符串,则将另一个操作数转换为字符串:

> var messageCount = 3;
> 'You have ' + messageCount + ' messages'
'You have 3 messages'

要在多个步骤中连接字符串,使用+=操作符:

> var str = '';
> str += 'Multiple ';
> str += 'pieces ';
> str += 'are concatenated.';
> str
'Multiple pieces are concatenated.'

字符串方法

字符串有许多有用的方法(参见字符串原型方法)。以下是一些例子:

> 'abc'.slice(1)  // copy a substring
'bc'
> 'abc'.slice(1, 2)
'b'
> '\t xyz  '.trim()  // trim whitespace
'xyz'
> 'mjölnir'.toUpperCase()
'MJÖLNIR'
> 'abc'.indexOf('b')  // find a string
1
> 'abc'.indexOf('x')
-1

语句

JavaScript 中的条件和循环在以下部分介绍。

条件

if语句有一个then子句和一个可选的else子句,根据布尔条件执行:

if (myvar === 0) {
    // then
}
if (myvar === 0) {
    // then
} else {
    // else
}
if (myvar === 0) {
    // then
} else if (myvar === 1) {
    // else-if
} else if (myvar === 2) {
    // else-if
} else {
    // else
}

我建议始终使用大括号(它们表示零个或多个语句的块)。但如果一个子句只是一个语句,你不必这样做(对于控制流语句forwhile也是如此):

if (x < 0) return -x;

以下是一个switch语句。fruit的值决定执行哪个case

switch (fruit) {
    case 'banana':
        // ...
        break;
    case 'apple':
        // ...
        break;
    default:  // all other cases
        // ...
}

case后的“操作数”可以是任何表达式;它通过===switch的参数进行比较。

循环

for循环的格式如下:

for (⟦«init»⟧; ⟦«condition»⟧; ⟦«post_iteration»⟧)
    «statement»

init在循环开始时执行。在每次循环迭代之前检查condition;如果变为false,则终止循环。post_iteration在每次循环迭代后执行。

此示例在控制台上打印数组arr的所有元素:

for (var i=0; i < arr.length; i++) {
    console.log(arr[i]);
}

while循环在其条件成立时继续循环其主体:

// Same as for loop above:
var i = 0;
while (i < arr.length) {
    console.log(arr[i]);
    i++;
}

do-while循环在其条件成立时继续循环其主体。由于条件跟随主体,因此主体始终至少执行一次:

do {
    // ...
} while (condition);

在所有循环中:

  • break离开循环。
  • continue开始新的循环迭代。

函数

定义函数的一种方式是通过函数声明

function add(param1, param2) {
    return param1 + param2;
}

前面的代码定义了一个函数add,它有两个参数param1param2,并返回这两个参数的总和。这是如何调用该函数的:

> add(6, 1)
7
> add('a', 'b')
'ab'

定义add()的另一种方式是通过将函数表达式分配给变量add

var add = function (param1, param2) {
    return param1 + param2;
};

函数表达式产生一个值,因此可以直接用于将函数作为参数传递给其他函数:

someOtherFunction(function (p1, p2) { ... });

函数声明被提升

函数声明是提升的-完整地移动到当前范围的开头。这允许您引用稍后声明的函数:

function foo() {
    bar();  // OK, bar is hoisted
    function bar() {
        ...
    }
}

请注意,虽然var声明也被提升(参见变量被提升),但是它们执行的赋值不会:

function foo() {
    bar();  // Not OK, bar is still undefined
    var bar = function () {
        // ...
    };
}

特殊变量参数

您可以使用任意数量的参数调用 JavaScript 中的任何函数;语言永远不会抱怨。但是,它将使所有参数通过特殊变量arguments可用。arguments看起来像一个数组,但没有数组方法:

> function f() { return arguments }
> var args = f('a', 'b', 'c');
> args.length
3
> args[0]  // read element at index 0
'a'

参数太多或太少

让我们使用以下函数来探索 JavaScript 中如何处理太多或太少的参数(函数toArray()显示在将参数转换为数组中):

function f(x, y) {
    console.log(x, y);
    return toArray(arguments);
}

将忽略额外的参数(除了arguments):

> f('a', 'b', 'c')
a b
[ 'a', 'b', 'c' ]

缺少参数将获得值undefined

> f('a')
a undefined
[ 'a' ]
> f()
undefined undefined
[]

可选参数

以下是为参数分配默认值的常见模式:

function pair(x, y) {
    x = x || 0;  // (1)
    y = y || 0;
    return [ x, y ];
}

在第(1)行,||运算符返回x,如果它是真值(不是nullundefined等)。否则,它将返回第二个操作数:

> pair()
[ 0, 0 ]
> pair(3)
[ 3, 0 ]
> pair(3, 5)
[ 3, 5 ]

强制参数个数

如果要强制执行arity(特定数量的参数),可以检查arguments.length

function pair(x, y) {
    if (arguments.length !== 2) {
        throw new Error('Need exactly 2 arguments');
    }
    ...
}

将参数转换为数组

arguments不是数组,它只是类似数组(参见类似数组对象和通用方法)。它有一个length属性,您可以通过方括号中的索引访问其元素。但是,您无法删除元素或调用其中任何数组方法。因此,有时需要将arguments转换为数组,这就是以下函数所做的事情(它在类似数组对象和通用方法中有解释):

function toArray(arrayLikeObject) {
    return Array.prototype.slice.call(arrayLikeObject);
}

异常处理

处理异常的最常见方法(参见第十四章)如下:

function getPerson(id) {
    if (id < 0) {
        throw new Error('ID must not be negative: '+id);
    }
    return { id: id }; // normally: retrieved from database
}
function getPersons(ids) {
    var result = [];
    ids.forEach(function (id) {
        try {
            var person = getPerson(id);
            result.push(person);
        } catch (exception) {
            console.log(exception);
        }
    });
    return result;
}

try子句包围关键代码,如果在try子句内抛出异常,则执行catch子句。使用前面的代码:

> getPersons([2, -5, 137])
[Error: ID must not be negative: -5]
[ { id: 2 }, { id: 137 } ]

严格模式

严格模式(参见严格模式)启用更多警告,并使 JavaScript 成为一种更干净的语言(非严格模式有时被称为“松散模式”)。要打开它,请首先在 JavaScript 文件或<script>标记中键入以下行:

'use strict';

您还可以为每个函数启用严格模式:

function functionInStrictMode() {
    'use strict';
}

变量作用域和闭包

在 JavaScript 中,你在使用变量之前通过var声明变量:

> var x;
> x = 3;
> y = 4;
ReferenceError: y is not defined

你可以使用单个var语句声明和初始化多个变量:

var x = 1, y = 2, z = 3;

但我建议每个变量使用一个语句(原因在Syntax中有解释)。因此,我会重写上一个语句为:

var x = 1;
var y = 2;
var z = 3;

由于提升(参见变量被提升),通常最好在函数的开头声明变量。

变量是函数作用域的

变量的作用域总是整个函数(而不是当前的块)。例如:

function foo() {
    var x = -512;
    if (x < 0) {  // (1)
        var tmp = -x;
        ...
    }
    console.log(tmp);  // 512
}

我们可以看到变量tmp不仅限于从第(1)行开始的块;它存在直到函数的结束。

变量被提升

每个变量声明都是提升的:声明被移动到函数的开头,但它所做的赋值保持不变。例如,考虑下面函数中第(1)行的变量声明:

function foo() {
    console.log(tmp); // undefined
    if (false) {
        var tmp = 3;  // (1)
    }
}

在内部,前面的函数是这样执行的:

function foo() {
    var tmp;  // hoisted declaration
    console.log(tmp);
    if (false) {
        tmp = 3;  // assignment stays put
    }
}

闭包

每个函数都与包围它的函数的变量保持连接,即使它离开了被创建的作用域。例如:

function createIncrementor(start) {
    return function () {  // (1)
        start++;
        return start;
    }
}

从第(1)行开始的函数离开了它被创建的上下文,但仍然连接到start的一个活动版本:

> var inc = createIncrementor(5);
> inc()
6
> inc()
7
> inc()
8

闭包是一个函数加上与其周围作用域的变量的连接。因此,createIncrementor()返回的是一个闭包。

IIFE 模式:引入新的作用域

有时你想引入一个新的变量作用域——例如,防止一个变量成为全局的。在 JavaScript 中,你不能使用块来做到这一点;你必须使用一个函数。但是有一种使用函数的块状方式的模式。它被称为IIFE立即调用函数表达式,发音为“iffy”):

(function () {  // open IIFE
    var tmp = ...;  // not a global variable
}());  // close IIFE

确保按照前面的示例精确地输入(除了注释)。IIFE 是一个在定义后立即调用的函数表达式。在函数内部,存在一个新的作用域,防止tmp成为全局的。请参阅IIFE 引入新的作用域了解 IIFE 的详细信息。

IIFE 的用例:通过闭包无意中共享

闭包保持与外部变量的连接,有时这并不是你想要的:

var result = [];
for (var i=0; i < 5; i++) {
    result.push(function () { return i });  // (1)
}
console.log(result1; // 5 (not 1)
console.log(result3; // 5 (not 3)

第(1)行返回的值始终是i的当前值,而不是函数创建时的值。循环结束后,i的值为 5,这就是为什么数组中的所有函数都返回该值。如果你想让第(1)行的函数接收当前i值的快照,你可以使用 IIFE:

for (var i=0; i < 5; i++) {
    (function () {
        var i2 = i; // copy current i
        result.push(function () { return i2 });
    }());
}

对象和构造函数

本节涵盖了 JavaScript 的两种基本面向对象的机制:单个对象和构造函数(它们是对象的工厂,类似于其他语言中的类)。

单个对象

像所有的值一样,对象都有属性。实际上,你可以把对象看作是一组属性,其中每个属性都是一个(键,值)对。键是一个字符串,值是任何 JavaScript 值。

在 JavaScript 中,你可以直接通过对象字面量创建普通对象:

'use strict';
var jane = {
    name: 'Jane',
    describe: function () {
        return 'Person named '+this.name;
    }
};

前面的对象有属性namedescribe。你可以读取(“获取”)和写入(“设置”)属性:

> jane.name  // get
'Jane'
> jane.name = 'John';  // set
> jane.newProperty = 'abc';  // property created automatically

describe这样的函数值属性被称为方法。它们使用this来引用调用它们的对象:

> jane.describe()  // call method
'Person named John'
> jane.name = 'Jane';
> jane.describe()
'Person named Jane'

in运算符检查一个属性是否存在:

> 'newProperty' in jane
true
> 'foo' in jane
false

如果读取一个不存在的属性,你会得到值undefined。因此,前面的两个检查也可以这样执行:

> jane.newProperty !== undefined
true
> jane.foo !== undefined
false

delete运算符移除一个属性:

> delete jane.newProperty
true
> 'newProperty' in jane
false

任意属性键

属性键可以是任何字符串。到目前为止,我们已经在对象文字和点运算符之后看到了属性键。但是,只有在它们是标识符时,才能以这种方式使用它们(参见Identifiers and Variable Names)。如果要使用其他字符串作为键,必须在对象文字中对其进行引用,并使用方括号来获取和设置属性:

> var obj = { 'not an identifier': 123 };
> obj['not an identifier']
123
> obj['not an identifier'] = 456;

方括号还允许您计算属性的键:

> var obj = { hello: 'world' };
> var x = 'hello';
> obj[x]
'world'
> obj['hel'+'lo']
'world'

提取方法

如果提取一个方法,它将失去与对象的连接。单独使用时,该函数不再是一个方法,this 的值为 undefined(在严格模式下)。

例如,让我们回到之前的对象 jane

'use strict';
var jane = {
    name: 'Jane',
    describe: function () {
        return 'Person named '+this.name;
    }
};

我们想从 jane 中提取方法 describe,将其放入变量 func 中,并调用它。但是,这样做不起作用:

> var func = jane.describe;
> func()
TypeError: Cannot read property 'name' of undefined

解决方案是使用所有函数都具有的 bind() 方法。它创建一个新函数,其 this 始终具有给定值:

> var func2 = jane.describe.bind(jane);
> func2()
'Person named Jane'

方法内的函数

每个函数都有自己的特殊变量 this。如果在方法内部嵌套函数,这是不方便的,因为您无法从函数中访问方法的 this。以下是一个示例,我们调用 forEach 以使用函数遍历数组:

var jane = {
    name: 'Jane',
    friends: [ 'Tarzan', 'Cheeta' ],
    logHiToFriends: function () {
        'use strict';
        this.friends.forEach(function (friend) {
            // `this` is undefined here
            console.log(this.name+' says hi to '+friend);
        });
    }
}

调用 logHiToFriends 会产生一个错误:

> jane.logHiToFriends()
TypeError: Cannot read property 'name' of undefined

让我们看看修复这个问题的两种方法。首先,我们可以将 this 存储在不同的变量中:

logHiToFriends: function () {
    'use strict';
    var that = this;
    this.friends.forEach(function (friend) {
        console.log(that.name+' says hi to '+friend);
    });
}

或者,forEach 有一个第二个参数,允许您为 this 提供一个值:

logHiToFriends: function () {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name+' says hi to '+friend);
    }, this);
}

在 JavaScript 中,函数表达式经常用作函数调用中的参数。当您从这些函数表达式之一引用 this 时,一定要小心。

构造函数:对象的工厂

到目前为止,您可能认为 JavaScript 对象 是从字符串到值的映射,这是 JavaScript 对象文字所暗示的概念,它看起来像其他语言的映射/字典文字。但是,JavaScript 对象还支持一项真正面向对象的功能:继承。本节并未完全解释 JavaScript 继承的工作原理,但它向您展示了一个简单的模式,以便您开始。如果您想了解更多,请参阅第十七章。

除了作为“真正的”函数和方法外,函数在 JavaScript 中还扮演另一个角色:如果通过 new 运算符调用,它们将成为 构造函数——对象的工厂。因此,构造函数在其他语言中是类的粗略类比。按照惯例,构造函数的名称以大写字母开头。例如:

// Set up instance data
function Point(x, y) {
    this.x = x;
    this.y = y;
}
// Methods
Point.prototype.dist = function () {
    return Math.sqrt(this.x*this.x + this.y*this.y);
};

我们可以看到构造函数有两个部分。首先,函数 Point 设置实例数据。其次,属性 Point.prototype 包含一个具有方法的对象。前者数据对每个实例都是特定的,而后者数据在所有实例之间共享。

要使用 Point,我们通过 new 运算符调用它:

> var p = new Point(3, 5);
> p.x
3
> p.dist()
5.830951894845301

pPoint 的一个实例:

> p instanceof Point
true

数组

数组是可以通过从零开始的整数索引访问的元素序列。

数组文字

数组文字对于创建数组很方便:

> var arr = [ 'a', 'b', 'c' ];

前面的数组有三个元素:字符串 'a''b''c'。您可以通过整数索引访问它们:

> arr[0]
'a'
> arr[0] = 'x';
> arr
[ 'x', 'b', 'c' ]

length 属性指示数组有多少个元素。您可以使用它来追加元素和删除元素:

> var arr = ['a', 'b'];
> arr.length
2
> arr[arr.length] = 'c';
> arr
[ 'a', 'b', 'c' ]
> arr.length
3
> arr.length = 1;
> arr
[ 'a' ]

in 运算符也适用于数组:

> var arr = [ 'a', 'b', 'c' ];
> 1 in arr // is there an element at index 1?
true
> 5 in arr // is there an element at index 5?
false

请注意,数组是对象,因此可以具有对象属性:

> var arr = [];
> arr.foo = 123;
> arr.foo
123

数组方法

数组有许多方法(参见Array Prototype Methods)。以下是一些示例:

> var arr = [ 'a', 'b', 'c' ];
> arr.slice(1, 2)  // copy elements
[ 'b' ]
> arr.slice(1)
[ 'b', 'c' ]
> arr.push('x')  // append an element
4
> arr
[ 'a', 'b', 'c', 'x' ]
> arr.pop()  // remove last element
'x'
> arr
[ 'a', 'b', 'c' ]
> arr.shift()  // remove first element
'a'
> arr
[ 'b', 'c' ]
> arr.unshift('x')  // prepend an element
3
> arr
[ 'x', 'b', 'c' ]
> arr.indexOf('b')  // find the index of an element
1
> arr.indexOf('y')
-1
> arr.join('-')  // all elements in a single string
'x-b-c'
> arr.join('')
'xbc'
> arr.join()
'x,b,c'

遍历数组

有几种用于遍历元素的数组方法(参见Iteration (Nondestructive))。最重要的两个是 forEachmap

forEach 遍历数组并将当前元素及其索引传递给函数:

[ 'a', 'b', 'c' ].forEach(
    function (elem, index) {  // (1)
        console.log(index + '. ' + elem);
    });

前面的代码产生以下输出:

0\. a
1\. b
2\. c

请注意,第(1)行中的函数可以忽略参数。例如,它可能只有参数elem

map通过将函数应用于现有数组的每个元素来创建一个新数组:

> [1,2,3].map(function (x) { return x*x })
[ 1, 4, 9 ]

正则表达式

JavaScript 内置支持正则表达式(第十九章是教程,更详细地解释了它们的工作原理)。它们由斜杠分隔:

/^abc$/
/[A-Za-z0-9]+/

方法 test(): 是否有匹配项?

> /^a+b+$/.test('aaab')
true
> /^a+b+$/.test('aaa')
false

方法 exec(): 匹配和捕获组

> /a(b+)a/.exec('_abbba_aba_')
[ 'abbba', 'bbb' ]

返回的数组包含索引 0 处的完整匹配项,索引 1 处的第一个组的捕获,依此类推。还有一种方法(在RegExp.prototype.exec: Capture Groups中讨论)可以重复调用此方法以获取所有匹配项。

方法 replace(): 搜索和替换

> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]')
'[a] [bbb]'

replace的第一个参数必须是带有/g标志的正则表达式;否则,只会替换第一个匹配项。还有一种方法(如在String.prototype.replace: Search and Replace中讨论的)可以使用函数来计算替换。

数学

Math(参见第二十一章)是一个具有算术函数的对象。以下是一些示例:

> Math.abs(-2)
2
> Math.pow(3, 2)  // 3 to the power of 2
9
> Math.max(2, -1, 5)
5
> Math.round(1.9)
2
> Math.PI  // pre-defined constant for π
3.141592653589793
> Math.cos(Math.PI)  // compute the cosine for 180°
-1

标准库的其他功能

JavaScript 的标准库相对简陋,但还有更多可以使用的东西:

Date(第二十章)

一个日期的构造函数,其主要功能是解析和创建日期字符串以及访问日期的组件(年、小时等)。

JSON(第二十二章)

一个具有解析和生成 JSON 数据功能的对象。

console.* 方法(参见控制台 API

这些特定于浏览器的方法不是语言本身的一部分,但其中一些也适用于 Node.js。

第二部分:背景

原文:II. Background

译者:飞龙

协议:CC BY-NC-SA 4.0

这部分解释了 JavaScript 的历史和性质。它对语言进行了广泛的初步介绍,并解释了它存在的背景(不过不涉及太多技术细节)。

这部分不是必读的;你可以在没有阅读它的情况下理解本书的其余部分。

第二章 为什么选择 JavaScript?

原文:2. Why JavaScript?

译者:飞龙

协议:CC BY-NC-SA 4.0

有很多编程语言。为什么你要使用 JavaScript?本章将从七个重要方面来看,这些方面在你选择编程语言时很重要,并且认为 JavaScript 总体上做得很好:

  1. 它是免费提供的吗?
  2. 它是一种优雅的编程语言吗?
  3. 在实践中有用吗?
  4. 它有好的工具,特别是好的集成开发环境(IDE)吗?
  5. 它对你想做的事情来说足够快吗?
  6. 它被广泛使用吗?
  7. 它有未来吗?

JavaScript 是免费提供的吗?

JavaScript 可以说是最开放的编程语言:它的规范 ECMA-262 是 ISO 标准。许多独立方实现都紧密遵循这一规范。其中一些实现是开源的。此外,语言的演变由 TC39 委员会负责,该委员会由包括所有主要浏览器供应商在内的几家公司组成。其中许多公司通常是竞争对手,但他们为了语言的利益而共同合作。

JavaScript 优雅吗?

是和不是。我用不同范式的几种编程语言写了大量代码。因此,我很清楚 JavaScript 并不是优雅的巅峰。然而,它是一种非常灵活的语言,有一个相当优雅的核心,并且使你能够使用面向对象编程和函数式编程的混合。

JavaScript 引擎之间的语言兼容性曾经是一个问题,但现在不再是了,部分得益于测试 262 套件,该套件检查引擎是否符合 ECMAScript 规范。相比之下,浏览器和 DOM 的差异仍然是一个挑战。这就是为什么通常最好依赖框架来隐藏这些差异。

JavaScript 有用吗?

世界上最美丽的编程语言是无用的,除非它能让你编写你需要的程序。

图形用户界面

在图形用户界面领域,JavaScript 受益于成为HTML5的一部分。在本节中,我使用 HTML5 这个术语来表示“浏览器平台”(HTML、CSS 和浏览器 JavaScript API)。HTML5 得到了广泛的部署,并不断取得进展。它正在慢慢地成为一个完整的层,用于编写功能齐全的跨平台应用程序;类似于 Java 平台,它几乎像一个嵌入式操作系统。HTML5 的卖点之一是它让你编写跨平台的图形用户界面。这些总是一种妥协:你放弃了一些质量,以换取不受限于单一操作系统。过去,“跨平台”意味着 Windows、Mac OS 或 Linux。但现在我们有了两个额外的交互平台:Web 和移动。使用 HTML5,你可以通过诸如PhoneGapChrome AppsTideSDK等技术来针对所有这些平台。

此外,一些平台将 Web 应用程序作为本地应用程序或允许你本地安装它们——例如 Chrome OS、Firefox OS 和 Android。

其他补充 JavaScript 的技术

除了 HTML5 之外,还有更多的技术可以补充 JavaScript,使语言更有用:

JavaScript 有大量的库,可以让你完成各种任务,从解析 JavaScript(通过Esprima)到处理和显示 PDF 文件(通过PDF.js)。

Node.js

Node.js 平台让你编写服务器端代码和 shell 脚本(构建工具、测试运行器等)。

JSON(JavaScript 对象表示法,在第二十二章中介绍)

JSON 是一种根植于 JavaScript 的数据格式,在 Web 上交换数据变得流行(例如网络服务的结果)。

NoSQL 数据库(例如CouchDBMongoDB

这些数据库紧密集成了 JSON 和 JavaScript。

JavaScript 有好的工具吗?

JavaScript 正在获得更好的构建工具(例如Grunt)和测试工具(例如mocha)。Node.js 使得可以通过 shell 运行这些类型的工具(不仅仅在浏览器中)。在这个领域的一个风险是分裂,因为我们逐渐得到了太多这样的工具。

JavaScript 的 IDE 空间仍处于萌芽阶段,但正在迅速成长。网络开发的复杂性和动态性使得这个空间成为创新的肥沃土壤。两个开源的例子是BracketsLight Table

此外,浏览器正变得越来越强大的开发环境。特别是 Chrome 最近取得了令人印象深刻的进展。有趣的是看到 IDE 和浏览器在未来将整合到多大程度。

JavaScript 足够快吗?

JavaScript 引擎取得了巨大的进步,从缓慢的解释器发展为快速的即时编译器。它们现在已经足够快,适用于大多数应用。此外,已经在开发新的想法,使 JavaScript 足够快以适用于其余的应用:

  • asm.js是 JavaScript 的(非常静态的)子集,在当前引擎上运行速度很快,大约相当于编译后的 C++的 70%。例如,它可以用于实现网络应用的性能关键算法部分。它还被用于将基于 C++的游戏移植到网络平台上。
  • ParallelJS可以并行化使用新数组方法mapParfilterParreducePar(现有数组方法mapfilterreduce的可并行化版本)的 JavaScript 代码。为了使并行化工作,回调必须以特殊的方式编写;主要限制是不能改变在回调中未创建的数据。

JavaScript 被广泛使用吗?

通常广泛使用的语言有两个好处。首先,这样的语言有更好的文档和支持。其次,更多的程序员知道它,这在你需要雇佣某人或者寻找基于该语言的工具的客户时非常重要。

JavaScript 被广泛使用,并获得了前述两个好处:

  • 如今,JavaScript 的文档和支持以各种形式呈现:书籍、播客、博客文章、电子邮件通讯、论坛等等。第三十三章指引您前往重要资源。
  • JavaScript 开发人员需求量大,但他们的人数也在不断增加。

JavaScript 有未来吗?

有几件事表明 JavaScript 有一个光明的未来:

  • 语言正在稳步发展;ECMAScript 6 看起来不错。
  • 有许多与 JavaScript 相关的创新(例如前述的 asm.js 和 ParallelJS,微软的 TypeScript 等)。
  • JavaScript 作为一个不可或缺的部分所在的网络平台正在迅速成熟。
  • JavaScript 得到了众多公司的支持,没有单个人或公司控制它。

结论

考虑到使一种语言具有吸引力的前述因素,JavaScript 的表现非常出色。它当然并不完美,但目前很难超越它,而且情况只会变得更好。

第三章:JavaScript 的本质

原文:3. The Nature of JavaScript

译者:飞龙

协议:CC BY-NC-SA 4.0

JavaScript 的本质可以总结如下:

它是动态的

许多东西都可以改变。例如,你可以自由地添加和删除对象的属性(字段)。而且你可以直接创建对象,而不需要先创建对象工厂(例如类)。

它是动态类型的

变量和对象属性始终可以保存任何类型的值。

它是功能性的和面向对象的

JavaScript 支持两种编程语言范式:函数式编程(一流函数、闭包、通过bind()进行部分应用、数组的内置map()reduce()等)和面向对象编程(可变状态、对象、继承等)。

它默默失败

直到 ECMAScript 3,JavaScript 才没有异常处理。这就解释了为什么语言经常默默失败并自动转换参数和操作数的值:它最初无法抛出异常。

它部署为源代码

JavaScript 始终以源代码部署,并由 JavaScript 引擎编译。源代码具有灵活的交付格式和抽象引擎之间的差异的好处。为了保持文件大小小,使用了两种技术:压缩(主要是 gzip)和最小化(通过重命名变量、删除注释等使源代码更小;有关详细信息,请参见第三十二章)。

它是 Web 平台的一部分

JavaScript 是 Web 平台(HTML5 API、DOM 等)的一个重要组成部分,以至于很容易忘记前者也可以在没有后者的情况下使用。然而,JavaScript 在非浏览器环境中的使用越多(如 Node.js),它就越明显。

怪癖和非正统特性

一方面,JavaScript 有一些怪癖和缺失的功能(例如,它没有块作用域变量,没有内置模块,也不支持子类化)。因此,在其他语言中学习语言特性的地方,你需要在 JavaScript 中学习模式和解决方法。另一方面,JavaScript 包括非正统的特性(如原型继承和对象属性)。这些也需要学习,但更像是一种特性而不是错误。

请注意,JavaScript 引擎已经变得非常智能,并在幕后修复了一些怪癖。例如:

  • 就规范而言,JavaScript 没有整数,只有浮点数。在内部,大多数引擎尽可能使用整数。
  • 可以说,JavaScript 中的数组太灵活了:它们不是元素的索引序列,而是从数字到元素的映射。这样的映射可以有空洞:数组“内部”没有关联值的索引。再次,引擎通过使用优化表示来帮助数组不具有空洞。

优雅的部分

但 JavaScript 也有许多优雅的部分。Brendan Eich 最喜欢的是:¹

  • 一流函数
  • 闭包
  • 原型
  • 对象字面量
  • 数组字面量

最后两个项目,对象字面量和数组字面量,让你可以从对象开始,并在后来引入抽象(比如构造函数,JavaScript 中类的类比)。它们还支持 JSON(见第二十二章)。

请注意,优雅的部分可以帮助你解决怪癖。例如,它们允许你在语言内部实现块作用域、模块和继承 API。

影响

JavaScript 受到了几种编程语言的影响(如[图 3-1](ch03.html#fig3-1 “图 3-1: 影响 JavaScript 的编程语言。”)所示):

  • Java 是 JavaScript 语法的榜样。它还导致 JavaScript 将值分为原始值和对象,并引入了Date构造函数(这是java.util.Date的一个移植)。
  • AWK 启发了 JavaScript 的函数。实际上,关键字function来自 AWK。
  • Scheme 是 JavaScript 拥有一流函数(它们被视为值并且可以作为参数传递给函数)和闭包(见第十六章)的原因。
  • Self 对 JavaScript 不寻常的对象导向风格负有责任;它支持对象之间的原型继承。
  • Perl 和 Python 影响了 JavaScript 对字符串、数组和正则表达式的处理。
  • 除了实际的语言之外,HyperTalk 影响了 JavaScript 如何集成到 Web 浏览器中。这导致 HTML 标签具有事件处理属性,如onclick

图 3-1。影响 JavaScript 的编程语言。


¹ Brendan Eich,“JavaScript 简史”,2010 年 7 月 21 日,bit.ly/1lKkI0M

第四章:JavaScript 的创建方式

原文:4. How JavaScript Was Created

译者:飞龙

协议:CC BY-NC-SA 4.0

了解 JavaScript 的创建原因和方式有助于我们理解它的特点。

1993 年,NCSA 的 Mosaic 是第一个广受欢迎的 Web 浏览器。1994 年,成立了一家名为网景的公司,以利用新兴的万维网的潜力。网景创建了专有的 Web 浏览器 Netscape Navigator,在 1990 年代占主导地位。许多最初的 Mosaic 作者继续在 Navigator 上工作,但两者故意没有共享代码。

网景很快意识到 Web 需要变得更加动态。即使你只是想检查用户在表单中输入的正确值,也需要将数据发送到服务器以便提供反馈。1995 年,网景聘请了 Brendan Eich,并承诺让他在浏览器中实现 Scheme(一种 Lisp 方言)。²在他开始之前,网景与硬件和软件公司 Sun(后来被 Oracle 收购)合作,将其更静态的编程语言 Java 包含在 Navigator 中。因此,网景内部激烈争论的一个问题是为什么 Web 需要两种编程语言:Java 和一种脚本语言。脚本语言的支持者提出了以下解释:³

我们旨在为 Web 设计师和兼职程序员提供“粘合语言”,他们正在从图像、插件和 Java 小程序等组件构建 Web 内容。我们认为 Java 是由高价程序员使用的“组件语言”,而粘合程序员——Web 页面设计师——将组件组装起来,并使用[一种脚本语言]自动化它们的交互。

当时,网景管理层决定脚本语言必须具有类似于 Java 的语法。这排除了采用现有的语言,如 Perl、Python、TCL 或 Scheme。为了捍卫 JavaScript 的想法,网景需要一个原型。艾奇在 1995 年 5 月的 10 天内写了一个原型。JavaScript 的第一个代号是 Mocha,由马克·安德森创造。后来,网景营销部门出于商标原因和因为几个产品的名称已经带有前缀“Live”,将其更改为 LiveScript。1995 年 11 月底,Navigator 2.0B3 发布,包括原型,继续在早期没有进行重大更改的情况下存在。1995 年 12 月初,Java 的势头增长,Sun 授权商标 Java 给网景。语言再次更名为最终名称 JavaScript。⁴


² Brendan Eich,“流行程度”,2008 年 4 月 3 日,bit.ly/1lKl6fG

³ Naomi Hamilton,“编程语言 A-Z:JavaScript”,Computerworld,2008 年 7 月 30 日,bit.ly/1lKldIe

⁴ Paul Krill,“JavaScript 创造者思考过去和未来”,InfoWorld,2008 年 6 月 23 日,bit.ly/1lKlpXO;Brendan Eich,“JavaScript 简史”,2010 年 7 月 21 日,bit.ly/1lKkI0M

第五章 标准化:ECMAScript

原文:5. Standardization: ECMAScript

译者:飞龙

协议:CC BY-NC-SA 4.0

JavaScript 推出后,微软在 Internet Explorer 3.0(1996 年 8 月)中实现了相同的语言,但名称不同,称为 JScript。为了限制微软,网景决定标准化 JavaScript,并要求标准组织 Ecma International 托管标准。ECMA-262 的规范工作始于 1996 年 11 月。由于 Sun(现在是 Oracle)对 Java 一词拥有商标,因此标准化的官方名称不能是 JavaScript。因此,选择了 ECMAScript,源自 JavaScriptEcma。但是,该名称仅用于指代语言的版本(其中一个指的是规范)。每个人仍然称该语言为 JavaScript

ECMA-262 由 Ecma 的 技术委员会 39(TC39)管理和发展。其成员是微软、Mozilla 和 Google 等公司,它们指定员工参与委员会工作;例如 Brendan Eich、Allen Wirfs-Brock(ECMA-262 的编辑)和 David Herman。为了推进 ECMAScript 的设计,TC39 在开放渠道(如邮件列表 es-discuss)上举行讨论,并定期举行会议。会议由 TC39 成员和受邀专家参加。2013 年初,与会人数从 15 到 25 人不等。

以下是 ECMAScript 版本(或 ECMA-262 的 版本)及其主要特性的列表:

ECMAScript 1(1997 年 6 月)

第一版

ECMAScript 2(1998 年 8 月)

编辑更改以使 ECMA-262 与标准 ISO/IEC 16262 保持一致

ECMAScript 3(1999 年 12 月)

do-while,正则表达式,新的字符串方法(concatmatchreplaceslice,使用正则表达式的split等),异常处理等

ECMAScript 4(2008 年 7 月放弃)

ECMAScript 4 被开发为 JavaScript 的下一个版本,原型是用 ML 编写的。然而,TC39 无法就其功能集达成一致。为防止僵局,委员会于 2008 年 7 月底举行会议,并达成了一致,总结在 四点 中:

  1. 开发了 ECMAScript 3 的增量更新(成为 ECMAScript 5)。
  2. 开发了一个比 ECMAScript 4 更少,但比 ECMAScript 3 的增量更新更多的主要新版本。新版本的代号是 Harmony,因为它的构思是在一个和谐的会议中产生的。Harmony 将分为 ECMAScript 6 和 ECMAScript 7。
  3. ECMAScript 4 中将被删除的特性包括包,命名空间和早期绑定。
  4. 其他想法将与 TC39 共识开发。

因此,ECMAScript 4 的开发人员同意使 Harmony 比 ECMAScript 4 更少激进,而 TC39 的其他成员同意继续推动事情向前发展。

ECMAScript 5(2009 年 12 月)

添加了严格模式,获取器和设置器,新的数组方法,对 JSON 的支持等(参见 第二十五章)

ECMAScript 5.1(2011 年 6 月)

编辑更改以使 ECMA-262 与国际标准 ISO/IEC 16262:2011 的第三版保持一致

ECMAScript 6

目前正在开发中,预计将在 2014 年底得到批准。大多数引擎可能会在批准时支持最重要的 ECMAScript 6 特性。完整支持可能需要更长时间。

达成共识并创建标准并不总是容易的,但由于前述各方的协作努力,JavaScript 是一种真正开放的语言,由多个供应商实现,具有非常高的兼容性。这种兼容性是通过非常详细但具体的规范实现的。例如,规范经常使用伪代码,并且它由一个测试套件test262补充,该测试套件检查 ECMAScript 实现的兼容性。有趣的是,ECMAScript 并不由万维网联盟(W3C)管理。TC39 和 W3C 在 JavaScript 和 HTML5 之间存在重叠时进行合作。

相关文章
|
3月前
|
存储 JavaScript 前端开发
大话 JavaScript(Speaking JavaScript):第十六章到第二十章
大话 JavaScript(Speaking JavaScript):第十六章到第二十章
大话 JavaScript(Speaking JavaScript):第十六章到第二十章
|
3月前
|
存储 JavaScript 前端开发
大话 JavaScript(Speaking JavaScript):第十一章到第十五章
大话 JavaScript(Speaking JavaScript):第十一章到第十五章
|
3月前
|
移动开发 JavaScript 前端开发
大话 JavaScript(Speaking JavaScript):第六章到第十章
大话 JavaScript(Speaking JavaScript):第六章到第十章
|
3月前
|
存储 移动开发 JavaScript
大话 JavaScript(Speaking JavaScript):第三十一章到第三十三章
大话 JavaScript(Speaking JavaScript):第三十一章到第三十三章
|
3月前
|
存储 JavaScript 前端开发
大话 JavaScript(Speaking JavaScript):第二十六章到第三十章
大话 JavaScript(Speaking JavaScript):第二十六章到第三十章
|
3月前
|
Web App开发 JSON JavaScript
大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章
大话 JavaScript(Speaking JavaScript):第二十一章到第二十五章
|
2月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
27 0
|
2月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
70 0
|
7天前
|
JavaScript 前端开发 应用服务中间件
node.js之第一天学习
node.js之第一天学习
|
1月前
|
运维 JavaScript 前端开发
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!