首页

前英语流利说设计总监:从民房到纳斯达克的理想主义设计路

雪涛

可能对于一些人来说,流利说是一份工作,而对于我来说,流利说却是一段深刻的旅程,改变了我的生活,也塑造了我的性情、人格。从 2013 年作为第 7 号员工加入流利说,为之效力6年,从最早期民房创业,到纳斯达克 IPO,这段经历,我自认为颇具一个理想主义者的传奇色彩,有些故事,是我愿意,也值得分享的。

老王(流利说 CEO 王翌)以前总借他恩师的话说,人这一辈子能做一两件漂亮事就不错了。我觉得,流利说,算我过去做的一件漂亮事。以前,我也常和同事说,如果你现在一切的经历,在日后不足以用故事说与人听,可能你现在经历的还不够痛,你还不够刻苦。所幸,过去的经历,给我留下了几个故事。

这一次,我想分享当初选择加入流利说的故事,分享这个过程中我的思考、行动。一方面,单纯的想记录这段故事。因为随着年岁增加,记性却是退减的。文字是最好的保存记忆的方式;另一方面,常常遇到设计师朋友们聊如何选择工作机会,遇到创业的邀约机会怎么判断、决策的问题,我希望这个故事,能给遇到此类问题的朋友一些启发。

好,听故事吧。

缘起

2012 年,我退出了联合创立一年半的公司。而在一年半以前,我从阿里云公司离开,放弃了一笔可观的 RSU 股票,当时,我是阿里云最早的 28 位设计师之一。结束创业后,2012 年 11 月 14 日 19:17,我发了一条微博,表示想看看新的机会。这条微博,有 16 个转发。其中有 1 个转发,被流利说联合创始人 Ben 看到,他把这条微博转发给了联合创始人王翌(流利说 CEO)。

在此之前,我和 Ben、王翌素不相识,网友都谈不上。Ben 之所以看到那条转发,是因为他关注好友里面,有一位正是我一款浏览器插件产品 – 微博急简 的用户。

有一颗创作的心 – 微博急简

△ 使用「微博急简」前后,微博主页的对比

容我多说几句,介绍一下微博急简这款产品。2011 年左右,新浪微博的使用体验,非常糟糕,逐步商业化带来的各种广告,让原本不好的体验,变得更加让我无法容忍。很快,一个叫 stylish 的 Chrome 插件工具,在微博设计/技术圈子里流传。用户安装 stylish 插件后,通过修改 CSS 来定制自己的微博体验。同时,你还可以把 CSS 分享给其他用户。

这太好玩了!我按自己的使用喜好,给自己订制了一套极简体验的新浪微博。玩了几天后,我决定做一个改善微博体验的浏览器插件。为什么要重新做一个插件呢?我认为,stylish 的使用门槛、操作成本都太高,仅仅是专业人士的玩具。我希望普通人,也可以通过一键安装浏览器插件,获得舒服的微博体验。因此,我给插件取名 – 微博急简。看得出来,我是多么急迫的想简化微博的体验。

两周后,微博急简就上线了。几个版本后,体验就趋于稳定。高峰的时候,有 10 万左右的用户使用,相关微博话题有 600 万。有很多行业内的大咖成为我的用户,像冯大辉、少楠、方军等。获得了很多用户的好评,最让我嘚瑟的是现任丁香园产品总监少楠的评价:用一个插件秒杀了新浪UED团队。

通过微博急简这个产品,我想分享以下几点:

  • 设计师应该对体验保持敏感度。不要被体验糟糕的产品破坏了味蕾;
  • 如果有机会,就要试图去改变它;
  • 出于兴趣,单纯的为自己做设计、创作。即使没有金钱回报;

这三点,既是我的观点,也是我的特质。正是因为这样的特质,才遇到后面加入流利说的契机。

面对首次邀约

回到故事主线。

我发完那条微博后的 1 小时,王翌就给了我微博私信。老实说,今天是我第一次注意到这个「相隔 1 小时」的时间细节。王翌的行动力、执行力,对于人才的执着追求,着实让我佩服。这不仅仅是对我,对流利说早期的员工,以及后面的核心员工,都是如此。

他的消息里,有三个关键词:exciting、团队成立 2 个月、移动教育。对于这条消息,我礼貌的回应,但内心其实是「呵呵」的。即使,我看到他 LinkedIn 主页上 Google PM 的经历,很亮眼。呵呵的原因是,这个公司太早期了,才两个月。2013 年,移动教育是啥东西?而且,创始人还这么不务实,动不动就标榜是一个 exciting 的机会。

虽然,王翌后续一直联系我,但我基本是忽略的状态。期间,我短暂的加入了一个朋友的创业公司。2013 年 1 月底,春节前的几天,我再次收到王翌的私信:流利说 App 即将上架,想约我再聊一聊。于是,我们约在文二西路白鸦(有赞创始人)的贝塔咖啡。

△ 贝塔咖啡馆

那天晚上,王翌给我展示了流利说最早的 App,程序其实还不太稳定。但他仍旧极具信心的表达了对于语音互动的看好,以及发出盛情邀请。我对这个 App 的产品与交互的第一印象,是好的,但对于王翌的第一印象却是复杂的,既觉得这人有激情、有想法,同时又觉得不太靠得住,夸夸其谈,他太会说了。

如何设计深色模式?这3点因素需要考虑

雪涛

深色模式该从何处着手设计又要考虑哪些因素?一起来看看~

其实回顾我们常用的APP,有很多都更新了深色模式,而且每个APP对深色的定义和设计都有差异。

实际上深色模式已经来临,而且在很多产品中都能发现它的身影,之后也会愈加流行。那么设计师面对深色模式,该从何处着手设计又要考虑哪些因素呢?

本文就为大家提供一份全面的总结。文章目录如下:

选择深色模式的原因

1. 需求趋势

过去一年以来,Android 10和iOS 13上都适配了深色模式,而且Apple和Google也一直致力于将资源和注意力投入到深色模式中,这也让深色模式备受用户的关注。

2. 专注内容

深色模式在弱光环境下具有更好的可读性,让我们更专注于眼前的屏幕。同时深色的背景会降低内容周围元素的影响,特别是以图片和视频为主的应用,让用户更专注于内容。

作为内容消费型应用的Netflix ,把深色背景作为默认设计样式,深色的设计让用户更能集中注意力,延长使用时间。

3. 减轻刺激

相对于其他颜色,深色系的设计在夜晚看着最舒服。可能晚上玩手机不用担心光线太刺眼,但是深色模式对护眼并没有什么帮助,只能说可以减少对眼睛的刺激。

4. 提高续航

深色模式更省电只适用于OLED屏幕。OLED面板的每个像素点可以单独发光,当使用深色模式时,部分像素点被熄灭,只点亮部分像素,屏幕的一部分相当于处在休眠状态,所以会更加省电。

平台设计指南

1. iOS平台深色模式设计

在深色模式下,Apple重新审视了iOS中UI样式和颜色的含义,让我们来看看在iOS上设计深色模式带来的变化。

语义化颜色(Semantic Colors)

所谓语义化颜色,就是不再通过某一固定的RGB色值来描述颜色,而是根据用途来描述,让界面元素可以自动适配当前的外观模式。

淘宝团队就参考了苹果官方的适配建议,通过语义化颜色的方式进行适配,使适配成本大幅降低。设计师根据不同UI元素的特性先期制定颜色语义化规则,进而技术在框架层面通过「颜色自动反转」技术实现颜色反转。

系统颜色

除了语义化颜色,Apple还提供了9种预定义的系统颜色,在浅色和深色模式中,这些颜色会动态变化,支持整个系统的外观,同样也可以自适应选定的界面样式。

模糊与动态效果

在iOS13上,苹果引入了4种模糊效果和8种动态效果,它们自动适应iOS界面样式。这是在浅色和深色模式下不同的模糊效果。

苹果还在iOS深色模式排版套件中引入4种动态效果,其中3种为叠加效果,1种分隔效果。

2. Android平台深色模式设计

谷歌提供了广泛的文档支持,帮助设计师了解深色主题如何在Android生态系统中运行。

Elevation(阴影)

UI界面元素间的投影最能让用户清晰地感知用户界面的深度。在设计深色主题时,组件将保留与浅色主题相同的默认阴影组件。Elevation越靠上, 颜色就会越浅。

无障碍性与对比

深色UI设计中的背景应足够暗以显示白色文本。设计师要注意背景和文字之间至少使用15.8:1的对比度。这样可以确保将正文放在最前面时,能通过WCAG(Web内容无障碍指南,使网站内容更容易访问)的AA标准。

颜色

深色模式必须避免饱和的颜色,以免引起眼睛疲劳。相反,设计师应专注于使用不饱和的颜色,以增加清晰度。主色和辅色的选择还取决于对浅色和深色UI主题的考虑。

