GithubHelp home page GithubHelp logo

maoni's People

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

maoni's Issues

深入理解 Intersection Observer

稿定设计导出-20190829-160220

什么是 Intersection Observer

Intersection Observer API 提供给web开发者,一种异步查询元素相对于其他元素或窗口位置的能力。它常用于追踪一个元素在窗口的可视问题,比如下图,滚动页面,顶部会提示绿色方块的可视性。

iframe

在 Intersection Observer 出来之前,传统位置计算的方式,依赖于对DOM状态的轮询计算,然而这种方式会在主线程里密集执行从而造成页面性能问题,getBoundingClientRect()的频繁调用也可能引发浏览器的样式重计算和布局。如果是在iframe里,因为同源策略,我们不能直接访问元素,也就很难用传统方式去处理iframe里的元素。

Intersection Observer 的设计,就是为了更方便的处理元素的可视问题。使用 Intersection Observer 我们可以很容易的监控元素进入和离开可视窗口,实现节点的预加载和延迟加载。Intersection Observer 并不是基于像素变化的实时计算,它的反馈会有一定的延时,这种异步的方式减少了对DOM和style查询的昂贵计算和持续轮询,相比传统方式降低了CPU、GPU的消耗。

Intersection Observer 使用方法

// 定义相交监视器
var observer = new IntersectionObserver(changes => {
  for (const change of changes) {
    console.log(change.time);               // 发生变化的时间
    console.log(change.rootBounds);         // 根元素的矩形区域的信息
    console.log(change.boundingClientRect); // 目标元素的矩形区域的信息
    console.log(change.intersectionRect);   // 目标元素与视口(或根元素)的交叉区域的信息
    console.log(change.intersectionRatio);  // 目标元素与视口(或根元素)的相交比例
    console.log(change.target);             // 被观察的目标元素
  }
}, {});

// 开始观察某个目标元素
observer.observe(target);

// 停止观察某个目标元素
observer.unobserve(target);

// 关闭监视器
observer.disconnect();

1. IntersectionObserver Callback

当目标元素和根元素相交发生改变时,会触发监视器的回调。

callback IntersectionObserverCallback = void (
    sequence<IntersectionObserverEntry> entries, 
    IntersectionObserver observer
);

回调的参数,entries 是一个IntersectionObserverEntry数组,数组的长度是我们监控目标元素的个数,另一个参数是 IntersectionObserver 对象。

2. IntersectionObserver Entry

IntersectionObserverEntry 对象提供了目标元素与跟元素相交的详细信息。他有如下几个属性。

interface IntersectionObserverEntry {
  readonly attribute DOMHighResTimeStamp time;
  readonly attribute DOMRectReadOnly? rootBounds;
  readonly attribute DOMRectReadOnly boundingClientRect;
  readonly attribute DOMRectReadOnly intersectionRect;
  readonly attribute boolean isIntersecting;
  readonly attribute double intersectionRatio;
  readonly attribute Element target;
};

image

  1. time:发生相交到相应的时间,毫秒。
  2. rootBounds:根元素矩形区域的信息,如果没有设置根元素则返回null,图中蓝色部分区域。
  3. boundingClientRect:目标元素的矩形区域的信息,图中黑色边框的区域。
  4. intersectionRect:目标元素与视口(或根元素)的交叉区域的信息,图中蓝色方块和粉红色方块相交的区域。
  5. isIntersecting:目标元素与根元素是否相交
  6. intersectionRatio:目标元素与视口(或根元素)的相交比例。
  7. target:目标元素,图中黑色边框的部分。

3. IntersectionObserver Option

IntersectionObserver Option 是 IntersectionObserver 构造函数的第二个参数,用来配置监视器的部分信息。

dictionary IntersectionObserverInit {
  Element?  root = null;
  DOMString rootMargin = "0px";
  (double or sequence<double>) threshold = 0;
};
  1. root:设置监视器的根节点,不传则默认为视口。
  2. rootMargin: 类似于 CSS 的 margin 属性。用来缩小或扩大 rootBounds,从而影响相交的触发。
  3. threshold:属性决定相交比例为多少时,触发回调函数。取值为 0 ~ 1,或者 0 ~ 1的数组。
    如下图,当我们把 threshold 设置为 [0, 0.25, 0.5, 0.75, 1],绿色方块分别在 0%,25%,50%,75%,100% 可见时,触发回调函数。

threshold

Intersection Observer 使用场景

1. 构建自定义的预加载或延迟加载DOM和数据。

比如说我们可以用 Intersection Observer 实现图片的懒加载,当图片达到用户可视区的时候再进行加载。
优化大型网站性能,优先渲染用户可视的视图,根据用户页面滚动,按需加载视图。

2. 实现高性能的滚动列表。

实现列表的上拉加载,减少长列表的滑动卡顿。

3. 计算元素的可视性。

优化广告推广的有效性,只有广告进入用户可视区,才算是有效的广告计数。
实现用户把播放器移除可视区时的自动暂停,进入可视区再进行播放。
还可以高效的实现视差动画。

Intersection Observer 兼容性

image

IntersectionObserver 目前已经被主流的浏览器支持,我们可以使用渐进支持的方式使用它。对于目前浏览器的生态,我们也要做好向下降级的措施。
我们也可以使用 IntersectionObserver polyfill 增加浏览器的兼容,具体可以查看 polyfill.io。

image

参考

  1. IntersectionObserver’s Coming into View
  2. w3c Intersection Observer doc

2018,再见。

2018混的这么差,所以要写一篇文章mark一下🤔。2018年,一定会在我的记忆中占有极大的重量,雄心满满的出发,而既疲倦又麻木的坐下来开始写失败感言。

从新蜂工作室到友萌科技有限公司

2017年我成立了新蜂工作室,新蜂工作室是一个产品研发设计工作室,最初由我业余时间所创,想去连接更多的独立且各具技能的自由职业者组成一个团队。组合我们个体的技能实现更大的社会价值,也放大了每个个体的技能价值。
当时我们都还是兼职状态,合作过的工程师大概已经有20多人。那段时间真的是很疯狂,我上班的时候要联系客户,进行团队沟通,下班的时候还要赶项目。每谈成一个项目就屁颠屁颠高兴的不成,项目快交付出了很多bug又关心则乱,就这样兼职干了五个月,做了6个项目,盈利70万左右。当时我感觉挺有戏的,一帮人业余时间居然能搞出这么多事情,一定是有市场的。

之后成立友萌公司,则是有三点原因:

  1. 17年我24岁,当时拿到的offer最高是4.2万,再要高的基本上就被人力各种盘算。毕竟面技术岗,人力习惯的把年龄=能力,最后以年龄或经验不足pass掉了。我当时认为,年龄不是我的错,我可不要到30岁谢顶了猜得到我想要的,只有当老板就没人能拒绝我。
  2. 因为是兼职做工作室,做外包的事情好像整个公司都知道了,上班的时候干别的事情,还拉着同事一起干。。。年底绩效非常低,我的月薪又一直徘徊在3万左右没变过,月薪普涨也略过了我。出于职业道德还是全职干就不用昧良心了。
  3. 最主要的原因还是觉得工作室的模式可以work,有了公司就可以接更大的项目,全职也可以缴纳社保。

从北向南

离开了待了三年的北京,终于可以做个断舍离的人,想走就走,不被任何事情牵绊。北京是个好城市但不是我的家。
回到了老家,自己喜欢的人却不常在,那便就成了冰冷的城市,一个我不喜欢的地方。
杭州是个精致的城市,房价相对很低,是个可以长期宅的城市。
新加坡,超级美丽的城市,我喜欢日落,我喜欢看海,我喜欢半夜在酒吧调酒,喜欢听别人自说自话。但我不喜欢短暂,总有一天我会回去。
无论从北向南,还是一路向北,我心无所属。难道这是宅的最高境界,有电有网就是世界😁。

小七

我几乎没有想过死亡这个主题,一旦遇到,我就选择了逃避,逃避心里会好受很多。我的猫🐈叫小七,一只可爱的猫,一只粘人的猫,一只好吃的猫。因为肝脏问题,10月份离开了,而我至今都很难接受。死亡意味着,一旦离开,就是永远。永远也听不到它打呼噜,永远也不能给它挠痒,永远也不能给它铲屎。死亡是生命的轮回,尊重它便是对生命的认识。

我是个失败者

17年许下一个愿望,如果盈利能超200万,工作室的人就新年一起去美国🇺🇸,现在只能呵呵的破产了。做黄了一个又一个产品,我对失败已经麻木了。现在我已经放弃再去接项目了,不是因为气垒,而是我做什么都会失败,投给我多少资金都会失败,已经不好意思再坑项目方了。
产品失败我并不难受,而那些信任我,而我又做出许诺没有兑现的队友,就真的真的不好意思。他们本可以继续自己安稳的人生路线,本可以不用在项目失败后听我乱bb,本可以一年拿着40w工资,现在估计都腰斩了。现在才懂得,能力不足,千万别承诺。
我是个失败者?生活没有那么容易的事情,没有重复练习的事情,没有答案的习题。想做成一件事情,真的很难很难,做成了是幸运。失败又不是错误,失败了又能怎样,世界不是依旧运转。反正失败不是我的错,可能是别人的,可能是世界的,反正不是我的。成功的对面不是失败,而是平庸。无论是成功亦或失败,都是努力争取过。让我喝茶看报,我做不到,我有我想要的,翻越这座山,世界会听到我的声音。
野心,能力不足以实现,所有的路都是弯路。出来一年我也看到了自己很多的不足,19年我已经看到自己要走的路,即使是弯路,我也要边走边试。

