0%

Vue 模板语法

1 插值语法

功能:用于解析标签体内容

写法:,xxx是js表达式,直接读取js中的所有data属性

2 指令语法

功能: 用于解析标签(包括标签属性,标签内容等)

举例:v-bind:href=”xxx”,简写为:href=”xxx”,xxx同样可以写js表达式,且可以直接读取到data里的所有属性

写法:v-xxx

高阶函数模板

1
2
3
4
5
function HoF0(fn){
return function(...args){
return fn.apply(this,args);
}
}

Once

1
2
3
4
5
6
7
8
9
function once(fn){
return function(..args){
if(fn){
const ret=fn.apply(this,args);
fn=null;
return ret;
}
}
}

Throttle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function throttle(fn,time=500){
let timer;
return function(...args){
if(timer==null){
fn.apply(this.args);
timer=setTimeout(()=>{
timer=null;
},time)
}
}

}
btn.onclick=throttle(function(e){
circle.innerHTML=parseInt(circle.innerHTML)+1;
circle.className='fade';
setTimeout(()=>circle.className='',250);
});

Debouce

1
2
3
4
5
6
7
8
9
10
11
12
function debouce(fn,dur){
dur=dur||100;
var timer;
return function(){
clearTimeout(timer);
timer=setTimeOut(()=>{
fn.apply(this,arguments);
},dur);
}


}

iterative

1
2
3
4
5
6
7
8
9
10
11
12
13
function iterative(fn){
return function(subject,...rest){
if(isIterable(subject)){
const ret=[];
for(let obj of subject){
ret.push(fn.apply(this,[obj,...rest]));
}
return ret;
}
return fn.apply(this,[subject,...rest]);
}
}

过程抽象

HOF

装饰器

命令式/声明式

洗牌算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function*draw(cards){
const c=[...cards];
for(let i=c.length;i>0;i--){
const pIdx=Math.floor(Math.random()*i);
[c[pIdx],c[i-1]]=[c[i-1],c[pIdx]];
yield c[i-1];
}
}
function generate(amount,count){
if(count<=1)return [amount];
const cards=Array(amount-1).fill(0).map((_,i)=>i+1);
const pick=draw(cards);
for(let i=0;i<count;i++){
result.push(pick.next().value);
}
result.sort((a,b)=>a-b);
for(let i=count-1;i>0;i--){
result[i]=result[i]-result[i-1];
}
return result;
}

watch()

监视属性watch:

1 当被监视的属性变化时(也可以监视计算属性),回调函数自动调用,进行相关操作

2 监视的属性必须存在才能好进行监视

3 监视的两种写法:

(1)在Vue.createApp时传入watch配置

(2)通过vm.$watch监视

4 深度监视

(1)Vue中的watch默认不监视对象内部值的改变(一层)

(2)配置deep为true,可以检测对象内部值改变(多层)

监视多级结构中某个属性的变化:’number.a’

监视多级结构中所有属性第1变化,用deep:true

PS:(1)Vue自身可以检测对象内部值的改变但是Vue提供的watch默认不可以

(2)使用watch时根据数据的具体结构,决定是否采用深度监视

vm.$watch(expOrFn,callback,[options])

  • expOrFn:{string/Function}
  • callback:{Function/Object}
  • options:{Object}
    1. deep:{boolean}
    2. immediate:{boolean}

返回值:unwatch函数

用处:观察Vue实例上的一个表达式或者一个函数计算结果的变化,回调函数得到的参数为新值和旧值。表达式只接受简单的键路径,对于复杂表达式,用函数取代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//HTML
<div id="app">
{{a}},{{b}}
</div>
//键路径
<script type="text/javascript">
var vm=new Vue({
el:"#app",
data:{
a:{value1:'hello'},
b:{value2:'beautiful'}
}

});
vm.$watch('b.value2',function(newVal,oldVal){
console.log(newVal,oldVal);
});
vm.b.value2='bind';//bind beautiful
//修改对象内部的值但是侦听的是对象,对其属性或元素的更改不会触发侦听器,因为他们引用相同对象
vm.$watch('a',function(newVal,oldVal){
console.log(newVal,oldVal);
});
vm.a.value1='hei';//没有侦听到
//为了发现对象内部值的变化,可以在选项参数中指定 deep: true。这个选项同样适用于监听数组变更。
vm.$watch('a',function(newVal,oldVal){
console.log(newVal,oldVal);
},{deep:true});
vm.a.value1='hei';//
//在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调
vm.$watch('a',function(newVal,oldVal){
console.log(newVal,oldVal);
},{immediate:true});
vm.a={}//{_ob_:Observer}undefined {_ob_:Observer}{_ob_:Observer}
</script>

在带有immediate选项时不能在第一次回调时取消侦听给定的property

1
2
3
4
5
6
7
const unwatch=vm.$watch(
'a',
function(newVal,oldVal){
console.log(newVal,oldVal);
unwatch()
},{immediate:true}
)//报错

要在回调函数里调用一个取消侦听的函数应该先检查其函数的可用性

1
2
3
4
5
6
7
8
let unwatch=null;
unwatch=vm.$watch(
'a',
function(newVal,oldVal){
console.log(newVal,oldVal);
if(unwatch){unwatch();}
},{immediate:true}
)

Vue3中的watch