文字不透明度

在深色背景上设计浅色文字时,高度强调的文字不透明度为87%;一般提示文字的不透明度为60%;禁用文字的不透明度为38%。

深色模式的设计要点

苹果和谷歌都利用各自的设计原则,为深色模式设计做准备工作。在实际设计过程中,不单需要这些基本原则,更重要的是要注意设计深色模式的实用要点。

1. 背景灰度

设计深色背景时不是简单的把白变成黑,而是对背景使用比较暗的色调,以减少眼睛疲劳。

在浅色模式中,我们倾向于用细微的阴影来传达界面深度,使用起来更加自然。但是在大多数深色模式下,阴影的效果并不明显,通常用颜色的深浅来传达界面的层级关系。

关键点:注意应用场景

在知乎的深色模式中,背景的设计从深到浅使用了三级灰度,让页面的层级更分明。

一级灰度的应用场景主要是大的背景色,使用面积相对比较大颜色最深;二级灰度的应用场景是选项的背景色,根据选项的数量设置使用面积,位置排布比较灵活;三级灰度的颜色最浅,使用面积最小,通常用在分割线中。

2. 文字对比

白底黑字和黑底白字带给我们的用眼体验是不一样的。设计不当的深色模式常常因为强对比而变得很刺眼,同时为了提高对光线的吸收虹膜会张得更开,更容易造成眼部疲劳。

关键点:文字间的对比

深色模式中,文字的用色通常是纯灰色,不同位置的文字例如标题、正文和注释使用深浅不同的颜色作对比。上图是深色的微信,就利用这种方法来区分不同文字内容,展示文字层次关系。

另外每个应用的定位都不一样,界面中想传达的信息也有差异,所以要注意不同的设计思路。

关键点:文字与背景的对比

已经更新深色模式的应用主要分为两大类,一类属于工具型应用例如QQ、微信、百度网盘等,这类应用追求的是信息的有效传达,在设计时文字内容和背景色的区分比较明显。

上图是百度网盘的深色模式,可以看出来标题文字与背景有很明显的对比,保障了用户使用时的可操作性和易读性。

这样的设计不需要用户过于沉浸式的阅读,只需要帮助用户快速找到有用的信息并方便使用,这是工具型应用在设计深色模式时必备的原则。

另一类属于内容型应用例如知乎、简书等,这些应用更注重沉浸式的阅读体验,因为用户通常会在某个界面中停留很久来查看内容,所以需要文字与背景的低对比度为阅读营造柔和的氛围。

简书的深色模式中,文字与背景的对比关系就设计得很弱,整个界面呈现出灰色调,这样的设计有助于在弱光环境下的长时间阅读和浏览。

3. 图标/按钮

深色模式应该避免使用特别鲜艳的颜色,较高的明度和饱和度会与深色背景形成强烈的对比,让页面的可读性变差并加深刺激。

关键点:降低色彩明度

在由浅变深的过程中,知乎对改变了界面中所有图标的颜色。界面里面的图标和主题按钮的色彩,在色相、饱和度上都没有变化,但是明度被不同程度的降低,保证了在不同光照条件下的内容的可读性。

这是深色模式中处理色彩的一种方法,虽然在浅色界面中,我们更喜欢鲜艳的颜色,但明度低的颜色更适合深色主题。匹配这两个模式另一个比较好的方法是创建互补的色板。

结论

无论深色或者浅色,都只是产品向用户呈现的一种界面状态,最终的目的是为了更良好的使用体验。

不管选择什么样的模式,都要记得从产品自身出发,并牢记这几点:

  • 了解趋势:熟悉深色模式流行起来的原因,以及苹果和谷歌对此的相关研究。
  • 找对方向:在准备设计深色模式前,要先了解清楚产品对应的平台、满足的标准。

  • 掌握要点:设计深色模式更多要求的是颜色上的变化,利用灰度色阶拉开背景颜色,把握文字与背景间的强弱关系,适当降低图标的明度和饱和度。

文章来源:优设    作者:Clip设计夹

设计手记:给设计师参考会不会限制设计师的思维呢?

蓝蓝

蓝蓝设计 魏华写

     在承担ui设计项目中,常常碰到一些客户不给设计师看己往的软件或本公司设计师设计但没有被客户认可的设计方案,怕限制设计师的思路。那究竟给设计师参考会不会限制设计师的思维呢?


     实际上不会的,这是更多维度了解客户的思考和角度,排除己被尝试过的选项。


     在进行一个ui 设计前,深度方面应该要了解业务,用户角色,环境生态,交互流程,关键功能,核心价值,用户体验的峰值点和难点在哪里…….总之了解的越深越好。


     宽度方面,要看到竞品分析,行业趋势,国内外优秀案例欣赏,专业文章观点,应该是可以拓展设计师的思维。


     设计最核心的目标应该在于解决问题,而不是单纯的让界面好看。


      优秀的设计师要明确解决问题的目标,博采众长,独立思考,看众多的竞品是看众多的解题思路,多方位角度看问题。各产品资源,核心技术不同,取舍不同,理解信息架框,运营思路,用户特征,技术实现可能性,不会只照着您给的资料比猫画虎。


       因此,我认为,放心的给设计师参考资料吧,互相的了解,沟通越多,越容易出好的作品。



这五张图是最近蓝蓝设计的稿件,展现从一个模糊的概念性需求到一个可视化概念性方案的设计过程。

    点击查看原图

点击查看原图


点击查看原图

2看文章.png点击查看原图

循环设计,用户体验如何呼唤时代变革

分享达人

关于循环设计,可持续发展是商业领域非常关注的话题,作为UX需提前转变思维,给企业带来更多价值,一线大厂已在运用这种思维


本文共 3589 字,预计阅读 10 分钟

译者推荐|本文从“可持续”和“设计”的两点谈起,来论述从线性经济向可持续经济的转变,以及“可持续设计”的四个主要阶段:理解、定义、制造、发布。

“循环设计”不是为了追求时髦或者抬升设计地位,而是将这个已经日益庸俗化的“设计”冠为自己的定语,是设计本身发展所趋,以及可持续发展所需,设计界需要对自己的责任有所承担,形成一个全局观、系统性看待设计问题的方式。让回收利用和可持续发展成为一种规范,从而做到一劳永逸。

我们生活在一个呼唤变革的世界。在过去的50年中,现代社会所依赖的漫不经心和无休止的消费是不可持续的。我们从小就不关心自己的事情。如果有什么东西坏了,我们也就不修了。产品被设计成用完直接丢弃,而不是去修复。数字产品也不例外。然而,为了解决这些问题,出现了一种新的思维方式:循环设计(可持续设计)①。(益达说:其实这个理念和风格已经存在了很长的时间,大多应用在不为大众所知的能源、材料再生流程之中,然而随着时代的发展,循环设计可以变得更加普世。)

①注:循环设计是20世纪80-90年代产生的一种设计风格,他又称回收设计,是指实现广义收回和利用的方法,即在进行产品设计时,充分考虑产品零部件及材料回收的可能性,回收价值的大致方法,回收处理结构工艺性等于与回收有关的一系列问题,以达到零部件及材料资源和能源的再利用。它旨在通过设计来节约能源和材料,减少对环境的污染,使人类的设计物能多次反复利用,形成产品设计和使用的良性循环。

那么,循环设计方法意味着什么?在数字产品上要如何使用?在回答这些问题之前,首先,我们需要仔细观察我们是如何构建我们的世界,为什么这个世界已经不可持续了,并且要理解环保世界的需求是如何改变我们的思维方式,促使我们渴望从线性设计模型转变为循环设计模型。


向循环转变


我们的经济主要基于“按需配置”流程之上。在此线性系统中,我们构建了会在一段时间后淘汰的产品,并且将其组件视为垃圾。与此相反,循环设计方法将产品的生命周期视为一个闭环,其中资源不断地被重新利用。


在“经典”线性模型中,产品经历了生产、消费和破坏的各个阶段,最终以浪费告终。在设计一款循环产品过程中,我们使用的方法包含四大阶段,这四个阶段形成了一个闭环,并形成了一个恒定的循环,在这个循环中,不仅仅只有闪闪发亮的、新的,未使用过的材料才被受欢迎。

 

循环设计方法的四个阶段是:

理解 / 定义 / 制造 / 发布



当我们同时看线性设计和循环设计模型方法时,有一点吸引人的是,开始设计东西的时候,方法的差异。从只是生产某种东西,到对我们将要生产的产品做出深思熟虑的决定,以及在实施过程中付出的努力和关心,这是一个大转变。


看看我们现在的立场


为什么做出这种转变如此的重要?我确信每个看新闻的人都听说过气候变化。NASA 致力于解决环境问题,因此我们都可以非常详细地了解人类行为和无限增长对我们生态系统的影响。


