js 转义
ASCII字符可以以\x##
形式的十六进制表示,例如:
'\x41'; // 完全等同于 'A'
还可以用\u####
表示一个Unicode字符:
'\u4e2d\u6587'; // 完全等同于 '中文'
多行字符串
由于多行字符串用\n
写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用反引号 ` ... ` 表示:
`这是一个 多行 字符串`;
反引号``` !左边
模板字符串
如果有很多变量需要连接,用+
号就比较麻烦。ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量
var name = '小明'; var age = 20; var message = `你好, ${name}, 你今年${age}岁了!`; alert(message);
字符串是不可变的
var s = 'Test'; s[0] = 'X'; alert(s); // s仍然为'Test'
C#中
string str = "asdasd";
Console.WriteLine(str[5]);
//str[5] = '9'; //属性或索引器“string.this[int]”赋值 -- 它是只读的
JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内容,而是返回一个新字符串:
str.toUpperCase() 转为大写
str.toLowerCase() 小写
indexOf() 指定字符串出现的位置 下标 没有找到指定的子串,返回-1
substring()
返回指定索引区间的子串:
var s = 'hello, world' s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello' s.substring(7); // 从索引7开始到结束,返回'world'
Array 任意数据类型,并通过索引来访问每个元素
var arr = [1, 2, 3.14, 'Hello', null, true];
var arr = [1, 2, 3]; arr.length; // 3 arr.length = 6; arr; // arr变为[1, 2, 3, undefined, undefined, undefined] arr.length = 2; arr; // arr变为[1, 2]
Array
可以通过索引把对应的元素修改为新的值
var arr = ['A', 'B', 'C']; arr[1] = 99; arr; // arr现在变为['A', 99, 'C']
如果通过索引赋值时,索引超过了范围,同样会引起Array
大小的变化:
var arr = [1, 2, 3]; arr[5] = 'x'; arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的Array
却不会有任何错误。在编写代码时,不建议直接修改Array
的大小,访问索引时要确保索引不会越界。Array
也可以通过indexOf()
来搜索一个指定的元素的位置
var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引为0
arr.indexOf(20); // 元素20的索引为1
arr.indexOf(30); // 元素30没有找到,返回-1
arr.indexOf('30'); // 元素'30'的索引为2
30
和字符串'30'
是不同的元素。
slice() 截取Array
的部分元素,然后返回一个新的Array
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C'] arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']
如果不给slice()
传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个Array
:
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; var aCopy = arr.slice(); aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G'] aCopy === arr; // false
push()
向Array
的末尾添加若干元素,pop()
则把Array
的最后一个元素删除掉:
var arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的长度: 4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr; // [1, 2, 'A']
arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次
arr; // []
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []
如果要往Array
的头部添加若干元素,使用unshift()
方法,shift()
方法则把Array
的第一个元素删掉:
var arr = [1, 2];
arr.unshift('A', 'B'); // 返回Array新的长度: 4
arr; // ['A', 'B', 1, 2] arr.shift(); // 'A' arr; // ['B', 1, 2]
arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
arr; // []
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []
sort()
可以对当前Array
进行排序,它会直接修改当前Array
的元素位置,直接调用时,按照默认顺序排序:
var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
reverse()
把整个Array
的元素给掉个 个,也就是反转:
var arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']
splice()
方法是修改Array
的“万能方法” 它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // ["Yahoo", "AOL", "Excite"]
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
concat()
方法把当前的Array
和另一个Array
连接起来,并返回一个新的Array
:
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
concat()
方法并没有修改当前Array
,而是返回了一个新的Array
。 concat()
方法可以接收任意个元素和Array
,并且自动把Array
拆开,然后全部添加到新的Array
里
var arr = ['A', 'B', 'C'];
arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]
join()
方法是一个非常实用的方法,它把当前Array
的每个元素都用指定的字符串连接起来,然后返回连接后的字符串:
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'
"A-B-C-1-2-3"
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join(); //"A,B,C,1,2,3"
如果Array
的元素不是字符串,将自动转换为字符串后再连接。
如果数组的某个元素又是一个Array
,则可以形成多维数组,例如:
var arr = [[1, 2, 3], [400, 500, 600], '-'];
上述Array
包含3个元素,其中头两个元素本身也是Array
。
var arr = [[1, 2, 3], [400, 500, 600], '-'];
arr[1][1] //500
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。
JavaScript的对象用于描述现实世界中的某个对象。
var xiaoming = { name: '小明', birth: 1990, school: 'No.1 Middle School', height: 1.70, weight: 65, score: null };
xiaoming.name //"小明"
xiaoming["name"]
//"小明"
JavaScript用一个{...}
表示一个对象,键值对以xxx: xxx
形式申明,用,
隔开。注意,最后一个键值对不需要在末尾加,
,如果加了,有的浏览器(如低版本的IE)将报错。
var xiaohong = {
name: '小红',
'middle-school': 'No.1 Middle School'
};
xiaohong
的属性名middle-school
不是一个有效的变量,就需要用''
括起来。访问这个属性也无法使用.
操作符,必须用['xxx']
来访问:
xiaohong.middle-school 报错 school is not defined
xiaohong['middle-school']; // 'No.1 Middle School' 可以取出来
实际上JavaScript对象的所有属性都是字符串,不过属性对应的值可以是任意数据类型。
如果访问一个不存在的属性会返回什么呢?JavaScript规定,访问不存在的属性不报错,而是返回undefined
:
var xiaoming = { name: '小明' };
xiaoming.name // "小明"
xiaoming.age //undefined
JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:
var xiaoming = { name: '小明' }; xiaoming.age; // undefined xiaoming.age = 18; // 新增一个age属性 xiaoming.age; // 18 delete xiaoming.age; // 删除age属性 xiaoming.age; // undefined delete xiaoming['name']; // 删除name属性 xiaoming.name; // undefined delete xiaoming.school; // 删除一个不存在的school属性也不会报错
如果我们要检测xiaoming
是否拥有某一属性,可以用in
操作符:
var xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
'name' in xiaoming; // true 'grade' in xiaoming; // false
如果in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的:
因为toString
定义在object
对象中,而所有对象最终都会在原型链上指向object
,所以xiaoming
也拥有toString
属性。
要判断一个属性是否是xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:
var xiaoming = { name: '小明' }; xiaoming.hasOwnProperty('name'); // true xiaoming.hasOwnProperty('toString'); // false
JavaScript把null
、undefined
、0
、NaN
和空字符串''
视为false
,其他值一概视为true
,因此上述代码条件判断的结果是true
for
循环的一个变体是for ... in
循环
它可以把一个对象的所有属性依次循环出来:
var o = { name: 'Jack', age: 20, city: 'Beijing' }; for (var key in o) { console.log(key); // 'name', 'age', 'city' }
要过滤掉对象继承的属性,用hasOwnProperty()
来实现:
var o = { name: 'Jack', age: 20, city: 'Beijing' }; for (var key in o) { if (o.hasOwnProperty(key)) { console.log(key); // 'name', 'age', 'city' } }
由于Array
也是对象,而它的每个元素的索引被视为对象的属性,因此,for ... in
循环可以直接循环出Array
的索引:
var a = ['A', 'B', 'C']; for (var i in a) { console.log(i); // '0', '1', '2' console.log(a[i]); // 'A', 'B', 'C' }
请注意,for ... in
对Array
的循环得到的是String
而不是Number
。
var str ='asdfghjkl';
for (var key in str) {
console.log(key); //0 1 2 3 4 5 6 7 8
}
JavaScript的默认对象表示方式{}
可以视为其他语言中的Map
或Dictionary
的数据结构,即一组键值对。
但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。
为了解决这个问题,最新的ES6规范引入了新的数据类型Map
。要测试你的浏览器是否支持ES6规范,请执行以下代码,如果浏览器报ReferenceError错误,那么你需要换一个支持ES6的浏览器:
var m = new Map(); var s = new Set();Map
是一组键值对的结构,具有极快的查找速度
举个例子,假设要根据同学的名字查找对应的成绩,如果用Array
实现,需要两个Array
:
var names = ['Michael', 'Bob', 'Tracy']; var scores = [95, 75, 85];
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
names.indexOf("Tracy") // 2
scores[2] // 85
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); m.get('Michael'); // 95
初始化Map
需要一个二维数组,或者直接初始化一个空Map
。Map
具有以下方法:
var m = new Map(); // 空Map m.set('Adam', 67); // 添加新的key-value m.set('Bob', 59); m.has('Adam'); // 是否存在key 'Adam': true m.get('Adam'); // 67 m.delete('Adam'); // 删除key 'Adam' m.get('Adam'); // undefined
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
var m = new Map(); m.set('Adam', 67); m.set('Adam', 88); m.get('Adam'); // 88
Set
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,也没有重复的key。 Map也没有重复key
要创建一个Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
var s = new Set([1, 2, 3, 3, '3']); s; // Set {1, 2, 3, "3"}
注意数字3
和字符串'3'
是不同的元素。
通过add(key)
方法可以添加元素到Set
中,可以重复添加,但不会有效果:
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
通过delete(key)
方法可以删除元素:
var s = new Set([1, 2, 3]); s; // Set {1, 2, 3} s.delete(3); s; // Set {1, 2}
遍历Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。
具有iterable
类型的集合可以通过新的for ... of
循环来遍历。for ... of
循环是ES6引入的新的语法,请测试你的浏览器是否支持:
用for ... of
循环遍历集合,用法如下:
var a = ['A', 'B', 'C']; var s = new Set(['A', 'B', 'C']); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); for (var x of a) { // 遍历Array console.log(x); } for (var x of s) { // 遍历Set console.log(x); } for (var x of m) { // 遍历Map console.log(x[0] + '=' + x[1]); }
你可能会有疑问,for ... of
循环和for ... in
循环有何区别?for ... in
循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array
数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
当我们手动给Array
对象添加了额外的属性后,for ... in
循环将带来意想不到的意外效果:
var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) { console.log(x); // '0', '1', '2', 'name' }
for ... in
循环将把name
包括在内,但Array
的length
属性却不包括在内。for ... of
循环则完全修复了这些问题,它只循环集合本身的元素:length
属性也不包括在内。
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x of a) {
console.log(x); // 'A', 'B', 'C'
}
这就是为什么要引入新的for ... of
循环。
然而,更好的方式是直接使用iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数。以Array
为例:
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
forEach()
方法是ES5.1标准引入的,你需要测试浏览器是否支持。
Set
与Array
类似,但Set
没有索引,因此回调函数的前两个参数都是元素本身:
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});
Map
的回调函数参数依次为value
、key
和map
本身:
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Array
的element
:
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
console.log(element);
});
函数体内 如果没有return
语句,函数执行完毕后也会返回结果,只是结果为undefined
。
function abs(x) { if (x >= 0) { return x; } else { return -x; } }
由于JavaScript的函数也是一个对象,上述定义的abs()
函数实际上是一个函数对象,而函数名abs
可以视为指向该函数的变量。
因此,第二种定义函数的方式如下:
var abs = function (x) { if (x >= 0) { return x; } else { return -x; } };
在这种方式下,function (x) { ... }
是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs
,所以,通过变量abs
就可以调用该函数。
上述两种定义完全等价
JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:
abs(10, 'blablabla'); // 返回10 abs(-9, 'haha', 'hehe', null); // 返回9
传入的参数比定义的少也没有问题:
abs(); // 返回NaN
此时abs(x)
函数的参数x
将收到undefined
,计算结果为NaN
。
要避免收到undefined
,可以对参数进行检查:
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
arguments
JavaScript还有一个免费赠送的关键字arguments
,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments
类似Array
但它不是一个Array
:
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
利用arguments
,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
实际上arguments
最常用于判断传入参数的个数。你可能会看到这样的写法:
// foo(a[, b], c)
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 实际拿到的参数是a和b,c为undefined
c = b; // 把b赋给c
b = null; // b变为默认值
}
// ...
}
要把中间的参数b
变为“可选”参数,就只能通过arguments
判断,然后重新调整参数并赋值。
rest参数
由于JavaScript函数允许接收任意个参数,于是我们就不得不用arguments
来获取所有参数:
function foo(a, b) { var i, rest = []; if (arguments.length > 2) { for (i = 2; i<arguments.length; i++) { rest.push(arguments[i]); } } console.log('a = ' + a); console.log('b = ' + b); console.log(rest); }
为了获取除了已定义参数a
、b
之外的参数,我们不得不用arguments
,并且循环要从索引2
开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的rest
参数,有没有更好的方法?
ES6标准引入了rest参数,上面的函数可以改写为:
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
rest参数只能写在最后,前面用...
标识,从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
,所以,不再需要arguments
我们就获取了全部参数。
如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组(注意不是undefined
)
因为rest参数是ES6新标准,所以你需要测试一下浏览器是否支持。请用rest参数编写一个sum()
函数,接收任意个参数并返回它们的和
function sum(...rest) {
console.info(rest);
}