admin 管理员组

文章数量: 887021

learn

这篇文章是自己的学习笔记,只是方便自己查阅,内容为空的话,是开发当中较少用到;需要的同学还是查看官方文档比较权威:

/

一、let 和 const命令

1、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';//ReferenceErrorlet tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

  • ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

  • 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为‘’暂时性死区“(temporal dead zone,简称TZD)

if(true)
{ tmp = 'abc'; // ReferenceErrorconsole.log(tmp);  // ReferenceErrorlet tmp; // TDZ结束console.log(tmp);// undefinedtmp=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不允许在相同作用域内,重复声明同一个变量。

2、块级作用域

为什么需要块级作用域

  • 第一种场景,内层变量可能会覆盖外层变量。
  • 第二种场景,用来计数的循环变量泄露为全局变量。

ES6 的块级作用域

  • ES6 允许块级作用域的任意嵌套。内层作用域可以定义外层作用域的同名变量。
  • 块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
// IIFE写法
(()=>{var tmp = ...;
}())
// 块级作用域写法
{let tmp = ...;
}

块级作用域与函数声明

balabala

  • 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
 块级作用域内部的函数声明语句,建议不要使用
{let a = 'secret';function f() {return a;}
}// 块级作用域内部,优先使用函数表达式
{let a = 'secret';let f = () => {return a;};
}
  • ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
if(true) let x=1;
// Uncaught SyntaxError: Lexical declaration cannot appear in a single-statement contextif(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() {}
}

3、const命令

基本用法

  • 声明变量,就必须立即初始化,不能留到以后赋值。
  • const的作用域与let命令相同:只在声明所在的块级作用域内有效。
  • 不提升,存在暂时性死区,只能在声明的位置后面使用。
  • 不可重复声明。

本质

  • 变量指向的那个内存地址所保存的数据不得改动。
    • 简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量
    • 复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针const只能保证这个指针是固定的(即总是指向另一个固定的地址),因此,讲一个对象声明为常量必须非常小心。
const foo = {};
foo.prop=123;
foo.prop;foo={}
// Uncaught TypeError: Assignment to constant variable.
赋值给常量变量

ES6声明变量的六种方法

var、function 、let、const、import、class

var
let
const
function 
class
import

4、顶层对象的属性

5、globalThis对象

二、变量的解构赋值

1、数组的解构赋值

2、对象的解构赋值

3、字符串的解构赋值

4、数值和布尔值的解构赋值

5、函数参数的解构赋值

6、圆括号问题

7、用途

交换变量的值
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});
提取JSON数据
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)]

函数参数的默认值
遍历Map结构
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");

四、字符串的扩展

1、字符的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
  • JavaScript 共有 6 种方法可以表示一个字符。
'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

2、字符串的遍历器接口

  • 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);
}
// "𠮷"

3、直接输入U+2028U+2029

  • JavaScript 规定有5个字符,不能在字符串里面直接使用,只能使用转义形式。
U+005C:反斜杠(reverse solidus)
U+000D:回车(carriage return)
U+2028:行分隔符(line separator)
U+2029:段分隔符(paragraph separator)
U+000A:换行符(line feed)
  • JavaScript 字符串直接输入 U+2028(行分隔符)和 U+2029(段分隔符)。
const PS = eval("'\u2029'");

4、JSON.stringfy()的改造

  • JSON.stringfy()方法将一个javascript 对象或值转换为json字符串
  • 如果遇到0xD8000xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""

5、模板字符串

  • $('#result').append(`
    There are <b>${basket.count}</b> items
    in your basket,<em>${basket.onSale}</em>
    are on sale!
    `)
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PAvJAUUt-1687858237972)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028104851808.png)]

  • 定义多行字符串

// 多行字符串
`In JavaScript this isnot 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}?`
  • 大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性
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!'

6、实例:模板编译

7、标签模板

8、模板字符串的限制

五、字符串的新增方法