但好消息是我们不必继续这样做,因为我们可以很容易从数字世界中“产生”方式中学习事物的产生。电力废弃物已成为现代世界的主要废弃物来源之一。大量的手机和电脑被扔掉,随之经济是建立在每年都有新东西的基础上的。


当您的手机屏幕意外碎裂时,我们该怎么办?我们知道如何处理它吗?我们知道如何修理吗?我们并不知道……但是幸运的是,有些设计师对此问题提出了解决方案。Fairphone② 是一种合乎情理,模块化的智能手机,其组件数量很少,可以轻松更换和回收。大公司也应朝这个方向迈出一步,让回收利用和可持续发展成为一种时尚和规范,一劳永逸。

② Fairphone:这家公司生产的手机希望实现全球手机供应链的公平贸易,具体而言就是不使用“冲突矿物”并且确保生产手机的工人没有被奴役和压榨,目前仍然坚持在非洲贫困和战乱的国家进口材料,已经在刚果和卢旺达境内找到了一些矿山,用更好的商业实践推动当地经济更健康地发展。


设计和设计师的重要性


设计师,比任何其他专业人士,都更有可能在一转变中产生巨大的影响的人。我还敢说,我们有责任采用可持续设计的方式行动和思考。因为是我们创造了那些最终出现在传送带上的东西。我们也有责任教育我们的用户。幸运的是,越来越多的人重视具有可持续发展目标的产品或品牌,或者重视起在产品背后有意义的故事。同样,可持续发展不仅成为流行语,而且成为一种价值观,被越来越多的人意识到基于有限资源的无限增长是无法实现的目标。但是,要从线性经济向可持续经济转变,我们需要学习不同的思维方式。幸运的是,智能设备和数字产品的时代带来了一种复杂的设计思维方法,可以作为物理世界中生产链的范例。


用户体验必须提供什么


地球上有一个地方,您不能随便丢东西:互联网。这是一个对已有产品进行再构思的地方,您只能去完善它,不能丢弃它,因为如果您一夜之间说:“我不喜欢我的网站,明天我将推出一个全新的网站”,那您便会失去用户。

如果我们看一下可持续发展设计方法的四个主要阶段,就会发现我们在用户体验设计中使用的方法与此很相似。

让我们再次看一下四个阶段,然后将其更详细地分解:

理解

当我们谈论与循环设计相关的理解时,我们谈论的是在开始设计一个未来的产品之前就了解它的用户和环境。研究一直是数字产品设计的基础。与数字产品的连接比与实体产品的连接要更多的涉及到人类的心理。因此不可避免地要开发出新的研究方法,以帮助我们洞察用户在使用某种产品时的想法、感受和行为。但这不仅与用户有关, 研究还必须深入到经济领域,并研究未来产品的组成部分,同时牢记它们必须可被再次利用。


定义

在此阶段,将定义(商业)目标,并构建一个商业模型画布作为生产过程的计划。用户体验使用这种方法已有一段时间了,让涉众参与其中,并在设计过程中更多地激活它们。为我们设计的产品设定一个目标是至关重要的,因为有了它,我们可以为用户创造额外的价值。因此,无论是制作商业模型画布还是举办精彩的价值主张研讨会,在生产方式中实施这些方法都会对当前的生产流程产生巨大的影响。


制造

这是关键部分。现在我们正在做的事情就好像没有明天一样。随着每种无法回收的产品的出现,我们产生的废料越来越多。但是循环方法是为产品创建一个原型,并定义将需要使用那些材料反映在产品原型上,并在定义阶段概述的商业模型上定义材料。原型设计和构思是用户体验设计过程中的关键要素,这也是为什么需要制作原型。


发布 

根据循环设计模型,随着产品的发布,生产周期进入了第四阶段,然同时理解阶段又重新开始了。对于数字产品来说,这是自然发生的事前:你发布一个产品,基于该版本收集反馈,然后构思它,周而复始,这个循环再次产生。


但是,观察这个循环并建立这些连接仅仅是冰山一角。在数字时代发展起来的设计思维给世界带来了许多反思。


变革中的大佬


幸运的是,已经有许多大品牌意识到转变的必要性,并采取和提出了数字设计思维方法来支持转变,并建立循环设计的时代。根据《循环设计指南》,“我们应该把我们设计的所有东西都看作软件产品和服务——这些产品和服务可以基于我们通过反馈得到的数据而不断的发。”


用户体验研究和用户体验设计一直是在做的一件事是:基于全面的研究和真实的用户需求来构建产品。上面的设计指南是非常复杂的工具,具有许多可能的方法。它强调了从产品到服务流程转变的重要性,并展示如何使用敏捷流程并将其实施到构建产品的方法之中。


IDEO(全球顶尖的设计咨询公司)与 Ellen Macarthur Foundation(艾伦·麦克阿瑟基金会)合作,试图“试图通过设计构建一个具有恢复性和再生性的经济框架”。在这里,您可以找到几乎每个生产方面和领域——例如食品、时装、经济和设计——并在每个领域中提出解决方案,以打破线性生产系统。


耐克还宣布了其基于循环设计模型生产高品质运动鞋的新方法原则。正如您已经看到的那样,无论您身处哪个经济领域,都可以为循环生产过程的蓬勃发展做贡献,并成为一支主导力量。


重要的结论


我认为,作为设计师,我们始终要为变革而努力,并始终努力与客户、产品或服务保持紧密的关系,并通过构思使其不断完善,以实现这一目标。这是因为伟大的事情只有通过时间和不断的反思才能实现。在离线世界中,数字设计过程也有很多东西可以贡献。希望通过教育,能有更多的大公司意识到用户真正想要的产品是具有更多功能并可持续使用的,而不仅仅是将它们当作一次性产品,一旦它们不像最初那样光鲜就把她扔掉。

转自:站酷-大猴儿er 


六个好用的程序员开发在线工具

seo达人

网上可以找到前端开发社区贡献的大量工具,这篇文章列出了我最喜欢的一些工具,这些工具给我的工作带来了许多便利。


1. EnjoyCSS


老实说,虽然我做过许多前端开发,但我并不擅长 CSS。当我陷入困境时,EnjoyCSS 是我的大救星。EnjoyCSS 提供了一个简单的交互界面,帮助我设计元素,然后自动输出相应的 CSS 代码。




EnjoyCSS 可以输出 CSS、LESS、SCSS 代码,并支持指定需要支持哪些浏览器及其版本。开发简单页面时用起来比较方便,但不太适合复杂一点的前端项目(这类项目往往需要引入 CSS 框架)。

2. Prettier Playground


Prettier 是一个代码格式化工具,支持格式化 JavaScript 代码(包括 ES2017、JSX、Angular、Vue、Flow、TypeScript 等)。Prettier 会移除代码原本的样式,替换为遵循最佳实践的标准化、一致的样式。IDE 大多支持 Prettier 工具,不过 Prettier 也有在线版本,让你可以在浏览器里格式化代码。




如果工作电脑不在手边,使用移动端设备或者临时借用别人的电脑查看代码时,Prettier Playground 非常好用。相比在 IDE 或编辑器下使用 Prettier,个人更推荐通过 git pre-commit hook 配置 Prettier:hook 可以保证整个团队使用统一的配置,免去各自分别配置 IDE 或编辑器的麻烦。如果是老项目,hook 还可以设置只格式化有改动的单个文件甚至有改动的代码段,避免在 IDE 或编辑器下使用 Prettier 时不小心格式了大量代码,淹没了 commit 的主要改动,让 review 代码变得十分痛苦。

3. Postman


Postman 一直在我的开发工具箱里,测试后端 API 接口时非常好用。GET、POST、DELETE、OPTIONS、PUT 这些方法都支持。毫无疑问,你应该使用这个工具。




Postman 之外,Insomnia 也是很流行的 REST API 测试工具,亮点是支持 GraphQL。不过 Postman 从 去年夏天发布的 v7.2 起也支持了 GraphQL。

4. StackBlitz


Chidume Nnamdi 盛赞这是每个用户最喜欢的在线 IDE。StackBlitz 将大家最喜欢、最常用的 IDE Visual Studio Code 搬进了浏览器。


StackBlitz 支持一键配置 Angular、React、Ionic、TypeScript、RxJS、Svelte 等 JavaScript 框架,也就是说,只需几秒你就可以开始写代码了。


我觉得这个在线 IDE 很有用,特别是可以在线尝试一些样例代码或者库,否则仅仅尝试一些新特性就需要花很多时间在新项目初始化配置上。有了 StackBlitz,无需在本地从头搭建环境,花上几分钟就可以试用一个 NPM 包。很棒,不是吗?




微软官方其实也提供了在线版本的 VSCode,可以在浏览器内使用 VSCode,并且支持开发 Node.js 项目(基于 Azure)。不过 StackBlitz 更专注于优化前端开发体验,界面更加直观一点,也推出了 beta 版本的 Node.js 支持(基于 GCP,需要填表申请)。

