这篇文章是自己的学习笔记,只是方便自己查阅,内容为空的话,是开发当中较少用到;需要的同学还是查看官方文档比较权威:
https://es6.ruanyifeng.com/
let
命令不存在变量提升,暂时性死区(变量在声明之前都是不能使用的)
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
for
循环的计数器
for(let i = 0; i<10 ;i++){
//...
}
console.log(i);
// ReferenceError: i is not defined
计数器i
只在for
循环体内有效,在循环体外引用就会报错。
var
,最后输出的是10
。var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
let
,声明的变量仅在块级作用域内有效,最后输出的是 6。var a = [];
for(let i=0;i<10;i++)
{
a[i]=function(){
console.log(i)
};
}
a[6]();//6
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNS4oInt-1687858237969)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028102103739.png)]
for
循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。(同一个作用域不可使用let
重复声明同一个变量)for(let i=0;i<3;i++)
{
let i='abc';
console.log(i)
};
// abc
// ab
// abc
console.log(foo);//undefined
var foo=2;
console.log(foo);//Uncaught SyntaxError: Identifier 'foo' has already been declared
let foo=2;
let
命令,它声明的变量就“绑定”(binding)这个区域,不再受外部的影响。var tmp = 123;
if(true){
tmp = 'abc';//ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为‘’暂时性死区“(temporal dead zone,简称TZD)
if(true)
{
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp);// undefined
tmp=123;
console.log(tmp;)//123
}
上面代码中,在let
命令声明变量tmp
之前,都属于变量tmp
的‘死区’。
typeof
操作不安全[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ka4mBXO-1687858237970)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028102509709.png)]
let
不允许在相同作用域内,重复声明同一个变量。
// IIFE写法
(()=>{
var tmp = ...;
}())
// 块级作用域写法
{
let tmp = ...;
}
balabala
块级作用域内部的函数声明语句,建议不要使用
{
let a = 'secret';
function f() {
return a;
}
}
// 块级作用域内部,优先使用函数表达式
{
let a = 'secret';
let f = () => {
return a;
};
}
if(true) let x=1;
// Uncaught SyntaxError: Lexical declaration cannot appear in a single-statement context
if(true) {let x=1;}
'use strict';
if(true)
function f() {}
// Uncaught SyntaxError: In strict mode code, functions can only be declared at top level or inside a block.
'use strict';
if(true){
function f() {}
}
const
命令const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。const
只能保证这个指针是固定的(即总是指向另一个固定的地址),因此,讲一个对象声明为常量必须非常小心。const foo = {};
foo.prop=123;
foo.prop;
foo={}
// Uncaught TypeError: Assignment to constant variable.
赋值给常量变量
var、function 、let、const、import、class
var
let
const
function
class
import
let x=1;
let y=2;
[x,y]=[y,x];
//[2,1]
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象返回。有了解构赋值,取出这些值就非常方便。
function example()
{
return [1,2,3]
}
let [a,b,c]=example();
function example(){
return {
foo:1,
bar:2
}
}
let {foo,bar}=example()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u06f83jL-1687858237971)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028103308366.png)]
解构赋值可以方便地将一组参数与变量名对应起来。]
// 参数是一组有次序地值
function f([x,y,z]){...}
f([1,2,3])
// 参数是一组无次序地值
function f({x,y,z}){...}
f({z:3,y:2,x:1});
let jsonData={id:42,status:'OK',data:[867,5309]};
let {id,status,data:number}=jsonData;
id;//42
status;//'OK'
number;//[867,5309]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tqs3SFlY-1687858237971)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028103454663.png)]
const map = new Map();
map.set("first", "hello");
map.set("second", "world");
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ChorC7YJ-1687858237972)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028103651814.png)]
// 获取键名
for(let [key] of map){
//...
}
// 获取键值
for(let [,value] of map){
//...
}
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const {SourceMapConsumer,SourceNode}=require("source-map");
Unicode
表示法\uxxxx
形式表示一个字符,其中xxxx
表示字符的 Unicode 码点。'\u0061'
// 'a'
"\u{20BB7}"
// ""
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;
hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
for ...of
循环 ,可以识别大于0xFFFF
的码点for(let item of 'foo')
{
console.log(item)
}
// "f"
// "o"
// "o"
let text = String.fromCodePoint(0x20BB7);
for (let i of text) {
console.log(i);
}
// ""
U+2028
和U+2029
U+005C:反斜杠(reverse solidus)
U+000D:回车(carriage return)
U+2028:行分隔符(line separator)
U+2029:段分隔符(paragraph separator)
U+000A:换行符(line feed)
const PS = eval("'\u2029'");
JSON.stringfy()
的改造JSON.stringfy()
方法将一个javascript 对象或值转换为json字符串0xD800
到0xDFFF
之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
$('#result').append(`
There are ${basket.count} items
in your basket,${basket.onSale}
are on sale!
`)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PAvJAUUt-1687858237972)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028104851808.png)]
定义多行字符串
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
string text line 1
string text line 2
${}
之中 ,${}// 字符串中嵌入变量
let name ='Bob',time='today';
`Hello ${name},how are you ${time}?`
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i6qVFjWM-1687858237972)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028105135977.png)]
function fn()
{
return 'Hello World';
}
`foo ${fn()} bar`
// 'foo Hello World bar'
\
转义let greeting = `\`Yo\` World!`;
`Yo` World1!'
String.fromCodePoint()
可以识别大于0xFFFF
的字符,弥补了String.fromCharCode()
方法的不足。
String.raw()
codePointAt()
codePointAt()
方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。codePointAt()
方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。normalize()
Unicode
正规化。includes()
:返回布尔值,表示是否找到了参数字符串startsWith()
:返回布尔值,表示参数字符串是否在原字符串的头部。endsWith()
:返回布尔值,表示参数字符串是否在原字符串的尾部。let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s5NiBg20-1687858237972)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028105439045.png)]
repeat()
repeat
方法返回一个新字符串,表示将原字符串重复n次。'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
padStart()
用于头部补全,padEnd()
用于尾部补全'x'.padStart(5,'ab')
'ababx'
'x'.padEnd(5,'ab')
'xabab'
1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
trimStart()
,trimEnd()
trim
: 修剪
trimStart()
消除字符串头部的空格。trimEnd()
消除尾部的空格。let s=' abc ';s.trim()const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
trimLeft()
是trimStart()
的别名,trimRight()
是trimEnd()
的别名。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vuIgNvvy-1687858237973)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028105916477.png)]
matchAll()
返回一个正则表达式在当前字符串的所有匹配。
replaceAll()
replace()
只能替换第一个匹配'aabbcc'.replace('b','_')
'aa_bcc'
'aabbcc'.replace(/b/g,'_') //借助正则表达式
'aa__cc'
'aabbcc'.replaceAll('b','_') // 可以一次性替换所有匹配
'aa__cc'
replaceAll
, 可以一次性替换所有匹配,返回一个新字符串,不会改变原字符串。replaceAll()
的第二个参数replacement
是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。RegExp
构造函数function log(x,y='World')
{
console.log(x,y);
}
log('Hello')
// Hello World
log('Hello','China')
// Hello China
log('Hello','')
// Hello
let x=99;
function foo(p=x+1){
console.log(p);
}
foo() //100
x=100;
foo() //101
function foo({x,y=5}){
console.log(x,y);
}
foo({}) // undefined 5
foo() // Cannot destructure property 'x' of 'undefined' as it is undefined.
函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
function foo({x,y=5}={}){
console.log(x,y)
}
foo() // undefined 5
上面代码指定,如果没有提供参数,函数foo
的参数默认为一个空对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrmLfXBH-1687858237973)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211022140359620.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RTc6SsRx-1687858237974)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211022142246573.png)]
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]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltuir5u6-1687858237974)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211022143954293.png)]
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。除非显式传入undefined
length
属性指定了默认值以后,函数的length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
((a)=>{}).length
1
((a=5)=>{}).length
0
((a,b,c=5)=>{}).length
2
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1;
function f(x,y=x)
{
console.log(y);
}
f(2) //2
let x=1;
function f(y=x){
let x=2;
console.log(y);
}
f() //1
function f(y=x){
let x=2;
console.log(y);
}
f() //Uncaught ReferenceError: x is not defined
x
不存在,就会报错。var x=1;
function foo(x=x){}
foo() // Uncaught ReferenceError: Cannot access 'x' before initialization
上面代码中,参数x=x形成一个单独作用域。实际执行的是let x=x
,由于暂时性死区的原因,这行代码会报错。
复杂的例子
函数foo
的参数形成一个单独作用域。这个作用域里面,首先声明了变量x
,然后声明了变量y
,y
的默认值是一个匿名函数。这个匿名函数内部的变量x
,指向同一个作用域的第一个参数x
。函数foo
内部又声明了一个内部变量x
,该变量与第一个参数x
由于不是同一个作用域,所以不是同一个变量,因此执行y
后,内部变量x
和外部全局变量x
的值都没变。
如果将var x = 3
的var
去除,函数foo
的内部变量x
就指向第一个参数x
,与匿名函数内部的x
是一致的,所以最后输出的就是2
,而外层的全局变量x
依然不受影响。
var x=1;
function foo(x,y=()=>{x=2;})
{
var x=3;
y();
console.log(x)
}
foo();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXejQtgd-1687858237974)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211022161308423.png)]
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}
foo() // 2
x // 1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EoTpJWzO-1687858237974)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211022161902913.png)]
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
function throwIfMissing()
{
throw new Error('Missing parameter');
}
function foo(mustBeProvied =throwIfMissing())
{
return mustBeProvied;
}
foo()
// Uncaught Error: Missing parameter
另外,可以将参数默认值设为undefined
,表明这个参数是可以省略的。
function foo(optional=undefined){...}
rest
是形参,承载了所有的函数参数,可以随意取名。function func1(...rest){
console.log(rest)
}
func1(1,2,3) // [1,2,3]
rest
参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。function add(...values){
let sum=0;
for(var val of values){
sum+=val;
}
return sum;
}
add(2,5,3)
rest
参数代替arguments
变量的例子// arguments变量的写法
function sortNumbers()
{
return Array.from(arguments).sort();
// arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.from先将其转为数组。
}
//rest参数的写法
const sortNumbers=(...numbers)=>numbers.sort()
function push(array,...items)
{
items.forEach((item)=>{array.push(item);
console.log(item)})}
var a=[];
push(a,1,2,3)
function f(a,...b,c)
{
///....
}
// Uncaught SyntaxError: Rest parameter must be last formal parameter
length
属性,不包括rest
参数。(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
function doSomething(a,b){
'use strict';
//code
}
name
属性,返回该函数的函数名。function foo(){}
foo.name//'foo'
var f=v=>v;
//等同于
var f=function(v){return v;}
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]
const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]
this
对象 ,内部this
就是定义时上层作用域中的this
。new
命令,否则会抛出一个错误。arguments
对象,该对象在函数体内不存在yield
命令,因此箭头函数不能用作Generator
函数。function Timer(){
this.s1=0;
this.s2=0;
//箭头函数
setInterval(()=>this.s1++,1000)
//普通函数
setInterval(function(){
this.s2++;
},1000);
}
var timer =new Timer();
setTimeout(()=>console.log('s1: ',timer.s1),3100);
setTimeout(()=>console.log('s2: ',timer.s2),3100);
Timer
函数内部设置了两个定时器,分别使用了箭头函数和普通函数。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ypo5m5dO-1687858237975)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211025004721316.png)]
箭头函数实际上可以让this
指向固定化,绑定this
使得它不再可变,这种特性很有利于封装回调函数。
eg
function foo(){
return ()=>{
return ()=>{
return ()=>{
console.log('id: ',this.id);
}
}
}
}
var f=foo.call({id:1});
var t1 = f.call({id: 2})()();// id: 1
var t2 = f().call({id: 3})();// id: 1
var t3 = f()().call({id: 4});// id: 1
this
的指向只有一个,就是函数foo的this
,这是因为所有的内层函数都是箭头函数,都没有自己的this
,它们的this
其实都是最外层的foo
函数的this
。
Function.prototype.toString()
catch
命令的参数省略扩展运算符(spread)是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1,2,3])
// 1 2 3
console.log(1,...[2,3,4],5)
// 1 2 3 4 5
function push(array,...items){
array.push(...items);
}
function add(x,y){
return x+y
}
const numbers=[4,38];
add(...numbers) //42
const arr=[
...(x>0?['a']:[]),'b',
]
如果扩展运算符后面是一个空数组,则不产生任何效果。
[...[],1]
//[1]
注意 ,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
(...[1,2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1,2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1,2])
//1 2
上面三种情况,扩展运算符都放在圆括号里面,但是前两种情况会报错,因为扩展运算符所在的括号不是函数调用。
由于扩展运算符可以展开数组**,所以不再需要apply
方法,**将数组转为函数的参数了。
//ES5的写法
function f(x,y,z){
//...
}
var agrs=[0,1,2];
f.apply(null,args);
//ES6的写法
function f(x,y,z){
//...
}
let args=[0,1,2]
f(...args);
下面是扩展运算符取代apply
方法的一个实际的例子,
Math.max
方法,简化求出一个数组最大元素的写法。//ES5的写法
Math.max.apply(null,[14,3,77])
//ES6的写法
Math.max(...[14,3,77])
//等同于
Math.max(14,3,77);
上面代码中,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用Math.max
函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用Math.max
了。
另一个例子是通过push
函数,将一个数组添加到另一个数组的尾部。
//ES5 的写法
var arr1=[0,1,2];
var arr2=[3,4,5];
arr1.push(...arr2);
上面代码的 ES5 写法中,push
方法的参数不能是数组,所以只好通过apply
方法变通使用push
方法。有了扩展运算符,就可以直接将数组传入push
方法。
不懂
下面是另外一个例子。
//ES5
new (Date.bind.apply(Date,[null,2015,1,1]))
//ES6
new Date(...[2005,1,1])
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1=[1,2];
const a2=a1;
a2[0]=2;
a1 // [2,2]
上面的代码,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。
ES5只能变通方法来复制数组。
const a2=a1.concat();
const a1=[1,2];
const a2=a1.concat();
a2[0]=2;
a1 //[1,2]
上面的代码,a1会返回原数组的克隆,再修改a2就不会对a1产生影响。
扩展运算符提供了复制数组的简便写法。
const a1=[1,2];
const a2=[...a1];
const [...a2]=a1;
上面的两种写法,a2都是a1的克隆。
扩展预算符提供了数组合并的新写法。
const arr1=['a','b'];
const arr2=['c'];
const arr3=['d','e'];
arr1.concat(arr2,arr3);
//['a', 'b', 'c', 'd', 'e']
[...arr1,...arr2,...arr3]4
//['a', 'b', 'c', 'd', 'e']
不过,这两种方法都是浅拷贝,使用的时候需要注意。
const a1 = [{foo:1}];
const a2 = [{bar:2}];
const a3 = a1.concat(a2);
const a4 = [...a1,...a2];
a3 === a4;//false
a3[0] === a1[0]; //true
a4[0]=== a1[0]; //true
上面代码中,a3
和a4
是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
对象合并:
Object.assign(target,source1,source2,...)
扩展运算符可以与解构复制结合起来,用于生成数组。
//ES5
a = list[0],rest = list.slice(1)
//ES6
[a,...rest]=list
下面是另外的一些例子
const [first,...rest]=[1,2,3,4,5];
first // 1
rest // [2,3,4,5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会出错。
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
正确返回字符串长度的函数
function length(str){
return [...str].length;
}
length('x\uD83D\uDE80y')
带有Iterator
接口的对象转为数组
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
Array.from()
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']
let arr2 = Array.from(arrayLike);
['a', 'b', 'c']
arguments
对象。// NodeList对象
let ps = document.querySelectotAll('p');
Array.from(ps).filter(p=>{
return p.textContent.length>100;
});
//arguments 对象
function foo(){
var args = Array.from(arguments);
//...
}
项目代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8GrAU2Gj-1687858237975)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211020104129198.png)]
任何有length
属性的对象,都可以通过Array.from
方法转为数组,而此时扩展运算符就无法转换。
Array.from({length:3});
// [undefined, undefined, undefined]
Array.prototype.slice
,对于还没有部署该方法的浏览器
const toArray = (()=>Array.from?Array.from:obj=>[].slice.call(obj))();
`4
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]
取出一组 DOM 节点的文本内容
let spans = document.querySelectorAll('span.name');
// map()
let names1 = Array.prototype.map.call(spans,s=>s.textContent);
//Array.from()
let names2 = Array.from(spans,s=>s.textContent)
下面的例子将数组中布尔值为false
放入成员转为0
Array.from([1,0,2,0,3],(n)=>n||0)
// [1, 0, 2, 0, 3]
返回各种数据的类型。
function typesOf()
{
return Array.from(arguments,value=>typeof value)
};
typesOf(null,[],NaN)
//['object', 'object', 'number']
如果map
函数用到了this
关键字,还可以传入Array.from
的第三个参数,用来绑定this
。
Array.from()
可以将各种值转为真正的数组,并且还提供map
功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。
Array.from({length:2},()=>'jack')
//['jack', 'jack']
Array.from
的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。
Array.from()
将字符串转为数组,然后返回字符串的长度。
Array.of()
Array.of()
方法用于将一组值,转换为数组。Array.of(3,11,8)
// [3, 11, 8]
Array.of(3)
// [3]
Array.of(3).length
// 1
Array()
的不足。因为参数个数的不同,会导致Array()
的行为有差异。Array()
// []
Array(3)
// [empty × 3] [, , ,]
Array(3,11,8)
// [3, 11, 8]
copyWithin()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-canMsATb-1687858237975)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211020133843029.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omrd93Cx-1687858237976)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211020134657182.png)]
copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,会修改当前数组。Array.prototype.copyWith(target,start = 0,end = this.length)
target
(必需):从该位置开始替换数据。如果为负值,表示倒数。start
(可选):从该位置开始读取数据,默认为0。如果为负值,表示从末尾开始计算。end
(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。[1,2,3,4,5].copyWithin(0,4)
// [5,2,3,4,5]
表示从4号位直到数组结束的成员(5),复制到从0号开始的位置,结果覆盖了原来的1和2。
[1,2,3,4,5].copyWithin(0,3,4)
// [4,2,3,4,5]
[1,2,3,4,5].copyWithin(0,-2,-1)
// [4,2,3,4,5]
[].copyWithin.call({length:5,3:1},0,3) //{length:5,0:1,3:1}
Array.from.({length:5,3:1})---> [undefined,undefined,undefined,1,undefined][undefined,undefined,undefined,1,undefined].copyWithin(0,3)-->
[1,undefined,undefined,1,undefined]-->
{length:5,0:1,3:1}
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]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fW5Cy0tf-1687858237976)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211020162922932.png)]
find()
和findIndex()
find()
undefined
.[1,4,-5,10].find(n=>n<0)
//-5
[1,5,10,15].find(function(value,index,arr){
return value>9;
})
//10
findIndex()
[1,5,10,15].findIndex((value,index,arr)=>value>9)
// 2
this
对象。function f(v){
return v>this.age;
}
let person = {name:'John',age:20}
[10,12,26,15].find(f,person);
// 26
上面的代码中,find
函数接收了第二个参数person对象,回调函数中的this对象指向person对象。
NaN
,弥补了数组的indexOf
方法的不足。Object.is()
方法判断两个值是否为同一个值。[NaN].indexOf(NaN)
-1
[NaN].findIndex(y=>Object.is(NaN,y))
0
fill()
fill
方法使用给定值,填充一个数组。数组中已有的元素,会被全部抹去。['a', 'b', 'c'].fill(7)
// [7, 7, 7],数组中已有的元素,会被全部抹去。
new Array(3).fill(7)
// [7, 7, 7]
fill
方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。['a','b','c'].fill(7,1,2)
// ['a',7,'c']
let arr = new Array(3).fill({name:'Mike'}); arr[0].name='Ben';
arr;
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5],[5],[5]]
entries()
,keys()
和values()
for...of
循环进行遍历,它们都返回一个遍历器对象,唯一的区别是
keys()
是对键名的遍历,values()
是对键值的遍历,entries()
是对键值对的遍历。for(let index of ['a','b'].keys())
{
console.log(index) // 0 1
}
for(let index of ['a','b'].values())
{
console.log(index) // 'a' 'b'
}
for(let [index,elem] of ['a','b'].entries())
{
console.log(index,elem)
// 0 'a'
// 1 'b'
}
for...of
循环,可以手动调用遍历器对象的next
方法,进行遍历。let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
inclues()
Array.prototype.incluedes
方法返回一个布尔值,表示某个数组是否包含给定的值[1,2,3].includes(2)
// true
[1,2,3].includes(4)
// false
[1,2,3,NaN].includes(NaN)
// true
0
。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4
,但数组长度为3
),则会重置为从0
开始。[1,2,3].includes(3,3)
// false
[1,2,3].includes(3,-1)
// true
indexOf
方法,检查是否包含某个值。if(arr.indexOf(el)!==-1){
...
}
一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1
,表达起来不够直观。
二是,它内部使用严格相等运算符(===)进行判断,这会导致对NAN
的误判。
[NAN].indexOf(NAN)
// -1
[NAN].includes(NAN)
// true
const contains=(()=>{
Array.prototype.includes
? (arr,value) => arr.includes(value)
: (arr,value) => arr.some(el=>el===value)
})();
contains(['foo','baz'],'baz');// false
Map.prototype.(key)
,WeakMap.prototype.has(key)
,Reflect.has(target,prototypeKey)
。has
方法,是用来查找值的,比如Set.prototype.has(value)
、WeakSet.prototype.has(value)
。flat()
,flatMap()
flat()
Array.prototype.flat()
用于将嵌套的数组“拉平”[1,2,[3,4]].flat()
// [1, 2, 3, 4]
flat()
默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()
方法的参数写成一个整数,表示想要拉平的层数,默认为1。[1,2,[3,4,[5,6]]].flat(2)
// [1, 2, 3, 4, 5, 6]
Infinity
,有多少层嵌套,都要转成一维数组[1,[2,[3]]].flat(Infinity)
// [1,2,3]
flat()
方法会跳过空位。[1,2,,4,5].flat()
// [1,2,4,5]
flatMap()
Array.prototype.map()
),然后对返回值组成的数组执行flat()
方法。该方法返回一个新数组,不改变原数组。[2,3,4].flatMap(x=>[x,x*2])
// [2, 4, 3, 6, 4, 8]
flatMap()
只能展开一层数组。[1,2,3,4].flatMap(x=>[[x*2]])
// [[2], [4], [6], [8]]
flatMap()
方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。arr.flatMap(function callback(currentValue[,index[,array]])
{}[,thisArg]
)
flatMap()
方法还可以有第二个参数,用来绑定遍历函数里面的this
。
Array.prototype.sort()
的排序稳定性const arr = [
'peach',
'straw',
'apple',
'spork'
];
const stableSorting = (s1, s2) => {
if (s1[0] < s2[0]) return -1;
return 1;
};
arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]
上面代码对数组arr
按照首字母进行排序。排序结果中,straw
在spork
的前面,跟原始顺序一致,所以排序算法stableSorting
是稳定排序。
const unstableSorting = (s1, s2) => {
if (s1[0] <= s2[0]) return -1;
return 1;
};
arr.sort(unstableSorting)
// ["apple", "peach", "spork", "straw"]
上面代码中,排序结果是spork
在straw
前面,跟原始顺序相反,所以排序算法unstableSorting
是不稳定的。
foo
直接写在大括号里面const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
let birth = '2000/01/01';
const Person = {
name: '张三',
//等同于birth: birth
birth,
//等同于 hello.function()...
hello(){
console.log('我的名字是',this.name);
}
}
function getPoint(){
const x = 1;
const y = 10;
return {x,y};
}
getPoint()
// {x:1,y:10}
let ms = {};
function getItem(key){
return key in ms ? ms[key] : null;
}
function setItem(key,value){
ms[key]=value;
}
function clear(){
ms={};
}
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
}
const cart = {
_wheels: 4,
get wheels(){
return this._wheels;
}
set wheels(value){
if(value<this._wheels){
throw new Error('数值太小了!0')
}
this._wheels = value;
}
}
let user = {name:'test'};
let foo ={baz:'baz'};
console.log(user,foo)
// {name: "test"} {bar: "baz"}
console.log({user, foo})
// {user: {name: "test"}, foo: {bar: "baz"}}
const obj = {
f(){
this.foo = 'bar';
}
};
new obj.f() // 报错obj.f is not a constructor
//方法一
obj.foo=true;
//方法二
obj['a'+'bc']=123;
直接用标识符作为属性名,
用表达式作为属性名,这时要将表达式放在刚括号之内。
var obj = {
foo: true,
bac: 123
}
let proKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123,
}
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
//报错
const foo = 'bar';
const bar = 'abc';
const baz = {[foo]};
//正确
const foo = 'bar';
const baz = {[foo]:'abc'};
[object Object]
const keyA = {a:1};
const keyB = {b:2};
const myObejct = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // {[obejct object]:'valueB'}
[keyA]
和[keyB]
得到的是都是[object object]
,所以[keyB]
会把[keyA]
覆盖掉,而myObject
最后只有一个[object Object]
属性。
name
属性,返回函数名。对象方法也是函数,依次也有name
属性。const person = {
sayName()
{console.log('hello');
},
};
person.sayName.name
// 'sayName'
getter
)和存值函数(setter
),描述对象的get
和set
属性上面,返回值是方法名前加上get
和set
。const obj = {
get foo(){},
set foo(x){}
};
const descriptor = Object.getOwnPropertyDescriptor(obj,'foo');
descriptor.get.name
// 'get foo'
bind
方法创造的函数,name
属性返回bound
加上原函数的名字,var doSomething = function(){console.log('a')}; doSomething.bind().name
// 'bound doSomething'
Founction
构造函数创造的函数,name
属性返回anonymous
(new Function()).name // "anonymous"
Symbol
值,那么name
属性返回的是这个Symbol
值的描述。const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
console.log(obj[key1].name,obj[key2].name)
obj[key1].name; // "[description]"
obj[key2].name; // ""
https://zhuanlan.zhihu.com/p/56741046
Descriptior
),用来控制该属性的行为。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.stringfy()
: 只串行对象自身的可枚举的属性。
Object.assign()
:忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
ES6 一共有 5 种方法可以遍历对象的属性。
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
返回数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
数组,键名
数组,键名
键名,数组
const proto={
foo:'hello'
};
const obj = {
foo:'world',
find(){
return super.foo;
}
};
Object.setPrototypeOf(obj,proto);
obj.find()
const proto = {a:1};
const obj={
a:2,
find(){
return super.a
}
};
Object.setPrototypeOf(obj,proto);
obj.find();//1
setPrototypeOf :
设置一个指定的对象的原型到另一个对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFfLbPS1-1687858237976)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211026142758752.png)]
supper
关键字表示原型对象时,只能用在对象的方法之中。// 报错
const obj = {
foo: super.foo
}
// 报错
const obj = {
foo: () => super.foo
}
// 报错
const obj = {
foo: function () {
return super.foo
}
}
第一种写法是super
用在属性里面,第二种和第三种写法是super
用在一个函数里面,然后赋值给foo
属性。
supper.foo
等同于Object.getPrototype(this).foo
(属性) 或 Object.getPrototype(this).call(this)
方法。const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
上面代码中,super.foo
指向原型对象proto
的foo
方法,但是绑定的this
却还是当前对象obj
,因此输出的就是world
。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
function baseFunction({a,b}){
// ...
}
function wrapperFunction({x,y,...restConfig}){
// 使用x和y参数进行操作
// 其余参数传给原始函数
return baseFunction(restConfig);
}
原始函数baseFunction
接受a和b作为参数,函数wrapperFunction
在baseFunction
的基础上进行了扩展,能够接受多余的参数,并且保留原始函数的行为。
let z={a:3,b:4};
let n={...z};
n;
// {a: 3, b: 4}
let foo = {...['a','b','c']};
foo;
// {0: 'a', 1: 'b', 2: 'c'}
{...'hello'}
// {0: 'h', 1: 'e', 2: 'l', 3: 'l', 4: 'o'}
Object.assign()
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
// 写法一
let obj = {a:1,b:2};
const clone1 = {
_proto_: Object.getPrototypeOf(obj),
...obj
};
console.log(clone1);
// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj,
)
console.log(clone2);
//写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
console.log(clone3);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOKnLD3H-1687858237977)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211026165940625.png)]
let ab = {...a,...b};
// 等同于
let ab = Object.assign({},a,b);
let newVersion = {
...previousVersion,
name: 'New Name'
}
let aWithDefaults = { x:1, y:2, ...a};
// 等同于
let aWithDefaults = Object.assign({},{x:1,y:2},a);
//等同于
let aWithDefaults = Object.assign({x:1,y:2},a);
AggregateError
错误对象Object.is()
==
自动转换数据类型 , +0等于
-0===
NaN
不等于自身,+0等于
-0Object.is
用来比较两个值是否严格相等。Object.is(+0,-0)
false
Object.is(NaN,NaN)
true
Object.is({},{})
false
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OLlAvujY-1687858237977)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211026183651601.png)]
Object.defineProperty(Object,'is',{
value: function(x,y) {
if(x===y){
return x!==0 || 1/x === 1/y;
}
// 针对NaN的情况
return x!==x && y!==y;
},
configurable: true,
enumerable: false,
writable: true
})
Object.assign()
JSON.parse(JSON.stringify('aaa'))
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}
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}
Object.assign()
会直接返回该参数。如果该参数不是对象,则会先转成对象,然后返回。由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。const obj = {a:1};
Object.assign(obj) === obj
// true
Object.assign(2)
Number {2}[[Prototype]]: Number[[PrimitiveValue]]: 2
typeof Object.assign(2)
'object'
Object.assign(undefined)
// VM984:1 Uncaught TypeError: Cannot convert undefined or null to object
Object.assign(null)
// VM1032:1 Uncaught TypeError: Cannot convert undefined or null to object
enumerable: false
)。Object.assign()
方法实行的是浅拷贝,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
对于这种嵌套的对象,一旦遇到同名属性,Object.assign()
的处理方法是替换,而不是添加。
const target = {a:{b:'c',d:'e'}};
const source = {a:{b:'hello'}};
Object.assign(target,source);
// { a: { b: 'hello' } }
_defaultDeep()
Object.assign()
可以用来处理数组,但是会把数组视为对象。
Object.assign([1,2,3],[4,5])
// [4, 5, 3]
Object.assign()
只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
const source = {
get foo()
{return 1}} ;
const target = {};
Object.assign(target,source)
// {foo:1}
为对象添加属性
为对象添加方法
克隆对象
合并多个对象
为属性指定默认
Object.getOwnPropertyDescriptors()
__proto__属性、Object.setPrototypeOf()、Object.getPrototypeOf()
Object.keys(),Object.values(),Object.entries()
Object.fromEntries
**
)2 ** 2 // 4
2 ** 3 // 8
message.body.user.firstName
这个属性,安全的写法是写成下面这样。// 错误的写法
const firstName = message.body.user.first || 'default' ;
// 正确的写法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName ) || 'default';
optional chaining poerator
) ?.
简化上面的写法const firstName = message?.body?.user?.firstName || 'default';
const animationDuration = response.settings?.animationDuration??300;
上面代码中,如果response.settings
是null
或undefined
,或者response.settings.animationDuration
是null
或undefined
,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断。
||=、&&=、??=
先进行逻辑运算,根据运算结果,再视情况进行赋值运算。// 或赋值运算符
x ||= y
// 等同于
x || (x = y)
// 与赋值运算符
x &&= y
// 等同于
x && (x = y)
// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)
user.id = user.id || 1;
user.id ||=1
user.id属性如果不存在,则设为1
function example(pots){
opts.foo = pots.foo ?? 'bar';
opts.baz ?? (pots.baz = 'qux');
}
function example(opts){
opts.foo ??= 'bar';
opts.baz ??= 'qux';
}
set
和map
数据结构Set
Set
本身是一个构造函数,用来生成Set
数据结构const s = new Set();
[2,3,5,4,5,2,2,].forEach(x=>s.add(x));
for(let i of s){
console.log(i)
}
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 接受类似数组的对象作为参数
const set = new Set(document.querySelectorAll('div'));
set.size
// 类似于
const set = new Set();
document.querySelectorAll('div').forEach(div => set.add(div));
set.size
[...new Set([1,2,3,4,5,5])]
// [1, 2, 3, 4, 5]
join
: 将数组元素转换为字符串[...new Set('ababbc')].join('')
// "abc"
内部算法用的是Same-value-zero-equality
类似于精确相等运算符(===)
NaN等于自身
两个对象总是不想等,由于两个空对象不相等,所以它们被视为两个值。
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
Set
实例的属性和方法属性
Set.prototype.constructor
: 构造函数,默认是Set
函数Set.prototype.size
:返回Set
实例的成员总数。方法
add
添加某个值,返回Set
结构本身
delete
删除某个值,返回一个布尔值,表示删除是否成功。
has
返回一个布尔值,表示该值是否为Set
的成员。
clear
清除所有成员,没有返回值。
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
Object
和 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.form
方法可以将Set
结构转为数组。const items = new Set([1,2,3,4,5,]);
const array = Array.from(items);
function dedupe(array){
return Array.from(new Set(array));
}
dedupe([1,1,2,3]) //[1,2,3]
Set.prototype.keys()
: 返回键名的遍历器Set.prototype.values()
:返回键值的遍历器
keys
方法和values
方法的行为完全一致。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
Set.prototype.entries()
:返回键值对的遍历器for(let item of set.entries()){
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
Set.prototype.forEach()
:使用回调函数遍历每个成员,用于对每个成员执行某种操作,没有返回值。该函数的参数与数组的forEach
一致,依次为键值、键名、集合本身。Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。forEach
方法还可以有第二个参数,表示绑定处理函数内部的this
对象。let set = new Set([1,4,9]) ;
set.forEach((value,key)=>{
console.log(key+':'+value)
})
// 1 : 1
// 4 : 4
// 9 : 9
let set = new Set([1,4,9]) ;
set.forEach((value,key,collect)=>{console.log(collect)})
//{1, 4, 9}
//{1, 4, 9}
//{1, 4, 9}
Set
的遍历顺序就是插入顺序,使用Set
保存一个回调函数列表,调用时就能保证按照添加顺序调用。
Set
结构的实例默认可遍历,它的默认遍历器生成函数就是它的values
方法。Set.prototype[Symbol.iterator] === Set.prototype.values
for...of
循环遍历 Set。let set = new Set(['red','green','blue']);
for(let x of set){
console.log(x);
}
//red
//green
//blue
WeakSet
Map
const map = new Map()
set 如果对同一个键多次赋值,后面的值将覆盖前面的值。
map.set(1,'aaa')
map.set(1,'bbb')
get
map.get(1) // 'bbb'
delete
has
clear
const m = new Map();
const o = {p:'hello World'};
m.set(o,'content');
m.get(o); //'content'
m.delete(o); //true
m.has(o); // false
const map =new Map([['F','no'],['T','yes']]);
for(let item of map.keys())
{
console.log(item)
}
//F
//T
for(let item of map.values()){
console.log(item)
}
// no
// yes
for(let item of map.entries()){
console.log(item)
}
// ['F', 'no']
// ['T', '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
结构才将其视为同一个键。
const map = new Map();
map.set(['a'],555);
map.get(['a']);
// undefined
const map = new Map();
let a=['a'];
map.set(a,555);
map.get(a);
// 555
0
和-0
就是一个键,布尔值true
和字符串true
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然NaN
不严格相等于自身,但 Map 将其视为同一个键。const myMap = new Map().set(true,7).set({foo:3},['abc']);
[...myMap]
// [[true,7],[{foo:3},['abc']]]
// 将数组传入 Map 构造函数,就可以转为 Map。
new Map([...myMap])
{
true=>7,
{foo:3}=>['abc']
}
let [key,value] of map.entries()
->obj[k]=valuefunction strMapToObj(strMap){
let obj = Object.create(null);
for(let [key,value] of strMap){
obj[key]=value;
}
return obj;
}
const myMap = new Map().set('yes',true).set('no',false);
strMapToObj(myMap);
// {yes: true, no: false}
Map
可以通过Object.entries()
let obj = {'a':1,'b':2};
let map1 = new Map(Object.entries(obj));
// Map(2) {'a' => 1, 'b' => 2}
实现一个转换函数
function objToStrMap(obj){
let strMap = new Map();
for (let k of Object.keys(obj)){
strMap.set(k,obj[k])
}
return strMap;
}
objToStrMap({yes:true,no:false})
// Map(2) {'yes' => true, 'no' => false}
Map 转为 JSON
Map
转为JSON
要区分两种情况,Map
的键名都是字符串,这时可以选择转为对象JSON
function strMapToJson(strMap){
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes',true).set('no',false);
strMapToJson(myMap)
// '{'yes',true,'no':false}'
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']]]'
JSON 转为 Map
function jsonToStrMap(jsonStr){
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{'yes':true,'no':false}')
// Map{'yes' => true,'no' => false}
function objToStrMap(obj){
let map=new Map();
for(let [key,value] of Object.entries(obj)){
map.set(key,value)
}
return map;
}
function jsonToStrMap(jsonStr){
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes":true,"no":false}')
// Map(2) {'yes' => true, 'no' => false}
WeakMap
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qv9eh34m-1687858237977)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211101111258436.png)]
WeakRef
FinalizationRegistry
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);
},
}
);
obj.count = 1;
// setting count
++obj.count;
// getting count
// setting count
// 2
解释
var proxy = new Proxy(target, handler);
targrt
: 表示所要拦截的目标对象。handler
: 对象,用来定制拦截行为。练习
配置对象有一个get
方法,用来拦截对目标对象属性的访问请求。
var proxy = new Proxy({},{
get: function(target,propKey){
return 35;
}
});
console.log(proxy.time,
proxy.name,
proxy.title,)
// 35 35 35
proxy
对象是obj
对象的原型,obj
对象本身并没有time
属性,所以根据原型链,会在proxy
对象上读取该属性,导致被拦截。
handler 没有设置任何拦截,等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target,handler);
proxy.a = 'b';
console.log(target.a);
// 'b'
同一个拦截器函数,可以设置拦截多个操作
var handler = {
get: function (target, name) {
if (name === "prototype") {
return Object.prototype;
}
return "Hello, " + name;
},
apply: function (target, thisBinding, args) {
return args[0]+'8';
},
construct: function (target, args) {
return { value: args[1] };
},
};
var fproxy = new Proxy(function (x, y) {
return x + y;
}, handler);
console.log(
fproxy(1, 2),
new fproxy(1, 2),
fproxy.prototype === Object.prototype,
fproxy.foo === "Hello, foo"
);
new fproxy(1, 2);
fproxy.prototype === Object.prototype;
proxy
支持的拦截操作一览get(target,propKey,receiver)
: 拦截对象属性的读取,比如proxy.foo
和proxty['foo']
set(target,propKey,value,receiver)
:拦截对象属性的设置has(target,propKey)
: 拦截propKey in proxy
的操作,返回一个布尔值。deleteProperty(target,propKey)
:拦截delete proxy[propKey]
的操作,返回一个布尔值ownKeys(target)
:拦截ObjectgetOwnPropertyNames(proxy)
、``Object.getOwnPropertySymbols(proxy)、
Object.keys(proxy)、
for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而
Object.keys()`的返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target,propKey)
:…Proxy
实例的方法Proxy.revocable()
proxy.revocable()
方法返回一个可取消的Proxy实例
let target = {};
let handler ={};
let {proxy,revoke} = Proxy.revocable(target,handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable()
方法返回一个对象,该对象的proxy
属性是Proxy
实例,revoke
属性是一个函数,可以取消proxy
实例。上面代码中,当执行revoke
函数之后,再访问proxy
实例,就会抛出一个错误。
Proxy.revocable()
的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
this
问题虽然Proxy
可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在Proxy
代理的情况下,目标对象内部的this
关键字会指向Proxy
代理。
const target = {
m: function() {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target,handler);
target.m() // false
proxy.m() //true
上面代码中,一旦proxy
代理target
,target.m()
内部的this
就是指向proxy
,而不是target
.
Web
服务的客户端Proxy
对象可以拦截目标对象的任意属性,这使得它很合适用来写Web
服务的客户端。const service = createWebService('http://example.com/data');
service.employees().then(json=>{
const employees() = JSON.parse(json);
// ...
})
Reflect
Promise对象
Promise
的含义pending(进行中)
fulfilled(已成功)
rejected(已失败)
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
. 只要这两种情况发生,状态就凝固了。resolved
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7lIhhToX-1687858237978)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102175827805.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRkjgKjl-1687858237978)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102180535933.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vxISQIwl-1687858237978)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102180717807.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1JszTc8-1687858237978)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102180829020.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JkVV912m-1687858237978)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102180908941.png)]
end
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMCOLj6U-1687858237979)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102144554699.png)]
Promise实例
const promise = new Promise(function(resolve,reject){
// ... some code
if(/*异步操作成功*/){
resolve(value);
}else{
reject(error)
}
})
resolve
: 将Promise
对象的状态从pending
变成resolved
,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
: pending
-> rejected
在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。Promise
实例生成以后,可以用then
方法分别指定resolved
状态 和rejected
状态的回调函数promise.then(function(value){
//success
},function(error){
//failure
})
resolved
,第二个rejectedPromise
对象的简单例子
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, "done");
});
}
timeout(100).then(
(value) => {
console.log(value);
},
(error) => {
console.log(error);
}
);
// done
setTimeout
相关语法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KnOxFn6l-1687858237979)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102145443973.png)]
Promise 新建后就会立即执行
// promise新建后就会立即执行
let promise = new Promise((resolve, reject) => {
console.log("Promise");
resolve();
});
promise.then(()=>{
console.log('resolved.')
});
console.log('Hi!');
//Promise
//Hi!
//resolved.
Promise新建后立即执行,所以首先输出的是Promise
。然后,then
方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved
最后输出。
function loadImageAsync(url) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = function(){
reject(new Error('Could not load image at '+url));
};
image.src = url;
});
}
const getJSON = function (url) {
const promise = new Promise(function (resolve, reject) {
const handler = function () {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.responese);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "applicaion/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(
(json) => {
console.log("Contents: " + json);
},
function (error) {
console.error("出错了", error);
}
);
上面代码中,getJSON
是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise
对象。需要注意的是,在getJSON
内部,resolve
函数和reject
函数调用时,都带有参数。
const p1 = new Promise(function(resolve,reject){
// ...
});
const p2 = new Promise(function(resolve,reject){
// ...
resolve(p1);
})
resolve()
函数除了正常的值以外,还可能是另一个Promise实例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcGDNk7x-1687858237979)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102174330299.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lY7C0z8L-1687858237980)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102175035700.png)]
const p1 = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("fail")),3000);
});
const p2 = new Promise((resolve,reject)=>{
setTimeout(()=>resolve(p1),1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
调用resolve
或reject
并不会终结 Promise 的参数函数的执行
new Promise((resolve,reject)=>{
resolve(1);
console.log(2);
}).then(r=>{
console.log(r);
})
Promise.prototype.then()
Promise.prototype.catch()
Promise.prototype.fianlly()
Promise.all()
Promise.race()
Promise.allSettled()
Promise.any()
Promise.resolve()
Promise.reject()
Promise.try()
Iterator
和for...of
循环Iterator
(遍历器)的概念for...of
Array
: for…of…Object(for...in...)
Map
: for…of…Set
: for…of…[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dad8q7Y9-1687858237980)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103141119512.png)]
Iterator
接口Iterator
接口部署在数据结构的Symbol.iterator
属性[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oh1rUjir-1687858237980)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103141456952.png)]
数据结构没有(比如对象)
一个对象如果要具备可被for...of
循环调用的 Iterator 接口,就必须在Symbol.iterator
的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0Q7ngvk-1687858237980)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103141824079.png)]
Iterator
接口的场合解构赋值
Set
结构进行解构赋值,会默认调用Symbol.iterator
方法let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
扩展运算符
扩展运算符(…)也会调用默认的 Iterator 接口。
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
yield*
yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
由于数组的遍历会调用遍历器接口,所以接受数组作为参数的场合,其实都调用了遍历器接口。
for...of
Array.from()
// Array.from(arrayLik[,maoFn[,thisArg]])
// 从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
console.log(Array.from('foo'));
// expected output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]
Map(),Set(),WeakMap(),WeakSet()
// 比如 new Map(['a',1],['b',2])
Promise.all()
Promise.race()
Iterator
接口let str = "123456";for(let item of str){console.log(item)}
// 1
// 2
// 3
// 4
// 5
// 6
Iterator
接口与Generator
函数retrun()
、throw()
for...of
循环for...of
循环可以代替数组实例的forEach
方法for...in
循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of
循环,允许遍历获得键值。for...in
for...of
两种循环的方式的区别[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUXLWkDG-1687858237981)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103160113401.png)]
for...of
keys()、values()、entries()[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GLdAIy9s-1687858237981)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103160511745.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cii5OMH6-1687858237981)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103160701472.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmyldxbI-1687858237981)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103160711709.png)]
for...of
循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in
循环也不一样。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLepksjz-1687858237982)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103162045866.png)]
Set
和Map
结构[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AUynkZcC-1687858237982)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103171719126.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wph6Rpc6-1687858237982)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103171923562.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vEmEyPMm-1687858237982)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103185657645.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dye1Z9r4-1687858237983)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20200320102839512.png)]
for...of
循环用于字符串、arguments
对象的例子。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uzsn6v2K-1687858237983)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103190209218.png)]
for...of
,for...in
循环可以用来遍历键名。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GLpzA5vV-1687858237983)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104102049701.png)]
for...in
循环是以字符串作为键名“0”、“1”、“2”等等。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dv3xGHCx-1687858237983)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104102615875.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-soP3hQSs-1687858237984)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104102927937.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CCKSq7wn-1687858237984)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104105036001.png)]
Generator
函数的语法[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SlCztWzk-1687858237984)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104133951766.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P3Nb8JHm-1687858237985)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104113121004.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qLny4KmI-1687858237985)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104133856954.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EDkDIRDB-1687858237985)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104113238866.png)]
其它待续…
Generator
函数的语法async
函数Generator 函数的语法糖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UonQ9JVj-1687858237985)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104162126545.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8LZtzYSE-1687858237986)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104162337682.png)]
async function getStockPriceByName(name){
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then((result)=>{
console.log(result);
})
// 函数声明
async function foo(){}
// 函数表达式
const foo = async function(){};
// 对象的方法
let obj = {async foo(){}};
obj.foo().then(console.log('...'))
// Class的方法
class Storage{
constructor(){
this.cachPromise = caches.open('avatars');
}
async getAvatar(name){
const cache = await this.cachPromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const Storage = new Storage();
Storage.getAvatar('jake').then(console.log('...'))
// 箭头函数
const foo = async()=>{}
错误处理机制
Promise
对象[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbArmQ3K-1687858237986)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104165903468.png)]
// 返回一个promise
async function f(){
return 'hello world';
}
f().then(v => console.log(v))
// hello world
Promise
对象的状态变化[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIUPzCJt-1687858250564)(null)]
await
命令await
命令后面是一个promise
对象,返回该对象的结果。
async function f() {
return await 123;
}
f().then((v) => console.log(v));
//123
class Sleep{
constructor(timeout){
this.timeout = timeout;
}
then(resolve,reject){
let startTime = Date.now();
setTimeout(()=>resolve(new Date() - startTime),this.timeout)
}
}
(async()=>{
const sleepTime = await new Sleep(1000);
console.log(sleepTime)
})()
// 1000
休眠,程序停顿
面试题目
https://juejin.cn/post/6844903474212143117#heading-5
function sleep(interval){
return new Promise(resolve=>{
setTimeout(resolve,interval)
})
}
// 用法
async function one2FiveInasync(){
for(let i=0;i<=5;i++){
console.log(i);
await sleep(1000)
}
}
one2FiveInasync();
// 1
// 2
// 3
// 4
// 5
1,2,3,4
,循环结束后在大概第 5 秒的时候输出 5
await
命令后面的Promise
对象如果变为reject
状态,则reject
的参数会被catch
方法的回调函数接收到。async function f(){
return Promise.reject('出错了!')
}
f()
.then(v=>{console.log(v)})
.catch(error=>{console.log(error)
})
await
放在try...catch
结构里面。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0LAxC3b-1687858251260)(null)]
await
后面的Promise
对象再跟一个catch
方法,处理前面可能出现的错误。async function f() {
await Promise.reject("出错了").catch((e) => console.log(e));
return await Promise.resolve("hello world");
}
f().then(v=>console.log(v))
await
后面的异步操作出错,那么等同于async
函数返回的 Promise 对象被reject
。try...catch
代码块之中。防止出错的方法,也是将其放在try...catch
代码块之中。async function main(){
try{
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val2);
console.log('Final:',val3);
}
catch(err){
console.log(err)
}
}
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test(){
let i;
for(i=0;i<NUM_RETRIES;i++){
try{
await superagent.get('http://google.com/this-throws-an-error')
break;
}catch(err){}
}
console.log(i);
}
test();
使用注意点
await
命令放在try...catch
代码块中
多个await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
// 写法一
let [foo,bar]=await Promise.all([getFoo(),getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
async 函数可以保留运行堆栈。
const a=()=>{
b().then(()=>c())
}
const a = async()=>{
await b();
c();
}
上面代码中,b()
运行的时候,a()
是暂停运行,上下文环境都保存着。一旦b()
或c()
报错,错误堆栈将包括a()
。
async
函数的实现原理async
函数的实现原理,就是将Generator
函数和自动执行器,包装在一个函数里。
async function chainAnimationAsync(elem,animations){
let ret = null;
try{
for(let anim of animations){
ret = await anim(elem);
}
}catch(error){
/*
忽略错误,继续执行
*/
}
}
async function logInOrder(urls){
// 并发读取远程URL
const textPromises = urls.map(async url=>{
const response = await fetch(url);
return response.text();
});
// 按次序输出
for(const textPromise of textPromises){
console.log(await textPromise);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnwCIlYp-1687858250885)(null)]
上面代码中,虽然map
方法的参数是async
函数,但它是并发执行的,因为只有async
函数内部是继发执行,外部不受影响。后面的for...of
循环内部使用了await
,因此实现了按顺序输出。
await
await
命令,解决模块异步加载的问题。let output;
async function main() {
const dynamic = await import(someMission);
const data = await fetch(url);
output = someProcess(dynamic.default, data);
}
main(); //调用函数
//exports = module.exports = {};
exports = module.exports = {output};// output ? output: undefined
// 也可写成立即执行函数的形式
// let output ;
// (async function main(){
// const dynamic = await import(someMission);
// const data = await fetch(url);
// output = someMission(dynamic.default,data);
// })();
// export{output}
立即执行函数:
// awaiting.js
let output;
(async function main() {
const dynamic = await import(someMission);
const data = await fetch(url);
output = someProcess(dynamic.default, data);
})();
export { output };
加载这个模块的写法
// usage.js
import { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100)), 1000);
…待续
class
的基本语法JavaScript
语言中,生成实例对象的传统方法是通过构造函数。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toSring=function(){
return '('+this.x+','+this.y+')';
};
var p =new Point(1,2)
console.log(p)
ES6
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
return '('+this.x+','+this.y+')';
}
}
constructor()
方法,这就是构造方法,而this
关键字则代表实例对象
定义方法不需要function
方法之间不需要逗号
可以看作构造函数的另一种写法
class Point{}
typeof Point // 'function'
Point === Point.prototype.constructor // true
class Bar{
doStuff(){
console.log('stuff');
}
}
const b = new Bar();
b.doStuff() // stuff
constructor
方法new
命令生成对象实例时,自动调用该方法constructor()
方法默认返回实例对象(即this
),完全可以指定返回另外一个对象。class Foo{
constructor(){
return Object.create(null);
}
}
new Foo() instance Foo
// false
new
调用,普通构造函数不用new
也可以执行。class Foo{
constructor(){
return Object.create(null);
}
}
Foo()
// Class constructor Foo cannot be invoked without 'new'
new
命令class Point{
// ...
}
//报错
var Point = Point(2,3);
//正确
var Point = new Point(2,3);
getter
)和存值函数(setter
)class CustomHTMLElemt{
constructor(element){
this.element = element;
}
get html(){
return this.element.innerHTML;
}
set html(value){
this.element.innerHTML = value;
}
}
let methodName = 'getArea';
class Squre {
constructor(length){
//...
}
[methodName](){
}
}
Class
表达式const MyClass = class Me{
getClassName(){
return Me.name;
}
}
let inst = new MyClass();
inst.getClassName() // Me
Me.name
// ReferenceError: Me is not defined
Me
,但是Me
只在 Class 的内部可用,指代当前类。MyClass
引用。Me
,也就是可以写成下面的形式。const MyClass = class{}
// 写出立即执行的Class
let person = new (class {
constructor(name) {
this.name = name;
}
sayName(){
console.log(this.name)
}
})('张三');
person.sayName()
// 张三
类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式。
不存在提升
new Foo();
class Foo{};
ReferenceError: Cannot access 'Foo' before initialization
继承,必须保证子类在父类之后定义。
{
let Foo = class{};
class Bar extends Foo{
}
}
name 属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class
继承,包括name
属性。
class Point{};
Point.name
// 'Point' 返回紧跟在class关键字后面的类名
Generator 方法
this
的指向
this
Proxy
,获取方法的时候,自动绑定this
。static
关键字,就表示该方法不会被实例继承,而是通过类来调用,这就称为"静态方法"。class Foo{
static classMethod(){
return 'hello';
}
}
console.log(Foo.classMethod())
// hello
var foo = new Foo();
foo.classMethod()
//TypeError: foo.classMethod is not a function
this
关键字 ,这个this
指的是类,而不是实例。class Foo{
static bar(){
this.baz();
}
static baz(){
console.log('hello');
}
baz(){
console.log('world');
}
}
Foo.bar()
// 'hello'
new.target
属性Module
的语法Module
的加载实现let
取代var
var
命令存在变量提升作用,let
命令没有这个问题。在let
和const
之间,建议优先使用const
,尤其是在全局环境,不应该设置变量,只应设置常量。
// bad
var a = 1, b = 2, c = 3;
// good
const a = 1;
const b = 2;
const c = 3;
// best
const [a, b, c] = [1, 2, 3];
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// accrptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`
// 结构赋值
const arr = [1,2,3,4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first,second] = arr;
// bad
function getFullName(user){
const firstName = user.firstName
const lastName = user.secondName;
}
//good
function getFullName(obj){
const {firstName,lastName} = obj;
}
// best
function getFullName({firstName,secondName}){}
// bad
function processInput(input){
return [left,right,top,bottom];
}
// good
function processInput(input){
return {left,right,top,bottom};
}
const {left,right} = processInput(input);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUZmmBUQ-1687858250220)(null)]
//bad
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
Object,assign
方法[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FV3y8OBB-1687858249872)(null)]
// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
// bad
const obj = {
id: 5,
name: "San Francisco",
};
obj[getKey("enable")] = true;
//good
const obj = {
id: 5,
name: "San Fancisco",
[getKey("enable")]: true,
};
var ref = "some value";
// bad
const atom = {
ref: ref,
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
...
)拷贝数组// bad
const len = items.length;
const itemsCopy = [];
let i;
for(i=0;i<len;i++){
itemsCopy[i] = item[i];
}
// good
// 使用扩展运算符(...)拷贝数组
const itemCopy = [...items];
// 使用Array.from方法,将类似数组的对象转为数组。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
Array.from
方法,将类似数组的对象转为数组const foo = document.querySlectorAll('.foo');
const nodes = Array.from(foo);
(()=>{
console.log('Welcome to the Internet.');
})()
// bad
[1, 2, 3].map(function (x) {
return x * x;
});
// good
[1, 2, 3].map((x) => {
return x * x;
});
// best
[1, 2, 3].map(x => x * x);
Function.prototype.bind
,不应再用self/_this/that
绑定this
// bad
const self = this;
const boundMethod = function(...params){
return method.apply(self,params);
}
// acceptable
const boundMethod = method.bind(this);
// good
const boundMethod = (...params) => method.apply(this,params);
// bad
function divide(a,b,option = false){
}
//good
function divide(a,b,{option = false} = {}){
}
arguments
变量,使用rest运算符(...
)代替。// bad
function concatenateAll(){
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args){
return args.join('')
}
// bad
function handleThings(opts){
opts = opts || {};
}
// good
function handleThings(opts = {}){
// ...
}
Map
结构key:value
的数据结构,使用Map
结构。let map = new Map(arr);
for(let key of map.keys()){
console.log(key);
}
for(let value of map.values()){
console.log(value);
}
for(let item of map.entries()){
console.log(item[0],item[1])
}
for(let [key,value] of map.entries()){
console.log(key,value)
}
Class
// bad
function Queue(contents = []){
this._queue = [...contents];
}
Queue.prototype.pop = function(){
const value = this._queue[0];
this._queue.splice(0,1);
return value;
}
// good
class Queue{
constructor(contents = []){
this._queue = [...contents];
}
pop(){
const value = this._queue[0];
this._queue.splice(0,1);
return value;
}
}
extends
实现继承,不破坏instanceof
运算的风险// 使用extends 实现继承,不破坏instanceof运算的风险
const inherits = require('inherits');
function PeekableQueue(contents){
Queue.apply(this,contents);
}
import
取代require()
// CommonJS的写法
const moduleA = require('moduleA');
const func1 = module.func1;
const func2 = module.func2;
// ES6的写法
import {func1,func2} from 'moduleA';
export
取代mudule.exports
// commonJS的写法
var React = require('react');
var Breadcrumbs = React.createClass({
render(){
return <nav />;
}
})
module.exports = Breadcrumbs;
// ES6的写法
import React from 'react';
class Breadcrumbs extends React.Component{
render(){
return <nav />
}
}
export default Breadcrumbs;
export default
function makeStyleGuide(){
}
export default makeStyleGuide;
const StyleGuide = {
es6:{
}
}
export default StyleGuide;
ESLint
的使用基本用法
var f = v => v;
var f = function(v){ return v;}
var f = v => return v;
//出错
var f=()=>5;
var f=function(){return 5};
var sum=(num1,num2)=>num1+num2;
var sum=function(num1,num2){
return num1+num2;
}
如果箭头函数的代码块多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum=(num1,num2)=>{return num1+num2;}
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
//报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
下面是一种特殊情况,虽然可以运行,但会得到错误的结果。
let foo=()=>{a:1};
foo()
上面代码中,原始意图是返回一个对象{ a: 1 }
,但是由于引擎认为大括号是代码块,所以执行了一行语句a: 1
。这时,a
可以被解释为语句的标签,因此实际执行的语句是1;
,然后函数就结束了,没有返回值。
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let fn=()=>viod doesNotReturn();
箭头函数可以与变量解构结合使用。
const full=({first,last})=>first+' '+last;
//等同于
function full(person){
return person.first+' '+person.last;
}
箭头函数使得表达更加简洁。
const isEven=n=>n%2===0;
const square=n=>n*n;
简化回调函数。
例一
[1,2,3].map(function(x){
return x*x;
});
//箭头函数写法
[1,2,3].map(x=>x*x)
例二
var result=values.sort(function(a,b){
return a-b;
});
//箭头函数写法
var result=values.sort((a,b)=a-b);
call()
,您能够使用属于另一个对象的方法。var person = {
fullName:function()
{return this.firstName+''+this.lastName;
}
};
var person1={firstName:'Bill',lastName:'Gates',};
var person2={firstName:'Steve',lastName:'Jobs'}; person.fullName.call(person1);
//'BillGates'
person.fullName.call(person2);
//'SteveJobs'
带参数的call()
方法
call()
方法可接受参数:
var person = {
fullName:function(city,country)
{ return this.firstName+' '+this.lastName+','+city+','+country}
};
var person1={firstName:'Bill',lastName:'Gates'};
person.fullName.call(person1,'Seattle',"USA");
//'Bill Gates,Seattle,USA'
var person = {
fullName:function(city,country)
{return this.firstName+' '+this.lastName+','+city+','+country}
};
var person1={firstName:'Bill',lastName:'Gates'};
person.fullName.apply(person1,['Seattle',"USA"]);
'Bill Gates,Seattle,USA'
person.fullName.apply(person1,['Seattle','USA'])
javaScript严格模式
在JavaScript严格模式下,如果apply()方法的第一个参数不是对象,则它将成为被调用函数的所有者(对象)。在“非严格”模式下,它成为全局对象。
在现代的前端开发中,处理异步的代码随处可见,熟悉和掌握异步操作的流程控制是成为合格开发者的基本功
alue的数据结构,使用
Map`结构。
let map = new Map(arr);
for(let key of map.keys()){
console.log(key);
}
for(let value of map.values()){
console.log(value);
}
for(let item of map.entries()){
console.log(item[0],item[1])
}
for(let [key,value] of map.entries()){
console.log(key,value)
}
Class
// bad
function Queue(contents = []){
this._queue = [...contents];
}
Queue.prototype.pop = function(){
const value = this._queue[0];
this._queue.splice(0,1);
return value;
}
// good
class Queue{
constructor(contents = []){
this._queue = [...contents];
}
pop(){
const value = this._queue[0];
this._queue.splice(0,1);
return value;
}
}
extends
实现继承,不破坏instanceof
运算的风险// 使用extends 实现继承,不破坏instanceof运算的风险
const inherits = require('inherits');
function PeekableQueue(contents){
Queue.apply(this,contents);
}
import
取代require()
// CommonJS的写法
const moduleA = require('moduleA');
const func1 = module.func1;
const func2 = module.func2;
// ES6的写法
import {func1,func2} from 'moduleA';
export
取代mudule.exports
// commonJS的写法
var React = require('react');
var Breadcrumbs = React.createClass({
render(){
return <nav />;
}
})
module.exports = Breadcrumbs;
// ES6的写法
import React from 'react';
class Breadcrumbs extends React.Component{
render(){
return <nav />
}
}
export default Breadcrumbs;
export default
function makeStyleGuide(){
}
export default makeStyleGuide;
const StyleGuide = {
es6:{
}
}
export default StyleGuide;
ESLint
的使用基本用法
var f = v => v;
var f = function(v){ return v;}
var f = v => return v;
//出错
var f=()=>5;
var f=function(){return 5};
var sum=(num1,num2)=>num1+num2;
var sum=function(num1,num2){
return num1+num2;
}
如果箭头函数的代码块多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum=(num1,num2)=>{return num1+num2;}
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
//报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
下面是一种特殊情况,虽然可以运行,但会得到错误的结果。
let foo=()=>{a:1};
foo()
上面代码中,原始意图是返回一个对象{ a: 1 }
,但是由于引擎认为大括号是代码块,所以执行了一行语句a: 1
。这时,a
可以被解释为语句的标签,因此实际执行的语句是1;
,然后函数就结束了,没有返回值。
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let fn=()=>viod doesNotReturn();
箭头函数可以与变量解构结合使用。
const full=({first,last})=>first+' '+last;
//等同于
function full(person){
return person.first+' '+person.last;
}
箭头函数使得表达更加简洁。
const isEven=n=>n%2===0;
const square=n=>n*n;
简化回调函数。
例一
[1,2,3].map(function(x){
return x*x;
});
//箭头函数写法
[1,2,3].map(x=>x*x)
例二
var result=values.sort(function(a,b){
return a-b;
});
//箭头函数写法
var result=values.sort((a,b)=a-b);
call()
,您能够使用属于另一个对象的方法。var person = {
fullName:function()
{return this.firstName+''+this.lastName;
}
};
var person1={firstName:'Bill',lastName:'Gates',};
var person2={firstName:'Steve',lastName:'Jobs'}; person.fullName.call(person1);
//'BillGates'
person.fullName.call(person2);
//'SteveJobs'
带参数的call()
方法
call()
方法可接受参数:
var person = {
fullName:function(city,country)
{ return this.firstName+' '+this.lastName+','+city+','+country}
};
var person1={firstName:'Bill',lastName:'Gates'};
person.fullName.call(person1,'Seattle',"USA");
//'Bill Gates,Seattle,USA'
var person = {
fullName:function(city,country)
{return this.firstName+' '+this.lastName+','+city+','+country}
};
var person1={firstName:'Bill',lastName:'Gates'};
person.fullName.apply(person1,['Seattle',"USA"]);
'Bill Gates,Seattle,USA'
person.fullName.apply(person1,['Seattle','USA'])
javaScript严格模式
在JavaScript严格模式下,如果apply()方法的第一个参数不是对象,则它将成为被调用函数的所有者(对象)。在“非严格”模式下,它成为全局对象。
在现代的前端开发中,处理异步的代码随处可见,熟悉和掌握异步操作的流程控制是成为合格开发者的基本功