1、String.fromCodePoint()

可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足。

2、String.raw()

3、实例方法:codePointAt()

  • ES6 提供了codePointAt()方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。
  • codePointAt()方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。

4、实例方法:normalize()

  • 将字符的不同表示方法统一为同样的形式,这称为Unicode正规化。

5、inclueds(),startWith(),endsWith()

  • 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)]

6、repeat()

  • repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

7、padStart(),padEnd()

  • padStart()用于头部补全,
  • padEnd()用于尾部补全
'x'.padStart(5,'ab')
'ababx''x'.padEnd(5,'ab')
'xabab'
  • 第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
  • 常见用途补全定位数,下面代码生成10位的数值字符串。
1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

8、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)]

9、matchAll()

返回一个正则表达式在当前字符串的所有匹配。

10、replaceAll()

  • replace()只能替换第一个匹配
'aabbcc'.replace('b','_')
'aa_bcc' 
'aabbcc'.replace(/b/g,'_')  //借助正则表达式
'aa__cc'
'aabbcc'.replaceAll('b','_') // 可以一次性替换所有匹配
'aa__cc'
  • replaceAll , 可以一次性替换所有匹配,返回一个新字符串,不会改变原字符串。
  • replaceAll()的第二个参数replacement是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zr5rCHkI-1687858237973)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211028110346846.png)]

六、正则的扩展

1、RegExp构造函数

七、数值的扩展

八、函数的扩展

1、函数参数的默认值

基本用法

  • 函数的参数设置默认值,即直接写在参数定义的后面
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() //100x=100;
foo() //101

与解构赋值默认值结合使用

  • 例一
function foo({x,y=5}){console.log(x,y);
}
foo({}) // undefined 5foo() // 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
  • 上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。
let x=1;
function f(y=x){let x=2;console.log(y);
}
f() //1
  • 函数f调用时,参数y=x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。
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,然后声明了变量yy的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。

如果将var x = 3var去除,函数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){...}

2、rest参数(转为数组)

  • rest是形参,承载了所有的函数参数,可以随意取名。
function func1(...rest){console.log(rest) 
}
func1(1,2,3) // [1,2,3]
  • ES6引入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)
  • rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
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

3、严格模式

  • 从 ES5 开始,函数内部可以设定为严格模式。
function doSomething(a,b){'use strict';//code
}
  • 规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

4、name属性

  • 函数的name属性,返回该函数的函数名。
function foo(){} 
foo.name//'foo'

5、箭头函数

基本用法

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

尾调用优化

7、函数参数的尾逗号

8、Function.prototype.toString()

9、catch命令的参数省略

九、数组的扩展

1、扩展运算符…

  • 扩展运算符 化骨绵掌
  • rest 剩余扩展符 吸星大法
含义

扩展运算符(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 numberconsole.log((...[1,2]))
    // Uncaught SyntaxError: Unexpected numberconsole.log(...[1,2])
    //1 2

    上面三种情况,扩展运算符都放在圆括号里面,但是前两种情况会报错,因为扩展运算符所在的括号不是函数调用。

替代函数的apply方法

由于扩展运算符可以展开数组**,所以不再需要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])
    

扩展运算符的应用

(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的克隆。

(2)合并数组

扩展预算符提供了数组合并的新写法。

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

上面代码中,a3a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。

对象合并:

Object.assign(target,source1,source2,...)

(3)与解构赋值结合

扩展运算符可以与解构复制结合起来,用于生成数组。

//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]
//报错
(4)字符串

将字符串转为真正的数组。

[...'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') 
(5)实现了Iterator接口的对象

带有Iterator接口的对象转为数组

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
(6)Map和Set结构,Generator函数

2、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']
  • 应用, DOM 操作返回的 NodeList 集合、函数内部的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()将字符串转为数组,然后返回字符串的长度。

3、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]

4、数组实例的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)]

