JS拾遗笔记
本文主要记录了 JS 基础以及 ES6 的相关笔记,随缘更新。
JS 基础
基本概念
JS 中 label 标签用来终止或者跳过外层循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25top: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (i === 1 && j === 1) break top; console.log("i=" + i + ", j=" + j); } } // i=0, j=0 // i=0, j=1 // i=0, j=2 // i=1, j=0 // 直接进入下一轮外层循环 top: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (i === 1 && j === 1) continue top; console.log("i=" + i + ", j=" + j); } } // i=0, j=0 // i=0, j=1 // i=0, j=2 // i=1, j=0 // i=2, j=0 // i=2, j=1 // i=2, j=2
在 ES5 之前,JS 有六种基本对象
- number
- string
- boolean
- object
- undefined
- null
1
2
3
4
5
6
7
8
9
10
11
12typeof window; // "object" typeof {}; // "object" typeof []; // "object" function f() {} typeof f; // "function" v; // ReferenceError: v is not defined typeof NaN; // 'number' typeof v; // "undefined" typeof null; // "object"
typeof null
的结果是object
,这是由于 JS 历史遗留问题造成的。JS 中所有数字都是以 64 位浮点数表示的,而有些小数用二进制不能精确的表示,例如
要理解这个问题,我们需要知道十进制和二进制的区别。十进制是以 10 为基数的数制,也就是说,每个位上的数字都是 10 的幂次方的系数。比如,123.45 可以表示为:
二进制是以 2 为基数的数制,也就是说,每个位上的数字都是 2 的幂次方的系数。比如,101.11 可以表示为:
当我们把一个十进制小数转换成二进制小数时,我们需要不断地用 2 去 x 这个小数的小数部分,并把余数作为二进制位。比如,0.1 转换成二进制的过程如下:
可以看到,当我们遇到了之前出现过的余数 0.2 时,就会出现循环。所以,0.1 的二进制表示就是:
其中$\overline{0011}$表示 0011 这个部分是无限重复的。
如果一个十进制小数能被 2 整除,那么它就能用有限的二进制位来表示。比如,0.5 可以表示为$(0.1)_2$,因为$0.5 \div 2 = 0 … 1$。但如果一个十进制小数不能被 2 整除,那么它就可能用无限的二进制位来表示。比如,0.3 可以表示为$(0.\overline{010011})_2$³。
数值转换函数。
parseInt
:将一个字符串转化为整数。parseFloat
:将一个字符串转化为浮点数。isFinite
:判断是否是正常的数值。先转化成数值再进行判断。isNaN
:判断是否是NaN
。先转换成数值再进行判断。
JS 在圆括号里面只能是表达式,在遇到花括号的时候一律解释为代码块。
delete
用于删除对象的属性,但有注意点,一是不能通过delete
语句的结果来判断删除属性是否成功,因为删除一个不存在的属性delete
也会返回true
,只有当对象属性设置为不可配置的时候返回false
。而且只能删除自身的属性,不能删除继承的属性。1
2
3
4
5
6var obj = { p: 1 }; Object.keys(obj); // ["p"] delete obj.p; // true obj.p; // undefined Object.keys(obj); // []
in
运算符可以判断对象属性是否含有某个key
,注意会沿着对象的原型链进行查找。1
2
3
4
5
6
7
8var person = { name: "老张" }; for (var key in person) { if (person.hasOwnProperty(key)) { console.log(key); } } // name
你可以传递任意数量的参数给
Function
构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。1
2
3
4
5
6var foo = new Function('return "hello world";'); // 等同于 function foo() { return "hello world"; }
var
提升只会把声明提升,不会提升赋值语句。1
2
3
4
5
6
7f(); var f = function () {}; // TypeError: undefined is not a function // 等同于 var f; f(); f = function () {};
- 函数的
name
属性会显示函数的名字,函数的length
会显示函数预期传入的参数的个数。这个属性可用于函数的方法的重载。为何内置函数的
toString
方法返回的是function (){[native code]}
,这是因为JS
内部方法的函数不是通过原生JS
实现的,而是由C++
等代码实现的。 - 闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。闭包的另一个用处,是封装对象的私有属性和私有方法。
为了避免解析的歧义,
JavaScript
规定,如果function
关键字出现在行首,一律解释成语句。因此,引擎看到行首是function
关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。1
2
3
4
5
6
7
8function(){ /* code */ }(); // SyntaxError: Unexpected token ( var f = function f(){ return 1}(); f // 1 (function(){ /* code */ }()); // 或者 (function(){ /* code */ })();
对象 Object
数组的
length
是一个动态属性,只要是数组,就一定有length
属性。该属性是一个动态的值,等于键名中的最大整数加上1
,没有显示的元素填空值。更改length
属性会导致,数组长度增大,添加空值。或减小,直接裁剪元素。1
2
3
4
5
6
7
8
9
10
11var arr = ["a", "b"]; arr.length; // 2 arr[2] = "c"; arr.length; // 3 arr[9] = "d"; arr.length; // 10 arr[1000] = "e"; arr.length; // 1001
注意一些方法对于数组空位的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33var a = [, , ,]; a.forEach(function (x, i) { console.log(i + ". " + x); }); // 不产生任何输出 for (var i in a) { console.log(i); } // 不产生任何输出 Object.keys(a); // [] var a = [undefined, undefined, undefined]; a.forEach(function (x, i) { console.log(i + ". " + x); }); // 0. undefined // 1. undefined // 2. undefined for (var i in a) { console.log(i); } // 0 // 1 // 2 Object.keys(a); // ['0', '1', '2']
Object.keys()
与Object.getOwnPropertyNames
二者返回结果在大多数情况下都是相同的,但前者只会返回可枚举的属性,后者可以返回不可枚举的属性。二者都不能获取原型链上面的属性。1
2
3
4var a = ["Hello", "World"]; Object.keys(a); // ["0", "1"] Object.getOwnPropertyNames(a); // ["0", "1", "length"]
valueOf
方法的主要用途是,JavaScript
自动类型转换时会默认调用这个方法。1
2
3
4
5
6var obj = new Object(); obj.valueOf = function () { return 2; }; 1 + obj; // 3
Object.defineProperty
方法的详细解释Object.defineProperty
方法是一个JavaScript
的静态方法,它可以用来在一个对象上直接定义一个新属性,或者修改一个已经存在的属性,并返回该对象。这个方法的作用是可以精确地控制对象属性的特征,比如是否可写、是否可枚举、是否可配置等。Object.defineProperty
方法的语法如下:1
Object.defineProperty(obj, prop, descriptor);
其中,
obj
是要定义或修改属性的对象,prop
是要定义或修改的属性名,descriptor
是一个对象,用来描述该属性的特征。descriptor
对象可以包含以下几个键:value
:该属性的值,可以是任何有效的JavaScript
值,默认为undefined
。writable
:该属性是否可写,如果为true
,则可以通过赋值运算符修改该属性的值,否则不能,默认为false
。enumerable
:该属性是否可枚举,如果为true
,则可以通过for...in
循环或Object.keys
方法遍历该属性,否则不能,默认为false
。configurable
:configurable
是一个布尔值,表示属性的可配置性,默认为false
。如果设为false
,将阻止某些操作改写属性描述对象,比如无法删除该属性,也不得改变各种元属性(value
属性除外)。也就是说,configurable
属性控制了属性描述对象的可写性。get
:该属性的getter
函数,用来返回该属性的值,如果没有getter
函数,则为undefined
。当访问该属性时,会调用这个函数,并把this
绑定到所属对象上。默认为undefined
。set
:该属性的setter
函数,用来设置该属性的值,如果没有setter
函数,则为undefined
。当给该属性赋值时,会调用这个函数,并把this
绑定到所属对象上。默认为undefined
。
注意:
descriptor
对象中不能同时出现value
或writable
和get
或set
这两组键,否则会抛出异常。也就是说,一个属性要么是数据描述符(有value
和writable
),要么是访问器描述符(有get
和set
)。使用
Object.defineProperty
方法可以创建或修改对象的属性,并且可以精细地控制其特征。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106// 创建一个空对象 let obj = {}; // 定义一个普通的数据属性 Object.defineProperty(obj, "a", { value: 1, writable: true, enumerable: true, configurable: true, }); // 定义一个只读的数据属性 Object.defineProperty(obj, "b", { value: 2, writable: false, enumerable: true, configurable: true, }); // 定义一个不可枚举的数据属性 Object.defineProperty(obj, "c", { value: 3, writable: true, enumerable: false, configurable: true, }); // 定义一个不可配置的数据属性 Object.defineProperty(obj, "d", { value: 4, writable: true, enumerable: true, configurable: false, }); // 定义一个访问器属性 let eValue = 5; Object.defineProperty(obj, "e", { get() { return eValue; }, set(newValue) { eValue = newValue; }, enumerable: true, configurable: true, }); console.log(obj.a); // 1 console.log(obj.b); // 2 console.log(obj.c); // 3 console.log(obj.d); // 4 console.log(obj.e); // 5 obj.a = 10; // 可以修改a的值 obj.b = 20; // 不可以修改b的值 obj.e = 50; // 可以通过setter函数修改e的值 console.log(obj.a); // 10 console.log(obj.b); // 2 console.log(obj.e); // 50 for (let key in obj) { console.log(key); // a b d e (c不可枚举) } delete obj.a; // 可以删除a属性 delete obj.b; // 可以删除b属性 delete obj.c; // 可以删除c属性 delete obj.d; // 不可以删除d属性(不可配置) delete obj.e; // 可以删除e属性 console.log(obj.a); // undefined console.log(obj.b); // undefined console.log(obj.c); // undefined console.log(obj.d); // 4 console.log(obj.e); // undefined // 修改d的特征(不可配置) Object.defineProperty(obj, "d", { value: 40, writable: false, enumerable: false, configurable: true, // 报错 }); // 有关 get 与 set 的设置 let Person = {}; let temp = null; // 定义一个有名字的函数 function test(val) { if (arguments.length == 0) { // 如果没有参数,表示是getter return temp; } else { // 如果有参数,表示是setter temp = val; } } // 把有名字的函数赋给set和get Object.defineProperty(Person, "name", { get: test, // 不加括号 set: test, // 不加括号 });
Object.getOwnPropertyDescriptor()
方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。不能用于获取继承属性。1
2
3
4
5
6
7
8var obj = { p: "a" }; Object.getOwnPropertyDescriptor(obj, "p"); // Object { value: "a", // writable: true, // enumerable: true, // configurable: true // }
Object.getOwnPropertyNames
方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。1
2
3
4
5
6
7
8
9
10var obj = Object.defineProperties( {}, { p1: { value: 1, enumerable: true }, p2: { value: 2, enumerable: false }, } ); Object.getOwnPropertyNames(obj); // ["p1", "p2"]
实例对象的
propertyIsEnumerable()
方法返回一个布尔值,用来判断某个属性是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回false
。1
2
3
4
5var obj = {}; obj.p = 123; obj.propertyIsEnumerable("p"); // true obj.propertyIsEnumerable("toString"); // false
如果原型对象的某个属性的
writable
为false
,那么子对象将无法自定义这个属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22var proto = Object.defineProperty({}, "foo", { value: "a", writable: false, }); var obj = Object.create(proto); obj.foo = "b"; obj.foo; // 'a' // 规避方法 var proto = Object.defineProperty({}, "foo", { value: "a", writable: false, }); var obj = Object.create(proto); Object.defineProperty(obj, "foo", { value: "b", }); obj.foo; // "b"
具体来说,如果一个属性的
enumerable
为false
,下面三个操作不会取到该属性。for..in
循环Object.keys
方法JSON.stringify
方法
当一个对象的属性的
configurable
设置为false
的时候,writable
属性只有在false
改为true
时会报错,true
改为false
是允许的。value
属性的情况比较特殊。只要writable
和configurable
有一个为true
,就允许改动value
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var obj = Object.defineProperty({}, "p", { writable: true, configurable: false, }); Object.defineProperty(obj, "p", { writable: false }); // 修改成功 var o1 = Object.defineProperty({}, "p", { value: 1, writable: true, configurable: false, }); Object.defineProperty(o1, "p", { value: 2 }); // 修改成功 var o2 = Object.defineProperty({}, "p", { value: 1, writable: false, configurable: true, }); Object.defineProperty(o2, "p", { value: 2 }); // 修改成功
get
与set
关键字的使用上面两种写法,虽然属性 p 的读取和赋值行为是一样的,但是有一些细微的区别。第一种写法,属性 p 的 configurable 和 enumerable 都为 false,从而导致属性 p 是不可遍历的;第二种写法,属性 p 的 configurable 和 enumerable 都为 true,因此属性 p 是可遍历的。实际开发中,写法二更常用。
1
2
3
4
5
6
7
8
9// 写法二 var obj = { get p() { return "getter"; }, set p(value) { console.log("setter: " + value); }, };
内置对象
利用
Object.prototype.toString
方法来写一个能够判断所有数据类型的函数1
2
3
4
5
6
7const getType = (obj) => { const type = typeof obj; if (type !== "object") return type; return Object.prototype.toString .call(obj) .replace(/^\[object (\S+)\]$/, "$1"); };
数组的
toString
方法,注意数组的shift
与pop
方法会返回当前弹出的值。数组的join
方法,如果数组成员是undefined
或null
或空位,会被转成空字符串。数组的splice
方法也能使用负数索引,只使用一个参数就会把从当前索引往后全部删除掉。大部分遍历类型的数组原生函数都会跟三个对象element, index, arr
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38var a = ["a", "b", "c", "d", "e", "f"]; a.splice(4, 2, 1, 2); // ["e", "f"] a; // ["a", "b", "c", "d", 1, 2] var a = [1, 1, 1]; a.splice(1, 0, 2); // [] a; // [1, 2, 1, 1] var a = [1, 2, 3, 4]; a.splice(2); // [3, 4] a; // [1, 2] var arr = [1, 2, 3]; arr.toString(); // "1,2,3" var arr = [1, 2, 3, [4, 5, 6]]; arr.toString(); // "1,2,3,4,5,6" var a = [1, 2, 3, 4]; a.join(" "); // '1 2 3 4' a.join(" | "); // "1 | 2 | 3 | 4" a.join(); // "1,2,3,4" Array.prototype.join.call("hello", "-"); // "h-e-l-l-o" var obj = { 0: "a", 1: "b", length: 2 }; Array.prototype.join.call(obj, "-"); // 'a-b' var a = ["a", "b", "c", "d", "e", "f"]; a.splice(-4, 2); // ["c", "d"] function log(element, index, array) { console.log("[" + index + "] = " + element); } [2, 5, 9].forEach(log); // [0] = 2 // [1] = 5 // [2] = 9
注意,对于空数组,
some
方法返回false
,every
方法返回true
,回调函数都不会执行。toFixed()
方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串。由于浮点数的原因,小数 5 的四舍五入是不确定的,使用的时候必须小心。
1
2
3
4
5(10).toFixed(2); // "10.00" (10.005).toFixed(2); // "10.01" (10.055).toFixed(2); // 10.05 (10.005).toFixed(2); // 10.01
Number.prototype.toPrecision
方法用于将一个数转为指定位数的有效数字。由于浮点数的原因,该方法四舍五入依然不可靠。
1
2
3
4
5(12.34).toPrecision(1); // "1e+1" (12.34).toPrecision(2); // "12" (12.34).toPrecision(3); // "12.3" (12.34).toPrecision(4); // "12.34" (12.34).toPrecision(5); // "12.340"
toLocaleString
方法的使用。1
2
3
4
5
6
7
8(123).toLocaleString("zh-Hans-CN", { style: "currency", currency: "CNY" }); // "¥123.00" (123).toLocaleString("de-DE", { style: "currency", currency: "EUR" }); // "123,00 €" (123).toLocaleString("en-US", { style: "currency", currency: "USD" }); // "$123.00"
Math
对象的相关方法。Math.abs()
:绝对值Math.ceil()
:向上取整Math.floor()
:向下取整Math.max()
:最大值Math.min()
:最小值Math.pow()
:幂运算Math.sqrt()
:平方根Math.log()
:自然对数Math.exp()
:e 的指数Math.round()
:四舍五入Math.random()
:随机数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function ToInteger(x) { x = Number(x); return x < 0 ? Math.ceil(x) : Math.floor(x); } ToInteger(3.2); // 3 ToInteger(3.5); // 3 ToInteger(3.8); // 3 ToInteger(-3.2); // -3 ToInteger(-3.5); // -3 ToInteger(-3.8); // -3 Math.round(0.1); // 0 Math.round(0.5); // 1 Math.round(0.6); // 1 // 等同于 Math.floor(x + 0.5); Math.round(-1.1); // -1 Math.round(-1.5); // -1 Math.round(-1.6); // -2
Date
对象可以作为普通函数直接调用,返回一个代表当前时间的字符串。Date
实例求值默认调用的是toString
方法而不是valueOf
方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45// 参数为时间零点开始计算的毫秒数 new Date(1378218728000); // Tue Sep 03 2013 22:32:08 GMT+0800 (CST) // 参数为日期字符串 new Date("January 6, 2013"); // Sun Jan 06 2013 00:00:00 GMT+0800 (CST) // 参数为多个整数, // 代表年、月、日、小时、分钟、秒、毫秒 new Date(2013, 0, 1, 0, 0, 0, 0); // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) new Date(2013); // Thu Jan 01 1970 08:00:02 GMT+0800 (CST) new Date(2013, 0); // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) new Date(2013, 0, 1); // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) new Date(2013, 0, 1, 0); // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) new Date(2013, 0, 1, 0, 0, 0, 0); // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) // 返回时间戳 Date.now(); // 1364026285194 new Date("2013-2-15"); new Date("2013/2/15"); new Date("02/15/2013"); new Date("2013-FEB-15"); new Date("FEB, 15, 2013"); new Date("FEB 15, 2013"); new Date("February, 15, 2013"); new Date("February 15, 2013"); new Date("15 Feb 2013"); new Date("15, February, 2013"); // Fri Feb 15 2013 00:00:00 GMT+0800 (CST) Date.parse("Aug 9, 1995"); Date.parse("January 26, 2011 13:51:50"); Date.parse("Mon, 25 Dec 1995 13:30:00 GMT"); Date.parse("Mon, 25 Dec 1995 13:30:00 +0430"); Date.parse("2011-10-10"); Date.parse("2011-10-10T14:48:00");
JSON
的格式规定- 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
- 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和
null
(不能使用NaN
,Infinity
,-Infinity
和undefined
)。 - 字符串必须使用双引号表示,不能使用单引号。
- 对象的键名必须放在双引号里面。
- 数组或对象最后一个成员的后面,不能加逗号。
JSON.stringfy
可以接受三个参数自定义行为。toJSON
用于自定义返回值作为参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// 默认输出 JSON.stringify({ p1: 1, p2: 2 }); // JSON.stringify({ p1: 1, p2: 2 }) // 分行输出 JSON.stringify({ p1: 1, p2: 2 }, null, "\t"); // { // "p1": 1, // "p2": 2 // } var obj = { a: { b: 1 } }; function f(key, value) { console.log("[" + key + "]:" + value); return value; } JSON.stringify(obj, f); // []:[object Object] // [a]:[object Object] // [b]:1 // '{"a":{"b":1}}' var user = { firstName: "三", lastName: "张", get fullName() { return this.lastName + this.firstName; }, toJSON: function () { return { name: this.lastName + this.firstName, }; }, }; JSON.stringify(user); // "{"name":"张三"}"
通过
defineProperty
实现对象的深拷贝。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function copyObject(orig) { return Object.create( Object.getPrototypeOf(orig), Object.getOwnPropertyDescriptors(orig) ); } function copyObject(orig) { var copy = Object.create(Object.getPrototypeOf(orig)); copyOwnPropertiesFrom(copy, orig); return copy; } function copyOwnPropertiesFrom(target, source) { Object.getOwnPropertyNames(source).forEach(function (propKey) { var desc = Object.getOwnPropertyDescriptor(source, propKey); Object.defineProperty(target, propKey, desc); }); return target; }
API
MutationObserver
options
配置选项。childList
:子节点的变动(指新增,删除或者更改)。attributes
:属性的变动。characterData
:节点内容或节点文本的变动。subtree
:布尔值,表示是否将该观察器应用于该节点的所有后代节点。attributeOldValue
:布尔值,表示观察attributes
变动时,是否需要记录变动前的属性值。characterDataOldValue
:布尔值,表示观察characterData
变动时,是否需要记录变动前的值。attributeFilter
:数组,表示需要观察的特定属性(比如['class','src']
)MutationRecord
对象MutationRecord
对象包含了DOM
的相关信息,有如下属性:type
:观察的变动类型(attributes
、characterData
或者childList
)。target
:发生变动的DOM
节点。addedNodes
:新增的DOM
节点。removedNodes
:删除的DOM
节点。previousSibling
:前一个同级节点,如果没有则返回null
。nextSibling
:下一个同级节点,如果没有则返回null
。attributeName
:发生变动的属性。如果设置了attributeFilter
,则只返回预先指定的属性。oldValue
:变动前的值。这个属性只对attribute
和characterData
变动有效,如果发生childList
变动,则返回null
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// Select the node that will be observed for mutations const targetNode = document.getElementById("some-id"); // Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = (mutationList, observer) => { for (const mutation of mutationList) { if (mutation.type === "childList") { console.log("A child node has been added or removed."); } else if (mutation.type === "attributes") { console.log(`The ${mutation.attributeName} attribute was modified.`); } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); // Start observing the target node for configured mutations observer.observe(targetNode, config); // Later, you can stop observing observer.disconnect(); // 保存所有没有被观察器处理的变动 var changes = mutationObserver.takeRecords(); // 停止观察 mutationObserver.disconnect();
addEventListener
第三个参数配置。
其次,第三个参数除了布尔值useCapture
,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。capture
:布尔值,如果设为true
,表示监听函数在捕获阶段触发,默认为false
,在冒泡阶段触发。once
:布尔值,如果设为true
,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false
。passive
:布尔值,设为true
时,表示禁止监听函数调用preventDefault()
方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false
。signal
:该属性的值为一个AbortSignal
对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。JS 中的
dispatchEvent
是一个用于在代码中生成和触发事件的方法。它可以用来模拟用户的操作,如点击或按键,或者自定义一些事件,如动画结束或数据更新。使用dispatchEvent
的步骤如下:- 首先,使用
Event
构造函数创建一个新的Event
对象,指定事件的类型和一些可选的属性,如是否可冒泡或是否可取消。 - 然后,使用
element.dispatchEvent()
方法将事件派发到一个指定的事件目标,如一个元素或一个文档。这个方法会同步地调用所有监听该事件的事件处理函数,并返回一个布尔值,表示事件是否被取消了。 - 最后,如果需要,可以在事件处理函数中使用
event.preventDefault()
方法来阻止事件的默认行为,或者使用event.stopPropagation()
方法来阻止事件的进一步传播。
下面是一个简单的例子,演示了如何使用
dispatchEvent
来触发一个自定义的build
事件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 创建一个新的Event对象,指定事件类型为build const event = new Event("build"); // 监听build事件 elem.addEventListener( "build", (e) => { // 在事件处理函数中执行一些操作 console.log("build event triggered"); }, false ); // 将build事件派发到elem元素 elem.dispatchEvent(event);
Event.stopImmediatePropagation
方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比Event.stopPropagation()
更彻底。- 首先,使用
鼠标事件的种类
- 点击事件
click
:按下鼠标(通常是按下主按钮)时触发。dblclick
:在同一个元素上双击鼠标时触发。mousedown
:按下鼠标键时触发。mouseup
:释放按下的鼠标键时触发。
- 移动事件
mousemove
:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。mouseenter
:鼠标进入一个节点时触发,进入子节点不会触发这个事件。mouseover
:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件。mouseout
:鼠标离开一个节点时触发,离开父节点也会触发这个事件。mouseleave
:鼠标离开一个节点时触发,离开父节点不会触发这个事件。
- 点击事件
键盘事件的种类
keydown
:按下键盘时触发。keypress
:按下有值的键时触发,即按下Ctrl
、Alt
、Shift
、Meta
这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown
事件,再触发这个事件。keyup
:松开键盘时触发该事件。
进度事件的种类
进度事件用来描述资源加载的进度,主要由AJAX
请求、<img>、<audio>、<video>、<style>、<link>
等外部资源的加载触发,继承了ProgressEvent
接口。它主要包含以下几种事件。abort
:外部资源中止加载时(比如用户取消)触发。如果发生错误导致中止,不会触发该事件。error
:由于错误导致外部资源无法加载时触发。load
:外部资源加载成功时触发。loadstart
:外部资源开始加载时触发。loadend
:外部资源停止加载时触发,发生顺序排在error、abort、load
等事件的后面。progress
:外部资源加载过程中不断触发。timeout
:加载超时时触发。
表单事件
input
:input
事件当<input>、<select>、<textarea>
的值发生变化时触发。对于复选框(<input type=checkbox>
)或单选框(<input type=radio>
),用户改变选项时,也会触发这个事件。另外,对于打开contenteditable
属性的元素,只要值发生变化,也会触发input
事件。select
:select
事件当在<input>、<textarea>
里面选中文本时触发。1
2
3
4
5
6
7
8
9
10// HTML 代码如下 // <input id="test" type="text" value="Select me!" /> var elem = document.getElementById("test"); elem.addEventListener( "select", function (e) { console.log(e.type); // "select" }, false );
change
事件当<input>、<select>、<textarea>
的值发生变化时触发。它与input
事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发,另一方面input
事件必然伴随change
事件。具体来说,分成以下几种情况。invalid
:用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid
事件。
拖拉事件
- 事件:
drag
、dragstart
DataTransfer
接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17div.addEventListener("drop", function (e) { e.preventDefault(); e.stopPropagation(); var fileList = e.dataTransfer.files; if (fileList.length > 0) { var file = fileList[0]; var reader = new FileReader(); reader.onloadend = function (e) { if (e.target.readyState === FileReader.DONE) { var content = reader.result; div.innerHTML = "File: " + file.name + "\n\n" + content; } }; reader.readAsBinaryString(file); } });
- 事件:
动态加载脚本防止浏览器假死
1
2
3
4
5
6["a.js", "b.js"].forEach(function (src) { var script = document.createElement("script"); script.src = src; script.async = false; document.head.appendChild(script); });
popstate
事件在浏览器的history
对象的当前记录发生显式切换时触发。注意,调用history.pushState()
或history.replaceState()
,并不会触发popstate
事件。该事件只在用户在history
记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用history.back()
、history.forward()
、history.go()
时触发。
该事件对象有一个state
属性,保存history.pushState
方法和history.replaceState
方法为当前记录添加的state
对象。1
2
3
4
5
6
7
8
9window.onpopstate = function (event) { console.log("state: " + event.state); }; history.pushState({ page: 1 }, "title 1", "?page=1"); history.pushState({ page: 2 }, "title 2", "?page=2"); history.replaceState({ page: 3 }, "title 3", "?page=3"); history.back(); // state: {"page":1} history.back(); // state: null history.go(2); // state: {"page":3}
hashchange
事件在URL
的hash
部分(即#
号后面的部分,包括#
号)发生变化时触发。该事件一般在window
对象上监听。hashchange
的事件实例具有两个特有属性:oldURL
属性和newURL
属性,分别表示变化前后的完整URL
。1
2
3
4
5
6
7
8
9// URL 是 http://www.example.com/ window.addEventListener("hashchange", myFunction); function myFunction(e) { console.log(e.oldURL); console.log(e.newURL); } location.hash = "part2"; // http://www.example.com/ // http://www.example.com/#part2
浏览器的弹窗
alert
:方法弹出的对话框,只有一个“确定”按钮,往往用来通知用户某些信息。用户只有点击“确定”按钮,对话框才会消失。对话框弹出期间,浏览器窗口处于冻结状态,如果不点“确定”按钮,用户什么也干不了。window.alert()
方法的参数只能是字符串,没法使用CSS
样式,但是可以用\n
指定换行。1
2window.alert("Hello World"); alert("本条提示\n分成两行");
prompt
:window.prompt()
方法弹出的对话框,提示文字的下方,还有一个输入框,要求用户输入信息,并有“确定”和“取消”两个按钮。它往往用来获取用户输入的数据。window.prompt()
的返回值有两种情况,可能是字符串(有可能是空字符串),也有可能是null
。具体分成三种情况。- 用户输入信息,并点击“确定”,则用户输入的信息就是返回值。
- 用户没有输入信息,直接点击“确定”,则输入框的默认值就是返回值。
- 用户点击了“取消”(或者按了
ESC
按钮),则返回值是null
。
1
var result = prompt("您的年龄?", 25);
confirm
:window.confirm()
方法弹出的对话框,除了提示信息之外,只有“确定”和“取消”两个按钮,往往用来征询用户是否同意。1
2
3
4
5
6var okay = confirm("Please confirm this message."); if (okay) { // 用户按下“确定” } else { // 用户按下“取消” }
window
窗口的相关的方法open
:新建一个浏览器窗口1
2
3
4
5var popup = window.open( "somepage.html", "DefinitionsWindows", "height=200,width=200,location=no,status=yes,resizable=yes,scrollbars=yes" );
close
:关闭当前的窗口stop
:停止资源的加载
window.navigator
属性指向一个包含浏览器和系统信息的Navigator
对象。脚本通过这个属性了解用户的环境信息。ajax
的基本使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37//接口地址 https://api.apiopen.top/getJoke //获取元素对象 const btn = document.querySelector("#btn"); btn.addEventListener("click", function () { //创建 Promise const p = new Promise((resolve, reject) => { //1.创建对象 const xhr = new XMLHttpRequest(); //2. 初始化 xhr.open("GET", "https://api.apiopen.top/getJoke"); //3. 发送 xhr.send(); //4. 处理响应结果 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { //判断响应状态码 2xx if (xhr.status >= 200 && xhr.status < 300) { //控制台输出响应体 resolve(xhr.response); } else { //控制台输出响应状态码 reject(xhr.status); } } }; }); //调用then方法 p.then( (value) => { console.log(value); }, (reason) => { console.warn(reason); } ); });
浏览器的相关知识
同源策略(
same-origin policy
)
要求:- 协议相同
- 域名相同
- 端口相同
但是,浏览器对于
cookie
的处理并没有完全遵守同源策略的规定。实际上,浏览器允许同一个域名下的不同端口之间共享cookie
。也就是说,如果一个网址是http://www.example.com:8000
,它可以读取和写入http://www.example.com:8001的cookie
,反之亦然。这是因为浏览器实现来说,“cookie
区分域,而不区分端口”。JSONP
的使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function addScriptTag(src) { var script = document.createElement("script"); script.setAttribute("type", "text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag("http://example.com/ip?callback=foo"); }; function foo(data) { console.log("Your public IP address is: " + data.ip); } foo({ ip: "8.8.8.8", });
CORS
的使用- 满足简单请求的条件:
- 请求方法是以下三种方法之一
GET
POST
HEAD
HTTP
的头信息不超出以下几种字段。Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
- 请求方法是以下三种方法之一
- 对于简单请求,浏览器直接发出
CORS
请求。具体来说,就是在头信息之中,增加一个Origin
字段。
1
2
3
4
5
6GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
1
2
3
4Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
- 相应预检请求的
node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 导入http模块 const http = require("http"); // 创建一个http服务器 const server = http.createServer((req, res) => { // 获取请求方法和请求头 const method = req.method; const headers = req.headers; // 如果请求方法是OPTIONS,表示是预检请求 if (method === "OPTIONS") { // 设置响应头,允许跨域请求,允许的请求方法和请求头 res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); res.setHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization" ); // 结束响应,不返回任何数据 res.end(); } else { // 如果请求方法不是OPTIONS,表示是正式请求 // 设置响应头,允许跨域请求,返回数据的类型和编码 res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Content-Type", "text/plain; charset=utf-8"); // 返回一些数据 res.end("这是一个跨域请求的响应"); } }); // 监听3000端口 server.listen(3000, () => { console.log("服务器启动成功,监听3000端口"); });
- 满足简单请求的条件:
读取生成
URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var droptarget = document.getElementById("droptarget"); droptarget.ondrop = function (e) { var files = e.dataTransfer.files; for (var i = 0; i < files.length; i++) { var type = files[i].type; if (type.substring(0, 6) !== "image/") continue; var img = document.createElement("img"); img.src = URL.createObjectURL(files[i]); img.onload = function () { this.width = 100; document.body.appendChild(this); URL.revokeObjectURL(this.src); }; } }; // HTML 代码如下 // <input type="file" onchange="onChange(event)"> function onChange(event) { var file = event.target.files[0]; var reader = new FileReader(); reader.onload = function (event) { console.log(event.target.result); }; reader.readAsText(file); }
ES6
块级作用域
let
与const
都不会发生变量提升,并且都是块级作用域
这是因为 var 和 let/const 在变量声明时有不同的机制。var 声明的变量会发生变量提升,也就是说,它们的声明会被提升到作用域的顶部,但是赋值操作不会被提升。所以在声明之前使用 var 变量,不会报错,但是变量的值是 undefined,而不是赋值后的值。例如:
1
2
console.log(a); // 输出undefined
var a = 10; // 声明和赋值都被提升
上面的代码相当于:
1
2
3
var a; // 声明提升
console.log(a); // 输出undefined
a = 10; // 赋值保留
而 let/const 声明的变量不会发生变量提升,也就是说,它们的声明和赋值都不会被提升。而且,在声明之前使用 let/const 变量会报错,因为 let/const 声明的变量在初始化之前存在一个暂时性死区(Temporal Dead Zone),在这个区域内,变量不能被访问或者赋值。例如:
1
2
console.log(b); // 报错,b未定义
let b = 10; // 声明提升,赋值不提升
寄生式继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 原型链继承
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function () {
console.log(this.name + " is eating.");
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类的构造函数
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 设置原型链,让 Dog 继承 Animal
Dog.prototype.constructor = Dog; // 修正构造函数指向
Dog.prototype.bark = function () {
console.log(this.name + " is barking.");
};
var dog = new Dog("Buddy", "Labrador");
dog.eat(); // 调用从 Animal 继承的方法
dog.bark(); // 调用 Dog 的方法
// ES6 类继承
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + " is eating.");
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类的构造函数
this.breed = breed;
}
bark() {
console.log(this.name + " is barking.");
}
}
const dog = new Dog("Buddy", "Labrador");
dog.eat(); // 调用从 Animal 继承的方法
dog.bark(); // 调用 Dog 的方法
手写一个 new 函数
关键在于如果函数返回了一个对象则以这个对象为
new
的返回结果。
1
2
3
4
5
const newFunc = (constructor, ...params) => {
const obj = Object.create(constructor.prototype);
const result = constructor.apply(obj, params);
return typeof result === "object" && result !== null ? result : obj;
};
数组的新增方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const array1 = ["a", "b", "c", "d", "e"];
// Copy to index 0 the element at index 3
console.log(array1.copyWithin(0, 3, 4));
// Expected output: Array ["d", "b", "c", "d", "e"]
// Copy to index 1 all elements from index 3 to the end
console.log(array1.copyWithin(1, 3));
// Expected output: Array ["d", "d", "e", "d", "e"]
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
// [4, 2, 3, 4, 5]
// -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}
// 将2号位到数组结束,复制到0号位
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]
// 对于没有部署 TypedArray 的 copyWithin 方法的平台
// 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]
["a", "b", "c"].fill(7);
// [7, 7, 7]
new Array(3).fill(7)[
// [7, 7, 7]
("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"}]
let arr = new Array(3).fill([]);
arr[0].push(5);
arr;
// [[5], [5], [5]]
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"
空值运算符
它的行为类似||
,但是只有运算符左侧的值为 null
或 undefined
时,才会返回右侧的值。
1
2
3
const headerText = response.settings.headerText ?? "Hello, world!";
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
Symbol
基本使用
1
2
3
4
5
6
7let s1 = Symbol("foo"); let s2 = Symbol("bar"); s1; // Symbol(foo) s2; // Symbol(bar) s1.toString(); // "Symbol(foo)" s2.toString(); // "Symbol(bar)" s1.description; // "foo"
获取
Symbol
对象属性名1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const 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)] let obj = { [Symbol("my_key")]: 1, enum: 2, nonEnum: 3, }; Reflect.ownKeys(obj); // ["enum", "nonEnum", Symbol(my_key)]
Symbol
值的重用有时,我们希望重新使用同一个
Symbol
值,Symbol.for()
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol
值。如果有,就返回这个Symbol
值,否则就新建一个以该字符串为名称的Symbol
值,并将其注册到全局。1
2
3
4
5
6
7
8let s1 = Symbol.for("foo"); let s2 = Symbol.for("foo"); s1 === s2; // true let s1 = Symbol.for("foo"); Symbol.keyFor(s1); // "foo" let s2 = Symbol("foo"); Symbol.keyFor(s2); // undefined
Proxy
- 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)
。Reflect
配合Proxy
实现观察者模式。1
2
3
4
5
6
7
8
9
10
11
12
13var obj = new Proxy( {}, { get: function (target, propKey, receiver) { console.log(`getting ${propKey}!`); return Reflect.get(target, propKey, receiver); }, set: function (target, propKey, value, receiver) { console.log(`setting ${propKey}!`); return Reflect.set(target, propKey, value, receiver); }, } );
Proxy.revocable()
方法返回一个可取消的Proxy
实例。1
2
3
4
5
6
7let target = {}; let handler = {}; let { proxy, revoke } = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo; // 123 revoke(); proxy.foo; // TypeError: Revoked
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
上获取默认行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
// 老写法
"assign" in Object; // true
// 新写法
Reflect.has(Object, "assign"); // true
Proxy(target, {
set: function (target, name, value, receiver) {
var success = Reflect.set(target, name, value, receiver);
if (success) {
console.log("property " + name + " on " + target + " set to " + value);
}
return success;
},
});
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log("get", target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log("delete" + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log("has" + name);
return Reflect.has(target, name);
},
});
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]); // 1
// 新写法
Reflect.apply(Math.floor, undefined, [1.75]); // 1
iterator
- 简单遍历器实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14var it = makeIterator(["a", "b"]); it.next(); // { value: "a", done: false } it.next(); // { value: "b", done: false } it.next(); // { value: undefined, done: true } function makeIterator(array) { var nextIndex = 0; return { next: function () { return nextIndex < array.length ? { value: array[nextIndex++], done: false } : { value: undefined, done: true }; }, }; }
return
方法的使用场合是,如果for...of
循环提前退出(通常是因为出错,或者有break
语句),就会调用return
方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return
方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// return() 方法必须返回一个对象 function readLinesSync(file) { return { [Symbol.iterator]() { return { next() { return { done: false }; }, return() { file.close(); return { done: true }; }, }; }, }; } // 以下两种情况会导致 return 方法的执行 // 情况一 for (let line of readLinesSync(fileName)) { console.log(line); break; } // 情况二 for (let line of readLinesSync(fileName)) { console.log(line); throw new Error(); }
generator
基本实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function* helloWorldGenerator() { yield "hello"; yield "world"; return "ending"; } var hw = helloWorldGenerator(); hw.next(); // { value: 'hello', done: false } hw.next(); // { value: 'world', done: false } hw.next(); // { value: 'ending', done: true } hw.next(); // { value: undefined, done: true }
throw
方法和return
方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45var 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 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 } 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 } g.next(); // { value: 5, done: false } g.next(); // { value: 7, done: true }
async 函数的实现原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const asyncFunc = () => {
return spawn(function* () {});
};
const spawn = (genF) => {
return new Promise((resolve, reject) => {
let gen = genF();
const step = (nextF) => {
let next;
try {
next = nextF();
} catch (error) {
reject(error);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
(v) => step(() => gen.next(v)),
(r) => step(() => gen.throw(r))
);
};
step(() => gen.next(undefined));
});
};
super
关键字的注意点
由于
this
指向子类实例,所以如果通过super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
ES6 属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
for...in
:循环遍历对象自身的和继承的可枚举属性(不含Symbol
属性)Object.keys(obj)
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol
属性)的键名Object.getOwnPropertyNames(obj)
:回一个数组,包含对象自身的所有属性(不含Symbol
属性,但是包括不可枚举属性)的键名Object.getOwnPropertySymbols(obj)
:返回一个数组,包含对象自身的所有Symbol
属性的键名Reflect.ownKeys(obj)
:返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是Symbol
或字符串,也不管是否可枚举
上述遍历,都遵守同样的属性遍历的次序规则:
- 首先遍历所有数值键,按照数值升序排列
- 其次遍历所有字符串键,按照加入时间升序排列
- 最后遍历所有
Symbol
键,按照加入时间升序排
1
2
Reflect.ownKeys({ [Symbol()]: 0, b: 0, 10: 0, 2: 0, a: 0 });
// ['2', '10', 'b', 'a', Symbol()]