5. Bit.dev


软件开发的基本原则之一就是代码复用。代码复用减少了开发量,让你不用从头开发组件。


这正是 Bit.dev 做的事,分享可重用的组件和片段,降低开发量,加速开发进程。


除了公开分享,它还支持在团队分享,让团队协作更方便。


正如 Bit.dev 的口号「组件即设计体系。协同开发更好的组件。」所言,Bit.dev 可以用来创建设计体系,允许团队内的开发者和设计师一起协作,从头搭建一套设计体系。


Bit.dev 目前支持 React、Vue、Angular、Node 及其他 JavaScript 框架。




在 Bit.dev 上不仅可以搜索组件,还可以直接查看组件的依赖,浏览组件的代码,甚至在线编辑代码并查看预览效果!选好组件后可以通过 Bit.dev 的命令行工具 bit 在本地项目引入组件,也可以通过 npm、yarn 引入组件。

6. CanIUse


CanIUse是非常好用的在线工具,可以方便地查看各大浏览器对某个特性的支持程度。


我过去经常碰到自己开发的应用的一些功能在其他浏览器下不支持的情况。比如我的作品集项目使用的某个特性在 Safari 下不支持,直到项目上线几个月后我才意识到。这些经验教训让我意识到需要检查浏览器兼容性。


我们来看一个例子吧。哪些浏览器支持 WebP 图像格式?




如你所见,Safari 和 IE 目前不支持 WebP。这意味着需要为不兼容的浏览器提供回退选项,比如:


<picture>

