demo:http://9.135.218.65:8081/
素材示例:
这次就给一个已经运行几年的vue2项目加上单元测试。当中遇到了一些问题,但其实也是写单元测试会遇到的常见问题,在此做个实践记录
首先,要先安装好本次接入单元测试的相关依赖
1 | # unit testing |
或者
1 | # 安装 Jest 和 Vue Test Utils |
另外,项目为typescript项目,还需要安装typescript转码依赖
1 | npm install --save-dev jest-preset-typescript ts-jest @vue/cli-plugin-unit-jest @babel/preset-typescript |
至此,依赖安装完成
如果需要快速体验的话,也可以直接使用官方文档里的脚手架
1
2
3 git clone https://github.com/vuejs/vue-test-utils-getting-started
cd vue-test-utils-getting-started
npm install
jest的配置可以直接在package.json中写,或者新建文件 jest.config.js
1 | module.exports = { |
完成依赖安装和基本配置后,接下来就是具体单元测试代码的接入
1 | function sum(a: number, b: number) { |
1 | import { sum } from "./index"; |
1 | <template> |
1 | import { shallowMount } from '@vue/test-utils' |
1 | <template> |
1 | import { shallowMount } from '@vue/test-utils' |
1 | <template> |
1 | import { shallowMount } from '@vue/test-utils' |
从这里开始引入mock的概念
1 | <template> |
1 | import { shallowMount } from '@vue/test-utils' |
运行测试时,组件运行在jest-dom环境中(或开发人员配置的其它dom环境甚至node环境),而实际的业务代码有可能会在全局作用域或上层作用域中挂载一些属性在dom节点/window节点下,则组件中可以直接运行 window.xxx.xxx
这样的代码
但这样的代码,在进行测试时,会报错 xxx is not defined
此时,就要对组件中依赖到的对象进行mock
1 | <template> |
1 | import { shallowMount } from '@vue/test-utils' |
页面交互经常会涉及service api调用。有部分业界人士会直接启动一个mock server,测试时组件就直接向mock server请求数据,然后验证组件的状态变化是否符合期待
但也有部分业界人士比较习惯直接对http client进行mock。jest在这方面也提供了特性
1 | <template> |
1 | import { shallowMount } from '@vue/test-utils' |
1 | npx jest |
可以看到全量测试运行结果
1 | npx jest --coverage |
指令跑完后,在项目根目录下会生成 coverage
文件夹,里面的 lcov-report
文件夹包含html、js、css文件,可构建成一个静态站点,看到被测试代码的详细覆盖情况
针对变更文件进行测试,且每次文件变更并保存后触发测试
1 | npx jest --watch |
当前信用卡业务前端部分被统计的代码行数为2771行,单元测试覆盖行数为1259行,单元测试行覆盖率为45.43%
以下是详细的测试覆盖推进细节:
综上:
以上特指项目中的js、ts源码,不包含css、html等其他代码
jest中的Istanbul进行代码行数统计时,使用一套内部规则进行计算,并不受源码的回车换行符影响。故编辑器中显示代码文件的行数,和jest产出的测试覆盖报告的的代码行数并不是一个概念
这就是问题所在。
听过千百遍的人几乎是烦不胜烦,不知道是什么的人也是一头雾水。
不管你喜不喜欢,黑客增长法正在被不断使用。
而这也是我们每年都能看到几家新的创业公司的原因,其增长率绝对是荒谬的。
黑客增长术才出现了几年,但它已经火了。每个创业公司都在寻找增长黑客。
原因很显然:每个人都想以可怕的速度进行增长,获得数百万用户和美元的收入。
不过,黑客增长术到底意味着什么?
是时候一劳永逸地回答这个问题了。我甚至会在这份增长黑客指南中告诉你如何做。
我将涵盖很多信息,但你可以跳到下面的任何部分。
你有可能在线下进行增长黑客行为。例如,你可能会把 20 世纪 50 年代麦当劳在每个州际公路出口出现算作增长黑客行为。
他们意识到州际公路将是大势所趋,所以他们出现在他们知道客户会大量出现的地方。
然而,这个相当新的概念大多适用于初创公司的世界。他们没有庞大的营销预算,所以不能依靠超级碗广告或时代广场广告牌。
这就是为什么他们必须找到更便宜的方式来推销自己。
他们往往拥有的是一种非常可扩展的产品。
比如考虑一下Dropbox。他们的云存储服务提供的基本上只是服务器上的磁盘空间,可以通过互联网访问。
他们可以随时购买或租用更多的服务器,为新用户提供更多的空间。
或者考虑 Uber。这种出租车替代服务依靠普通人用自己的车在 A 地接别人,并把他们安全送到 B 地–而支付则通过应用进行。
2015 年美国有超过 2.6 亿辆注册汽车,这也是非常容易扩展的市场。他们提供应用,无限量的用户可以通过网络下载并使用。其余的由用户提供。
像肥皂这种传统产品,规模化程度不高。每次用完肥皂,你都要买新的肥皂。
但是,每当有另一个用户注册到 Facebook 时,你的体验就会变得更好。
另外,产品的运行方式让它可以推销自己。如果你周五晚上用 Uber 去朋友家,他们问你怎么去的,你就说:”我坐 Uber 去的。”
自然而然,这个词就会传播开来。如果你喜欢这个服务,并且有朋友可以从使用这个服务中受益(除了你从你的朋友在平台上受益之外),你很可能会给朋友分享他。
这就是增长黑客如何大规模地利用口碑来实现我们所看到的指数级增长率。
好了,是时候看看一些用正确方式进行增长黑客术的创业公司的例子了。
但今天,我不会只向你展示伟大的例子。我还会给你一个简单的八步流程,你可以按照这个流程来尝试在自己的业务中应用增长黑客术。
你会认为这对任何公司来说都是轻而易举的,对吗?
好吧,在以前,你有时可以摆脱一个平庸的产品,如果你只是营销它足够。
例如,可口可乐多年来推出了很多其他软饮料,如雪碧和芬达。 他们中的大多数人都没有可乐的味道好。
(还有,你们谁还记得 New Coke 吗?)
但是,通过广泛(和昂贵)的广告,他们使他们流行,现在他们在杂货店的饮料货架上与可乐本身一起到处都是。
今天要做到这一点要难得多,因为关于一个新产品的消息传播非常迅速。
如果你的产品不好,全世界知道的速度会比你想象的要快。
例如,2009 年,当美国联合航空公司的工人通过互相扔行李的方式传递行李时,他们最终打破了一位顾客的吉他。
他们承认是自己做的,但他们拒绝赔偿这个人的损失。
结果是什么呢?不是一首,而是三首关于蹩脚的美联航服务的歌曲,包括视频都出来了。
第一首至今已经聚集了 1500 万的浏览量。
这是相当糟糕的公关联合。如果你的产品很烂,它可以在比你建立它的时间更短的时间内消失。
解决这个问题的方法是什么?很简单,获取反馈。
你必须以最快的速度把你的产品推出去,开始收集反馈,并定期不断提高产品与市场的契合度。
我从中学到了这个教训。当我们开始 Kissmetrics 的时候,我们用了所有的资金来建立产品。
我们花了一年的时间来打造它。当我们发布它时,我们了解到,我们的客户对他们的社交网络已经提供的指标感到满意。
这可不是什么好事。
下面就来看看正确的方法是什么。
我们在 Crazy Egg 上就是这么做的。人们带着关于客户行为的问题来找我们。
他们说:”我们在广告上花了这么多钱,但实际上我们并不知道客户在做什么,他们在哪里点击,或者他们的行为是什么。”
只有在那时,我们才开始深入研究这个话题,并考虑创造一个能解决这个问题的产品。我们不仅仅是开发一个 “感觉是个好主意 “的产品。
不要躲在地下室里,开发了半年的东西,然后拿出来。挥舞着它,问:”你们觉得怎么样?”
马上询问反馈。
想象一下,一个朋友在吃饭时告诉你她公司的一个问题。你们一起在餐巾纸上勾画出一个解决方案。
当你拿到那个草图的那一刻,你就可以把它展示给其他人看。
我们只开发了一个月就推送了 Crazy Egg 的第一个版本,开始收集反馈。然后,每个月,我们都会发布一个改进版本。
得益于我们的快速发布和不断收集反馈意见,我们只用了半年时间就有了一个像样的产品,客户也乐于付费。
不仅如此,公开发布更新所产生的媒体和轰动效应,也帮助我们在推出 “疯狂的蛋 “的时候,建立了一个万人的等待名单。
而对于这一万名付费客户,我们的获客成本是多少呢?是零。
另一个绝对搞定反馈部分的公司的例子是 Instagram。
最初,创始人涉足了一款名为 Burbn 的社交网络应用,是为威士忌饮用者准备的。他们意识到,这款应用使用最多的功能是他们的照片分享机制。
随后,他们才开始关注摄影应用,他们认为这已经是一个饱和的市场。
与用户来回交流,他们最终意识到,对于所有的应用来说,分享照片要么太复杂,要么不是应用的主要功能。
他们只是简单地将他们所知道的所有应用中最好的部分,比如 Hipstamatic 的照片过滤器和 Burbn 的分享方式,去掉了所有其他的东西,然后就有了! 他们制作了一个大家都想要的伟大应用。
再加上出色的时机(Instagram 与 iPhone 4 同时推出),你就知道他们是如何在第一天就获得 2.5 万个安装量,在两个月内达到 100 万用户。
制作一个伟大的产品的另一部分是验证你的产品理念。
你想知道人们想要你将要创造的东西的一个可靠的方法吗?
请他们为它付费。
如果你想创建一个应用程序,向人们展示城里最好的茶点,而你知道它需要花费 1000 美元来开发,从 50 个朋友那里获得 20 美元(或从 20 个朋友那里获得 50 美元)将为你解决开发成本问题。
而且,你会 100%确定:
在你还没有产品之前就向你要钱,这似乎有悖常理。
但是,如果你仔细想想,你一直都在为一些事情提前付费:电影票、机票、音乐会、活动、健身房会员卡,以及各种各样的东西。
不管你最后去不去,你都要为这些东西付钱。
验证你的产品就更好了。在某些情况下,如果你最终没有打造出产品,你可以直接把钱还回去。
作家 Ryan Holiday 提前卖出了 2000 多本《障碍就是路》,在他写书的这段时间里,他为自己的燕麦片付了钱,并确保这本书一出版就会成功。
你想知道另一个成功的产品与市场契合度验证的例子吗?看看 Airbnb?
这个想法的诞生是出于需要。创始人 Joe 和 Brian,付不起房租。于是,他们想把公寓地板上的三张气垫租出去赚钱。
当三个人出现时,每个人付给他们 80 美元一晚的费用,他们想,”嗯,这也许值得。”嗯,这可能值得进一步探索”
一旦他们在西南偏南(SXSW)推出,他们就不断接到预订。但他们只得到了几个预订,每周能赚到 200 美元左右。
尽管如此,他们知道兴趣是存在的。他们要做的就是改进产品。
如果你没有想法,就从免费开始。
创建一个博客或 YouTube 频道,并围绕你想建立业务的利基提供内容。在社交媒体上分享你的内容。
这是了解人们喜欢和不喜欢什么以及他们想要和需要什么的最简单方法。这是一个很好的渠道,可以让你的想法得到反馈。
更重要的是,正如你所看到的,如果你收集电子邮件地址,你甚至可以建立一个由渴望和忠诚的追随者组成的观众,他们不能等到你真正推出产品。
你可以通过赠送电子书、开发小测验、或制作电子邮件系列或一组酷炫的视频来实现。现在有了智能手机和 InVideo 等无缝视频编辑解决方案,制作视频比以往任何时候都要容易。
让人们有机会获得你的一些最好的内容,以换取他们的电子邮件地址,你将立即开始建立一个观众。
这是目前最简单的创业方式,而且绝对没有风险。
好的,我们假设你有一个想法,并且你已经验证了它。
接下来,我们将看看你如何避免 Airbnb 犯下的一些错误,使他们的初期增长停滞不前。
猜猜 Airbnb 一开始的目标客户是谁?
是每一个旅行的人。
看看他们最初的三位顾客–租他们气垫的人。他们是一个 30 岁的印度男人,一个 35 岁的波士顿女人,还有一个 45 岁的犹他州的四个孩子的父亲。
交叉点在哪里?是什么把这些人联系在一起?他们的共同点是什么?
你看,每个新的创新产品都必须经历一个生命周期。这就是所谓的创新扩散法则,它看起来是这样的。
要想接触到大多数人,你的产品必须首先成功通过创新者和早期采用者。
这些都是你需要明确定位的小群体和社区。杰弗里-摩尔写了整整一本书叫《跨越鸿沟》,讲的就是这个现象。
产品要么吸引了前 15%的市场,要么就去死在那里。
如果你的目标客户是 “所有人”,那么你就没有办法在前 15%的市场上实现增长黑客,因为你甚至不知道该说服谁来购买。
而且,你怎么才能做好这个工作呢?
你应该建立一个客户档案。考虑你产品的各个方面。然后问自己。
谁能从我们的产品中获得最大的利益?
要具体。尽可能地描述一个真实的人。
如果 Dropbox 要告诉你他们理想的初始客户,他们可能会说这样的话。
一个 22 岁的白人男性 谁是技术精通, 住在旧金山或湾区, 是瘦,只有几个真正的好朋友, 穿 XYZ 品牌的衣服, 并花了大部分时间在网上。
这就是你应该做的细节。
而且,在一开始,你其实是想专门迎合这些人的需求。
传统的产品,如传统出版商出版的书籍,在推出之前就必须制造大量的轰动效应,以确保推出成功。
对于现代软件产品来说,发布前发生的事情并没有发布后发生的事情那么重要。
Dropbox 并没有举办一场只有邀请人参加的大型发布会。他们只是在 2008 年的 TechCrunch50 上向公众发布。
他们更聪明的举动是在推出服务后,将服务变成了仅限邀请。
这很聪明,是吧?
他们在每年他们的理想客户聚集的活动上推出了这项服务,然后在产品周围营造了一个排他性的光环。
希望加入服务的人需要现有用户的邀请才能加入。由于每个人都想知道 Dropbox 是什么,它是如何工作的,所以等待名单很快就炸开了。
但是,神秘感几乎总是伴随着怀疑。所以,为了让潜在用户了解 Dropbox 是怎么回事,他们制作了一个简短的演示视频。
他们为当时非常流行的社交新闻网络 Digg 的用户定制了该视频。同样,这些用户都是他们的理想目标。网络极客,技术宅,还有书呆子。
创始人之一的德鲁-休斯顿(Drew Houston)在整个演示文稿中放置了大约 12 个内部笑话。在 24 小时内,这段视频就有了 1 万个 diggs(相当于点赞),消息像野火一样传播开来,他们的等待名单上的用户也从 5000 人跃升到了 75000 人。
相比于在谷歌 AdWords 上为一款 99 美元的产品每次收购花费 300 美元,这对他们来说似乎是更好的策略,他们现在拥有 5 亿用户。
这种类型的增长跳跃在初创企业的早期是至关重要的,可以推动产品突破 15%的市场份额边界,而产品要起飞,就需要这样的增长。
这里还有一个在社区中传播消息的好例子。Hotmail。
如果你是 Gmail 的用户,Hotmail 似乎很老套,你可能早就忘了它。但是,自从微软收购了 Hotmail 之后,他们已经发展到了超过 4 亿用户。在 2012 年左右之前,他们一直领先于 Gmail。
他们做了什么,导致微软一开始就把他们买下来?他们发展得很快。
在辩论广告牌等营销方案时,他们的投资人有了一个想法。为什么不干脆在他们的用户发送的每封电子邮件的末尾放上一张纸条,上面写着 “PS:我爱你。在 Hotmail 获取你的免费邮件”?
这无疑是值得一试的,它将注册人数增加到了每天 3000 人,在 6 个月内,他们的用户群翻了一番–从 50 万增加到 100 万。
之后,增长变得更快。仅仅 5 周后,他们就统计出了 200 万用户。
1200 万用户是相当不错的–尤其是当这意味着互联网上每 5 个人中就有一个人。
通过将 “要求分享 “保留在他们的系统中,他们确保了他们击中了正确的目标群体。
Hotmail 电子邮件用户向他们的朋友发送电子邮件,这些朋友很可能与他们相似,因此也是理想的客户。
Uber 也做了同样的事情。他们等了整整一年,在流行的节日 South by Southwest(SXSW)上向时髦人士和科技人士免费发放乘车券,推广他们的服务,而不是依靠广告。
但所有这些成功的故事都带来了一个重要的问题。
你如何才能发现一个等待发生的汹涌的成功?一个出色的想法和一个糟糕的想法之间有什么区别?
在这一点上,我们转向盗版指标。
PayPal 黑手党是一群企业家和投资人,他们都是通过 PayPal 一炮而红,然后在硅谷创立了其他公司。
Dave McClure 曾是 PayPal 的市场总监,他之后也创办了一些公司。但最引人注目的是科技孵化器/加速器 500 Startups。
而 Dave 也明白增长黑客思维和品牌营销理念之间的关键区别。
大约十年前,Dave 发表了他对创业公司指标的看法。他的观点是衡量几个关键的增长驱动因素,而忽略其他一切。
他用有用的缩写 AARRR 概述了漏斗阶段的指标。用户于是亲切地给它起了个绰号 “海盗度量”。
所概述的漏斗阶段包括:
你可以为每个阶段挑选出几个关键指标,准确地了解你的努力(和想法)的回报情况。
然后,根据这些知识,你知道创业想法是否有吸引力,或者你是否应该转向其他东西。
漏斗顶端的第一个阶段是收购
。
这措施正是它的声音。你可以看看新的网站或应用程序的流量,甚至可以看看新的眼球来估计你的影响力。
你有多少人暴露在你的信息传递中?在这里,你要看的是付费广告、公关、SEO 等方面的表现。
只要确保不惜一切代价避免虚荣的指标。
例如,如果你在 Facebook 广告上花了钱,但所有这些访客都立即跳转,那一开始就是不合格的流量。这意味着,你可能找错了目标人群。
这说明你需要再去充实这些客户角色。
所以你可以在你的获取指标中增加层次,比如所有网站访客至少点击了两个页面或者至少停留了十秒钟。这告诉你这些人确实对你的产品感兴趣。
第二步是看激活度
,也就是在访问你的网站或应用后停留的人数。
戴夫-麦克卢尔将其称为 “快乐的第一次访问”。
有时,这意味着一个硬性目标,如选择进入或注册。但除此之外,它也可以适用于高质量的访问,即有人在你的应用里面使用了一个关键功能,或者在网站页面上浏览的时间超过了几分钟。
这也是我当初创建 Crazy Egg 的原因。
Google Analytics 可能会告诉你,有 100 人访问了你的主页。如果你在分析像 SEO 这样的获取策略,这很有帮助。
但如果你想弄清楚这 100 人在你的网站上做了什么,那就没有帮助了。
你根本不知道他们是否觉得这个页面有帮助,是否很好地提供了他们正在寻找的信息,或者他们是否有潜在的兴趣注册。
而这些东西最终决定了他们是否成为付费客户。
这就是 Crazy Egg 发挥作用的地方。
你可以根据他们的行为和点击量,从字面上看出他们是否在激活。
让人们到你的网站很容易与广告。获得合格的人是一个有点困难。
但实际上如何让这些人选择在或注册?这完全是另一回事。
它告诉你,人们是否真正喜欢你的产品概念。
在获得这些激活,或快乐的第一次访问后,下一步是长期保留
他们。
想一想移动应用吧。
你很兴奋,急于下载那个大家都在谈论的新应用。你前往应用商店,点击下载,几乎无法控制自己的兴奋。
你用了几个小时,很开心,但后来又出现了一些事情。于是你把它收了起来。
而且你再也没有登录过!
你并不孤单。说真的,每个人都会这样做。
今天我们用手机的时间比用台式电脑的时间还多。84%的时间我们都在用手机,我们都在手机应用里。
然而应用的平均留存率却很糟糕。看看这张可怕的图,让应用主们看看。
这就告诉你,几乎 80%的用户在下载后三天内就会离开,再也不会回来。这个数字在几个月内会变得更糟糕,上升到高达 95%。
这就是为什么留存如此重要。你在广告上花钱,或者创造一个很棒的首次体验,但流失会杀死大多数创业公司。
你失去客户的速度会比你获得客户的速度更快,你很快就会失去现金(和业务)。
《哈佛商业评论》和贝恩公司发表的一项旧研究表明,大多数新客户在一段时间内都无利可图。
我们今天可以争论实际的数字,但同样的基本原则仍然适用。许多新客户在一开始是无利可图的,因为你必须在广告、人员、办公场所等方面花钱。
更糟糕的是,大多数 SaaS 公司每月只收取其价值的一小部分。这意味着你需要将一个客户保留 3 到 6 个月(甚至可能是 1 年),才能重获新生。
大多数营销博客文章都集中在获取渠道或战术上。他们谈论 “钱在列表中 “如何让电子邮件订阅者购买他们的第一个产品。
他们是对的……在一定程度上。
但真正的增长黑客知道,钱在客户名单中。你通常可以通过专注于增加客户保留而不是专注于新的收购来更快、更容易地增加收入。
因此,除非你是在移动应用业务中,否则人们在第一次或第二次参与后退出是一个糟糕的坏迹象。
没有多少公司能像 Dropbox 或 Facebook 那样建立病毒式的推荐
循环。事实是,这需要一些特殊的事情同时发生。
你需要时机,一个惊人的产品,以及网络效应。当一个人对产品的使用增加了另一个人的价值时,就会发生这种情况。
这就像 1+1=3 一样。
但无论哪种方式,你仍然希望尽可能多地推动推荐。同样,这些是快乐的付费客户向他们的家人、朋友和同事推荐你。
再说一次,如果你做得好,这应该是很容易赚钱的。
为你传播消息的客户越多,你在收购上的花费就越少,这意味着你离盈利越近。
你的客户应该告诉他们的朋友,并催生你的产品。Net Promoter Score 是我最喜欢的衡量技术之一,因为它非常简单。
但你要继续阅读下面的内容,才能知道如何正确使用它。
到目前为止,我们所做的一切都只有一个目标。收入
。
最终,人们给你冷硬的现金是最好的产品验证形式。
要达到这个目标可能需要一段时间。可能要经过几次反复或枢纽才能打出一个成功的组合。
但是,如果你把前四个步骤都做好了,收入的问题就会迎刃而解。
现在你明白了做增长黑客决策的框架。你看到了每一个都在帮助你定义成功方面扮演着不同的角色。
接下来,让我们开始深入研究。
我们将通过历史上一些增长最快的公司所使用的具体策略、技巧和黑客来深入探讨其中的每一项。
我们将从漏斗顶端的收购开始。
肖恩-埃利斯可能创造了增长黑客的概念。
但 Eric Ries 帮助将其普及到大众中。
在 Eric 出版《精益创业》一书之前,科技界和软件界是人们知道许多核心增长黑客理念的唯一地方。
埃里克帮助把它放在了地图上,把它正式化,使它可以适用于世界各地各种规模的公司和各个行业。
那本书中最重要的主题之一是关于增长的三大引擎。
这是公司扩大获客规模最可靠的三条路径。但同时做多于其中之一的事情几乎是不可能的。
诀窍是要弄清楚哪种最适合自己的产品类型:
这三个 “引擎 “中的每一个都能发挥作用。然而,每一个执行的程度主要取决于你的业务。
现在让我们分别去看看每个引擎如何以及何时发挥最大的作用。
这一步只是意味着挖掘更大的系统和更大的用户群,并利用同行产品的影响力,真正渗透到大部分市场。
你的目标客户仍然是你的理想客户,但你正在扩展到所有人都存在的平台。
Dropbox 用病毒式推荐策略杀出了一条血路。
给人们提供免费的存储空间,让他们向朋友推荐产品,简直是天才,因为它做了两件事:
当这种推荐朋友的策略开始产生效益时,他们将其发挥到了极致。
这是与 Dropbox 的另一个关键区别。
许多公司过于分散投资。他们在这里投入 100 美元,在那里再投入 100 美元,看到的是 1%的微薄投资回报。
所有的初创公司都是现金紧缺的。这甚至是那些筹集了大量资金的公司。
原因是你经常要和庞大的企业集团竞争,他们的资金是你的几十亿,而你的资金是几百万。
小公司就不能分散投资了。他们需要把所有的鸡蛋放在一个篮子里,以产生尽可能大的回报。
如果有些东西不奏效,你就尽快改变方向,尝试其他东西。
Dropbox 循环使用了几个不同的想法,直到他们找到了病毒式推荐朋友的方法。当他们看到它的效果时,他们加倍努力,给用户提供更多的空间。
这听起来很像应该在漏斗中更进一步的 Referral 指标。而且在一定程度上是这样的。
但不要搞错了,这是他们领先的收购增长杠杆,因为订阅量增加了 60%。这是他们领先的收购增长杠杆 因为订阅量增加了 60%。
添加其他操作,比如连接你的社交账户以获得更多的存储空间,就像是火上浇油。
然而,他们花了一段时间才想明白,这种策略会如此有效。
例如,在打这个战术之前,他们尝试了广告活动和公关的付费策略。
据创始人德鲁-休斯顿(Drew Houston)说,没有一个成功。于是他们不断迭代,直到找到了行之有效的策略。
为每个用户提供激励措施,让更多的人加入到平台上,是启动病毒式营销活动的好方法,但让你的产品自己来营销就更好了。
苹果公司就是这样做的,他们的广告很好地运用了这一策略。还记得流行的 iPod 广告,黑色的剪影和白色的耳机吗?
通过将耳机做成白色,苹果确保每个人都能认出他们。耳机通常是黑色的,所以通过调整这个功能,他们把所有的客户变成了行走的广告。
你还想知道另一个例子吗?
WordPress 正在为 29%的在线网站提供动力,而且它是免费的。不过,免费版有一个问题。
您的域名将始终显示为 wordpress.yourdomain.com。
每个访问您的免费 WordPress 博客的人都会立即知道这是一个 WordPress 网站。
但是,让我们来谈谈一个更强大的增长黑客。
在 Facebook 将其彻底接管之前,MySpace 统治了这个场景。他们是最大的平台之一。
事实上,他们是如此之大,以至于 YouTube 利用他们的成功来飙升流量。
YouTube 的见解是让视频易于分享。他们免费创建了嵌入代码,鼓励用户将他们的视频添加到 MySpace 等其他网站。
这让 YouTube 的内容呈现在所有人面前。
首先,他们得到了品牌认可。人们开始熟悉他们是谁,他们做了什么。
它还通过吸走其他网站的权威,帮助创建反向链接和推荐流量。
快进几年,YouTube 现在每天处理超过 30 亿次搜索,使其成为第二大在线搜索引擎。
每个人的宠儿,Facebook,使用嵌入作为一个早期的增长黑客,以确保他们达到他们的目标,在一年内获得 2 亿新用户。
他们给用户提供了一个选择,通过创建不同的徽章让他们嵌入到其他地方,比如他们的博客、网站和论坛中,以显示他们在 Facebook 上。
这些徽章今天仍然存在。
这创造了数十亿的印象,数亿的点击量,以及每月数百万的注册量。
而且,这并不是唯一一家使用这种策略的巨头。你有没有尝试过在你的博客上分享YouTube视频?
他们让嵌入视频变得超级简单,所以很多人都会这么做。他们为你创建整个代码并突出显示,这样你只需按Cmd+C(或Ctrl+C,如果你在Windows上),然后将其粘贴到你的编辑器中。
但这不是它如此受欢迎的唯一原因。还因为YouTube视频非常容易分享。
我们还希望人们将他们的数据嵌入到Crazy Egg中–这不是一个好主意。
为什么呢?
哪家公司愿意展示他们的流量、点击、收入数字和转化率?
没有人愿意!
但是,猜猜谁想分享他们发现的最新有趣的猫咪视频?每个人都想!
小贴士:给人们一个深入挖掘你的嵌入的理由。YouTube播放器会自动播放下一个视频,或者在每个视频结束时给你一个相关视频的选择。这使得你在看完一个嵌入视频后,极有可能切换到YouTube。
当你决定是否要让你的产品可嵌入时,要确保客户有一个嵌入的理由,它很容易做到,并且你要吸引他们深入挖掘你的嵌入。
不过,还有比嵌入更强大的东西,特别是如果你做对了:集成。
您是否知道,只要让人们使用他们在Facebook、Twitter或Google上已有的账户注册您的服务,您就可以增加高达50%的注册量?
整合你的服务与另一个服务无缝对接,可以让你非常轻松地接触到数百万的潜在客户。
PayPal在大多数市场上挣扎着进入大门。很少有零售商真正将其作为一种选择。
但是,一旦他们与eBay达成协议,并将PayPal作为选项提供给Visa和万事达卡旁边,闸门就打开了。
在过去的几年里,他们的支付量甚至翻了一番。
最终,eBay在2002年以15亿美元收购了PayPal。
考虑到PayPal现在的价值不仅超过了eBay,而且还超过了美国运通,这简直是偷鸡不成蚀把米。对他们来说,有一个强大的整合就够了。
但PayPal和Facebook已经是旧闻了。那最近的一些创业公司呢?
今天的整合同样有效。只不过现在,像Facebook和PayPal这样的公司才是你想要整合的对象。
Spotify就是一家这样做的公司。
下面是它的样子。
与Facebook的整合是一个非常有针对性的举措。Facebook已经是一个分享兴趣的平台,尤其是音乐(例如视频形式)。
Spotify只是让这种体验变得更好。通过在应用和Facebook流中显示你的朋友听的音乐,人们开始发现这个应用。
“汤姆在听Spotify上的Jay -Z的歌。
嗯,我想知道那是什么。让我看看 哦,这是免费的流媒体。太棒了!”
Spotify又有了新用途。顺便说一句,这正是他们得到我的方式。
与嵌入一样,确保你的整合对用户来说是有意义的,而且你的入驻过程是顺利的,这样双方都能受益。
Airbnb没有,这就是为什么他们的增长黑客最终停止了工作。幸运的是,他们在那个时候已经不需要它了。
当他们开始创建房源时,他们真的想利用Craigslist的庞大网络。
但是Craigslist并没有公开他们的API,所以Airbnb的人不得不创建一个非常困难的技术解决方案。
最终,他们实现了这一点,人们可以通过简单的点击来交叉发布他们的Airbnb列表。
当时,每月约有5000万独立用户使用Craigslist。
这些房源后来得到了大规模的曝光,导致Airbnb的用户增长了一大段。
但我们只能说,这并不是一个很认可的程序,也根本没有得到Craigslist的认可。最终,他们不得不停止整合。
另一个与嵌入和集成类似的概念是 “Powered by badges”。
不过这些都需要大量的测试和优化,因为不可能马上就知道什么是有效的。
例如,对于Kissmetrics,我们发现 “Analytics by… “比 “Powered by… “好用得多。
整合、嵌入和徽章当然是你最好的赌注之一,可以让你的产品走向病毒式传播,并获得足够大的市场,成为热门产品。
尽管如此,这些例子更多的是想法,而不是 “复制这个 “计划。
你看,当越来越多的公司开始利用它们时,增长黑客通常会相当快地停止工作。
因此,与其试图复制这些例子中的每一个,不如试着进入正确的心态,看到尚未开发的机会和新的方法,让你使用类似的策略来营销你的产品。
那么,你一定要想出一些聪明的策略吗?
不,你不需要。
这里有一个使用增长黑客的初创公司的例子,它不是一家价值10亿美元的科技初创公司。
听说过 “海报沟 “吗?他们是一个让你买卖艺术品和海报的市场。
他们通过利用病毒式策略,如结构良好的赠品和联盟营销,招募 “校园创业者 “为他们获取用户,只用了一小部分成本就将他们的市场发展到100万美元。
很基本的战术,利用病毒式的基本本钱,对吧?
你不需要很花哨。你可以简单地测试已经有效的战术,看看它们的效果如何。如果你获得了很好的投资回报率,那么就可以加倍地扩大它的规模。
他们没有试图让尽可能多的人获得机会,而是反其道而行之。
Facebook一开始只针对特定的常春藤盟校。你必须在每所学校都有一个电子邮件地址才能访问。
从那里,他们慢慢扩展到其他高调的学校。这限制了供应,刺激了需求。
Facebook具有病毒性,随着越来越多的朋友加入,每个用户的价值都会增加。
然而,这个平台的粘性非常高。几乎没有任何其他平台可以与之相比。
平均每个人每天在Facebook上花费大约一个小时的时间。这几乎和人们白天吃饭的时间一样多。
而且这些趋势数字还在不断增长!
Facebook很清楚他们的用户喜欢什么,不喜欢什么。然后,他们像木偶大师一样操纵这些东西,让人们每天继续多次登录。
这意味着他们已经征服了产品粘性的神奇公式。
高留存率+低流失率+网络效应。
Kissmetrics前增长总监Lars Lofgren展示了如果你任其发展,流失率往往会(也会)开始侵蚀新客户的获取。
而且他还引用了Eric Ries的一个简单的经验法则,用于粘性产品。
管辖粘性增长引擎的规则非常简单: 如果新客户的获取率超过了流失率,产品就会增长。
David Skok在他关于SaaS指标的文章中写下了你可以称之为Churn Bible的东西。在其中,他引用了NetSuite的Ron Gill的话,他基本上说,流失率就像你能看到的年率或回报率的天花板。
换句话说,你只能在你的流失率低的情况下成长。
负流失也可以改变游戏规则。这时,从现有客户那里增加收入的收益超过了老客户离开所损失的收入。
大卫-斯科克概述了两种增加的方法。
第一个是将价格与产品使用量挂钩。这样,客户使用你的产品越多,他们支付的价格就越高。例如,根据他们发送电子邮件的数量来收费。
另一个选择是向上销售或交叉销售更多/更大版本的产品。这在B2B领域特别有帮助,你可以添加一个大的、可定制的 “企业 “产品。
诚然,这是个有点深奥的宅男话题。
但坚持产品是绝对必要的。
流失率可以(也将)决定你的产品最终的成功程度。
问题是,许多其他类型的公司并没有从在线增长中获益那么多。
像Nespresso咖啡机这样的硬件公司,不能那么容易地 “病毒式 “传播,也不能通过简单地创造一个有粘性的产品来实现增长。
这些公司是如何成长的呢?他们利用商业广告。
Groupon在短短几年内通过付费增长上市时,也采取了类似的策略。
他们销售的是硬商品和服务。
他们的诀窍是在任何竞争对手跟上之前尽可能快地成长,这样他们就可以达到规模,上市,并主导优惠券领域。
你可以说所有你想要的关于他们的股票价格或性能。
但你无法反驳的是他们的增长策略。
他们的规模在一年内增长了228%。
怎么做到的?
就在上市之前,他们必须向美国证券交易委员会披露财务数据。
而其中最让人眼前一亮的一项是,他们在一个季度内花了1.79亿美元获取了3300万新用户!
“网络营销 “这一项是目前最大的费用调整。
所以他们是直接花钱获取新用户。
他们的商业模式表明,在广告上花钱是一种积极的投资。他们能够在每个注册用户上赚取一定的利润。
增长的数学很简单。花尽可能多的钱!
你有客户的终身价值(LTV)在方程的一边。这是每个客户在一段时间内的平均价值。
因此,如果有人每月付给你100美元,持续两年,那就是2400美元的LTV。
你可以也应该尽力提高这个数字。下面基于留存的策略可以帮助你。
但除此之外,你要将这个数字与获客成本(CAC)进行比较。
把获取一个客户所需的所有费用加起来。你有可能拥有设备、销售人员、营销人员、广告活动等。
因此,你在市场和销售上的花费几乎总是比你想象的要高。
中间的某个地方是你的投资回收期。这是你收回这些初始成本所需的时间。
而你可以将这一点之后的所有东西回馈给管理费用或利润。
亚马逊Prime会员每年的消费超过1300美元。这是非Prime客户消费的两倍。每月总计近1亿美元。
额外的利润率使他们能够重新投资于未来的增长。
例如,亚马逊出了名的产品销售利润率较低。
但最近,他们已经开始创造、销售和分销自己的产品。
而且最好的是,他们只是简单地看一个类别中最畅销的产品。所以他们已经知道什么是最有效的。
亚马逊前员工Rachel Greer透露了亚马逊是如何利用他们新的自有品牌来提高每单位销售利润的。
亚马逊既能追踪人们购买的商品,也能追踪他们搜索到的和找不到的商品,这让这家电子零售商在小卖家面前拥有巨大的优势。
他们正在研究哪些商品化的产品已经表现良好,然后简单地削弱竞争。
付费增长从一些基本的东西开始,比如广告或公关。
但正如你所看到的,它还包括在增加利润的同时降低成本,以进一步利用权力地位。
引起更多的关注和兴趣永远是第一步。然而,如果不能让这些关注度持续下去,你将永远面临一场艰苦的战斗。
以下是如何激活新的网站访问量和应用安装者,让他们长期留在这里。
你想尽快提高转化率吗?
其实很简单。
你所要做的就是消除你的选择页面上的一些表格字段。
我只需从注册页面中删除一个表格字段,就能将我的转化率提高26%。
但是,等等。它不可能这么简单,对吗?
不幸的是,它不是。
你只是在这个免费的步骤上让选择加入变得更容易了。免费转换并不能支付账单。只有付费客户才会这样做。
这里有一个几年前的完美例子,证明了这一点。
在人们注册时要求他们提供信用卡,会增加很多摩擦。他们还不确定自己是否想要或需要你的产品。
去掉这个要求,就容易多了。
所以很容易猜到这对转化率的影响。
这是Totango的一项研究发现的。
你自己的情况是独一无二的。重复这项研究可能会给你不同的结果。
不过,重点还是在于。
如果更多的人只是要离开,那么让更多的人转化是没有用的。这不是一个成功的激活。
相反,你希望合格的人留在这里。注册或选择加入是一个好的开始。但它们并不能给你提供全貌。
在注册后叠加每次浏览的页面可能会给你提供你需要的背景。
Fraser Deans强调了成功收购的三个不同阶段。
第一阶段概述了别人到达你的注册页面的所有步骤。
例如,也许他们在谷歌上搜索了一个痛点,找到了一篇博客文章,然后点击了那个页面。
也许他们先看到了一个广告,引导他们进入了一个登陆页面。
也可能他们直接输入了你的品牌名称。
这里的诀窍是优化用户流,加快每一个人的速度。
比如,我的很多网站都会在前面和中间设置一个工具选择功能。
访问Crazy Egg主页,你会看到的是一个URL栏,可以即时获得热图。
我们为什么要这么做?
我们在 “塑造 “用户流。有人可以在一篇博客文章中读到关于常见网站错误的内容。
然后,一旦他们开始浏览网站以获得更多的信息,我们可以向他们展示如何修复他们的网站。
这是令人难以置信的强大。它免费给别人带来直接的价值。
这也是我见过的最好的方法之一,让人们立即了解你的产品如何使他们受益。
当他们看到热图的那一刻,他们就会产生 “哈哈 “的感觉。
因此,从那里开始,它是一个简单的销售。
不过,让别人转化只是战役的一半。
下一步是确保发生 “快乐的第一次体验”。
这是入职阶段,在这个阶段,你可以使用教程来帮助人们学习如何从你的产品中获得最大的好处。
Claire Suellent概述了三种不同的方法,包括:
服务和关注程度也主要取决于产品类型。
例如,像Canva这样的简单产品是相当直接的。
通过拖拽一个小方框来裁剪照片,非常直观。
他们可能有能力使用自助服务教程,使用callout和工具提示来告诉用户下一步该点击哪里。
产品越复杂,你最初需要做的手工操作就越多。
问题不在于人们理解你产品的功能。这些很容易看到。
问题是让人们了解如何从这些东西中获益。而这往往需要一段时间来沉淀。
像HubSpot和Infusionsoft这样真正复杂的产品甚至需要你做正式的培训,然后他们才会让你松开产品。
很显然,对于大多数产品来说,要求一次性收取3000元的上岗费是矫枉过正。
但它的作用是将注册的人数缩小到只有合格的人。
HubSpot愿意打赌,当你真的经历了这个过程,你将能够更有效地使用他们的产品。
而结果是,你会坚持做一个付费客户的时间更长。
把你的入职序列看作是自己的漏斗,可以帮助你发现问题所在。
例如,沿着宾果卡创造者的每个步骤,看看下面的转换。
从签到仪表板到创建列表的人数看起来不错!
但下一步(从创建列表到定制列表)就没有那么好了。
这让你看到了转化路障藏在哪里,一览无余。从一个功能到另一个功能的过渡对你来说可能看起来很简单。
然而,客户数据却描绘了另一幅图景。
LinkedIn通过在零到100的范围内显示你的个人资料有多 “完整”,这也是这种策略的另一种变化。
和以往一样,只有测试才能看出什么是有效的。这就是为什么增长黑客的思维方式对成功至关重要。
你需要假设潜在的解决方案,测试,衡量影响,并继续迭代,直到你找到答案。
例如,Buffer的Leo Widrich告诉Chargify,他们是如何在入职过程中不强迫用户分享一些东西,从而反直觉地发现了更多的成功。
这与你的想法完全相反,因为Buffer背后的整个理念就是改善人们分享内容的方式。有的时候,在你测试不同的方法之前,你根本无法知道什么会有效。
接下来,在整个体验中调整你的信息传递。
例如,营销自动化可以帮助你定制信息。
因此,你可以看到某人是否已经使用了某个特定的功能,如果没有,就给他们发送一封完全不同的提醒邮件来推敲他们。
大多数自动化平台将允许你为你想要的行动设置事件。
然后,你可以根据用户是否采取了该行动来改变后续消息。
如果LinkedIn上有人上传了照片,但没有填写技能、专长或以前的工作经历,猜猜他们即将收到哪条消息?
像这样的滴灌邮件不仅仅是为了辅助激活。
事实上,在留存阶段,你往往可以从中获得更多的价值,让人们一次又一次地回来。
针对常规活动,其产出机制可大致进行如下划分:
针对上述划分的步骤,可提取出具体的服务:
其中,规则服务
需要察觉用户用户完成的各项任务(登陆注册、开户、入金、出金等),会依赖登陆注册服务、开户服务、出入金服务等其他服务,这里使用发布/订阅
模式进行机制实现:
另外,可能还需要一些服务:
对于一个具体的活动页,其可复用的东西,是页面组件,以及组件在页面中的组合关系,也就是:
接下来再设计好使用模版生成页面
的调度关系,即可批量生产 h5
经过简单调研,初步考虑使用 gatsby 作为静态页生成器
开发人员开发出常用的活动交互组件(比如页面轮播 banner,抽奖转盘,手机注册 form 表单等)
开发人员在 gatsby 的架构约束下使用模版拼装组件,然后使用模版生成实际页面;也可以使用 gatsby 对接具体的数据源,在 CMS 中进行组件拼装
CMS 需要对接活动涉及到的微服务群,比如奖品服务、发奖服务等。大部分的对接,是常见的表单输入、列表展示、详情展示,但针对活动页组装
组装的这部分对接,会相对复杂。
针对活动页组装
的交互,主要是 3 个点:
待详细考虑
硬核
的东西是什么)HTTPS 基于 HTTP 协议,并在其之上增加了一个 SSL/TLS(以下简称为 “SSL”)加密层。服务器和客户端之间仍然使用 HTTP 协议规范进行通信,但通过安全的 SSL 连接,对其请求和响应进行加密和解密。SSL 层有 2 个主要目的:
真正巧妙的部分是 – 任何人都可以拦截你与服务器交换的信息,包括你们正在商定使用的密钥和加密策略,但仍然无法读取你发送的任何实际数据。
客户端和服务器之间的 SSL 连接是通过握手建立起来的,其目标是:
一旦建立了连接,双方就可以使用约定的算法和密钥安全地发送消息。我们将把握手分为 3 个主要阶段–’你好’、’证书交换’和’密钥交换’:
客户端生成一个随机密钥,用于主要的对称算法,并使用同样在 Hello 阶段商定的算法和服务器的公钥(在 SSL 证书上找到)对其进行加密。客户端将这个加密后的密钥发送给服务器,服务器使用私钥进行解密,握手的有趣部分就完成了。双方都很高兴,因为他们正在和正确的人交谈,并且已经秘密地商定了一个密钥来对称地加密他们将要发送给对方的数据。现在,HTTP 请求和响应可以形成一个明文信息,然后加密后发送。对方是唯一一个知道如何解密这个消息的人,所以中间人(攻击者)无法读取或修改他们可能截获的任何请求。
基本上,SSL 证书就是一个文本文件,任何有文本编辑器的人都可以创建一个。你可以轻而易举地创建一个证书,声称你是谷歌公司,你控制着域名 gmail.com。如果这就是整个流程,那么 SSL 就会成为一个笑话 – 身份验证实质上就会变成:客户端问服务器 “你是 Google 吗?”,服务器回答 “呃,是的,这里有一张纸,上面写着’我是 Google’”,客户端说 “好的,太好了,这是我所要的数据”。防止这场闹剧的关键之处在于数字签名,它可以让一方验证另一方的纸片是否真的合法。
有两个合理的理由可以让你信任证书:
第一个标准很容易检查。你的浏览器预装了获取自证书颁发机构(CA)的可信任 SSL 证书列表,你可以查看,添加和删除。这些证书由一组集中的(理论上,通常在实践中)极其安全、可靠和值得信赖的组织所控制,如赛门铁克、Comodo 和 GoDaddy。如果一个服务器出示的证书来自该列表,那么你知道你可以信任他们。
第二个标准要难得多。服务器很容易说 “是的,我是微软,你信任赛门铁克,赛门铁克信任我,所以都很酷”。有点聪明的客户可能就会去问赛门铁克 “我这里有一个微软,他们说你信任他们,这是真的吗?” 但即使赛门铁克说 “是的,我们认识他们,微软是合法的”,你还是不知道这个自称是微软的服务器到底是微软还是其他糟糕的东西。这就是数字签名的作用。
如前所述,SSL 证书有一个相关的公钥/私钥对。公钥作为证书的一部分进行分发,而私钥则受到非常安全的保护。这对非对称密钥在 SSL 握手中用于交换双方进一步的密钥,以对称地加密和解密数据。客户端使用服务器的公钥对对称密钥进行加密,并安全地发送给服务器,服务器则使用其私钥进行解密。任何人都可以使用公钥加密,但只有服务器可以使用私钥解密。
数字签名的情况正好相反。证书可以由另一个当局 “签署”,这样,该当局就可以有效地记录在案,说 “我们已经核实,该证书的控制人也控制着证书上所列的财产(域)”。在这种情况下,权威机构使用他们的私钥(广义上)对证书的内容进行加密,而这个加密文本作为数字签名附在证书上。任何人都可以用权威机构的公钥对这个签名进行解密,并验证其结果是否达到预期的解密值。但只有权威机构才能使用私钥对内容进行加密,所以只有权威机构才能真正在第一时间创建一个有效的签名。
所以,如果有一个服务器来宣称自己有一个由赛门铁克(或其他 CA)签署的 Microsoft.com 的证书,你的浏览器不必相信它。如果它是合法的,赛门铁克会用他们的(超机密)私钥来生成服务器 SSL 证书的数字签名,你的浏览器可以使用他们的(超公开)公钥来检查这个签名是否有效。赛门铁克会採取步骤,确保他们所签署的机构真的是 Microsoft.com 的拥有者,因此,鉴于你的客户信任赛门铁克,它可以肯定它真的是 Microsoft Inc.
请注意,所有的根 CA 证书都是 “自签名 “的,这意味着数字签名是用证书自己的私钥生成的。根 CA 的证书并没有什么本质上的特别之处–如果你想的话,你可以生成自己的自签证书,并使用这个证书来签署其他证书。但由于你的随机证书并没有作为 CA 预装到任何地方的浏览器中,因此没有一个浏览器会信任你签署自己的或其他证书。你实际上是在说:”呃,是的,我是微软,这里有我自己签发和签名的官方身份证书。”所有正常运行的浏览器都会针对你的可疑证书抛出一个非常可怕的错误信息。
这给所有的浏览器和操作系统发布商带来了巨大的负担,他们只信任干净的根 CA,因为他们的用户最终会信任这些组织来审核网站并保证证书的安全。这不是一件容易的事。
有趣的是,你的客户端在技术上并不是要验证是否应该相信发送证书的一方,而是要验证是否应该相信证书中包含的公钥。SSL 证书是完全开放和公开的,所以任何攻击者都可以抢夺微软的证书,拦截客户端对 Microsoft.com 的请求,并向其出示合法证书。客户端会接受这一点,并愉快地开始握手。然而,当客户端加密将用于实际数据加密的密钥时,它将使用这个真实证书中的微软的真实公钥来加密。由于攻击者没有微软的私钥来解密,所以他们现在被卡住了。即使完成了握手,他们仍然无法解密密钥,因此也无法解密客户端发送给他们的任何数据。只要攻击者不控制可信证书的私钥,秩序就会得到维护。如果客户机以某种方式被欺骗,相信一个私钥被攻击者控制的证书和公钥,麻烦就开始了。
不行。
公钥加密的魔力意味着,攻击者可以观察你的客户端和服务器之间交换的每一个字节的数据,并且仍然不知道你们之间在说什么,除了大概有多少数据在交换。然而,在不安全的 wifi 网络上,你的正常 HTTP 流量仍然是非常脆弱的,一个脆弱的网站可以成为任何数量的变通方法的受害者,这些变通方法以某种方式欺骗你发送 HTTPS 流量,要么通过普通 HTTP,要么就是完全发送到错误的地方。例如,即使一个登录表单通过 HTTPS 提交用户名/密码组合,如果表单本身是通过 HTTP 不安全地加载,那么攻击者可以在表单到达你的机器的途中拦截表单的 HTML,并修改它来发送登录细节到他们自己的端点。
如果你使用的也是你公司控制的机器,那么是的。请记住,在每一个信任链的根部都有一个隐含的受信任的 CA,而这些权威机构的列表就存储在你的浏览器中。你的公司可以利用他们对你的机器的访问权限,将他们自己的自签证书添加到这个 CA 列表中。然后他们就可以拦截你所有的 HTTPS 请求,出示声称代表相应网站的证书,这些证书由他们的假 CA 签署,因此毫无疑问地被你的浏览器信任。由于你会使用他们的假证书的公钥对你所有的 HTTPS 请求进行加密,他们可以使用相应的私钥对你的请求进行解密和检查(甚至修改),然后将其发送到预定的位置。他们可能不会。但他们可以。
顺便说一句,这也是你使用代理检查和修改 iPhone 应用发出的本来无法访问的 HTTPS 请求的方法。
Lavabit 是爱德华-斯诺登在 2013 年 NSA 泄密疯狂期间的超级安全电子邮件提供商。正如我们所看到的,任何数量的标准黑客技术都无法让 FBI 看到 Lavabit 和其客户之间的任何数据。没有 Lavabit SSL 证书的私钥,该机构就完蛋了。然而,一位乐于助人的美国法官告诉 Lavabit 的创始人 Ladar Levison,他必须交出这个密钥,实际上给了 FBI 自由窥探流量的权力。莱维森勇敢地试图拖延时间,交出了 11 页 4 号字体的 2,560 个字符的密钥,但却遭到了命令,要求他以有用的格式交出密钥,否则将面临每天 5,000 美元的罚款,直到他交出为止。
一旦他遵守规定,Lavabit 的 CA GoDaddy 就撤销了证书,(正确地)认为它已被破坏。这将 Lavabit 证书添加到证书撤销列表(CRL)中,这是一个客户不应再信任的证书列表,以提供安全的连接。篡改、自签或其他不可信的证书会导致浏览器显示一个大的红色错误信息,并阻止或直接禁止用户的进一步行动。不幸的是,浏览器将继续信任一个被破坏的证书,直到他们拉出最新的 CRL 更新,这个过程显然在实践中是不完美的。
HTTPS 并不是牢不可破的,SSL 协议也必须不断发展,因为针对它的新的攻击被发现并被压制。但它仍然是一种令人印象深刻的强大的传输秘密数据的方式,而不会在乎谁看到你的信息。当然,这里还有许多实施细节没有提到,比如握手信息的确切格式和顺序,简略的握手以获取最近的会话,而不必重新协商密钥和密码套件,以及在每个阶段有许多不同的加密选项。要记住的关键是,虽然 HTTPS 能保证数据在传输过程中的安全,但它绝对不能保护你(作为用户或开发者)免受 XSS 或数据库泄漏或任何其他事情的影响。庆幸有它在背后支持你,但要保持警惕。用威尔-史密斯的不朽名言:”在阴影中行走,在沉默中移动,防范地外暴力。”
如果你喜欢这个,你可能会喜欢我解释 2015 年 SSL 中 FREAK 漏洞细节的文章。
原文:How does HTTPS actually work?
译者:evan
]]>1 | ### 这个 PR 做了 |
1 | ### 这个 PR 做了 BUG FIX |
1 | git commit -m 'feat: add footer' |
1 | [ |
针对上述问题对应的测试用例,基本可以分为以下几种:
其中交互测试是成本最高的测试,可以再细分为下面几种:
另外,针对当前团队技术栈,还需要在 redux 场景下进行测试
上述测试用例具体实践方式,可以看 react+react-router+react-redux 项目单元测试实践记录
在具体实际中,针对一个组件的测试代码,其交互测试与其他测试测试代码对比,一般会是 6:1
测试覆盖概念说明
这是目前全量单元测试覆盖率限制,各个指标未满足覆盖度,则单元测试指令返回失败
1 | coverageThreshold: { |
1 | =============================== Coverage summary =============================== |
针对上述问题对应的测试用例,基本可以分为以下几种:
其中交互测试是成本最高的测试,可以再细分为下面几种:
另外,针对当前团队技术栈,还需要在 redux 场景下进行测试
上述测试用例具体实践方式,可以看 react+react-router+react-redux 项目单元测试实践记录
在具体实际中,针对一个组件的测试代码,其交互测试与其他测试测试代码对比,一般会是 6:1
通过上面的论述,基本可以得出单元测试具体的成本描述
另外也有单元测试具体的收益描述
1 | sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386 |
1 | sudo chmod +x /usr/local/bin/gitlab-runner |
1 | sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash |
1 | sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner |
runner 启动后,需要注册入 gitlab 服务中
开始注册
1 | sudo gitlab-runner register |
1 | Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com ) |
1 | Please enter the gitlab-ci token for this runner |
1 | Please enter the gitlab-ci tags for this runner (comma separated) |
1 | Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell: |
1 | Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! |
1 | stages: |
cache: ...
部分表示 node_modules 需要缓存tags: - fe-ci
表示此阶段指定使用 tags 为 ‘fe-ci’ 的 runner 进行执行react+react-router+react-redux 项目单元测试实践记录
Gitlab 自动部署之二:安装 GITLAB-RUNNER
Enzyme 是由 Airbnb 推出的流行的测试库。它已经发布了很长一段时间,且 react 官方文档建议减少使用 Enzyme 作为编写测试用例的模板。Enzyme 的 API 旨在通过模仿 jQuery 的 API 来实现直观和灵活的 DOM 操作和遍历。
React Testing Library – 一个非常通用的名字,它作为一个测试库,旨在解决与其他测试库不同的用例。React Testing Library 迫使你编写不脆弱的测试 – 测试并不是测试具体实现,而是测试组件的功能。它鼓励编写代码的最佳实践,并使代码具备可测试性,和测试正确的条件。
更新–React Testing Library 现在改名为@testing-library/react。
让我们来看看 Enzyme 与@testing-library/react 的一些区别。
在 Enzyme 中,你需要配置适配器,使其与 React 16 一起工作。还有其他的第三方适配器可以使 Enzyme 与这些库一起工作。
1 | import Enzyme from "enzyme"; |
在 @testing-library/react 中,不需要太多的设置。你必须安装 @testing-library/react npm 模块,然后就可以了。
1 | npm install --save-dev react-testing-library |
当你在 Enzyme 中编写测试时,你有方法检查类的状态属性,检查传递给组件的 props 是什么。但是如果你是用道具和状态来测试组件,那就意味着你的测试很脆弱。如果明天有人改变了状态变量的名称,那么你的测试就会失败。即使组件的功能是一样的,只是因为组件中使用的状态变量名重名了,测试就会失败。由此可见单元测试的脆性。
而@testing-library/react 没有测试状态或道具的方法。相反,它测试的是 dom,也就是用户正在与之交互的东西。
@testing-library/react 的指导原则之一是
如果涉及到渲染组件,它处理的是 DOM 节点而不是组件实例,也不应该鼓励处理组件实例。
所以,你没有得到组件实例的方法,也没有自己调用组件的方法。相反,你就像用户一样在 DOM 上工作。想测试对服务器的异步函数调用吗?从 DOM 中获取按钮,点击它,模拟 API,然后在 DOM 中检查结果。
在 Enzyme 中,你有 ForceUpdate 方法来重新渲染组件。如果你在组件内部窥探一些箭头函数(组件内部的箭头函数是错误的),那么你将不得不强制更新组件。
在@testing-library/react 中,你没有任何这样的方法。相反,它只使用 DOM 进行测试。
在@testing-library/react 中,你没有直接的方法来测试组件的实例。所以,在 React 测试库中,没有对组件进行浅层或深层的渲染。
Enzyme 不是一个强约束(opionated)的库,它提供了访问组件内部的方法,即组件的实例方法、状态和道具。它提供了访问组件内部的方法,即组件的方法、状态和属性。但是 Enzyme 也提供了访问组件的 DOM 的方法,所以通过使用 Enzyme,你可以选择测试组件的内部结构,即组件的方法,状态和属性。
所以通过使用 Enzyme,你可以选择测试组件的内部,也可以选择不测试。Enzyme 并不强制执行任何关于你应该如何测试组件的意见。
@testing-library/react 是强约束(opionated)的库。它只提供给你渲染组件和访问 DOM 的方法,不提供访问组件的方法。它不提供访问组件内部的方法。
虽然@testing-library/react 的目标是与 Enzyme 竞争,并鼓励你从用户的角度编写可测试的代码和对应测试,但它们都有用例。你不能用一个代替另一个。有时你确实需要测试组件内部的状态变化或功能,尽管从用户的角度来看,它可能没有意义。在这些情况下,需要用 Enzyme 来测试实例方法。React Testing Library 很适合测试组件的 DOM,因为它允许你像用户使用它一样进行测试。
原文: Difference between enzyme and react-testing-library
译者: Evan
]]>给项目添加测试,并不单纯只是工具使用上的变化和代码量的增加,更为本质的变化,是代码设计上的变化、开发工作模式的变化
ps: 在开始使用 typescript 的类型约束后,也会有上述变化产生
1 | function sum(a: number, b: number) { |
1 | import { sum } from "./index"; |
1 | import React from "react"; |
1 | import React from "react"; |
1 | import React, { useState } from "react"; |
1 | import React from "react"; |
mock
1 | import React, { useState } from "react"; |
1 | import React from "react"; |
1 | import React, { useState } from "react"; |
1 | import React from "react"; |
1 | import React, { useState } from "react"; |
1 | import React from 'react' |
1 | /* istanbul ignore file */ |
关于 storeSlice 这种写法,可以看 @reduxjs/toolkit
1 | import React, { useState } from "react"; |
1 | import React from "react"; |
经过一些实践后,提炼出下面三个方面的测试:
好吧,首先,我离题了,我是这两个“框架”的忠实粉丝。我经常在 Twitter 或 Instagram 上看到对它们的赞赏,但是,在谈论这些工具时我被问得最多的问题是:哪个更好?
我应该使用 Next.JS 吗?但是我听说 Gatsby 也很 🔥,也许我应该使用 Gatsby?
因此,我想对此进行一些更深入的讨论,并希望得出一些更明确的选择。
让我们开始吧!
那么,除了以前听人提起过但从未真正了解过的流行语外,Gatsby 和 Next 是什么?
用最基本的话来说,create-react-app 会给你创建一个 React 项目的 boilerplate,而这两个框架将为你创建一个应用奠定基础。
不过,他们已经脱离了 create-react-app,在某种意义上,他们并没有被归类为 boilerplate,而是工具箱,打好了基础,然后给你一套如何建造房子的说明。
总结一下:
create-react-app - 为 React 项目奠定基础。剩下的就看你的了。
Gatsby & Next - 奠定 React 项目的基础。给你指导你应该如何在它们之上构建。
…
可是 那很奇怪?他们俩都做…同一件事?
有点。
乍一看,它们看上去都非常相似:
但实际上,它们有着本质上的区别。
OK,我们将要开始获取一些技术知识点,请紧跟我的思路…不会很糟糕的!
Gatsby 是一款静态网站生成工具。静态网站生成器在构建时生成静态 HTML。它不使用服务器。
Next.JS 主要是一个服务器端渲染页面的工具。每当有新的请求进来时,它都会利用服务器动态生成 HTML。
当然,两者都可以在客户端调用 API。根本的区别是 Next 需要服务器才能运行。Gatsby 完全可以在没有任何服务器的情况下运行。
Gatsby 只是在构建时生成纯 HTML/CSS/JS,而 Next 则在运行时生成 HTML/CSS/JS。所以每次有新的请求进来,它都会从服务器上创建一个新的 HTML 页面。
我不打算在这里太深入地了解每个人的优点和缺点,然而,为了更深入地阅读,请查看这篇文章。- https://dev.to/stereobooster/server-side-rendering-or-ssr-what-is-it-for-and-when-to-use-it-2cpg
这两个工具之间的另一个根本区别是它们处理数据的方式。
Gatsby 告诉你应该如何处理应用中的数据。
Next.js 则完全由你自己来决定。
Gatsby 使用的是一种叫做 GraphQL 的东西。GraphQL 是一种查询语言,如果你熟悉 SQL,它们的工作方式非常相似。用户将使用一种特殊的语法,在组件中描述用户想要的数据,然后这些数据将会被提供。
当组件需要时,Gatsby 会在浏览器中提供这些数据。
一个例子:
1 | import React from "react"; |
在上面的例子中,你可以看到我们有一个查询来获取标题,然后在组件中显示标题。太棒了!
Gatsby 还有很多针对各种数据源的插件,这(理论上)使得它很容易针对很多数据源进行整合。一些数据源插件的例子是 Contentful、Wordpress、MongoDB 和 Forestry。这允许你做一些事情,比如把你的网站挂到一个 CMS 上,并对内容进行外部控制。
当为生产构建时,不再使用 GraphQL,而是将数据持久化为 JSON 文件。
… 好吧,酷。
Next.js 则是另一种方式:如何管理数据完全由你自己决定。你必须在自己的架构上决定如何管理数据。
这样做的好处是,你不会被捆绑在任何你想或不想使用的技术上。
你应该使用 Gatsby 还是 Next,很大程度上取决于你的实际情况,因为其实他们都很酷。
如果你有很多内容,或者你希望你的内容随着时间的推移会有很大的增长,那么静态生成的网页并不是你最好的解决方案。原因是,如果你有很多内容,建立网站需要很多时间。
当创建一个有数千页的非常大的应用时,重建速度会相当慢。而且如果你必须在点击发布后等待一大段时间才能上线,这不是一个完美的解决方案。
因此,如果您的网站内容随着时间的推移会不断增长,那么 Next.JS 是您的最佳选择。
另外,如果您希望在访问数据方面有更多的自由,那么 Next.JS 值得一提。
在这里值得一提的是,Next 的文档是我见过的最好的文档。它有一个交互:在您浏览完一节内容时,会对你进行测验,以确保你能跟上:)真棒! 👏
在创建小型网站和博客时,我倾向于使用 Gatsby,这是我个人的喜好。它的生态系统非常适合连接到 CMS(简直是轻而易举),并且有一些很棒的指南,告诉你如何去使用它。
在我看来,Gatsby 更容易上手和运行,这一点值得记住。同样,文档的水平也很高,里面有很多教程,可以跟着学。
Gatsby 还附带了一些 “入门 “模板,以及一个相对较新的特性”主题”,这些都使得一个功能齐全的网络应用能够快速地建立和运行。
原文: Gatsby vs Next.JS - What, Why and When?
作者: James Bedford
译者: Evan
]]>1 | <div style="position: fixed;"> |
如上,div 中的 iframe 内容超过屏幕长度,却无法滚动
1 | <div |
触发弹框中 input 元素 blur 事件时,对弹框聚焦
对于可能产生滚动条的子元素,设置子元素的 min-height 大于父元素的 height,尽量使 iOS 产生 scrollView
问题未彻底解决:在上述三种处理方式叠加后,频繁触发 input blur 事件,仍有小几率触发弹框无法滚动的现象
使用 gatsby cli 遇到问题
使用 gatsby cli 构建初始项目
1 | gatsby new my-blazing-fast-site |
遇到问题:
1 | > pngquant-bin@5.0.2 postinstall /Users/yantianyu/Documents/repo/my-blazing-fast-site/node_modules/pngquant-bin |
https://github.com/gatsbyjs/gatsby/issues/20389
https://gist.github.com/XYShaoKang/ae657eb81279528cca718c678be28215
]]>站点使用 hexo next 主题,配置文件中已经带了使用 jsDelivr cdn 的资源配置实例,用户启用便可
根目录下themes\next\_config.yml
文件中,vendors 项下,所有资源皆可配置使用 jsDelivr cdn
1 | vendors: |
站点静态资源,也使用 jsDelivr cdn
根目录下themes\next\_config.yml
1 | # Assets |
使用图片懒加载,减少首屏加载时间
项目根目录下,执行 npm install hexo-lazyload-image --save
根目录下_config.yml
1 | # 图片懒加载 |
博客开启了 post_asset_folder 选项
根目录下_config.yml
1 | post_asset_folder: true; |
并在文章中使用 这种方式引用图片,则打包出来的图片,会是这种形式:
1 | https://evanhongyousan.github.io/2020/05/23/landing-page-builder-introduct/image2020-5-14_17-40-17.png |
可以看到图片并不在 images
文件夹中,则文章图片并未使用 cdn 加速
执行 npm install hexo-tag-asset-img --save
根目录下_config.yml
1 | asset_img_url: https://cdn.jsdelivr.net/gh/evanhongyousan/evanhongyousan.github.io |
打包发布后,可以看到文章对图片的引用已经变更:
1 | https://cdn.jsdelivr.net/gh/evanhongyousan/evanhongyousan.github.io/2020/05/23/landing-page-builder-introduct/image2020-5-14_17-40-17.png |
完成上述步骤后,可以看到页面加载仍有资源阻塞
1 | # 404 |
则使用 fonts.googleapis.com 镜像处理字体资源文件加载失败的问题
根目录下_config.yml
1 | font: |
对公司而言,需要一个统一用户管理体系,其用户后续可根据相关法规协议应用于公司品牌下的其他业务。
统一登录组件基础搭建,实现用户的登录、注册功能。此标准组件可以应用在品牌下下所有需要用户登录、注册的场景。
以上接口参数与 page path 和下面服务参数保持一致
在移动端场景中,‘滑动’交互已经是个常见的交互。而在移动端浏览器下,对页面的滑动又有可能触发 浏览器回退/浏览器下拉刷新
,影响交互结果
1 | var xPos = null; |
压缩版:
1 | var xPos = null; |
1 | html, |
如果样式设置不生效:
1 | (function () { |
谷歌分析,是 Google 发布的一款免费的网站分析服务,包括多个报告,可对整个网站的访问者进行多角度的跟踪,并能持续跟踪营销广告系列的效果,GA 默认提供的报告如下:
Google Analytics Solution 是一个完整的数据解决方案,从数据的收集、管理、分析、可视化、优化、到终极目的转化,一气呵成,其中各个产品相互依赖,构成了强大的企业级数字营销整合工具。
作为开发,在这项工作流中,属于数据的第一道经手人,一般关注 Google analysis 和 Google tag manager 即可
对于上述概念,网上有很丰富的资料,可以使用 Google 搜索“ga+概念名称”,通过更详细的图文或视频进行理解,这边就不做详细描述了。
对于开发,需要重点理解【事件】概念与【来源、媒介和广告(流量来源追踪)】概念,这是开发做相关对接涉及到最多的概念。
字段 | 说明 |
---|---|
账号 | ga 的入口,整个组织的最高层级,一个 google 账号可以有 100 个 ga 账号 |
媒体资源和应用 | 网站、移动应用或设备。一个账号可以包含多个媒体资源,上限 50。一个媒体资源对应一个网站或应用 |
数据视图 | 报告的入口。通过这种定义的视图可以查看媒体资源中的数据。一个媒体资源可以有 25 个数据视图 |
可以在左上方做三个纬度的切换,在左下方(管理按钮)做三个纬度的管理
gtm 属于代码管理工具,对于开发的影响:接入 gtm 后,事件上报相关代码就可以不再由开发在项目代码中插入,改为在 gtm 后台中处理。
场景 | 触发器 | 说明 |
---|---|---|
网页浏览 | 网页浏览 | 在网络浏览器开始加载网页时立即触发 |
DOM 已准备就绪 | 在浏览器在 HTML 中完成整页构建且文档对象模型 (DOM) 做好解析准备后触发 | |
窗口已加载 | 在页面(包括图片和脚本等嵌入资源)完全加载后触发 | |
点击 | 所有元素 | 跟踪网页上任何元素(例如链接、图片、按钮等)获得的点击 |
仅链接 | 仅跟踪使用 元素的 HTML 链接(如 Google.com)获得的点击。 | |
其他 | 元素可见性 | 当网络浏览器视口显示所选元素时,就会触发 Google 跟踪代码管理器的元素可见性触发器。 |
表单提交 | 在发送表单时触发 | |
历史记录更改 | 网址片段(井号后面部分)发生更改或网站使用的是 HTML5 pushstate API,那么基于历史记录更改事件的触发器将会触发 | |
JavaScript 错误 | 发生未捕获的 JavaScript 异常 (window.onError) 时触发 | |
滚动深度 | 根据用户向下滚动网页的距离来触发 | |
计时器 | 定时间隔向跟踪代码管理器发送事件 | |
YouTube 视频 | 根据用户与嵌入网页中的 YouTube 视频进行的互动来触发代码 | |
自定义事件 | 跟踪您的网站或移动应用上发生的、未按标准方法处理的互动 | |
触发器组 | 将两个或多个触发器的条件作为一个进行评估 |
更详细的说明,请看触发器说明文档:https://support.google.com/tagmanager/answer/7679319?hl=zh-Hans&ref_topic=7679108
字段 | 说明 |
---|---|
账号 | gtm 的入口,整个组织的最高层级 |
容器 | 网站、移动应用或设备。 |
以上,就是 gtm 的基本接入实例
营销推广人员通常使用 ‘https://evanhongyousan.github.io/?utm_source=googleTagManager&utm_medium=fragment&utm_campaign=fragmentTest’ 这样的链接进行流量追踪,但链接完全可以做到‘https://evanhongyousan.github.io/#agency1’ 这样的形式,以动态化配置各项 utm 参数
身担运营、推广职责的人员,通常会对特定的运营类、推广类页面有较为精准的数据统计需求,因为他们在制定相关报表时,经常会以单个运营活动为中心进行数据组织
则上述人员在使用 ga 时,会希望数据视图只展示某个特定活动的数据,故他们在提相关活动需求时,每个活动都会给出新的 ga 埋点需求。
但其实,ga 本身已经给出了更为优雅的使用方式
下面以一个实例进行说明
blog 站点 https://evanhongyousan.github.io/ 整站都进行了 ga 埋点,故可以在 ga 后台中看到相关上报数据:
blog 站点以 blog 生成时间来组织相关 url,其文章页面 url 皆以下面这样的格式生成:
1 | https://evanhongyousan.github.io/2019/12/29/load-img-step-by-step/ |
站点运营人员希望后续专门统计 2019 年 7 月下所有文章报表数据,可以这么做:
2019-07's page
2019-07's page
,过滤器->添加过滤条件/2019/07/
(比如https://evanhongyousan.github.io/2019/07/09/redis-transaction/ ),故过滤条件可以这么设置创建了新数据视图并设置好过滤器后,再过几小时,可以看到 ga 后台中会有相应数据:
ps:google 的说明文档 验证数据视图过滤器 中,新创建的过滤器需要等待 24 小时才能产生效果。本次实践只需要几小时即看到效果,原因可能是数据量小。
1 | set NODE_ENV=production node xxx.js |
1 | export NODE_ENV=production node xxx.js |
1 | npm install --save-dev cross-env |
1 | { |
在web 性能优化之图片部分中收集了一个把图片转换为低质量 base64code 的工具: lqip-loader。但具体如何将低质量图片占位符和图片逐步加载相结合,就需要自行实践。以下是一次实践探索。
npx create-react-app lqip-loader-demo
npm run eject
暴露 webpack 构建配置npm install --save-dev lqip-loader
config\webpack.config.js
中添加 lqip-loader 配置1 | { |
上述步骤完成后,webpack 打包输入的图片资源,就会变为一个对象:其中 src 属性为原图片资源,preSrc 属性为不超过 400byte 的低质量图片 base64code,下面是一个实例:
修改src\App.js
文件
1 | import React from "react"; |
于是可以看到第一个 img 标签直接使用低质量 base64code,第二个 img 标签仍然对高质量的图片资源做出请求
图片逐步加载,就是在页面载入时,先显示低质量 base64code 图片占位符,在页面 onload 事件触发后(就是用户可交互后),再进行高质量图片的载入
要完成上述需求,需要完成组件src\LqImg.js
:
1 | import React from "react"; |
然后改写src\App.js
:
1 | import React from "react"; |
完成后,可以看到
上述 demo 可看 lqip-loader-demo
一般而言,在静态资源中,图片大小占比远大于 js、css 大小占比。对图片压缩的一些体积,可能已经比完整的 js、css 文件要大。故在 web 性能优化范畴中,图片优化是非常重要的组成部分。
根据自身项目所属工作流,比如 webpack 或 gulp,搜索webpack images compress
或gulp images compress
便可
不同网络环境(Wifi/4G/3G)下,加载不同尺寸和像素的图片
JavaScript 绑定事件检测窗口大小
CSS 媒体查询
1 | @media screen and (max-width: 640px) { |
img 标签属性
1 | <img |
在清晰的图像加载完成前,先使用统一占位符
前两天看到个关键词是“github action”,经查询了解到是 github 的持续集成服务,今天来进行配置尝试
项目 | 说明 |
---|---|
https://github.com/EvanHongYousan/blog | blog 源码 |
https://github.com/EvanHongYousan/EvanHongYousan.github.io | 静态文件 |
ssh-keygen -f blog-deploy-key
,在 .ssh/
文件中 生成一组 公钥(blog-deploy-key.pub)与私钥(blog-deploy-key)setting -> deploy keys -> add deploy key
,命名p_rsa
,把 blog-deploy-key.pub 中的内容填入这里注意,需要把
Allow write access
选项钩上
setting -> Secrets -> add a new secret
,命名s_rsa
,把 blog-deploy-key 中的内容填入1 | deploy: |
.github/workflows/main.yml
文件1 | name: Deploy Blog |
1 | ? Something is already running on port 8000. |
但我检查后发现 8000 端口并未被占用
添加 host:
1 | 127.0.0.1 localhost |
于是 roadhog 的服务可正常监听 8000 了
我把上面那个 host 取消,在命令行中 ping 了一下 localhost
1 | ping localhost |
发现解析失败
估计是 roadhog 内部执行某操作时,直接使用了“localhost”,但 localhost 在 mac 上会解析失败,于是导致问题出现
]]>linux 是默认区分文件名大小写
另外观察到,至少在 os x 下,git 默认也不区分文件名大小写
这就很容易带来问题
估计和文件系统有关,先不深究
代码中,对 api.js 的引用是这么写的
1 | import { xxxMethod } from "./Api"; |
在 os x 中运行正常,未报错
部署至 linux 服务器时,就报典型的“can’t resolve ./Api ……” 这样的错误
解决方式很明显:文件api.js
改名为 Api.js
不过,在本地通过编辑器做了文件名更改后,发现 git 未检测到文件名变动,于是要对此进行解决
经搜索后,发现有下面几个方法
git mv -f OldFileNameCase newfilenamecase
git config core.ignorecase false
1 | Rename FILE.ext to whatever.ext |
方法 2 也会引来问题,具体请看解决 Git 默认不区分文件名大小写的问题
最后选择了方法 1,问题解决
本次问题出现,说明”文件名建议只使用小写字母,不使用大写字母。”这种规范果然有其出现的原因。
后续制定相关规范时,若无特别原因,都要将其纳入
How do I commit case-sensitive only filename changes in Git?
解决 Git 默认不区分文件名大小写的问题
为什么文件名要小写?
当前 web 应用访问速度过慢,用户等待时间过长,对用户体验造成很大影响。而用户访问 web 页面等待时间过长,会降低用户对品牌的信任度,加大了品牌运营工作的困难度
另外,搜索引擎会降低加载速度过慢站点的权重,使站点曝光率下降
工具选型
压缩率对比
结论
改动背景
目标
工具确认
解析输出 hash name 的影响
改动说明
对开发的影响
改动背景
目标
选型
分析
决策
改动说明
改动影响
改动背景
选型
分析
决策
改动说明
改动影响
至此,准备完成
通过 img 标签,使用 ga 的Measurement Protocal Reference协议,把数据上报至 ga server。下面是一个 img 标签例子:
1 | <img src="https://www.google-analytics.com/collect?v=1&tid=UA-XXXXXXXX-1&cid=CLIENT_ID_NUMBER&t=event&ec=email&ea=open&el=recipient_id&cs=newsletter&cm=email&cn=Campaign_Name&z=123456" /> |
各个参数含义
key | value 说明 |
---|---|
v | ga 版本号码,值为 1。必填 |
tid | ga 跟踪 ID, 用于区分是要向哪个 Google Analytics(分析)媒体资源发送数据。必填 |
cid | 每位用户专属的 ID,必填 |
t | 针对特定用户收集的互动数据类型,针对当前文档的场景,可考虑设为 emailview。必填 |
ec | 事件类别,这裡设 email |
ea | 事件活动,这裡设 open |
el | 事件标籤 |
cs | 广告活动来源,这裡设 newsletter |
cm | 广告活动媒介,这裡同样设 email |
cn | 广告活动名称 |
z | 缓存无效化随机数字 |
更详细的参数参考在这里
下面是一个真实例子:
1 | https://www.google-analytics.com/collect?v=1&tid=UA-146663661-1&cid=001&t=event&ec=email&ea=open&el=recipient_id&cs=newsletter&cm=email&cn=Campaign_Name&z=123456 |
使用浏览器直接做 get 请求
在 ga 中可以看到
当然,使用 img 标签的缺陷,就在于当邮件系统阻挡图片时,上报会失效
追踪邮件链接,主要使用网址产生器
为链接加上 get 参数标识别。
比如,给
1 | https://act.moomoo.com/invite?code=643273466b693964783047384d452b397054794277773d3d&type=promotion |
加上 get 参数标识,变成
1 | https://act.moomoo.com/invite?code=643273466b693964783047384d452b397054794277773d3d&type=promotion&utm_source=google&utm_medium=email&utm_campaign=invite_act&utm_term=invite%2Bstock&utm_content=invite_link |
若目标页面引入了 ga 跟踪代码,ga 跟踪代码会识别到链接上的 get 参数,进行上报时就会按照参数值进行归类整合
以下是 google 提供的参数填写说明
key | value 说明 | example |
---|---|---|
utm_source | 广告活动来源:必须提供,表示搜寻引擎、电子报名称或其他来源。 | utm_source=google |
utm_medium | 广告活动媒介:必须提供,表示媒介,例如电子邮件或单次点击出价。 | utm_medium=cpc |
utm_term | 广告活动字词:用于付费搜寻,表示此广告的关键字。 | utm_term=running+shoes |
utm_content | 广告活动内容:用于 A/B 测试和指定内容广告,表示连到同一个网址的不同广告或连结。 | utm_content=logolink 或 utm_content=textlink |
utm_campaign | 广告活动名称:用于关键字分析,表示某项产品促销或策略性广告活动。 | utm_campaign=spring_sale |
对于业务事件触发的自动邮件推送,开发人员只需要往邮件模板里添加 img 标签、跳转链接添加 ga 参数便可
对于公司内部产品、运营人员的主动推送的营销邮件,也可请求开发人员协助,望 html 中添加上报。
所以,加上报并不算麻烦,相关工作重点仍然是营销渠道开发与管理维护。
但如果要计算距离某点一定范围内有多少个点时,“遍历所有的点计算出距离再做比对”在性能上肯定无法令人接受。
所以,这种时候,可以考虑直接使用 sql 划分矩形:
1 | select id from positions where x0-r < x < x0+r and y0-r < y < y0+r |
不过,这种查询方式,在高并发场景,性能可能仍然达不到要求
业界比较通用的地理位置距离排序算法是 GeoHash 算法。
以一句话总结的话:GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。
假设有一个点[31.1932993, 121.43960190000007],则:
之后,作出查询时,就是:
1 | select id from positions where geoHash like "wtw37%"; |
获取到的点都按勾股定理算一算,排除不合理的点
仔细观察相邻方格,我们会发现两个小方格会在 经度或纬度的二进制码上相差 1;我们通过 GeoHash 码反向解析出二进制码后,将其经度或纬度(或两者)的二进制码加一、减一,再次组合为 GeoHash 码。然后获取到的点都按勾股定理算一算,排除不合理的点
不过,在人力资源和产出需求相矛盾的情况下,iframe 又是解决这一矛盾的利器。
在一个纯展示推广页中,有个区域,固定展示之前已经做好的另一页面里的部分内容,且此区域不可滚动。
之前已做好的页面实际上在另一站点,逻辑较为复杂,业务耦合度高,很多业务并未抽出做成公用服务。
当前推广站点若要重构相关内容,花费的代价会比较大。经讨论,决定使用 iframe 载入相关需要展示的页面。
问题背景:产品希望实时根据页面内容生成长图,引导用户分享长图
结论:iframe 里面的内容无法被解析,所以这个需求点只能放弃
经过这半年的满负荷和之前几年的半负荷「活动页」项目运载,不论是产品侧还是开发侧,都可以明显察觉到 – 大部分的活动推广页,其实都有着相似的流程、逻辑,当中有很多明显可复用的模块在等待被抽象提取。
对于这些可复用的模块,目前可抽取出这么一些:
根据上面罗列的模块,模块间有一定的层级关系,但同一层级下的不同模块基本互不干涉。而活动推广页的构建,也基本是上述模块的堆砌。
所以,如果要完成「活动页可配置化」这个需求,基本要做到下面几点:
按照上述分析,可使用组合模式来满足上述需求
一句话描述:组合模式就是构建一个树,然后对里面的节点进行深度优先的遍历。
下面是一个简单地例子:
1 | <html> |
组合模式解决的问题,主要是模块的统一组织、驱动问题。这里面的前提条件有两个:
初步来看,上面提取出的上报模块、大部分奖励业务模块可以很好地和组合模式结合,而分享模块和奖励模块中的邀请小模块有一定的联系,目前还不好处理。
因为不了解后端业务情况,上述模块抽象提取在和后端业务结合后,肯定还有比较大的调整。
目前先思考这么多,完。
]]>有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
首先是几个按钮的绘制:
1 | <body> |
然后定义一个 setCommand 函数,setCommand 函数负责往按钮上面安装命令:
1 | var setCommand = function (button, command) { |
定义两个功能对象:
1 | var MenuBar = { |
定义几个命令对象:
1 | function RefreshCommand(receiver) { |
最后,通过命令对象,把命令接收者(功能对象)和命令请求者(按钮)联系起来:
1 | var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar); |
1 | var bindClick = function (button, func) { |
可以看到,上面这段代码中,并没有 command 和 receiver 两个概念。
原因就是,相对于简单例子中的传统命令模式实现,javascript 版命令模式实现利用了高阶函数特性(函数可作为参数被传递,函数可作为返回值被输出)。
首先是一种最简单的撤销:针对上一步的操作,再做一次’反向操作’。下面是一些简单例子的罗列:
下面是一个例子:
1 | <body> |
1 | var ball = document.getElementById("ball"); |
添加一个 undo 按钮:
1 | <button id="cancelBtn">cancel</cancel> <!--增加取消按钮--> |
添加 undo 操作:
1 | MoveCommand.prototype.undo = function () { |
移动操作(execute 方法)也需要改写:
1 | MoveCommand.prototype.execute = function () { |
关联 undo 按钮和 undo 操作:
1 | var cancelBtn = document.getElementById("cancelBtn"); |
针对撤销功能的实现,上面的是对上一步操作做反向操作,但其实我们也可以换一种思路:把整个环境初始化,然后把做过的操作重新执行一遍,执行到准备撤销的前一步操作为止。
下面是一个利用重做功能去完成撤销功能的例子:
1 | <html> |
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。所以,迭代器模式可以把迭代的过程从业务逻辑中分离出来,完成一次解耦。
1 | var each = function (ary, callback) { |
上面的 each 函数属于内部迭代器,each 函数的内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用。
内部迭代器在调用的时候非常方便,外界不用关心迭代器内部的实现,跟迭代器的交互也仅仅是一次初始调用,但这也刚好是内部迭代器的缺点。由于内部迭代器的迭代规则已经被提前规定,上面的 each 函数就无法同时迭代 2 个数组了。
比如现在有个需求,要判断 2 个数组里元素的值是否完全相等, 如果不改写 each 函数本身 的代码,我们能够入手的地方似乎只剩下 each 的回调函数了,代码如下:
1 | var compare = function (ary1, ary2) { |
外部迭代器必须显式地请求迭代下一个元素,下面是一个外部迭代器的实现:
1 | var Iterator = function (obj) { |
下面是 compare 函数的改写:
1 | var compare = function (iterator1, iterator2) { |
针对有一定排列顺序的迭代器,有正序,自然有倒序。如果上面 [迭代器模式的简单实现]一节中的迭代顺序为正序,则倒序会是下面这样:
1 | var reverseEach = function (ary, callback) { |
分析下来,迭代器有必要提供一个停止迭代的方式,以避免资源的浪费。下面是一个终止迭代的方式:
1 | var each = function (ary, callback) { |
不同浏览器环境下,获取到上传对象的方式是不同的:
1 | var getActiveUploadObj = function () { |
如果要通过迭代器获取上传对象的话,大概是这个样子:
1 | var iteratorUploadObj = function () { |
1 | if (type1) { |
增强一点可读性:
1 | if (type1 && type2 === 1) { |
上面的判断逻辑十分常见,是程序后续运营维护的噩梦之一。当业务复杂度开始膨胀后,这样的判断逻辑代码会变得十分难读,修改起来也是非常困难。随着版本的迭代与开发人员的变更,这样的判断逻辑代码最后有可能变成无人敢动的代码块。
观察上面的原始代码,可以看到总共有 4 个判断条件。要把这些判断条件都构造成节点,串联成一个职责链,则这些节点,都必须暴露出一个相同的接口。
先构建 4 个判断函数
1 | function condition1(type1, type2) { |
建立节点类,用于实例化节点
1 | function Chain(fn) { |
于是,可以构成职责链:
1 | var chain1 = new Chain(conditin1); |
当判断函数需要通过异步请求结果才能知道是否调用下一个判断函数时,节点对象自身需要有一个主动调用下一个节点的方法
1 | Chain.prototype.next = function () { |
example:
1 | function condition5(type1, type2) { |
http 协议在 TCP/IP 协议群中的位置:
TCP/IP 协议群的分层:
其中,与 HTTP 关系密切的协议:
名称 | 分层结构 | 主要作用 | 作用描述 |
---|---|---|---|
DNS 服务 | 应用层 | 负责域名解析 | 根据域名查询 IP 地址,或根据 IP 地址反查域名 |
TCP 协议 | 传输层 | 确保可靠性 | 提供可靠的字节流服务 |
IP 协议 | 网络层 | 负责传输 | 把各种数据包传送给对方 |
用户通常使用主机名或域名来访问对方的计算机,而不是直接通过 IP 地址访问。因为与 IP 地址的一组纯数字相比,用字母配合数字的表示形式来指定计算机名更符合人类的记忆习惯。但要让计算机去理解名称,相对而言就变得困难了。因为计算机更擅长处理一长串数字。
为了解决上述的问题,DNS 服务应运而生。DNS 协议提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。
TCP 协议为了更容易传送大数据,将大块数据分割成以报文段(segment)为单位的数据包进行管理。采用三次握手(three-way handshaking)策略,把数据包送出去后并确认是否成功送达(确保可靠性)。
握手过程中使用了 TCP 的标志(flag) — SYN(synchronize) 和 ACK(acknowledgement)。发送端首先发送一个带 SYN 标志的数据包给对方。接收端收到后,回传一个带有 SYN/ACK 标志的数据包以示传达确认信息。最后,发送端再回传一个带 ACK 标志的数据包,代表“握手”结束。若在握手过程中某个阶段莫名中断,TCP 协议会再次以相同的顺序发送相同的数据包。
IP 协议的作用是把各种数据包传送给对方。而要保证确实传送到对方那里,则需要满足各类条件。其中两个重要的条件是 IP 地址和 MAC 地址(Media Access Control Address)。
名称 | 描述 |
---|---|
IP 地址 | 指明了节点被分配到的地址,容易发生改变 |
MAC 地址 | 是指网卡所属的固定地址,一般不会发生改变 |
IP 间的通讯依赖 MAC 地址,而 IP 地址可以和 MAC 地址进行配对,并且通过 ARP 协议(Address Resolution Protocol)来解析地址,从而通过通信方的 IP 地址反查出对应的 MAC 地址。当主机 A 要与主机 B 通信时,ARP 协议(地址解析协议)可以将主机 B 的 IP 地址(192.168.1.2)解析成主机 B 的 MAC 地址,MAC 地址一旦确定,主机 A 就能向主机 B 发送 IP 通信了。
web 页面性能优化,主要是以下几个方向:
在过去,设计师负责设计,程序员负责编码。他们间互不干涉。但随着 CSS 中 transitions 和 animations 的到来,设计和编码的界限模糊了。设计师描述设计,而程序员把设计师的描述翻译成具体代码–这种合作模式不再像过去那么简单。为了高效地协作,设计师必须懂点代码,而程序员必须懂点设计。
举个例子,假设一位设计师要求开发人员实现一个如下所示的盒子反弹动画,在没有跨界知识和通用标准表述的情况下,设计师和程序员的沟通会有少许信息丢失。程序员没有足够的信息去了解设计师的意图,设计师也不知道他们到底可以选择什么。这样的沟通会有信息缺失,你最后完成的效果可能是下面这样的:
这样的效果并不会令人兴奋。虽然这已经符合动画效果的基本标准,但我们绝对可以做得更好。
首先要看的是 animation-timing-function 属性。在上面的例子中,我们对此属性赋值 linear,这意味着盒子以相同的速度不断运动。在某些情况下,这是可取的;然而,在现实世界中,运动通常不是线性的。
一个简单的解决方法是改变 animation-timing-function 属性。这使得每个动画的开始部分和结束部分比中间部分稍慢,这会令一些动画更自然。以下是启用了缓动功能的方块:
这只是一个小小的改进,所以我们仍有很多工作要做。同一时间内一次又一次地出现相同的动画,令方块看起来仍然机械又僵硬。在反弹之间增加一点点延迟,可让动画看起来更自然些:
现在这个动画看起来就像盒子自己在跳跃,而不是简单地被上下移动。跳跃之间有一点蓄力和停滞,模仿了活着的生物做同样的事情时会有的表现。尽管我们没有提供盒子跳跃的设计参考,但我们都对生物跳跃的表现有很好的了解。我们知道真实跳跃是什么样子,通过模仿,动画可以更自然。我们可以做更多的事情来让这种感觉变得更加突出。
如果你看动画片,你会注意到一些现实生活中的动作往往会被夸大、漫画化。做得好的话,这些动作就像在真实世界中一样自然,还带了些特有的魅力和个性。
在这个阶段,设计师和开发者之间的合作是至关重要的 – 但许多设计师可能都不知道这些选择的存在,所以得提醒开发人员将这些选择提供给设计人员。
通过在方块上添加一些轻微的变形,我们可以令动画丰满许多:
现在,盒子是一个活着的角色。仍有很多事情需要调整,但这已经比原来的动画更进一步 – 以一种非常好的方式。
现在我们更进一步,在跳跃结束时添加一点反弹:
第二次反弹让盒子看起来更有活着的感觉,但似乎仍然缺失了些东西。与其他成熟动画相比,这个反弹看起来很僵硬。我们需要再添加一点扭动:
最终的微妙变形使得反弹看起来更加自然。总体而言,第一个例子中我们的基本线性反弹有了巨大的改善。
这正是我们正在寻找的东西,但我们仍可以用定制的三次 Bézier 曲线进一步调整移动速率:
如果设计人员和开发人员不都了解基本的动画原理和控制方式,那就不可能做出生动的动画。这篇文章只是抓住了这两个领域一些浅显的地方。如果您是网页设计师或与设计师合作的网页开发人员,我强烈建议您阅读这两个领域的内容。
对于动画原则,奥利约翰斯顿和弗兰克托马斯的《The Illusion of Life: Disney Animation》是一本伟大的入门书籍。学会关于动画原则的通用语言后,设计人员和开发人员之间的沟通和协作将变得更加容易。
对于 CSS 动画的控制和变化,其可能性几乎是无止境的,因为延迟和计时很容易调整。如前所述,如果您不喜欢现成的易用的定时功能,则可以使用 cubic-bezier()来创建自己的定时功能。您还可以对动画做出调整,使其更接近于漫画或更接近于现实。重要的是,设计师和开发人员都要考虑这些变化,而不是盲目地沟通且不考虑用户体验。互相共享知识和互相协作可以将简单的动画变成很棒的动画。
原文:https://css-tricks.com/making-css-animations-feel-natural/
译者:evanyan
]]>原生 canvas 只提供了画布实现,在游戏开发角度上,对开发者而言,仍有许多工作要做:
其中,除游戏业务逻辑外,其余部分皆可重复使用。
所以,可以认为,引入游戏引擎有一定的必要性。
我们的目的是用户增长,具体手段是营销活动页。
所以,游戏载体是营销活动页,则游戏的业务与体积都必须轻量、小型,便于用户接受、传播。
另外,微信小游戏在传播方面有其天然的巨大优势,在这方面也要有一定的考虑。
对于游戏引擎的调研,需要从以下角度调研:
Hilo 作为一个跨终端的互动小游戏解决方案,同时有称综合解决方案。从它的演变来看,Hilo 属于阿里前端在实践总总结出来的一套工具库。
Egret 不仅仅提供了一个基于 HTML5 技术的游戏引擎,更是提供了原生打包工具和众多周边产品,使其成为“解决方案”
老牌又流行的解决方案
满分 5 星
比较维度:
1 | function callBack(str) { |
1 | var logPick = (function () { |
装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
当脚本运行时,在子类中增加行为会影响原有类所有的实例,而装饰者却不然,它能给不同对象各自添加新行为。这就是装饰者模式的好处。
1 | Function.prototype.after = function (afterfn) { |
1 | Function.prototype.before = function (beforefn) { |
通过 CSS 的 display 属性,我们可以控制元素及其子元素在页面绘制时的表现。通过 display:inline,我们可以将这个盒子放在其兄弟姐妹中,就像文本一样。通过 display:table,我们可以欺骗盒子,使其表现的像 table 元素一样。
对于 display 属性,有两个值可以控制元素是否构成盒子。none 值将导致盒子与其内容不在页面上绘制。另一方面,新规则 display:contents; 会令盒子周围的框会被完全省略,但其内容会被正常绘制。
理解 display: contents; 最简单的方法,就是想象元素的开始标签和结束标签被删除,只剩下内容。在规范中,它规定 -
For the purposes of box generation and layout, the element must be treated as if it had been replaced in the element tree by its contents
例如下面的标签 -
1 | <div class="outer"> |
和下面的样式 -
1 | .outer { |
通常情况下,我们期望在页面上绘制元素的方式会是这样 -
但是,如果我们将 display: contents 添加到.outer 元素样式中,那么它将这样显示 -
从视觉上而言,上述结果与我们所期望的结果完全相同–元素的开始标签和结束标签被删除的话,的确会是这个样子。
这个 CSS 规则虽然看似简单,但有很多边界案例和特定行为需要注意。我们必须记住,display: contents; 规则仅仅会令页面在绘制元素时,不绘制它的‘框’,它不会对 html 文档中的标签有其他任何影响。
如果元素被其内容替换,那么对于应用于它的 attribute 意味着什么?由于这个替换只是可视化层面上的替换,html 文档中,标签仍然存在,所以我们实际上仍然可以通过它的 attribute 来对元素进行选择、定位和交互。
我们仍然可以通过它的 ID 来标识元素,例如,使用 aria-labelledby 对其进行引用。
1 | <div id="label" style="display: contents;">Label here!</div> |
但是,我发现我们无法再使用元素 id 导航到元素。
1 | <div id="target" style="display: contents;">Target Content</div> |
正如我们刚刚介绍的,我们仍然可以定位使用 display: contents; 的元素。实际上,我们也可以定位使用 display: none; 的元素,但绑定在上面的事件永远不会被触发,因为我们无法和元素进行交互。但是,由于使用 display: contents; 的元素仍然可见,我们可以通过元素内容与其进行交互。
例如,我们对一个元素设置点击事件,并打印 this 值。我们仍然可以获取.outer 元素,因为它的标签仍然存在于文档中。
1 | <div class="outer">I’m some content</div> |
使用 display: content; 的元素的伪元素被认为是其子元素的一部分,因此显示为正常。
1 | <style> |
上面的 style 标签会产生下面的样式 -
对于替换元素,请看这里: 替换元素(replaced element)与非替换元素(non-replaced element)
当替换元素与 from 元素使用 display: contents; 时,他们会有不同的表现。
替换元素,比如 img 元素,其外观与盒模型由外部资源控制。试图删除这样的元素框并没有什么意义,因为程序不清楚此元素的盒模型是什么(此句的原文: Attempting to remove the box for elements like this doesn’t really make sense because it isn’t entirely clear what the “box” is.)。对于这些元素,display: contents 的功能与 display: none 完全相同。元素的整个框和内容都没有绘制在页面上。
从我们网页作者的角度来看,很多表单元素并不是由一个个“盒子”组成。但在背后,它们由一些较小的元件组成。与被替换的元素类似,删除这些元素的框没有意义,因为他们没有构成盒模型。因此,对于 select,input 和 textarea 等表单元素,display: contents 的功能与 display: none 完全相同。
full list of elements that display: contents works differenly fo
当涉及到 display: contents 时,button 和 a 元素似乎都没有任何特殊的行为。然而,了解这条规则如何影响它们是有用的,因为这些影响可能不会立刻表现出来。
按钮不是由其他框组成的表单元素之一。因此,display: contents; 将只删除按钮周围的框,而让按钮的内容正常显示。如果在表单中使用,单击按钮仍然会尝试提交表单,正如我们已经介绍的那样,按钮上的任何事件侦听都将正常运行。
对于链接,也是相同的情况。视觉层面上,元素周围的框会被删除,链接的内容会被保留。由于 attribute 通常不受此 CSS 规则的影响,因此该链接仍然可以正常运行,并可用于正常导航。
在过去,我们必须以语义化和 CSS 样式化的方式来设计 HTML。这导致我们要么包装了太多的元素,要么元素太少以至于需要启用兄弟元素选择器。那些需要使用兄弟元素选择器的情况,是引入 CSS Grid Layout 的重要原因。
举个例子,我们来看这个布局 -
我们有两张彼此相邻的“卡片”,每张都有一个标题,一个段落和一个页脚。我们想要的是每张卡内的每个部分都是相同的高度,而不管每个部分的内容如何(例如,第一张卡片的标题只有 1 行,而第二张卡片的标题有 3 行,但是第一张卡片的标题部分高度应该与第二张卡片相匹配)。
我们可以使用 CSS Grid 来实现这种布局,但是我们需要每个“卡片”中的所有元素成为彼此的兄弟元素。所以,我们可能需要像这样布局我们的 HTML -
1 | <div class="grid"> |
我们可以应用以下样式 -
1 | .grid { |
虽然这不是一个错误的构建文档方式,但通过 article 元素将每个元素分组可能更有意义。这时候就需要 display:contents; 出场了。我们有了两全的方案 - 通过有意义的语义化方式去组织元素,同时 CSS 也以合理的方式去完成布局。
1 | <div class="grid"> |
使用与上面相同的 CSS,我们可以实现我们想要的布局。
在撰写本文时,display:contents; 仅在两个主流浏览器中得到支持,其他支持很快就会到来。
2018 年 03 月 27 日浏览器兼容状况
因此,此功能目前仍应被视为渐进式增强功能,并应有适当的降级处理。
1 | article { display: grid; grid-template-rows: 200px 1fr auto; /* e.g. Use a fixed |
原文: How display: contents; Works
作者: Ire Aderinokun
译者: evanyan
]]>1 | //Object.defineProperty(obj, prop, descriptor) |
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由 getter-setter 函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
当且仅当该属性的 writable 为 true 时,value 才能被赋值运算符改变。默认为 false。
一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。
一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。
如果一个描述符不具有 value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value 或 writable)和(get 或 set)关键字,将会产生一个异常。
在未引入 angluar1.x 的情况下,要实现 angular1.x 的数据绑定设计,大概会是下面这样:
1 | <!DOCTYPE html> |
1 | let validator = { |
Object.observe(obj, callback[, acceptList]) 方法对对象(或者其属性)进行监控观察,一旦其发生变化时,将会执行相应的 handler。
现在 Object.observe 将不加入 es7 An update on Object.observe
Object.prototype.watch(prop, handler) 方法对对象属性进行监控观察,一旦其发生变化时,将会执行相应的 handler。
此方法只在 Firefox 58 之前的 Firefox 中实现,其余浏览器及浏览器版本均不实现此方法 Object.prototype.watch()
]]>1 | <html> |
先看 html 结构:
1 | <div id="horizontal" class=""> |
然后看两个动画:
1 | @-webkit-keyframes horizontal { |
显然,包裹 div.ball 的两个元素,div#horizontal 负责 x 轴平移,div#vertical 负责 y 轴平移,x 轴、y 轴两种平移混合,就实现各种斜移。
接下来是动画属性设置:
1 | .horizontal { |
对于碰撞运动而言,在 y 轴上,始终有重力的影响。
所以,y 轴动画 animation-timing-function 属性被设置为 ease-in,从开始到结束逐渐加速(从高处到低处逐渐加速);然后,animation-direction 设置为 alternate,则动画正向播完后,会反向播出,则反向播出时,结束到开始回事逐渐减速(从低处到高处逐渐减速);最后,animation-iteration-count 设置为 infinite,动画会无限循环。
至于 x 轴,其不受其他力的影响,所以在 x 轴方向上,速度不变,所以 animation-timing-function 属性被设置为 linear,从开始到结束速度不变。其余属性,与 y 轴一样。
另外,x 轴和 y 轴的具体移动距离,这个由 javascript 计算得出:
1 | $ph = $("body").height(); |
至此,低成本碰撞动画简述完毕。
]]>在交互丰富的页面中(比如各种推广活动页),通常会有多个被观察者(每个 ajax 请求都对应一个被观察者),对应多个观察者(页面 ui 组件)。
随着数据模型复杂度的提高,这种多对多的关系整理起来会越来越复杂(被观察者会被多个观察者观察,观察者自然也有可能观察多个被观察者),所以,我们需要对此进行解决。
发布/订阅模式是观察者模式的变体。
在观察者模式中,观察者需要到被观察中进行注册。
在发布/订阅模式中,订阅者(观察者)不需要到发布者(被观察者)中注册,他们之间存在一个一个主题/事件频道。代码可以在频道中定义各种事件,发布者可以通过事件广播参数,订阅者可以通过事件接收参数。
我们可以通过一个实例来了解发布/订阅模式的运行原理。
1 | function Publisher() { |
发布/订阅模式使用方式简述完毕。
Learning JavaScript Design Patterns
Learning JavaScript Design Patterns 中文版
所以,我们需要对【数据变化】 -> 【UI 变化】这部分的逻辑进行解耦
观察者模式(Observer pattern)是一种管理对象及其行为和状态之间的关系的得力工具。
用 JavaScript 的话来说,这种模式的实质就是对可以对程序中的某个对象的状态进行观察,并且在其发生改变时能够得到通知。
所以,这个模式可以解决我们现在面对的问题:”对【数据变化】 -> 【UI 变化】这部分的逻辑进行解耦”
观察者模式中存在两个角色:观察者和被观察者(又名订阅者和发布者)。下面是观察者模式的实现原理。
可以通过一个实例来了解观察者模式的运行原理。
首先,建立一个描述观察者的类。
1 | function Observer() { |
然后,建立一个描述观察者队列的类。
1 | function ObserverList() { |
接着,建立一个描述被观察者的类。
1 | function Subject() { |
注意这里 Subject 类的 Notify 方法。在观察者模式中,观察者可以观察到被观察者,原因就是:被观察者把观察者的引用存储起来,被观察者可以进行”通告”,对所有观察者进行调用(发送信息)。
下面是一个具体的实例
1 | <html> |
至此,观察者模式使用方式简述完毕。
Learning JavaScript Design Patterns
Learning JavaScript Design Patterns 中文版
要构建良好的测试,对被测代码也有一定的要求。
前端组件的使用场景中,有大量的异步操作。上述 3 个测试框架,只有 mocha 可以很方便地进行异步测试。所以,测试框架使用 mocha。
google mocha 的使用教程,断言库都是 chai,所以。。。。。。
javascript 的代码覆盖率工具中,istanbul 流行度最高。
chrome 的 headless 模式发布后,phantomjs 的作者已经宣布不再维护 phantomjs。第一个方案否决。
chrome 的 headless 模式目前才发布两个月,还没有多少实践案例,鉴于其可通过 webdriver 驱动,暂时观察之。
webdriver 已被 w3c 标准化,各大浏览器都已实现其标准。这种驱动浏览器的方式,使用十分广泛,实践案例很多。
Selenium 和 webdriver 目前是合并趋势。
综上,使用第四方案。对应至 node.js 平台,使用 selenium-webdriver 模块和 selenium 服务进行通信。
我们可以构建一个简单的项目,在这个过程中了解测试的基本概念
1 | mkdir fe_tests_example |
建立文件 ./utils/add.js
1 | module.exports = function(a, b) { |
建立文件 ./tests/add.test.js
1 | var expect = require('chai').expect; |
运行指令
1 | mocha tests\*.js |
可以看到
1 | D:\work\fe_tests_example (master) (fe_tests_example@1.0.0) |
这就是一次单元测试。
其中,add.test.js 是测试脚本,一个个 it 代码块就是一个个测试用例。
接下来是在浏览器环境下进行单元测试:
首先
1 | mocha init fe_unit_tests |
可以看到项目下多了个 fe_unit_tests 文件夹
建立 fe_unit_tests/add.js 文件
1 | function add(a, b) { |
修改 fe_unit_tests/tests.js 文件:
1 | var expect = chai.expect; |
修改 fe_unit_tests/index.html 文件
1 | <!DOCTYPE html> |
这就是浏览器环境下的单元测试
我们将继续通过实例项目来了解 UI 测试
下载最新的JDK,安装之。
下载最新的selenium,一个 jar 包,不用安装。
运行 selenium 服务
1 | java -jar selenium-server-standalone-2.45.0.jar |
可以看到
1 | λ java -jar selenium-server-standalone-3.4.0.jar |
下载对应的浏览器驱动
browser | component |
---|---|
Chrome | chromedriver(.exe) |
Internet Explorer | IEDriverServer.exe |
Edge | MicrosoftWebDriver.msi |
Firefox 47+ | geckodriver(.exe) |
PhantomJS | phantomjs(.exe) |
Opera | operadriver(.exe) |
Safari | safaridriver |
在这里,我下载了 chrome、firefox、ie 的驱动,和刚才下好的 selenium jar 包放入同一个文件夹。
这里有需要注意的几个点:
ps:上面是自行安装配置使用 selenium 服务的方法。还可以通过工具包对 selenium 进行安装配置使用,具体请看ui-h5-dialog 组件 test 分支的 test_guide.md
接下来,回到 fe_tests_example 项目中
1 | npm install selenium-webdriver --save |
建立 ui_tests/test1.js 文件
1 | var webdriver = require('selenium-webdriver'), |
建立 ui_tests/test2.js 文件
1 | var webdriver = require('selenium-webdriver'), |
建立 ui_tests/test3.js 文件
1 | var webdriver = require('selenium-webdriver'), |
然后
1 | $ mocha .\ui_tests\*.js |
可以看到
以上就是一次完整的 UI 测试。
测试覆盖率是针对单元测试的指标,他有下面几个维度:
我们来实践一下。
1 | npm install istanbul --save |
建立 utils/ifElseTest.js 文件
1 | module.exports = function(a) { |
建立 test/ifElseTest.test.js 文件
1 | var expect = require('chai').expect; |
改写 package.json 中的 scripts 部分:
1 | { |
然后:
1 | npm run test-cov |
可以看到:
1 | λ npm run test-cov |
此时项目中生成了 coverage 文件夹,打开 coverage/lcov-report/index.html,可以看到:
可以看到,ifElseTest.js 中,其中有一个 if-else 代码块没有被测到。
我们改写一下 ifElseTest.test.js 文件:
1 | var expect = require('chai').expect; |
然后
1 | npm run test-cov |
可以看到:
1 | λ npm run test-cov |
可以看到测试覆盖率达到 100%,被测代码的每一行都被测到。