两个“坑”:

  1. watch监视的是reactive定义的响应式数据则无法正确获得oldValue,watch监视的是reactive定义的响应式数据则强制开启深度监视,只能获取newValue,无法获取oldValue

  2. 监视reactive定义的某个对象中的属性(也是对象),deep有效,仍然无法获取oldValue

关于.value:

  1. 如果ref定义的是一个数字或者字符,比如ref(0)不能用.value
  2. 如果ref定义的是一个对象,则里面的属性本质上是由reactive定义,如果要监视里面的属性则得用.value,.value后由reactive定义,则自动开启深度监视,或者开启深度监视
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 setup(){
let person=reactive({
firstName:"张",
lastName:"三",
fullName:'',
job:{
one:'doctor',
tow:'teacher'
}

})
let sum=ref(0)
let msg=ref("你好呀")
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log("sum变化了",newValue,oldValue)
})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log("sum或msg变化了",newValue,oldValue)
})
//情况三:监视reactive定义的响应式数据:若watch监视的是reactive定义的响应式数据则无法正确获得oldValue,watch监视的是reactive定义的响应式数据则强制开启深度监视,只能获取newValue,无法获取oldValue
watch(person,(newValue,oldValue)=>{
console.log("person变化了",newValue,oldValue)
},{deep:false})//deep配置无效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.firstName,(newValue,oldValue)=>{
console.log("person的firstName变化了",newValue,oldValue)
})

//情况五:监视reactive定义的一个响应式数据中的某些属性
watch([()=>person.firstName,()=>person.lastName],(newValue,oldValue)=>{
console.log("person的firstName/lastName变化了",newValue,oldValue)
})
//特殊情况监视reactive定义的某个对象中的属性(也是对象),deep有效,仍然无法获取oldValue
watch(()=>person.job,(newValue,oldValue)=>{
console.log("person的job变化了",newValue,oldValue)
},{deep:true})
person.fullName=computed({
get(){
return person.firstName+'-'+person.lastName

},
set(value){
const nameArr=value.split('-')
person.firstName=nameArr[0]
person.lastName=nameArr[1]

}
})
watch()
return {
person,
sum,
msg
}
}

1
2
3
4
5
6
7
8
9
10
11
function addLoadEvent(func){
var oldonload=window.onload;
if(typeof oldonload!='function'){
window.onload=func;//如果处理函数还没有绑定任何函数,就添加新函数
}else{
window.onload=function(){
oldonload();
func();
}
}
}

arr.reduce(callback(accumulator,currentValue,index,array),InitValue)

callback:执行数组中的每个值的函数,包括4个参数:

accumulator

  • currentValue

    数组中正在处理的元素。

  • index 可选

    数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。

  • array可选

    调用reduce()的数组

initialValue可选

作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

返回累计处理后的结果

求数组中的所有值的和

1
2
3
4
5
6
7
8
9
10
var sum=[0,1,2,3].reduce(function(accumulator,currentValue){
return accumulator+currentValue;
},0)
//6
//累加对象数组里的值
var InitValue=0;
var sum=[{x:1},{x:2},{x:3}].reduce(function(accumulator,currentValue){
return accumulator+currentValue;
},InitValue);
console.log(sum);//6

将二维数组转为一维

1
var flattened=[[0,1],[2,3],[4,5],[6,7]].reduce(function(a,b){return a.concat(b);},[])

计算数组中每个元素出现的个数

1
2
3
4
5
6
7
8
9
10
11
var names=['Alice','Bob','Ann','Alice','Bob'];
var countNames=names.reduce((allNames,name)=>{
if(name in allNames){
allNames[name]++;
}
else{
allNames[name]=1;
}
return allNames;
},{});
console.log(countNames);

按照属性对object分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var people=[
{name:'Alice',age:20},
{name:'Max',age:20},
{name:'Jane',age:21}
]
function groupBy(objectArray,property){
return objectArray.reduce(function(acc,obj){
var key=obj[property];
if(!acc[key]){
acc[key]=[];
}
acc[key].push(obj);
return acc;

},{});
}
var groupedPeople=groupBy(people,'age');
console.log(groupedPeople);

使用扩展运算符绑定包含在对象数组中的数组

1
2
3
4
5
6
7
8
9
var friends=[{
name:'Anna',
books:['Bible','Harry Potter'],
age:21
},
{name:'Bob',books:['War and Peace'],age:16},
{name:'Mike',books:['The Shining','The Lord of the Rings'],age:18}];
var allBooks=friends.reduce((prev,curr)=>{return [...prev,...curr.books]},['Alphabet']);
console.log(allBooks);

数组去重

1
2
3
4
5
6
7
8
9
let myArray=[6,6,6,2,3,3,4,4,4,5];
let myOrderedArray=myArray.reduce((accumulator,currval)=>{
if(accumulator.indexOf(currval)==-1){
accumulator.push(currval);
}
return accumulator;

},[])
console.log(myOrderedArray);

期约状态机

Promise可以通过 new 操作符来实例化。创建新期约时需要传入
执行器(executor)函数作为参数

1
2
3
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>