5、数组实例的find()findIndex()

find()

  • 用于找出第一个符合条件的数组成员,参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回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**。
[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

6、数组实例的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]]

7、数组实例的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']

8、数组实例的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和Set数据解构有一个has方法,需要注意与includes区分。。
    • Map的has(),用来查找键名的,比如Map.prototype.(key),WeakMap.prototype.has(key),Reflect.has(target,prototypeKey)
    • Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)WeakSet.prototype.has(value)

9、数组实例的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

10、数组的空位

11、Array.prototype.sort()的排序稳定性

  • 排序稳定性(stable sorting)是排序算法的重要算法,指的是排序关键字相同的项目,排序前后的顺序不变。
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按照首字母进行排序。排序结果中,strawspork的前面,跟原始顺序一致,所以排序算法stableSorting是稳定排序。

const unstableSorting = (s1, s2) => {if (s1[0] <= s2[0]) return -1;return 1;
};arr.sort(unstableSorting)
// ["apple", "peach", "spork", "straw"]

上面代码中,排序结果是sporkstraw前面,跟原始顺序相反,所以排序算法unstableSorting是不稳定的。

  • 插入排序、合并排序、冒泡排序等都是稳定的.
  • 堆排序、快速排序等是不稳定的

十、对象的扩展

1、属性的简洁表示法

  • 变量foo直接写在大括号里面
  • 属性简写
  • 方法也可以简写
const o = {method() {return "Hello!";}
};// 等同于const o = {method: function() {return "Hello!";}
};
let birth = '2000/01/01';
const Person = {name: '张三',//等同于birth: birthbirth,//等同于 hello.function()...hello(){console.log('我的名字是',this.name);}
}
  • 函数的返回值
function getPoint(){const x = 1;const y = 10;return {x,y};
}
getPoint()
// {x:1,y:10}
  • CommonJS模块输出一组变量
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}
  • 属性的赋值器(setter)和取值器(getter)
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

2、属性名表达式

  • JavaScript 定义对象的属性,有两种方法。
//方法一
obj.foo=true;//方法二
obj['a'+'bc']=123;

直接用标识符作为属性名,

用表达式作为属性名,这时要将表达式放在刚括号之内。

  • 使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。
var obj = {foo: true,bac: 123
}
  • ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内
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] 属性。

3、方法的name属性

  • 函数的name属性,返回函数名。对象方法也是函数,依次也有name属性。
const person = { sayName() {console.log('hello');}, 
};
person.sayName.name
// 'sayName'
  • 对象的方法使用了取值函数(getter)和存值函数(setter),描述对象的getset属性上面,返回值是方法名前加上getset
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; // ""

4、属性的可枚举和遍历

浅拷贝深拷贝

可枚举性

  • 对象的每个属性都有一个描述对象(Descriptior),用来控制该属性的行为。Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。
let obj = {foo:123};Object.getOwnPropertyDescriptor(obj,'foo'){"value": 123,"writable": true,"enumerable": true,"configurable": true
}

描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略enumerablefalse的属性。

for...in 循环:只遍历对象自身的和集成的可枚举的属性。

Object.keys():返回对象自身的所有可枚举的属性。

JSON.stringfy() : 只串行对象自身的可枚举的属性。

Object.assign():忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。

属性的遍历

ES6 一共有 5 种方法可以遍历对象的属性。

  • for…in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

  • Object.keys(obj)

返回数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名

  • Object.getOwnPropertyNames(obj)

数组,键名

  • Object.getOwnPropertySymbols(obj)

数组,键名

  • Reflect.ownKeys(obj)

键名,数组

5、super关键字

  • 指向当前对象的原型对象。
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指向原型对象protofoo方法,但是绑定的this却还是当前对象obj,因此输出的就是world

6、对象的扩展运算符

解构赋值

  • 对象的解构赋值用于从一个对象取值
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作为参数,函数wrapperFunctionbaseFunction的基础上进行了扩展,能够接受多余的参数,并且保留原始函数的行为。