CanIUse 还可以在命令行下使用,例如,在命令行下查看 WebP 图像格式的浏览器兼容性:caniuse webp(运行命令前需要事先通过 npm install -g caniuse-cmd安装命令行工具。


10 个超有用的 JavaScript 技巧

seo达人

方法参数的验证

JavaScript 允许你设置参数的默认值。通过这种方法,可以通过一个巧妙的技巧来验证你的方法参数。


const isRequired = () => { throw new Error('param is required'); };

const print = (num = isRequired()) => { console.log(`printing ${num}`) };

print(2);//printing 2

print()// error

print(null)//printing null

非常整洁,不是吗?


格式化 json 代码

你可能对 JSON.stringify 非常熟悉。但是你是否知道可以用 stringify 进行格式化输出?实际上这很简单。


stringify 方法需要三个输入。 value,replacer 和 space。后两个是可选参数。这就是为什么我们以前没有注意过它们。要对 json 进行缩进,必须使用 space 参数。


console.log(JSON.stringify({name:"John",Age:23},null,'\t'));

>>>

{

"name": "John",

"Age": 23

}

从数组中获取唯一值

要从数组中获取唯一值,我们需要使用 filter 方法来过滤出重复值。但是有了新的 Set 对象,事情就变得非常顺利和容易了。


let uniqueArray = [...new Set([1, 2, 3, 3, 3, "school", "school", 'ball', false, false, true, true])];

>>> [1, 2, 3, "school", "ball", false, true]

从数组中删除虚值(Falsy Value)

在某些情况下,你可能想从数组中删除虚值。虚值是 JavaScript 的 Boolean 上下文中被认定为为 false 的值。 JavaScript 中只有六个虚值,它们是:


undefined

null

NaN

0

"" (空字符串)

false

滤除这些虚值的最简单方法是使用以下函数。


myArray.filter(Boolean);

如果要对数组进行一些修改,然后过滤新数组,可以尝试这样的操作。请记住,原始的 myArray 会保持不变。


myArray

   .map(item => {

       // Do your changes and return the new item

   })

   .filter(Boolean);

合并多个对象

假设我有几个需要合并的对象,那么这是我的首选方法。


const user = {

    name: 'John Ludwig',

    gender: 'Male'

};

const college = {

    primary: 'Mani Primary School',

    secondary: 'Lass Secondary School'

};

const skills = {

   programming: 'Extreme',

   swimming: 'Average',

   sleeping: 'Pro'

};

const summary = {...user, ...college, ...skills};

这三个点在 JavaScript 中也称为展开运算符。你可以在这里学习更多用法。


对数字数组进行排序

JavaScript 数组有内置的 sort 方法。默认情况下 sort 方法把数组元素转换为字符串,并对其进行字典排序。在对数字数组进行排序时,这有可能会导致一些问题。所以下面是解决这类问题的简单解决方案。


[0,10,4,9,123,54,1].sort((a,b) => a-b);

>>> [0, 1, 4, 9, 10, 54, 123]

这里提供了一个将数字数组中的两个元素与 sort 方法进行比较的函数。这个函数可帮助我们接收正确的输出。


Disable Right Click

禁用右键

你可能想要阻止用户在你的网页上单击鼠标右键。


<body oncontextmenu="return false">

   <div></div>

</body>

这段简单的代码将为你的用户禁用右键单击。


使用别名进行解构

解构赋值语法是一种 JavaScript 表达式,可以将数组中的值或对象的值或属性分配给变量。解构赋值能让我们用更简短的语法进行多个变量的赋值。


const object = { number: 10 };


// Grabbing number

const { number } = object;


// Grabbing number and renaming it as otherNumber

const { number: otherNumber } = object;

console.log(otherNumber); //10

获取数组中的最后一项

可以通过对 splice 方法的参数传入负整数,来数获取组末尾的元素。


let array = [0, 1, 2, 3, 4, 5, 6, 7]

console.log(array.slice(-1));

>>>[7]

console.log(array.slice(-2));

>>>[6, 7]

console.log(array.slice(-3));

>>>[5, 6, 7]

等待 Promise 完成

在某些情况下,你可能会需要等待多个 promise 结束。可以用 Promise.all 来并行运行我们的 promise。


const PromiseArray = [

   Promise.resolve(100),

   Promise.reject(null),

   Promise.resolve("Data release"),

   Promise.reject(new Error('Something went wrong'))];


Promise.all(PromiseArray)

 .then(data => console.log('all resolved! here are the resolve values:', data))

 .catch(err => console.log('got rejected! reason:', err))

关于 Promise.all 的主要注意事项是,当一个 Promise 拒绝时,该方法将引发错误。这意味着你的代码不会等到你所有的 promise 都完成。


如果你想等到所有 promise 都完成后,无论它们被拒绝还是被解决,都可以使用 Promise.allSettled。此方法在 ES2020 的最终版本得到支持。


const PromiseArray = [

   Promise.resolve(100),

   Promise.reject(null),

   Promise.resolve("Data release"),

   Promise.reject(new Error('Something went wrong'))];


Promise.allSettled(PromiseArray).then(res =>{

console.log(res);

}).catch(err => console.log(err));


//[

//{status: "fulfilled", value: 100},

//{status: "rejected", reason: null},

//{status: "fulfilled", value: "Data release"},

//{status: "rejected", reason: Error: Something went wrong ...}

//]

即使某些 promise 被拒绝,Promise.allSettled 也会从你所有的 promise 中返回结果。

你所不知道的XML

前端达人

一、XML:

XML(Extensible Markup Language 可扩展标记语言),XML是一个以文本来描述数据的文档。

1. 示例:

<?xml version="1.0" encoding="UTF-8"?>
<people>
    <person personid="E01">
        <name>Tony</name>
        <address>10 Downing Street, London, UK</address>
        <tel>(061) 98765</tel>
        <fax>(061) 98765</fax>
        <email>tony@everywhere.com</email>
    </person>
    <person personid="E02">
        <name>Bill</name>
        <address>White House, USA</address>
        <tel>(001) 6400 98765</tel>
        <fax>(001) 6400 98765</fax>
        <email>bill@everywhere.com</email>
    </person>
</people>

2. 用途:

(1)充当显示数据(以XML充当显示层)

(2)存储数据(存储层)的功能

(3)以XML描述数据,并在联系服务器与系统的其余部分之间传递。(传输数据的一样格式)

从某种角度讲,XML是数据封装和消息传递技术。

3.解析XML:
3.1 :使用SAX解析XML

3.1.1 什么是SAX:

SAX是Simple API for XML的缩写
SAX 是读取和操作 XML 数据更快速、更轻量的方法。SAX 允许您在读取文档时处理它,从而不必等待整个文档被存储之后才采取操作。它不涉及 DOM 所必需的开销和概念跳跃。 SAX API是一个基于事件的API ,适用于处理数据流,即随着数据的流动而依次处理数据。SAX API 在其解析您的文档时发生一定事件的时候会通知您。在您对其响应时,您不作保存的数据将会被抛弃。

3.1.2 SAX解析XML方式:

SAX API中主要有四种处理事件的接口,它们分别是ContentHandler,DTDHandler, EntityResolver 和 ErrorHandler 。实际上只要继承DefaultHandler 类就可以,DefaultHandler实现了这四个事件处理器接口,然后提供了每个抽象方法的默认实现。
// 创建SAX解析器工厂对象
SAXParserFactory spf = SAXParserFactory.newInstance();
// 使用解析器工厂创建解析器实例
SAXParser saxParser = spf.newSAXParser();
// 创建SAX解析器要使用的事件侦听器对象
PersonHandler handler = 
                         new PersonHandler();
// 开始解析文件
saxParser.parse(
            new File(fileName), handler);


3.2. DOM解析XML:

DOM:Document Object Model(文档对象模型)
DOM的特性:
定义一组 Java 接口,基于对象,与语言和平台无关将 XML 文档表示为树,在内存中解析和存储 XML 文档,允许随机访问文档的不同部分。

DOM解析XML
DOM的优点,由于树在内存中是持久的,因此可以修改后更新。它还可以在任何时候在树中上下导航,API使用起来也较简单。 

DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
DocumentBuilder db = builder.newDocumentBuilder();
db.parse("person.xml");
NodeList node_person = doc.getElementsByTagName("person");

 3.3. JDOM解析XML:

JDOM是两位著名的 Java 开发人员兼作者,Brett Mclaughlin 和 Jason Hunter 的创作成果, 2000 年初在类似于Apache协议的许可下,JDOM作为一个开放源代码项目正式开始研发了。

JDOM 简化了与 XML 的交互并且比使用 DOM 实现更快,JDOM 与 DOM 主要有两方面不同。首先,JDOM 仅使用具体类而不使用接口。这在某些方面简化了 API,但是也限制了灵活性。第二,API 大量使用了 Collections 类,简化了那些已经熟悉这些类的 Java 开发者的使用。
 

解析步骤:
(1)SAXBuilder sax = new SAXBuilder();
(2)Document doc = sax.build(….);
(3)Element el = doc.getRootElement();(4)List list = el.getChildren();
(5)遍历内容


3.4. DOM4J解析XML:

dom4j是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。如今你可以看到越来越多的Java软件都在使用dom4j来读写XML,特别值得一提的是连Sun的JAXM也在用dom4j。这是必须使用的jar包, Hibernate用它来读写配置文件。
解析步骤:
(1)SAXReader sax = new SAXReader();
(2)Document doc = sax.read(Thread.currentThread().getContextClassLoader()
          .getResourceAsStream("person.xml"));
(3)Element root = doc.getRootElement();
(4)Iterator iterator = root.elementIterator();
(5)遍历迭代器


4.各种解析方法比较:
JDOM 和 DOM 在性能测试时表现不佳,在测试 10M 文档时内存溢出。
SAX表现较好,这要依赖于它特定的解析方式。一个 SAX 检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中。DOM4J是这场测试的获胜者,目前许多开源项目中大量采用 DOM4J,例如大名鼎鼎的 Hibernate 也用 DOM4J 来读取 XML 配置文件。
xstream 实现XML的转换


5.案例:

public class Person {
    private String personid;
    private String name;
    private String address;
    private String tel;
    private String fax;
    private String email;

    @Override
    public String toString() {
        return "Person{" +
                "personid='" + personid + '\'' +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", tel='" + tel + '\'' +
                ", fax='" + fax + '\'' +
                ", email='" + email + '\'' +
                '}';
    }

    public String getPersonid() {
        return personid;
    }

    public void setPersonid(String personid) {
        this.personid = personid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    public String getFax() {
        return fax;
    }

    public void setFax(String fax) {
        this.fax = fax;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}



<?xml version="1.0" encoding="UTF-8"?>
<people>
    <person personid="E01">
        <name>Tony Blair</name>
        <address>10 Downing Street, London, UK</address>
        <tel>(061) 98765</tel>
        <fax>(061) 98765</fax>
        <email>blair@everywhere.com</email>
    </person>
    <person personid="E02">
        <name>Bill Clinton</name>
        <address>White House, USA</address>
        <tel>(001) 6400 98765</tel>
        <fax>(001) 6400 98765</fax>
        <email>bill@everywhere.com</email>
    </person>
</people>


import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Hu Guanzhong
 * SAX解析的特点:
 * 1、基于事件驱动
 * 2、顺序读取,速度快
 * 3、不能任意读取节点(灵活性差)
 * 4、解析时占用的内存小
 * 5、SAX更适用于在性能要求更高的设备上使用(Android开发中)
 *
 */
public class PersonHandler extends DefaultHandler{
    private List<Person> persons = null;
    private Person p;//当前正在解析的person
    private String tag;//用于记录当前正在解析的标签名

    public List<Person> getPersons() {
        return persons;
    }

    //开始解析文档时调用
    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        persons = new ArrayList<>();
        System.out.println("开始解析文档...");
    }

    //在XML文档解析结束时调用
    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
        System.out.println("解析文档结束.");
    }

    /**
     * 解析开始元素时调用
     * @param uri 命名空间
     * @param localName 不带前缀的标签名
     * @param qName 带前缀的标签名
     * @param attributes 当前标签的属性集合
     * @throws SAXException
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        if ("person".equals(qName)){
            p = new Person();
            String personid = attributes.getValue("personid");
            p.setPersonid(personid);
        }
        tag = qName;
        System.out.println("startElement--"+qName);
    }

    //解析结束元素时调用
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if ("person".equals(qName)) {
            persons.add(p);
        }
        tag = null;
        System.out.println("endElement--"+qName);
    }

    //解析文本内容时调用
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        if (tag != null) {
            if ("name".equals(tag)) {
                p.setName(new String(ch,start,length));
            }else if("address".equals(tag)){
                p.setAddress(new String(ch,start,length));
            }else if("tel".equals(tag)){
                p.setTel(new String(ch,start,length));
            }else if("fax".equals(tag)){
                p.setFax(new String(ch,start,length));
            }else if("email".equals(tag)){
                p.setEmail(new String(ch,start,length));
            }
            System.out.println(ch);
        }
    }
}



public class XMLDemo {

    /**
     * 使用第三方xstream组件实现XML的解析与生成
     */
    @Test
    public void xStream(){
        Person p = new Person();
        p.setPersonid("1212");
        p.setAddress("北京");
        p.setEmail("vince@163.com");
        p.setFax("6768789798");
        p.setTel("13838389438");
        p.setName("38");

        XStream xStream = new XStream(new Xpp3Driver());
        xStream.alias("person",Person.class);
        xStream.useAttributeFor(Person.class,"personid");
        String xml = xStream.toXML(p);
        System.out.println(xml);

        //解析XML
        Person person = (Person)xStream.fromXML(xml);
        System.out.println(person);
    }

    /**
     * 从XML文件中读取对象
     */
    @Test
    public void xmlDecoder() throws FileNotFoundException {
        BufferedInputStream in = new BufferedInputStream(new FileInputStream("test.xml"));
        XMLDecoder decoder = new XMLDecoder(in);
        Person p = (Person)decoder.readObject();
        System.out.println(p);
    }
    /**
     * 把对象转成XML文件写入
     */
    @Test
    public void xmlEncoder() throws FileNotFoundException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.xml"));
        XMLEncoder xmlEncoder = new XMLEncoder(bos);
        Person p = new Person();
        p.setPersonid("1212");
        p.setAddress("北京");
        p.setEmail("vince@163.com");
        p.setFax("6768789798");
        p.setTel("13838389438");
        p.setName("38");
        xmlEncoder.writeObject(p);
        xmlEncoder.close();
    }

    /**
     * DOM4J解析XML
     * 基于树型结构,第三方组件
     * 解析速度快,效率更高,使用的JAVA中的迭代器实现数据读取,在WEB框架中使用较多(Hibernate)
     *
     */
    @Test
    public void dom4jParseXML() throws DocumentException {
        //1 创建DOM4J的解析器对象
        SAXReader reader = new SAXReader();
        InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("com/vince/xml/person.xml");
        org.dom4j.Document doc = reader.read(is);
        org.dom4j.Element rootElement = doc.getRootElement();
        Iterator<org.dom4j.Element> iterator = rootElement.elementIterator();
        ArrayList<Person> persons = new ArrayList<>();
        Person p = null;
        while(iterator.hasNext()){
            p = new Person();
            org.dom4j.Element e = iterator.next();
            p.setPersonid(e.attributeValue("personid"));
            Iterator<org.dom4j.Element> iterator1 = e.elementIterator();
            while(iterator1.hasNext()){
                org.dom4j.Element next = iterator1.next();
                String tag = next.getName();
                if("name".equals(tag)){
                    p.setName(next.getText());
                }else if("address".equals(tag)){
                    p.setAddress(next.getText());
                }else if("tel".equals(tag)){
                    p.setTel(next.getText());
                }else if("fax".equals(tag)){
                    p.setFax(next.getText());
                }else if("email".equals(tag)){
                    p.setEmail(next.getText());
                }
            }
            persons.add(p);
        }
        System.out.println("结果:");
        System.out.println(Arrays.toString(persons.toArray()));
    }

    /**
     * JDOM解析 XML
     * 1、与DOM类似基于树型结构,
     * 2、与DOM的区别:
     * (1)第三方开源的组件
     * (2)实现使用JAVA的Collection接口
     * (3)效率比DOM更快
     */
    @Test
    public void jdomParseXML() throws JDOMException, IOException {
        //创建JDOM解析器
        SAXBuilder builder = new SAXBuilder();
        InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("com/vince/xml/person.xml");
        org.jdom2.Document build = builder.build(is);
        Element rootElement = build.getRootElement();
        List<Person> list = new ArrayList<>();
        Person person = null;
        List<Element> children = rootElement.getChildren();
        for(Element element: children){
            person = new Person();
            String personid = element.getAttributeValue("personid");
            person.setPersonid(personid);
            List<Element> children1 = element.getChildren();
            for (Element e: children1){
                String tag = e.getName();
                if("name".equals(tag)){
                    person.setName(e.getText());
                }else if("address".equals(tag)){
                    person.setAddress(e.getText());
                }else if("tel".equals(tag)){
                    person.setTel(e.getText());
                }else if("fax".equals(tag)){
                    person.setFax(e.getText());
                }else if("email".equals(tag)){
                    person.setEmail(e.getText());
                }
            }
            list.add(person);
        }
        System.out.println("结果:");
        System.out.println(Arrays.toString(list.toArray()));
    }

    /**
     * DOM解析XML
     * 1、基于树型结构,通过解析器一次性把文档加载到内存中,所以会比较占用内存,可以随机访问
     * 更加灵活,更适合在WEB开发中使用
     */
    @Test
    public void domParseXML() throws ParserConfigurationException, IOException, SAXException {
        //1、创建一个DOM解析器工厂对象
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //2、通过工厂对象创建解析器对象
        DocumentBuilder documentBuilder = factory.newDocumentBuilder();
        //3、解析文档
        InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("com/vince/xml/person.xml");
        //此代码完成后,整个XML文档已经被加载到内存中,以树状形式存储
        Document doc = documentBuilder.parse(is);
        //4、从内存中读取数据

        //获取节点名称为person的所有节点,返回节点集合
        NodeList personNodeList = doc.getElementsByTagName("person");
        ArrayList<Person> persons = new ArrayList<>();
        Person p = null;
        //此循环会迭代两次
        for (int i=0;i<personNodeList.getLength();i++){
            Node personNode = personNodeList.item(i);
            p = new Person();
            //获取节点的属性值
            String personid = personNode.getAttributes().getNamedItem("personid").getNodeValue();
            p.setPersonid(personid);
            //获取当前节点的所有子节点
            NodeList childNodes = personNode.getChildNodes();
            for (int j = 0;j<childNodes.getLength();j++){
                Node item = childNodes.item(j);
                String nodeName = item.getNodeName();
                if ("name".equals(nodeName)) {
                    p.setName(item.getFirstChild().getNodeValue());
                }else if("address".equals(nodeName)){
                    p.setAddress(item.getFirstChild().getNodeValue());
                }else if("tel".equals(nodeName)){
                    p.setTel(item.getFirstChild().getNodeValue());
                }else if("fax".equals(nodeName)){
                    p.setFax(item.getFirstChild().getNodeValue());
                }else if("email".equals(nodeName)){
                    p.setEmail(item.getFirstChild().getNodeValue());
                }
            }
            persons.add(p);
        }
        System.out.println("结果:");
        System.out.println(Arrays.toString(persons.toArray()));
    }

    /**
     * SAX解析的特点:
     * 1、基于事件驱动
     * 2、顺序读取,速度快
     * 3、不能任意读取节点(灵活性差)
     * 4、解析时占用的内存小
     * 5、SAX更适用于在性能要求更高的设备上使用(Android开发中)
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     */
    @Test
    public void saxParseXML() throws ParserConfigurationException, SAXException, IOException {
        //1、创建一个SAX解析器工厂对象
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        //2、通过工厂对象创建SAX解析器
        SAXParser saxParser = saxParserFactory.newSAXParser();
        //3、创建一个数据处理器(需要我们自己来编写)
        PersonHandler personHandler = new PersonHandler();
        //4、开始解析
        InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("com/vince/xml/person.xml");
        saxParser.parse(is,personHandler);
        List<Person> persons = personHandler.getPersons();
        for (Person p:persons){
            System.out.println(p);
        }
    }
}

