参考阮一峰ECMAScript 6 入门http://es6.ruanyifeng.com/#docs/let
目录
一、let和const命令
1.let
- 只在代码块内有效
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
- 设置循环变量的部分是父作用域,循环体内部是单独子作用域
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
- 不存在变量提升
console.log(foo); // 输出undefined
var foo = 2;
//实际为
var foo;
console.log(foo);
foo=2;
故为undefined
console.log(bar); // 报错ReferenceError
let bar = 2;
无提升,打印前并无变量bar
,故报错
- 暂时性死区(temporal dead zone,TDZ)
在代码块内,使用let声明变量前,变量不可用
死区内typeof
也不可用
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
typeof tmp;// ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
- 注意情况
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
let
不允许在相同作用域内,重复声明同一个变量。
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
function func(arg) {
let arg;
}
func() // 报错
function func(arg) {
{
let arg;
}
}
func() // 不报错
2.块级作用域
- 块级作用域内声明的函数类似于
let
,对作用域外没有影响。 - 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 函数声明还会提升到所在的块级作用域的头部。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
运行到ES6环境中为
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
- 块级作用域需要大括号
3.const命令
- 声明只读常量,声明后不可修改
- 一旦声明,必须立刻初始化,不可后续赋值
- 作用域与
let
一致 - 不提升,存在暂时性死区,不可重复声明
- 本质
对数值、字符串、布尔值等简单数据,内存地址中的值不改变,等同常量。
对对象和数组等复合型数据,保证的是指向内存地址的指针不可变,但指针指向的数据可变。
例如对于对象:
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
数组同理。
- 可用
Object.freeze
冻结对象
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
4.顶层对象属性
- ES5中,浏览器环境中
window
,它还具有实体含义,指窗口对象,Node是global
,顶层对象属性等价于全局变量, - ES6中,
var
function
声明的全局变量是顶层对象属性;let
const
class
声明的全局变量不是顶层对象属性。
globalThis对象
顶层对象提供全局作用域,但
- 同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用
this
变量,但是有局限性。 - 全局环境中,
this
会返回顶层对象。但是,Node 模块和 ES6 模块中,this
返回的是当前模块。
函数里面的this
,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this
会指向顶层对象。但是,严格模式下,这时this
会返回undefined
。
不管是严格模式,还是普通模式,new Function('return this')()
,总是会返回全局对象。但是,如果浏览器用了
CSP(Content Security Policy,内容安全策略),那么eval
、new Function
这些方法都可能无法使用。 - ES2020在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
二、变量的解构赋值
1.数组的解构赋值
- 模式匹配,从数组中提取值对变量赋值,解构不成功为
undefiend
let [a, b, c] = [1, 2, 3];
let [x, , y] = [1, 2, 3];//x =1;y=3
//剩余模式
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
- 右边是不可遍历解构将会报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
- 默认值
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x = 1] = [null];//x=null
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。
let x;
if ([1][0] === undefined) {
x = f();
} else {
x = [1][0];
}
- 嵌套赋值
2.对象的解构赋值
- 数组的元素有序,变量的取值由它的位置决定;而对象的属性无序,变量必须与属性同名,才能取到正确的值。
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
- 解构失败等于
undefiend
-将对象方法赋值到变量上
// 将Math的对数正弦余弦赋值到变量
let { log, sin, cos } = Math;
// console.log赋值给log
const { log } = console;
log('hello') // hello
- 内部机制:先找到同名属性,然后再赋给对应的变量
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
foo是模式,baz才是变量
- 嵌套赋值,注意模式与变量
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} 是模式
- 默认值:生效条件为对象属性值===
undefiend
- 注意
1)代码块
let x;
{x} = {x: 1};//SyntaxError: syntax error
// 正确的写法
let x;
({x} = {x: 1});
2)允许左边不放变量名
({} = [true, false]);
3)数组也是一种对象,键值对
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
3.字符串解构赋值
字符串转换成类数组对象属性也可解构赋值
const [a, b] = 'hi';//a=h b=i
let {length : len} = 'hello';//len = 5
4.数值和布尔值的解构赋值
- 将其转为对象再赋值,
undefiend
与null
不能转换成对象
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
5.函数参数的解构赋值
[[1, 2], [3, 4]].map(([a, b]) => a + b);
- 指定默认值
给函数函数指定默认值
//给函数指定默认值
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]
给变量指定默认值
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]
6.括号
- 变量声明不允许带括号
//报错
let [(a)] = [1];
function f([(z)]) { return z; }//函数参数也是变量声明
- 模式不允许放括号
[({ p: a }), { x: c }] = [{}, {}];
- 赋值语句和非模式可以使用括号
[(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});
- map中使用 遍历
三、字符串的拓展
1.Unicode表示法
- \uxxxx:\u0000-\uFFFF
"\uD842\uDFB7"// "𠮷" 超过用双字节表示
"\u20BB7"// " 7" \u20bb不可打印,显示为空格 后面跟一个7
- ES6的大括号表示法,本质与四字节的UTF-16编码等价
"\u{20BB7}" // "𠮷"
let hello = 123;
hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true
- JS共有6种方法表示一个字符
1 '\z' === 'z' //转义
2 '\172' === 'z' //八进制
3 '\x7A' === 'z' // 十六进制
4 '\u007A' === 'z' // unicode码
5 '\u{7A}' === 'z' // ES6大括号
2字符串的遍历器接口
for…of
循环遍历
for (let codePoint of 'foo') {
console.log(codePoint)
}//码点循环遍历
// "f"
// "o"
// "o"
//码点转换为字符,识别大于oxFFFF的码点
let text = String.fromCodePoint(0x20BB7);
for (let i of text) {
console.log(i);
}// "𠮷"
//使用for循环会认为是有两个字符
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
3.直接输入U+2028 和 U+2029
- 可正确解析json
4.JSON.stringify() 的改造
UTF-8 标准规定,0xD800
到0xDFFF
之间的码点,不能单独使用,必须配对使用
- ES2019 改变了JSON.stringify()的行为。如果遇到0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
5.模板字符串
6.模板编译
7.模板标签
8.模板字符串的限制
- jQuery
四、字符串新增方法
1.String.fromCodePoint()
- ES5码点返回对应字符,>
0xFFFF
的部分舍弃,方法定义再字符串的实例上
String.fromCharCode(0x20BB7) // "ஷ"为0x20BB
- ES6
String.fromCodePoint()
,可以识别 ,定义在String上
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'// true
2.String.raw()
- 模板字符处理方法
3.实例方法,codePointAt
- ES5
var s = "𠮷";
s.length // 2 两个字符
s.charAt(0) // '' 无法读取整个字符
s.charAt(1) // ''
s.charCodeAt(0) // 55362 返回前两个字节
s.charCodeAt(1) // 57271 返回后两个字节
-ES6 正确处理四字节
codePointAt()
方法
let s = '𠮷a';
s.codePointAt(0) // 134071正确返回十进制码点
s.codePointAt(1) // 57271 与codePointAt()相同
s.codePointAt(2) // 97 位置是1 但必须传入2
s.codePointAt(0).toString(16) // "20bb7"
- 补充
1)for of
正确识别
let s = '𠮷a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61
2)拓展运算符(…
)
let arr = [...'𠮷a']; // arr.length === 2
arr.forEach(
ch => console.log(ch.codePointAt(0).toString(16))
);
// 20bb7
// 61
3)测试字符是否是四字节
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("𠮷") // true
is32Bit("a") // false
4.实例方法:normalize()
- 将字符的不同表示方法统一为同样的形式
不能识别三个
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
5.实例方法:includes(),startsWith(),endsWith()
let s = 'Hello world!';
s.startsWith('world',6) // true 参数字符在原字符串头部,第六个开始
s.endsWith('Hello',5) // true 参数字符在原字符串尾部,前5个
s.includes('o',6) // true 字符串中有 第6开始
6.实例方法:repeat()
- 返回新字符串,将原字符串重复n次
1 'x'.repeat(3) // "xxx"
2 'na'.repeat(2.9) // "nana" 向下取整
3 'na'.repeat(Infinity) // RangeError 无穷报错
4 'na'.repeat(-1) // RangeError 负数报错
5 'na'.repeat(-0.9) // "" 取整为-0,然后为0
6 'na'.repeat('na') // "" 字符串转为数字0
7 'na'.repeat(NaN) // "" nan=0
7.实例方法:padStart(),padEnd()
- 头尾部补全
'x'.padStart(5, 'ab') // 'ababx' 5是补全长度 ab是补全字符
'x'.padEnd(4, 'ab') // 'xaba'
'xxx'.padEnd(2, 'ab') // 'xxx' 超过长度不生效
'abc'.padStart(10, '0123456789') // '0123456abc' 截位补全
'x'.padStart(4) // ' x' 空格补全
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" 提示字符串格式
8.实例方法:trimStart(),trimEnd()
- 头尾部消除空格
const s = ' abc ';
s.trim() // "abc" 均消除
s.trimStart() // "abc " 头部消除 trimLeft()
s.trimEnd() // " abc 尾部消除 trimRight()
五、正则的拓展
1.RegExp构造函数
- ES5中
var regex = new RegExp(/xyz/i);
var regex = new RegExp('xyz', 'i');
var regex = new RegExp(/xyz/, 'i');//错误模式
- ES6中
new RegExp(/abc/ig, 'i').flags // "i" 将ig覆盖 flag方法返回正则表达式修饰符
2字符串的正则方法
String.prototype.match
调用RegExp.prototype[Symbol.match]
String.prototype.replace
调用RegExp.prototype[Symbol.replace]
String.prototype.search
调用RegExp.prototype[Symbol.search]
String.prototype.split
调用RegExp.prototype[Symbol.split]
3u修饰符
正确处理四字节UTF-16编码
- 点字符,码点大于0xFFFF的字符不能识别
var s = '𠮷';
/^.$/.test(s) // false 认为是两个字符,匹配失败
/^.$/u.test(s) // true
- Unicode字符表示法,识别大括号,避免解读为量词
- /\u{61}/.test('a') // false 不加u 被识别成61个u
/\u{61}/u.test('a') // true
/\u{20BB7}/u.test('𠮷') // true
- 量词
识别码点大于0xFFFF
/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true
- 预定义模式
预定义 \S 匹配非空白字符 ,识别码点大于0xFFFF,
/^\S$/u.test('𠮷') // true
- i修饰符
/[a-z]/i.test('\u212A') // false 无法识别非规范的K字符
/[a-z]/iu.test('\u212A') // true
- 转义
/\,/ // /\,/ 无效
/\,/u // 报错
4.RegExp.prototype.unicode属性
- 从
unicode
属性看出是否设置了u修饰符
const r1 = /hello/;
const r2 = /hello/u;
r1.unicode // false
r2.unicode // true
5.y修饰符
- 与
g
的区别,y
从剩余字符头部开始
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
//剩余_aa_a
r1.exec(s) // ["aa"] //剩余的里面有就可以
r2.exec(s) // null //剩余的里面第一个不是a 失败
y
会发现非法字符
6.RegExp.prototype.sticky属性
- 用
sticky
属性,检测是否设置了y
修饰符
var r = /hello\d/y;
r.sticky // true
7.RegExp.prototype.flags属性
flags
属性,返回正则表达式的修饰符。
/abc/ig.source
// "abc"
/abc/ig.flags
// 'gi'
8.s修饰符:dotAll模式
点(.
)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u
修饰符解决;另一个是行终止符(line terminator character)。
- 行终止符
U+000A 换行符(\n
)
U+000D 回车符(\r
)
U+2028 行分隔符(line separator)
U+2029 段分隔符(paragraph separator) s
修饰符 任意匹配多个字符,点代表一切字符
/foo.bar/s.test('foo\nbar') // true
是否处在dotAll
模式
re.dotAll // true
9.后行断言
- 先行(否定)断言
/x(?=y)/
x在y前面
/x(?!y)/
x不在y前面 - 后行(否定)断言,先匹配右边x 再左边y
/(?<=y)x/
x在y后面
/(?<!y)x/
x不在y后面
10.Unicode属性类
- \p{…}和\P{…}
匹配希腊字母,以u修饰符结尾
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
11.具名组匹配
- 可以为每一组匹配指定名字
?<组名>
,加了id
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31
- 解构赋值和替换
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one // foo
two // bar
字符串替换时,使用$<组名>
引用具名组。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'
- 引用,
\k<组名>
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
///^(?<word>[a-z]+)!\k<word>$/=/^(?<word>[a-z]+)!\<word>[a-z]$/
12.String.prototype.matchAll()
循环去除每一轮的正则匹配
var regex = /t(e)(st(\d?))/g;
var string = 'test1test2test3';
var matches = [];
var match;
while (match = regex.exec(string)) {
matches.push(match);
}
matches
// [
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"],
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"],
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
String.prototype.matchAll()
方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。
// 转为数组方法一
[...string.matchAll(regex)]
// 转为数组方法二
Array.from(string.matchAll(regex))
六、数值的拓展
1.二进制和八进制表示法
- 二进制
0b(0B)
,八进制0o(0O)
- 转换
Number('0b111') // 7
Number('0o10') // 8
2.Number.isFinite(),Number.isNaN()
- 检查一个数值是否为有限的(finite)
Number.isFinite(15); // true
Number.isFinite(-Infinity); // false
Number.isFinite("25") ;// false false 类型不是数值返回false
- 检查一个值是否为NaN。参数类型不是
NaN
一律返回false
Number.isNaN('15') // false
Number.isNaN(9/NaN) // true
- 以上方法只对数值有效,传统全局方法会将非数值转换为数值
isFinite("25") // true
isNaN("NaN") // true
3.Number.parseInt(),Number.parseFloat()
- 将全局方法移植到
Number
对象上
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true
parseInt('12.34') // 12
Number.parseInt('12.34') // 12
parseFloat('123.45#') // 123.45
Number.parseFloat('123.45#') // 123.45
4.Number.isInteger
- 数值是否为整数
Number.isInteger(25.1) // false
Number.isInteger(25.0) // true
Number.isInteger('15') // false
- 数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)
Number.isInteger(3.0000000000000002) // true 16个十进制位转换成二进制超过53位
- 数值的绝对值小于
Number.MIN_VALUE
(5E-324)会被自动转为 0
Number.isInteger(5E-325) // true
5.Number.EPSILON
- 对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的1.00…001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方。
Number.EPSILON === Math.pow(2, -52)
- 设置“能够接受的误差范围”
误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)
)
5.551115123125783e-17 < Number.EPSILON * Math.pow(2, 2)
// true
- 6.误差检查函数。
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
6.安全整数和Number.isSafeInteger()
- 安全整数
Number.MAX_SAFE_INTERGER
和Number.MIN_SAFE_INTERGER
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
- 判断一个整数是否落在这个范围之内
Number.isSafeInteger(9007199254740990) // true
Number.isSafeInteger('a') // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false
7.Math 对象的扩展
Math.trunc()
截取整数部分,非数值转换成数值,
Math.trunc(-0.1234) // -0
Math.trunc(false) // 0
Math.trunc(null) // 0
//对于空值和无法截取整数的值,返回NaN。
Math.trunc(NaN); // NaN
Math.trunc('foo'); // NaN
Math.trunc(); // NaN
Math.trunc(undefined) // NaN
代码模拟
Math.trunc = Math.trunc || function(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};
Math.sign()
判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。无法转为数值的值,会返回NaN。
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
代码模拟
Math.sign = Math.sign || function(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};
Math.cbrt()
计算一个数的立方根。对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
Math.cbrt(-1) // -1
代码模拟。
Math.cbrt = Math.cbrt || function(x) {
var y = Math.pow(Math.abs(x), 1/3);
return x < 0 ? -y : y;
};
Math.clz32()
count leading zero 参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。
Math.clz32(0) // 32
Math.clz32(1 << 1) // 30 左移
Math.clz32(3.2) // 30 只考虑整数部分
//其他值转换成数值
Math.clz32('foo') // 32
Math.clz32(true) // 31
Math.imul()
返回两个数以 32 位带符号整数形式相乘的32位带符号结果
Math.imul(-1, 8) // -8
超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。
Math.fround()
返回一个数的32位单精度浮点数形式。
对于 -224 至 224 之间的整数(不含两个端点),返回结果与参数本身一致
Math.fround(2 ** 24 + 1) // 16777216 丢失精度
对于 NaN 和 Infinity,此方法返回原值。对于其它类型的非数值,Math.fround 方法会先将其转为数值,再返回单精度浮点数。
模拟
Math.fround = Math.fround || function (x) {
return new Float32Array([x])[0];
};
Math.hypot()
返回所有参数的平方和的平方根
Math.hypot(3, 4, 'foo'); // NaN
Math.expm1()
返回 ex - 1即Math.exp(x) - 1。Math.log1p()
即Math.log(1 + x)Math.log10()
返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。Math.log2()
返回以 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)
8.指数运算符
- 从最右边开始计算
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
- 赋值运算符
let b = 4;
b **= 3;
// 等同于 b = b * b * b;
9.BigInt数据类型
- 没有位数限制数据后面加后缀
n
1234 // 普通整数
1234n // BigInt
typeof
返回bigint
typeof 123n // 'bigint'
- BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突。
-42n // 正确
+42n // 报错
- BigInt对象
BigInt('123') // 123n
构造函数必须有参数,参数必须可以正常转为数值
new BigInt() // TypeError
BigInt(undefined) //TypeError
BigInt(null) // TypeError
BigInt('123n') // SyntaxError 无法解析成Number类型
BigInt('abc') // SyntaxError
BigInt(1.5) // RangeError 小数也报错
BigInt 对象继承了 Object 对象的两个实例方法。
BigInt.prototype.toString()
BigInt.prototype.valueOf()
它还继承了 Number 对象的一个实例方法。本地时间转换成字符串
BigInt.prototype.toLocaleString()
三个静态方法
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max) // -2width - 1到2width - 1 - 1对应的数
// 9223372036854775807n
BigInt.asUintN(64, max + 1n) // 0 到 2width - 1 之间对应的值。
// 9223372036854775808n
BigInt.parseInt('9007199254740993', 10)
- 转换规则
Boolean(0n) // false
Boolean(1n) // true
Number(1n) // 1
String(1n) // "1"
//取反运算
!0n // true
!1n // false
- 数学运算
+
、-
、*
和**
这四个二元运算符,与 Number 类型的行为一致。除法运算/会舍去小数部分,返回一个整数。
9n / 5n
// 1n
不带符号的右移位运算符>>>
,一元的求正运算符+
会报错
- 其他运算
BigInt 对应的布尔值,与 Number 类型一致,即0n会转为false,其他值转为true。
if (0n) {
console.log('if');
} else {
console.log('else');
}
// else
比较运算符(比如>)和相等运算符(==)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。
0n < 1 // true
0n < true // true
0n == 0 // true
0n == false // true
0n === 0 // false
BigInt 与字符串混合运算时,会先转为字符串,再进行运算。
'' + 123n // "123"
七、函数的扩展
1.参数默认值
- 在函数体中,不能用let或const再次声明,否则会报错。
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
- 不能有同名参数
// 报错
function foo(x, x, y = 1) {
// ...
}
- 与解构赋值结合使用
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
- 函数参数默认值是一个有具体属性的对象,没设置解构
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
- 函数参数的默认值是空对象,设置了对象解构赋值的默认值
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 无值的情况
m1({x: 3}) // [3, 0] //多一个解构赋值,没有参数就解构里面的数
m2({x: 3}) // [3, undefined]
// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
- 参数默认位置
function f(x, y = 5, z) {
return [x, y, z];
}
//非尾部的参数设置默认值,实际上这个参数是没法省略的
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
function foo(x = 5, y = 6) {
console.log(x, y);
}
//undefined触发了默认值
foo(undefined, null) // 5 null
- 函数length属性
返回没有默认值的参数个数
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
- 作用域
参数默认值,单独作用域
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
- 应用,指定参数不可省略,否则抛出错误
function throwIfMissing() {
throw new Error(‘Missing parameter’);
}
function foo(mustBeProvided = throwIfMissing()) { //函数名后有括号,运行时执行
return mustBeProvided;
}
foo() //没有赋值,找默认值里的函数
// Error: Missing parameter
2.rest参数
- 获取函数多余参数,就不需要使用
argument
对象了,rest是数组
function add(...values) { //引入任意数目的参数
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
length
属性不包括rest参数
3.严格模式
-
函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
-
规避方法
'use strict'; //全局严格
function doSomething(a, b = a) {
// code
}
函数包在立即执行函数里面
const doSomething = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());
4.name属性
- 返回函数的函数名
匿名函数不一样,具名函数一样
var f = function () {};
//匿名函数赋值给变量
// ES5
f.name // ""
// ES6
f.name // "f"
- 注意 构造函数 bind
(new Function).name // "anonymous"
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
5.箭头函数
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
- 返回对象须加括号
let getTempItem = id => ({ id: id, name: "Temp" });
- 与变量解构结合使用
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
- 注意
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
function foo() {
setTimeout(() => {
console.log('id:', this.id); //指向函数定义生效时所在的对象
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42 //普通函数则输出21
- 让this指向固定化
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000); //this timer定义时的作用域
// 普通函数
setInterval(function () {
this.s2++; //运行时的定义域
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
- 三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:
arguments
、super
、new.target
。 - 箭头函数没有自己的this,所以当然也就不能用
call()
、apply()
、bind()
这些方法去改变this的指向 - 不适合场合
1)
定义方法对象
2)
需要动态this - 嵌套的箭头函数
6.尾调用优化
- 调用帧形成的调用栈
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于 只保留内层函数调用帧
g(3);
- 尾递归,只有一个调用帧,不容易发生“栈溢出”
将内部变量改写成函数的参数
阶乘
function tailfactorial(n, total) {
if (n === 1) return total;
return tailfactorial(n - 1, n * total);
}
function factorial(n) {
return tailFactorial(n, 1); //方便阅读检查
}
factorial(5) // 120
斐波那契
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
与柯里化结合
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
const factorial = currying(tailFactorial, 1);
factorial(5) // 120 变成了只接收一个参数
- 严格模式
ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
func.arguments
:返回调用时函数的参数。
func.caller
:返回调用当前函数的那个函数。
尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真
- 正常模式下的尾递归优化
1)蹦床函数
function trampoline(f) {
while (f && f instanceof Function) { //返回一个函数就继续执行该函数
f = f();
}
return f;
}
7.函数参数尾逗号
function clownsEverywhere(
param1,
param2, //添加新参数直接添加,不用在上一行加逗号
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);
8.Function.prototype.toString()
- 注释 空格全部包含
function /* foo comment */ foo () {}
foo.toString()
// "function /* foo comment */ foo () {}"
catch命令的参数省略
- 允许catch省略语句
try {
// ...
} catch {
// ...
}
八、数组的扩展
1.扩展运算符
- 将数组转换成参数序列
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
- 只有函数调用时,扩展运算符才可以放到圆括号中
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2
- 取代apply
//js不提供求数组中最大元素的方法
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
Array.prototype.push.apply(arr1, arr2);//ES5,push不能是数组
arr1.push(...arr2); //ES6
- 应用
(1)复制数组
ES5,复制只会复制指针,不是全新数组
const a2 = a1;
变通方法
const a2 = a1.concat();
ES6扩展运算符
const a2 = [...a1];
const [...a2] = a1;
(2)
合并数组
[...arr1, ...arr2, ...arr3] 新数组是原数组的浅拷贝,只是引用
(3)
与解构赋值生成数组
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"]; //拓展运算符只能放在最后一位
first // "foo"
rest // []
(4)
字符串
[...'hello']
// [ "h", "e", "l", "l", "o" ]
[...'x\uD83D\uDE80y'].length // 3 正确识别四字节字符串
(5)遍历器
(6)Map 、Set、Generator,均是Iterator接口的对象
没有的使用扩展运算符会报错
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3] //转为一个数组
转为数组
// arguments对象
function foo() {
const args = [...arguments];
}
// NodeList对象
[...document.querySelectorAll('div')]
2.Array.from
- 将类似数组的对象(array-like object)和可遍历(iterable)的对象,转为真正的数组
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
function foo() {
var args = Array.from(arguments);
// ...
}
字符串
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
set
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
有length属性的对象,类数组
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
- 替代方法 [].slice.call(obj)
const toArray = (() =>
Array.from ? Array.from : obj => [].slice.call(obj)
)();
- 第二个参数,提供map功能
Array.from(arrayLike, x => x * x);
3.Array.of
- Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1, 2) // [1, 2]
- 模拟
function ArrayOf(){
return [].slice.call(arguments);
}
4.数组实例的copyWithin
Array.prototype.copyWithin(target, start = 0, end = this.length)
//target必选
// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
// 将3号位复制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}
//理解
({0:undefined,1:undefined,2:undefined,3: 1,4:undefined,5:undefined,length: 5}).copyWithin(0,3,5);
结果为:
{0:1,1:undefined,2:undefined,3: 1,4:undefined,5:undefined,length: 5};
也就是
{0:1,3:1,length:5}
5.数组实例的find()和findIndex()
- 找出第一个符合条件的数组成员
返回成员
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10 不符合条件返回undefiend
返回成员位置
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2 不符合条件返回-1
接受第二个参数,对象。绑定回调函数的this对象
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);
6.数组实例的fill
- 三个参数,填充内容、起始位置、结束位置
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
- 填充类型位对象,为浅拷贝,被赋值的对象指的是同一个内存地址
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
7.数组实例的entries(),keys()和values()
- 利用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"
- 利用遍历器对象的next方法遍历
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
8.数组实例的includes()
-某个数组是否包含给定的值
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true 倒数位置
[1, 2, 3].includes(3, -4);//true 超过数组长度重置为0
-替代
const contains = (() =>
Array.prototype.includes
? (arr, value) => arr.includes(value)
: (arr, value) => arr.some(el => el === value)
)();
contains(['foo', 'bar'], 'baz'); // => false
9.flat(),fiatMap()
- 拉平数组
[1, 2, [3, [4, 5]]].flat(2) //两层需要2
// [1, 2, 3, 4, 5] 返回新数组
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5] //遇空位会跳过去
- 对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
10.数组的空位
- ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
forEach(), filter(), reduce(), every() 和some()都会跳过空位。
map()会跳过空位,但会保留这个值
join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
- ES6 则是明确将空位转为
undefined
。
11.Array.prototype.sort() 的排序稳定性
九、对象的扩展
1.属性的简洁表示法{x,y}
- 大括号,写入变量和函数,作为对象属性和方法,但不能用作构造函数
function getPoint() {
const x = 1;
const y = 10;
return {x, y}; //大括号
}
getPoint() // {x:1, y:10}
let birth = '2000/01/01';
const Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
2.属性名表达式[a]:2
- 字面量定义对象, 表达式放在方括号内作为对象的属性名
let propKey = ‘foo’;
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
obj['abc'] //123
- 定义方法名
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
- 属性名表达式与简洁表示法,不能同时使用,会报错。
const foo = 'bar';
const baz = { [foo] };
- 属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串
[object Object]
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
//[keyA]和[keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而myObject最后只有一个[object Object]属性。
myObject // Object {[object Object]: "valueB"}
3.方法的name属性
返回函数名
f.name//f
- bind方法创造的函数,name属性返回bound加上原函数的名字
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
- Function构造函数创造的函数,name属性返回anonymous
(new Function()).name // "anonymous"
- 对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
4.属性的可枚举和遍历
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true, //可枚举性
// configurable: true
// }
- 作用是不想让一些操作遍历到自己的这个属性
- 属性的遍历
(1)for…in
for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()] 数值 字符 symbol
5.super关键字
- this关键字总是指向函数所在的当前对象,super,指向当前对象的原型对象
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto); //将obj的原型对象设置为proto
obj.foo() // "world" 绑定的this却还是当前对象obj,super.foo指向原型对象proto的foo方法
6.对象的扩展运算符
- 解构赋值,等号右边必须是对象,将右边的键和值拷贝到左边,且必须是最后一个参数,
- let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
//右边不能转化为对象
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
//不是最后一个参数
let { x, ...y, ...z } = someObject; // 句法错误
- 浅拷贝,是引用不是副本
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2 x是解构赋值所在的对象,拷贝了对象obj的a属性,解构赋值拷贝的是这个值的引用,而不是这个值的副本
- 解构赋值只能读取属性,读不到属性值
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
newObj//{}
x // 1
y // undefined
z // 3
- 扩展运算符
取出对象所有可遍历属性
拓展运算符后面必须是变量名,不能是解构赋值表达式
//数组是特殊对象
let foo = { ...['a', 'b', 'c'] };
foo // {0: "a", 1: "b", 2: "c"}
- 转换
//1自动转换为Number{1},没有自身属性,返回空对象
// 等同于 {...Object(1)}
{...1} // {}
//字符串转换成类数组对象
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
- 克隆对象(不会)
- 合并俩对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
7.链判断运算符
- 链判断运算符
?.
判断是否有对象
const firstName = message?.body?.user?.firstName || 'default';
//等于
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
判断对象方法是否存在,如果存在就立即执行的例子
iterator.return?.()
- 常用形式
```javascript
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b() a.b不是函数会报错
a?.()
// 等同于
a == null ? undefined : a()
- 注意
(1)短路机制
(2)
delete a?.b
// 等同于
a == null ? undefined : delete a.b //如果a是undefined或null,会直接返回undefined,而不会进行delete运算。
(3)括号影响
(a?.b).c
// 等价于
(a == null ? undefined : a.b).c //只对内部有影响,.c会永远执行
(4)报错
// 构造函数
new a?.()
new a?.b()
// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`
// 链判断运算符的左侧是 super
super?.()
super?.foo
// 链运算符用于赋值运算符左侧
a?.b = c
(5)右侧不得为十进制数值
为了保证兼容以前的代码,允许foo?.3:0
被解析成foo ? .3 : 0
,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
8.null判断运算符??
- 运算符左侧的值为null或undefined时,返回右侧的值
十、对象新增的方法
1.Object.is()同值相等
==:自动转换类型
===:Nan不等于自身,0+等于0-
- 比较两个值是否严格相等
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
2.Object.assign()
将源对象(source)的所有可枚举属性,复制到目标对象(target)
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3} 同名属性会覆盖
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错,一参数会转成对象
Object.assign(undefined) // 报错
Object.assign(null) // 报错
多参数的,如果无法转成对象,就会跳过
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
- 只拷贝自身属性,不拷贝继承属性
- 注意点
(1)浅拷贝
(2)同名属性替换
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
(3)数组的处理
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3] 视为对象
(4)取值函数的处理
求值后再赋值,而不是复制函数
- 用途
(1)为对象添加属性
Object.assign(this, {x, y})
(2)为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
(3)克隆对象
function clone(origin) {
return Object.assign({}, origin);
}//原始对象拷贝到一个空对象,就得到了原始对象的克隆
(4)合并对象
const merge =
(target, ...sources) => Object.assign(target, ...sources);
(5)为属性指定默认值
3.Object.getOwnPropertyDescriptors()
- 返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
4.__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
- Object.setPrototypeOf()设置一个对象的prototype对象,返回参数对象本身
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);//返回object本身
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
- Object.getPrototypeOf(),读取对象原型
- 参数不是对象会被转为对象,返回第一个参数,undefiend和null无法转为对象
5.Object.keys(),Object.values(),Object.entries()
- Object.keys(),返回数组,参数对象自身的可遍历属性的键名,无继承
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
- Object.values(),返回数组,参数对象本身可遍历属性的键值,无继承
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"] 属性名为数值的属性,是按照数值大小,从小到大遍历的
- Object.entries()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
6.Object.fromEntries(),将一个键值对数组转为对象
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }
十一、Symbol
- 防止属性名的冲突,Symbol 值不是对象,是一种类似于字符串的数据类型
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo'); //参数只是描述
let s2 = Symbol('foo');
s1 === s2 // false
- 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.prototype.description
const sym = Symbol('foo');
String(sym) // "Symbol(foo)"
sym.toString() // "Symbol(foo)"
//直接使用
sym.description // "foo"
3.属性名Symbol
- 可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性
let a = {};
a[mySymbol] = 'Hello!'
let a = {
[mySymbol]: 'Hello!'
};
//错误写法
const a = {};
a.mySymbol = 'Hello!'; //此行为导致是字符串而不是symbol值
- 定义常量,常量值不相等
const log = {};
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info')
}
4.消除魔术字符串
5.属性名的遍历
- Object.getOwnPropertySymbols()方法,返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值
const obj = {};
const foo = Symbol('foo');
obj[foo] = 'bar';
for (let i in obj) {
console.log(i); // 无输出
}
Object.getOwnPropertyNames(obj) // [] 得不到Symbol键名
Object.getOwnPropertySymbols(obj) // [Symbol(foo)]
Reflect.ownKeys()
方法可以返回所有类型的键名
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 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
调用Symbol.for(“cat”)30 次,每次都会返回同一个 Symbol 值,供全局环境搜索,但是调用Symbol(“cat”)30 次,会返回 30 个不同的 Symbol 值
- Symbol.keyFor()方法
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
7.模块的Singleton模式
8.内置的Symbol值
十二、Set和Map数据结构
1.Set
- 类似于数组,但是成员的值都是唯一的,没有重复的值。
可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
- 去重
[...new Set('ababbc')].join('')
// "abc"
NaN等于自身,对象不相等
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
l
et set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
- Set 实例的属性和方法
Set 结构的实例有以下属性。
- Set.prototype.constructor:构造函数,默认就是Set函数。
- Set.prototype.size:返回Set实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
- Set.prototype.add(value):添加某个值,返回 Set 结构本身。
- Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
- Set.prototype.clear():清除所有成员,没有返回值。
Array.from
方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
- 遍历操作
- Set.prototype.keys():返回键名的遍历器
- Set.prototype.values():返回键值的遍历器
- Set.prototype.entries():返回键值对的遍历器
- Set.prototype.forEach():使用回调函数遍历每个成员
- 遍历应用
扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
数组的map和filter方法也可以间接用于 Set
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
并集 、交集 、差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
2.WeakSet
- WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。WeakSet 的成员只能是对象,而不能是其他类型的值
- WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中
- 任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]} //是数组成员,不是数组本身
- 方法
- WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
- WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
- WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
- WeakSet 没有size属性,没有办法遍历它的成员
3.Map
- 它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现
- Map 可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
对同一个键多次赋值,后面的值将覆盖前面的值
const map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
-Map 结构的实例有以下属性和操作方法
- size属性返回 Map 结构的成员总数
- Map.prototype.set(key, value),设置键名key对应的键值为value,然后返回整个 Map 结构
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c'); //返回的是当前的Map对象,因此可以采用链式写法
- Map.prototype.get(key)
读取key对应的键值,如果找不到key,返回undefined
const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数
m.get(hello) // Hello ES6!
- Map.prototype.has(key)返回一个布尔值,表示某个键是否在当前 Map 对象之中
const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // true
- Map.prototype.delete(key)删除某个键,返回true。如果删除失败,返回false
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
- Map.prototype.clear(),清除所有成员,没有返回值
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
- 遍历方法
Map.prototype.keys():返回键名的遍历器。
Map.prototype.values():返回键值的遍历器。
Map.prototype.entries():返回所有成员的遍历器。
Map.prototype.forEach():遍历 Map 的所有成员。 - 使用扩展运算符转为数组结构
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
- 结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有map和filter方法)
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
- forEach方法
- 转换
- Map转为数组
[...myMap]
- 数组转为Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
//
[[Entries]]: Array(2)
0: {true => 7}
key: true
value: 7
1: {Object => Array(1)}
key: {foo: 3}
value: ["abc"]
length: 2
- Map 转为对象, Map 的键都是字符串,它可以无损地转为对象
- 通过Object.entries()
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
- Map 转为 JSON
- JSON 转为 Map
4.WeakMap
- WeakMap结构与Map结构类似,也是用于生成键值对的集合
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
WeakMap的键名所指向的对象,不计入垃圾回收机制
const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key
- 只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
- 没有keys()、values()和entries()方法,也没有size属性
十三、Proxy
1.Proxy 对象
var proxy = new Proxy(target, handler);//拦截对象,拦截行为
var proxy = new Proxy({}, {
get: function(target, propKey) { //配置对象有一个get方法,参数为目标对象和要访问的属性
return 35;
}
});
proxy.time // 35 //拦截函数返回值为35,固访问任何属性都返回35
proxy.name // 35
proxy.title // 35
- 将 Proxy 对象,设置到object.proxy属性,从而可以在object对象上调用
var object = { proxy: new Proxy(target, handler) };
- Proxy 实例也可以作为其他对象的原型对象
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
2.Proxy 支持的拦截操作
- get(target, propKey, receiver):拦截对象属性的读取,目标对象、属性名和 proxy 实例本身
- set(target, propKey, value, receiver):拦截对象属性的设置,目标对象、属性名、属性值和 Proxy 实例本身
- 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 实例作为函数调用的操作,目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)目标对象、构造函数的参数对象、创造实例对象时,new命令作用的构造函数
3.Proxy.revocable()
- 返回一个可取消的 Proxy 实例
let {proxy, revoke} = Proxy.revocable(target, handler);
//Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例
proxy.foo = 123;
proxy.foo // 123
revoke();//执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
proxy.foo // TypeError: Revoked
4.this问题
Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。
十四、Reflect
1.概述
- 从Reflect对象上可以拿到语言内部的方法
- 修改某些Object方法的返回结果,让其变得更合理。
- 让Object的命令式操作都变成函数行为
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
- Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
2.静态方法
3观察者模式
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
十五、Promise对象
1.Promise的含义
- 异步编程,是一个容器,保存未来才会结束的事件,是异步操作
- 特点
- 有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败),只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态 - 称为 resolved(已定型),从
pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 - 无法取消Promise,一旦新建它就会立即执行,无法中途取消。如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
2.基本用法
- Promise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){ //将Promise对象的状态从“未完成”变为“成功”
resolve(value); //将异步操作的结果,作为参数传递出去
} else { //将Promise对象的状态从“未完成”变为“失败”
reject(error); //在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
}
});
//当前脚本同步任务执行完毕后输出
promise.then(function(value) { //参数是回调函数
// success状态的回调函数
}, function(error) { //可选
// failure状态的回调函数
});
- 异步加载图片例子
function loadIamgeAsyny(url){
return new Promise(function(resolve,reject){
const image=new Iamge();
image.onload=functiong(){
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
- Promise对象实现Ajax
- resolve参数情况
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
}) //3s后改变状态
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000) //p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态
}) //1s后改变状态
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
3.Promise.prototype.then
- 采用链式的then
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
4.Promise.prototype.catch()
- getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
- 如果 Promise 状态已经变成resolved,再抛出错误是无效的。
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
- someAsyncThing函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
5.Promise.prototype.finally
- finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
6.Promise.all()
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result) //p1会resolved
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');//p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例
})
.then(result => result)
.catch(e => e);
//Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
7.Promise.race()
- 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
const p = Promise.race([p1, p2, p3]);
8.Promise.allSettled()
- 只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
//对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。
await Promise.allSettled(promises);
removeLoadingIndicator();
//返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected
9.Promise.any()
- Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。
10.Promise.resolve()
- 将现有对象转为 Promise 对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
- 参数
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象
//thenable对象指的是具有then方法的对象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
(3)参数不是具有then方法的对象,或根本就不是对象
const p = Promise.resolve('Hello');
//回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。
p.then(function (s){
console.log(s)
});
// Hello
(4)不带有任何参数
const p = Promise.resolve();
p.then(function () {
// ...
});//返回一个resolved状态的 Promise 对象。
11.Promise.reject()
- 生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
12.应用
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;//一旦加载完成,Promise的状态就发生变化。
image.src = path;
});
};
13.Promise.try()
十六、Iterator和for…of循环
1.Iterator概念
- Iterator 接口
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++]} :
{done: true};
}
};
}
2.默认Iterator接口
- Symbol.iterator属性
const obj = {
[Symbol.iterator] : function () { //预定义好的,类型为Symbol的特殊值,要放在方括号内
return {
next: function () {
return {
value: 1, //当前成员值
done: true //表示遍历是否结束
};
}
};
}
};
- 原生具备 Iterator 接口的数据结构如下,不用自己写遍历器生成函数,
for...of
循环会自动遍历它们
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象 - 数组的Symbol.iterator属性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); //调用属性
//得到遍历器对象
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
- 部署生成器方法
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; } //返回当前函数遍历器对象
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
- 调用数组的Symbol.iterator方法
类数组对象调用
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
3.调用Iterator接口的场合
(1)解构赋值,Symbol.iterator方法
默认调用Symbol.iterator方法
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
(2)yield*
(3)其他场合
for…of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
Promise.all()
Promise.race()
4.字符串的Iterator 接口
是类数组对象
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
5.Iterator 接口与 Generator 函数
6.遍历器对象的 return(),throw()
7.for…of 循环
- 数组
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
//空对象obj部署了数组arr的Symbol.iterator属性
for(let v of obj) {
console.log(v); // red green blue
}
for...in
与for...of
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
} //获取键名
for (let a of arr) {
console.log(a); // a b c d
}//获取键值
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
- Set 和 Map 结构
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
- 计算生成的数据结构
entries()
返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
keys()
返回一个遍历器对象,用来遍历所有的键名。
values()
返回一个遍历器对象,用来遍历所有的键值。
let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
- 类数组对象
字符串,会正确识别 32 位 UTF-16 字符。
for (let x of 'a\uD83D\uDC0A') {
console.log(x);
}
// 'a'
// '\uD83D\uDC0A'
DOM NodeList对象
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}
arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'
使用Array.from方法将其转为数组。
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
for (let x of Array.from(arrayLike)) {
console.log(x);
} //a,b
- 对象,for…of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for…in循环依然可以用来遍历键名
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (let e in es6) {
console.log(e);
}
// edition
// committee
// standard
使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}
- 比较遍历语法
for循环
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
数组提供内置的forEach方法
myArray.forEach(function (value) {
console.log(value);
});
十七、Generator
1.简介
- 状态机,封装多个内部状态;会返回一个遍历器对象,是一个遍历对象生成函数
function* helloWorldGenerator() { //关键字与函数名中间有星号,
yield 'hello'; //用yield表达式,定义不同内部状态
yield 'world'; //yield表达式是暂停执行的标记
return 'ending';
}
var hw =helloWorldGenerator();//函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
hw.next() //只有next时,函数才会执行
// { value: 'hello', done: false }//而next方法可以恢复执行
hw.next()
// { value: 'world', done: false }
hw.next() //没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
yield表达式如果用在另一个表达式之中,必须放在圆括号里面
function* demo() {
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
- 与 Iterator 接口的关系
var myIterable = {};
myIterable[Symbol.iterator] = function* () {//Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被...运算符遍历了
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
function* gen(){
// some code
}
var g = gen(); //调用时生成遍历器对象g
g[Symbol.iterator]() === g
// true
2.next方法的参数
function* f() {
for(var i = 0; true; i++) {
var reset = yield i; //表达式本身没有返回值,故reset的值是undefiend
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false } //有参数,即表达式有了返回值,为true
3.for…of 循环
-不需要调用next
方法,for...of
循环可以自动遍历 Generator 函数运行时生成的Iterator对象
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
- 斐波那契
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
yield curr; //产出一个curr
[prev, curr] = [curr, prev + curr];
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
- 不具备接口的
将 Generator 函数加到对象的Symbol.iterator属性上面。
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
- 其他可将 Generator 函数返回的 Iterator 对象,作为参数。
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
4.Generator.prototype.throw()
- Generator 函数返回的遍历器对象,都有一个
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);
}
//catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
// 内部捕获 a
// 外部捕获 b
- 内外部throw
因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw方法抛错只可能抛出在函数外部。
如果 Generator 函数内部和外部,都没有部署try…catch代码块,那么程序将报错,直接中断执行。
var g = function* () {
while (true) {
try {
yield;
} catch (e) {
if (e != 'a') throw e;
console.log('内部捕获', e);
}
}
};
var i = g();
i.next(); //不执行next则无法启动内部代码,try catch不会捕获到
try { //若只有throw没有try catch,代码会报错
throw new Error('a'); //是全局的throw命令,
throw new Error('b');
} catch (e) { //捕获到错误后就不会继续执行try后面的语句
console.log('外部捕获', e);
}
// 外部捕获 [Error: a]
throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。
function* foo() {
var x = yield 3;
var y = x.toUpperCase(); //数值是没有toUpperCase方法,抛出 TypeError 错误
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42);
} catch (err) {
console.log(err);
}
5.Generator.prototype.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 } //Generator 函数的遍历终止,无参数返回value属性为undefined
g.next() // { value: undefined, done: true }
Generator 函数内部有try…finally代码块
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false } //直接进入finally
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }//最后才返回return指定的
6.next()、throw()、return() 的共同点
7.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
yield*
表达式
等同于在 Generator 函数内部,部署一个for…of循环
有return语句时,则需要用var value = yield* iterator的形式获取return语句的值
function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) { //手动
console.log(i);
}
yield 'y';
}
//替换成
function* bar() {
yield 'x';
yield* foo(); //等同于在 Generator 函数内部,部署一个for...of循环。
yield 'y';
}
yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员
function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
任何数据结构只要有 Iterator 接口,就可以被yield*遍历
let read = (function* () {
yield 'hello';
yield* 'hello';
})();
read.next().value // "hello"
read.next().value // "h"
8.作为对象属性的Generator函数
- 简写
let obj = {
* myGeneratorMethod() {
···
}
};
//等价于
let obj = {
myGeneratorMethod: function* () {
// ···
}
};
9.Generator函数的this
- Generator 函数总是返回一个遍历器,这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。
function* g() {
this.a = 11;
}
let obj = g(); //g()返回的是遍历器对象,不是this
obj.next();
obj.a // undefined //拿不到属性
Generator 函数也不能跟new命令一起用,会报错
function* F() {
yield this.x = 2;
yield this.y = 3;
}
new F() //他不是构造函数
// TypeError: F is not a constructor
- 绑定 Generator 函数内部的this的方法
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var obj = {};//成一个空对象,使用call方法绑定 Generator 函数内部的this
var f = F.call(obj);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
obj.a // 1
obj.b // 2
obj.c // 3
obj换成F.prototype
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
将F改成构造函数
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
10.含义
- 状态机
var clock = function* () {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
- Generator 与协程
- Generator 与上下文
11.应用
- 异步操作的同步化表达
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();//函数不执行,返回遍历器
// 加载UI
loader.next() //开始执行,显示+加载
// 卸载UI
loader.next()
- 控制流管理
看书 - 部署Iterator 接口
for of 可以自动遍历 yield语句
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) { //for of 可以自动遍历 yield语句
console.log(key, value);
}
// foo 3
// bar 7
- 作为数据结构
function* doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
// task是一个函数,可以像回调函数那样使用它
}
十八、Generator 函数的异步应用
1.传统方法
回调函数
事件监听
发布/订阅
Promise 对象
2.基本概念
看书
十九、async 函数
1.特点
- async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
functionName();
- async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
- async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。用then方法指定下一步的操作
2.用法
3.语法
- 返回 Promise 对象
async function f() {
return 'hello world'; //then方法回调函数的参数
}//返回 Promise 对象
f().then(v => console.log(v))
// "hello world"
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error: 出错了
- Promise 对象的状态变化
只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。 - await 命令
await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。
class Sleep {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) { //then方法
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}
(async () => {
const sleepTime = await new Sleep(1000);
console.log(sleepTime);
})();
// 1000
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行
async function f() {
await Promise.reject('出错了'); //eject的参数会被catch方法的回调函数接收到。
await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
不中断执行方法
async function f() {
try { //第一个await放在try...catch结构里面
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
async function f() {
await Promise.reject('出错了') //await后面的 Promise 对象再跟一个catch方法
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hello world
- 错误处理
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了'); //async函数返回的 Promise 对象被reject。
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
- 使用注意点
多个请求并发执行,Promise.all
方法
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
4.async 函数的实现原理
将 Generator 函数和自动执行器,包装在一个函数里
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () { //spawn函数是自动执行器
// ...
});
}
spawn源码
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
5.与其他异步处理方法的比较
看书
6.实例:按顺序完成异步操作
没懂
7.顶层await
二十、Class 的基本语法
1.类的由来
- ES5 的构造函数,对应 ES6 的类的构造方法。
class Point {
constructor() { //类的方法都定义在prototype对象上面
// ...
}
toString() { //类内部定义的方法,不可枚举
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
Object.assign
方法可以很方便地一次向类添加多个方法
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, { //将sourse属性添加到target目标对象上
toString(){},
toValue(){}
});
- constructor
class Point {
} //new的时候,会默认添加construcotr方法
// 等同于
class Point {
constructor() {} //该方法默认返回实例对象,this
//也可指定对象 return Object.create(null);
}
- 取值函数(getter)和存值函数(setter)
存值函数和取值函数是设置在属性的 Descriptor 对象上的
class MyClass {
constructor() {
// ...
}
get prop() { 自定义取值函数
return 'getter';
}
set prop(value) { //自定义存值函数
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123; //存值
// setter: 123
inst.prop //取值
// 'getter
- 属性表达式
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
- Class 表达式
const MyClass = class Me {
getClassName() {
return Me.name; //Me只在 Class 的内部可用,指代当前类
}
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
- 注意点
- 严格模式(默认)
- 不存在提升
- name 属性
class Point {}
Point.name // "Point"
- Generator 方法:某个方法之前加上星号(*),就表示该方法是一个 Generator 函数
- this的指向
箭头函数内部的this总是指向定义时所在的对象
this.getThis = () => this;
2.静态方法
静态方法可以与非静态方法重名 static
父类的静态方法,可以被子类继承
class Foo {
static classMethod() { //该方法不会被实例继承
return 'hello';
}
}
Foo.classMethod() // 'hello' //直接通过类来调用
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
3.实例属性的新写法
//旧写法
class IncreasingCounter {
constructor() {
this._count = 0;
}
//新写法
class IncreasingCounter {
_count = 0;
4.静态属性
Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。属性不需要实例化类就可以通过类名直接使用
// 老写法
class Foo {
// ...
}
Foo.prop = 1;
// 新写法
class Foo {
static prop = 1;
}
5.私有方法和私有属性
class Widget {
foo (baz) {
bar.call(this, baz);
} //使得bar实际上成为了当前模块的私有方法。
// ...
}
function bar(baz) {
return this.snaf = baz;
}
- 私有属性、方法的提案
加#
私有属性也可以设置 getter 和 setter 方法
当前属性只能在当前类内访问
class Foo {
#a;
#b;
constructor(a, b) {
this.#a = a;
this.#b = b;
}
#sum() {
return #a + #b;
}
6.new.target属性
返回new命令作用于的那个构造函数
function Person(name) {
if (new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
二十一、Class的继承
1.简介
- 通过
extends
关键字实现继承
super
关键字,表示父类的构造函数,用来新建父类的this对象。
实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y),在子类constructor之中
this.color = color; //只有super后才可以使用this
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
静态方法也会被继承
2.Object.getPrototypeOf()
从子类获取父类
Object.getPrototypeOf(ColorPoint) === Point
3.super关键字
- 子类的构造函数必须执行一次super函数
class A {}
class B extends A {
constructor() {
super(); //返回的是子类B的实例,即super内部的this指的是B的实例
}
}
- super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
//普通方法
class A {
p() {
return 2;
}
constructor(){ //a是父类A实例的属性,super.a就引用不到它。
this.a=2;
}
}
class B extends A {
constructor() {
super();
get m() {
return super.a;
}
console.log(super.p()); // 2
}
}
let b = new B();
b.a //undefined
- 通过
super
对某个属性赋值,这时super
就是this
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3; //=this.x=3
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
- 子类普通方法中通过
super
调用父类的方法时,方法内部的this
指向当前的子类实例
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print(); //super.print.call(this)
}
}
let b = new B();
b.m() // 2
- 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例
class Parent {
static myMethod(msg) { //静态方法为类所有
console.log('static', msg);
}
myMethod(msg) { //此为实例方法
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1 //this指向Child.不是Child的实例
var child = new Child();
child.myMethod(2); // instance 2
- 清晰地表明super的数据类型,就不会报错。
class A {}
class B extends A {
constructor() {
super();
console.log(super.valueOf() instanceof B); // true
}
}
let b = new B();
4.类的prototype 属性和__proto__属性
- 作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
- 实例的 proto 属性
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
5.原生构造函数的继承
- 原生构造函数
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object() - ES6 允许继承原生构造函数定义子类
6.Mixin 模式的实现
- 多个对象合成一个新的对象,新对象具有各个组成成员的接口
const a = {
a: 'a'
};
const b = {
b: 'b'
};
const c = {...a, ...b}; // {a: 'a', b: 'b'}
这块不懂
function mix(...mixins) {
class Mix {
constructor() {
for (let mixin of mixins) {
copyProperties(this, new mixin()); // 拷贝实例属性
}
}
}
for (let mixin of mixins) {
copyProperties(Mix, mixin); // 拷贝静态属性
copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== 'constructor'
&& key !== 'prototype'
&& key !== 'name'
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
二十二、Module的语法
1.概述
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载
2.严格模式
- 严格模式主要有以下限制
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀 0 表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface
3.export命令
export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
export
输出变量
// profile.js 独立的用户信息文件 模块
export var firstName = 'Michael'; //使用export关键字输出该变量,使外部能获取
export var lastName = 'Jackson';
export var year = 1958;
另一种写法
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
//可以在脚本尾部,一眼看清楚输出了哪些变量。
export { firstName, lastName, year };
expor
t输出函数或类(class)
export function multiply(x, y) {
return x * y;
};
as
关键字重命名
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
- 需要处于模块顶层
function foo() {
export default 'bar' // SyntaxError
}
foo()
4.import命令
- 具有提升到整个模块的效果
import { lastName as surname } from './profile.js';
- 允许在加载模块的脚本里面,改写接口。a的属性可以成功改写
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
a.foo = 'hello'; // 合法操作
5.模块的整体加载
- 用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
6.export default 命令
指定模块的默认输出
// export-default.js
export default function foo() {
console.log('foo');
}
import foo from export-default//不用大括号
// 或者写成
function foo() {
console.log('foo');
}
export default foo; //本质是将后面的值,赋给default变量
//默认输出是一个函数
7.export与import的复合写法
没看懂、 看书
8.模块的继承
// circleplus.js
export * from 'circle'; //再输出circle模块的所有属性和方法
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
9.跨模块常量
// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
10.import()
- import()返回一个 Promise 对象
- 运行时执行,什么时候运行到这一句,就会加载指定的模块,异步加载
- 适用场合
(1)按需加载。
button.addEventListener('click', event => {
import('./dialogBox.js') //用户点击了按钮,才会加载这个模块
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});
(2)条件加载
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
(3)动态的模块路径
import(f()) //根据函数f的返回结果,加载不同的模块
.then(...);
-
注意