3 月 8 号的那天下午,接到区疾控中心的电话:由于我 7 日早上乘坐的网约车司机核酸检测异常,我需要立刻通过非公共交通的方式回到家中,等待社区进一步的通知。接到电话后,有点懵逼。同事也听到我接到的是疾控中心的电话,还开玩笑说会不会是骗子。然后一查电话号码,果然显示为疾控中心。然后立刻和领导请假,打车回家。
回家的路上和女朋友打了个电话,把情况说了下。司机师傅听到后,默默的摇下了车窗(后面流调的时候,工作人员还找我要了他的电话号码,估计也要被隔离了,也挺不好意思的)。
(隔离酒店外景,拍摄于2022年03月12日。上海浦东)
回家之后就是等社区医院的电话,一直等到睡觉都没任何反应。由于女朋友没有打疫苗,非常担心万一有问题会被交叉感染,所以到家之后就开始都带着口罩,吃饭说话都离得远远的。晚上睡不踏实,三点多的时候看到手机有未接来电和短信提示,猜到是社区打电话来了。回拨之后果然是社区医生(听声音应该是刚睡着,真的挺辛苦的),告诉我说我被确定为密接,第二天会有专门的工作人员上门测核酸以及会将我拉到集中隔离点进行隔离。然后继续睡觉。
第二天又开始等。先是早上有社区的人说是流调,简单问了下我们的姓名、身份证号以及电话号码,其他啥也没问。中午的时候,又接到另外一个电话,是疾控中心的工作人员,这次才是真正的流调。过程中问了我从 7 号到 8 号整两天的活动轨迹。由于这两天是工作日,基本上就两点一线,所以和我接触的出了网约车司机就只有同事了。最后这些人都被判定为次密,大部分同事都接到社区的电话让居家隔离,少部分同事也被拉到酒店进行集中隔离。下午的时候有工作人员过来做核酸了,捅鼻子那是真的痛,眼泪都要流出来了。一直等到晚上睡觉,还是没等到接送我去集中隔离的电话。算下来,已经有 3 天没洗澡了,脸和头发都变得很油腻,整个人状态都不好。
第三天继续开始等。女朋友等的也有点着急了,开始打卫建委的电话询问情况。下午 2 点多终于接到社区卫生院的电话,说 120 会在半小时之内到达,让我收拾好行李准备出发。当时心想终于马上可以脱掉口罩,洗漱下好好休息了。然后又是继续等。等到下午 6 点多的时候,车还没来。打电话过去询问,说是车没空,只能继续等,具体要等到什么时候,她那边也不确定。等啊等,等啊等。晚上 10 点多快 11 点的时候,终于又一次接到电话,说车已经在楼下等我了,让我赶紧下楼。这一次是终于等到了。
(救护车内,拍摄于2022年03月09日)
到了隔离酒店又是等入住手续,一个多小时以后终于到我了。期间听到办理入住流程的医务工作人员在和领导申请援助,说是人都要虚脱了。晚上 12 点多的时候终于住进了隔离酒店。从此开始了隔离生活。
(酒店室内,拍摄于2022年03月10日)
一个礼拜以前,怎么着也想不到疫情形势会突然变得这么严峻。本来打算这周末去拍结婚证照片,然后下周请天假回去领证的,现在所有的计划都打乱了。人算不如天算,等隔离结束了,如果疫情没有得到有效控制,估计也还是不好出上海市。那样就只能再慢慢等待了。
最后,感谢下在疫情中辛苦付出的医护工作者和社区基层人员,希望疫情早点结束!
距离上一次更新博客,已经差不多一年时间了。年底的时候准备写一篇总结回顾一下,但是一方面因为喜欢拖延,另外一方面由于好像确实没什么特别好写的,所以最后春节都过完了还没开始 。本来最近也没时间 ,但是发生了一件哭笑不得的事情(脚趾头骨折),使我意外多了十多天的病假,借此机会聊聊这段时间发生的事吧。
(家附近公园的狗狗。拍摄于2021年05月1日)
工作方面根据业务方向的不同,主要可以分为两个阶段,前期(20 年 4 月份~ 20 年 8 月份)主要在做公司内部协同系统,负责任务模块、团队模块等核开发,参与较多产品讨论;,后期(20 年 9 月份~至今)主要在做买菜供应商系统,先后参与责门店模块,货品/商品模块的开发工作;同时在 21 年 2 月份开始以产品的角色参与到需求上线监控系统中。总体而言中规中矩,技术上几乎停滞不前。从转入到买菜项目组开始,天天加班加点写业务代码,疲于奔命。基本上晚上回到家都已经 1、2 点了,更晚到家也是家常便饭。回到家躺在床上经常思考人生的意义以及理想的生活到底是怎样的。
五一假期眼瞅着要结束了,然后在 3 号晚上突然骨折了。这天傍晚帮阿姨提行李,小小的 20 寸的行李箱,本来以为很轻,所以轻轻地拎了一下。没想到箱子沉的跟个铁块一样,巨大的反作用力把我带向了地面。为了防止摔倒,脚本能的用力往前掂了一下。瞬间脚趾头的剧痛沿着神经递质光速传到大脑,这种感觉和崴脚类似,当时觉得应该是肌肉扭伤没太在意,还继续拉着行李箱上了几个台阶。到家感觉还是有点疼同时也肿了。不过没太在意,晚上继续穿着拖鞋走路,洗澡。第二天早上也没有太恶化,仍然是脚趾头肿着,其他没啥变化。女朋友坚持去医院看一下,我寻思反正假也请了,去医院看一下还能开个病假单也不错。当时还是不以为意,中午穿着拖鞋还做了饭,下午穿着拖鞋去了医院。到医院后老流程走起来,挂号排队拍 x 射线检查,医生看了下结果,说拍片显示可能是内生软骨瘤。女朋友一听可能是肿瘤有点着急了,跟医生说我不痛不痒,之前也没征兆,应该不是肿瘤吧。医生感觉专业性被冒犯了,不过好在他也年轻,性格比较平和,所以耐心而又专业的解释了起来。末了,开了个核磁共振的检查,而且在病历本上写上了自己的电话号码,让我们有疑问随时问他,瞬间有点感动了。回来后有点担心了,开始查资料去了解这种肿瘤。晚上吃完饭看了一眼脚背,发现半个脚背都有点发红,跟女朋友说了声,她坚持要去看急诊,我觉得没那么严重就犹豫了一会。后来还是觉得去一下比较放心就打车去看急诊了。急诊大夫看了一眼脚的情况和下午拍的 x 射线片子,说角度不太好不能确定是否骨折了,需要补拍个 ct。但是同样的,基本上可以确定有软骨瘤。有无骨折只会影响手术方案。然后又说脚红肿是因为里面出血了,这只脚不能再用力了。女朋友听后马上去租了个轮椅,把我推到了 ct 室,拍好后又推到问诊室。医生看完 ct 片子,说骨折了,需要尽快手术。他这边最快周五可以(当天是周三)手术。手术需要去除肿瘤,取自体骨填充到被侵蚀的脚趾骨,然后用钢板固定。我一听感觉有点严重,得回去再了解了解后做决定。回来后就开始在微医上找专家挂号,确诊下具体情况。挂到了六院和瑞金医院的主任医师。
]]>近两个月前换到了一个面向内部业务的小组,和之前面对商家端的业务感觉还是不太一样。我思考了下这种不一样的感觉的根源,应该是参与产品的程度导致的。越复杂的系统,参与的人越多,每个人对于整个体统的感知度越低,带来的结果就是每个人都只是系统中的一颗螺丝钉。而在一个小型的系统中,每个人都能够对整个系统有更全面的了解,从而能够参与到产品方案的制定和决策,在有些时候甚至能够影响产品的形态,这在大型的系统中通常不会发生。相较而言,前者对于产品方面的参与非常少,只需要关注实现部分,而后者既要关注产品也要关注实现,这就引发了我对于产品和技术的思考。
在每个人都能够很好的完成技术需求的前提下,经常听到领导强调,要有产品思维。那么,什么是产品思维呢?以及为什么需要有产品思维呢?对于这个问题,思考到以下几点。
其一,产品思维即用户思维。之前也经常思考这个问题,对这个问题的理解是产品思维即站在用户的角度思考问题,从而产生实际的价值。这种思维是区别于技术思维的,技术思维习惯从技术层面去分析思考问题,对于用户是否有很大的价值并不十分关心。
其二,产品思维要求有领导力。前几天看到阿里玉伯前辈的文章,对产品思维有了一种新的理解。在他的早课的一系列的文章中可以看到他对于各种产品的思考。在他的文章 团队的文化根源 中,他提到他们团队的文化根源中的一点即为“产品梦”,这里的产品梦我理解为创造力以及实现创造思维的执行力。这种创造力和执行力让我想起了另外一个名次:领导力。维基百科中领导力的解释为:领导也称为领导力,是个人或是组织带领其他个人、团队或是整个组织的能力,是社会学中的一个研究领域,也是实务技能。这个词很早就经常出现在各行各业的招聘要求中,现在在我看来,它和产品思维其实相互重叠交错。
其三,产品思维有助于构建可持续交互的代码结构。呈现在用户面前的产品实际上是多方思维的集合:产品提出产品逻辑,后端控制数据关联,前端实现页面交互。在这个过程中,一个合格的产品应该对整个产品的短中长期都有一个比较清晰的规划。在这个前提下,产品功能的增删改都变得有迹可循,而此时如果技术拥有产品思维的话,会有预见性的设计自己的代码结构,为之后的变化留下充分的可能性。所以,在技术实力差不多的情况下,产品思维对于提高代码效率有着非常重要的作用。
产品的实质在于能够为用户提供价值,技术只是实现价值的工具。除非技术本身就是提供给用户的价值(比如教学),那么就永远需要更关注的是产品而非技术。但是作为一个技术人员,技术不能成为实现工具过程中的短板。这也很好理解,比如需要实现一个大表单的修改及提交,从产品的层面来说,只要能提供顺畅的填写体验,不管是通过原生的 js、早期的 jQuery 还是目前流行的 React/Vue,都无关紧要。但是如果只会 React,而且交互非常卡顿,那么这时候技术就会比产品更重要了,因为这时候技术成为了短板。在差不多技术水平的前提下,更拥有产品思维的人,会拥有更大的价值。评估这种产品能力对技术管理者而言就显得十分重要了,特别是对于大型团队的技术管理者而言。
关于产品经理,多说两句。产品经理作为一个产品背后逻辑的源头,会对产品最终的形态产生非常大的影响,所有一个好的产品经理非常重要。熟悉竞品、了解用户是产品经理的基本功,抽象归纳功能则是产品经理的核心能力。基本功只需要靠脚踏实地的使用、思考竞品,访问用户、了解场景即可,而核心能力则需要在基本功的基础上进行更加深入的思考和取舍,这是非常难的。然而据我观察,很多产品的基本功都不够,更别提抽象归纳组合能力了。这就导致项目没有明确清晰的规划,需求三番五次的修改,一句话总结就是:将帅无能,累死三军。希望每个程序员都能遇到一个合格的产品,也希望每一个产品都能成长为优秀的产品。
纵观互联网大佬发家史,非常多的创始人都是技术出身,然后才慢慢转型到产品和管理。可以说在创业的过程中,技术不是他们的短板,而是他们创业前期坚实的基础。于我而言,夯实基础,开阔技术视野仍然是目前的重点。与此同时,多发现、多思考,培养产品思维,希望有一天也能做出自己的产品,即使能让很少一部分人得到收获,也就够了。
图文无关
写在前面:好久没写博客了,想到哪写到哪,不限于工作、学习或者生活。就把这当时一块自留地吧,随便种点东西。
这两天把图床弄了一下。之前七牛云链接失效导致所有图片都无法查看给我留下了很惨痛的教训,所以这次重新寻找图床方案首要条件就是:稳定可靠。其次考虑到的是安全问题和访问速度。最终采用的方案是直接购买阿里云和腾讯云的对象存储服务(亚马逊云服务也开了,但是好像比较慢)。结合 xnip+iPic+云,简直有点爽。腾讯云比阿里云便宜一些,但是阿里云的链接不能直接访问,直接访问的时候会强制弹窗下载,体验非常不好。所以最终选择了腾讯云,上面的图片就是存在腾讯云上的,体验非常好。在写博客的时候,Typora 结合 iPic 可以直接将本地的图片自动上传到服务器,而不需要先在 iPic 中上传,然后复制链接到博客中。这两者的结合带来的写博客的体验简让写博客变成了一件很让人享受的事情,特别是在需要用图片的时候。
在学习 TypeScript 的路上又停了几天了。在新项目中尽自己目前的水平补充了各种类型,慢慢的体会到了这种类型变成的好处。代码不用运行就能知道是否有错误,有点厉害。不过目前的使用方式还非常基础,稍微复杂一点的类型定义、约束、判断之类的就不会了。还得多看看文档,然后找个比较负责的例子看一下。
这两天工作状态挺不好的,办公室太闷了,啥也不想写。
先睡了,明天再写。 2020-04-01
一晃两周过去了,这两周也发生了挺多事情的。
上周又换办公室了,总的来说这边比之前的地方更舒服些,因为不太闷了。而且现在也是一个靠窗的位置,和当年的位置一模一样,恍惚间有穿越的感觉。除了换办公室之外,这次也把我从之前的组调了出来,也不知道是短时间的还是长时间的。搞不懂为什么前几天谈话的时候为啥不直接告知我,而是通过小组长传达。新组更忙一些,但是参与感更强,所以也更有意思一些。
最近看了一档节目《李自然说》,越听越发现这个人还挺有意思的。他之前做过美股交易员、倒卖过电脑、主讲过日本历史、卖过围棋教学光碟,然后现在在创业的同时搞自媒体。惊讶于一个人居然能有过如果多的经历,并且每一项经历都可以用于谋生。真的是生命在于体验。眼界的开阔以及强执行力往往决定了一个人的高度,这些特质都不是一朝一夕形成的,需要不断的去学习和训练。希望自己也能慢慢的有所提高吧。
最近确实是有点忙了,计划好的技术学习停滞不前。我发现洗澡的时候思维最活跃,会想一些关于技术、产品、团队建设、职业规划等等之类的事情,所以有时候洗澡会洗很久。工作忙导致下班后非常累,累了自然想放松下,然后玩手机或者看会游戏直播,洗完澡继续玩手机。有时候会玩手机到很晚,放下手机又睡不着了。从而没休息好,第二天赖床,然后精神状态不好,提前半小时到公司了也不想学习。就这样循环往复,一天一天飞快的过去了。这其实是个恶性循环,而打破这个循环的关键点就在于好好休息。
好了,不多说了,准备睡觉。每天看看睡觉数据。(然后,上周基本上还是在一点半到两点之间才会睡觉。还真是打死不改啊)
已经挺久没有逛过葡萄了,前两天吃饭排队的时候无意间去逛了下滴灌区,了解到这个素未蒙面的学长的故事。做有意义的人,干有意义的事,珍惜每一天。
可点击播放
]]>昨天在毫无征兆的前提下公司宣布今天所有的技术都得留在公司跨年。这个消息也算是今年生活的一个写照:社畜,毫无生活的工作着。无奈之余,就寻思那就将今年的总结写一下吧,也算是给今年一个交代吧。
工作方面可以从这几个方面来总结:技术、业务和团队合作。这三点基本上也涵盖了工作中所需要的方方面面。
技术
技术的成长速度基本上是衡量这一年工作质量的基本指标。先下结论:除了经验性的获取技术成长之外,今年仍然没有大的突破。
单元测试。测试覆盖率一直以来都是用于评价一个系统稳定性的重要指标,但是由于其需要较高的时间和精力投入,导致基本上所有的业务组的覆盖率都不高。在这样的背景下,一边摸索着一边写测试,终于将项目的测试覆盖率提高到超过业务类型项目所要求的数字。需要指出的是,对于测试的理解还处在比较粗浅的程度,自己的大部分的测试解决方案都来自于经验的积累,而非知识原理性的应用。需要加强。
Hooks 及重构。重构分为了两部分,前期的重构纯粹的是从组件的结构和组件内的数据流方向来进行,未完全脱离之前的框架,也就是用之前的工作经验来处理当时的工作。之后学习到了 hooks,利用 hooks 中提供的类似于 redux 的数据管理方式来重构之前的组件。重构后的组件结构合理,逻辑清晰,便于维护。重构的后期,由于各种其他业务的打断,并没有很完整的进行下去,导致后期的重构效果不明显,今后需要加强。
业务
和以前相比,在业务上有了更大的未知性,也就是说现在对业务的了解越来越片面了,有些时候根本不知道某个需求的来龙去脉,只是在产品同学的解释下一步步的实现逻辑。以前做的每一个需求都是一个完成的产品,现在做的需求,有时候它会是一个产品,有时候它仅仅是一个配件。技术服务于业务,很多最终呈现给用户的并不是技术的好坏,而是业务的本质。在我们绞尽脑汁的将页面优化了零点几秒的时候,我们会发现很多用户连 IE 和 Chrome 都搞不清楚。这时候,这种优化就显得有点讽刺了。作为一个贴近用户的技术人员,精进业务和提高技术同样重要。今年在业务上投入的时间和精力还不够,虽然有了通过分析埋点数据来分析业务的尝试,但是不够系统也不够全面(没有改进后的对比),这是比较遗憾的。需要好好考虑下怎样系统且全面的了解当前的业务。
团队合作
作为一个大头兵,这个其实没有太多好写的。团队内紧跟组长的步伐,团队间紧跟负责人的步伐,不卑不亢,团结友善吧。
TypeScript 基础知识学习,来源为官方文档,用于定期复习巩固。
两种写法,someValue 本身可能不是 string 类型,用户直接断言其为 string 类型:
let strLength: number = (<string>someValue).length
let strLength: number = (someValue as string).length
关键词:var, let, const
1 | interface Point { |
1 | interface SearchFunc { |
1 | interface StringArray { |
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员
1 | interface Counter { |
1 | class Greeter { |
1 | class Octopus { |
1 | let passcode = "secret passcode"; |
1 | function add(x: number, y: number): number { |
1 | let myAdd: (x: number, y: number) => number = function( |
1 | function buildName(firstName: string, ...restOfName: string[]) { |
1 | let suits = ["hearts", "spades", "clubs", "diamonds"]; |
1 | function identity<T>(arg: T): T { |
1 | function identity<T>(arg: T): T { |
let output = identity<string>("myString")
let output = identity("myString");
1 | function loggingIdentity<T>(arg: T[]): T[] { |
1 | function identity<T>(arg: T): T { |
1 | function identity<T>(arg: T): T { |
1 | interface GenericIdentityFn<T> { |
1 | interface Lengthwise { |
1 | enum Direction { |
1 | enum Enum { |
1 | const enum Enum { |
1 | declare enum Enum { |
1 | function isFish(pet: Fish | Bird): pet is Fish { |
1 | type Name = string; |
1 | const getClassNameSymbol = Symbol(); |
介绍了一些 Symbols 的通用方法
1 | let list = [4, 5, 6]; |
工作之后,越来越少的时间与自己的内心进行交流了。写年终总结是一个为数不多的与内心进行交流的契机,所以每次开始写年终总结都有一点犹豫、期待和兴奋呢。和往常一样,下面会从工作和生活两方面来进行总结。
这个坑是个深坑,填不了了。之后有时间有契机的话可以说说这段时间发生的事情,大概就是换工作面试之类的。
]]>本来准备去年年底写完的,一直拖一直拖,这个坑估计是填不上了。
浏览器跨域保护是一种很常见的策略,各大浏览器都会有这种保护机制,但是在本地进行调试的时候我们并不希望这个策略生效,因为生效的话我们就没法正常拿到数据了。
]]>最近遇到这样一个问题:点击按钮下载文件的时候,在 Safari 和 Chrome 上都没有问题,在 Firefox 和 Edge 上则无反应。一开始我以为是后者默默的把下载好的文件保存在了浏览器的默认位置导致的,直到昨天产品大佬告诉我默认文件夹并没有对应的文件,这才怀疑可能是在实现过程中有兼容性问题。
点击下载的时候有一段代码是这样的:1
2
3
4
5
6
7
8
9
10let a = document.createElement('a');
a.style = "display: none";
document.body.appendChild(a)
let url = window.URL.createObjectURL(blob);
let disposition = response.headers.get('Content-Disposition');
let filename = (disposition &&disposition.replace(/attachment;filename=/,'')) || 'data.xlsx'
filename = decodeURI(filename);
a.href = url;
a.download = filename;
a.click();
这段代码其实很简单,就是先创建个 Blob 对象,然后点击它下载。由于之前没有接触过这个 API, 所以稍微去了解了下,发现两篇文章:JavaScript 中 Blob 对象, JS前端创建html或json文件并浏览器导出下载,比较通俗易懂的介绍了下 Blob 对象的使用方式。这一步是没有什么问题的。
自然而言的会怀疑到这个API的浏览器兼容性,然后查了一下,结果如下图所示。我是用的 Firefox 是 62 版本的,所以应该也不存在兼容性问题。
然后开始怀疑是不是点击事件没有触发。
触发一个点击事件的前提:事件绑定在元素上,那么如何将一个点击事件绑定在元素上呢?
以下内容引用自 addEventListener vs onclick
Event Listeners (addEventListener and IE’s attachEvent)
在 IE 9 及以前,需要用 attachEvent
方法来绑定点击事件:element.attachEvent('onclick', function() { /* 具体函数*/})
在其他绝大多数浏览器中,则可以用 addEventListener
来进行绑定:element.addEventListener('click', function() { /* 具体函数*/ }, false);
利用这种方式添加事件绑定的时候,理论上可以在同一个元素绑定无数的事件,唯一需要考虑的就是性能问题以及客户端的内存,这些就都和浏览器有关了。
上面的例子中添加的函数都是匿名函数,其实可以先声明一个函数,然后通过函数引用的方式来绑定事件:
1 | var myFunctionReference = function() { /* do stuff here*/ } |
利用 addEventListener
进行绑定的时候需要传第三个参数,这个参数是用来控制事件是否冒泡的。在 attachEvent
方法中没有此参数。
Inline events (HTML onclick=”” property and element.onclick)
在支持 JavaScript 的浏览器中,我们可以在元素上加上事件监听函数,方法如下:<a id="testing" href="#" onclick="alert('did stuff inline');">Click me</a>
虽然大部分有经验的程序员都很少用这种方式,但这种方式确实也可以达到目的,而且简单直接。这种写法的缺点也显而易见:函数必须要非常简单。
另外一种写法是:element.onclick = function () { /*do stuff here */ };
这种写法和上面其实是等价的,不过这样写的话函数的复杂程度就可以大大提高了。
利用行间事件的写法有一个很明显的缺点:每一个元素都只能有一个对应的事件,事件是作为元素的属性存储的,这样一来如果有多个同样的事件,那么前者就会被后者覆盖:
1 | var element = document.getElementById('testing'); |
上面这段代码执行的时候,只有第二个函数会执行,因为第一个事件被覆盖掉了。
上面两个点都没有解决到实际的问题,最后终于在 stackOverflow 上看到了一个问题: Blob createObjectURL download not working in Firefox (but works when debugging) 利用里面提到的解决方案顺利解决了这个兼容性问题。
]]>已经很久没有制定学习计划了,每次都是心血来潮的看到想学的内容然后开始搜索,搜完相关资料后兴致没有了就放弃了。虽然挺舒服的,但从长远的角度来说,这不是一种良性的状态,所以开始恢复写博客,然后将近段时间的学习计划列出来,一步一个脚印。
这块知识一直都没有花费时间和精力去学习,每次碰到与这块内容相关的问题都不知所措,所以想拿出时间学习这方案的想法越来越成熟。
学习途径:看书《图解 HTTP》
随着互联网时代的到来,为了满足来自世界各地的访客的需求,网站的多语言版本设计显得越来越重要。本篇博客主要用来说明怎样开发多语言版本。
技术栈:React.js + Next.js + Mobx.js + i18next
WIP
]]>本博客用来记录在工作中遇到的各种疑难杂症及对应的解决方案,方便遇到同样问题时的检索。
问题及解决方案汇总:
官网个人版的数据直接放到重构的机构版时系统报错。由于分工不同,机构版将不同功能分割出来了,把共用部分放在了 common 中,其余不同模块分为了不同的项目,每个项目都是一个单独的 next.js 项目。需要引用项目外的共用文件的时候就利用 bindfs 来进行映射。
1 | |--- common |
由于和之前的项目相比并没有做过多的改动,但只要一启动就报这种错误,然后用关键词 Module parse failed: Unexpected token (290:73) You may need an appropriate loader to handle this file type。搜出来结果很多,基本上都是和 webpack 配置相关,然后我反思了下这两个项目都没有配置过 webpack,所以应该不是这里面出现的问题。左看右看,终于找到一条让我眼前一亮的答案:
1 | Next only compiles files inside of the next root, which in your case is root/server. You can probably use something like 3732 to compile files outside of the root. |
原文链接:Spread Operator not building – Webpack #3819
看到这个答案后,将所有链接到项目外的路径改成项目内就好了。
系统有部分数据保存在 mongoDB 中,取出的数据很多时候会是字符串形式,这时候最普遍的得到具体数据的方式是利用 JSON.parse 来进行解析。大部分的时候都能成功解析,也存在有些时候会出现描述中的错误,这时候就需要分析其中的原因了。
这个问题其实是因为需要解析的不是一个有效的 JSON 对象导致的,以下引用自 JSON.parse unexpected token s :
What you are passing to JSON.parse method must be a valid JSON after removing the wrapping quotes for string.
so something is not a valid JSON but “something” is.
A valid JSON is -
1
2
3
4
5
6
7
8 > JSON = null
> /* boolean literal */
> or true or false
> /* A JavaScript Number Leading zeroes are prohibited; a decimal point must be followed by at least one digit.*/
> or JSONNumber
> /* Only a limited sets of characters may be escaped; certain control characters are prohibited; the Unicode line separator (U+2028) and paragraph separator (U+2029) characters are permitted; strings must be double-quoted.*/
> or JSONString
>
/* Property names must be double-quoted strings; trailing commas are forbidden. */or JSONObjector JSONArray
1 | > Examples - |
You may want to look JavaScript and JSON differences
说了这么多,那我们应该怎么去处理这种情况呢?其实很简单,用 try…catch 就行了。如果是合法的 JSON 对象就解析,如果不是则不进行处理。
利用 forEach 进行循环然后遍历改变原数组的需求非常常见,但稍有使用不当就会造成并没有改变原数据的结果,这主要是因为下面这个原因。
因为在 safari 中没找到简单快捷的操纵 cookie 的方法(在 chrome 中利用扩展 editThisCookie 很方便,在 safari 中可以直接在 console 中加入 cookie,但总归不方便),所以开发的时候一直都是在 chrome 中。项目上线后在 safari 中也测了测,没有发现问题,直到有一天同事告诉我所有的页面在后退的时候都会出现报错页面,而且只会出现在 Safari 中。百思不解,首先想到的是浏览器的兼容性,也找了很多相关的内容来看,有文章提到是因为返回的时候从缓存里面拿数据的原因,并没有提到具体的解决措施。然后想到如果真的是这个原因,那么之前做的个人版应该也会有同样的问题,所以就测了下之前的个人版,发现却并没有这个问题,所以就把思考的重点转移到了分模块打包上。在 next.js 项目的 issue 中找了找也没有找到相关的内容,问题暂时搁置。
大概两天后,老板说点击工商页面的时候也出现了报错页面,好像突然就有灵感了!联想到之前也有同事告诉我点击链接的时候会报错,给了我一个新思路:从链接入手!然后在官方文档上看到这么一句话:For the initial page load, getInitialProps will execute on the server only. getInitialProps will only be executed on the client when navigating to a different route via the Link component or using the routing APIs.
到这其实已经心中有数了。之前一直认为 getInitialProps 只会在服务器端执行,所以为了辨识用户身份,将服务器相关的参数传入到了这个函数中,然后在通过 Link 组件或者前进后退的时候,这个函数也会执行,然而此时找不到在函数中使用的参数(如 process 对象),所以系统持之以恒的报错!想到这点,问题便迎刃而解了。
有客户在微信打开我们的网页时发现空白页,测试发现在 safari,chrome,firefox 下都是没问题的。分析发现微信内置的浏览器版本对应于 chrome43 版本左右,版本过低导致不支持 es6 语法导致。问题分析到这就迎刃而解了,在 next.js 项目下搜索 browser 顺利找到解决方案。
1 | const originalEntry = newConfig.entry; |
1 | // polyfills.js |
组内的一个小伙伴在发送请求的时候发生了这种错误,我拉完代码后却什么问题都没有。
推测有可能导致的原因:本地 node 版本,项目依赖包的版本,浏览器版本,用户信息等等。在一一排除这些因素后,项目启动还是会出现报错。网上搜索了下,没有找到特别符合的同类错误。因为使用的是 isomorphic-fetch 来请求数据的,所以也把源码稍微打开看了下,并没有找到这种类型的报错。几乎放弃的时候想起来有人提过这种错误可能是在请求中有中文导致的,逐一去检查,终于发现了原来是同事在 cookie 中添加了未编码的中文名,编码后重新尝试,终于好了。
尝试进行接口测试,本地部署的时候需要运行 node 服务,随便填了个端口:6666。
多次重启未果,猜测可能是端口的原因,网上一查还真是。在 chrome 中 6666 端口被禁用的,其他被禁用的端口如下:
1: // tcpmux
7: // echo
9: // discard
11: // systat
13: // daytime
15: // netstat
17: // qotd
19: // chargen
20: // ftp data
21: // ftp access
22: // ssh
23: // telnet
25: // smtp
37: // time
42: // name
43: // nicname
53: // domain
77: // priv-rjs
79: // finger
87: // ttylink
95: // supdup
101: // hostriame
102: // iso-tsap
103: // gppitnp
104: // acr-nema
109: // pop2
110: // pop3
111: // sunrpc
113: // auth
115: // sftp
117: // uucp-path
119: // nntp
123: // NTP
135: // loc-srv /epmap
139: // netbios
143: // imap2
179: // BGP
389: // ldap
465: // smtp+ssl
512: // print / exec
513: // login
514: // shell
515: // printer
526: // tempo
530: // courier
531: // chat
532: // netnews
540: // uucp
556: // remotefs
563: // nntp+ssl
587: // stmp?
601: // ??
636: // ldap+ssl
993: // ldap+ssl
995: // pop3+ssl
2049: // nfs
3659: // apple-sasl / PasswordServer
4045: // lockd
6000: // X11
6665: // Alternate IRC [Apple addition]
6666: // Alternate IRC [Apple addition]
6667: // Standard IRC [Apple addition]
6668: // Alternate IRC [Apple addition]
6669: // Alternate IRC [Apple addition]
本博客用于记录与MySQL相关的知识点。
table-name
; 本域名即将到期(6月22日),之后域名地址更改为:detachment.top。如果你一直在关注我的博客,别忘了更换地址。
同时,在我 github 上的 https://github.com/Detachment/Detachment.github.io 这个项目里,你也可以找到我的最新博客地址,谢谢关注。
从正式辞职到又一次开始上班,我用了整整 13 个月(差一天),时间不饶人。
谨以此文记录在前端路上走的每一步。
假设我们足够聪明,我们可以在脑海中以时间为横坐标,以对自己的认可程度为纵坐标,作一条曲线。那么这条曲线便是我们的人生之路。很久以前睿智的长辈就告诉我们,有几个时刻对这条曲线的影响会远远大于其他时刻,这些时刻包括:高考、工作和婚姻。目前我已经经历了其中了两个时间点,我认为这么说其实并不恰当。知道自己想干什么 以及 开始追求喜欢的人,在我看来才是最关键的两个点。
大学四年在一种不断尝试以及不断失败的模式中度过,结果就是直到毕业也没有确定自己将来到底要走一条怎样的路。毕业后进入一家机械公司,为了更好的了解工艺,选择在制造部门呆了半年,然后转到设计部。这之后在设计部做了近一年半的机械工程师。表面上的一切顺利与躁动不安的年龄格格不入。15 年下半年发生了两件事情,其一是遇到了一个喜欢的女生,其二是家里发生了一些变故。这两件事情就像是两阵风,迎面吹过,平息了内心的躁动,竟考虑安定下来了。然而时间的力量是巨大的,只用了半年,事情发生了很大的变化,此时我又开始寻思改变了。创造所带来的成就感可以让我感觉到自己的存在,在这样的环境下我无法做到这点,不能再这样消极度日了。下定决心要转行了。
既然要转,就面临着选择的问题。根据面向工资转行定理,很自然的想两个行业:金融和计算机。在我看来,计算机的趣味性和可创造性胜过金融,所以我用小脑做出了转计算机的决定。然后又面临选择了:做前端还是后端?要想了解一个复杂的事情,大多会选择自己熟悉的点进行切入。最开始接触的是 Python,大学的时候两个室友都选修过这门课。然后通过了解知道这门胶水语言的强大,但也正因为它的强大,我放弃了它。因为如果选择学习它,不可避免的需要在更细分的领域做出选择,而做出选择不仅需要精力还需要智慧。在这样的背景下,机缘巧合接触到了前端。最初对前端的了解是学习路线明确,可快速上手,而且涉及到视觉和交互等方面(后来才知道这部分其实已经细分到 UI 及交互设计师了)。然后就开始了前端的学习之路。
学习路线在我的 16 年总结中已经说的比较详细了,总的来说就是一个不断尝试的过程。这篇博客中就另外两个选择说说自己当时的想法吧:其一是为什么不选择参加培训班,其二是为什么在自学半年后放弃工作机会而继续学习。
参加培训班从来都没有出现在我的选择范围,细细一想,存在的原因有以下几点。上面也提到过,整个大学阶段我都处于一种比较低落的状态,虽然在不断的尝试,却并没有寻找到自己的一条路。时间虽然能慢慢减弱这种挫败感,但却无论如何也无法消除。这种自我否定的想法让我时而变得非常消极,因此我想再给自己一段思考的时间,而这段时间到底多长,我无从得出结论。基于这样的原因,我希望这段时间能够完全按照自己的想法来生活,不管是学习、玩耍还是作息。而这一点,培训机构是无论如何也满足不了的。所以就此一点,就可以完全将培训机构排除在外了。对于一个从来没有出现在脑子里面的问题作出答案,原因是被问到的次数太多了。
为什么不尽早参加工作? 一部分是因为上面提到的原因:我仍然没有找到自己想要的答案,这段时间尚不能结束。另一部分的原因是当时技术水平太低,很容易限制自己的眼界,从而形成技术天花板。当然了,这只是我的想法,对于是否正确,我现在还无法做出判断。只需要对自己的选择负责就问心无愧了。
工作还差三天就满一个月了,说一说这一个月来的感受。
刚入职的时候,先熟悉公司的整体业务逻辑、技术栈和工具。知道了前端主要用 React 之后,开始跟着官方文档学习。学了差不多一个礼拜,文档还没有完全看完呢,松哥找我说,先写起来吧,这样学不是办法。然后就给了我一个重构页面的小任务练手。从这开始才真正的接触到公司的业务逻辑。这才感受到,看起来非常简单的页面,背后的逻辑竟这么复杂。之前自学的时候所接触和了解的部分和真正工作中的复杂程度根本不在一个量级,我所看到的只是冰山一角罢了。
写了两三天,一头雾水,完全不了解里面的数据流,写出来的页面显示不了任何数据。松哥过来看了看,啪啪啪的一顿写,数据就出来了。根据他修改的部分,我又重新去看了看 Reflux 的官方文档,再来对比实际的代码进行理解,终于有了一点感觉。继续写了两三天,数据没啥问题了,但又有了其他问题:关联样式。松哥一直强调代码和样式的复用,表面上我也确实理解,但是在实际的应用过程中却很难取得这样的效果。这个还得建立在对整个系统样式的理解上,否则很难做到游刃有余的复用之前的样式。
一个礼拜又过去了,给了我一个真正项目上的简单页面的编写任务。初步了解了下,感觉非常简单,一两天就能做出来。哈哈哈,还是太年轻。我的想法是先从接口获取数据,然后根据数据编写页面。捣鼓了两三天,又是 JQuery 又是 Openresty 的看,想把数据弄到手,结果一无所获。这时候松哥又出现了,告诉我接口先不管,先用静态的数据来模拟,之后再来帮我一起写接口。又是两三天,自认为样式差不多了,给松哥看了看,满以为完成的很好。松哥一看,一脸黑线:你这写的啥样式?这样的样式能复用吗?能自适应吗?有初始化吗?哈哈哈,我被问的一脸懵逼,确实是没有注意这些方面。之前自学的时候,只管在表面上看起来达到效果,哪管得上规范呐。这方面还是得多注意注意。之后开始写接口了,也是一问三不知,被松哥吐槽你咋啥都不知道。看完写好的代码,又学习了新姿势,开心。
高中物理老师说的一句话到现在还记得,说的是人对于知识体系的理解程度分为四个阶段:1. 不知道不知道;2. 知道不知道;3. 不知道知道;4. 知道知道。认为很有道理,然后对比分析了下自己,很明显的处于第一个阶段。所以有时候根本不知道自己应该加强哪些方面,因为所有方面都需要加强。这也是目前面临的一个最大的问题。我认为解决问题的最佳途径就是多和松哥以及公司的前辈交流。调整自己的步调,努力做到和公司共振的状态。
下个月的目标:一方面加强对公司业务逻辑的理解,另一方面在数据库和网络协议方面要下点心思。其实了解数据库也是为了更好的理解业务逻辑,有机统一。还有一点就是:技术博客还是得定期写一写,鞭策自己不断的学习。给自己定一个小要求,每两周写一篇技术博客,主题不限。每个月写一写总结,就更新一下这篇文章。
到昨天为止,工作满两个月。本来计划好的两周一篇技术博客,一个月一次的总结,现在却并没有做到。应该是时间有点紧,暂时还么有完全适应这种工作节奏吧。
这个月大体在忙两件事:1. 学习 redux,为重构后台界面做准备;2. 补充完善现在正在做的 H5 页面。懂得了一个道理:在工作中,快速学习和快速开发能力是非常重要的。以前自己一直走的是系统性学习的路线,现在应该要在快速的经验式学习上有所投入。
本篇博客为 JavaScript 基础知识总结,长期更新。
按值访问:
按引用访问:
只能给引用类型值动态添加属性
两种情况下会延长: with 语句以及 try-catch 语句的 catch 块
原理:找到不再继续使用的变量,然后释放其占用的内存。
JavaScript 中最常用的垃圾收集方式
不太常见的垃圾收集策略。含义是跟踪记录每个值被引用的次数。
讨论的是以怎样的标准来确定是否回收
在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。常被称为类,但并不妥当。因为尽管 ECMAScript 从技术上讲是一门面向对象的语言,但是不具备面向对象语言所支持的类和接口等基本结构。
所有对象都有 toLocalString(), toString(), valueOf() 方法,对于数组:
每个方法接收两个参数:处理函数及作用域(可选)。处理函数接收三个参数:数组项、项的位置及数组对象。都不会改变原数组。
归并数组所有项,最终返回一个值。每个方法接收两个参数:作用在每一项的函数及归并基础值(可选)。每个函数接收四个参数:前一个值、当前值、项的索引以及数组对象
都是日期对象中的属性,书本 P102
var expression = / pattern(模式) / flags(标志)
,或者用 new RegExp() 构造函数主要实例属性有:global, ignoreCase, lastIndex, multiline, source。能获取模式的各种信息,但用途不大
主要属性有 input, lastMatch, lastParen, leftContext, multiline, rightContext
虽比较完备,但还是缺少一些特性,比如:
函数是一个对象,所以函数名实际上是一个指向函数对象的指针,不会和函数绑定
将函数名想象为指针,有助于理解为什么 ECMAScript 中没有重载的概念
JavaScript 引擎是怎么确定代码中是否有函数声明的?先得扫描全部代码还是有特定的标记?
函数可以作为值来使用
函数内部有两个特殊的对象:arguments, this
每个函数都有两个属性:length 和 prototype
每个函数包含两个非继承而来的方法:apply(),call()。都用于设置函数中的 this 的值。最强大的地方在于扩中函数作用域。
还有一个方法: bind(),用于绑定函数实例的 this 的值
函数继承而来的 toString(), toLocalString(), valueOf() 都会返回函数代码
三个特殊引用类型:Boolean, Number, String。每当读取一个基本类型时,后台会自动创建一个对应的基本包装类型对象。
引用类型及基本包装类型的主要区别在于对象的生存期。
用处不大,容易造成误解
由 ECMAScript 实现提供的,不依赖宿主环境的对象,在程序执行之前已经存在
ECMAScript 中有两种属性:数据属性和访问器属性
利用 Object.defineProperties() 方法一次定义多个属性
利用 Object.getOwnPropertyDescriptor() 方法,接收两个参数,分别为属性所在对象及要读取描述符的属性名
抽象创建具体对象的过程。解决了创建多个相似对象的问题,却没解决对象识别的问题(即怎么知道一个对象的类型)
prototype 属性是一个指针,指向的对象包含了由特定类型的所有实例共享的属性和方法
构造函数模式定义实例属性,原型模式定义方法和共享属性
将原型放在构造函数里面,通过 if 判断达到只执行一次的效果
和工厂模式基本一样,区别在于使用 new 操作符。。。
OO 语言支持两种继承方式:接口继承及实现继承。前者继承方法签名,后者继承方法本身。由于在 ECMAScript 中函数无签名,所以只能实现继承,主要通过原型链来实现。
基本思想是利用原型链让一个引用类型继承另一个引用类型的属性和方法。让一个原型对象等于另一个类型的实例
借用构造函数的基本思想是在子类型构造函数的内部调用超类型构造函数
有时候也叫作伪经典继承,结合原型链继承和借用构造函数继承。使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。最大的问题是无论什么情况下都会两次调用超类型的构造函数:一次是在创建子类型原型的时候,一次是在子类型构造函数的内部
借助原型可以基于已有对象创建新对象。 ES5 中的 Object.create() 方法用于规范原型式继承
思路与寄生构造函数和工厂模式类似
这种方式可以解决在组合继承模式下的问题。寄生组合式继承通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。普遍认为这种方式是引用类型最理想的继承方式。
定义函数的两种方式:函数声明及函数表达式
递归函数是一个函数调用自身的情况下构成的。递归的时候,用 arguments.callee 比直接用函数名保险
闭包是指有权访问另外一个函数作用域的函数。
[[Scope]] 属性保存全局变量对象的作用域链
作用域链本质上是一个指向变量对象的指针列表,只引用但不包含实际的变量对象
闭包会携带包含函数的活动对象,所以比一般函数更占内存,所以使用的时候需要注意
闭包只能取得包含函数中任何变量的最后一个值
闭包中引用了包含函数的活动对象
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。函数声明后面不能加圆括号,函数表达式可以。自执行匿名函数可以减少闭包占用内存的问题,因为执行完即销毁,没有指向匿名函数的引用了
任何函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。
特权方法:指的是有权访问私有变量和私有函数的公用方法
缺点:针对每个实例都会创建同样一组新方法
在私有作用域中封装构造函数并将公有方法添加到构造函数的原型中。这样每个通过这个构造函数创建的实例都会共享同样的方法。好处是有利于代码复用,坏处是每个实例都没有了自己的私有变量
模块模式:为单例创建私有变量和特权方法。单例指的是只有一个实例的对象。JavaScript 中一般利用对象字面量的方式创建单例
(不知所云)
事件流:描述从页面接收事件的顺序。一种是冒泡流(IE),一种是事件捕获流(网景)。
从具体发生的元素到不具体的上层节点。
现代浏览器都支持事件冒泡
接收事件的顺序和事件冒泡相反
老版本浏览器不支持,建议放心使用事件冒泡,有特殊需要时再使用事件捕获
“DOM2 级事件” 中规定 的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段
事件是用户或者浏览器自身执行的某种动作,而响应这个动作的函数就叫做事件处理程序
定义了两个方法:addEventListener() 以及 removeEventListener(),都接收三个参数:要处理的事件名,事件处理函数以及布尔值
通过 addEventListener() 添加的事件处理程序只能通过 removeEventListener() 来移除
定义了两个方法:attachEvent() 以及 detachEvent(),都接收两个参数:事件处理程序名及处理函数,事件会添加到冒泡阶段
和 DOM0 级处理程序的差异:DOM0 级中事件处理程序在元素的作用域作用域中运行,而 IE 则在全局作用域,因此 this 值不同
有多个处理程序时,后添加的先执行
触发 DOM 上的事件时会产生一个事件对象 event,这个对像包含所有与事件相关的信息
将所有的情况封装到一个函数中,根据不同的情况判断后执行(其实主要是 IE 和其他浏览器)
后面的内容之后再来复习
Ajax 指的是 Asynchronous JavaScript XML,技术的核心是 XMLHttpRequest 对象(简称 XHR)
常用于向服务器查询某些信息
查询字符串的参数名和值都需要使用 encodeURIComponent() 编码后才能放在 URL 末尾
向服务器发送应该被保存的数据
模仿表单提交来发送数据
并非所有浏览器都完整的实现了 2 级规范,但所有的浏览器都实现了它规定的部分内容
FormData 为序列化表单及创建与表单格式相同的数据(用于通过 XHR 传输)提供了便利
方便之处体现在不需要明确的在 XHR 对象上设置请求头部
IE8 是唯一支持 timeout 属性的浏览器
6个进度事件:loadstart, progress, error, abort, load, loadend。目前大部分支持前面五个事件,目前还没有浏览器支持 loadend 事件。
三个属性:lengthComputable, position, totalSize
可以根据这个属性为用户创建一个进度指示器
CORS(Cross-Origin Resource Sharing) 跨域源资源共享
基本思想:使用自定义的 http 头部让浏览器与服务器进行沟通,从而决定请求或者响应是否成功
在 IE8 中引入了 XDR(XDomainRequest) 类型,与 XHR 类似,但能实现安全可靠的跨域通信。
所有 XDR 请求都是异步执行的,不能用它来创建同步请求
其他浏览器都通过 XMLHttpRequest 对象实现了对 CORS 的原生支持
withCredentials 属性
条件判断浏览器,主要区别在于 XDR 和 XHR
图像 Ping 是与服务器进行简单、单向的跨域通信的一种方式
常用于跟踪用户点击页面或动态广告曝光次数。缺点有二:其一是只能发送 GET 请求,其二是无法访问服务器的响应文本。
图像 Ping 只能用于浏览器与服务器间的单向通讯
JSONP 是 JSON with padding 的简写,由回调函数及数据组成。
极为流行,因为简单易用。与图像 Ping 相比,优点在于可以直接访问响应文本,而且支持浏览器和服务器之间双通信。两点不足:从其他域加载涉及到安全问题,要确定请求失败并不容易。
Comet 是一种服务器向页面推送数据的技术。(由 Alex Russell 发明)
两种实现 Comet 方式:长轮询和流
长轮询是传统轮询(也称为短轮询)的翻版:页面发起一个服务器请求,然后服务器一直保持打开,直到有数据可发送
HTTP 流:浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性的向服务器发送数据
SSE(服务器发送事件):围绕只读 Comet 交互推出的 API 或者模式。支持短轮询、长轮询和 HTTP 流,而且在断开时能够自动确定何时重新连接。
目标是在一个单独的持久连接上提供双工、双向通信
好处:能够在客户端和服务器之间发送非常少的数据,适合移动应用
坏处:制定协议的时间比制定 JavaScript API 的时间还要长,而且可能存在一致性和安全性的问题
考虑两点:其一是否有自由度建立和维护 Web Sockets 服务器;其二是否需要双向通信
CSRF(Cross-Site Reqeust Forgery):跨站点请求伪造
利用 Object.prototype.toString.call(name) 对变量名为 name 的对象进行检测,返回的结果形式 [object NativeConstructorName] 就显示了其构造函数名。注意这种方法不能检测非原生构造函数的构造函数名。
惰性载入表示函数执行的分支仅会发生一次。
实现方式:其一是在函数被调用时再处理函数,其二是在声明时就指定适当的函数
主要用于事件处理程序及定时器中
基本方法:使用闭包返回一个函数
一旦把对象定义为防篡改,就无法撤销了。
Object.preventExtensions() 以及 Object.isExtensible()
不可扩展,但已有成员可以修改和删除
密封对象不可扩展而且已有成员不能被删除
通过 Object.seal() 来密封,也可以通过 Object.isSealed() 来检测是否被密封
最严格的防篡改级别是冻结对象,既不可扩展,又是密封的,而且数据属性的 [[Writtable]] 特性会被设置为 false。如果定义 [[Set]] 函数,访问器属性仍然是可写的。
Object.freeze()
关于定时器要记住的最重要的事情是:指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时实际执行代码。
主要的问题是某些间隔可能会被跳过,而且多个定时器的代码执行之间的间隔可能会比预期小。解决办法是链式调用延时定时器。
###
]]>