实习的时候写的学习笔记 如有错误请各位大佬直接留言批判,防止错误的信息会误导他人!
---------------------------------------------------------------------------------
第一章----略过
第二章 let,const
1.let const无变量提升,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
例1:if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
例2:function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
使用typeof时 如变量未声明 则报 undefined 如变量在声明前使用typeof 则会报错
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
2.let const块级作用域
例1:var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
导致原因:内部的tmp变量存在变量提升 ,未声明使用则 undefined
如无块级作用域 在之前的for循环会存在变量泄露 for循环结束后 内部变量仍然存在
例如: var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
例如2:{{{{ {let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
每一层都是一个独立的作用域
注意:允许在块级作用域内声明函数。
函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
同时,函数声明还会提升到所在的块级作用域的头部。
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
{
let a = 'secret';
function f() {
return a;
}
}
// 块级作用域内部,优先使用函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址)
例如:
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
如果const数组 可以使用追加 等方法 ,但不不可以直接赋值
this 变量
this永远指向一个对象
1 全局作用域下 this指向windows,严格情况下 为undefined
2 函数调用时看 如何调用 如普通 构造函数 指向 windows'
3 如 new 构造函数 指向new 的对象
4 箭头函数中 由于箭头函数没有自己的this 所以 箭头函数中的this指向与箭头函数同级的上下文
5 call bind apply 指向借用的方法中的this
6 默认绑定情况 谁调用指向谁
可以看这位大佬的文章: https://www.jianshu.com/p/66eb9b21105d写的清晰明了
第三章 解构赋值
简单的解构
const [a, b, c] = [1, 2, 3];
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
解构可以 设置默认值,并且可以不完全解构
ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。。
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
对象解构赋值
变量必须与属性同名,才能取到正确的值
// 例一
let { log, sin, cos } = Math;
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
对象的解构赋值可以取到继承的属性
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);
const { foo } = obj1;
foo // "bar"
* 如果要解构一个已经定义过的变量需要谨慎
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
正确:
// 正确的写法
let x;
({x} = {x: 1});
因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系
数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
试验:
***************************有需要get的点************************
需要注意哪个是模式 (模式匹配不赋值)哪个是变量 (匹配模式可以赋值)
如果只是模式匹配则会报错 p is notdefnied
如果是变量 则会赋值
对象的解构也可以指定默认值。 默认值生效的条件是,对象的属性值严格等于undefined。
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
// 先将右边的数值和布尔值转化为对象,判断数值和布尔值得方法中是否有toString等方法
let {toString: s} = true;
s === Boolean.prototype.toString // true
无法对null和undefined进行解构会报错
会报错
对函数的传参可以进行解构 并且可以进行默认值的设置
例1:
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
例2:
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
在解构过程中最好避免使用圆括号
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号
解构的作用
1.交换变量
2.从json中提取数据
3.给函数的参数设置默认值,设置函数传参的形式 ,函数返回值的数据提取
4.map方法中传参的设置
5.const { SourceMapConsumer, SourceNode } = require("source-map");
第四章 字符串的扩展
1.字符的 Unicode 表示法
例如: "\u0061"
// "a"
这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。
"\uD842\uDFB7"
// "𠮷"
'\u{1F680}' === '\uD83D\uDE80'
// true
javaScript 字符串允许直接输入字符,以及输入字符的转义形式。
'中' === '\u4e2d'
U+005C:反斜杠(reverse solidus)
U+000D:回车(carriage return)
U+2028:行分隔符(line separator)
U+2029:段分隔符(paragraph separator)
U+000A:换行符(line feed)
形式
const json = '"\u2028"';
JSON.parse(json); // 可能报错
JSON 格式已经冻结(RFC 7159),没法修改了。为了消除这个报错,ES2019 允许 JavaScript 字符串直接输入 U+2028(行分隔符)和 U+2029(段分隔符)。
const PS = eval("'\u2029'");
0xD800到0xDFFF之间的码点,不能单独使用,必须配对使用。
模板字符串
1.以``标识 模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
2. 如果不想要这个换行,可以使用trim方法消除它。
3. 模板字符串中嵌入变量,需要将变量名写在${}之中。
`User ${user.name} is not authorized to do ${action}.`);
4.大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"
5.如果模板字符串中的变量没有声明,将报错。
6. 如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。
7. 如果模板字符串中的变量没有声明,将报错。
8. 模板字符串甚至还能嵌套
const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>
* 使用模板字符串时 可以直接使用或者使用函数式调用
let func = (name) => `Hello ${name}!`;
func('Jack') // "Hello Jack!"
字符串方法
1.String.fromCharCode()
将unicode码转化成字符串 可以识别大于0xFFFF的码点
方法有多个参数,则它们会被合并成一个字符串返回。
注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。
2.String.raw()
3.codePointAt()方法
能够正确处理 4 个字节储存的字符,返回一个字符的码点。
let arr = [...'𠮷a']; // arr.length === 2
arr.forEach(
ch => console.log(ch.codePointAt(0).toString(16))
);
// 20bb7
// 61
codePointAt()方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("𠮷") // true
is32Bit("a") // false
4.normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。
normalize方法可以接受一个参数来指定normalize的方式,参数的四个可选值如下。
NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。
NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。)
NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。
'\u004F\u030C'.normalize('NFC').length // 1
'\u004F\u030C'.normalize('NFD').length // 2
5.
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
支持第二个参数,startsWith,includes表示开始搜索的位置。endsWith()表示在前n个位置搜索
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
6. repeat() ,repeat方法返回一个新字符串,表示将原字符串重复n次。
如果参数是正数小数 向下取整
参数是 NAN 等同 0
参数是 负数 或者 Infinity 报错
参数是 -1 ~0 等同0
7 . padStart(n,'234323') padEnd() 在字符串的头部/尾部补全(参数)以满足 n
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
上面代码中,padStart()和padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789')
// '0123456abc'
如果省略第二个参数,默认使用空格补全长度
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
8. trimStart() trimEnd() trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
9. matchAll() 匹配正则
第六章: 数值的扩展
数值的方法
1.Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
2.Number.isNaN一个 用来检查一个数字是否为NAN
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
3.parseInt() 和parseFloat()
将数值转化为一个整形和浮点型
4. Number.isInteger()用来判断一个数值是否为整数。
Number.isInteger(25) // true
Number.isInteger(25.1) // false
JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。
如果传参不是数值 则返回false
**注意,由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。
Number.isInteger(3.0000000000000002) // true
**如果一个数值的绝对值小于Number.MIN_VALUE(5E-324),即小于 JavaScript 能够分辨的最小值,会被自动转为 0。这时,Number.isInteger也会误判。
Number.isInteger(5E-324) // false
Number.isInteger(5E-325) // true
5. Number.EPSILON表示 1 和大于1 之间的一个极小的常量
对于 64 位浮点数来说, 大于 1 的最小浮点数相当于二进制的1.00..001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方。
Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"
6.Number.EPSILON可以用来设置“能够接受的误差范围”。比如,误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2))
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
1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // true
7.Number.MAX_SAFE_INTEGER 表示js能表示的最大限数值2 的53次方
8. Number.MIN_SAFE_INTEGER表示 js能表示的最小的数值-2的53次方
Number.isSafeInteger()表示数字是否在这个范围内
Number.isSafeInteger('a') // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false
Number.isSafeInteger(Infinity) // false
Number.isSafeInteger(-Infinity) // false
MATH的扩展
1.Math.trunc 方法用于去除一个数的小数部分,返回整数部分
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc内部使用Number方法将其先转为数值。
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
兼容处理
Math.trunc = Math.trunc || function(x) {return x < 0 ? Math.ceil(x) : Math.floor(x);};
2.Math.sign
方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
参数为正数,返回+1;
参数为负数,返回-1;
参数为 0,返回0;
参数为-0,返回-0;
其他值,返回NaN。
3. Math.cbrt计算一个数字的立方根
对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
4.Math.clz32() 将一个数字转化为32位的2进制数 返回这个32位2进制数从第0位到非0数字前有多少个前导0
对于小数,Math.clz32方法只考虑整数部分。
对于空值或其他类型的值,Math.clz32方法会将它们先转为数值,然后再计算。
Math.clz32() // 32
Math.clz32(NaN) // 32
Math.clz32(Infinity) // 32
Math.clz32(null) // 32
Math.clz32('foo') // 32
Math.clz32([]) // 32
Math.clz32({}) // 32
Math.clz32(true) // 31
5.Math.fround 返回一个32位单精度浮点数值,可以用来判断精度
// 未丢失有效精度
Math.fround(1.125) // 1.125
Math.fround(7.25) // 7.25
// 丢失精度
Math.fround(0.3) // 0.30000001192092896
Math.fround(0.7) // 0.699999988079071
Math.fround(1.0000000123) // 1
对于 NaN 和 Infinity,此方法返回原值。对于其它类型的非数值,Math.fround 方法会先将其转为数值,再返回单精度浮点数。
参数的绝对值大于 224
6.Math.hypot(3,4) //5 此方法返回传参的平方和的平方根 3*3+4*4 = 5*5
如传参不是数值,则会将传参先转化为数值,如果传参不能转化为数值 则返回NaN
7. Math.expm1(x) // ex - 1
8。Math.log1p(x) 即Math.log(1 + x) 如果x<-1 返回NaN
9. Math.log2(x)返回以 2 为 底的x的对数。如果x小于 0,则返回 NaN。
10.Math.log10(x)返回以 10 为底的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)
11. 新增指数运算符 **
2**2=4
2**3=8
ES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示
1234 // 普通整数
1234n // BigInt
// BigInt 的运算
1n + 2n // 3n
0b1101n // 二进制
0o777n // 八进制
0xFFn // 十六进制
BigInt 与普通整数是两种值,它们之间并不相等。
42n === 42 // false
typeof运算符对于 BigInt 类型的数据返回bigint。
可以使用负号但是不可以使用正号
必须有后缀n
使用typeof bigint
转化规则BigInt(123) // 123n
BigInt()构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。
new BigInt() // TypeError
BigInt(undefined) //TypeError
BigInt(null) // TypeError
BigInt('123n') // SyntaxError
BigInt('abc') // SyntaxError
上面代码中,尤其值得注意字符串123n无法解析成 Number 类型,所以会报错。
参数如果是小数,也会报错。
BigInt(1.5) // RangeError
BigInt('1.5') // SyntaxError
函数的扩展
传参问题
1.可以给函数的传参设置默认值
三元表达式 如果变量的值赋为false 或者undefined 但是对应的布尔值为false,则该赋值不起作用。
参数变量是默认声明的,所以不能用let或const再次声明。 函数不能有同名参数。
如果传参是一个表达式,则每次使用函数的时候都会重新计算这个表达式
function foo(p = x + 1) {
console.log(p);
}
2.可以和解构赋值结合使用
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
上面代码指定,如果没有提供参数,函数foo的参数默认为一个空对象。
function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
// 报错
上面代码中,如果函数fetch的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method);
}
fetch('http://example.com')
// "GET"
如果传参是一个对象,对象中多个属性赋默认值,但是调用的时候没有在传参中放入空对象,会出错,更改可以写
function( {age:555}={} ){ }
// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
function m2({x, y} = { 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]
设置传参默认值时 需要在尾部赋值,否则赋默认值变量后仍然有变量的话 无法省略已经赋默认值的变量
给默认值赋undefined 会触发默认值,null不会有此效果
函数的传参如果设置默认值,length的属性将为没赋默认值的传参个数,length失真
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
如果传参设置默认值,则传参内自己形成一个作用域
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
**********************有需要get的点**************************
let foo = 'outer';
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}
bar(); // outer
上面代码中,函数bar的参数func的默认值是一个匿名函数,返回值为变量foo。函数参数形成的单独作用域里面,并没有定义变量foo,所以foo指向外层的全局变量foo,因此输出outer。
参数的默认值不是在定义时执行,而是在运行时执行。
rest参数
rest参数 function ma(...rest){ console.log(rest) }
ma(1,4,5,5,6,8)
将传参以...rest的形式转为一个数组 数组名为 rest 以替换arguments(类数组对象)rest可以使用数组的方法
调用时可以传入不限个数的参数
1. 函数.name 返回函数的函数名字
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz
Function构造函数返回的函数实例,name属性的值为anonymous。
(new Function).name // "anonymous"
bind返回的函数,name属性值会加上bound前缀。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
箭头函数
基本使用方法
圆括号设置传参 =>{ return 语句} 如果只有一条语句 默认return
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
大括号被解释称代码块 所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
let getTempItem = id => ({ id: id, name: "Temp" });
function test(){console.log(12344)}
var ma = test
ma()
var rong = test() // 返回的执行结果
注意事项:
箭头函数没有自己的this,所以 箭头函数内部的this指向上下文
不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
箭头函数内无法用arguments 使用rest参数代替
不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
箭头函数根本没有自己的this,导致内部的this就是外层代码块的this
由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
* 不适用场合:
1.给对象的属性定义方法
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
} // this会指向全局
箭头函数可以嵌套使用
const pipeline = (...funcs) =>
val => funcs.reduce((a, b) => b(a), val);
下面是一个部署管道机制(pipeline)的例子,即前一个函数的输出是后一个函数的输入。
const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);
addThenMult(5)
// 12
函数尾调用
函数中最后一步调用其他函数
尾调用不一定出现在函数尾部,只要是最后一步操作即可。
尾调用优化-------在函数的最后一步进行调用函数 不用保存外层函数内的其他变量,外部函数的调用帧也不用保存,只保存内部函数的调用帧,节省内存。
只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。
尾递归
最后一步进行自调用,防止栈溢出
阶乘:
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
斐波那切数列
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
es6 中函数参数可以有尾逗号与数组和对象保持一致!!
es2019中 Function.prototype.toString()会返回原函数中每一处,注释 空格都会原样返回
es6中 try-catch要求 catch(err)必须有参数 es2019中可以省略参数
第八章 数组的扩展
扩展运算符
... 相当于rest参数的逆运算 将数组和对象转化为参数序列
和rest参数相结合
function push(array, ...items) {
array.push(...items);
}扩展运算符 ... 相当于rest参数的逆运算 将数组和对象转化为参数序列,也可以解构字符串
和rest参数相结合
function push(array, ...items) {
array.push(...items);
}
扩展运算符后面还可以放置表达式。
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];
注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
var args = [0, 1, 2];
f.apply(null, args);
// ES6的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);//函数调用
// ES5 的写法
Math.max.apply(null, [14, 3, 77])//替代apply方法
// ES6 的写法
Math.max(...[14, 3, 77])
//push使用
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
扩展运算符的应用
可以用于复制数组 由于数组是复杂类型 的 直接 var arr1 = arr2 这样复制的只是复制了数组的数据存放地址
使用扩展运算符 var arr1= [...arr2] 可以复制数组中的每一个数据
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
两种写法都是 a2复制了a1的数据
可以使用数组 进行数组数据的拼接方法都是浅拷贝,
var arr1 = [...arr2,...arr3,...arr4];
a = list[0], rest = list.slice(1)
[a, ...rest] = list //数组的解构
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];
扩展运算符还可以将字符串转为真正的数组。
[...'hello']
// [ "h", "e", "l", "l", "o" ]
那就是能够正确识别四个字节的 Unicode 字符。
'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3
四个字节的 Unicode 字符的函数,都有这个问题。因此,最好都用扩展运算符改写。
let str = 'x\uD83D\uDE80y';
str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'
[...str].reverse().join('')
// 'y\uD83D\uDE80x'
根据unicode码倒序字符串
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
数组的扩展
Array.from
将类数组对象 和可遍历(iterable)的对象转化为数组,
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
上面代码中,字符串和 Set 结构都具有 Iterator 接口,因此可以被Array.from转为真正的数组。
所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
下面的例子将数组中布尔值为false的成员转为0。
Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]
return Array.from(arguments, value => typeof value)
Array.from()可以将各种值转为真正的数组,并且还提供map功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。
Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array() // []注意点
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
上面代码中,Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组
copyWithin()
数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。
一个看起来没什么用 用起来还容易出错的破方法
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数。
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
// 将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]
数组实例的 find() 和 findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1, 4, -5, 10].find((n) => n < 0)
// -5
find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
fill()方法
用来填充数组,或者数组初始化
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
数组实例的 entries(),keys() 和 values()
用于遍历数组,keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
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"
includes()
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。
[NaN].indexOf(NaN)
// -1
includes使用的是不一样的判断算法,就没有这个问题。
[NaN].includes(NaN)
// true
另外,Map 和 Set 数据结构有一个has方法,需要注意与includes区分。
Map 结构的has方法,是用来查找键名的,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。
Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。
数组实例的 flat(),flatMap()
flat()用来扁平化数组, 数组的成员有可能还是数组,使其扁平化,默认拉平一层,可以通过传参使其扁平化几层
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
如果原数组有空位,flat()方法会跳过空位。
flatMap()传参为一个函数,对函数进行执行,返回的结果进行扁平化处理后返回数组,只能展开一层
flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。
arr.flatMap(function callback(currentValue[, index[, array]]) {
// ...
}[, thisArg])
flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this。
数组的空位置
ES6 则是明确将空位转为undefined。
扩展运算符(...)也会将空位转为undefined。copyWithin()会连空位一起拷贝。
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
fill()会将空位视为正常的数组位置。
new Array(3).fill('a') // ["a","a","a"]
for...of循环也会遍历空位。
第九章 对象的扩展
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
1. let propKey = 'foo';
1let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
2. let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
3. let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
对象方法的name返回值是函数名
有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous。
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
for...in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
对象的枚举
我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替。
ES6 一共有 5 种方法可以遍历对象的属性。
(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 键,按照加入时间升序排列。
super 关键字
————————————————————————————
对象与扩展运算符
1.由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
2. 解构赋值必须是最后一个参数,否则会报错。
3. 解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
4.ES6 规定,变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式,
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3
改后 :let { x, ...{ y, z } } = o;
对象的扩展运算符和数组中的一样
1.对象是特殊的数组,可以将对象展开
2.扩展运算符如果和空对象组合无影响,无效果,如果扩展运算符后面不是对象 ,则会自动转化为对象
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
对象的扩展运算符等同于使用Object.assign()方法。Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。
// 写法一
// 写法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
链判断运算符
判断一个对象是否为空需要用三元运算符
es2020采用了链判断运算符?.
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
上面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。
if (myForm.checkValidity?.() === false) {
// 表单校验失败
return;
}
es2020 链判断运算符的短路机制等 https://es6.ruanyifeng.com/#docs/object
对象方法的扩展
1.Object.is()es5对比两个对象是否相等使用===和== 后者会进行隐式转换 前者NaN不等于自己。Object.is()的对比机制和===相似 一是+0不等于-0,二是NaN等于自身。
2.Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
1. 如果只有一个参数,Object.assign会直接返回该参数。
2. 如果参数不是对象则先转化为对象。 null 和undefined无法转化为对象 报错
3. 如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
4.如果source是数字或者布尔类型的 则不会被拷贝
5. Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
6.属性名为 Symbol 值的属性,也会被Object.assign拷贝。
注意点
1.实行浅拷贝
2.同名属性会覆盖
3.对于数组的处理会将数组看成一个对象
4.如果赋值的是一个取值函数,将对函数取值后赋值
可用于对象属性和方法的追加
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
克隆对象 :采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。
function clone(origin) {
return Object.assign({}, origin);
}
合并对象:
const merge =
(...sources) => Object.assign({}, ...sources);
为属性赋默认值:
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}
Object.getOwnPropertyDescriptors()
方法会返回某个对象属性的描述对象(descriptor)
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 } }
该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
Object.getOwnPropertyDescriptors()方法可以实现一个对象继承另一个对象。
const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
})
);
es5对于对象的继承都是使用原型链来进行继承
es6Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
Object.setPrototypeOf()
Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方 法。
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
const o = Object.setPrototypeOf({}, null);
Object.getPrototypeOf()
用于获取原型的方法 ,传入的参数如果不是对象将会自动转化为对象
如果参数是undefined或null,它们无法转为对象,所以会报错。
Object.keys()遍历对象的键值,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for...of循环使用。
let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
Object.values()遍历对象的键值
如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo')
// ['f', 'o', 'o']
Object.entries() 遍历对象中所有可以遍历的键值对
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
果原对象的属性名是一个 Symbol 值,该属性会被忽略。
Object.entries方法的另一个用处是,将对象转为真正的Map结构。
该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }
第十章 symbol
symbol用来设定唯一的独一无二的值。是第七种数据类型
注意,Symbol函数前不能使用new命令,
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
1.Symbol 值不能与其他类型的值进行运算,会报错。
2.Symbol值可以显示字符串
3.另外,Symbol 值也可以转为布尔值,但是不能转为数值。
Symbol可以添加一个描述
const sym = Symbol('foo');
sym.description // "foo"
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。防止改写 误改
et mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
注意,Symbol 值作为对象属性名时,不能用点运算符。
在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
定义常量 保证各个常量不相等
const log = {};
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');
可用作消除魔术字符串
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]
Symbol.for()有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。
使某个函数永远的返回值为同一个值
为了防止这种情况出现,我们就可以使用 Symbol。
// mod.js
const FOO_KEY = Symbol.for('foo');
function A() {
this.foo = 'hello';
}
if (!global[FOO_KEY]) {
global[FOO_KEY] = new A();
}
module.exports = global[FOO_KEY];
第十一章 Set Map
set数据结构 用来表示唯一的值,类似拥有独一无二数据的数组
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
数组去重 的 方法
[... new Set()]
Array.from(new Set([4,5,6,34,5,3,34,4,2,4]))
字符串去重
[...new Set('ababbc')].join('')
Set结构内部不会进行类型转换,’5‘和5 不想等,内部的对比机制与 === 相似, 但是 ===认为 NaN 不等于自身,但是 Set 认为NaN等于NaN
Set 内部认为 两个空对象 是不相同的
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
Set实例的属性:
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。
Set实例方法
四个操作方法。
1.Set.prototype.constructor,默认Set函数 生成独一无二的可遍历的数据结构
2.Set.prototype.size, 返回Set数据结构的大小
3.Set.prototype.add, 表示添加某个值,返回Set数据结构
4.Set.prototype.delete, 删除某个值,返回布尔类型,表示成功与否
5. Set.prototype.has, 返回布尔类型,表示是否为Set数据结构
6.Set.prototyoe.clear, 无返回值,表示清除 Set结构
// 对象的写法
const properties = {
'width': 1,
'height': 1
};
if (properties[someName]) {
// do something
}
// Set的写法
const properties = new Set();
properties.add('width');
properties.add('height');
if (properties.has(someName)) {
// do something
}
Array.from方法可以将 Set 结构转为数组。(因为Set的数据结构为类数组并不是真正的数组)
需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
4种Set实例的遍历方法
7.Set.prototype.keys, 遍历Set数据结构的键名
8. Set.prototype.values, 遍历Set数据结构的键值
9. Set.prptptypr.entries, 遍历Set数据结构的所有键值对
10. Set.prptotype.forEach, 使用回调函数遍历每个成员
由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。
数组的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}
因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
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}
同步改变set数据
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。WeakSet 在es6中是不可以遍历的
WeakSet 结构有以下三个方法。无clear方法
WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
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"
任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
对同一个键赋值多次,会覆盖
对未知的键进行读取,会返回undefined
只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
Map.prototype.set(key,value) 设置一个Map结构,如果已有key 则更新,如过无值就新生成 Map结构 返回整个Map结构
Map.prototype.get(key),返回对应key的value值,如果没有key 则返回undefined
Map.prototype.has(key),返回 布尔类型, 表示Map结构中是否有这个key值
Map.prototype.delete(key),返回 布尔类型 ,表示是否成功删除这个key值
Map.prototype.clear(), 无返回值, 清除Map所有成员
Map.prototype.keys():返回键名的遍历器。
Map.prototype.values():返回键值的遍历器。
Map.prototype.entries():返回所有成员的遍历器。
Map.prototype.forEach():遍历 Map 的所有成员。
遍历顺序就是插入顺序
Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...)。
Map本身是类数组,并没有数组的方法,但是可以通过扩展运算符 或者数组的fliter和map方法进行遍历
1 可以 数组转化Map
2 Map 转化数组 [...new Map ()]
Map([1,2,3,3],[54,e5rt,3])
对象转为 Map 可以通过Object.entries()。
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
Map转化Json
当 数据的key和value 都是字符串格式的
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
当 数据的key 和value 不都是字符串格式的
另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
WeakMap结构与Map结构类似,也是用于生成键值对的集合。
weakMap只接受对象为键名
如果你要往对象上添加数据,又不想干扰垃圾回收机制,防止其他变量对于 之前对象的引用 而引起之前的对象不会被回收,采取weakMap进行赋值的变量 以后的引用只是弱引用 不会影响变量的垃圾回收机制。
第十二章 Proxy
proxy可以对外界的访问进行一个拦截过滤和改写。
var 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);}});
var proxy = new Proxy( target, handler )
target表示所对的目标对象进行拦截,handle表示 如果拦截的设置
注意,要使得proxy起作用,必须针对proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
const pro = new Proxy({}, {
get: function() {
console.log('Proxy');
}
})
将 Proxy 对象,设置到object.proxy
属性,从而可以在object对象上调用。
var object = { proxy: new Proxy(target, handler) };
proxy可以设置多个拦截操作,如果未设置拦截操作的对象将按照原有方式进行处理
Proxy 拦截方法
get(target, propKey, receiver):拦截对象属性的读取,接受三个参数 目标函数,属性名 proxy实例本身 比如proxy.foo和proxy['foo']。拦截的操作定义在prototype上 proxy对其进行操作后,对象也会生效,具有继承效果。
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)。
const proxy = new Proxy({}, {get: function(target, key, receiver) {return receiver;}});
proxy.getReceiver === proxy // true
上面代码中,proxy对象的getReceiver属性是由proxy对象提供的,所以receiver指向proxy对象。