如果再给一次机会

如果真的再给一次机会,我还是会这样,但不会再拉朋友一起做。毕竟人人都喜欢分享成功,而不是失败。

总之

18年不悔也不爽,滚出,不送。

一生所爱

一生所爱

周末北京细雨绵绵,宅在家里看《人人都是产品经理》,里面一幅幅的思维导图着实吸引了我。如果当初没有放弃画画,我也能画出这样的思维导图。此时不禁想起大学里的P神,手绘1000多张图层的精美画作。

小学的时候我想用彩笔去画奥特曼,我爸给我报了画画班。虽然十五年过去了,我仍记得第一节课的题目:”画一个苹果“。下课结束后,我看着我那扭曲的非圆,就知道画画不适合我,这不是我想要的,我和我的绘画一样,然后都没了然后。

大一的时候,同学开始学吉他,花了半个月就已经能弹的很好了。和他一起买了吉他,每天他都会教我。但我实在乐律全无,一个月留下的只有手茧,然后就默默自我否定和放弃了。

看《中华小当家》,作为一个热爱生活追求美食的人,我也想做一个更厉害的厨师。用认真的美食表达态度,用料理让食客感到幸福。但我却无法去分配更多的时间和精力在这方面。

来到外面的花花世界,我们好奇不曾所见,我们想经历远方。我们会对很多事情,很多人感兴趣,我们希望它们在我们的世界留下倒影,留下痕迹。它们有光鲜的外表,它们会多彩我们的世界,它们,我们不曾拥有。而人的一生不是静止的,有太多太多的插曲,很多事情我们都浅尝辄止,不是因为入门遇到麻烦,就是岁月磨平了激情,最终只是我们世界的一个过客。

记得有一个对万科之争的评论:”你抢了年轻人的女朋友,年轻人抢了你的公司,时间花在哪里,成就就在哪里。“ 人的时间和精力有限,我们很难爱上一件事情又能很好的办好其他的事情,时间花在哪里,成就就在哪里。把时间和精力分散到很多兴趣上只会让人一事无成。

后来恍然大悟,我们对很多事情都会感兴趣,但是我们不一定热爱它们,至少不都是一生所爱。

感兴趣和热爱差别真的很大。我朋友老唐曾说过,”我干技术十几年了,虽然不能说非常热爱技术,我也对游戏很感兴趣,但是除了技术没有什么更适合我的。不是我选择了技术,而是技术选择了我“。不知道是不是十几年的光阴磨平了老唐对技术的激情,但是朴素的话是他对技术最好的表白。

罗永浩在陌陌直播的时候说过,若你在人生二十几岁的时候就知道自己热爱什么,那么非常恭喜你真的很幸运。如果三十几岁才明白一生所爱,那么也恭喜你。如果四十几岁才有所悟,那么也恭喜你,不晚。能发现自己一生所爱,而且能全身心的投入,真的很幸福。不论早晚,你所热爱的终将在你的世界停留。

愿幸运的人,能早日发现一生所求,遇见一生所爱。Good time, hard time, never bad time.

基于fastlane的移动运用部署

基于fastlane的移动运用部署

Dont Repeat Yourself

在程序的世界里,一切重复性的,流程化的工作都可以交给自动化去完成。

在移动开发中也是如此:其实写代码只是我们开发过程中的一部分,除此之外我们还需要进行编译,打包,上传,部署,库管理,版本控制等等Coding之外的杂事,而正是这些乏味而重复的工作占用了我们宝贵的时间。

所以在“懒人”遍布的工程师世界中,总会有人想尽办法做出改变,于是这些“懒人”们乐此不疲的造出许多美妙的轮子,既方便了自己,又帮助了他人,让这个世界变得更加美好。

这篇文章介绍其中一个轮子:Fastlane,这个Github上的明星项目截止到目前共获得1万多个Star,并且还有2000多个Fork。

大家都知道,最近几年,随着智能手机的普及,移动端不仅要承载更多业务场景的实现,并且还要应对不断变化业务需求。这就要求我们移动团队能够迅速响应变化,快速的迭代。那么随之而来的问题就是如何保障在不牺牲质量的前提下,尽可能的提升速度,我认为这一切需要建立在高质量的持续测试和持续交付体系之上。

但是移动端本身兴起的时间就比较短,各方面的成熟度也有所欠缺,能够拿来用的工具更是少之又少,随着业务深度广度的增加,迭代速度的加快,诸如证书管理,打包,上传,发布这类重复而毫无技术含量的工作逐渐占用了大家的时间,团队内部对此诟病不已。

所以我们一直在寻求一种工具,一种解决方案,旨在彻底解放工程师的“双手”。

Fastlane


Fastlane 是用 Ruby 语言编写的一套自动化工具集和框架,每一个工具实际都对应一个 Ruby 脚本,用来执行某一个特定的任务,而 Fastlane 核心框架则允许使用者通过类似配置文件的形式,将不同的工具有机而灵活的结合在一起,从而形成一个个完整的自动化流程。

到目前为止,Fastlane 的工具集大约包含170多个小工具,基本上涵盖了打包,签名,测试,部署,发布,库管理等等移动开发中涉及到的内容。

关于这些工具的描述和使用可以看这里:

https://docs.fastlane.tools/actions/Actions/

如果这些工具仍然没有符合你需求的,没有关系,得益于Fastlane本身强大的Action和Plugin机制,如果你恰好懂一些Ruby开发的话,可以很轻易的编写出自己想要的工具。

移动端持续交付场景

在 开发 -> 测试 -> 上线 的流程中,移动端一般会经过以下步骤:

  1. 执行Git Pull命令,拉最新的代码到本地
  2. Pod Install 安装最新的依赖库
  3. 在Xcode中将Build Version增加
  4. 确认证书,pp文件
  5. 在Xcode点击Archive编译并打包
  6. 选择输出一个iOS AppStore模式的ipa文件
  7. 通过Application Loader将IPA上传到ITC(TestFlight)
  8. 然后等待ITC Process完成后,登录上去选择刚才的Build进行TestFlight测试
  9. 由于修改了版本号,所以需要将代码Commit和Push一下

手动部署流程 。。。

如果线上跟测发现有问题,那么需要修复完毕后重复上面的8个步骤。
其实做过这件事的同学应该都有体会,顺利的话差不多一次得30分钟吧,如果某一次Build Version忘记增加了,那么前面的工作就白做了。

而 Fastlane 则可以帮我们从以上步骤中解脱出来,真正专注于解决问题本身。使用了Fastlane这么长的时间,我最深的感受就是:Fastlane真正的将工程师从各种无聊而又必须要做的重复性劳动和流程化工作中解放出来,专注于业务或架构本身,使得整个开发效率,测试效率,运维效率大大提升。

以下将持续交付的流程拆分细说。

fastlane 设置

fastlane init

然后跟随配置引导,填写App和ITC相关信息,然后Fastlane会在项目目录下创建一个fastlane目录,里面包含所有和此项目相关的配置,剩下要做的就是将以上的流程配置在fastlane目录下的Fastfile中。

具体参考:
https://fabric.io/features/distribution?utm_campaign=fastlane.tools

团队开发证书管理

关于 iOS 的开发证书等相关内容,请先阅读:Maintaining Your Signing Identities and Certificates

多人团队开发带来的问题:

  1. 每个成员都有不同的证书,结果导致极大冗余的pp文件
  2. 每当添加设备或者证书过期,都要手动创建下载最新的pp文件

fastlane match 提供一种基于 git 的团队开发证书的管理方案

通过私有仓库存储 cer 证书 和 pp 文件,并自动更新过期证书和对应的pp文件。

match

fastlane match init

填写相关app信息和私有 git repo 地址。

match 使用请参考: https://github.com/fastlane/fastlane/tree/master/match#readme

具体可参考:

  1. https://codesigning.guide/
  2. https://docs.fastlane.tools/codesigning/getting-started/#

签名

cert

cert 是用来自动创建维护 iOS 证书的 fastlane action。

fastlane cert

具体使用参考: https://github.com/fastlane/fastlane/tree/master/cert#readme

sigh

sigh 是用来创建,更新,下载,匹配 pp文件的 fastlane action。

fastlane sigh

具体使用参考: https://github.com/fastlane/fastlane/tree/master/sigh#readme

打包

gym

gym 是用来打包的 fastlane action。

fastlane gym

具体使用参考:https://github.com/fastlane/fastlane/tree/master/gym

testflight

testflight

也可以使用pilot 进行 testflight 的管理

snapshot

snapshot 配合 frameit 使用,来生成 app 截图

deliver

将 ipa,metadata,屏幕截图发布到ITC上

fastlane 扩展

自定义Action

Fastlane的Plugin机制

发布Plugin

android 平台

平心而论,Fastlane本身对Android平台的支持力度确实有限,15个核心工具中仅有2个是用于Android平台的,其中:

Supply是用于上传APK文件和同步Meta信息到Google Play商店(类似iOS的Deliver)

Screengrab是用于生成各种屏幕尺寸的截屏,然后上传这些截屏到Google Play商店

好吧,看上去这两个工具都和Google Play相关,这个对于国内大部分Android开发者来说,似乎都派不上用场。

