聊聊一线开发的基本素养

早上闹铃一响,摸到手机,关掉闹铃,打开微信,刷下朋友圈,刷到昨晚半夜的这样一条消息

寥寥数语,形象跃然纸上!

顿时人也精神了,想要立刻爬起来。

原因无他,也被深深伤害过,感同身受!

程序开发说简单也简单,按照程序语法写就行,说复杂也复杂,同一段业务逻辑,实现效果一致,但是实现方式可能千差万别,有些代码短小精干,有的你会很惊奇居然一团糟的情况下程序还能正常的跑起来!

仔细分析你会发现,每个人都有自己的代码风格,有的人并不是能力不行,但是他的代码就是一个字:“糙”!

细问原因,往往都会往逻辑复杂、工期紧上面靠。

更深层次分析,实际上这是一个习惯问题,基本底线问题。

我觉得一个优质的一线开发应该具备如下这些基本素养:

  • 理清需求所有业务逻辑

    面对需求不仅要摸清明面上的业务逻辑,还需要考虑异常兼容情况,最怕有if没有else,有异常捕获而没有捕获到异常的后续处理。只有做到了整个逻辑的完备,才能说理解了需求。

  • 代码整体结构清晰,可维护

    合理抽象封装,明明是面向对象的高级语言,有些人偏偏喜欢面向过程记流水账。同时,请保持克制,别炫技,为了抽象而抽象。有一个KISS原则,叫做keep sample and stupid.

  • 每行代码、每个变量都是有意义的

    杜绝没有意义的代码,特别需要注意重复性的模板代码,每次碰到要在一坨坨的模板代码上迭代新功能都恨不得推翻重写,因为屎山会越堆越大!~

  • 注释清晰、代码工整

    点开文件第一眼就乱糟糟的绝对很打击继续看下去的勇气,能做到如沐春风,任何人都能很流畅的读下去就达标了。对于某些逻辑很绕的,必须写出ugly代码的地方,我的做法是注明原因,常规吐槽一下,打个todo标签

  • 一点强迫症、一点细节控、一点对美的追求

    这一点不多说,懂的人自然懂,不懂的人说再多也没用,因为大家的追求不一样!

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

一场关于系统架构的讨论

背景

公司新业务有视频点播与直播需求,经过一番对比分析,最终选择了腾讯云作为视频服务的供应商。

对于如何接入有如下争论点:
1.由中台统一对接视频服务,封装统一对外接口,如后续要切换视频服务供应商,上层无需更改

一致点: 统一封装对外服务
争议点: 要不要做到可无缝切换视频播放供应商而不影响上层业务

2.APP与WEB侧基于腾讯视频上传&播放SDk封装通用组件

一致点: 统一封装SDK
争议点: 封装粒度是强封装还是弱封装。即隐藏内部实现,只暴露自定义参数;还是组件参数透传腾讯SDk相关参数,只简单封装通用业务逻辑

对于上述争议点,会议最终也没讨论出结论来。个人觉得,这两个争论点实际上是一个业务架构问题与技术架构问题,只有把这两个问题理清楚,才能做出清晰的判断。

业务架构

业务架构:实际上就是理清各方如何分工,如何交互,如何设计的问题。

对于该场景的有两种架构方式,一种为中控式、一种为并行式。

中控式

中控式可理解为由一个中枢统一控制,大致结构图如下所示:

核心思路: 由中台统一对接视频服务,抹平各服务商的差异,做到底层视频服务切换对上层无感知

好处:

  1. 视频服务切换,对业务方无影响,无额外开发工作量

难点&弊端:

  1. 视频服务灰度切换问题
  2. 中台需抽象化视频服务为共有服务,制定自有功能与交互逻辑;功能取交集,无法使用特有视频服务独有亮点功能
  3. 中台逻辑复杂化,兼容维护成本高,不易迭代

并行式

并行式从字面意思可理解为并行设计,两条路线不冲突,大致结构图如下所示:

核心思路: 对接不同的视频服务上采用并行方案,互不影响

好处:

  1. 灰度切换方便,影响范围可控制在较小的业务范围
  2. 无需兼容处理,代码逻辑清晰,精简;全部切换后相关代码可整体废弃
  3. 可充分利用视频服务商的全部功能

难点&弊端:

  1. 切换视频供应商时需重新设计与开发,带来巨额工作量

