一、let命令
1.let
命令所在的代码块内有效,var
声明的在全局范围内都有效
2.for
循环的计数器,就很合适使用let
命令
3.let声明的内部变量i
和外部变量i
是分离的
4.var
命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined
let命令不会发生“变量提升”现象,它所声明的变量一定要在声明后使用,否则报错
5.let
不允许在相同作用域内,重复声明同一个变量
6.let会导致暂时性死区,在代码块内,使用let
命令声明变量之前,该变量都是不可用的
let
实际上为 JavaScript 新增了块级作用域
1.外层作用域无法读取内层作用域的变量
2.内层作用域可以定义外层作用域的同名变量
二、const命令
1.const
声明一个只读的常量。一旦声明,常量的值就不能改变
2.const
一旦声明变量,就必须立即初始化,不能留到以后赋值
3.const
的作用域与let
命令相同:只在声明所在的块级作用域内有效
4.const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用
5.const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const
只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心
var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性
二、变量的解构赋值
1.完全解构
let [a, b, c] = [1, 2, 3];//a=1,b=2,c=3
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 // []
如果解构不成功,变量的值就等于undefined
。
let [foo] = [];
let [bar, foo] = [1];
以上两种情况都属于解构不成功,foo
的值都会等于undefined
。
2.不完全解构
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
3.如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错
4.对于 Set 结构,也可以使用数组的解构赋值
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
三、
字符串的扩展
1.字符串的Unicode表示法
JavaScript 允许采用\uxxxx
形式表示一个字符,其中xxxx
表示字符的 Unicode 码点
这种表示法只限于码点在\u0000
~\uFFFF
之间的字符。超出这个范围的字符,必须用两个双字节的形式表示
2.codePointAt
ES6提供了codePointAt
方法,能够正确处理4个字节储存的字符,返回一个字符的码点
var s = '?a';
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271 s.codePointAt(2) // 97
codePointAt
方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString
方法转换一下
var s = '?a';
s.codePointAt(0).toString(16) // "20bb7"
s.codePointAt(2).toString(16) // "61"
你可能注意到了,codePointAt
方法的参数,仍然是不正确的。比如,上面代码中,字符a
在字符串s
的正确位置序号应该是1,但是必须向codePointAt
方法传入2。解决这个问题的一个办法是使用for...of
循环,因为它会正确识别32位的UTF-16字符
var s = '?a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16)); } // 20bb7 // 61
codePointAt
方法是测试一个字符由两个字节还是由四个字节组成的最简单方法
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("?") // true
is32Bit("a") // false
3.String.fromCodePoint()
ES6提供了String.fromCodePoint
方法,可以识别大于0xFFFF
的字符,在作用上,正好与codePointAt
方法相反
String
.fromCodePoint(0x20BB7)
// "?"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true
上面代码中,如果String.fromCodePoint
方法有多个参数,则它们会被合并成一个字符串返回。
注意,fromCodePoint
方法定义在String
对象上,而codePointAt
方法定义在字符串的实例对象上。
4.字符串的便利器接口
ES6为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被for...of
循环遍历。
for (let codePoint of 'foo') {
console.log(codePoint)
} // "f" // "o" // "o"
除了遍历字符串,这个遍历器最大的优点是可以识别大于
0xFFFF
的码点,传统的for
循环无法识别这样的码点
var text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "?"
上面代码中,字符串
text
只有一个字符,但是for
循环会认为它包含两个字符(都不可打印),而for...of
循环会正确识别出这一个字符
5.at()
ES5对字符串对象提供charAt
方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF
的字符
'abc'.charAt(0) // "a"
'?'.charAt(0) // "\uD842"
上面代码中,charAt
方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。
目前,有一个提案,提出字符串实例的at
方法,可以识别Unicode编号大于0xFFFF
的字符,返回正确的字符。
'abc'.at(0) // "a"
'?'.at(0) // "?"
6.includes(), startsWith(), endsWith()
includes():返回布尔值,表示是否找到了参数字符串 startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
var s = 'Hello world!'
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代码表示,使用第二个参数n
时,endsWith
的行为与其他两个方法有所不同。它针对前n
个字符,而其他两个方法针对从第n
个位置直到字符串结束。
7.repeat()
repeat
方法返回一个新字符串,表示将原字符串重复n
次
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
参数如果是小数,会被取整(舍去小数)
'na'.repeat(2.9) // "nana"
如果
repeat
的参数是负数或者Infinity
,会报错
但是,如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于-0
,repeat
视同为0
参数NaN
等同于0
如果repeat
的参数是字符串,则会先转换成数字
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
8.
padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()
用于头部补全,padEnd()
用于尾部补全
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串
如果省略第二个参数,默认使用空格补全长度
9、模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量,模板字符串中嵌入变量,需要将变量名写在${}
之中,大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
//模板字符串还能调用函数
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的
toString
方法
如果模板字符串中的变量没有声明,将报错
上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义
var greeting = `\`Yo\` World!`;
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中,如果你不想要这个换行或空格,可以使用trim
方法消除它
`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim()
10.标签模板
模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
alert`123`
// 等同于
alert(123)
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数
但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数
var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
上面代码中,模板字符串前面有一个标识名tag
,它是一个函数。整个表达式的返回值,就是tag
函数处理模板字符串后的返回值
函数tag
依次会接收到多个参数。
function tag(stringArr, value1, value2){
// ...
}
// 等同于
function tag(stringArr, ...values){
// ...
}
“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容
var message =
SaferHTML`<p>${sender} has sent you a message.</p>`;
function SaferHTML(templateData) {
var s = templateData[0];
for (var i = 1; i < arguments.length; i++) {
var arg = String(arguments[i]);
// Escape special characters in the substitution.
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
// Don't escape special characters in the template.
s += templateData[i];
}
return s;
}