在把一个期约实例传给 console.log()时,控制台输出(可能因浏览器不同而略有差异)表明该实例处于待定(pending)状态。如前所述,期约是一个有状态的对象,可能处于如下 3 种状态之一:

  • 待定(pending)是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。无论落定为哪种状态都是不可逆的。只要从待定转换为兑现或拒绝,期约的状态就不再改变。而有时候也称为“解决”,resolved)

  • 兑现(fulfilled)每个期约只要状态切换为兑现,就会有一个私有的内部值(value)

  • 拒绝(rejected)如果期约被拒绝,程序就会期待期约状态改变时可以拿到拒绝的理由

    期约用途

    期约主要有两大用途。首先是抽象地表示一个异步操作。期约的状态代表期约是否完成。某些情况下,这个状态机就是期约可以提供的最有用的信息。

通过执行函数控制期约状态

期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。其中,控制期约状态的转换是
通过调用它的两个函数参数实现的。这两个函数参数通常都命名为 resolve()和 reject()。调用resolve()会把状态切换为兑现,调用 reject()会把状态切换为拒绝。另外,调用 reject()也会抛出错误

1
2
3
4
5
6
let p1=new Promise((resolve,reject)=>resolve());
setTimeout(console.log,0,p1);//Promise <resolved>
let p2 = new Promise((resolve, reject) => reject());
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught error (in promise)

在前面的例子中,并没有什么异步操作,因为在初始化期约时,执行器函数已经改变了每个期约的状态,执行器函数是同步执行的。这是因为执行器函数是期约的初始化程序。

添加 setTimeout 可以推迟切换状态:

1
2
3
4
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));
// 在 console.log 打印期约实例的时候,还不会执行超时回调(即 resolve())
setTimeout(console.log, 0, p); // Promise <pending>

无论 resolve()和 reject()中的哪个被调用,状态转换都不可撤销了。于是继续修改状态会静默失败,如下所示:

1
2
3
4
5
6
let p = new Promise((resolve, reject) => {
resolve();
reject(); // 没有效果
});
setTimeout(console.log, 0, p); // Promise <resolved>

Promise.resolve()

下面两个期约实例实际上是一样的:

let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();

这个解决的期约的值对应着传给 Promise.resolve()的第一个参数。使用这个静态方法,实际上可以把任何值都转换为一个期约

1
2
3
4
5
6
7
8
setTimeout(console.log,0,Promise.resolve());
// Promise <resolved>: undefined
setTimeout(console.log, 0, Promise.resolve(3));
// Promise <resolved>: 3
// 多余的参数会忽略
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise <resolved>: 4

对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。因此,Promise.resolve()可以说是一个幂等方法

1
2
3
4
5
6
7
8
9
10
11
let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p));
// true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));
// true
//这个幂等性会保留传入期约的状态:
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending>
setTimeout(console.log, 0, p === Promise.resolve(p)); // true

注意,这个静态方法能够包装任何非期约值,包括错误对象,并将其转换为解决的期约。因此,也可能导致不符合预期的行为:

1
2
3
4
let p = Promise.resolve(new Error('foo'));
setTimeout(console.log, 0, p);
// Promise <resolved>: Error: foo

Promise.reject()

与 Promise.resolve()类似,Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过 try/catch 捕获,而只能通过拒绝处理程序捕获)。下面的两个期约实例实际上是一样的:

let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();

这个拒绝的期约的理由就是传给 Promise.reject()的第一个参数。这个参数也会传给后续的拒绝处理程序:

1
2
3
4
let p = Promise.reject(3);
setTimeout(console.log, 0, p); // Promise <rejected>: 3
p.then(null, (e) => setTimeout(console.log, 0, e)); // 3

关键在于,Promise.reject()并没有照搬 Promise.resolve()的幂等逻辑。如果给它传一个期约对象,则这个期约会成为它返回的拒绝期约的理由:

1
2
3
setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
// Promise <rejected>: Promise <resolved>

同步/异步执行的二元性

1
2
3
4
5
6
7
8
9
10
11
12
try {
throw new Error('foo');
} catch(e) {
console.log(e); // Error: foo
}
try {
Promise.reject(new Error('bar'));
} catch(e) {
console.log(e);
}
// Uncaught (in promise) Error: bar

第一个 try/catch 抛出并捕获了错误,第二个 try/catch 抛出错误却没有捕获到,这里的同步代码之所以没有捕获期约抛出的错误,是因为它没有通过异步模式捕获错误。从这里就可以看出期约真正的异步特性:它们是同步对象(在同步执行模式中使用),但也是异步执行模式
的媒介。
在前面的例子中,拒绝期约的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理的。因此,try/catch 块并不能捕获该错误。代码一旦开始以异步模式执行,则唯一与之交互
的方式就是使用异步结构——更具体地说,就是期约的方法。

Promise.prototype.then()

Promise.prototype.then()是为期约实例添加处理程序的主要方法。这个 then()方法接收最多 两个参数:onResolved 处理程序和 onRejected 处理程序。这两个参数都是可选的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。传给 then()的任何非函数类型的参数都会被静 默忽略。如果想只提供 onRejected 参数,那就要在 onResolved 参数的位置上传入 undefined。这 样有助于避免在内存中创建多余的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function onResolved(id) { 
setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
setTimeout(console.log, 0, id, 'rejected');
}
let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));
let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));
p1.then(() => onResolved('p1'),
() => onRejected('p1'));
p2.then(() => onResolved('p2'),
() => onRejected('p2'));
//(3 秒后)
// p1 resolved
// p2 rejected
1
2
3
4
5
6
7
8
9
10
11
12
13
14