当然这也不能怪Fastlane重iOS,轻Android。和iOS不同,Android本身就没有那么多琐碎的事情,比如:证书管理,Provisioning文件管理,推送证书管理,签名等等,也不需要专门使用一个类似Testflight的测试平台来分发测试包,基本上一个APK打包出来,就能随便安装了。

不过俗话说得好:“家家有本难念的经”,如果从开发复杂度来比较,Android平台其实是有过之而无不及的。功能开发完毕只是一切的开始,多系统,多屏幕,多厂商的适配,五花八门的市场分发才是真正的难题。如何快速,低成本的解决这些难题,将成为Andriod工程师们所面临的挑战。

多渠道打包

我相信每个公司的市场部门都希望精确的统计到各个投放渠道的安装,激活,注册,留存,变现等数据,从而能够有的放矢。这一切的数据统计都来源于渠道包的生成。由于国内Android市场众多,所以在最终发版的时候,我们需要提供多个渠道包给市场同学,如果手动处理的话,那么步骤大概如下:

执行Git Pull命令,拉最新的代码到本地

修改渠道标识,如:在APK的META-INFO目录增加一个空的文件,文件名为渠道名

执行./gradlew clean命令清理环境

执行./gradlew assembleRelease打包Release版本

将生成的APK文件重新命名为改渠道对应的名字

然后以上步骤重复N次,N=渠道数,如果N>=3,我相信无论是多么有耐心的人,经过两三次下来,结果都会崩溃,尤其是万一上线前发现版本中包含Bug,那么只能重来一遍,此时N=渠道数x重来的次数。

其实我这里描述的场景可能稍显夸张,我相信大部分公司都会或多或少使用一些自动化的方式来处理,比如通过Shell命令等等。这里我们可以看看使用Fastlane如何进行处理:

首先,我们自定义一个Action:add_channels_to_apk,这个Action的作用就是:

拷贝最终打包生成的apk文件,并修改文件名为渠道名,如gengmei_qq_630.apk

然后将一个渠道名写入到apk文件的META-INFO目录中

其次,新建一个txt文件,里面写入所有需要打包的渠道名,如:QQ,360,Baidu...等等,渠道名之间用逗号隔开。

最后,在Fastfile中定义一个Lane来进行最终的集成处理:

desc "Package a new app version with different channels"
lane :do_package_apk do |options|
  project = "#{options[:project]}"
  target_version = options[:version]
  hipchat(message: "Start package #{project} at version #{target_version}")
  git_pull
  gradle(task: "clean")
  gradle(task: "assembleRelease")
  add_channels_to_apk(channels: './channels.txt')
  hipchat(message: "Deliver app #{project} successfully!")
end

接下来的事就简单多了,每次需要打包的时候,只要执行如下的命令即可:

fastlane do_package_apk project:hso version:6.3.0

无论是5个渠道,还是50个渠道,1分钟内全部搞定,非常的方便。

JS 异步编程模型

JS 异步模型

Javascript 除了代码其余全是并行。 这句话说明了JavaScript最大的特点,本文将从JS的单线程,常见的并发模型,事件轮询并发模型等讲解JS的异步模型。

JS 单线程

Javascript 语言的一个特点就是单线程,也就是说,同一时间只能做一件事。这么一听好像在效率上比其他语言有很大的劣势。那么Javascript 为什么还是单线程的? 这跟历史有关系。JavaScript从诞生起就是单线程。原因大概是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果 (比如一个线程要修改一个页面节点,一个线程要删除该节点),对于一种网页脚本语言来说,这就太复杂了。后来就约定俗成,JavaScript为一种单线程语言。(HTML5 Worker API可以实现多线程,但是子线程是不能修改 DOM 的,只有通过 postMessage 方法进行通信,所以 JavaScript 本身始终是单线程的。)。

那么 JavaScript 一开始的宿主是浏览器并采用了单线程的方式运行,那么为什么设计 Node.js 的时候依旧保留单线程的运行方式?

node.js中的第一个基本论点是I/O是昂贵的,第二个基本的论点是「每个连接一个线程」的模式是很耗内存的。

所以 node.js 采用了单线程的异步事件和单线程的非阻塞I/O。

常见异步并发模型

单线程阻塞并发

如果某个任务很耗时,比如涉及很多I/O(输入/输出)操作,那么单线程的运行大概是下面的样子。线程同一时间只处理一个任务,每次一个。

上图的绿色部分是程序的运行时间,红色部分是等待时间。可以看到,由于I/O操作很慢,所以这个线程的大部分运行时间都在空等I/O操作的返回结果。这种运行方式称为"同步模式"(synchronous I/O)或"堵塞模式"(blocking I/O)。

这种方式的优点是简单,缺点是任何一个异步都将挂起其他任务。

对于单线程的JS如果以这种并发模型来运行,那web体验将是灾难的。

多线程阻塞并发

如果采用多线程,同时运行多个任务,那很可能就是下面这样。

上图表明,多线程不仅占用多倍的系统资源,也闲置多倍的资源,这显然不合理。

这种方式的优点是简单,比使用fork进程对内核更加友好,因为线程占用更少的系统开销,比单线程阻塞时能响应其他事物;缺点:你的机器可能没有线程,而且面向线程的编程由于担心对共享资源的访问控制,可能会很快地变得非常复杂。

Apache 就是用这种方式实现并发的,它的一个请求产生一个线程(或者进程,取决于配置文件)。随着并发连接数的增加以及为了服务更多的并发客户端而对更多线程的需求,你可以看见系统统开销是如何吃掉内存的。

单线程非阻塞并发

单线程非阻塞的并发模型就是为了解决以上并发模型的缺点,以最小资源效率最大化。单线程遇到耗时I/O时将向任务队列发送I/O命令并挂起I/O完成的回调,并继续执行其他任务,当I/O完成时,任务队列会插入I/O完成事件,执行线程响应事件并执行返回的I/O。如下图:

上图主线程的绿色部分,还是表示运行时间,而橙色部分表示空闲时间。每当遇到I/O的时候,主线程就让Event Loop线程去通知相应的I/O程序,然后接着往后运行,所以不存在红色的等待时间。等到I/O程序完成操作,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。

可以看到,由于多出了橙色的空闲时间,所以主线程得以运行更多的任务,这就提高了效率。这种运行方式称为"异步模式"(asynchronous I/O)或"非堵塞模式"(non-blocking mode)。

Nginx和Node.js不是多线程,因为多个线程和多个进程会需要大量的内存。它们都是单线程的,但是是事件驱动。通过在一个线程中处理多个连接,这消除了由上千个线程/进程所产生的系统消耗。

event loop 并发模型

Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制。JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

  4. 主线程不断重复上面的第三步。

我们举一个打印的例子

setTimeout(() => console.log('one'), 0);
Promise.resolve().then(() => console.log('two'));
console.log('three');

上面的代码都想表达立即打印的意思,那么他们又是以怎样的顺序就行输出的呢?

运行到第一行代码时,我们遇到的是一个定时器,执行栈将向消息队列发送定时器事件,将在下一轮事件循环中将计时器回调放入执行栈中执行。

运行到第二行代码时,Promise.resolve() 将立刻返回一个 resolve 状态的 Promise,触发 then 的回调函数将放入本轮事件循环的末尾等待执行。

运行到第三行代码时,执行栈会执行代码并打印。输出 "three"。执行栈所有任务执行完成后,执行本轮消息队列内的事件,打印 "two", 下个事件轮询将打印 "one"。所以输出的顺序是 three -> two -> one。

JS 实现异步的方式

所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。

相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。

常见的异步场景有:

单个异步: 一个异步任务执行完成后,执行某些任务。
A --> S

多个异步继发:只有当前一个异步完成后,才会开始下一个异步。

A1 -->
--> A2
--> A3

多个异步并发:多个异步同时发生。

A1 -->
A2 -->
A3 -->

多个异步并发,所有异步完成后汇总:
A1 --> |
A2 --> | --> A4
A3 --> |
多个异步并发竞争,最先完成的执行:

A1 ----> |
A2 -> | --> A2
A3 ------> |

更复杂的便是以上情况的组合。

回调函数

所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。

读取文件进行处理,是这样写的。

fs.readFile('/etc/passwd', 'utf-8', function (err, data) {
  if (err) throw err;
  console.log(data);
});

上面代码中,readFile函数的第三个参数,就是回调函数,也就是任务的第二段。等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行。

一个有趣的问题是,为什么 Node 约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null)?

原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了。在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段。

回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

比如用回调函数的方式实现多异步继发:

A(
  B(
    C(
      ...
    )
  )
)

多个异步继发就会出现我们不想见到的 callback hell

对于多个异步并发,所有异步完成后汇总和多个异步并发竞争,最先完成的执行,我们可以通过计数器去控制。

Promise

回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。

fs.readFile(fileA, 'utf-8', function (err, data) {
  fs.readFile(fileB, 'utf-8', function (err, data) {
    // ...
  });
});

不难想象,如果依次读取两个以上的文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。因为多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。这种情况就称为"回调函数地狱"(callback hell)。

Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。采用 Promise,连续读取多个文件,写法如下。

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});

上面代码中,我使用了fs-readfile-promise模块,它的作用就是返回一个 Promise 版本的readFile函数。Promise 提供then方法加载回调函数,catch方法捕捉执行过程中抛出的错误。

可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。

Generator

从惰性计算说起 -> 惰性计算 -> thunk -> 协程 -> 外部迭代器 -> 幂等性

事件监听

发布订阅

