面试官问发布订阅模式是在问什么?

大家好,我是若川。最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 参与,已进行了三个多月,大家一起交流学习,共同进步。

本文来自 @simonezhou 小姐姐投稿的第八期笔记。面试官常问发布订阅、观察者模式,我们日常开发也很常用。文章讲述了 mitt、tiny-emitter、Vue eventBus这三个发布订阅、观察者模式相关的源码。

源码地址

  1. mitt:https://github.com/developit/mitt

  2. tiny-emitter:https://github.com/scottcorgan/tiny-emitter

1. mitt 源码解读

1.1 package.json 项目 build 打包(运用到包暂不深究,保留个印象即可)

执行 npm run build:

// 
"scripts": {..."bundle": "microbundle -f es,cjs,umd","build": "npm-run-all --silent clean -p bundle -s docs","clean": "rimraf dist","docs": "documentation readme src/index.ts --section API -q --parse-extension ts",...},
  • 使用 npm-run-all(A CLI tool to run multiple npm-scripts in parallel or sequential:https://www.npmjs.com/package/npm-run-all) 命令执行38a43cfbe1353f746aacbc56ca41baa8.png

  • clean 命令,使用 rimraf(The UNIX command rm -rf for node. https://www.npmjs.com/package/rimraf)删除 dist 文件路径

  • bundle 命令,使用 microbundle(The zero-configuration bundler for tiny modules, powered by Rollup. https://www.npmjs.com/package/microbundle) 进行打包

  • microbundle 命令指定 format: es, cjs, umd,  package.json 指定 soucre 字段为打包入口 js:

37e472a5cf49fde482c5b540cdd2ee24.png

{"name": "mitt",          // package name......"module": "dist/mitt.mjs",    // ES Modules output bundle"main": "dist/mitt.js",      // CommonJS output bundle"jsnext:main": "dist/mitt.mjs",   // ES Modules output bundle"umd:main": "dist/mitt.umd.js",  // UMD output bundle"source": "src/index.ts",     // input"typings": "index.d.ts",     // TypeScript typings directory"exports": {"import": "./dist/mitt.mjs",    // ES Modules output bundle"require": "./dist/mitt.js",  // CommonJS output bundle"default": "./dist/mitt.mjs"  // Modern ES Modules output bundle},...
}

1.2 如何调试查看分析?

使用 microbundle watch 命令,新增 script,执行 npm run dev:

"dev": "microbundle watch -f es,cjs,umd"

对应目录新增入口,比如 test.js,执行 node test.js 测试功能:

const mitt = require('./dist/mitt');const Emitter = mitt();Emitter.on('test', (e, t) => console.log(e, t));Emitter.emit('test', { a: 12321 });

对应源码 src/index.js 也依然可以加相关的 log 进行查看,代码变动后会触发重新打包

1.3. TS 声明

使用上可以(官方给的例子),比如定义 foo 事件,回调函数里面的参数要求是 string 类型,可以想象一下源码 TS 是怎么定义的:

import mitt from 'mitt';// key 为事件名,key 对应属性为回调函数的参数类型 
type Events = {foo: string;bar?: number; // 对应事件允许不传参数
};const emitter = mitt<Events>(); // inferred as Emitter<Events>emitter.on('foo', (e) => {}); // 'e' has inferred type 'string'emitter.emit('foo', 42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'. (2345)emitter.on('*', (type, e) => console.log(type, e) )

源码内关于 TS 定义(关键几句):

export type EventType = string | symbol;// Handler 为事件(除了*事件)回调函数定义
export type Handler<T = unknown> = (event: T) => void;// WildcardHandler 为事件 * 回调函数定义
export type WildcardHandler<T = Record<string, unknown>> = (type: keyof T,   // keyof T,事件名event: T[keyof T]  // T[keyof T], 事件名对应的回调函数入参类型
) => void;export interface Emitter<Events extends Record<EventType, unknown>> {// ...on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;on(type: '*', handler: WildcardHandler<Events>): void;// ...emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;// 这句主要兼容无参数类型的事件,如果说事件对应回调必须传参,使用中如果未传,那么会命中 never,如下图emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
}

以下是会报 TS 错误:
ec8c9b2e7517ceb1420899bc4403dcbe.png
以下是正确的:
34533b96ea79b0d3884413712a106362.png

1.4 主逻辑

  1. 整体就是一个 function,输入为事件 Map,输出为 all 所有事件 Map,还有 on,emit,off 几个关于事件方法:

export default function mitt<Events extends Record<EventType, unknown>>(// 支持 all 初始化all?: EventHandlerMap<Events>
): Emitter<Events> {// 内部维护了一个 Map(all),Key 为事件名,Value 为 Handler 回调函数数组all = all || new Map();return {all,   // 所有事件 & 事件对应方法emit,  // 触发事件on,   // 订阅事件off   // 注销事件}
}
  1. on 为【事件订阅】,push 对应 Handler 到对应事件 Map 的 Handler 回调函数数组内(可熟悉下 Map 相关API https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map):

on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {// Map get 获取const handlers: Array<GenericEventHandler> | undefined = all!.get(type);// 如果已经初始化过的话,是个数组,直接 push 即可if (handlers) {handlers.push(handler);}// 如果第一次注册事件,则 set 新的数组else {all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);}
}
  1. off 为【事件注销】,从对应事件 Map 的 Handlers 中,splice 掉:

off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {// Map get 获取const handlers: Array<GenericEventHandler> | undefined = all!.get(type);// 如果有事件列表,则进入,没有则忽略if (handlers) {// 对 handler 事件进行 splice 移出数组// 这里是对找到的第一个 handler 进行移出,所以如果订阅了多次,只会去除第一个// handlers.indexOf(handler) >>> 0,>>> 为无符号位移// 关于网上对 >>> 用法说明:It doesn't just convert non-Numbers to Number, it converts them to Numbers that can be expressed as 32-bit unsigned ints.if (handler) {handlers.splice(handlers.indexOf(handler) >>> 0, 1);}// 如果不传对应的 Handler,则为清空事件对应的所有订阅else {all!.set(type, []);}}
}
  1. emit 为【事件触发】,读取事件 Map 的 Handlers,循环逐一触发,如果订阅了 * 全事件,则读取 * 的 Handlers 逐一触发:

emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {// 获取对应 type 的 Handlerslet handlers = all!.get(type);if (handlers) {(handlers as EventHandlerList<Events[keyof Events]>).slice().map((handler) => {handler(evt!);});}// 获取 * 对应的 Handlershandlers = all!.get('*');if (handlers) {(handlers as WildCardEventHandlerList<Events>).slice().map((handler) => {handler(type, evt!);});}
}

为什么是使用 slice().map() ,而不是直接使用 forEach() 进行触发?具体可查看:https://github.com/developit/mitt/pull/109,具体可以拷贝相关代码进行调试,直接更换成 forEach 的话,针对以下例子所触发的 emit 是错误的:

import mitt from './mitt'type Events = {test: number
}const Emitter = mitt<Events>()
Emitter.on('test', function A(num) {console.log('A', num)Emitter.off('test', A)
})
Emitter.on('test', function B() {console.log('B')
})
Emitter.on('test', function C() {console.log('C')
})Emitter.emit('test', 32432) // 触发 A,C 事件,B 会被漏掉
Emitter.emit('test', 32432) // 触发 B,C,这个是正确的// 原因解释:
// forEach 时,在 Handlers 循环过程中,同时触发了 off 操作
// 按这个例子的话,A 是第一个被注册的,所以第一个会被 slice 掉
// 因为 array 是引用类型,slice 之后,那么 B 函数就会变成第一个
// 但此时遍历已经到第二个了,所以 B 函数就会被漏掉执行// 解决方案:
// 所以对数组进行 [].slice() 做一个浅拷贝,off 的 Handlers 与 当前循环中的 Handlers 处理成不同一个
// [].slice.forEach() 效果其实也是一样的,用 map 的话个人感觉不是很语义化

1.5 小结

  • TS keyof 的灵活运用

  • undefined extends Events[Key] ? Key : never,为 TS 的条件类型(https://www.typescriptlang.org/docs/handbook/2/conditional-types.html)

  • undefined extends Events[Key] ? Key : never,当我们想要编译器不捕获当前值或者类型时,我们可以返回 never类型。never 表示永远不存在的值的类型

// 来自 typescript 中的 lib.es5.d.ts 定义/*** Exclude null and undefined from T*/
type NonNullable<T> = T extends null | undefined ? never : T;// 如果 T 的值包含 null 或者 undefined,则会 never 表示不允许走到此逻辑,否则返回 T 本身的类型
  • mitt 的事件回调函数参数,只会有一个,而不是多个,如何兼容多个参数的情况,官方推荐是使用 object 的(object is recommended and powerful),这种设计扩展性更高,更值得推荐。4a1ed410a4533b8621329f077e5f6e2d.png

2. tiny-emitter 源码解读

2.1 主逻辑

  1. 所有方法都是挂载在 E 的 prototype 内的,总共暴露了 once,emit,off,on 四个事件的方法:

function E () {// Keep this empty so it's easier to inherit from// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}// 所有事件都挂载在 this.e 上,是个 object
E.prototype = {on: function (name, callback, ctx) {},once: function (name, callback, ctx) {},emit: function (name) {},off: function (name, callback) {}
}module.exports = E;
module.exports.TinyEmitter = E;
  1. once 订阅一次事件,当被触发一次后,就会被销毁:

once: function (name, callback, ctx) {var self = this;// 构造另一个回调函数,调用完之后,销毁该 callbackfunction listener () {self.off(name, listener);     // 销毁callback.apply(ctx, arguments);  // 执行};listener._ = callback// on 函数返回 this,所以可以链式调用return this.on(name, listener, ctx); // 订阅这个构造的回调函数
}
  1. on 事件订阅

on: function (name, callback, ctx) {var e = this.e || (this.e = {});// 单纯 push 进去,这里也没有做去重,所以同一个回调函数可以被订阅多次(e[name] || (e[name] = [])).push({fn: callback,ctx: ctx});// 返回 this,可以链式调用return this;
}
  1. off 事件销毁

off: function (name, callback) {var e = this.e || (this.e = {});var evts = e[name];var liveEvents = []; // 保存还有效的 hanlder// 传递的 callback,如果命中,就不会被放到 liveEvents 里面// 所以这里的销毁是一次性销毁全部相同的 callback,与 mitt 不一样if (evts && callback) {for (var i = 0, len = evts.length; i < len; i++) {if (evts[i].fn !== callback && evts[i].fn._ !== callback)liveEvents.push(evts[i]);}}// Remove event from queue to prevent memory leak// Suggested by https://github.com/lazd// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910// 如果没有任何 handler,对应的事件 name 也可以被 delete(liveEvents.length)? e[name] = liveEvents: delete e[name];// 返回 this,可以链式调用return this;
}
  1. emit 事件触发

emit: function (name) {// 取除了第一位的剩余所有参数var data = [].slice.call(arguments, 1);// slice() 浅拷贝var evtArr = ((this.e || (this.e = {}))[name] || []).slice();var i = 0;var len = evtArr.length;// 循环逐个触发 handler,把 data 传入其中for (i; i < len; i++) {evtArr[i].fn.apply(evtArr[i].ctx, data);}// 返回 this,可以链式调用return this;
}

2.2 小结

  • return this,支持链式调用

  • emit 事件触发时,[].slice.call(arguments, 1) 剔除第一个参数,获取到剩余的参数列表,再使用 apply 来调用

  • on 事件订阅时,记录的是 { fn, ctx },fn 为回调函数,ctx 支持绑定上下文

3. mitt 与 tiny-emitter 对比

  • TS 静态类型校验上 mitt > tiny-emitter,开发更友好,对于回调函数参数的管理,tiny-emitter 支持多参数调用的,但是 mitt 提倡使用 object 管理,设计上感觉 mitt 更加友好以及规范

  • 在 off 事件销毁中,tiny-emitter 与 mitt 处理方式不同,tiny-emitter 会一次性销毁所有相同的 callback,而 mitt 则只是销毁第一个

  • mitt 不支持 once 方法,tiny-emitter 支持 once 方法

  • mitt 支持 * 全事件订阅,tiny-emitter 则不支持

4. Vue eventBus 事件总线(3.x 已废除,2.x 依然存在)

  • 关于 events 的处理:https://github.com/vuejs/vue/blob/dev/src/core/instance/events.js

  • 事件相关初始化:https://github.com/vuejs/vue/blob/dev/src/core/instance/index.js

  1. 初始化过程

// index.js 调用 initMixin 方法,初始化 _events object
initMixin(Vue)// event.js 定义 initEvents 方法
// vm._events 保存所有事件 & 事件回调函数,是个 object
export function initEvents (vm: Component) {vm._events = Object.create(null)// ...
}// index.js 调用 eventsMixin,往 Vue.prototype 挂载相关事件方法
eventsMixin(Vue)// event.js 定义了 eventsMixin 方法
export function eventsMixin (Vue: Class<Component>) {// 事件订阅Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {}// 事件订阅执行一次Vue.prototype.$once = function (event: string, fn: Function): Component {}// 事件退订Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {}// 事件触发Vue.prototype.$emit = function (event: string): Component {}
}
  1. $on 事件订阅

// event 是个 string,也可以是个 string 数组
// 说明可以一次性对多个事件,订阅同一个回调函数
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {const vm: Component = thisif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$on(event[i], fn)}} else {// 本质是就是对应 event,push 对应的 fn(vm._events[event] || (vm._events[event] = [])).push(fn)// 以下先不展开,关于 hookEvent 的调用说明// optimize hook:event cost by using a boolean flag marked at registration// instead of a hash lookupif (hookRE.test(event)) {vm._hasHookEvent = true}}return vm
}
  1. $once 事件订阅&执行一次

// 包装一层 on,内包含退订操作以及调用操作
// 订阅的是包装后的 on 回调函数
Vue.prototype.$once = function (event: string, fn: Function): Component {const vm: Component = thisfunction on () {vm.$off(event, on)fn.apply(vm, arguments)}on.fn = fnvm.$on(event, on)return vm
}
  1. $off 事件退订

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {const vm: Component = this// 没有传参数,说明全部事件退订,直接清空if (!arguments.length) {vm._events = Object.create(null)return vm}// 存在 event 数组,遍历逐一调用自己if (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$off(event[i], fn)}return vm}// 以下情况为非数组事件名,为单一事件,则获取该事件对应订阅的 callbacksconst cbs = vm._events[event]// 若 callbacks 为空,什么都不用做if (!cbs) {return vm}// 如果传入的 fn 为空,说明退订这个事件的所有 callbacksif (!fn) {vm._events[event] = nullreturn vm}// callbacks 不为空,并且 fn 不为空,则为退订某个 callbacklet cblet i = cbs.lengthwhile (i--) {cb = cbs[i]// 订阅多次的 callback,都会被退订,一次退订所有相同的 callbackif (cb === fn || cb.fn === fn) {cbs.splice(i, 1)break}}return vm
}
  1. $emit 事件触发

Vue.prototype.$emit = function (event: string): Component {const vm: Component = thisif (process.env.NODE_ENV !== 'production') {const lowerCaseEvent = event.toLowerCase()if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {tip(`Event "${lowerCaseEvent}" is emitted in component ` +`${formatComponentName(vm)} but the handler is registered for "${event}". ` +`Note that HTML attributes are case-insensitive and you cannot use ` +`v-on to listen to camelCase events when using in-DOM templates. ` +`You should probably use "${hyphenate(event)}" instead of "${event}".`)}}// 获取这个 event 的 callbacks 出来let cbs = vm._events[event]if (cbs) {cbs = cbs.length > 1 ? toArray(cbs) : cbs// 获取除了第一位,剩余的其他所有参数const args = toArray(arguments, 1)const info = `event handler for "${event}"`// 遍历逐一触发for (let i = 0, l = cbs.length; i < l; i++) {// 以下暂不展开,这是 Vue 中对于方法调用错误异常的处理方案invokeWithErrorHandling(cbs[i], vm, args, vm, info)}}return vm
}

实现逻辑大致和 mitt,tiny-emitter 一致,也是 pubsub,整体思路都是维护一个 object 或者 Map,on 则是放到数组内,emit 则是循环遍历逐一触发,off 则是查找到对应的 handler 移除数组
TODO:

  • Vue 中对于方法调用错误异常的处理方案:invokeWithErrorHandling

  • hookEvent 的使用&原理

5. 附录

  • rimraf:https://www.npmjs.com/package/rimraf

  • microbundle:https://www.npmjs.com/package/microbundle

  • package.json exports 字段:https://nodejs.org/api/packages.html#packages_conditional_exports

  • Map:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map

  • TS 条件类型:https://www.typescriptlang.org/docs/handbook/2/conditional-types.html

  • TS Never:https://www.typescriptlang.org/docs/handbook/basic-types.html#never

  • TS keyof: https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#the-keyof-type-operator

  • What is the JavaScript >>> operator and how do you use it? https://stackoverflow.com/questions/1822350/what-is-the-javascript-operator-and-how-do-you-use-it


最近组建了一个江西人的前端交流群,如果你是江西人可以加我微信 ruochuan12 私信 江西 拉你进群。

推荐阅读

1个月,200+人,一起读了4周源码
我历时3年才写了10余篇源码文章,但收获了100w+阅读

老姚浅谈:怎么学JavaScript?

我在阿里招前端,该怎么帮你(可进面试群)

6eca8fed5ac3f169045590ba94480d97.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》10余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助1000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

ae2688d7b41166f14c1f6e1fa7ba452c.png

识别方二维码加我微信、拉你进源码共读

今日话题

略。欢迎分享、收藏、点赞、在看我的公众号文章~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

figma下载_不用担心Figma中的间距

figma下载重点 (Top highlight)I spend way too much time caring about spacing when designing interfaces and building design systems. You are probably no stranger to the constant 1 px and 8 px nudging, continuous checking of the bottom or in-between space for…

【建议收藏】面试官贼喜欢问的 32+ vue 修饰符,你掌握几种啦?

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;已进行了三个多月&#xff0c;大家一起交流学习&#xff0c;共同进步。前言vue简洁好用体现在很多个地方&#xff0c;比如其内置了32修饰符&#xff0c;可以很…

知识管理系统Data Solution研发日记之一 场景设计与需求列出

在平时开发的过程中&#xff0c;经常会查找一些资料&#xff0c;从网上下载一些网页&#xff0c;压缩格式文件到自己的电脑中&#xff0c;然后阅读。程序有别于其他行业的一个特征是&#xff0c;所有的资料&#xff0c;数据&#xff0c;压缩文件&#xff0c;只用于产生可以工作…

shields 徽标_我们如何准确地记住著名徽标的特征和颜色?

shields 徽标The logos of global corporations like Apple, Starbucks, Adidas, and IKEA are designed to create instant brand associations in the minds of billions who see them every day. But how accurately can we remember the features and colors of these famo…

面了三次字节,他的一些感悟

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;已进行了三个多月&#xff0c;大家一起交流学习&#xff0c;共同进步。今天分享一篇小K投稿的字节面试记录&#xff0c;这是他第三次面字节了&#xff0c;之前…

解决Wireshark安装Npcap组件失败

2019独角兽企业重金招聘Python工程师标准>>> 解决Wireshark安装Npcap组件失败 从Wireshark 3.0开始&#xff0c;Npcap取代Winpcap组件&#xff0c;成为Wireshark默认的网卡核心驱动。由于该组件属于驱动程序&#xff0c;所以安装时候容易被杀毒/防火墙软件拦截&…

adobe清理工具_Adobe终于通过其新的渐变工具实现了这一点-UX评论

adobe清理工具的Photoshop (Photoshop) UX:用户体验&#xff1a; At first glance, the UX looks okay; it’s pretty clear. The user gets to know how to use this tool right away. The color palette is located above, and the gradient down below. The diamond betwee…

新手向:前端程序员必学基本技能——调试JS代码

1前言大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;已进行三个月了&#xff0c;大家一起交流学习&#xff0c;共同进步。想学源码&#xff0c;极力推荐之前我写的《学习源码整体架构系列》 包含jQuery、un…

iOS开发ApplePay的介绍与实现

1、Apple Pay的介绍 Apple Pay官方1.1 Apple Pay概念 Apple Pay&#xff0c;简单来说, 就是一种移动支付方式。通过Touch ID/ Passcode&#xff0c;用户可使用存储在iPhone 6, 6p等之后的新设备上的信用卡和借记卡支付证书来授权支付&#xff1b; 它是苹果公司在2014苹果秋季新…

mes建设指南_给予和接受建设性批评的设计师指南

mes建设指南Constructive criticism, or more plainly, feedback, plays a crucial role in a designer’s job. Design is an iterative process, so we are often either asking for feedback on our own work or dishing it out to a fellow designer.建设性的批评&#xff…

面试官:请实现一个通用函数把 callback 转成 promise

1. 前言大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;或者在公众号&#xff1a;若川视野&#xff0c;回复"源码"参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。已进行…

java中filter的用法

filter过滤器主要使用于前台向后台传递数据是的过滤操作。程度很简单就不说明了&#xff0c;直接给几个已经写好的代码&#xff1a; 一、使浏览器不缓存页面的过滤器 Java代码 import javax.servlet.*;import javax.servlet.http.HttpServletResponse;import java.io.IOExcept…

open-falcon_NASA在Falcon 9上带回了蠕虫-其背后的故事是什么?

open-falconYes, that’s right. The classic NASA “worm” logo is back! An image of the revived NASA worm logo was released on Twitter by NASA Administrator Jim Bridenstine as well as press release on the NASA.gov website. NASA explained that original NASA …

听说你对 ES6 class 类还不是很了解

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与。前言在ES5中是原型函数&#xff0c;到了ES6中出现了"类"的概念。等同于是ES5的语法糖&#xff0c;大大提升了编写代码的速度&#xff0c;本文只讲一些常用的&…

一篇文章带你搞懂前端面试技巧及进阶路线

大家好&#xff0c;我是若川。最近有很多朋友给我后台留言&#xff1a;自己投了不少简历&#xff0c;但是收到的面试邀请却特别少&#xff1b;好不容易收到了大厂的面试邀请&#xff0c;但由于对面试流程不清楚&#xff0c;准备的特别不充分&#xff0c;结果也挂了&#xff1b;…

小屏幕 ui设计_UI设计基础:屏幕

小屏幕 ui设计重点 (Top highlight)第4部分 (Part 4) Welcome to the fourth part of the UI Design basics. This time we’ll cover the screens you’ll likely design for. This is also a part of the free chapters from Designing User Interfaces.欢迎使用UI设计基础知…

RabbitMQ指南之四:路由(Routing)和直连交换机(Direct Exchange)

在上一章中&#xff0c;我们构建了一个简单的日志系统&#xff0c;我们可以把消息广播给很多的消费者。在本章中我们将增加一个特性&#xff1a;我们可以订阅这些信息中的一些信息。例如&#xff0c;我们希望只将error级别的错误存储到硬盘中&#xff0c;同时可以将所有级别&am…

不用任何插件实现 WordPress 的彩色标签云

侧边栏的标签云&#xff08;Tag Cloud&#xff09;一直是 WordPress 2.3 以后的内置功能&#xff0c;一般直接调用函数wp_tag_cloud 或者在 Widgets 里开启即可&#xff0c;但是默认的全部是一个颜色&#xff0c;只是大小不一样&#xff0c;很是不顺眼&#xff0c;虽然可以用 S…

随时随地能写代码, vscode.dev 出手了

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与。今天偶然看到了 VSCode 官方发布了一条激动人心的 Twitter&#xff0c;vscode.dev[1] 域名上线了&#xff01;image-20211021211915942新的域名 vscode.dev[2] 它是一个…

七种主流设计风格_您是哪种设计风格?

七种主流设计风格重点 (Top highlight)I had an idea for another mindblowing test, so here it is. Since you guys liked the first one so much, and I got so many nice, funny responses and private messages on how accurate it actually was, I thought you will prob…