ES6学习摘要
let,const
for
循环,函数内部的变量i
与循环变量i
不在同一个作用域,有各自单独的作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
不存在变量提升
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp; //有let命令,所以tmp的作用域就只有在块作用里
}
有些“死区”比较隐蔽,不太容易发现。
function bar(x = y, y = 2) {
//x=y,y没有声明
return [x, y];
}
bar(); // 报错
不允许重复声明
影响
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
*块级作用域与函数声明
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。,但实际上浏览器可以运行
ES5 会替升声明的函数到函数头部
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
相当于
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!');
if (false) {
}
}
f();
}());
ES6
相当于
function f() { console.log('I am outside!'); }
(function () {
var f = undefine //***
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f(); //f is not a function
}());
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。- 同时,函数声明还会提升到所在的块级作用域的头部。
注意
应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
let f = function() { console.log('I am inside!');
}
}
f(); //
}());
const
变量指向的那个内存地址所保存的数据不得改动
要是保存的是对象,只能保证对象指针不变,而原对象还是可以操作
如果真的想将对象冻结,应该使用Object.freeze
方法。
const foo = Object.freeze({
});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
上面代码中,常量foo
指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。
除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES6 声明变量的六种
var function let const import class
顶层对象的属性
全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
globalThis 对象
变量的解构赋值
数组的解构赋值
let [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1, [[2], 3]];
let [head, ...tail] = [1, 2, 3, 4];//tail [2, 3, 4]
let [a, [b], d] = [1, [2, 3], 4];//不完全解构
let [x, y, z] = new Set(['a', 'b', 'c']);
如果等号的右边不是数组(或者严格地说,不是可遍历的结构,),那么将会报错
默认值
ES6 内部使用严格相等运算符(===
),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined
,默认值才会生效
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
function f() {
console.log('aaa');
}
let [x = f()] = [1];//具有惰性,若是能取到值就不运行
let [x = 1, y = x] = []; // x=1; y=1
对象的解构赋值
数组的元素是按次序排列的,变量的取值由它的位置决定;
而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let {
foo, bar } = {
foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
// 例一
let {
log, sin, cos } = Math;
// 例二
const {
log } = console;
log('hello') // hello
如果变量名与属性名不一致,必须写成下面这样。
let {
foo: baz } = {
foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = {
first: 'hello', last: 'world' };
let {
first: f, last: l } = obj;
f // 'hello'
l // 'world'
与数组一样,解构也可以用于嵌套结构的对象。
let obj = {
p: [
'Hello',
{
y: 'World' }
]
};
let {
p: [x, {
y }] } = obj;
x // "Hello"
y // "World"
({
foo: obj.prop, bar: arr[0] } = {
foo: 123, bar: true });
obj // {prop:123}
arr // [true]
对象的解构赋值可以取到继承的属性
也有默认值
1)如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
let x;
{
x} = {
x: 1};
// SyntaxError: syntax error
// 正确的写法
let x;
({
x} = {
x: 1});
字符串的解构赋值
数值和布尔值的解构赋值
1
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错
函数参数的解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
圆括号问题
尽量不用
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
[(b)] = [3]; // 正确
({
p: (d) } = {
}); // 正确
[(parseInt.prop)] = [3]; // 正确
用途
7
字符串扩展
字符的 Unicode 表示法
\uxxxx这种表示法只限于码点在\u0000
~\uFFFF
之间的字符
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
// "?"
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;
hell\u{
6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true
字符串的遍历器接口
字符串可以被for...of
循环遍历。
可以识别大于0xFFFF
的码点
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "?"
5个字符不能在字符串里面直接使用
- U+005C:反斜杠(reverse solidus)
- U+000D:回车(carriage return)
- U+2028:行分隔符(line separator)
- U+2029:段分隔符(paragraph separator)
- U+000A:换行符(line feed)
ES2019 允许 JavaScript 字符串直接输入 U+2028(行分隔符)和 U+2029(段分隔符)。
JSON.stringify() 的改造
模板字符串甚至还能嵌套
${ }
如果使用模板字符串表示多行字符串,则所有的空格、缩进和换行都会被保留在输出中。
大括号内可以放入任意的JavaScript表达式,可以进行运算,以及引入对象属性。
模板字符串之中还可以调用函数
实例:模板编译
标签模板
alert`123`
// 等同于
alert(123)
let a = 5;
let b = 10;
tag`Hello ${
a + b } world ${
a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。
字符串新增方法
String.fromCodePoint()
主要原因js以utf-16编码,字符占2个字节,需要4个字节表示的字符,js会认为使两个字符
charAt(),charCodeAt()都不能正确处理4个字节表示的字符
codePointAt()可以返回正确的码点,以十进制编码,但是index要跳着
比如:下列取a码点要从2开始取
let s = '?a';
s.codePointAt(0).toString(16) // "20bb7"
s.codePointAt(2).toString(16) // "61"
改良:for of可以遍历字符串
let s = '?a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
codePointAt(),charAt(),charCodeAt()都运用在实例上,都是通过index来获取内容
String.fromCharCode(),String.fromCodePoint() 运用在string类上,输入编码输出字符
String.fromCharCode()不能识别码点大于0xFFFF
的字符
String.fromCodePoint()
//可以输入多位
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true
String.raw()
String.raw`Hi\n${
2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw`Hi\\n`
// 返回 "Hi\\\\n"
// `foo${1 + 2}bar`
// 等同于
String.raw({
raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
实例方法:codePointAt()
实例方法:normalize()
欧洲语言有语调符号和重音符号。为了表示它们,
一种是直接提供带重音符号的字符,比如Ǒ
(\u01D1)。
另一种是提供合成符号,两个字符合成一个字符,比如O
(\u004F)和ˇ
(\u030C)合成Ǒ
(\u004F\u030C)。
js不能识别
'\u01D1'==='\u004F\u030C' //false
'\u01D1'.length // 1
'\u004F\u030C'.length // 2
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true
[实例方法:includes(), startsWith(), endsWith()](http://es6.ruanyifeng.com/#docs/string-methods#实例方法:includes(), startsWith(), endsWith())
-
includes():返回布尔值,表示是否找到了参数字符串。
-
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
-
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
-
let s = 'Hello world!'; s.startsWith('world', 6) // true 表示从index=6开始检索 s.endsWith('Hello', 5) // 表示前5个字符 s.includes('Hello', 6) // false
实例方法:repeat()
repeat
方法返回一个新字符串,表示将原字符串重复n
次。
参数是负数或者Infinity
参数如果是小数,会被取整。
参数是 0 到-1 之间的小数,则等同于 0
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
实例方法:padStart(),padEnd()
补全字符串长度
padStart()
和padEnd()
一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串
'abc'.padStart(10, '0123456789')
实例方法:trimStart(),trimEnd()
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
实例方法:matchAll()
正则扩展
RegExp 构造函数
返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符
new RegExp(/abc/ig, 'i').flags
ES5
var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;
var regex = new RegExp(/xyz/i);