function onResolved(id) {
setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
setTimeout(console.log, 0, id, 'rejected');
}
let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));
let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));
// 非函数处理程序会被静默忽略,不推荐
p1.then('gobbeltygook');
// 不传 onResolved 处理程序的规范写法
p2.then(null, () => onRejected('p2'));
// p2 rejected(3 秒后

Promise.prototype.then()方法返回一个新的期约实例:

1
2
3
4
5
let p1 = new Promise(() => {}); 
let p2 = p1.then();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false

这个新期约实例基于 onResovled 处理程序的返回值构建。换句话说,该处理程序的返回值会通过 Promise.resolve()包装来生成新期约。如果没有提供这个处理程序,则 Promise.resolve()就会 包装上一个期约解决之后的值。如果没有显式的返回语句,则 Promise.resolve()会包装默认的返回 值 undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let p1 = Promise.resolve('foo'); 
// 若调用 then()时不传处理程序,则原样向后传
let p2 = p1.then();
setTimeout(console.log, 0, p2); // Promise <resolved>: foo
// 这些都一样
let p3 = p1.then(() => undefined);
let p4 = p1.then(() => {});
let p5 = p1.then(() => Promise.resolve());
setTimeout(console.log, 0, p3); // Promise <resolved>: undefined
setTimeout(console.log, 0, p4); // Promise <resolved>: undefined
setTimeout(console.log, 0, p5); // Promise <resolved>: undefined
//如果有显式的返回值,则 Promise.resolve()会包装这个值:

// 这些都一样
let p6 = p1.then(() => 'bar');
let p7 = p1.then(() => Promise.resolve('bar'));
setTimeout(console.log, 0, p6); // Promise <resolved>: bar
setTimeout(console.log, 0, p7); // Promise <resolved>: bar
// Promise.resolve()保留返回的期约
let p8 = p1.then(() => new Promise(() => {}));
let p9 = p1.then(() => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p8); // Promise <pending>
setTimeout(console.log, 0, p9); // Promise <rejected>: undefined
//抛出异常会返回拒绝的期约:

let p10 = p1.then(() => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p10); // Promise <rejected> baz
//注意,返回错误值不会触发上面的拒绝行为,而会把错误对象包装在一个解决的期约中:
...
let p11 = p1.then(() => Error('qux'));
setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux

onRejected 处理程序也与之类似:onRejected 处理程序返回的值也会被 Promise.resolve() 包装。乍一看这可能有点违反直觉,但是想一想,onRejected 处理程序的任务不就是捕获异步错误吗? 因此,拒绝处理程序在捕获错误后不抛出异常是符合期约的行为,应该返回一个解决期约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let p1 = Promise.reject('foo'); 
// 调用 then()时不传处理程序则原样向后传
let p2 = p1.then();
// Uncaught (in promise) foo
setTimeout(console.log, 0, p2); // Promise <rejected>: foo
// 这些都一样
let p3 = p1.then(null, () => undefined);
let p4 = p1.then(null, () => {});
let p5 = p1.then(null, () => Promise.resolve());
setTimeout(console.log, 0, p3); // Promise <resolved>: undefined
setTimeout(console.log, 0, p4); // Promise <resolved>: undefined
setTimeout(console.log, 0, p5); // Promise <resolved>: undefined
// 这些都一样
let p6 = p1.then(null, () => 'bar');
let p7 = p1.then(null, () => Promise.resolve('bar'));
setTimeout(console.log, 0, p6); // Promise <resolved>: bar
setTimeout(console.log, 0, p7); // Promise <resolved>: bar
// Promise.resolve()保留返回的期约
let p8 = p1.then(null, () => new Promise(() => {}));
let p9 = p1.then(null, () => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p8); // Promise <pending>
setTimeout(console.log, 0, p9); // Promise <rejected>: undefined
let p10 = p1.then(null, () => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p10); // Promise <rejected>: baz
let p11 = p1.then(null, () => Error('qux'));
setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux

Promise.prototype.catch()

Promise.prototype.catch()方法用于给期约添加拒绝处理程序。这个方法只接收一个参数: onRejected 处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用 Promise.prototype. then(null, onRejected)

1
2
3
4
5
6
7
let p = Promise.reject(); 
let onRejected = function(e) {
setTimeout(console.log, 0, 'rejected');
};
// 这两种添加拒绝处理程序的方式是一样的:
p.then(null, onRejected); // rejected
p.catch(onRejected); // rejected

Promise.prototype.catch()返回一个新的期约实例:

1
2
3
4
5
let p1 = new Promise(() => {}); 
let p2 = p1.catch();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false

Promise.prototype.finally()

Promise.prototype.finally()方法用于给期约添加 onFinally 处理程序,这个处理程序在期 约转换为解决或拒绝状态时都会执行。这个方法可以避免 onResolved 和 onRejected 处理程序中出 现冗余代码。但 onFinally 处理程序没有办法知道期约的状态是解决还是拒绝,所以这个方法主要用清理代码

