字符串

字符串方法(String methods)

字符串有许多有用的方法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> 'abc'.slice(1)  // 复制子字符串
'bc'
> 'abc'.slice(1, 2)
'b'

> '\t xyz '.trim() // 移除空白字符
'xyz'

> 'mjölnir'.toUpperCase()
'MJÖLNIR'

> 'abc'.indexOf('b') // 查找字符串
1
> 'abc'.indexOf('x')
-1

函数

函数声明提升(Function declarations are hoisted)

函数声明会被提升,他们全被移动到当前作用域开始之处。这允许你在函数声明之前调用它们:

1
2
3
4
5
6
function foo() {
bar(); // 没问题,bar被提升
function bar() {
...
}
}

注意:虽然变量声明也会被提升,但赋值的过程不会被提升:

1
2
3
4
5
6
function foo() {
bar(); // 有问题,bar是undefined
var bar = function () {
// ...
};
}

特殊变量arguments(The special variable arguments)

在JavaScript中你可以调用任意函数并传递任意数量的参数——语言绝不会抱怨(参数检测)。都可以正常工作,然而,使所有参数可访问需要通过特殊变量 arguments。arguments 看起来像数组,但它没有数组的方法(称为类数组 array-like)。

1
2
3
4
5
6
> function f() { return arguments }
> var args = f('a', 'b', 'c');
> args.length
3
> args[0] // 获取索引为0的元素
'a'

太多或太少参数(Too many or too few arguments)

让我们通过下面的函数探索JavaScript中传递太多或太少参数时如何处理(函数 toArray在后面提到)

1
2
3
4
function f(x, y) {
console.log(x, y);
console.log(toArray(arguments));
}

多出的参数将被忽略(可以通过arguments访问):

1
2
3
> f('a', 'b', 'c')
a b
[ 'a', 'b', 'c' ]

缺少的参数将会是undefined:

1
2
3
4
5
6
> f('a')
a undefined
[ 'a' ]
> f()
undefined undefined
[]

可选参数(Optional parameters)

下面是一个常见模式,给参数设置默认值:

1
2
3
4
5
function pair(x, y) {
x = x || 0; // (*)
y = y || 0;
return [ x, y ];
}

在(*)这行,如果x是真值(除了:null,undefined 等), 操作符返回x。否则,它返回第二个操作数。

1
2
3
4
5
6
> pair()
[ 0, 0 ]
> pair(3)
[ 3, 0 ]
> pair(3, 5)
[ 3, 5 ]

强制数量(Enforcing an arity)

如果你想强制参数的数量,你可以检测arguments.length:

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

将arguments 转换为数组(Converting arguments to an array)

arguments 不是一个数组,它仅仅是类数组(array-like):它有一个length属性,并且你可以通过方括号索引方式访问它的元素。然而,你不能移除元素,或在它上面调用任何数组方法。因此,有时你需要将其转换为数组。这就是下面函数的作用。

1
2
3
function toArray(arrayLikeObject) {
return [].slice.call(arrayLikeObject);
}

严格模式

严格模式(Strict mode)

严格模式开启检测和一些其他措施,使JavaScript变成更整洁的语言。推荐使用严格模式。为了开启严格模式,只需在JavaScript文件或script标签第一行添加如下语句:

1
'use strict';

你也可以在每个函数上选择性开启严格模式,只需将上面的代码放在函数的开头:

1
2
3
function functionInStrictMode() {
'use strict';
}

下面的两小节看下严格模式的三大好处。

明确错误(Explicit errors)

让我们看一个例子,严格模式给我们明确的错误,否则JavaScript总是静默失败:下面的函数 f() 执行一些非法操作,它试图更改所有字符串都有的只读属性——length:

1
2
3
function f() {
'abc'.length = 5;
}

当你调用上面的函数,它静默失败,赋值操作被简单忽略。让我们将 f() 在严格模式下运行:

1
2
3
4
function f_strict() {
'use strict';
'abc'.length = 5;
}

现在浏览器报给我们一些错误:

1
2
> f_strict()
TypeError: Cannot assign to read only property 'length' of abc

不是方法的函数中的this(this in non-method functions)

在严格模式下,不作为方法的函数中的this值是undefined:

1
2
3
4
5
function f_strict() {
'use strict';
return this;
}
console.log(f_strict() === undefined); // true

在非严格模式下,this的值是被称作全局对象(global object)(在浏览器里是window):

1
2
3
4
function f() {
return this;
}
console.log(f() === window); // true

不再自动创建全局变量(No auto-created global variables)