技术架构

技术架构:技术实现细节如何做到高内聚、低耦合、易扩展、良好的版本迭代与向下兼容等,设计思路依赖业务架构。

对于争论点二,有如下两种技术架构思路,一种为集中兼容处理,一种为特定处理。

集中处理-强封装

对于各个视频服务SDK集中处理,统一对外参数,大致结构图如下所示:

核心思路: 隐藏内部实现逻辑,提供统一的对外出口

好处:

  1. 强有力的控制,可做到内部变更对外部无感知

难点&弊端:

  1. 事前需要超前规划,强有力的抽象,需保持克制只提供最小功能集,避免兼容问题
  2. 前期即需考虑封装不同视频服务保证后续能无缝切换问题,实现复杂,工作量大

特定处理-弱封装

对各个视频服务SDK特定处理,不同视频服务对应封装不同的前端组件,大致结构图如下所示:

核心思路: 最小开闭原则,不对视频SDK的做二次封装是,透传参数即可;额外扩展自定义通用业务封装。

好处:

  1. 可获得特定视频服务商全量的功能服务
  2. 组件文档无需单独编写,开发人员参考官方文档即可,鼓励DIY,遇到问题自己排查,而不是推给组件提供者
  3. 无需考虑兼容其他视频服务,代码简洁、清晰,可维护强

弊端:

  1. 切换视频服务需重新设计对应的组件,顶层业务侧也需根据新组件的参数规范做调整

个人想法

1.克制、恰到好处

这条实际上是一个平衡与取舍问题,不能一点不往将来考虑,但也不能一下考虑得太遥远,考虑太远的问题在于前期投入巨大工作量后期可能一点用不上。

2.干净、利落、清晰

个人在做业务架构时,会更多的考虑什么样的架构能够让技术架构更简洁、清晰,便于维护。尽量避免架构设计划定的框,造成技术实现细节繁琐复杂。

3.低成本,高收益

对于这个问题,干一件事总成本是相对固定的,对于上面的问题,我更倾向于前期采用低成本,后续真正要切的时候再投入另外的成本。

4.清晰而坚定的开头、清晰而坚定的收尾

选择了一种架构就应该从上到下保持一致,在一条道上做到最好,避免三心二意,这也要一点,那也要一点,最后几不像。

最后,不同的选择对应不同的代价;系统架构的难点很多时候不在于如何架构,而在于如何平衡与取舍;萝卜白菜,各有所爱。

作者公众号:

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

人生中唯一的2019

2019

工作

  • 获得了2019年度优秀员工,公司组织去了一趟巴厘岛,感谢被认可
  • 这一年以来,把负责的保洁业务线业务梳理清晰,人员结构相对合理
  • 今年最后一个季度调整到职业培训业务线,面临新的挑战(新团队搭建+爆发式的业务需求)
  • 从0到1搭建前端监控平台:实现HTTP异常监控、页面性能监控,从收集、存储、分析、可视化全流程自建
  • 绩效从Q1-Q3保持全A,Q4B+,从入职连续保持6个季度全A记录,如果加上2018年的年度绩效A,则为连续7A,证明从干了接近8年的传统行业转到互联网公司,从传统行业的业务系统架构师转到互联网公司的前端开发还算成功
  • 职级晋升成功,由T5晋升到T6,离之前在传统行业里的高级系统架构师职级还差一级,希望来年能更上一层楼

个人

  • 读书:《大型网站技术架构》《月亮与六便士》《如何阅读一本书》《棋王》
  • 今年读书变少了,大部分时间投入到了极客时间的专栏上:《数据结构与算法之美》《Elasticsearch核心技术与实战》《重学前端》《从0打造音视频直播系统》,买了还没看的有:《从0开始学为服务》《设计模式之美》《Java核心技术面试精讲》,希望新的一年能够撸一遍
  • 今年带着家人去了这么几个地方:天津、苏州、曼谷&普吉岛;公司团建去了一趟山西(悬空寺-恒山-应县木塔-云冈石窟)
  • 在接近年底的时候开通了个人公众号,与维护了3年的博客保持同步

2020

工作

  • 完成职业培训业务线团队的搭建,打造有战斗力、有影响力的团队
  • 支撑职培业务走上正轨
  • 前端监控体系完善
  • 希望能做到一半时间团队管理、一半时间coding,理想状态为团队强大稳定,有大把的时间可投入到想做的技术坑里不起来。。。