1
2
3
4
5
6
7
let p1 = Promise.resolve(); 
let p2 = Promise.reject();
let onFinally = function() {
setTimeout(console.log, 0, 'Finally!')
}
p1.finally(onFinally); // Finally
p2.finally(onFinally); // Finally

Promise.prototype.finally()方法返回一个新的期约实例:

1
2
3
4
5
let p1 = new Promise(() => {}); 
let p2 = p1.finally();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false

这个新期约实例不同于 then()或 catch()方式返回的实例。因为 onFinally 被设计为一个状态 无关的方法,所以在大多数情况下它将表现为父期约的传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let p1 = Promise.resolve('foo'); 
// 这里都会原样后传
let p2 = p1.finally();
let p3 = p1.finally(() => undefined);
let p4 = p1.finally(() => {});
let p5 = p1.finally(() => Promise.resolve());
let p6 = p1.finally(() => 'bar');
let p7 = p1.finally(() => Promise.resolve('bar'));
let p8 = p1.finally(() => Error('qux'));
setTimeout(console.log, 0, p2); // Promise <resolved>: foo
setTimeout(console.log, 0, p3); // Promise <resolved>: foo
setTimeout(console.log, 0, p4); // Promise <resolved>: foo
setTimeout(console.log, 0, p5); // Promise <resolved>: foo
setTimeout(console.log, 0, p6); // Promise <resolved>: foo
setTimeout(console.log, 0, p7); // Promise <resolved>: foo
setTimeout(console.log, 0, p8); // Promise <resolved>: foo
//如果返回的是一个待定的期约,或者 onFinally 处理程序抛出了错误(显式抛出或返回了一个拒绝期约),则会返回相应的期约(待定或拒绝)
// Promise.resolve()保留返回的期约
let p9 = p1.finally(() => new Promise(() => {}));
let p10 = p1.finally(() => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p9); // Promise <pending>
setTimeout(console.log, 0, p10); // Promise <rejected>: undefined
let p11 = p1.finally(() => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p11); // Promise <rejected>: baz

非重入期约方法

当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟在添加这个处 理程序的代码之后的同步代码一定会在处理程序之前先执行。即使期约一开始就是与附加处理程序关联 的状态,执行顺序也是这样的。这个特性由 JavaScript 运行时保证,被称为“非重入”(non-reentrancy) 特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let synchronousResolve; 
// 创建一个期约并将解决函数保存在一个局部变量中
let p = new Promise((resolve) => {
synchronousResolve = function() {
console.log('1: invoking resolve()');
resolve();
console.log('2: resolve() returns');
};
});
p.then(() => console.log('4: then() handler executes'));
synchronousResolve();
console.log('3: synchronousResolve() returns');
// 实际的输出:
// 1: invoking resolve()
// 2: resolve() returns
// 3: synchronousResolve() returns
// 4: then() handler executes

在这个例子中,即使期约状态变化发生在添加处理程序之后,处理程序也会等到运行的消息队列让 它出列时才会执行。

传递解决值和拒绝理由

到了落定状态后,期约会提供其解决值(如果兑现)或其拒绝理由(如果拒绝)给相关状态的处理 程序。拿到返回值后,就可以进一步对这个值进行操作。比如,第一次网络请求返回的 JSON 是发送第 二次请求必需的数据,那么第一次请求返回的值就应该传给 onResolved 处理程序继续处理。当然,失 败的网络请求也应该把 HTTP 状态码传给 onRejected 处理程序。

在执行函数中,解决的值和拒绝的理由是分别作为 resolve()和 reject()的第一个参数往后传 的。然后,这些值又会传给它们各自的处理程序,作为 onResolved 或 onRejected 处理程序的唯一 参数。

1
2
3
4
let p1 = new Promise((resolve, reject) => resolve('foo')); 
p1.then((value) => console.log(value)); // foo
let p2 = new Promise((resolve, reject) => reject('bar'));
p2.catch((reason) => console.log(reason)); // bar

then()和 catch()的 onRejected 处理程序在语义上相当于 try/catch。出发点都是捕获错误之 后将其隔离,同时不影响正常逻辑执行。为此,onRejected 处理程序的任务应该是在捕获异步错误之 后返回一个解决的期约。下面的例子中对比了同步错误处理与异步错误处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log('begin synchronous execution'); 
try {
throw Error('foo');
} catch(e) {
console.log('caught error', e);
}
console.log('continue synchronous execution');
// begin synchronous execution
// caught error Error: foo
// continue synchronous execution
new Promise((resolve, reject) => {
console.log('begin asynchronous execution');
reject(Error('bar'));
}).catch((e) => {
console.log('caught error', e);
}).then(() => {
console.log('continue asynchronous execution');
});
// begin asynchronous execution
// caught error Error: bar
// continue asynchronous execution

期约连锁

每个后续的处理程序都会等待前一个期约解决,然后实例化一个新期约并返回它。这种结构可以简 洁地将异步任务串行化,解决之前依赖回调的难题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function delayedResolve(str) { 
return new Promise((resolve, reject) => {
console.log(str);
setTimeout(resolve, 1000);
});
}
delayedResolve('p1 executor')
.then(() => delayedResolve('p2 executor'))
.then(() => delayedResolve('p3 executor'))
.then(() => delayedResolve('p4 executor'))
// p1 executor(1 秒后)
// p2 executor(2 秒后)
// p3 executor(3 秒后)
// p4 executor(4 秒后)