在非严格模式下,如果你给不存在的变量赋值,JavaScript会自动创建一个全局变量:

1
2
3
4
> function f() { foo = 5 }
> f() // 不会报错
> foo
5

在严格模式下,这会产生一个错误:

1
2
3
> function f_strict() { 'use strict'; foo2 = 4; }
> f_strict()
ReferenceError: foo2 is not defined

变量作用域和闭包

变量和函数作用域(Variables are function-scoped)

变量的作用域总是整个函数(没有块级作用域)。例如:

1
2
3
4
5
6
7
8
function foo() {
var x = -3;
if (x < 0) { // (*)
var tmp = -x;
...
}
console.log(tmp); // 3
}

我们可以看到tmp变量不仅在(*)所在行的语句块存在,它在整个函数内都存在。

变量提升(Variables are hoisted)

变量声明会被提升:声明会被移到函数的顶部,但赋值过程不会。举个例子,在下面的函数中(*)行位置声明了一个变量。

1
2
3
4
5
6
function foo() {
console.log(tmp); // undefined
if (false) {
var tmp = 3; // (*)
}
}

在内部,上面的函数被执行像下面这样:

1
2
3
4
5
6
7
function foo() {
var tmp; // declaration is hoisted
console.log(tmp);
if (false) {
tmp = 3; // assignment stays put
}
}

闭包(Closures)

每个函数保持和函数体内部变量的连接,甚至离开创建它的作用域之后。例如:

1
2
3
4
5
function createIncrementor(start) {
return function () { // (*)
return start++;
}
}

在(*)行开始的函数在它创建时保留上下文,并在内部保存一个start活动值:

1
2
3
4
5
6
7
> var inc = createIncrementor(5);
> inc()
5
> inc()
6
> inc()
7

闭包是一个函数加上和其作用域链的链接。因此,createIncrementor() 返回的是一个闭包。

IIFE:模拟块级作用域(IIFE: Simulating block scoping)

有时你想模拟一个块,例如你想将变量从全局作用域隔离。完成这个工作的模式叫做 IIFE(立即执行函数表达式(Immediately Invoked Function Expression)):

1
2
3
(function () {  // 块开始
var tmp = ...; // 非全局变量
}()); // 块结束

上面你会看到函数表达式被立即执行。外面的括号用来阻止它被解析成函数声明;只有函数表达式能被立即调用。函数体产生一个新的作用域并使 tmp 变为局部变量。

闭包实现变量共享(Inadvertent sharing via closures)

下面是个经典问题,如果你不知道,会让你费尽思量。因此,先浏览下,对问题有个大概的了解。

闭包保持和外部变量的连接,有时可能和你想像的行为不一致:

1
2
3
4
5
6
var result = [];
for (var i=0; i < 5; i++) {
result.push(function () { return i }); // (*)
}
console.log(result[1]()); // 5 (不是 1)
console.log(result[3]()); // 5 (不是 3)

(*)行的返回值总是当前的i值,而不是当函数被创建时的i值。当循环结束后,i的值是5,这是为什么数组中的所有函数的返回值总是一样的。如果你想捕获当前变量的快照,你可以使用 IIFE:

1
2
3
4
5
for (var i=0; i < 5; i++) {
(function (i2) {
result.push(function () { return i2 });
}(i)); // 复制当前的i
}

对象和继承

任意键属性(Arbitrary property keys)

属性的键可以是任意字符串。到目前为止,我们看到的对象字面量中的和点操作符后的属性关键字。按这种方法你只能使用标识符。如果你想用其他任意字符串作为键名,你必须在对象字面量里加上引号,并使用方括号获取和设置属性。

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

方括号允许你动态计算属性关键字:

1
2
3
4
5
> var x = 'name';
> jane[x]
'Jane'
> jane['na'+'me']
'Jane'

引用方法(Extracting methods)

如果你引用一个方法,它将失去和对象的连接。就其本身而言,函数不是方法,其中的this值为undefined(严格模式下)。

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

解决办法是使用函数内置的bind()方法。它创建一个新函数,其this值固定为给定的值。

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

方法内部的函数(Functions inside a method)

每个函数都有一个特殊变量this。如果你在方法内部嵌入函数是很不方便的,因为你不能从函数中访问方法的this。下面是一个例子,我们调用forEach循环一个数组:

1
2
3
4
5
6
7
8
9
10
11
var jane = {
name: 'Jane',
friends: [ 'Tarzan', 'Cheeta' ],
logHiToFriends: function () {
'use strict';
this.friends.forEach(function (friend) {
// 这里的“this”是undefined
console.log(this.name+' says hi to '+friend);
});
}
}

