每日一题

链式调用

链式调用是常见的js一种函数方式,我们看看他是如何实现的

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
*
* 链式调用
*
* 原理: 都是通过返回对象供之后进行调用。
* 1、this的作用域链,jQuery的实现方式,通常链式调用都是采用这种方式。
* 2、返回对象本身,同 this 的区别就是显式返回链式对象
* 3、闭包返回对象的方式实现,与函数柯里化类似
*
* !!! 箭头函数this指向
*/


let Person = function() {};
Person.prototype.setAge = function(age){
this.age = age;
log(`ta${age}岁`)
return this;
}
Person.prototype.setWeight = function(weight){
this.weight = weight;
log(`体重${weight}kg`)

return this;
}

Person.prototype.eat = function (food) {
this.food = food
log(`eat ${food}`)
return this
}

Person.prototype.run = function(merter) {
this.merter = merter
log(`run ${merter}米`)
return this
}

Person.prototype.sleep = function (hour) {
this.hour = hour
log(`sleep ${hour} hours`)
return this
}


var person = new Person();

person.setAge(19).setWeight(110).eat('apple').run(30).sleep(8)


// 使用对象形式

const Monkey = {
name: null,
age: null,
weight: null,
setName: function(name) {
this.name = name
return this
},
setAge: function(age) {
this.age = age;
return this
},
setWeight: function(weight) {
this.weight = weight
return this
},
get: function() {
let desc = `TA is ${this.name} and ${this.age}岁 、 ${this.weight}kg`
log(desc)
}
}


Monkey.setName('monkey').setAge(9).setWeight(23).get()

js版本 sleep 函数

sleep 函数,作用是使程序暂停指定的时间,其他语言自带sleep函数,而JS却需要稍微改造一下,已实现sleep函数

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

// 通过 while(true)死循环实现

function sleep (time) {
let timeStamp = new Date().getTime();
let endTime = timeStamp + time;
while(true) {
if ( new Date().getTime() > endTime) {
return
}
}
}

// Promise 实现

function sleep2 (delay) {
return new Promise((res, rej) => {
setTimeout(res, delay)
})
}

// 思考: 以下执行顺序是什么?原理

log(1)
sleep2(1000).then(log(2))
log(3)
sleep(1000)
log(4)

发布订阅者模式

这是经典的一个开发模式,很多场景以及sdk会使用它

1、创建一个 EventEmitter 类
2、在该类上创建一个事件中心(Map)
3、on 方法用来把函数 fn 都加到事件中心中(订阅者注册事件到调度中心)
4、emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码)
5、off 方法可以根据 event 值取消订阅(取消订阅)
6、once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
7、注册一个 newListener 用于监听新的事件订阅

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

class EventEmitter {
// 用来存放注册的事件与回调
constructor() {
this._events = {}
}

// 将事件回调函数存储到对应的事件上
on (eventName, callback) {

// 如果绑定的事件不是newListener 就触发改回调
if (this._events[eventName]) {
if(this.eventName !== "newListener"){
this.emit("newListener", eventName)
}
}

// 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
const callbacks = this._events[eventName] || []
callbacks.push(callback)
this._events[eventName] = callbacks
}

// args 用于收集发布事件时传递的参数
emit (eventName, ...args) {
const callbacks = this._events[eventName] || []
// 获取到事件对应的回调函数依次执行
callbacks.forEach(cb => cb(...args))
}

// 找到事件对应的回调函数,删除对应的回调函数
off (eventName, callback) {
const callbacks = this._events[eventName] || []
const newCallBacks = callbacks.filter(cb => cb != callback && cb.initialCallback != callback )
// 基本思路: 未注销的函数依旧保留
this._events[eventName] = newCallBacks
}

once (eventName, callback ) {
// 由于需要在回调函数执行后,取消订阅当前事件,所以需要对传入的回调函数做一层包装,然后绑定包装后的函数
const one = (...args) => {
callback(...args)
this.off(eventName, one)
}

// 由于:我们订阅事件的时候,修改了原回调函数的引用,所以,用户触发 off 的时候不能找到对应的回调函数
// 所以,我们需要在当前函数与用户传入的回调函数做一个绑定,我们通过自定义属性来实现
one.initialCallback = callback
this.on(eventName, one)
}
}

手写Promise函数

Promise的出现解决了之前回调地狱的状况。

基本概念:

  1. Promise三个状态: pending、fulfilled、 rejected.默认状态pending
  2. new Promise接受 executor()执行器,内含参数 resolve和reject
  3. 状态单向变更且无法更改
  4. promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;
  5. 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value;
  6. 如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason;
  7. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected;
  8. catch() 方法返回一个Promise,并且处理拒绝的情况。我们知道then方法的第二个参数其实就是干这个用的,catch只是一个别名。
  9. finally和catch方法只是then的一个别名,实际上返回的还是一个promise,完全可以这样写:promise.then().finally().then().catch().then()

按照这个思路,我们实现Promise

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
58
59

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class MyPromise {
constructor(executor) {
this.status = PENDING,
this.value = undefined
this.reason = undefined

this.onResolvedCb = []

this.onRejectedCb = []

const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
this.onResolvedCb.forEach(cb => cb())
}
}

const reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCb.forEach(cb => cb())
}
}

try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}


then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}

if (this.status === REJECTED) {
onRejected(this.reason)
}

if (this.status === PENDING) {
this.onResolvedCb.push(() => {
onFulfilled(this.value)
})

this.onRejectedCb.push(() => {
onRejected(this.reason)
})
}
}
}

上述写法通过观察者模式,该模式一般为 收集依赖 -》触发通知 =》执行依赖,在 Promise 中执行顺序为 then收集依赖 - 异步出发 - resolve - resolve执行依赖

手写Promise.all

Promise是常用的异步处理方式,他有 all、allSettled、race 三个方法

共同点: 都是将多个Promise组合成一个返回

不同点: race 谁先返回谁先输出;all 只有有一个失败及全部失败,全部成功才会返回;allSettled 不管是否成功都会返回,且每个返回携带 Promise 状态。

这里我们实现 Promise.all 函数

核心思路!: 返回一个大Promise,然后遍历内部promise函数,执行成功计数器加一,全部执行完毕则返回

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

const PromiseAll = function(promiseArr=[]) {
return new Promise((resolve, reject) => {
let result = []
let completeIndex = 0
if (!Array.isArray(promiseArr)) {
const type = typeof promiseArr
reject(`TypeError: ${type} ${promiseArr} is not iterable`)
}
promiseArr.forEach((promise, index) => {
Promise.resolve(promise).then(res => {
result[index] = res
completeIndex++
if (promiseArr.length === completeIndex ) {
resolve(result)
}
}).catch(err => {
reject(err)
})
})
})
}

// 示例用法
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'foo');
});
promiseAll([promise1, promise2, promise3])
.then((results) => {
console.log(results); // [3, 42, 'foo']
})
.catch((error) => {
console.error(error);
});

npm包开发-软链

走过路过,留下买路财,壮士