高性能Javascript读书总结

前端达人

1. 加载和执行

尽量将所有的<script>标签放在</body>标签之前,确保脚本执行前页面已经完成了渲染,避免脚本的下载阻塞其他资源(例如图片)的下载。

合并脚本,减少页面中的<script>标签

使用<script>标签的defer和async属性(两者的区别见这里)

通过Javascript动态创建<script>标签插入文档来下载,其不会影响页面其他进程

2.数据存取

由于作用域链的机制,访问局部变量比访问跨作用域变量更快,因此在函数中若要多次访问跨作用域变量,则可以用局部变量保存。

避免使用with语句,其会延长作用域链

嵌套的对象成员会导致引擎搜索所有对象成员,避免使用嵌套,例如window.location.href

对象的属性和方法在原型链的位置越深,访问的速度也越慢

3.Dom编程

进行大段HTML更新时,推荐使用innerHTML,而不是DOM方法

HTML集合是一个与文档中元素绑定的类数组对象,其长度随着文档中元素的增减而动态变化,因此避免在每次循环中直接读取HTML集合的length,容易导致死循环

使用节点的children属性,而不是childNodes属性,前者访问速度更快,且不包含空白文本和注释节点。

浏览器的渲染过程包括构建DOM树和渲染树,当DOM元素的几何属性变化时,需要重新构造渲染树,这一过程称为“重排”,完成重排后,浏览器会重新绘制受影响的部分到屏幕中,这一过程称为“重绘”。因此应该尽量合并多次对DOM的修改,或者先将元素脱离文档流(display:none、文档片段),应用修改后,再插入文档中。

每次浏览器的重排时都会产生消耗,大多数浏览器会通过队列化修改并批量执行来优化重排过程,可当访问元素offsetTop、scrollTop、clientTop、getComputedStyle等一系列布局属性时,会强制浏览器立即进行重排返回正确的值。因此不要在dom布局信息改变时,访问这些布局属性。

当修改同个元素多个Css属性时,可以使用CssText属性进行一次性修改样式,减少浏览器重排和重绘的次数

当元素发生动画时,可以使用绝对定位使其脱离文档流,动画结束后,再恢复定位。避免动画过程中浏览器反复重排文档流中的元素。

多使用事件委托,减少监听事件

4.算法和流程控制

for循环和while循环性能差不多,除了for-in循环最慢(其要遍历原型链)

循环中要减少对象成员及数组项的查询次数,可以通过倒序循环提高性能

循环次数大于1000时,可运用Duff Devices减少迭代次数

switch比if-else快,但如果具有很多离散值时,可使用数组或对象来构建查找表

递归可能会造成调用栈溢出,可将其改为循环迭代

如果可以,对一些函数的计算结果进行缓存

5.字符串和正则表达式

进行大量字符串的连接时,+和+=效率比数组的join方法要高

当创建了一个正则表达式对象时,浏览器会验证你的表达式,然后将其转化为一个原生代码程序,用户执行匹配工作。当你将其赋值给变量时,可以避免重复执行该步骤。

当正则进入使用状态时,首先要确定目标字符串的起始搜索位置(字符串的起始位置或正则表达式的lastIndex属性),之后正则表达式会逐个检查文本和正则模式,当一个特定的字元匹配失败时,正则表达式会试着回溯到之前尝试匹配的位置,然后尝试其他路径。如果正则表达式所有的可能路径都没有匹配到,其会将起始搜索位置下移一位,重新开始检查。如果字符串的每个字符都经历过检查,没有匹配成功,则宣布彻底失败。

当正则表达式不那么具体时,例如.和[\s\S]等,很可能会出现回溯失控的情况,在js中可以应用预查模拟原子组(?=(pattern))\1来避免不必要的回溯。除此之外,嵌套的量词,例如/(A+A+)+B/在匹配"AAAAAAAA"时可能会造成惊人的回溯,应尽量避免使用嵌套的量词或使用预查模拟原子组消除回溯问题。

将复杂的正则表达式拆分为多个简单的片段、正则以简单、必需的字元开始、减少分支数量|,有助于提高匹配的效率。

6.快速响应的用户界面

  • 单个JavaScript运算操作时间不应该超出100ms,否则可能会阻塞用户操作
  • 如果要执行长时间的运算,可以通过定时器将计算过程分割成多个步骤,使UI可以得到更新,例如