调用 logHiToFriends 会产生错误:

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

有两种方法修复这问题。

1:将this存储在不同的变量。

1
2
3
4
5
6
7
logHiToFriends: function () {
'use strict';
var that = this;
this.friends.forEach(function (friend) {
console.log(that.name+' says hi to '+friend);
});
}

2:forEach的第二个参数允许提供this值。

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

在JavaScript中函数表达式经常被用作函数参数。时刻小心函数表达式中的this。

构造函数:对象工厂(Constructors: factories for objects)

目前为止,你可能认为JavaScript的对象仅是键值的映射,通过JavaScript对象字面量可以得出这个观点,看起来很像其他语言中的地图/字典(map/dictionary)。然而,JavaScript对象也支持真正意义上的面向对象特性:继承(inheritance)。这里只作简单介绍。

除了作为“真正”的函数和方法,函数还在JavaScript中扮演第三种角色:如果通过new操作符调用,他们会变为构造函数,对象的工厂。构造函数是对其他语言中的类的粗略模拟。约定俗成,构造函数的第一个字母大写。例如:

1
2
3
4
5
6
7
8
9
// 设置实例数据
function Point(x, y) {
this.x = x;
this.y = y;
}
// 方法
Point.prototype.dist = function () {
return Math.sqrt(this.x*this.x + this.y*this.y);
};

我们看到构造函数分为两部分:首先,Point函数设置实例数据。其次,Point.prototype属性包含对象的方法。前者的数据是每个实例私有的,后面的数据是所有实例共享的。

我们通过new操作符调用Point:

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

p是Point的一个实例:

1
2
3
4
> p instanceof Point
true
> typeof p
'object'

数组

数组方法(Array methods)

数组有许多方法。举些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
> var arr = [ 'a', 'b', 'c' ];

> arr.slice(1, 2) // 复制元素
[ 'b' ]
> arr.slice(1)
[ 'b', 'c' ]

> arr.push('x') // 在末尾添加一个元素
4
> arr
[ 'a', 'b', 'c', 'x' ]

> arr.pop() // 移除最后一个元素
'x'
> arr
[ 'a', 'b', 'c' ]

> arr.shift() // 移除第一个元素
'a'
> arr
[ 'b', 'c' ]

> arr.unshift('x') // 在前面添加一个元素
3
> arr
[ 'x', 'b', 'c' ]

> arr.indexOf('b') // 查找给定项在数组中的索引,若不存在返回-1
1
> arr.indexOf('y')
-1

> arr.join('-') // 将元素拼接为一个字符串
'x-b-c'
> arr.join('')
'xbc'
> arr.join()
'x,b,c'

遍历数组(Iterating over arrays)

有几种方法可以遍历数组元素。其中两个最重要的是 forEach 和 map。

forEach遍历整个数组,并将当前元素和它的索引传递给一个函数:

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

上面代码的输出

1
2
3
0. a
1. b
2. c

注意(*)行的函数参数是可省略的。例如:它可以只有一个参数 elem。

map创建一个新数组,通过给每个存在数组元素应用一个函数:

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

正则表达式(Regular expressions)

JavaScript内建支持正则表达式。他们被双斜线分隔:

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

方法 test():测试是否匹配(Method test(): is there a match?)

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

方法 exec():匹配和捕获组(Method exec(): match and capture groups)

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

返回的数组第一项(索引为0)是完整匹配,捕获的第一个分组在第二项(索引为1),等。有一种方法可以反复调用获取所有匹配。

方法 replace():搜索并替换(Method replace(): search and replace)

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

replace的第一个参数必须是正则表达式,并且开启全局搜索(/g 标记),否则仅第一个匹配项会被替换。有一种方法使用一个函数来计算替换项。

数学(Math)

Math是一个有算数功能的对象。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> Math.abs(-2)
2

> Math.pow(3, 2) // 3^2
9

> Math.max(2, -1, 5)
5

> Math.round(1.9)
2

> Math.cos(Math.PI) // 预定义常量π
-1

标准库的其他功能(Other functionality of the standard library)

JavaScript标准库相对简单,但有很多其他东西你可以使用:

Date:日期构造函数,主要功能有转换和创建日期字符串,访问日期组成部分(年,小时等)。
JSON:一个对象,功能是转换和生成JSON数据。
console.* 方法:浏览器的具体方法,不是语言成分的部分,但他们也可以在Node.js中工作。


参考:https://blog.csdn.net/Jesounao/article/details/50545336