let和const
ES5有全局作用域和函数作用域,ES6引入块级作用域,前提是必须有大括号,块级作用域不能重复声明变量并且变量只在所声明的块级作用域内有效。
const一旦声明必须要初始化(赋值)并且值的内存地址不能改变,let没有这个限制。
变量的解构赋值
数组的解构赋值
模式匹配即等号两边的模式相同
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
如果等号的右边 不是数组,那么就会报错
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口,要么本身就不具备Iterator接口。
比如对于Set结构也可以使用 数组的解构赋值
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
默认值
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
上面最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明。
对象的解构赋值
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc // Object {start: Object}
start // Object {line: 1, column: 5}
上面代码有三次解构赋值,分别是对loc、start、line三个属性的解构赋值。注意,最后一次对line属性的解构赋值之中,只有line是变量,loc和start都是模式,不是变量。
默认值
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var {x: y = 3} = {};
y // 3
var {x: y = 3} = {x: 5};
y // 5
var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null
上面代码中,属性x等于null,因为null与undefined不严格相等,所以是个有效的赋值,导致默认值3不会生效。
默认值生效的条件是,对象的属性值严格等于undefined。
字符串的解构赋值
因为字符串有Iterator接口,所以也可以被解构赋值
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
函数参数的解构赋值
// 嵌套解构赋值
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
字符串的扩展
模版字符串
如果换行需要用''包裹
`In JavaScript '\n' is a line-feed.`
模版字符串中需要使用反引号,则前面要用反斜杠转义
let greeting = `\`Yo\` World!`;
如果使用模版字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
$('#list').html(`
- first
- second
`);
上面代码中,所有模版字符串的空格和换行都是被保留的,如果你不想要这个换行,可以用trim方法消除
$('#list').html(`
- first
- second
`.trim());
模版字符串还可以嵌套
const tmpl = addrs => `
${addrs.map(addr => `
${addr.first}
${addr.last}
`).join('')}
`;
const data = [
{ first: '', last: 'Bond' },
{ first: 'Lars', last: '' },
];
console.log(tmpl(data));
//
//
//
// Bond
//
// Lars
//
//
//
实例方法
includes(), startsWith(), endsWith()
- includes:返回布尔值,表示是否找到了参数字符串
- startsWith:返回布尔值,表示参数字符串是否在原字符串的头部
- endsWith:返回布尔值,表示参数字符串是否在原字符串的尾部
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
如果repeat的参数是字符串,则会先转换成数字。
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
padStart(),padEnd()
ES6引入字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
trimStart(),trimEnd()
它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
trimLeft()是trimStart()的别名,trimRight()是trimEnd()的别名。
replaceAll()
字符串的实例方法replace()只能替换第一个匹配。如果要替换所有的匹配,不得不使用正则表达式的g修饰符。
'aabbcc'.replace(/b/g, '_')
// 'aa__cc'
ES2021 引入了replaceAll()方法,可以一次性替换所有匹配。
'aabbcc'.replaceAll('b', '_')
// 'aa__cc'
rest参数
ES6引入rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
下面是一个rest参数代替arguments变量的例子。
// arguments变量的写法
function sortNumbers() {
return Array.from(arguments).sort()
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort()
arguments对象不是数组而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.from先将其转为数组。rest参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。
注意:rest参数之后不能有其他参数(即只能是最后一个参数),否则会报错。
数组的扩展
扩展运算符
扩展运算符是三个点...。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
function add (x, ...y) {
console.log(y) // [1, 2 , 3, 4]
}
const numbers = [1, 2, 3]
add(0, ...numbers, 4)
扩展运算符后面可以跟数组或字符串等任何定义了遍历器接口的对象,都可以用扩展运算符转为真正的数组。因为扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象都可以使用扩展运算符。
Array.from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象和可遍历(iterable)的对象。
类似数组的对象本质特征只有一点,即必须有length属性。扩展运算符只能转换可遍历对象。
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from([1, 2, 3], x => x * x)
// [1, 4, 9]
entries()、keys()、values
ES6提供这三个方法用于遍历具有Interator特性的对象。它们都返回一个遍历器对象,可以用for...of循环遍历
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
对象的扩展
扩展运算符
对象中的扩展运算符用于取出参数对象的所有可遍历属性,拷贝到当前对象中。
对象的扩展运算符等同于使用Object.assgin()方法
let aClone = {...a}
// 等同于
let aClone = Object.assgin({}, a)
下面是扩展运算符例子
let foo = {...['a', 'b']}
// {0: 'a', 1: 'b'}
{...'ab'} // {0: 'a', 1: 'b'}
rest参数是指函数中剩余参数,在最后的位置,扩展运算符在数组、对象中,如果是等号左边就是跟rest参数一样,在等号右边就是遍历对象操作
运算符的扩展
链判断运算符
如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在,避免报错,例如:
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName)
当然我们也可以用三元运算符来判断,不过这样写都比较麻烦,链判断运算符简化了上面的写法,左侧的对象是否为null或undefined,如果是就返回undefined不会向下执行。
const firstName = message?.body?.user?.firstName
// 也可以判断方法是否存在,如果存在就立即执行
iterator.return ? .()
Null判断运算符
在我们读取属性的时候如果是null或undefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。但这样子会有个问题,我们是需要属性的值为null或undefined的时候取默认值,但如果属性的值为空字符串、false或0,默认值也会生效,就会产生bug。为了避免这种情况,ES引入Null判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
const animationDuration = response.settings?.animationDuration ?? 300;
以上两种方式最大的好处就是规避了如果数据是0、false、空字符串,只针对null或undefined生效
Iterator
Iterator(遍历器)的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列,三是供for...of使用。