个人

  • 英语学习得捡起来了,定个小目标,每天新记3个单词,一年1000
  • 每个季度至少阅读一本书
  • 刷完极客时间里已购买的专栏
  • 今年博客写得也相对较少了,今年得加油,多沉淀总结,尽量保持每月一篇

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

年终总结攻略,再也不愁如何下笔了!

目的

如果不明白一件事的目的与价值,那么就不会有做下去的动力。

  • 生活部分:时光记录机
  • 工作部分:让你的leader知道你有哪些不满意,新的一年有哪些想法和预期,好做针对性改善与调整
  • 不会总结与规划,就不知道有哪些不足,就不会有改变与前进的方向
  • 辞旧迎新打卡!~

写些什么?

工作之后,大部分人的人生不足100年,你是否每一年都过得有价值?能够对自己负责?

关于上一年

生活方面

要点:

  1. 只要还能记起来的东西都可以往这里塞,能记起来,证明对自己的影响深刻。
  2. 要避免大杂烩。
  • 今年生活中值得记录下来的重要事件、瞬间?(比如相了几次亲?😁)
  • 有哪些不如意?如何避免 or 接受?
  • 今年有哪些收获?
  • 思想上有哪些转变?

工作方面

  • 今年做了哪些有价值的工作事项?
  • 过去一年做得不满意的地方
  • 有哪些收获?(技术能力、沟通能力、协调能力、需求把控、项目管理能力,有哪些量变到质变的蜕变?)
  • 有没有哪些对接不畅、工作不畅;工作方式、方法是否需要改进?

关于下一年

要点:

  1. Flag时间,Your show time!
  2. 可落地 or 里程碑计划

生活方面

要点:一定要敢想,今年不行明年再来!

  • 出行计划。来一场说走就走的旅行?
  • 脱单?结婚?生娃?
  • 买车?买房?
  • 要读哪些书?

工作方面

要点:如果你不知道该写些啥,那么大概率在接下来的一年你会无所事事的度过。有这么一句话:”有些人工作了10年,实际上经验只有1年,另外9年在重复第一年。“

  • 想要往哪些方向发力?
  • 有哪些地方需要改进?
  • 工作晋升?(flag立在这里,提醒自己要时刻准备)
  • 哪些技术能力想要提高?
  • 准备今年刷完哪些技术书籍,哪些课程?

其它

这个部分为随意发挥环节,可以是吐槽牢骚、也可以是美好幢景。进入了状态、甚至可以赋诗一首!

每年一篇,在XXX年之后再来一一回顾,看到当年的自己,这么多年的成长与改变,应当会宛然一笑。

作者公众号:

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

【鸡汤第2期】不要追求完美,要随心所欲地生活

关于完美主义

  • 简单说,完美主义就是追求一个较高水平的目标,不接受一个较低水平的、但可用的结果。
  • 完美主义的最大问题是,它实际上让你追求高成本。
  • 完美主义带来的高压力,也不利于身心健康。不要追求完美,要随心所欲地生活。

来源:科技爱好者周刊:第 86 期

关于生活

  • 默克尔写了一段非常有意境有哲理的祝婚词:“爱情不是两个人的深情对视,而是看向同一个远方。

    一个与众不同还很正确的视角与切入点,能够带来意想不到的效果

  • 《明朝那些事》最后专门写徐霞客,历史虚无主义分子当年明月总结了一句话“成功就是用自己的方式度过人生”。用在这两人身上,很合适。

来源:「侣行」夫妇(张昕宇、梁红)花了一亿人民币环球旅行有意义吗?

关于浪漫

杜甫

再说大唐帝国的浪漫。盛唐的浪漫真叫浪漫,它是浪漫到骨子里面的。我们现在的人都不浪漫,要浪漫就毕不了业,再浪漫就找不到工作,你怎么浪漫?

我告诉大家,杜甫的浪漫,可不是他的现实主义。杜甫20岁的时候,他漫游五岳,到今天的江苏、浙江一带。你以为他像旅游一样,找个旅游公司糊弄一下,照个相就回来了?

他漫游多长时间你知道吗?从20岁一直玩到了24岁,又过了两年,他又往今天的齐赵去了,赵就是河北,齐就是山东这一带,玩了多长时间,大家知道吗?他说“快意八九年”,这叫浪漫精神