Promise.all()

Promise.all方法用于将多个 Promise 实例,这个静态方法接收一个可迭代对象,将参数转为 Promise 实例,再包装成一个新的 Promise 实例。

  • 合成的期约只会在每个包含的期约都解决之后才解决
  • 如果至少有一个包含的期约待定,则合成的期约也会待定。如果有一个包含的期约拒绝,则合成的 期约也会拒绝
1
2
3
4
5
6
7
//永远待定
let p1=Promise.all([new Promise(()=>{})]);
setTimeout(console.log,0,p1);//Promise <pending>
//一次拒绝会导致最终期约拒绝
let p2=Promise.all([Promise.resolve(),Promise.reject(),Promise.resolve()]);
setTimeout(console.log,0,p2);//Promise <rejected>
//Uncaught (in promise) undefined

如果所有期约都成功解决,则合成期约的解决值就是所有包含期约解决值的数组,按照迭代器顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let p = Promise.all([ 
Promise.resolve(3),
Promise.resolve(),
Promise.resolve(4)
]);
p.then((values) => setTimeout(console.log, 0, values)); // [3, undefined, 4]
// 虽然只有第一个期约的拒绝理由会进入
// 拒绝处理程序,第二个期约的拒绝也
// 会被静默处理,不会有错误跑掉
let p = Promise.all([
Promise.reject(3),
new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
p.catch((reason) => setTimeout(console.log, 0, reason)); // 3

Promise.race()

Promise.race()静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。这个 方法接收一个可迭代对象,返回一个新期约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 解决先发生,超时后的拒绝被忽略
let p1 = Promise.race([
Promise.resolve(3),
new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
setTimeout(console.log, 0, p1); // Promise <resolved>: 3
// 拒绝先发生,超时后的解决被忽略
let p2 = Promise.race([
Promise.reject(4),
new Promise((resolve, reject) => setTimeout(resolve, 1000))
]);
setTimeout(console.log, 0, p2); // Promise <rejected>: 4
// 迭代顺序决定了落定顺序
let p3 = Promise.race([
Promise.resolve(5),
Promise.resolve(6),
Promise.resolve(7)
]);
setTimeout(console.log, 0, p3); // Promise <resolved>: 5

串行期约合成

1
2
3
4
5
6
7
function addTwo(x){return x+1;}
function addThree(x){return x+3;}
function addFive(x){return x+5;}
function addTen(x){
return [addTwo,addThree,addFive].reduce((promise,fn)=>promise.then(fn),Promise.resolve(x));
}
addTen(8).then(console.log);

done

Promise 对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
Promise.prototype.done=function(onResolved,onRejected){
this.then(onResolved,onRejected)
.catch(function(reason){
//抛出一个全局错误
setTimeout(()=>{throw reason},0);
});

}

finally

finally()f方法用于指定不管Promise对象最后状态如何,都会执行的操作,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

1
2
3
4
5
6
7
8
9
10
11
12
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
Promise.prototype.finally=function(callback){
let P=this.constructor;
return this.then(
value=>P.resolve(callback()).then(()=>value),
reason=>P.resolve(callback()).then(()=>{throw reason})
);
}

用途

将图片的加载写成一个Promise,一旦加载完成,Promise的状态发生变化

1
2
3
4
5
6
7
8
9
//加载图片
const preloadImage=function(path){
return new Promise(function(resolve,reject){
const image=new Image();
image.onload=resolve;
image.onerror=reject;
image.src=path;
})
}

把它当成一种数据格式,而不是编程语言。JSON 不属于 JavaScript,
它们只是拥有相同的语法而已。JSON 也不是只能在 JavaScript 中使用,它是一种通用数据格式。很多语言都有解析和序列化 JSON 的内置能力。

语法

  • 简单值:字符串、数值、布尔值和 null 可以在 JSON 中出现,就像在 JavaScript 中一样。特殊值 undefined 不可以。
  • 对象:第一种复杂数据类型,对象表示有序键/值对。每个值可以是简单值,也可以是复杂类型。
  • 数组:第二种复杂数据类型,数组表示可以通过数值索引访问的值的有序列表。数组的值可以是任意类型,包括简单值、对象,甚至其他数组。

简单值

JavaScript 字符串与 JSON 字符串的主要区别是,JSON 字符串必须使用双引号(单引号会导致语法错误)。布尔值和 null 本身也是有效的 JSON 值

对象

与 JavaScript 对象字面量相比,JSON 主要有两处不同。首先,没有变量声明(JSON 中没有变量)。其次,最后没有分号(不需要,因为不是 JavaScript 语句)。同样,用引号将属性名包围起来才是有效的JSON。属性的值可以是简单值或复杂数据类型值,后者可以在对象中再嵌入对象.

1
2
3
4
5
6
7
8
9
{
"name": "Nicholas",
"age": 29,
"school": {
"name": "Merrimack College",
"location": "North Andover, MA"
}
}

数组

数组在 JSON 中使用 JavaScript 的数组字面量形式表示.

JavaScript

1
2
let values = [25, "hi", true];

Json

1
[25,"hi",true]

JavaScript序列化为Json

stringfy():在序列化 JavaScript 对象时,所有函数和原型成员都会有意地在结果中省略。此外,值为 undefined的任何属性也会被跳过。最终得到的就是所有实例属性均为有效 JSON 数据类型的表

1
2
3
4
5
6
7
8
9
10
11
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book);

结果:

{“title”:”Professional JavaScript”,”authors”:[“Nicholas C. Zakas”,”Matt Frisbie”],
“edition”:4,”year”:2017}

还可以接收两个参数。这两个参数可以用于指定其他序列化 JavaScript 对象的方式。第一个参数是过滤器,可以是数组或函数;第二个参数是用于缩进结果 JSON 字符串的选项

1
2
3
4
5
6
7
8
9
10
11
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book, ["title", "edition"]);

  • 如果第二个参数是一个函数,则行为又有不同。提供的函数接收两个参数:属性名(key)和属性值(value)。可以根据这个 key 决定要对相应属性执行什么操作。这个 key 始终是字符串,只是在值
    不属于某个键/值对时会是空字符串
  • 返回的值就是相应 key 应该包含的结果。注意,返回 undefined 会导致属性被忽略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};
let jsonText = JSON.stringify(book, (key, value) => {
switch(key) {
case "authors":
return value.join(",")
case "year":
return 5000;
case "edition":
return undefined;
default:
return value;
}
});
/*{"title":"Professional JavaScript","authors":"Nicholas C. Zakas,Matt
Frisbie","year":5000}*/


JSON.stringify()方法的第三个参数控制缩进和空格。在这个参数是数值时,表示每一级缩进的空格数。

1
2
3
4
5
6
7
8
9
10
11
12
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017
};