扩展运算符

  • 对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
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);
  • 后加表达式

7、AggregateError错误对象

十、对象的新增方法

1、Object.is()

  • == 自动转换数据类型 , +0等于-0
  • === NaN不等于自身,+0等于-0
  • Object.is 用来比较两个值是否严格相等。
Object.is(+0,-0)
falseObject.is(NaN,NaN)
trueObject.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
})

2、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()会直接返回该参数。如果该参数不是对象,则会先转成对象,然后返回。由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。
const obj = {a:1};
Object.assign(obj) === obj
// true
Object.assign(2)
Number {2}[[Prototype]]: Number[[PrimitiveValue]]: 2typeof Object.assign(2)
'object'Object.assign(undefined)
// VM984:1 Uncaught TypeError: Cannot convert undefined or null to objectObject.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' } }
深拷贝
  • Lodash的_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}

常见用途

为对象添加属性

为对象添加方法

克隆对象

合并多个对象

为属性指定默认

3、Object.getOwnPropertyDescriptors()

4、__proto__属性、Object.setPrototypeOf()、Object.getPrototypeOf()

5、Object.keys(),Object.values(),Object.entries()

6、Object.fromEntries

十二、运算符的扩展

1、指数运算符

  • 指数运算符(**
2 ** 2 // 4
2 ** 3 // 8

2、链判断运算符

  • 编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取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';

3、Null判断运算符

  • ??
const animationDuration = response.settings?.animationDuration??300;

上面代码中,如果response.settingsnullundefined,或者response.settings.animationDurationnullundefined,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断。

  • 多个逻辑运算符一起使用,必须用括号表明优先级

4、逻辑赋值运算符

  • ||=、&&=、??=先进行逻辑运算,根据运算结果,再视情况进行赋值运算。
// 或赋值运算符
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';
    }
    

十四、setmap数据结构

1、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 // 1set.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 // 2s.has(1) // true
    s.has(2) // true
    s.has(3) // falses.delete(2);
    s.has(2) // false
    
  • ObjectSet ,判断是否包括一个键,写法不同

// 对象的写法
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():返回键值的遍历器
    • 由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以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 : 9let 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

2、WeakSet

3、Map

基本用法
  • Object 结构提供了**“字符串—值”的对应,Map 结构提供了“值—值”**的对应,是一种更完善的 Hash 结构实现。
方法
操作方法
  • 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
遍历方法
  • keys()
  • values()
  • entries()
  • forEach()
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
// yesfor(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
    
    • 由上可知,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
    • 如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefinednull也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
与其他数据结构的互相转换
  • 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']
}
  • 数组 转为 Map :将数组传入 Map 构造函数,就可以转为 Map
  • Map 转为对象
    • let [key,value] of map.entries()->obj[k]=value
    • 如果所有Map的键 都是字符串,它可以无损地转为对象。
function 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
    • 对象转为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
    • map->object->json
    • map.entries()–> obj[k]=value->json.stringify(obj)
    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 .
    • map->[…map]->json.stringify(array)
    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

    • json->object->map
    • json.parse(json)->obj.entries()-> map.set(key,value)
    
    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}
    

4、WeakMap

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qv9eh34m-1687858237977)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211101111258436.png)]

5、WeakRef

6、FinalizationRegistry

十五、Proxy (16:0-18:0)

1、概述

概念

  • Proxy可以理解成,在目标对像之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
  • 例子:
var obj = new Proxy({},{get: function (target, propKey, receiver) {console.log(`getting ${propKey}!`);return Reflect.get(target, propKey, receiver);},set: function (target, propKey, value, receiver) {console.log(`setting ${propKey}!`);return Reflect.set(target, propKey, value, receiver);},}
);
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;