setTimeout(function(){
    process(todo.shift());

    if (todo.length > 0) {
        setTimeout(arguments.callee, 25);
    } else {
        callback();
    }
})




较长时间的计算过程也可以按照代码运行的时间进行分割,每次控制运行的时间,例如

setTimeout(function(){
    let start = +new Date();
    do {
        process(todo.shift());
    } while(todo.length > 0 && (+new Date() - start) < 50)

    if (todo.length > 0) {
        setTimeout(arguments.callee, 25);
    } else {
        callback();
    }
})


  • 高频率重复的定时器数量尽量要少,建议使用一个独立的重复定时器
  • 使用WebWork进行计算

7. AJAX

  • 设置HTTP头部信息进行缓存,例如
Expires: Mon,28 Jul 2018 23:30:30 GMT


  • 对于一些函数的计算结果进行本地缓存

8. 编程实践

  • 避免使用evalFunction进行双重求值
  • 使用Object/Array字面量定义,不要使用构造函数
  • 使用延迟加载消除函数中重复的工作
  • 使用位操作,例如与1进行按位与计算,得到奇偶交替


if (i & 1) {
    className = 'odd';
} else {
    className = 'even';
}   


  • 多使用JS内置的原生方法,例如Math对象等

9.构建和部署

  • 合并、压缩多个js文件
  • 设置HTTP缓存
  • 使用内容分发网络CDN

10.性能分析工具

————————————————
版权声明:本文为CSDN博主「PAT-python-zjw」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zjw_python/java/article/details/105293878

修复一个因为 scrollbar 占据空间导致的 bug

seo达人

背景

这一个因为滚动条占据空间引起的bug, 查了一下资料, 最后也解决了,顺便研究一下这个属性, 做一下总结,分享给大家看看。


正文

昨天, 测试提了个问题, 现象是一个输入框的聚焦提示偏了, 让我修一下, 如下图:


image.png


起初认为是红框提示位置不对, 就去找代码看:


<Input

 // ...

 onFocus={() => setFocusedInputName('guidePrice')}

 onBlur={() => setFocusedInputName('')}

/>


<Table

 data-focused-column={focusedInputName}

 // ...

/>

代码上没有什么问题, 不是手动设置的,而且, 在我和另一个同事, 还有PM的PC上都是OK的:


image.png


初步判断是,红框位置结算有差异, 差异大小大概是17px, 但是这个差异是怎么产生的呢?


就去测试小哥的PC上看, 注意到一个细节, 在我PC上, 滚动条是悬浮的:

image.png


在他PC上, 滚动条是占空间的:


image.png


在他电脑上, 手动把原本的 overscroll-y: scroll 改成 overscroll-y: overlay 问题就结局了。


由此判定是: 滚动条占据空间 引起的bug。


overscroll-y: overlay

CSS属性 overflow, 定义当一个元素的内容太大而无法适应块级格式化上下文的时候该做什么。它是 overflow-x 和overflow-y的 简写属性 。

/* 默认值。内容不会被修剪,会呈现在元素框之外 */

overflow: visible;


/* 内容会被修剪,并且其余内容不可见 */

overflow: hidden;


/* 内容会被修剪,浏览器会显示滚动条以便查看其余内容 */

overflow: scroll;


/* 由浏览器定夺,如果内容被修剪,就会显示滚动条 */

overflow: auto;


/* 规定从父元素继承overflow属性的值 */

overflow: inherit;

官方描述:

overlay  行为与 auto 相同,但滚动条绘制在内容之上而不是占用空间。 仅在基于 WebKit(例如,Safari)和基于Blink的(例如,Chrome或Opera)浏览器中受支持。

表现:

html {

 overflow-y: overlay;

}

兼容性

没有在caniuse上找到这个属性的兼容性, 也有人提这个问题:


image.png


问题场景以及解决办法

1. 外部容器的滚动条

这里的外部容器指的是html, 直接加在最外层:


html {

 overflow-y: scroll;

}

手动加上这个特性, 不论什么时候都有滚动宽度占据空间。


缺点: 没有滚动的时候也会有个滚动条, 不太美观。


优点: 方便, 没有兼容性的问题。


2. 外部容器绝对定位法

用绝对定位,保证了body的宽度一直保持完整空间:


html {

 overflow-y: scroll; // 兼容ie8,不支持:root, vw

}


:root {

 overflow-y: auto;

 overflow-x: hidden;

}


:root body {

 position: absolute;

}


body {

 width: 100vw;

 overflow: hidden;

}

3. 内部容器做兼容


.wrapper {

   overflow-y: scroll; // fallback

   overflow-y: overlay;

}

总结

个人推荐还是用 overlay, 然后使用scroll 做为兜底。


内容就这么多, 希望对大家有所启发。


文章如有错误, 请在留言区指正, 谢谢。

将 Gatsby 项目迁移到 TypeScript

seo达人

之前花了些时间将gatsby-theme-gitbook迁移到 Typescript,以获得在 VSCode 中更好的编程体验.

整体差不多已经完成迁移,剩下将 Gatsby 的 API 文件也迁移到 TS,这里可以看到 gatsby#21995 官方也在将核心代码库迁移到 Typescript,准备等待官方将核心代码库迁移完成,在迁移 API 文件.


这篇文章用XYShaoKang/gatsby-project-config,演示如何将 gatsby 迁移到 TypeScript,希望能帮到同样想要在 Gatsby 中使用 TS 的同学.


迁移步骤:


TS 配置

配置 ESLint 支持 TS

完善 GraphQL 类型提示

初始化项目

gatsby new gatsby-migrate-to-typescript XYShaoKang/gatsby-project-config

cd gatsby-migrate-to-typescript

yarn develop

TS 配置

安装typescript

添加typescript.json配置文件

修改 js 文件为 tsx

补全 TS 声明定义

安装typescript

yarn add -D typescript

添加配置文件tsconfig.json

// https://www.typescriptlang.org/v2/docs/handbook/tsconfig-json.html

{

 "compilerOptions": {

   "target": "esnext", // 编译生成的目标 es 版本,可以根据需要设置

   "module": "esnext", // 编译生成的目标模块系统

   "lib": ["dom", "es2015", "es2017"], // 配置需要包含的运行环境的类型定义

   "jsx": "react", // 配置 .tsx 文件的输出模式

   "strict": true, // 开启严格模式

   "esModuleInterop": true, // 兼容 CommonJS 和 ES Module

   "moduleResolution": "node", // 配置模块的解析规则,支持 node 模块解析规则

   "noUnusedLocals": true, // 报告未使用的局部变量的错误

   "noUnusedParameters": true, // 报告有关函数中未使用参数的错误

   "experimentalDecorators": true, // 启用装饰器

   "emitDecoratorMetadata": true, // 支持装饰器上生成元数据,用来进行反射之类的操作

   "noEmit": true, // 不输出 js,源映射或声明之类的文件,单纯用来检查错误

   "skipLibCheck": true // 跳过声明文件的类型检查,只会检查已引用的部分

 },

 "exclude": ["./node_modules", "./public", "./.cache"], // 解析时,应该跳过的路晋

 "include": ["src"] // 定义包含的路径,定义在其中的声明文件都会被解析进 vscode 的智能提示

}

将index.js改成index.tsx,重新启动服务,查看效果.


其实 Gatsby 内置了支持 TS,不用其他配置,只要把index.js改成index.tsx就可以直接运行.添加 TS 依赖是为了显示管理 TS,而tsconfig.json也是这个目的,当我们有需要新的特性以及自定义配置时,可以手动添加.

补全 TS 声明定义

打开index.tsx,VSCode 会报两个错误,一个是找不到styled-components的声明文件,这个可以通过安装@types/styled-components来解决.

另外一个错误绑定元素“data”隐式具有“any”类型。,这个错误是因为我们在tsconfig.json中指定了"strict": true,这会开启严格的类型检查,可以通过关闭这个选项来解决,只是我们用 TS 就是要用它的类型检查的,所以正确的做法是给data定义类型.

下面来一一修复错误.


安装styled-components的声明文件


yarn add -D @types/styled-components

修改index.tsx


import React, { FC } from 'react'

import styled from 'styled-components'

import { graphql } from 'gatsby'

import { HomeQuery } from './__generated__/HomeQuery'


const Title = styled.h1`

 font-size: 1.5em;

 margin: 0;

 padding: 0.5em 0;

 color: palevioletred;

 background: papayawhip;

`


const Content = styled.div`

 margin-top: 0.5em;

`


interface PageQuery {

 data: {

   allMarkdownRemark: {

     edges: Array<{

       node: {

         frontmatter: {

           title: string

         }

         excerpt: string

       }

     }>

   }

 }

}