3. CSS 编写规范

🎈At-rule

  • at-rule-name-case: "lower", @命名规则:小写
  • at-rule-name-space-after: "always-single-line", 这里必须始终在单行声明块在规则名称后面有一个空格。
  • at-rule-semicolon-newline-after: "always" 有必须始终是分号后换行符

🎈Block rule

  • block-closing-brace-empty-line-before: "never", {闭合前不许有空行}
  • block-closing-brace-newline-after: "always" 有必须始终是右括号之后换行。
  • block-closing-brace-newline-before: "always-multi-line" 这里必须始终在多行块右括号之前有一个换行符。
  • block-closing-brace-space-before: "always-single-line" 这里必须始终在单行块右括号前一个空格。
  • block-no-empty: true 不允许空块
  • block-opening-brace-newline-after: "always-multi-line" 这里必须始终在多行块开括号后换行
  • block-opening-brace-space-after: "always-single-line", 这里必须始终在单行块开括号后有一个空格。
  • block-opening-brace-space-before: "always",有必须是开括号前一个空格

🎈Color

  • color-hex-case: "lower", 颜色编码小写
  • color-hex-length: "short", 短(速记变种:3、4个字符)
  • color-no-invalid-hex: true, 禁止无效的16进制色码 如位数错误,或数字错误

🎈Comment

  • comment-empty-line-before: [ "always", {
    except: ["first-nested"],
    ignore: ["stylelint-commands"],
    } ],
    注释前有一个空行,第一个孩子钱必须不空行,忽略传递命令stylelint注释
  • comment-no-empty: true, 注释不能为空
  • comment-whitespace-inside: "always", 注释符号与内容间有空格

Custom property

  • custom-property-empty-line-before: [ "always", {
    except: [
    "after-custom-property",
    "first-nested",
    ],
    ignore: [
    "after-comment",
    "inside-single-line-block",
    ],
    } ],
    自定义属性前有空行,嵌套自定义属性和它们的父节点的第一个不须空行,忽略评论后的空行

🎈Declaration

  • declaration-bang-space-after: "never", 生命时!后不许空格
  • declaration-bang-space-before: "always", !前有且仅有一个空格
  • declaration-block-no-duplicate-properties: [ true, {
    ignore: ["consecutive-duplicates-with-different-values"],
    } ],
    明块中禁止有重复的属性,忽略不同的值复制的连续性能。
  • declaration-block-no-redundant-longhand-properties: true, 禁止可以组合成一个速记属性普通写法的属性。如 padding-any
  • declaration-block-no-shorthand-property-overrides: true, 禁止重写相关的手写速记的属性的属性。
  • declaration-block-semicolon-newline-after: "always-multi-line",多行规则时,分号后必须换行符。
  • declaration-block-semicolon-space-after: "always-single-line", 单行声明块是,分号后面必须有一个空格。
  • declaration-block-semicolon-space-before: "never",分号前禁止空格
  • declaration-block-single-line-max-declarations: 1, 限制单行声明块内声明的数量。
  • declaration-block-trailing-semicolon: "always",有必须是一个尾随分号
  • declaration-colon-newline-after: "always-multi-line", 生命属性值较多时,冒号后必须换行
  • declaration-colon-space-after: "always-single-line", 单行是冒号后必须空格
  • declaration-colon-space-before: "never", 冒号前禁止空格
  • declaration-empty-line-before: [ "always", {
    except: [
    "after-declaration",
    "first-nested",
    ],
    ignore: [
    "after-comment",
    "inside-single-line-block",
    ],
    } ],

Font family

  • font-family-no-duplicate-names: true, 禁止重复的字体系列名称。

Function

  • function-calc-no-unspaced-operator: true,
    操作前后,必须有一个空格或一个换行符加缩进
  • function-comma-newline-after: "always-multi-line", 函数多行数在逗号后换行 translate(a, b)
  • function-comma-space-after: "always-single-line", 函数单行是逗号后空一格
  • function-comma-space-before: "never", 逗号前不空格
  • function-linear-gradient-no-nonstandard-direction: true,
  • function-max-empty-lines: 0,函数内不允许有空航
  • function-name-case: "lower",
  • function-parentheses-newline-inside: "always-multi-line", 有必须始终是多线功能的括号内换行。
  • function-parentheses-space-inside: "never-single-line", 单行是空号和内容不得有空格
  • function-whitespace-after: "always",

General / Sheet

  • "indentation": 2, 缩进空格数
  • keyframe-declaration-no-important: true, !important 单独一个声明块
  • length-zero-no-unit: true, 0后无长度单位
  • max-empty-lines: 1,

Media feature

  • media-feature-colon-space-after: "always",冒号后有且只有一个空格
  • media-feature-colon-space-before: "never",冒号前无空格 如: @media (max-width: 600px) {};
  • media-feature-name-case: "lower", 媒体功能名小写
  • media-feature-name-no-unknown: true,
  • media-feature-parentheses-space-inside: "never", 括号与内容家不许有空格
  • media-feature-range-operator-space-after: "always", 运算符后有空格
  • media-feature-range-operator-space-before: "always", 运算符前有空格 如:@media (max-width >= 600px) {}
  • media-query-list-comma-newline-after: "always-multi-line", 这里必须始终在多行媒体查询列表中的逗号后换行。
  • media-query-list-comma-space-after: "always-single-line", 这里必须始终在单行媒体查询列表中的逗号后空格。
  • media-query-list-comma-space-before: "never",
  • no-empty-source: true, 资源禁止为空
  • no-eol-whitespace: true, 尾部不能为空
  • no-extra-semicolons: true,
  • no-invalid-double-slash-comments: true, no //
  • no-missing-end-of-source-newline: true, 结束时空一行
  • number-leading-zero: "always", .5 ❌; 0.5 ✔️
  • number-no-trailing-zeros: true, 1.0 ❌; 1 ✔️
  • property-case: "lower", 属性名称小写
  • property-no-unknown: true,

Rule

  • rule-nested-empty-line-before: [ "always-multi-line", {
    except: ["first-nested"],
    ignore: ["after-comment"],
    } ],
  • rule-non-nested-empty-line-before: [ "always-multi-line", {
    ignore: ["after-comment"]`
    } ],
    多行规则前有一个空行。 忽略注释之后随之而来的规则。

Selector

  • selector-attribute-brackets-space-inside: "never", [禁止空白] 如:[target] {}

  • selector-attribute-operator-space-after: "never", []选择器中的运算符前后不允许有空格

  • selector-attribute-operator-space-before: "never",

  • selector-combinator-space-after: "always",

  • selector-combinator-space-before: "always",
    组合选择器如:~ > + 前后必须有空格

  • selector-descendant-combinator-no-non-space: true, .foo .bar {} 有且只有一个空格

  • selector-list-comma-newline-after: "always", 有必须始终是逗号后换行。如:
    a,

    b {}

  • selector-list-comma-space-before: "never", 逗号前不允许有空行

  • selector-max-empty-lines: 0,

  • selector-pseudo-class-case: "lower", 伪类名称小写

  • selector-pseudo-class-no-unknown: true,

  • selector-pseudo-class-parentheses-space-inside: "never", 如:input:not([type="submit"]) {}

  • selector-pseudo-element-case: "lower",

  • selector-pseudo-element-colon-notation: "double", ::

  • selector-pseudo-element-no-unknown: true,

  • selector-type-case: "lower",

  • selector-type-no-unknown: true,

🎈Shorthand property

  • shorthand-property-no-redundant-values: true, 在简写属性不允许重复值。

🎈String

  • string-no-newline: true,

🎈Unit

  • unit-case: "lower",
  • unit-no-unknown: true,

Value list

  • value-list-comma-newline-after: "always-multi-line", 属性值多行是可用逗号后换行
  • value-list-comma-space-after: "always-single-line",
  • value-list-comma-space-before: "never",
  • value-list-max-empty-lines: 0,

2. Javascript 编写规范

Airbnb React/JSX 编码规范

算是最合理的React/JSX编码规范之一了

内容目录

  1. 基本规范
  2. Class vs React.createClass vs stateless
  3. 命名
  4. 声明模块
  5. 代码对齐
  6. 单引号还是双引号
  7. 空格
  8. 属性
  9. Refs引用
  10. 括号
  11. 标签
  12. 函数/方法
  13. 模块生命周期
  14. isMounted

Basic Rules 基本规范

  • 每个文件只写一个模块.
  • 推荐使用JSX语法.
  • 不要使用 React.createElement,除非从一个非JSX的文件中初始化你的app.

创建模块

Class vs React.createClass vs stateless

  • 如果你的模块有内部状态或者是refs, 推荐使用 class extends React.Component 而不是 React.createClass ,除非你有充足的理由来使用这些方法.
    eslint: react/prefer-es6-class react/prefer-stateless-function

    // bad
    const Listing = React.createClass({
      // ...
      render() {
        return <div>{this.state.hello}</div>;
      }
    });
    
    // good
    class Listing extends React.Component {
      // ...
      render() {
        return <div>{this.state.hello}</div>;
      }
    }

    如果你的模块没有状态或是没有引用refs, 推荐使用普通函数(非箭头函数)而不是类:

    // bad
    class Listing extends React.Component {
      render() {
        return <div>{this.props.hello}</div>;
      }
    }
    
    // bad (relying on function name inference is discouraged)
    const Listing = ({ hello }) => (
      <div>{hello}</div>
    );
    
    // good
    function Listing({ hello }) {
      return <div>{hello}</div>;
    }

Naming 命名

  • 扩展名: React模块使用 .jsx 扩展名.

  • 文件名: 文件名使用驼峰式. 如, ReservationCard.jsx.

  • 引用命名: React模块名使用驼峰式命名,实例使用骆驼式命名. eslint: react/jsx-pascal-case

    // bad
    import reservationCard from './ReservationCard';
    
    // good
    import ReservationCard from './ReservationCard';
    
    // bad
    const ReservationItem = <ReservationCard />;
    
    // good
    const reservationItem = <ReservationCard />;
  • 模块命名: 模块使用当前文件名一样的名称. 比如 ReservationCard.jsx 应该包含名为 ReservationCard的模块. 但是,如果整个文件夹是一个模块,使用 index.js作为入口文件,然后直接使用 index.js 或者文件夹名作为模块的名称:

    // bad
    import Footer from './Footer/Footer';
    
    // bad
    import Footer from './Footer/index';
    
    // good
    import Footer from './Footer';
  • 高阶模块命名: 对于生成一个新的模块,其中的模块名 displayName 应该为高阶模块名和传入模块名的组合. 例如, 高阶模块 withFoo(), 当传入一个 Bar 模块的时候, 生成的模块名 displayName 应该为 withFoo(Bar).

为什么?一个模块的 displayName 可能会在开发者工具或者错误信息中使用到,因此有一个能清楚的表达这层关系的值能帮助我们更好的理解模块发生了什么,更好的Debug.

```jsx
// bad
export default function withFoo(WrappedComponent) {
  return function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }
}

// good
export default function withFoo(WrappedComponent) {
  function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }

  const wrappedComponentName = WrappedComponent.displayName
    || WrappedComponent.name
    || 'Component';

  WithFoo.displayName = `withFoo(${wrappedComponentName})`;
  return WithFoo;
}
```
  • 属性命名: 避免使用DOM相关的属性来用作其他的用途。

为什么?对于styleclassName这样的属性名,我们都会默认它们代表一些特殊的含义,如元素的样式,CSS class的名称。在你的应用中使用这些属性来表示其他的含义会使你的代码更难阅读,更难维护,并且可能会引起bug。

```jsx
// bad
<MyComponent style="fancy" />