13种proxy支持的拦截操作一览

  • get(target,propKey,receiver) : 拦截对象属性的读取,比如proxy.fooproxty['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):…

2、Proxy实例的方法

3、Proxy.revocable()

proxy.revocable()方法返回一个可取消的Proxy实例

let target = {};
let handler ={};
let {proxy,revoke} = Proxy.revocable(target,handler);proxy.foo = 123;
proxy.foo // 123revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable()方法返回一个对象该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消proxy实例。上面代码中,当执行revoke函数之后,再访问proxy实例,就会抛出一个错误。

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

4、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.

5、实例:Web 服务的客户端

  • Proxy对象可以拦截目标对象的任意属性,这使得它很合适用来写Web服务的客户端。
const service = createWebService('');service.employees().then(json=>{const employees() = JSON.parse(json);// ...
})

十六、Reflect

十七、Promise对象

1、Promise的含义

  • 异步编程的一种解决方案。
  • 三种状态:pending(进行中) fulfilled(已成功) rejected(已失败)
  • Promise对象的状态改变,只有两种可能:从pending 变为fulfilled 和从pending变为rejected . 只要这两种情况发生,状态就凝固了。resolved

2、基本用法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 codeif(/*异步操作成功*/){resolve(value);}else{reject(error)}
    })
    
    • resolve : 将Promise 对象的状态从pending变成resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    • rejectpending -> rejected 在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
    • Promise 实例生成以后,可以用then方法分别指定resolved状态 和rejected状态的回调函数
    promise.then(function(value){//success
    },function(error){//failure
    })
    
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X2MvZkUD-1687858237979)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211102150616977.png)]
      • 接受两个回调函数作为参数,第一个是resolved ,第二个rejected
  • Promise对象的简单例子

    • 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))
        
  • 调用resolvereject并不会终结 Promise 的参数函数的执行

new Promise((resolve,reject)=>{resolve(1);console.log(2);
}).then(r=>{console.log(r);
})

3、Promise.prototype.then()

4、Promise.prototype.catch()

5、Promise.prototype.fianlly()

6、Promise.all()

7、Promise.race()

8、Promise.allSettled()

9、Promise.any()

10、Promise.resolve()

11、Promise.reject()

12、应用

13、Promise.try()

十八、Iteratorfor...of循环

1、Iterator(遍历器)的概念

  • 遍历器(Iterator)一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 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)]

2、默认Iterator接口

  • 默认的Iterator接口部署在数据结构的Symbol.iterator属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oh1rUjir-1687858237980)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103141456952.png)]

  • 数据结构没有(比如对象)

    • 是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
  • 原生具备 Iterator 接口的数据结构如下。

    • Array
    • Map
    • Set
    • String
    • TypedArray
    • 函数的 arguments 对象
    • NodeList 对象
  • 一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0Q7ngvk-1687858237980)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103141824079.png)]

3、调用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()

4、字符串的Iterator接口

let str = "123456";for(let item of str){console.log(item)}
//  1
//  2
//  3
//  4
//  5
//  6

5、Iterator接口与Generator函数

6、遍历器对象的retrun()throw()

7、for...of 循环

数组

  • for...of循环可以代替数组实例的forEach方法
  • JavaScript 原有的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)]

SetMap结构

  • Set 结构遍历时,返回的是一个值
  • Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 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循环用于字符串、
  • DOM NodeList 对象、
  • arguments对象的例子。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uzsn6v2K-1687858237983)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211103190209218.png)]

对象

  • 署了 Iterator 接口后才能使用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 函数的语法

1、简介

  • 语法上:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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函数

1、含义

  • Generator 函数的语法糖

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UonQ9JVj-1687858237985)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20211104162126545.png)]

2、基本用法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 函数有多种使用形式
// 函数声明
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()=>{}

3、语法

错误处理机制

返回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对象,返回该对象的结果。
  • 如果不是 Promise 对象,就直接返回对应的值。

async function f() {return await 123;
}
f().then((v) => console.log(v));
//123class 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