let jsonText = JSON.stringify(book, null, 4);

toJSON():在要序列化的对象中添加 toJSON()方法,序列化时会基于这个方法返回适当的 JSON 表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017,
toJSON: function() {//箭头函数不能定义toJSON(),因为剪头函数的词法作用域是全局作用域,在这种情况不合适
return this.title;
}
};
let jsonText = JSON.stringify(book);//book对象返回图书的书名(this.title)

toJSON()方法可以与过滤函数一起使用,在把对象传给 JSON.stringify()时会执行如下步骤。

(1) 如果可以获取实际的值,则调用 toJSON()方法获取实际的值,否则使用默认的序列化。
(2) 否则,使用默认序列化时,如果提供了第二个参数,则应用过滤。
(3) 第(2)步返回的每个值都会相应地进行序列化。
(4) 如果提供了第三个参数,则相应地进行缩进。

ES6中堆JSON.stringfy的改造

UTF-8 标准规定,0xD8000xDFFF之间的码点,不能单独使用,必须配对使用。比如,\uD834\uDF06是两个码点,但是必须放在一起配对使用,代表字符𝌆。这是为了表示码点大于0xFFFF的字符的一种变通方法。单独使用\uD834\uDFO6这两个码点是不合法的,或者颠倒顺序也不行,因为\uDF06\uD834并没有对应的字符。

JSON.stringify()的问题在于,它可能返回0xD8000xDFFF之间的单个码点。

1
JSON.stringify('\u{D834}') // "\u{D834}"

为了确保返回的是合法的 UTF-8 字符,ES2019 改变了JSON.stringify()的行为。如果遇到0xD8000xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。

1
JSON.stringify('\u{D834}') // ""\\uD834""JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""

解析选项

JSON.parse():接收一个参数,这个函数称为还原函数,还原函数接收两个参数,属性名key和属性名value,如果还原函数返回undefined,则结果中删除相应键,如果返回了其他任何值,则该值就会成为相应键的值插入到结果中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas",
"Matt Frisbie"
],
edition: 4,
year: 2017,
releaseDate: new Date(2017, 11, 1)
};
let jsonText = JSON.stringify(book);
let bookCopy = JSON.parse(jsonText,
(key, value) => key == "releaseDate" ? new Date(value) : value);
alert(bookCopy.releaseDate.getFullYear());//对象仙贝序列化为JSON字符串,又被重新解析为一个对象bookCopy,还原函数查找"releaseDate"键,找到后根虎日期字符创建新的Date对象,得到的bookCopy.releaseDate属性又变回Date对象,可以调用其getFullYear()方法

JavaScript字符表示

JavaScript 字符串由 16 位码元(code unit)组成。对多数字符来说,每 16 位码元对应一个字符。换
句话说,字符串的 length 属性表示字符串包含多少 16 位码元

JavaScript 字符串使用了两种 Unicode 编码混合的策略:UCS-2 和 UTF-16。对于可以采用 16 位编码
的字符(U+0000~U+FFFF),这两种编码实际上是一样的。

查看指定码元

charCodeAt()

1
2
3
4
5
6
let message = "abcde";
// Unicode "Latin small letter C"的编码是 U+0063
console.log(message.charCodeAt(2)); // 99
// 十进制 99 等于十六进制 63
console.log(99 === 0x63); // true

这个对应关系在扩展到 Unicode 增补字符平面时就不成立了。即 16 位只能唯一表示,65 536 个字符。这对于大多数语言字符集是足够了,在 Unicode 中称为基本多语言平面(BMP)。为了表示更多的字符,Unicode 采用了一个策略,即每个字符使用另外 16 位去选择一个增补平面。这种每个字符使用两个 16 位码元的策略称为代理对。