// good
<MyComponent variant="fancy" />
```

Declaration 声明模块

  • 不要使用 displayName 来命名React模块,而是使用引用来命名模块, 如 class 名称.

    // bad
    export default React.createClass({
      displayName: 'ReservationCard',
      // stuff goes here
    });
    
    // good
    export default class ReservationCard extends React.Component {
    }

Alignment 代码对齐

  • 遵循以下的JSX语法缩进/格式. eslint: react/jsx-closing-bracket-location

    // bad
    <Foo superLongParam="bar"
         anotherSuperLongParam="baz" />
    
    // good, 有多行属性的话, 新建一行关闭标签
    <Foo
      superLongParam="bar"
      anotherSuperLongParam="baz"
    />
    
    // 若能在一行中显示, 直接写成一行
    <Foo bar="bar" />
    
    // 子元素按照常规方式缩进
    <Foo
      superLongParam="bar"
      anotherSuperLongParam="baz"
    >
      <Quux />
    </Foo>

Quotes 单引号还是双引号

  • 对于JSX属性值总是使用双引号("), 其他均使用单引号. eslint: jsx-quotes

为什么? JSX属性 不能包括转译的引号, 因此在双引号里包括像 "don't" 的属性值更容易输入.
HTML属性也是用双引号,所以JSX属性也遵循同样的语法.

```jsx
// bad
<Foo bar='bar' />

// good
<Foo bar="bar" />

// bad
<Foo style={{ left: "20px" }} />

// good
<Foo style={{ left: '20px' }} />
```

Spacing 空格

  • 总是在自动关闭的标签前加一个空格,正常情况下也不需要换行. eslint: no-multi-spaces, react/jsx-space-before-closing

    // bad
    <Foo/>
    
    // very bad
    <Foo                 />
    
    // bad
    <Foo
     />
    
    // good
    <Foo />
  • 不要在JSX {} 引用括号里两边加空格. eslint: react/jsx-curly-spacing

    // bad
    <Foo bar={ baz } />
    
    // good
    <Foo bar={baz} />

Props 属性

  • JSX属性名使用骆驼式风格camelCase.

    // bad
    <Foo
      UserName="hello"
      phone_number={12345678}
    />
    
    // good
    <Foo
      userName="hello"
      phoneNumber={12345678}
    />
  • 如果属性值为 true, 可以直接省略. eslint: react/jsx-boolean-value

    // bad
    <Foo
      hidden={true}
    />
    
    // good
    <Foo
      hidden
    />
  • <img> 标签总是添加 alt 属性. 如果图片以presentation(感觉是以类似PPT方式显示?)方式显示,alt 可为空, 或者<img> 要包含role="presentation". eslint: jsx-a11y/img-has-alt

    // bad
    <img src="hello.jpg" />
    
    // good
    <img src="hello.jpg" alt="Me waving hello" />
    
    // good
    <img src="hello.jpg" alt="" />
    
    // good
    <img src="hello.jpg" role="presentation" />
  • 不要在 alt 值里使用如 "image", "photo", or "picture"包括图片含义这样的词, 中文也一样. eslint: jsx-a11y/img-redundant-alt

为什么? 屏幕助读器已经把 img 标签标注为图片了, 所以没有必要再在 alt 里说明了.

```jsx
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />

// good
<img src="hello.jpg" alt="Me waving hello" />
```
  • 使用有效正确的 aria role属性值 ARIA roles. eslint: jsx-a11y/aria-role

    // bad - not an ARIA role
    <div role="datepicker" />
    
    // bad - abstract ARIA role
    <div role="range" />
    
    // good
    <div role="button" />
  • 不要在标签上使用 accessKey 属性. eslint: jsx-a11y/no-access-key

为什么? 屏幕助读器在键盘快捷键与键盘命令时造成的不统一性会导致阅读性更加复杂.

// bad
<div accessKey="h" />

// good
<div />
  • 避免使用数组的index来作为属性key的值,推荐使用唯一ID. (为什么?)
// bad
{todos.map((todo, index) =>
  <Todo
    {...todo}
    key={index}
  />
)}

// good
{todos.map(todo => (
  <Todo
    {...todo}
    key={todo.id}
  />
))}

Refs

  • 总是在Refs里使用回调函数. eslint: react/no-string-refs

    // bad
    <Foo
      ref="myRef"
    />
    
    // good
    <Foo
      ref={ref => { this.myRef = ref; }}
    />

Parentheses 括号

  • 将多行的JSX标签写在 ()里. eslint: react/wrap-multilines

    // bad
    render() {
      return <MyComponent className="long body" foo="bar">
               <MyChild />
             </MyComponent>;
    }
    
    // good
    render() {
      return (
        <MyComponent className="long body" foo="bar">
          <MyChild />
        </MyComponent>
      );
    }
    
    // good, 单行可以不需要
    render() {
      const body = <div>hello</div>;
      return <MyComponent>{body}</MyComponent>;
    }

Tags 标签

  • 对于没有子元素的标签来说总是自己关闭标签. eslint: react/self-closing-comp

    // bad
    <Foo className="stuff"></Foo>
    
    // good
    <Foo className="stuff" />
  • 如果模块有多行的属性, 关闭标签时新建一行. eslint: react/jsx-closing-bracket-location

    // bad
    <Foo
      bar="bar"
      baz="baz" />
    
    // good
    <Foo
      bar="bar"
      baz="baz"
    />

Methods 函数

  • 使用箭头函数来获取本地变量.

    function ItemList(props) {
      return (
        <ul>
          {props.items.map((item, index) => (
            <Item
              key={item.key}
              onClick={() => doSomethingWith(item.name, index)}
            />
          ))}
        </ul>
      );
    }
  • 当在 render() 里使用事件处理方法时,提前在构造函数里把 this 绑定上去. eslint: react/jsx-no-bind

为什么? 在每次 render 过程中, 再调用 bind 都会新建一个新的函数,浪费资源.

```jsx
// bad
class extends React.Component {
  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv.bind(this)} />
  }
}

// good
class extends React.Component {
  constructor(props) {
    super(props);

    this.onClickDiv = this.onClickDiv.bind(this);
  }

  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv} />
  }
}
```
  • 在React模块中,不要给所谓的私有函数添加 _ 前缀,本质上它并不是私有的.

为什么?_ 下划线前缀在某些语言中通常被用来表示私有变量或者函数。但是不像其他的一些语言,在JS中没有原生支持所谓的私有变量,所有的变量函数都是共有的。尽管你的意图是使它私有化,在之前加上下划线并不会使这些变量私有化,并且所有的属性(包括有下划线前缀及没有前缀的)都应该被视为是共有的。了解更多详情请查看Issue #1024, 和 #490

```jsx
// bad
React.createClass({
  _onClickSubmit() {
    // do stuff
  },

  // other stuff
});

// good
class extends React.Component {
  onClickSubmit() {
    // do stuff
  }