“快意”两字只可意会不可言传

孟浩然

孟浩然,你们知道“春眠不觉晓”对吧,那叫浪漫。我举个例子,他40岁跑到这个地方来考进士,他当时是扬名天下的大诗人。用我们今天的话来讲,他的微信群里那些人都是李白、王维这些人,都是一流的精英。

孟浩然这个人,40多岁还在考进士,他以为他考不上了,人生无常,他没有考取,但他家里比较富有,就回到湖北老家襄阳,活了60多岁,他就没干活了。

他写的《春晓》,“春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少”,我们现在是把它作为小孩子一二年级读的诗。

我告诉同学们,这首诗根本就是读歪了,小孩子根本就读不懂。他写的是封建士大夫的闲情逸致。什么叫闲情逸致?就是没有事干,才写出这种诗来。

你看他第一句话,“春眠不觉晓”,渲染出一种什么样的生活情调?懒散,这就是睡觉睡到自然醒。“春眠不觉晓”,就是外面太阳爬得老高老高,露珠在太阳底下晶莹闪亮,我们的孟浩然还在梦中逍遥,第一句是说他没有醒。

第二句说“处处闻啼鸟”,第一句是他没有醒,第二句是他醒了,他是怎么样醒的?这句就告诉了我们,是鸟叫醒的。

这就间接告诉我们写诗的心境,春天的鸟儿欢快地叫个不停,扰了我们孟浩然的清梦,却依然心情大好。既交代了怎么醒的,也交代了醒时的心情,轻松惬意。

第三四句就写“夜来风雨声,花落知多少”,写的是什么东西?写的是醒后的所思所想。这时候他起床了没有?没有。他睡眼惺忪,马上就想到昨天夜晚,刮了一夜的风,下了一夜的雨,外面的花吹掉了几朵呢?

好在哪个地方?我告诉大家,他这个时候四五十岁,是在他中晚年写的,一个四五十岁的大男人,早上一醒来,睁开眼睛,就马上关心花掉了几朵。

我们学校里有个桂子山,山上有花有鸟。我现在都活了60多岁,从来没关心过花掉了几朵,花掉了关我什么事。那首诗写的什么东西?闲情,什么事都没有。这才叫人的生活,这叫内在的浪漫。

保持“闲情”很重要!

来源:如果李白有朋友圈

最后

  • 如果觉得这碗鸡汤好喝,请点右下角【在看】
  • 如果还想继续喝,请【关注】
  • 如果被哪句鸡汤打动,请【评论】+【转发】喂给更多的人

最后,努力工作,优先解决了生存问题,才有资格解决生活问题,进而随心所欲的生活

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

【鸡汤第1期】从心所欲而不逾矩

从心所欲而不逾矩

  • 凭借一己之力,用100多个短视频,就获得了CNN积40年之功力才能在海外社交媒体上收割到的流量(订阅量700万+)

    再次印证精品依然是营销的利器。曾经被问到技术基础建设怎么向其他团队推广,答案就是把他做成精品

  • 她的世界,是雨落屋檐,灶台炉火;是挥锄刨姜,石磙碾玉;是肩抗枯柴,黄瓜入怀,既满足了人们对山水田间生活的想象,也在喧嚣闹腾的人心中营造出一片桃花源。

    好的文字真的能打动人心!

  • 穿得朴实,气质干净,话不多,手脚勤快……李子柒能打通中西审美,靠的就是她传递出的一份对生活的热爱、一种恬静的感觉。
    有点说不清?对,文化软实力,怎么能说清?

    什么是”文化“?我也说不清,《失控》一书里有这样两句话:【自然不仅仅是一个储量丰富的生物基因库,还是一个“文化基因库”】,【文化基因(meme):也称弥母,文化传播的最小单位,通过模仿等非遗传途径而得以代代相传。】

  • 嫌人家技能太多?那是少见多怪,是没干过农活的井蛙之谈。岛叔从来没有质疑过在农村生活的大姨:你怎么又会做床单,又会纳鞋底?又会养羊,又会养猪?又会种花生,又会种麦子?那是她的日常,那是她的人生。

    在分工越来越细致化的今天,没有在农村生活过的人估计真的很难理解

  • 我们虽然不喜欢给人、给事贴标签,但是传播规律证明,“标签化传播”确实有效。

    记住最后一句【’标签化传播‘确实有效】;最近看到周一围的一个新闻,说是不反对被贴上”油腻“的标签,因为他的职业是演员,不同的标签是对他演技的肯定;也不喜欢把真实的自己暴露给大家,避免刻板印象。

  • 围绕李子柒被热议不断的“文化输出”这词容易被外媒曲解,理解成国家行为、“组织预谋”云云。其实不是的,不妨用“文化影响”这个稍微中性一点的词。

    从”文化输出“到”文化影响“,赞!

  • 如何产生影响?归根结底是要有好的东西、好的作品。正如金庸、动作片、武侠片、流行歌构建起了香港流行文化形象一样,前提是这些文化作品质量不错,人民群众喜闻乐见、易于接受。如果说没有作品又大谈特谈“输出”,那就是空中楼阁。

    没有质量的输出,激不起半点水花。我相信,一个好的作者如果想要他的输出打动读者,首先得要打动他自己!

