目录
(5)、includes(), startsWith(), endsWith()
(3)、Symbol.prototype.description
(6)、Symbol.for(),Symbol.keyFor()
(6)、类的 prototype 属性和__proto__属性
一、JavaScript新增基础
1.let 关键字
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
- 不允许重复声明
- 块儿级作用域
- 不存在变量提升
- 不影响作用域链
注:
- ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域
2.const 关键字
const 关键字用来声明常量,const 声明有以下特点:
- 不允许重复声明
- 块儿级作用域
- 声明必须赋初始值
- 值不允许修改
- 标识符一般为大写
3.变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
(1)、数组的解构赋值
1.基本语法
“模式匹配”:只要等号两边的模式相同,左边的变量就会被赋予对应的值
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
//解构不成功,foo的值都会等于undefined
let [foo] = [];
let [bar, foo] = [1];
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
如果等号的右边不是数组,那么将会报错。
2.默认值
解构赋值允许指定默认值
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
注意,ES6 内部使用严格相等运算符(
===
),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined
,默认值才会生效。
如果默认值是一个表达式,那么这个表达式是惰性求值的
function f() {
console.log('aaa');
}
let [x = f()] = [1];
//只有在用到的时候,才会求值
//如果x能取到值,所以函数f根本不会执行
默认值可以引用解构赋值的其他变量,但该变量必须已经声明
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
(2)、对象的解构赋值
1.基本语法
对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
赋值到某个变量:
let { log, sin, cos } = Math;
如果变量名与属性名不一致,必须写成下面这样:
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'
嵌套结构:
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}
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
obj // {prop:123}
arr // [true]
2.默认值
默认值生效的条件是,对象的属性值严格等于undefined
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
3.注意点
- 不能将大括号放在首行
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
// 正确的写法
let x;
({x} = {x: 1});
- 解构赋值允许等号左边的模式之中,不放置任何变量名
({} = [true, false]);
({} = 'abc');
({} = []);
- 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
(3)、字符串的解构赋值
字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值
let {length : len} = 'hello';
len // 5
(4)、数值和布尔值的解构赋值
等号右边是数值和布尔值,则会先转为对象
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
(5)、函数参数的解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
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]
function move({x, y} = { x: 0, y: 0 }) { //给变量指认默认值
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
(6)、圆括号问题
- ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号
- 建议只要有可能,就不要在模式中放置圆括号。
不得使用圆括号:
- 变量声明语句
// 全部报错
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let { o: ({ p: p }) } = { o: { p: 2 } };
- 函数参数
// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }
- 赋值语句的模式
// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];
可以使用圆括号:
赋值语句的非模式部分,可以使用圆括号
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
(7)、用途
- 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
//简单明了
- 从函数返回多个值
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
- 函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
- 提取 JSON 数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
- 函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
- 遍历 Map 结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
- 输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
4.字符串新增
字符串新增的方法:
- 字符的 Unicode 表示法
- 字符串的遍历器接口
- 直接输入 U+2028 和 U+2029
- JSON.stringify() 的改造
- 模板字符串
-
模板字符串的限制
我们简单讲解一下
(1)、字符串的遍历器接口
for...of
循环遍历字符串的遍历器接口和模板字符串
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
(2)、模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识,特点:
- 字符串中可以出现换行符,空格和缩进
- 可以使用 ${xxx} 形式输出变量
- 如果你不想要换行,可以使用
trim
方法消除它
注意:当遇到字符串与变量拼接的情况使用模板字符串
字符串中可以出现换行符,空格和缩进:
//定义字符串
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
<li>艾伦</li>
</ul>`;
console.log(str);
变量拼接:
//变量拼接
let name = '小可爱';
let result = `欢迎${name}访问我的文章`;
console.log(result);
//欢迎小可爱访问我的文章
注:
- 大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性
- 模板字符串之中还能调用函数
- 如果大括号中的值不是字符串,将按照一般的规则转为字符串。(
toString
方法)- 如果需要引用模板字符串本身,在需要时执行,可以写成函数
trim
方法:
//定义字符串
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
<li>艾伦</li>
</ul>`.trim();
console.log(str);
5.字符串新增方法
(1)、String.fromCodePoint()
用于从 Unicode 码点返回对应字符
String.fromCodePoint(0x20BB7)
// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true
(2)、String.raw()
返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法,
它将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用
String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw`Hi\u000A!`;
// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"
//如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义
String.raw`Hi\\n`
// 返回 "Hi\\\\n"
String.raw()
本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有raw
属性的对象,且raw
属性的值应该是一个数组,对应模板字符串解析后的值。
如果String.raw()
方法的第一个参数是一个对象,它的raw
属性等同于原始的模板字符串解析后得到的数组
// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
(3)、codePointAt()
JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2
个字节。对于那些需要4
个字节储存的字符(Unicode 码点大于0xFFFF
的字符),JavaScript 会认为它们是两个字符
let s = '𠮷a';
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97
//字符在字符串中的位置(从 0 开始)。上面代码中,JavaScript 将“𠮷a”视为三个字符
//所以建议使用遍历的方法避免出错
注:
codePointAt()
方法是测试一个字符由两个字节还是由四个字节组成的最简单方法codePointAt()
方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString()
方法转换一下
(4)、normalize()
许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode 提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ
(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O
(\u004F)和ˇ
(\u030C)合成Ǒ
(\u004F\u030C)
'\u01D1'==='\u004F\u030C' //false
'\u01D1'.length // 1
'\u004F\u030C'.length // 2
用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true
normalize
方法可以接受一个参数来指定normalize
的方式
参数的四个可选值如下:
NFC
,默认参数,表示“标准等价合成”,返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。NFD
,表示“标准等价分解”,即在标准等价的前提下,返回合成字符分解的多个简单字符。NFKC
,表示“兼容等价合成”,返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize
方法不能识别中文。)NFKD
,表示“兼容等价分解”,即在兼容等价的前提下,返回合成字符分解的多个简单字符。
'\u004F\u030C'.normalize('NFC').length // 1
'\u004F\u030C'.normalize('NFD').length // 2
//NFC参数返回字符的合成形式,NFD参数返回字符的分解形式
(5)、includes(), startsWith(), endsWith()
传统上,JavaScript 只有indexOf
方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
- 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
个位置直到字符串结束
(6)、repeat()
返回一个新字符串,表示将原字符串重复n
次
- 参数如果是小数,会被取整
- 参数是负数或者
Infinity
,会报错 - 如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于
-0
,repeat
视同为 0 - 参数
NaN
等同于 0 - 如果
repeat
的参数是字符串,则会先转换成数字
//重复n次
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
//小数,会被取整
'na'.repeat(2.9) // "nana"
//是负数或者Infinity,会报错
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
//如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。
'na'.repeat(-0.9) // ""
//NaN等同于 0
'na'.repeat(NaN) // ""
//字符串,则会先转换成数字
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
(7)、padStart(),padEnd()
如果某个字符串不够指定长度,会在头部或尾部补全
padStart()
用于头部补全,padEnd()
用于尾部补全
padStart()
和padEnd()
一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串:
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
- 如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串
- 如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串,从后方截补的字符串
- 如果省略第二个参数,默认使用空格补全长度
(8)、trimStart(),trimEnd()
ES2019 对字符串实例新增了trimStart()
和trimEnd()
这两个方法。它们的行为与trim()
一致,trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
- 浏览器还部署了额外的两个方法,
trimLeft()
是trimStart()
的别名,trimRight()
是trimEnd()
的别名- 这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效
(9)、matchAll()
matchAll()
方法返回一个正则表达式在当前字符串的所有匹配
(10)、replaceAll()
replaceAll()
方法,可以一次性替换所有匹配
用法:
String.prototype.replaceAll(searchValue, replacement)
//searchValue是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有g修饰符)
注:
如果searchValue是一个不带有g修饰符的正则表达式,replaceAll()会报错。这一点跟replace()不同
replaceAll()
的第二个参数replacement
是一个字符串,表示替换的文本,其中可以使用一些特殊字符串:
$&
:匹配的字符串。$`
:匹配结果前面的文本。$'
:匹配结果后面的文本。$n
:匹配成功的第n
组内容,n
是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。$$
:指代美元符号$
。
6.正则的扩展
正则新增的有:
- RegExp 构造函数
如果
RegExp
构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
- u 修饰符
ES6 对正则表达式添加了
u
修饰符,含义为“Unicode 模式”
- RegExp.prototype.unicode 属性
正则实例对象新增
unicode
属性,表示是否设置了u
修饰符
- y 修饰符
除了
u
修饰符,ES6 还为正则表达式添加了y
修饰符,叫做“粘连”(sticky)修饰符。也是全局匹配
- RegExp.prototype.sticky 属性
是否设置了
y
修饰符。
- RegExp.prototype.flags 属性
返回正则表达式的修饰符
- s 修饰符:dotAll 模式
- 后行断言
- Unicode 属性类
- 具名组匹配
- 字符串的正则方法
-
正则匹配索引
7.数值的扩展
数值扩增的有:
- 二进制和八进制表示法
ES6 提供了二进制和八进制数值的新的写法,分别用前缀
0b
(或0B
)和0o
(或0O
)表示
- 数值分隔符
- Number.isFinite(), Number.isNaN()
Number.isFinite()
用来检查一个数值是否为有限的(finite),即不是Infinity
Number.isNaN()
用来检查一个值是否为NaN
- Number.parseInt(), Number.parseFloat()
ES6 将全局方法
parseInt()
和parseFloat()
,移植到Number
对象上面,行为完全保持不变
- Number.isInteger()
Number.isInteger()
用来判断一个数值是否为整数。
- Number.EPSILON
ES6 在
Number
对象上面,新增一个极小的常量Number.EPSILON
。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。
- 安全整数和 Number.isSafeInteger()
ES6 引入了
Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示这个范围的上下限。
Number.isSafeInteger()
则是用来判断一个整数是否落在这个范围之内。
- Math 对象的扩展
Math.trunc
方法用于去除一个数的小数部分,返回整数部分Math.sign
方法用来判断一个数到底是正数(+1)、负数(-1)、零(0)、负零(-0)、其他值,(NaN)
。对于非数值,会先将其转换为数值。Math.cbrt()
方法用于计算一个数的立方根Math.clz32()
方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0Math.imul
方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。Math.fround
方法返回一个数的32位单精度浮点数形式Math.hypot
方法返回所有参数的平方和的平方根Math.expm1(x)
返回 ex - 1,即Math.exp(x) - 1
Math.log1p(x)
方法返回1 + x
的自然对数,即Math.log(1 + x)
。如果x
小于-1,返回NaN
。Math.log10(x)
返回以 10 为底的x
的对数。如果x
小于 0,则返回 NaNMath.log2(x)
返回以 2 为底的x
的对数。如果x
小于 0,则返回 NaN。Math.sinh(x)
返回x
的双曲正弦(hyperbolic sine)Math.cosh(x)
返回x
的双曲余弦(hyperbolic cosine)Math.tanh(x)
返回x
的双曲正切(hyperbolic tangent)Math.asinh(x)
返回x
的反双曲正弦(inverse hyperbolic sine)Math.acosh(x)
返回x
的反双曲余弦(inverse hyperbolic cosine)Math.atanh(x)
返回x
的反双曲正切(inverse hyperbolic tangent)
- BigInt 数据类型
8.函数的扩展(rest参数,箭头函数)
- 函数参数的默认值
- rest 参数
ES6 引入 rest 参数(形式为
...变量名
),用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
// arguments变量的写法
function sortNumbers() {
return Array.from(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
//rest 参数必须是最后一个形参
// 报错
function f(a, ...b, c) {
// ...
}
- 严格模式
规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错
- name 属性
函数的
name
属性,返回该函数的函数名
- 箭头函数
ES6 允许使用“箭头”(
=>
)定义函数,通用写法为:var f = v => v; // 等同于 var f = function (v) { return v; }; let fn = (arg1, arg2, arg3) => { return arg1 + arg2 + arg3; }
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用
return
语句返回var sum = (num1, num2) => { return num1 + num2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
// 报错 let getTempItem = id => { id: id, name: "Temp" }; // 不报错 let getTempItem = id => ({ id: id, name: "Temp" });
箭头函数有几个使用注意点:
- 箭头函数没有自己的
this
对象,this等同于上一层非箭头函数的this值或全局对象(window或undefined)(严格模式this是undefined)- 不可以当作构造函数,也就是说,不可以对箭头函数使用
new
命令,否则会抛出一个错误。- 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。- 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
- 函数参数的尾逗号
-
Function.prototype.toString()
修改后的
toString()
方法,明确要求返回一模一样的原始代码
- catch 命令的参数省略
9.数组的扩展
数值新增的有:
- 扩展运算符
扩展运算符(spread)是三个点(
...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
- Array.from()
Array.from
方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
- Array.of()
Array.of()
方法用于将一组值,转换为数组。
- 数组实例的 copyWithin()
数组实例的
copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。Array.prototype.copyWithin(target, start = 0, end = this.length)
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
- 数组实例的 find() 和 findIndex()
- 数组实例的
find
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true
的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
。- 数组实例的
findIndex
方法的用法与find
方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
- 数组实例的 fill()
fill
方法使用给定值,填充一个数组。fill( 给定值, 起始位置(可选),结束位置(可选,结束为位置之前))
- 数组实例的 entries(),keys() 和 values()
ES6 提供三个新的方法——
entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of
循环进行遍历,唯一的区别是keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历。
- 数组实例的 includes()
Array.prototype.includes
方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes
方法类似
- 数组实例的 flat(),flatMap()
- 数组的成员有时还是数组,
Array.prototype.flat()
用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响,默认只会“拉平”一层[1, 2, [3, 4]].flat() // [1, 2, 3, 4] [1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
Array.prototype.map()
),然后对返回值组成的数组执行flat()
方法。该方法返回一个新数组,不改变原数组。只能展开一层数组// 相当于 [[2, 4], [3, 6], [4, 8]].flat() [2, 3, 4].flatMap((x) => [x, x * 2]) // [2, 4, 3, 6, 4, 8]
- 数组的空位
数组的空位指,数组的某一个位置没有任何值。比如,
Array
构造函数返回的数组都是空位。ES6 则是明确将空位转为
undefined
- Array.prototype.sort() 的排序稳定性
排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变
const arr = [ 'peach', 'straw', 'apple', 'spork' ]; const stableSorting = (s1, s2) => { if (s1[0] < s2[0]) return -1; return 1; }; arr.sort(stableSorting) //straw在spork的前面 ["apple", "peach", "straw", "spork"] //稳定 const unstableSorting = (s1, s2) => { if (s1[0] <= s2[0]) return -1; return 1; }; arr.sort(unstableSorting) //不稳定
注:
- 插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。
10.对象的扩展
对象新增的有:
- 属性的简洁表示法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
- 属性名表达式
ES6 允许字面量定义对象时,用(表达式)作为对象的属性名,即把表达式放在方括号内。
let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };
- 方法的 name 属性
函数的
name
属性,返回函数名。对象方法也是函数,因此也有name
属性。
name
属性返回函数名
- 属性的可枚举性和遍历
可枚举性:
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。let obj = { foo: 123 }; Object.getOwnPropertyDescriptor(obj, 'foo') // { // value: 123, // writable: true, // enumerable: true, // configurable: true // }
描述对象的
enumerable
属性,称为“可枚举性”,如果该属性为false
,就表示某些操作会忽略当前属性。有四个操作会忽略
enumerable
为false
的属性。
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。属性的遍历:
ES6 一共有 5 种方法可以遍历对象的属性。
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。Reflect.ownKeys
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
- super 关键字
this
关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super
,指向当前对象的原型对象
- 对象的扩展运算符
- AggregateError 错误对象
AggregateError(errors[, message])
- errors:数组,它的每个成员都是一个错误对象。该参数是必须的。
- message:字符串,表示 AggregateError 抛出时的提示信息。该参数是可选的。
11.对象的新增方法
对象新增的有:
- Object.is()
Object.is
就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致不同之处只有两个:
+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
- Object.assign()
Object.assign()
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
Object.assign()
方法的第一个参数是目标对象,后面的参数都是源对象。注意:
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
- Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()
方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。
- __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
__proto__
属性(前后各两个下划线),用来读取或设置当前对象的原型对象Object.setPrototypeOf
方法的作用与__proto__
相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。- Object.getPrototypeOf()与
Object.setPrototypeOf
方法配套,用于读取一个对象的原型对象Object.create()
(生成操作)
- Object.keys(),Object.values(),Object.entries()
Object.keys
方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
-
Object.fromEntries()
bject.fromEntries()
方法是Object.entries()
的逆操作,用于将一个键值对数组转为对象。
12.运算符的扩展
- 指数运算符
指数运算符(
**
)这个运算符是右结合
// 相当于 2 ** (3 ** 2) 2 ** 3 ** 2 // 512
- 链判断运算符
链判断运算符
?.
有三种写法。
obj?.prop
// 对象属性是否存在obj?.[expr]
// 对象属性是否存在func?.(...args)
// 函数或对象方法是否存在
- Null 判断运算符
Null 判断运算符
??
。它的行为类似||
,但是只有运算符左侧的值为null
或undefined
时,才会返回右侧的值。
- 逻辑赋值运算符
引入了三个新的逻辑赋值运算符(logical assignment operators),将逻辑运算符与赋值运算符进行结合
// 或赋值运算符 x ||= y // 等同于 x || (x = y) // 与赋值运算符 x &&= y // 等同于 x && (x = y) // Null 赋值运算符 x ??= y // 等同于 x ?? (x = y)
13.Symbol
(1)、概述
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值,它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型,Symbol 特点如下:
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其它数据进行运算
- Symbol 定义的对象属性不能使用 for…in 循环遍 历 ,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
Symbol 值通过Symbol
函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
let s = Symbol();
typeof s
// "symbol"
注意:
Symbol
函数前不能使用new
命令Symbol
函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol
函数的返回值是不相等的// 没有参数的情况 let s1 = Symbol(); let s2 = Symbol(); s1 === s2 // false // 有参数的情况 let s1 = Symbol('foo'); let s2 = Symbol('foo'); s1 === s2 // false
- Symbol 值不能与其他类型的值进行运算
- Symbol 值可以显式转为字符串、布尔值,但是不能转为数值
let sym = Symbol('My symbol'); String(sym) // 'Symbol(My symbol)' sym.toString() // 'Symbol(My symbol)' let sym = Symbol(); Boolean(sym) // true !sym // false if (sym) { // ... } Number(sym) // TypeError sym + 2 // TypeError
(2)、Symbol的使用
//创建 Symbol
let s1 = Symbol();
console.log(s1);
console.log(typeof s1);
//添加标识的 Symbol
let s2 = Symbol("张三");
let s2_2 = Symbol("张三");
console.log(s2);
console.log(s2_2);
console.log(s2 === s2_2);
//使用 Symbol for 定义
let s3 = Symbol.for("张三");
let s3_2 = Symbol.for("张三");
console.log(s3);
console.log(s3_2);
console.log(s3 === s3_2);
//在方法中使用 Symbol
let game = {
name: "狼人杀",
[Symbol('say')]: function () {
console.log("我可以发言")
},
[Symbol('zibao')]: function () {
console.log('我可以自爆');
}
};
console.log(game);
注意:遇到唯一性的场景时要想到 Symbol
(3)、Symbol.prototype.description
创建 Symbol 的时候,可以添加一个描述。
const sym = Symbol('foo');
sym.description // "foo"
(4)、作为属性名的 Symbol
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
注意:
- Symbol 值作为对象属性名时,不能用点运算符
- 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中
(5)、属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()
方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]
扩展:
Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。:let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]
(6)、Symbol.for(),Symbol.keyFor()
Symbol.for()方法:
有时,我们希望重新使用同一个 Symbol 值,Symbol.for()
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
注:
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
Symbol.keyFor()
方法:
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key
。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
(7)、Symbol内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
- Symbol.hasInstance
当其他对象使用
instanceof
运算符,判断是否为该对象的实例时,会调用这个方法class MyClass { [Symbol.hasInstance](foo) { return foo instanceof Array; } } [1, 2, 3] instanceof new MyClass() // true
- Symbol.isConcatSpreadable
对象的
Symbol.isConcatSpreadable
属性等于一个布尔值,表示该对象用于Array.prototype.concat()
时,是否可以展开let arr1 = ['c', 'd']; ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e'] arr1[Symbol.isConcatSpreadable] // undefined let arr2 = ['c', 'd']; arr2[Symbol.isConcatSpreadable] = false; ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
- Symbol.species
对象的
Symbol.species
属性,指向一个构造函数。创建衍生对象时,会使用该属性。class MyArray extends Array { } const a = new MyArray(1, 2, 3); const b = a.map(x => x); const c = a.filter(x => x > 1); b instanceof MyArray // true c instanceof MyArray // true /* 子类MyArray继承了父类Array,a是MyArray的实例,b和c是a的衍生对象。 你可能会认为,b和c都是调用数组方法生成的,所以应该是数组(Array的实例), 但实际上它们也是MyArray的实例。 */ class MyArray extends Array { static get [Symbol.species]() { return Array; } } const a = new MyArray(); const b = a.map(x => x); b instanceof MyArray // false b instanceof Array // true //a.map(x => x)生成的衍生对象,就不是MyArray的实例,而直接就是Array的实例。
ymbol.species
的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。
- Symbol.match
ymbol.species
的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。
- Symbol.replace
对象的
Symbol.replace
属性,指向一个方法,当该对象被String.prototype.replace
方法调用时,会返回该方法的返回值。
- Symbol.search
对象的
Symbol.search
属性,指向一个方法,当该对象被String.prototype.search
方法调用时,会返回该方法的返回值。
- Symbol.split
对象的
Symbol.split
属性,指向一个方法,当该对象被String.prototype.split
方法调用时,会返回该方法的返回值。
- Symbol.iterator
对象的
Symbol.iterator
属性,指向该对象的默认遍历器方法
- Symbol.toPrimitive
对象的
Symbol.toPrimitive
属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toPrimitive
被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
- Number:该场合需要转成数值
- String:该场合需要转成字符串
- Default:该场合可以转成数值,也可以转成字符串
- Symbol. toStringTag
对象的
Symbol.toStringTag
属性,指向一个方法。在该对象上面调用Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]
或[object Array]
中object
后面的那个字符串。
- Symbol. unscopables
对象的
Symbol.unscopables
属性,指向一个对象。该对象指定了使用with
关键字时,哪些属性会被with
环境排除。
二、JavaScript新增结构,方法
1.Set
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历
(1)、Set的基础使用
集合的属性和方法:
- size:返回集合的元素个数
- add():增加一个新元素,返回当前集合
- delete():删除元素,返回 boolean 值
- has():检测集合中是否包含某个元素,返回 boolean 值
- clear():清空集合,返回 undefined
//创建一个空集合
let s = new Set();
//创建一个非空集合
let s1 = new Set([1, 2, 3, 1, 2, 3]);
//集合属性与方法
//返回集合的元素个数
console.log(s1.size);
//添加新元素
console.log(s1.add(4));
//删除元素
console.log(s1.delete(1));
//检测是否存在某个值
console.log(s1.has(2));
//清空集合
console.log(s1.clear());
(2)、遍历操作
Set 结构的实例有四个遍历方法,可以用于遍历成员。
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员
注:
keys
方法、values
方法、entries
方法返回的都是遍历器对象(详见《Iterator 对象》一章)。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys
方法和values
方法的行为完全一致。entries
方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等
(3)、WeakSet
eakSet 结构与 Set 类似,也是不重复的值的集合。
WeakSet 与 Set 有两个区别:
- WeakSet 的成员只能是对象,而不能是其他类型的值
- WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
WeakSet 结构有以下三个方法:
- WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
- WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
- WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
2. Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。
(1)、Map的基础使用
Map 的属性和方法:
- size:返回 Map 的元素个数
- set():增加一个新元素,返回当前 Map
- get():返回键名对象的键值
- has():检测 Map 中是否包含某个元素,返回 boolean 值
- clear():清空集合,返回 undefined
//创建一个空 map
let m = new Map();
//创建一个非空 map
let m2 = new Map([
["name", "张三"],
["gender", "女"]
]);
//属性和方法
//获取映射元素的个数
console.log(m2.size);
//添加映射值
console.log(m2.set("age", 6));
//获取映射值
console.log(m2.get("age"));
//检测是否有该映射
console.log(m2.has("age"));
//清除
console.log(m2.clear());
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0
和-0
就是一个键,布尔值true
和字符串true
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然NaN
不严格相等于自身,但 Map 将其视为同一个键
let map = new Map();
map.set(-0, 123);
map.get(+0) // 123
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.set(NaN, 123);
map.get(NaN) // 123
(2)、遍历方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
Map.prototype.keys()
:返回键名的遍历器。Map.prototype.values()
:返回键值的遍历器。Map.prototype.entries()
:返回所有成员的遍历器。Map.prototype.forEach()
:遍历 Map 的所有成员。
(3)、WeakMap
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合
WeakMap
与Map
的区别有两点:
WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名WeakMap
的键名所指向的对象,不计入垃圾回收机制。
3. Class
(1)、Class的概述
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是 一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,它的一些如下:
- class:声明类
- constructor:定义构造函数初始化
- extends:继承父类
- super:调用父级构造方法
- static:定义静态方法和属性
//父类
class Phone {
//构造方法
constructor(brand, color, price) {
this.brand = brand;
this.color = color;
this.price = price;
}
//对象方法
call() {
console.log("我可以打电话!!!")
}
}
//子类
class SmartPhone extends Phone {
constructor(brand, color, price, screen, pixel) {
super(brand, color, price);
this.screen = screen;
this.pixel = pixel;
}
//子类方法
photo() {
console.log("我可以拍照!!");
}
playGame() {
console.log("我可以玩游戏!!");
}
//方法重写
call() {
console.log("我可以进行视频通话!!");
}
//静态方法
static run() {
console.log("我可以运行程序")
}
static connect() {
console.log("我可以建立连接")
}
}
//实例化对象
const Nokia = new Phone("诺基亚", "灰色", 230);
const iPhone6s = new SmartPhone("苹果", "白色", 6088, "4.7inch", "500w");
//调用子类方法
iPhone6s.playGame();
//调用重写方法
iPhone6s.call();
//调用静态方法
SmartPhone.run();
/*
我可以玩游戏
我可以进行视频通话
我可以运行程序
*/
(2)、constructor 方法
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加
class Point {
}
// 等同于
class Point {
constructor() {}
}
(3)、静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
静态方法也是可以从super
对象上调用的。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
静态属性:
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
// 老写法
class Foo {
// ...
}
Foo.prop = 1;
//上面的写法为Foo类定义了一个静态属性prop
// 新写法
class Foo {
static prop = 1;
}
(4)、Class 的继承
Class 可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多
class Point {
}
class ColorPoint extends Point {
}
(5)、super 关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同
- 第一种情况,
super
作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。
class A {}
class B extends A {
constructor() {
super();
}
}
- 第二种情况,
super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
(6)、类的 prototype 属性和__proto__属性
- 子类的
__proto__
属性,表示构造函数的继承,总是指向父类 - 子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。 - 子类实例的
__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性。也就是说,子类的原型的原型,是父类的原型。
5. Reflect 与 Proxy
(1)、概述
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
(2)、Proxy
一个 Proxy 对象由两个部分组成: target 、 handler 。
var proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler
参数的写法。其中,new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
拦截读取属性行为:
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
如果
handler
没有设置任何拦截,那就等同于直接通向原对象。
Proxy 支持的拦截操作一览一共 13 种:
- get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 - set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 - has(target, propKey):拦截
propKey in proxy
的操作,返回一个布尔值。 - deleteProperty(target, propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值。 - ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 - preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 - getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 - isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 - setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
(3)、Proxy 实例的方法
get()方法:
get
方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。get(target, propKey, receiver)
拦截读取操作:
var person = { name: "张三" }; var proxy = new Proxy(person, { get: function(target, propKey) { if (propKey in target) { return target[propKey]; } else { throw new ReferenceError("Prop name \"" + propKey + "\" does not exist."); } } }); proxy.name // "张三" proxy.age // 抛出一个错误 /* 如果访问目标对象不存在的属性,会抛出一个错误。 如果没有这个拦截函数,访问不存在的属性,只会返回undefined。 */
get
方法可以继承。
set()方法:
set
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。set(target, propKey, value, receiver)
第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。
const handler = { set: function(obj, prop, value, receiver) { obj[prop] = receiver; } }; const proxy = new Proxy({}, handler); proxy.name= 'Tom'; proxy.name=== proxy // true const exam = {} Object.setPrototypeOf(exam, proxy) exam.name = "Tom" exam.name === exam // true
apply() 方法:
apply
方法拦截函数的调用、call
和apply
操作。
apply
方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this
)和目标对象的参数数组。var handler = { apply (target, ctx, args) { return Reflect.apply(...arguments); } };
has()方法:
has()
方法用来拦截HasProperty
操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in
运算符。
has()
方法可以接受两个参数,分别是目标对象、需查询的属性名。has(target, propKey)
construct()方法:
construct()
方法用于拦截new
命令,下面是拦截对象的写法。const handler = { construct (target, args, newTarget) { return new target(...args); } };
construct()
方法可以接受三个参数:
target
:目标对象。
args
:构造函数的参数数组。
newTarget
:创造实例对象时,new
命令作用的构造函数
deleteProperty()方法:
deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除。deleteProperty(target, propKey)
defineProperty()方法:
defineProperty()
方法拦截了Object.defineProperty()
操作。defineProperty(target, propKey, propDesc)
getOwnPropertyDescriptor() 方法:
getOwnPropertyDescriptor()
方法拦截Object.getOwnPropertyDescriptor()
,返回一个属性描述对象或者undefined
。
getPrototypeOf()方法:
getPrototypeOf()
方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
isExtensible()方法:
isExtensible()
方法拦截Object.isExtensible()
操作。
ownKeys()方法:
ownKeys()
方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for...in
循环
preventExtensions()方法:
preventExtensions()
方法拦截Object.preventExtensions()
。该方法必须返回一个布尔值,否则会被自动转为布尔值。
setPrototypeOf()方法:
setPrototypeOf()
方法主要用来拦截Object.setPrototypeOf()
方法。
Proxy.revocable()方法:
Proxy.revocable()
方法返回一个可取消的 Proxy 实例。
(4)、Reflect
Reflect
对象与Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API。
Reflect
对象的设计目的有这样几个:
- 将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。 - 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。 - 让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。 Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
(5)、Reflect静态方法
Reflect
对象一共有 13 个静态方法:
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
Reflect.get(target, name, receiver)方法:
Reflect.get
方法查找并返回target
对象的name
属性,如果没有该属性,则返回undefined
。
Reflect.set(target, name, value, receiver)方法:
Reflect.set
方法设置target
对象的name
属性等于value
。
Reflect.has(obj, name)方法:
Reflect.has
方法对应name in obj
里面的in
运算符。
Reflect.deleteProperty(obj, name)方法:
Reflect.deleteProperty
方法等同于delete obj[name]
,用于删除对象的属性。
Reflect.construct(target, args)方法:
Reflect.construct
方法等同于new target(...args)
,这提供了一种不使用new
,来调用构造函数的方法。
Reflect.getPrototypeOf(obj)方法:
Reflect.getPrototypeOf
方法用于读取对象的__proto__
属性,对应Object.getPrototypeOf(obj)
。
Reflect.setPrototypeOf(obj, newProto)方法:
Reflect.setPrototypeOf
方法用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)
方法。它返回一个布尔值,表示是否设置成功。
Reflect.apply(func, thisArg, args)方法:
Reflect.apply
方法等同于Function.prototype.apply.call(func, thisArg, args)
,用于绑定this
对象后执行给定函数。
Reflect.defineProperty(target, propertyKey, attributes)方法:
Reflect.defineProperty
方法基本等同于Object.defineProperty
,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty
代替它。
Reflect.getOwnPropertyDescriptor(target, propertyKey)方法:
Reflect.getOwnPropertyDescriptor
基本等同于Object.getOwnPropertyDescriptor
,用于得到指定属性的描述对象,将来会替代掉后者。
Reflect.isExtensible (target)方法:
Reflect.isExtensible
方法对应Object.isExtensible
,返回一个布尔值,表示当前对象是否可扩展。
Reflect.preventExtensions(target)方法:
Reflect.preventExtensions
对应Object.preventExtensions
方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。
Reflect.ownKeys (target)方法:
Reflect.ownKeys
方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames
与Object.getOwnPropertySymbols
之和。
6.Promise
是异步编程的一种解决方案。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
(1)、Promise的特点
Promise
对象有以下两个特点:
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
(2)、基本用法
Promise
对象是一个构造函数,用来生成Promise
实例
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise
对象传出的值作为参数。
(3)、Promise的其他方法
- Promise.catch()
用于指定发生错误时的回调函数
- Promise.finally()
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
- Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例const p = Promise.all([p1, p2, p3]); //p1、p2、p3都是 Promise 实例
p
的状态由p1
、p2
、p3
决定,分成两种情况:
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。- 只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
- Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。const p = Promise.race([p1, p2, p3]);
只要
p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
-
Promise.allSettled()
用来确定一组异步操作是否都结束了(不管成功或失败)
- Promise.any()
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回
-
Promise.resolve()
有时需要将现有对象转为 Promise 对象,
Promise.resolve()
方法就起到这个作用
- Promise.reject()
会返回一个新的 Promise 实例,该实例的状态为
rejected
。
7.Generator 函数(生成器)
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
(1)、Generator 函数组成
Generator 有两个区分于普通函数的部分:
-
一是在 function 后面,函数名之前有个 * ;
-
函数内部有 yield 表达式。
其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
(2)、yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
遍历器对象的next
方法的运行逻辑如下:
- 遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。 - 下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式。 - 如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。 - 如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
。
注意:
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行
(3)、next 方法
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function* sendParameter(){
console.log("start");
var x = yield '2';
console.log("one:" + x);
var y = yield '3';
console.log("two:" + y);
console.log("total:" + (x + y));
}
//next不传参
var sendp1 = sendParameter();
sendp1.next();
// start
// {value: "2", done: false}
sendp1.next();
// one:undefined
// {value: "3", done: false}
sendp1.next();
// two:undefined
// total:NaN
// {value: undefined, done: true}
//next传参
var sendp2 = sendParameter();
sendp2.next(10);
// start
// {value: "2", done: false}
sendp2.next(20);
// one:20
// {value: "3", done: false}
sendp2.next(30);
// two:30
// total:50
// {value: undefined, done: true}
(4)、throw
方法
可以在函数体外抛出错误,然后在 Generator 函数体内捕获
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
遍历器对象
i
连续抛出两个错误。第一个错误被 Generator 函数体内的catch
语句捕获。i
第二次抛出错误,由于 Generator 函数内部的catch
语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch
语句捕获。
(5)、Generator.return()
可以返回给定的值,并且终结遍历 Generator 函数
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
/*
返回值的value属性就是return()方法的参数foo。
并且,Generator 函数的遍历就终止了,返回值的done属性为true,
以后再调用next()方法,done属性总是返回true
*/
(6)、yield* 表达式
如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// x
// a
// b
// y
/*
foo和bar都是 Generator 函数,在bar里面调用foo,就需要手动遍历foo。
如果有多个 Generator 函数嵌套,写起来就非常麻烦
*/
yield*
表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
8.Iterator(迭代器)
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
Iterator 的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费。
Iterator 的遍历过程是这样的:
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的
next
方法,可以将指针指向数据结构的第一个成员。 - 第二次调用指针对象的
next
方法,指针就指向数据结构的第二个成员。 - 不断调用指针对象的
next
方法,直到它指向数据结构的结束位置。
工作原理:
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
- 每调用 next 方法返回一个包含 value 和 done 属性的对象
9.async 函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
(1)、async 函数基础
语法:
async function name([param[, param[, ... param]]]) { statements }
- name: 函数名称。
- param: 要传递给函数的参数的名称。
- statements: 函数体语句。
返回值:
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
async function helloAsync(){
return "helloAsync";
}
console.log(helloAsync()) // Promise {<resolved>: "helloAsync"}
helloAsync().then(v=>{
console.log(v); // helloAsync
})
async 函数有多种使用形式:
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
(2)、await
await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。
语法:
[return_value] = await expression;
- expression: 一个 Promise 对象或者任何要等待的值。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
await针对所跟不同表达式的处理方式:
- Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
- 非 Promise 对象:直接返回对应的值。
10.Module(模块化)
(1)、概述
JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require
、Python 的import
,甚至就连 CSS 都有@import
,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
ES6 模块不是对象,而是通过export
命令显式指定输出的代码,再通过import
命令输入
由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各种好处,ES6 模块还有以下好处。
- 不再需要
UMD
模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。 - 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者
navigator
对象的属性。 - 不再需要对象作为命名空间(比如
Math
对象),未来这些功能可以通过模块提供。
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
(2)、严格模式
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
。
严格模式主要有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化- 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected
、static
和interface
)
上面这些限制,模块都必须遵守。
注意this
的限制。ES6 模块之中,顶层的this
指向undefined
,即不应该在顶层代码使用this
。
(3)、export 命令
export
命令用于规定模块的对外接口
export 命令可以出现在模块的任何位置,但必需处于模块顶层
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export
关键字输出该变量。下面是一个 JS 文件,里面使用export
命令输出变量。
需要特别注意的是,export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
//写法一
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//写法二
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
as 的用法:
通常情况下,export
输出的变量就是本来的名字,但是可以使用as
关键字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
(4)、import 命令
import
命令用于输入其他模块提供的功能
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
import
后面的from
指定模块文件的位置,可以是相对路径,也可以是绝对路径。如果不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置
import { myMethod } from 'util';
//til是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块
import 命令的特点:
- 只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
- 单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import
- 静态执行特性:import 是静态执行,所以不能使用表达式和变量
(5)、export default
命令
使用import
命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default
命令,为模块指定默认输出。
import
命令可以为该匿名函数指定任意名字:
// import-default.js
import customName from './export-default';
customName(); // 'foo'
特点:
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
(6)、export 与 import 的复合写法
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
export 与 import 可以在同一模块使用,使用特点:
- 可以将导出接口改名,包括 default。
- 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
(7)、import()
支持动态加载模块
概述:
import()
返回一个 Promise 对象。import()
函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。import()
函数与所加载的模块没有静态连接关系,这点也是与import
语句不相同。import()
类似于 Node 的require
方法,区别主要是前者是异步加载,后者是同步加载。
适用场合:
- 按需加载
import()
可以在需要的时候,再加载某个模块。button.addEventListener('click', event => { import('./dialogBox.js') .then(dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ }) });
- 条件加载
import()
可以放在if
代码块,根据不同的情况,加载不同的模块。if (condition) { import('moduleA').then(...); } else { import('moduleB').then(...); }
- 动态的模块路径
import()
允许模块路径动态生成import(f()) .then(...);
根据函数
f
的返回结果,加载不同的模块
本文由博主在学习过程中所写,如有错误和问题,望理解。