  // other stuff
}
```
  • render 方法中总是确保 return 返回值. eslint: react/require-render-return

    // bad
    render() {
      (<div />);
    }
    
    // good
    render() {
      return (<div />);
    }

Ordering React 模块生命周期

  • class extends React.Component 的生命周期函数:
  1. 可选的 static 方法
  2. constructor 构造函数
  3. getChildContext 获取子元素内容
  4. componentWillMount 模块渲染前
  5. componentDidMount 模块渲染后
  6. componentWillReceiveProps 模块将接受新的数据
  7. shouldComponentUpdate 判断模块需不需要重新渲染
  8. componentWillUpdate 上面的方法返回 true, 模块将重新渲染
  9. componentDidUpdate 模块渲染结束
  10. componentWillUnmount 模块将从DOM中清除, 做一些清理任务
  11. 点击回调或者事件处理器onClickSubmit()onChangeDescription()
  12. render 里的 getter 方法getSelectReason()getFooterContent()
  13. 可选的 render 方法renderNavigation()renderProfilePicture()
  14. render render() 方法
  • 如何定义 propTypes, defaultProps, contextTypes, 等等其他属性...

    import React, { PropTypes } from 'react';
    
    const propTypes = {
      id: PropTypes.number.isRequired,
      url: PropTypes.string.isRequired,
      text: PropTypes.string,
    };
    
    const defaultProps = {
      text: 'Hello World',
    };
    
    class Link extends React.Component {
      static methodsAreOk() {
        return true;
      }
    
      render() {
        return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>
      }
    }
    
    Link.propTypes = propTypes;
    Link.defaultProps = defaultProps;
    
    export default Link;
  • React.createClass 的生命周期函数,与使用class稍有不同: eslint: react/sort-comp

  1. displayName 设定模块名称
  2. propTypes 设置属性的类型
  3. contextTypes 设置上下文类型
  4. childContextTypes 设置子元素上下文类型
  5. mixins 添加一些mixins
  6. statics
  7. defaultProps 设置默认的属性值
  8. getDefaultProps 获取默认属性值
  9. getInitialState 或者初始状态
  10. getChildContext
  11. componentWillMount
  12. componentDidMount
  13. componentWillReceiveProps
  14. shouldComponentUpdate
  15. componentWillUpdate
  16. componentDidUpdate
  17. componentWillUnmount
  18. clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
  19. getter methods for render like getSelectReason() or getFooterContent()
  20. Optional render methods like renderNavigation() or renderProfilePicture()
  21. render

isMounted

为什么? isMounted 反人类设计模式:(), 在 ES6 classes 中无法使用, 官方将在未来的版本里删除此方法.

JavaScript 内存管理

程序运行需要内存

程序的运行需要内存,而计算机的内存资源是有限的,所以我们要对内存资源进行合理有效的管理。对于持续运行的程序,我们除了要分配内存运行,也要及时的回收不再使用内存,否则内存占用会越来越高,导致系统性能问题或程序崩溃。内存泄漏就是不再使用的内存而没有及时回收。

内存生命周期

手动管理内存

对于一些语言(比如C语言),为开发者提供了malloc(),free()方法进行管理内存。开发者可以根据自身的需要进行分配内存,同时也要手动处理内存回收,对内存的管理具有很高的灵活性,当然也增加了额外的管理工作。

托管管理内存

现代的一些语言,增加了垃圾回收机制,提供托管内存管理,减轻开发者的负担。

垃圾回收机制

内存泄漏的调试方法

常见的导致内存泄漏的原因

2019 OKR

image

2019年,充满无数可能的一年,列了一下自己19年能够想到的事情,年中可能会调整,完成70%就算是满意。

  • 杭州定居
    1. 新工作
    2. 新房子
    3. 杭州落户
  • 职业发展
    1. 前端技术专家
    2. 技术管理初中级
  • 财务目标
    1. 总资产翻一倍
    2. 投资渠道多样化
      a. 固定资产 50%
      b. 股市 定投10w
      c. 货币基金 剩余资产
  • 学习计划
    1. web开发
    1. web图形
  • 新蜂工作室
    1. 重新运作
  • 个人成长
    1. 看10本书
    1. 学习剪辑
    2. 写作
    3. 烹饪
  • 产品
    1. 菠萝文档
  • 健康
    1. 减脂
    2. plasma项目
    3. 体检

webpack 使用备忘录

常用模块处理

ts

css

文件

工程化

文件指纹策略

构建优化

webpack 原理

loader 和 插件

iOS app 发布流程

iOS app 发布流

iOS app 发布流程大致归纳有以下几点:

  1. 创建发布证书
  2. 创建App ID
  3. 创建对应的Provisioning Profile文件
  4. 设置 Bundle ID 和 App ID一致
  5. 设置Xcode Code Sign Identifer,选择合适的Profile和证书进行签名
  6. 打包,上传,提交,发布

准备工作

  1. 安装 Xcode
  2. 创建 Xcode project
  3. 注册苹果开发者账号

创建证书

创建发布证书

打开 Xcode 账户设置

在第一栏的证书中的 iOS Distribution, 点击创建。本地就会生成开发所需要的发布证书。

在本地运用 Keychain 中,可以查看下载的证书。

确认证书是包含私钥的,发布证书缺少私钥就无法使用,所以要对证书进行备份。

也可以登录 developer.apple.com/account,进行发布证书的创建

创建 App ID

  1. 登录 developer.apple.com/account, 选择 Indentifiers 中的 App IDs。
  2. 点击加好进行创建

创建 provisioning profile 文件

  1. 登录 developer.apple.com/account, 选择 Provisioning Profiles 中的 All。
  2. 点击加好进行创建

    发布类型有 App Store, tvOS App Store, Ad Hoc tvOS Ad Hoc, 根据自身发布需求进行选择。

配置

添加账户

在 Xcode > Preferences 中的 Accouts 里添加开发者账号

安装证书和provisioniong profile 文件

点击开发者账号,下载所需的证书和 pp文件

也可以去 developer.apple.com/account 进行下载

配置 code sign


在该界面配置 bundleid, version, build, team。 配置项需和证书,pp文件一致。

打包


选择通用设备,点击 Product > Archive 进行打包

上传

打包后,点击 Upload to App Store

提交、发布

登录 iTunes Connect, 对上传的app 进行提交,待苹果审核通过后便可以进行发布。

稀碎总结

css父块级元素透明,子级元素不透明的方法

web app 发布流程

web 开发流程
关于web的开发流程大家在工作中都有自己的一套工作流,这里简单讲一下前端组sona的开发工作流。
明确需求
拿到产品经理需求后一定要仔细阅读,主要注意需求的数据流、逻辑、交互。
数据流:根据页面承载的信息的数据流来确定Reducer的数据结构,要分析数据的流向,常常会发现需求有些数据无源而来,无源而去。
逻辑:整个业务流的逻辑是否合理,逻辑能否完全控制数据加工、流向。
交互:分析页面交互合理性,具体设备展现合理性。
技术评估
评估技术实现难度,是否需要先进行调研。开发工期较紧时,能否简化实现。
工期评估
确定业务的特性分支,写ROADMAP.md文档
最好对所做的业务能有工期的估计
一般一个后台的页面,列表展示一般为(2个小时)
一般一个具体页面平均工时为半天,组件无法复用复杂度再另估
一般一个微信页面工时为半天,组件无法复用复杂度另估
对接资源
跟UI设计对接UI素材
将素材上传OSS,cdn加速
跟后端开发对接rap,并根据rap确定reducer数据结构
基于master分支创建特性分支
联调
提测
merge to test
envoyer自动部署
envoyer有时可能因为网络抖动的原因无法部署,可以登录envoyer.io检查和手动部署
上线
merge to master
去envoyer.io手动部署上线
web 上线流程
把特性分支merge到dev分支上解决冲突
把特性分支merge到test分支上提测
把特性分支merge到master分支上上线
关于上线部署操作参考 web开发流程第6,7点
CLI命令
npm run dev 本地开发
npm run test 生成提测环境资源
npm run build 生成生产资源

Lint

什么是 Lint

在计算机科学中,lint是一种工具程序的名称,它用来标记源代码中,某些可疑的、不具结构性(可能造成bug)的段落。它是一种静态程序分析工具,最早适用于C语言,在UNIX平台上开发出来。后来它成为通用术语,可用于描述在任何一种计算机程序语言中,用来标记源代码中有疑义段落的工具。

为什么使用 Lint

对于个人而言,Lint 可以帮助开发者避免部分错误。
对于大型多人开发的项目,Lint 可以帮助团队统一编程风格,增加工程健壮性。

常见的 JavaScript Lint 工具

关于这几种 Lint 工具的对比可参考 JavaScript 代码静态质量检查
关于 JavaScript 编程规范可参考 JavaScript 编写规范

常见的 CSS Lint 工具

关于这几种 Lint 工具的对比可参考 CSS 代码静态质量检查
关于 CSS 编程规范可参考 CSS 编写规范

静态类型检查工具

CLI 工具

我写了一个前端基础设施的CLI工具,lint工具选取的是 ESLint + styleLint + flow
详细使用请见 rrn-cli

前端基础架构

JavaScript Style

lint

doc

脚手架

pack

config

Mock和联调

UI 测试

错误监控系统

CI

功能库

组件库

统计系统

业务层

命令行程序开发

命令行程序开发教程

一种编程语言是否易用,很大程度上,取决于开发命令行程序的能力。

Node.js 作为目前最热门的开发工具之一,怎样使用它开发命令行程序,是 Web 开发者应该掌握的技能。

本文同过四个方面讲解命令行程序开发,它们各有长短,使用者应根据自身环境选择合适的方式。

一、 可执行脚本

Hello World

There must have a "Hello World" way to program.

hello.js

#!/usr/bin/env node

console.log('hello world');

现在 hello 就可以执行了。

node ./hello.js

如果想把前面的 node 去掉,修改 hello.js 文件权限再执行

chmod 755 hello.js

./hello.js

如果想把 hello 前面的路径去除,可以将 hello 的路径加入环境变量 PATH

命令行参数

系统参数可以通过 process.argv 来获取。

argvs.js

#!/usr/bin/env node

console.log(process.argv[2]);

执行 node ./argvs.js hello 将打印出 "hello"

细可参考 doc

新建进程

脚本可以通过 child_process 模块新建子进程,从而执行 Unix 系统命令。

child-porcess.js

#!/usr/bin/env node
var name = process.argv[2];
var exec = require('child_process').exec;

var child = exec('echo hello ' + name, function(err, stdout, stderr) {
if (err) throw err;
  console.log(stdout);
});

更多工具

为了提高开发效率,我们可以使用一些封装的现有工具。

shell.js

shelljs 模块重新包装了 child_process,调用系统命令更加方便。

ShellJS is a portable (Windows/Linux/OS X) implementation of Unix shell commands on top of the Node.js API. You can use it to eliminate your shell script's dependency on Unix while still keeping its familiar and powerful commands. You can also install it globally so you can run it from outside Node projects - say goodbye to those gnarly Bash scripts!

文档

commander.js

commander.js 是 node.js 命令行开发的完整解决方案,TJ 大神出品。

选择项解析
#!/usr/bin/env node

/**
* Module dependencies.
*/

var program = require('commander');

program
.version('0.0.1')
.option('-p, --peppers', 'Add peppers')
.option('-P, --pineapple', 'Add pineapple')
.option('-b, --bbq-sauce', 'Add bbq sauce')
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
.parse(process.argv);

console.log('you ordered a pizza with:');
if (program.peppers) console.log(' - peppers');
if (program.pineapple) console.log(' - pineapple');
if (program.bbqSauce) console.log(' - bbq');
console.log(' - %s cheese', program.cheese);

选项通过optin()函数定义。

子命令

当定义.command() 却没有对应的.action(callback), 就会由子命令进行处理

// file: ./examples/pm
var program = require('..');

program
.version('0.0.1')
.command('install [name]', 'install one or more packages')
.command('search [query]', 'search with optional query')
.command('list', 'list packages installed', {isDefault: true})
.parse(process.argv);

子命令应该在该目录下,命名为"program-command"

此例 ./examples/pm 目录下应该有 pm-install pm-search pm-list

帮助文档

commander 会根据 option 自动生成文档

更多请参考 commander.js 文档

yarg.js

参数解析
#!/usr/bin/env node
var argv = require('yargs').argv;

console.log('hello ', argv.name);

使用下面两种方式都可以

$ hello --name=tom
hello tom

$ hello --name tom
hello tom
参数配置
  • demand:是否必选
  • default:默认值
  • describe:提示
#!/usr/bin/env node
var argv = require('yargs')
.option('n', {
  alias : 'name',
  demand: true,
  default: 'tom',
  describe: 'your name',
  type: 'string'
})
.argv;

console.log('hello ', argv.n);

上面代码指定 -n / --name 不可省略, 默认值是 tom, 并给出一行提示。

帮助信息

根据option() describe 生成

子命令
#!/usr/bin/env node
var argv = require('yargs')
.command("morning", "good morning", function (yargs) {
  console.log("Good Morning");
})
.command("evening", "good evening", function (yargs) {
  console.log("Good Evening");
})
.argv;

console.log('hello ', argv.n);

用法:

$ hello morning -n tom
Good Morning
hello tom

其他事项

1. 返回值

根据 Unix 传统,程序执行成功返回 0,否则返回 1 。

2. 从定向

Unix 允许程序之间使用管道重定向数据。

$ ps aux | grep 'node'

脚本可以通过监听标准输入的data 事件,获取重定向的数据。

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(data) {
  process.stdout.write(data);
});

3. 系统信号

操作系统可以向执行中的进程发送信号,process 对象能够监听信号事件。

process.on('SIGINT', function () {
  console.log('Got a SIGINT');
  process.exit(0);
});

二、 npm script

Node 开发离不开 npm,而脚本功能是 npm 最强大、最常用的功能之一,如果对上面的脚本编写有所了解,下面我们可以通过 npm script 对脚本进行组合和执行。

npm 使用 scripts 字段定义命令。

{
  // ...
  "scripts": {
    "hello": "node ./scripts/hello.js",
  }
}

通过 npm run 执行命令

npm run hello === node ./hello.js

2.1 原理

npm 脚本的原理非常简单。每当执行 npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的脚本命令。因此,只要是 Shell(一般是 Bash)可以运行的命令,就可以写在 npm 脚本里面。

比较特别的是,npm run新建的这个 Shell,会将当前目录的node_modules/.bin子目录加入PATH变量,执行结束后,再将PATH变量恢复原样。

这意味着,当前目录的node_modules/.bin子目录里面的所有脚本,都可以直接用脚本名调用,而不必加上路径。比如,当前项目的依赖里面有 eslint,只要直接写eslint **.js 就可以了。

"lint": "eslint **.js"

而不用写成下面这样。

"lint": "node_modules/.bin/eslint **.*js"

我们也可以通过 package.json 中的 bin 字段定义可执行脚本的路径。

{
  // ...
  "bin": {
    "hello": "./scripts/hello.js"
  },
  "scripts": {
    "hello": "node hello",
  }
}

执行 npm link 可以把 hello 的路径加入环境变量PATH, 直接执行$ hello, 就不用输入路径了。

2.2 其他

2.2.1 通配符

* 表示任意文件名,** 表示任意一层子目录

2.2.2 传参

通过 -- 向 npm 脚本传入参数。

"lint": "eslint **.js"

向上面的 npm run lint 命令传入参数,必须写成下面这样。

$ npm run lint -- --reporter checkstyle > checkstyle.xml

2.2.3 执行顺序

  • 如果是并行执行(即同时的平行执行),可以使用&符号。
  • 如果是继发执行(即只有前一个任务成功,才执行下一个任务),可以使用&&符号。

2.2.4 钩子

npm 脚本有 prepost 两个钩子。举例来说,build 脚本命令的钩子就是 prebuild 和 postbuild。

"prebuild": "echo I run before the build script",
"build": "cross-env NODE_ENV=production webpack",
"postbuild": "echo I run after the build script"

2.2.5 变量

通过 npm_package_ 前缀,npm 脚本可以拿到 package.json 里面的字段。比如,下面是一个 package.json。

{
  "name": "hello",
  "version": "1.0.0"
}

那么,变量 npm_package_name 返回 hello,变量 npm_package_version 返回 1.0.0

console.log(process.env.npm_package_name); // hello
console.log(process.env.npm_package_version); // 1.0.0

如果是 Bash 脚本,可以用 $npm_package_name$npm_package_version 取到这两个值。

三、 构建工具

为什么使用构建工具?

一句话:自动化。

对于需要反复重复的任务,例如压缩(minification)、编译、单元测试、linting等,自动化工具可以减轻你的劳动,简化你的工作。

当你配置好项目构建工具,任务运行器就会自动帮你或你的小组完成大部分无聊的工作。

3.1 grunt

3.1.1 安装CLI

npm install -g grunt-cli

3.1.2 Grunt 项目结构

一般需要在你的项目中添加两份文件:package.json 和 Gruntfile。

package.json: 此文件被npm用于存储项目的元数据,以便将此项目发布为npm模块。你可以在此文件中列出项目依赖的grunt和Grunt插件,放置于devDependencies配置段内。

Gruntfile: 此文件被命名为 Gruntfile.js 或 Gruntfile.coffee,用来配置或定义任务(task)并加载Grunt插件的。 此文档中提到的 Gruntfile 其实说的是一个文件,文件名是 Gruntfile.js 或 Gruntfile.coffee。

3.1.3 Gruntfile 结构

Gruntfile由以下几部分构成:

  • "wrapper" 函数
  • 项目与任务配置
  • 加载grunt插件和任务
  • 自定义任务

通过一个简单的 Gruntfile.js 来讲解

module.exports = function(grunt) {

// Project configuration.
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  uglify: {
     options: {
     banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
   },
    build: {
    src: 'src/<%= pkg.name %>.js',
    dest: 'build/<%= pkg.name %>.min.js'
    }
  }
});

// 加载包含 "uglify" 任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');

// 自定义 任务 "hello"
grunt.registerTask('hello', 'Log hello.', function() {
  grunt.log.write('Logging hello...').ok();
});

// 默认被执行的任务列表。
grunt.registerTask('default', ['uglify', 'hello']);

};
"wrapper" 函数

每一份 Gruntfile (和grunt插件)都遵循同样的格式,你所书写的Grunt代码必须放在此函数内:

module.exports = function(grunt) {
  // Do grunt-related things in here
};
项目和任务配置

大部分的Grunt任务都依赖某些配置数据,这些数据被定义在一个object内,并传递给 grunt.initConfig 方法。

在下面的案例中,grunt.file.readJSON('package.json') 将存储在package.json文件中的JSON元数据引入到grunt config中。 由于<% %>模板字符串可以引用任意的配置属性,因此可以通过这种方式来指定诸如文件路径和文件列表类型的配置数据,从而减少一些重复的工作。

你可以在这个配置对象中(传递给initConfig()方法的对象)存储任意的数据,只要它不与你任务配置所需的属性冲突,否则会被忽略。此外,由于这本身就是JavaScript,你不仅限于使用JSON;你可以在这里使用任意的有效的JS代码。如果有必要,你甚至可以以编程的方式生成配置。

与大多数task一样,grunt-contrib-uglify 插件中的uglify 任务要求它的配置被指定在一个同名属性中。在这里有一个例子, 我们指定了一个banner选项(用于在文件顶部生成一个注释),紧接着是一个单一的名为build的uglify目标,用于将一个js文件压缩为一个目标文件。

// Project configuration.
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  uglify: {
    options: {
      banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    build: {
    src: 'src/<%= pkg.name %>.js',
    dest: 'build/<%= pkg.name %>.min.js'
    }
  }
});
加载 Grunt 插件和任务

像 concatenation、[minification]、grunt-contrib-uglify 和 linting这些常用的任务(task)都已经以grunt插件的形式被开发出来了。只要在 package.json 文件中被列为dependency(依赖)的包,并通过npm install安装之后,都可以在Gruntfile中以简单命令的形式使用:

// 加载能够提供"uglify"任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
自定义任务
// 自定义 任务 "hello"
grunt.registerTask('hello', 'Log hello.', function() {
  grunt.log.write('Logging hello...').ok();
});

gulp

四、 脚手架工具

yeoman

1. 开源项目目录规范

开源项目目录规范

此为前端开发团队遵循和约定的开源项目目录规范,意在实现开源项目目录结构的一致性。

说明

文档中使用的关键字「MUST」,「MUST NOT」,「REQUIRED」,「SHALL」,「SHALL
NOT」,「SHOULD」,「SHOULD NOT」,「RECOMMENDED」,「MAY」和「OPTIONAL」在RFC2119中被说明。

还未定稿,对规范中提及的点有不赞同的欢迎提出 issues 讨论。

目录规范

参加的目录结构为:

.
├── .editorconfig
├── .travis.yml
├── dist
├── doc
├── README.md
├── src

README.md

每个项目都必须「MUST」包含一个README.md文件,此文件中应当「SHOULD」概要描述此项目的功能和特点等信息。

.editorconfig

每个项目应当「SHOULD」包含.editorconfig,用来统一配置编辑器的换行、缩进存储格式,使用方式请参考editorconfig是什么?

src

项目中所有 JS 源码应当「SHOULD」存放在此目录下,且所有JS文件编写应当「SHOULD」遵循Javascript 编码规范

css、themes、less、sass

样式类文件存放应当「SHOULD」遵循以下规律,且文件编写应当「SHOULD」遵循CSS 编码规范

  • 不带主题的样式文件应当「SHOULD」统一存放在 css 目录下面,且样式中使用的背景图片资源应当「SHOULD」统一存放在 css/images 目录下面。
  • 带主题的样式文件应当「SHOULD」统一存放在 themes 目录下对应的主题目录下,默认的主题应当「SHOULD」采用 default 作为主题名称,且应当「SHOULD」默认提供,样式中对应图片文件应当「SHOULD」存放在样式文件所在的主题目录下的 images 目录下。
  • less 格式的样式文件应当「SHOULD」统一存放在 less 目录下面。
  • sass 格式的样式文件应当「SHOULD」统一存放在 sass 目录下面。

doc

所有项目应当「SHOULD」包含一个 doc 目录,用来存放详细的 API 使用文档。

dist

dist 作为项目输出目录,所有编译生成、提供给用户使用的文件应当「SHOULD」存放在此目录。

为了让不太擅长 node.js 的用户可以正常使用编译后的代码,dist 目录应当「SHOULD」包含基本输出结果并提交在 github 中。

build

所有工具类脚本应当「SHOULD」放在此目录。

test

所有测试相关代码应当「SHOULD」放在此目录。

src_cov

为了测试代码覆盖率,所有为测试覆盖率生成的新 JS 文件应当「SHOULD」存放在此目录下面。

猫腻和他的2017

  • 巩固 web 开发基础
    • Javascript 巩固
    • HTML5 巩固
    • CSS 巩固
    • DOM 操作
    • 基础数据结构
    • 基础算法
    • 前端工程
    • web动画
    • Google Web Fundamentals
  • 深入 Native 开发
    • iOS
      • OC
      • Swift
    • Android
      这部分要在三月份完成
  • 开发 Universal App
    这部分内容要在四月份完成
  • 同构架构
  • 后端开发
  • 项目管理和团队建设
  • 抽时间看源码
  • 英语学习
    • 系统学习
    • 考个托福不过分吧
  • 读书
    • 高性能网站建设指南
    • 高性能网站建设进阶指南
    • Web 性能权威指南
    • Javascript 权威指南
    • Javascript 设计模式与开发实践
    • HTML5权威指南
    • App 研发录
    • 软技能 代码之外的生存指南
    • 禅与摩托车维修艺术
    • 产品经理20堂必修课
    • 就喜欢你看不惯我又干不掉我的样子
    • 就喜欢你看不惯我又干不掉我的样子 2
    • HTML5 Canvas核心技术:图形、动画与游戏开发
    • About Face
    • 批判性思维
    • 学会提问
    • 演讲的艺术
    • 演讲的技术
    • 像TED一样演讲
    • 会演讲的人成功机会多两倍
  • 旅行
    十一:新西兰🇳🇿 或者 日本🇯🇵
  • 视频编辑
    • 七月份制作完一个视频:小七和小八
  • 锻炼
  • 调整作息
  • 学习平面设计
    设计只有心没有能力,自学感觉也是弱鸡,估计会报培训,同道之人可以走起~
  • 公开课
    • 斯坦福大学公开课:如何创业
    • 哈佛大学公开课:领袖心理学
  • 电子设备
    • Mac
    • VR
    • 🎧
  • 厨艺
  • 财富管理
  • 职业规划
  • 公众号运营
    • 关注人数过2000 不过分吧
    • 每月最少四篇文章不过分吧
  • 小物件
    • 夜光书签

4. Markdown 编写规范

Markdown 编写规范

此为前端开发团队遵循和约定的 Markdown 编写规范,意在提高文档的可读性。

说明

文档中使用的关键字「MUST」,「MUST NOT」,「REQUIRED」,「SHALL」,「SHALL
NOT」,「SHOULD」,「SHOULD NOT」,「RECOMMENDED」,「MAY」和「OPTIONAL」在 RFC2119 中有说明。

还未定稿,对规范中提及的点有不赞同的欢迎提出 issues(请添加 markdown 标签)讨论。

规则

  • 后缀必须「MUST」使用 .md

  • 文件名必须「MUST」使用小写,多个单词之间使用-分隔。

  • 文件编码必须「MUST」用 UTF-8。

  • 文档标题应该「SHOULD」这样写。

    Markdown 编写规范
    ==========================
    
  • 章节标题必须「MUST」以 ## 开始,而不是 #

  • 章节标题必须「MUST」在 # 后加一个空格,且后面没有 #

    // bad
    ##章节1
    
    // bad
    ## 章节1 ##
    
    // good
    ## 章节1
    
  • 章节标题和内容间必须「MUST」有一个空行。

    // bad
    ## 章节1
    内容
    ## 章节2
    
    // good
    ## 章节1
    
    内容
    
    ## 章节2
    
  • 代码段的必须「MUST」使用 Fenced code blocks 风格,如下所示:

      ```
      console.log("");
      ```
    
  • 表格的写法应该「SHOULD」参考 GFM,如下所示:

    First Header  | Second Header
    ------------- | -------------
    Content Cell  | Content Cell
    Content Cell  | Content Cell
    
    | Left-Aligned  | Center Aligned  | Right Aligned |
    | :------------ |:---------------:| -----:|
    | col 3 is      | some wordy text | $1600 |
    | col 2 is      | centered        |   $12 |
    | zebra stripes | are neat        |    $1 |
    
  • 中英文混排应该「SHOULD」采用如下规则:

    • 英文和数字使用半角字符
    • 中文文字之间不加空格
    • 中文文字与英文、阿拉伯数字及 @ # $ % ^ & * . ( ) 等符号之间加空格
    • 中文标点之间不加空格
    • 中文标点与前后字符(无论全角或半角)之间不加空格
    • 如果括号内有中文,则使用中文括号
    • 如果括号中的内容全部都是英文,则使用半角英文括号
    • 当半角符号 / 表示「或者」之意时,与前后的字符之间均不加空格
    • 其它具体例子推荐阅读这里
  • 中文符号应该「SHOULD」使用如下写法:

    • 用直角引号(「」)代替双引号(“”),不同输入法的具体设置方法请参考这里
    • 省略号使用「……」,而「。。。」仅用于表示停顿
    • 其它可以参考知乎规范
  • 表达方式,应当「SHOULD」遵循《The Element of Style》:

    • 使段落成为文章的单元:一个段落只表达一个主题
    • 通常在每一段落开始要点题,在段落结尾要扣题
    • 使用主动语态
    • 陈述句中使用肯定说法
    • 删除不必要的词
    • 避免连续使用松散的句子
    • 使用相同的结构表达并列的意思
    • 将相关的词放在一起
    • 在总结中,要用同一种时态(这里指英文中的时态,中文不适用,所以可以不理会)
    • 将强调的词放在句末

扩展阅读

  • Google 后来也出了 Markdown 规范,很多和这里是一样的,但也增加了一些约定,可以参考

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.