来源:【岛叔说】何必争论李子柒

首期说明

  • 一直有边阅读边划线的习惯,故而不经意间就会积累不少鸡汤
  • 鸡汤太多缺少整理
  • 灵光一闪的思考缺乏记录,遗失掉很遗憾
  • 信息爆发的时代,有价值的信息往往需要人肉大浪淘沙
  • 整理记录&顺带分享
  • 熬鸡汤费时,不定期发布

最后

  • 如果觉得这碗鸡汤好喝,请点右下角【在看】
  • 如果还想继续喝,请【关注】
  • 如果被哪句鸡汤打动,请【评论】+【转发】喂给更多的人!

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

手写JS函数的call、apply、bind实现

  之所以要写这篇,是因为曾经面试被要求在白纸上手写bind实现

  结果跟代码一样清晰明确,一阵懵逼,没写出来!

  下面,撸起袖子就是干!~

  把call、apply、bind一条龙都整一遍!~~

call

定义与使用

Function.prototype.call(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call

1
2
3
4
5
6
7
8
// Function.prototype.call()样例
function fun(arg1, arg2) {
console.log(this.name)
console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
// 接受的是一个参数列表;方法立即执行
fun.call(_this, 1, 2)
1
2
3
// 输出:
YIYING
3

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 自定义call实现
* @param context 上下文this对象
* @param args 动态参数
*/
Function.prototype.ownCall = function(context, ...args) {
context = (typeof context === 'object' ? context : window)
// 防止覆盖掉原有属性
const key = Symbol()
// 这里的this为需要执行的方法
context[key] = this
// 方法执行
const result = context[key](...args)
delete context[key]
return result
}
1
2
3
4
5
6
7
8
// 验证样例
function fun(arg1, arg2) {
console.log(this.name)
console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
// 接受的是一个参数列表;方法立即执行
fun.ownCall(_this, 1, 2)
1
2
3
// 输出:
YIYING
3

apply

定义与使用

Function.prototype.apply(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

1
2
3
4
5
6
7
8
// Function.prototype.apply()样例
function fun(arg1, arg2) {
console.log(this.name)
console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
// 参数为数组;方法立即执行
fun.apply(_this, [1, 2])
1
2
3
// 输出:
YIYING
3

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 自定义Apply实现
* @param context 上下文this对象
* @param args 参数数组
*/
Function.prototype.ownApply = function(context, args) {
context = (typeof context === 'object' ? context : window)
// 防止覆盖掉原有属性
const key = Symbol()
// 这里的this为需要执行的方法
context[key] = this
// 方法执行
const result = context[key](...args)
delete context[key]
return result
}
1
2
3
4
5
6
7
8
// 验证样例
function fun(arg1, arg2) {
console.log(this.name)
console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
// 参数为数组;方法立即执行
fun.ownApply(_this, [1, 2])
1
2
3
// 输出:
YIYING
3

bind

定义与使用

Function.prototype.bind()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
1
2
3
4
5
6
7
8
9
// Function.prototype.bind()样例
function fun(arg1, arg2) {
console.log(this.name)
console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
// 只变更fun中的this指向,返回新function对象
const newFun = fun.bind(_this)
newFun(1, 2)
1
2
3
// 输出:
YIYING
3

手写实现

1
2
3
4
5
6
7
8
9
10
11
/**
* 自定义bind实现
* @param context 上下文
* @returns {Function}
*/
Function.prototype.ownBind = function(context) {
context = (typeof context === 'object' ? context : window)
return (...args)=>{
this.call(context, ...args)
}
}
1
2
3
4
5
6
7
8
9
// 验证样例
function fun(arg1, arg2) {
console.log(this.name)
console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
// 只变更fun中的this指向,返回新function对象
const newFun = fun.ownBind(_this)
newFun(1, 2)
1
2
3
// 输出:
YIYING
3

最后,麻烦以后面试不要再考这道题了!~~~

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

你是否也默默吐槽过XXX的代码写得真烂?

  前段时间在做前端监控代码的交接,交接之前对代码质量有点忧虑(实际上也没有想象的那么差… - _ - )

  忧虑的原因是什么呢,曾经做一线开发时,用到公司基础框架组提供的框架时,当大家觉得不好用或者翻看源代码发现代码质量不高时,都会整齐一致的默默的一顿吐槽!~

  后来,当自己也成为了基础框架研发组的一员时,就特别小心,深知吐槽的威力…

  这里是背景,再回到本次代码交接。

  忧虑的根本原因还是在于**对现有代码不满意,感觉还有优化的空间**

  那么,问题就来了,既然都对现有代码不满意,那为啥还会写出这样的代码呢?

  这个人是不是有毛病?是不是做事比较糙?不靠谱?

  其实,并不是!

  在我看来,**每一个阶段都有每一个阶段的代码与之完美契合**,项目到了下一阶段,如果代码还在停留在上一阶级,那么看起来就会相当丑陋。

  这也与我自己一直践行的**简单直接**,反对滥用设计模式、反对过度设计相匹配。

  那么,怎么才能避免被人吐槽呢?

  很简单,**不断的重构!~**

  在我看来,好的代码or产品都并不是一蹴而就的,都是**不断打磨**出来的。

  本来前几天就想把这篇文章写下来的,标题都起好了,叫做《关于代码重构的一点思考》,迟迟无法下笔,因为按我之前写文章的套路,都会先把整个文章的结构先想出来,针对于这个标题,始终想不好文章的整体结构、有哪几个标题段落

  早上,看到了公众号【顾问之路】的一篇《用心生活还是用脑生活?思考了一年之后,我决定选择前者》, 因为下面的几句话,有了启发

  • 我好像一直在用头脑生活,用各种范式武装自己
  • 朋友是这么说的:我们都这么熟了,你没有必要用你对待工作的态度来对我
  • 在过去的那么多年里,我好像一直都在顽固的坚持用**工作脑**去同时对待工作和生活

  最后,感谢**猎头麦**,从此我的文章风格进入了新时代!哈哈哈!~

  回想起16年在Blog中写过一篇叫《关于代码质量的一点思考》,顺便搬运到公众号上来了,点击这里

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

一文搞定JS异常捕获

  关于JS的异常捕获,主要分为两种,一种为同步情况下的异常捕获,一种为一步执行下的异常捕获;异常捕获的【坑】主要集中在异步场景。

同步

  在同步场景下,简单粗暴,直接使用try/catch解决问题。

1
2
3
4
5
6
7
8
9
10
11
12
(function() {
try {
// catch: ReferenceError: obj is not defined
console.log(obj.error)
} catch (e) {
console.log('catch:', e)
}
})();

catch: ReferenceError: obj is not defined
at <anonymous>:4:17
at <anonymous>:8:3

异步

setTimeout异常

在异步场景下,按照上面的异常捕获方式是无法捕获到异常的,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无法捕获到异常
(function() {
try {
setTimeout(function() {
console.log(obj.error)
}, 500)
} catch (e) {
console.log('catch:', e)
}
})()

Uncaught ReferenceError: obj is not defined
at <anonymous>:4:19
at i (init.js:1)

之所以无法捕获到异常,原因在于异步方法执行时,主流程已执行完毕,try/catch已经退出函数调用栈;正确的异常捕获如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function() {
try {
setTimeout(function() {
try {
console.log(obj.error)
} catch (e) {
// 此处捕获到异常
console.log('catch1:', e)
}
}, 500)
} catch (e) {
// 此处无法捕获到异常
console.log('catch2:', e)
}
})()

catch1: ReferenceError: obj is not defined
at <anonymous>:5:21
at i (init.js:1)

从这里可以看出,最外层的try/catch是不生效的,可以去掉。

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
(function() {
try {
const promise = new Promise((resolve, reject) => {
setTimeout(function() {
// 此处的异常无法被捕获
console.log('1', obj.error)
resolve(100)
})
})
promise.then(result => {
console.log('result:', result)
}).catch(error => {
// 此处无法捕获到异常
console.log('catch1:', error)
})
} catch (e) {
// 此处无法捕获到异常
console.log('catch2:', e)
}
})();

Uncaught ReferenceError: obj is not defined
at <anonymous>:6:26
at i (init.js:1)

代码二:

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
(function() {
try {
const promise = new Promise((resolve, reject) => {
console.log(obj.error)
setTimeout(function() {
resolve(100)
})
})
promise.then(result => {
console.log('result:', result)
}).catch(error => {
// 此处捕获到异常
console.log('catch1:', error)
})
} catch (e) {
// 此处无法捕获到异常
console.log('catch2:', e)
}
})()

catch1: ReferenceError: obj is not defined
at <anonymous>:4:19
at new Promise (<anonymous>)
at <anonymous>:3:21
at <anonymous>:19:3

总结:

  1. 最外层的try/catch对于promise中的异常捕获完全无效
  2. new Promise()中的异步异常(setTimeout)只能在内部捕获
  3. new Promise()中的同步异常只能通过catch捕获

核心:熟悉EventLoop就知道,每个任务使用独立的函数调用栈;所以,每一个task都需要单独捕获异常;使用promise.catch能够捕获到promise任务的异常。

async/await异常

追寻talk is cheap,show me the code的原则,这里直接上验证代码,让实际结果来说明一切。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function f() {
return new Promise((resolve, reject) => {
console.log(obj.error)
setTimeout(function() {
// 1.这里的异常只能在setTimeout内部使用try/catch捕获
// console.log(obj.error)
resolve(100)
})
})
}
async function main() {
try {
const result = await f().catch(e => {
// 2.优先捕获到异常
console.log('catch1:', e)
})
console.log(result)
} catch (e) {
// 3.如果没有catch1,这里也能捕获到异常
console.log('catch2:', e)
}
}
main()

总结:

  1. new Promise()中的异步异常(setTimeout)只能在内部捕获
  2. 可以使用.catch也可使用try/catch来捕获异常,其中.catch优先级较高

本文首发于公众号

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

前端防抖与节流

  函数防抖与节流印象中都是为了控制函数执行频率,比如避快速点击提交按钮多次提交、避免模糊搜索框keyup事件监听时快速打字每次都去调用一次查询接口、避免页面滚动监听事件快速执行影响性能等;之前对防抖和节流的具体概念缺乏了解,比如他们有啥区别?具体是怎么样的?如何方便的理解这两个概念?翻了翻资料,结合自己的理解与思考整理如下

防抖

名词解释

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

example1: 模糊搜索框keyup事件监听,始终要等到用户输入告一段落后才会执行数据查询
example2: 游戏释放技能有一个准备时间,当准备阶段被打断时,需要重新走技能准备,准备完成才能够释放技能

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 防抖
* @param fn 需要做防抖处理的函数
* @param delay 技能准备(延迟)执行时间
* @returns {Function}
*/
function debounce(fn, delay = 500) {
let time = null
return () => {
clearTimeout(time)
time = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
// 只会打印debounce2
const debounceTest = debounce((param) => { console.log(param) })
debounceTest('debounce1')
debounceTest('debounce2')

节流

名词解释

高频事件触发,但在n秒内只会执行一次

example1:查询按钮,第一次可以立即点击,防止快速点击控制3s之内只能点一次
example2:游戏技能冷却,可用时随时可立即执行,当执行后有一个冷却时间

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 节流
* @param fn 需要做节流处理的函数
* @param limit 技能的冷却(限制)时间
* @returns {Function}
*/
function throttle(fn, limit = 500) {
let flag = true
return () => {
if (flag) {
fn.apply(this, arguments)
flag = false
setTimeout(() => { flag = true }, limit)
}
}
}
// 只会打印throttle1
const throttleTest = throttle((param) => { console.log(param) })
throttleTest('throttle1')
throttleTest('throttle2')

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。