”搜索列表“就是有一个搜索框和一个显示搜索结果的列表的一种业务场景。在用户输入文字的时候能够在列表中展示搜索相关的信息。这个业务场景应该很多同学都做过,今天笔者就带着大家来逐步优化这种业务场景。本文建议在 PC 端上浏览,笔者已经为大家准备好了 demo,观众老爷们可以点开后续章节的的 demo 🔗进行调试。
首先有几个要求:
首先第一版先完成业务,我以 react 为列
|
|
好的,第一版demo1我们已经完成,我们来看看有哪些问题?
想一想这种场景,当用户快速输入时,在一个单词或一个词组没有输完其实可以不调 api 进行请求,有同学可能会想到 防抖、节流 来优化服务调用频次,当然个人认为在这里使用防抖更合适,防抖的间隔取多少合适呢?在不影响用户体验的情况下,一般 30-100 ms 我觉得都比较合理,好了,这种优化方式大家应该都用过,这里就不做代码演示了。
我们看下需求“要即时响应用户搜索”,需要为搜索框绑定哪些事件呢?原生的 onchange 、oninput 作为了备选项,他们执行的时机略有差异,大家可以在 MDN 上查看区别。对于使用 React 的用户,React 已经封装了原生事件,保证 onChange 事件能够在合适的时机触发。但你试着输入 CJK(中文、日文、韩文) 文本呢?会发生什么?我们在输入一些合成文本时会使得情况变得复杂起来,你切换到中文输入法时,试着输入”zhongguo”,你会发现还没有输入”中国”时已经就调了 8 次无效的 api,为什么说是无效的呢,我认为这些中间搜索结果其实用户并不关心,反而觉得这是 bug。
|
|
我先在代码中添加 onCompositionStart、onCompositionUpdate、onCompositionEnd 事件,你可以在demo2中测试输入非 CJK 和 CJK 文本时控制台打印出的日志。
我们可以利用输入非 CJK 文本时只会执行 onInput 事件,输入 CJK 文本时会依次执行
onCompositionStart、(onCompositionUpdate、onInput 持续输入会是一个重复的过程)、最后我们选择待选文本后执行 onCompositionEnd 事件。通过上面测试,对于用户输入 CJK 文本时我们事件应该在 onCompositionEnd 中执行呢?
代码实现
|
|
现在我们的代码已经优化了这种场景,你可以试试看。demo3
这种优化方案我也在 element-ui 的 el-input 源码中看到过。当然 CompositionEvent 表示用户间接输入文本(如使用输入法)时发生的事件,还支持语音输入等,大家可以自己去探索下。
现在我们需要考虑 api 返回的数据特别大,list 10000 条以上,在启用 React fiber (React Concurrent Features) 特性之前,React 可能会长时间占用 js 线程,用户输入的数据无法及时在 input 中反馈,你可以使用新版 React 和 ReactDOM 来缓解这个问题,这种使用方式也比较简单,就不做代码演示。
我们看下另一种方案,虚拟化列表滚动方案,本质上就是无论数据多少,我们只显示容器内或 viewport 内的条目即可,用户滚动时在动态变更容器内显示的数据。知乎-饿了么团队有篇文章介绍虚拟列表实现原理挺不错,推荐大家阅读再谈前端虚拟列表的实现。在这里我推荐一个组件 react-tiny-virtual-list,gzip 后大小只有 3kb,作者考虑了很多东西,如列表中项目高度一致、高度不一致情况、自定义容器不可见区域的 buffer(解决滑太快会看见页面空白的现象),作者做了很多demo,大家可以去试试,在这里就不做代码演示了。
由于 internet 是一个大型的网状结构,我们在频繁向后端发送请求的情况下,很有可能每次请求所选择的路径不相同(路由和寻址)以及一些其他原因,导致先发出的请求后接收到响应。如果浏览器发出了两个请求 1,2 ,浏览器却先收到请求 2 的响应并绘制到浏览器中,然后再接收到请求 1 的响应并绘制到浏览器中,这里如果 1 和 2 搜索的关键字不同就会导致 bug(搜索关键字和查询结果不一致)。
|
|
demo4这里技巧性比较强,利用 fetch 方法每次返回不同的 Promise 对象内存地址,并始终只记录最后一次的 Promise 对象内存地址,在接收到响应时判断并只处理最后一个请求的响应。思想最初来源于Handling API request race conditions in React
Handling API request race conditions in React文章还提供了一个思路取消之前的所有请求,这里还有个好处,如果提前取消请求,浏览器就会省略解析 Response 这一过程,前面一种方式浏览器其实是解析了 Response ,只是我们没有用到而已demo5。
代码如下:
|
|
比如你快速输入”123”将在控制台看见如下效果:
“搜索列表“只是一个小小的业务场景,如果你想去优化总是有些突破口。但笔者想说的,我们开发也需要考虑开发成本和收益,对于收益和开发成本不对等的情况下大家也没必要做这么多优化(又不是不能用),也比较反对“过早优化”。
本文也是 2022 年的第一篇文章,祝大家能够在新的一年里“升职加薪、早日实现财务自由”。如果本文对你有帮助请你点击“关注”和“点赞”就是对我的支持,谢谢。
欢迎转载 请注明出处
]]>参考文档:
再谈前端虚拟列表的实现
Handling API request race conditions in React
react-tiny-virtual-list
学完这部分,你会从更高维度去思考问题,当别人还在依赖于直觉、经验做决策的时候,你已经从思维层面考虑到更多可能性和更完整全面的解决方案。
模块二,工作能力。 我会教你如何修炼语言表达能力、书面写作能力和职场学习能力。通过这些能力的修炼,你不仅能够把自己的工作结果最大化地展现出来,而且,依托强大的学习能力,你能及时更新自己知识体系和思维工具,不断迎接新的挑战。
其次,我会带你超越工作技能的维度,从行业知识和商业思维的角度,帮你建立一个完整的工作能力框架。这样你在职场中,就不会只是一个纯粹的执行者,你还有丰富的行业和商业能力储备,无论是做一个非常基础的工作,还是负责一条完整的业务线,你都有能力胜任。
模块三,人际合作。 这个部分我会介绍职场上真正有价值的人际关系该是什么样的,带你建立正确的经营职场人际关系的思维,学习职场人际关系建立与经营的方法技巧。
所谓流程思维,简单地说,就是在解决问题的时候,你会不会想到从流程出发来考虑和解决问题。
应用流程的工作场景也有强场景和弱场景之分。
强场景,就是那些客观上具有非常严密的流程,工作过程中不得不严格遵守的工作场景,例如转化漏斗分析。
弱场景,就是那些虽然也有一定的流程,但是流程的逻辑并不是非常严密的工作场景,例如活动运营。
所以无论是强场景,还是弱场景,都建议你严格依靠严密的流程来约束自己的工作,把流程思维应该当作一种自觉的、随时都要想到的“工具”。
优化流程一般指的是对既有的流程进行改造。
流程优化就是对其中节点要素的增减,或者是对其中逻辑关系的局部优化。它们的目的都是让流程更顺、效率更高、成果更大。
原来的流程:产品开发——测试——运维部署——开发上线——产品(人员)验收。
优化后的流程:产品开发——测试——产品(人员)验收——运维部署——开发上线。
你要负责一个项目或者一项业务,制定流程的能力是带领项目或者业务突围的必备条件。
SOP,是 Standard Operating Procedure:标准作业程序,指将某一事件的标准操作步骤和要求以统一的格式描述出来,用于指导和规范日常的工作。
关键节点是一个最有目的性或者有关键产出物的环节。 确认关键节点的过程,就是理清事情的头绪或者关键操作的过程。
如,在电商购物过程中,浏览商品、点击购买、填写地址、提交订单、支付货款都是必要的操作步骤,但只有支付货款是关键节点,因为只要货款没有支付完成,这个购物行为就没有成功。
确认了关键节点后,接下来就要找到节点事件或者操作要点之间的逻辑关系,用箭头表示出前后的顺序关系。
如,电商 App 的用户行为流程大致如下:下载——打开App——浏览商品详情页——点击购买——填写地址——提交订单——支付货款。
以上两步虽然表明了不同节点之间的逻辑关系,但只是一个粗线条的流程,只适合某些岗位在某些场景下的工作。
与外部门的协作也要放进整体流程去考虑,千万不要把流程中的决定性事项放在自己把控不住的范围。
如,增加细节设计、增加边界设计、增加不同场景设计等(使我们的流程更加精细化、严密的流程)。
在做汇报时,需要用数据增强你的职场说服力。
数据思维,简单来说就是在分析问题、做决策、汇报工作等关键工作场景中,依靠数据说话。
成为一个善于运用数据的高手,我认为你至少要在四个方面展开数据思维的修炼:
对外界、对自己多使用数据,培养对数据非常敏感,看任何问题,都会基于数据体现来考虑。
所谓数据至上,就是任何事情,能用数据,就不用文字。 你需要在问题分析的时候、在执行落地的时候、在呈现结果的时候保持数据至上的意识。
运用数据的时候要时刻保持精确性,因为精确能够体现你的专业性。
尽量不要用概略性的表述。
不同的场景下追求相应的精确度。
在工作场合中,仅仅能使用数据是不够的,还需要精确使用数据,只有你的表述很精确,才是真正有说服力的。
俗话说,没有对比就没有伤害,在数据应用方面,没有对比就不知道好坏。
比如国家统计局公布GDP 的时候,不仅要公布本月的数值,更重要的是要公布和上个月的对比增减幅度,以及和去年同一个月份的对比情况;
复盘其实是一个围棋术语,也称 “复局”,指的是“对局”完毕后,回顾推演这一盘棋的记录,以检查下棋的过程中招法的优劣与得失关键。把复盘放到工作中来说,就是通过剖析工作全貌,把其中每个局部拿出来进行仔细分析,以萃取核心工作经验与价值。
总结是对结果好坏的分析,而复盘是对产生结果原因的深度分析。如果复盘中缺乏对于原因的深度剖析,就起不到复盘应该有的作用。
对公司而言,复盘为公司积累了处理相关领域的宝贵经验,一次投入多重收益;
对团队而言,复盘帮助团队成员共享了完整的经验方法,大家都能从中受益;
对自己而言,复盘总结的经验知识,你可以复用在以后的类似工作中,省时省力更省心。
在进行一项工作复盘的时候,首先要对事件的过程和信息进行完整的梳理。因为在做一件工作的时候,基于协作等因素,信息分散在不同的人和角落中。把这些信息进行系统的汇总,你对整件事情才会有完整的认知,复盘才会更全面。否则,信息的缺失,让你的复盘工作可能根本没法往下进行。
对一项工作的完整过程及信息进行梳理,需要把时间线和事件线结合起来。
时间线,就是工作进行的重要时间点构成的脉络。这有助于你从时间维度来梳理重要的事件,避免遗漏。
事件线,就是重要事件,按照时间顺序进行的脉络,也是复盘架信息。
复盘第二步就需要剖析重要的节点事件,从中寻找这些重要事件进展过程中存在的问题。这里可以从事和人两个层面入手。
事的层面,事的层面主要从两个方面来进行剖析:工作内容和工作方法,剖析工作内容要区分积极的事情和消极的事情,寻找做得好的方面和原因,以及不好的方面和原因。
人的层面,任何工作都离不开人,其中不可避免地存在着各种各样与人相关的问题,对于人的剖析,可以从四个维度上来展开。
第一,剖析人的情绪、决策行为,以及情绪对决策行为的影响。
第二,剖析环境因素对于人的影响。
第三,不仅剖析自己,还要剖析他人。
第四,对利益相关方的影响进行剖析。
在第二步的复盘中,你一定会分析出大量有价值的经验信息,但它们过于庞杂且相对孤立,这样的经验你即使总结出来,可能在下次遇到问题的时候也很难复用。
第一,寻找经验之间的联系,进行归类总结。
第二,对经验进行重要性排序,记住关键经验。
第一,不断发问。 为了达到深度复盘的目的,你需要不断地发问,在不断追问中找到问题的核心,提炼出有价值的经验教训。
第二,坦诚面对。 复盘的时候,坦诚面对是一个很大的挑战,尤其是对于失败事情的复盘,很多人缺乏自我剖析的勇气,觉得事情都已经失败了,再去揭伤疤太痛苦了。所以,有的人就会避重就轻、敷衍了事。
拆解思维,指的是把一个问题拆分成颗粒度更细的维度,从更细小的组成部分考虑和分析问题,从而找到解决方案的一种思维。
举个很简单的例子,假如一款 App,被要求在年底达到 1000 万的用户量,可以从时间维度上拆出具体的目标(按月)。
方案规划类问题往往需要通过 PPT 呈现解决方案,所以也可以叫 PPT 类问题。大到公司的战略规划、年度业务规划,小到个人的工作汇报、产品规划、用户调研方案等都属于此类问题。
方案规划类问题通常都比较宏大,宏大的问题往往需要非常具体、细化的拆解之后,才有可落地的完整解决方案。比如,公司的年度规划,必须落地到一个个具体的业务目标上。
方案规划类问题的解决,可以借助于结构重构法、自由发散法两种方法。
结构重构法,就是通过使用业内已经成熟的思维方法,重新搭建一个全新的框架来解决问题。
自由发散法,指的是在发散思考的时候,没有成熟框架依托,只能靠你头脑中储备的知识来思考可能的维度,在此基础上再进行归类分组,搭建成一个可能的框架来解决问题的方法。
数据计算类问题,指的是以严格的数据考核的工作问题,比如销售目标的拆解、投资回报率的计算、用户量的增长等诸如此类的问题。
第一步:找到基本公式。
如以“全年增长 5000 万收入目标达成”是一个计算销售收入的问题,一般的计算公式是销售额=销售数量销售单价,但这个问题中求解的是会员收入问题,所以,公式需要进行相应的改造。
第二步:改造解题公式。
根据业务收入实际的来源,改造为这个公式:会员收入增长= 新增会员数 会员价格。
第三步:拆解解题方案。
在上面的公式中,会员价格是一个固定的数值,因此,求解会员收入增长的问题,就转换成了如何增长新增会员数的问题。因为新会员是要靠大量的新用户转化而来的,所以问题会进一步转化为获取多少新用户。
流程梳理类问题,指的是问题解决环节之间具有前后密切承继关系的问题。
最重要的是,当你通过拆解流程的细分环节分析出了问题点,之后就可以对相应环节进行细致的排查,就此找到解决问题的办法。
对工作区分优先级, 区分工作的优先级(先做什么后做什么)
详细拆解四象限法则,破解不会用优先级思维的困境
提到优先级思维,就绕不开四象限法则,这是优先级思维最好的落地工具。它根据事情紧迫性和重要性两个维度,构成了一个四象限的矩阵,把事情分为重要紧急、重要不紧急、不重要紧急、不重要不紧急,然后优先选择第一象限重要的事情去做。
紧迫性主要是从时间维度上来评估,一件事情到底紧迫与否,就看离最终的时间节点的远近,如果截止日期迫在眉睫,那么就非常紧迫,反之紧迫性就没有那么高。
任务的重要性可以按业务类工作与商业收益评估、协作类工作与影响力评估、事务类工作与职业成长价值评估
一般:业务类工作的重要性 > 协作类工作的重要性 > 事务类工作的重要性。
不敢说、不屑说
在职场中,有一类人自信十足,他们并不怵说话,但你听他们的话总是感觉云里雾里、一团乱麻、没有重点,怎么也理不清。
结论先行、以上统下、归类分组、逻辑递进。
最好用一句话提纲挈领,抓住听讲者的注意力。
当你抛出观点或者总括内容之后,马上就要进入细节的内容。如果你是发表观点,那么马上就要说明你的理由。如果是分析问题,那么就展开现象、原因和解决办法。在说的时候,你应该注意两点。
职场上的写作核心目的是信息沟通、说服别人,所以逻辑性是职场写作最重要的要求。比如,你要通过邮件说服别人支持你的工作,或者说服上级接受你的方案,不能只讲道理,没有证据。也不能观点和理由混杂在一起,这样是不行的。
当职场中遇到需要写作的场合,而又不知道如何写的时候,最快速的解决办法就是模仿他人的写作方法来先完成任务。之后,再考虑长期训练相应能力的问题。
职场学习和学校的学习有很大的不同,其中最关键的一个就是:学校学习的是知识,职场学习的是能力。
当一个人处于基础执行层时,最核心的就是把工作任务搞定的能力。这时候,你承担得更多的是一些具体的落地工作,虽然这些事情中也需要你思考一些细枝末节问题的合理性,但是,主要还是领导规划你来执行。
领导交给你的那些事情,做的时候你都能搞定,但是,为什么领导要让你这么做,背后的原因是什么,有些你都是不甚明白。
所谓的研究能力,就是面对问题,分析清楚问题的本质,找到问题出现的深层原因,基于原因寻找可靠的解决办法。
为了达到这个目的,你要去研究业务知识,你得了解你们公司业务整体运转的逻辑是什么。因为你所面对的问题肯定不是无缘无故产生的,更不是孤立存在的,一定是业务链条上其他的环节触发了什么因素,导致了现在你所要面对和解决的问题。你要把这些因素挖出来,找到解决的办法。
你要承担起一个管理者的角色,你要突破的是综合能力。因为你要管理几个人来完成工作任务,你不仅要学会规划的能力(规划事情的能力和人员安排的能力),而且要学习管理人的能力。此外,因为你不仅要和上级领导相处,你还要和下级、同级的其他管理者相处,你处在了夹心层,要处理各种各样的关系,你要学习人际管理的方法。
你要突破综合能力的瓶颈,就需要学习规划能力、管理能力、人际关系能力。这每一项都有很多要学习的东西,而且,不再像执行能力和研究能力那样,仅仅是处理的信息,你要处理与人相关的问题,复杂性进一步提高。
从执行能力到研究能力的学习,其实是从硬技能到软技能的学习。所谓硬技能,就像产品经理的需求文档撰写能力、画原型图的能力,这些侧重于解决具体问题的能力。而软技能,就像这门课程中工作思维的学习。
所谓通用性能力,就是那些在解决任何具体问题的时候都可能用到的能力,它们的普适性比较强,比如第二模块中讲到的语言、书面表达能力,行业视野和商业思维,都是通用性的工作能力。
所谓专业性能力,就是针对特定问题才能够用的,解决问题具有相对适用性范围的能力,比如对于产品经理来说,交互设计就是专业性能力。
所谓操作性能力,就是那种具有明确的操作方法,你按照具体的操作方法反复练习就可以掌握的能力。比如 Excel 技能、活动运营的操作流程、需求文档撰写能力等。
所谓指导性能力,就是那种没有什么操作流程,只是一个知识、原则、理论、框架,你在解决问题的时候,可以借助于它们,来指导你产出一些方法、激活一些思路。以此来解决相应的问题。
很多同学进入一个岗位,日复一日干着重复的工作,节奏堪比钟表一样,一眼就可以望到头。这种无望却无奈的困境,以前存在于很多传统企业中,现在已经遍及互联网大厂。
在互联网企业中,职业升迁有两条典型的路径:一条是专业线,也就是通常大家说的大厂的 P 序列;一条是管理线,也就是所谓的 M 序列。
快速发展的企业,规模会不断扩大,人员规模也就需要同等扩大,这时候就会让你有机会扩大自己的工作技能(内部转岗)。
关于职场人的能力,以往其实有一种说法,叫作“T 型能力”,就是一横一竖,一横指的是你要在职业能力的横向方面向广博修炼,一竖指的是你要在专业能力的纵向方面向精深修炼。“T型能力”是过去人们常常津津乐道的一个指导原则。
“π 型能力”是我借用北大刘澜教授提出的一个概念。它指的是,在横向的一横上,你要广撒网,扩展自己的能力的宽度;从纵向的两竖上,你至少要在两个职业方向上,精深的修炼自己的工作技能。
概括来说,与你所在企业相关的行业有关的一切知识和信息,都属于这个范畴。
PS:以职业性打动对方也有一个负面清单,就是千万不要以领导压人。有些人遇到事情推动不下去,就拿领导说事,企图逼迫别人就范,这实际上在证明自己的无能,只能让别人对你心生厌恶。当然,有的时候遇到一些油盐不进,就是喜欢讲道理的人,可以适当、委婉的借助于领导的权威加速事情的推进,但是,注意掌握分寸。
以成果回馈对方,指的是你和别人的合作,应该建立在有成果的工作产出基础之上。这就对你提出了较高的要求,意味着你在做任何事情的时候,都尽可能以有所产出为出发点来考虑问题,因为如果别人支持你的事情要总是没有成果,他们也受不了,因为他的领导也会拷问他的工作成果,他的工作绩效,部分的取决于你的工作成果。
如果你们的成果确实比较显著,你应该花时间写一份项目工作成果总结报告,对支持此项工作的同事表达感谢,其中你一定要提到他们的名字,并且最好把成果和具体人关联起来,这样才能表达出你的诚意,然后以邮件的形式发送给所有参与项目的人,并且抄送他们的领导。
要做到的这一点,我认为至少要解决三个问题:
下面我就从沟通前、沟通中、沟通后三个环节,跟你聊一聊如何更好地和别人沟通,获得他们实际的支持行动。
一定要在沟通之前做充分的准备,不要赤手空拳去沟通,否则结果惨不忍睹。
在沟通前,需要首先弄清自己的目的,你找别人只是同步信息,还是要达成什么共识做决策 。
如果你只是同步信息,那么你要传递什么信息,一定要清楚、明确,不能模糊和模棱两可。
如果你是要达成共识,那么,你要做的准备工作就更多了,你要根据问题的性质、沟通的对象、行动计划等因素,综合考虑该怎么尽快地达成自己的目的。
首先,如果是一个重要、难办的问题,你就考虑清楚它的难点是什么,针对这个难点别人可能关心的是什么,会提出什么问题,预设一些可能的问题,并提前准备好回答的逻辑和答案,以做到有备无患。
其次,达成共识是一件比较难的事情,你要注意沟通的对象,不同的对象对于同一个问题他们的关注点可能也会不同,你也要提前考虑到这种差异可能给你带来的挑战。
最后,通常某件事情达成共识之后,都会有相应的落地动作,所以,在你沟通前想好期望的时间、节奏安排。这样人家对你的行动计划才有明确的概念。
在行动计划中,还有很重要的一点,就是你要提前想好自己的备选方案,如果你的第一方案没有通过,那么马上抛出备选方案来沟通。尽可能在一次沟通中就达成共识,毕竟如果多次沟通就需要别人更多的时间。
总之,在沟通开始前,你的准备越是充分,你在沟通的过程中才能更加专业地解答别人的问题。你对待事情是认真严肃的,你的这种专业精神会影响到他,他也会认真对待你的事情,真心帮你把事情做好。
如果你是和领导沟通,那要侧重准备与 Why 相关的问题,和同事沟通就侧重准备与 How 相关的问题。
职场中的沟通,会有各种各样的情景,很多时候都是有业绩压力和利益争执的情景。这时候人的行为、语言、情绪、行为都会偏离日常状态。如果你是沟通的发起人,一定要注意对沟通情景的设计和管理。
这主要从两个方面来展开:一个是人的方面,一个是环境的方面。
如果你预计是这个沟通可能会有激烈的交锋,或者各种不太和谐的情况发生,那么,你就可以在沟通的环境因素方案有所考虑,比如你可以定一个大一点的会议室,或者比较敞亮的会议室。可以在开场的时候,准备一些暖场的笑话之类的。尽可能创造一个相对弱化紧张氛围的环境。
如果你是一对一的沟通,要谈论一些艰难的话题,可以考虑不要在正式的场合,可以约沟通对象到户外散步,或者去喝咖啡、吃个饭,在喝咖啡吃饭的时候去做些重要的铺垫。不要小看这些微观环境的因素,它们对于沟通会有潜移默化的影响。
当然,对于环境的设计其实能做的事情不多,主要还是对人的方面的管理。其中又包括对于他人的管理和对自己的管理。
对于他人的管理,你能做的虽然不多,但你可以决定沟通的时间点。假如你的沟通对象心情不好,你就不要在这种时候去找他沟通非常重要的事情,因为如果他有情绪,可能就会把这种情绪延续到和你的问题沟通中,很可能你的沟通事情本身没有问题,但是,因为情绪因素,反而你就不幸躺枪了。
仔细倾听。 在沟通中,仔细倾听很重要。你可能认为这是一句正确的废话。其实不是。你可以回想一下,在那些正式的场合中,你是如何听别人讲话的?你是在真的倾听吗?
放下自我。 当别人质疑自己观点的时候,很多人会本能地产生防备的心理。他们往往认为别人是在故意为难自己,或者是挑战自己的尊严。
更好的策略是应该放下自我、认真听取别人的观点,从中寻求可能有价值的意见,即使提出不同观点的那个人可能说得完全没有道理,你也能够从中判断出这个人的水平,从而在以后的工作中决定该怎么和这个人相处,甚至由个性推到共性,学会判断这一类人,学习和这一类人相处。
延迟回应 。 当别人对你的观点或者方案提出质疑的时候,本来你没有想好问题的最优答案,但是,还是想急于回应别人的问题,因为你觉得既然是在现场沟通,你就必须现场给出答复,其实,这是一种思维定式。你完全可以延迟回应,先告诉对方自己还没有想好,等回去想好了再给出回复。
沟通把控。 在沟通中,你一定要注意不要被带了节奏,带到沟里去了。否则,你不仅浪费了时间,还没有产生沟通的结果。所以,当某个沟通对象滔滔不绝地谈论偏离主要问候的观点时,你一定要及时引导到讨论的主线上。
当你结束一次和领导的讨论,或者和同事的一次脑暴之后,你应该立即着手记录你们讨论中彼此主要的观点,存在的核心问题,等待解决的问题。否则,这些信息内容会随着时间消逝,在你的大脑中逐渐变模糊,这样就影响了后面问题的解决。
同时,你也要复盘沟通本身,提高自己沟通的能力,尽可能让一次沟通产生工作成果价值和工作能力价值等多重价值。
在说稀疏数组之前,你需要知道很多语言将数组的分为稀疏数组与密集数组(区别是数组的各个子元素是否有孔,我们称为”hole”)。也就是说稀疏数组中的元素之间可以有空隙,在那些仅有少部分项被使用的数组中,hole 可以大大减少内存空间的浪费。
快数组是一种线性的存储方式。新创建的空数组,默认的存储方式是Fast Elements方式,快数组长度是可变的,可以根据元素的增加和删除来动态调整存储空间大小,内部是通过扩容和收缩机制实现,那来看下源码中是怎么扩容和收缩的。
前面说了新创建的数组,默认是Fast Elements方式。在Fast Elements 模式中有一个扩展,是 Fast Holey Elements 模式。Fast Holey Elements 模式适合于数组中的空洞情况,即只有某些索引存有数据,而其他的索引都没有赋值的情况。在 Fast Holey Elements 模式下,当容量小于1024时,没有赋值的数组索引将会存储一个特殊的值,这样在访问这些位置时就可以得到 undefined。但是 Fast Holey Elements 同样会动态分配连续的存储空间,分配空间的大小由最大的索引值决定。
慢数组是一种字典的内存形式。不用开辟大块连续的存储空间,节省了内存,但是由于需要维护这样一个 HashTable,其效率会比快数组低。
在 Fast Elements 模式下,capacity 用于指示当前内存占用量大小,通常根据数组当前最大索引的值确定。在数组索引过大,超过 capacity 到一定程度( 由V8中 kMaxGap 常量决定,其值为 1024) ,数组将直接转化为 Dictionary Elements 模式。
更多关于V8的知识请移步 justjavac 的专栏
这里我对数组的 map、find、findIndex、filter、forEach、reduce方法做实验。
|
|
根据规范中定义的算法,如果被 map 调用的数组是稀疏数组,新数组将也是离散的保持相同的索引为空。
|
|
|
|
|
|
|
|
|
|
本文只是想通过测试让大家更了解常见数组方法如何来处理稀疏数组,常见的数组方法如 map、forEach、filter、reduce 都会跳过数组中的 hole 元素。前端还有很多细节可挖掘,我们继续前行吧。
]]>本文将带你完成以下任务,相信你会更好掌握 Promise。
JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个。
完整题目
答案
解释
实现Promise.all()
// 结束条件:有一个 Promise rejected 或 所有 Promise resolved。
|
|
实现Promise.any()
|
|
实现Promise.race()
结束条件:一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
Promise.allSettled()
目前是用在我们SSR项目中,一次性会在服务端发起多个请求,总不能一个请求挂掉都把成功的请求都丢弃吧,你可是试试这个方法。
多个返回promise的函数串行执行
Promise 超时设计,利用Promise.race来实现
行文匆忙(主要是要哄👶睡觉了),本文主要是个人对 Promise 的一些理解,如有错误还望斧正。
]]>在看一篇 木易杨大佬,三年前端寒冬入大厂,收获蚂蚁、字节 offer 面经分享的文章,其中一道字节跳动的CSS题目吸引了我(哈哈,因为我对这个知识点比较含糊,答不上来),下面我们大家就一起来看看这道题吧。
|
|
首先我们还是看一下,flex属性的定义吧!
flex属性其实是flex-grow(主轴空间多余时放大比例)、flex-shrink(主轴空间不足时缩小比例)、flex-basis(分配多余空间之前占据的主轴空间)这三个属性的简写。
flex虽说时前面提到的三个属性的简写,但在一些情况下是可以省略的。
flex
单值语法: 值必须为以下其中之一:
一个无单位数(number): 它会被当作 flex-grow 的值。
一个有效的宽度(width)值: 它会被当作 flex-basis 的值。
关键字none,auto或initial.
双值语法: 第一个值必须为一个无单位数,并且它会被当作 flex-grow 的值。第二个值必须为以下之一:
一个无单位数:它会被当作 flex-shrink 的值。
一个有效的宽度值: 它会被当作 flex-basis 的值。
三值语法:
第一个值必须为一个无单位数,并且它会被当作 flex-grow 的值。
第二个值必须为一个无单位数,并且它会被当作 flex-shrink 的值。
第三个值必须为一个有效的宽度值, 并且它会被当作 flex-basis 的值。
上面提到的有效的宽度值,其实就是 number + unit,如 3px、 5rem等。
首先,left 的 flow-basis 为 500px,right 的 flow-basis 为 400px,两者之后大于 container 容器的宽度 600px,这时肯定会触发 left 和 right 的压缩来适应容器的宽度,但如何分配压缩比的呢?
left 的 width + right 的 width = container 的 width,这个公式肯定成立。
但为题是 left 的 width 和 right 的 width 如何表示呢?
用数学的用法来解答,假设缩放基数为 x。
现在答案出来了,left 的 width 约为 285.72px,right 的 width 约为 314.28px。
|
|
结果符合预期,当container 的 width = left 的 width + right 的 width,直接按各个 flex item 的 flow-basis 展示就好。
|
|
这种情况也比较简单,可以这样理解,left 和 right 按 flow-basis 分配,还剩下 100px,则将起分为 left 的 flex-grow + right 的 flex-grow 份,然后 left 占 自己的 flex-grow 份。
]]>
在前端面试有一个非常重要的环节,也是面试者最担心的一个环节。对“手撕代码”的考察需要面试者平时总结和积累(临时抱佛脚是不好使的),在这里笔者就自己如何攻破“手撕代码”环节总结了一些经验,希望能帮助你挑战高薪,迎娶白富美😄😄😄。
|
|
|
|
|
|
|
|
|
|
|
|
说明:通过new WeakMap()来避免循环引用(拷贝引用类型时并保存其地址,后面遇到引用类型先检查是否已经保存了)
通过Reflect.ownKeys(obj)遍历出obj自身的所有可枚举和不可枚举的属性以及symbol属性
拷贝对应属性的属性描述符
|
|
|
|
|
|
|
|
目前还存在参数适配的问题
|
|
上面代码完全是笔者手敲,难免有错误,还望斧正。这种题目还有很多(实现简易版的EventEmitter、简易版模版引擎等),笔者会持续更新。如果对你有帮助,俺希望送上你的github小星星,在此感谢。
本文同步发布于个人博客掘金知乎专栏
也就是你本地的 id_rsa.pub 文件,将文件内容添加到远程下列目录中
/root/.ssh/authorized_keys
yum提供了查找、安装、删除某一个、一组甚至全部软件包的命令。
yum [options] [command] [package …]
options:可选,选项包括-h(帮助),-y(当安装过程提示选择全部为”yes”),-q(不显示安装的过程)等等。
command:要进行的操作。
package操作的对象。
1.列出所有可更新的软件清单命令:yum check-update
2.更新所有软件命令:yum update
3.仅安装指定的软件命令:yum install
4.仅更新指定的软件命令:yum update
5.列出所有可安裝的软件清单命令:yum list
6.删除软件包命令:yum remove
7.查找软件包 命令:yum search
8.清除缓存命令:
yum clean packages: 清除缓存目录下的软件包
yum clean headers: 清除缓存目录下的 headers
yum clean oldheaders: 清除缓存目录下旧的 headers
yum clean, yum clean all (= yum clean packages; yum clean oldheaders) :清除缓存目录下的软件包及旧的headers
list open files
是一个列出当前系统打开文件的工具
lsof -i tcp:port // 返回pid
kill pid // 杀死进程
显示目录
ls 目录名称
ls 无参数时,显示当前目录下的文件
ls / 显示根目录下的文件
ls -l
前0~9位分别代表
0代表文件类型,d为目录,-为文件,l为链接文档等
1~3代表属主权限user(rwx分别代表read、write、execute,没有该权限则-表示)
4~6代表属组权限group
7~9代表其他用户权限others
ls -a
ls -d
Change Directory
切换目录
cd 相对路径或绝对路径
Print Working Directory
显示目前所在的目录
-P :显示出确实的路径,而非使用连结 (link) 路径。
make directory
创建新目录
-m :配置文件的权限
mkdir -m 711 test2
-p :帮助你直接将所需要的目录(包含上一级目录)递归创建起来!
mkdir -p test1/test2
删除空的目录
-p :连同上一级『空的』目录也一起删除
拷贝文件和目录
rm [-fir] 文件或目录
-f :就是 force 的意思,忽略不存在的文件,不会出现警告信息;
-i :互动模式,在删除前会询问使用者是否动作
-r :递归删除啊!最常用在目录的删除了!这是非常危险的选项!
移动文件与目录,或修改名称
mv [-fiu] source destination
-f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖
-i :若目标文件 (destination) 已经存在时,就会询问是否覆盖!
-u :若目标文件已经存在,且 source 比较新,才会升级 (update)
chgrp [-R] 属组名 文件名
更改文件属组,-R代表递归操作目录中的所有文件和目录
chown [–R] 属主名 文件名
chown [-R] 属主名:属组名 文件名
更改文件属主,也可以同时更改文件属组
更改文件9个属性
Linux文件属性有两种设置方法,一种是数字,一种是符号。
chmod [-R] xyz 文件或目录 // xyz分别代表权限值,x为user,y为grounp,z为others,r(4)w(2)x(1)
chmod u=rwx,g=rx,o=r 文件或目录
chmod
由第一行开始显示文件内容
cat [-AbEnTv]
文件内容从最后一行开始显示,tac与cat命令刚好相反。
tac 文件路径
查看文件显示行号
nl [-bnw] 文件路径
一页一页翻动查看文件
more 文件路径
一页一页翻动查看文件(可以往前翻)
less 文件路径
less运行时可以进行下列操作
/字串 :向下搜寻『字串』的功能;
?字串 :向上搜寻『字串』的功能;
n :重复前一个搜寻 (与 / 或 ? 有关!)
取出文件前面几行
head [-n number] 文件
显示前number行
取出文件后面几行
tail [-n number] 文件
-n :后面接数字,代表显示几行的意思
-f :表示持续侦测后面所接的档名,要等到按下[ctrl]-c才会结束tail的侦测
看服务端日志用的比较多: tail -nf 100 文件
添加新的用户账号
useradd 选项 用户名
-c comment 指定一段注释性描述。
-d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。
-g 用户组 指定用户所属的用户组。
-G 用户组,用户组 指定用户所属的附加组。
-s Shell文件 指定用户的登录Shell。
-u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号。
如:useradd -d /usr/sam -m sam
删除帐号,将/etc/passwd等系统文件中的该用户记录删除,必要时还删除用户的主目录。
userdel 选项 用户名
-r 它的作用是把用户的主目录一起删除。
修改用户账号就是根据实际情况更改用户的有关属性,如用户号、主目录、用户组、登录Shell等。
usermod 选项 用户名
指定和修改用户口令
passwd 选项 用户名
-l 锁定口令,即禁用账号。
-u 口令解锁。
-d 使账号无口令。
-f 强迫用户下次登录时修改口令。
增加一个新的用户组
groupadd 选项 用户组
如Linux下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建。
-g GID 指定新用户组的组标识号(GID)。
-o 一般与-g选项同时使用,表示新用户组的GID可以与系统已有用户组的GID相同。
删除一个已有的用户组
groupdel 用户组
修改用户组的属性
roupmod 选项 用户组
-g GID 为用户组指定新的组标识号。
-o 与-g选项同时使用,用户组的新GID可以与系统已有用户组的GID相同。
-n 新用户组 将用户组的名字改为新名字。
如果一个用户同时属于多个用户组,那么用户可以在用户组之间切换,以便具有其他用户组的权限。
newgrp groupName
/etc/passwd文件是用户管理工作涉及的最重要的一个文件。
/etc/group文件记录的是用户所属的用户组。
/etc/shadow
主目录:一般是用户登录后的目录,各用户的主目录都被组织在同一个特定的目录下,而用户主目录的名称就是该用户的登录名。
添加批量用户
检查文件系统的磁盘空间占用情况。
df [-ahikHTm] 目录或文件名
-a :列出所有的文件系统,包括系统特有的 /proc 等文件系统;
-k :以 KBytes 的容量显示各文件系统;
-m :以 MBytes 的容量显示各文件系统;
-h :以人们较易阅读的 GBytes, MBytes, KBytes 等格式自行显示;
-H :以 M=1000K 取代 M=1024K 的进位方式;
-T :显示文件系统类型, 连同该 partition 的 filesystem 名称 (例如 ext3) 也列出;
-i :不用硬盘容量,而以 inode 的数量来显示
对文件和目录磁盘使用的空间的查看。
du [-ahskm] 文件或目录名称
-a :列出所有的文件与目录容量,因为默认仅统计目录底下的文件量而已。
-h :以人们较易读的容量格式 (G/M) 显示;
-s :列出总量而已,而不列出每个各别的目录占用容量;
-S :不包括子目录下的总计,与 -s 有点差别。
-k :以 KBytes 列出容量显示;
-m :以 MBytes 列出容量显示;
Linux 的磁盘分区表操作工具
fdisk [-l] 装置名称
-l :输出后面接的装置所有的分区内容。若仅有 fdisk -l 时, 则系统将会把整个系统内能够搜寻到的装置的分区均列出来。
磁盘分割完毕后自然就是要进行文件系统的格式化,格式化的命令非常的简单,使用 mkfs(make filesystem) 命令。
mkfs [-t 文件系统格式] 装置文件名
file system check
用来检查和维护不一致的文件系统
fsck [-t 文件系统] [-ACay] 装置名称
磁盘挂载与卸除
mount [-t 文件系统] [-L Label名] [-o 额外选项] [-n] 装置文件名 挂载点
]]>
如果 margin-left 和 margin-right 都设置为 auto,则他们两个值相等,所以水平能够居中。
如果 margin-top 和 margin-bottom 都设置为 auto,则他们实际等于0,所以就不能垂直居中。
CSS5th分享原文
meta 标签
CSS(暂不可用)
scroll-snap-type // 定义在滚动容器中的一个snap点如何被执行。
scroll-snap-align
scroll-margin // 定义滚动捕捉区域的开始,该区域用于将此框捕捉到snapport。
scroll-padding
声明属性(–开头)
使用($开头)
CSS Houdini 是各大厂商的工程师所组成的工作小组,志在建立一系列的 API,让开发者能够介入浏览器的 CSS engine 操作,帶给开发者更多的解决方案。
外部尺寸: 根据元素的上下文确定大小,而不考虑其内容。
如:width: 400px;
内部尺寸: 根据元素的内容确定大小,而不考虑其上下文。
如:widht: min-content;
w3c#intrinsic-sizes
利用@supports,本地CSS功能检测
mix-blend-mode // 描述了元素的内容应该与元素的直系父元素的内容和元素的背景如何混合。
background-blend-mode // 定义该元素的背景图片和背景色如何混合。
isolation // 定义该元素是否必须创建一个新的stacking context。
filter // 将滤镜效果应用于元素。
isolation 属性的主要作用是当和background-blend-mode属性一起使用时,可以只混合一个指定元素栈的背景:它允许使一组元素从它们后面的背景中独立出来,只混合这组元素的背景。
矩形镂空
outline: 999px solid rgba(0, 0, 0, .5);
椭圆镂空
box-shadow: 0 0 0 9999px rgba(0, 0, 0, .5);
不规则镂空
mask-composite
允许使用者通过部分或者完全隐藏一个元素的可见区域。这种效果可以通过遮罩或者裁切特定区域的图片。
具体有下列属性
mask-image
mask-mode
mask-repeat
mask-position
mask-clip
mask-origin
mask-size
mask-type
mask-composite
CSS遮罩CSS3 mask/masks详细介绍
要创建一个 transition,浏览器需要看到样式的变化
使用 requestAnimationFrame 来让浏览器看到样式变化
|
|
使用 getComputedStyle(elem).property 来让浏览器看到样式变化
|
|
虽然 getComputedStyle(elem) 不会更新样式,但是 getComputedStyle(elem).property 会更新样式。
使用 getComputedStyle 的代价很高
transitionend
transitionend 事件会在 CSS transition 结束后触发。
但下列情况不会出发
当 transition 完成前移除 transition 时,比如移除css的transition-property 属性,事件将不会被触发。
当 transition 完成前设置 display 为”none”,事件同样不会被触发。
当 transition 完成前元素被移除不会触发。
为了防止以上 transition 被取消
setTimeout 将其添加到异步任务队列中。
transitioncancel 事件。
还有 transitionrun 事件(需确保一开始是有 transition 的)。
animation
animation-timing-function只在关键帧之间 适用
WEB ANIMATIONS
特性检查(html.animate)
polyfill (web-animations-js)
Animating like you just don’t care with Element.animate
尽量只对 transform 和 opacity 使用动画。
根据用户的偏好,禁用复杂动画,媒体查询 prefers-reduced-motion 可偏向于用户。
移动端一般动画和过渡时间把握在300ms,根据屏幕大小适当调整。
text-fill-color
设置字体的填充颜色,如果未设置此属性,则字体颜色为 color 属性的值。
text-fill-color 与 color 的区别
text-fill-color 只设置字体的颜色,color 设置所有的前景色。相当于 text-fill-color 为 color 的子集。
background-clip
置元素的背景(背景图片或颜色)是否延伸的范围(border-box/padding-box/content-box/text等)。
box-decoration-break
指定一个元素的片段时、多行、多列或页面断应该呈现形式( 默认slice,clone独立呈现)。
clip-path
该属性可以创建一个只有元素的部分区域可以显示的剪切区域(circle、ellipse、polygon等)。
object-fit
指定可替换元素的内容应该如何适应到其使用的高度和宽度确定的框。
可替换元素有哪些?iframe、video、embed、img等
writing-mode
设置文本行如何被布置(水平或垂直),以及其中块前进方向。
shape-outside
shape-outside的CSS 属性定义了一个可以是非矩形的形状,相邻的内联内容应围绕该形状进行排列。
CSS Regions && CSS Exclusions
|
|
|
|
以 # 开头的行就是注释,会被解释器忽略。
|
|
PS:EOF 也可以使用其他符号(‘ !)。
|
|
|
|
|
|
|
|
|
|
|
|
PS:分别代表开始位置和结束位置,没有结束位置则到字符串末尾
|
|
|
|
|
|
|
|
|
|
PS:其他特殊符号。
参数处理 说明
$# 传递到脚本的参数个数
$ 以一个单字符串显示所有向脚本传递的参数。
如”$“用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。
如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
|
|
PS:表达式和运算符之间要有空格,加(+)、减(-)、乘(*)、除(/)、取余(%)、赋值(=)、条件表达式的等于(==)和不等于(!=)
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
运算符 说明
-eq 检测两个数是否相等,相等返回 true。
-ne 检测两个数是否不相等,不相等返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。
运算符 说明
! 非运算,表达式为 true 则返回 false,否则返回 true。
-o 或运算,有一个表达式为 true 则返回 true。
-a 与运算,两个表达式都为 true 才返回 true。
运算符 说明
&& 逻辑的 AND
|| 逻辑的 OR
运算符 说明
= 检测两个字符串是否相等,相等返回 true。
!= 检测两个字符串是否相等,不相等返回 true。
-z 检测字符串长度是否为0,为0返回 true。
-n 检测字符串长度是否为0,不为0返回 true。
str 检测字符串是否为空,不为空返回 true。
文件测试运算符用于检测 Unix 文件的各种属性。
操作符 说明
-b file 检测文件是否是块设备文件,如果是,则返回 true。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。
-d file 检测文件是否是目录,如果是,则返回 true。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。
-p file 检测文件是否是有名管道,如果是,则返回 true。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。
-r file 检测文件是否可读,如果是,则返回 true。
-w file 检测文件是否可写,如果是,则返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。
|
|
printf format-string [arguments…]
参数说明:
format-string: 为格式控制字符串
arguments: 为参数列表。
%d,用来输出十进制整数。
%f,用来输出实数(包括单,双精度),以小数形式输出,默认情况下保留小数点6位。
%c,用来输出一个字符。
%s,用来输出一个字符串。
%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4.2f 指格式化为小数,其中.2指保留2位小数。
序列 说明
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f 换页(formfeed)
\n 换行
\r 回车(Carriage return)
\t 水平制表符
\v 垂直制表符
\ 一个字面上的反斜杠字符
\ddd 表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd 表示1到3位的八进制值字符
Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
数值测试、字符串测试、文件测试类似
|
|
|
|
|
|
PS:let 命令,它用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量。
PS:while循环可用于读取键盘信息。
|
|
|
|
PS:until 循环执行一系列命令直至条件为 true 时停止,这恰好与 while 相反。
|
|
PS:case以esac结尾。
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。
break命令
break命令允许跳出所有循环(终止执行后面的所有循环)。
continue
continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。
|
|
PS:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)。
$1、$2、${10}分别代表第一个、第二个、第十个参数。
函数返回值在调用该函数后通过 $? 来获得。
参数处理 说明
$# 传递到脚本的参数个数
$ 以一个单字符串显示所有向脚本传递的参数
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ 与$相同,但是使用时加引号,并在引号中返回每个参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
命令 说明
command > file 将输出重定向到 file。
PS:注意任何file1内的已经存在的内容将被新内容替代。
command < file 将输入重定向到 file。
command >> file 将输出以追加的方式重定向到 file。
PS:追加。
n > file 将文件描述符为 n 的文件重定向到 file。
n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m 将输出文件 m 和 n 合并。
n <& m 将输入文件 m 和 n 合并。
<< tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入。
|
|
简单总结下Mobx常用api的使用
Observable // 将一个数据变成可观察数据(数组不是真正的数组)
extendObservable() // 将动态添加的数据变为可观察(对象)
如果是方法的还需要observable.box来修饰
调用get和set方法可以访问和修改原始类型值
可以根据多个可观察数据产生一个新的可观察数据
自动追踪可观察数据,当在可观察数据发生变化时执行(会初始化执行一次)
当第一个参数为true,执行第二个参数方法
它接收两个函数参数,第一个(数据函数)是用来追踪并返回数据作为第二个函数(效果 函数)的输入
action
action.bound // 多一个功能绑定this
runInAction
将多次可观察数据的改变合并到一次触发(优化性能)
import {PropTypes as mobxPropTypes} from ‘mobs-react’;
给UI组件使用 @observer // 把react组件的render方法包装成autorun
observe 和 intercept 可以用来监测单个 observable(它们不追踪嵌套的 observable) 的变化。
intercept 可以在变化作用于 observable 之前监测和修改变化。 observe 允许你在 observable 变化之后拦截改变。
递归地将一个(observable)对象转换为 javascript 结构。
支持 observable 数组、对象、映射和原始类型。
spy(listener). 注册一个全局间谍监听器,用来监听所有 MobX 中的事件。
trace 是一个小工具,它能帮助你查找为什么计算值、 reactions 或组件会重新计算。
细粒度拆分视图组件
使用专用组件处理列表
尽可能晚地解构可观察数据
webpack4
webpack、webpack-cli、babel-core、babel-loader、babel-preset-env \ 常见依赖
babel-plugin-transform-class-properties // class支持属性
babel-plugin-transform-decorators-legacy // 支持decorators语法
babel-preset-react // jsx依赖
clientWidth、clientHeight:是指元素内容+内边距大小,不包括边框、外边距、滚动条部分。
offsetWidth、offsetHeight:是指元素内容+内边距大小+边框大小,不包括外边距和滚动条部分。
scrollWidth、scrollHeight:是指元素内容+内边距+对应方向的溢出部分。
clientLeft、clientTop:是指元素的内边距的外边缘和边框的外边缘的距离(其实边框的宽度)。
offsetLeft、offsetTop:元素的边框的外边缘距离与已定位的父容器(offsetparent)的左边距离(不包括元素的边框和父容器的边框)。
scrollTop、scrollLeft:获取或设置一个元素垂直或水平滚动的像素数。
pageXOffset、pageYOffset:返回文档在窗口左上角水平和垂直方向滚动的像素。
event.clientX、event.clientY // 鼠标相对于视口左上角X,Y坐标(不包括工具栏和滚动条)
event.pageX、event.pageY // 鼠标相对于文档左上角X,Y坐标
event.offsetX、event.offsetY // 鼠标相对于事件源元素(srcElement)的X,Y坐标(只有ie支持)
event.screenX、event.screenY // 鼠标相对于用户显示器屏幕左上角的X,Y坐标。
document.documentElement.clientWidth
document.documentElement.clientHeight
document.documentElement.scrollWidth
document.documentElement.scrollHeight
window.screen.width
window.screen.height
window.screen.availWidth
window.screen.availHeight
window.innerWidth
window.innerHeight
window.outerWidth
window.outerHeiht
ele.getBoundingClientRect()
ele.getClientRects()
window.getComputedStyle(element, [pseudoElt])
]]>git clone [remoteUrl] // clone远程库到当前目录(项目名为远程项目名,会自动设置origin为remoteUrl的引用)
git clone [remoteUrl] [本地项目名] // clone项目并会重命名为[本地项目名]
PS:自动设置本地 master 分支跟踪clone的远程仓库的 master 分支
用于从另一个存储库下载对象和引用
git fetch // 取回远程主机所有分支的更新
git fetch [remoteUrl] // 取回远程主机[remoteUrl]的更新到本地仓库(不自动合并到工作区)
git fetch [upstream] // 取回远程主机的引用[upstream]的更新到本地仓库
git fetch [remoteUrl] [branchName] // 取回远程主机[remoteUrl]的分支[branchName]的更新
git fetch [remoteUrl|upstream] [远程分支名]:[本地分支名] // 将[远程分支]fetch到本地并命名为[本地分支名]
用于切换分支或恢复工作区文件
git checkout - // 切到最近的一次分支
git checkout [branchName] // 切换到[branchName]分支
git checkout -b [branchName] // 创建并切换到[branchName]分支
git checkout -b [localBranch] [remoteUrl]/[remoteBranch] // 创建并切换到[localBranch]并追踪[remoteUrl]/[remoteBranch]
git checkout -m [branchName] // 如果你在错误分支中开发,但又不允许直接切换分支(因为本地有修改),git会帮我们将错误分支到代码合并到branchNane
git checkout -- [fileName] // 撤销工作区的指定文件的操作(没有通过git add添加到暂存区)
git checkout . // 撤销工作区的所有文件的操作
git checkout head -- [fileName] // 撤销文件到上次commit的时候(head指向上次commit)
git checkout -b [localBranchName] [upstream]/[remoteBranchName] // 以远程[remoteBranchName]为模板新建[localBranchName]并切换到该分支并追踪到[upstream]/[remoteBranchName]【2018-12-19】
用于从另一个存储库或本地分支获取并集成(git fetch + git merge FETCH_HEAD)
git pull [远程主机名] [远程分支名]:[本地分支名]
git pull origin next:master // 取回origin主机的next分支,与本地的master分支合并
git pull origin next // 若想取回origin主机的next分支并与当前分支合并,可省略当前分支名
一旦当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名
git pull origin
如果当前分支只有一个追踪分支,连远程主机名都可以省略
git pull
清理远程已删除本地还存在的分支(2020-04-20)
git pull -p
将文件内容添加到索引/暂存区
git add [path] // [path]可以是文件也可以是目录
git add . // 将所有修改添加到暂存区
git add -u [path] // 把[path]中所有跟踪文件中被修改过或已删除文件的信息添加到暂存区(省略
git add -A [path] // 所有跟踪文件中被修改过或已删除文件和所有未跟踪的文件信息添加到暂存区(省略
git add -i [path] // 查看已跟踪的文件是否有更改、是否有添加到暂存区
用于显示工作目录和暂存区的状态
git status -uno // 只列出所有已经被git管理的且被修改但没提交的文件
git status -s // --short 格式化输出git status
将暂存区当前内容与描述更改的用户和日志消息一起存储在新的提交中
git commit -a // 会对以已追踪的文件自动执行git add并commit(只会对已追踪的文件有效果)
git commit -m ‘注释’ // 带注释的提交
git commit --amend -m ‘注释’// 尝试重写提交(修改上次commit,如果提交内容没有更改,将使用本次提交注释覆盖上次提交注释)
用于将本地分支的更新
git push [远程主机名] [本地分支名]:[远程分支名] // 将[本地分支名]推送到[远程主机名]的[远程分支名](远程分支名不存在时则新建)
git push [远程主机名] :[远程分支名] // 省略本地分支,表示删除[远程主机名]的[远程分支名]
git push [远程主机名] -d [远程分支名] // 与上等价(--delete)
git push [upstream] [branchName] // 推送[branchName]分支到远端
git push -a [远程主机名] // 将本地的所有分支都推送到远程主机(--all)
git push [远程主机名] HEAD // 将当前分支推送到远程的同名分支
git push [远程主机名] --tags // 推送所有标签
git push [远程主机名] [tagName] // 推送单个标签
git push [远程主机名] :[tagName] // 删除远程标签
git push [远程主机名] tag [tagName] // 将本地标签[tagName]推送到远端[remoteUrl]
查看(当前分支前会有星号)、创建、删除分支
git branch // 查看本地分支
git branch -r // 查看远端分支
git branch -a // 本地+远程分支列表(--all)
git branch [branchName] // 新建[branchName]分支
git branch -v // 查看分支的最近commit及注释
git branch -vv // 查看本地
git branch -D [branchName] // 删除[branchName]分支(需要切换到要删除分支以外的分支)
git branch -m [oldBranchName] [newBranchName] // 重命名分支
git branch --set-upstream-to [remoteUrl]/[branchName] // 为当前分支建立追踪关系,追踪为[remoteUrl]/[branchName]
git branch --unset-upstream // 撤销本地分支与远程分支的追踪关系
用于将两个或两个以上的开发历史加入一起
git merge [branchName] // 合并branchName分支到当前分支的顶部
git merge -s ours [branchName] // 合并branchName分支到当前分支,并使用ours合并策略(该参数将强迫冲突发生时,自动使用当前分支的版本)
git merge -s theies [branchName] // 同上,但该参数将强迫冲突发生时,自动使用被合并分支的版本
如在 dev 分支上执行:git rebase master
作用:该命令会把你的”dev”分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch)(这些补丁放到”.git/rebase”目录中),然后把最新的“master”代码合并到“dev”分支,最后把之前临时保存的这些补丁应用到”dev”分支上。
git describe // 显示离当前提交最近的标签
git checkout dev
Git rebase remoteDev
// 如果有冲突,解决冲突—循环
git add .
git rebase --continue
git rebase --abort // 在过程中可以终止rebase,恢复到rebase开始前的状态。
git rebase 与 git merge
|
|
用于显示提交日志信息
git log -1 // 查看最近一条commit记录
git log -n // 最近n次提交
git log --oneline // 单行显示日志
git log --pretty=oneline // 查看以前提交记录
git log --no-merges // 显示整个提交历史记录,但跳过合并
git log [dirName]/[fileName] // 查看当前分支dirName目录下fileName文件的提交日志
git log --since=”2 weeks ago” -- [fileName] // 显示最近两周fileName文件的提交日志
git log --name-status [branchName1]..[branchName2] // 显示branchName2分支尚未在branchName1分支中的提交
git log --follow [fileName] // 显示fileName文件的更改信息,包括更名之前的提交
git log --branches // 显示所有分支的提交
git log --branches --not --remotes=origin // 显示所有分支的提交(但不包括本地追踪远程分支origin)
git log [remoteUrl]/[branchName] // 查看远端某分支的日志
git log [commitId] // 查看对应commitId的提交
git log --author=[userName] // 查看属于userName提交的记录
git log -p // 查看提交历史并显示每次提交的内容差异
git log -p -2 // 最近两条
git log --stat // 每次提交的简略的统计
git log --pretty=[args] // 参数为 oneline:一行显示,还有short,full ,fuller
用于汇总git日志输出(commit次数+提交注释)
git shortlog -s // 汇总每位开发者commit次数
git shortlog -n // commit次数排名
工作目录(Working tree)和暂存区域快照(index)之间的差异
git diff [fileName] // 比较当前文件和暂存区文件差异
git diff [commitId1] [commitId2] // 比较两次提交之间的差异
git diff [branch1] [branch2] // 比较两个分支之间的差异
git diff --staged // 比较暂存区和版本库差异
git diff --cached // 比较暂存区和版本库差异(git add后尚未git commit)
git diff --stat // 仅仅比较统计信息
git diff HEAD // 自上次提交以来工作树中的更改
git diff [branchName] // 查看工作目录和某分支的差异
git diff HEAD^ HEAD // 比较上次提交和上上次提交
git diff // 查看未暂存的修改
git diff --cached // --staged 查看已暂存的修改
用于从工作区和索引中删除文件(git rm 删除文件可以被git记录下来,rm只是物理删除)
git rm --cached [fileName] // 只是从暂存区中删除文件索引
git rm [fileName] // 工作区和暂存区同时删除文件
PS:其他参数-f为--force -r为递归处理该目录下的所有文件
用于移动或重命名文件
git mv [fileName] [dirName] // 将文件[fileName]移动到目录[dirName]中去
git mv [oldFileName] [newFileName] // 重命名
作用是修改HEAD的位置,即将HEAD指向的位置改变为之前存在的某个版本(这个版本之后的commit都将消失)【2018-12-18更新】
git reset HEAD [fileName] // 撤销已经git add到暂存区的指定文件的操作(原理是重新取最后一次commit的内容)
git reset HEAD // 撤销已经git add到暂存区的操作
git reset HEAD~1 // 重置到上次commit
git reset [commitId] // 重置到commitId
git reset --soft [commitId] // HEAD回退到commitId,暂存区和工作区不变
git reset --mixed [commitId] // HEAD回退到commitId,暂存区改变,工作区不变(默认方式)
git reset --hard [commitId] // HEAD回退到commitId,暂存区和工作区都将改变(非常危险)
git reset soft,hard,mixed之区别深解
作用通过反做创建一个新的版本,这个版本的内容与我们要回退到的目标版本一样,但是HEAD指针是指向这个新生成的版本,而不是目标版本。
适用场景: 如果我们想恢复之前的某一版本(该版本不是merge类型),但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法。
Git恢复之前版本的两种方法reset、revert(图文详解)【2018-12-18更新】
git remote rename [shortOldName] [shortNewName]// 修改一个远程仓库的简写名(引用)
git remote rm [shortname] // 移除一个源
git remote show [remoteName] // 查看某一个远程仓库的更多信息
git remote // 查看远程仓库(origin代表你本地clone的远程地址)
git remote -v // 查看远程仓库(带fetch、push地址)
git remote add [shortName] [url] // 添加远程仓库(shortname为以后的引用名)
git remote set-url [shortOldName] [url] // 修改源[shortOldName]的地址
git remote // 管理一组跟踪的存储库
git remote // 查询当前库的远程库
git remote -v // (--verbose)查看库的远程fetch和push地址(前提是有对应权限)
git remote add [shortName] [remoteUrl] // 添加远程仓库[shortName]为其简短引用
适用于在处理需要较长时间的任务task1时又有紧急任务task2需要处理,可以通过git stash来保存本次的修改并将工作目录恢复到HEAD提交,等完成紧急任务task2后又继续之前的任务task1
git stash // 将当前任务存储起来(保存本地修改,并恢复工作目录以匹配HEAD提交)
git stash list // 查看已存储的任务列表
git stash apply stash@{2} // 应用已存储的任务列表的第2+1条
git stash drop stash@{2} // 从已存储的任务列表移除第2+1条
git stash pop // 取出已保存的最近任务(git stash apply + git stash drop)
PS:适用于在处理需要较长时间的任务时,有紧急任务需要处理在其他分支处理
用于创建,列出,删除或验证使用GPG签名的标签对象
git tag // 列出所有标签
git tag -a [tagName] -m [options] HEAD // 为当前HEAD创建标签
git tag -a [tagName] -m [options] [commitID] // 为某个commitID创建标签
git tag -l // 查看所有标签
git tag -d [tagName] // 删除某个标签
git tag -l ‘关键字’ // 列出满足关键字的标签
git tag -v [tagName] // 查询tagName是否已经使用
git tag [tagName] // 创建轻量级标签(轻量级标签实际上就是一个保存着对应提交对象的校验和信息的文件,其实就是不带-a,-s 或 -m)
git tag -a [tagName] -m [说明] // 创建一个带说明的标签名为tagName的标签
git tag -a [tagName] [commitId] // 为某个commitId创建tagName
PS:实质tag保存在.git/refs/tags中
用于初始化或更新或检查子模块
场景:基于公司的项目会越来越多,常常需要提取一个公共的类库提供给多个项目使用,下面介绍下基本使用步骤
克隆含有子模块的项目
git clone [remoteUrl] // clone 主项目
git submodule init // 初始化本地配置文件
git submodule update // clone相关的子模块
或者
git clone --recursive [remoteUrl] // 先clone主项目,再递归clone子模块
为项目添加子项目
git submodule add [remoteUrl] // 添加子模块,[remoteUrl]为子模块远程地址
删除某个子项目
git rm --cached [subModuleName]
rm -rf [subModuleName]
rm .gitmodules
vim .git/config
git commit -a -m ‘remove [subModuleName] submodule’
git submodule命令
用于显示各种类型的对象
git show [tagName] // 查看相应标签的版本信息
git show [tagName]^{tree} // 显示标签[tagName]指向的树
git show -s --format=%s [tagName]^{commit} // 显示标签[tagName]指向的提交主题
git show next~10:Documentation/README //
git show [commitId] // 查看某次提交的内容
git format-patch // 创建最新提交的修补程序
git format-patch [commitId] // 为指定[commitId]创建补丁
git apply [补丁标记id] // 使用补丁修改本地文件而不创建提交
git am [补丁标记id] // 使用补丁修改本地文件并创建提交
用于获取并设置存储库或全局选项
git config --list // 查看所有的git配置
git config [key] // 查看特定项配置
git branch -D [branchName] // 删除本地分支
git push [远程主机名] -d [远程分支名] // 删除远程分支
git fetch [远程主机名] master:[本地分支名] // 以远程的master作为本地的[本地分支名]
git push [远程主机名] [本地分支名] // 将本地分支推送到远端
git branch --set-upstream-to [远程主机名]/[远程分支名]
git checkout -b [localBranchName] [upstream]/[remoteBranchName]
git update-index –assume-unchanged -path 可以忽略文件
git update-index –no-assume-unchanged –path 可以取消忽略文件
lsof -i:port号 // 查端口所用PID
kill PID // 杀掉进程
netstat -aon | findstr port号 // 查端口所用PID
tasklist | findstr PID // 根据PID查进程
taskkill /pid PID -t -f // 杀掉进程
git init // 创建一个空的Git仓库或重新初始化一个现有仓库
git reflog // 查看所有分支的所有操作记录(包括commit和reset的操作)
git cherry-pick [commitHash] // 把某个分支的commit作为一个新的commit引入到你当前分支上
git help // 查看帮助列表
git help [key] // 查看特定[key]相关帮助
git mergetool // 用于运行合并冲突解决工具来解决合并冲突
git blame [file] // 用来定位每一行代码的最后一次修改者
ifconfig // 查看ip地址等信息
ipconfig // 查看ip地址(window)
更新于[2020-06-08]
]]>
我们生活中常见的劫持有,DNS劫持(运营商作怪)、路由劫持、代理服务器劫持(这个好理解只提一下)、HTTP劫持、软件劫持。
随后我将介绍这些劫持和如何预防这些劫持。
当用户输入URL直到网页显示,这个过程发生了以下事情,信息首先会通过浏览器发送,然后经路由中转,接着DNS将域名解析成IP,找到服务器后服务器会发送内容给用户,接着再由路由转发数据,最后浏览器将内容呈现给用户(视实际情况,这个过程中还可能存在更多关卡,比如说防火墙、代理服务器等)。
由此可见,无论浏览器、路由、DNS、服务器等任一环节中出了叛徒,用户请求的网页就可能惨遭删改。
在用户输入URL后到专门的DNS服务器进行查询IP,后面的通信很多都依赖于这个IP地址,如果这个IP地址是错误的呢?
为什么说DNS劫持又是运营商劫持呢?运营商目前竞争比较激烈,为了进一步推广业务,或者进行额外的创收,一些管理不严的二线运营商或者运营商的分部,就会在DNS解析上动歪脑筋了。
选择可靠的DNS服务器,例如奇虎360、诺顿、Comodo、百度、阿里、Google等企业,都有提供DNS解析服务,你也可以选择Open DNS这样的老牌免费DNS服务。
还可以向运营商投诉,如果运营上不处理,可以直接向工信部投诉。
DNS劫持一般会替换调整个网页(返回的IP地址不对哒嘛),与DNS劫持不同的是网页劫持往往只是在页面上添加一个小窗,但这小窗并不属于网页本身的广告,有时候无论你访问什么网页,这小窗都不会消失,甚是烦人。
HTTP劫持的原理就是在服务器和用户之间的信息传输之中添油加醋,这是由于信息没有被加密而造成的。用户请求了网站服务器,服务器返还网页给用户,在传输过程中就给了他人加料的机会。
将网页升级为HTTPS的连接是最有效的方法,使用HTTPS之后,在传输数据过程中,数据是加密的,在传输过程中就很难被篡改了。
HTTPS不仅可以防止HTTP劫持,也能够较好地防止DNS劫持,这是由于HTTPS的安全是由SSL来保证的,需要正确的证书,连接才会成立。如果DNS把域名解析到了不对应的IP,是无法通过证书认证的,连接会被终止。
在我们的发送request和接受response,都会经过路由,路由器其实也会进行网页劫持。例如小米路由器,就曾经做过劫持网页的事情。虽然性质不严重,没有张贴引人注目的广告,只是把404之类的页面替换成自家网页,但这总归是不对的。
这个解决办法,只能购买靠谱和权威厂家的路由了。
有的同学可能使用过一些软件来全局的清除广告,但是,这是通过全局流量管控来实现的,电脑所有的网络流量都会经由去广告软件之手,因此软件要进行网页劫持,也是轻而易举的事情。例如,著名的去广告软件AD safe,就干过劫持网页的事情。
由此可见,使用三方去广告软件也不是很靠谱,还请读者自我分辨。
数组、链表、散列表、栈、队列、图、树
数组,在内存上必须给出连续的空间(一但内存空间不足时,需要将整个数组移位到另一块内存足够大的地方)。
内存空间占用的少,因为链表还要保存下一个节点的内存地址。
访问性:访问性好,数组内的数据可随机访问,因为其内存地址时连续的。
操作性:操作性差,在数组中插入、删除元素,都会导致其后的元素整体移动(插入时向后移动、删除时向前移动)。
扩展性:扩展性差,因为一个数组建立后所占用的空间大小就是固定的(大多数语言时这样的)。
大O表示:读取 O(1),插入 O(n),删除 O(n)
链表,内存地址上可以是不连续的,每个链表的节点包括当前节点的值和下一个节点的内存地址(单向链表的一个,双向链表的话会有两个)。
访问性:访问性差,链表不具备随机访问性,其每次访问都是从头开始访问。
操作性:操作性好,只需要操作对应节点之前的内存地址为变更后的节点地址。
扩展性:扩展性好,因为其内存不连续,只要还有剩余内存即可。
大O表示:读取 O(n),插入 O(1),删除 O(1)
散列表使用数组来存储数据,通过一个散列函数将其映射到数组不同位置。
较低的填装因子(填装因子=散列表包含的元素数/位置总数)。
良好的散列函数。
使用数组链表处理冲突。
大O表示:平均情况读取、插入、删除都为O(1),最糟糕情况读取、插入、删除都为O(n)
非加权图—广度优先搜索用于查找其最短路径。
加权图(权重为正)—狄克斯特拉算法用于查找其最短路径。
加权图(权重有负)—贝尔曼-福德算法用于查找其最短路径。
]]>参考文档
数组与链表的优缺点
在JS这门语言中,变量分为两种类型:基本类型(Undefined、Null、Boolean、Number 和String)和引用类型(Object、Array、Function等)。
对应存储内存又分为栈内存(Stack)和堆内存(Heap)。
作用:存储基本类型的变量和存储引用类型的变量内存地址。
特点:这些基本类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。
作用:实际存储引用类型的变量的值(通过和栈内存中保存的内存地址关联起来)。
特点:这种值的大小不固定(比如说一个Array的length是可以动态改变的,因此不知道其需要的内存大小),因此不能把它们保存到栈内存中。
当已经不需要某块内存时这块内存不能被垃圾回收机制及时处理(间歇的不定期的寻找到不再使用的变量),并释放掉它们所指向的内存。
这是javascript中最常用的垃圾回收方式。当变量进入执行环境时,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境所引用变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量不需要访问这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
如何给对象添加标记?
反转特殊位或者添加对象列表
【2018-11-22看见优秀的文章,比较好的说明了JS内存机制】
标记清除算法将“不再使用的对象”定义为“无法达到的对象”。简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
JavaScript 内存机制
另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。
PS:该方式不能处理循环引用。
通过控制面板 Performance 选中 Memory 查看 JS Heap(正常情况会有升有降)
通过查看 Main 了解主线程在各个时间段执行了那些函数来进行排查(所以避免在开发中写过多的匿名函数,不然你将看到很多anonymous function)
通过控制面板 Memory 选中 Heap snapshot 可以进行具体分析(快照有一个相互比较的功能,可能比较两个快照的差异)
我们都知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象。
PS:这里我先说明一下直接执行 Person 会返回 undefined,new Person(…) 会返回一个对象(即我们的this对象)。
(1) 创建一个新对象
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象即类的实例)
(3) 执行构造函数中的代码(即为这个新对象添加属性)
(4) 返回新对象
如果不明白,请看前辈整理的文章
每一个 function 声明时都会有一个内部属性 [[Scope]],例如声明 foo 函数会创建一个 foo.[[Scope]] 属性
在函数执行时,会创建一个叫做执行环境/执行上下文(execution context,下文均用EC表示)的内部对象(独一无二)。
函数每次执行时的执行环境独一无二
多次调用同一函数就多次创建执行环境
并且函数执行完毕后,执行环境就会被销毁
这里我们来看一个稍微复杂一点的场景
foo函数在预编译阶段创建了bar函数,于是bar函数创建了属性[[Scope]],包含bar被创建的作用域中对象的集合,也就是复制了foo.EC
所以我们可以得到
PS:由于bar函数是在foo函数执行时创建的,所以bar[[Scope]]=foo.EC
bar函数执行,过程同foo函数执行相近,整理出 bar.EC
js引擎就是通过作用域链的规则来进行变量查找(准确的说应该是执行上下文的作用域链)
查找过程就拿上面的代码来说,比如说我在bar函数执行console.log(a);
那么bar函数执行时,js引擎想要打印a,于是就去作用域链上查找
第一层AO没有(bar运行时产生的)
第二层AO没有(foo运行时产生的)
第三层GO找到了变量a (foo定义是a为undefined,预编译时a被赋值为1)
于是返回了变量a的值
如果在bar函数中在创建一个der函数,der的EC又会是怎么样呢?读者自行脑补吧(大体思路类似)
[[Scope]]与作用域链
<script src="script.js"></script>
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
<script async src="script.js"></script>
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载并行进行,且并立即执行。
<script defer src="script.js"></script>
有 defer,加载和渲染后续文档元素的过程将和 script.js 的加载并行进行,但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
<script defer async src="script.js"></script>
同时存在时,async生效。
defer 和 async 在内联脚本无作用。
在下载时和与HTML解析异步的,执行时阻塞HTML解析(包括没有defer 和 async属性的场景)。
async异步下载后立即执行(可能不按下载顺序执行,适用于无任何依赖的脚本)。
defer异步下载后等文档完成解析后,触发 DOMContentLoaded 事件前执行(安下载顺序执行,适用于有依赖关系的脚本)。
PS:CSS并行下载,JS串行下载,相对于HTML解析来说。
defer和async的区别
可以添加至主屏幕
实现离线缓存功能
实现了消息推送
App Manifest
Service Worker
Push && Notification(push: server 将更新的信息传递给 SW notification: SW 将更新的信息推送给用户)
讲讲PWA
在JavaScript中,任务被分为Task(又称为MacroTask,宏任务)和MicroTask(微任务)两种。
process.nextTick(node独有), Promises, Object.observe(废弃), MutationObserver
script(同步代码), setTimeout, setInterval, setImmediate(node独有), I/O, UI rendering
script(同步代码) -> MicroTask -> MacroTask
在执行上面代码时有产生了一些 MicroTask 和 MacroTask 会挂起,在一下次Event Loop再触发,以此类推。
可以看看我之前的博客
|
|
|
|
]]>
Model>View(单向)
Model<>View(单向)
通过数据劫持和发布者-订阅者模式的方式来实现。
设计模式,是一套经过前人总结、业务验证并适合于特定业务开发的代码组织方式,可能会有一些同学会认为设计模式没有用,我这里需要指出设计模式并不是万能的只适合于特定业务场景的开发(对我们的业务开发起到一定的指导作用,所有设计模式的目的都是让开发者编写可维护、易扩展的代码),其实你日常开发中或多或少都使用过设计模式,只是你不知道名字而已(如,绑定事件和触发事件这就是一个简单的发布-订阅模式)。
本文所有设计模式都是使用 JavaScript 语言书写,这些案例都是较为基础的,目的是帮助前端同学更好的理解设计模式。
为了你更好的理解设计模式,你需要了解设计模式的六大原则:单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则,可以看这篇设计模式六大原则。
本文将同步发布于Blog、掘金、segmentfault、知乎等处,如果本文对你有帮助,记得为我得到我的个人技术博客项目给个star哦。
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
设计模式一般需要满足下列原则【2019-01-09更新】
SOLID 是几个单词首字母组合而来,分别表示 单一功能原则、开闭原则、里氏替换原则、接口隔离原则以及依赖反转原则。
单一功能原则:如果一个类干的事情太多太杂,会导致后期很难维护。我们应该厘清职责,各司其职减少相互之间依赖。
开闭原则:“开”指的就是类、模块、函数都应该具有可扩展性,“闭”指的是它们不应该被修改。也就是说你可以新增功能但不能去修改源码。
里氏替换原则:名字很唬人,其实道理很简单,就是子类不要去重写父类的方法。
接口隔离原则:JavaScript 几乎没有接口的概念,所以这条原则很少被使用。官方定义是“客户端不应该依赖它不需要的接口”,也就是接口最小化,把接口解耦。
依赖反转原则:高层次模块不能依赖低层次模块,它们依赖于抽象接口,抽象接口不能依赖具体实现,具体实现依赖抽象接口。总结下来就两个字,解耦。
定义:简单工厂模式是由一个方法来决定到底要创建哪个类的实例, 而这些实例通常都拥有相同的接口(属性和方法)。
举例:计算器(加、减、乘、除)、自行车售卖(山地、公路)、饮料机(咖啡、牛奶、水)、RPG中职业(战士、法师、射手)
需要详细了解该模式,请访问该链接
定义:单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
举例::模态框、登录控件、注销控件
需要详细了解该模式,请访问该链接
定义:策略模式包括两个部分,算法的使用部分(不变的)和算法的实现部分(可变的)。
举例:表单效验(是否为空、长度、手机号、邮箱等等)、计算年终奖(工资、效绩)
需要详细了解该模式,请访问该链接
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。代理对象和本体对象实现了同样的接口,并且会把任何方法调用传递给本体对象;
举例: 图片预加载、图片懒加载、合并HTTP请求(代理收集一定时间内的所有HTTP请求,然后一次性发给服务器)、惰性加载(通过代理处理和收集一些基本操作,然后仅在真正需要本体的时候才加载本体)、缓存代理(缓存请求结果、计算结果)
需要详细了解该模式,请访问该链接
定义:中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。
举例:手机购买页面(颜色、数量、内存、价格)、MVC模式(控制层便是位于表现层与模型层之间的中介者)
需要详细了解该模式,请访问该链接
定义:装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责。装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的。与继承相比,装饰者是一种更轻便灵活的做法。
举例:雷霆战机(吃道具的例子)
需要详细了解该模式,请访问该链接
定义:对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
举例:模块通信、事件绑定与触发、售楼中心
需要详细了解该模式,请访问该链接
定义:适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。速成包装器(wrapper)。
举例:常用于接口适配、兼容多个库(如Prototype库的$函数和YUI的get方法)
需要详细了解该模式,请访问该链接
定义:迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
举例:jquery的$.each()、
需要详细了解该模式,请访问该链接
定义:桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。
举例:用桥接模式联结多个类、事件监控
需要详细了解该模式,请访问该链接
定义:外观模式(Facade)为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口使得这一子系统更加容易使用。
举例:兼容浏览器事件绑定、兼容浏览器阻止冒泡、默认事件
需要详细了解该模式,请访问该链接
定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
举例:
需要详细了解该模式,请访问该链接
定义:模版方法模式由二部分组成,第一部分是抽象父类,第二部分是具体实现的子类,一般的情况下是抽象父类封装了子类的算法框架,包括实现一些公共方法及封装子类中所有方法的执行顺序,子类可以继承这个父类,并且可以在子类中重写父类的方法,从而实现自己的业务逻辑。
举例:泡饮品(茶 和 coffee)、公司面试(百度面试 和 阿里面试)
需要详细了解该模式,请访问该链接
定义:组合模式(Composite)将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象(子对象)和组合对象的使用具有一致性。
举例:文件扫描(目录为组合对象和文件为子对象)、dom节点操作
需要详细了解该模式,请访问该链接
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态。
举例:分页控件、撤销组件
需要详细了解该模式,请访问该链接
定义:职责链模式(Chain of responsibility)是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
举例:挤公交车递钱(只有售票员可以收钱)、交押金预定手机
需要详细了解该模式,请访问该链接
定义:状态模式(State)定义一个对象,这个对象可以通过管理其状态从而使得应用程序作出相应的变化。状态模式是一个非常常用的设计模式,它主要有两个角色组成(环境类、状态类)。
举例:文件下载(开始、暂停、完成、失败等)、红绿灯
需要详细了解该模式,请访问该链接
定义:享元模式是一种用于性能优化的模式,如果系统中因为创建了大量类似的对象而导致内存不足或占用过高这种模式就非常有用了(具体做法缓存对象从而达到重复利用)。
举例:内衣厂展示许多商品展示、地图应用(对象池)
需要详细了解该模式,请访问该链接
CSS 伪类在 input 或 textarea 元素显示 placeholder text 时生效。
PS:可以配合 :not() 伪类等配合,优化表单。
CSS 伪类,表示一个元素获得焦点或该元素的后代元素获得焦点。换句话说,元素自身或者它的某个后代匹配:focus伪类。
神奇的选择器 :focus-within
元素本身不产生任何边界框,而元素的子元素与伪元素仍然生成边界框,元素文字照常显示。为了同时照顾边界框与布局,处理这个元素时,要想象这个元素不在元素树型结构里,而只有内容留下。这包括元素在原文档中的子元素与伪元素,比如::before和::after这两个伪元素,如平常一样,前者仍然在元素子元素之前生成,后者在之后生成。
MDN-placeholder-shown
五个最新的CSS特性以及如何使用它们
contain 属性允许开发者声明当前元素和它的内容尽可能的独立于 DOM 树的其他部分。这使得浏览器在重新计算布局、样式、绘图或它们的组合的时候,只会影响到有限的 DOM 区域,而不是整个页面。
MDN-contain