codePointAt()

为正确解析既包含单码元字符又包含代理对字符的字符串,可以使用 codePointAt()来代替charCodeAt()。跟使用 charCodeAt()时类似,codePointAt()接收 16 位码元的索引并返回该索引位置上的码点(code point)。码点是 Unicode 中一个字符的完整标识。比如,”c”的码点是 0x0063,而”☺”的码点是 0x1F60A。码点可能是 16 位,也可能是 32 位,而 codePointAt()方法可以从指定码元位置识别完整的码点。

1
2
3
4
5
6
let message = "ab☺de";
console.log(message.codePointAt(1)); // 98
console.log(message.codePointAt(2)); // 128522
console.log(message.codePointAt(3)); // 56842
console.log(message.codePointAt(4)); // 100

给定UTF-16码原创建字符

fromCharCode()

1
2
3
4
5
6
7
8
9
10
11
12
13
// Unicode "Latin small letter A"的编码是 U+0061
// Unicode "Latin small letter B"的编码是 U+0062
// Unicode "Latin small letter C"的编码是 U+0063
// Unicode "Latin small letter D"的编码是 U+0064
// Unicode "Latin small letter E"的编码是 U+0065
console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65)); // "abcde"
// 0x0061 === 97
// 0x0062 === 98
// 0x0063 === 99
// 0x0064 === 100
// 0x0065 === 101
console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"

fromCodePoint()

fromCodePoint():这个方法接收任意数量的码点,返回对应字符拼接起来的字符串

1
2
3
4
console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101)); // ab☺de
console.log(String.fromCodePoint(97, 98, 128522, 100, 101)); // ab☺de


normalize()规范化

Unicode提供了 4种规范化形式,可以将类似上面的字符规范化为一致的格式,无论底层字符的代码是什么。这 4种规范化形式是:NFD(Normalization Form D)、NFC(Normalization Form C)、
NFKD(Normalization Form KD)和 NFKC(Normalization Form KC)。可以使用 normalize()方法对字符串应用上述规范化形式,使用时需要传入表示哪种形式的字串:”NFD”、”NFC”、”NFKD”或”NFKC”。

通过比较字符串与其调用 normalize()的返回值,就可以知道该字符串是否已经规范化了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let a1 = String.fromCharCode(0x00C5),
a2 = String.fromCharCode(0x212B),
a3 = String.fromCharCode(0x0041, 0x030A);
// U+00C5 是对 0+212B 进行 NFC/NFKC 规范化之后的结果
console.log(a1 === a1.normalize("NFD")); // false
console.log(a1 === a1.normalize("NFC")); // true
console.log(a1 === a1.normalize("NFKD")); // false
console.log(a1 === a1.normalize("NFKC")); // true
// U+212B 是未规范化的
console.log(a2 === a2.normalize("NFD")); // false
console.log(a2 === a2.normalize("NFC")); // false
console.log(a2 === a2.normalize("NFKD")); // false
console.log(a2 === a2.normalize("NFKC")); // false
// U+0041/U+030A 是对 0+212B 进行 NFD/NFKD 规范化之后的结果
console.log(a3 === a3.normalize("NFD")); // true
console.log(a3 === a3.normalize("NFC")); // false
console.log(a3 === a3.normalize("NFKD")); // true
console.log(a3 === a3.normalize("NFKC")); // false

未规范化:

1
2
3
4
5
6
7
8
let a1 = String.fromCharCode(0x00C5),
a2 = String.fromCharCode(0x212B),
a3 = String.fromCharCode(0x0041, 0x030A);
console.log(a1, a2, a3); // Å, Å, Å
console.log(a1 === a2); // false
console.log(a1 === a3); // false
console.log(a2 === a3); // false

选择同一种规范化形式可以让比较操作符返回正确的结果:

1
2
3
4
5
6
7
let a1 = String.fromCharCode(0x00C5),
a2 = String.fromCharCode(0x212B),
a3 = String.fromCharCode(0x0041, 0x030A);
console.log(a1.normalize("NFD") === a2.normalize("NFD")); // true
console.log(a2.normalize("NFKC") === a3.normalize("NFKC")); // true
console.log(a1.normalize("NFC") === a3.normalize("NFC")); // true

媒体查询常被用于以下目的:

  • 有条件的通过 @media@import at-rulesCSS 装饰样式。

  • media= 属性为

DOMTokenList.toggle()

DOMTokenList接口的toggle()方法从列表中删除一个给定的标记并返回false,如果标记不存在,则添加并且函数返回true

tokenList.toggle(token,force);

参数:

token:标记列表中你想探查并切换的DOMSring

force(可选):Boolean值,设置后会将方法变成单向操作,如果设置为false,则会删除标记列表中匹配的给定标记,且不会再添加,如设置为true,则将在标记中添加给定标记,且不会再度删除

返回值:

为布尔值

eg

HTML

1
<span class="a b">classList is 'a b'</span>

JavaScript

1
2
3
4
5
6
7
8
9
10
var span = document.querySelector("span");
var classes = span.classList;
span.onclick = function() {
var result = classes.toggle("c");
if(result) {
span.textContent = "'c' added; classList is now '" + classes + "'.";
} else {
span.textContent = "'c' removed; classList is now '" + classes + "'.";
}
}