const Home: FC<PageQuery> = ({ data }) => {

 const node = data.allMarkdownRemark.edges[0].node


 const title = node.frontmatter?.title

 const excerpt = node.excerpt


 return (

   <>

     <Title>{title}</Title>

     <Content>{excerpt}</Content>

   </>

 )

}


export default Home


export const query = graphql`

 query HomeQuery {

   allMarkdownRemark {

     edges {

       node {

         frontmatter {

           title

         }

         excerpt

       }

     }

   }

 }

`

这时候会出现一个新的错误,在excerpt: string处提示Parsing error: Unexpected token,这是因为 ESLint 还无法识别 TS 的语法,下面来配置 ESLint 支持 TS.


配置 ESLint 支持 TypeScript

安装依赖


yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

配置.eslintrc.js


module.exports = {

 parser: `@typescript-eslint/parser`, // 将解析器从`babel-eslint`替换成`@typescript-eslint/parser`,用以解析 TS 代码

 extends: [

   `google`,

   `eslint:recommended`,

   `plugin:@typescript-eslint/recommended`, // 使用 @typescript-eslint/eslint-plugin 推荐配置

   `plugin:react/recommended`,

   `prettier/@typescript-eslint`, // 禁用 @typescript-eslint/eslint-plugin 中与 prettier 冲突的规则

   `plugin:prettier/recommended`,

 ],

 plugins: [

   `@typescript-eslint`, // 处理 TS 语法规则

   `react`,

   `filenames`,

 ],

 // ...

}

在.vscode/settings.json中添加配置,让VSCode使用ESLint扩展格式化ts和tsx文件


// .vscode/settings.json

{

 "eslint.format.enable": true,

 "[javascript]": {

   "editor.defaultFormatter": "dbaeumer.vscode-eslint"

 },

 "[javascriptreact]": {

   "editor.defaultFormatter": "dbaeumer.vscode-eslint"

 },

 "[typescript]": {

   "editor.defaultFormatter": "dbaeumer.vscode-eslint"

 },

 "[typescriptreact]": {

   "editor.defaultFormatter": "dbaeumer.vscode-eslint"

 }

}

完善 GraphQL 类型提示

// index.tsx

import React, { FC } from 'react'

// ...

interface PageQuery {

 data: {

   allMarkdownRemark: {

     edges: Array<{

       node: {

         frontmatter: {

           title: string

         }

         excerpt: string

       }

     }>

   }

 }

}


const Home: FC<PageQuery> = ({ data }) => {

 // ...

}


export default Home


export const query = graphql`

 query HomeQuery {

   allMarkdownRemark {

     edges {

       node {

         frontmatter {

           title

         }

         excerpt

       }

     }

   }

 }

`

我们看看index.tsx文件,会发现PropTypes和query结构非常类似,在Gatsby运行时,会把query查询的结果作为组件prop.data传入组件,而PropTypes是用来约束prop存在的.所以其实PropTypes就是根据query写出来的.


如果有依据query自动生成PropTypes的功能就太棒了.

另外一个问题是在query中编写GraphQL查询时,并没有类型约束,也没有智能提示.


总结以下需要完善的体验包括:


GraphQL 查询编写时的智能提示,以及错误检查

能够从 GraphQL 查询生成对应的 TypeScript 类型.这样能保证类型的唯一事实来源,并消除 TS 中冗余的类型声明.毕竟如果经常需要手动更新两处类型,会更容易出错,而且也并不能保证手动定义类型的正确性.

实现方式:


通过生成架构文件,配合Apollo GraphQL for VS Code插件,实现智能提示,以及错误检查

通过graphql-code-generator或者apollo生成 TS 类型定义文件

如果自己去配置的话,是挺耗费时间的,需要去了解graphql-code-generator的使用,以及Apollo的架构等知识.

不过好在社区中已经有对应的 Gatsby 插件集成了上述工具可以直接使用,能让我们不用去深究对应知识的情况下,达到优化 GraphQL 编程的体验.

尝试过以下两个插件能解决上述问题,可以任选其一使用


gatsby-plugin-codegen

gatsby-plugin-typegen

另外还有一款插件gatsby-plugin-graphql-codegen也可以生成 TS 类型,不过配置略麻烦,并且上述两个插件都可以满足我现在的需求,所以没有去尝试,感兴趣的可以尝试一下.


注意点:


Apollo不支持匿名查询,需要使用命名查询

第一次生成,需要运行Gatsby之后才能生成类型文件

整个项目内不能有相同命名的查询,不然会因为名字有冲突而生成失败

下面是具体操作


安装vscode-apollo扩展

在 VSCode 中按 Ctrl + P ( MAC 下: Cmd + P) 输入以下命令,按回车安装


ext install apollographql.vscode-apollo

方式一: 使用gatsby-plugin-codegen

gatsby-plugin-codegen默认会生成apollo.config.js和schema.json,配合vscode-apollo扩展,可以提供GraphQL的类型约束和智能提示.

另外会自动根据query中的GraphQL查询,生成 TS 类型,放在对应的tsx文件同级目录下的__generated__文件夹,使用时只需要引入即可.

如果需要在运行时自动生成 TS 类型,需要添加watch: true配置.


安装gatsby-plugin-codegen


yarn add gatsby-plugin-codegen

配置gatsby-config.js


// gatsby-config.js

module.exports = {

 plugins: [

   // ...

   {

     resolve: `gatsby-plugin-codegen`,

     options: {

       watch: true,

     },

   },

 ],

}

重新运行开发服务生成类型文件


yarn develop

如果出现以下错误,一般是因为没有为查询命名的缘故,给查询添加命名即可,另外配置正确的话,打开对应的文件,有匿名查询,编辑器会有错误提示.


fix-anonymous-operations.png


这个命名之后会作为生成的类型名.


修改index.tsx以使用生成的类型


gatsby-plugin-codegen插件会更具查询生成对应的查询名称的类型,保存在对应tsx文件同级的__generated__目录下.


import { HomeQuery } from './__generated__/HomeQuery' // 引入自动生成的类型

// ...


// interface PageQuery {

//   data: {

//     allMarkdownRemark: {

//       edges: Array<{

//         node: {

//           frontmatter: {

//             title: string

//           }

//           excerpt: string

//         }

//       }>

//     }

//   }

// }


interface PageQuery {

 data: HomeQuery // 替换之前手写的类型

}


// ...

将自动生成的文件添加到.gitignore中


apollo.config.js,schema.json,__generated__能通过运行时生成,所以可以添加到.gitignore中,不用提交到 git 中.当然如果有需要也可以选择提交到 git 中.

# Generated types by gatsby-plugin-codegen

__generated__

apollo.config.js

schema.json

方式二: 使用gatsby-plugin-typegen

gatsby-plugin-typegen通过配置生成gatsby-schema.graphql和gatsby-plugin-documents.graphql配合手动创建的apollo.config.js提供GraphQL的类型约束和智能提示.

根据GraphQL查询生成gatsby-types.d.ts,生成的类型放在命名空间GatsbyTypes下,使用时通过GatsbyTypes.HomeQueryQuery来引入,HomeQueryQuery是由对应的命名查询生成


安装gatsby-plugin-typegen


yarn add gatsby-plugin-typegen

配置


// gatsby-config.js

module.exports = {

 plugins: [

   // ...

   {

     resolve: `gatsby-plugin-typegen`,

     options: {

       outputPath: `src/__generated__/gatsby-types.d.ts`,

       emitSchema: {

         'src/__generated__/gatsby-schema.graphql': true,

       },

       emitPluginDocuments: {

         'src/__generated__/gatsby-plugin-documents.graphql': true,

       },

     },

   },

 ],

}

//apollo.config.js

module.exports = {

 client: {

   tagName: `graphql`,

   includes: [

     `./src/**/*.{ts,tsx}`,

     `./src/__generated__/gatsby-plugin-documents.graphql`,

   ],

   service: {

     name: `GatsbyJS`,

     localSchemaFile: `./src/__generated__/gatsby-schema.graphql`,

   },

 },

}

重新运行开发服务生成类型文件


yarn develop

修改index.tsx以使用生成的类型


gatsby-plugin-codegen插件会更具查询生成对应的查询名称的类型,保存在对应tsx文件同级的__generated__目录下.


// ...


// interface PageQuery {

//   data: {

//     allMarkdownRemark: {

//       edges: Array<{

//         node: {

//           frontmatter: {

//             title: string

//           }

//           excerpt: string

//         }

//       }>

//     }

//   }

// }


interface PageQuery {

 data: GatsbyTypes.HomeQueryQuery // 替换之前手写的类型

}


// ...

将自动生成的文件添加到.gitignore中


__generated__能通过运行时生成,所以可以添加到.gitignore中,不用提交到 git 中.当然如果有需要也可以选择提交到 git 中.

# Generated types by gatsby-plugin-codegen

__generated__

日历

链接

个人资料

蓝蓝 http://www.lanlanwork.com

存档