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 明确规定,如果区块中存在
let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 -
总之,在代码块内,使用
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+2028
和U+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字符串- 如果遇到
0xD800
到0xDFFF
之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。
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
,然后声明了变量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){...}
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
上面代码中,a3
和a4
是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
对象合并:
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)
。
- Map的has(),用来查找键名的,比如
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
按照首字母进行排序。排序结果中,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
是不稳定的。
- 插入排序、合并排序、冒泡排序等都是稳定的.
- 堆排序、快速排序等是不稳定的
十、对象的扩展
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
),描述对象的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; // ""
4、属性的可枚举和遍历
浅拷贝深拷贝
可枚举性
- 对象的每个属性都有一个描述对象(
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
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
指向原型对象proto
的foo
方法,但是绑定的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作为参数,函数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);
- 后加表达式
7、AggregateError
错误对象
十、对象的新增方法
1、Object.is()
==
自动转换数据类型 , +0等于
-0===
NaN
不等于自身,+0等于
-0Object.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()
会直接返回该参数。如果该参数不是对象,则会先转成对象,然后返回。由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。
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.settings
是null
或undefined
,或者response.settings.animationDuration
是null
或undefined
,就会返回默认值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'; }
十四、set
和map
数据结构
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
-
-
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()
:返回键值的遍历器- 由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以
keys
方法和values
方法的行为完全一致。
- 由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以
let set = new Set(['red','green','blue']);for(let item of set.keys()){console.log(item);
}
// red
// green
// blue
for(let item of set.values()){console.log(item)
}
// red
// green
// blue
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
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然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.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)
:…
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
,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
:pending
->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))
-
-
-
调用
resolve
或reject
并不会终结 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()
十八、Iterator
和for...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)]
Set
和Map
结构
- 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)全局变量和线程安全
-
在
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];
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
版权声明:本文标题:learn 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1731222343h1471785.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论