导航
[[深刻 01] 执行上下文 ](https://juejin.im/post/684490…
[[深刻 02] 原型链 ](https://juejin.im/post/684490…
[[深刻 03] 继承 ](https://juejin.im/post/684490…
[[深刻 04] 事件循环 ](https://juejin.im/post/684490…
[[深刻 05] 柯里化 偏函数 函数记忆 ](https://juejin.im/post/684490…
[[深刻 06] 隐式转换 和 运算符 ](https://juejin.im/post/684490…
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…
[[深刻 08] 前端平安 ](https://juejin.im/post/684490…
[[深刻 09] 深浅拷贝 ](https://juejin.im/post/684490…
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…
[[深刻 11] 前端路由 ](https://juejin.im/post/684490…
[[深刻 12] 前端模块化 ](https://juejin.im/post/684490…
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定 ](https://juejin.im/post/684490…
[[深刻 14] canvas](https://juejin.im/post/684490…
[[深刻 15] webSocket](https://juejin.im/post/684490…
[[深刻 16] webpack](https://juejin.im/post/684490…
[[深刻 17] http 和 https](https://juejin.im/post/684490…
[[深刻 18] CSS-interview](https://juejin.im/post/684490…
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…
[[深刻 20] 手写函数 ](https://juejin.im/post/684490…
[[react] Hooks](https://juejin.im/post/684490…
[[部署 01] Nginx](https://juejin.im/post/684490…
[[部署 02] Docker 部署 vue 我的项目 ](https://juejin.im/post/684490…
[[部署 03] gitlab-CI](https://juejin.im/post/684490…
[[源码 -webpack01- 前置常识] AST 形象语法树 ](https://juejin.im/post/684490…
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程 ](https://juejin.im/post/684490…
[[源码] Redux React-Redux01](https://juejin.im/post/684490…
[[源码] axios ](https://juejin.im/post/684490…
[[源码] vuex ](https://juejin.im/post/684490…
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…
[[源码 -vue03] watch 侦听属性 – 初始化和更新 ](https://juejin.im/post/684490…
[[源码 -vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490…
[[源码 -vue05] Vue.extend ](https://juejin.im/post/684490…
[[源码 -vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790…
前置常识
一些单词
bail:保险,保障
parallel:并行的
series:串行的,间断的
optional:可选的
// All Hook constructors take one optional argument, which is a list of argument names as strings.
// 所有的 Hook 构造函数都接管一个可选的参数,是一个参数名称字符串组成的数组
practice:实际
// The best practice is to expose all hooks of a class in a hooks property
// 最佳实际是在 hooks 属性中公开类的所有钩子
accelerate:减速
brake:刹车
waterfall:瀑布
公布订阅模式
- 发布者:publisher
- 订阅者:subscribe
- 中介:topic/event channel
-
(<font color=red> 中介 </font>) 既要接管 (<font color=red> 发布者 </font>) 公布的音讯,又要将音讯派发给订阅了该事件的 (<font color=red> 订阅者 </font>)
- (中介) 须要依据 (不同的事件),贮存相应的 (订阅者) 信息
- 通过 (中介对象) 齐全 (解耦) 了 (发布者) 和 (订阅者)
-
公布订阅模式代码实现 – es5
// 公布订阅模式 <script> const pubsub = {}; // pubsub 中介对象 // pubsub 中具备 subscribe,unSubscribe,publish 等办法 (function(pubsub) {const topics = {}; // topics 是一个 (事件) 和订阅该事件的 (订阅者) 对应关系的一个对象 // key:是不同的事件 // value:是订阅了该事件的订阅者的数组 // event_name: [{fname: fn.name, fn}] // 订阅 pubsub.subscribe = function(eventName, fn) { // 不存在该事件对应的订阅者对象数组,新建 // 存在,向数组中增加订阅者对象 if (!topics[eventName]) {topics[eventName] = []} topics[eventName].push({ fname: fn.name, fn }) } // 公布 pubsub.publish = function(eventName, params) {if (!topics[eventName].length) {return} // 取出所有订阅者中的更新函数,并执行 topics[eventName].forEach((item, index) => {item.fn(params) }) } // 勾销订阅 pubsub.unSubscribe = function(eventName, fname) {if (!topics[eventName]) {return} topics[eventName].forEach((item, index) => {if (item.fname === fname) {topics[eventName].splice(index, 1) // 删除该事件对应的订阅者对象的更新函数同名的订阅者对象 } }) } })(pubsub) function goFn1(params) {console.log('goFn1', params) } function goFn2(params) {console.log('goFn2', params) } pubsub.subscribe('go', goFn1) // 订阅 pubsub.subscribe('go', goFn2) pubsub.publish('go', '1111111') // 公布 pubsub.unSubscribe('go', goFn2.name) // 勾销订阅 pubsub.publish('go', '22222') </script>
-
公布订阅模式代码实现 – es6
<script> class PubSub { // PubSub 类 constructor() {this.topics = {} // 保护事件和订阅者对应关系的对象 // 事件 <-> 订阅者数组 } subscribe(eventName, fn) { // 订阅 if (!this.topics[eventName]) {this.topics[eventName] = []} this.topics[eventName].push({ fname: fn.name, fn }) } publish(eventName, params) { // 公布 if (!this.topics[eventName].length) {return} this.topics[eventName].forEach((item, index) => {item.fn(params) }) } unSubscribe(eventName, fname) { // 勾销订阅 if (!this.topics[eventName]) {return} this.topics[eventName].forEach((item, index) => {if (item.fname === fname) {this.topics[eventName].splice(index, 1) } }) } } const pubsub = new PubSub() function goFn1(params) {console.log('goFn1', params) } function goFn2(params) {console.log('goFn2', params) } pubsub.subscribe('go', goFn1) pubsub.subscribe('go', goFn2) pubsub.publish('go', '1') // 公布 pubsub.unSubscribe('go', goFn2.name) // 勾销订阅 pubsub.publish('go', '2') </script>
Tapable
- 外围就是公布订阅模式
- 装置:
npm install tapable -S
SyncHook
- 同步形式的公布订阅模式
const synchook = new SyncHook(['params'])
synchook.tap() // 订阅,注册
-
synchook.call() // 公布,执行
const {SyncHook} = require('tapable') class Work {constructor() { this.hooks = {city: new SyncHook(['who']) // 这里要求在订阅事件时的回调中须要传参 } } // 订阅,注册 tap(eventName) {this.hooks.city.tap(eventName, function(who){console.log(who, eventName) }) } // 公布,执行 publish() {this.hooks.city.call('woow_wu7') } } const work = new Work() work.tap('chongqing') // 订阅 work.tap('hangzhou') work.publish() // 公布
--------- 比照参考 const {SyncHook} = require('tapable') class Lesson {constructor() { this.hook = {arch: new SyncHook(['name']) // 同步钩子,在 .tap() 函数的参数函数中接管一个参数} } tap() {this.hook.arch.tap('react', (name) => {// name 参数是在 .call() 时传入的 console.log('react', name) }) this.hook.arch.tap('vue', (name) => {console.log('vue', name) }) } publish() {this.hook.arch.call('woow_wu7') } } const lesson = new Lesson(['name']) lesson.tap() // 注册 lesson.publish() // 公布
SyncHook 模仿实现
class SyncHook {constructor() {this.observers = []
// observers 是观察者对象组成的数组,观察者对象中蕴含 event 事件名称, 和 fn 工作函数
// [{event:eventName, fn: fn}]
}
// 注册观察者对象
// 更简略点能够间接注册事件
tap(eventName, fn) {
this.observers.push({
event: eventName,
fn
})
}
// 执行注册的观察者对象中的 fn 函数
call(...params) {this.observers.forEach(item => item.fn(item.event, ...params))
}
}
const synchook = new SyncHook(['params'])
synchook.tap('react', function(eventName, name) {console.log(eventName, name)
})
synchook.tap('vue', function(eventName, name) {console.log(eventName, name)
})
synchook.call('woow_wu7')
SyncBailHook
- SyncBailHook 提供了 (终止执行) 订阅者数组中监听函数的机制
-
当 .tap() 中的监听函数返回值是 ( <font color=red>!undefined</font>) 时会 (<font color=red> 终止执行 </font>) .call() 函数
let {SyncBailHook} = require('tapable'); class Lesson {constructor() { this.hooks = {arch: new SyncBailHook(['name']) }; } tap() {this.hooks.arch.tap('vue', function(name) {console.log('vue', name); return 'SyncBailHook 当返回值是!undefined 时,就会进行执行'; // ---------------------------- SyncBailHook 当.tap() 的参数监听函数返回值是 ( !undefined) 时就不再往下继续执行 // return undefined ---------- 返回值是 undefined 则不受影响,因为函数默认的返回值就是 undefined }); this.hooks.arch.tap('react', function(name) {console.log('react', name); }); } start() {this.hooks.arch.call('woow_wu7'); } } let lesson = new Lesson(); lesson.tap(); lesson.start();
SyncBailHook 模仿实现
class SyncBailHook {constructor() {this.observers = []
// observers 是观察者对象组成的数组,观察者对象中蕴含 event 事件名称, 和 fn 工作函数
// [{event:eventName, fn: fn}]
}
// 注册观察者对象
// 更简略点能够间接注册事件
tap(eventName, fn) {
this.observers.push({
event: eventName,
fn
})
}
// 执行注册的观察者对象中的 fn 函数
call(...params) {// this.observers.forEach(item => item.fn(item.event, ...params))
let res = undefined;
for(let i = 0; i < this.observers.length; i++) {const currentObj = this.observers[i] // ---------------------------------- 屡次用到,就缓存晋升性能
res = currentObj.fn(currentObj.event, ...params)
if (res !== undefined) {// --------------------------- 循环数组时做判断,如果函数返回值是 (!undefined) 就跳出 .call() 函数
return
}
}
}
}
const syncBailHook = new SyncBailHook(['params'])
syncBailHook.tap('react', function(eventName, name) {console.log(eventName, name)
return 'stop'
})
syncBailHook.tap('vue', function(eventName, name) {console.log(eventName, name)
})
syncBailHook.call('woow_wu7')
SyncWaterfallHook
let {SyncWaterfallHook} = require('tapable');
// SyncWaterfallHook 将上一个.tap() 的参数回调函数的 ( 返回值) 作为 (参数) 传给下一个 .tap() 的参数回调函数
class Lesson {constructor() {
this.hooks = {arch: new SyncWaterfallHook(['name'])
};
}
// 注册监听函数
tap() {this.hooks.arch.tap('vue', function(name) {console.log('vue', name);
return 'vue 不错';
// ---------------------------- SyncBailHook 当返回值是 !undefined 时就不再往下继续执行
// return undefined ---------- 返回值是 undefined 则不受影响,因为函数默认的返回值就是 undefined
});
this.hooks.arch.tap('react', function(name) {console.log('react', name); // 这里的 name 就是上一个回调的返回值,即 vue 不错
return 'react 不错'
});
this.hooks.arch.tap('node', function(name) {console.log('node', name); // 这里的 name 是上一个回调的返回值,即 react 不错
});
}
start() {this.hooks.arch.call('woow_wu7');
}
}
let lesson = new Lesson();
lesson.tap();
lesson.start();
// vue woow_wu7
// react vue 不错
// node react 不错
SyncWaterfallHook 模仿实现
-
次要利用数组的 reduce() 办法迭代
class SyncWaterfallHook {constructor() {this.observers = [] // observers 是观察者对象组成的数组,观察者对象中蕴含 event 事件名称, 和 fn 工作函数 // [{event:eventName, fn: fn}] } // 注册观察者对象 // 更简略点能够间接注册事件 tap(eventName, fn) { this.observers.push({ event: eventName, fn }) } // 执行注册的观察者对象中的 fn 函数 call(...params) {// this.observers.forEach(item => item.fn(item.event, ...params)) const [first, ...rest] = this.observers const fisrtRes = this.observers[0].fn(...params) rest.reduce((a, b) => {return b.fn(a) }, fisrtRes) // 第一次:当 reduce 存在第二个参数时,a = fisrtRes, b 则就是数组的第一个成员 // 第二次:a = 第一次的返回值 b.fn(a),b 则是数组的第二个成员 // ... } } const syncWaterfallHook = new SyncWaterfallHook(['params']) syncWaterfallHook.tap('react', function(name) {console.log('react', name) return 'react ok' }) syncWaterfallHook.tap('vue', function(name) {console.log('vue', name) return 'vue ok' }) syncWaterfallHook.tap('node', function(name) {console.log('node', name) }) syncWaterfallHook.call('woow_wu7') // react woow_wu7 // vue react ok // node vue ok
SyncLoopHook
-
当 以后监听函数返回 (!undefined) 时会屡次执行,直到以后的监听函数返回 undefined 时进行执行以后函数,继续执行下一个函数
let {SyncLoopHook} = require('tapable'); // SyncLoopHook // 当 .tap() 的回调函数中返回值是 !undefined 时就会屡次执行,晓得返回值是 undefined 就会终止执行,则继续执行下一个 .tap() class Lesson {constructor() { this.hooks = {arch: new SyncLoopHook(['name']) }; } index = 0; // 这里 index 是挂在 Lesson.prototype 上的 // 如果在 constructor 中申明 this.index = 0 则 this 示意实例对象 // 之所以在 tap() 中能够调用 this.index 是因为 tap 的调用切实 less 实例上调用的,所以 this 示意实例则能够获取到 this.index // 注册监听函数 tap() { const that = this; this.hooks.arch.tap('react', function(name) {console.log('react', name); return ++that.index === 3 ? undefined : 'react 不错'; // 返回值不为 undefined 就会始终执行该 tap 函数,直到为 undefined }); this.hooks.arch.tap('node', function(name) {console.log('node', name); }); } start() {this.hooks.arch.call('woow_wu7'); } } let lesson = new Lesson(); lesson.tap(); lesson.start();
SyncLoopHook 模仿实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
class SyncLoopHook {constructor() {this.observers = [];
// observers 是观察者对象组成的数组,观察者对象中蕴含 event 事件名称, 和 fn 工作函数
// [{event:eventName, fn: fn}]
}
// 注册观察者对象
// 更简略点能够间接注册事件
tap(eventName, fn) {
this.observers.push({
event: eventName,
fn
})
}
// 执行注册的观察者对象中的 fn 函数
call(...params) {
this.observers.forEach(item => {
let res;
do {res = item.fn(...params)
} while(res !== undefined);
})
}
}
const syncLoopHook = new SyncLoopHook(['params'])
let count = 0;
syncLoopHook.tap('react', function(name) {console.log('react', name)
return ++count === 3 ? undefined : 'go on'
})
// 留神:函数的作用域是函数定义时所在的对象
// 当在 .call() 办法中屡次调用时,count 会减少,因为函数和变量的作用域都是在申明时确认的
syncLoopHook.tap('vue', function(name) {console.log('vue', name)
})
syncLoopHook.tap('node', function(name) {console.log('node', name)
})
syncLoopHook.call('woow_wu7')
</script>
</body>
</html>
AsyncParallelHook – 通过 .tapAsync(_, (name, cb)=>{异步代码})
和 .callAsync(_, ()=>{})
调用
- 异步并行的钩子
- <font color=red>parallel:是并行的意思 </font>
-
<font color=red>series:是串行的意思 </font>
let {AsyncParallelHook} = require('tapable'); // AsyncParallelHook 是异步并行的钩子 // parallel:并行 // series:串行 class Lesson {constructor() { this.hooks = {arch: new AsyncParallelHook(['name']) }; } // 注册监听函数 tap() {this.hooks.arch.tapAsync('react', function (name, cb) { // ----------- tapAsync 异步注册 setTimeout(function () {console.log('react', name); cb(); // --------------------------------------------------------- cb() 示意执行结束 }, 1000) }); this.hooks.arch.tapAsync('node', function (name, cb) {setTimeout(function () {console.log('node', name); cb(); // ----------------- 只有有一个 cb() 没有执行,在 callAsync() 中的回调函数就不会触发}, 1000) }); } start() {this.hooks.arch.callAsync('woow_wu7', function () { // --------------- callAsync 异步执行 console.log('end') }); } } let lesson = new Lesson(); lesson.tap(); lesson.start();
AsyncParallelHook 模仿实现
class AsyncParallelHook {constructor() {this.observers = [];
}
tapAsync(eventName, fn) {
this.observers.push({
event: eventName,
fn
})
}
callAsync(...params) {const callbackFn = params.pop()
// 取出 callAsync() 的最初一个参数,这里只有两个参数,即取出 cb 回调
// 留神:pop(), shift() 返回被删除的元素,扭转原数组
// 留神:push(), unshift() 返回操作后的数组长度,扭转原数组
const that = this
// 固定 thiss
let count = 0
// 计数,用于统计 cb 回调执行的次数
function done() {
count++;
if (count === that.observers.length) {// 保障每一个 .tap() 中都执行了 cb() 回调
// 相等阐明每个 .tap() 中都执行了 cb()
// 阐明:此条件就是执行 callAsync() 中的参数回调函数的条件
callbackFn()}
}
this.observers.forEach((item, index) => {item.fn(...params, done)
// done 函数作为参数传递给 .tapAsync() 在外部被调用})
}
}
const asyncParallelHook = new AsyncParallelHook(['params'])
asyncParallelHook.tapAsync('react', function(name, cb) {setTimeout(() => {console.log('react', name)
cb()}, 1000);
})
asyncParallelHook.tapAsync('vue', function(name, cb) {setTimeout(() => {console.log('vue', name)
cb()}, 1000);
})
asyncParallelHook.tapAsync('node', function(name, cb) {setTimeout(() => {console.log('node', name)
cb()}, 1000);
})
asyncParallelHook.callAsync('woow_wu7', function() {console.log('end')
})
AsyncParallelHook – 通过 .tapPromise()
和 .promise().then()
调用
const {AsyncParallelHook} = require('tapable')
class Lesson {constructor() {
this.hook = {arch: new AsyncParallelHook(['name'])
}
}
tap() {
this.hook.arch.tapPromise('react', name => {return new Promise((resolove) => {setTimeout(() => {console.log('react', name)
return resolove()}, 1000);
})
})
this.hook.arch.tapPromise('vue', name => {return new Promise((resolove) => {setTimeout(() => {console.log('react', name)
return resolove()}, 1000);
})
})
}
publish() {this.hook.arch.promise('woow_wu7').then(() => {console.log('end')
})
}
}
const lesson = new Lesson(['name'])
lesson.tap() // 注册
lesson.publish() // 公布
AsyncParallelHook – 通过 .tapPromise()
和 .promise().then()
调用 – 模仿实现
class AsyncParallelHook {constructor() {this.observers = [];
}
tapPromise(eventName, fn) {
this.observers.push({
event: eventName,
fn
})
}
promise(...params) {const promiseArr = this.observers.map(item => item.fn(...params))
return Promise.all(promiseArr)
// 返回一个 Promise.all() 函数
// 所有 resolve() 才 resolve()
// 一个 rejectI() 就 reject()
}
}
const asyncParallelHook = new AsyncParallelHook(['params'])
asyncParallelHook.tapPromise('react', function (name) {return new Promise((resolve, reject) => {setTimeout(() => {console.log('react', name)
return resolve()}, 1000);
})
})
asyncParallelHook.tapPromise('vue', function (name) {return new Promise((resolve, reject) => {setTimeout(() => {console.log('react', name)
return resolve()}, 1000);
})
})
asyncParallelHook.promise('node').then(function (name) {console.log('end')
})
AsyncSeriesHook – 异步串行钩子
-
在前一个.tap() 执行完才会继续执行下一个.tap(),所有的.tap() 中的 cb() 都执行完,才执行.callAsync() 中的回调函数
const {AsyncSeriesHook} = require('tapable') class Lesson {constructor() { this.hook = {arch: new AsyncSeriesHook(['name']) } } tap() {this.hook.arch.tapAsync('react', (name, cb) => {setTimeout(() => {console.log('react', name) cb()}, 1000); }) this.hook.arch.tapAsync('vue', (name, cb) => {setTimeout(() => {console.log('vue', name) cb()}, 1000); }) } publish() {this.hook.arch.callAsync('woow_wu7', () => {console.log('end') }) } } const lesson = new Lesson(['name']) lesson.tap() lesson.publish()
AsyncSeriesHook 模仿实现
class AsyncSeriesHook {constructor() {this.observers = []
}
tapAsync(name, fn) {
this.observers.push({
event_name: name,
fn
})
}
callAsync(...params) {const lastCallback = params.pop()
let index = 0;
const that = this;
function next() {if (index === that.observers.length) {return lastCallback()
// 如果不等,就永远不会执行 lastCallback()}
that.observers[index++].fn(...params, next)
// index++
// 先整个赋值即 that.observes[0]
// 再 index = index + 1 => index = 1
// 递归 next() 办法,直到 index === that.observers.length 后则返回 lastCallbackk() 进行递归
}
next()}
}
const asyncSeriesHook = new AsyncSeriesHook(['name'])
asyncSeriesHook.tapAsync('react', function(name, cb) {setTimeout(() => {console.log('react', name)
cb()}, 1000);
})
asyncSeriesHook.tapAsync('vue', function(name, cb) {setTimeout(() => {console.log('vue', name)
cb()}, 1000);
})
asyncSeriesHook.callAsync('woow_wu7', function() {console.log('end')
})
AsyncSeriesWaterfullHook
const {AsyncSeriesWaterfallHook} = require('tapable')
class Lesson {constructor() {
this.hook = {arch: new AsyncSeriesWaterfallHook(['name'])
}
}
tap() {this.hook.arch.tapAsync('react', (name, cb) => {setTimeout(() => {console.log('react', name)
cb(null, 'next-react')
// 当第一个参数布尔值是 false 时,将传递 'next-react' 作为下一个 .tapAsynce() 中参数回调函数的参数
// 当第一个参数布尔值是 true 时,将中断下一个 .tapAsynce() 的执行
// 留神: 尽管会中断下一个.tapAsynce()
// 然而因为调用了 cb() 且为.tapAsynce() 的总个数时,所以 callAsync 的第二个参数回调会执行,即会打印 'end'
}, 1000);
})
this.hook.arch.tapAsync('vue', (name, cb) => {setTimeout(() => {console.log('vue', name)
cb(null, 'next-vue')
// 如果是 cb('null', 'next-vue') 第一个参数布尔值是 true,则不会执行下一个.tapAsynce()}, 1000);
})
}
publish() {this.hook.arch.callAsync('woow_wu7', () => {console.log('end')
})
}
}
const lesson = new Lesson(['name'])
lesson.tap()
lesson.publish()
AsyncSeriesWaterfullHook 模仿实现
class AsyncSeriesWaterfallHook {constructor() {this.observers = []
}
tapAsync(name, fn) {
this.observers.push({
event_name: name,
fn
})
}
callAsync(...params) {const lastCallback = params.pop()
let index = 0;
const that = this;
function next(err, data) {let task = that.observers[index]
if (!task || err || index === that.observers.length) {
// 如果该 task 不存在
// 或者 err 存在
// 或者 index 达到最大长度,其实能够不判断,因为 index 超出 that.observers.length 时,task 将不存在了,满足第一个条件
// 满足以上条件,都间接返回 lastCallback
return lastCallback()}
if (index === 0) {task.fn(...params, next)
} else {task.fn(data, next)
}
index++
}
next()}
}
const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(['name'])
asyncSeriesWaterfallHook.tapAsync('react', function(name, cb) {setTimeout(() => {console.log('react', name)
cb(null, 'next-react')
}, 1000);
})
asyncSeriesWaterfallHook.tapAsync('vue', function(name, cb) {setTimeout(() => {console.log('vue', name)
cb(null, 'next-vue')
}, 1000);
})
asyncSeriesWaterfallHook.callAsync('woow_wu7', function() {console.log('end')
})
材料
tapable https://github.com/webpack/ta…
tapable https://juejin.im/post/684490…
tapable https://juejin.im/post/684490…