休眠,程序停顿

面试题目

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
  • 代码执行时,立即输出 0,之后每隔 1 秒依次输出 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('')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()

4、async函数的实现原理

async函数的实现原理,就是将Generator函数和自动执行器,包装在一个函数里。

5、与其他异步处理方式的比较

async function chainAnimationAsync(elem,animations){let ret = null;try{for(let anim  of animations){ret = await anim(elem);}}catch(error){/*忽略错误,继续执行*/}
}

6、实例:按顺序完成异步操作

async function logInOrder(urls){// 并发读取远程URLconst 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,因此实现了按顺序输出。

7、顶层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的基本语法

1、简介

类的由来

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 // trueclass 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 的内部可用,指代当前类。
  • 在 Class 外部,这个类只能用MyClass引用。
  • 如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式。
const MyClass = class{}
  • 采用 Class 表达式,可以写出立即执行的 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

2、静态方法

  • 类相当于实例的原型,所有类中定义的方法,都会被实例继承。
  • 如果在一个方法前,加一个static关键字,就表示该方法不会被实例继承,而是通过类来调用,这就称为"静态方法"。
class Foo{static classMethod(){return 'hello';}
}
console.log(Foo.classMethod())
// hellovar 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'

3、实例属性的新写法

4、静态属性

5、私有方法和私有属性

6、静态块

7、new.target属性

二十三、class的继承

二十四、Module的语法

二十五、Module的加载实现

二十六、编程风格

1、块级作用域

(1)let取代var

  • var命令存在变量提升作用,let命令没有这个问题。

(2)全局变量和线程安全

  • letconst之间,建议优先使用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];
    

2、字符串

  • 静态字符串一律使用单引号或反引号,不使用双引号.
  • 动态字符串使用反引号。
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// accrptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`

3、解构赋值

  • 使用数组成员对变量赋值时,优先使用解构赋值。
// 结构赋值
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.firstNameconst 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);

4、对像

  • 单行定义的对象,最后一个成员不以逗号结尾。
  • 多行定义的对象,最后一个成员以逗号结尾。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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;},
};

5、数组

  • 使用扩展运算符(...)拷贝数组
// 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);

6、函数

  • 立即执行函数写成箭头函数的形式
(()=>{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 = {}){// ...
}

7、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)
}

8、Class

  • 用 Class,取代需要 prototype 的操作.
// 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);
}

9、模块

  • 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; 

10、ESLint的使用

二十七、读懂风格

函数的扩展

5.箭头函数

基本用法

  • ES6允许使用‘箭头(=>)定义函数
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);
      

Generator函数的语法

call、bind、apply

call

  • 方法重用,能够在不同对象上使用的方法。通过 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'

apply

  • apply() 方法与 call() 方法非常相似,apply() 方法接受数组形式的参数
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()方法的第一个参数不是对象,则它将成为被调用函数的所有者(对象)。在“非严格”模式下,它成为全局对象。

bind

在现代的前端开发中,处理异步的代码随处可见,熟悉和掌握异步操作的流程控制是成为合格开发者的基本功

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)
}

8、Class

  • 用 Class,取代需要 prototype 的操作.
// 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);
}

9、模块

  • 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; 

10、ESLint的使用

二十七、读懂风格

函数的扩展

5.箭头函数

基本用法

  • ES6允许使用‘箭头(=>)定义函数
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);
      

Generator函数的语法

call、bind、apply

call

  • 方法重用,能够在不同对象上使用的方法。通过 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'

apply

  • apply() 方法与 call() 方法非常相似,apply() 方法接受数组形式的参数
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()方法的第一个参数不是对象,则它将成为被调用函数的所有者(对象)。在“非严格”模式下,它成为全局对象。

bind

在现代的前端开发中,处理异步的代码随处可见,熟悉和掌握异步操作的流程控制是成为合格开发者的基本功

本文标签: Learn