let声明:
声明一个变量,有暂时性死区、无变量提升有块级作用域
// 声明的是一个变量,变量在声明且赋值后,能更改其赋的值
let a = 1;
a = 2;
console.log(a); // 2
// 暂时性死区:只要代码块内有let它所声明的变量就“绑定”这个区域,不再受外部的影响。
var a = 3;
{
a = 123; // Identifier 'a' has already been declared
let a;
}
// 无变量提升
console.log(a); // a is not defined
let a = '张三';
// 块级作用域
{
let n = '张三'
}
console.log(n); // n is not defined
const声明:
声明一个常量,有暂时性死区、无变量提升有块级作用域
// 声明一个常量,一旦声明必须立即初始化,一旦初始化之后,它的值不能再进行任何修改!
const a = 2;
a = 4;// Assignment to constant variable.(赋值给常数变量。)
// 初始值为对象时为特殊情况
const obj = {};
obj.name = "张三";
obj.age = 24;
console.log(obj); // {name: "张三", age: 24}
// 初始值为数组时也是特殊情况
const arr = [];
arr[0] = 1;
arr[1] = 2;
console.log(arr); // [1, 2]
// 其他的同let是一样的
var声明:
var声明一个变量,无暂时性死区,有变量提升无块级作用域,只有函数作用域和全局作用域
// 声明一个变量
var a = 1;
a = 2;
console.log(a); // 2
// 无暂时性死区
var a = 3;
{
a = 123;
var a;
}
console.log(a); // 123
// 变量提升
console.log(a); // undefined
var a = 3;
// 全局作用域:
var a = 3;
function fun () {
console.log(a);
}
fun(); // 3
// 函数作用域
function fun () {
var a = 3;
console.log(a);
}
fun(); // 3
console.log(a); // a is not defined
补充:对象window中声明的变量,具有全局作用域,在函数中声明的变量具有函数作用域,在代码块中用let、const声明的变量具有块级作用域。
这个东西只说用法,没有太多概念性的东西
交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
console.log(x,y); // 2 1
从函数返回多个值
// 返回一个数组
function fun () {
return [1, 2, 3];
}
let [x, y, z] = fun();
console.log(x, y, z); // 1, 2, 3
// 返回一个对象
function fun () {
return {
name: '张三',
age: 24,
sex: '女'
}
}
let {name, age, sex} = fun();
console.log(name, age, sex); // 张三 24 女
函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
提取 JSON 数据
let data = {
name: '张三',
age: 24
};
let {name, age} = data;
console.log(name, age); // 张三 24
函数参数的解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
遍历 Map 结构
for (let [key, value] of map) {
console.log(key + " is " + value);
}
输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
字符串的遍历器接口
for (let codePoint of 'foo') {
console.log(codePoint)
}
模板字符串
let name = '张三';
let str = `${name}的女朋友`;
console.log(str); // 张三的女朋友
includes() :
返回布尔值,表示是否找到了参数字符串。
let str = "张三的女朋友";
console.log(str.includes("张三")); // true
补充: indexOf如果查找到了就返回索引,未找到则返回-1
startsWith() :
返回布尔值,表示参数字符串是否在原字符串的头部。
let str = "张三的女朋友";
console.log(str.startsWith("张")); || console.log(str.indexOf("张") == 0); // true
endsWith():
返回布尔值,表示参数字符串是否在原字符串的尾部。
let str = "张三的女朋友";
console.log(str.endsWith("友")); || console.log(str.indexOf("友") == str.length - 1); // true
repeat():方法返回一个新字符串,表示将原字符串重复n次。
let str = '6';
console.log(str.repeat(3)); // 666
padStart()用于头部补全
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
padEnd():用于尾部补全
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
关于去除空格
(1). trim():
方法会从一个字符串的两端删除空白字符。
let str = ' 10 10 ';
console.log(str); // ' 10 10 '
console.log(str.trim()); // '10 10'
// 如果要去除所有空格
let str = ' 10 10 ';
console.log(str.replace(/ /g, '')); // 1010
(2). trimStart():
消除字符串头部的空格
let str = ' 10 10 ';
console.log(str); // ' 10 10 '
console.log(str.trimStart()); // '10 10 '
(3). trimEnd()
消除尾部的空格
let str = ' 10 10 ';
console.log(str); // ' 10 10 '
console.log(str.trimEnd()); // ' 10 10'
replaceAll():
可以一次性替换所有匹配。
'aabbcc'.replaceAll('b', '_') == 'aabbcc'.replace(/b/g, '_') // true
matchAll():返回一个正则表达式在当前字符串的所有匹配
let str = 'aabb';
console.log([...str.matchAll(/a/g)]);//[["a", index: 0, input: "aabb", groups: undefined] ["a",
index: 1, input: "aabb", groups: undefined]]
y 修饰符
“粘连”(sticky)修饰符。后一次匹配都从上一次匹配成功的下一个位置开始,y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
先行断言
x只有在y前面才匹配
let s = 'xyzx';
let RE = /x(?=y)/;
RE.exec(s); // ["x", index: 0, input: "xyzx", groups: undefined]
注:以上可以看出匹配到的结果索引在第一个的位置
先行否定断言
x只有不在y前面才匹配
let s = 'xyzx';
let RE = /x(?!y)/;
RE.exec(s); // ["x", index: 3, input: "xyzx", groups: undefined]
注:以上可以看出匹配到的结果索引在第四个的位置
后行断言
z只有在y后面才匹配
let s = 'xyzx';
let RE = /(?<=y)z/;
RE.exec(s); // ["z", index: 2, input: "xyzx", groups: undefined]
注:以上可以看出匹配到的结果索引在第3个的位置
后行否定断言
z只有不在y后面才匹配
let s = 'xyyiizx';
let RE = /(?<!y)z/;
RE.exec(s); // ["z", index: 5, input: "xyyiizx", groups: undefined]
注:以上可以看出匹配到的结果在第6个位置
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES2018 引入后行断言
flags返回正则表达式的修饰符。
/xyz/i.flags // "i"
sticky表示是否设置了y修饰符。
/xyz/y.sticky // true
数值分隔符
ES2021,允许 JavaScript 的数值使用下划线(_)作为分隔符。
1_000_000_000_000 // 1000000000000
数值分隔符有几个使用注意点
- 不能放在数值的最前面(leading)或最后面(trailing)。
- 不能两个或两个以上的分隔符连在一起。
- 小数点的前后不能有分隔符。
- 科学计数法里面,表示指数的`e`或`E`前后不能有分隔符。
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
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
Number.parseInt()
ES6 将全局方法parseInt(),移植到Number对象上面,行为完全保持不变。为了逐步减少全局性方法,使得语言逐步模块化。
Number.parseInt('12.34') // 12
注:用来获取一个浮点数的整数部分
Number.parseFloat()
ES6 将全局方法parseFloat(),移植到Number对象上面,行为完全保持不变。为了逐步减少全局性方法,使得语言逐步模块化。
Number.parseFloat('123a') // 123
注:用来截取数值部分
Number.isInteger()
用来判断一个数值是否为整数。
Number.isInteger(25) // true
Number.isInteger(25.1) // false
整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。
Number.isInteger(25) // true
Number.isInteger(25.0) // true
由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。
Number.isInteger(3.0000000000000002) // true
安全整数## 和 Number.isSafeInteger()
JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。
Number.MAX_SAFE_INTEGER === 9007199254740991// true
Number.MIN_SAFE_INTEGER === -9007199254740991// true
Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。
Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false
Math.trunc()
用于去除一个数的小数部分,返回整数部分。
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc('123.456') // 123 // 内部使用`Number`方法将其先转为数值。
Math.sign()
用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
- 参数为正数,返回`+1`;
- 参数为负数,返回`-1`;
- 参数为 0,返回`0`;
- 参数为-0,返回`-0`;
- 其他值,返回`NaN`。
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
Math.cbrt()
用于计算一个数的立方根。
Math.cbrt(-1) // -1
Math.cbrt(0) // 0
Math.cbrt(1) // 1
Math.imul()
返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
Math.imul(2, 4) // 8
Math.imul(-1, 8) // -8
Math.imul(-2, -2) // 4
Math.hypot
返回所有参数的平方和的平方根。
Math.hypot(3, 4); // 5
注:勾三股四玄五
对数方法
1.`Math.expm1(x)`返回 ex - 1,即`Math.exp(x) - 1`。
2. `Math.log1p(x)`方法返回`1 + x`的自然对数,即`Math.log(1 + x)`。如果`x`小于-1,返回`NaN`。
3. `Math.log10(x)`返回以 10 为底的`x`的对数。如果`x`小于 0,则返回 NaN。
4. `Math.log2(x)`返回以 2 为底的`x`的对数。如果`x`小于 0,则返回 NaN。
双曲函数方法
- `Math.sinh(x)` 返回`x`的双曲正弦(hyperbolic sine)
- `Math.cosh(x)` 返回`x`的双曲余弦(hyperbolic cosine)
- `Math.tanh(x)` 返回`x`的双曲正切(hyperbolic tangent)
- `Math.asinh(x)` 返回`x`的反双曲正弦(inverse hyperbolic sine)
- `Math.acosh(x)` 返回`x`的反双曲余弦(inverse hyperbolic cosine)
- `Math.atanh(x)` 返回`x`的反双曲正切(inverse hyperbolic tangent)
BigInt 数据类型
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity。ES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n。
1234 // 普通整数
1234n // BigInt
// BigInt 的运算
1n + 2n // 3n
BigInt 同样可以使用各种进制表示,都要加上后缀n。
0b1101n // 二进制
0o777n // 八进制
0xFFn // 十六进制
BigInt 与普通整数是两种值,它们之间并不相等。
42n === 42 // false
typeof运算符对于 BigInt 类型的数据返回bigint。
typeof 123n // 'bigint'
BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突。
-42n // 正确
+42n // 报错
JavaScript 以前不能计算70的阶乘(即70!),因为超出了可以表示的精度。
let p = 1;
for (let i = 1; i <= 70; i++) {
p *= i;
}
console.log(p); // 1.197857166996989e+100
现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。
let p = 1n;
for (let i = 1n; i <= 70n; i++) {
p *= i;
}
console.log(p); // 11978571...00000000n
数学运算方面,BigInt 类型的+、-、*和**这四个二元运算符,与 Number 类型的行为一致。除法运算/会舍去小数部分,返回一个整数。
9n / 5n
// 1n
// 例外:
- 不带符号的右移位运算符`>>>`
- 一元的求正运算符`+`
BigInt 不能与普通数值进行混合运算。
1n + 1.3 // 报错
BigInt 与字符串混合运算时,会先转为字符串,再进行运算。
'' + 123n // "123"
函数参数的默认值
function fn(x, y = 1) {
console.log(x + y);
}
fn(10); // 11
// 注:通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
rest 参数
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
3.严格模式
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 报错
function doSomething(a, b = a) {
'use strict';
// code
}
// 报错
const doSomething = function ({a, b}) {
'use strict';
// code
};
// 报错
const doSomething = (...a) => {
'use strict';
// code
};
const obj = {
// 报错
doSomething({a, b}) {
'use strict';
// code
}
};
name 属性
function foo() {}
foo.name // "foo"
箭头函数
const fn = x => x+1; // 最简写法
// 等价于:
const fn = (x) => {
return x+1;
}
注:如果只有一个函数形参,()可省略;如果箭头函数只有一行语句{}和return可省略;
特殊情况
//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
特别注意!!!(此处面试考了几百遍,敲黑板!!!)
(1)箭头函数没有自己的`this`对象,内部的`this`就是定义时上层作用域中的`this`。。
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用`new`命令,否则会抛出一个错误。
(3)不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数。
扩展运算符
复制数组
const a1 = [1, 2];
const a2 = [...a1]; // [1, 2];
注:返回的是一个新数组
合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const newArr = [...arr1, ...arr2]; //['a', 'b', 'c'];
// `a3`和`a4`是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];
a3[0] === a1[0] // true
a4[0] === a1[0] // true
将字符串转为真正的数组。
[...'hello']
// [ "h", "e", "l", "l", "o" ]
数组去重
[...new Set(arr)];
注:如果是数组对象,不能使用该方法去重
Array.from()
用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
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']
Array.of()
用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
find()
用于找出第一个符合条件的数组成员。
[1, 4, -5, 10, -10].find(n => n < 0) // -5
findIndex()
返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
[1, 4, -5, 10, -10].findIndex(n => n == 1) // 0
fill()
使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
注:第一个参数是用什么填充,第二个是填充起始位置的索引,第三个参数是结束位置的索引
includes()
方法返回一个布尔值,表示某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
keys()
是对键名的遍历,数组的键名为索引。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
values()
是对键值的遍历
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
entries()
是对键值对的遍历。
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
flat()
用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
flatMap()
flatMap()方法对原数组的每个成员执行一个函数
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
超级重点(最常用的数组方法)
forEach
方法对数组的每个元素执行一次给定的函数。(类似于for循环)
[1,2,3].forEach((item, index, arr) => console.log(item,index,arr))
// 1 0 (3) [1, 2, 3]
// 2 1 (3) [1, 2, 3]
// 3 2 (3) [1, 2, 3]
注:个人喜欢结合箭头函数使用,该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。
filter
创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。(也就是过滤出满足当前条件的所有元素)
[{name: '张三',id: 1},{name: '李四',id: 2},{name: '张三',id: 3}].filter(item => item.name == "张三");
// [{name: '张三',id: 1},{name: '张三',id: 3}] 筛选出name == "张三"的数据.
也可以设置同forEach一样的参数。该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。
map
创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
// 第一种用法
[{name: '张三',id: 1},{name: '李四',id: 2},{name: '张三',id: 3}].map(item => item.name);
// ["张三", "李四", "张三"]
// 第二种用法
[1, 2, 3].map(item => item*10);
// [10, 20, 30]
// 第三种用法
[{name: '张三',id: 1},{name: '李四',id: 2},{name: '张三',id: 3}].map(item => {
return {
value: item.id,
label: item.name
}
});
// [{label: '张三',value: 1},{label: '李四',value: 2},{label: '张三',value: 3}]
该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。
some
方法测试数组中是不是至少有1个元素通过了被提供的函数测试。(只要有一个元素满足条件就返回true)
const roleList = [{roleId: 1, roleName: '超级管理员'},{roleId: 2, roleName: '审核员'},{roleId: 3, roleName: '学员'}]; // 当前用户的角色
// 判断他是不是超级管理员
const isAdmin = roleList.some(item => item.roleId == 1); // true
if (isAdmin) {
}
该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。
every
测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。
const roleList = [{roleId: 1, roleName: '超级管理员'},{roleId: 2, roleName: '审核员'},{roleId: 3, roleName: '学员'}]; // 当前用户的角色
// 判断他不是超级管理员
const isAdmin = roleList.every(item => item.roleId != 1); // false
if (isAdmin) {
}
该方法可以传入三个参数,item数组中正在处理的当前元素,index数组中正在处理的当前元素的索引,arr源数组。
reduce()
累加
let reduce=(...arr)=>{
return arr.reduce((num,sum)=>{
return sum + num;
},0);
}
console.log(reduce(...arr));//15
将二维数组转化为一维
const flattened = [[0, 1], [2, 3], [4, 5]].reduce((a, b) => a.concat(b),[]);
// [0, 1, 2, 3, 4, 5]
计算数组中每个元素出现的次数
const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
const countedNames = names.reduce((allNames, name) => {
if (name in allNames) {
allNames[name]++;
}
else {
allNames[name] = 1;
}
return allNames;
}, {});
// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
数组去重
let arr = [1,2,1,2,3,5,4,5,3,4,4,4,4];
let result = arr.sort().reduce((init, current) => {
if(init.length === 0 || init[init.length-1] !== current) {
init.push(current);
}
return init;
}, []);
console.log(result); //[1,2,3,4,5]
按顺序运行Promise
function runPromiseInSequence(arr, input) {
return arr.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(input)
);
}
function p1(a) {
return new Promise((resolve, reject) => {
resolve(a * 5);
});
}
function p2(a) {
return new Promise((resolve, reject) => {
resolve(a * 2);
});
}
function f3(a) {
return a * 3;
}
function p4(a) {
return new Promise((resolve, reject) => {
resolve(a * 4);
});
}
const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10)
.then(console.log); // 1200
使用 reduce实现map
if (!Array.prototype.mapUsingReduce) {
Array.prototype.mapUsingReduce = function(callback, thisArg) {
return this.reduce(function(mappedArray, currentValue, index, array) {
mappedArray[index] = callback.call(thisArg, currentValue, index, array)
return mappedArray
}, [])
}
}
[1, 2, , 3].mapUsingReduce(
(currentValue, index, array) => currentValue + index + array.length
) // [5, 7, , 10]
该方法可以传入四个参数,acc累加器,cur当前值,idx当前索引,arr为源数组。以上操作还有其他方式,就不一一列举了。
属性的简洁表示法
const name = "张三";
const id = 1;
const obj = {
name: name,
id: id
};
// 如果对象的属性名和变量名一致,可简写成:
const obj = {name,id};
obj // {name: "张三", id: 1}
属性名表达式
// 第一种
obj['a' + 'bc'] = 123;
// 第二种
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
方法的 name 属性
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
属性的可枚举性和遍历
Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略`enumerable`为`false`的属性。
- `for...in`循环:只遍历对象自身的和继承的可枚举的属性。
- `Object.keys()`:返回对象自身的所有可枚举的属性的键名。
- `JSON.stringify()`:只串行化对象自身的可枚举的属性。
- `Object.assign()`: 忽略`enumerable`为`false`的属性,只拷贝对象自身的可枚举的属性。
属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
(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 键,按照加入时间升序排列。
// 比较简单就不写了
解构赋值
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
扩展运算符
(1). 取出参数对象的所有可遍历属性,拷贝到当前对象之中
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
(2). 如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
(3). 合并两个对象(此处用的较多)
let params = {
...this.pageObj, // 分页对象
...this.searchForm // 搜索框表单对象
}
(4). 如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉
let obj = {name: '张三', id: 1}; // 可能里面还有几十条数据,但是我们只想改变用户名
let userObj = {...obj,name:'李四'}; // {name: "李四", id: 1}
扩展: 如果想完整克隆一个对象,还拷贝对象原型的属性,可采用以下方法。
// 写法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
Object.is()
它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign()
用于对象的合并
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
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}
浅拷贝
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
// `Object.assign()`拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
数组的处理
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
取值函数的处理
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// `Object.assign()`只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}
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 } }
__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
以上方法都是用来操作原型链的,比较简单省略不写
Object.keys(),Object.values(),Object.entries()
Object.keys()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。Object.values()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
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.fromEntries()
方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }
运算符扩展
指数运算符(**)
2 ** 2 // 4
2 ** 3 // 8
let a = 1.5;
a **= 2;
// 等同于 a = a * a;
let b = 4;
b **= 3;
// 等同于 b = b * b * b;
链判断运算符
如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName这个属性,安全的写法是写成下面这样。
// 错误的写法
const firstName = message.body.user.firstName || 'default';
// 正确的写法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
// 链判断运算符简写
const firstName = message?.body?.user?.firstName || 'default';
(1)短路机制
本质上,?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行。
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
(2)括号的影响
如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
(a?.b).c
// 等价于
(a == null ? undefined : a.b).c
(3)报错场合
以下写法是禁止的,会报错。
// 构造函数
new a?.()
new a?.b()
// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`
// 链判断运算符的左侧是 super
super?.()
super?.foo
// 链运算符用于赋值运算符左侧
a?.b = c
4)右侧不得为十进制数值
为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
Null 判断运算符
读取对象属性的时候,如果某个属性的值是null或undefined,有时候需要为它们指定默认值。
const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;
开发者的原意是,只要属性的值为null或undefined,默认值就会生效,但是属性的值如果为空字符串或false或0,默认值也会生效。
为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
逻辑赋值运算符
将逻辑运算符与赋值运算符进行结合。
// 或赋值运算符
x ||= y
// 等同于
x || (x = y)
// 与赋值运算符
x &&= y
// 等同于
x && (x = y)
// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)
// 老的写法
user.id = user.id || 1;
// 新的写法
user.id ||= 1;
Symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。
const sym = Symbol('foo');
sym.description // "foo"
你只需要暂时知道有这么个数据类型,表示独一无二的值即可。因为项目上真的很少很少用到。如果想看详细介绍,请移步阮一峰的es6文档
Set 和 Map 数据结构
Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
set
1. add方法为其添加成员;
2. has查看是否有该值;
3. delete删除某个值,返回布尔值
4. clear清除所有成员
5. size返回Set实例的成员总数
// 比较简单,省略代码
2.Map
它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
map
1. has查看是否有该值
2. get获取属性
3. set设置属性
4. delete删除属性
5. size属性返回 Map 结构的成员总数。
6. clear方法清除所有成员,没有返回值。
// 比较简单,省略代码
Set和Map的遍历
//set
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
//-------------------------------------------------------------------------
// map
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
4.WeakSet
1.WeakSet 的成员只能是对象,而不能是其他类型的值。2.WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
WeakMap
1.只接受对象作为键名(null除外),不接受其他类型的值作为键名。2.WeakMap的键名所指向的对象,不计入垃圾回收机制。
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
WeakMap 的用途
let myWeakmap = new WeakMap();
myWeakmap.set(
document.getElementById('logo'),
{timesClicked: 0})
;
document.getElementById('logo').addEventListener('click', function() {
let logoData = myWeakmap.get(document.getElementById('logo'));
logoData.timesClicked++;
}, false);
上面代码中,document.getElementById('logo')是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。
WeakRef
用于直接创建对象的弱引用
let target = {};
let wr = new WeakRef(target);
target是原始对象,构造函数WeakRef()创建了一个基于target的新对象wr。这里,wr就是一个 WeakRef 的实例,属于对target的弱引用,垃圾回收机制不会计入这个引用,也就是说,wr的引用不会妨碍原始对象target被垃圾回收机制清除。
Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
- **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
也是 ES6 为了操作对象而提供的新 API,该方法用的比较少,所以暂不讨论,只需要暂时知道有这些东西即可。
(1) 将`Object`对象的一些明显属于语言内部的方法(比如`Object.defineProperty`),放到`Reflect`对象上。现阶段,某些方法同时在`Object`和`Reflect`对象上部署,未来的新方法将只部署在`Reflect`对象上。也就是说,从`Reflect`对象上可以拿到语言内部的方法。
(2) 修改某些`Object`方法的返回结果,让其变得更合理。比如,`Object.defineProperty(obj, name, desc)`在无法定义属性时,会抛出一个错误,而`Reflect.defineProperty(obj, name, desc)`则会返回`false`。
(3) 让`Object`操作都变成函数行为。某些`Object`操作是命令式,比如`name in obj`和`delete obj[name]`,而`Reflect.has(obj, name)`和`Reflect.deleteProperty(obj, name)`让它们变成了函数行为。
(4)`Reflect`对象的方法与`Proxy`对象的方法一一对应,只要是`Proxy`对象的方法,就能在`Reflect`对象上找到对应的方法。这就让`Proxy`对象可以方便地调用对应的`Reflect`方法,完成默认行为,作为修改行为的基础。也就是说,不管`Proxy`怎么修改默认行为,你总可以在`Reflect`上获取默认行为。
Promise
含义
是异步编程的一种解决方案
特点。
(1)对象的状态不受外界影响。`Promise`对象代表一个异步操作,有三种状态:`pending`(进行中)、`fulfilled`(已成功)和`rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`pending`变为`fulfilled`和从`pending`变为`rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
2.基本用法
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
.then()
操作成功时的回调
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
.catch()
操作失败时的回调
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
.finally()
指定不管 Promise 对象最后状态如何,都会执行的操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
.all()
用于将多个 Promise 实例,包装成一个新的 Promise 实例。(可用于同时进行多个请求)
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
.race()
将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
.allSettled()
等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
.any()
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
Promise.any([
fetch('https://v8.dev/').then(() => 'home'),
fetch('https://v8.dev/blog').then(() => 'blog'),
fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => { // 只要有一个 fetch() 请求成功
console.log(first);
}).catch((error) => { // 所有三个 fetch() 全部请求失败
console.log(error);
});
10..resolve()操作成功时传入
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么`Promise.resolve`将不做任何修改、原封不动地返回这个实例。
(2) 参数是一个thenable对象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
(3).参数不是具有then()方法的对象,或根本就不是对象
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
// Hello
(4)不带有任何参数
const p = Promise.resolve();
p.then(function () {
// ...
});
事件循环(考点)
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。
reject()操作失败时传入
Promise.reject('出错了')
.catch(e => {
console.log(e === '出错了')
})
// true
Iterator 和 for...of 循环
Iterator
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
Iterator 的遍历过程:
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的`next`方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的`next`方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的`next`方法,直到它指向数据结构的结束位置。
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 }
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String(字符串是一个类似数组的对象,也原生具有 Iterator 接口。)
- TypedArray
- 函数的 arguments 对象
- NodeList 对象(类似数组的对象)
只要数据结构具备Iterator(遍历器)接口便可以使用for...of遍历,对象(Object)没有默认部署 Iterator 接口,但是能通过特殊的方式进行for...of遍历
for...of 循环
数组
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!