
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>echizen</title>
 <link href="http://echizen.github.io/" rel="self"/>
 <link href="http://echizen.github.io"/>
 <updated>2025-01-28T06:26:38+00:00</updated>
 <id>http://echizen.github.io</id>
 <author>
   <name>echizen</name>
   <email>hiechizen@gmail.cn</email>
 </author>

 
 <entry>
   <title>2024 - 万物之中，希望至美🌱</title>
   <link href="http://echizen.github.io/think/2025/01-28-2024-summary"/>
   <updated>2025-01-28T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2025/2024-summary</id>
   <content type="html">
&lt;p&gt;24年的年终总结，静默的总结几个关键事项和内心成长。又是不白活的一年。&lt;/p&gt;

&lt;h1 id=&quot;语雀翻篇了---巨变与迷茫&quot;&gt;语雀翻篇了 - 巨变与迷茫&lt;/h1&gt;
&lt;p&gt;当23年年末我做着新年的规划，在业余计划做一些尝试探索性的事情。&lt;/p&gt;

&lt;p&gt;当CY24 S1规划进行表格编辑器的渐进式升级，并且在春节前后的空档期完成了70%的基础框架重构事项时，对着自己的代码大感优雅终于可以摆脱技术债进入新一代架构的迭代了，也终于完成了多年前就想建筑的合理的框架和插件体系。我问着我的主管做到怎样是超出预期的375，我们都对表格编辑器渐进式升级迭代的项目充满期待。&lt;/p&gt;

&lt;p&gt;却在3月初被通知要调整到前端智能研发项目。表格这边全年不会有大的项目级需求，而探索期的智能研发项目急需建设。&lt;/p&gt;

&lt;p&gt;一开始我是没得选择，我当时并不认可这个项目的价值，因为我站在自己这样一个开发者的角度，觉得视觉还原在我的研发历程中占比不到10%，就是我们实现了AI的100%视觉还原度，作用也有限，更不用说当时那让人窒息的视觉还原效果。。。虽然dafeng从企业多元用工、组织结构、商业价值等角度跟我聊过好几轮，当我当时的关注点只在自己这样的开发者上，并不能站在更多的维度去思考问题，依旧是觉得这不是我想做的产品。&lt;/p&gt;

&lt;p&gt;我是一个特别在意做什么的开发者，我无法抛开产品纯看技术，更无法都不看只看结果。从这个角度看我其实不是一个很好的打工人，因为我“挑活”。&lt;/p&gt;

&lt;details class=&quot;lake-collapse&quot;&gt;&lt;summary id=&quot;u8af5d941&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;折叠一下负能量内容&lt;/span&gt;&lt;/summary&gt;&lt;p id=&quot;ubdc2372f&quot; class=&quot;ne-p&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;所以从3月到7月我过的都挺痛苦，比较快节奏的做了很多事：譬如一开始做了个半成品能力的openAI，就是那种开放了又没全开，做这种能力让我觉得很难受；后来做数据飞轮，这个倒是有复杂度也很有价值，但说到底还是工程能力建设，idea不来源于我，也没学到新东西，做下来没有什么成就感；后续node层一个老哥离职了我接手了一些接口和稳定性建设，这个属于在node领域我既不专业也没兴趣，实在难以使命必达因为需要就能做到，不过小菜居然接触到node层OOM的问题和corefile文件的排查，也是有学到了。再后来又去紧急支援python校准工程层，老实说那时候我连python语法都不会，一天派10个工单过来，我问gpt都问不过来。。。在我吭哧吭哧的想着要好好学一些python时，又在团队应急完后被调离这块内容......整个就是做的又杂又累又没成就感。&lt;/span&gt;&lt;/p&gt;&lt;p id=&quot;uc22ce954&quot; class=&quot;ne-p&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;那几个月常常有离职冲动。人也十分迷茫，虽然到手的工作本着职业精神和一些惯性还是尽职尽责的做了，但是不清楚自己在干什么，也不清楚自己想干什么。没有离职的原因也仅仅是没想清楚想干什么，不知道往哪里去，如果仅仅是不喜欢当前的工作环境，盲目跳槽很容易再进入一个不喜欢的环境。&lt;/span&gt;&lt;/p&gt;&lt;p id=&quot;u947ffbe9&quot; class=&quot;ne-p&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;很长的时间我都缺乏动力，来蚂蚁后，在保险的3年很稳定，虽然不是最喜欢的工作内容，但是充满挑战和活力整个人也比较积极能打。来到语雀后，因为太喜欢语雀这个产品，期待值很高，也很喜欢语雀这个团队的氛围，没想到来了的两年，每年一个核弹级变化，打得我手足无措。&lt;/span&gt;&lt;/p&gt;&lt;p id=&quot;u836f33c5&quot; class=&quot;ne-p&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;那段时间我对公司的一切东西都不感兴趣，我觉得没有长期主义，所有的产品都没有感情只是赚钱、绩效工具，做起来没什么意思伤感情，毕竟一个程序员对自己做的东西是可能像养宠物一样投入感情的。我排斥这些：&lt;/span&gt;&lt;/p&gt;&lt;ul class=&quot;ne-ul&quot;&gt;&lt;li id=&quot;u4033ec42&quot; data-lake-index-type=&quot;0&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;一款用户使用度很高的产品，到了所谓的“成熟期”后就会大幅减少投入，进入积累问题的阶段，并没有精益求精不断持续迭代的。虽然我知道商业公司要不断拓增长，理解但不喜欢&lt;/span&gt;&lt;/li&gt;&lt;li id=&quot;ud8aa8cc7&quot; data-lake-index-type=&quot;0&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;“一朝天子一朝臣”的更迭。在非核心类的产品上，似乎产品不是基石，而是团队和组织，人比事更重要&lt;/span&gt;&lt;/li&gt;&lt;li id=&quot;u1c3b1308&quot; data-lake-index-type=&quot;0&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;急功近利包容性太少，一方面想大跃进的几个人超越人家几百个人的团队干的事情，一方面又没有耐心和恒心去持续投入，静待花开。&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p id=&quot;u35c039c9&quot; class=&quot;ne-p&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;可能这些话本该一个离职者才能轻松的说出来，我这种还打算继续干的人不应该直接这么喷，影响不好，但是anyway，不吐不快一次。&lt;/span&gt;&lt;/p&gt;&lt;p id=&quot;u21ca8821&quot; class=&quot;ne-p&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p id=&quot;ua9cb1037&quot; class=&quot;ne-p&quot;&gt;&lt;span class=&quot;ne-text&quot;&gt;但虽然我上面吐槽了很多，但是有一点我必须为我的团队正名，经历一年后回首，就是它虽然不是语雀那种风格了，但是它并不“急功近利”，而是真的有恒心和毅力去深耕，我们能用1年多的时间来做前端智能研发项目就很能说明问题了，这件事是这么困难，很多时候看出码结果可能都感到“很糟糕”。（但现阶段即使对比所有现有产品，我们在还原度上都不输）。但公司和团队依旧在持续投入鼓励我们把难的事情做下去，我觉得整个上游的管理层都是很有勇气的，还有dafeng作为第一owner抗住了多大的压力。我都不敢想象换个环境这个项目会被叫停多少轮了。&lt;/span&gt;&lt;/p&gt;&lt;/details&gt;

&lt;p&gt;不管愿不愿意接受，“语雀”翻篇了，真的不在了，甚至语雀团队的那些让我喜欢的特性也都会不在了。&lt;/p&gt;

&lt;p&gt;直接点说这个过程就是，一个追求佛系的人，想去投入一个喜欢的产品安安静静地做东西，但是遇到了大变更，原先想做的产品不是重点了，团队换方向了，进入新一轮更艰难的创业周期，做着一款自己并不是很buy in的产品，原本踏实深入技术非常低调淡定的一群同事都要站起来卷新方向了，整个环境重新变得高压和混乱。&lt;/p&gt;

&lt;p&gt;我就这么被动的纠结迷茫着，回头看这段日子，我对自己很失望，因为我没有去追求改变，而是一直蜷缩着被拥抱变化。&lt;/p&gt;

&lt;p&gt;直到被调整工作内容去做智能研发的模型准确度方向。&lt;/p&gt;

&lt;h1 id=&quot;模型训练---重新激活&quot;&gt;模型训练 - 重新激活&lt;/h1&gt;
&lt;p&gt;做模型准确度是很让人振奋的，这是很核心的内容直接决定了产品的质量；而且在AI时代，谁会不想走的跟模型跟近一点呢，又有几个团队在搞一线的模型类产品，有机会真的有训练卡、推理卡资源去深度接触模型训练呢。一下子我就活过来了，对工作重拾热情。就是门外汉的我不清楚要怎么做才能提高准确度，不过有yijun这种很专业的人士带我入门，让我了解了这个领域的框架，然后我得以进一步探索。&lt;/p&gt;

&lt;p&gt;这也让我意识到自己之前没认识到的部分，&lt;strong&gt;做什么对我来说远比我想象的重要，我一定要去做自己认可感兴趣的工作内容&lt;/strong&gt;。否则我就废了不会能干好的，日后也要有这种追求。&lt;/p&gt;

&lt;p&gt;不详细聊具体工作成果，这个在我12月的月报里有整理。&lt;/p&gt;

&lt;p&gt;重要的是，通过一轮轮的训练观察，我逐渐发掘了这类UI识别类模型的特性，了解了这些领域模型。也了解了模型训练的链路，针对一些小模型日后有啥需求自己就能干了，不再完全依赖算法了。&lt;strong&gt;技术上能独立无依赖对我来说很重要&lt;/strong&gt;，以前做智能UI时，虽然也是算法驱动的千人千面的UI推荐，但那个项目并没有让我真的懂模型，我就是一个完全依赖算法的懂业务的合作前端，这一直是我的遗憾。以前保险有前端做宠物识别类的端智能时我就很羡慕觉得走在前列，现在想想我也揭开那条蒙着的衫知道在干什了。&lt;/p&gt;

&lt;p&gt;“术”、“法”、“道”中虽然“术”在被大家轻视的底层，但我觉得也十分重要，它是基础，&lt;strong&gt;技术是产品的基石，很多时候没有那个技术才无法做出想要的产品&lt;/strong&gt;。所以“术”的层面，&lt;strong&gt;我敲开了AI技术的大门，进入到模型训练的实操和应用层面&lt;/strong&gt;，虽然我还有特别多的理论知识需要补充，但是入门后再深入学习都是时间问题了，我也可以点亮算法技能树的所有需要的枝桠。&lt;/p&gt;

&lt;p&gt;就冲着这一点，我其实就有充足的驱动力，找到了那种为了做一件事可以不吃不睡的兴奋感。&lt;/p&gt;

&lt;p&gt;在“法”的修炼上：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;摸索当下场景下，一些提升准确度的方案。通过引入模型矩阵，让大模型和小模型合作，有效提高了识别准确度，通过多个专家模型的训练，解决了一些占比高的大头场景的准确度问题。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;模型训练的核心环节，是面向目标高效构建多风格数据集，面向训练产物构建科学准确的评测方案，然后才是模型的选型、训练方案的优化。这里面备数、评测都需要工程化自动化建设，关键的人需要做的是分析产物，找出不准确的地方，对比猜测可能性原因，针对性备数，实验性训练来验证。&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;当下大模型在具体任务上的幻觉和确定性不足问题都比较严重，关键是用有效措施去限制约束它，端到端之间目前不能靠大模型直出，工程层会介入很多来保障&lt;/strong&gt;。当然想dp R1出来后，到达第一梯队，我们又可以寄希望于模型本身的能力来推进一步而做薄工程层了&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;深度思考---希望在闪光&quot;&gt;深度思考 - 希望在闪光&lt;/h1&gt;
&lt;p&gt;在“道”的道路上，我是十分不足的。我对“法”的定义是“方法论” - 做成一件事可以抽象的关键举措和通用方法；我对“道”的定义是大家所说的“认知”，更具体一些是对一些方向有正确的判断，有自己独立的思考和原发的见解。&lt;/p&gt;

&lt;p&gt;最近2年我对我身边的事物就已经有2个错误的判断：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;图生码大模型：我过于关注当下的能力局限和使用体验，从视觉还原的视角来说确实没啥可看好的，但是这只是第一步，这个领域代表的其实是一种新型的互联网产品生产模式的革新。在不久的未来，是可能出现只要产品经理，有业务sense和商业嗅觉，就能去做出产品，不会再有“我有一个想法，就差程序员来干活”的情况了。&lt;strong&gt;未来企业用工成本会大大下降，机器代替程序员编码，程序员变成高级监工；而在传统企业之外，个人开发者、创业者会遍地开花，再也不需要那么高的开发人员成本了，大家可以用较少的资源来验证自己的idea，应用会爆发式增长，很多蓝海、小众需求都会被满足，我感觉独立开发者、创业的春天要来了&lt;/strong&gt;。从Bolt.new、V0.dev、Devin、Cursor这些爆火的产品就能感受到这个方向被看好的程度，虽然大家的产品形态不同，但都是这个方向的。&lt;/li&gt;
  &lt;li&gt;对象搞得&lt;a href=&quot;https://github.com/lobehub/lobe-chat&quot;&gt;lobe-chat&lt;/a&gt;，我一开始说这不就是个套壳应用，聊天框架么，有啥技术壁垒么，怎么能干过同类产品，但后续52.2k的star、不断扩大的用户、商业化的初步尝试ok，都让我在不断反思自己的判断。可能&lt;strong&gt;一方面是chat模式依旧是主流的和模型交互的方式、信息的来源，有最大的应用市场，另一方面开源社区的力量真的很强大，精致的产品走开源路线不仅可以收到一线反馈来了解需求做决策、借助社区力量完善产品，对于初创团队也是一个非常好的低成本宣发途径，能在前期快速扩大用户群，建立产品心智&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;从一开始dafeng跟我讲这个项目的意义、前景、商业价值时，我自己的视野的局限让我是不能理解的，后来我看到了超级多的合作团队的需求，看到了公司在这方面的勇于投入，看到了项目一步步做大。意识到&lt;strong&gt;在企业用工场景下，视觉还原这个切入点，和V0.dev走的不同路线，才是亮点能解决问题的关键&lt;/strong&gt;。而且只要第一步还原做好了，未来还有超级多能做的东西，视觉还原 -&amp;gt; 布局合理 -&amp;gt; 逻辑生成 -&amp;gt; 应用制作 -&amp;gt; 乃至上下游整条产研链路。我感觉充满希望，十分美好。&lt;/p&gt;

&lt;p&gt;另外不得不提yijun，虽然十分可惜，他已经江湖再见了，但他就是我职业生涯的第二导师，我在他身上学到了特别多的东西，虽然他走了看不到了，我还是想记录一下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;最深的应该是敢于取舍、抓清主线的勇气，这是我最缺的，我尝尝陷入在具体的零碎的事情里，一个事情没做完善我就出不来，导致重要的事排不进去。但yijun总能在判断轻重缓急后，选择顶着压力先放置日常事务，集中力量搞突破性事项。这个事情说起来简单，判断重要程度的能力我大多数时候也有，但是是需要一定的勇气和担当的，因为放下一部分紧急不重要的事不仅要对抗自己的惯性和强迫症，还需要面对各依赖方压力的。&lt;/li&gt;
  &lt;li&gt;认知，模型效果能一步步做好，离不开他大刀阔斧的颠覆性的几轮方案升级。走之前我问他有没有看到我什么缺陷和对我成长上的建议，他也是建议我&lt;strong&gt;要慢下来去思考，去协作和更多人一起做事，把自己的紧迫感降低，然后去提升认知，去主导关键的方向和策略&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;专业度，作为一个前端，node资深专家，能在1年内变成应该是整个团队最懂模型的人，他的学习方法、效率、动手能力都让人佩服&lt;/li&gt;
  &lt;li&gt;助人的精神，我越来越“淡泊”，没有少年时想表现自己的那份心后很多时候做事都不倾向去宣扬，但是每次做个啥，他会先来做宣发，让合作的同学做的事被“看见”；日常的事务中，无论是具体问题的答疑，还是方向的解惑，都十分到位的助人为乐。能遇到这样的同事是一种幸运，这种同事真的十分少见，不仅是在和他一起做模型期间，之前node基建答疑、fecodex的服务端OOM问题时他都非常专业负责且帮助他人。当你诚心帮助别人时，确实是能获得尊重和感激，遇见的不仅是合作的同事，更是一个可靠的盟友。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;没做模型训练之前，我很焦虑，因为那个阶段它窒息的效果让人很担心能否做起来，但是做了模型之后，我就有了yijun那种坦然自信，再也没有那么焦虑了，因为有了主观能动性，真的能看到自己做的事情让它的效果一步步变好。这一年，我真的是亲历了校准工程引入、模型矩阵引入、MLLM引入后准确度一步步提高的过程，它曲折艰难，如同所有的成功不是一蹴而就。&lt;strong&gt;我觉得这段宝贵的经历对我至关重要，很多事情是一步步探索出来的&lt;/strong&gt;，站在起点看不清路没有关系，要有恒心和毅力去不断试路，只要方向对了，路走慢点也不要怕，没按时到达标志地也不要紧张，只要方向对的在前进，那就是有希望的～如果都在尽力，就不要给自己太大压力，也不要传导压力。&lt;/p&gt;

&lt;p&gt;deepseekV3的R1用下来真的不错，没想到这么快我国的模型就追上了第一梯队，如此智能化的模型给了我们更多的选择，能力和效果有望进一步提升，当然对应着我们背后模型设计的不断升级。&lt;/p&gt;

&lt;p&gt;2025年，可以做的事很多，从模型准确度进一步提升到逐步做到真正的智能研发，是一个让人期待和振奋的事情。但能否做好，没有了扛把子yijun，挑战也很大，我欠缺的知识很多需要持续学习补充，需要不断关注领域发展，去寻找有效实现路径，可能是我工作以来要面对的最难的事情。但是我感受到充满希望～&lt;/p&gt;

&lt;h1 id=&quot;一点点变化&quot;&gt;一点点变化&lt;/h1&gt;
&lt;p&gt;今年还有一个变化就是我不再有“躺平”的想法了，好多年我都憧憬着“年轻时努力工作赚钱，早日财富自由去环游世界，去体验不同的work和lifestyle”。但是现在就没有这种想法了，总觉得&lt;strong&gt;还是得干点什么发点光发点热，要有自己的价值&lt;/strong&gt;。可能是卷久了想躺，躺久了又想卷吧，总会疲劳的切换模式吧。也有可能是对象太优秀了，刺激到了我。无论什么原因，我也不知道这种状态会持续多久，反正目前持续了4个月了，但这种有点追求的状态相比于无欲无求的“peace&amp;amp;love”的状态更让我心安。&lt;/p&gt;

&lt;p&gt;另外24年确实很忙，累到周末只想躺尸休息，在双休日基本没有出门过几次，对象都吐槽生活没有以前丰富了，我也感觉到生活质量下降了，所以&lt;strong&gt;新一年要通过合理规划时间，来保证生活的精彩程度&lt;/strong&gt;，周末还是要多出门体验不同的东西。&lt;/p&gt;

&lt;h1 id=&quot;我结婚啦&quot;&gt;我结婚啦！&lt;/h1&gt;
&lt;p&gt;我这途径1年半虽迟但到的婚礼超出预期～我想记录下这个人生重大时刻亲历的一些想法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2024/001.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2024/002.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2024/003.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2024/004.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2024/005.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;婚礼现场很美，气氛很热烈，仿佛一场梦，穿越成女明星的那种，很少当主角，在这个盛大的筵席上当了一次主角，很珍贵。&lt;/p&gt;

&lt;p&gt;“我婚礼不落泪”，和同事说这句话时并没有底气，但是确实全程没落泪，都是开开心心的，面对老妈送亲的老泪纵横和对象送捧花时的一番告白，确实有点挑战泪腺。但是全程我确实都开开心心的，我从来不觉得结婚意味着和自己父母亲人的分离，只是多了一些新的家人，是一件值得庆祝的事🎉。&lt;/p&gt;

&lt;p&gt;我也没有觉得婚礼有多超出的美好和感人，亲历后，还是觉得这就是一场隆重的纪念仪式，我们日常的生活也很美好，有很多温馨感人的时刻～&lt;/p&gt;

&lt;p&gt;但如果可以选择，我依旧会说不想要这种劳民伤财、消费主义的东西。。。另外我觉得婚俗里有很多物化女性对女性不友好的东西，非女权挺钝感的我都感受出来了，我不知道其他女性同胞们是怎么过来的，譬如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;彩礼、各种礼金。我觉得良好的婚姻是人生合伙人，平等的一起生活，大家一起共同承担，并没有谁绝对吃亏谁绝对占便宜，那么为什么女性要收钱呢，总有种“拿人手软吃人嘴软”的感觉，只要收了钱我觉得我就不再是独立女性而是个“别人家的小媳妇”了&lt;/li&gt;
  &lt;li&gt;陪嫁用品。一般是床上用品、厨房用品，这就妥妥的狭隘定义女性功能属性了。。。&lt;/li&gt;
  &lt;li&gt;婚礼当天的什么：“上车前吃妈妈喂的饭”、“接亲车上了后不回头” - 寓意和娘家的分离不回头；“走5个布袋铺的路” - 代代相传； “摆红枣、花生、桂圆、莲子” - 早生贵子……&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;虽然我吐槽这个，但并没有心力去对抗这些婚俗。而且这个绝对不是主旋律，绝大部分时间都是很愉快的，对象是个很棒的人，对象的家人也都很随和、开放，这些婚俗也不是他们导致的他们也只是选择了keep，大家相处的还是很融洽的。我只是感叹一下婚俗传统的不合理，我也感谢对象家人的辛苦付出，如果不是他们，我们两个懒人不可能搞出这么隆重的仪式来纪念，也没有那个氪金的能力🐶。&lt;/p&gt;

&lt;p&gt;我时常感叹做人不要斤斤计较，充满阳光，真诚待人，世界自会用阳光拥抱。一些重大的事情，譬如选择学校、从事程序员这个行业、去的第一家公司、遇到我的对象和他的家人，这些事都没有想过要索取获得多少，我只是选择了喜欢的东西，能感受美好感受发光点，认识事物两面性也能接受一些缺陷，然后尽力做认知范围里能做的好的事情，我发现我遇到的都远比我以为的要好。它有运气的成分，也有我豁达心态的成分吧～&lt;/p&gt;

&lt;p&gt;一场隆重的仪式后回归日常，我和对象会把接下来这2w天的继续过的有质量有温度～&lt;/p&gt;

&lt;p&gt;2024，我的工作上有过非常纠结痛苦的时刻，但我的生活一直是💯分～&lt;/p&gt;

&lt;p&gt;不得不承认，随着经历的增长，年少的意气风发和热情都会消散，我现在看到热血澎湃的年轻人像看到几年前的自己一样，我更喜欢他们不谙世事的单纯和不可一世的勇气，但终究经历多了心里的东西多了后，无法再那么简单纯粹了。我现在更能接受 “工作 !== 事业”的说法，在公司是一场修行，自己才是自己成长的第一负责人，其实也是唯一负责人，所以无论什么时候清楚自己想要什么才是重要的，可以把公司当成修炼地，通过攻克一个个难关，提升自己做事的能力。为自己负责的时候，不会再为工作把生活搞乱纠结“work life balance”的方法，也不会因为什么而委屈。希望我的思维是真的成长了，不要只增长年龄，还需要时间来检验。&lt;/p&gt;

&lt;p&gt;:::success
愿我能满怀希望的走完2025，想锻炼的都能成长，早日拥有准确的判断力、AI领域的技术敏锐度和模型层的技术底盘、成难事的能力、独立生存的勇气，以及选择一切的自由🌟。&lt;/p&gt;

&lt;p&gt;:::&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>2022 - 平凡人能守住的小确幸</title>
   <link href="http://echizen.github.io/think/2023/01-01-summary"/>
   <updated>2023-01-01T23:02:32+00:00</updated>
   <id>http://echizen.github.io/think/2023/summary</id>
   <content type="html">
&lt;p&gt;去语雀获得更好的阅读体验：&lt;a href=&quot;https://www.yuque.com/echizen/xgfvrp/ud4dgt0sffvqbvic&quot;&gt;https://www.yuque.com/echizen/xgfvrp/ud4dgt0sffvqbvic&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/001.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从新冠走出来不久，还有些乏力，可能写出来的东西会少了些往年的活力。回首这一年我都会觉得挺幸福圆满的～ 不像今年的大多数自媒体的总结和年终演讲充斥着艰难不易，我生活中很多细碎的小幸福，工作中放飞自我选择了自己想做的事，一度觉得很完美，可能这是我&lt;strong&gt;幸福感的高光年度&lt;/strong&gt;～ 如果不是年末席卷而来的疫情冲击着自己、家人、朋友，空气中都弥漫着伤痛，这篇回忆总结会充满欢乐吧。&lt;/p&gt;

&lt;p&gt;时代的一粒沙，落在每个人的身上都是一座山。22 年确实不易，经济下行，到处都风雨飘摇，&lt;strong&gt;在这样的年份里依旧能收获普通人的幸福甚至是圆满，确实有对所在环境的更多的感恩&lt;/strong&gt;，譬如蚂蚁在艰难的大环境下没有大规模裁员，让千万员工依旧体面安心的活着。譬如疫情肆虐时，团队人文化的关怀，得以零压下安心修养度过时艰。&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;tQRQ2&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;时代剪影&quot;&gt;时代剪影&lt;/h1&gt;

&lt;p&gt;打算每年总结加一个时代篇章，记录一下这一年给自己留下的深刻的大事件，除了看见自己，也能看见这一年世界发生了什么：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;元宇宙&lt;/strong&gt;：去年 facebook 就转 meta 大搞元宇宙，我也浅了解了一下，很有想象潜力，但目前进展还缓慢，我自己买过一个 pico neo3，体验后实在没法忍颗粒状的画质和贫乏的可玩内容而退货了，年末入了一个 nreal 的 air 眼镜，画质确实好了很多，但似乎只能作为扩展屏，也是吃灰了。年末看 meta 也跌了 60%。&lt;strong&gt;但我依旧相信 VR、AR 会带我们进入的沉浸式新体验的未来，也有点期望自己能成为这种新体验模式的技术和内容贡献者&lt;/strong&gt;（虽然目前还停留在光想没做的阶段）。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AI&lt;/strong&gt;：今年的 AI 发展绝对让人惊艳，曾经我们觉得富有创造力的领域也引来的冲击，深度学习神经网络的发展下算法变成难以预测结果的黑盒，复杂度的提升带来了多样性和跟人脑更接近的结果。
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Midjourney、Stable Diffusion 的 AI 画作&lt;/strong&gt;在宏大虚拟场景和插画领域，精挑细选后甚至超过人类，关键是这个产出效率人类无法企及&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;copilot 在编程辅助领域也让人惊艳&lt;/strong&gt;，虽然让 ai 直接理解需求出结果程序还很遥远，但在局部区域，每个功能函数的补全准确度已经很高，而在注释补全上，我个人的采纳率已经不低于 80%了。&lt;/li&gt;
      &lt;li&gt;而年末风靡的&lt;strong&gt;openAI 的 chatGPT&lt;/strong&gt;简直王炸，我们看到了它强大的文本理解能力，上下文多轮关联能力，对结果迭代的学习能力，不仅可以给出编程问题的答案、debug，还能作为百科知识的检索入口，也能产出毫无违和感的小作文。之前一直希望拥有的提高个人基础知识获取效率的对话机器人感觉离问世不远了～&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/002.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;疫情&lt;/strong&gt;：
    &lt;ul&gt;
      &lt;li&gt;奥密克戎超强的逃逸能力让今年的疫情防控变得格外艰难，&lt;strong&gt;杭州&lt;/strong&gt;从 4 月底进入&lt;strong&gt;常态化核酸&lt;/strong&gt;的防疫阶段，疫情控制还算好，除了隔三差五做核酸成为习惯，市内的生活受影响不大，并没有出现大规模封控状况。&lt;/li&gt;
      &lt;li&gt;3-5 月上海疫情一片哀嚎，也出现了一批被封的信息&lt;/li&gt;
      &lt;li&gt;当 10 月份的二十大还在强调精准防疫，官方媒体还在&lt;a href=&quot;https://mp.weixin.qq.com/s/Inz_kORneG7QyEttAXnZzg&quot;&gt;论证我国现状为啥不能全面放开&lt;/a&gt;时，11 月新疆火灾引来大规模的过度防疫的反抗声音。11 月-12 月开始了一系列&lt;a href=&quot;https://mp.weixin.qq.com/s/qID36h-4cKYY6n_WTyEg1A&quot;&gt;变化&lt;/a&gt;：11 月中上旬的”优化二十条”出台，11 月 30 号北京广州重庆宣布不再进行“社区核酸筛查”，成为实质上第一波放开的城市，然后非常快的&lt;strong&gt;12 月 10 号“新十条”发布，全国就都放开了&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;我本人也在&lt;strong&gt;12 月 20 号开始发烧抗原检测阳性，到 12 月底自己团队同事、在各地的全家人全部感染&lt;/strong&gt;，传播速度还是超出我的想象。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一年防疫真的不容易，就放开后各地传播感染速度来看就知道前面 11 个月的管控住有多艰难，也从深深的吐槽到有点理解了为啥出现那么多防疫过度的案例。12 月处于全国人民一起生病，“&lt;strong&gt;但愿人长久，千里共毒株&lt;/strong&gt;”、“&lt;strong&gt;海内存知己，天涯同发烧&lt;/strong&gt;”的历史性景象，大家一起发烧，一起研究怎么降温、治咳嗽、缓解刀片嗓。&lt;strong&gt;历史性的事件在这一刻映射在每一个人的身上，乐观的人笑称大家一起“参加了一个十几亿的大项目”&lt;/strong&gt;。&lt;br /&gt;疫情三年，终于画上了句号，从洪水猛兽到自限性上呼吸道感染，虽然吐槽众多，防疫转向非常快速，但毕竟是在病毒几经突变致死率不断下降传播性不断加强的结果，是根据环境变化调整的策略，&lt;strong&gt;长期来看我相信放开是合理的发展经济恢复生活的方式，只是我们不得不共历时艰，很多人在这个冬天受罪&lt;/strong&gt;，虽然说这话有些残忍，但真相是也有很多生命要定格在这个年末，诶，🙏🙏🙏&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;佩洛西访华&lt;/strong&gt;，一度让空气中弥漫着火药味，我记得那天团队还在海南，都有点担心晚上还能回去不。所幸最终只是军事演习示威。朋友圈一群嘲笑，似乎拱火战争的情绪高昂，但&lt;strong&gt;我坚定不移的反对一切战争，相信“发展才是硬道理”，希望我们几十年好不容易的发展成果不被战争破坏&lt;/strong&gt;。依旧记得小时候生活还比较艰难，父母那一代更是，爷爷奶奶辈更是经历过温饱都是问题的日子。现在我们成为第二大经济体太不容易了，只要有一场战争，这一切都大概率被破坏，&lt;strong&gt;一点火药都不知道引燃的是多大规模的冲突局面，我们普通人无法在战乱中独善其身&lt;/strong&gt;，战争了，我可能只能回老家大别山里种田了 😭。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;世界杯没看，但是&lt;strong&gt;卡塔尔&lt;/strong&gt;开拓了我对“富裕国度”的生活状态的眼界。希望通过勤劳的中国人民一代代的努力，我们也能早点步入富裕国家，人民过着富足自得的生活，不再需要为生计奔波&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;中美冲突&lt;/strong&gt;，经济封锁依旧存在。当前形势与我们读书时被灌输的“全球化”相反了。我们在艰难的国际环境中谋发展，一个大国的决策可真是不容易。另一方面也比较欣慰的是，这足够说明了大国崛起已经让世界第一经济体感到危机坐不住了，但我相信一切困难都只是延缓了历史的进程，而影响不了历史的趋势。我是真有一个愿望，“&lt;strong&gt;世界和平，经济发展&lt;/strong&gt;”。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;俄乌战争&lt;/strong&gt;那几天，我在网上追了两天的时间线，感觉非常魔幻。战争影响一直持续，这个样本也让我们看到战争危害有多大，你以为的一天碾压结束，在世界各国力量的加入角逐会拖成长久战。乌顽强抵抗，俄深陷战争泥潭，欧洲能源危机。当看到欧洲在深海的燃气管道被炸，我清楚的看到，世界才不是什么命运共同体，国与国也都是在为自己的利益斗争。&lt;strong&gt;这让我十分迷惑，是否真的能有达到和谐世界的方式，政治组织都是人为的，这种组织真的需要么，人类划分区域，分配利益，造成了很多的伤痛，大家都在探索高效生产，但有没有一种模式解决分配问题，让资源合理充分利用，人人都能怡然自得，能否真的打造人类的命运共同体。如果分配问题不解决，生产再多，总有人要的更多，各自为战，人性的自私让斗争永无停歇。一直觉得三体的世界挺黑暗的，但是如果这就是人类文明的真相，我都不觉得这是文明&lt;/strong&gt;。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;I8ZtM&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;工作&quot;&gt;工作&lt;/h1&gt;

&lt;p&gt;今年是我在蚂蚁这近 4 年，工作最开心最有成就感的一年～&lt;/p&gt;

&lt;p&gt;年初跟主管沟通后，调整了一下工作，从已经没什么想法的赠险离开了，来到保险管家，同时也把之前规划在德鲁伊要做的事做了。在管家工作还是满开心的，工作内容比之前有深度一些，可以和用户管家们直接接触，很快的拿到反馈，能动性也大了不少。&lt;/p&gt;

&lt;p&gt;后来转岗来到语雀，工作体验更上一层楼，语雀真的满足了我对快乐工作的大多数期待～ 感觉又回到刚步入职场的时候哪种开心工作的状态了，虽然作为一个新人在语雀的 2 个多月里还没做出啥大贡献，发挥啥主观能动性，但是稳步的熟悉了解过程让人心安，我也已经看到了很多可以做的事情，也治好了之前在保险总处于挖有哪些事情有意义可以去做的焦虑。我知道，在语雀这片土壤，再多一点点时间，我一定能开出花朵～&lt;/p&gt;

&lt;p&gt;整个一年回头去看，做的事情比较专注，一件件的都是不小的项目，能有始有终的完成，我觉得很欣慰，&lt;strong&gt;一方面要感谢前团队提供的环境和前主管的支持&lt;/strong&gt;。德鲁伊的相关项目，还要&lt;strong&gt;特别感谢师弟&lt;/strong&gt;，是他真正的成就了这些项目，我一个人的精力是搞不完的，很多时候我都只是动动嘴，大量的细节和实践都是他一行代码一行代码贡献出来的。&lt;strong&gt;绝对的年度最佳合作伙伴&lt;/strong&gt;～&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;zLku0&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;核心事项&quot;&gt;核心事项&lt;/h2&gt;

&lt;p&gt;&lt;a name=&quot;f3Uqc&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;德鲁伊微应用迁移&quot;&gt;德鲁伊微应用迁移&lt;/h3&gt;

&lt;p&gt;年初&lt;strong&gt;将经过 3 年发展，涉及 20w+行代码、200+页面、30+个代码贡献者、7 个业务方和技术子团队的一个巨石应用完成了有序拆分。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;:::success&lt;/p&gt;

&lt;p&gt;沉淀：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;沉淀了大型项目微应用拆分经验：大型项目晚期救治术（内部文章）&lt;/li&gt;
  &lt;li&gt;沉淀的检查文件是否被引用的内部工具@alipay/deadfile-checker
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;得&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;这个项目执行过程中深刻体会到了&lt;strong&gt;一个困难的事情要想完成真的要讲天时地利人和，造就合适的环境&lt;/strong&gt;，都不简单是技术的事情，这个事情技术上没有多大的困难，但是涉及众多参与方，平时让大家停滞一个月搞迁移是几乎不可能的，但是选在年关前后的业务喘息期就非常合适。早就想迁移微应用，但真的能做完，还是十分不易的。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;零故障&lt;/strong&gt;的完成了发布，在后期梳理风险并核对检查的过程中也发挥了关键作用，这个是超出自己预期的结果。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;失&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;做事情还是上线就完结了，没有耐得住性子追踪后续的优化，立即投身另一个战场了。虽然选择总有理由，一些都是投入产出比的考量，但是没有做到尽善尽美，对于项目的后续维护者，还是心里有愧
    &lt;ul&gt;
      &lt;li&gt;没有在微应用技术栈深造，甚至都没有跟随节奏继续升级，譬如路由编排配置的升级就没有跟进了&lt;/li&gt;
      &lt;li&gt;还遗留了一些不太好用，但是可以有更好方案的内容没有去优化&lt;/li&gt;
      &lt;li&gt;@alipay/deadfile-checker 也只是做到了满足自己的场景就 OK 了，没有进一步完善，以至于后续其他有团队也有相同诉求，但是使用过程中遇到问题时没有参与研发而是交给他们自己解决了。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;aX1fA&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;德鲁伊页面管理升级&quot;&gt;德鲁伊页面管理升级&lt;/h3&gt;

&lt;p&gt;发起了升级德鲁伊页面管理的项目，抽取保险各配置平台的共性，设计新的页面管理数据模型和平台交互，并做到老数据兼容和一键迁移。&lt;/p&gt;

&lt;p&gt;得&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;这是一个&lt;strong&gt;前端发起&lt;/strong&gt;的项目，并争取获得了服务端、产品同学的支持配合。前期提供了比较有价值的调研报告，过程中无论是产品功能还是整体技术方案都输出了关键性内容。完全的&lt;strong&gt;跳出了前端视角在做一个大的产品升级，复盘时也收获了服务端、测试、产品对我们前端的一致好评，这种被合作方信任认可的感觉是极好的&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;涉及了组内 7 个同学参与开发，在项目管理上有了更进一步的收获&lt;/li&gt;
  &lt;li&gt;更新了比较亮点的功能，譬如自动保存、多人占用等。&lt;strong&gt;个人也是做了一些小而美的事情&lt;/strong&gt;，譬如新手引导和彩蛋，写了一些功能文档《德鲁伊新版页面管理上线啦 🍾🥂🪄》 （内部文章）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;失&lt;br /&gt;同时这个项目也做的比较艰辛，最后在落地上也没能让各页面全面接入，更多的是跟随投保页整合的长江项目得到了应用。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;项目本身是升级，做了一些优化设计，但是我离开时都还没来得及上线杀手锏的新功能，导致业务主动接入的积极性低，而自己又并不想直接通过替换功能入口这种方式强制用户升级，作为业务开发深深理解平台大升级要适应的痛苦与不便。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;劳民伤财，性价比低&lt;/strong&gt;，拿到的结果是对不起辛苦开发的团队同学们，在这里给你们鞠一个&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/003.png&quot; alt=&quot;image&quot; /&gt;。&lt;/li&gt;
  &lt;li&gt;个人的技术投入产出都很低，更多的在做项目管理和局部功能开发，预期的开放 sdk，可插拔的插件系统都没有真的实践，还使用了 subtree 给项目合作埋坑了。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这是我今年投入精力最多，同时也做的最艰辛的一个项目，虽然是升级，但是不是简单改前端交互，动到了底层数据模型，导致开发量巨大，参与者都很幸苦，在业务线很难投入一个技术项目超过两周的，过程中一方面要加班加点赶进度，还要向上汇报找价值，苦思冥想找落地。那段时间每个双休日都在默默加班，一两个月后都有点情绪不稳定了，我不是一个十分坚定的人，一度做到怀疑自己，觉得重来一遍的话不会发起了，&lt;strong&gt;找不到好方向也不要去碰不确定大的事情&lt;/strong&gt;。最终虽然主管和合作方对项目整体还是认可的，但落地结果是不及我预期的，只能安慰自己就&lt;strong&gt;当是一次不成功的创业了&lt;/strong&gt;，至少锻炼了自己，自己怎么折腾都可以，也接受的了失败，但是没拿到理想的应用真的十分觉得对不起项目组成员的辛苦。不过虽然在我交手时，没有全面应用，还是长江项目的核心应用场景的，产品也还有一些杀手锏功能的设想可以迭代，寄希望于后续负责人的创造了。&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;YxFPn&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;预览器alipayaha-preview&quot;&gt;预览器@alipay/aha-preview&lt;/h3&gt;

&lt;p&gt;在保险管家的场景下，发现预览器可以很好的解决管家在 pc 端给用户直接试算保费的场景，让预览器有了新的应用场景，用前端能力解决了一个之前不可能做到而是业务强诉求的功能，也是&lt;strong&gt;意外之喜&lt;/strong&gt;。后来管家的同屏讲解也是拓展应用了预览器的能力。&lt;/p&gt;

&lt;p&gt;这个过程中，增加了通过外网将 rpc 接口代理到内网再去请求网关获取数据，将预览器做到&lt;strong&gt;外网开放可用&lt;/strong&gt;；实现了非常有诉求很难搞的&lt;strong&gt;页面跳转&lt;/strong&gt;能力。&lt;/p&gt;

&lt;p&gt;试算器的还做了很多细节但很有用的能力，解决了很多痛点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;多人使用账号的登录挤兑解决&lt;/li&gt;
  &lt;li&gt;内部接口外防的安全风险管控技术&lt;/li&gt;
  &lt;li&gt;在账号管理这个非常复杂的功能上也做出了很多技术保障：
    &lt;ul&gt;
      &lt;li&gt;批量录制账号：解决几百个账号录入的成本高的问题&lt;/li&gt;
      &lt;li&gt;登录检测：解决不可用账号的检测和下线&lt;/li&gt;
      &lt;li&gt;账号测算：解决未来的年龄断层，保障功能可用性&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;得&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;预览器的应用是超出我预期的，本来只是做一个有需求的 sdk 的小事，但随着应用场景的拓展，越来越发挥价值，而且这些能力非它不可。在我转岗后，还有很多其他 BU 的同学来咨询应用接入的场景问题，可见它虽小但是有场景诉求，是一个自己主导的技术型产品，算是符合“小而美”了，给了我不少的满足感。所以&lt;strong&gt;不怕事情小，从细微处洞察需求，只要是有场景有价值的，它就是一个好产品～&lt;/strong&gt; 最后把预览器交接给了一个师弟，也算走的安心点了，今年这个师弟也参与了很多预览器核心能力的建设，希望后续还能玩出更多的花样～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;失&lt;br /&gt;预览器的发展超出了我的期待，对它已经无憾了。如果说有什么没做好的，是离开时没有给它继续规划一些新能力，为未来做准备。&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;MNZpu&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;保险管家云外呼&quot;&gt;保险管家云外呼&lt;/h3&gt;

&lt;p&gt;web 端软电话是长时间在线的功能，且要求处理长时间多场景下所有状态流转，它的&lt;strong&gt;稳定性&lt;/strong&gt;对前端技术带来很高的要求和复杂度：解决如何保证&lt;strong&gt;多标签页、多页面软电话的连接和协同，如何处理电话掉线后的重连机制&lt;/strong&gt;，如何高效实时的展示用户的呼入等问题。通过&lt;strong&gt;合理的功能模块划分&lt;/strong&gt;，&lt;strong&gt;复杂的策略处理&lt;/strong&gt;，让每个页面、每个标签页都能享受到一致的通话服务，管家在保持通话的同时也可以同时进行工作台其他的操作。这是个人在业务需求里接触到的的复杂度比较高的功能了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/004.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;:::success
沉淀：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.yuque.com/echizen/rze0c6/rbkg0x?view=doc_embed&quot;&gt;好复杂的 web 端多标签页音频电话通信设计 🙃&lt;/a&gt;
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;得&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;这个本身是一个普通的业务需求，但是为了体验，让多窗口切换的通话变成可能性，做了很多技术研发，也沉淀了多窗口通信的能力。是一个发挥技术能动性，让产品更美好的实践～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;失&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;也没啥失的点，多窗口的协同能力可能给后续维护者带去了不小的复杂性 🐶。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;yQSZ9&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;保险管家-im&quot;&gt;保险管家 IM&lt;/h3&gt;

&lt;p&gt;非常积极兴奋的接了这个项目，并且是在确定转岗后依旧尽心尽力做的在保险的最后一个项目，不为别的，就是对技术最纯粹的那份好奇，想亲历 IM 的设计研发，为复杂应用的经历添加一笔。&lt;/p&gt;

&lt;p&gt;这是产品发起的一个升级型项目，将通用的 IM 工具建设了很多管家特色能力，从而提高管家的服务效率和体验。最大的改变是做了不同规则的 7 种聊天列表。&lt;/p&gt;

&lt;p&gt;这事情可充分发挥了技术的能动性，IM 原本是比较科学的 MVC 架构，这波产品升级，我注入了 task 层来做缓存和预请求，提升体验。并且设计了容器层，为未来的 electron 应用化打基础。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/005.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;:::success
沉淀：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;也是整过 web 端 IM 应用的人了 😬（内部文章）&lt;/li&gt;
  &lt;li&gt;之前做 web 端软电话时就已经做了一次&lt;strong&gt;多标签页单 ws 连接调度和通信&lt;/strong&gt;的事，但当时做的和软电话耦合度有点高，这次趁着相同诉求，再抽象了一下，单独抽离了只负责多标签页单 ws 连接调度和通信的 sdk** @alipay/aha-browserTab**
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;得：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;满足了对 IM 应用架构的好奇心，拥有了 IM 的研发经验，可以从零再快速撸一个&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;不仅最大可能性的满足产品的诉求，做的一系列的预加载、缓存优化确实有效果，也获得了产品的认可，感谢我们的产品承寅老师还给我提名了一个“看见”奖项，受宠若惊&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;fvMGr&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;去语雀&quot;&gt;去语雀&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;对语雀的感情&lt;/strong&gt;：还记得刚来蚂蚁时，用语雀就觉得比较惊艳，解决了在前公司一直没有好用的文档平台的遗憾，集合了很多功能卡片，编辑体验很好，随着后续迭代有了多人协同、强大的画板，体验也越来越好，更是爱不释手。当时就听说这个团队人数不多但是非常精华，p7\p8 浓度非常高，好奇着是怎样一个神仙团队。后来看到社区里自己关注的&lt;a href=&quot;/yanwang.lj&quot;&gt;@阎王(yanwang.lj)&lt;/a&gt;、&lt;a href=&quot;/niuyao.ny&quot;&gt;@平侠(niuyao.ny)&lt;/a&gt;、&lt;a href=&quot;/busi.hyy&quot;&gt;@不四(busi.hyy)&lt;/a&gt;、&lt;a href=&quot;/dafeng.xdf&quot;&gt;@达峰(dafeng.xdf)&lt;/a&gt;老师居然都在这里，更是心向往之，来了后接触真人也都没有人设崩塌非常的值得让人尊敬～ &lt;strong&gt;无论这一年语雀中途遇到了多少风波&lt;/strong&gt;，但能做出如此好体验的产品，解决了日常知识管理、文档协同的诉求，能成为蚂蚁和阿里的首选文档平台，并逐步探索商业化获得外部收入，&lt;strong&gt;它在我心中一直是一款值得尊重的产品&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;关于面试&lt;/strong&gt;：本来 22 年是个比较艰难的年份，不被裁员保命就已经很幸运了，听到 0hc 后更是不再关注转岗信息，没想到 8 月朋友居然告诉我语雀现在有 hc 在招人，特别意外，兴奋的没咋准备就上去自荐了，生怕被人抢先了，知道可转岗后的第三天就被面试了，还记得准备不足有点语无伦次，事后忐忑的睡不着，大周六早晨还给不四老师发了很多信息，来补充自己。还记得镜同问我“赠险的用户群体是谁”、“你做过哪些事来帮业务完成目标”，都是非常实在的问题，但之前的工作中我似乎很少从这个角度去帮业务思考问题。这次面试体验也很深，果然很多东西是需要提前准备预先演练的，去年晋升场上被反复推敲的东西很有逻辑，这次很突然的去面试表达那叫一个糟，&lt;strong&gt;向第三方介绍清楚自己做的东西的背景和关键过程的能力还是很必要的，今后要有意识的培养这方面的能力&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;所幸缘分还是让我走到了语雀，唯一有点愧疚的是对前主管，这年初我描述了我的感受和诉求，他给我带来了环境的变化，让我能更聚焦的在感兴趣的事情上，并在我忐忑的时候给予了我肯定，让我能有渐佳的工作体验，但是我却在新机会来临时选择了离开，那一阵子感觉自己就是个渣员工。。。不过后来原部门也是一系列的大调整，虽然是巧合，但如果我真的没走也是另一番镜像了，也释怀了那份愧疚。&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;wbDO0&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;转岗前的日子&quot;&gt;转岗前的日子&lt;/h3&gt;

&lt;p&gt;这是一段没有压力，享受的日子，虽然我依旧在搞 IM 这个大项目的开发，但是思想上是没有压力的。这种时期非常难得，人容易瞻前顾后，所以难以放空自己，一般只有高考过后、入职之前、转岗之前这样的交界点才能真的放空压力。&lt;/p&gt;

&lt;p&gt;这段难得的日子让我有两个感受：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;轻松零压给予的快乐是有限的&lt;/strong&gt;，当我每天没啥压力，能够睡足 8 小时，啥也不用多想就把手头的事做完，一段时间后也就麻木了，感觉不到快感了，所以&lt;strong&gt;期待退休后在家躺尸会很快乐是不可行的&lt;/strong&gt;。反而是有一点点的压力，攻克了难题，能注入一些快感，这也是一种不可缺少的快乐因素。可能上帝在造人时，为了我们的生产力创造力提升，注入了这样的限制&lt;/li&gt;
    &lt;li&gt;很多时候的过多的压力和焦虑是自己可以调节化解的，譬如同样是在密集开发阶段，平时我可能就已经在操心后面的落地数据和效果了。但本可以先安心搞完开发，再好好操心后面的事的，&lt;strong&gt;分阶段投入不同的精力，&lt;/strong&gt;就像工作时专心做事，休息时安心玩耍一样，&lt;strong&gt;过的纯粹一点，是可以主动调节适当减轻压力的&lt;/strong&gt;。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a name=&quot;fa2Nt&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;来语雀&quot;&gt;来语雀&lt;/h2&gt;

&lt;p&gt;&lt;a name=&quot;EzKqV&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;语雀的工作体验&quot;&gt;语雀的工作体验&lt;/h3&gt;

&lt;p&gt;真想好好夸一下语雀的环境，但做人要低调，我尽量从简一点～&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;工作内容与我的设想十分 match，随着工作年限增长，真的希望能进入真正的“深水区”，能够有复杂的业务场景进行深入的技术探索，持续投入和输出。越来越希望有场景做前端应用，而不是前端页面。在线文档编辑器确实是一个有足够复杂度的场景了，前端可以发挥很大的价值，越复杂的场景越容易投入获得心流体验，也更容易获得成就感。&lt;/li&gt;
    &lt;li&gt;工作内容更聚焦，不需要打一仗换一个山头，就好好持续的耕耘文档编辑器及周边就好&lt;/li&gt;
    &lt;li&gt;产品的持续投入，不做几个月就大换一波的产品，相信产品设计，好好实现他们的需求就是价值，业务目标和技术诉求高度重合&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;以上还是有点 🍋 了，我深知每条业务线都有自己的特色和背景，很多时候不是技术能改变或者选择的，大家都各有各的价值，能遇到理想的业务环境是一个选择，更是一份幸运。&lt;/p&gt;

&lt;p&gt;除了工作内容和个人诉求高度契合以外，语雀还有一些意外的惊喜：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;高度异步化的工作流，践行自己的产品观&lt;/strong&gt;。大家多是通过文档评审的评论和白雁异步沟通，再也不会有谁不管大事小事突然给你来个电话把你从沉浸的状态里强行拉出来了，甚至连钉钉消息都变少了，大家也都心照不宣的理解异步化，不会期待你的立即回复&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;产研关系的和谐&lt;/strong&gt;，无论产品方案还是上线节奏，都是可商量的&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;团队信息的高度透明化&lt;/strong&gt;，产品、设计、技术、测试、运营的信息都在一个团队下，可以看到各自最近的工作内容、okr 等等信息，不仅关注自己的工作内容，只要有心，可以很方便的知道整个语雀最近在做什么。每个月的大部门月会也是有总结有展望有分享&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;创业小规模团队的高效协作&lt;/strong&gt;，沟通障碍小，不需要费力拉通对齐&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;团队氛围异常的好&lt;/strong&gt;，每天吃完饭会一起在园区散个步或者爬个山，一批段子手，让休闲时间真的很轻松；周会大家各抒己见甚至可以互相争论；小团队爱桌游，各种烧脑，大团队有大事件后的集体爬山习俗；还有月会，各个群里密集的沟通，让氛围既轻松又有归属感&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;每次新功能上线前会发起所有人的体验建议和捉虫活动&lt;/strong&gt;，只要有精力，可以无限参与奉献。上线新功能会成为夸夸群，大家会友爱的给合作伙伴送上赞。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;同事真的很强，技术氛围很好&lt;/strong&gt;，有点啥不确定的总有人可以商量讨论，不会觉得孤军奋战。一把年纪了，还收获了一个师兄，甲哥也是个人狠话不多的厉害角色，特别感谢的是当我没 get 到一些设计的必要性时，总会很耐心的跟我讲解缘由，文档的小伙伴都是人好技术强的很棒的同事，大家对我这个新人都很照顾，主管也是超出预期的温和且令人尊重的技术范，一开始来之前对编辑器的理解都靠春哥输出的技术文章，还有那复杂的多人协同技术我至今都还没来得及深入了解。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;大家真的静得下心来做产品，搞技术&lt;/strong&gt;，从语雀那么高的单测覆盖率，从大家会剥专门的时间进行架构升级。一些改动本不能给产品带来新体验，但是可以让结构更清晰，功能更容易拓展，可维护性更强，就有人投入精力去做。&lt;strong&gt;我在这里看到了真正践行的长期主义&lt;/strong&gt;。这是一个迭代了 6 年的产品，也正是一代代开发匠心的打磨，它才能一直可拓展可维护。&lt;/li&gt;
    &lt;li&gt;经常各种会议上见玉伯 🐶&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a name=&quot;V6qgL&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;在语雀的起步&quot;&gt;在语雀的起步&lt;/h3&gt;

&lt;p&gt;来语雀之前，有点担心从没有接触过编辑器，会不会在前期很抓狂。一直想着提前熟悉下，但转岗前的一个月在密集开发 IM，终究是没有多少提前的熟悉。&lt;/p&gt;

&lt;p&gt;但真的进入编辑器领域才发现，这个领域很深，但对新人的体验也不差，毕竟并不需要徒手从零扣能力，已经积累了领域模型和一系列的框架范式，完全不会手足无措，只要一步步了解就好，之前的担心有些多余了。也感谢团队给的空间，可以没有太大压力的进入熟悉期。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;对编辑器领域的深入了解&lt;/strong&gt;：对编辑器是有很大的好奇心的，我希望自己能剥开它看见本质，能深入理解架构设计，未来有设计能力，而不是只能在特定框架下堆功能，在自己了解后，也&lt;strong&gt;要能有从零实现一款文档编辑器的能力&lt;/strong&gt;。从一开始完全不知道这个领域的问题，之前了解到的信息说编辑器要解决很多选区问题和浏览器表现不一致的处理，完全不清楚是有什么问题。通过前一个月各种读代码、修 bug，到后面重构表格提升性能、开发图册卡片，已经完全理解当前文档编辑器的 kernel、render、editor、view 层的分层设计，插件系统，能无障碍的进行比较大的新功能研发，你若问我做一个文档编辑器需要做些什么，我也能给出答案了～&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;表格性能优化&lt;/strong&gt;：这个做到一半离上线还有些距离，通过重构来提升大表格体验。也希望沉淀一套高效率的 grid 排版系统，编辑器在大内容下有一些性能问题，后面性能优化还有很多发挥空间。后面也想接管渲染层，交由框架层进行统一的 dom 读取和写入的调度，避免各个插件无序的读写导致的 reflow 性能问题。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;图册&lt;/strong&gt;：最后一个月正儿八经的接了个不小的产品需求，开发了图册卡片，和团队首页、知识库首页、个人花园页的模版，实践了一个完整的卡片开发，真的可以无障碍实现功能了。这个过程中我也就只发挥一点点的力所能及的能力，过程中及时向产品、设计提问题和建议，花了不少时间打磨还原她们想要的效果，以前在业务线因为总是工期紧张所以总是把性价比不高的效果和需求砍掉，现在我希望自己尽量不说不，只要不是不靠谱的东西，都花心思不计成本的去好好实现，和大家一起给用户最好的体验。（遗憾的是新冠的冲击发烧躺了几天，影响了进度，没能让图册在月底上线，不然大家的年终总结就可以放很多精彩的图片集了）&lt;/li&gt;
  &lt;li&gt;在熟悉期间，还&lt;strong&gt;沉淀了 8 篇关于编辑器设计和研发的文章&lt;/strong&gt;，可以作为后续开源后的文章雏形。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;总体来说，起步是稳的，达到预期的落地完成熟悉阶段了，但熟练度上跟前辈们还是有不小差距的。也有很多不足的地方：
:::warning&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;性格因素让我在再一次加入新团队时，和 3 年前相比并没有什么成长，依旧比较内向不确定想法是否正确，选择不发声，或者是有一些局促而思维锁死不知道说什么。我本可以更主动发挥更多余热的。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;我可以很快的去适应环境，融入成为一体，但是我也很容易丢失自我&lt;/strong&gt;，&lt;strong&gt;每次跳槽转岗都仿佛从零开始&lt;/strong&gt;，以前的经验似乎只能辅助我编码和定位问题，并不能在更高的设计层面融合，让我把历史的优秀经验搬过来改造执行。
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;nOvFT&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;学习&quot;&gt;学习&lt;/h2&gt;

&lt;p&gt;最欣慰的事情，是还能有学习的快感～&lt;/p&gt;

&lt;p&gt;:::success&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;补了一波 2022 的构建领域相关的信息：了解了 esbuild、swc、vite、module federation，bigfish 的 mfsu，发现前端技术真的发展快，当年我也是搞构建工具的，现在世界已经变天了。蚂蚁有统一的基建 bigfish，基本不需要自己折腾，目前还没有应用，但是自己的项目总有一些优化是可以做的。特别是来了语雀后直接用的是 webpack，我觉得明年一定有机会做一些编辑器相关的构建提速的事情。&lt;a href=&quot;https://www.yuque.com/echizen/rze0c6/ogp3mtq5pkqkfn0w?view=doc_embed&quot;&gt;构建 in 2022&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;了解了一波桌面化应用的信息，最后离开管家终究是没有实践，但是在语雀只要有精力后面可以深入接触了。&lt;a href=&quot;https://www.yuque.com/echizen/rze0c6/lm2g66?view=doc_embed&quot;&gt;桌面化应用调研与思考&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;最大的收获还是进入编辑器领域。特别是插件体系，有种当年看 webpack 源码的感觉，最开始在保险做运营平台编辑器时就想做可插拔的插件体系，用插件实现核心功能，也用插件扩展功能，但投入有限，终究是无法在 2 个月内把这一切做起来。念念不忘，必有回响，很开心在语雀编辑器里看到了理想的插件体系，编辑器虽然还有一些耦合需要优化的地方，但它大的分层框架清晰，就是一款前端应用该有的样子～
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;d4pTJ&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;一些总结---选择变化&quot;&gt;一些总结 - 选择变化&lt;/h2&gt;

&lt;p&gt;老实说，之前来蚂蚁后面对极快的节奏和高压的环境，虽然战斗力大大提升，但终究是不够快乐健康的。但这一年的逐步改变，让我重拾工作的快乐，我很看重这个，工作对我来说占比很高，我无法接受它只是一份谋生赚钱的途径，我希望自己在长期投入的东西上是快乐的满足的。&lt;/p&gt;

&lt;p&gt;有人说“&lt;strong&gt;自由意味着放弃一些东西，而能去选择自己最在乎的东西&lt;/strong&gt;”，这句话真对。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;年初我放弃了管理路线的尝试，这条路看上去似乎可以走的更远更轻松，但我着实不是那块料。&lt;/li&gt;
    &lt;li&gt;也放弃了做赠险和德鲁伊的 owner，似乎 owner 承担了更多的责任才能体现更大的价值，我似乎是在开倒车，非常忐忑的去做一个 IC，成为一个业务线下开发中的一员，&lt;strong&gt;我充满了对结果的猜测。但是过程是快乐的，更加聚焦后，我认为我是发挥了更大的价值的，做出了更多有价值的事情&lt;/strong&gt;，也在结果上获得了一定的认可。让我看到从心选择让自己快乐的事真的才是良策。同时也向所有 owner 和 1 号位致敬，前两年的 owner 经历，让我深深知道这个角色要花大量的精力在前期讨论、开会、协调资源、防控风险、汇报、规划这些事情上，真的很不容易。我选择了更“轻松”的活法，也是更适合我的方式。&lt;/li&gt;
    &lt;li&gt;在一年快结束时来到语雀，不用说也知道放弃了什么。从一个团队的核心力量，转岗到陌生领域成为小萌新，总还是有一些差距的，但是心里建设做的足够，也没啥不适应。工作更加开心了，对于编辑器领域，接触前充满好奇，接触后充满兴趣，&lt;strong&gt;我看到了满满的希望&lt;/strong&gt;。&lt;strong&gt;从来语雀开始，我就知道接下来至少一年都有的折腾不会感觉无聊了&lt;/strong&gt;～可能来的时间不是最理想的，但是面向未来是值得的。只期待语雀接下来的商业化之路顺利～&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;今年的工作状态是比较健康的&lt;strong&gt;有前期的调研，执行期的多方合作，完成后的总结输出&lt;/strong&gt;。今年写了 4 篇不同领域的深度调研，4 篇深度的技术向的项目总结（所以说我是一个十足的喜欢写的文档人）。有做一些需要很多人一起参与的大项目，也做了一些小而美一个人投入疯狂开发，但是有复杂度的强诉求的功能性项目，都挺美好的。除了个人的努力，环境对工作体验也很重要，所以过的开心时，更感谢团队提供的土壤～&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我不再有雄心壮志想要做出对行业有多大贡献的事，接受自己是个普通人，工作时和团队一起去做出体验很棒的产品就好，依附于平台也不丢人。但我依旧对自己有一些小期待，保持好奇心和一些投入，真有一天没有平台了，依旧能靠自己的技能谋生，能做自己的产品。&lt;/p&gt;

&lt;p&gt;这一年工作也有一些不如意：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;德鲁伊页面升级的高投入不理想的应用，我觉得是自己对事情的选型没做好。&lt;strong&gt;如果没有杀手锏新功能的开发，资源又不富足的情况下，真的谨慎升级，否则真的很难打动用户接入升级。&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;似乎与人打交道我真的太稚嫩，年中和一个外包同学发生了一些矛盾，被闹出让人大惊的场景，我没有过多的为自己申辩什么，还好时间自有答案；平时对合作同事要求太高，明明人家已经尽力了我还对结果不满意，给了大家太多压力；想到什么有益的事情就去做了，可能方式不是最让人舒服的，也会有我很不理解的事，有些东西超出我的复杂度；甚至 CR 这件小事，我都不知道如何说服对方接受我的修改意见，还有一些设计上别人不理解必要性时我有时会不知道怎么解释了。看上去让人无语的都是小事，似乎缺少一些必要的沟通技能。光靠真心真不行啊，很多时候我明明一片好心为别人考虑的但却不会被理解。&lt;/li&gt;
  &lt;li&gt;在语雀或多或少有点中规中矩，没有发光发热，且看渡过熟悉期后的新一年吧～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外我也能准确的知道自己价值所在，&lt;strong&gt;阿里有句话叫做“为什么非你不可”，我觉得这是一个很好的发掘个人价值的点&lt;/strong&gt;。相比于组织一群人做一件事的技术 PM，我其实更适合自己深入去做，它可以是个多人协作的项目，但自己一定要深入到核心能力建设，组织这件事我做不出大的差异，但深入到功能层，我总能做出差异化的亮点。&lt;strong&gt;我有很好节奏感&lt;/strong&gt;，事情可以放心交给我，&lt;strong&gt;只要是靠谱的总是能保证使命必达的；我有很好的风险意识和大局观&lt;/strong&gt;，能从整体看问题，不让某个环节出漏子；&lt;strong&gt;我会在具体的需求中挖出技术亮点&lt;/strong&gt;，做更多的尝试。我会看到和尝试过有些事情真的不是可以换任何人都能一样的，虽然这份自信在来语雀后的陌生环境起步期里没体现，但我知道未来它还会回来。&lt;strong&gt;相比于 3 年前的跳槽，陌生环境从头来觉得自己好像废物，现在很欣慰看到自己多了一份从容自信。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;但优点也同时也会造成缺陷：太注重结果，有时会导致过程进行的不完美，对团队同学缺少人文关怀；太注重节奏感，不能接受延期，一旦项目排期很紧，急功近利导致没有去做一些本身可以做的更好的点，总想快，而丢了好。&lt;/p&gt;

&lt;p&gt;留给日后可能还会纠结或迷茫的自己：
:::success
&lt;strong&gt;任何时候，都要怀抱有选择的希望，并有勇气做出改变和选择&lt;/strong&gt;
:::&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;taD0d&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;生活&quot;&gt;生活&lt;/h1&gt;

&lt;p&gt;这一年有一些不好的苗头，譬如很多没拆封的书，譬如后两个月逐渐捡起来去年戒掉的小视频的坏习惯，下班后累了需要换换脑子，需要探索个健康可控的放松方式。&lt;/p&gt;

&lt;p&gt;每年追香帅的课，看任泽平的经济报告，小明的日常 BB，但今年招财大牛猫的号被封了少了一个乐趣。但依旧在市场里装死，3 月后我就没有动过股票了，知道它还能再上去复苏回来，但如果我能发挥点主观能动性，应该能少损失一点吧，似乎对投资越来越提不起来兴趣了，看不懂市场，看到的都是投机和情绪。&lt;/p&gt;

&lt;p&gt;和男朋友这一年相处还比较融洽，并没有出现很多进入磨合期的争端，成年人的世界合适的沟通是至关重要的吧。不过应该也没法实现 30 岁之前把自己嫁出去的小期待了，豁达的随它吧～ 怂恿他整了个渣男锡纸烫后，我觉得颜值提升了一半，我功不可没 🐶：&lt;br /&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/006.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;tYCs6&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;出去走走&quot;&gt;出去走走&lt;/h2&gt;

&lt;p&gt;&lt;a name=&quot;JWdIa&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;周边&quot;&gt;周边&lt;/h3&gt;

&lt;p&gt;春天在樱花谷见到了心心念的漫山樱花～&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/007.jpeg&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/008.jpeg&quot; alt=&quot;image&quot; /&gt;
&lt;br /&gt;良渚公园的油菜花田，蜿蜒着小路，别有一番意境：&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/009.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;还记得清明、5.1 都被疫情封印在杭州的无奈，周边去的都不知道还能去哪了。&lt;/p&gt;

&lt;p&gt;打卡了桐庐，这个角度的新安江像不像肺～&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/010.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/011.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;去了安吉云上草原，但我这次怂的居然不敢玩高空秋千了。。。&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;FS1Tf&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;去大理&quot;&gt;去大理&lt;/h3&gt;

&lt;p&gt;一直嚷嚷着退休了要去大理开客栈的我，第二次去这个让我充满舒适的地方～可真是太爱这里的那份闲适了～&lt;br /&gt;&lt;a href=&quot;https://player.bilibili.com/player.html?bvid=BV1o3411U79y&quot;&gt;点击查看【bilibili】&lt;/a&gt;
&lt;a name=&quot;gE4aJ&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;海南&quot;&gt;海南&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;阿相团队的 outing 真的从不让人失望&lt;/strong&gt;，在去年我们熬夜肝了独库公路自驾游后，今年的海南也让人充满惊喜：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;划了丛林浆板，如果不是团队机会我可能自己永远不会体验这个项目&lt;/li&gt;
  &lt;li&gt;惊喜的&lt;strong&gt;学会了基础的冲浪&lt;/strong&gt;🏄‍♀️，我真的没有想到我居然能学会！&lt;/li&gt;
  &lt;li&gt;非常惶恐的下水浮潜，水下并不舒适，但也是很特别的体验了～&lt;/li&gt;
  &lt;li&gt;在&lt;strong&gt;三亚太阳湾环海公路见到了最美的海边&lt;/strong&gt;～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/012.jpeg&quot; alt=&quot;image&quot; /&gt;
&lt;br /&gt;&lt;a href=&quot;https://player.bilibili.com/player.html?bvid=BV1td4y177ku&quot;&gt;点击查看【bilibili】&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;oUazn&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;湘西--黔东南&quot;&gt;湘西 &amp;amp; 黔东南&lt;/h3&gt;

&lt;p&gt;这是转岗前给自己的 9 天假期，&lt;strong&gt;心无旁骛非常轻松的融入在不一样的世界中&lt;/strong&gt;～这条线路也是非常精华：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;张家界天门山的刺激震撼&lt;/li&gt;
  &lt;li&gt;武陵源悬浮柱的别致&lt;/li&gt;
  &lt;li&gt;凤凰古城满满的仙侠感&lt;/li&gt;
  &lt;li&gt;梵净山梵天净土的灵气&lt;/li&gt;
  &lt;li&gt;苗寨的万家灯火像是银河在这人间的倒影，是我的年度最佳场景&lt;/li&gt;
  &lt;li&gt;侗寨则充满侗族的生活气息，与世隔绝的小寨子，时间仿佛都不走了&lt;/li&gt;
  &lt;li&gt;堂安梯田五颜六色丑出预期 😂&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://player.bilibili.com/player.html?bvid=BV1qd4y1i7gk&quot;&gt;点击查看【bilibili】&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;oAi6P&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;生活琐事&quot;&gt;生活琐事&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;逐渐趋零的运动&lt;/strong&gt;：放弃了跳舞，真的记不住动作，我觉得我要是认真做的话得先去报个基础班。因为男朋友不爱动，之前喜欢的徒步爬山也不再有了。真真真的不行，&lt;strong&gt;新年 flag 可以都不立，但明年一定要加大运动量！！！&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;养挂的花&lt;/strong&gt;：我爱花，但依旧养啥挂啥，譬如被小区阿姨笑称把高架搬回家的绝好的月季，萎的我绝望的不忍心再养花了&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/013.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;煮茶&lt;/strong&gt;：年末买了个围炉煮茶的壶，本来想给看剧时添点乐趣，没想到到货第二天就成了新冠期间补充维生素、矿物质、水分的救命神器。年度最佳幸福感神器。&lt;/p&gt;

&lt;h2 id=&quot;新器材&quot;&gt;新器材&lt;/h2&gt;

&lt;p&gt;4 月份入手了大疆 mini3 pro，小巧轻便，从此成为旅行随行物品，fpv 就开始吃灰了，其实更喜欢 fpv 的第一视角体验感，但去的很多地方更想能记录下来，录视频还是无人机更好使。&lt;/p&gt;

&lt;p&gt;手机更新到了 iphone 14pro 后，无论官方说镜头和软件怎么更新，一拍都还是锐化的没眼看。于是后面又更新了相机到了索尼 A7M4，入手了大三元和小三元的 2 个镜头，算是大出血了。更高级的设备确实拍摄效果更好，但是这个重量再也回不到之前了，啥时候相机也能技术革新下，摆脱对这么大尺寸的物理结构的依赖。&lt;/p&gt;

&lt;p&gt;器材更新了不少，技术不见长，优质内容却没多少，记录的美好风景倒是一筐筐的。在国庆假期出去玩的路途中及时修图和剪视频发在 b 站、小红书、携程上，波澜不惊，觉得自己的很多内容吧，虽然不惊艳但也还不错，但总有那种其他人千赞数据的内容质量一般般的，&lt;strong&gt;果然做内容的博主果然是有啥流量密码，是我不知道的 🐶，掌握密码的小伙伴们来教教我，让我休闲时搞点乐子。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;nw1no&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;岁岁平安&quot;&gt;岁岁平安&lt;/h2&gt;

&lt;p&gt;今年真的不算太平。&lt;/p&gt;

&lt;p&gt;之前体检有些小问题，3 月份去做了个小手术，虽说小，但也体验了神奇的全麻体验，慌的狠。&lt;/p&gt;

&lt;p&gt;5 月份从昆明回来时，又遇到交通事故，索性只是一点皮外伤，但撞击的强大冲击力让后面很长一段时间坐车都有恐惧。&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2022/014.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;年末，又感染新冠，好多年不发烧的我，一度上了 39.7 度，再赶上姨妈加持，整个人就委靡了。阳康后还是浑身乏力，没有精神，夹杂着对老家父母感染后的担忧，一度陷入难以自拔的情绪低迷期。&lt;/p&gt;

&lt;p&gt;人生有红红火火的上升期，也会有生病这种低谷期，新冠那段时间我在自己的不舒适和网上众人的悲伤故事里沉沦了很久，开始从另一个纬度看待生活，仿佛回到了 3 年前父亲重病的那段艰难时光。“新冠面前，人人平等”，无论你是谁都免不了被感染，当药品被抢空时，无论你是谁也很难搞到药，当你躺在床上忍受病疼时，没有人可以给你缓解。&lt;strong&gt;这个时候会觉得人一生追寻的财富啊真的没啥价值，不如一个健康的体魄，以及在低谷期时有人照顾&lt;/strong&gt;。我是一个容易对别人的境况共情的人，也是一个容易想很多的人，这期间，我不断在想如果有一天生个大病或者是出了什么意外，卧床不起，我该怎么办，在这种情况下，平时的莺莺燕燕的小愉快看来都有些幼稚了，有价值的是有人能承受你的生命之重，能用靠谱的策略在危急关头救治你平安，在你恢复期悉心照顾，不生病时不可一世，生病时脆弱的只能靠别人。还有对父母深深的歉意，他们期望着我能工作的离家近一些，我期望着他们能来我的城市我好能照看到，最终在拉扯中各过各的，生病时我不能照顾到他们，感觉这种孩子养大了真的不值，&lt;strong&gt;希望后面能做出一些方案解决对老人的照顾问题&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;“同甘者众，共苦者寡”，这是人生不易，也是人情可贵的地方，人生这场修行，还是太浅了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果许愿能灵验，我真的希望是“平安喜乐”&lt;/strong&gt;，很多时候平安也靠运气。&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;RLl7B&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;新的感悟&quot;&gt;新的感悟&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;健健康康积极向上的是生活，生病倒下萎靡不振的也是生活&lt;/strong&gt;，但两种境况差距很大，年末的这段时间我的情绪在后一种境况中沉沦了太久。还是要以更客观的态度看待，&lt;strong&gt;它们是生活的上下限，不能厚此薄彼，要充分调动资源建设环境，既能保证在上限能充分享受生活，也要保证在下限能够体面尊严的安全度过并尽量舒适，所以在上升期要有危机意识，为未来多做保障措施，有备无患，在困难期怀抱对一切都会过去的信念，能看到未来的美好生活。在上升期有危机意识，为未来多做保障措施，有备无患，在困难期怀抱对一切都会过去的信念，能看到未来的美好生活。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;跟 peace&amp;amp;love 的状态越来越接近，越来越明白，生活不是竞技场，不是要比别人好，从小的教育让我们习惯了比较和冲刺，但真相是每个人终其一生都是在找寻那份能让自己舒适可持续的状态，而每个人理想的都不一样，有人能在不断挑战自己中获得快感，有人能在普通平淡中获得闲适。哪种是自己想要的，是需要每个人去体验探索的，于我，做一些有意义的事能发挥价值就挺不错，有精力就再完善多做一些，累了就节奏慢点，并不是躺平休闲的生活才快乐，无限加强的状态也会逼的自己太累，只希望自己能够在任何时候都有选择的能力，可以自由的调节节奏，做自己感兴趣的事。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;年近三十，终于接受自己不是一个英雄人物，不是天选之子，一个平凡人，最重要的是活出自己的节奏，让自己舒适可持续的状态，标准都是自己定的。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2021-采菊东篱</title>
   <link href="http://echizen.github.io/think/2022/01-01-summary"/>
   <updated>2022-01-01T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2022/summary</id>
   <content type="html">
&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/001.png&quot; alt=&quot;image&quot; /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;因为对语雀的逐渐喜爱和信任，开始把年终总结也搬运一份到&lt;a href=&quot;https://www.yuque.com/echizen/xgfvrp/aczn61&quot;&gt;语雀&lt;/a&gt;。 - 第 7 年年终总结。&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;一直是个春节才静静在家写年终总结的人，今年想给团队篇凑个数，元旦就来赶集了。相对于内网版，跟公司内部相关的东西都脱敏了。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;今年的年终总结，我希望它不是 PR 文，而是对自己真实的回顾，是 30 年后依旧能拿出来的读的内容。不间断写了 7 年年终总结了，它可能会成为我的一个终身计划的形式，我在想，究竟该留下点什么，无非是:&lt;/p&gt;

&lt;p&gt;:::info
和这个世界的交互中，发生了些什么，明白了些什么，以及，对未来的期待。在这一年又一年流逝的时光里，自己发生了怎样的成长变化。
:::
​&lt;/p&gt;

&lt;p&gt;所以你打开的不仅是我的认真工作，更是我的精彩生活～
&lt;a name=&quot;SQYal&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;️-晋升&quot;&gt;🧘🏻‍♀️ 晋升&lt;/h1&gt;

&lt;p&gt;&lt;br /&gt;这里我不说怎么晋升的，好多大佬写过，我没什么更好的想法。我更想聊聊是晋升对我的改变。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;我无法淡然的说“晋升”这事是水到渠成的事，没什么重要的。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;它对我很重要。无关层级，无关薪酬，而是&lt;strong&gt;心态的改变&lt;/strong&gt;。一直有种声音在耳旁：“3 年 6，5 年 7”，这第 5 年我要是到不了 7，我就落伍了，曾经很自信自己是一名很棒的工程师，无法接受自己成长缓慢落后平均水平。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;所以过去来蚂蚁的 2 年里，毫不夸张的说，我为了晋升付出了很多努力，找不到好的方向和捷径时，也会硬肝。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;所以晋升之后，我如释重负，似乎是拿到了对自己能力的一份许可。还是不矫情的说，它没有带来兴奋或者喜悦，带来的是轻松。我感觉之前自己会有点病态，从走第一步时就一定要走到山顶拿到结果，无法面对任何放弃型行为。&lt;strong&gt;但是真实的世界不是这样的，会有探索和尝试，会发现什么方法是不合适的&lt;/strong&gt;，&lt;strong&gt;什么东西是伪需求，探索性事物，失败和放弃是常事。一个好的工程师，除了使命必达的配合业务完成目标，还应该有更多的探索和尝试，去为这个行业真的做出一些贡献。&lt;/strong&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;所以晋升之后，应该有同事看到我慢下来了，不知道有没有怀疑过我想佛了
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/002.png&quot; alt=&quot;image&quot; /&gt;。但是我在这个慢下来的过程中，去做了更多的调研，尝试去发现，我给了自己的底线是“不急于求成，做有价值的事情，即使当前没找到好的方向，也要持续尝试”。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;所以我并没有像去年对自己要求的那样，更狼性。而是往 peace&amp;amp;love 方向发展了，或许这个风格不太适合蚂蚁这么激烈竞争的环境，但是每个人都是有差异的，我对自己的要求是去做内心坚定对而重要的事就够了。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;不会止步于晋升到 7，但我的目标不会是晋升到 8。我的目标，是在程序员的职业生涯里，有创造性的开发，有影响力的技术产品，对这个行业有所贡献。&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;EcLLe&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;变迁&quot;&gt;变迁&lt;/h1&gt;

&lt;p&gt;&lt;a name=&quot;wHje1&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;-结束智能-ui-的探索旅程&quot;&gt;🏊 结束智能 UI 的探索旅程&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;记得去年年末做 21 年规划时，还在搞智能 UI 的我画了一张建设的大图。&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;非常惭愧的说我并没有把这条路走下去，中道崩殂了，虽然理论上我依旧觉得这个东西做透是应该这样做下去。但是过程中因为种种原因走不下去了。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;但也不是完全没有尝试和收获，譬如年初来公司的第一周，没有啥业务需求，我把数据自动化链路搞通了，从此可以自动化出数据报表，而我也在这个实践中，真正的拥有了基础的数据开发能力，这是我今年非常开心的一个高光时刻，记得那时我在月报中写道：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;学会了洗数据做报表这个喊了很久的事情，&lt;strong&gt;把不会的学会了可以极大的增加快乐感。如果接下来我不快乐了，我想我得反思是不是又没学习了。然后技术最好的学习方式真的是动手实践，看几天的学习都不如半小时的实操来的快速。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;譬如工程化出身，复用思维深深的根植在我心里，搞前端智能化方向的时候，我总想着要怎么沉淀一些公用规律，来达到场景间的复用，降低使用成本，和减少训练过程。但是进去后才明白，&lt;strong&gt;算法领域，效能和效果很难平衡，很好的提升效果背后免不了针对特定的场景数据的训练过程，毕竟没有两个场景的数据是真的契合的，毕竟机器学习是机器在大量的历史数据里训练模型&lt;/strong&gt;。从算法同学那边我也获得了两点与自己原本的猜测不符的事：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;人群的局限，一旦归因到业务人群，其实业务人群覆盖率很少，遇到大量未覆盖人群时，偏好便空白了。&lt;/li&gt;
  &lt;li&gt;算法同学的经验来说，跨投放场景之间的模型很难复用还有好的效果。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;在模型的方面，我做了一些尝试，基于智能 UI 的真实场景，利用 deep&amp;amp;wide 模型跑通了一个离线深度学习模型，过程中自行清晰特征宽表，利用 arks 特征提取库进行编码式的训练数据特征提取，深度学习模型训练，模型评估及优化，这个过程中，我个人对深度学习有了进一步的认知，具备了基本的离线模型训练能力。但是并没有落到应用，原因也很简单，因为我加入了业务人群特征的训练效果并不比通用模型好。。。原因是啥就难以获得可信性解释了，可能真的是算法同学说的业务人群覆盖率少吧。get 到了曾经有个同学抱怨的做算法很无力，因为结果很难掌握，也很难白盒化解释，你不能拍着胸脯说我算法推荐一定能提升 30%。算法模型训练可以说对于我是个非常陌生的领域，遇到很多奇奇怪怪的问题，靠着技术敏感度，竟然都一一解决了，&lt;strong&gt;技术有工种区分，但是思维方式终有相似之处&lt;/strong&gt;～&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;然后在归因分析这块，团队里有个同学做了很多种方法的尝试，最终也没有寻找到能够说服我们自己的 UI 维度的业务特征数据，一方面是我们的接入场景时个位数，还都是不同业务线，分析出来差异性真的挺大，如果需要真的找保险业务人群的 UI 特征，得需要更多丰富的数据，但是智能 UI 的应用成本真的太高了，场景很难拓展。另一方面，在点击问题上，点击的正样本数据真的占比太少了，给数据分析带来了很大关系。总之，尝试了几种方法后，都没有发现什么规律后，我们终止了。在此期间，也感谢这位同学&lt;strong&gt;对数据和结果的严谨态度，没有发现规律就是实事求是的没有发现，不因为投入了大量精力，就美化出一个结果&lt;/strong&gt;。这也是我第一次直面并接受大量探索性投入却拿不到结果的挫折。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;当归因分析没有获得突破时，自然沉淀就没有了，这个环也闭不了，就断了。我不确定是否特定人群真的有特定的 UI 偏好，且能被分析出来，之前也只是基于这个假设去做尝试，一个东西没有人做出来，极小概率是别人没想到从这个角度去探索，更大的概率是做这个的前人都弃坑了。之前跟几个其他 BU 做智能 UI 的同学聊，好几个都有在做探索，似乎到现在也都没有谁有突破性进展？&lt;strong&gt;但如果智能 UI 不能归因分析出业务人群的 UI 偏好，这个关键点不能突破，每个特定场景都还要算法同学进去优化，小流量场景不能使用，那在效能和应用场景上都限制太多，我不认为这是一个有价值进一步坚持的方向了。&lt;/strong&gt;&lt;br /&gt;&lt;strong&gt;​&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当时的心路感想：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这些事情不是没人想过的蓝海，没做是权衡后的决策，&lt;strong&gt;规划一件事不妨去和前辈们聊聊，看自己是否真的能解决前人没搞定阻塞性的问题&lt;/strong&gt;，很现实的点在于，不是建设一个没有的有需求的新能力就行，&lt;strong&gt;如果投入资源够多，前期是一定要验证可行性和价值的&lt;/strong&gt;。另外，&lt;strong&gt;做事情一直缺了个环节，就是前期的低成本小范围的实验，有了一定的数据支撑，再去决策做什么，才会有说服力&lt;/strong&gt;，而不是一开始规划一个大项目，然后被各种不确定性问的毫无底气。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;另外，在做业务时，配合算法同学进行了所有主流量投放展位的产品的算法推荐，提升率动不动就 40%+，所以可以理解业务为啥不买智能 UI 的帐了吧，产品推荐接入算法，业务投入是 0，回报 40%+，UI 推荐接入算法，要一大群素材准备，算法入驻调优后也就 10%的 CTR 提升。我个人的看法是：&lt;strong&gt;除非是业务合作渠道都铺尽了，没啥新增的流量来源了，产品算法推荐也覆盖完了，没有任何手段了，才是接入智能 UI 比较合适的时机吧。希望还在做智能 UI 的同学看看这个真实 case，前端同学拓展能力边界，参与智能化探索这个动力是没错的，但是要说智能 UI 能大幅提升业务增长，真的还没到那个地步。但是如果成本足够低，业务无感接入，那效果要求就没那么高，但这个成本低的前提就是要有可复用的素材和经验，很大一部分靠归因分析。&lt;/strong&gt;&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;至此，我介绍了我这半年智能 UI 的探索历程：&lt;br /&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;:::warning&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;大闭环的规划&lt;/li&gt;
  &lt;li&gt;数据自动化任务建立&lt;/li&gt;
  &lt;li&gt;工程化方向，接入 D2C 模版，进一步提升 UI 视图生产效率和准确性&lt;/li&gt;
  &lt;li&gt;自己进行业务模型训练，加入业务人群特征，数据并没有比通用模型好&lt;/li&gt;
  &lt;li&gt;归因分析，未发现任何可沉淀的，有说服力的，人群的 UI 偏好&lt;/li&gt;
  &lt;li&gt;智能 UI 场景可归因分析数据不足，想拉日常投放数据进入归因分析，无论是业务所在的算法团队，还是横向支持智能 UI 的算法同学，大家对这个方式存有质疑，没有拉到合作&lt;/li&gt;
  &lt;li&gt;通用模型的应用数据不佳，分析原因后，所在 BU 合适的场景真的不多，推广落地也停止了
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;这是一段艰难的探索，也取得了一定的效果和价值收获，但最终没有深入到理想的地步。&lt;strong&gt;失败并不可怕，至少曾经尝试过。只是希望以后能更客观的评估环境和资源，不要对能达到的境界盲目自信。&lt;/strong&gt;&lt;br /&gt;​&lt;br /&gt;
&lt;a name=&quot;litHA&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;️-迷雾中寻找新的方向&quot;&gt;🚣🏻‍♀️ 迷雾中寻找新的方向&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;前半年在智能 UI 上还是投入了大量精力。直到 7 月份确定这个方向不再继续了，然后就进入了漫长的调研和焦虑期。长达两个月。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;当时团队里还有 3 个比较优秀的同学，相比于自己，我更忧虑他们，如果不做智能 UI，我得给大家找到新的方向，时间不等人，所以这段时间，我真的是非常非常焦虑。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;我站在前端维度，从保险业务需求，和团队现状出发，做了一个整体的《新方向挖掘》。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;在前端智能化方向，我已经想的比较清楚了，在自己训练模型时我怀疑自己在干什么，这是一个前端该干的事么，前端真的去训练模型了，相对于算法优势是什么，仅仅因为缺算法同学支持？虽然当时我曾尝试用这样的观点说服自己：“只是想做产品，技术是工具，现在我的技术不应该分工种，需要什么缺什么我就去做什么”。但是人终究精力是有限的，也是有所擅长和偏好的，前面这种想法是种硬肝。在前端智能化方向，核心能力是算法能力，我认可的模式是：&lt;br /&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;:::success&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果是自己去做算法相关的内容，优势在哪里，我目前认同下面两个方向：
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;用 AI 能力解决涉及前端领域的问题&lt;/strong&gt;，譬如自动化测试、视觉还原度校验、D2C，这个自己解决自己领域的问题，从头撸到尾，因为前端对这个领域是最熟悉的，还是有优势的&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;端智能及背后工程体系建设，&lt;/strong&gt;这个也是因为端上是前端的大本营，我们是有优势的。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;有靠谱的算法团队同学一起共建的项目，且有业务诉求&lt;/strong&gt;，我们更多的是工程上的串联。
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;其他的如果说前端自己去训练算法模型来应用，我认为这是一个走偏了的方向，如何确保自己的投入（毕竟日常工作中还有大量前端工作，算法和前端领域差异还是蛮大的），如何确保做的比算法同学好，如何获得业务方信任，都是问题，这个模式我自己就曾走偏过。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;挖了一圈后，我并没有什么找到什么新的让人眼前一亮的方向。但是我有了新的感悟：&lt;br /&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;专注于某一领域的问题，针对特定场景提供产品化解决方案，也是有价值的。看着 bakery 一路走过来也深有感触，当初谁也不知道一个为了解决验收问题平台现在能长这么大，甚至成为基础能力提供方，开放 api 供其他系统借用，&lt;strong&gt;产品是磨出来的，路是走出来的，而对做产品的人要求不畏艰难寂寞，独具匠心&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;还有我们组之前脑爆会曾经提过通过智能分析，在用户进入页面时，给用户一些主动推送。最后不太笃定效果，且有团队也在探索，就没有开始想先看看他们做的效果，现在其他团队把“主动服务”做的这么好，着实给我上了一课，&lt;strong&gt;一个 idea，前期是快试牛刀的最小 MVP 版本的实验，一旦验证了价值，就要去快速投入，会遇到很多问题，死磕到底总有解&lt;/strong&gt;。还是那句，产品是一步步做出来的。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;最后在做智能和业务需求中，我觉察到我们的数据做的还不够好，还有很多数据看不到，得需要 BI 同学介入洗报表，还有很大比例的数据非常不直观导致看数效率低下，譬如跟页面元素相关的曝点数据可视化，还有用户行为动线分析，这块其实对交互同学和产品同学都很重要，但是却一直没有人做（可能现在是不是关注用户体验做体验洞察的好时机，我还真不笃定）。所以我觉得数据可视化是一块于前端来说，有技术挑战，涉及核心能力的领域，也是一块有价值的领域，满足我筛选方向的“&lt;strong&gt;一定要有价值&lt;/strong&gt;，业务增长 or 技术大幅度提效 &amp;amp;&amp;amp; &lt;strong&gt;一定要有前端技术发挥的空间&lt;/strong&gt;”的原则，被我定为第一个着手去做的新方向，而这之前先要把可视化能力建设起来，新的方向起航了。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;后面过程中，我还有很多对产品的思考，我反思到自己之前因为比较急于求成，很多事情没有做透。&lt;strong&gt;一款产品成败可以看两方面：&lt;/strong&gt;&lt;br /&gt;&lt;strong&gt;​&lt;/strong&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;:::info&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;它是否是蓝海领域，有着强诉求，没有竞争对手&lt;/strong&gt;。这是我一直想找寻的方向，其实也是找到了比较容易成功的方向。但是现在各领域真的比较成熟了，找到荒地太难了。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;它是否有着明显的竞争优势，除了体验好，是否解决了关键问题和矛盾，比竞争对手做的彻底透彻。&lt;/strong&gt;不是不能卷，只是不要做没有深度的重复性卷，如果能卷出新深度、新能力，已有的领域也不是不能去做。
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;你以为不是重点的东西，往往做好了能成为产品很大的亮点，每个地方都好用实用，才会有更多人用。就感觉自己之前还是太急太想抓重点了，譬如随大流需要蹭智能化，很多时候很迷茫，但我看到别人踏踏实实的一点一滴的把产品做深入，获得广泛应用，验证价值，我才醒悟&lt;strong&gt;方向不仅在于找蓝海，也可以是从身边出发的真实诉求出发，去把事情做深入，做到极致&lt;/strong&gt;。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;ocSUn&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;️-可视化与数据化&quot;&gt;🧗‍♀️ 可视化与数据化&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;这个我就不多说了，我在内部写了不少规划和总结说明这个。&lt;br /&gt;
&lt;br /&gt;做预览时，我就是抱着解决身边真实问题的视角出发，跟遇到的问题死磕到底，从一开始就重视体验，并且保证长期维护。再也不会有先小步快跑让线上 run 起来，然后一堆优化的 todolist 总想着留着后面做，但是后面总会有新的事情把优化的优先级顶下去，最后做是做了，但总是差点味道。如果不是心态的改变，我不会选择去做预览，毕竟有了模拟器的能力下，预览看上去就只是封装了个 sdk，虽然背后要建设的其实很多，但这事听上去就觉得很 mini。最后投产后，得到使用方真切的正面反馈时，那个感觉真的太棒了，它是做产品的原动力～&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;数据这块非常魔幻的就是，&lt;strong&gt;大家都觉得数据很重要，是智能化的基础，而我们沉淀了这么多数据，究竟在干什么，也不清楚&lt;/strong&gt;，一线业务开发反正除了看报表里的业务 kpi 数据外，其他也很少从数据里获得收获和灵感。就感觉&lt;strong&gt;数据的价值还是没被发挥出来，但是他的价值到底是什么，我们应该怎么去发掘它，有说不透彻，数据不做出来，肯定猜不出，也许做出来了，就能看出问题，但是谁也不笃定一定能看出什么，这个不确定的价值让大家都不太敢动&lt;/strong&gt;。&lt;br /&gt;​&lt;br /&gt;
&lt;a name=&quot;JCYIu&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;️-对自己的认知&quot;&gt;✉️ 对自己的认知&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;来蚂蚁也快 3 年了，这 3 年毫无疑问，战斗力大幅增强，有并发 8 件事的时候，有日肝 2000 行代码的时候，有连轴 28 天的时候。虽然我觉得这都没啥可骄傲的，我一点也不想这样，这样一点也不健康。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;以前毕业的 2 年多我一直在做工程化和平台化建设方面的内容，技术成长很多，每天的主业都是技术项目，总是学到很多新知识，用技术的新方法解决很多问题，那段时间快乐又有成就感。直到前公司发展不行了，业务停滞了，带来的也是技术没啥可挖的了，因此我来寻找更大的平台，从基础技术团队转到业务团队，还记得当初的初心是：&lt;strong&gt;在业务的高速发展里，发掘技术能做的事，发挥技术更大的价值&lt;/strong&gt;。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;但是不得不承认，这近 3 年的体验里，我发现在业务的敏感度上我始终是有欠缺的，我很难从业务中挖掘到技术点，我总是遗憾的发现 90%的业务都没啥技术含量，更多的是对综合能力的需要，我很难寻找到可以给业务带来增量的技术点。但是在研发链路偏工程化的领域我始终都更敏感和热情，我不知道这一切为什么会是这样，可能敏感性方面也是有个体差异的？很难培养？&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;以后的路怎么走呢？我想还是活的快乐且有价值点，不去补短板，而是去拓宽长板。&lt;br /&gt;​&lt;br /&gt;
&lt;a name=&quot;ZgsUc&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;-研发数据&quot;&gt;📈 研发数据&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/003.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;代码活跃度分布可以看到，5、7、8 月份都是比较少的，78 月就是那段迷茫找方向的阶段，没怎么写代码，心里非常慌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;但年终数据拉开看&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/004.png&quot; alt=&quot;image&quot; /&gt;，在团队里，1 年 10w+行，我的代码量依旧第一。。。这不是牛逼的见证，但着实是强度还比较大的见证。我一直希望可以多思考，多写有意义的有质量的代码，不过这已经比刚来蚂蚁半年 30 万的吓人数据好很多了～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;代码 CR 数据可以看出，1227 个，这一年我真的给团队同学看了不少代码，提了不少问题哇。其中伴随着很多比较抓狂想摔电脑的时刻。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;让人欣喜的改变数据，是沉淀方面明显较往年有更多的成长，技术影响力排名前三。一直觉得自己比较喜欢记录，直到团队有非常 6 的三位大佬，才觉得自己的内容质量和密度都有很大欠缺。文字的魅力很强，看到大佬们的文章，都会很有收获。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/005.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;当我收到正面反馈的时候，发现文字不仅记录了我自己的生活和感悟，也能给别人带来收获或是思考，这个感觉很美妙～&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/006.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/007.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;最后发现我最在意的是技术产品，结果蚂蚁内源社区里，我这两个存在都还是边缘人士。。。希望自己早日能在这里有自己主 owner 的技术作品，更希望以后不仅用文章说话，更用代码和作品说话。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在蚂蚁大学的学习，今年上半年在里面学了 27 个课程，大多数都是数据和算法部分的，这个真的要感谢蚂蚁的环境，如果不是来这里，我应该不会接触到这些，没有我们丰富的数据，在一些小公司根本无法研究这些。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;NeDeo&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;-方向tl-or-ic&quot;&gt;🌓 方向：TL or IC&lt;/h1&gt;

&lt;p&gt;&lt;br /&gt;记得主管第一次问我是想当虚线 TL 还是独立 IC 时，我一时呆滞，之前从来没想过我要去当虚线 TL，毕竟我是这么不善于与人打交道。但是我又充满好奇心，总是想体验新事物，有这个机会，我就很想尝试一下，但内心有很多的矛盾，觉得自己背叛了去年说的一切以技术为第一导向的宗旨，于是就有了很多不一样的磨练。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;从一开始我觉得每个人都对自己负责，我为什么要对别人的绩效负责。到后来不做智能 UI 重新换方向时焦虑到失眠，觉得自己阻塞了也就荒废了自己，但是阻塞了别人的发展，这个杠杆效应让我不能接受。我记得刚开始我跟朋友说不适应这些，我说没有真的去爱每一个同学，所以做不到尽心尽力，我朋友说虽然她也没有相关经验，但是管理肯定不是靠这种走心的模式，推荐我去理论学习一下。然后我听了 1 个多月的管理方面的书籍。当然日常工作中也在去摸索，&lt;strong&gt;最后我觉得还是要走心，不是说要有多爱每一个同学，而是去发现他们的优点，真的认可他们，然后也站在他们的角度多思考，这样自己不会割裂，而且确实用心去看真的每个人都有很可爱优秀的一面。&lt;/strong&gt;&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;世事难料，当 2 个月后，我终于规划好要做什么事情时，迎来了团队调整。。。然后规划的内容，我变成了单兵作战，从有优秀的同学不知道干什么，到有好多想干的但一个人没法很快搞完，2 个极端的切换。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;接下来就到了很艰难的时刻了，具体的不多说了，忙碌的业务需求来了，人员又遇到很难的 case，整个过程中我尽力了，偶尔也会濒临崩溃，大多数时候我都是自己默默解决问题，但这 2 个月里我记得我找了主管求助了好几轮。外包同学和比较极端 case 的新人，真的比较折腾人，让我这个在这个领域的新人有点 PTSD 了。看到其他同学带着一群优秀的同学们做出很棒的东西，颇有建设江山之感，而我还在带大家头疼的赶着不延期的完成业务需求，颇有一种温饱都没解决的感觉。整个人都很沮丧受挫，不过后来想想，真的 case 也比较极端，把我换个人不一定能有什么区别，不能因为这个事情就否定自己。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;合作伙伴方面有培养团队的一个同学到 W5 的层级的目标，但是前 2 个月引导后，后面有点放羊了，这个事情我一直有点后悔自己没有尽力，这个同学认真负责且优秀靠谱，经常被产品、服务端、测试等合作方同学问能不能让其转正，后续希望还有机会，能为他做些更好的引导。&lt;br /&gt;
&lt;br /&gt;不过最后我还是终止了虚线 TL 的短暂尝试，选择继续 IC。人生在世么，还是要开心一点，做自己喜欢的事情，明显学习、做产品更能让我开心快乐，那 2 个月不怎么写代码一直找方向的日子过的太虚了。我也更擅长这个，无论我怎么尝试，我始终不太善于沟通，看我能写这么多内心戏很多，但是经常开会时面对面时一言不发。&lt;strong&gt;如果不敢涉足自己不擅长的领域，那是我不能接受的胆怯，但是如果尝试了不喜欢或者不适合，那我觉得就去选择喜欢的适合的事情&lt;/strong&gt;。现在的我确实还不太适合做管理，继续好好做几年一线开发吧～&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;这个短暂的体验也让我深深感受到 TL 们的不容易，虽然看着他们不做需求，自由的学习充电开发，过的很开心的样子，但是每个好的 TL 心里都装着团队的人，脑子里都是对每个人的责任，这份压力还是很大很大的。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;有尝试就有思考，浅薄的说一下我对一个好的 TL 的认识：&lt;br /&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;:::success&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如同《逆商》里说的：&lt;strong&gt;“每个扎营者心里都住着攀登者，关键是怎么唤醒它”，&lt;/strong&gt;所以&lt;strong&gt;管理的魅力，是去唤醒每个员工心里住着的的攀登者，让每个人成为更好的自己。&lt;/strong&gt;（这个更多是心态和意识层面，我觉得真心为每个人好是我唯一做到的）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;高屋建瓴的前瞻和规划能力，能带大家一起把蛋糕做大，产生增量价值。&lt;/strong&gt;（目前不太够，但我觉得我能慢慢培养好这个能力）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;能帮助团队同学寻找合作资源，譬如跨团队、跨工种的合作方，一起将事情做起来。&lt;/strong&gt;（这个能力我几乎没有，每次找资源时都有充分准备，但我就是打动不了对方。。。以致于总是想我能不能自己都干了。。。）
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;生活中总有被击中灵魂的一刻，当烧鹅在保险讲武堂给我们分享一个技术人的经历和 Oasis 的发展时，说过“&lt;strong&gt;一个技术人很重要的是有自己的技术产品&lt;/strong&gt;”，这句话击中了我的内心，我觉得再这样下去，&lt;strong&gt;如果到我离开前端的那一刻，都没有做出什么有价值的历久弥香的技术产品，该是多么的遗憾&lt;/strong&gt;。很钦佩的人曾说过“&lt;strong&gt;始终相信，只要坚持去做，一个人终会活成他理想的样子&lt;/strong&gt;”，我内心的向往是他们这个样子的，致敬的同时，也希望自己一步步向这个方向走。无论现状怎样，内心的期待不可以丢。&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;:::info
所以，我对自己的方向要求是，职业生涯里，要做出有价值有影响力经得起时间检验的技术产品，有技术代表作。为了这个目标，持之以恒的发掘、探索、尝试、输出。
:::
​&lt;br /&gt;
&lt;a name=&quot;rC6L5&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;-灵魂问题&quot;&gt;✨ 灵魂问题&lt;/h1&gt;

&lt;p&gt;&lt;br /&gt;今年我依旧有追寻那个灵魂问题：活着是为了什么？我想怎样的生活？当我临终时，我回忆着怎样的一生，才算不虚此行。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;一年又一年，答案渐渐清晰。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;我划分出生命中三个重要的部分：&lt;br /&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;:::success&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;旅行：它是与世界广度交互的过程，会让我觉得时间被放慢，有长度有质量，会让我感受到世界的真实多一份，也让我对世间万物的热爱多一份。&lt;/li&gt;
  &lt;li&gt;读书：我只能体验一份人生，剩下的就从书中获得吧，书籍让人打开思维的广度，可以跟不同时空、领域的人建立联系。&lt;/li&gt;
  &lt;li&gt;科学与艺术：这是人类创造的魅力，技术让我着迷，艺术让我沉醉，希望在人类文明的长河里，多体验，能也有创造～
:::
​&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;创建了一个知识库：《&lt;a href=&quot;https://www.yuque.com/echizen/xgfvrp&quot;&gt;亦池的人间体验志&lt;/a&gt;》，虽然目前内容贼少，以后也不一定是这个语雀知识库，希望用一生去记录。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我对生活的核心定义是【体验】，希望体验经历尽可能多的东西，希望老了的时候，我丰富的活过，经历过，热爱着，无悔了&lt;/strong&gt;。当然我希望体验的多了，&lt;strong&gt;但也不能都浅尝辄止，能有一个领域足够深入。&lt;/strong&gt;&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;有人问过我，&lt;strong&gt;你有没有持续的在投入一件事情，它是你的核心，让你能不计较一时的得意失意，能踏实的向目标迈进。&lt;/strong&gt;那天晚上我失眠了很久，这个答案我还没有找到，也是需要持续寻找的。这个问题，它应该有答案。&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;U0gKQ&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;丰富的生活&quot;&gt;丰富的生活&lt;/h1&gt;

&lt;p&gt;&lt;a name=&quot;zxfQ8&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;-学习&quot;&gt;📓 学习&lt;/h2&gt;

&lt;p&gt;&lt;a name=&quot;GFeSy&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;读书&quot;&gt;读书&lt;/h3&gt;

&lt;p&gt;&lt;br /&gt;从《价值》开始，想用读书的习惯戒掉刷抖音的习惯，真的戒掉了！后面为了填充碎片化时间，开始听书，然后就打开新世界的大门了～&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;读过的书：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;《价值》&lt;/li&gt;
  &lt;li&gt;《影响力》&lt;/li&gt;
  &lt;li&gt;《社会心理学》&lt;/li&gt;
  &lt;li&gt;《穷查理宝典》&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;主要学习的课程：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;《香帅中国财富报告》（2020-2021）&lt;/li&gt;
  &lt;li&gt;《香帅中国财富报告》（2021-2022）&lt;/li&gt;
  &lt;li&gt;《香帅的北大金融学课》&lt;/li&gt;
  &lt;li&gt;《薛兆丰的经济学课》（ing）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/008.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/009.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;得到学习了 16 门课程&lt;/li&gt;
  &lt;li&gt;樊登读书的半年里，听了 179 本书，基本日均一本。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;今年读书对我影响最深的两点：&lt;br /&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;:::success&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;见识到时间的价值，用长期主义的眼光，面对很多抉择时会不一样，能更沉静有力不浮躁。譬如考虑到活到 100 岁，我就会注意身体健康，有意识的锻炼身体。想到 5 年后，10 年后的自己，我就不会急功近利，而是选择对未来更有价值的事情。&lt;/li&gt;
  &lt;li&gt;拥有成长型心态。这个真的非常非常重要，它让人不害怕失败，不害怕别人的眼光，更勇于挑战自己，因为我们的目标是成长，过程都是探索和学习，更坦然更积极去经历。没有面子骄傲，没有失败受挫，没有“不耻下问”，有的是对真相的探索，对能力的获得。
:::&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;​&lt;br /&gt;
&lt;a name=&quot;Y4T9X&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;投资&quot;&gt;投资&lt;/h3&gt;

&lt;p&gt;&lt;br /&gt;业余时间大部分都在学习经济学、金融学知识，倒不是多有吸引力，只是觉得是生存技能，理论确实学到了不少。&lt;br /&gt;
&lt;br /&gt;但众所周知，今年大行情很惨淡，特别“中丐股”的忠实股民，理论和实践的距离还是很大的，听了这么多课，依旧不能坚定的选方向，看未来，依旧被市场情绪所牵动，想静下来不瞎操作，只能靠装死。也越来越疑惑，我们真的有价值投资么，还大多数都只是在投资情绪。最后看不懂不操作了两个月。这条路还得继续学习和实践。&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;xyBiV&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;-旅行&quot;&gt;🏔 旅行&lt;/h2&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;2021 啊，我真是把前两年在旅行上落下来的行程都补上了～&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;来康康，我都去过哪些地方：&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;有一直在 todolist 里的大目标地，没想到我居然一年内成就了 3 个：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;西安
    &lt;ul&gt;
      &lt;li&gt;华山：真的是又险又壮丽，第一次遇到我也不敢爬的路，去前猛如虎，去后战五渣&lt;/li&gt;
      &lt;li&gt;大唐不夜城：这个灯火辉煌的真的美爆了&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/010.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/011.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;独库公路：十里不同天，真的是一道不长但精湛的路线，还是和团队一起 outing 去的，不得不说，我们团队真的是一个很会玩的团队，被十里画廊和巴音布鲁克的草原感动到，大爱新疆错落有致的雪山+草原景观，也大爱新疆的美食。回来后，经常还是会走神进入到笔直的独库公路上，两旁是青青绿草，远处是蔚蓝天空，极度解压～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/012.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/013.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/014.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;川藏线：想去了 8 年了，终于去了！一个长达 8 天的假期，拉了 4 个很棒的同伴组织了这场旅行，在美景画廊里尽情沉醉，圆满了～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/015.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/016.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;一些小地方也去过不少：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;春天人少花多的四歌樱花园，空中看，是一团团毛绒绒的樱花哇。&lt;/li&gt;
  &lt;li&gt;slow life 的花鸟岛，夜晚看白色的海浪拍打着窗前的岩石，远处是灯塔的灯光在旋转，还有阳光下波光粼粼的海面让我第一次发现大海的美～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/017.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;还有周边的一些小地方，如富春江、径山、莫干山、建德大慈岩悬空寺、千岛湖&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/018.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/019.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;​&lt;br /&gt;
&lt;a name=&quot;Ddl15&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;-新体验&quot;&gt;🪂 新体验&lt;/h2&gt;

&lt;p&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2 次玻璃栈道，内心毫无波澜。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/020.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/021.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2 次滑翔伞，在富阳体验的，感受就是还不够刺激，后来在羊湖看到了滑翔伞，真的是心痒痒，朋友们以后要去玩这个，去更美的地方吧&lt;/li&gt;
  &lt;li&gt;1 次跳伞，这个真的是相当刺激，3500 米的空中一跃而下，空中待了 10 多分钟，特别爽快～&lt;/li&gt;
  &lt;li&gt;爬华山，包括几个小时的夜爬，两只腿走完了北、东、南、西峰，累到瘫&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/022.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;很喜欢的一个风格的摄影朋友执掌的一次非常美的古装户外拍摄，特别满足&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/024.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/023.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/025.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一次奢靡的黑珍珠餐厅里安，体验结果是再也不去了，性价比不高，还太费钱了。。。&lt;/li&gt;
  &lt;li&gt;话剧、音乐剧的现场体验&lt;/li&gt;
  &lt;li&gt;太阳马戏团的《X 秀》，太酷了！&lt;/li&gt;
  &lt;li&gt;开始学舞蹈，虽然跳的很渣，但是这项运动真的是太有趣了～&lt;/li&gt;
  &lt;li&gt;买了大疆 FPV，虽然拍视频不太稳，但第一人称视角的体验简直太棒了，带着它短短的时间内飞过草原、雪山、峡谷、湖泊，也是很完美了～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;也有很傻缺的体验，譬如为了体验喝到断片的感觉，进医院喜提急性肠胃炎，骂完自己决定以后再也不把伤害身体的事情列为 todolist。。。&lt;br /&gt;​&lt;br /&gt;
&lt;a name=&quot;eMsf1&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;-摄影视频&quot;&gt;🏞 摄影视频&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;正儿八经的创作了 11 个视频：&lt;br /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/026.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/027.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;br /&gt;21 年新接触的这个记录生活的方式很棒，会继续下去，等春节了给自己剪个年度回忆。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;还有无数个记录着我快乐生活的照片～&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;WMLMb&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;-遇见篇&quot;&gt;🙈 遇见篇&lt;/h1&gt;

&lt;p&gt;&lt;br /&gt;3 月脑袋一热在内网发了个很走心的脱单帖《这个春天，能否遇见你》，然后就开始了奇妙的经历。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;认识了不少有趣的人，譬如有一样热爱生活，吃喝玩乐样样精彩的小姐姐。有世另我的一个大姐姐，岁月没有抹去她对生活的热爱，而是活得更通透，看到她，想到 10 年后的自己也许也是这样，会很安心。&lt;del&gt;有很欣赏文风和摄影作品的一个朋友，之前觉得我们很多地方很相似，不过更多的了解之后发现更多的是不同，特别是对生活的态度和对生命意义的理解，最后我们愉快的成为了聊景色和摄影的网友～ 譬如有很让人敬佩的产品经理，会半夜跟你聊拼多多和淘宝运营模式差异，让我看到无论工作多久，都可以对工作热爱的很纯粹，认真做事很有魅力，差异也让我们停留在朋友～ 还有比较有趣豁达的朋友，写段子，初次见面送一本书的那种～ 亦有能一下子聊 5 个小时海阔天空，观点差异巨大还能聊的下去的那种～ 还有爱好都挺相同，但永远不再一个节奏的算法朋友～更是有遇见一个相处非常愉快，有过原来两个人可以这么合拍的高光体验，但尚未开始就结束，留下满脑问号，最后喜提“感谢厚爱，祝你幸福”，&lt;/del&gt;以为我的这份遇见的运气也就止步于此，终究是底色悲凉了。(划掉是因为这些都是我的经历，但是现在都是历史了)&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;在我将所有的期望放回人群，依旧没有迷失自己，坚定不随意选择，即使一个人也要好好生活之后。世界给了我更大的惊喜，遇见了我的男朋友，原来真的有那么一个人，会发现似乎茫茫人海就是为了遇见他。譬如我觉得自己写的很多东西戏太多，太理想主义，但他就是也喜欢这份理想浪漫主义；譬如我们都非常认真的对待感情；譬如我们工作起来，都可以很忘我很肝；譬如他虽说自己很宅不爱动，还是会很开心的和我东走走西逛逛；还有很多很多的小细节，让你发现这个人居然如此的宝藏～&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;欣赏他的才华，敬佩他的能力，喜欢他的可爱，感谢他的认真，无法描述他多方位全角度的美好，时间没有让我们深入了解对方而失望，而是愈发觉得对方很好，也是他，跟代码一样，让我认识另一个自己。原来我也可以接受不吃辣，我也可以分清情绪和事实，在冲突时冷静处理，我也不是那么工作狂，比起他来差远了，我也可以因为一个人而将自己放在后面。原来我依旧有被爱的运气，和爱人的能力。​&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;遇见了他，感谢曾经自己的不将就，也感谢其他人对我的不选择，才让我还有机会走进这神奇的缘分。毫不客气的说，如果 2021 年，觉得最值得书写的是什么，那么多的精彩，都抵不上这份存在～&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;这份遇见是何等的幸运，也是自己的勇敢无畏，感谢春三月那个勇敢的自己迈出的第一步，更感谢他的存在～&lt;br /&gt;
&lt;br /&gt;亲密关系也是人生非常重要的一个组成部分，如果没有这个，我觉得人生是遗憾而不完整的。2021 我们经历了太多美好和感动，这段亲密关系让每个日子都开始变得有温度 🌻，（友情提示，以下新的更新内容狗粮浓度极高 🙈）：&lt;br /&gt;​&lt;br /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;是在爬雪山累的没有力气聊天时，一直走在我旁边，分享给我一半耳机；是在下山后虚脱躺尸时，送来血氧检测仪，测到 55 的血氧后，把我硬拽到急救站吸氧，是自己也高反时还为队友们跑来跑去。&lt;/li&gt;
    &lt;li&gt;是大风瑟瑟的亚丁山顶、奶绿色的巴松措岸边、波光粼粼的羊湖湖畔，醉心飞穿越机的我身边，这个人或是撑伞，或是挡风&lt;/li&gt;
    &lt;li&gt;是在稻城手可摘星辰的满天繁星下，是在不知名的小城路上，一边散步，一遍诉说彼此的过往和期待&lt;/li&gt;
    &lt;li&gt;是在那么多个周末的日子里，开着车儿，放着音乐，唱着歌，去往一个个周边小景点&lt;/li&gt;
    &lt;li&gt;是在加班的夜晚，桌上有一杯温热的牛奶，是抱着我喜欢的毛绒玩具给我开门说“辛苦了，欢迎回家”～&lt;/li&gt;
    &lt;li&gt;是在冬至这样会被遗忘的日子里，在一起仪式感的吃汤圆&lt;/li&gt;
    &lt;li&gt;是在我 emo 的时候，给予莫大的安慰和陪伴。会轻拍我的背摇曳着，摇摇晃晃，就像荡秋千一样，说着“烦恼烦恼快走掉，快乐快乐快回来”，还感叹到“我这个样子好像跳大神啊”&lt;/li&gt;
    &lt;li&gt;是看到什么有趣可爱的小物件，随时就会给对方捎上一份，譬如小鸡灯、小黄鸭、大鹅&lt;/li&gt;
    &lt;li&gt;是明明都在长胖，却快乐的继续喝着一杯又一杯奶茶&lt;/li&gt;
    &lt;li&gt;是在任何一个本该玩耍的地方，都会乱入正经的讨论；是大周日的，我们也会在家里充电一整天，累了就看看对方笑一笑～&lt;/li&gt;
    &lt;li&gt;是坐手扶梯下楼时，夸张的大幅甩着手，说：“这样我们就像是自己在走路了”&lt;/li&gt;
    &lt;li&gt;是看到这个人笑时，觉得世界都灿烂了；是看到这个人难过时，真的会很心疼，拼命想能做点什么。&lt;/li&gt;
    &lt;li&gt;是本都不喜欢打卡的人，在一起后总是标志性的拍合照，因为有彼此，所以这些时刻都是这么珍贵&lt;/li&gt;
    &lt;li&gt;是在一起的每一餐我主动不吃辣，开始一起看动漫，而这个人开始放弃宅迈开步子陪我去每一个想去的地方，我们都为彼此改变，在慢慢融合。&lt;/li&gt;
    &lt;li&gt;是在有矛盾时，会默契的先控制住情绪，各自冷静思考，过两天后一起客观的好好复盘，坦诚的说出彼此的感受和期待。&lt;/li&gt;
    &lt;li&gt;是不时听到车里放的歌都是我喜欢的，原来这个人默默把我日常说的喜欢的歌都收集到了一个歌单里，命名叫“&lt;strong&gt;000 foreach&lt;/strong&gt;”🎐&lt;/li&gt;
    &lt;li&gt;是我会间断性记录我们生活点点滴滴的美好，而他一遍又一遍的回看。它们是时光的宝藏，我们一起分享&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;无法预知未来会发生什么，但很心安。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;世界可可爱爱，有人陪我一起奇奇怪怪。在浪漫的去看诗和远方后，也能静静地一起看人间烟火。无论外面是晴空万里还是电闪雷鸣，回头是彼此的微笑，这就够了。想和这个人一起踏破山河，看遍世间美景，也想和他在每个日常的日子相依相伴，一起学习，一起工作，不断沉淀，不断成长。历经岁月沧桑，愿我们永远是执手笑对世界的那两个少年。&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2021/028.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;br /&gt;乱入一下，还多了一个好友小姐姐，超级有趣又有才，我们在一起有聊不完不疲惫的话题，我们都喜欢探索新事物，都有自己明确的态度和观点，这份遇见一样让我觉得很幸福～&lt;br /&gt;​&lt;br /&gt;
&lt;a name=&quot;lq9V3&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;2022-寄语&quot;&gt;🐣2022 寄语&lt;/h1&gt;

&lt;p&gt;&lt;br /&gt;2021 我过的很精彩，淋漓尽致很痛快！&lt;br /&gt;​&lt;/p&gt;

&lt;p&gt;希望 2022 的自己：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;找到长期为之奔赴的事情，能有更多的沉淀和输出&lt;/li&gt;
  &lt;li&gt;能做出有价值的技术产品，也能成为团队的杠杆&lt;/li&gt;
  &lt;li&gt;和我最亲爱的男朋友一起探索，一起成长，一起奋斗，也一起看人间繁华，体验更多世间美好～&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;一篇从 21 年写到 22 年真的跨年的年终总结，你好，2022！我们来了～&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2020-暗夜已临，黎明终至</title>
   <link href="http://echizen.github.io/think/2021/02-11-2020-summary"/>
   <updated>2021-02-11T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2021/2020-summary</id>
   <content type="html">
&lt;p&gt;在疫情的一片惊恐和哀嚎中过了最没有年味的2020年春节，还记得大雾弥漫的连自家头顶的烟花都看不清，0点过后关注着武汉封城后的报道，和朋友们感叹艰难，从武汉回来的妹妹说她失眠到半夜，初一看这武汉几近崩溃的医疗系统下的护士撕心裂肺的哭声和崩溃，劝说着家人们取消拜年在家，心中不知如何描述那份酸楚，也不知道2021将面临多少艰难，也见到了各种牛鬼蛇神充满玄幻比剧本还夸张的社会新闻事件。所幸的是在井然有序中引来了2021年的春节～，这一年国内疫情得到有序控制，慢慢的恢复了生产，恢复了旅行，经济也迅速复苏，偶有疫情区域性的爆发，但都能在严格的防疫措施中被控制住，年底能回家团聚了～ 再看看国外的各种严峻，第一次真切的感受到社会主义国家的强大，强管控策略在公共危机事件前的优势，虽然和平的日子总是吐槽自由被限制了，这个疫情让我对生活的这个国度多了很多热爱。&lt;/p&gt;

&lt;p&gt;所以，生活在美好里环境里是一份可遇不可求的幸运，大多数时候我们会面对不那么美好的环境，但是难能可贵的是看这不美好的环境一步步变好，特别是变好的过程中有自己的一份努力。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976494607-eaeeda95-335a-42e7-a00b-7e5c59eec003.png#align=left&amp;amp;display=inline&amp;amp;height=204&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=1080&amp;amp;originWidth=1920&amp;amp;size=2931050&amp;amp;status=done&amp;amp;style=none&amp;amp;width=362&quot; alt=&quot;image.png&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976505166-1fb7afcf-a7bd-4841-9e19-a5bcf2a4b267.png#align=left&amp;amp;display=inline&amp;amp;height=205&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=1080&amp;amp;originWidth=1920&amp;amp;size=2719662&amp;amp;status=done&amp;amp;style=none&amp;amp;width=365&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;年初在家看了n次雪，年终倒是看不到雪了。&lt;/p&gt;

&lt;h1 id=&quot;那些我带来的改变&quot;&gt;那些我带来的改变&lt;/h1&gt;

&lt;h2 id=&quot;保险首套组件体系建设---智能组件&quot;&gt;保险首套组件体系建设 - 智能组件&lt;/h2&gt;

&lt;p&gt;一套相对完善的保险组件体系。&lt;/p&gt;

&lt;p&gt;智能组件相对普通npm包组件，业务组件背后有一套schema配置，可以做到通过配置来调整组件的样式功能等，结合我们的精细化运营平台德鲁伊的能力，让组件能够拥有针对人群、渠道、业务规则等等运营规则差异化展示的能力，也就可以称的上是智能组件了。&lt;/p&gt;

&lt;p&gt;组件建设这一过程极度舒适，从先了解蚂蚁这边移动端的组件体系，主要是sherry-component，再看怎么在sherry-component之上搭建保险的组件体系，结合德鲁伊的精细化运营能力。通过insiop-cli提供了组件创建、开发、构建的环境，再整合进bakery让组件在本地有完善的mock环境，在德鲁伊平台也有了预览数据源，前期发布流程通过postpublish hooks打通了到德鲁伊平台的注册通知，后期在雨燕迭代系统支持npm包迭代和蚂蚁资产中心建设后，打通了组件的迭代管理流程。除了常规的新建、开发、构建、发布的必要流程外，还支持了组件的自动埋点、疲劳度等业务能力，统一监控降级的能力，动态下发能力。结合mars和remix的跨端能力，智能组件也支持mars和remix版本，成为一个可跨端的组件体系，支持了多个业务线的应用。消费端，德鲁伊平台托管着组件管理中心，以及组件数据源配置的预览发布。后续智能组件成为保险智能搭建的基础原料，支撑起保险在营销搭建领域的相对领先的向前发展。&lt;/p&gt;

&lt;p&gt;回头来看，智能组件里我觉得比较有成就感的两点是：&lt;strong&gt;高效速度的实现和一个体系化的建设&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;高效速度得益于年初因为疫情在家办公，一天居然能真实的工作13小时，如此高效舒适的环境让我一度产出很高，在家两周比较意外的完成了保险智能组件体系的建设，这个战斗力还是不错的。&lt;/p&gt;

&lt;p&gt;智能组件体系建设，技术上与我来说不算一件有挑战的事，对于本就是做工程体系建设出生的我整个流程里没有知识盲区，更多的是针对保险当前的场景和蚂蚁已有的建设，做整合建设，也正是整合建设，速度才能这么快，站在巨人的肩膀上，拒绝重复建设，做出差异化，快速应用做出价值。&lt;/p&gt;

&lt;p&gt;相对于去年半年在相互宝业务需求里痛苦挣扎，用体力堆需求产出，这个智能组件的建设是一个相对体系化的建设，锻炼是更多一些，做下来也更愉快和有成就感一些，找回了曾经的快乐和自信。&lt;/p&gt;

&lt;p&gt;再看这里面也有比较遗憾的两点：&lt;strong&gt;前期推广不够尽力，没有将这套体系做的更通用更多的应用场景&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;推广方面一方面是我一开始并没有意识到技术产品还是需要推广运营的，在前公司里向来是开发，开发好了分享一下，群里贴个使用教程，后续就是各方接入的辅助依旧之后的维护了，一度让我觉得产品做的好满足别人的需求自然就可以用了，哪需要逼人用或者求着人用。而我当时对保险现状的判断是压根没有组件建设，这个正是弥补空白提高复用提高效率的，自然是有需求的。然后就发现2～3个月都是组内人在用，拍着良心说，这套建设真的是有价值的，从我自己的体验来说，它解决了我自己很多业务复用问题，但是就是很少有新用户加入使用，这在当时是我不太理解的。&lt;/p&gt;

&lt;p&gt;而我对组件场景的定义是通用化和复用，有复用的需求上组件，最大的复用场景是搭建体系，这个其实也并没有错。&lt;/p&gt;

&lt;p&gt;慢慢的，各种场景大家接触到这套组件体系，特别是下半年营销搭建兴起后，组件生产和应用得到更多提升，沉淀了60+组件，支撑了700多个场景。组件体系和开发模式逐渐被大家接受。如果一开始我多一些输出介绍安利，这一切应该来的更快一些。&lt;/p&gt;

&lt;p&gt;由于我把组件定位在基础建设和搭建物料上，对其没有过多的期望，基础建设完了就没有更多的规划了。8月份我看到蚂蚁移动资产中心建设起来了，其实整个蚂蚁都是缺这套体系的，我却没有为此做出努力和贡献，之前我就觉得我初来乍到不可能走出保险的，用阿里的话说，是给自己设限了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/212960/1612977023945-f836e518-bfba-4eaf-a9ab-9899415485b9.jpeg?x-oss-process=image/auto-orient,1#align=left&amp;amp;display=inline&amp;amp;height=4032&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=IMG_7749.HEIC.JPG&amp;amp;originHeight=4032&amp;amp;originWidth=3024&amp;amp;size=3169012&amp;amp;status=done&amp;amp;style=none&amp;amp;width=3024&quot; alt=&quot;IMG_7749.HEIC.JPG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;蓝天下的黄龙时代&lt;/p&gt;

&lt;h2 id=&quot;与智能ui的虐恋&quot;&gt;与智能UI的虐恋&lt;/h2&gt;

&lt;p&gt;我与智能UI真的算是一段虐恋了。。。&lt;/p&gt;

&lt;p&gt;智能UI是一套个性化推荐解决方案，根据用户画像，为不同的用户推荐符合其偏好的UI样式，从而提高CTR沉淀人群偏好差异和特定人群偏好画像，为UI设计提供决策参考。&lt;/p&gt;

&lt;p&gt;曾经的曲折和委屈在11月的月报里我就总结了一回：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;h3 id=&quot;做到自闭的智能ui&quot;&gt;做到自闭的智能UI&lt;/h3&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;不知道为啥做的预热&lt;/strong&gt;：11月和万花筒合作打通的智能UI发上了线，这个项目简直太艰难了，难到我都快做自闭了，不得不一吐为快抒发下。一开始5月份qz和设计师yl就提出想做ui的智能化推荐，然后万花筒也来保险谈合作想接入头部流量场景，这时候我还在搞提效图片生产的智能合图，并没有对智能ui的必要性有啥理解。后来小组扛起了用户增长的目标，各种调研了一圈，没有挖掘到用户增长下前端的发力点，唯独看到智能UI是可以尝试的方向，而且是被其它BU验证有效的模式，大家把智能UI在保险推行不起来归因为“接入万花筒成本大，运营不能接受去其它平台配置”，这个成本确实有，这样接入，前端需要通过bff和C端自行对接，每个接入方都得对接一遍，运营习惯了德鲁伊做私域投放，不太愿意接受尝试去其它地方做投放。但是不可否认这其实是验证效果的最快最小成本途径。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;愉快的启动：&lt;/strong&gt;于是和万花筒一来二去谋划用德鲁伊系统去对接万花筒，把智能ui的能力直接嵌入我们的平台。并整体看了下链路，也给万花筒的链路提了很多建议，万花筒配合做了2.0版本的改造，做了很多体验优化和系统串联的工作，系统的打通让智能UI本身的配置成本大大降低。整个过程中，和万花筒的开发们对接的还是很高效愉快的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;跌跌撞撞的开发：&lt;/strong&gt;在开发中，遇到系分敲定上线后，服务端突然撤出资源的窘状，整个过程中，服务端都不太相信和配合这件事，最后tl们对焦了几次才投入资源继续做。然后服务端进度非常缓慢，直接延期了发布到了双十一之后，而且过程非常不愉快，找不到人，把自己作为个资源完全不考虑上下游和场景，各种问题，直接让工作4年的我暴走第一次在合作问题上找老板，都觉得对不住被我们拖住的万花筒合作方。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;跪在地上求接入：&lt;/strong&gt;好不容易上线后，本月接下来的落地过程果然跟想象的一样艰难，甚至比想象的还艰难。本想找个简单的场景先看效果，因为自己在赠险业务域，业务方也比较开放有接受度，pd也追求用户增长，所以第一个场景力求该业务线。期间做了以下6个场景的尝试，都已各种原因的失败不了了之&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;依旧相信的未来：&lt;/strong&gt;虽然我现在遇到了很多问题，但是我和ax一样从原理和其它bu的数据上是相信智能ui在用户增长上的效果的，千人千面的最佳推荐，整体最优一定会优于局部最优。不过我们通过系统打通解决了配置提效，但是大头是算法提效、素材生产提效，算法通过训练通用的业务垂类算法模型，通用海王星配置，还是能解决的，素材生产方面财富线同学已经有具体的行动在解决了，未来的成本降低还是可以期待的。再经过接下来把德鲁伊的产品推荐链路和ui推荐链路打通承接上，以后我们的算法推荐能力还是比较强的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;思考&lt;/strong&gt;：看上去在做一件非常正确的事，但是过程却如此艰辛，甚至即使能增长也很难落地，思考后我觉得可能与保险当前所处的阶段有关，目前保险还在比较快速的发展，还没到流量增长瓶颈期，不需要如此精细化的模式，特别是成本高，roi可能不如其他的投入，如果真的有一天其他的途径都很难再增长了，我觉得即使成本依旧存在，业务也会愿意尝试智能ui的。我现在能做的是让更多的业务知道这个能力的存在，对有意向的业务积极协助接入。并尽力优化能提效降成本的环节，其他的譬如算法资源实在是我无力解决的问题，实在是需要更上层把这件事摆上一个规划里投入资源才能解决的了，我就不跟自己杠了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;收获：&lt;/strong&gt;虽然我投入了大量的精力，在与技术与前端本身无关的领域，而且面临难以落地拿不到结果的窘境，但是还是有收获的，通过和各方的沟通，我真实的理解了这种”根据用户画像，为不同的用户推荐符合其偏好的UI样式，从而提高CTR沉淀人群偏好差异和特定人群偏好画像，为UI设计提供决策参考”的模式，并相信它是未来，我深入的了解了这个过程中的设计、运营、工程链路、算法环节，特别是填充了我曾经一片空白的算法推荐链路的知识，可能刚开始提要做智能ui时，大部分人只知道是算法推荐ui，并不了解真实情况，但是现在我已经很清楚了，也看到了这个链路上存在的问题，和未来的方向。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;小试牛刀&lt;/strong&gt;：经过12月份的努力，1月我们上线了2个智能ui场景，都带来了约10%的增长。更重要的是，在这个过程中，我发现了2个事实：&lt;strong&gt;用户对样式效果足够敏感，样式的调整会带来很大的影响，不同的样式受众的反馈差别很大，在样式上做区分和推荐对用户增长是一个有效途径，是值得投入的。智能化推荐ui，即使是和人工精选的ui对比，还是有比较大的提升，智能ui是有效的。&lt;/strong&gt;这让我由一种天降大任被团队挑出去扛这个试点项目的被动变成了我相信这件事价值后的主动，相信，然后才会笃定的做出进展，消除困难带来的负面情绪。&lt;/p&gt;

&lt;p&gt;但是这个过程中也有一些问题，譬如后置的算法和数据链路非常漫长，从随机实验的1周，到算法实验的1周，算法模型不理想时还带来不定时常的调整，再到对比数据及归因分析，1个月就过去了，而且现在的数据和算法链路都是万花筒团队的同学在支持，任何调整和数据捞取都是要提需求等待的，倒不是说万花筒团队人员的问题，其实人员支持的很好，只是资源有限难以满足这么多业务系统的需求。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;新的起航&lt;/strong&gt;：年底整体看了一下智能ui在阿里淘系双十一，1688的应用，以及目前蚂蚁的应用现状，做了一下未来一年的规划，仅仅是作为业务接入方，打通工程体系还是不够的，目前保险正在建设的D2C物料生产体系需要继续扎实的做下去和应用场景结合起来，最核心的算法和数据环节的提效和自动化方案也需要深入一起看看是否可以有突破性的进展。这部分对于我全是需要学习的领域，团队也招聘了一个有算法背景的前端小伙伴，可以一起研究。&lt;/p&gt;

&lt;p&gt;2021年智能UI将是充满挑战的一年。希望年底，规划的那张图能真实实现&lt;/p&gt;

&lt;h2 id=&quot;前端如何做用户增长&quot;&gt;前端如何做用户增长&lt;/h2&gt;

&lt;p&gt;自从来到用户增长小组，时刻就在琢磨前端怎么做用户增长。期间我也有过这样的思考：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“gy在寿险营销弹屏上做了一个小创新，在按钮呼吸动效的基础上，触发手机的振动系统，以提升用户注意，进而提升转化。在养老金再投场景得到了超过30%的CTR提升”，gy做的这件事让被用户增长和智能化需求困扰了几个月的我看到了曙光，或许一开始我们的方向就歪了，把算法能力摆在第一位，发现前端好难作出贡献，回归了前端是距离用户终端最近的角色上来，我们在交互创新上可以做的更多，用户界面才是我么的主阵地，以前也不是没想过，总是以为只有小游戏类的富交互才能带来改变，而这类游戏互动技术在这个保险前端都比较缺失自己不感兴趣这方面比较难做，现在gy的实验证实了，并非一定需要复杂游戏，一个小动画，一些小动效，一些加强的交互，都能带来不错的效果。这样后面的路就清晰了，&lt;strong&gt;一方面我们需要设计并实验更丰富的C端玩法，另一方面我们需要沉淀工程能力，让玩法的生产、投放、数据对比、优秀案例展览，都高效方便的进行，让优秀的玩法被运用的更多，带来规模化的增长。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但是一波探索后没有找到轻量化接入动效这一核心能力的方案，后续也因为智能ui和D2C占用了精力没有在这个方向探索下去。&lt;/p&gt;

&lt;p&gt;不过动效和动画在增长方面的巨大力量依旧是毋庸置疑的，或许有一天我有精力想法了会重新探索。&lt;/p&gt;

&lt;p&gt;增长方面，多是带着外包同学配合后端做增长平台，开发了用户生命周期、增长模型、网格圈人、流失预测及分析等功能，这部分多是服务端的设计，我边学习其思想边做系分安排外包同学开发。&lt;/p&gt;

&lt;p&gt;后续参与设计开发了智能文案、智能投放计划、智能产品货架，以及打通产品推荐和智能ui推荐链路，在智能投放部分从设计到开发都倾注了自己的心血。&lt;/p&gt;

&lt;p&gt;目前看到增长平台沉淀了一些不错的能力，但还都比较点状，没有从运营视角串起来成为一个增长方案大盘，这部分是需要继续抽象串联的。前端方面，智能ui和动效也是撬动增长不错的值得继续投入的立脚点。&lt;/p&gt;

&lt;h2 id=&quot;物料生产与沉淀&quot;&gt;物料生产与沉淀&lt;/h2&gt;

&lt;p&gt;在前期智能合图的复杂模版制作，和平时组件开发中繁杂的样式调整里，我越来越觉得需要和快速生产视觉物料的工具，imgcook很智能，但还是不够，特别是面对要接收动态化数据的视图，imgcook可以100%还原视觉稿，但是它理解不了哪部分的样式要如何固定容器区域来适应动态化的数据，这部分只有开发者自己能理解。所以还需要个能快速矫正调整ui并绑定数据的平台来提效。&lt;/p&gt;

&lt;p&gt;10月初我研究完imgcook的能力后，又断断续续的写了系分，找了上下游对接，但是进展缓慢，迟迟没有搞出demo。&lt;/p&gt;

&lt;p&gt;后来资产中心的设计被团队认同，更多人加入，并且抽象到更高更广的维度，以支持更多的场景。更多人员的加入，出现了很多很棒的设计想法，譬如wg基于iceluna的设计比我之前夭折的demo版体验就好很多。进度也提上来了，1个月搞了一版初稿，但演示时大家都觉得体验还不够好，还需要优化，特别是ui矫正环节，其实最好的是像chrome devtools那样的调试。然后有小伙伴非常迅速的在一周内把devtools的ui矫正模式继承进去了，将进度推进了一大步，直达ui矫正器的终态，这个物料生产拥有了良好的ui矫正能力，增强实现了数据绑定能力，真正的强大起来，相信是款利器。另一只小分队在搞资产中心沉淀部分以及资产数据化的探索。团队的力量让这款产品一步步成型。我也在年前设计了物料和上层消费平台对接的部分，年后开发一波，早日为物料生产投入使用助力。&lt;/p&gt;

&lt;p&gt;这是我心心念念了大半夜一直想做的产品，但是其中的核心技术都不是我提供的，这件事也一直是我心中2020的年度遗憾。&lt;/p&gt;

&lt;h1 id=&quot;那个我并不享受的业务开发&quot;&gt;那个我并不享受的业务开发&lt;/h1&gt;

&lt;p&gt;没办法，选择了业务线，再不喜欢，该担的责任还是要担。&lt;/p&gt;

&lt;p&gt;年底换组从相互宝来到赠险后，生存环境好了很多，虽然会有阶段性忙碌的时候，但是整体上节奏可控。让我得空可以做些技术项目，十分感谢leader们当初的调换了。&lt;/p&gt;

&lt;p&gt;这一年在业务线心态是有变化的，以前在相互宝，可能因为节奏紧需求变更多，一直非常抵触，主管都跟我说过不要总把业务方看成对立面，要压迫我们。今年再也没有这个想法了，更多的能从业务方视角去看，多听听她们的想法，不理解的多问问，为什么某些需求一定要倒排上线，某些可以商量。连那种赶着上线却迟迟不配置好上线素材的这种以前让我炸毛的事情，也能和同事吐槽两句一笑而过了，可能她们有优先级更高的事情要处理忙不过来而已。我的心愿变成了解业务目标，帮他们一起更好的实现业务目标。不过还是看不清整体的运营策略，可能这方面还要多追问。日常确实做过不少价值不大的需求，对于需求的价值和合理性也是需要再多思考的，要把精力放在最重要的事情上。&lt;/p&gt;

&lt;p&gt;在赠险里，我沉淀了常见链路模型的业务sdk，组件体系建设好后，也沉淀了很多组件，技术和业务双赢在赠险这里是体现了的。这可能也是我之前在基础平台呆久了后对复用的执念。一些抽象复用，一开始会被质疑这样做的复杂度，但是时间拉长去看，意义就来了，譬如开通协议和开通组件，一开始觉得改个协议还得去发sdk太麻烦了，但是后来监管需求来了要改协议时，改个sdk代码，就能全部更新了，甜头就尝到了，开通组件一开始也被怀疑必要性，后来我们在营销页面、宝贝金、小医保这些新产品里可以配置化直接使用，便利性也体现了。快节奏和kpi导向逼得大家不得不快速拿结果，缺少沉淀，但是时间拉长来看，那些起步麻烦的沉淀能发挥长期价值。&lt;/p&gt;

&lt;p&gt;比较有成就感的需求是监管改造中代理重写了pushWindow，通过底层的代理适配，一举解决了运营和技术需要全面换链接的问题，更难得的是拉通全组同学参与规则维护和接入验证。不过也不是一帆风顺，一开始大家的方案是德鲁伊做个链接替换的工具，运营选择需要套壳的小程序，只是我觉得这样需要人肉一个个替换，代码里的链接我们还能一举搜索到，但运营就惨了，光德鲁伊上投放的链接都没发全部找出来替换一轮，而这事我们在代码层面重写pushWindow后就能程序化的解决，而且这个底层功能做好后，德鲁伊工具可以直接用来直接匹配目标小程序体验也会更好。只是这么做风险比较大，万一代码有问题，影响的是所有页面的跳转，所以一开始有同学建议不要搞这么危险的改动。后来和星焰一起梳理了下方案和存在的风险，通过明确的规则、兜底、以及较完善的监控，还是把这个风险比较大的事情给做了，并平稳上线。上线后监控检测了很多异常，当时也一阵心慌，跟星焰说我有点后悔坚持做这个了，所幸排查下来都是些监控代码条件不严谨或者正常的jsapi error，并没有实质性的异常问题。这个突然插入的事情着实让我忙碌了好几天到半夜，但是挺开心的，还是要坚持初心，做有价值的事。&lt;/p&gt;

&lt;p&gt;除了负责德鲁伊运营平台的业务，年底小伙伴转业务线，还接手负责了赠险，2021的业务压力将会非常大，我也很担心我的精力被全部吞噬掉，但是团队需要时确实应该站出来。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976959436-ff735ffb-1afd-42c8-a2ef-c9fc24cbc3ba.png#align=left&amp;amp;display=inline&amp;amp;height=407&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=4032&amp;amp;originWidth=3024&amp;amp;size=15011630&amp;amp;status=done&amp;amp;style=none&amp;amp;width=305&quot; alt=&quot;image.png&quot; /&gt;
有阳光的工位，幸福感爆棚&lt;/p&gt;

&lt;h1 id=&quot;痛过才知道要怎样&quot;&gt;痛过才知道要怎样&lt;/h1&gt;

&lt;p&gt;我时常痛苦，“当你最痛苦时就是你成长的时候”，这应该是句鸡汤。痛苦并不一定伴随这真的成长，但是成长或多或少都伴随着痛苦。&lt;/p&gt;

&lt;p&gt;年底的两个月，一些挫折让我有种顿悟看清自己的感觉。&lt;/p&gt;

&lt;p&gt;我有时候嘲讽自己“一手好牌打得稀烂”，曾经的沉淀来到蚂蚁后烟消云散，仿佛从0开始，特别是刚进来时很长的一段时间天天在琐碎的需求里摸爬滚打，完全迷失自我，如同惊弓之鸟，面对一点点意外就惊慌失措的处理方式也让我像个实习生。&lt;/p&gt;

&lt;p&gt;我发觉出自己，除了不够自信，仿佛自己不配蚂蚁似的，也不知道为啥有这种潜意识，可能校招失利多了然后在外pr的都是顶级大佬让我把蚂蚁神化了？最大的问题是处事态度，我还是比较佛系，慢悠悠的讲究水到渠成。一副事情不找上我，我绝对不站出来扛事情的态度，（当然这个点也有自己精力有限处理不过来的关系）。我也比较低调，譬如不在群里说话，一些沟通和努力都是在默默做着，从来不想着表现什么，有点“清高”的觉得酒香不怕巷子深，（当然酒不一定够香，真的香也是怕巷子深的）。当自己很多努力做出来的东西变成别人体系里一环的时候，我是有些失落的。&lt;/p&gt;

&lt;p&gt;殊不知在我迟钝的反应里，其实蚂蚁是个“杀机四伏”的竞争环境，身边大有人在“如狼似虎”，该出手时就出手，等着水到渠成，可能都流成另一条渠了。所以需要发声，需要争取，该担责的时候站出来，想做的时候站出来。我很大的败笔就是不知道有些环境机会是要争取的，表现也是一种证明，否则怎么让人相信我可以。&lt;/p&gt;

&lt;p&gt;更大的一个问题，是我反思来蚂蚁的1年多，虽然我的抗压能力、处事能力、应对速度、战斗力都提高了，认知和思维也拔高了，但是在技术方面却停滞了，我有多久没有学习新技能了？在D2C物料生产的项目上这个问题暴露的更是明显，为什么我很早就有了想法和产品设想，却迟迟没有做出来，为什么我一开始尝试后就说过ui矫正的最佳模式就是像chrome的devtools那样，却没有去实现？为什么我的demo不是基于iceluna，比别人的成熟度低了一大截？&lt;/p&gt;

&lt;p&gt;一方面是我虽然会有想法出产品方案，但是在技术方案上调研不足，做事没有足够的拼劲一气呵成。&lt;/p&gt;

&lt;p&gt;大的团队环境也是重思维想法方面的，让我渐渐麻木，忽视一个技术人的核心竞争力还是技术，技术过硬才会无可替代，换个角度看，虽然全面发展最佳，但一个人技术实力够强，其他方面欠缺点，大家也都能包容。&lt;/p&gt;

&lt;p&gt;另一方面，确实时间精力有限，支撑业务之余，我们还要追求技术产出，如果一件事去做是要有个学习过程的，必然导致进展受限。所以一旦接触到不熟悉的领域，我的第一反应是没时间研究，先上个临时方案run起来，后面再研究，譬如面对devtools就是这样。&lt;/p&gt;

&lt;p&gt;人总是要理清主次，既然觉得技术的世界最精彩，最喜欢做这个，那么，接下来，&lt;strong&gt;不惜一切代价追求技术的成长&lt;/strong&gt;。要做很酷的技术人。&lt;/p&gt;

&lt;p&gt;另一件非常遗憾的是，这一年我都没有出远门，如果5月份去趟北京也算的话，那还是为了见朋友，这让我觉得我丧失了生活的一大乐趣，觉得对不起自己，看到有人的年终总结里，在那么忙碌的状态下，路上写代码发布的状态下，一年还能跑10几个城市，我觉得自己太羞愧了，忙都是借口，还是爱的不够深沉。&lt;/p&gt;

&lt;p&gt;少的可怜的出门的照片：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976648264-e81c6714-8b47-4e38-a272-08d58b814b48.png#align=left&amp;amp;display=inline&amp;amp;height=560&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=19116478&amp;amp;status=done&amp;amp;style=none&amp;amp;width=746&quot; alt=&quot;image.png&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976667558-b81e406e-73ad-4293-b83c-1208976078cd.png#align=left&amp;amp;display=inline&amp;amp;height=1512&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=19176251&amp;amp;status=done&amp;amp;style=none&amp;amp;width=2016&quot; alt=&quot;image.png&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976698420-599d88e0-98f8-4be9-9bed-4c201cc6ebec.png#align=left&amp;amp;display=inline&amp;amp;height=1512&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=12689266&amp;amp;status=done&amp;amp;style=none&amp;amp;width=2016&quot; alt=&quot;image.png&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/212960/1612976782116-536bcd96-52b3-4206-a3a3-7536223a2470.jpeg#align=left&amp;amp;display=inline&amp;amp;height=3024&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=IMG_6690.HEIC.JPG&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=4435488&amp;amp;status=done&amp;amp;style=none&amp;amp;width=4032&quot; alt=&quot;IMG_6690.HEIC.JPG&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976715295-5764612f-a1df-44b2-882d-19573804314c.png#align=left&amp;amp;display=inline&amp;amp;height=1512&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=18529553&amp;amp;status=done&amp;amp;style=none&amp;amp;width=2016&quot; alt=&quot;image.png&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612977051747-ace5c150-3466-419f-90c3-335438a0601c.png#align=left&amp;amp;display=inline&amp;amp;height=540&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=1080&amp;amp;originWidth=1920&amp;amp;size=4710279&amp;amp;status=done&amp;amp;style=none&amp;amp;width=960&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;一些成长&quot;&gt;一些成长&lt;/h1&gt;

&lt;p&gt;来到用户增长小组，最大的收获是增加了输入。关注了阿相和星焰后，有种语带逛的感觉，开始发现语雀上又这么多可充电的内容，还有ata上，还有阿里大学，蚂蚁大学，相比以前在外面，在社区里苦苦检索干货的艰难，内部资料充实多了～有种只要你有时间，这里就有无限可以了解学习的内容。如果是要做一件事，这些资料在调研和了解其他团队建设，做差异化和核心能力时，又尤为重要。多吸收阿里的知识库，多么简单直接的方式，但是我今年才会，要感谢团队的同事。其中年度印象最深刻的还是圣司老师的&lt;a href=&quot;https://mp.weixin.qq.com/s/lq7XQGM56CovF2Q_Y8Ht4A&quot;&gt;《我的前端成长之路：内观自在，外观世音，追寻内心平静》&lt;/a&gt;，一篇让人越看心越静的文章。&lt;/p&gt;

&lt;p&gt;今年打破了自己的界限，发声了一些变化，譬如去学习了用户增长系列课程，了解了一下运营的视角。去学习数据链路和算法链路入门的课程，希望能清楚链路，为接下来智能ui这部分的难题提供点思路。以前总是觉得术业有专攻，自己也不是很聪明，我的精力把前端搞搞好就不错了，学其他的分散了精力，也难以有竞争力，但是现在发现有个事实是，局限在前端领域真的处处受限，团队是需要懂算法、数据、产品、运营等各方向的人才的，按照啥理论来了，一个领域，如果花40%的时间能学得80%的知识，那剩下20%的深入型知识可能要耗费60%的时间，所以多学学别的方向没准收益率更高呢。&lt;/p&gt;

&lt;h1 id=&quot;一些小开心&quot;&gt;一些小开心&lt;/h1&gt;

&lt;h2 id=&quot;我有小屋啦&quot;&gt;我有小屋啦&lt;/h2&gt;

&lt;p&gt;这算是了却了一桩心愿，摇号1年多，总是惦记着房子，钱也不敢投资全都闲置着随时准备冻资的状态实在是太不好了，一咬牙一跺脚，不等新房了，去买了二手房，似乎看上去很不划算，但又选择活在当下了一次，让自己早日解脱。&lt;/p&gt;

&lt;p&gt;对现在的房子不算很满意，因为实在没有精力管装修，买了精装修，但是其实我对装修是个很有想法的人，所以总是和理想差距不小，对现实妥协了下。好处是我终于可以买自己喜欢的家具，搞点喜欢的装饰，买精致的日用品，再也不用搬家，再也不因为搬家的恐惧压抑买买买，追求点品质生活了。&lt;/p&gt;

&lt;p&gt;同时配置一份固定资产，非常安心了～这么想也挺nice呢，剩下的多加油吧，未来也许我可以去换环境更好更喜欢的房子呢～&lt;/p&gt;

&lt;p&gt;阳光的午后，在阳台的摇篮上睡个午觉，扫一眼四周是自己的花花草草；在满是阳光的书桌上办公，水果和茶摆在一角；躺在沙发上，投大屏看电影，享受周五晚上的肆意生活。我可以在周末约一屋子好友给大家做饭，大家吃完可以一起聊聊天，今年组了蘑菇街的老同事，和蚂蚁这边团队的同事两次局，都很开心。也可以把父母接过来，一起过了元旦～就缺一只狗狗了，等过些年我不那么忙了就可以养了～&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/212960/1612976884020-58cf9e4c-cf7b-4dda-a583-ed88888a4780.jpeg#align=left&amp;amp;display=inline&amp;amp;height=280&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=IMG_8123.HEIC.JPG&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=3868650&amp;amp;status=done&amp;amp;style=none&amp;amp;width=373&quot; alt=&quot;IMG_8123.HEIC.JPG&quot; /&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976876795-f4dab93c-3079-4ef0-ac48-67fe30b6e91a.png#align=left&amp;amp;display=inline&amp;amp;height=279&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=15908254&amp;amp;status=done&amp;amp;style=none&amp;amp;width=372&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612976903748-b02d0f87-c64e-4afb-bcbf-22f69e0abf21.png#align=left&amp;amp;display=inline&amp;amp;height=464&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=1440&amp;amp;originWidth=1080&amp;amp;size=3136116&amp;amp;status=done&amp;amp;style=none&amp;amp;width=348&quot; alt=&quot;image.png&quot; /&gt;
喊大家干饭&lt;/p&gt;

&lt;h2 id=&quot;啊我爱这奢靡的团建&quot;&gt;啊，我爱这奢靡的团建&lt;/h2&gt;

&lt;p&gt;因为tl比较豪迈，又乐于分享他的财富，奢靡的团建打开了新世界的大门，特别是曲水兰亭，感叹澡堂子还能搞成这样，真真是贫穷限制想象力。我开始明白并不是所有的高价都是收智商税，一些高价的产品确实有更好的用户体验，舍得花钱确实可以过的更开心。这也影响了我的消费观，居家我开始买一些相对奢侈的东西，拿到材质，会发现，啊，真香！&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612977069930-f77ddefb-45e0-4423-801b-51ff1af3400a.png#align=left&amp;amp;display=inline&amp;amp;height=281&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=1080&amp;amp;originWidth=1440&amp;amp;size=1683797&amp;amp;status=done&amp;amp;style=none&amp;amp;width=375&quot; alt=&quot;image.png&quot; /&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/212960/1612977097998-bac2e230-9f9c-400d-9ab9-b0be8b824f37.jpeg#align=left&amp;amp;display=inline&amp;amp;height=280&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=IMG_7452.HEIC.JPG&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=2521851&amp;amp;status=done&amp;amp;style=none&amp;amp;width=373&quot; alt=&quot;IMG_7452.HEIC.JPG&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612977126481-55948edb-87a2-48ab-a361-342c60c7e1a0.png#align=left&amp;amp;display=inline&amp;amp;height=281&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=17121388&amp;amp;status=done&amp;amp;style=none&amp;amp;width=375&quot; alt=&quot;image.png&quot; /&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612977570814-710817a3-4453-4444-b392-80ef362a580c.png#align=left&amp;amp;display=inline&amp;amp;height=280&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=1080&amp;amp;originWidth=1440&amp;amp;size=3582934&amp;amp;status=done&amp;amp;style=none&amp;amp;width=373&quot; alt=&quot;image.png&quot; /&gt;
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612977164826-b88f54f0-9bbe-4c57-891f-76a68cdc9352.png#align=left&amp;amp;display=inline&amp;amp;height=261&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=2592&amp;amp;originWidth=3840&amp;amp;size=15362059&amp;amp;status=done&amp;amp;style=none&amp;amp;width=386&quot; alt=&quot;image.png&quot; /&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/212960/1612977329215-dd77aa48-9d17-4da2-b165-4a75d22b553d.jpeg#align=left&amp;amp;display=inline&amp;amp;height=268&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=Snapseed.jpg&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=3377677&amp;amp;status=done&amp;amp;style=none&amp;amp;width=357&quot; alt=&quot;Snapseed.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/212960/1612977248124-81d90ae4-25a1-4189-a6b4-7346c6ee03ea.jpeg?x-oss-process=image/auto-orient,1#align=left&amp;amp;display=inline&amp;amp;height=497&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=IMG_7758.HEIC.JPG&amp;amp;originHeight=4032&amp;amp;originWidth=3024&amp;amp;size=2363658&amp;amp;status=done&amp;amp;style=none&amp;amp;width=373&quot; alt=&quot;IMG_7758.HEIC.JPG&quot; /&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612977150906-128c1394-0461-4f76-ba0b-69da3ecdd91e.png#align=left&amp;amp;display=inline&amp;amp;height=499&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=4032&amp;amp;originWidth=3024&amp;amp;size=18457036&amp;amp;status=done&amp;amp;style=none&amp;amp;width=374&quot; alt=&quot;image.png&quot; /&gt;
好些时候我也不知道我们在吃些什么，反正很奢靡就对了~
&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/jpeg/212960/1612977261629-fa05452b-8587-4e75-a567-691e67e5ae78.jpeg#align=left&amp;amp;display=inline&amp;amp;height=279&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=IMG_7903.HEIC.JPG&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=2156173&amp;amp;status=done&amp;amp;style=none&amp;amp;width=372&quot; alt=&quot;IMG_7903.HEIC.JPG&quot; /&gt;
我们是一个很有温度的团队，会有譬如冬至一起吃汤圆的小美好～&lt;/p&gt;

&lt;h2 id=&quot;啊我股票赚钱啦&quot;&gt;啊，我股票赚钱啦&lt;/h2&gt;

&lt;p&gt;这一年的股市行情，要想不赚钱才要本事。。。深切感受到站在风口上，猪也会飞，相比18年买股票，各种研究操作才能在那个大趋势下跌的行情下少亏一点，今年这是只要买的是还不错的公司，躺在那里就能涨，握住了就是赚钱。。。才进去2个半月，都能收益近50%。。。这么一想，今年这买房或许是影响我收入的一个操作呢。&lt;/p&gt;

&lt;p&gt;18年初入股市时，还正儿八经学习了很多投资理财知识，那些专业的经济学内容基本忘光了。不过见识过18年的大跌，绝对的心态稳如狗，涨跌都比较淡定，绝对不影响正常生活的心情。对这玩意，我的理解是大的政策趋势要有感知，譬如大放水带来股市的大涨是必然性，这时候入就对了。至于买哪个，看看市场上，越大的公司会越发展越顺利，“垄断”自然吸引的利润，所以买每个行业的龙头总没错，再直白点科技公司目前还在发展的黄金期，想象空间大赚钱快。至于板块，看不明白，不懂不操作。然后一些危机时间，譬如拼多多员工猝死、阿里监管，这种会在一定时间内影响股价，注意到了及时跑路，跌一波在较低未知再买回来，或者没及时注意到没来得及跑也没关系，好公司整体会是向上的，短期波动持有住会过去的。然后市场很多时候炒的真的是情绪，譬如富途年后突然涨了4倍，这突然的猛涨有啥逻辑可分析，就是情绪啊，跟上情绪赚一笔但也容易被挂在山顶上。我这个模式一些小盈利是没问题的，赚大的是不太可能的，没有真正懂这个和投入精力去研究，还是找不到像电动车这样翻好几十倍的行情的，遇到B站这种能翻几倍的其实也是靠运气。&lt;/p&gt;

&lt;p&gt;脑子不太够用，工作之外，喜欢看一些说人话的东西，譬如香帅的2021财富报告，还打算看香帅的金融学讲义，公众号刷的勤快一点的是招财大牛猫的，说人话有温度。学点理财总是没错的。&lt;/p&gt;

&lt;p&gt;其实吧，这些收益倒真不用开心，现在股市涨幅只不过是大放水带来的通货膨胀在资本一线股市里的提前反应，不跑个20%的收益，真不敢说到时候能跑赢通胀，不去做投资才是危险的。现在这个阶段主力是靠工作赚钱。&lt;/p&gt;

&lt;p&gt;看上去我的用户画像是财迷呀，咋开心的事都跟钱有关。。。&lt;/p&gt;

&lt;h1 id=&quot;2021的一些期望&quot;&gt;2021的一些期望&lt;/h1&gt;

&lt;p&gt;不打算立太多的flag，好好去做才是王道，去年那么多看上去很对的flag结果都是没完成。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2020可能是压力太大，平时生活中不愿意动脑子，解压方式是看垃圾剧，夸张程度是这一年刷的比过去10年可能都多，也染上了抖音的恶习。习惯一旦染上，长期的积累对时间的消耗太严重了，这个坏习惯一定得戒掉。彻底远离垃圾剧，抖音每天刷时间不超过半小时。换成看轻松的书或者是打扫卫生的解压方式。&lt;/li&gt;
  &lt;li&gt;出山，这个环境里抛弃“非淡泊无以明志，非宁静无以致远”的状态了，保守型转攻击型。&lt;/li&gt;
  &lt;li&gt;一定要出远门旅行，不能放弃对生活的及时享受。&lt;/li&gt;
  &lt;li&gt;核心是，&lt;strong&gt;不惜一切代价追求技术成长&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一年我总结了一个不错的可持续的状态，可以继续保持：“想要的都要尽力，求而不得的放下执念”。&lt;/p&gt;

&lt;p&gt;真是一篇酝酿很久，长的不行的总结，看到最后的各位，你们的耐力非比寻常，新年快乐～&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://intranetproxy.alipay.com/skylark/lark/0/2021/png/212960/1612977693643-b1ff9755-5611-40fb-a7ae-8d7baadccb4e.png#align=left&amp;amp;display=inline&amp;amp;height=1512&amp;amp;margin=%5Bobject%20Object%5D&amp;amp;name=image.png&amp;amp;originHeight=3024&amp;amp;originWidth=4032&amp;amp;size=16240387&amp;amp;status=done&amp;amp;style=none&amp;amp;width=2016&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>致未来</title>
   <link href="http://echizen.github.io/think/2020/06-26-future"/>
   <updated>2020-06-26T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2020/future</id>
   <content type="html">
&lt;p&gt;最近我开始畅想自己最期待的未来的生活始怎样，我觉得这个问题迫在眉睫，因为&lt;strong&gt;如果连未来最期待的模样都不清晰，那现在的努力又是为了什么呢&lt;/strong&gt;。我希望我想清楚未来最向往的生活状态，以终为始，在面对选择时不那么犹豫，在需要站出来时多一些勇气。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;在这个世界活一遍，从出生到死亡，我们究竟是为了什么而活&lt;/strong&gt;。我并不纠结我从哪里来，要到哪里去这样的经典问题，但是我却很想看清楚生命的意义在哪里。这个问题我也问了很多身边的人，听了一些别人的答案。这也是我首次把一些想法写在公开的站点上，而不是私人播客里的原因，我也很好奇别人的答案。&lt;/p&gt;

&lt;p&gt;于我来说，很久之前我就清楚最追求的是自由，但是自由的维度很广泛，没人能绝对自由。财富自由不及精神自由，精神自由需要内心的平静。我希望未来，我能生活自由，倒不是跟字节28岁的郭宇那样手握几个亿去过向往的生活那样的财富自由，当然也不可能那样，没有过多的物质追求的我，财富自由不过是能维持几十年正常生活水平罢了，不买奢饰品，对一些高消费产品也没太大兴趣，虽然买房，但是房子在我心中也不是必需品，这样一看，这个自由还是有些希望的。&lt;/p&gt;

&lt;p&gt;我是一个对这个世界充满兴趣和好奇的人，我喜欢尽可能的体验不一样的内容，而且喜欢去没去过的地方，看没看过的风景，所以我理想的未来，是心动了就可以出去走走，喜欢哪里就可以住上一段时间，深度体验当地的生活，感知大地的呼吸，融入当地的人们，看到有趣的工作，就能去尝试去参与。在路上可能3年5年，不太可能是几十年，所以更好的状态不是永远在路上，而是先把想去的地方都走上一遍后，后续想去哪里，都能尽快安排起来。我还会再收养一只流浪狗，去哪儿都尽量带上它～我也可能累了就在一个山川秀美的地方盖个小小的房子，养花种草，并进行着自由职业维持收支平衡。&lt;/p&gt;

&lt;p&gt;有那么多的地方想去看看，想去雪乡看童话书中的房子和灯笼，找到童年的感觉～想去长白山看天池，看这个在火山口上的湖泊～想深度体验318入藏，想听听那么多骑行入藏的人的想法和经历，想去纳木错湖边坐一下午发一下午呆，还是最爱高原了，雪山，草甸，湖泊，一切都让我觉得世界无比干净，心灵无比平静～想去看婺源的油菜花，日本的樱花，林芝的桃花，伊犁的杏花，荷兰的郁金香，好好体验一望无际的花海～想去探访那么多少数名族的村寨～想去北极体验极昼极夜，日不落和夜不尽是一种怎样的体验呢，想去住冰屋～想去芬兰看极光，虽然从别人的图片来看并不是很好看，但是没见过就想去看看～想坐邮轮去太平洋上感受一望无际，与世隔绝～想去京都，喜欢日本的精致～想去北海道泡温泉～想去东南亚群岛，穿着各式各样的裙子走在海滩上，躺在日光浴下，住在海景房里，体验影视作品里拍烂了的画面～想去非洲大草原，想看自由的长颈鹿和大象们～想去冰岛，去看看ling镜头下那些唯美的画面～想去瑞士，躺在雪山下的草甸上～想去大理生活一段时间，我是真的想去洱海边开客栈，去过那么多地方，还是很爱大理～想再去高原看星空，可以在帐篷里住一晚上，看一晚上并且没给自己冻坏的那种……还有那么多我叫不上名字的地方，还有一些我还未发现的地方，一些造型奇特的桥梁，一些山巅，一些湖边～&lt;/p&gt;

&lt;p&gt;还有那么多的事想体验，想去蹦极，虽然我觉得我可能只会享受跳下去的那一瞬间，而不喜欢被荡来荡去；想去跳伞，想去玩滑翔伞，我可太向往飞起和高空跳下的感觉了，哪怕一会儿也很好呀；想去潜水，看看海底深处；想去冲浪，感受浮沉；想去跳舞，虽然从来没学过，可是看到别人跳舞就很心动啊；想学乐器，虽然我还没有任何乐理基础；想去一起徒步沙漠，体验一下在一望无际的黄沙中走上几天的感觉，这样的情景会逼出自己怎样的想法呢，还是要带上骆驼的；甚至，想去在向导的带领下爬雪山，能爬多高爬多高；一直热爱自然风光，我也想更多元的体验，去体验人文，去看看那么多人类留下的遗迹，去看看那些博物馆，去看看那些艺术品；去看那些经典的书，优秀的影视作品，去读前人给我们留下的东西……我觉得我想做的足够塞满我一生了，只是差自由，财富和时间自由支持我去做这些。&lt;/p&gt;

&lt;p&gt;世界上有这么多职业，我也很乐意去参与更多种工作，从体验出发，去发掘每种工作的乐趣和烦恼，或许再更丰富的深入体验后，我能迸发出一些想法，我能做些什么，让这个世界更美好，也能有些收入让我继续去更多的地方，体验更多的生活。&lt;/p&gt;

&lt;p&gt;生活的意义一定不止是去旅行去享乐，还是需要有自我价值的实现。我还不清晰我实现那些价值的方式是什么，正是需要我去看更多，接触更多，才会有更具体的想法吧。最基础的，一路上我都会以文章、照片、视频记录这些，留给自己回忆，也让大家看看世界还有这么多美好，生活还是可以选择的。在这个过程中，我希望能通过更多的体验，认识更多的人，参与更多的体验，拓展自己的认知，这种认知不来源于书本文字教育课程，而来自生活体验，必定更真实深刻，我也希望改变自己一些执念，也能影响我的性格，也许以后我能慢慢开朗健谈活泼起来呢。长期来看，我可能更可能成为一个自由职业者。&lt;/p&gt;

&lt;p&gt;对于生活的意义，大部分人都是希望活过留下些什么，很多人会把爱倾注在孩子身上，通过孕育教育一个生命，把最好的留给他，塑造最美好的他，来延续自己的意义，孩子记得自己，自己就还一直或者。我能理解这种想法，但是我本身无法拥有这些想法。我希望自己能做一些让世界更美好，让生活更美好的事情，往大了可能是一些让世界更美好的产品，往小了是对身边的人，带给他们温暖。这个商业社会，再牛逼的公司，我对他们这些充满盈利性的产品和事情不是非常喜欢，更喜欢情怀一点的产品，能力不够，如果像大佬们那么有能力可以更自由的化，我很愿意去做一些不以盈利为目的的事情。&lt;strong&gt;人这一辈子，会为生存做无数利益化的东西，但是希望可以做那么几件完全不以盈利为目的的东西，也希望给这个世界留下一些可以存在的美好，希望更多的人去留下美好，不为盈利为传承的话，留下的东西，它们将是人类文明真正美好的东西。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这些，大多数人都会怀疑能做到不，但是我看到一些人活成了我向往的样子，譬如读书时就一直关注的王源宗大佬，他所选择的生活所做的事就完全是自己向往的，当然他把热爱做成了非常成功的事业，即实现了财富完全自由，过着喜欢的生活，还留下那么多美好的延时摄影作品，更是出了8kRAM这种更有价值的平台，这个不是每个人都能实现。平凡一点的，譬如知乎的肥虫。他们的存在，让我觉得这些不是幻想，问题是有没有毅力和勇气选择这样的生活方式，它有更多的不确定性，它的未来没有保障。&lt;/p&gt;

&lt;p&gt;我越来越确定，我不想要普通的生活，不是说平凡简单的生活不好，而是每个人喜欢的东西不同。我无法接受自己在30岁前完成结婚，30多岁生个孩子，然后对孩子的青春倾注大部分精力十几年，慢慢从职场退出，这12年里大部分时间都是孩子的成长教育和家庭琐事，然后关心上学问题、学区房、文理科、大学专业，孩子长大了又开始担心他的工作，谋划他的结婚生子，看着他继续循环自己的一生。我理解这个过程中肯定有很多温馨感动的时刻，但是他需要我倾注太多精力了，让我想做的都做不了了。&lt;strong&gt;在应该做什么的年纪做什么，似乎在按人生课表进度生活，虽然我也理解这个课表是亿级人类共同制造的，对于大多数人来说是最优方式&lt;/strong&gt;。但是总会有异类，譬如我。所以我的规划里没有孩子这个组成部分，一方面我对孩子没那么浓厚的喜爱，虽然他们天真可爱，但不足以支撑我的人生乐趣，我无法做到全心全意为他，没法以他的喜怒哀乐为我的喜怒哀乐，我妈妈为我做的那些我觉得我没法为孩子做到。而且我的选择，如果有孩子就要把最好的给他，全身心爱她，毕竟是一个生命，是一个孩童时代完全受自己影响的生命，但这个时间和精力成本我真的付不起。而且人生太短暂了，我30-50岁的时光，有了孩子的责任，以上的向往都无法实现了。没有孩子，我只用负担好照顾父母的责任，自由的灵魂和不稳定飘荡的生活承受不了孩子的责任了，我非常清楚这一点。至于另一半，遇到了，可以一起去体验这些自然是最美好的，可以快乐有人分享，孤单有人消散，可以一起交流碰撞更多的思想，但是如果运气不好，一个人也没关系，将就一下没有共同爱好和匹配的三观，在一起也并不会让生活变好，这些未来我一个人也是ok的。当然还是希望自己运气能好点，能有人一起去看世界～&lt;/p&gt;

&lt;p&gt;从未来再回到现在，现在的选择就更清晰了，我还是不会改变多少，会尽量做好正在做的每一件事。朋友会反问我你这么向往自由，追求快乐，可你现在这么不自由这么累，现在我可以很淡定的回答，这是沉淀，最近的知乎热门话题，32岁前端找不到工作跑滴滴，就很现实，我们&lt;strong&gt;当前处在非常好的职业上升期，自然要奋力一博，现在的努力是投入产出比最高的。没有啥商业嗅觉投资致富，也没能在早些进入一些潜力企业手握大量期权坐等上市的普通程序员，现在努力工作依旧是最好的选择，短期的辛苦是为了未来自由的成本&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;但是无论以后要经历什么，都希望自己永远保持自由的灵魂，善良的心，以及对世界和生活的热爱。回顾到此的生命历程，都还可以，拥有非常自由上山摘野果下河摸鱼的童年，只是有点可惜没有条件在有足够时间和精力的时期学些感兴趣的东西，不过也不像现在孩子这样被各种作业辅导兴趣班的压力摧残。高中时学习尽力了，虽然高考发挥失常考了最差的一次，但是也不至于是遗憾。当初杠父母自己选了大学和专业，现在才在杭州，才是一个程序员。大学接触并喜欢编程的我，成为程序员，实在是美好的职业，特别是意外的赶上移动互联网兴起，这个职业薪酬回报也不错。日常生活工作中，也是去做自己认为有价值的事，因为热爱，所以即使工作到半夜，也没那么痛苦。一切都还ok，期待未来活成期待的样子～&lt;/p&gt;

&lt;p&gt;在我的想法里，&lt;strong&gt;我们没法选择出生，不知为啥就来到了这个世界，甚至我们可能就是科幻作品里的那样，是实验品，但是既然来了，就想多体验一些，尽可能多的和这个世界交互，多走一些地方，看一些风景，体验一些生活&lt;/strong&gt;，我非常自我化，我只觉得我有自由的意识和行动力时我才是活着的，我希望我的生活能尽量丰富一些，相比于动漫、游戏、影视剧，我更愿意把时间精力放在现实世界里，就是想多一些多元的体验。&lt;strong&gt;也许在我们的意识不止在地球上从出生到死亡这些长度，也许我们的意识更长，但我希望我回首地球上这些精力时，能说足够精彩了，这个世界我来过，体验过，努力过，爱过&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这么看看，生活还是很美好，充满动力的～ 希望10年后20年后，回头来review下这些是否都已实现或者正在实现，希望等到60岁时，我能清晰的回答现在还没法回答的生命的意义，能清楚的知道自己这一辈子活着的意义。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>2019-生活虐待了我，我依然热爱生活</title>
   <link href="http://echizen.github.io/think/2020/02-09-2019-summary"/>
   <updated>2020-02-09T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2020/2019-summary</id>
   <content type="html">
&lt;p&gt;还是在春节假期正儿八经的坐下来写一篇年终总结，虽然又拖到除夕了。之前总觉得留着假期在家写比较好，心情平静可以客观的回顾这一年，但是现在貌似我的心情也没多平静。。。&lt;/p&gt;

&lt;p&gt;这篇想多说一些故事，多记录一些可爱的人们，我想几十年以后我记不得这年都干过什么了，但我希望我能记得经历的那么几个重要的事情，记得当时的这些感受，记得自己从生活中品味出来的味道。还有这些可爱的人们，是TA们，让生活有温度。&lt;/p&gt;

&lt;h1 id=&quot;一些故事&quot;&gt;一些故事&lt;/h1&gt;

&lt;h2 id=&quot;父亲住院&quot;&gt;父亲住院&lt;/h2&gt;

&lt;p&gt;这一路全是心酸史，没有经验的我也走了很多弯路。&lt;/p&gt;

&lt;p&gt;我记得前两年开始我就担心家里人身体健康问题，新年愿望就是家人身体健康。然而怕啥来啥，爸爸18年底有说过头疼头晕不舒服，妹妹带去医院看过一次，医生草草看了下开了点药，说没事，爸爸吃了说好了些，我也就没在意了，但是这其实就是被错过发现病况的前期。&lt;/p&gt;

&lt;p&gt;正月时我爸呕吐严重就住进去县医院，诊断肠梗阻，不能吃东西，天天输营养液，治疗了2周。因为不能吃东西，特别虚弱，爸爸打电话跟我说觉得自己不行了，医生把常规治疗方案都用了一遍不见效，当时我们说的是2周是极限，不行就动手术切除梗阻的部分。好在快接近2周时好转了，这一遭我爸身体非常虚弱，抵抗力也大降。&lt;/p&gt;

&lt;p&gt;我以为这件事就过去了，回家好好修养就好。结果没过1个月，打电话来说头晕厉害，要看医生。当时我心里还没有特别在意，事实是父母那辈一点小事根本不会随意麻烦我们，求助时都是已经很严重了。我爸说希望尽快，根据症状，约了个神经内科下周的特需号的医生。等我爸到杭州，见到我爸时才看到事情严重性，人走路都走不稳，还拎着一盒土鸡蛋给我说家里的很营养，泪目。但是我又是没预料到医院设备的紧张，排颅脑核磁共振就给安排到一周后，就这么安排我爸在杭州住一周等待拍片，其实这里做的也不对，基础检查没必要先去邵逸夫这种神经内科特别出名的医院，因为慕名而来的患者太多，医疗设备排不上，不如先去普通医院把基础检查做了在去大医院挂专家号看诊。等待期间带我爸去周边绿城心血管医院看了一圈的心脏、脊柱，都有点小毛病，但不至于造成他这么严重的症状，也看到了小一点的医院基本不需要排队，很多检查项当天就能做，心里暗暗骂了自己蠢。爸爸在身边的每一天都感觉他的身体每况日下，自己查了资料，也知道很有可能是颅脑肿瘤，可能恶性可能良性。所以核磁共振结果看到听神经瘤，就有心里准备不慌了，好消息是良性的，但是坏消息是肿瘤很大了，50mm*48mm，非常危险，赶紧挂了神经外科的专家号，咨询手术的事，建议是尽快手术，很危险，颅压过高随时都可能脑疝，但是手术要等床位，床位可能在一周后能排上。&lt;/p&gt;

&lt;p&gt;当时内心只想早点安排上手术，天真的我以为本省的安徽省立医院能快点，毕竟没邵逸夫的神经内外科这么有名。结果去了安徽，亲戚帮忙找了一个这方面的专家A，专家说要排上住院1个月后，手上病人太多。。。当时真是很绝望，因为知道爸爸随时有危险等不了，其实医生也很无奈，患者家属早点不发现病情，发现了就急成这样一分钟不愿等。这时亲戚又帮忙联系了一个专家B虽然不是听神经瘤方向的但也是颅脑手术专家，但是可以早点安排手术，1周左右就可以入院。然后就在专家B这里挂了号办理住院预约，回杭州我也继续在邵逸夫办理了住院预约，打算哪里快就来哪里，最终杭州比安徽的快了一天，但是家里人考虑路途、护理、医疗报销还是在选择了安徽。&lt;/p&gt;

&lt;p&gt;在预约住院的当天，杭州的主治医师就拉着我详细的说了我爸肿瘤的情况，告知了手术方案，风险。因为肿瘤太大，手术奉献很高，严重的可能植物人，治好了也有一些后遗症。这里我想说真的是很喜欢杭州这样的大城市，医疗公开透明，至少能排上队被安排治疗，不需要各种暗门，而且遇到的这么多医生态度都很友善。而老家那边如果不是亲戚帮忙不知道要什么时候能排上，会有多绝望。而且在杭州当天主治医生就把所有情况都跟我讲了，了解清楚后踏实很多，安徽的A专家是话都不愿意都说两句，一直很不耐烦的爱等不等，我很忙，B专家是手术前签字才听他助手讲解了手术风险这些基本情况。。。&lt;/p&gt;

&lt;p&gt;知道风险很大，内心还是有点难过的，觉得万一是不好的结果，我爸得多可怜，操劳半辈子还没享受生活就先这样了。打电话给我妈说时，当然这时没敢说风险很大，我妈就哭个不停，我只能安慰，告诉她别怕，咱治好就好。这时候我真切的感受到我是我们家的主力，承载着对她们的责任。&lt;/p&gt;

&lt;p&gt;如期住院，入院检查并观察1周后安排手术，手术7小时很顺利，肿瘤全切除，到此一切都挺顺利。&lt;/p&gt;

&lt;p&gt;但是事情还没有结束，这个挫折没这么快跨过。术后我爸发烧，连续烧了4天后，医生判断是术后脑部感染，手术风险时介绍过，但是没想到小概率事件也发生了。要安排腰大池引流，就是脊柱打孔穿进一根管子将脑脊液一点点的引出来，人体会再一点点生成新的脑脊液，起到置换作用。一般3-5天引流就会起作用，炎症消除，但是我爸整整引流了14天，在重症监护室里度过了14天！这是我爸生病以来我度过的第三段焦心的时期，也是最担心的时期，因为术后疼痛我爸本身状态很不好，精神不佳，跟我说痛的不亚于死了一次，我爸本是个非常坚强扛疼的那种人，我以前见他腿上烫伤皮肉模糊都没喊疼，现在说这话我知道是痛苦的受不了了。另外通过查资料我也知道这个脑脊液引流非常危险，不仅很痛，而且二次感染风险很大，我对医院也有点不是很信任，因为手术那样无菌的环境7小时内都感染了，我很难相信术后无菌条件并不好的术后监护室能不感染。其次是时间太长了，14天炎症才消除撤掉引流，这期间实在是煎熬，一方面我爸在里面插管不能翻身不能动弹，身体受着折磨，一方面是家人在外面担心风险担心病情，心理折磨，一家人都是筋疲力尽，如果说之前我都很淡定，照顾着大家的情绪，此时我已经控制不好自己的情绪了。&lt;/p&gt;

&lt;p&gt;所幸，结果是好的，炎症消除，1个月后顺利出院，恢复的也不错，虽然有些后遗症，但不影响正常生活。&lt;/p&gt;

&lt;p&gt;在医院见到了不少的人间冷暖，有父亲绝症，放弃自己的一切回来陪着化疗，即使看不到希望，也陪着一起走下去的，我都不知道能安慰啥，一切话语都显得好苍白；有个中年妇女病了，丈夫和孩子很少来看，自己60多岁的母亲来照顾，60多岁的老人连自己都照顾不好，我妈我都没教会她自己独立处理医院的检查和生活，可想老人面对医院有多茫然，最开始两天我还尽力帮着点，后来我发现我帮不了帮不完……;还有老奶奶病了，危重病人，孩子不来探望，打几个电话都是要钱，照顾的老爷爷依旧每天很乐观面对，一面照顾自家危重病人，一面鼓励其他病人家属；还有手术中进了谈话室出来后大哭1小时的病人家属…… 在医院里我有空闲时间时，会帮助我能看见的需要帮助的人，也认识了一些很有爱的人。怎么说呢，会有一些很冷漠的存在，但是大多数人都是善良而美好的，所以所生长的这个社会才这么可爱。&lt;/p&gt;

&lt;p&gt;这件事，给了我非常多的感触。首先，任何事情都要自己掌握信息才能安心，虽然专业的事教给专业的人去做是最有效的，但是也要有自己的判断，不能过于相信。我是尊敬医务工作者的，但是他们每天这么忙难免有疏忽的时候，医生中也不是每一个人每一分钟都能正确判断尽职尽责，但是你的家人，生命只有一次。譬如我爸爸在18年底去医院那次，也是挺有名的安徽医科大学附属医院神经内科的专家医师就误诊，没查出病因，去医院前自己先查下资料，对一些要做的基础检查做些了解，譬如我爸这种靠人眼看看，很难看出问题所在，必须拍CT、核磁共振才能发现问题，这些基础检查是必须要做的。其次，医患的关系紧张是有原因的，当然我是痛恨一切医闹的，医生因为繁忙和患者家属情况各异，不主动说清楚病情细节是可以理解的，但是家属主动询问时，特别是我们这种住院多时讲道理的家属，能否多透露一些详情，而不是总是安慰性的说我们在尽力，这些症状都还正常，情况发展因人而异这些话。我一度通过查看前一日我爸的医药清单（医院系统这个功能本是给家属核对扣费信息的）来了解我爸前一天用了什么药，都是治疗什么的，来反推我爸病情，还有就是每天找护士看一些基本信息，体温、脉搏、血脂、脑脊液白细胞数这些，但是基本都没能在查房时从医生那里获得什么有效信息。患者家属内心的各种不安和推测，以及对医护人员的不理解其实就是来源于这种信息不透明对等造成的。非常现实，我没有任何渠道了解我爸当天的病情到底怎么样了。直到在我爸的出院小结里，我才明白术后那14天我爸出现的不仅仅是颅脑术后感染，还有那么多的并发症，我也相信医生们都很尽力在用一切可行方案治疗，救了我爸一命，我也很感激。只是医院关于患者病情的信息透明真的还可以做的更好。&lt;/p&gt;

&lt;p&gt;以前对亲戚我都是没什么感觉，只觉得因为血缘关系硬凑在一起，也没啥可聊的，逢年过节办喜事时的走动更是形式化，我一度不明白为啥需要和亲戚保持联系，亲戚有啥用。但是经过这件事后，我的感受完全不一样了，我爸生病期间，受到了太多照顾，有熟悉医院的亲戚帮忙联系医疗资源，有在合肥的亲戚住院期间常来探望，安慰我爸妈的情绪，有老家的帮忙照顾老家。我爸手术那天，更是来了近10名亲属陪同，让我很惊讶，一下子就心安了，万一有啥不好的情况出现我也不会手足无措了。亲戚是一种多么神奇又有人情味的存在啊，在老家就是这样，谁家都会有遇到挫折的时候，但是有亲戚的照料，会方便很多。非常感恩。&lt;/p&gt;

&lt;p&gt;不过在医院真的是太无力了，就是你明明很担心，但是啥也做不了，只能干着急，这种感觉非常不好，而不是其他事情，努努力就能办到，医院这里主观能动性为0。然后现在虽然医院里环境好了很多，但是还是有各种不方便。珍爱生命，注重健康，远离住院吧。&lt;/p&gt;

&lt;p&gt;以前遇到挫折，我都会跟自己说没关系，吸取经验，获得成长，这只是一个考验。但直到现在，我还是不能从这段经历中获得些什么，我不能理解身边的同龄人还在父母的守护下愉快成长时，我经历这些，成为负重者，我有什么收获，无法安慰自己这是段有价值的经历。甚至很长一段时间让我草木皆兵，非常没有安全感，都有点创伤应激性反应了，可能像医院另一个小姐姐说的那样，这些遇到了没有什么道理，世界从来不是公平的，我们遇到了只能接受并承担。&lt;/p&gt;

&lt;p&gt;成为家里的主力军后，一方面欣慰自己能为家里做点事情了，一方面开始担心有一天自己出意外了，父母怎么办，所以开始有了保险意识，医疗险、重疾险、意外险这些都给自己备上。这也是我与保险的结缘之始吧。&lt;/p&gt;

&lt;h2 id=&quot;去蚂蚁&quot;&gt;去蚂蚁&lt;/h2&gt;

&lt;p&gt;跳槽前，我对自己的知识体系做了梳理，对自己做过的事做了总结梳理，这个过程还是很重要的，是对自己前2年半的复盘，复盘结果还是挺满意的，感谢自己一直折腾，一直前进。&lt;/p&gt;

&lt;p&gt;目标阿里，这就是开发者向往的圣殿，是技术的认证。不过我之前并没想好要去哪个部门，要去做些什么，我是清楚了我为啥要离职，但是其实是没清楚自己想去阿里干些什么的，这个问题其实很关键，跳槽前应该想清楚。&lt;/p&gt;

&lt;p&gt;经过知识体系的梳理，和做作工作的复盘，前后面了2个阿里的BU，面试过程都还是挺顺利的，一度有点小膨胀，觉得自己挺好，（这也让我以为自己一进入蚂蚁就能发光发热，和后来的适应生活形成巨大反差。。。）。这里非常感谢前一个面试BU的主管，在我拒绝offer时，推心置腹的跟我聊了非常多，给了我蛮高的认可。当时我内心非常纠结去哪儿，其实不进去体验，仅凭面试交流，很难断定哪里更适合自己。不过对于所要去的团队有什么业务和技术项目，自己进去会干什么，这些真的是要认真仔细的聊，聊的越清晰越好。当时我是有点凭感觉选择，不够理智的，因为我学长去了蚂蚁保险，而我虽然一心想做基础建设，但是在蘑菇街后1年，深感离业务远了的基建没有成就感，想去感受下业务，想看下业务高速发展期有那些技术需求，而蚂蚁保险正在蓬勃发展，特别是相互宝业务，很吸引人，加上面试官都非常有水平有人格魅力，我学长也选择了这里，我就在没聊清楚自己来了后做什么，这里究竟是怎样的情况下，也选择了这里。除去工作内容和强度了解不够外，其他信息也确实如期，业务蓬勃发展，处处都是技术需求，只要你有时间做，同时们都是大佬，很有水平。遗憾的就是很向往玉伯那边做基础建设的团队，当时自信心不够，没敢去投。其实面对自己向往的团队，大胆去投，蚂蚁真的是求贤若渴，特别缺人，即使没通过，1次也不会影响啥，还有很多去其他部门的机会。
伏枥近3年后，迎接蚂蚁新生活。&lt;/p&gt;

&lt;h2 id=&quot;在蚂蚁&quot;&gt;在蚂蚁&lt;/h2&gt;

&lt;p&gt;来蚂蚁工作，挑战还是非常多的，不同阶段也不一样。&lt;/p&gt;

&lt;p&gt;反思一下，是入职前心理建设没做好，对自己会面对的内容没有预期，只是以为工作强度会大一些。加上之前在医院的1个月基本荒废没写啥代码，入职时特别不适应。&lt;/p&gt;

&lt;p&gt;入职1周后，就投入正式工作了，团队业务发展很好，需求爆发，人员不足，工作强度非常大，基本上是以前的2倍，刚开始自己又不熟悉，前3个月过的非常艰难，非常直白的说就是活干不完，也不是说多难，就是我评估10天的内容，但是给的时间只有5天，速度跟不上。我记得有那么三四次，下班就直接崩溃，坐车回家路上眼泪就刷刷刷的那种，还有回家一个人在房间里不开灯，崩溃发泄一通的那种。回头想想，也没遇到啥大事，就是连续加班太久，情绪不稳定。自己是个很注重履约的人，当遇到自己评估做不完但是必须做完的事情时，压力很大就会崩溃掉。不过不论前一天晚上多么崩溃，第二天都正常上班，所有的节奏都按时推进。前三个月我也是无数次想逃离，觉得自己还是个热爱生活的人，需要这么跟自己杠么。但是让自己不离开的，一部分是觉得就这么逃了太怂，以后没法跟自己和解，另一部分是虽然苦，但是对自己现在做的事和对蚂蚁和对相互宝这个业务的认同，还有对自己牛逼同事们的景仰，希望提高自己能力。&lt;/p&gt;

&lt;p&gt;遇到转折点是一个心智之战的项目，非常大的一个项目，我觉得是1个月才能搞定的，最后半个月就开发完了，这个项目中我中间断了一天外，连轴了21天，中间因为太累了半夜还发了条朋友圈说不知道自己在干什么，不知道自己为了什么，丧的不行。&lt;/p&gt;

&lt;p&gt;那个时候，我基本就是压到临界点了，我跟自己对话了一下，是走还是留，走去哪里，留为了什么。最后综合各种原因，我选择了留，并且跟自己约定，留就坚定，不再吐槽，开始接受，对于不好的要么改变，改变不了就先接受，很多事情就是这样，有能力就去改变啊，改变不了又不愿意接受不就是逼自己不舒坦么。其实这是个心态上的积极转变，在大学培养的自由的意识，刚来时和阿里文化是有一定冲突的，有这么一个融合的过程。&lt;/p&gt;

&lt;p&gt;心态改变后，我感受到了很多之前感受不到的东西。譬如心智之战项目里，大家做到了我之前认为跟笑话一样做不完的事，我看到了一个战役下来后，leader们是如何排兵布阵，集中人力资源排解万难的把事情做完。整个项目挑战都很大，特别是对前端团队，时间紧凑，但是leader们把节奏抓的很好，关键节点都能如期推进，有条不紊。项目成员更是互相补位，互帮互助，我所负责的子项目因为变更多是重灾区，期间收到大家特别多的支持，不只是精神上的鼓励，而是真的进入场景帮我完成一些需求变更的。我看到了同事们的奉献和担当，一件事不论是谁负责，大家追求的是把它做完做好。这件事让我看到了阿里的执行力，这么牛逼的一个公司，其实跟一个人一样，从来没有轻易的成功，都靠奋斗的结果。牛逼的公司，牛逼的事情，背后是一个个奋斗的个体。这个团体的战斗力是如此之强，让我心生敬畏。&lt;/p&gt;

&lt;p&gt;心智之战里，我所负责的子项目部分遇到非常多挑战，工作量本就很大，然后遇到一系列变更，提测前一个星期，基本每天傍晚产品经理都来说有需求变更，最后复盘时发现有20多个变更点。在时间紧张的情况下，每一个变更都带来挑战。然后还有合作方数据算法同学数据提供不及时影响联调进度的挑战，还有这是个复交互的项目但是自己没有缺乏动画类开发经验，最后兼容性测试时低端机性能和兼容性的挑战。我在扛住了绝大多数挑战后，最后求助组内动画开发的同学，帮我搞定了低端机性能问题。在这样的节奏下，最后上线后没有任何故障，保障了稳定性。这让我看到我虽然会有崩溃的时候，但是还是能抗压，能扛起来的，也看到了身后的团队，大家追求的是把事情做好，当因为一些客观原因自己这边压力过大时，也不必死撑，身后有团队的支持。&lt;/p&gt;

&lt;p&gt;我leader跟我说，虽然经历时很痛苦，但以后你回头看，这是一个可贵的经历，毕竟1年也可能只遇到一次这样量级紧迫度和挑战度的项目。并且问我回头看还有哪些可以改进的，我回头想想，没有太大的可以改进的地方，一方面是当时我尽力了，所做的都是自己能做到最好的，一方面是我该成长的，当回顾时找不到可以进步的地方，说明自己还没进步到下一个阶段。&lt;/p&gt;

&lt;p&gt;前四个月我基本是一方面硬扛需求，一方面去适应，让自己和阿里的环境氛围节奏适应。我几乎没有任何精力处理工作以外的东西，也没有什么拿的出手的技术产出。&lt;/p&gt;

&lt;p&gt;后面的时间，随着更适应了，也更熟悉了，工作效率自然提高了，会有一些时间拿出来干些业务需求以外的事情了。回头看，一开始还是太急了，急于发光发热，然而自己连节奏都不能跟上，有句话说的好：来蚂蚁时想着要改变世界，来了后才发现最需要改变的是自己。其实更需要的是做好心理建设，像我主管跟我一直说的但我一直没当回事，前6个月好好适应环境熟悉业务。&lt;/p&gt;

&lt;p&gt;但是第二阶段的挑战是，时间有了，你不能浪费，总得干出点什么来。这方面我做的不好，我参与了一些技术项目的共建，但是只是局部于共建，领一个功能区域，吭哧吭哧用余下的时间搞出来，会有些自己的想法跟大家交流，但是都不是什么颠覆性的想法，并没有达到“事情因我而不同”的境界，对此是不够的。&lt;/p&gt;

&lt;p&gt;我还花了不少时间做了很基础的事情，都是解决自己工作中遇到的不方便的地方，如小程序调试非常不方便，特别是组件数据查看，开发cli工具结合ide加了自己的dev模式，查看组件数据和diff出数据变更；在老的chair的node项目中引入ts，支持混编式开发流程；根据内部b端node应用http服务的特点，封装node基础插件，支持和c端rpc服务一样的使用姿势；还有一系列业务开发中封装的基础组件。这些都非常基础，也耗时间，但是就是一个当前层级开发应该做的日常工作，做了拿不到什么结果，因为比较零散，影响面和作用有限。但是呢就是忍不住会去做，我觉得开发进阶之路就像爬山，心中的目标是山顶，但是一路会遇到各种荆棘和绊脚石，横着的树木，可以选择被刺过，越过绊脚石，绕过倒下的树木，冲着目标，快速登顶，会受伤，但是总会过去的，总是走过了，这样的速度最快。但是我想选择一步步，把能砍的荆棘砍掉，把路中间的石头搬掉，把倒下的树木扶正，能铺出一条更好的路最好，没能铺出路之前也希望清理路障，让自己走的开心，让后面走的人不再遇到障碍。就是很追寻匠心这种风格。&lt;/p&gt;

&lt;p&gt;最近离开了相互宝业务组，来到了业务稍微不那么紧张的一个组，开始接手一些体系化建设的内容，回到了熟悉的领域，我以为一切做起来都会挺轻松。然后才发现面临的是另一种挑战，需要贴着业务场景做些体系化的建设，如果是从0开始，自己知道怎么做，但是时间不允许，也不允许重复造轮子，所以需要了解集团内已有的各种相关内容，然后在他们的基础上拓展业务特色的内容，并且打通各个系统上下游，需要去熟悉各种上下游系统，对于我这还不是很熟体系内内容的员工，还有比较闷不去积极跨部门沟通的人，挑战还是很大的。目前回头看，全是可以做的更好的地方，需要更勇敢开放一点，去提前规划，主动沟通推进。要提高的还有很多，加油吧！&lt;/p&gt;

&lt;p&gt;在蚂蚁的日子，我从最开始各种不适应，自信心完全摧毁，觉得自己像个废人一样啥都干的没别人好，也没啥特色不可替代的地方，到接受忘记过去，从0开始，开始发现自己可以把一些事情做的不一样，慢慢找到希望，满满重塑信心。未来会很长也会很难，自己软肋很多，需要提高的各种综合能力也很多，勇敢，开放，奔跑吧。&lt;/p&gt;

&lt;h1 id=&quot;技术&quot;&gt;技术&lt;/h1&gt;

&lt;p&gt;今天的技术进度条基本都是靠前半年拽动的。&lt;/p&gt;

&lt;p&gt;感受最深的是花了一些时间读webpack、vue、react的核心代码，读了之后特别踏实，之前一直再用，偶尔debug时翻翻部分代码，没有系统的去了解，总是觉得工作3年了，让我写个双向绑定我还写不出来，但是读了之后，对其思路和实现都比较清晰了，虽然说这种对于自己工作应用上没有直接效果，但是很多东西都清晰了很踏实，如果不是理解这些，我牛逼的学长以前也做不出那些创新型的东西，去熟悉这些还是个必要的基础吧。虽然说是对工作没有直接作用，但是对面试是真的有用，可以滔滔不绝的说的很彻底，看到面试官看你的眼睛都是发光的。。。&lt;/p&gt;

&lt;p&gt;罗列下来，大概这些吧：
• 进阶知识更加巩固，年初跳槽季，对自己的知识体系进行了系统梳理，并趁此时机，阅读了webpack、vue、react的核心代码，搞清楚了一些关键技术的核心实现，并沉淀了相关的15篇文章：http://echizen.github.io/a_tech。对这些内容可以说是有了深刻见解，并能自己简单实现。
• 对于vue转小程序工具vx，仔细研读了js框架部分，除了自己服务的转化和打包部分，搞清楚了整体架构，能像所有内容都是自己做的一样完整解释和实现它。
• 蘑菇街年初高峰离职期，接手了近10个内部的基建设备的工具、平台，搞清楚了蘑菇街包括vue页面打包构建、前端发布系统、代码搜索平台、eslint校验拦截系统等，基本上蘑菇街的前端建设和技术都已基本搞清楚了。
• 在蘑菇街和运维团队打造了部署在docker上的node应用发布体系，对docker有了更多了解，对node的进程管理，应用部署也有了更多了解
• 对监控产品的熟悉了解：在蚂蚁，参与了蚂蚁前端监控平台的开发，了解了监控产品的设计架构，了解了数据前端的内容（但是停留在了解层面，没有实践）。并在后期，参与了组内先知巡检系统的开发。加上日常工作中的clue、xflush的使用，对完善的监控系统有了体感。
• 通过对vm.js源码的阅读调试，掌握了js解析执行js的能力，之前只知道怎么从字符串解析成ast，再ast修改成另一种语法，然后new Function的方式执行。现在明白了怎么用js去直接执行ast，获得运行结果
• ts能力的加强，学会了很多高阶用法实现基础库的能力，并探索了chair应用（egg在蚂蚁内部的上层框架）在ts\js混搭模式下的开发，和chair toB产品的ts良好实践。能扩展egg，加强能力。
• 通过解决工作中的问题，翻chrome、chrome devtools-frontend、electron，掌握了chrome devtools的实现，和chrome浏览器插件机制的设计实现。
• 还有很多时间花在对蚂蚁技术体系的学习熟悉上，譬如组件系统、搭建系统、监控系统，蚂蚁的技术体系是个庞然大物，这些的学习都不能说是技术上多大的成长，但是都是以后工作之本&lt;/p&gt;

&lt;h1 id=&quot;工作&quot;&gt;工作&lt;/h1&gt;

&lt;p&gt;在蘑菇街呆了几个月后去了蚂蚁，非常感谢蘑菇街，给我塑造了这么好的工作环境，简直堪比象牙塔，给了我充分的自由和空间，做了很多尝试和发展，而且这是一家有着独特风格的公司，有着鲜明的文化，真心祝福它未来越来越好。它也给予了我很多支持，让我能成为今天的我，特别感人的是，在我提出我大概率跳槽时，还是给了我晋升通道，并且评估过后晋升了，主管跟我说你走是你的选择，但是让你继续晋升是对你能力的认可，特别感动。&lt;/p&gt;

&lt;p&gt;蚂蚁呢，另一番完全不一样的体验，它很累，没那么有温度，但是它真的很牛逼，在这里真的是有更大的舞台，能做更多的事，更大的平台有更广的视角，也有一批让你学习的伙伴，在这里，即使被动都会被推着进步。&lt;/p&gt;

&lt;p&gt;工作上一件很有感触的事是相互宝一周年的新闻发布会，我作为开发被邀请到现场，看到很多媒体，看到一些平台理赔保障的患者上台和我们说的那些话，非常感人，那是我第一次看到自己代码背后的东西，都是活生生的生命，那时候感觉一切都是那么有意义，这么累的几个月，成就感不再来源于技术成长，而是所做项目的意义，相互宝是伟大有价值的，这就是平台的力量，赋予了代码生命力，我时常在想，一直追求技术进步是为了什么，写代码是为了什么，
代码的另一方面的魅力，不就来源于他对现实世界的影响么。&lt;/p&gt;

&lt;p&gt;我的认知也有了很多改变，以前蘑菇街舒适的环境，让我有一套理论，业务需求就该怎么怎么样，譬如合理排期不倒排，不临时变更需求等等，但是在相互宝，我发现倒排和需求变更都是家常便饭，一度非常排斥，觉得业务方自己没想清楚上线前改需求而且不能改发布时间是在压榨我们，因为觉得不合理，就不想去配合接这样的变更。我的主管跟我说更合理的排期和需求变更把控是我们的追求，但是我们还是一个快速奔跑的业务，很多理想化的东西推行不了，稳定的业务才会达到我那些完善的预期。“实际互联网产品，既不是敏捷开发，也不是瀑布模型，而是像沙丁鱼一样，”动态”中寻求有序，也就是其实很乱，很多业务都是这么慢慢打出来的，世界是不完美的，而很多问题，都需要时间，世界的不完美，会很难受，但是很多时候，就是这样”。这番话深深的改变了我，我觉得自己一直太理想化，心里的那个砝码就不适合当前业务现状，我开始去接受他的不完美，去以同理心接纳业务方，这样，工作也愉快一些了。&lt;/p&gt;

&lt;p&gt;整体来说，19年工作上没什么亮点，没有值得说的owner的大的专项。&lt;/p&gt;

&lt;p&gt;• 在蚂蚁，抗压能力增长，工作效率被迫提高，面对业务需求，战斗力增强
• 蚂蚁身边的同事非常优秀，人人都是榜样，吸取了不少营养，看到了优秀的人可以多优秀，效率高的人可以有多高的效率，也从同事们身上学到了很多，一些是沉淀的技术、文章，一些是面对问题的解决方式，一些是和业务方合作的方式，一些是良好的习惯。对整个人横向的能力有很多提高。&lt;/p&gt;

&lt;p&gt;写了很多很多的代码，“硬核码农”这个目标是达到了，因为我们团队业务发展快，人力还在逐步扩充中，每个人工作强度都不小，这种情况下，据数据显示，我6个月写了30w行左右代码，团队排名第二，后几个月代码量排名第一。我并不是一个喜欢写重复代码的人，以前还搞过代码重复度检测工具。主要是业务需求多，自己又闲不住，投了不少时间多做了一些，譬如一些重构，一些业务场景通用能力的抽取，一些从业务中抽象的基础组件，一些基础建设。但是怎么说呢，这个代码写的只有苦劳，功劳不大，不够有价值，解决的都是自己当前场景的问题，很少是能帮助到团队的内容，但是没办法，基础该做的还是应该做。只是接下来解决了一些日常开发的痛点和能力后，要再站高一点，多些更有价值的代码，为团队做贡献。&lt;/p&gt;

&lt;p&gt;相比与代码，“硬核码农”，更需要做好的是综合能力发展的工程师，这些真的还有非常多需要提高的地方，执行力、时间管理、跨团队和上下游的合作沟通，太多时候自己过于佛系，不发声，不去争取，不去改变，不去主动让事情因自己而不同，一方面是精力不支，这个需要更好的时间管理，做事情分清轻重缓急，放弃一些优先保障最有价值的，另一方面是不够自信不够勇敢，不敢发声，这个就要咬咬牙突破下自己的畏惧了。&lt;/p&gt;

&lt;p&gt;下一年，一定不能继续这个样子了。同时记住自己来这里的初心：在业务蓬勃发展的土壤里，发现技术的机会，赋予技术价值，用技术提效、保证稳定性，让开发生活更美好。&lt;/p&gt;

&lt;h1 id=&quot;生活&quot;&gt;生活&lt;/h1&gt;

&lt;p&gt;• 扛过了父亲颅内肿瘤就医手术，这个家庭遇到最艰难的时刻。虽然在安抚母亲情绪方面做的不够好，但是整个过程中自己发挥了能发挥的中坚力量。
• 买房进行中，虽然一直没摇到号
• 不负好时光：秋天开始周末会有一天出门徒步旅行，愉悦了身心，锻炼了身体，拍到了不错的照片，还认识了几个朋友。并发现用一天出去玩，一天在家学习，学习效率更高，周末也更有存在感了。
• 开始不时和老友约饭聊人生，从与人交谈中获得新收获
心态与能力
• 通过年初的阅读3个库的源码和接手10个前辈们留下的产品，源码阅读、快速在大量代码里找到关心内容的能力增强
• 效率有一定提高，现在基本是以前的1.4倍速了
• 抗压能力提高，变乐观了，虽然还是遇到很多挑战，但是相信一切总有解决办法，并不断追求
• 即使是以前2倍的工作量和1.5倍的时间投入下，仍旧保持初心，保守节操，写代码追求通用性，追求通用能力的建设，并不断探索更好的开发模式
• 思维方式转变，从单纯的只追求技术，成长与否只评估技术，到意识到能力是多元化的，每一方面都是成长，只有技术的成长是无法立足走远的。
• 过程上对自己更狠了，但是结果上对自己更包容&lt;/p&gt;

&lt;p&gt;经常觉得快压到自己的能力边界了，但是&lt;em&gt;能力虽然每个人不一样，有些大佬就是没法齐肩，就像读书时代总是考不过班上的第一名一样，但是方法上可以成长，通过总结磨练，总有一套方法论的东西出现，让人在下一次遇到相同或者类似的事时做的更好，得到成长&lt;/em&gt;。我无法质的提高自己的能力，但是我可以通过量的磨练获得方法，让自己不断进步起来。我觉得这是思维上的一个大成长，也是让自己不至于在这么多优秀的人身边失去斗志的信念。&lt;/p&gt;

&lt;h1 id=&quot;可爱的ta们&quot;&gt;可爱的TA们&lt;/h1&gt;

&lt;p&gt;我决定每年的总结新增一个板块，记录这一年遇到的可爱的人们。&lt;/p&gt;

&lt;p&gt;随着年龄增长，身上烟火气越来越重，越来越能感知到人与人之间的感情，一个人一路走过需要记得的除了那些经历，还有那些人，事情因人而不同，很多时候触动你内心的不是什么牛逼的事，而是温情的一瞬间。自己也越来越注重在这个世界走过后能留下什么，像那部动画说的，当这世界上最后一个人将你忘记你就真的死了，那么如果没有人记得我爱我，我也不曾在这个世界上存在过吧。&lt;/p&gt;

&lt;p&gt;我前主管是一个非常有温度的人，以前在蘑菇街时，不仅关注我们的工作和成长，也关心我们的生活，而且非常坦诚非常实在的为你好。我准备离职前纠结要不要退出晋升流程时跟主管说了，主管没有过多的挽留，而是跟我说我有点难过的不是你要走，而是你不信任我，走之前那么纠结的问题都没有找我，没有提前说，还说他最欣慰的是好几年前带的员工还会在和他联系咨询一些工作选择方面的疑惑，颇有师者的风范。也是那时候我才感受到他是这么的信任我，而我顾虑太多。在我带我爸爸回安徽省内就医发信息请假后，打电话跟我说现在你是你们家主力了，撑着点，直到你压力大，但是没办法，一步步去治疗吧。就是这么久都是在照顾家里人情绪，突然被一个长辈照顾了情绪，当时真的有点泪奔，并且在就医期间给了我不少建议。&lt;/p&gt;

&lt;p&gt;来到蚂蚁后，我的队友们非常有温度，有段时间相互宝前端就我和hw老师、二哥，而且扛过了一段需求井喷期，战友情。这份战友情很不一样，大家都很忙压力很大，但是会在做完自己的事情后的一点间隙（这种间隙可能只有一天半天），还会来帮助你补位赶进度，从来不区分功过，真的就完全一个连接体，前期我收到很多照顾，后期我有精力时也尽力反哺。&lt;/p&gt;

&lt;p&gt;hw老师真的特别好，冲在最前面给我们扛压力，接最多的需求，我们做不完的还帮我们，每天晚上催我们下班，说太晚了身体不好，搞不完明天帮你搞，真的不是说说，是真的会去帮你搞。这个真的蛮难得的，一般的p7不会把这么多活揽在自己这里，而是排兵布阵的分发出去。我们也都非常心疼hw老师，尽力去多分担一点，但是毕竟那时候我和二哥都刚入职不久熟悉度有限，没能扛大旗。之前我对蚂蚁工作环境上的所有不适应再遇到hw老师后都迎刃而解了，很多时候一个环境问题的报错，因为不熟悉机制，都不知道怎么排查，以前各种折腾几小时，有hw老师几分钟就告知你了问题所在，也学到了方法，真正的带你熟悉工作环境的学长，有了他后，我迅速适应了工作。在我性格内向比较拘束时，非常nice的在吃饭散步时我们聊天熟悉起来。而且甚至在我需要帮助还没有动口时就主动来帮忙解惑。后期我们独立做项目后，有次遇到个因为我不了解机制没能事先协调好临时需要运营申请修改内容，运营不配合的事，这个跟hw老师一点关系都没有，我觉得我应该自己想办法搞定，但是他看到我有难处后，还是主动帮我去跟运营再次沟通，推动解决问题，到了很晚才走，hw老师的好，是我对一个师兄期望值的3倍，这么良心的学长，真的是遇到是非常大的幸运～hw老师真的触动了我，让我想做一个好人，发光发热不仅是做出有价值的事，还有去力所能及的帮助别人，被人需要，也是一种很大的价值。以后我也会注意去帮助别人了。&lt;/p&gt;

&lt;p&gt;二哥很强，对业务方不合理的需求也很刚，对我很照顾，会帮我怼人的那种，在我压力很大时跟我一起发泄帮我排解，甚至跟我主管反馈我很辛苦压力很大需要休息调整…记得有次受了委屈在和合作方沟通后顺便在二哥附近坐了会，轻描淡写的说了下不开心的事，然后说我要睡会累了，其实是有点撑不住眼泪要下来了要躲会，二哥默默的给我戴上了帽子盖上啥也没说，我稳定后他跟我将不能默默受委屈啊，觉得不合理就说出来，该怼的怼回去，该跟项目负责人反馈的反馈，要去沟通协调，而不是受委屈并默默做了，真的特别暖心。战友一起互相搀扶走了一路了，铁一般的友谊了～&lt;/p&gt;

&lt;p&gt;在相互宝业务里，苦过，认同它的价值愿意战斗，并意外收获这2枚可爱的战友，真的是很棒～
其实还有好多可爱的人们，譬如我的第一个leader，一开始总是觉得他像班主任很怕他，他会不断给你压力目标鞭策你，但是后面会发现他特别真诚，真心像帮助你成长，只是他自己太优秀未免要求比较高压力比较大让人害怕，但是压一压成长速度会快点，就是比较累，而且他真的非常优秀，现在的他和6个月前的他已经不是一个他了，进化的非常快，执行力思维境界种种，皆为楷模；我的主管呢，既有温度也很优秀，在我还没入职前了解我爸住院后，几次询问状况，这也成为我选择现在部门的一个原因，因为有温度，工作中压力大时也会和他诉说，他一方面是安慰，让你感受到情绪被照顾到，同时也从更高的视角让你去理解当前的处境，然后更好的去支持。然后有非常强的自制力，能很早爬起来看书，一周2本，中午健身2小时，吃健康餐，严格时间管理，立flag马上就执行那种，叹为观止叹为观止；现在的小组leader你能感受到他也是非常nice的一个人，不仅认真负责，而且难得的会感受到被保护，（蚂蚁是个只看重事情的地方，护犊子的行为之前都没有见到，但是怎么说呢，还是很喜欢护犊子的leader，有安全感，虽然我也不是个会造次的人），而且不一样的地方是他有点不太忍心给你压力，就很善良，像朋友一样，而且创造了愉快的团队氛围和很好的技术学习环境；还有个热情的小姐姐，在我入职前期没磨合好时不免有些吐槽，小姐姐都是听完后给我些建议，把我拉回解决问题而不是吐槽问题的主道路上。&lt;/p&gt;

&lt;p&gt;还有我亲爱的可爱的最爱的小姐姐，我们成功打破距离的限制，1年半的北京杭州分离时光并没有让我们有丝毫的生疏，联系密切，动不动互相分享段子、新闻，斗图，有压力或者糟心事互相排解。活了这么多年，终得一挚友吧～还有克瑞这些蘑菇老友，不时可以一起聚聚唠唠嗑。还有数次和小苹果聊人生，小苹果不仅在技术发展、代码节操上对我影响很多，也是个非常善良的人，曾跟我说被人需要是一种幸福，这个境界之前我是体会不到的。&lt;/p&gt;

&lt;p&gt;我的很多朋友都比我更善良真诚，给予我引导、正能量以及帮助，他们是我的灯塔，我希望自己开始不仅追求做个有能力有担当的人，也要一样做个善良有温度的人，也能更多的被人需要。&lt;/p&gt;

&lt;h1 id=&quot;2020展望&quot;&gt;2020展望&lt;/h1&gt;

&lt;h2 id=&quot;待改进&quot;&gt;待改进&lt;/h2&gt;

&lt;p&gt;• 心态不稳，遇到问题，慌慌张张
• 不敢发声，不敢选择
• 不能合理判断舍得，对事情做判断，集中精力做最有价值的事情&lt;/p&gt;

&lt;h2 id=&quot;生活习惯&quot;&gt;生活习惯&lt;/h2&gt;

&lt;p&gt;成长来源与习惯：&lt;/p&gt;

&lt;p&gt;• 高质量周末：周末的晚上睡觉不超过1点，早晨一天自然醒，一天必须9天前起床，感受足够时间长度的一天。
• 一天轻松休闲式：1）天气好出去走走呼吸新鲜空气也锻炼身体 2）机会合适约朋友们聚聚聊聊天，避免孤僻。3）天气不好&amp;amp;没约到朋友：整理文档
• 一天学习成长式：抛开业务代码，沉淀技术产品，或者是学习新知识
• 每周运动，如果周末 未能出去徒步，则下周晚上靠仰卧起坐每天&amp;gt;50个，或者瑜伽&amp;gt;20min补全
• 每周留出一个晚上时间（3小时），总结沉淀文档。
• 文章阅读todolist:  收集周报或者其他地方获得的文章，列出阅读列表，并逐项阅读，每个季度总结对自己最有用处的文章列表，写收获感想。
• 养成记录的好习惯：随时随地收集闪现的idea，并进行后期调研和可行性分析。
• 阅读2本对思维拔高有帮助的书。书待搜集
• 改变面对问题的思考和应对方式：永远不和同事只抱怨问题，不传播负面情绪，遇到问题考虑解决方案，以共同探讨解决方案去交流，不以吐一时之快去吐槽。
• 对项目细粒度排期，做到每天都可控
• 晚上尽量9点后进入非业务需求开发状态
• 至少每两周一次大扫除
• 每周todolist，排优先级，review完成情况&lt;/p&gt;

&lt;h3 id=&quot;技术-1&quot;&gt;技术&lt;/h3&gt;
&lt;p&gt;• 搞清蚂蚁bff层的架构设计
• 阅读egg源码，了解egg设计，熟练拓展插件和各种功能
• 全面了解蚂蚁现有主流的基础建设、后台系统和解决的问题，以及亮点技术
• 每月完成1-2个产品的调研了解
• 落实native开发的学习，能写简单的native页面，搞清楚前端调用native组件的实现链路
• 学习electron，能写桌面应用，通过读源码了解electron的实现。&lt;/p&gt;

&lt;h3 id=&quot;工作-1&quot;&gt;工作&lt;/h3&gt;
&lt;p&gt;• 独立owner一个业务线，和业务方友好合作，高效高质量开发。（但是业务不是我的追求和主线，不追求前瞻性了解业务）
• 有2个自己主导的对大家有帮助的技术产品
• 能通过技术手段让大家工作效率提高，稳定性提高，从而有一定的技术影响力。
• 一方面吸收新内容，一方面要将自己以往的积累和现有的工作联系起来，发掘经验的价值&lt;/p&gt;

&lt;h3 id=&quot;生活-1&quot;&gt;生活&lt;/h3&gt;

&lt;p&gt;• 每周和父母打电话，关心父母身体状况
• 买房
• 远途旅行一次，下一站去哪儿还没想好
• 爬华山
• 正向理财，股票基金类营收
• 至少开辟5个自己喜欢吃的新菜谱
• 多扩展交际圈，遇到更多有趣的人&lt;/p&gt;

&lt;h3 id=&quot;能力与心态&quot;&gt;能力与心态&lt;/h3&gt;
&lt;p&gt;• 成熟稳重点，遇到任何事稳住，不要慌
• 专心，工作时一心一意，娱乐时忘乎所以
• 提高效率，记录自己完成内容的时间，反思时间的使用情况，探索提效法则
• 抗压心态，愉快工作，遇到任何高强度高压力的时候，多想想别人承受的更多&lt;/p&gt;

&lt;h1 id=&quot;结语&quot;&gt;结语&lt;/h1&gt;

&lt;p&gt;2019，陪家人度过艰难时期，打赢肿瘤之战。来到蚂蚁，开启新篇章，也遇到很多挑战。&lt;/p&gt;

&lt;p&gt;2019，很苦，很累，不知道这种是不是成长，但是很幸运遇到这么多优秀的同事，一定能成长，对工作上的结果不满意，但是因为种种现实原因也能跟自己和解。2020，希望对自己的生活工作规划把控的更好一点，跑的快一点&lt;/p&gt;

&lt;p&gt;被生活虐，扔保持对生活的热爱&lt;/p&gt;

&lt;p&gt;有自己的立场和选择，但仍尊重不一样的存在，欣赏多元化的世界&lt;/p&gt;

&lt;p&gt;遵守现有的规章制度，但仍探索追求更合理的存在&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>react和redux生态源码解析</title>
   <link href="http://echizen.github.io/tech/2019/04-07-react-redux"/>
   <updated>2019-04-07T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/react-redux</id>
   <content type="html">
&lt;p&gt;redux是我阅读知名库源码的迈开的第一步，2年前结合函数式编程思想也总结过redux源码阅读笔记。但是当时没有将整个体系串起来弄清楚。现在再读，redux简洁明了的源码真是感人，整个流程特别容易理解了。&lt;/p&gt;

&lt;h1 id=&quot;redux&quot;&gt;redux&lt;/h1&gt;

&lt;p&gt;核心能力：提供数据存储，分发、变更的机制。意在管控数据流的变更，让一切变更可感知，配合单向数据流。&lt;/p&gt;

&lt;p&gt;包含&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createStore、applyMiddleware、bindActionCreator、combineReducer&lt;/code&gt;四个组成部分，也是对外提供的api。其中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bindActionCreator&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;combineReducer&lt;/code&gt;为辅助工具。&lt;/p&gt;

&lt;h2 id=&quot;createstore&quot;&gt;createStore&lt;/h2&gt;

&lt;p&gt;核心的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createStore&lt;/code&gt;提供获取state的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getState&lt;/code&gt;方法和变更数据的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;方法，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyMiddleware&lt;/code&gt;提供的中间件组装机制，能让中间件在数据变更前进行对应处理，这个阶段可以更改state，中间件可以封装出更有能力的dispatch。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;的能力是调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reducer&lt;/code&gt;函数传入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oldState&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action&lt;/code&gt;获得最新的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt;, 并且调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store.subscribe&lt;/code&gt;注册的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;listeners&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;getState则直接返回createStore里的局部变量currentState&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果有enhancer如调用中间件的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyMiddleware&lt;/code&gt;，则将当前函数作为参数传给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyMiddleware&lt;/code&gt;处理。否则，构造出&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getState\dispatch&lt;/code&gt;返回&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_8f77e43g8ihabkhd7dkh427e485a1_1016x424.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;applymiddleware&quot;&gt;applyMiddleware&lt;/h2&gt;

&lt;p&gt;真正调用createStore获得dispatch和getState，给每个中间件注入这2个接口&lt;/p&gt;

&lt;h3 id=&quot;dispatch的封装&quot;&gt;dispatch的封装&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种写法是为了保证是middleware第一层获取到的dispatch是最初来自createStore的的dispatch，而后面&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_dispatch=compose.apply(undefined, chain)(store.dispatch)&lt;/code&gt;的dispatch则是被中间件包装处理的含有各种功能的dispatch。而这一步正是调用下面redux thunk中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createThunkMiddleware&lt;/code&gt;封装的dispatch的地方:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_3ga706ke2f6c8b7l483dk44dliee0_914x404.png&quot; alt=&quot;&quot; /&gt;
可以看出next就是compose组合的各个中间件函数。&lt;/p&gt;

&lt;h3 id=&quot;redux中间件&quot;&gt;redux中间件&lt;/h3&gt;

&lt;p&gt;中间件可以接受dispatch和getState函数，且必须返回action, 格式：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//封装action&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个函数式的经典风格其实挺绕的，第一层是在applyMiddleware里调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;middleware(middlewareAPI)&lt;/code&gt;消费，第二层next是applyMiddleware的compose获取封装的dispatch时消费，第三层action是最终action触发dipatch调用时消费。&lt;/p&gt;

&lt;h3 id=&quot;applymiddleware的调用时机&quot;&gt;applyMiddleware的调用时机&lt;/h3&gt;

&lt;p&gt;createStore的调用有2种格式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_3heb64b1gfglli84k4c0fbake2icj_818x270.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_6dh8fkbb95g6g0hliig76hi8gl4l4_810x190.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;无论哪一种，最终经过内部逻辑后都是一样的后续流程。最后一个参数就是enhancer，最终会调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enhancer(createStore)(reducer, preloadedState)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;所以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyMiddleware&lt;/code&gt;里的第一层&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return (createStore) =&amp;gt; {}&lt;/code&gt;在这里调用，第二层也紧接着被调用。&lt;/p&gt;

&lt;p&gt;可以看出，applyMiddleware作用是获取各个中间件封装后的最终dispatch并和原始store一起返回。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_42j9gieh2cdf7818k6kehgh48ab6f_1660x702.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;bindactioncreator&quot;&gt;bindActionCreator&lt;/h2&gt;

&lt;p&gt;调用姿势：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;compName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compNameState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bindActionCreators&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compNameAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;comp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compNameAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connect&lt;/code&gt;来源于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;react-redux&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bindActionCreators&lt;/code&gt;来源于redux。&lt;/p&gt;

&lt;p&gt;将action函数用dispatch封装一层，视图层就不需要再传入dispatch了。bindActionCreators中的dispatch来源于视图层&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;provider&amp;gt;&lt;/code&gt;中注入的store里的dispatch&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_1e1lh0h5026jcb92l4l5ld3egfc6g_922x166.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;redux-thunk&quot;&gt;redux-thunk&lt;/h1&gt;

&lt;p&gt;为了解决频繁出现的异步action的需求，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redux-thunk&lt;/code&gt;也是必不可少的库&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_79le1kej1he53aidfa7ilc7d4c4k5_932x414.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当有异步等需要处理时，我们写aciton时格式是：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actionFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getstate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;otherFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就能将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redux-thunk&lt;/code&gt;注册的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getState&lt;/code&gt;给内部层层调用，直到最终dipatch获得的是简单的对象型action，走next(action)。这里的next如果有中间件，会进入中间件里。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_68l0j0g3jhg2c0043c08hjd48b13d_1080x360.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;中间件里的next指向下一个中间件:&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store =&amp;gt; next =&amp;gt; action =&amp;gt; { //handler }&lt;/code&gt;&lt;/p&gt;

&lt;h1 id=&quot;react-redux&quot;&gt;react-redux&lt;/h1&gt;

&lt;p&gt;可以看到redux虽然总是被用在react的生态体系内，但是其实他们没有绑定关系，redux完全是个独立的管理数据的库。要和react配套使用，要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;react-redux&lt;/code&gt;来作为连接桥梁。&lt;/p&gt;

&lt;p&gt;理解了这个库的作用才理解了react和redux那一套是怎么闭环的。&lt;/p&gt;

&lt;p&gt;它的实现是将redux store里记录的state 注入到组件里，通过提供&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Provider&lt;/code&gt;和高阶组件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connect&lt;/code&gt;来连接。&lt;/p&gt;

&lt;h2 id=&quot;provider&quot;&gt;Provider&lt;/h2&gt;

&lt;p&gt;Provider仅仅是获取到createStore创建出的store对象供自己调用，再传递到内部组件，实现让开发者写的组件能通过props获取到redux管理的数据。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190407_61e0dg7j88dgl1i5ih29cacd1ijhk_470x274.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;connect&quot;&gt;Connect:&lt;/h2&gt;

&lt;p&gt;通过调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store.subcribe&lt;/code&gt;来监听state的变更，在state变更时对自己的组件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setState&lt;/code&gt;，从而达到更新视图获取组件的state
还会有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapStateToProps&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapDispatchToProps&lt;/code&gt;来分发只记录被过滤的reducer和action作为当前组件的state
其他一坨判断都是更新之类的把控&lt;/p&gt;

&lt;h1 id=&quot;参考文档&quot;&gt;参考文档&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/ecmadao/Coding-Guide/blob/master/Notes/React/Redux/Redux%E5%85%A5%E5%9D%91%E8%BF%9B%E9%98%B6-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md&quot;&gt;Redux入坑进阶-源码解析&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>react16的更新关注点</title>
   <link href="http://echizen.github.io/tech/2019/04-06-react16-new"/>
   <updated>2019-04-06T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/react16-new</id>
   <content type="html">
&lt;p&gt;春节期间系统的翻了一下2年没系统翻的react官方文档。记录了一些react的更新点，其实每一个改变点结合源码看都有其内部实现改动带来的原因，从外部需求看都是为了解决一些痛点。这些更新还是很给力的。&lt;/p&gt;

&lt;h1 id=&quot;生命周期&quot;&gt;生命周期&lt;/h1&gt;

&lt;h2 id=&quot;按功能分类&quot;&gt;按功能分类&lt;/h2&gt;

&lt;p&gt;装载&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;constructor()&lt;/li&gt;
  &lt;li&gt;static getDerivedStateFromProps()&lt;/li&gt;
  &lt;li&gt;render()&lt;/li&gt;
  &lt;li&gt;componentDidMount()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;更新&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;static getDerivedStateFromProps()：类似componentsWillReceiveProps&lt;/li&gt;
  &lt;li&gt;shouldComponentUpdate()&lt;/li&gt;
  &lt;li&gt;render()&lt;/li&gt;
  &lt;li&gt;getSnapshotBeforeUpdate()&lt;/li&gt;
  &lt;li&gt;componentDidUpdate()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;卸载&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;componentWillUnmount()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;错误处理&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;static getDerivedStateFromError()&lt;/li&gt;
  &lt;li&gt;componentDidCatch()&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;按调用阶段分类&quot;&gt;按调用阶段分类&lt;/h2&gt;

&lt;p&gt;reconcile阶段的生命周期：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;getDerivedStateFromProps&lt;/li&gt;
  &lt;li&gt;shouldComponentUpdate&lt;/li&gt;
  &lt;li&gt;render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;commit阶段的生命周期：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;getSnapshotBeforeUpdate&lt;/li&gt;
  &lt;li&gt;componentDidMount&lt;/li&gt;
  &lt;li&gt;componentDidUpdate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;需要注意的是，由于异步渲染，在“渲染”时期（如render）和“提交”时期（如getSnapshotBeforeUpdate和componentDidUpdate）间可能会存在延迟。&lt;/p&gt;

&lt;h2 id=&quot;变更背后&quot;&gt;变更背后&lt;/h2&gt;

&lt;p&gt;这里面其实有很多有趣的关联关系，随便挑两个说说：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;废除了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentsWillReceiveProps&lt;/code&gt;: 一开始大家非常不习惯，至少我react15的代码里大片组件都在使用这个生命周期。但是这个东西其实挺烦人，给个生命周期钩子本来是让你感知到props的变化，然而使用场景都是在setState，根据props内容去setState，甚至setState多次，根据不同条件不同的set，更甚的还有异步的setState。既然大家是要根据props来setState，不如提供个方法&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getDerivedStateFromProps&lt;/code&gt;，入参含有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextProps&lt;/code&gt;，让你返回新的state。这样做的好处是你是声明式一次性返回该定型的state，如何更新何时更新交给了react内部来协调，来达到更好的优化，而不是让你自己各种setState把更新调用时机交给你了，搞出很多不推荐用法和性能问题。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillMount&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillUpdate&lt;/code&gt;被废除了：这个是有一定原因的，fiber的协调算法让这2个声明周期不得不废除，原本的这2个调用时期因为新的协调算法会根据浏览器空闲状态去决定是否在当前时机继续diff及进行后面的commit，所以如果浏览器繁忙，协调过程会暂停，一些情况下会重新来，所以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillMount&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillUpdate&lt;/code&gt;如果依旧保留，他们后续不一定会立即发生&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt;，这2个生命周期也可能在一次组件挂载和更新过程中被调用多次，自然失去了意义。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;新的生命周期文档：
&lt;a href=&quot;https://react.docschina.org/docs/react-component.html#%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&quot;&gt;组件生命周期&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;fiber&quot;&gt;fiber&lt;/h1&gt;

&lt;p&gt;fiber前面写过专门的，就不重复了。这个更新点当之无愧是react16之所以是16的关键。他的设计让react16很多部分的代码简直是重构&lt;/p&gt;

&lt;h1 id=&quot;hooks&quot;&gt;hooks&lt;/h1&gt;

&lt;p&gt;hooks让你与React状态以及函数式组件的生命周期特性hooks的函数。hooks是为了让你抛弃class使用React的&lt;/p&gt;

&lt;p&gt;hooks在1个多月前的react 16.8.0正式发布，我春节看它还是提案阶段，节后上班就看到它被正式发布了，这年头前端开发要跟上使用库的更新节奏得大步快跑啊。。。&lt;/p&gt;

&lt;p&gt;这玩意在社区也引起了广泛的关注和讨论。&lt;/p&gt;

&lt;p&gt;初次看下来我最大的感受是，react开发团队真的是在追求函数式和纯度的路上越走越远，坚持初心。之前推荐开发多用函数式组件，但是涉及到组件内部state和生命周期就没法用函数式组件。class组件含有内部state时其实是不纯的，相同props可能会有不同的渲染结果。&lt;/p&gt;

&lt;p&gt;现在hooks终于能实现一切皆函数了，通过state Hook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useState&lt;/code&gt;让state得到支持，通过Effect Hook &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useEffect&lt;/code&gt;让生命周期这样的需求得以支持，其实生命周期也就是根据变更在特定的阶段执行的有副作用变更的函数，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getDerivedStateFromProps&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;demo:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;useEffect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;react&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`You clicked &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; times`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;You&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;clicked&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;times&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/p&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;Click&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;me&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/button&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/div&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;hooks的意义很明显：组件state和逻辑分离复用，在不同的组件之间复用有状态的行为的方式&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://react.docschina.org/docs/hooks-intro.html&quot;&gt;官方文档&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我这个文档demo党，没有在项目里实用hooks，对它的理解还是不深切。可以看看创造较复杂场景使用hooks解决，并对比出优缺点给出体验报告的长文：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/61427880&quot;&gt;探React Hooks&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;render-props&quot;&gt;render props&lt;/h1&gt;

&lt;p&gt;render props是简单情境下替换高阶组件的方案&lt;/p&gt;

&lt;p&gt;demo：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Cat&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/cat.jpg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Mouse&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;handleMouseMove&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;handleMouseMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;handleMouseMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clientX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clientY&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;onMouseMove&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;handleMouseMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/*
          Instead of providing a static representation of what &amp;lt;Mouse&amp;gt; renders,
          use the `render` prop to dynamically determine what to render.
        */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/div&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MouseTracker&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Move&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;around&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/h1&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Mouse&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Cat&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mouse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/div&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;render props: https://react.docschina.org/docs/render-props.html 。&lt;/p&gt;

&lt;h1 id=&quot;context的变更&quot;&gt;context的变更&lt;/h1&gt;

&lt;p&gt;context在以前一直被官方标注着将废弃，这玩意确实有点别扭，你可跨组件绕过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;props&lt;/code&gt;插入数据了，很容易造成组件维护和复用问题。然而广大开发者总是遇到一个数据要传给多个组件，可能是很叶子节点的组件要用根节点的一个数据，一层层传props真的太头疼了。场景需求，官方终于把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context&lt;/code&gt;摆上了正统地位，给了更声明化的语句让使用者了解到context的存在和内容，来解决维护问题。&lt;/p&gt;

&lt;p&gt;demo：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Context lets us pass a value deep into the component tree&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// without explicitly threading it through every component.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Create a context for the current theme (with &quot;light&quot; as the default).&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ThemeContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;light&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Use a Provider to pass the current theme to the tree below.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Any component can read it, no matter how deep it is.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// In this example, we&apos;re passing &quot;dark&quot; as the current value.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ThemeContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Provider&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dark&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Toolbar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/ThemeContext.Provider&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// A component in the middle doesn&apos;t have to&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// pass the theme down explicitly anymore.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Toolbar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ThemedButton&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/div&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ThemedButton&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Assign a contextType to read the current theme context.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// React will find the closest theme Provider above and use its value.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// In this example, the current theme is &quot;dark&quot;.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;contextType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ThemeContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Button&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;;
&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这一波新增了相关api:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;React.createContext&lt;/li&gt;
  &lt;li&gt;Context.Provider&lt;/li&gt;
  &lt;li&gt;Class.contextType&lt;/li&gt;
  &lt;li&gt;Context.Consumer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我理解通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;React.createContext&lt;/code&gt;声明context和默认值，再通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context.Provider&lt;/code&gt;在需要的地方注入，大大提高了对context的感知，这玩意不在是上游注入的黑盒了。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/context.html&quot;&gt;详情戳官网context文档&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;对render阶段生命周期多次调用无负作用的检测&quot;&gt;对render阶段生命周期多次调用无负作用的检测&lt;/h1&gt;

&lt;p&gt;commit阶段通常非常快，但是render阶段可能会很慢，因此，react16的异步模式将render步骤变成碎片化处理，通过暂停和恢复重启来避免阻塞浏览器。这就意味着react在commit之前可能触发render阶段的生命周期多次，因为出现错误或者插入了一个更高优先级的任务，一些render阶段的声明周期调用后甚至不会有对应的commit阶段发生。&lt;/p&gt;

&lt;p&gt;因为这个情况的存在，所以要求render阶段的生命周期不能带有副作用，即多次调用和一次调用结果一样没有啥区别，不会带来改变。&lt;/p&gt;

&lt;p&gt;如果开发者没有遵循这个规范该怎么保证正确性呢？react给了一个不是非常有力但是有大于无的检测方案：在开发模式下，内部会重复调用以下容易产生副作用的方法，来对比结果如果不一致抛出异常：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Class的constructor方法&lt;/li&gt;
  &lt;li&gt;render&lt;/li&gt;
  &lt;li&gt;setState updater functions (the first argument)&lt;/li&gt;
  &lt;li&gt;getDerivedStateFromProps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects&quot;&gt;详情看官网detecting-unexpected-side-effects章节&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;传递refer&quot;&gt;传递refer&lt;/h1&gt;

&lt;p&gt;在高复用基础ui组件库下，会有将ref传给子元素的需求，以前的react版本传递变得很痛苦，react16针对此场景给出了新api来支持。&lt;/p&gt;

&lt;p&gt;demo:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FancyButton&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forwardRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;FancyButton&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/button&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// You can now get a ref directly to the DOM button:&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;FancyButton&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Click&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;me&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/FancyButton&amp;gt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/forwarding-refs.html&quot;&gt;详情查看官网forwarding-refs&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;关于复合的建议&quot;&gt;关于复合的建议&lt;/h1&gt;

&lt;p&gt;用于同一路径或数据但渲染不同的children&lt;/p&gt;

&lt;p&gt;也可用于简单情境下替换高阶组件的方案，避免多层级透传props也可以使用这个方式。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://react.docschina.org/docs/composition-vs-inheritance.html&quot;&gt;复合vs继承&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这其实不能算react16的更新啦…&lt;/p&gt;

&lt;h1 id=&quot;reactpurecomponentreactmemo&quot;&gt;react.PureComponent、react.memo&lt;/h1&gt;

&lt;p&gt;通过对比props和state来决定是否走render逻辑的方法：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;shouldComponentUpdate&lt;/li&gt;
  &lt;li&gt;react.PureComponent&lt;/li&gt;
  &lt;li&gt;函数式组件：react.memo&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>react16-fiberNode diff</title>
   <link href="http://echizen.github.io/tech/2019/04-06-react-fibernode-diff"/>
   <updated>2019-04-06T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/react-fibernode-diff</id>
   <content type="html">
&lt;p&gt;在了解了链表增删改查、fiberNode结构、react 16组件更新流程后，我们可以来具体看看fiberNode diff算法的具体实现了。&lt;/p&gt;

&lt;h1 id=&quot;reconcile流程&quot;&gt;reconcile流程&lt;/h1&gt;

&lt;p&gt;先来回顾下reconcile的流程：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果当前节点不需要更新，直接把子节点clone过来，跳到5；要更新的话打个tag&lt;/li&gt;
  &lt;li&gt;更新当前节点状态（props, state, context等）&lt;/li&gt;
  &lt;li&gt;调用shouldComponentUpdate()，false的话，跳到5&lt;/li&gt;
  &lt;li&gt;调用render()获得新的子节点，并为子节点创建fiber（创建过程会尽量复用现有fiber，子节点增删也发生在这里）&lt;/li&gt;
  &lt;li&gt;如果没有产生child fiber，该工作单元结束，把effect list归并到return，并把当前节点的sibling作为下一个工作单元；否则把child作为下一个工作单元&lt;/li&gt;
  &lt;li&gt;如果没有剩余可用时间了，等到下一次主线程空闲时才开始下一个工作单元；否则，立即开始做&lt;/li&gt;
  &lt;li&gt;如果没有下一个工作单元了（回到了workInProgress tree的根节点），进入pendingCommit状态&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;react在比对子fiber时，会根据子fiber类型在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileChildFibers&lt;/code&gt;里有不同的比对方案：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileSingleElement&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileSinglePortal&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileSingleTextNode&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileChildrenArray&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileChildrenIterator&lt;/code&gt;&lt;/p&gt;

&lt;h1 id=&quot;reconcilechildrenarray&quot;&gt;reconcileChildrenArray&lt;/h1&gt;

&lt;p&gt;具体看看子元素是数组时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileChildrenArray&lt;/code&gt;这个最为复杂的比对场景&lt;/p&gt;

&lt;h2 id=&quot;源码&quot;&gt;源码&lt;/h2&gt;

&lt;p&gt;packages/react-reconciler/src/ReactChildFiber.js:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reconcileChildrenArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;currentFirstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expirationTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ExpirationTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__DEV__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// First, validate keys.&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;knownKeys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;knownKeys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;warnOnInvalidKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;knownKeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resultingFirstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;currentFirstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lastPlacedIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nextOldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;nextOldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;nextOldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sibling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updateSlot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;expirationTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nextOldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shouldTrackSideEffects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;alternate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// We matched the slot, but we didn&apos;t reuse the existing fiber, so we&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// need to delete the existing child.&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;deleteChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// newFiber.index = newIndex;。lastPlacedIndex的作用没看到&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;lastPlacedIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;placeChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lastPlacedIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;resultingFirstChild&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sibling&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nextOldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// We&apos;ve reached the end of the new children. We can delete the rest.&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;deleteRemainingChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resultingFirstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// If we don&apos;t have any more existing children we can choose a fast path&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// since the rest will all be insertions.&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;expirationTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;lastPlacedIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;placeChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lastPlacedIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;resultingFirstChild&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sibling&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resultingFirstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Add all children to a key map for quick lookups.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;existingChildren&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapRemainingChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Keep scanning and use the map to restore deleted items as moves.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 从existingChildren找出newIdx相同的元素复用更新&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updateFromMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;existingChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;newChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;expirationTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shouldTrackSideEffects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;alternate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// The new fiber is a work in progress, but if there exists a&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// current, that means that we reused the fiber. We need to delete&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// it from the child list so that we don&apos;t add it to the deletion&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// list.&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;existingChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;lastPlacedIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;placeChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lastPlacedIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;resultingFirstChild&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sibling&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;previousNewFiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shouldTrackSideEffects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Any existing children that weren&apos;t consumed above were deleted. We need&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// to add them to the deletion list.&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;existingChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resultingFirstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;解析&quot;&gt;解析&lt;/h2&gt;

&lt;p&gt;讲真，第一二遍看我都是懵的，特别不理解为啥要做两次循环及第一次循环的作用。&lt;/p&gt;

&lt;p&gt;还是老方法，慢点创造个实例去debug，快点自己模拟一下跟着代码走一遍流程。具体case就不放了。解释如下：&lt;/p&gt;

&lt;p&gt;第一次遍历：先遍历新元素链，找到index相同的元素，主要判断key是否相同，相同的话update老节点生成新节点newFiber。如果遍历到的相同index而元素不相等则结束第一个循环。&lt;/p&gt;

&lt;p&gt;第一次遍历完后：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;新链结束老链没有：把老链中剩余的fiber都删除&lt;/li&gt;
  &lt;li&gt;老链结束新链没有：把新链中剩下的都插入&lt;/li&gt;
  &lt;li&gt;其他情况：把老链按key放入map里，遍历新链，从老链中找和新链key相同的fiber，更新成newfiber，供后面赋值给previousNewFiber构成新链中的一环并进行下一轮循环，也是个链表元素移动的过程，再将老链中该元素删除。遍历处理完所有newChildren生成新链后，删除老链中剩下的元素【核心步骤】&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;看下来还会有这些问题：&lt;/p&gt;

&lt;h3 id=&quot;返回为啥是一个fiber节点&quot;&gt;返回为啥是一个fiber节点？&lt;/h3&gt;

&lt;p&gt;这里比较疑惑的是，因为原本要diff的是一个列表，常规思路diff完后应该返回更新的fiber元素列表，为啥就返回了一个元素？reconcileChildFibers最终的返回是当前节点的第一个孩子节点。reconcileChildrenArray返回的也是一个节点，由于是链表结构，知道第一个节点，就能知道整个链路。&lt;/p&gt;

&lt;h3 id=&quot;effect的作用&quot;&gt;effect的作用&lt;/h3&gt;

&lt;p&gt;在fiber diff出结果后的操作代码里，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createChild&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;placeChild&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deleteChild&lt;/code&gt;这些方法都没有直接操作fiberNode的增删或者dom的增删，而是去操作了effect。譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deleteChild&lt;/code&gt;的代码：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;childToDelete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shouldTrackSideEffects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Noop.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Deletions are added in reversed order so we add it to the front.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// At this point, the return fiber&apos;s effect list is empty except for&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// deletions, so we can just append the deletion to the list. The remaining&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// effects aren&apos;t added until the complete phase. Once we implement&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// resuming, this may not be true.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastEffect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nextEffect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;childToDelete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastEffect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;childToDelete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstEffect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;returnFiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastEffect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;childToDelete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;childToDelete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nextEffect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;childToDelete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;effectTag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Deletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么这样操作effect和打effectTag的作用是？&lt;/p&gt;

&lt;p&gt;effect是side effect，代表将要做的DOM change及；诶性。每个workInProgress tree节点上都有一个 effect list 用来存放diff结果，当前节点更新完毕会向上merge effect list（queue收集diff结果），供后续commit阶段，对整个fiberTree 映射的dom 在effect list里的做增、删、更新处理。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>react16-fiber协调算法</title>
   <link href="http://echizen.github.io/tech/2019/04-06-react-fiber"/>
   <updated>2019-04-06T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/react-fiber</id>
   <content type="html">
&lt;p&gt;是的，都9012年了，我还在写fiber，因为我2个月前才认真看。。。&lt;/p&gt;

&lt;p&gt;react是一个非常有革命性的框架，其开发者非常具有创新意识，在当年开创react时创造了view=f(data)，设计用函数式的模式让data直接反应视图。使用vdom diff来提高组件更新性能（虽不是首创）。在react15已经大范围普及的情况下，又做出了颠覆式的更新，react16 变更的东西非常多，很多地方就是推翻再来，譬如fiber 算法，不是局限于通过更优的算法来提升性能，而是结合浏览器现状，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestAnimationFrame&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestIdleCallback&lt;/code&gt;在浏览器空闲时碎片化分步更新内容，来达到更优的体验。&lt;/p&gt;

&lt;p&gt;提到fiber算法的优化效果，想必都见过这张对比图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/190406_5gkdlca7k824he218jca83109fb39_550x280.gif&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/190406_379jij3e66jkag26b94860hbe9d3l_550x280.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;背景&quot;&gt;背景&lt;/h1&gt;

&lt;p&gt;主要是为了适应动画场景，以及大规模组件更新的流畅性，改善体验。将原递归式同步协调算法，改为fiber算法的协调方式。&lt;/p&gt;

&lt;p&gt;特点：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;可拆分，可中断任务&lt;/li&gt;
  &lt;li&gt;可重用各分阶段任务，且可以设置优先级&lt;/li&gt;
  &lt;li&gt;可以在父子组件任务间前进后退切换任务&lt;/li&gt;
  &lt;li&gt;render方法可以返回多元素（即可以返回数组）&lt;/li&gt;
  &lt;li&gt;支持异常边界处理异常&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;fibernode-fibertree&quot;&gt;fiberNode fiberTree&lt;/h1&gt;

&lt;p&gt;为了达到这些能力，react将底层更新单元的数据结构改成了链表结构。&lt;/p&gt;

&lt;p&gt;以前的协调算法是递归调用，通过react dom 树级关系构成的栈递归。&lt;/p&gt;

&lt;p&gt;而fiber是扁平化的链表的数据存储结构，通过child找子节点，return找父节点，sibling找兄弟节点。遍历从递归改为循环&lt;/p&gt;

&lt;p&gt;通过fiberNode描述组件节点，return\child\sibling，将底层原栈结构改成链表结构，更加节省内存。而且链表结构在处理时刻随时暂停恢复，无需一气呵成阻塞ui。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190406_7je839e1e4k7eg80h0bia1fabi67l_1306x1136.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Fiber算法的拆分单位是fiber（fiber tree上的一个节点），实际上就是按虚拟DOM节点拆，因为fiber tree是根据vDOM tree构造出来的，树结构一模一样，只是节点携带的信息有差异。实际上是vDOM node粒度的拆分（以fiber为工作单元），每个组件实例和每个DOM节点抽象表示的实例都是一个工作单元。工作循环中，每次处理一个fiber，处理完可以中断/挂起整个工作循环&lt;/p&gt;

&lt;h1 id=&quot;更新过程&quot;&gt;更新过程&lt;/h1&gt;

&lt;h2 id=&quot;reconciler和commit&quot;&gt;reconciler和commit&lt;/h2&gt;

&lt;p&gt;React组件渲染分为两个阶段：reconciler（有些地方称为render）、commit&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;协调reconcile(render)：对Virtual DOM操作阶段，对应到新的调度算法中，就是通过 Diff Fiber Tree 找出要做的更新工作。这是一个js计算过程，计算结果可以被缓存，计算过程可以被打断，也可以恢复执行。
所以，react介绍 Fiber Reconciler 调度算法时，有提到新算法具有可拆分、可中断任务的新特性，就是因为这部分的工作是一个纯js计算过程，所以是可以被缓存、被打断和恢复的&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;提交更新commit: 渲染阶段，拿到更新工作，提交更新并调用对应渲染模块（react-dom）进行渲染。为了防止页面抖动，该过程是同步且不能被打断。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在reconcile阶段的每个工作循环中，每次处理一个fiber，处理完可以中断/挂起整个工作循环。通过每个节点更新结束时向上归并effect list来收集任务结果，reconciliation结束后，根节点的effect list里记录了包括DOM change在内的所有side effect&lt;/p&gt;

&lt;p&gt;在commit 阶段，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commitRoot&lt;/code&gt;中遍历&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextEffect&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invokeGuardedCallback(null, commitAllHostEffects, null)&lt;/code&gt;;，根据&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;effect&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;effectTag&lt;/code&gt;，进行对应的插入、更新、删除操作，根据tag不同，调用不同的更新方法，如class直接return,HostComponent和HostText才有对应的dom更新操作，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FiberNode.stateNode&lt;/code&gt;找到对应的dom节点。例如commitUpdate中又会更新FiberProps和domProps。&lt;/p&gt;

&lt;h2 id=&quot;异步调用的实现&quot;&gt;异步调用的实现&lt;/h2&gt;

&lt;p&gt;异步任务调度有两种方式，主要是通过该任务的优先级进行判断，主要有两种：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;animation（动画）：则会调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestAnimationFrame&lt;/code&gt; API 告诉浏览器，在下一次重绘之前调用该任务来更新动画&lt;/li&gt;
  &lt;li&gt;其他异步任务：则会调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestIdleCallback&lt;/code&gt; API 告诉浏览器，在浏览器空闲时期依次调用任务，这就可以让开发者在主事件循环中执行后台或低优先级的任务，而且不会对像动画和用户交互等关键的事件产生影响&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;双缓冲技术&quot;&gt;双缓冲技术&lt;/h3&gt;

&lt;p&gt;更新方式，更新过程中采用了双缓冲技术，通过存储的2颗fiber树处理：current、workInProgress，更新新树，丢弃老树&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190406_0g1cjlhfkk02ke3g8340lfbg26dij_1356x1012.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;两颗 Fiber Tree：current、workInProgress，它们之间是通过每个Fiber节点上的alternate属性联系在一起。所有更新都是在workInProgress上进行操作，等更新完毕之后，再把current指针指向workInProgress，从而丢弃旧的Fiber Tree&lt;/p&gt;

&lt;h2 id=&quot;过程概述&quot;&gt;过程概述&lt;/h2&gt;

&lt;p&gt;setState - scheduleWork - requestWork获得可用的协调计算时间 - performWork进入workLoop循环 -beginWork中进行fiberNode的diff获得变更的node节点记录到effectList，此过程中如果有shouldComponentUpdate方法调用后false则不进行下一步获得新node的比对 - commit阶段将effectList中所有需要更新的node取出，操作dom更新&lt;/p&gt;

&lt;p&gt;具体更新过程：更新节点的props\state\context等属性，打effectTag - shouldComponentUpdate - 调用render()获得最新ReactElement 并为其绑定fiber节点，会尽力重用原节点 - 如果是新建节点直接mount，如果是更新类型则调用fiber协调算法 - 根据类型调用不同类型的diff算法 - 获得变更，打上effectTag，记录effectList  - 有child继续 - 没有child，将effectList合并到return，开始sibling作为下一个更新单元 - 如果没有时间了，等待下一次主线程空闲 - 如果没有sibling了，进入commit&lt;/p&gt;

&lt;p&gt;commit过程：遍历root的effectList, 通过nextEffect对应的fiber节点的effectTag获得增删改的操作类型 ，node类型是class直接继续, 直到HostComponent和HostText才有对应的dom更新操作&lt;/p&gt;

&lt;h2 id=&quot;过程详解&quot;&gt;过程详解&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190406_24idh37ad28ig61f94ea6ckb108g4_1302x472.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190406_11i0i4bidlbihc486l7004ckagl5f_1294x130.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;第一部分从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReactDOM.render()&lt;/code&gt; 方法开始，把接收的React Element转换为Fiber节点，并为其设置优先级，记录update等。这部分主要是一些数据方面的准备工作。&lt;/li&gt;
  &lt;li&gt;第二部分主要是三个函数：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scheduleWork&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestWork&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;performWork&lt;/code&gt;，即安排工作、申请工作、正式工作三部曲。React 16 新增的异步调用的功能则在这部分实现。&lt;/li&gt;
  &lt;li&gt;第三部分是一个大循环，遍历所有的Fiber节点，通过Diff算法计算所有更新工作，产出 EffectList 给到commit阶段使用。这部分的核心是 beginWork 函数。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beginWork&lt;/code&gt;来看，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beginWork&lt;/code&gt;阶段，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;updateHostComponent&lt;/code&gt;的时候会执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileChildFibers&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mountChildFibers(&lt;/code&gt;初始化的时候)。主要分为两部分，一部分是对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt;的处理，一部分是根据&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fiber&lt;/code&gt;对象的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tag&lt;/code&gt;类型，调用对应的update方法（&lt;strong&gt;vdom diff的步骤就是在这里&lt;/strong&gt;）：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;先更新classComponent的Instance，期间调用生命周期函数&lt;/li&gt;
  &lt;li&gt;根据shouldUpdate来判断是否需要更新&lt;/li&gt;
  &lt;li&gt;需要更新的话调用render()获得最新ReactElement&lt;/li&gt;
  &lt;li&gt;如果是新建节点直接mount，如果是更新类型则调用fiber协调算法&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileChildFibers&lt;/code&gt;函数中主要是根据newChild类型，调用不同的Diff算法：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileSingleElement、reconcileSinglePortal、reconcileSingleTextNode、reconcileChildrenArray&lt;/code&gt;，在子节点上添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;effectTag&lt;/code&gt;记录变更类型如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;placement&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconcileSingleElement&lt;/code&gt;的diff方式可参考：&lt;a href=&quot;https://react.jokcy.me/book/flow/reconcile-children/single.html&quot;&gt;https://react.jokcy.me/book/flow/reconcile-children/single.html&lt;/a&gt; 。array子元素群的diff方式：&lt;a href=&quot;https://react.jokcy.me/book/flow/reconcile-children/array.html&quot;&gt;https://react.jokcy.me/book/flow/reconcile-children/array.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;commit阶段做的事情是拿到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reconciliation&lt;/code&gt;阶段产出的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EffectList&lt;/code&gt;，即所有更新工作，提交这些更新工作并调用渲染模块（react-dom）渲染UI。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后通过事件触发进入到commitAllHostEffects里进行dom操作步骤。&lt;/p&gt;

&lt;p&gt;可以看下某次debug的函数调用栈：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190406_6b06ll862cf5keh0c10l10kd7eka1_588x1096.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;react16 更新元素的调用栈清爽的多，不想react15 因为递归能出现上百个调用栈，让你debug到迷失。&lt;/p&gt;

&lt;p&gt;整个过程已经没有事务系统了，那之前用事务系统解决的问题如元素选中状态、生命周期的调用怎么处理的呢？在commitRoot里还是会有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prepareForCommit()&lt;/code&gt;来处理如focus元素选中问题，也会有对应生命周期如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getSnapshotBeforeUpdate()&lt;/code&gt;的调用&lt;/p&gt;

&lt;h2 id=&quot;过程图&quot;&gt;过程图&lt;/h2&gt;

&lt;p&gt;最后上一张网传的非常详细的流程导图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190406_01f3d1791ahh1ki92cbfgh1h75k06_1376x4096.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/37095662&quot;&gt;React Fiber架构&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://juejin.im/post/5b7016606fb9a0099406f8de&quot;&gt;React16源码之React Fiber架构&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.ayqy.net/blog/dive-into-react-fiber/&quot;&gt;完全理解React Fiber&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>react-导读</title>
   <link href="http://echizen.github.io/tech/2019/03-31-react-guide"/>
   <updated>2019-03-31T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/react-guide</id>
   <content type="html">
&lt;p&gt;看react源码前可以先这些资料，帮助抓住整体思路，了解设计。&lt;/p&gt;

&lt;h1 id=&quot;设计思想&quot;&gt;设计思想&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/design-principles.html&quot;&gt;https://reactjs.org/docs/design-principles.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;讲了符合组件的设计和意义，讲了什么场景下的内容会被抽象到react框架内部实现，特别是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scheduling&lt;/code&gt;章节好好的解释了一波react声明式组件的优势，可以将组件更新流程交给框架层来调度，也就让框架可以优化处理，譬如延时、合并更新这些，也是fiber算法 暂停重启更新、等待浏览器空闲时片段式更新 这种做法的基础，总之这个介绍让我豁然开朗，发现了声明式的魅力并在自己的一些开发设计中用上。&lt;/p&gt;

&lt;h1 id=&quot;核心模型&quot;&gt;核心模型&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/reactjs/react-basic#transformation&quot;&gt;https://github.com/reactjs/react-basic#transformation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍了对dom节点的element抽象、符合组件的模型、state状态模型等。&lt;/p&gt;

&lt;h1 id=&quot;核心实现&quot;&gt;核心实现&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/implementation-notes.html&quot;&gt;https://reactjs.org/docs/implementation-notes.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;react16以前的设计，解释了各种组件类型的实现、渲染、更新实现等。&lt;/p&gt;

&lt;h1 id=&quot;fiber概念&quot;&gt;fiber概念&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/acdlite/react-fiber-architecture&quot;&gt;https://github.com/acdlite/react-fiber-architecture&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍了fiber架构设计，协同和更新2个阶段，scheduling和work部分，fiber节点的构成等重要概念，不过没有连成整体。&lt;/p&gt;

&lt;h1 id=&quot;react-componentselementsinstances&quot;&gt;react Components,Elements,Instances&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html&quot;&gt;https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Components&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Elements&lt;/code&gt;, 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Instances&lt;/code&gt;这3个基础元素的含义和实现以及联系。&lt;/p&gt;

&lt;h1 id=&quot;从0实现react系列&quot;&gt;从0实现react系列&lt;/h1&gt;

&lt;p&gt;从0实现向来是抓住整体思路和设计的不二法宝&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://react-china.org/t/react-react/26788&quot;&gt;React 源码分析，实现一个简易版的React&lt;/a&gt;
&lt;a href=&quot;https://github.com/hujiulong/blog&quot;&gt;https://github.com/hujiulong/blog&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>vue源码-自定义事件</title>
   <link href="http://echizen.github.io/tech/2019/03-28-vue-event"/>
   <updated>2019-03-28T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/vue-event</id>
   <content type="html">
&lt;p&gt;浅文预告，vue的自定义事件这块没啥好挖的，就是常见的订阅发布模式。我可能还应该去探索下他的原生事件代理机制。&lt;/p&gt;

&lt;h1 id=&quot;源码&quot;&gt;源码&lt;/h1&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/core/instance/events.js&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;initEvents&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_hasHookEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// init parent attached events&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;listeners&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_parentListeners&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;updateComponentListeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;listeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;once&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;once&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$once&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updateComponentListeners&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;listeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;oldListeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;updateListeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldListeners&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;eventsMixin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hookRE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/^hook:/&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$on&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// optimize hook:event cost by using a boolean flag marked at registration&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// instead of a hash lookup&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hookRE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_hasHookEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$once&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$off&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// all&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// array of events&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// specific event&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// specific handler&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$emit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lowerCaseEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lowerCaseEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lowerCaseEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;tip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;`Event &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lowerCaseEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; is emitted in component `&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;formatComponentName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; but the handler is registered for &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;. `&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;`Note that HTML attributes are case-insensitive and you cannot use `&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;`v-on to listen to camelCase events when using in-DOM templates. `&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;`You should probably use &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hyphenate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; instead of &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.`&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;cbs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`event handler for &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;解析&quot;&gt;解析&lt;/h1&gt;

&lt;p&gt;简单来说就是在vue构造函数上绑定了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$on&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$once&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$off&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$emit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initEvents&lt;/code&gt; 里将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm.$options._parentListeners&lt;/code&gt;更新到当前vm上，删除老的listeners，增加新的listeners&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm._events&lt;/code&gt;里维护者事件及回调函数队列&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$on(event, fn)&lt;/code&gt;就是向 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm._events[event]&lt;/code&gt;里push fn&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$off(event, fn)&lt;/code&gt;就是吧 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm._events[event]&lt;/code&gt;里和fn相同的回调都移除掉&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$once&lt;/code&gt;就是先&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$off&lt;/code&gt;再&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$on&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$emit(event)&lt;/code&gt;就是找到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm._events[event]&lt;/code&gt;的回调，挨个执行。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm._events[event]&lt;/code&gt;在模板层声明时已经指定了，编译成createElement时会有相应事件参数，举个例子，如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;welcome-button v-on:welcome=&quot;sayHi&quot;&amp;gt;&amp;lt;/welcome-button&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;welcome-button&lt;/code&gt;组件里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;button v-on:click=&quot;$emit(&apos;welcome&apos;)&quot;&amp;gt;click&amp;lt;/button&amp;gt;&lt;/code&gt;，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;welcome-button&lt;/code&gt;组件实例经历了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initEvents&lt;/code&gt;后，它的_events上会有从调用方父组件上获得的welcome事件回调函数，从而可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$emit(&apos;welcome&apos;)&lt;/code&gt;调用到对应的回调函数。&lt;/p&gt;

&lt;h1 id=&quot;拓展&quot;&gt;拓展&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/youngwind/blog/issues/94&quot;&gt;vue源码学习系列之十一：组件化原理探索（父子组件通信）&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>vue源码-dom diff</title>
   <link href="http://echizen.github.io/tech/2019/03-25-vue-dom-diff"/>
   <updated>2019-03-25T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/vue-dom-diff</id>
   <content type="html">
&lt;p&gt;vue的组件更新过程经历dom diff和patch过程。本文就介绍vdom的diff算法。硬核预警。&lt;/p&gt;

&lt;h1 id=&quot;核心代码&quot;&gt;核心代码&lt;/h1&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/core/vdom/patch.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updateChildren&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;removeOnly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldKeyToIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;idxInOld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;elmToMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;refElm&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// removeOnly is a special flag used only by &amp;lt;transition-group&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// to ensure removed elements stay in correct relative positions&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// during leaving transitions&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;canMove&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeOnly&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isUndef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Vnode has been moved left&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isUndef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sameVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;patchVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sameVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;patchVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;newEndVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sameVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Vnode moved right&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;patchVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;canMove&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeOps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;insertBefore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeOps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nextSibling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;newEndVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sameVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Vnode moved left&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;patchVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;canMove&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeOps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;insertBefore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldEndVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isUndef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldKeyToIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldKeyToIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createKeyToOldIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;idxInOld&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldKeyToIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isUndef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;idxInOld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// New element&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;createElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;elmToMove&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;idxInOld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;cm&quot;&gt;/* istanbul ignore if */&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elmToMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;It seems there are duplicate keys that is causing an update error. &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
              &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Make sure each v-for item has a unique key.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sameVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elmToMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;patchVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elmToMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;idxInOld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;canMove&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeOps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;insertBefore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;elmToMove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// same key but different element. treat as new element&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;createElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartVnode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;newStartVnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;refElm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isUndef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elm&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;addVnodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;refElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedVnodeQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStartIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;removeVnodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parentElm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldCh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStartIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldEndIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;案例&quot;&gt;案例&lt;/h2&gt;

&lt;p&gt;光看代码会懵逼，不妨结合几个典型例子跑一下：&lt;/p&gt;

&lt;p&gt;循环中，每一轮结束记录的是当前状态的dom顺序&lt;/p&gt;

&lt;h3 id=&quot;例1倒序&quot;&gt;例1：倒序&lt;/h3&gt;

&lt;p&gt;old：a b c d e&lt;/p&gt;

&lt;p&gt;now：e d c b a&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;使用规则：
sameVnode(oldStartVnode, newEndVnode) -&amp;gt;
nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))

#1
old: abcde
new: edcba
oS:a  oE:e  nS:a nE:b
res：bcdea -  oS  `a` moved after oE `e`

#2
old: a bcde
new: edcb a
oS:b  oE:e  nS:e nE:b
res：cdeba -   oS `b` moved after oE `e`

#3
old: ab cde
new: edc ba
oS:c  oE:e  nS:e nE:c
res：decba -   oS `c` moved after oE `e`

#4
old: abc de
new: ed cba
oS:d  oE:e  nS:e nE:d
res：edcba -   oS `d` moved after oE `e`

#5
old: abcd e
new: e dcba
oS:e  oE:e  nS:e nE:e
rule: `sameVnode(oldStartVnode, newStartVnode), patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res：edcba

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;例2换序&quot;&gt;例2：换序&lt;/h3&gt;

&lt;p&gt;old: a b c d e&lt;/p&gt;

&lt;p&gt;new: a c e b d&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#1
old: abcde
new: acebd
oS:a  oE:e  nS:a  nE:d
rule: `sameVnode(oldStartVnode, newStartVnode) -&amp;gt; patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res：abcde 

#2
old: a bcde
new: a cebd
oS:b  oE:e  nS:c  nE:d
rule: 进入`createKeyToOldIdx`的key值比较环节。对c和old进行key值比较，发现与第二个相等，则把o2移到o1前面。oldCh[2] = undefined, 此时，oldStartIdx === 1，后面的循环中，oldCh[1]会导致 isUndef(oldStartVnode)，直接oldStartIdx++
res：acbde 
oldCh[2] = undefined

#3
(U表示undefined)
old: a bUde
new: ac ebd
oS:b  oE:e  nS:e nE:d
rule: sameVnode(oldEndVnode, newStartVnode) -&amp;gt;  nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
res: acebd -&amp;gt; 上一次结果acbde 中：oE `e` moved before oS `b`

#4
(U表示undefined)
old: a bUd e
new: ace bd
oS:b  oE:d  nS:b nE:d
rule: `sameVnode(oldStartVnode, newStartVnode) -&amp;gt; patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res: acebd 

#5
(U表示undefined)
old: ab Ud e
new: aceb d
oS:U  oE:d  nS:d nE:d
rule: `isUndef(oldStartVnode)`
res: acebd 

#6
(U表示undefined)
old: abU d e
new: aceb d
oS:d  oE:d  nS:d nE:d
rule: `sameVnode(oldStartVnode, newStartVnode) -&amp;gt; patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res: acebd 

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;例3增加元素&quot;&gt;例3：增加元素&lt;/h3&gt;

&lt;p&gt;old: a b c&lt;/p&gt;

&lt;p&gt;new: a d b c&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#1
old: abc
new: adbc
oS:a  oE:c  nS:a  nE:c
rule: `sameVnode(oldStartVnode, newStartVnode) -&amp;gt; patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res：abc

#2
old: a bc
new: a dbc
oS:b  oE:c  nS:d  nE:c
rule: `isUndef(idxInOld)) -&amp;gt; createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)`d与任何old都不相等，则createElm, 将nS:d 嵌入到oS:b前面
res：adbc


#3
old: a bc
new: ad bc
oS:b  oE:c  nS:b  nE:c
rule: `sameVnode(oldStartVnode, newStartVnode) -&amp;gt; patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res：adbc

#4
old: ab c
new: adb c
oS:c  oE:c  nS:c  nE:c
rule: `sameVnode(oldStartVnode, newStartVnode) -&amp;gt; patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res：adbc

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;例4增加元素&quot;&gt;例4：增加元素&lt;/h3&gt;

&lt;p&gt;old: a b c&lt;/p&gt;

&lt;p&gt;new: a c&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#1
old: abc
new: ac
oS:a  oE:c  nS:a  nE:c
rule: `sameVnode(oldStartVnode, newStartVnode) -&amp;gt; patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)`
res：abc

#2
old: a bc
new: a c
oS:b  oE:c  nS:c  nE:c
rule: `sameVnode(oldEndVnode, newStartVnode) -&amp;gt; nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)`
res：acb -&amp;gt; oE `c` moved before oS `b`


#3
old: a b c
new: ac
oS:b  oE:b  newStartIdx = 2     newEndIdx = 1 , newStartIdx &amp;gt; newEndIdx, 结束循环
移除oldStartIdx:1 至 oldEndIdx:1 之间的元素即b
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
res: 13

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;特别说明下&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createKeyToOldIdx&lt;/code&gt;并进行后续查找的作用：双端比较完后，双端没有相同的节点，不代表就没有和中间元素相同的节点，所以会拿newStart和old队列里比较找出key相同的做下一步比较&lt;/p&gt;

&lt;p&gt;这个算法过程是不是清晰多了，那么为啥要用这种算法呢，为啥一直在对比oldStartVnode、oldEndVnode、newStartVnode、newEndVnode这些值呢？&lt;/p&gt;

&lt;h1 id=&quot;vdom-diff算法&quot;&gt;vdom diff算法&lt;/h1&gt;

&lt;p&gt;先聊聊vdom，vdom是为了减轻性能压力。dom是昂贵的，昂贵的一方面在dom本身的重量，dom节点在js里的描述是个非常复杂属性很多原型很长的超级对象，另一方面是浏览器渲染进程和js进程是独立分离的，操作dom时的通信和浏览器本身需要重绘的时耗都是很高的。&lt;/p&gt;

&lt;p&gt;所以大家机智的搞了个轻量的vdom去模拟dom，vdom每个节点都只挂载js操作的必要属性，每次组件update时都先操作vdom，通过vdom的比对，得到一个真实dom的需要操作的集合。整个机制是在JavaScript层面计算，在完成之前并不会操作DOM，等数据稳定之后再实现精准的修改。&lt;/p&gt;

&lt;p&gt;vdom是vue2才引入的，在依赖收集的基础上加持vdom diff，直接定位所需diff的组件，类似react dom diff + shouldComponentUpdate优化处理，快速跳过无需diff的组件。性能得到大幅提升。&lt;/p&gt;

&lt;p&gt;另外，vdom是抽象dom，可跨端，web和native可以根据抽象层实现自己的render层。这就达到了让核心dom diff算法层统一逻辑，但是渲染层交给不同端自己实现。&lt;/p&gt;

&lt;h2 id=&quot;vdom-diff算法优化&quot;&gt;vdom diff算法优化&lt;/h2&gt;

&lt;p&gt;传统diff算法通过循环递归对节点进行依次对比效率低下，算法复杂度达到O(N^3)，主要原因在于其追求完全比对和最小修改，而React、Vue则是放弃了完全比对及最小修改，才实现从O(N^3) =&amp;gt; O(N)。&lt;/p&gt;

&lt;p&gt;优化措施有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;分层diff：不考虑跨层级移动节点，让新旧两个VDOM树的比对无需循环递归（复杂度大幅优化，直接下降一个数量级的首要条件）。这个前提也是Web UI 中 DOM 节点跨层级的移动操作特别少，可以忽略不计。&lt;/li&gt;
  &lt;li&gt;找到diff的关键属性，发生以下情况则跳过比对，变为插入或删除操作：
    &lt;ul&gt;
      &lt;li&gt;组件的Type(Tagname)不一致，原因是绝大多数情况拥有相同type的两个组件将会生成相似的树形结构，拥有不同type的两个组件将会生成不同的树形结构，所以type不一致可以放弃继续比对。&lt;/li&gt;
      &lt;li&gt;列表组件的Key不一致，旧树中无新Key或反之。毕竟key是元素的身份id，能直接对应上是否是同一个节点。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;对触发了getter/setter 的组件进行diff，精准减少diff范围&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/36500459&quot;&gt;Virtual DOM 背后的秘密（Diff 篇）&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;双端比较算法内容&quot;&gt;双端比较算法内容&lt;/h2&gt;

&lt;p&gt;dom diff算法其实有很多很多种，vue采用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;snabbdom&lt;/code&gt;的双端比对算法，算法复杂度仍为O(N)。&lt;/p&gt;

&lt;p&gt;可了解下snabbdom本身的代码：&lt;a href=&quot;https://github.com/snabbdom/snabbdom/blob/b4d69aea97a7e57e8642d5401f9e36f6e566294f/src/snabbdom.ts#L179&quot;&gt;https://github.com/snabbdom/snabbdom/blob/b4d69aea97a7e57e8642d5401f9e36f6e566294f/src/snabbdom.ts#L179&lt;/a&gt;。vue2是完全使用了这部分内容。&lt;/p&gt;

&lt;p&gt;可以看到，一款优秀的框架也不是啥都原创，而是在一个好的设计上不断吸收并进，不断使用更优秀的东西优化发展自己。&lt;/p&gt;

&lt;h2 id=&quot;vue和react-15的vdom-diff算法优劣&quot;&gt;vue和react 15的vdom diff算法优劣&lt;/h2&gt;

&lt;p&gt;vue和react15的vdom diff算法是不一样的。&lt;/p&gt;

&lt;h3 id=&quot;react-15-dom-diff&quot;&gt;react 15 dom diff&lt;/h3&gt;

&lt;p&gt;简要描述下react的dom diff过程：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://pic4.zhimg.com/80/c0aa97d996de5e7f1069e97ca3accfeb_hd.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/80/7b9beae0cf0a5bc8c2e82d00c43d1c90_hd.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;if (child._mountIndex &amp;lt; lastIndex)，则进行节点移动操作&lt;/p&gt;

&lt;p&gt;看当前遍历到的新节点在老树中的位置_mountIndex与老树中最大访问过的下标lastIndex比较。（lastIndex会记录老树中的访问过的元素的最大位置或者是新插入的元素的位置）
只关注老树中当前访问过的位置lastIndex的移动状况，如果_mountIndex大于这个值，则表示在访问点后面，暂时不操作。只有小于了，才去操作移动。这种思想保证永远只有一个方向向后移动，提高效率。但是如果是最后一个节点移动到第一个，则效率会很低。&lt;/p&gt;

&lt;p&gt;更多react 15 diff解释可查看&lt;a href=&quot;https://zhuanlan.zhihu.com/p/20346379&quot;&gt;React 源码剖析系列 － 不可思议的 react diff&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;对比&quot;&gt;对比&lt;/h3&gt;

&lt;p&gt;直接用“章辰-转转”知乎文章里整理的这张实际案例的对比图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://pic4.zhimg.com/80/v2-3e18884058de8cbea2a9b6174b73cce3_hd.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图例：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;绿色表示更新的节点，黑色表示复用的节点&lt;/li&gt;
  &lt;li&gt;红色表示新插入的节点，逗号之间表示老节点被删除或被移动&lt;/li&gt;
  &lt;li&gt;黄/蓝色表示移动的节点，对应理论最优不动/移动解&lt;/li&gt;
  &lt;li&gt;前两行在生命周期的二次渲染中上色，后三行则在首次渲染后手工上色&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;,&lt;/code&gt;作为分隔符，以便看起来更像数组原本的结构&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;可以看出react 15这种算法非常不适合倒序输出，全部向后移。而vue的双端比较法比较折中适合大多数场景，表现是向前移动。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;所以针对这种特征&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;建议，在开发组件时，保持稳定的 DOM 结构会有助于性能的提升；&lt;/li&gt;
  &lt;li&gt;建议，在开发过程中，尽量减少类似将最后一个节点移动到列表首部的操作，当节点数量过大或更新操作过于频繁时，在一定程度上会影响 React 的渲染性能。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;vdom-diff是否一定性能更好&quot;&gt;vdom diff是否一定性能更好&lt;/h1&gt;

&lt;p&gt;这个问题在知乎上有非常好的讨论，直接摘录有意义的片段：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;原生 DOM 操作 vs. 通过框架封装操作。这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作，让你用更声明式的方式来描述你的目的，从而让你的代码更容易维护。&lt;strong&gt;没有任何框架可以比纯手动的优化 DOM 操作更快，因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作，它的实现必须是普适的&lt;/strong&gt;。针对任何一个 benchmark，我都可以写出比任何框架更快的手动优化，但是那有什么意义呢？在构建一个实际应用的时候，你难道为每一个地方都去做手动优化吗？出于可维护性的考虑，这显然不可能。框架给你的保证是，你在不需要手动优化的情况下，我依然可以给你提供过得去的性能。不要天真地以为 Virtual DOM 就是快，diff 不是免费的，batching 么 MVVM 也能做，而且最终 patch 的时候还不是要用原生 API&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;对 React 的 Virtual DOM 的误解。如果没有 Virtual DOM，每次有变动就整个重新渲染整个应用简单来想就是直接重置 innerHTML。在一个大型列表所有数据都变了的情况下，重置 innerHTML 其实是一个还算合理的操作… 真正的问题是在 “全部重新渲染” 的思维模式下，即使只有一行数据变了，它也需要重置整个 innerHTML，这时候显然就有大量的浪费。
-innerHTML:  render html string O(template size) + 重新创建所有 DOM 元素 O(DOM size)&amp;gt; &amp;gt; -Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Virtual DOM render + diff 显然比渲染 html 字符串要慢，但是！它依然是纯 js 层面的计算，比起后面的 DOM 操作来说，依然便宜了太多。可以看到，innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关，但 Virtual DOM 的计算量里面，只有 js 计算和界面大小相关，DOM 操作是和数据的变动量相关的。前面说了，和 DOM 操作比起来，js 计算是极其便宜的。这才是为什么要有 Virtual DOM：它保证了 1）不管你的数据变化多少，每次重绘的性能都可以接受；2) 你依然可以用类似 innerHTML 的思路去写你的应用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;by 尤雨溪&lt;/p&gt;

&lt;p&gt;在尤雨溪看来：Virtual DOM 真正的价值从来都不是性能，而是它 1) 为函数式的 UI 编程方式打开了大门；2) 可以渲染到 DOM 以外的 backend，比如 ReactNative。&lt;/p&gt;

&lt;p&gt;先说innerHTML还是Document.createElement + appendChild谁更优劣（举例增加dom的api，实际上不同情况自然会调用不同的dom操作api），毕竟场景不同效果不一样，大面积的改动前者性能更优，而小范围不连贯的dom节点更新后面的方式精确改动dom显然性能更好。&lt;/p&gt;

&lt;p&gt;再者，同样都是Document.createElement + appendChild去精确改动的情况下，react还多了一步dom diff的过程，虽然js执行快，但怎么也是多了一步，自然比不上直接去patch阶段的操作dom。&lt;/p&gt;

&lt;p&gt;再退一步，同样都是dom diff+ patch操作dom的方案，你通过手动优化在自己的场景下总能写出性能更高的dom操作方式。但是你不能为每个场景都去手写优化。如果在vdom diff上遇到无法优化的性能问题了，比较合理的做法应该是针对这一类特定场景替换成更优的dom diff + patch方案。&lt;/p&gt;

&lt;p&gt;更多讨论查看：&lt;a href=&quot;https://www.zhihu.com/question/31809713&quot;&gt;网上都说操作真实 DOM 慢，但测试结果却比 React 更快，为什么?&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;参考&quot;&gt;参考&lt;/h1&gt;

&lt;p&gt;在研究vdom diff算法时参考了很多有价值的文章，推进理解上了一个深度，统一列举下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/20346379&quot;&gt;React 源码剖析系列 － 不可思议的 react diff&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/36259218&quot;&gt;Virtual DOM 背后的秘密（DOM 篇）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/36500459&quot;&gt;Virtual DOM 背后的秘密（Diff 篇）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/31809713&quot;&gt;网上都说操作真实 DOM 慢，但测试结果却比 React 更快，为什么?&lt;/a&gt;, 尤雨溪、胡子大哈、司徒正美、罗志宇的回答都非常有价值，知乎当年的氛围真的是很好的，可现在，唉，挽尊…&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>vue-mvvm设计模式</title>
   <link href="http://echizen.github.io/tech/2019/03-24-vue-mvvm"/>
   <updated>2019-03-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/vue-mvvm</id>
   <content type="html">
&lt;p&gt;mvvm是个老词汇了，那么究竟什么叫mvvm呢？&lt;/p&gt;

&lt;p&gt;一图解释之：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://pic4.zhimg.com/80/edd0080fb145315fbc96164c219fee7e_hd.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;从 M 到 V 的映射（Data Binding），这样可以大量节省你人肉来 update View 的代码（也就是所说的 UI 逻辑）&lt;/li&gt;
  &lt;li&gt;从 V 到 M 的事件监听（DOM Listeners），这样你的 Model 会随着 View 触发事件而改变&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以vue是个mvvm结构的框架，旨在高效的帮你解决数据到视图的更新。&lt;/p&gt;

&lt;p&gt;vue如何实现这个的可以看之前的一篇文章的例子: &lt;a href=&quot;https://echizen.github.io/tech/2019/03-24-vue-bind-demo&quot;&gt;vue-双向绑定的简单实现&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;流程图展示就是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190324_7fd41bi9431412hd8k1f39k883085_1898x1278.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;组件更新的过程主要有2个阶段：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;比对(diff的运算过程)&lt;/li&gt;
  &lt;li&gt;修改(diff的返回结果，输出生成的patch并操作)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;整个数据更新到页面视图更新的流程就是，vue里mountComponent里创建了Watcher, Watcher的constructor里调用了getter，获取视图层直接关联的数据，从而触发属性的getter方法进行依赖收集。当监听数据的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setter&lt;/code&gt;调用时触发&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;watcher.update&lt;/code&gt;，而updateComponent即是watcher里作为回调的getter函数，从而在updateComponent中调用_update，_update再调用__patch__方法批量派发更新内容, web端的patch方法对最终的dom节点更新。&lt;/p&gt;

&lt;h1 id=&quot;参考&quot;&gt;参考&lt;/h1&gt;

&lt;p&gt;我写的比较少，因为下面的几篇参考文档里已经写得很好了，想了解可以认真看看。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/36929146/answer/97611755&quot;&gt;mvvm中 viewmodel该如何设计&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jianshu.com/p/1ccc2cb4f907&quot;&gt;Vue中的MVVM(2)–view -&amp;gt; model的绑定&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ustbhuangyi.github.io/vue-analysis/reactive/component-update.html&quot;&gt;组件更新&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>vue-双向绑定的简单实现</title>
   <link href="http://echizen.github.io/tech/2019/03-24-vue-bind-demo"/>
   <updated>2019-03-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/vue-bind-demo</id>
   <content type="html">
&lt;p&gt;了解了上一篇文章中介绍的内容后，应该会清楚vue中一个重要功能&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v-model&lt;/code&gt;的实现了。硬文预告。&lt;/p&gt;

&lt;p&gt;先上simple版代码：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Two-way data-binding&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;v-model=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;observe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;defineReactive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;defineReactive&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addSub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeToFragment&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;flag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createDocumentFragment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;compile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compile&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\{\{(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;.*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)\}\}&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 节点类型为元素&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// 解析属性&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;v-model&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 获取v-model绑定的属性名&lt;/span&gt;
                        &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                            &lt;span class=&quot;c1&quot;&gt;// 给相应的data属性赋值，进而触发该属性的set方法&lt;/span&gt;
                            &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
                        &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 将data的值赋给该node&lt;/span&gt;
                        &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;v-model&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 节点类型为text&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 获取匹配到的字符串&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
        &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//  this为watcher函数&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//  console.log(this);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nodeType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 获取data中的属性值&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 触发相应属性的get&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;addSub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nodeToFragment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 编译完成后，将dom返回到app中&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;fuck world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;简单版的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defineReactive&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dep&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Watcher&lt;/code&gt;加持后，响应式数据部分完成了，这个流程是数据-视图的流动，保证了通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm.text=&lt;/code&gt;调用数据更新时，触发&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Watcher&lt;/code&gt;里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt;方法更新相应节点dom内容；而视图-数据的反应，则是通过事件绑定来完成的，初次渲染视图时，就对视图里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v-model&lt;/code&gt;的节点做了事件监听，保证&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt;事件触发时，能将用户输入的数据及时反映到数据层&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm&lt;/code&gt;的对应data上。&lt;/p&gt;

&lt;p&gt;流程图展示就是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/3360875-4428cd8a2fdc4fac.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/730/format/webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这样就是一个双向绑定的简单实现了，当然这里只为了展现这个核心原理，vue中的真实处理要比这精细的多，不会有各种判断的硬编码，兼容各种节点类型各种情况。更新视图也没有这么简单直接调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.node.nodeValue&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.node.value&lt;/code&gt;直接赋值，而是有个批量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patch&lt;/code&gt;的过程，将一个事件循环周期内需要更新的内容先收集起来，再一次性更新来提高性能。不过看完后，是否有了双向绑定我也能实现的感觉呢，并不是多么神密的设计哦。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>vue源码-响应式数据原理</title>
   <link href="http://echizen.github.io/tech/2019/03-23-vue-reactive-data"/>
   <updated>2019-03-23T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/vue-reactive-data</id>
   <content type="html">
&lt;p&gt;开始写vue源码阅读后的总结&lt;/p&gt;

&lt;p&gt;说到vue的设计，不得不提响应式数据的实现，这是vue的一个鲜明特点了。硬文预告。&lt;/p&gt;

&lt;p&gt;官网的图很好的解释了响应式的设计：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cn.vuejs.org/images/data.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过给视图中使用到的data和props设置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object.defineProperty&lt;/code&gt; 的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getter&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setter&lt;/code&gt;方法，在getter中进行依赖收集，只收集视图render依赖的数据。data有一个Deps订阅者，在修改的时候会通知所有的Watcher观察者
setter中则去触发观察者Watcher的notify，进行后续的virtual dom比对和视图层更新。&lt;/p&gt;

&lt;h1 id=&quot;源码&quot;&gt;源码&lt;/h1&gt;

&lt;p&gt;这部分相关的代码在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/core/observer&lt;/code&gt;中。关键片段：&lt;/p&gt;

&lt;h2 id=&quot;definereactive&quot;&gt;defineReactive&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;Define a reactive property on an Object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;设置getter和setter，将操作对象变成响应式。&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;defineReactive&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;customSetter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;shallow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;configurable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// cater for pre-defined getter/setters&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;childOb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shallow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;enumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;configurable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reactiveGetter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;childOb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;childOb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;dependArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reactiveSetter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;
      &lt;span class=&quot;cm&quot;&gt;/* eslint-disable no-self-compare */&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;cm&quot;&gt;/* eslint-enable no-self-compare */&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;customSetter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;customSetter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;setter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;childOb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shallow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defineReactive&lt;/code&gt;用在props、data等数据的每个属性上。可以看出对于每个数据都初始化了一个依赖收集器&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dep = new Dep()&lt;/code&gt;，然后在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get&lt;/code&gt;方法里调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dep.depend()&lt;/code&gt;来进行依赖收集，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set&lt;/code&gt;方法中调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dep.notify&lt;/code&gt;通知依赖收集器发生变更，进行相应的处理。&lt;/p&gt;

&lt;p&gt;与之相关的还有以下两部分：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;observe(): 是props、data等数据的每个属性上直接调用的方法，也就是调用源头。一波判断后会调用observer。通过hasOwn(value, ‘&lt;strong&gt;ob&lt;/strong&gt;‘)来判断是否已经存在一个observer了，存在则直接返回&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Observer{}: Observer class that are attached to each observed object. Once attached, the observer converts target object’s property keys into getter/setters that collect dependencies and dispatches updates.
为数据加上响应式属性进行双向绑定, 对 对象和数组分别进行defineReactive操作。如果是数组则对数组的每一项进行observe；若是对象，则在defineReactive方法的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let childOb = !shallow &amp;amp;&amp;amp; observe(val)&lt;/code&gt; 这步对每个属性都做了observe的操作&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;dep&quot;&gt;Dep&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;A dep is an observable that can have multiple directives subscribing to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;addSub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;removeSub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;depend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addDep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;notify&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// stabilize the subscriber list first&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;subs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// the current target watcher being evaluated.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// this is globally unique because there could be only one&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// watcher being evaluated at any time.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;targetStack&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pushTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;targetStack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_target&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;popTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;targetStack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Dep是发布者，也是依赖收集者。&lt;/p&gt;

&lt;p&gt;可以订阅多个观察者，依赖收集之后Deps中会存在一个或多个Watcher对象，在数据变更的时候通知所有的Watcher。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subs&lt;/code&gt;记录订阅者列表，有用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addSub&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;removeSub&lt;/code&gt;增删方法, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;depend()&lt;/code&gt;则是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dep.target.addDep(this)&lt;/code&gt; ,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notify&lt;/code&gt;是遍历 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subs[i].update()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dep.target&lt;/code&gt;这个文件作用域变量比较难懂，他其实是观察者Watcher。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Watcher.get&lt;/code&gt;方法调用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pushTarget&lt;/code&gt;，将Watcher设置为了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dep.target&lt;/code&gt;。所以Dep和Watcher的关系非常鬼畜，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;depIns.depend&lt;/code&gt;里是在向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WatcherIns.addDep(depIns)&lt;/code&gt;，向watcher的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newDeps&lt;/code&gt;队列里添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;depIns&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WatcherIns.addDep&lt;/code&gt;又会调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dep.addSub(watchIns)&lt;/code&gt;，向dep的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subs&lt;/code&gt;队列里添加watcher，所以是在进行相互关联。&lt;strong&gt;Dep是个中间枢纽，联系着源数据和观察者，并且做到一个Dep联系多个观察者&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;watcher&quot;&gt;watcher&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;A watcher parses an expression, collects dependencies, and fires callback when the expression value changes. This is used for both the $watch() api and directives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Watcher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;deep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;dirty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;newDeps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;depIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ISet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;newDepIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ISet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expOrFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_watchers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// options&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deep&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lazy&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sync&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sync&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sync&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;uid&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// uid for batching&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dirty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// for lazy watchers&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDeps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depIds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDepIds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expression&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expOrFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// parse expression for getter&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expOrFn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;expOrFn&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parsePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expOrFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;`Failed watching path: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expOrFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; `&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Watcher only accepts simple dot-delimited paths. &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;For full control, use a function instead.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lazy&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Evaluate the getter, and re-collect dependencies.
   */&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;pushTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`getter for watcher &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// &quot;touch&quot; every property so they are all tracked as&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// dependencies for deep watching&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;traverse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;popTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cleanupDeps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Add a dependency to this directive.
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;addDep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDepIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDepIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDeps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addSub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Clean up for dependency collection.
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;cleanupDeps&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDepIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeSub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depIds&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depIds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDepIds&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDepIds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDepIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDeps&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDeps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newDeps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* istanbul ignore else */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dirty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;queueWatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Deep watchers and watchers on Object/Arrays should fire even&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// when the value is the same, because the value may&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// have mutated.&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;isObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deep&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// set new value&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`callback for watcher &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dirty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Depend on all deps collected by this watcher.
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;depend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Remove self from all dependencies&apos; subscriber list.
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;teardown&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// remove self from vm&apos;s watcher list&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// this is a somewhat expensive operation so we skip it&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// if the vm is being destroyed.&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_isBeingDestroyed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_watchers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeSub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看出，watcher和Dep建立了关联，通过Dep.notify-&amp;gt; subs.update = watcher.update - &amp;gt; watcher.run - &amp;gt; cb ，这个流程确保在监听数据发生变更后调用对应的回调处理函数&lt;/p&gt;

&lt;h1 id=&quot;数据更新到视图更新流程&quot;&gt;数据更新到视图更新流程&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;修改数据
触发set
Dep.notify
watcher.update
watcher.run
    watcher.get - getter：这里取名很混淆，这个getter不是字面意思的getter也是不是expOrFn的字面意思，而是真正监听到数据变更后要做出响应操作的函数，譬如更新视图
        watcher建立时的expOrFn
            更新dom或其他类型的响应操作
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再看看更新视图的部分：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190323_3bc9ha0a0i4cc667470c82b0ljhgk_946x1242.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以当某个响应式数据发生变化的时候，它的setter函数会通知闭包中的Dep，Dep则会调用它管理的所有Watch对象。触发Watch对象的update实现。异步执行update的时候，会调用queueWatcher函数。
Watch对象并不是立即更新视图，而是被push进了一个队列queue，此时状态处于waiting的状态，这时候继续会有Watch对象被push进这个队列queue，等到下一个tick运行时，这些Watch对象才会被遍历取出，更新视图。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextTick(flushSchedulerQueue)&lt;/code&gt;中的flushSchedulerQueue是下一个tick时的回调函数，主要目的是执行Watcher的run函数，用来更新视图&lt;/p&gt;

&lt;h1 id=&quot;灵魂发问&quot;&gt;灵魂发问&lt;/h1&gt;

&lt;p&gt;你理解响应式数据了么？如果觉得理解了，不如问问自己：有哪些watcher呢？&lt;/p&gt;

&lt;p&gt;一开始读这个的我以为视图层依赖的每个数据都对应一个watcher，后来被同事反问啪啪打脸觉得自己蠢，watcher对应的回调是视图更新函数，所以怎么可能data的每个属性都对应着一个watcher实例，那多个data的属性变化，就会调用多次视图更新，显然不合理。&lt;/p&gt;

&lt;p&gt;我总结出有这么几种watcher（不一定全面）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;render watcher：就是文中举例的视图层依赖数据的watcher，当视图层依赖的某个数据变更时，调用对应视图的更新。&lt;/li&gt;
  &lt;li&gt;v-model watcher：双向绑定型的watcher，框架自动绑定的watcher，保证视图变化通过事件绑定调用触发对应的数据变化，数据变化触发对应的视图部分更新&lt;/li&gt;
  &lt;li&gt;computed\watch watcher：手动添加的watcher，就是每个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;computed: {}&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;watch: {}&lt;/code&gt;的每一项对应着一个watcher实例，他的回调是要执行对应的用户定义函数，完成相应数据的被通知变更。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;参考&quot;&gt;参考&lt;/h1&gt;

&lt;p&gt;首推官方文档：&lt;a href=&quot;https://cn.vuejs.org/v2/guide/reactivity.html&quot;&gt;深入响应式原理&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ps:还有人在知乎喷vue的中文文档不好，一个框架官网连核心内容的原理都介绍了，简直良心。&lt;/p&gt;

&lt;p&gt;answershuto的文章是我阅读vue源码的指导性文章，感恩，写的非常好~&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/answershuto/learnVue&quot;&gt;Vue.js 源码解析&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>webpack-性能优化</title>
   <link href="http://echizen.github.io/tech/2019/03-19-webpack-performance"/>
   <updated>2019-03-19T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/webpack-performance</id>
   <content type="html">
&lt;p&gt;webpack性能优化分为两方面，一方面是development模式下提高开发环境的构建速度，让你的改动快速的更新到浏览器供调试，另一方面是production模式下提高生产环境的bundle代码质量，减少体积，优化代码的运行时性能。&lt;/p&gt;

&lt;p&gt;anyway，我认为在条件允许的情况下，首要优化方案都是&lt;strong&gt;升级到最新版webpack&lt;/strong&gt;，毕竟其他措施都是辅助，webpack核心代码的优化才是主力，webpack1的速度和4的是不能比的。&lt;/p&gt;

&lt;p&gt;然后webpack4提供了两种mode供配置，每种mode都有一套性能较佳的默认配置，省去了我们很多人工配置的注意点。&lt;/p&gt;

&lt;h1 id=&quot;优化development模式构建性能&quot;&gt;优化development模式构建性能&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;选择合适的source-map，development用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cheap-module-source-map&lt;/code&gt;&lt;/strong&gt;。source-map因为其体积大信息多构建慢，真的是最能影响构建速度的一个方面了。在符合需要的情况下选择一个尽量简单的譬如不包含列信息sourcemap，性能提升看得见。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;拆项目，减少构建范围&lt;/strong&gt;。再怎么优化也扛不住几年时间码起来的巨大的项目，不如拆成几个独立的部分单独构建。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;配置external，不打包react\react-dom\vue这些&lt;/strong&gt;，直接引用cdn资源。这些资源变更少体积大，根本无需实时参与构建，拆出去，构建压力小不少&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;使用happypack&lt;/strong&gt;， 让loader可以多进程去处理文件。webpack4对uglifyjs插件都有了多进程支持，但是官方对loader还没有支持多进程,目前happypack仍是一个好选择&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;开启uglifyjs并行压缩（其实默认就是开启的）：
  minimizer: [
        new UglifyJsPlugin({ parallel: true })
  ]&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;配置resolve.modules指定模块获取范围&lt;/strong&gt;，减少loader的处理查找范围&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;精确的resolve.extensions&lt;/strong&gt;，作用同上&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;配置DLLPlugin&lt;/strong&gt;:这个的作用和配置external是异曲同工，通过单独的webpack配置文件指定哪些公用基础库生成dll，譬如vue/vuex/vue-router，通过配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new webpack.DllPlugin(options)&lt;/code&gt;生成dll的js bundle和dll的manifest.json，manifest.json记录的是这些生成dll模块的map映射表，供后面项目模块import和require引用时查找。然后再在项目的webpack中使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new webpack.DllReferencePlugin({ manifest: require(&quot;path/dll.manifest.json&quot;)})&lt;/code&gt;来引入dll文件关系供使用。dllPlugin相对external的好处是更加动态化，如果你某一天真的需要升级其中一个npm包了结合&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-asset-html-webpack-plugin&lt;/code&gt;资源重新打包和路径替换都可以自动处理，而external的资源只能手动处理更新了，不过dllPlugin的配置使用姿势更麻烦。 &lt;a href=&quot;https://webpack.docschina.org/plugins/dll-plugin/&quot;&gt;配置姿势详情https://webpack.docschina.org/plugins/dll-plugin/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;开缓存[]有哪些地方可开&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bable-loader&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cacheDirectory:true&lt;/code&gt;&lt;/strong&gt;： 或设置指定路径值，当有设置时，指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建，将会尝试读取缓存，来避免在每次执行时，可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。（如果设置了一个空值 (loader: ‘babel-loader?cacheDirectory’) 或者 true (loader: babel-loader?cacheDirectory=true)，loader 将使用默认的缓存目录 node_modules/.cache/babel-loader，如果在任何根目录下都没有找到 node_modules 目录，将会降级回退到操作系统默认的临时文件目录。）&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;优化production模式bundle代码&quot;&gt;优化production模式bundle代码&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;optimization.splitChunks&lt;/code&gt;&lt;/strong&gt;：作用是以一定规则划分多个bundle文件，将每个文件体积减小，更有价值的是划分规则，譬如你可以将node_modules下的文件都划分到一个独立的chunks里，npm包里的内容变更频率很低，这样每次构建发布这部分都是不变的内容，网站使用时就能充分利用浏览器缓存加速了。&lt;a href=&quot;https://juejin.im/post/5b99b9cd6fb9a05cff32007a&quot;&gt;详情请看https://juejin.im/post/5b99b9cd6fb9a05cff32007a&lt;/a&gt; 。提取出第三方库代码和异步加载的代码，webpack会根据下述条件自动进行代码块分割（你也可以根据自己的需求配置更佳的规则）：
    &lt;ul&gt;
      &lt;li&gt;新代码块可以被共享引用，或者这些模块都是来自node_modules文件夹里面&lt;/li&gt;
      &lt;li&gt;新代码块大于30kb（min+gziped之前的体积）&lt;/li&gt;
      &lt;li&gt;按需加载的代码块，并行请求最大数量应该小于或者等于5&lt;/li&gt;
      &lt;li&gt;初始加载的代码块，并行请求最大数量应该小于或等于3&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;optimization.runtimeChunk : true  ||  &quot;multiple&quot;&lt;/code&gt;&lt;/strong&gt;，将webpack动态化的脚手架代码单独拆出，可以让根据文件内容hash生成文件名方案的项目，保持原代码不变即bundle文件不更改，重新发布资源链接为更改，能够走缓存。runtime文件每次build都会改变，单独拎出，重新构建发布只影响该资源的请求。不过要注意，如果不是spa，多资源入口，每个都拆出单独的runtime，会导致多个重复代码文件被加载。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;开启treeshaking&lt;/strong&gt;，不过treeshaking要求有点多，很多资源包不符合规范用不了
    &lt;ul&gt;
      &lt;li&gt;使用 ES2015 模块语法（即 import 和 export）。所以如果npm包里指定了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;的入口（这个入口是es module源码文件），webpack会优先使用这个入口的文件，且关闭babel之类工具将 ES2015 模块语法转换为 CommonJS 模块的处理，babel配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modules:false&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;有副作用不能被treeshaking的文件，如polyfill相关的，在项目 package.json 文件中，添加一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sideEffects&lt;/code&gt; 属性，指明对应文件。&lt;/li&gt;
      &lt;li&gt;引入一个能够删除未引用代码(dead code)的压缩工具(minifier)（例如 UglifyJSPlugin），这个倒是production模式下webpack默认配置就有。毕竟treeshaking在webpack层面做的只是标记使用片段，未使用片段的删除是通过UglifyJS去做的。
  这个最大的尴尬点是第三方包，有些没有直接提供&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;入口，有些没有指明sideEffects，虽然webpack允许我们在loader层去配置这个，但是我们也不能确定那么多npm包，具体哪个有副作用，这个使用姿势不现实。这个还是需要时间成为社区一种规范做法的。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mini-css-extract-plugin 或者extract-text-webpack-plugin&lt;/code&gt;，将css单独生成一个文件&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;polyfile文件单独拆离，根据浏览器环境动态加载：&lt;a href=&quot;https://webpack.docschina.org/guides/shimming/#%E5%8A%A0%E8%BD%BD-polyfills&quot;&gt;参考https://webpack.docschina.org/guides/shimming/#%E5%8A%A0%E8%BD%BD-polyfills&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;optimization.minimize: true&lt;/code&gt;（pro默认状态）&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;两种模式下公用的优化方案&quot;&gt;两种模式下公用的优化方案&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ant-design&lt;/code&gt;，务必使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;babel-plugin-import&lt;/code&gt;插件来按需加载模块，既能减少构建体积，也减少了开发环境下的构建代码处理量，提高了构件速度&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;异步动态import&lt;/strong&gt;，对构建速度的提升作用类似拆项目，对构建结果更是减少了单个资源的体积&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;ignore某些用不到的大资源&lt;/strong&gt;，譬如moment里的locale相关文件，用分析工具会看到体积巨大，可以配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;希望以上内容能实践下来真的帮你提高工作中webpack的速度，而不仅是作为面试的回答。&lt;/p&gt;

&lt;h1 id=&quot;参考&quot;&gt;参考&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://juejin.im/entry/5c302140f265da611b587f99&quot;&gt;使用webpack4提升编译速度&lt;/a&gt;
&lt;a href=&quot;https://www.ibm.com/developerworks/cn/web/wa-lo-expedite-webpack/index.html&quot;&gt;加速 Webpack&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>webpack-loader与plugin简析</title>
   <link href="http://echizen.github.io/tech/2019/03-18-webpack-loader-plugin"/>
   <updated>2019-03-18T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/webpack-loader-plugin</id>
   <content type="html">
&lt;p&gt;本文浅度预警，只是loader和plugin作为webpack的一个重要内容，应该作为一个章节单独理理。&lt;/p&gt;

&lt;h1 id=&quot;loader&quot;&gt;loader&lt;/h1&gt;

&lt;p&gt;loader的核心作用就是把非标准js模块转化为js模块，这些非js模块包括css、file以及vue、jsx这样的模块，供后续webpack实现模块化处理并打成bundle文件。&lt;/p&gt;

&lt;p&gt;webpack的loader设计是如此有魅力，正式因为它的存在，我们才能all in js，前端工程化之路才如此规模化。&lt;/p&gt;

&lt;p&gt;来看看几个典型loader的处理方式：&lt;/p&gt;

&lt;h2 id=&quot;file-loader&quot;&gt;file-loader&lt;/h2&gt;

&lt;p&gt;从最简单的开始，file-loader没有对原文件做任何处理。会根据文件内容生成hash值（或配置其他规则生成一个唯一值），将这个值作为资源路径名url，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emitFile(url, content)&lt;/code&gt;放到 public path 目录下，将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return &apos;module.exports = __webpack_public_path__ + &apos;+ JSON.stringify(url)&lt;/code&gt;, 这样其他文件里引用这个file的地方就会使用生成的资源路径了。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/webpack-contrib/file-loader/blob/master/src/index.js&quot;&gt;file-loader代码&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;url-loader&quot;&gt;url-loader&lt;/h2&gt;

&lt;p&gt;url-loader一般用来处理将小size的图片资源转化为base64编码嵌入到代码内。&lt;/p&gt;

&lt;p&gt;所以url-loader 并不复制文件，而是把文件做base64编码，直接嵌入到CSS/JS/HTML代码中：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;获取 limit 参数&lt;/li&gt;
  &lt;li&gt;如果 文件大小在 limit 之内，则直接返回文件的 base64 编码后内容&lt;/li&gt;
  &lt;li&gt;如果超过了 limit ，则调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file-loader&lt;/code&gt;处理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/webpack-contrib/url-loader/blob/master/src/index.js&quot;&gt;url-loader代码&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;css-loader与style-loader&quot;&gt;css-loader与style-loader&lt;/h2&gt;

&lt;p&gt;css-loader和style-loader就复杂一些了。&lt;/p&gt;

&lt;p&gt;一般css-loader和style-loader都是配套使用，代在经过style-loader处理期间会调用css-loader。&lt;/p&gt;

&lt;p&gt;css-loader 的作用是处理css中的 @import 和 url 这样的外部资源，替换成require。转化过程中将本文件的代码以及本文件的依赖都送入到exports的数组里，并生成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports.locals&lt;/code&gt;含有唯一标识符的map来支持css module映射表。最终将css文件内容处理成可以require的模块。&lt;/p&gt;

&lt;p&gt;style-loader 相对好理解一点，作用是把样式插入到 DOM中，方法是在head中插入一个style标签，并把样式写入到这个标签的 innerHTML 里&lt;/p&gt;

&lt;p&gt;借用一下别人的图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/lihongxun945/diving-into-webpack/raw/master/images/style-loader-and-css-loader-pipeline.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/webpack-contrib/css-loader&quot;&gt;css-loader代码&lt;/a&gt;
&lt;a href=&quot;https://github.com/webpack-contrib/style-loader&quot;&gt;style-loader代码&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;开发loader&quot;&gt;开发loader&lt;/h2&gt;

&lt;p&gt;开发loader的姿势倒是非常简单，&lt;/p&gt;

&lt;p&gt;同步loader：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;someSyncOperation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;同步loader返回多个结果：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;someSyncOperation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;异步loader：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;someAsyncOperation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;详情：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://webpack.docschina.org/api/loaders/&quot;&gt;loader API&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://webpack.docschina.org/contribute/writing-a-loader/&quot;&gt;编写一个 loader&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;plugin&quot;&gt;plugin&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调，开发者可以引入它们自己的行为到 webpack 构建流程中&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;插件能在构建的各个阶段插入自己的行为，能影响改变构建输出内容，拆分增加构建文件等等&lt;/p&gt;

&lt;p&gt;一个插件由以下构成&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个具名 JavaScript 函数。&lt;/li&gt;
  &lt;li&gt;在它的原型上定义 apply 方法。&lt;/li&gt;
  &lt;li&gt;指定一个触及到 webpack 本身的 事件钩子。&lt;/li&gt;
  &lt;li&gt;操作 webpack 内部的实例特定数据。&lt;/li&gt;
  &lt;li&gt;在实现功能后调用 webpack 提供的 callback。&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 一个 JavaScript class&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyExampleWebpackPlugin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 将 `apply` 定义为其原型方法，此方法以 compiler 作为参数&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compiler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 指定要附加到的事件钩子函数&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;compiler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tapAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;MyExampleWebpackPlugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;This is an example plugin!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Here’s the `compilation` object which represents a single build of assets:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 使用 webpack 提供的 plugin API 操作构建结果&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们来写一个简单的示例插件，生成一个叫做 filelist.md 的新文件；文件内容是所有构建生成的文件的列表。这个插件大概像下面这样：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileListPlugin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compiler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// emit 是异步 hook，使用 tapAsync 触及它，还可以使用 tapPromise/tap(同步)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;compiler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tapAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;FileListPlugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 在生成文件中，创建一个头部字符串：&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filelist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;In this build:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// 遍历所有编译过的资源文件，&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 对于每个文件名称，都添加一行内容。&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;assets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;filelist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;- &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// 将这个列表作为一个新的文件资源，插入到 webpack 构建中：&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;compilation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;assets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;filelist.md&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filelist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filelist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

      &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FileListPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;更多参考：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://webpack.docschina.org/contribute/writing-a-plugin/&quot;&gt;编写一个插件&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://webpack.docschina.org/api/plugins/&quot;&gt;Plugin API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;参考文献&quot;&gt;参考文献&lt;/h1&gt;

&lt;p&gt;作者写的真的很不错，他的系列是webpack官网资料外我作为webpack源码阅读的指导文章，本文也是大量引用了以下文章内的片段。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/lihongxun945/diving-into-webpack/blob/master/3-style-loader-and-css-loader.md&quot;&gt;webpack 源码解析三：详解 style-loader 和 css-loader&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/lihongxun945/diving-into-webpack/blob/master/4-file-loader-and-url-loader.md&quot;&gt;webpack 源码解析 四：file-loader 和 url-loader&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>webpack-源码分析</title>
   <link href="http://echizen.github.io/tech/2019/03-17-webpack-source-code"/>
   <updated>2019-03-17T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/webpack-source-code</id>
   <content type="html">
&lt;h1 id=&quot;感受&quot;&gt;感受&lt;/h1&gt;

&lt;p&gt;先说下阅读感受，一开始打开webpack仓库，看到100多个文件，我的内心是懵逼的。然后看到一大半都是plugin，心中窃喜这些应该是不需要了解的。再后来读着读着发现很多想了解的功能实现都是通过plugin的。。。&lt;/p&gt;

&lt;p&gt;webpack的代码真的还是很复杂的，感受最深的是其利用tapable来实现的一套插件体系，扩展性很好，设计者还是很机智的，在这套体系上，内部近百个插件有条不紊，还能支持外部开发自定义插件来扩展功能，如果不是这个机制，整个庞大的构建流会更迷吧。支持这么多功能的构建工具开发出来复杂度真的挺高的。&lt;/p&gt;

&lt;p&gt;主流程还是比较清晰的在compiler里，但是去看compiler和compilation的生命周期就各有几十个，每个调用一批插件协调处理，如果不去debug，这个事件触发机制根本就找不到会触发哪些插件的事件回调，根本无法也没有意义一一看完，只能说后面有需要时再看具体的了。&lt;/p&gt;

&lt;p&gt;抓住主线搞清整体流程，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seal&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emitAssets&lt;/code&gt;这些核心步骤实现，可以通过通过几个常用的loader和plugin了解了下这两部分的实现。&lt;/p&gt;

&lt;p&gt;我向往的境界是读了能了解设计思路，放开徒手也能写出核心逻辑代码实现，再以后对新东西能自行通过api特征就推断出实现方式，然后读源码验证一下。显然差的还远，目前也就勉强够用，能了解到实现方式，能够知道去哪一块debug来解决遇到的问题，能够写loader和plugin扩展个性化功能。我没有在一堆源码里迷失主线，活着出来了已是万幸了。。。&lt;/p&gt;

&lt;h1 id=&quot;概况&quot;&gt;概况&lt;/h1&gt;

&lt;p&gt;初始化配置参数 -&amp;gt; 绑定事件钩子回调 -&amp;gt; 确定Entry逐一遍历 -&amp;gt; 使用loader编译文件 -&amp;gt; 输出文件&lt;/p&gt;

&lt;p&gt;网上有个复杂的图，画清楚了这个步骤：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_2b4k7ad7k75468g03b1eagca24j19_4436x4244.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;webpack主要的2个部分是Compiler和Compilation，Compiler基本上只是执行最低限度的功能，以维持生命周期运行的功能。它将所有的加载、打包和写入工作，都委托到注册过的插件上。它只是构建任务调度器，而compilation则是具体的构建内容步骤&lt;/p&gt;

&lt;h1 id=&quot;tapable机制&quot;&gt;tapable机制&lt;/h1&gt;

&lt;p&gt;要搞清webpack，还是先老老实实花时间搞清楚tapable机制，否则没法理解插件是怎么注册触发的。&lt;/p&gt;

&lt;p&gt;tapable提供类似的插件接口。webpack 中许多对象扩展自 Tapable 类。这个类暴露 tap, tapAsync 和 tapPromise 方法，可以使用这些方法，注入自定义的构建步骤，这些步骤将在整个编译过程中不同时机触发。&lt;/p&gt;

&lt;p&gt;tapable提供了很多类型的钩子，如同步的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SyncHook&lt;/code&gt;和异步的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsyncHook&lt;/code&gt;。我们一开始创建钩子类型和可获取参数，相当于规范了这个钩子的模样，然后在这个钩子上注册插件，后续再在触发这个钩子上的插件，传入规范定的参数，进而触发插件注册的处理函数。&lt;/p&gt;

&lt;p&gt;example:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 创建钩子&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Car&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;accelerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;newSpeed&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;brake&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;calculateRoutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AsyncParallelHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 调用对应的钩子，会触发注册的插件回调函数&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;setSpeed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newSpeed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;accelerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newSpeed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;useNavigationSystemPromise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculateRoutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getRoutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;nx&quot;&gt;useNavigationSystemAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculateRoutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getRoutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 注册插件&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myCar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Car&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Use the tap method to add a consument&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;myCar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;brake&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;WarningLampPlugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;warningLamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;myCar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;accelerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;LoggerPlugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newSpeed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Accelerating to &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newSpeed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;myCar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculateRoutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tapPromise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;GoogleMapsPlugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// return a promise&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;google&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;maps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;route&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;myCar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculateRoutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tapAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;BingMapsPlugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nx&quot;&gt;bing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;routesList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// call the callback&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;详情可见&lt;a href=&quot;https://webpack.docschina.org/api/plugins/#tapable&quot;&gt;https://webpack.docschina.org/api/plugins/#tapable&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;代码&lt;a href=&quot;https://github.com/webpack/tapable&quot;&gt;https://github.com/webpack/tapable&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;webpack中的hooks&quot;&gt;webpack中的hooks&lt;/h2&gt;

&lt;p&gt;利用tapable这样的设计就做到了将webpack功能碎片化，每个阶段拆成一个hooks，然后各种内外部插件都可以往这个hooks上注册插件，webpack在编译过程中的合适阶段会调用对应的钩子，从而触发一系列挂载在这个钩子上的插件。这样让webpack的每个构建过程的功能都可以无限扩展和定制。实现了一个强大的系统。&lt;/p&gt;

&lt;p&gt;如complilation上注册了几十个hooks，在编译的不同阶段会被触发:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Compilation&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Tapable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;cm&quot;&gt;/**
	 * Creates an instance of Compilation.
	 * @param {Compiler} compiler the compiler which created the compilation
	 */&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compiler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hooks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncHook&amp;lt;Module&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;buildModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncHook&amp;lt;Module&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;rebuildModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncHook&amp;lt;Module, Error&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;failedModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncHook&amp;lt;Module&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;succeedModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;

			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncHook&amp;lt;Dependency, string&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;addEntry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncHook&amp;lt;Dependency, string, Error&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;failedEntry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncHook&amp;lt;Dependency, string, Module&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;succeedEntry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;

			&lt;span class=&quot;cm&quot;&gt;/** @type {SyncWaterfallHook&amp;lt;DependencyReference, Dependency, Module&amp;gt;} */&lt;/span&gt;
			&lt;span class=&quot;na&quot;&gt;dependencyReference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SyncWaterfallHook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
				&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dependencyReference&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dependency&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;

      &lt;span class=&quot;cm&quot;&gt;/** more **/&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;webpack的主要功能都是通过plugin以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.hooks.xxx.tap(&apos;pluginName&apos;, fn)&lt;/code&gt;挂载到hooks上，在特定时机执行。
执行时即为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.hooks.xxx.call(&apos;args&apos;, callback)&lt;/code&gt;方式，清楚这个模式后，看源码就轻松很多。&lt;/p&gt;

&lt;h1 id=&quot;流程&quot;&gt;流程&lt;/h1&gt;

&lt;p&gt;创建compiler，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebpackOptionsApply.process&lt;/code&gt; 会根据配置项注册对应的内部插件，compiler.run进入核心流程。
webpack的强大在于在生命周期调用对应的插件处理&lt;/p&gt;

&lt;p&gt;关键生命周期：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;entry-options&lt;/li&gt;
  &lt;li&gt;compile&lt;/li&gt;
  &lt;li&gt;make: 分析入口，创建模块对象&lt;/li&gt;
  &lt;li&gt;build-module: 构建模块&lt;/li&gt;
  &lt;li&gt;after-compile:&lt;/li&gt;
  &lt;li&gt;emit: seal封装。生成assets&lt;/li&gt;
  &lt;li&gt;after-emit: render组合输出&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;翻阅源码时重点查看如下关键函数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;compile&lt;/li&gt;
  &lt;li&gt;make&lt;/li&gt;
  &lt;li&gt;compilation.addEntry&lt;/li&gt;
  &lt;li&gt;compilation: _addModuleChain&lt;/li&gt;
  &lt;li&gt;buildModule（NormalModule.js）&lt;/li&gt;
  &lt;li&gt;build - doBuild（NormalModule.js）&lt;/li&gt;
  &lt;li&gt;doBuild - runLoaders: 调用loaders, 任何模块都被转成了标准的JS模块&lt;/li&gt;
  &lt;li&gt;parse: 获得ast，调用 acorn 对JS代码进行语法分析，然后收集其中的依赖关系&lt;/li&gt;
  &lt;li&gt;seal - createChunkAssets - mainTemplate.render, render的renderBootStrap是生成webpack样板代码,template.js的renderChunkModules是真正拼接模块代码字符串的地方。根据之前收集的依赖，决定生成多少文件，每个文件的内容是什么。&lt;/li&gt;
  &lt;li&gt;render：这里是生成每个模块的代码片段&lt;/li&gt;
  &lt;li&gt;emitAssets: 将各模块的代码片段整合输出到chunk文件中&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;其他一些记录点&quot;&gt;其他一些记录点&lt;/h1&gt;

&lt;h2 id=&quot;插件调用时是在创建的临时函数里&quot;&gt;插件调用时是在创建的临时函数里&lt;/h2&gt;

&lt;p&gt;调用的插件处理方法都是通过new Function通过字符串拼接生成的。这个函数不止是开发定义的函数，前后还有很多webpack加入的内容，每个函数都不同。&lt;/p&gt;

&lt;p&gt;注册插件时，会将要调用的函数先生成好放在柯里化的返回函数里，然后在调用时进入执行，并接收所需参数&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_861ld8be6e4j2fb356kkc6gd4jha3_780x396.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.hooks.make.callAsync(compilation,...&lt;/code&gt;调用时的临时函数有：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_18j04gkh96dh0f1412dkke695e1c6_786x1406.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;意义何在？我理解是因为多样性，为了给每个插件调用加入不同的上下文执行代码。&lt;/p&gt;

&lt;h2 id=&quot;loader&quot;&gt;loader&lt;/h2&gt;

&lt;p&gt;loader是把非js模块处理成js模块&lt;/p&gt;

&lt;p&gt;runLoaders返回的result里是含有buffer原数据的数组，经过createSource处理成经过转化后的代码string,
这时候如果loader返回了ast后面将用这个ast，如果没有在经过doBuild回调里的this.parser.parse，调用parse.js中parse函数里的acorn.parse获得ast&lt;/p&gt;

&lt;h2 id=&quot;代码拼接生成&quot;&gt;代码拼接生成&lt;/h2&gt;

&lt;p&gt;代码分为webpack自己的脚手架代码部分，和经过loader处理的模块代码部分。&lt;/p&gt;

&lt;p&gt;render - renderBootstrap处理脚手架部分 - this.hooks.render.call - this.hooks.render.tap(“mainTemplate”) - this.hooks.module.call - this.hooks.module.tap(“JavascriptModulesPlugin”)处理模块拼接部分 - emitAssets将拼接的代码输出成对应bundle文件&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_50243l55lacacg2d6ffhi8l086hbe_1268x1110.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_01k2heagjbl943d8j019dfh593jd4_1772x1064.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_4i0ebgi9c2a8ic3k350kjjhg4gd09_1730x1466.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_56f4ge5i715ggff225g02k3i419i2_1366x964.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_1g04jebfika5b6dgdff98b8ag778e_1194x436.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_46388kijljfdg3g858k0f2fj49dc8_980x1042.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;会看到一些CacheSource利用缓存加速：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_415l8lkg88allfljb1l6c16c7634f_1582x1472.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;还有ReplaceSource既是处理模块化转化的部分：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_32gji9f5ik1k02eidib6k32c1139j_1730x1428.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;遍历拼接代码片段:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_24k4i0l2g8chc47gf1ca4ce7l5a3b_1868x972.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后输出到chunk文件：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_3646jci30gg129802eiefb63f2973_1506x982.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/190317_8ef9l3a4e1b674fbhh5499280172b_1334x1306.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过以上断点跟踪，就走完了代码拼接到输出的流程。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>webpack-打包后代码分析</title>
   <link href="http://echizen.github.io/tech/2019/03-17-webpack-bundle-code"/>
   <updated>2019-03-17T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/webpack-bundle-code</id>
   <content type="html">
&lt;p&gt;你是否清楚webpack究竟将你的代码处理成了什么样子？webpack是如何实现模块化的？为啥webpack既能支持es6 module又能支持commonjs规范的module？为啥webpack打包出来的模块在commonjs规范的模块里引用时要加default，即&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const moduleName = require(&apos;modulePath&apos;).default&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;如果你都清楚，那么本文可以跳过了~&lt;/p&gt;

&lt;h1 id=&quot;webpack是什么&quot;&gt;webpack是什么&lt;/h1&gt;

&lt;p&gt;官方说: &lt;strong&gt;webpack 是一个现代 JavaScript 应用程序的静态模块打包器(static module bundler)&lt;/strong&gt;。浏览器原生是不支持模块化的，虽然有新版本已经开始支持，但是考虑兼容性在相当长的一段时间里我们不能依赖，而webpack帮我们实现了模块化的支持，我们将代码按功能有序的进行模块化组织，webpack将这些模块化的代码合并打包成一个bundle文件，并使用自己的脚手架代码实现了模块化的语义，让模块与模块之间能够作用域隔离，能够互相引用。&lt;/p&gt;

&lt;p&gt;webpack比较神奇的是不仅支持js的模块化，还能支持css\file\image等各种文件的模块化，得益于强大的loader将各种类型的文件处理成js模块。&lt;/p&gt;

&lt;h1 id=&quot;webpack打包产物分析&quot;&gt;webpack打包产物分析&lt;/h1&gt;

&lt;h2 id=&quot;demo&quot;&gt;demo&lt;/h2&gt;

&lt;p&gt;文中相关代码在这里：&lt;a href=&quot;https://github.com/echizen/webpack-demo/tree/master/webpack-mod&quot;&gt;https://github.com/echizen/webpack-demo/tree/master/webpack-mod&lt;/a&gt;，可以自行clone下来验证&lt;/p&gt;

&lt;p&gt;我们通过一个最简单的例子来分析打包产物，创造3个文件：&lt;/p&gt;

&lt;p&gt;index.js:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./message.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msgEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;message.js:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./name.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;index.js:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;webpack配置：&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./src/index.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;index.bundle.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;optimization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;看看index.bundle.js的内容：&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// The module cache&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// The require function&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Check if module is in cache&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Create a new module (and put it into the cache)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;na&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;na&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;na&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Execute the module function&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Flag the module as loaded&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Return the exports of the module&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// expose the modules object (__webpack_modules__)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// expose the module cache&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// define getter function for harmony exports&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;enumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// define __esModule on exports&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Symbol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toStringTag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toStringTag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;__esModule&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// create a fake namespace object&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// mode &amp;amp; 1: value is a module id, require it&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// mode &amp;amp; 2: merge all properties of value into the ns&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// mode &amp;amp; 4: return value when already ns object&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// mode &amp;amp; 8|1: behave like require&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__esModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;enumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// getDefaultExport function for compatibility with non-harmony modules&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__esModule&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getModuleExports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// Object.prototype.hasOwnProperty.call&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hasOwnProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// __webpack_public_path__&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// Load entry module and return exports&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/************************************************************************/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 0 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// CONCATENATED MODULE: ./src/name.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// CONCATENATED MODULE: ./src/message.js&lt;/span&gt;


&lt;span class=&quot;cm&quot;&gt;/* harmony default export */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// CONCATENATED MODULE: ./src/index.js&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony export (binding) */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;


&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony default export */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msgEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;分析&quot;&gt;分析&lt;/h2&gt;

&lt;p&gt;可以看出，webpack打包出来的文件，用installedModules记录缓存的模块分析结果。自定义了一个require的实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__&lt;/code&gt;来实现模块的依赖引入功能。&lt;/p&gt;

&lt;p&gt;这个函数即是实现模块化的重点。原模块被包裹在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;function(module, __webpack_exports__, __webpack_require__) {}&lt;/code&gt;函数中。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__&lt;/code&gt;里创造的局部变量module变量来记录此模块函数调用后返回的结果，将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports, module, module.exports, __webpack_require__&lt;/code&gt;作为参数传入，每个模块函数，先调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__.r(__webpack_exports__)&lt;/code&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;上定义&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__esModule&lt;/code&gt;属性，作用是和前辈babel转化的结果保持一致，表明这是个由 es6 转换来的 commonjs 输出。。然后执行原模块代码，将export出的内容处理到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;上，如果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export default&lt;/code&gt;的内容则直接放置到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports[&quot;default&quot;]&lt;/code&gt;上，其他的非default的valName的export通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__.d&lt;/code&gt;实现的在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;defineProperty&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valName&lt;/code&gt;的getter来获取。这样调用了包裹原模块的函数后，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__&lt;/code&gt;函数最终return出来的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;上就有我们所有export出来的内容，实现了隔离作用域的模块化。&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c1&quot;&gt;// 调用处&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Execute the module function&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 定义处&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// CONCATENATED MODULE: ./src/name.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// CONCATENATED MODULE: ./src/message.js&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/* harmony default export */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// CONCATENATED MODULE: ./src/index.js&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony export (binding) */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;


&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony default export */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msgEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后还定义了一群常用的功能函数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;webpack_require&lt;/strong&gt;.d: define getter function for harmony exports，使用Object.defineProperty来定义对象上指定属性的getter函数&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;webpack_require&lt;/strong&gt;.r: define __esModule on exports&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;webpack_require&lt;/strong&gt;.t: create a fake namespace object，根据不同的mode做不同的处理&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;webpack_require&lt;/strong&gt;.n: getDefaultExport function for compatibility with non-harmony modules&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;webpack_require&lt;/strong&gt;.o: object.prototype.hasOwnProperty.call&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;scope-hoisting&quot;&gt;Scope Hoisting&lt;/h2&gt;

&lt;p&gt;认真观察你会发现一个现象，按理说我们是3个文件对应的3个模块，但是在打包产物里却被合并成了一个模块。这就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope Hoisting&lt;/code&gt;特性。&lt;/p&gt;

&lt;p&gt;webpack3开始引入了Scope Hoisting，Scope Hoisting 的实现原理其实很简单：分析出模块之间的依赖关系，尽可能的把打散的模块合并到一个函数中去，但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。&lt;/p&gt;

&lt;p&gt;能使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope Hoisting&lt;/code&gt;特性的要求：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;必须是ES6规范的模块化&lt;/li&gt;
  &lt;li&gt;没有使用 eval() 函数&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;modules列表用了Scope Hoisting作用域提升合并成了一个。好处：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;代码体积更小，因为函数申明语句会产生大量代码；&lt;/li&gt;
  &lt;li&gt;代码在运行时因为创建的函数作用域更少了，内存开销也随之变小。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;commonjs规范的模块打包&quot;&gt;commonjs规范的模块打包&lt;/h2&gt;

&lt;p&gt;上面的示例是es6规范的模块，我们再来看看commonjs规范的模块：&lt;/p&gt;

&lt;p&gt;index.js&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./name.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;name.js&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nx&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;nx&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打包产物：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// The module cache&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// The require function&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Check if module is in cache&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Create a new module (and put it into the cache)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;installedModules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;na&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;na&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 			&lt;span class=&quot;na&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Execute the module function&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moduleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Flag the module as loaded&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;c1&quot;&gt;// Return the exports of the module&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 省略n个__webpack_require__上挂载的功能函数&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;c1&quot;&gt;// Load entry module and return exports&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; 	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/************************************************************************/&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 0 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// import message from &apos;./message.js&apos;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// export const msg = message&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// export default msgEntry = message + &apos;!&apos;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt;


&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 1 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// export const name = &apos;world&apos;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nx&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;nx&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到cmd规范的模块没有使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope Hoisting&lt;/code&gt;了，区别也就是从es module都是在操作’module.exports’，变成了如果原模块透出的方式是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;则继续赋值给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;的对应属性上，如果原模块使用是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports.default&lt;/code&gt;则赋值给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports.default&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;所以webpack通过在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__&lt;/code&gt;上内部构造一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;的局部变量，无论原模块是es6还是commonjs，或者是2者混用，都将原透出属性透出到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;的对应属性上，将透出的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;属性透出到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports.default&lt;/code&gt;，来达到统一，内部可以支持各种类型的模块通用，因为都已经转化成了commonjs的格式。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;为啥commonjs格式的模块引入webpack打包后的模块需要加default&quot;&gt;为啥commonjs格式的模块引入webpack打包后的模块需要加default？&lt;/h3&gt;

&lt;p&gt;即&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const moduleName = require(&apos;modulePath&apos;).default&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;上面的分析我们知道，commonjs 里的require对应的是webpack内部的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__&lt;/code&gt;透出的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;，而default的对象被赋值在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports.default&lt;/code&gt;上，default也只是module.exports的一个叫default名的普通透出对象，自然是要加default属性声明的。&lt;/p&gt;

&lt;p&gt;那么es6的模块为啥就可以直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt;而不用管&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;呢？因为规范定义的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import moduleName from &apos;modulePath&apos;&lt;/code&gt;就是引入一个模块&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export default&lt;/code&gt;的内容啊，webpack为了遵循这个规范，在内部对default的内容做了标记处理，在引入的时候直接透出default的内容。&lt;/p&gt;

&lt;p&gt;为了查看这个，我们必须打破&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope Hoisting&lt;/code&gt;特性，来看看import语句和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__&lt;/code&gt;的对应调用关系。于是我们在message.js里使用下eval：&lt;/p&gt;

&lt;p&gt;message.js&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./name.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;var test = 1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;index.js&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./message.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msgEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打包出来模块相关部分的代码：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 0 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony import */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_name_js__WEBPACK_IMPORTED_MODULE_0__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;var test = 1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony default export */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_name_js__WEBPACK_IMPORTED_MODULE_0__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* name */&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 1 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony export (binding) */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// let c1 = &apos;c1&apos;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// let c2 = &apos;c2&apos;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// module.exports = {&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 	c1,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 	c2,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// }&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 2 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony export (binding) */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony import */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_message_js__WEBPACK_IMPORTED_MODULE_0__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;


&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_message_js__WEBPACK_IMPORTED_MODULE_0__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* default */&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony default export */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;msgEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_message_js__WEBPACK_IMPORTED_MODULE_0__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* default */&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// var c = require(&apos;./name.js&apos;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// exports.default = c&lt;/span&gt;


&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到bundle发生了很大的变化，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export default&lt;/code&gt;的内容不是再赋值给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_exports__[&quot;default&quot;]&lt;/code&gt;，而是赋值给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_exports__&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;这样内部定义属性的getter上，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt;语句出处理成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__(moduleId)[&quot;a&quot;]&lt;/code&gt;这样找到对应的属性，即es6模块中也没有将default看成特殊属性，而是取了别名，内部也通过别名去&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__webpack_require__&lt;/code&gt;找到对应内容，这里的注释&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/* default */&lt;/code&gt;在标记时起到与原代码透出变量对应的功能。&lt;/p&gt;

&lt;p&gt;为什么要这么搞呢？为啥default的es6和commonjs引入不搞成一致呢？为了跟规范保持一致啊，规范定义的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import moduleName from &apos;modulePath&apos;&lt;/code&gt;就是引入一个模块&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export default&lt;/code&gt;的内容，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const moduleName = require(&apos;modulePath&apos;)&lt;/code&gt;获取的是modulePath对应的模块透出的所有内容的一个对象即&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;的内容，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;只是一个普通属性即&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports.default&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;再提个历史，在 babel5 时代，大部分人在用 require 去引用 es6 输出的 default，只是把 default 输出看作是一个模块的默认输出，所以 babel5 对这个逻辑做了 hack，如果一个 es6 模块只有一个 default 输出，那么在转换成 commonjs 的时候也一起赋值给 module.exports，即整个导出对象被赋值了 default 所对应的值。这样就不需要加 default。&lt;/p&gt;

&lt;p&gt;babel5的这种做法其实会出问题：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// a.js&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 新增&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// b.js &lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./a.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 由之前的 输出 123, 变成 { default: 123, a: 123 }，导致使用方要改动引入代码&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以babel6不再做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports=exports.default&lt;/code&gt;的处理了，如果因为历史问题依赖这个处理，可以加plugin:&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;babel-plugin-add-module-exports&lt;/code&gt;&lt;/p&gt;

&lt;h1 id=&quot;webpack和babel&quot;&gt;webpack和babel&lt;/h1&gt;

&lt;p&gt;webpack和babel都支持es6 module，babel通常作为webpack的loader处理js，这2者又是什么关系呢？&lt;/p&gt;

&lt;p&gt;其实webpack2开始已经支持es6 module的处理，如果只是为了模块化，无需在加载babel，只是es6不止module还有很多其他特性需要babel帮我们处理。babel也是将es6 module转化成commonjs的module，从而再经过webpack时当commonjs module进行处理。&lt;/p&gt;

&lt;p&gt;譬如, es module:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;babel转化后，处理成commonjs格式：&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__esModule&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;webpack的打包类型&quot;&gt;webpack的打包类型&lt;/h1&gt;

&lt;p&gt;webpack支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;var、umd、commonjs、commonjs2、amd、amd-require、this、window、global、jsonp&lt;/code&gt;10种类型的打包产物：&lt;a href=&quot;https://webpack.docschina.org/configuration/output/#output-librarytarget&quot;&gt;output-librarytarget&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;var-默认&quot;&gt;var (默认)&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// __webpack_require__ 相关定义&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 0 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// module code&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;amd&quot;&gt;amd&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;modName&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// modules code&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;commonjs2&quot;&gt;commonjs2&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// modules code&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;umd&quot;&gt;umd&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;指定为modName时：&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webpackUniversalModuleDefinition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;amd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;modName&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;modName&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// modules code&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;未指定时：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webpackUniversalModuleDefinition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;amd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// modules code&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;window--global&quot;&gt;window || global&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;指定为modName时：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;modName&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// __webpack_require__ 相关定义&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// modules code&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;未指定时：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// __webpack_require__ 相关定义&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 0 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// module code&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;this&quot;&gt;this&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;指定为modName时：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;modName&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// __webpack_require__ 相关定义&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// modules code&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;未指定时：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// __webpack_require__ 相关定义&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 0 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// module code&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;commonjs&quot;&gt;commonjs&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;指定为modName时：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;modName&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// __webpack_require__ 相关定义&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// modules code&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output.library&lt;/code&gt;未指定时：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/******/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webpackBootstrap&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// __webpack_require__ 相关定义&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})([&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* 0 */&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/***/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// module code&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到各种模块化的方式都是使用对应规范的代码包裹了webpack原本的脚手架代码和模块加载代码，使执行后的结果挂载到入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;windows&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports[&quot;modName&quot;]&lt;/code&gt;这些对象上。&lt;/p&gt;

&lt;h1 id=&quot;参考文档&quot;&gt;参考文档&lt;/h1&gt;

&lt;p&gt;自己倒腾了demo之后，去网上搜了下，发现这位同僚的文章讲的更透彻：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://juejin.im/post/5a2e5f0851882575d42f5609&quot;&gt;import、require、export、module.exports 混合使用详解&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>webpack-打包原理</title>
   <link href="http://echizen.github.io/tech/2019/03-16-webpack-bundle-theory"/>
   <updated>2019-03-16T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/webpack-bundle-theory</id>
   <content type="html">
&lt;p&gt;开始填坑系列，将前2个月原理&amp;amp;源码维度学习的webpack\vue\react所得收获整理出来。&lt;/p&gt;

&lt;p&gt;webpack的打包原理是怎样，其实刨除loader\plugin的核心功能实现起来并不难，这份仓库代码很好的展现了核心逻辑：&lt;a href=&quot;https://github.com/ronami/minipack&quot;&gt;https://github.com/ronami/minipack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;简要代码：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;localRequire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;localRequire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})({&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./message.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_message2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_interopRequireDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

      &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_interopRequireDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__esModule&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_message2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./message.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;__esModule&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./name.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

      &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./name.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;__esModule&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打包过程：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;提供一个入口文件&lt;/li&gt;
  &lt;li&gt;babylon分析这个入口文件
    &lt;ul&gt;
      &lt;li&gt;提取依赖&lt;/li&gt;
      &lt;li&gt;获得babel转成es5后的兼容代码&lt;/li&gt;
      &lt;li&gt;确定个全局标识符id&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;再对入口文件的依赖进行递归处理，进行上述分析，得到依赖图graph&lt;/li&gt;
  &lt;li&gt;生成bundle
    &lt;ul&gt;
      &lt;li&gt;将graph的每个片段都插入一些代码，成为 { id: [func(require, module, exports){ origin code}, dependenceMap]}的结构&lt;/li&gt;
      &lt;li&gt;定义自己的依赖加载函数require&lt;/li&gt;
      &lt;li&gt;将两部分字符串代码片段合成&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;核心是打包工具自己实现的依赖加载器require，require里创造的module变量来记录fn调用后返回的结果，babel转完后，如果有返回值会挂到exports的变量上，着这里被module.exports接收，并作为require函数的返回，所以require函数的返回是执行对应的文件后所获得的该文件export出来的内容，localRequire里将这个值返回出来也就是原文件import 需要得到的值，供原文件引用。&lt;/p&gt;

&lt;p&gt;想想17年我做vue转小程序的vx打包工具时，并不了解这样的打包实现，但是也是用了相同的方式，babel分析ast得到依赖图，再对每个依赖递归处理，技术贵重的是思路啊。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>一个常见需求的nginx配置踩雷</title>
   <link href="http://echizen.github.io/tech/2019/02-18-nginx-index-page"/>
   <updated>2019-02-18T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2019/nginx-index-page</id>
   <content type="html">
&lt;p&gt;最近想给做的node应用服务提供nginx静态资源代理层。之前没做是不要约束大家的路径，后来想想不想被约束的开发可以不用，但是作为基础服务这个能力还是要有的，还有想用的人呢。况且，nginx代理静态资源可比node性能高多了，特别是大文件。&lt;/p&gt;

&lt;p&gt;本以为很快就能配完测好上线了，好歹我也还是比较熟nginx的，结果一动手，发现踩了一片雷。。。&lt;/p&gt;

&lt;p&gt;需求非常常见：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;静态资源走nginx代理&lt;/li&gt;
  &lt;li&gt;web接口代理给node应用&lt;/li&gt;
  &lt;li&gt;首页可以免路径，直接通过域名xxx.com访问&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;静态资源可以约束下路径到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/static&lt;/code&gt;文件夹下。&lt;/p&gt;

&lt;h2 id=&quot;location-优先级&quot;&gt;location 优先级&lt;/h2&gt;

&lt;p&gt;最折腾人的就是第3项。脑子一拍最先想到配置就是用location的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;= /&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
  listen 80;
  root /Users/echizen/demo/nginx-learn/site1;

  location = / {
    index /static/index.html;
  }

  location / {
    proxy_pass http://127.0.0.1:8020;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是测试时一直走到proxy_pass的第二个location block了。翻下&lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_core_module.html#location&quot;&gt;location配置文档&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;using the “=” modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates. For example, if a “/” request happens frequently, defining “location = /” will speed up the processing of these requests, as search terminates right after the first comparison. Such a location cannot obviously contain nested locations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;还给了个案例，看着优先级规则没理解错啊：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;location = / {
    [ configuration A ]
  }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;location / {
      [ configuration B ]
  }&lt;/p&gt;

&lt;p&gt;location /documents/ {
      [ configuration C ]
  }&lt;/p&gt;

&lt;p&gt;location ^~ /images/ {
      [ configuration D ]
  }&lt;/p&gt;

&lt;p&gt;location ~* .(gif|jpg|jpeg)$ {
      [ configuration E ]
  }
  The “/” request will match configuration A, the “/index.html” request will match configuration B, the “/documents/document.html” request will match configuration C, the “/images/1.gif” request will match configuration D, and the “/documents/1.jpg” request will match configuration E.&lt;/p&gt;

&lt;h2 id=&quot;index&quot;&gt;index&lt;/h2&gt;

&lt;p&gt;毕竟代码量少，挨个怀疑就怀疑到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index&lt;/code&gt;了，一翻&lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_index_module.html#index&quot;&gt;index配置文档&lt;/a&gt;，果然踩雷，连例子都甩出来了：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It should be noted that using an index file causes an internal redirect, and the request can be processed in a different location. For example, with the following configuration:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;location = / {
      index index.html;
  }&lt;/p&gt;

&lt;p&gt;location / {
      …
  }
  a “/” request will actually be processed in the second location as “/index.html”.&lt;/p&gt;

&lt;p&gt;index内部是通过重定向实现的啊，就给重定向到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;location /&lt;/code&gt;了&lt;/p&gt;

&lt;h2 id=&quot;try_files&quot;&gt;try_files&lt;/h2&gt;

&lt;p&gt;那就用try_files吧，不过一开始是拒绝这个玩意的，因为我明明知道准确路径，为啥要try_files来消耗资源，而且这货还必须至少2条file规则。后来仔细反思，其实挺好，这样多配几个规则可见兼容更多的index位置场景，把最常见的放第一个就好。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files&quot;&gt;try_files文档&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;值的注意的是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;try_files最好在最后一条配上404，否则资源找不到nginx会陷入循环寻找导致500&lt;/li&gt;
  &lt;li&gt;这货默认内部有最后一条规则走$uri，也就是路由的路径去寻找，所以看到没有命中自己index规则而是走了真正的路由路径也不要奇怪。
    &lt;blockquote&gt;
      &lt;p&gt;If none of the files were found, an internal redirect to the uri specified in the last parameter is made&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;合度代理&quot;&gt;合度代理&lt;/h2&gt;

&lt;p&gt;之前孤陋寡闻，为了能将所有的路径都转发给node服务，规则配置的是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;location / {
  proxy_pass http://127.0.0.1:8020;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是其实nginx对静态资源的代理性能更好，为啥不优先走nginx呢。所以可以优化成：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;location / {
  try_files $uri @proxy;
}

location @proxy {
  proxy_pass http://127.0.0.1:8020;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;安全性&quot;&gt;安全性&lt;/h2&gt;

&lt;p&gt;目前为止，看上去需求都完成了：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
  listen 80;
  root /Users/echizen/demo/nginx-learn/site1;

  location = / {
    try_files index.html /static/index.html;
  }

  location / {
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_pass http://127.0.0.1:8020;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是内心惴惴不安，这么合适么，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try_files $uri&lt;/code&gt;让所有请求都先按请求路径查询了一遍，也就是说服务器根目录下的任何文件都被搞成静态资源代理了，服务器所有代码文件现在都可以在浏览器上直接访问查看了。。。虽说我是给内网应用配模板的，安全风险可控，但是总觉得是掉节操。&lt;/p&gt;

&lt;p&gt;所以我不再所有代理所有路径了，只让static成为可访问路径：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
  listen 80;
  root /Users/echizen/demo/nginx-learn/site1;

  location = / {
    try_files /static/index.html index.html $uri=404;
  }

  location /static/ {
  }

  location / {
    proxy_pass http://127.0.0.1:8020;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;去除路径中的固定字段&quot;&gt;去除路径中的固定字段&lt;/h2&gt;

&lt;p&gt;node代理都是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;koa-static&lt;/code&gt;习惯了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/static/demo.js&lt;/code&gt;只需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://xxx.com/demo.js&lt;/code&gt;就能访问，无需非得在路由里加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static&lt;/code&gt;，虽说也可以用封装一下自定义版的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;koa-static&lt;/code&gt;，也借用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send&lt;/code&gt;包但是把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static&lt;/code&gt;路径补上，但是这样就改动了已有应用的路由要求，而且nginx明显有能力将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static&lt;/code&gt;层去掉。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try_files /static/$uri&lt;/code&gt;搞起来就好。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
  listen 80;
  root /Users/echizen/demo/nginx-learn/site1;

  location = / {
    try_files /static/index.html index.html $uri=404;
  }

  location / {
    try_files /static/$uri @proxy;
  }

  location @proxy {
    proxy_pass http://127.0.0.1:8020;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不过这也不是最优方案，因为所有请求都会先去匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/static/$uri&lt;/code&gt;, 性能再高也是要去判断文件是否存在的，是个消耗。只是对于我的场景，内网应用，保持一致的习惯接入用户最小的改动比一点点的性能损耗来说是合适的。&lt;/p&gt;

&lt;p&gt;可见最终这个nginx层对开发来说无感知，只是要求如果想使用nginx的能力就把静态资源放站点的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/static&lt;/code&gt;路径就好，如果不使用这个不走nginx配置指定的首页，请求也会走到node服务的接口。&lt;/p&gt;

&lt;h2 id=&quot;残留的疑惑&quot;&gt;残留的疑惑&lt;/h2&gt;

&lt;p&gt;在此途中也遇到了2个查了文档搜了google也无法解释的现象：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try_files&lt;/code&gt;的file顺序调一下，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt;放前面(该文件不存在)，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http:xxx.com&lt;/code&gt;就不能正常代理到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static/index.html&lt;/code&gt;了，而是走了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proxy_pass&lt;/code&gt;。即：&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  server {
    listen 80;
    root /Users/echizen/demo/nginx-learn/site1;

    location = / {
      try_files index.html /static/index.html;
    }

    location / {
      proxy_pass http://127.0.0.1:8020;
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;难道是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try_files&lt;/code&gt;是个获取文件的尝试，会按优先级命中所有路由？即1.获取/Users/echizen/demo/nginx-learn/site1/index.html静态文件，不存在。 2.尝试http://127.0.0.1:8020 代理，如果404的话继续下去 3.获取/Users/echizen/demo/nginx-learn/site1/static/index.html…&lt;/p&gt;

&lt;p&gt;2.上面的写法不ok，但是加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;location /static/&lt;/code&gt;的规则就正常工作了，这个强行我也解释不了了。。。即：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  server {
    listen 80;
    root /Users/echizen/demo/nginx-learn/site1;

    location = / {
      try_files index.html /static/index.html;
    }

    location /static/ {
    }

    location / {
      proxy_pass http://127.0.0.1:8020;
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;难道是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try_files&lt;/code&gt;只是管理路由优先级，并不做可访问性代理？所以一定要后面接的path路径本身在其他规则下可访问？。但是try_files的文档介绍它的功能是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Checks the existence of files in the specified order and uses the first found file for request processing&lt;/code&gt;，明显是查找文件是否存在，存在就响应啊。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>2018年终总结-积蓄</title>
   <link href="http://echizen.github.io/think/2019/02-04-2018summary"/>
   <updated>2019-02-04T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2019/2018summary</id>
   <content type="html">
&lt;p&gt;年终总结是我的传统项目，今年时间尤其紧张，导致我现在过年前一天晚上23点还在这儿写着。&lt;/p&gt;

&lt;p&gt;这一年我自己感觉最大的变化是成熟了，不知这是不是一件值得高兴的事。我不再那么情绪化，理性值又上升很多，看事情角度也更广了。但是这也是一个令我畏惧的事，我害怕失去年少的天真烂漫，我怕活的现实，我喜欢心里的浪漫主义，喜欢浮在云端的诗和远方。同时对年龄很敏感了，在我心中，一直有种过了24岁就不再年轻的认知。。。我不断的提醒自己，心态保持年轻，不断学习，不断翻新生活体验，好好保养自己，就不会被生理年龄束缚。&lt;/p&gt;

&lt;h1 id=&quot;技术与工作&quot;&gt;技术与工作&lt;/h1&gt;

&lt;p&gt;今年发生了一些事，整体给我的影响是我变踏实了很多。同时感受到不管鸡汤怎么麻痹自己，真相是&lt;strong&gt;压力是最快且有效的成长方式&lt;/strong&gt;，有目的的学习也是最快最有效的学习。&lt;/p&gt;

&lt;h2 id=&quot;拓广度挖深度&quot;&gt;拓广度，挖深度&lt;/h2&gt;

&lt;p&gt;一直心虚不了解服务器环境，想要接触服务器，但是一直没有机会。调组了，感谢团队leader的支持，让我终于能接触实战这方面了~ 在公司node服务部署发布的基建过程中，真是好好的拓展了这方面的知识，linux系统、一些脚本如shell脚本、expect脚本、makefile脚本、nginx、docker容器……全都不是停留在了解层，或多或少实战使用了，不过认知的不深，只能算广度上的拓展，和后端及运维们是没法相提并论的，但是只是作为个前端弄点小型node应用，还是够用了的。&lt;/p&gt;

&lt;p&gt;node应用也做了2个内部小项目在线上服务着，也算是个新的实战，不过这块实在没啥好说，拿着koa写点node应用还是没啥难度的。和同事研究的一些数据聚合监控、进程锁这些应用层的基础服务，觉得自己做做应用层还不了解原理的实在差太远了，简直不值得提。数据库和一些后端的思想也捡起来了些，想读书时我也是写过1年php的人，但是3年不写就全忘了。所以技术真是个不用就会忘的东西。&lt;/p&gt;

&lt;p&gt;vue平台系统的项目也做了一个，是内容业务这边一个页面搭建系统，这次推翻了以前电商那边的那套，抛开历史包袱，做了很多新尝试。从开发者的模块设计、打包，到搭建平台的配合，独立设计了一套，最终上线运行证明整个体系的可用性。所以它带给我的不仅仅是一个vue的中型项目的实践，更是模块体系的设计。&lt;/p&gt;

&lt;p&gt;深度方面，我开始关注常用工具的实现方式，不了解实现用起来看上去也没啥问题。就是有时候会有种我这么用它会不会xxx的疑惑不能解。但是当自己遇到不常见的复杂场景时，才会发现思路阻塞，不知道要怎么解决，了解实现的高手就会从底层分析找出方案。还有就是不知道实现，就不知道还有哪些地方可以优化，上层怎么配合使用更高效。遇到需要debug源码的bug时也会慢很多。基于以上3点，我在最后3个月，看了pm2、vue、webpack、react的源码（react还没看完），特别是vue的看完后通透多了。这种看源码的操作是最直接的了解实现方式，感觉还是收益的，虽然还不能立即体现其应用价值，但是我觉得未来会发挥作用的。我觉得自己最近踏实多了。&lt;/p&gt;

&lt;p&gt;我都工作快3年了，有点懊悔为什么现在才觉悟去认真看源码，以前都是用啥看啥，需要啥就去看某一小部分的实现，从来没有串起来过。以前急于做东西出成绩，这种模式高效高产，但是底子不够，遇到更有难度的场景，就只能求助了。后一个季度业务不怎么忙碌，也让我有时间静下心来好好去深挖一下。看完后，会发现原理其实很简单，其实还是要基础知识牢靠，知道用这些方式去组合实现这样的效果。但是自己还没达到从实现方式中找出灵感思路，从设计者角度理解其设计的境界。&lt;/p&gt;

&lt;h2 id=&quot;代码质量检测平台&quot;&gt;代码质量检测平台&lt;/h2&gt;

&lt;p&gt;这是自己的一个idea，想在静态检查方面做一些尝试，来检测代码质量。首先一个重点就是重复度检测，基于ast，在js和css方面都能出不错的检测效果。然后结合公司统一的eslint规则检查、单测，给出每个项目的检测结果。为此我不仅做了cli工具，还做了一个平台叫skynet，hhh，给出每个项目的检测评分和具体细节，汇表成长记录来反映单个项目的质量情况。记录平台上项目的各种维度的数据，来反映整体项目的情况。&lt;/p&gt;

&lt;p&gt;也得感谢leader让我这么试水，但是上线一个季度观察后，我发现效果其实不达预期，我希望是给别人一种可以了解自己代码质量的方式，然后去优化代码。但是只是个辅助工具，不涉及任何拦截，所以大家并不会真的去根据报表优化项目。我依旧觉得这是个不错的有意思的产品，但是不是个有效的产品，因为没触碰到痛点，代码质量一直是程序员的自我修养，不能引起广泛共识去重视，也就不会发挥价值了。所以从工作角度说，要想出成绩，做的东西还是要解决痛点，能落地。&lt;/p&gt;

&lt;p&gt;技术上我也没有做足，在重复度检测工具上都是暴力穷举，应该使用算法来优化性能的。&lt;/p&gt;

&lt;p&gt;但是anyway，这是我的一个技术方面的idea从萌发到落地成产品的过程，还是值得mark的。&lt;/p&gt;

&lt;h2 id=&quot;一些不算成功的项目&quot;&gt;一些不算成功的项目&lt;/h2&gt;

&lt;p&gt;今年整体来看因为公司转型，我这边的业务压力小了，做了一些尝试性的东西。有几个从时间来看，为了一些小的场景，兼容历史包袱，做了不小的调整，最终给使用者带来的副作用大于收益。所以结果导向是有道理的，一切开发还是要以解决更大更多的问题为先。&lt;/p&gt;

&lt;p&gt;还给我一个不一样的认知是项目是会失败的，这其实是家常便饭。。。没什么大不了的，重要的是过程中有收获。当然更要有反思，为啥失败，以后如何避免再发生。&lt;/p&gt;

&lt;h2 id=&quot;负责项目协调给他人创造价值&quot;&gt;负责项目，协调给他人创造价值&lt;/h2&gt;

&lt;p&gt;虽然我还没完全hold住自己的成长，但是我有个进步是，开始关注自己给他人带来的东西。&lt;/p&gt;

&lt;p&gt;带过2个新人，我会去设计怎么让她们由浅入深的学习，怎么平滑的成长到能独立做事情。&lt;/p&gt;

&lt;p&gt;带项目组做项目更是，一开始非常痛苦，以前自己吭哧吭哧就都搞出来了，风险完全可控。但是作为负责人带项目就累多了，从一开始的需求剖析、技术设计，到开发过程中进度把控、解决成员们遇到的疑难问题，到后期的测试把控质量。虽然自己没写多少代码但是累成狗。这个过程中，我也会不断想怎么给成员们带来价值，而不是仅仅让他们完成需求，怎么满足他们的成长。这么想我很累，但是我意识到我不断去想这些东西时是我在进步。&lt;/p&gt;

&lt;h2 id=&quot;晋升&quot;&gt;晋升&lt;/h2&gt;

&lt;p&gt;17年1月晋升后。18年4月份又被主管提名了晋升，作为16届唯一一个去晋升的，我还是挺开心的。但是没能通过，我意识到自己的表现能力太差了点，其实17年做的事很有价值也挺有技术含量，所以对这个结果也是挺失落的。未能完成前期一年晋升一次的目标，时间不能回退，终究是个遗憾了。&lt;/p&gt;

&lt;h2 id=&quot;weekly&quot;&gt;weekly&lt;/h2&gt;

&lt;p&gt;18还有个很进步的点，我从10月开始推行了月计划，给每月要做的事按优先级排出来。实施之后，执行力高了很多。&lt;/p&gt;

&lt;p&gt;总体来看，工作上没啥出色的，技术上更踏实，但是离各路大佬们的水平距离还很大，要继续再接再励。&lt;/p&gt;

&lt;p&gt;我开始坚定自己的方向，至少接下来3年我都是想好好做技术，研究的更深入，不可替代性要体现在自己能解决别人解决不了的难题上。也要逐步抬高视角，既落地具体实现，也把控高层设计。&lt;/p&gt;

&lt;h1 id=&quot;生活&quot;&gt;生活&lt;/h1&gt;

&lt;h2 id=&quot;理财&quot;&gt;理财&lt;/h2&gt;

&lt;p&gt;本年度最大的学习就是理财了，开始基金和股票的投入。虽然赶上股灾，收益是负数，亏的还不少。但是这段寒冬里的理财经历挺宝贵的。从宏观上去看经济，对很多生活和社会问题有了更深的认识，对zf的政策更是。。。我开始看到人口红利流失、老龄化在接下来会给我们带来多大的挑战；我开始明白房价为啥一直高升不降，以及各线城市未来降的几率；贸易战打的也让我看到我国的国际地位和面临的形式；也看到了我们的经济健康度，08年还在读书时吹嘘的4万亿拉动内需度过经济危机给我们带啦了多久的后遗症，为啥现在要去杠杆搞供给侧改革，把18年搞成寒冬，为啥又不能继续这么大力把经济往良性上拉了；还有我国特别国情下的市场经济和zf调控之间相互作用带来的奇妙反应。&lt;/p&gt;

&lt;p&gt;闲余的碎片化时间我看的经济相关的文章比技术类的都多，一方面是一直看技术有点太狭隘了，怕自己思维固化，看看经济发散思维。另一方面，我觉得学点经济总是有用的。同事总说我你老看那些玩意也没用啊，手里的股票不还是跌，是的，个人如何在大局向下的情况下保持收入向上，这个形式想赚钱太难了，但是我依旧没有退市，把自己扔里面，热情和关注度会高很多。我很喜欢去看宏观方面的经济，虽然就算经济崩了，我现在也不能保住自己的资产不贬值，只是因为能力不够，但是提前了解这些知识，未来我有能力（应该说是有资产了。。。）了，一定能有用。&lt;/p&gt;

&lt;p&gt;学了一年，说了很多，但是依旧不能预测股市走向，毕竟这玩意收实时刺激、经济基本面、市场情绪多方综合作用，想预估太难，但是整体大方向基本是看的出来的，只是具体时间段内的预期值真是估不准。。。学宏观经济是一方面，但是没有为了炒股去学啥k线图，总感觉那是玄学，没道理啊。。。&lt;/p&gt;

&lt;h2 id=&quot;旅行&quot;&gt;旅行&lt;/h2&gt;

&lt;p&gt;打卡了稻城亚丁，依旧成行不易，依旧没人陪我一起出去看看世界。不过靠着在内网吆喝，还是拉了几个小伙伴成行了。川西风景如预期的美，稻城亚丁出行条件比较苦，但是非常值得去看看。&lt;/p&gt;

&lt;p&gt;旅行已经没最初的那种激情了，因为这种请司机的半自由行都是去大众景点，时间行程也不自由。憧憬的是自由的去小众的地方好好沉浸式体验。这个愿望很难达到，只能和旅行大佬们成行了。所以自己如果不扩展下社交圈和方式真的只能不能有突破。&lt;/p&gt;

&lt;p&gt;anyway，能够开着越野车满世界转转，过一段无拘无束的生活，依旧是我现在最期待的远方~&lt;/p&gt;

&lt;h2 id=&quot;摄影&quot;&gt;摄影&lt;/h2&gt;

&lt;p&gt;一年里出去拍了不少，不过也没啥突破性成长。&lt;/p&gt;

&lt;p&gt;倒是在稻城没有三脚架和快门线，徒手拍出绝美星空后，就开始对器材没追求了，打消了所有升级器材的想法。那张星空真是年度最佳摄影经历。在川西还是拍了不少好照片的，ps稻城的星空真的是美哭了。&lt;/p&gt;

&lt;p&gt;比较大的进步是开始拍了不少自己，对镜头适应了不少，拍出了不少自己喜欢的照片，算是记录了一下青春吧。通常是自己选好机位角度感觉，找人帮我按下快门，感谢我的男朋友和妹妹，还有同事。认识自己也挺重要的，知道自己长啥样，适合什么风格，外形上的优缺点。五官挺精致的，不过，我的死角也是真的多啊，还千万不能笑。。。&lt;/p&gt;

&lt;h2 id=&quot;感情&quot;&gt;感情&lt;/h2&gt;

&lt;p&gt;so sad，我结束了第二段感情经历。。。&lt;/p&gt;

&lt;p&gt;遗憾还是遗憾的，遇到了挺喜欢的人，却深入了解后发现对生活的态度不同，也没有啥共同话题，最终选择分开。&lt;/p&gt;

&lt;p&gt;我觉得在这段感情里我成熟多了，比较好的控制情绪，情绪化的时候很少，不胡闹。也尽力去尊重别人，没有给别人多少侵入，克制自己不去改变别人，每个人都有选择生活方式和想成为什么样的人的权利，不因为伴侣身份就要求干扰别人，即使别人为自己改变也应该是主动地。&lt;/p&gt;

&lt;p&gt;我也很感谢对方，让我看到了人是可以很简单纯粹的，看到了可以怎样的温暖纯良。&lt;/p&gt;

&lt;p&gt;增加一次经历不免多一丝难过，但是我越来越意识到自己想要和什么样的人一起生活，适合什么样的感情了，也算是有收获。&lt;/p&gt;

&lt;p&gt;打算好好过一段空窗期了。好好让自己变得更好吧~不过想要遇到match的人，等是不行的，光让自己变得好也是不够的，还是要扩大交际圈，去结实相同兴趣圈的人啊。&lt;/p&gt;

&lt;h1 id=&quot;这一年影响我的事件&quot;&gt;这一年影响我的事件&lt;/h1&gt;

&lt;p&gt;除了这几块的整体review，这次还想记录一些影响我的大事件。事件顺序&lt;/p&gt;

&lt;h2 id=&quot;丢狗&quot;&gt;丢狗&lt;/h2&gt;

&lt;p&gt;年初我莫名其妙非常意外的丢了狗狗小北，其实是被以前一起养狗的前任以接狗回家过年团聚一下为由，毫无防备的送出去了，年后沟通了约3-4次没能接回来，对方说要长期养然后把我拉黑了。。。真是黑人问号脸。。。这莫名其妙毁三观的操作我都不知道怎么描述。&lt;/p&gt;

&lt;p&gt;这事一方面是让我觉得人心可畏，一起4年也没能看清对方居然会作出这种举动，所以与人结交最重要的还是人品啊，人品不够好，就是一颗地雷。不过不管别人怎样，虽然自己心里也气的牙痒痒，恨不得大街上能再遇见后一砖头拍下去。但是终究没有再联系纠结这事，也没有用任何手段去把狗以同样方式骗回来，毕竟这狗真说也不是我一个人的。无论别人怎样，还是要保持自己的善良，不然世界还有啥憧憬，对自己的行为如何释怀。&lt;/p&gt;

&lt;p&gt;不过自己终究是畏缩讨厌和这样的人继续纠结，而没有捍卫自己的正当权利，还是要学会保护自己，古语说的很到位，害人之心不可有，防人之心不可无。学会保护自己是一方面，当权利被侵犯时，维权这个能力也要好好加强。&lt;/p&gt;

&lt;p&gt;丢狗的那段日子相当的伤心，前2天同事一跟我提狗我就去卫生间流眼泪了，每晚梦见小北，连续梦了一个多星期，讲真从来没有这样的经历。后来很长时间都逃避狗的话题，见不得别人遛狗。被拉黑后因为觉得恶心没有尝试过任何渠道的继续联系，但是一直心有期待，直到9月才把没吃掉的2箱狗粮寄给流浪狗基地，断了自己小北还回回来的念想。&lt;/p&gt;

&lt;p&gt;不过，一切都过去了，现在也能风平浪静的回忆小北了，以后等我安定了，我还是会再去收养一直流浪狗，最好跟小北长得像的那种，虽然不会再是它，但是我会一样爱它。&lt;/p&gt;

&lt;p&gt;虽然当初那般难过，终究也没影响自己生活多少，感觉自己长大了，承受能力强了，不用再畏惧任何失去了。生活中难免遇到糟心事，不能一直沉浸其中，日子总要继续，总要学会自己开导自己。&lt;/p&gt;

&lt;h2 id=&quot;知乎被关注&quot;&gt;知乎被关注&lt;/h2&gt;

&lt;p&gt;这个事情来得就比较突然了。3月份看到美团开源了mpvue，并获得了巨大关注量，一看内容这和我17年做的东西基本一样啊。安奈不住激动心情去知乎一个相关问题下说了几句，可能是因为赶上热点了，也获得了不少的关注量，关注、点赞、私信，一波意外的流量。当时心有点激动，但是马上就冷静下来了。&lt;/p&gt;

&lt;p&gt;首先是和2个mpvue的核心开发交流了几句，其实当时自己回答一部分是安奈不住做了相同事的激动，一部分有点炫耀我们去年也作出了这样的东西投入使用了，回答色彩颇有些洋洋得意。但是对方交流时，态度非常谦虚，但是发现对方做这个不比我们晚，而且还剥离业务开源了，更是伟大，其实当时自己只是做转化和打包方面的，对我学长做的vue底层移植小程序这块了解并不到位，所以一波交流，其实高下立见。但是对方一直很友好很谦虚。让我感受到大牛都是谦虚的，自己一定不能半桶水还晃悠，要好好做技术。&lt;/p&gt;

&lt;p&gt;另一方面，团队里很佩服的2个大牛，技术水平很高，能力都很出众，但是基本不在社区露脸。我感到高手在民间，有水平的人都忙着写代码呢，哪会没事在各种社区说些不痛不痒的话（当然认真答题解决疑惑的值得尊敬），这让我重新反思各处蹦跶的大V们的质量，如果只是抓住流量成为网红，真的没什么值得follow的。&lt;/p&gt;

&lt;p&gt;本来我是个爱写东西的人，误打误撞来了一波流量，可以抓住好好利用，也许我也能混个小V出来，但是想到以上2点后，我不仅接下来都安静不答题了，连点赞都很谨慎，不要给关注者带来垃圾信息流。我也开始不再关注网红，讨厌撕逼，知乎环境变了后我活跃度也减了。我只关注那些认认真真回答的问题和文章了。自己依旧还是在自己的博客写点技术文，有些东西不写不总结，总是印象不深，写文章时的深层审查会加深理解，这是我坚持写的原因，当然能被其他人看到带来帮助更有价值，偶尔收到私信感谢我的某个文章的我都很开心，但是给别人看绝不是我写的第一目的。虽然后几个月，我读源码有很多收获，但是实在节奏太快，忙不过来，我的笔记都没能整理成文章发出来。&lt;/p&gt;

&lt;p&gt;流量和赞美让人会有眩晕，但是我不会去选择网红道路，我要好好成长为技术专家。&lt;/p&gt;

&lt;p&gt;下一年继续好好研究技术，总结一些干货。&lt;/p&gt;

&lt;h2 id=&quot;对外分享演讲&quot;&gt;对外分享演讲&lt;/h2&gt;

&lt;p&gt;年底的公司的M-DAY上，我作为前端对外做了一次分享，但因为当时公司站在风口浪尖上，前端对外技术分享这也是第一次，自己也没啥信心，所以并没有怎么宣传，前端场到场的人并不多。但是经过一周晚上回去review一遍ppt，我演讲的效果还挺好，反馈比较有趣且有价值。这件事让我这种害怕在众人前说话的人找回了不少信心，抵消了4月晋升演讲失利的阴影。还是需要一些事情来增强信心的，特别是在自己不擅长的讲话方面。所以这件事可圈可点喽。&lt;/p&gt;

&lt;h2 id=&quot;父母生病&quot;&gt;父母生病&lt;/h2&gt;

&lt;p&gt;18年，妈妈住了一次院，爸爸也去了一次省立医院，这2件事对我着实冲击不小。当时内心非常恐惧。&lt;/p&gt;

&lt;p&gt;我也意识到自己目前在应对风险上的无力，这也没啥办法，只能继续努力好好赚钱，买保险吧。虽然主流声音反对强调物质和钱，但是有时候钱真的能买来健康和生命，怎么不值得追求强调呢。好好赚钱依旧是目标。&lt;/p&gt;

&lt;p&gt;还有自己以为我这个人比较理性冷血，我曾经觉得男朋友要是突发变故，我不一定能不离不弃。但是真当父母生病了时，我的选择是如果需要，我可以放弃一切，去治疗他们，照顾她们，真到了这种场景才看的清自己啊，平时假设的都不准。所幸，爸爸妈妈都没啥大事，我也不算遇到了风险，只是次心里预演。&lt;/p&gt;

&lt;p&gt;祝福家人能健健康康的吧，这仍是最安心的事。&lt;/p&gt;

&lt;h1 id=&quot;review&quot;&gt;review&lt;/h1&gt;

&lt;p&gt;看看2018的todolist。&lt;/p&gt;

&lt;p&gt;减少焦虑，控制情绪，学会放松，有计划这些基本达到，但是不够勇敢，还是畏畏缩缩的不敢接受机会尝试。&lt;/p&gt;

&lt;p&gt;完成的&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;理财（虽然亏损中）&lt;/li&gt;
  &lt;li&gt;每周不少于6个小时的学习&lt;/li&gt;
  &lt;li&gt;工作之余开发至少一个项目，做出一个完整的产品。&lt;/li&gt;
  &lt;li&gt;周六多出门&lt;/li&gt;
  &lt;li&gt;学会游泳&lt;/li&gt;
  &lt;li&gt;国庆回家看父母，逢年过节都给家里寄东西慰问慰问&lt;/li&gt;
  &lt;li&gt;旅行，去稻城亚丁&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;未完成&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;每天20分钟零碎时间关注前沿技术发展&lt;/li&gt;
  &lt;li&gt;工作上有突破，至少构思、主导、完成一件对公司有价值的项目&lt;/li&gt;
  &lt;li&gt;至少看一本与技术和行业无关的对思维或心理有价值的书。&lt;/li&gt;
  &lt;li&gt;在摄影上深入&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;18年最大的心愿是希望自己快乐点，这点倒是完成的不错，虽然18年遇到各种不顺，但是心态一直还好，再也没有抑郁。不过走出抑郁后，期待更多，希望自己能做更多的事。18年我算是在一番思索后想明白了自己想成为一个怎样的事，想做怎样的事。&lt;/p&gt;

&lt;h1 id=&quot;end&quot;&gt;end&lt;/h1&gt;

&lt;p&gt;2018整体很衰，做事遇阻力很大，特别是工作上机会不多没啥突破。后面业务淡下来后，自己静下来好好修炼内功了。相信努力的人不会一直衰下去。&lt;/p&gt;

&lt;p&gt;回顾来看，自己能做的还很多，18年发挥的不好。19年形式会依旧严峻，会面对困境或者是疾风骤雨吧，不要畏惧，面对一切。希望能完成几件计划中的大改变呢。自己依旧与向往的能力向往的生活有很大差距，但是却少了焦虑感，毕竟我过得是自己的生活，找准目标继续努力，一段时间以后，我会达到自己的追求的。这也是成熟的表现吧。&lt;/p&gt;

&lt;p&gt;继续温暖纯良，追求爱与自由&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>pm2源码学习</title>
   <link href="http://echizen.github.io/tech/2018/12-22-pm2-code"/>
   <updated>2018-12-22T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/pm2-code</id>
   <content type="html">
&lt;p&gt;在使用过程中，本着对pm2 reload为啥可以不停服重启，pm2 update为啥可以解决各种奇怪的问题，为啥pm2有时会出现时序问题引发的异常，譬如pm2 logs自定义位置后有时启动会报log文件夹不存在而启动异常，然后其实log文件夹已在启动时被pm2成功创建了，我翻阅了下pm2的部分源码。记录下学习笔记。&lt;/p&gt;

&lt;p&gt;看完发现pm2的官网文档更新的可能不及时，文档里的用法和源码其实是有差距的。所以也会记录一些源码里看到的有用的用法。&lt;/p&gt;

&lt;h2 id=&quot;源码&quot;&gt;源码&lt;/h2&gt;

&lt;p&gt;pm2本是个cli工具，入口从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/bin&lt;/code&gt;文件夹看起：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;bin
    &lt;ul&gt;
      &lt;li&gt;pm2&lt;/li&gt;
      &lt;li&gt;pm2-dev&lt;/li&gt;
      &lt;li&gt;pm2-docker&lt;/li&gt;
      &lt;li&gt;pm2-runtime&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;pm2-dev: 开发利器啊，本地开发环境我们可以用pm2-dev去启动，这时候pm2不会以后台模式运行，而是在前台，所以一个ctrl+c就能方便退出。特点是能&lt;strong&gt;监听文件变更去自动重启&lt;/strong&gt;，其实就是默认配置了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;watch:true, autorestart:true&lt;/code&gt;，不过我还是指明&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autorestart: false&lt;/code&gt;，不然开发过程中一个错误就导致不断重启，连抛错信息都来不及看清。还是让它抛错，然后改掉错误，再通过watch的功能调用它重启。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;pm2-docker和pm2-runtime是一模一样的功能。都是以一个常驻进程的模式运行，好让docker容器不因&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD&lt;/code&gt;指令里的进程退出而终止运行。一般在docker容器里用pm2启动应用都会使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2-docker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;再来讲核心的pm2部分。&lt;/p&gt;

&lt;h3 id=&quot;概况&quot;&gt;概况&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181223_2cga789ck06egda0a8abk00g2e305_916x546.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;daemon&quot;&gt;daemon&lt;/h4&gt;

&lt;p&gt;pm2的daemon是一个后台常驻进程，相当于一个管理员的角色，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2 start&lt;/code&gt;这类启动命令里都会去检查daemon是否存在，不存在会启动。&lt;/p&gt;

&lt;p&gt;daemon的核心是God。God上定义了诸多方法，有自己内部使用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;findProcessById&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;findByName&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;killProcess&lt;/code&gt;等，也有透出给client调用的功能诸如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;startProcessId&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stopProcessId&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;restartProcessId&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deleteProcessId&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reloadProcessId&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;monitor&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notifyByProcessId&lt;/code&gt;等等等等。&lt;/p&gt;

&lt;p&gt;God上还挂载了一个clusters_db，记录着进程列表和状态，供各处查询使用。&lt;/p&gt;

&lt;p&gt;God上的bus则是一个事件机制，拥有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addListener&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;removeListener&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emit&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;off&lt;/code&gt;等方法：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;bus&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EventEmitter2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;wildcard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;delimiter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;maxListeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;God的work则是一个轮询所启动的应用进程状态的一个功能，会以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setInterval&lt;/code&gt;的方式去查询进程状态，如重启次数、占用内存。&lt;/p&gt;

&lt;h4 id=&quot;client-与-daemon-server&quot;&gt;client 与 daemon server&lt;/h4&gt;

&lt;p&gt;daemon里启动了一个server，可能会跟很多个client通信合作，取决于你在多少地方&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2 start&lt;/code&gt;了。&lt;/p&gt;

&lt;p&gt;client与server的通信是通过rpc的，借助的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;axon&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;req/rep&lt;/code&gt;模式。读源码时经常会读到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.client.call(&apos;xxx&apos;)&lt;/code&gt;而终止线索，这时候其实是通过rpc通信告知了server，调用了server上对应的方法。&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// client 的定义&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;rep&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// client 的使用案例, from executeRemote&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app_conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// daemon server 的定义&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;rep&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;killMe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;that&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;snapshotPM2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;snapshotPM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;profileStart&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;startProfilingPM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;profileStop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stopProfilingPM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;prepare&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;God&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prepare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;getMonitorData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;God&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getMonitorData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;client&quot;&gt;client&lt;/h4&gt;

&lt;p&gt;client的功能相对简单一点。&lt;/p&gt;

&lt;p&gt;启停daemon(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchDaemon\killDaemon&lt;/code&gt;)： client的start方法会去判断daemon是否存在，不存在启动daemon。&lt;/p&gt;

&lt;p&gt;rpc通信(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchRPC&lt;/code&gt;)：建立后，后续通过rpc与Daemon server通信：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;executeRemote -&amp;gt; client.call xxx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;还有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchBus&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disconnectBus&lt;/code&gt;，这个是通过axon的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pub-emitter / sub-emitter&lt;/code&gt;模式通信。&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;sub-emitter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub_sock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pub_socket_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;查询monitorData：还有一些虽然直接挂到client上的工具方法如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getAllProcess&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getAllProcessId&lt;/code&gt; 、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getProcessIdByName&lt;/code&gt;、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getProcessByName&lt;/code&gt;、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getProcessByNameOrId&lt;/code&gt;则都是通过调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;executeRemote(&apos;getMonitorData&apos;)&lt;/code&gt;由Daemon去实现的&lt;/p&gt;

&lt;p&gt;&lt;em&gt;这里我还没理解为啥既用了RPC模式，又用了PUB模式&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;核心方法&quot;&gt;核心方法&lt;/h3&gt;

&lt;h4 id=&quot;executeapp启动应用&quot;&gt;executeApp：启动应用&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;executeApp&lt;/code&gt;是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;God&lt;/code&gt;启动应用进程的方法。分了cluster模式和fork模式，cluster借助了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cluster&lt;/code&gt;的npm包来启动，form就是用的node原生的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;child_process&lt;/code&gt;的模式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181223_3lca92fb6ae64ik7ce8g199a0g9gc_3450x694.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;executeremoteclient调用daemon方法&quot;&gt;executeRemote：client调用daemon方法&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&quot;language-jasvascript&quot;&gt;Client.prototype.executeRemote = function executeRemote(method, app_conf, fn) {
  var self = this;

  // stop watch on stop | env is the process id
  if (method.indexOf(&apos;stop&apos;) !== -1) {
    this.stopWatch(method, app_conf);
  }
  // stop watching when process is deleted
  else if (method.indexOf(&apos;delete&apos;) !== -1) {
    this.stopWatch(method, app_conf);
  }
  // stop everything on kill
  else if (method.indexOf(&apos;kill&apos;) !== -1) {
    this.stopWatch(&apos;deleteAll&apos;, app_conf);
  }
  else if (method.indexOf(&apos;restartProcessId&apos;) !== -1 &amp;amp;&amp;amp; process.argv.indexOf(&apos;--watch&apos;) &amp;gt; -1) {
    delete app_conf.env.current_conf.watch;
    this.toggleWatch(method, app_conf);
  }

  if (!this.client || !this.client.call) {
    this.start(function(error) {
      if (error) {
        if (fn)
          return fn(error);
        console.error(error);
        return process.exit(0);
      }
      if (self.client) {
        return self.client.call(method, app_conf, fn);
      }
    });
    return false;
  }

  debug(&apos;Calling daemon method pm2:%s on rpc socket:%s&apos;, method, this.rpc_socket_file);
  return this.client.call(method, app_conf, fn);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;流程一览pm2-update&quot;&gt;流程一览：pm2 update&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2 update&lt;/code&gt;是个了解pm2工作流程的好入口，毕竟这个方法里包含了停止应用进程、停止daemon、重启daemon、重启daemon，启动、停止的流程都包含了。&lt;/p&gt;

&lt;p&gt;节约时间，不处理直接贴我读时记的流程了。。。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;update ：
    Client.executeRemote(‘notifyKillPM2&apos;
        God.pm2_being_killed = true;
     dump :
        getMonitorData:
            writeFileSync  DUMP_FILE_PATH MonitorData

     - killDaemon:
            _operate deleteProcessId all :
               Client.executeRemote deleteProcessId:
                   Client.stopWatch(deleteProcessId
                   Client.client.call(deleteProcessId
                        God.stopProcessId
                        -delete God.clusters_db[i
                            killProcess：process.kill
                            -God.notify(&apos;exit&apos;
                -Client.notifyGod(‘stop&apos;
                    this.executeRemote(‘notifyByProcessId&apos;
                        Client.client.call notifyByProcessId
                            God.bus.emit(&apos;process:event’
              
                stopProcessId - 
                    - delete God.clusters_db[id]
            - killAgent
                (KMDaemon = require(&apos;@pm2/agent/src/InteractorClient’), KMDaemon.killInteractorDaemon(that._conf :This module is used by PM2 to communicate with PM2.io&apos;s servers.:https://github.com/keymetrics/pm2-io-agent/blob/master/src/InteractorClient.js  
                -Client.killDaemon                  
                     this.executeRemote(&apos;killMe&apos;, {pid : process.pid})
                        God.bus.emit(&apos;pm2:kill’
                            that.rpc_socket.close
                            that.pub_socket.close
                            process.kill(parseInt(opts.pid), &apos;SIGQUIT’)
                            fs.unlinkSync(that.pid_path)
                            process.exit(cst.SUCCESS_EXIT)
     
       -that.Client.launchDaemon({interactor:false}
            require(&apos;child_process&apos;).spawn node Daemon.js
            -KMDaemon.launchAndInteract L重启Daemon
       -that.Client.launchRPC
            var req = axon.socket(&apos;req’);  this.client = new rpc.Client(req);
       -that.resurrect
            readFileSync DUMP_FILE_PATH
            parseDumpFile DUMP_FILE_PATH：to get process list
            that.Client.executeRemote(‘getMonitorData&apos;
            将monitor里拿到的进程列表遍历过滤掉没save到DUMP_FILE_PATH的进程 进行后续prepare处理
            that.Client.executeRemote(‘prepare’ -&amp;gt; self.client.call prepare
                God.executeApp env
                    God.nodeApp:cluster.fork  or God.forkMode:spawn
                    -set God.clusters_db
                    -God.notify(&apos;online&apos;

       -that.launchAll
            Modularizer.launchModules(CLI, cb);一系列start pm2 module的活动
       -KMDaemon.launchAndInteract
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注：缩进表述函数内部发生的步骤，即调用层级。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;- fn&lt;/code&gt;的格式标识fn是缩进之前的函数的回调函数。pm2里回调层级实在是好多。。。&lt;/p&gt;

&lt;p&gt;可以看到先是通过查询&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getMonitorData&lt;/code&gt;将当前运行的进程列表写入了DUMP_FILE_PATH，为之后的重启保存好数据。&lt;/p&gt;

&lt;p&gt;然后killDaemon步骤里先是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.kill&lt;/code&gt;了所有记录在clusters_db里的应用id, 然后在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client.killDaemon&lt;/code&gt;步骤里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.kill(parseInt(opts.pid), &apos;SIGQUIT’)&lt;/code&gt;，这个pid也就是pm2 daemon的进程id了。在kill之前还关闭了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpc、pub&lt;/code&gt;的socket通信通道的连接&lt;/p&gt;

&lt;p&gt;全部kill后，接着就来到重启的步骤了。先在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client.launchDaemon&lt;/code&gt;里通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;child_process&apos;).spawn&lt;/code&gt;启动了daemon.js的守护进程，然后当前执行命令的client和新启动的daemon建立通信通道，接着在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resurrect&lt;/code&gt;里读取&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DUMP_FILE_PATH&lt;/code&gt;结合getMonitorData拿到的进程信息，进行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prepare&lt;/code&gt;操作，这里调用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;God.executeApp&lt;/code&gt;启动了应用进程。&lt;/p&gt;

&lt;p&gt;所以pm2 update是一个daemon和应用进程都&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;restart&lt;/code&gt;的过程，真因为如此，才会解决一些宿主环境变更带来的鬼畜的问题。譬如我遇到linux机器上一个应用从fork模式切换到cluster模式后，无论pm2 reload restart还是直接delete后重启，cluster出来的进程都会因为莫名其妙接收到kill的SIGINT 0 信号而不断重启直至超过重启次数而停止。通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2 update&lt;/code&gt;将daemon也重启了一起就正常了。&lt;/p&gt;

&lt;h4 id=&quot;reload&quot;&gt;reload&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2 reload&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2 restart&lt;/code&gt;的过程也是值得看一下的。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cluster&lt;/code&gt;模式的reload能不停服重启，原因也很简单，因为它是先启动新的进程，待新进程都启动完毕后，再去kill老进程的，自然服务不会中断。&lt;/p&gt;

&lt;p&gt;不过真的要想不停服重启，应用必须做好处理，否则接收到kill的信号，应用就退出了，当前正在处理的工作譬如正在处理的一个请求还未返回就被退出了，也就会异常。正如官网所说的：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To be sure that all requests are properly handled in a reload, you need to be sure that your application shutdown, not leaving unanswered requests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;A graceful shutdown makes sure to handle all remaining queries before exiting the application and closes all external connections.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以我们在接收到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGINT&lt;/code&gt;的kill信号后应该：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;通知负载均衡器不要再给当前机器发送请求（如果有负载均衡的话）&lt;/li&gt;
  &lt;li&gt;处理完正在进行中的请求&lt;/li&gt;
  &lt;li&gt;释放资源，如数据库连接&lt;/li&gt;
  &lt;li&gt;退出&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;eg:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8000&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Express server listening on port &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SIGINT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SIGINT signal received.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Stops the server from accepting new connections and finishes existing connections.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// if error, log and exit with error (1 code)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// close your database connection and exit with success (0 code)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// for example with mongoose&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;mongoose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Mongoose connection disconnected&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;过程中也翻到一篇不错的pm2源码阅读文章：&lt;a href=&quot;https://github.com/chen2009277025/pm2_sourceread&quot;&gt;https://github.com/chen2009277025/pm2_sourceread&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://pm2.io/doc/en/runtime/best-practices/graceful-shutdown/&quot;&gt;Graceful Shutdown&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>npm包对应的rpm软件包制作</title>
   <link href="http://echizen.github.io/tech/2018/12-15-make-rpm"/>
   <updated>2018-12-15T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/make-rpm</id>
   <content type="html">
&lt;h1 id=&quot;rpm软件包制作&quot;&gt;rpm软件包制作&lt;/h1&gt;

&lt;p&gt;从一个前端角度来说，搞这个事可真够冷门的。。。但是确实遇到了需求去做了就记录一下。&lt;/p&gt;

&lt;p&gt;目的：稍微有点规模的公司对自己的服务器都有运维规范，譬如外网权限，一般是需要申请才能访问的。然而像pm2这样通用的工具，在我司是node应用进程管理标准采用的工具，如果都要每个开发者去申请外网权限来&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install&lt;/code&gt;实在是麻烦，不如做成rpm包放到内部私有yum源上，也方便运维配成node应用的标准基线，无需大家手动安装。（要是npm私有源也能这么玩被我司支持能默认安装其上的包我就不用这么折腾了。。。）=.=&lt;/p&gt;

&lt;p&gt;所以文章会介绍rpm包的制作流程，然后已pm2包的制作为示例，演示如何把npm包搞成rpm包。需要有基础的linux系统知识。&lt;/p&gt;

&lt;h2 id=&quot;rpm包是什么pm2又是什么&quot;&gt;rpm包是什么，pm2又是什么&lt;/h2&gt;

&lt;p&gt;不专业解释，rpm包是linux上通用软件安装包的一种格式。pm2是node进程管理的一种命令行工具，命令行工具其实是可执行命令，对应着背后的可执行文件，可执行文件可能是多种语言的譬如bash，他们最终都会翻译成机器码由操作系统去执行（不继续了，继续我也说不下去了…）。&lt;/p&gt;

&lt;h2 id=&quot;rpm包制作&quot;&gt;rpm包制作&lt;/h2&gt;

&lt;p&gt;官网文档教程最清晰：&lt;a href=&quot;https://fedoraproject.org/wiki/How_to_create_an_RPM_package/zh-cn&quot;&gt;https://fedoraproject.org/wiki/How_to_create_an_RPM_package/zh-cn&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;制作步骤&quot;&gt;制作步骤&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;安装&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild&lt;/code&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install rpm-build&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;切换到一个普通用户身份，&lt;strong&gt;不要用root身份制作rpm&lt;/strong&gt;，不然读写文件权限都是root，用户安装包时会存在很多权限问题。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; useradd simpleuser
 su simpleuser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;创建需要的工作目录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdir -pv ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;将源代码包放置到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/rpmbuild/SOURCES&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;将写好的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec&lt;/code&gt;文件放置到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/rpmbuild/SPECS&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;打包：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild -ba NAME.spec&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;rpm包包含源码包SRPM 和 应用包RPM ， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-ba&lt;/code&gt;是同时构建这2种类型的包。只构建RPM包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-bb&lt;/code&gt;，只构建SRPM包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-bs&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;build过程&quot;&gt;build过程&lt;/h3&gt;

&lt;p&gt;build的过程是根据spec的文件描述来进行的。&lt;/p&gt;

&lt;p&gt;我仅简单官网解释下这个过程。build分为6个阶段，每个阶段操作内容都在之前创建的那些目录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUILD,RPMS,SOURCES,SPECS,SRPMS&lt;/code&gt;，每个阶段都在一个特定的文件夹下进行，都将之前阶段生成的内容先copy到当前阶段对应的文件夹下，再进行其他操作。&lt;/p&gt;

&lt;p&gt;spec文件里的每个步骤都有一些内检指令和宏变量可用。如果这个步骤不需要特殊的操作，指令空着就好。&lt;/p&gt;

&lt;h4 id=&quot;prep&quot;&gt;%prep&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;读取的目录:%_sourcedir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;写入的目录:%_builddir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;默认动作：读取位于 %_sourcedir 目录的源代码和 patch 。之后，解压源代码至 %_builddir 的子目录并应用所有 patch。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一步其实主要做解压的操作。可以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%autosetup&lt;/code&gt;，有一些参数如，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n&lt;/code&gt;指定&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SOURCE&lt;/code&gt;软件包解压后的名称，如果与软件名也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.spec&lt;/code&gt;文件中定义的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;不一致，是必须指定的，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2&lt;/code&gt;的tgz包解压后的文件夹名称就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package&lt;/code&gt;，这里就要指定&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%autosetup -n package&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;centos7以前是不支持这个指令的，只能用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%setup&lt;/code&gt;，但是它就没办法指定&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n&lt;/code&gt;别名了。&lt;/p&gt;

&lt;h4 id=&quot;build&quot;&gt;%build&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;读取的目录:%_builddir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;写入的目录:%_builddir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;默认动作：编译位于 %_builddir 构建目录下的文件。通过执行类似 “./configure &amp;amp;&amp;amp; make” 的命令实现。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这是个构建的步骤，如果你对源代码要执行一系列构建步骤，可以在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.makefile&lt;/code&gt;里指定，然后在这一步执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;指令构建。&lt;/p&gt;

&lt;h4 id=&quot;install&quot;&gt;%install&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;读取的目录:%_builddir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;写入的目录:%_buildrootdir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;默认动作：读取位于 %_builddir 构建目录下的文件并将其安装至 %_buildrootdir 目录。这些文件就是用户安装 RPM 后，最终得到的文件。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里要注意的是，你希望最终你的安装包文件被存放到哪里，在这步里就要把它先copy到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%{buildroot}&lt;/code&gt;下的该目录下。一般软件包的安装位置都是有规范的，默认不做这步就直接安装到了根目录下了，这样肯定太好。&lt;/p&gt;

&lt;h4 id=&quot;check&quot;&gt;%check&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;读取的目录:%_builddir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;写入的目录:%_builddir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;默认动作：检查软件是否正常运行。通过执行类似 “make test” 的命令实现。很多软件包都不需要此步。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;bin&quot;&gt;bin&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;读取的目录:%_buildrootdir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;写入的目录:%_rpmdir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;默认动作：读取位于 %_buildrootdir 最终安装目录下的文件，以便最终在 %_rpmdir 目录下创建 RPM 包。在该目录下，不同架构的 RPM 包会分别保存至不同子目录， “noarch” 目录保存适用于所有架构的 RPM 包。这些 RPM 文件就是用户最终安装的 RPM 包。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这步是内置的步骤，不对开发者透出&lt;/p&gt;

&lt;h4 id=&quot;src&quot;&gt;src&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;读取的目录:%_sourcedir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;写入的目录:%_srcrpmdir&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;默认动作：创建源码 RPM 包（简称 SRPM，以.src.rpm 作为后缀名），并保存至 %_srcrpmdir 目录。SRPM 包通常用于审核和升级软件包。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;源码RPM包也不是必须的。&lt;/p&gt;

&lt;p&gt;可以关注下build时的日志来更好的了解这个过程：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181216_25g265i78fcj47788dhc1gefafe43_2554x1480.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;spec文件&quot;&gt;spec文件&lt;/h3&gt;

&lt;p&gt;也是只记几个要注意的点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: 软件包名，应与 SPEC 文件名一致。命名必须符合 软件包命名规定。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Version&lt;/strong&gt;: 上游版本号。请查看 版本标签规定。如果包含非数字字符，您可能需要将它们包含在 Release 标签中。如果上游采用日期作为版本号，请考虑以：yy.mm[dd] (例如 2008-05-01 可变为 8.05) 格式作为版本号。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Release&lt;/strong&gt;: 发行编号。初始值为 1%{?dist}。每次制作新包时，请递增该数字。当上游发布新版本时，请修改 Version 标签并重置 Release 的数字为 1。具体参考打包规定中的 Release 标签部分，以及 Dist tag。&lt;/li&gt;
  &lt;li&gt;Summary: 一行简短的软件包介绍。&lt;/li&gt;
  &lt;li&gt;Group&lt;/li&gt;
  &lt;li&gt;License&lt;/li&gt;
  &lt;li&gt;URL: 该软件包的项目主页。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Source0&lt;/strong&gt;: 软件源码包的 URL 地址。不一定是url，也可以是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SOURCE&lt;/code&gt;文件夹下的源码压缩包名。”Source” 与 “Source0” 相同。强烈建议提供完整 URL 地址，文件名用于查找 SOURCES 目录。如果可能，建议&lt;strong&gt;使用 %{name} 和 %{version} 替换 URL 中的名称/版本&lt;/strong&gt;，这样更新时就会自动对应。下载源码包时，需要 保留时间戳。如果有多个源码包，请用 Source1，Source2 等依次列出。如果你需要添加额外文件，请将它们列在后面。&lt;/li&gt;
  &lt;li&gt;Patch0&lt;/li&gt;
  &lt;li&gt;BuildArch&lt;/li&gt;
  &lt;li&gt;BuildRoot&lt;/li&gt;
  &lt;li&gt;BuildRequires: 编译软件包所需的依赖包列表，以逗号分隔。此标签可以多次指定。编译依赖 不会 自动判断，所以需要列出编译所需的所有依赖包。常见的软件包可省略，例如 gcc。如果有必要，你可以指定需要的最低版本（例：”ocaml &amp;gt;= 3.08”）。如果你需要找到包含 /EGGS 文件的软件包，可执行 “rpm -qf /EGGS”。如果你需要找到包含 EGGS 程序的软件包，可执行 “rpm -qf which EGGS”。请保持最小依赖（例如，如果你不需要 perl 的功能，可使用 sed 代替），但请注意，如果不包含相关依赖，某些程序会禁用一些功能；此时，你需要添加这些依赖。&lt;/li&gt;
  &lt;li&gt;Requires: 安装软件包时所需的依赖包列表，以逗号分隔。请注意， BuildRequires 标签是编译所需的依赖，而 Requires 标签是安装/运行程序所需的依赖。&lt;strong&gt;大多数情况下，rpmbuild 会自动探测依赖&lt;/strong&gt;，所以可能不需要 Requires 标签。然而，你也可以明确标明需要哪些软件包，或由于未自动探测所需依赖而需要手动标明。&lt;/li&gt;
  &lt;li&gt;%description&lt;/li&gt;
  &lt;li&gt;%prep: 打包准备阶段执行一些命令（如，解压源码包，打补丁等），以便开始编译。一般仅包含 “%autosetup”；如果源码包需要解压并切换至 NAME 目录，则输入 “%autosetup -n NAME”。&lt;/li&gt;
  &lt;li&gt;%build: 包含构建阶段执行的命令，构建完成后便开始后续安装。程序应该包含有如何编译的介绍。&lt;/li&gt;
  &lt;li&gt;%install: 包含安装阶段执行的命令。命令将文件从 %{_builddir} 目录安装至 %{buildroot} 目录。&lt;/li&gt;
  &lt;li&gt;%check: 包含测试阶段执行的命令。&lt;/li&gt;
  &lt;li&gt;%clean: 清理安装目录的命令。一般只包含：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -rf %{buildroot}&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;%files: 需要被打包/安装的文件列表。&lt;/li&gt;
  &lt;li&gt;%changelog: RPM 包变更日志。不是软件本身的变更日志。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;files&quot;&gt;%files&lt;/h4&gt;

&lt;p&gt;比较需要注意的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%files&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;此部分列出了需要被打包的文件和目录。所以需要最终在用户安装后系统里出现的文件都必须在这里定义。&lt;strong&gt;%{buildroot}文件夹下的最终文件都要在这里列出&lt;/strong&gt;。可以使用glob通配符。&lt;/p&gt;

&lt;p&gt;如果存在以下情况，可能引发错误：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;未匹配到任何文件或目录。也就意味着你的rpm安装完后，用户系统里没有任何文件变更，那你的软件包功能是？&lt;/li&gt;
  &lt;li&gt;文件或目录被多次列出。避免重复&lt;/li&gt;
  &lt;li&gt;未列出 %{buildroot} 下的某个文件或目录&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;您也可以使用 %exclude 来排除文件。这对于使用通配符来列出全部文件时会很有用，注意如果未匹配到任何文件也会造成失败。&lt;/p&gt;

&lt;h4 id=&quot;scriptlets&quot;&gt;Scriptlets&lt;/h4&gt;

&lt;p&gt;当用户安装 RPM 时，您可能想要执行一些命令。这可以通过 scriptlets 完成。&lt;/p&gt;

&lt;p&gt;脚本片段可以：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在软体包安装之前 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%pre&lt;/code&gt;) 或之后 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%post&lt;/code&gt;) 执行&lt;/li&gt;
  &lt;li&gt;在软体包卸载之前 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%preun&lt;/code&gt;) 或之后 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%postun&lt;/code&gt;) 执行&lt;/li&gt;
  &lt;li&gt;在事务开始 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%pretrans&lt;/code&gt;) 或结束 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%posttrans&lt;/code&gt;) 时执行&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;譬如我一开始做pm2的rpm包方式就是将pm2源码的tgz包，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%post&lt;/code&gt;步骤里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install -g pm2-2.3.2.tgz&lt;/code&gt;一把来实现。后来还借助npm来install有点多此一举。&lt;/p&gt;

&lt;h4 id=&quot;宏&quot;&gt;宏&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;%{_bindir}：	/usr/bin&lt;/li&gt;
  &lt;li&gt;%{_builddir}：	~/rpmbuild/BUILD&lt;/li&gt;
  &lt;li&gt;%{buildroot}：	~/rpmbuild/BUILDROOT&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;测试&quot;&gt;测试&lt;/h2&gt;

&lt;p&gt;制作好了后当然需要自己先安装测试下了&lt;/p&gt;

&lt;p&gt;安装：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo rpm -ihvv ~/rpmbuild/RPMS/${name}.rpm&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;你构建的软件包究竟叫什么可以去&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/rpmbuild/RPMS&lt;/code&gt;下查看，譬如我制作的pm2就在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/rpmbuild/RPMS/x86_64/pm2-3.2.2-1.el7.centos.x86_64.rpm&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-ihv&lt;/code&gt;参数就能安装，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-ihvv&lt;/code&gt;会打印安装过程中每步的信息，方便debug。&lt;/p&gt;

&lt;p&gt;卸载rpm包：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo rpm -e ${name}&lt;/code&gt;，万一你写了Scriptlets并且跟我一样悲催的在初始开发时写的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%preun&lt;/code&gt;脚本有问题，那么执行报错会导致卸载失败，可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo rpm -e --noscripts ${name}&lt;/code&gt;来不执行脚本单纯卸载&lt;/p&gt;

&lt;h2 id=&quot;更多查找rpm包相关信息的命令&quot;&gt;更多查找rpm包相关信息的命令&lt;/h2&gt;

&lt;p&gt;查找所有安装过的包含某个字符串的软件包: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpm -qa | grep ${string_name}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;查看rpm包中的文件安装位置：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpm -ql ${name}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;查看某rpm包中包含文件列表：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpm -ql ${name}.rpm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;查看某可执行命令是哪个软件包安装的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rpm -qf `which 命令名`    #返回软件包的全名
rpm -qif `which 命令名`   #返回软件包的有关信息
rpm -qlf `which 命令名`   #返回软件包的文件列表
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;查看某个文件是哪个软件包安装的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;whereis ${filename}
rpm -qf ${filepath}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;npm包转成rpm包&quot;&gt;npm包转成rpm包&lt;/h2&gt;

&lt;h3 id=&quot;npm-install-干了什么&quot;&gt;npm install 干了什么&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;从远程下载压缩包tgz&lt;/li&gt;
  &lt;li&gt;解压缩到规范的指定目录，譬如，npm安装在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/nodejs/lib/node_modules&lt;/code&gt;下&lt;/li&gt;
  &lt;li&gt;安装该包的依赖包&lt;/li&gt;
  &lt;li&gt;如果有可执行命令，创建可执行命令所在文件（package.json里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin&lt;/code&gt;所指向的文件）的软连接到可被系统查找到的命令目录，一般npm包的都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/nodejs/bin&lt;/code&gt;下&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;清楚了这个也就能清楚制作rpm包时需要做什么了。&lt;/p&gt;

&lt;h3 id=&quot;获取npm包源码&quot;&gt;获取npm包源码&lt;/h3&gt;

&lt;p&gt;直接从npm源找到的tgz包是不包含该包的依赖包，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm pack&lt;/code&gt;在原包安装目录下打的也是一样。一般做rpm包我们是把完整的源码整个拉下来，免得build的过程中再去拉取。&lt;/p&gt;

&lt;p&gt;打出包含node_modules文件夹的安装包可以借助&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm-bundle&lt;/code&gt;这个npm包工具。&lt;/p&gt;

&lt;p&gt;以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2&lt;/code&gt;来举例：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;安装pm2：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install -g pm2&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;进入包源码目录：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd /usr/local/lib/node_modules/pm2&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm-bundle&lt;/code&gt;打出包含node_modules目录依赖的tgz源码压缩包。然后放到上面说的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild/SOURCE&lt;/code&gt;，供后续&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild&lt;/code&gt;使用&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;pm2-的rpm包spec文件示例&quot;&gt;pm2 的rpm包spec文件示例&lt;/h3&gt;

&lt;p&gt;centos7版：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-spec&quot;&gt;Name:           pm2
Version:        3.2.2
Release:        1%{?dist}
Summary:        pm2 program
Summary(zh_CN): pm2 程序

License:        AGPL-3.0
URL:            https://pm2.io/doc/en/runtime/overview/
Source0:        %{name}-%{version}.tgz

Requires(post): nodejs
Requires(preun): nodejs

%global __requires_exclude ^/sbin/openrc-run$

%description
node pm2 for rpm version

%prep
%autosetup -n package

%build


%install
mkdir -p %{buildroot}/usr/local/nodejs/lib/node_modules/pm2
cp -af . %{buildroot}/usr/local/nodejs/lib/node_modules/pm2

%clean
rm -rf %{buildroot}

%files
/usr/local/nodejs/lib/node_modules/pm2/*
/usr/local/nodejs/lib/node_modules/pm2/.[a-z0-9_]*

%post
ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2 /usr/local/bin/pm2 
ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2-dev /usr/local/bin/pm2-dev
ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2-docker /usr/local/bin/pm2-docker
ln -s /usr/local/nodejs/lib/node_modules/pm2/bin/pm2-runtime /usr/local/bin/pm2-runtime

%preun
if [ $1 = 0 ] ; then
rm /usr/local/bin/pm2 /usr/local/bin/pm2-dev /usr/local/bin/pm2-docker /usr/local/bin/pm2-runtime
fi

%changelog
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%post&lt;/code&gt;中做了软连接的操作，软链目标路径只要是$PATH中存在的路径就好，npm install时虽然是用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/nodejs/bin&lt;/code&gt;，但是这个路径不一定在$PATH中，为了简化处理，我直接软链到了更通用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/bin&lt;/code&gt;，当然用更更通用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/bin&lt;/code&gt;更保险&lt;/p&gt;

&lt;h3 id=&quot;遇到的一些问题&quot;&gt;遇到的一些问题&lt;/h3&gt;

&lt;h4 id=&quot;指定安装目录&quot;&gt;指定安装目录&lt;/h4&gt;

&lt;p&gt;要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%install&lt;/code&gt;里创建目标安装目录，将源码文件移到这个目录里。&lt;/p&gt;

&lt;h4 id=&quot;安装时报和文件或文件夹找不到&quot;&gt;安装时报&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;..&lt;/code&gt;文件或文件夹找不到&lt;/h4&gt;

&lt;p&gt;之前&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%files&lt;/code&gt;定义的是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/usr/local/nodejs/lib/node_modules/pm2/*
/usr/local/nodejs/lib/node_modules/pm2/.*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样会把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/nodejs/lib/node_modules/pm2/.&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/nodejs/lib/node_modules/pm2/..&lt;/code&gt;包含进去，且不能通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%exclude&lt;/code&gt;指令排除掉，后改成了：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%files
/usr/local/nodejs/lib/node_modules/pm2/*
/usr/local/nodejs/lib/node_modules/pm2/.[a-z0-9_]*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;安装时报目标机上sbinopenrc-run依赖找不到&quot;&gt;安装时报目标机上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/sbin/openrc-run&lt;/code&gt;依赖找不到&lt;/h4&gt;

&lt;p&gt;rpm确实有依赖自动识别机制，能识别到这个依赖说明pm2里用了它，一搜发现有个模板文件.tpl中含有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/sbin/openrc-run&lt;/code&gt;，所有这种指令都会被自动依赖识别，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/bin/bash&lt;/code&gt;会被识别依赖&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sh&lt;/code&gt;。但是pm2并不是真的依赖&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openrc-run&lt;/code&gt;这个系统初始化工具啊，他是会判断宿主机环境，再去判断用哪一种的，譬如我的测试机上用的就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd&lt;/code&gt;而不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openrc-run&lt;/code&gt;，所以这并不是一个强依赖。&lt;/p&gt;

&lt;p&gt;需要有个姿势告诉rpm的自动依赖识别系统过滤它。方案就是centos7上是有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__requires_exclude&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__requires_exclude_from&lt;/code&gt;的用法的。详情见&lt;a href=&quot;https://fedoraproject.org/wiki/Packaging:AutoProvidesAndRequiresFiltering&quot;&gt;https://fedoraproject.org/wiki/Packaging:AutoProvidesAndRequiresFiltering&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;兼容问题&quot;&gt;兼容问题&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;制作rpm包还要注意打包机和安装机的系统版本差异，不同版本对rpm支持度不一样。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在centos6中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%global __requires_exclude&lt;/code&gt;宏不支持，无奈，部分文档中提的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%define __requires_exclude&lt;/code&gt;我试验了也不支持，我只能通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AutoReq: no&lt;/code&gt;暴力关掉所有自动依赖分析。&lt;/p&gt;

&lt;p&gt;centos6中也不支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%autosetup&lt;/code&gt;，不能解决rpm包和解压后文件目录名不一致问题，只能先将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm-bundle&lt;/code&gt;打的压缩包解压，将package文件夹名改成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pm2-3.2.2&lt;/code&gt;，再通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tar -zcvf pm2-3.2.2.tgz pm2-3.2.2&lt;/code&gt;压缩回去。&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://fedoraproject.org/wiki/How_to_create_an_RPM_package/zh-cn&quot;&gt;How to create an RPM package/zh-cn
&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://blog.51cto.com/nmshuishui/1583117&quot;&gt;使用rpm-build制作nginx的rpm包&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;这篇文章里的图非常有助于理解: &lt;img src=&quot;http://s3.51cto.com/wyfs02/M01/53/B3/wKiom1RtybbxDoijAARlofRyCW4338.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://man.linuxde.net/rpm&quot;&gt;rpm命令速查&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>从前端角度了解nginx</title>
   <link href="http://echizen.github.io/tech/2018/10-21-nginx"/>
   <updated>2018-10-21T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/nginx</id>
   <content type="html">
&lt;p&gt;以前的我只是写写前端，貌似不了解nginx也没什么关系，但是写node服务端后，开始发现不了解这个玩意，很多东西不知道怎么实现的，了解了一些之后，会发现，原来这么多神奇的事都是nginx提供的能力达成的，倒是解释了不少以前遇到的不能理解的现象~总之，虽然是个前端，多了解一点nginx绝对没有坏处。&lt;/p&gt;

&lt;p&gt;用node写一个服务后，虽然没有nginx也能跑起来，但是有没有想过，最基础的一个场景，一台机器上要跑不同端口的多个服务，怎么做到对外透出？毕竟外部过来的域名访问解析后都是去访问80端口了。还有static、proxy的服务其实用nginx做起来更方便彻底，性能还好。&lt;/p&gt;

&lt;h2 id=&quot;入门&quot;&gt;入门&lt;/h2&gt;

&lt;p&gt;万事开头难，学东西最靠谱的看官方文档。不过可能去nginx官网翻了半天都是产品介绍。。。所以贴心的送上下官方文档链接：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://nginx.org/en/docs/&quot;&gt;https://nginx.org/en/docs/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;还有来自中文社区的：&lt;a href=&quot;http://www.nginx.cn/doc/&quot;&gt;http://www.nginx.cn/doc/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有哪些模块我也就不重复写了，反正官网都能了解到，而且说得更权威。&lt;/p&gt;

&lt;p&gt;简介版还可以看看这篇文章：&lt;a href=&quot;https://www.jianshu.com/p/bed000e1830b&quot;&gt;https://www.jianshu.com/p/bed000e1830b&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd /usr/local/etc/nginx &amp;amp;&amp;amp; sudo nginx #去对应目录启动
nginx -s reload #重启
nginx -s quit #安全退出
nginx -s stop #快速退出
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;什么安装启动就不多说了，看官网&lt;/p&gt;

&lt;h2 id=&quot;功能&quot;&gt;功能&lt;/h2&gt;

&lt;p&gt;说说我认知到的nginx和大前端领域的结合点&lt;/p&gt;

&lt;h3 id=&quot;静态服务器&quot;&gt;静态服务器&lt;/h3&gt;

&lt;p&gt;可以起个静态服务器，访问静态资源。这是nginx的基本能力，利用server模块简单配配就能做到。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name site.nginx.com;
    index static/index.html;
    location / {
        root /Users/echizen/demo/nginx-learn/site1;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本地绑上host&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1 site.nginx.com&lt;/code&gt;，访问&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://site.nginx.com&lt;/code&gt;就能看到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Users/echizen/demo/nginx-learn/site1/static/index.html&lt;/code&gt;下的文件内容了。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注意：如果没有配置index 会默认找root下的index.html文件，找不到会报403，这个403有点迷惑性&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;这个功能不算是nginx必须存在的一个功能，毕竟能做到的方式太多：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;线上用的话，node里有很多处理静态文件的库，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;koa-static&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;如果是本地测试目的，要起个静态服务器，配置nginx显得过于麻烦，不如去项目目录下， 用python起一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python -m SimpleHTTPServer &amp;lt;port&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是nginx层能做的是动静态内容分离，并且进行一些缓存优化措施。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if-modify-since&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cache-Control&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Expires&lt;/code&gt;这些缓存相关的配置都可以通过nginx。&lt;/p&gt;

&lt;h3 id=&quot;反向代理&quot;&gt;反向代理&lt;/h3&gt;

&lt;p&gt;这个功能是nginx的一个亮点。&lt;/p&gt;

&lt;p&gt;反向代理是什么呢？&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;反向代理（Reverse Proxy）方式是指以代理服务器来接受internet上的连接请求，然后将请求转发给内部网络上的服务器，并将从服务器上得到的结果返回给internet上请求连接的客户端，此时代理服务器对外就表现为一个服务器。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;举个例子，比如我想访问 http://www.test.com/readme ，但www.test.com上并不存在readme页面，于是他是偷偷从另外一台服务器上取回来，然后作为自己的内容返回用户，但用户并不知情。这里所提到的 www.test.com 这个域名对应的服务器就设置了反向代理功能。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;正向代理（Forward Proxy）通常都被简称为代理，就是在用户无法正常访问外部资源，比方说受到GFW的影响无法访问twitter的时候，我们可以通过代理的方式，让用户绕过防火墙，从而连接到目标网络或者服务。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;正向代理的工作原理就像一个跳板，比如：我访问不了google.com，但是我能访问一个代理服务器A，A能访问google.com，于是我先连上代理服务器A，告诉他我需要google.com的内容，A就去取回来，然后返回给我。从网站的角度，只在代理服务器来取内容的时候有一次记录，有时候并不知道是用户的请求，也隐藏了用户的资料，这取决于代理告不告诉网站。 
作者：猿码道: 链接：https://www.jianshu.com/p/bed000e1830b&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;通过反向代理，可以实现：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一台机器充分利用多个域名&lt;/li&gt;
  &lt;li&gt;
    &lt;table&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;本地通过80端口代理到实际服务的端口，解决调试时端口带来的跨域问题。（不过更也可以用charles设定&lt;/td&gt;
          &lt;td&gt; &lt;/td&gt;
          &lt;td&gt;通过node proxy工具如：node-http-proxy, koa-proxy）&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name site1.nginx.com;
    index static/index.html;
    location / {
        root /Users/echizen/demo/nginx-learn/site1;
    }

    location /api {
        proxy_pass http://127.0.0.1:8020;
    }
}

server {
    listen 80;
    server_name site2.nginx.com;
    index static/index.html;
    location / {
        root /Users/echizen/demo/nginx-learn/site2;
    }

    location /api {
        proxy_pass http://127.0.0.1:8021;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样访问&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://site1.nginx.com&lt;/code&gt;就能看到site1的内容，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://site1.nginx.com/api/*&lt;/code&gt;可以看到site1下的接口服务，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://site2.nginx.com&lt;/code&gt;能看到site2的内容，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://site2.nginx.com/api/*&lt;/code&gt;可以看到site2的接口服务。实现了一台机器上提供多个独立的服务&lt;/p&gt;

&lt;h3 id=&quot;重定向到对应的pch5站点&quot;&gt;重定向到对应的pc、h5站点&lt;/h3&gt;

&lt;p&gt;很多网站pc和h5站点的域名是区分的，譬如蘑菇街pc是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.mogujie.com&lt;/code&gt;, h5是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://m.mogujie.com&lt;/code&gt;，会做到用户通过pc的机器打开https://m.mogujie.com链接自动跳转到pc的https://www.mogujie.com。&lt;/p&gt;

&lt;p&gt;这个功能通过nginx来判断&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$http_user_agent&lt;/code&gt;, 然后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rewrite&lt;/code&gt;就能实现。&lt;/p&gt;

&lt;h3 id=&quot;合并请求&quot;&gt;合并请求&lt;/h3&gt;

&lt;p&gt;网站性能优化，为了减少请求数，常将一定量的请求合并。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181021_1g4lb791ajj0cc5hflgd0jcc956hj_1066x174.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过nginx-http-concat模块（前端可以将多个资源的请求合并成一个请求，后台Nginx会获取各个资源并拼接成一个结果进行返回。&lt;/p&gt;

&lt;p&gt;Nginx配置, for example：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;location /__/mfp/ {
    concat on; # 是否打开资源合并开关
    concat_types application/javascript; # 允许合并的资源类型
    concat_unique off; # 是否允许合并不同类型的资源
    concat_max_files 20; # 允许合并的最大资源数目
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;权限控制&quot;&gt;权限控制&lt;/h3&gt;

&lt;p&gt;某些站点需要进行权限控制，譬如公司内网只允许在公司的内部网络里访问。用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngx_http_access_module&lt;/code&gt;模块的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deny&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allow&lt;/code&gt;就能做到&lt;/p&gt;

&lt;p&gt;eg:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;location / {
    deny  192.168.1.100;
    allow 192.168.1.10/200;
    allow 10.110.50.16;
    deny  all;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;负载均衡&quot;&gt;负载均衡&lt;/h3&gt;

&lt;p&gt;这个前端可能没有感知，但是也是nginx的一大核心功能。当一个服务部署在多台机器上构成集群时，为了避免流量不均衡导致部分机器压力过大部分机器限制，都是要设置负载均衡方案的。upstream 模块负责负载均衡模块，通过一个简单的调度算法来实现客户端IP到后端服务器的负载均衡。&lt;/p&gt;

&lt;p&gt;eg:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;upstream site.nginx.com{
    ip_hash; # 调度算法
    server 192.168.1.101:80;
    server 192.168.1.102:80 down;
    server 192.168.1.103:8080  max_fails=3  fail_timeout=20s;
    server 192.168.1.104:8080;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;第一行是调度算法，也就是流量分配策略，后面的server是构成集群的机器。&lt;/p&gt;

&lt;p&gt;有4种基础的调度算法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;weight 轮询（默认）。每个请求按时间顺序逐一分配到不同的后端服务器，如果后端某台服务器宕机，故障系统被自动剔除，使用户访问不受影响。weight。指定轮询权值，weight值越大，分配到的访问机率越高，主要用于后端每个服务器性能不均的情况下。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;ip_hash。每个请求按访问IP的hash结果分配，这样来自同一个IP的访客固定访问一个后端服务器，有效解决了动态网页存在的session共享问题。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;fair（第三方）。比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡，也就是根据后端服务器的响应时间来分配请求，响应时间短的优先分配。upstream_fair模块提供。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;url_hash（第三方）。按访问url的hash结果来分配请求，使每个url定向到同一个后端服务器，可以进一步提高后端缓存服务器的效率，也降低了session等需要共享信息的复杂度。使用要安装hash软件包。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;server 指定服务器ip和端口，以及在负载均衡调度中的状态。常用的状态有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;down，表示当前的server暂时不参与负载均衡。&lt;/li&gt;
  &lt;li&gt;backup，预留的备份机器。当其他所有的非backup机器出现故障或者忙的时候，才会请求backup机器，因此这台机器的压力最轻。&lt;/li&gt;
  &lt;li&gt;max_fails，允许请求失败的次数，默认为1。当超过最大次数时，返回proxy_next_upstream 模块定义的错误。&lt;/li&gt;
  &lt;li&gt;fail_timeout，在经历了max_fails次失败后，暂停服务的时间。max_fails可以和fail_timeout一起使用。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;参考文献&quot;&gt;参考文献&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://juejin.im/post/5bacbd395188255c8d0fd4b2&quot;&gt;Nginx与前端开发&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jianshu.com/p/bed000e1830b&quot;&gt;WEB请求处理二：Nginx请求反向代理&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>从websocket服务的nginx配置说起</title>
   <link href="http://echizen.github.io/tech/2018/10-21-nginx-websocket"/>
   <updated>2018-10-21T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/nginx-websocket</id>
   <content type="html">
&lt;p&gt;之所以去了解了一波nginx，最直接的导火索是我的服务上线后，里面的websocket请求400了，降级成了轮询。网上一查就有解决方案，but 这些配置都是啥意思呢？怎么验证可用性？我该怎么去给我们的运维同事提工单配置？&lt;/p&gt;

&lt;p&gt;现象：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181021_0ajg7f8hehbb176cehck3g9hl5c9f_522x140.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;亲测解决方案，配置nginx：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection &quot;upgrade&quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再来挖挖背后的故事&lt;/p&gt;

&lt;h2 id=&quot;websocket的连接有怎样的要求&quot;&gt;websocket的连接有怎样的要求？&lt;/h2&gt;

&lt;p&gt;websocket需要请求头和响应头都设置 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade: WebSocket&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: Upgrade&lt;/code&gt; 。&lt;/p&gt;

&lt;h3 id=&quot;upgrade_header-和-connection-upgrade&quot;&gt;Upgrade_header 和 Connection: Upgrade&lt;/h3&gt;

&lt;p&gt;来自wiki的解释：&lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header&quot;&gt;https://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The Upgrade header field is an HTTP header field introduced in HTTP/1.1. In the exchange, the client begins by making a cleartext request, which is later upgraded to a newer HTTP protocol version or switched to a different protocol.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;WebSocket also uses this mechanism to set up a connection with a HTTP server in a compatible way. The WebSocket Protocol has two parts: a handshake to establish the upgraded connection, then the actual data transfer. First, a client requests a WebSocket connection by using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade: WebSocket&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: Upgrade&lt;/code&gt; headers, along with a few protocol-specific headers to establish the version being used and set up a handshake. The server, if it supports the protocol, replies with the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade: WebSocket&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: Upgrade&lt;/code&gt; headers and completes the handshake. Once the handshake is completed successfully, data transfer begins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;也就是说从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTP/1.1&lt;/code&gt;开始在头信息里允许定义一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade&lt;/code&gt;字段，用来告诉服务端后续要使用新的http协议版本或者换一个协议，websocket就是从http协议发起要更换到ws协议的。所以需要添加这个字段。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection&lt;/code&gt;表示后续连接状态，譬如我们熟悉的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keep-alive&lt;/code&gt;，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: Upgrade&lt;/code&gt;是告诉服务器后续要进行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade&lt;/code&gt;，在websoket这里就是要切换协议。&lt;/p&gt;

&lt;h4 id=&quot;http-upgrade-机制&quot;&gt;HTTP Upgrade 机制&lt;/h4&gt;

&lt;p&gt;HTTP/1.1 引入了 Upgrade 机制，它使得客户端和服务端之间可以借助已有的 HTTP 语法升级到其它协议。详情见&lt;a href=&quot;http://httpwg.org/specs/rfc7230.html#header.upgrade&quot;&gt;RFC7230 6.7. Upgrade&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WebSocket 连接的建立是典型的 HTTP Upgrade 机制。以HTTP/1.1建立初次握手，在这之后，客户端和服务端之间就可以使用 WebSocket 协议进行双向数据通讯。&lt;/p&gt;

&lt;p&gt;HTTP Upgrade 响应的状态码是 101。&lt;/p&gt;

&lt;h2 id=&quot;为啥要在nginx里配置这2项&quot;&gt;为啥要在nginx里配置这2项&lt;/h2&gt;

&lt;p&gt;可能我们面临的情况是，本地用的好好的，发服务器上就不行了。那是因为出现这个400是走了代理的原因。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade&lt;/code&gt;都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hop-by-hop headers&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hop-by-hop headers&lt;/code&gt;只传输一层，默认不会被代理和缓存。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Connection: standard hop-by-hop headers (Keep-Alive, Transfer-Encoding, TE, Connection, Trailer, Upgrade, Proxy-Authorization and Proxy-Authenticate), any hop-by-hop headers used by the message must be listed in the Connection header, so that the first proxy knows it has to consume them and not forward them further. Standard hop-by-hop headers can be listed too&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;from: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hop-by-hop headers: These headers are meaningful only for a single transport-level connection and must not be retransmitted by proxies or cached. Such headers are: Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding and Upgrade. Note that only hop-by-hop headers may be set using the Connection general header.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;from: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这2个头不会被带到代理服务器上，那么代理服务器就不知道这个升级连接为ws的信息，也就不能成功建立ws的连接了。所以nginx为了解决这个问题，为我们提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proxy_set_header&lt;/code&gt;的配置，让我们把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection&lt;/code&gt;的信息带到代理服务器上。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Since version 1.3.13, nginx implements special mode of operation that allows setting up a tunnel between a client and proxied server if the proxied server returned a response with the code 101 (Switching Protocols), and the client asked for a protocol switch via the “Upgrade” header in a request.
As noted above, hop-by-hop headers including “Upgrade” and “Connection” are not passed from a client to proxied server, therefore in order for the proxied server to know about the client’s intention to switch a protocol to WebSocket, these headers have to be passed explicitly:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;from :&lt;a href=&quot;http://nginx.org/en/docs/http/websocket.html&quot;&gt;http://nginx.org/en/docs/http/websocket.html&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;验证&quot;&gt;验证&lt;/h2&gt;

&lt;p&gt;本地nginx配置一下代理先重现下400场景：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name local.skynet.meili-inc.com;
    index static/index.html;

    location / {
        proxy_pass http://127.0.0.1:8050;
        root /Users/echizen/repo/skynet;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时访问http://local.skynet.meili-inc.com下一个有ws请求的页面，我们看到请求头已经有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade: WebSocket&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: Upgrade&lt;/code&gt;了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181021_5f39ldgk6bh7b33hj982fk872fl81_482x866.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是因为代理服务器没收到这个头信息，没有成功建立websocket连接，返回的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: keep-alive&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181021_0hj5d00gb1f9a597j26gicgkcibll_604x566.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;现在我们配置上：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;location / {
    proxy_pass http://127.0.0.1:8050;
    root /Users/echizen/repo/skynet;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection &quot;upgrade&quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx -s reload&lt;/code&gt;重启后再访问，可以看到相应头已含有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade: WebSocket&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: Upgrade&lt;/code&gt;，并且状态码也变成了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;101 Switching Protocols&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/181021_56c1j22030jiad78i1dd61ck84091_630x510.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;多说一点关于101的：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;101 Switching Protocols: 服务器已经理解了客户端的请求，并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后，服务器将会切换到在Upgrade消息头中定义的那些协议。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;写的比较啰嗦，总结下。&lt;/p&gt;

&lt;p&gt;解决方案：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection &quot;upgrade&quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;原因： websocket需要设置头 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade: WebSocket&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection: Upgrade&lt;/code&gt; 。而这2个头信息都是hop-by-hop的头信息，不会被带到代理服务器上。需要在nginx层转发时再次设置
使用客户端http request中的upgrade字段来设置给被代理服务器的请求头中的upgrade字段。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>设计与交互</title>
   <link href="http://echizen.github.io/tech/2018/09-13-design"/>
   <updated>2018-09-13T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/design</id>
   <content type="html">&lt;p&gt;在给内部员工做后台系统经常面临缺产品，无UI。。。如何从无到有的搭建一个高大上的网站成了必须拥有的能力。&lt;/p&gt;

&lt;p&gt;更多的一点体会是，多人合作的一个项目，在初期就应该定好一些规则，除了代码规范，设计规范也是很重要的方面，譬如配色和某种交互形式。否则每个人都选择自己的那套方式，拼凑出来的产品必定操作是不连贯的，风格是不统一的。&lt;/p&gt;

&lt;h2 id=&quot;原则&quot;&gt;原则&lt;/h2&gt;

&lt;p&gt;对于交互设计必须有个大原则在心中，遵循原则去实践&lt;/p&gt;

&lt;p&gt;易用 &amp;gt; 美观&lt;/p&gt;

&lt;p&gt;多加的动效和交互要考虑是否直观，是否能让用户自行感知到功能点。多余的交互步骤不能是为了酷炫。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;保持克制： 能做，但想清楚了不做。设计者应当聚焦在最有价值产品功能打磨，并用尽可能少的设计元素将其表达。正如 Antoine de St.Exupery 所说：完美不在于无以复加，而在于无可删减，万事莫不如此。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;面向对象的方法： 探索设计规律，并将其抽象成『对象』，增强界面设计的灵活性和可维护性，同时也减少『设计者』的主观干扰，从而降低系统的不确定性。例如：色值换算、间距排版。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;模块化设计： 将复杂或者重复出现的局部封装成模块，提供有限接口与其他模块互动，最终全面减少系统的复杂度，进而增进可靠性以及可维护性。设计者可运用现有的组件/模板或者自行抽象可复用的组件/模板，节约无谓的设计且保持系统一致性，让『设计者』把创造力专注在最需要的地方。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在各种交互场景中，也有一些比较推荐的做法：&lt;/p&gt;

&lt;h3 id=&quot;对齐&quot;&gt;对齐&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://ant.design/docs/spec/alignment-cn&quot;&gt;https://ant.design/docs/spec/alignment-cn&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;表单项&quot;&gt;表单项&lt;/h3&gt;

&lt;p&gt;如果列表项很长，视觉容易串行，hover上后可以改变当前行背景色来强调&lt;/p&gt;

&lt;h3 id=&quot;强调&quot;&gt;强调&lt;/h3&gt;

&lt;p&gt;重点提示性文字，颜色高亮强调，容易忽略的重要操作，按钮高亮&lt;/p&gt;

&lt;h3 id=&quot;删除&quot;&gt;删除&lt;/h3&gt;

&lt;p&gt;危险的删除操作要加confirm提示&lt;/p&gt;

&lt;h3 id=&quot;提示message&quot;&gt;提示message&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;成功且有明显交互的，无需提示，譬如表单类目减少一项&lt;/li&gt;
  &lt;li&gt;错误必须提示，后端返回语句如果做不到精确，自己需要在润色文案提示出原因&lt;/li&gt;
  &lt;li&gt;请求耗时的操作加loading提示进行中&lt;/li&gt;
  &lt;li&gt;如果是在成功前要阻止其他操作的请求，可考虑整个页面loading&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;弹窗&quot;&gt;弹窗&lt;/h3&gt;

&lt;p&gt;少用&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;非主流程的辅助性信息填写&lt;/li&gt;
  &lt;li&gt;主页面信息拥挤情景，靠弹窗获得信息空间&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;popover&quot;&gt;popover&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;大多数人能明白，但少数人需要提示的图标类信息&lt;/li&gt;
  &lt;li&gt;hover了解详情类&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;相同或者类似的操作保持相同的交互&quot;&gt;相同或者类似的操作保持相同的交互&lt;/h3&gt;

&lt;p&gt;降低使用学习成本&lt;/p&gt;

&lt;h3 id=&quot;鼠标图标&quot;&gt;鼠标图标&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;可点击的元素&lt;/li&gt;
  &lt;li&gt;可拖拽的元素&lt;/li&gt;
  &lt;li&gt;可选中的元素&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;批量&quot;&gt;批量&lt;/h3&gt;

&lt;p&gt;列表类的操作，要能支持批量，譬如批量删除，来提升体验&lt;/p&gt;

&lt;h3 id=&quot;直接了当&quot;&gt;直接了当&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://ant.design/docs/spec/direct-cn&quot;&gt;https://ant.design/docs/spec/direct-cn&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;提前确定&quot;&gt;提前确定&lt;/h2&gt;

&lt;h3 id=&quot;主题色&quot;&gt;主题色&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;主色调、辅色&lt;/li&gt;
  &lt;li&gt;light\dark&lt;/li&gt;
  &lt;li&gt;输入框hover、focus变成主题色&lt;/li&gt;
  &lt;li&gt;浅色按钮hover后加深色值，深色按钮hover降浅色值&lt;/li&gt;
  &lt;li&gt;色块区域，border颜色加深一度&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;字体大小&quot;&gt;字体大小&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;普通文案&lt;/li&gt;
  &lt;li&gt;加大版标题&lt;/li&gt;
  &lt;li&gt;缩小版提示语&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;主题相关内容应变量保存，来应对后续修改&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;交互统一&quot;&gt;交互统一&lt;/h3&gt;

&lt;p&gt;有些交互会存在多种形式，每种各有适用场景，没有最佳实践，但是同一个系统中推荐统一体验&lt;/p&gt;

&lt;h4 id=&quot;加载更多&quot;&gt;加载更多&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;滚动加载：更多的用于手机端，适用于浏览多页可能性大的场景。优势是无感知无多余操作自动加载，劣势是会导致页面不断堆积dom元素，量大时需要动态移除来提升性能。&lt;/li&gt;
  &lt;li&gt;分页标签：适用于对后面的页面浏览需求不大的场景。可以方便的指定页码跳转，也没有性能负担&lt;/li&gt;
  &lt;li&gt;加载更多按钮&lt;/li&gt;
  &lt;li&gt;前几页使用滚动加载，浏览可能性小的后几页使用分页&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;按钮点击后请求未成功时的状态&quot;&gt;按钮点击后请求未成功时的状态&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;loading&lt;/li&gt;
  &lt;li&gt;disabled: 不可点击
可结合使用&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;卡片操作按钮&quot;&gt;卡片操作按钮&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;右上角图标：适用于操作场景频繁，与浏览需求同等地位，更直观。&lt;/li&gt;
  &lt;li&gt;浮层出现按钮：适用于主需求是浏览主题信息，操作需求场景少于浏览需求&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;表单验证&quot;&gt;表单验证&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;blur或者change时实时验证&lt;/li&gt;
  &lt;li&gt;统一保存时验证
最好2样都做，防止浏览器自动填充时不触发blur和change事件的校验&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;搜索&quot;&gt;搜索&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;500ms实时搜索：减少用户操作，自动智能，服务器请求压力大&lt;/li&gt;
  &lt;li&gt;搜索按钮触发搜索：适用于搜索内容量少，且不频繁的需求&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;他山之石&quot;&gt;他山之石&lt;/h2&gt;

&lt;p&gt;多逛好的设计素材类网站，有助于我们学习借鉴，快速造起美观的网站~&lt;/p&gt;

&lt;p&gt;大家共享。&lt;/p&gt;

&lt;h3 id=&quot;配色类&quot;&gt;配色类&lt;/h3&gt;

&lt;p&gt;始终觉得配色是我最大的障碍。。。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://colorhunt.co/&quot;&gt;https://colorhunt.co/&lt;/a&gt;：海量配色方案，可以pick心仪的颜色搭配留用&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.vanschneider.com/colors&quot;&gt;http://www.vanschneider.com/colors&lt;/a&gt;：独立设计师分享的个人搭配方案&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tool.c7sky.com/webcolor/#character_15&quot;&gt;https://tool.c7sky.com/webcolor/#character_15&lt;/a&gt;: 各种分类、主题建议&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/33549908&quot;&gt;网页设计中怎么配色？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;设计类&quot;&gt;设计类&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.seeseed.com/&quot;&gt;https://www.seeseed.com/&lt;/a&gt;：小而美的分享设计话题，强烈安利，质量很高&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dribbble.com/&quot;&gt;https://dribbble.com/&lt;/a&gt;：全球优秀设计作品集锦&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/alldesigns/&quot;&gt;http://www.mezzoblue.com/zengarden/alldesigns/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.uisdc.com/&quot;&gt;https://www.uisdc.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;素材类&quot;&gt;素材类&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://unsplash.com/&quot;&gt;https://unsplash.com/&lt;/a&gt;：图片&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://huaban.com/&quot;&gt;http://huaban.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://zoommyapp.com/&quot;&gt;http://zoommyapp.com/&lt;/a&gt;：图片&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.toptal.com/designers/subtlepatterns/&quot;&gt;https://www.toptal.com/designers/subtlepatterns/&lt;/a&gt;: 背景图&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.jiawin.com/&quot;&gt;http://www.jiawin.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zcool.com.cn/&quot;&gt;https://www.zcool.com.cn/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.egouz.com/sucai/&quot;&gt;http://www.egouz.com/sucai/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/19652441&quot;&gt;设计师找图片素材，有哪些较好的网站可推荐？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;参考作品&quot;&gt;参考作品&lt;/h3&gt;

&lt;h4 id=&quot;时尚&quot;&gt;时尚&lt;/h4&gt;

&lt;p&gt;各大奢饰品品牌官网，看下来都是黑白色调，简约大方&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.chanel.com/zh_CN/fashion/haute-couture.html&quot;&gt;chanel&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.gucci.cn/zh/&quot;&gt;gucci&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ysl.com/wy&quot;&gt;ysl&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.givenchy.com/apac/zh/homepage&quot;&gt;givenchy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.apple.com/cn/&quot;&gt;apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;动效&quot;&gt;动效&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.niika.com.au/&quot;&gt;https://www.niika.com.au/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.reverenddanger.com/&quot;&gt;http://www.reverenddanger.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.heandshephoto.com/&quot;&gt;http://www.heandshephoto.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>适时用起indexDB</title>
   <link href="http://echizen.github.io/tech/2018/06-23-indexdb"/>
   <updated>2018-06-23T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/indexdb</id>
   <content type="html">
&lt;p&gt;最近在做用户在富文本编辑器中编辑内容缓存时首次接触使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indexDB&lt;/code&gt;，较系统的整理了一下相关知识。&lt;/p&gt;

&lt;h2 id=&quot;使用场景&quot;&gt;使用场景&lt;/h2&gt;

&lt;p&gt;需要在web端存储大量数据，且只用兼容现代浏览器，具有一定复杂度。（兼容性和复杂性也是indexDB存在这么久运用却不太高的重要因素吧）&lt;/p&gt;

&lt;p&gt;web端存储数据的优先考虑项是localstorage，方便、兼容性好。&lt;/p&gt;

&lt;p&gt;但是indexDB也有它存在的场景：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;存储数据量更大，localstorage每个域名是3M左右（各浏览器有差别），indexDB大的多，一般来说不少于250MB&lt;/li&gt;
  &lt;li&gt;效率更高，数据存储结构加上索引能更高效的查询&lt;/li&gt;
  &lt;li&gt;异步操作，不阻塞主线程的js任务&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;譬如邮件各种编辑器场景下，缓存用户的文章、图片，以防止关闭标签页丢失数据，是不二选择。&lt;/p&gt;

&lt;h2 id=&quot;简介&quot;&gt;简介&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;IndexedDB 是一个用于在浏览器中储存&lt;strong&gt;较大&lt;/strong&gt;数据结构的 Web API, 并提供&lt;strong&gt;索引&lt;/strong&gt;功能以实现高性能查找. 像其他基于 SQL 的 关系型数据库管理系统 (RDBMS) 一样, IndexedDB 是一个&lt;strong&gt;事务型&lt;/strong&gt;的数据库系统. 然而, 它是使用 &lt;strong&gt;JavaScript 对象&lt;/strong&gt;而非列数固定的表格来储存数据的.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;存储键值对&quot;&gt;存储键值对&lt;/h3&gt;

&lt;p&gt;值可以是复杂的对象，表的key可以是对象的一个属性值，可以使用属性值创建索引提高查询效率&lt;/p&gt;

&lt;h3 id=&quot;创建在事务transactional模型上&quot;&gt;创建在事务（transactional）模型上&lt;/h3&gt;

&lt;p&gt;所有对于索引、表的操作都通过事务，事务有一系列的生命周期，并且不受人工影响而自动完成。&lt;/p&gt;

&lt;p&gt;事务模型对于多个同时进行的操作非常有效，譬如同时打开多个操作访问数据库，每个都能独立安全的进行而不会相互影响&lt;/p&gt;

&lt;h3 id=&quot;异步&quot;&gt;异步&lt;/h3&gt;

&lt;p&gt;所有api都是异步的，通过回调函数来获取返回&lt;/p&gt;

&lt;p&gt;IndexedDB使用大量requests来进行读写操作，requests拥有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onsuccess&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onerror&lt;/code&gt;放发，可以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addEventListener&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;removeEventListener&lt;/code&gt;来监听他们&lt;/p&gt;

&lt;h3 id=&quot;基于对象&quot;&gt;基于对象&lt;/h3&gt;

&lt;p&gt;这是与关系型数据库的很大不同，IndexedDB没有关系型数据库中的行和列来存储数据。它需要为存储对象创建一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object store&lt;/code&gt;和一个js对象去存储。每个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object store&lt;/code&gt;可以有独特的索引值来让其高效的查询和遍历&lt;/p&gt;

&lt;h2 id=&quot;概念&quot;&gt;概念&lt;/h2&gt;

&lt;h3 id=&quot;database&quot;&gt;database&lt;/h3&gt;
&lt;p&gt;由一个或者多个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object store&lt;/code&gt;构成，需要有：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;名称&lt;/li&gt;
  &lt;li&gt;版本：一般是在表字段有增删、修改时改动版本。通过打开一个更高版本的数据库，触发&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;versionchange&lt;/code&gt;和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upgradeneeded&lt;/code&gt;的事务，然后来更改表字段&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一个数据库在同一时刻可以被多个任务连接&lt;/p&gt;

&lt;h3 id=&quot;durable&quot;&gt;durable&lt;/h3&gt;
&lt;p&gt;readwrite事务的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IDBTransaction.oncomplete&lt;/code&gt;事件并不发生在所有数据都写完成，而是发生在系统被告知去写数据，但是并不是数据真的都写到了硬盘里，这样有利于提升效率&lt;/p&gt;

&lt;h3 id=&quot;object-store&quot;&gt;object store&lt;/h3&gt;
&lt;p&gt;object store记录的数据是键值对，每个object store需要有个唯一标识的key字段，如果定义了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key path&lt;/code&gt;，它使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in-line keys&lt;/code&gt;，否则，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out-of-line keys&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;可以指定记录中的一个字段作为键值（key path），或者可以使用自动生成的递增数字作为键值（key generate）&lt;/p&gt;

&lt;p&gt;比较难理解&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in-line keys&lt;/code&gt;：指定了使用存储对象中的特定key作为类似数据库表的主键，也就是object store的key path，这个key的每条记录值需要是唯一的&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out-of-line keys&lt;/code&gt;：不指定，由系统自动生成唯一的key，这时候存储对象中就没有这个key字段了&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180623_5d82eah6db593id136a72i93ic979_1474x586.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;看着这两个的定义还是有点复杂的。我们可以联系SQL数据库来思考。SQL的表结构包含多个字段，必须有一个主键。主键可以设置为自增或者是自定义。IndexedDB只不过是存储结构有一些不同，它更加灵活，它也需要一个主键，只是这个可以是数据上的，也可以是数据外的。KeyPath为No，也就是不在数据中保存主键，所以存储对象空间可以存放任何类型，而KeyPath为Yes，则需要在数据中保存主键，所以数据也就只能是对象了。Key Generator则是指定主键是否是自增的，如果是Yes，则主键自增，但是也可以自己指定，如果为No，则主键必须用户自己指定。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;key path和key generate配置的是对象存储空间的主键和是否自增。IndexedDB还支持索引和唯一索引，前提是对象存储空间存储的是对象。
    - &lt;a href=&quot;http://imushan.com/2018/02/27/js/javascript/IndexedDB%E7%AC%94%E8%AE%B0-%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/&quot;&gt;木杉的博客&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;transaction&quot;&gt;transaction&lt;/h3&gt;

&lt;p&gt;获取或者修改数据库里的数据都是通过事务。一个数据库连接可以同时存在多个活跃中的事务&lt;/p&gt;

&lt;p&gt;耗时太长的事务会被浏览器终止，也可以手动调用终止，终止时，会回滚该事务中的所有操作&lt;/p&gt;

&lt;h3 id=&quot;index&quot;&gt;index&lt;/h3&gt;

&lt;p&gt;索引，每条记录的唯一值，用来区别不同数据记录。是稳定的key-value结构，它的value是 object store 的key字段的值？（The index is a persistent key-value storage where the value part of its records is the key part of a record in the referenced object store）
区别记录&lt;/p&gt;

&lt;p&gt;索引是一种数据结构，而不是表象中的表中的一个字段，他相当于指向表中字段的指针&lt;/p&gt;

&lt;h3 id=&quot;key&quot;&gt;key&lt;/h3&gt;

&lt;p&gt;用来存储数据和获取数据的key，类似对象中的属性名&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;来源：自动生成的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key generator&lt;/code&gt;, 创建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object store&lt;/code&gt;时定义的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key path&lt;/code&gt;, 或者指定的一个名称&lt;/li&gt;
  &lt;li&gt;特征：1）key名不可重复，类似于对象中的属性不能重复 2）默认升序，后一条记录的比前一条记录的值大&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;value&quot;&gt;value&lt;/h3&gt;

&lt;p&gt;每条记录都有个value，可以存储js包含的类型: boolean, number, string, date, object, array, regexp, undefined, null. 甚至是Blobs 和 files&lt;/p&gt;

&lt;h3 id=&quot;key-path&quot;&gt;key path&lt;/h3&gt;

&lt;p&gt;存储结构store object对应存储数据中对象的属性名&lt;/p&gt;

&lt;p&gt;（以下内容懒得翻译了。。。。）&lt;/p&gt;

&lt;h2 id=&quot;scope&quot;&gt;scope&lt;/h2&gt;
&lt;p&gt;The set of object stores and indexes to which a transaction applies. The scopes of read-only transactions can overlap and execute at the same time. On the other hand, the scopes of writing transactions cannot overlap. You can still start several transactions with the same scope at the same time, but they just queue up and execute one after another.&lt;/p&gt;

&lt;h2 id=&quot;cursor&quot;&gt;cursor&lt;/h2&gt;
&lt;p&gt;A mechanism for iterating over multiple records with a key range. The cursor has a source that indicates which index or object store it is iterating. It has a position within the range, and moves in a direction that is increasing or decreasing in the order of record keys. For the reference documentation on cursors, see IDBCursor.&lt;/p&gt;

&lt;h2 id=&quot;key-range&quot;&gt;key range&lt;/h2&gt;
&lt;p&gt;A continuous interval over some data type used for keys. Records can be retrieved from object stores and indexes using keys or a range of keys. You can limit or filter the range using lower and upper bounds. For example, you can iterate over all values of a key between x and y.&lt;/p&gt;

&lt;h2 id=&quot;整理&quot;&gt;整理&lt;/h2&gt;

&lt;p&gt;存数据放value里就可以，创建key字段是为了方便查询，建Index是为了高效查询，而且index要求唯一&lt;/p&gt;

&lt;h2 id=&quot;使用&quot;&gt;使用&lt;/h2&gt;

&lt;h3 id=&quot;打开数据库&quot;&gt;打开数据库&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexedDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 如果数据库打开失败&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;数据库打开异常&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;        
    &lt;span class=&quot;c1&quot;&gt;// 存储数据结果&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;创建object-store数据表信息&quot;&gt;创建object store(数据表)信息&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onupgradeneeded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;数据库打开失败&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// 创建一个数据库存储对象&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createObjectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
      &lt;span class=&quot;na&quot;&gt;keyPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    
    &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;    
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个示例中创建了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in-line keys&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object store&lt;/code&gt;，要求所有存储的数据中都要含有id字段，并且以此创建索引，方便后续针对此字段的查找。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createObjectStore&lt;/code&gt;的第二个参数即可指定&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in-line keys&lt;/code&gt;中的keyPath&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;objectStore.createIndex&lt;/code&gt;的第一个参数是存储结构&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object store&lt;/code&gt;的key值，第二个字段是存储结构中的key字段对应的要存储的数据对象中的属性值&lt;/p&gt;

&lt;p&gt;也可以创建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out-of-line keys&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object store&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c1&quot;&gt;// 使用自动生成的递增数字作为键值&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createObjectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;autoIncrement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;    
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;增加更新记录&quot;&gt;增加/更新记录&lt;/h3&gt;

&lt;p&gt;有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;接口，在明确能区分是增加还是更新操作时，建议增加用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;接口与，更新用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;put&lt;/code&gt;接口，以提高性能。&lt;/p&gt;

&lt;p&gt;但是有些场景是无法区分是增加或者是更新操作的，譬如某个操作后需要更新数据，但是并不知道这条数据之前是否已经存在，那么都使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;put&lt;/code&gt;接口即可，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;put&lt;/code&gt;接口在没有记录是会增加，存在记录时则更新&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;readwrite&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 打开已经存储的数据对象&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 添加到数据对象中&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// 或者使用objectStore.add(value)   &lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`从数据库读取&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;异常`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;删除记录&quot;&gt;删除记录&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;readwrite&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 打开已经存储的数据对象&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 添加到数据对象中&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`删除&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;成功`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`从数据库删除&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;异常`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;查询记录&quot;&gt;查询记录&lt;/h3&gt;

&lt;p&gt;查询记录需要使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cursor&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cursor&lt;/code&gt;类似一个指针，在数据记录中一条条移动遍历出每条记录&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;recordList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;openCursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 如果没有遍历完，继续下面的逻辑&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;recordList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;            
          &lt;span class=&quot;c1&quot;&gt;// 继续下一个游标项&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 如果全部遍历完毕&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;存储的数据为&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;recordList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;存储非对象类型的普通数据&quot;&gt;存储非对象类型的普通数据&lt;/h3&gt;

&lt;p&gt;以上示例代码都是针对含有索引的object类型数据的存储。这也是大多�数应用场景&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180623_4i8i0640jc3l4ibi7k6ii704600d9_1102x110.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其实&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indexDB&lt;/code&gt;可以存储任何js中合法类型的数据，可以没有主键keyPath，不创建索引index。&lt;/p&gt;

&lt;p&gt;譬如:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 创建object store&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createObjectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 添加、更新数据&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;数据库结构：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180623_0ckg428ebkgj39b2267e6a30klaf8_1106x116.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;实例&quot;&gt;实例&lt;/h2&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DBOperator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 版本&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
  
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DBOpenPro&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 打开数据库&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexedDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// 如果数据库打开失败&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;数据库打开异常&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      
      &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;        
        &lt;span class=&quot;c1&quot;&gt;// 存储数据结果&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;nx&quot;&gt;DBOpenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onupgradeneeded&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
     
        &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;数据库打开失败&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    
        &lt;span class=&quot;c1&quot;&gt;// 创建一个数据库存储对象&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createObjectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
          &lt;span class=&quot;na&quot;&gt;keyPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;autoIncrement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    
        &lt;span class=&quot;c1&quot;&gt;// 定义存储对象的数据项&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;    
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 可以定义更多的字段&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DBOpenPro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;readonly&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 打开已经存储的数据对象&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 添加到数据对象中&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     
        &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DBOpenPro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;readwrite&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 打开已经存储的数据对象&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 添加到数据对象中&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     
        &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`从数据库读取&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;异常`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DBOpenPro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;readwrite&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 打开已经存储的数据对象&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dbName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 添加到数据对象中&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  
        &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;objectStoreRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`从数据库删除&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;异常`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;更多&quot;&gt;更多&lt;/h2&gt;

&lt;h3 id=&quot;范围查询&quot;&gt;范围查询&lt;/h3&gt;

&lt;p&gt;使用IDBKeyRange和Cursor进行范围查询&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;索引的有用之处，还在于可以指定读取数据的范围。这需要用到浏览器原生的IDBKeyRange对象。
    IDBKeyRange对象的作用是生成一个表示范围的Range对象。生成方法有四种：&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;lowerBound方法：指定范围的下限。&lt;/li&gt;
  &lt;li&gt;upperBound方法：指定范围的上限。&lt;/li&gt;
  &lt;li&gt;bound方法：指定范围的上下限。&lt;/li&gt;
  &lt;li&gt;only方法：指定范围中只有一个值。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面是一些代码实例：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;c1&quot;&gt;// All keys ≤ x	&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IDBKeyRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;upperBound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// All keys &amp;gt; y	&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IDBKeyRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lowerBound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// All keys ≥ x &amp;amp;&amp;amp; ≤ y	&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IDBKeyRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// All keys &amp;gt; x &amp;amp;&amp;amp; ≤ y	&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r7&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IDBKeyRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// The key = z	&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r9&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IDBKeyRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;前三个方法（lowerBound、upperBound和bound）默认包括端点值，可以传入一个布尔值，修改这个属性。&lt;/p&gt;

&lt;p&gt;生成Range对象以后，将它作为参数输入openCursor方法，就可以在所设定的范围内读取数据。&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;people&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;readonly&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;people&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IDBKeyRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;openCursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;field&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;（以上”范围查询”内容搬运于阮一峰老师博客）&lt;/p&gt;

&lt;h3 id=&quot;多个数据段的存储&quot;&gt;多个数据段的存储&lt;/h3&gt;

&lt;p&gt;一般数据库的表可以有多个字段，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createTime&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;operater&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;通过创建多个索引可以达到这个效果，但是索引是为了提高查询效率的，如果一个字段从来不会被作为查询条件，那么对其建立索引是没有意义的。&lt;/p&gt;

&lt;p&gt;目前对于这种场景，我的做法是通过存储object类型的数据来做到，需要更改哪个字段，就去改这个object对应属性值，再把object作为一条记录整体更新。不知道是否还有其他做法。&lt;/p&gt;

&lt;h2 id=&quot;封装&quot;&gt;封装&lt;/h2&gt;

&lt;p&gt;以上代码片段也看到了indexDB的api非常繁琐，操作起来不方便。&lt;/p&gt;

&lt;p&gt;那么我们项目中如果有大量应用时，可以用一些封装好的库来提高效率，譬如：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://localforage.github.io/localForage/#localforage&quot;&gt;localforage&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Concepts_Behind_IndexedDB&quot;&gt;MDN&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2017/07/html5-indexeddb-js-example/&quot;&gt;https://www.zhangxinxu.com/wordpress/2017/07/html5-indexeddb-js-example/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://imushan.com/2018/02/27/js/javascript/IndexedDB%E7%AC%94%E8%AE%B0-%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/&quot;&gt;http://imushan.com/2018/02/27/js/javascript/IndexedDB%E7%AC%94%E8%AE%B0-%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://javascript.ruanyifeng.com/bom/indexeddb.html&quot;&gt;http://javascript.ruanyifeng.com/bom/indexeddb.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>css玄学之一二</title>
   <link href="http://echizen.github.io/tech/2018/04-05-read-css-world"/>
   <updated>2018-04-05T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/read-css-world</id>
   <content type="html">
&lt;p&gt;css在我心中一直是玄学，因此一直希望遇见一本可以系统的介绍css规则。所以看到张鑫旭出了《css世界》，好不犹豫的买来读了。虽然这本书并没有如愿让我对css有系统的解惑，但是很多方面有了进一步认识。另外，张鑫旭大大真的是匠心，非常钦佩其十年如一日的深钻css世界，特别是他非常细致深入的不间断更新的博客。（个人不成熟的小感受是书中虽然有在按章节介绍内容，但是经常从a到b，导致读者主线丢失，感觉还是在一个个堆知识点，难以连成片。另外书中出现不少为了理解方便而取的名词，如“空白幽灵节点”，我还是希望书中的专业名词都跟标准里统一，不要自定义，否则出了这本书，我们可能就对不上了）。&lt;/p&gt;

&lt;p&gt;非常认同，css玄在最终表现是多个属性共同作用的结果，这多个属性常上达几十个，所以单从某个属性角度很多时候无法理解其表现&lt;/p&gt;

&lt;p&gt;再说说本文会有点乱，夹杂各种知识点。不能从实用角度来看待，更多的是去理解原理，为啥会有这么多玄乎的表现。如果从实用角度，我们可能不会组合这些属性来诞生这些奇怪的表现，或者加些属性来消除影响。但是作为一个前端，我认为还是有必要理解为什么是这样的表现的。&lt;/p&gt;

&lt;p&gt;主要涉及点:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;行框盒子前的strut&lt;/li&gt;
  &lt;li&gt;基线&lt;/li&gt;
  &lt;li&gt;line-height&lt;/li&gt;
  &lt;li&gt;vertical-align&lt;/li&gt;
  &lt;li&gt;font-size和文本高度&lt;/li&gt;
  &lt;li&gt;BFC&lt;/li&gt;
  &lt;li&gt;css层叠顺序&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;内联&quot;&gt;内联&lt;/h1&gt;

&lt;p&gt;相比于块级元素，内联元素出现的不少，但是属性表现经常不受重视，譬如我就发现很多现象之前没有认知到。&lt;/p&gt;

&lt;p&gt;先来介绍一波之前不被我理解的现象&lt;/p&gt;

&lt;h2 id=&quot;诡异的案例系列&quot;&gt;诡异的案例系列&lt;/h2&gt;

&lt;h3 id=&quot;1空元素高度不为0&quot;&gt;1.空元素高度不为0&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://codepen.io/echizen/pen/eMxbwO?editors=1100&quot;&gt;https://codepen.io/echizen/pen/eMxbwO?editors=1100&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180331_43hbfih4i05j7725f4be2c833f8ck_948x1130.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;块状元素line-height设置的高度不是最终高度&quot;&gt;块状元素line-height设置的高度不是最终高度&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://codepen.io/echizen/pen/XEOOJb?editors=1100&quot;&gt;https://codepen.io/echizen/pen/XEOOJb?editors=1100&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.box { line-height: 32px; }
.box &amp;gt; span { font-size: 24px; }
&amp;lt;div class=&quot;box&quot;&amp;gt;
　 &amp;lt;span&amp;gt;文字&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解释：&lt;/p&gt;

&lt;p&gt;其中有一个很关键的点，那就是24px的font-size大小是设置在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;span&amp;gt;&lt;/code&gt;元素上的，这就导致了外部&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt;元素的字体大小和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;span&amp;gt;&lt;/code&gt;元素有较大出入。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;span&amp;gt;&lt;/code&gt;标签前面实际上有一个看不见的类似字符的“幽灵空白节点”。看不见的东西不利于理解，因此我们不妨使用一个看得见的字符x占位，同时“文字”后面也添加一个x，便于看出基线位置，于是就有如下HTML：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;box&quot;&amp;gt;
　 x&amp;lt;span&amp;gt;文字x&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时，我们可以明显看到两处大小完全不同的文字。一处是字母x构成了一个“匿名内联盒子”，另一处是“文字 x”所在的&lt;span&gt;元素，构成了一个“内联盒子”。由于都受line- height:32px影响，因此，这两个“内联盒子”的高度都是32px。下面关键的来了，对字符而言，font-size越大字符的基线位置越往下(需要将line-height的行距上下平分)，因为文字默认全部都是基线对齐，所以当字号大小不一样的两个文字在一起的时候，彼此就会发生上下位移，如果位移距离足够大，就会超过行高的限制，而导致出现意料之外的高度。&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180401_088eehb5aal370gcb7287f37cdg00_928x262.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;解决方案：给box设置和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;span&lt;/code&gt;一样的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-size&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;2vertical-alignmiddle-不起作用&quot;&gt;2.vertical-align:middle 不起作用&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.box {
　 height: 128px;
}
.box &amp;gt; img {
　 height: 96px;
　 vertical-align: middle;
 }
&amp;lt;div class=&quot;box&quot;&amp;gt;
　 &amp;lt;img src=&quot;1.jpg&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时图片顶着.box元素的上边缘显示，根本没垂直居中，完全没起作用！&lt;/p&gt;

&lt;p&gt;解释：&lt;/p&gt;

&lt;p&gt;这种情况看上去是vertical-align:middle没起作用，实际上，vertical-align是在努力地渲染的，只是行框盒子前面的“幽灵空白节点”高度太小，如果我们通过设置一个足够大的行高让“幽灵空白节点”高度足够，就会看到vertical-align:middle起作用了。可以给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.box&lt;/code&gt;设置个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line-height: 128px&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;3margin值无效&quot;&gt;3.margin值无效&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;box&quot;&amp;gt;
　 &amp;lt;img src=&quot;mm1.jpg&quot;&amp;gt;
&amp;lt;/div&amp;gt;
.box &amp;gt; img {
　 height: 96px;
　 margin-top: -200px;
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;-200px远远超过图片的高度，图片应该完全跑到容器的外面，但是，图片依然有部分在.box元素中，而且就算margin-top设置成-99999px，图片也不会继续往上移动，完全失效。&lt;/p&gt;

&lt;p&gt;解释：&lt;/p&gt;

&lt;p&gt;的前面有个“幽灵空白节点”，而在CSS世界中，非主动触发位移的内联元素是不可能跑到计算容器外面的，导致图片的位置被“幽灵空白节点”的vertical-align:baseline给限死了。我们不妨把看不见的“幽灵空白节点”使用字符x代替。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180401_6l7235b9il0ki532fi1agf36g216a_906x350.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;因为字符x下边缘和图片下边缘对齐，字符x非主动定位，不可能跑到容器外面，所以图片就被限死在此问题，margin-top失效。&lt;/p&gt;

&lt;h3 id=&quot;4图片底部留白&quot;&gt;4.图片底部留白&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/5/3-5.php&quot;&gt;http:// demo.cssworld.cn/5/3-5.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;解释：&lt;/p&gt;

&lt;p&gt;常见的图片底部留白问题也是一样，间隙产生的三大元凶就是“幽灵空白节点”、line-height和vertical-align属性。&lt;/p&gt;

&lt;p&gt;解决：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;图片块状化。可以一口气干掉“幽灵空白节点”、line-height和vertical-align。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;容器line-height足够小。只要半行间距小到字母x的下边缘位置或者再往上，自然就没有了撑开底部间隙高度空间了。比方说，容器设置line-height:0。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;容器font-size足够小。此方法要想生效，需要容器的line-height属性值和当前font-size相关，如line-height:1.5或者line-height:150%之类；否则只会让下面的间隙变得更大，因为基线位置因字符x变小而往上升了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;图片设置其他vertical-align属性值。间隙的产生原因之一就是基线对齐，所以我们设置vertical-align的值为top、middle、bottom中的任意一个都是可以的。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;再来介绍下内联元素相关的概念和知识&lt;/p&gt;

&lt;h2 id=&quot;外部盒子和容器盒子&quot;&gt;外部盒子和容器盒子&lt;/h2&gt;

&lt;p&gt;“每个元素都两个盒子，外在盒子和容器盒子。外在盒子负责元素是可以一行显示，还是只能换行显示；容器盒子负责宽高、内容呈现什么的”&lt;/p&gt;

&lt;p&gt;按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display&lt;/code&gt;的属性值不同，值为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block&lt;/code&gt;的元素的盒子实际由外在的“块级盒子”和内在的“块级容器盒子”组成，值为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline-block&lt;/code&gt;的元素则由外在的“内联盒子”和内在的“块级容器盒子”组成，值为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline&lt;/code&gt;的元素则内外均是“内联盒子”。&lt;/p&gt;

&lt;p&gt;“遵循这种理解，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:block&lt;/code&gt;应该脑补成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:block-block&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:table&lt;/code&gt;应该脑补成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:block-table&lt;/code&gt;”&lt;/p&gt;

&lt;p&gt;“内联元素”指“外在盒子”为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline&lt;/code&gt;的元素。&lt;/p&gt;

&lt;h2 id=&quot;内联盒模型&quot;&gt;内联盒模型&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;内容区域（content area）。内容区域指一种围绕文字看不见的盒子，其大小仅受字符本身特性控制，本质上是一个字符盒子（character box）；但是有些元素，如图片这样的替换元素，其内容显然不是文字，不存在字符盒子之类的，因此，对于这些元素，内容区域可以看成元素自身。我们可以把文本选中的背景色区域作为内容区域&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;内联盒子（inline box）。“内联盒子”不会让内容成块显示，而是排成一行，这里的“内联盒子”实际指的就是元素的“外在盒子”，用来决定元素是内联还是块级。该盒子又可以细分为“内联盒子”和“匿名内联盒子”两类：　如果外部含内联标签（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;span&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;a&amp;gt;&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;em&amp;gt;&lt;/code&gt;等），则属于“内联盒子”；如果是个光秃秃的文字，则属于“匿名内联盒子”。需要注意的是，并不是所有光秃秃的文字都是“匿名内联盒子”，其还有可能是“匿名块级盒子”，关键要看前后的标签是内联还是块级。&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180410_1jll5lg1ic8llj5h8ib5d6c5j8fec_890x76.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;行框盒子（line box）。例如：&lt;/p&gt;

    &lt;p&gt;&amp;lt; p&amp;gt; 这是一行普通的文字，这里有个 &amp;lt; em&amp;gt;em&amp;lt; /em&amp;gt; 标签。 &amp;lt; /p&amp;gt;&lt;/p&gt;

    &lt;p&gt;每一行就是一个“行框盒子”，每个“行框盒子”又是由一个一个“内联盒子”组成的。&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180410_26f25k3iafibid5e3k8i70c579ji5_910x94.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;包含块（containing block）。例如：&lt;/p&gt;

    &lt;p&gt;&amp;lt; p&amp;gt;这是一行普通的文字，这里有个 &amp;lt; em&amp;gt;em&amp;lt; /em&amp;gt; 标签。&amp;lt; /p&amp;gt;&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt;标签就是一个“包含盒子”，包含块由一行一行的“行框盒子”组成。&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180410_7ibf6i64l8fji48e8063l37gg2200_910x92.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;strut---幽灵空白节点&quot;&gt;strut - 幽灵空白节点&lt;/h2&gt;

&lt;p&gt;在HTML5文档声明中，内联元素的所有解析和渲染表现就如同每个行框盒子的前面有一个“空白节点”一样。这个“空白节点”永远透明，不占据任何宽度，看不见也无法通过脚本获取，就好像幽灵一样，但又确确实实地存在，表现如同文本节点一样，因此，称之为“幽灵空白节点”&lt;/p&gt;

&lt;p&gt;“幽灵空白节点”实际上也是一个盒子，不过是个假想盒，名叫“strut”，是一个存在于每个“行框盒子”前面，同时具有该元素的字体和行高属性的0宽度的内联盒。&lt;/p&gt;

&lt;h2 id=&quot;基线&quot;&gt;基线&lt;/h2&gt;

&lt;p&gt;字母x的下边缘（线）就是我们的基线&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line-height&lt;/code&gt;行高的定义就是两基线的间距&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align&lt;/code&gt;的默认值就是基线&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-height&lt;/code&gt;，指的是字母x的高度，术语描述就是基线和等分线（meanline）（也称作中线，midline）之间的距离&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180331_01f9g86a5719569dg53hd0gk1a013_906x358.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align:middle&lt;/code&gt;。这里的middle是中间的意思。注意，跟上面的median（中线）不是一个意思。在CSS世界中，middle指的是基线往上1/2 x-height高度。我们可以近似理解为字母x交叉点那个位置。&lt;/p&gt;

&lt;h2 id=&quot;line-height&quot;&gt;line-height&lt;/h2&gt;

&lt;p&gt;对于块级元素，它指定元素中线框的最小高度。通过改变块级元素里面内联级别元素占据的高度来实现对块级元素最终高度的影响&lt;/p&gt;

&lt;p&gt;在未替换的内联元素中，它影响元素的可视高度。什么padding、border属性对可视高度是没有任何影响的，这也是我们平常口中的“盒模型”约定俗成说的是块级元素的原因。&lt;/p&gt;

&lt;p&gt;对于替代内联元素，如 button 或其他 input 元素，line-height 没有影响。（但是通过影响strut间接影响样式）&lt;/p&gt;

&lt;p&gt;默认值normal, 取决于用户代理。桌面浏览器（包括火狐浏览器）使用默认值，约为1.2，这取决于元素的 font-family。&lt;/p&gt;

&lt;p&gt;如果使用数值作为line-height的属性值，那么所有的子元素继承的都是这个值；但是，如果使用百分比值或者长度值作为属性值，那么所有的子元素继承的是最终的计算值&lt;/p&gt;

&lt;p&gt;内联元素的高度由固定高度和不固定高度组成，这个不固定的部分就是这里的“行距”。换句话说，line-height之所以起作用，就是通过改变“行距”来实现的。&lt;/p&gt;

&lt;p&gt;行距 = 行高−(em-box)。转换成CSS语言就是：行距 = line-height - font-size。em是一个相对font-size大小的CSS单位，因此1em等用于当前一个font-size大小&lt;/p&gt;

&lt;p&gt;强调：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line-height&lt;/code&gt;不能影响内联替换元素，有时候看到的表象的影响其实是影响了行框盒子里的“幽灵空白节点”&lt;/li&gt;
  &lt;li&gt;替换元素和内联非替换元素在一起时，由于同属内联元素，因此，会共同形成一个“行框盒子”，line-height在这个混合元素的“行框盒子”中扮演的角色是决定这个行盒的最小高度。例子：明明文字设置了line-height为20px，但是，如果文字后面有小图标，最后“行框盒子”高度却是21px或是22px。这种现象背后最大的黑手其实是vertical-align属性&lt;/li&gt;
  &lt;li&gt;对于块级元素，line-height对其本身是没有任何作用的，我们平时改变line-height，块级元素的高度跟着变化实际上是通过改变块级元素里面内联级别元素占据的高度实现的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:inline&lt;/code&gt;的内联元素的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line-height&lt;/code&gt;对最终内容盒子的高度没有显式影响，因为行高影响的是行框，并不是内联元素的高度，高度由font-size和font-family影响。而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:inline-block&lt;/code&gt;有，因为其内部盒子是块状盒子，块状盒子的内容高度由line-height、padding、border等共同决定&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;使用&quot;&gt;使用&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;单行文字垂直居中：只需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line-height&lt;/code&gt;属性就可以，无需同时设置height并与line-height相等&lt;/li&gt;
  &lt;li&gt;多行文字
    &lt;ul&gt;
      &lt;li&gt;多行文字使用一个标签包裹，然后设置display为inline-block。好处在于既能重置外部的line-height为正常的大小，又能保持内联元素特性，从而可以设置vertical-align属性，以及产生一个非常关键的“行框盒子，“每个“行框盒子”都会附带的一个产物——“幽灵空白节点”。“有了这个“幽灵空白节点”，我们的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line- height&lt;/code&gt;就有了作用的对象&lt;/li&gt;
      &lt;li&gt;因为内联元素默认都是基线对齐的，所以我们通过对元素设置vertical- align:middle来调整多行文本的垂直位置，从而实现我们想要的“垂直居中”效果。如果是要借助line-height实现图片垂直居中效果，也是类似的原理和做法。 
  eg:&lt;/li&gt;
    &lt;/ul&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  .box {
  　 line-height: 120px;
  　 background-color: #f0f3f9;
  }
  .content {
  　 display: inline-block;
  　 line-height: 20px;
  　 margin: 0 20px;
  　 vertical-align: middle;
  }
  &amp;lt;div class=&quot;box&quot;&amp;gt;
  　 &amp;lt;div class=&quot;content&quot;&amp;gt;基于行高实现的...&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;vertical-align&quot;&gt;vertical-align&lt;/h2&gt;

&lt;p&gt;vertical-align属性的默认值baseline在文本之类的内联元素那里就是字符x的下边缘，对于替换元素则是替换元素的下边缘。但是，如果是inline-block元素，则规则要复杂了：一个inline-block元素，如果里面没有内联元素，或者overflow不是visible，则该元素的基线就是其margin底边缘；否则其基线就是元素里面最后一行内联元素的基线。&lt;/p&gt;

&lt;p&gt;单行文本最终高度并不是由line-height决定。还受font-size和vertical-align影响&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.box { line-height: 32px; }
.box &amp;gt; span { font-size: 24px; }
&amp;lt;div class=&quot;box&quot;&amp;gt;
　 &amp;lt;span&amp;gt;文字&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/5/3-1.php&quot;&gt;http://demo.cssworld.cn/5/3-1.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.box&lt;/code&gt;最终高度并不是32px.&lt;/p&gt;

&lt;h3 id=&quot;值&quot;&gt;值&lt;/h3&gt;

&lt;p&gt;top/bottom和baseline/middle却是完全不同的两个帮派，前者对齐看边缘看行框盒子，而后者是和字符x打交道&lt;/p&gt;

&lt;h3 id=&quot;数字值&quot;&gt;数字值&lt;/h3&gt;

&lt;p&gt;数字值根据计算值的不同，相对于基线往上或往下偏移，到底是往上还是往下取决于vertical- align的计算值是正值还是负值，如果是负值，往下偏移，如果是正值，往上偏移。&lt;/p&gt;

&lt;p&gt;由于vertical-align的默认值是baseline，即基线对齐，而基线的定义是字母x的下边缘。因此，内联元素默认都是沿着字母x的下边缘对齐的。对于图片等替换元素，往往使用元素本身的下边缘作为基线&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/5/3-2.php&quot;&gt;http://demo.cssworld.cn/5/3-2.php&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;百分比值&quot;&gt;百分比值&lt;/h3&gt;

&lt;p&gt;凡是百分比值，均是需要一个相对计算的值，例如，margin和padding是相对于宽度计算的，line-height是相对于font-size计算的，而这里的vertical- align属性的百分比值则是相对于line-height的计算值计算的。&lt;/p&gt;

&lt;h3 id=&quot;文本值&quot;&gt;文本值&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;vertical-align:text-top：盒子的顶部和父级内容区域的顶部对齐。&lt;/li&gt;
  &lt;li&gt;vertical-align:text-bottom：盒子的底部和父级内容区域的底部对齐。
其中，理解的难点在于“父级内容区域”，这是个什么东西呢？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其可以看成是Firefox/IE浏览器文本选中的背景区域，或者默认状态下的内联文本的背景色区域。而所谓“父级内容区域”指的就是在父级元素当前font-size和font-family下应有的内容区域大小。&lt;/p&gt;

&lt;p&gt;因此，这个定义又可以理解为（以text-top举例）：假设元素后面有一个和父元素font- size、font-family一模一样的文字内容，则vertical-align:text-top表示元素和这个文字的内容区域的上边缘对齐。&lt;/p&gt;

&lt;h3 id=&quot;作用域&quot;&gt;作用域&lt;/h3&gt;

&lt;p&gt;vertical-align起作用是有前提条件的，这个前提条件就是：只能应用于内联元素以及display值为table-cell的元素。&lt;/p&gt;

&lt;p&gt;浮动和绝对定位会让元素块状化，此时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align&lt;/code&gt;无效&lt;/p&gt;

&lt;h3 id=&quot;复杂的案例&quot;&gt;复杂的案例&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/5/3-6.php&quot;&gt;http://demo.cssworld.cn/5/3-6.php&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;vertical-align-middle的近似垂直居中&quot;&gt;vertical-align: middle的近似垂直居中&lt;/h3&gt;

&lt;p&gt;vertial-align:middle可以让内联元素的真正意义上的垂直中心位置和字符x的交叉点对齐。&lt;/p&gt;

&lt;p&gt;基本上所有的字体中，字符x的位置都是偏下一点儿的，font-size越大偏移越明显，这才导致默认状态下的vertial-align:middle实现的都是“近似垂直居中”。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/5/3-8.php&quot;&gt;http://demo.cssworld.cn/5/3-8.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;line-height的高度大于内容高度，空白行距需要均匀分布在em-box的上下方，但是em-box和文字内容区域并不重合，无法感知(高度是m的高度)&lt;/p&gt;

&lt;p&gt;如果想要实现真正意义上的垂直居中对齐，只要想办法让字符x的中心位置就是容器的垂直中心位置即可，通常的做法是设置font-size:0，整个字符x缩小成了一个看不见的点，根据line-height的半行间距上下等分规则，这个点就正好是整个容器的垂直中心位置，这样就可以实现真正意义上的垂直居中对齐了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;高度的决定因素&quot;&gt;高度的决定因素&lt;/h1&gt;

&lt;p&gt;高度对于我来说也是很魔幻的，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line-height&lt;/code&gt;能决定块状元素的高度，却完全不能左右内联元素的高度。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-size&lt;/code&gt;也是只对内联元素高度有影响，却不是唯一的决定因素。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://codepen.io/anon/pen/KoeNZr&quot;&gt;https://codepen.io/anon/pen/KoeNZr&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;块状元素：line-height、height、padding、border共同作用&lt;/li&gt;
  &lt;li&gt;内联元素：内联盒子共同作用后的行框盒子结果，每个内联盒子，如果是纯文本，又是font-size和font-family作用的结果&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;看到一篇文章从字体设计来分析字体、字号和行高的关系的：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/27381252&quot;&gt;https://zhuanlan.zhihu.com/p/27381252&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;流&quot;&gt;流&lt;/h1&gt;

&lt;h2 id=&quot;float元素特性&quot;&gt;float元素特性&lt;/h2&gt;

&lt;p&gt;float元素破坏了正常流&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;包裹性：浮动元素内部宽度小于父元素时，最终宽度表现为内部宽度。如果内部内容能铺满父元素时，表现宽度为父元素宽度。改变默认的宽度显示类型就可以，添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;white-space:nowrap&lt;/code&gt;，可以让宽度表现从“包裹性”变成“最大可用宽度&lt;/li&gt;
  &lt;li&gt;块状化并格式化上下文；&lt;/li&gt;
  &lt;li&gt;破坏文档流；&lt;/li&gt;
  &lt;li&gt;没有任何margin合并；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;行框盒子和浮动元素的不可重叠性”，也就是“行框盒子如果和浮动元素的垂直高度有重叠，则行框盒子在正常定位状态下只会跟随浮动元素，而不会发生重叠”。float元素的“浮动参考”是“行框盒子”&lt;/p&gt;

&lt;h2 id=&quot;positionabsolute&quot;&gt;position:absolute&lt;/h2&gt;

&lt;p&gt;元素一旦position属性值为absolute或fixed，其display计算值就是block或者table&lt;/p&gt;

&lt;h2 id=&quot;clear&quot;&gt;clear&lt;/h2&gt;

&lt;p&gt;clear属性：元素盒子的边不能和前面的浮动元素相邻。&lt;/p&gt;

&lt;p&gt;clear属性只有块级元素才有效的，而::after等伪元素默认都是内联水平，这就是借助伪元素清除浮动影响时需要设置display属性值的原因。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.clear:after {
　 content: &apos;&apos;;
　 display: table;　 // 也可以是&apos;block&apos;，或者是&apos;list-item&apos;
　 clear: both;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;（1）如果clear:both元素前面的元素就是float元素，则margin-top负值即使设成-9999px，也不见任何效果。
（2）clear:both后面的元素依旧可能会发生文字环绕的现象&lt;/p&gt;

&lt;h2 id=&quot;块级格式化上下文bfc&quot;&gt;块级格式化上下文BFC&lt;/h2&gt;

&lt;p&gt;如果一个元素具有BFC，内部子元素不会影响外部的元素，也不会受外部元素影响。所以，BFC元素是不可能发生margin重叠的，因为margin重叠是会影响外面的元素的；BFC元素也可以用来清除浮动的影响，因为如果不清除，子元素浮动则父元素高度塌陷，必然会影响后面元素布局和定位，这显然有违BFC元素的子元素不会影响外部元素的设定。&lt;/p&gt;

&lt;p&gt;那什么时候会触发BFC呢？常见的情况如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;根元素；&lt;/li&gt;
  &lt;li&gt;float的值不为none；现在一些style lint规则的提示相当智能，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:inline-block; float: right&lt;/code&gt;会被提示&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float&lt;/code&gt;不为none时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dlisplay&lt;/code&gt;表现为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;overflow的值为auto、scroll或hidden；&lt;/li&gt;
  &lt;li&gt;display的值为table-cell、table-caption和inline-block中的任何一个；&lt;/li&gt;
  &lt;li&gt;position的值不为relative和static。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;包含块&quot;&gt;包含块&lt;/h2&gt;

&lt;p&gt;（1）根元素（很多场景下可以看成是&amp;lt;html&amp;gt;）被称为“初始包含块”，其尺寸等同于浏览器可视窗口的大小。&lt;/p&gt;

&lt;p&gt;（2）对于其他元素，如果该元素的position是relative或者static，则“包含块”由其最近的块容器祖先盒的content box边界形成。&lt;/p&gt;

&lt;p&gt;（3）如果元素position:fixed，则“包含块”是“初始包含块”。&lt;/p&gt;

&lt;p&gt;（4）如果元素position:absolute，则“包含块”由最近的position不为static的祖先元素建立，具体方式如下。&lt;/p&gt;

&lt;p&gt;如果该祖先元素是纯inline元素，则规则略复杂：&lt;/p&gt;

&lt;p&gt;假设给内联元素的前后各生成一个宽度为0的内联盒子（inline box），则这两个内联盒子的padding box外面的包围盒就是内联元素的“包含块”；
如果该内联元素被跨行分割了，那么“包含块”是未定义的，也就是CSS2.1规范并没有明确定义，浏览器自行发挥。
否则，“包含块”由该祖先的padding box边界形成。&lt;/p&gt;

&lt;p&gt;对于绝对定位元素。height:100%是第一个具有定位属性值的祖先元素的高度，而height:inherit则是单纯的父元素的高度继承&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;css世界的层叠规则&quot;&gt;css世界的层叠规则&lt;/h1&gt;

&lt;p&gt;我经历过很多设置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;z-index:99999&lt;/code&gt;元素还是被挡住的悲剧，曾不能理解为啥&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;z-index&lt;/code&gt;更大值的元素被更小值元素覆盖的现象。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://codepen.io/echizen/pen/OvdqWP?editors=1100&quot;&gt;https://codepen.io/echizen/pen/OvdqWP?editors=1100&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;z-index属性只有和定位元素（position不为static的元素）在一起的时候才有作用，flex盒子的子元素也可以设置z-index属性&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;层叠上下文，英文称作stacking context，是HTML中的一个三维的概念。如果一个元素含有层叠上下文，我们可以理解为这个元素在z轴上就“高人一等”。可以把层叠上下文理解为一种“层叠结界”，自成一个小世界。这个小世界中可能有其他的“层叠结界”，而自身也可能处于其他“层叠结界”中。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;层叠水平，英文称作stacking level，决定了同一个层叠上下文中元素在z轴上的显示顺序&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所有的元素都有层叠水平，包括层叠上下文元素，也包括普通元素。然而，对普通元素的层叠水平探讨只局限在当前层叠上下文元素中。为什么呢？因为不如此就没有意义。层叠上下文本身就是一个强力的“层叠结界”，而普通元素的层叠水平是无法突破这个结界和结界外的元素去较量层叠水平的。&lt;/p&gt;

&lt;h2 id=&quot;层叠顺序&quot;&gt;层叠顺序&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/mlcdn/c45406/180401_7j8941ijfkk3agib33i245232h10h_936x746.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;（1）位于最下面的background/border特指层叠上下文元素的边框和背景色。每一个层叠顺序规则仅适用于当前层叠上下文元素的小世界。&lt;/p&gt;

&lt;p&gt;（2）inline水平盒子指的是包括inline/inline-block/inline-table元素的“层叠顺序”，它们都是同等级别的。&lt;/p&gt;

&lt;p&gt;（3）单纯从层叠水平上看，实际上z-index:0和z-index:auto是可以看成是一样的。注意这里的措辞——“单纯从层叠水平上看”，实际上，两者在层叠上下文领域有着根本性的差异。&lt;/p&gt;

&lt;h2 id=&quot;层叠规则&quot;&gt;层叠规则&lt;/h2&gt;

&lt;p&gt;（1）谁大谁上：当具有明显的层叠水平标识的时候，如生效的z-index属性值，在同一个层叠上下文领域，层叠水平值大的那一个覆盖小的那一个。&lt;/p&gt;

&lt;p&gt;（2）后来居上：当元素的层叠水平一致、层叠顺序相同的时候，在DOM流中处于后面的元素会覆盖前面的元素。&lt;/p&gt;

&lt;h2 id=&quot;层叠特性&quot;&gt;层叠特性&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;层叠上下文的层叠水平要比普通元素高。&lt;/li&gt;
  &lt;li&gt;层叠上下文可以阻断元素的混合模式（参见http://www.zhangxinxu.com/wordpress/?p=5155的这篇文章的第二部分说明），隔离mix-blend-mode元素的混合。&lt;/li&gt;
  &lt;li&gt;层叠上下文可以嵌套，内部层叠上下文及其所有子元素均受制于外部的“层叠上下文”。&lt;/li&gt;
  &lt;li&gt;每个层叠上下文和兄弟元素独立，也就是说，当进行层叠变化或渲染的时候，只需要考虑后代元素。&lt;/li&gt;
  &lt;li&gt;每个层叠上下文是自成体系的，当元素发生层叠的时候，整个元素被认为是在父层叠上下文的层叠顺序中。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;层叠上下文的创建&quot;&gt;层叠上下文的创建&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;页面根元素天生具有层叠上下文，称为根层叠上下文&lt;/li&gt;
  &lt;li&gt;z-index值为数值的定位元素。对于position值为relative/absolute/fixed声明的定位元素，当其z-index值不是auto的时候，会创建层叠上下文。部分浏览器如chrome fixed时无需声明z-index就是层叠上下文&lt;/li&gt;
  &lt;li&gt;css3属性
    &lt;ul&gt;
      &lt;li&gt;
        &lt;table&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;元素为flex布局元素（父元素display:flex&lt;/td&gt;
              &lt;td&gt;inline-flex），同时z-index值不是auto。&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/li&gt;
      &lt;li&gt;元素的opacity值不是1。&lt;/li&gt;
      &lt;li&gt;元素的transform值不是none。&lt;/li&gt;
      &lt;li&gt;元素mix-blend-mode值不是normal。&lt;/li&gt;
      &lt;li&gt;元素的filter值不是none。&lt;/li&gt;
      &lt;li&gt;元素的isolation值是isolate。&lt;/li&gt;
      &lt;li&gt;元素的will-change属性值为上面2～6的任意一个（如will-change:opacity、will-change:transform等）。&lt;/li&gt;
      &lt;li&gt;元素的-webkit-overflow-scrolling设为touch。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div style=&quot;position:relative; z-index:auto;&quot;&amp;gt;
　&amp;lt;!-- 美女 --&amp;gt;
　&amp;lt;img src=&quot;1.jpg&quot; style=&quot;position:absolute; z-index:2;&quot;&amp;gt;　
&amp;lt;/div&amp;gt;
&amp;lt;div style=&quot;position:relative; z-index:auto;&quot;&amp;gt;
　&amp;lt;!-- 美景 --&amp;gt;
　&amp;lt;img src=&quot;2.jpg&quot; style=&quot;position:relative; z-index:1;&quot;&amp;gt;　
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果是美女在美景上&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div style=&quot;position:relative; z-index:0;&quot;&amp;gt;
　&amp;lt;!-- 美女 --&amp;gt;
　&amp;lt;img src=&quot;1.jpg&quot; style=&quot;position:absolute; z-index:2;&quot;&amp;gt;　
&amp;lt;/div&amp;gt;
&amp;lt;div style=&quot;position:relative; z-index:0;&quot;&amp;gt;
　&amp;lt;!-- 美景 --&amp;gt;
　&amp;lt;img src=&quot;2.jpg&quot; style=&quot;position:relative; z-index:1;&quot;&amp;gt;　
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果是美景在美女上。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/7/5-1.php&quot;&gt;http://demo.cssworld.cn/7/5-1.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们有时候会发现z-index嵌套错乱，这时要看看是不是受父级的层叠上下文元素干扰了，可能就豁然开朗了&lt;/p&gt;

&lt;h2 id=&quot;层叠上下文与层叠顺序&quot;&gt;层叠上下文与层叠顺序&lt;/h2&gt;

&lt;p&gt;一旦普通元素具有了层叠上下文，其层叠顺序就会变高。那它的层叠顺序究竟在哪个位置、哪个级别呢？&lt;/p&gt;

&lt;p&gt;这里需要分两种情况讨论：&lt;/p&gt;

&lt;p&gt;（1）如果层叠上下文元素不依赖z-index数值，则其层叠顺序是z-index:auto，可看成z:index:0级别
（2）如果层叠上下文元素依赖z-index数值，则其层叠顺序由z-index值决定。&lt;/p&gt;

&lt;p&gt;定位元素会层叠在普通元素的上面，其根本原因就是：元素一旦成为定位元素，其z-index就会自动生效，此时其z-index就是默认的auto，也就是0级别，根据上面的层叠顺序表，就会覆盖inline或block或float元素。而不支持z-index的层叠上下文元素天然是z-index:auto级别，也就意味着，层叠上下文元素和定位元素是一个层叠顺序的，于是当它们发生层叠的时候，遵循的是“后来居上”准则。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;其他的一些笔记记录…&lt;/p&gt;

&lt;h1 id=&quot;其他易被忽略的知识点&quot;&gt;其他易被忽略的知识点&lt;/h1&gt;

&lt;h2 id=&quot;块级元素与内联元素&quot;&gt;块级元素与内联元素&lt;/h2&gt;

&lt;p&gt;块级盒子就负责结构，内联盒子就负责内容&lt;/p&gt;

&lt;h3 id=&quot;块级盒子和内联盒子&quot;&gt;块级盒子和内联盒子&lt;/h3&gt;

&lt;p&gt;“块级元素的基本特征，也就是一个水平流上只能单独显示一个元素，多个块级元素则换行显示。”&lt;/p&gt;

&lt;p&gt;正是由于“块级元素”具有换行特性，因此理论上它都可以配合clear属性来清除浮动带来的影响。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.clear:after {
　 content: &apos;&apos;;
   display: table;　// 也可以是block，或者是list-item
　 clear: both;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/3/1-1.php&quot;&gt;http://demo.cssworld.cn/3/1-1.php&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;标记盒子&quot;&gt;标记盒子&lt;/h3&gt;

&lt;p&gt;li元素默认会有个圆点在首部&lt;/p&gt;

&lt;p&gt;“list-item元素会出现项目符号是因为生成了一个附加的盒子，学名“标记盒子”（marker box），专门用来放圆点、数字这些项目符号”&lt;/p&gt;

&lt;h3 id=&quot;外部盒子和容器盒子-1&quot;&gt;外部盒子和容器盒子&lt;/h3&gt;

&lt;p&gt;“每个元素都两个盒子，外在盒子和容器盒子。外在盒子负责元素是可以一行显示，还是只能换行显示；容器盒子负责宽高、内容呈现什么的”&lt;/p&gt;

&lt;p&gt;按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display&lt;/code&gt;的属性值不同，值为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block&lt;/code&gt;的元素的盒子实际由外在的“块级盒子”和内在的“块级容器盒子”组成，值为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline-block&lt;/code&gt;的元素则由外在的“内联盒子”和内在的“块级容器盒子”组成，值为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline&lt;/code&gt;的元素则内外均是“内联盒子”。&lt;/p&gt;

&lt;p&gt;“遵循这种理解，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:block&lt;/code&gt;应该脑补成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:block-block&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:table&lt;/code&gt;应该脑补成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:block-table&lt;/code&gt;”&lt;/p&gt;

&lt;h3 id=&quot;元素尺寸&quot;&gt;元素尺寸&lt;/h3&gt;

&lt;p&gt;width/height作用在容器盒子上。content box环绕着width和height给定的矩形。&lt;/p&gt;

&lt;h4 id=&quot;深藏不露的widthauto&quot;&gt;深藏不露的width:auto&lt;/h4&gt;

&lt;p&gt;（1）充分利用可用空间。比方说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt;这些元素的宽度默认是100%于父级容器的。fill-available。&lt;/p&gt;

&lt;p&gt;（2）收缩与包裹。典型代表就是浮动、绝对定位、inline-block元素或table元素，英文称为shrink-to-fit，直译为“收缩到合适”，“包裹性”。CSS3中的fit-content指的就是这种宽度表现。&lt;/p&gt;

&lt;p&gt;（3）收缩到最小。这个最容易出现在table-layout为auto的表格中。当每一列空间都不够的时候，文字能断就断，这种行为在规范中被描述为“preferred minimum width”或者“minimum content width。后来还有了一个更加好听的名字min-content。&lt;a href=&quot;http://demo.cssworld.cn/3/2-1.php&quot;&gt;http://demo.cssworld.cn/3/2-1.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;（4）超出容器限制。除非有明确的width相关设置，否则上面3种情况尺寸都不会主动超过父级容器宽度的，但是存在一些特殊情况。例如，内容很长的连续的英文和数字，或者内联元素被设置了white-space:nowrap。max-content&lt;/p&gt;

&lt;h3 id=&quot;外部元素流体特性&quot;&gt;外部元素流体特性&lt;/h3&gt;

&lt;h4 id=&quot;正常流宽度&quot;&gt;正常流宽度&lt;/h4&gt;

&lt;p&gt;block容器的流特性，其尺寸表现就会铺满容器。&lt;/p&gt;

&lt;p&gt;冗余的写法：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;a {
　　display: block;
　　width: 100%;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;保持流动性：“无宽度，无图片，无浮动”&lt;/p&gt;

&lt;p&gt;流动性，并不是看上去的宽度100%显示这么简单，而是一种margin/border/padding和content内容区域自动分配水平空间的机制。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://demo.cssworld.cn/3/2-3.php&quot;&gt;http://demo.cssworld.cn/3/2-3.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;不要给外层容器元素随意设置宽度，然后再通过各种计算设置内部元素宽度，导致难以维护，一个变更则要变动一片&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;格式化宽度&quot;&gt;格式化宽度&lt;/h4&gt;

&lt;p&gt;格式化宽度。格式化宽度仅出现在“绝对定位模型”中，也就是出现在position属性值为absolute或fixed的元素中。在默认情况下，绝对定位元素的宽度表现是“包裹性”，宽度由内部尺寸决定，但是，有一种情况其宽度是由外部尺寸决定的，对于非替换元素，当left/top或top/bottom对立方位的属性值同时存在的时候，元素的宽度表现为“格式化宽度”，其宽度大小相对于最近的具有定位特性（position属性值不是static）的祖先元素计算&lt;/p&gt;

&lt;h3 id=&quot;内部元素与流体特性&quot;&gt;内部元素与流体特性&lt;/h3&gt;

&lt;p&gt;判断一个元素使用的是否为“内部尺寸”呢？很简单，假如这个元素里面没有内容，宽度就是0，那就是应用的“内部尺寸”。&lt;/p&gt;

&lt;p&gt;内部尺寸”有下面3种表现形式。&lt;/p&gt;

&lt;p&gt;（1）包裹性。“shrink-to-fit”。除了“包裹”，还有“自适应性”。“自适应性”是区分后面两种尺寸表现很重要的一点。所谓“自适应性”，指的是元素尺寸由内部元素决定，但永远小于“包含块”容器的尺寸（除非容器尺寸小于元素的“首选最小宽度”）。换句话说就是，“包裹性”元素冥冥中有个max-width:100%罩着的感觉。因此，对于一个元素，如果其display属性值是inline-block，那么即使其里面内容再多，只要是正常文本，宽度也不会超过容器。于是，图文混排的时候，我们只要关心内容，除非“首选最小宽度”比容器宽度还要大，否则我们完全不需要担心某个元素内容太多而破坏了布局。
除了inline-block元素，浮动元素以及绝对定位元素都具有包裹性，均有类似的智能宽度行为。
&lt;a href=&quot;http://demo.cssworld.cn/3/2-4.php&quot;&gt;http://demo.cssworld.cn/3/2-4.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;（2）首选最小宽度。所谓“首选最小宽度”，指的是元素最适合的最小宽度。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;东亚文字（如中文）最小宽度为每个汉字的宽度。&lt;/li&gt;
  &lt;li&gt;西方文字最小宽度由特定的连续的英文字符单元决定。并不是所有的英文字符都会组成连续单元，一般会终止于空格（普通空格）、短横线、问号以及其他非英文字符等&lt;/li&gt;
  &lt;li&gt;类似图片这样的替换元素的最小宽度就是该元素内容本身的宽度&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;（3）最大宽度。&lt;/p&gt;

&lt;p&gt;最大宽度就是元素可以有的最大宽度。我自己是这么理解的，“最大宽度”实际等同于“包裹性”元素设置white-space:nowrap声明后的宽度。如果内部没有块级元素或者块级元素没有设定宽度值，则“最大宽度”实际上是最大的连续内联盒子的宽度&lt;/p&gt;

&lt;h3 id=&quot;高度height&quot;&gt;高度height&lt;/h3&gt;

&lt;p&gt;height:auto的表现也基本上就是这个套路：有几个元素盒子，每个多高，然后一加，就是最终的高度值了。height:auto也有外部尺寸特性。其仅存在于绝对定位模型中，也就是“格式化高度”&lt;/p&gt;

&lt;p&gt;height和width还有一个比较明显的区别就是对百分比单位的支持。对于width属性，就算父元素width为auto，其百分比值也是支持的；但是，对于height属性，如果父元素height为auto，只要子元素在文档流中，其百分比值完全就被忽略了。百分比高度值要想起作用，其父级必须有一个可以生效的高度值&lt;/p&gt;

&lt;p&gt;如果包含块的高度没有显式指定（即高度由内容决定），并且该元素不是绝对定位，则计算值为auto&lt;/p&gt;

&lt;h3 id=&quot;替换元素&quot;&gt;替换元素&lt;/h3&gt;

&lt;p&gt;通过修改某个属性值呈现的内容就可以被替换的元素就称为“替换元素”。因此，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;object&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;或者表单元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;input&amp;gt;&lt;/code&gt;都是典型的替换元素。&lt;/p&gt;

&lt;p&gt;（1）内容的外观不受页面上的CSS的影响。用专业的话讲就是在样式表现在CSS作用域之外。如何更改替换元素本身的外观？需要类似&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appearance&lt;/code&gt;属性，或者浏览器自身暴露的一些样式接口，例如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::-ms-check{}&lt;/code&gt;可以更改高版本IE浏览器下单复选框的内间距、背景色等样式，但是直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input[type=&apos;checkbox&apos;]{}&lt;/code&gt;却无法更改内间距、背景色等样式。&lt;/p&gt;

&lt;p&gt;（2）有自己的尺寸。在Web中，很多替换元素在没有明确尺寸设定的情况下，其默认的尺寸（不包括边框）是300像素×150像素，如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;等，也有少部分替换元素为0像素，如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;图片，而表单元素的替换元素的尺寸则和浏览器有关，没有明显的规律。&lt;/p&gt;

&lt;p&gt;（3）在很多CSS属性上有自己的一套表现规则。比较具有代表性的就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align&lt;/code&gt;属性，对于替换元素和非替换元素，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align&lt;/code&gt;属性值的解释是不一样的。比方说&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align&lt;/code&gt;的默认值的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseline&lt;/code&gt;，被定义为字符x的下边缘，但是到了替换元素那里就不适用了。因为替换元素的内容往往不可能含有字符x，于是替换元素的基线就被硬生生定义成了元素的下边缘。&lt;/p&gt;

&lt;p&gt;“替换元素和非替换元素之间只隔了一个src属性”&lt;/p&gt;

&lt;p&gt;“替换元素和非替换元素之间只隔了一个CSS content属性！”&lt;/p&gt;

&lt;p&gt;“content属性生成的内容和普通元素内容才会有很多不同的特性表现”&lt;/p&gt;

&lt;p&gt;（1）我们使用content生成的文本是无法选中、无法复制的，好像设置了user- select:none声明一般，但是普通元素的文本却可以被轻松选中。同时，content生成的文本无法被屏幕阅读设备读取，也无法被搜索引擎抓取&lt;/p&gt;

&lt;p&gt;（2）不能左右:empty伪类。:empty是一个CSS选择器，当元素里面无内容的时候进行匹配&lt;/p&gt;

&lt;p&gt;（3）content动态生成值无法通过js获取，如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;counter()&lt;/code&gt;的值&lt;/p&gt;

&lt;h3 id=&quot;content的使用&quot;&gt;content的使用&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;辅助元素生成
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.element:before {
　  content: &apos;&apos;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;清除浮动
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.clear:after {
　 content: &apos;&apos;;
　 display: table;　/* 也可以是&apos;block&apos; */
　 clear: both;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;图形生成&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;字体图标&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;图片生成&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;marginauto&quot;&gt;margin:auto&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;有时候元素就算没有设置width或height，也会自动填充&lt;/p&gt;

    &lt;div&gt;&lt;/div&gt;

    &lt;p&gt;此&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt;宽度就会自动填满容器。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;有时候元素就算没有设置width或height，也会自动填充对应的方位。例如：&lt;/p&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  div {
  　  position: absolute;
  　  left: 0; right: 0;
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;此时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt;宽度就会自动填满包含块容器。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;margin:auto的填充规则如下。&lt;/p&gt;

&lt;p&gt;（1）如果一侧定值，一侧auto，则auto为剩余空间大小。&lt;/p&gt;

&lt;p&gt;（2）如果两侧均是auto，则平分剩余空间。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;触发margin:auto计算有一个前提条件，就是width或height为auto时，元素是具有对应方向的自动填充特性的&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;margin-无效&quot;&gt;margin 无效&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;display计算值inline的非替换元素的垂直margin是无效的&lt;/li&gt;
  &lt;li&gt;表格中的&amp;lt;tr&amp;gt;和&amp;lt;td&amp;gt;元素或者设置display计算值是table-cell或table-row的元素的margin都是无效的&lt;/li&gt;
  &lt;li&gt;margin合并的时候，更改margin值可能是没有效果的&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;绝对定位元素非定位方位的margin值“无效”
  很多时候，我们对元素进行绝对定位的时候，只会设置1～2个相邻方位。例如：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  img { top: 10%; left: 30%;}   此时right和bottom值属于auto状态，也就是右侧和底部没有进行定位，此时，这两个方向设置margin值我们在页面上是看不到定位变化的
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;定高容器的子元素的margin-bottom或者宽度定死的子元素的margin-right的定位“失效”&lt;/li&gt;
  &lt;li&gt;鞭长莫及导致的margin无效
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;div class=&quot;box&quot;&amp;gt;
　     &amp;lt;img src=&quot;mm1.jpg&quot;&amp;gt;
　     &amp;lt;p&amp;gt;内容&amp;lt;/p&amp;gt;
　  &amp;lt;/div&amp;gt;

  .box &amp;gt; img {
  　  float: left;
  　  width: 256px;
  }
  .box &amp;gt; p {
  　  overflow: hidden;
  　  margin-left: 200px;
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;此时的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;margin-left&lt;/code&gt;从负无穷到256px都是没有任何效果的&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;内联特性导致的margin无效
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;div class=&quot;box&quot;&amp;gt;
  　 &amp;lt;img src=&quot;mm1.jpg&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
  .box &amp;gt; img {
  　  height: 96px;
  　  margin-top: -200px;
  } 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;absolute与overflow&quot;&gt;absolute与overflow&lt;/h3&gt;

&lt;p&gt;绝对定位元素不总是被父级overflow属性剪裁，尤其当overflow在绝对定位元素及其包含块之间的时候。即&lt;strong&gt;如果overflow不是定位元素，同时绝对定位元素和overflow容器之间也没有定位元素，则overflow无法对absolute元素进行剪裁&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div style=&quot;overflow: hidden;&quot;&amp;gt;
　 &amp;lt;img src=&quot;1.jpg&quot; style=&quot;position: absolute;&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;overflow元素父级是定位元素也不会剪裁，例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div style=&quot;position: relative;&quot;&amp;gt;
　&amp;lt;div style=&quot;overflow: hidden;&quot;&amp;gt;
　　 &amp;lt;img src=&quot;1.jpg&quot; style=&quot;position: absolute;&quot;&amp;gt;
　&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是，如果overflow属性所在的元素同时也是定位元素，里面的绝对定位元素会被剪裁：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div style=&quot;overflow: hidden; position: relative;&quot;&amp;gt;
　 &amp;lt;img src=&quot;1.jpg&quot; style=&quot;position: absolute;&quot;&amp;gt;　 &amp;lt;!-- 剪裁 --&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果overflow元素和绝对定位元素之间有定位元素，也会被剪裁：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div style=&quot;overflow: hidden;&quot;&amp;gt;
　 &amp;lt;div style=&quot;position: relative;&quot;&amp;gt;
　　 &amp;lt;img src=&quot;1.jpg&quot; style=&quot;position: absolute;&quot;&amp;gt;　 &amp;lt;!-- 剪裁 --&amp;gt;
　 &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果overflow的属性值不是hidden而是auto或者scroll，即使绝对定位元素高宽比overflow元素高宽还要大，也都不会出现滚动条。示例：&lt;a href=&quot;http://demo.cssworld.cn/6/5-11.php&quot;&gt;http://demo.cssworld.cn/6/5-11.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;由于position:fixed固定定位元素的包含块是根元素，因此，除非是窗体滚动，否则上面讨论的所有overflow剪裁规则对固定定位都不适用&lt;/p&gt;

&lt;p&gt;ps: transform属性对overflow剪裁规则有影响，overflow元素自身transform的时候，Chrome和Opera浏览器下的overflow剪裁是无效的。 当大家遇到absolute元素被剪裁或者fixed固定定位失效时，可以看看是不是transform属性在作祟&lt;/p&gt;

&lt;h3 id=&quot;absolute的流体特性&quot;&gt;absolute的流体特性&lt;/h3&gt;

&lt;p&gt;绝对定位元素也具有类似块级元素的流体特性，条件是“对立方向同时发生定位的时候”。&lt;/p&gt;

&lt;p&gt;如果只有left属性或者只有right属性，则由于包裹性，此时.box宽度是0。但是在本例中，因为left和right同时存在，所以宽度就不是0，而是表现为“格式化宽度”，宽度大小自适应于.box包含块的padding box，也就是说，如果包含块padding box宽度发生变化，.box的宽度也会跟着一起变&lt;/p&gt;

&lt;p&gt;绝对定位元素的这种流体特性比普通元素要更强大，普通元素流体特性只有一个方向，默认是水平方向，但是绝对定位元素可以让垂直方向和水平方向同时保持流动性。&lt;/p&gt;

&lt;h2 id=&quot;position-relative&quot;&gt;position: relative&lt;/h2&gt;

&lt;p&gt;相对定位元素的left/top/right/bottom的百分比值是相对于包含块计算的，而不是自身。注意，虽然定位位移是相对自身，但是百分比值的计算值不是。&lt;/p&gt;

&lt;p&gt;top和bottom这两个垂直方向的百分比值计算跟height的百分比值是一样的，都是相对高度计算&lt;/p&gt;

&lt;p&gt;当相对定位元素同时应用对立方向定位值的时候，也就是top/bottom和left/right同时使用的时候，其表现和绝对定位差异很大。绝对定位是尺寸拉伸，保持流体特性，但是相对定位却是“你死我活”的表现。&lt;strong&gt;top/bottom同时使用的时候，bottom被干掉；left/right同时使用的时候，right毙命。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;本文大量内容来源于张鑫旭的《css 世界》一书，是一篇读书笔记。非原创内容。&lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>node调用jest</title>
   <link href="http://echizen.github.io/tech/2018/02-16-jest-in-node"/>
   <updated>2018-02-16T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/jest-in-node</id>
   <content type="html">
&lt;p&gt;因为jest官网没有介绍node里调用jest的文档，只给出了cli和npm命令通过配置文件调用的方式，故将通过阅读源码获取的node调用jest的方式记录下来。&lt;/p&gt;

&lt;h1 id=&quot;入口&quot;&gt;入口&lt;/h1&gt;

&lt;p&gt;jest执行测试需要调用内部接口&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runCLI&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import { runCLI } from &apos;jest&apos;
runCLI(argv, projects)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;argv(object): 配置参数&lt;/li&gt;
  &lt;li&gt;projects(array): 被测试文件所在文件夹路径，一般与argv.projects项一致&lt;/li&gt;
  &lt;li&gt;return(promise): 返回promise表示测试进行状态&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;配置&quot;&gt;配置&lt;/h1&gt;

&lt;p&gt;上文中的argv参数列表和意义请参考: &lt;a href=&quot;https://facebook.github.io/jest/docs/en/configuration.html&quot;&gt;Configuring Jest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;argv各项类型: &lt;a href=&quot;https://github.com/facebook/jest/blob/master/types/Argv.js&quot;&gt;https://github.com/facebook/jest/blob/master/types/Argv.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;argv参数示例请参考: &lt;a href=&quot;https://github.com/facebook/jest/blob/master/packages/jest-config/src/valid_config.js&quot;&gt;https://github.com/facebook/jest/blob/master/packages/jest-config/src/valid_config.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;默认参数：&lt;a href=&quot;https://github.com/facebook/jest/blob/master/packages/jest-config/src/defaults.js&quot;&gt;https://github.com/facebook/jest/blob/master/packages/jest-config/src/defaults.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;需要注意的是&lt;strong&gt;如果argv的某一项类型是object，需要做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON.stringify&lt;/code&gt;转化成字符串&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;主要就是调用时需要找到入口和传参，其他的在Node端运行就没有障碍了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2018 todolist</title>
   <link href="http://echizen.github.io/think/2018/02-16-2018-todolist"/>
   <updated>2018-02-16T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2018/2018-todolist</id>
   <content type="html">
&lt;p&gt;新的一年开始之际，要立下一批flag，力求能实现，而且过去过的越不如意，就应该越对未来充满期望。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;最大的期待是调整好心态，靠近“不以物喜，不以己悲”的上乘境界。对自己的各方面有正确的认知，面对不足时不焦虑，而是有对策有步骤的去解决它&lt;/li&gt;
  &lt;li&gt;更勇敢，勇敢去尝试，勇敢去争取，勇敢抓住机会，不因失败后果而退缩，很多事情去尝试后失败比因害怕而不去尝试就更成功&lt;/li&gt;
  &lt;li&gt;学会放松，尽量的调节工作和生活的平衡，规划适度的休闲时间，且严格按照计划执行。不在休息时忧心工作，也不在该学习时沉迷娱乐&lt;/li&gt;
  &lt;li&gt;开始做一个有计划的人，精确到每个月，学什么，干什么，都具体化。同时为了锻炼规划能力，制定1年、3年、5年规划。工作上认真对待每个季度的绩效设定。&lt;/li&gt;
  &lt;li&gt;发散思维，注重idea的培养&lt;/li&gt;
  &lt;li&gt;更精致的面对生活&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;整了一群概念，这些都不是一年能实现的，需要长期以往。再来点18年的具体事项：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一点点积累理财知识，在买房之前，钱不能闲置，必须涉足基金、股票&lt;/li&gt;
  &lt;li&gt;每周必须有不少于6个小时的跳出工作内容的学习时间，增加学习广度。（工作日视情况23-24点，周末下午14:00-17:00）&lt;/li&gt;
  &lt;li&gt;每天在零碎时间不少于20分钟关注技术资讯，关注领域发展。（譬如InfoQ、前端早读课、知乎微博前端领域大V、淘宝腾讯前端团队博客）&lt;/li&gt;
  &lt;li&gt;工作之余开发至少一个项目，做出一个完整的产品。（产品构思已存在）&lt;/li&gt;
  &lt;li&gt;工作上有突破，至少构思、主导、完成一件对公司有价值的项目&lt;/li&gt;
  &lt;li&gt;至少看一本与技术和行业无关的对思维或心理有价值的书。（譬如心之道等）&lt;/li&gt;
  &lt;li&gt;周六非特殊情况不做与工作相关的事，留给学习和生活。天气好时都出门&lt;/li&gt;
  &lt;li&gt;学会游泳&lt;/li&gt;
  &lt;li&gt;在摄影上更进一步，不局限于自己折腾，还要每周从外界学习点摄影新知识。入手大光圈定焦镜头、广角镜头，拍更多的好片。&lt;/li&gt;
  &lt;li&gt;保持每周打扫卫生，保持整洁舒适的生活环境，给工位养绿植，家里每月购入鲜花点缀&lt;/li&gt;
  &lt;li&gt;国庆如果不去旅行，就回家看父母。没到节假日和家里忙的时间，买点东西寄回家&lt;/li&gt;
  &lt;li&gt;旅行，能结伴就去西藏、稻城亚丁，如果只有自己的话，就去西安&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;深感自己格还是太小了，越来心里会越有一个声音，你的梦想呢？你甘于如此的生活轨迹继续下去么？&lt;/p&gt;

&lt;p&gt;刚看了芋头的17年总结更是甚有此感，看到格局比你高几个境界的人的所思所想，不免让人感觉自己的局限。&lt;/p&gt;

&lt;p&gt;不过焦虑无益，还是一步步想好，再一步步去做吧。越来越感觉想清楚方向再去做，远比一直东打西闹不停的看上去很努力的做事要更重要。而这个想清楚方向的过程必定是比较头疼费劲的。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>部署单元测试环境需要做的思考</title>
   <link href="http://echizen.github.io/tech/2018/02-14-deploy-unit-test"/>
   <updated>2018-02-14T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2018/deploy-unit-test</id>
   <content type="html">
&lt;p&gt;17年年末给公司一条业务线部署了单元测试环境。这个项目比较特殊，受众是来自几十位开发者共同开发的近千个模块，有一个预装的开发环境，但是具体到每个模块，就是完全由开发者自己控制的，我们只是提供开发工具，软链到开发环境下。由于项目比较复杂，需要手动配置折腾的东西比较多，而且我们有个库-用类vue语法能同时支持h5端和微信小程序端，测试因此也要针对这种类型的模块提供h5端和小程序端的测试。一番折腾后对于部署单元测试环境感慨良多。&lt;/p&gt;

&lt;h1 id=&quot;起步思考清楚需求&quot;&gt;起步思考清楚需求&lt;/h1&gt;

&lt;p&gt;在做这一切之前，先得想清楚自己要搭的这个单测环境需要具备哪些能力。可以先从使用者的角度，譬如我的预期是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一键化生成单测文件&lt;/li&gt;
  &lt;li&gt;单测文件中包含基础样本代码，能够测试模块和组件初始化后的data数据结构和ui结构，避免开发者面对一片空白不知从何下手，也保证最基础的能自动做的测试内容由工具来做&lt;/li&gt;
  &lt;li&gt;测试库mock掉开发框架级别基础库里的需要mock的内容，譬如跳转、分享等封装的功能，并且对通用测试点譬如网络请求、打点等提供封装好的mock函数。让写测试的开发者只用关心自己的代码逻辑，不因为基础库而困扰&lt;/li&gt;
  &lt;li&gt;开发者在当前模块下调用命令运行测试，终端输出详细的测试报告&lt;/li&gt;
  &lt;li&gt;测试报告的生成能力&lt;/li&gt;
  &lt;li&gt;监控文件变更，根据变更生成新的测试结果的能力&lt;/li&gt;
  &lt;li&gt;对于从vue转小程序的模块，需要一份测试代码，最终能覆盖到h5端和小程序端的测试&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;测试框架的能力&quot;&gt;测试框架的能力&lt;/h1&gt;

&lt;p&gt;鉴于社区已有这么多成熟的单测框架，没必要手动开发一套单测框架，我们要做的是选套合适的单测框架，并能结合业务特点DIV到能够符合预期功能。&lt;/p&gt;

&lt;p&gt;首先我们来看看一个完善的比较理想的单测框架需要具备哪些能力：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;断言库： 且要和社区里常见的几种断言库如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Should.js&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chai&lt;/code&gt;，语法类似，减少开发者学习成本&lt;/li&gt;
  &lt;li&gt;测试运行库： 能够组织、控制并运行测试，这部分可配置度越高越有利于业务化&lt;/li&gt;
  &lt;li&gt;测试报告：能清晰的反映测试覆盖率，能定位到为覆盖的代码。即使框架未集成，也是需要测试环境搭建者集成的&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;还有一些其他的方便进行测试的功能：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;完善的mock方案：对于文件、模块、函数、对象等内容都要有可操作的mock方案&lt;/li&gt;
  &lt;li&gt;支持异步测试用例：毕竟异步操作会充斥在我们被测试代码的各个地方，对异步的支持是个想需求，而且需求量高，需要越方便使用越佳。&lt;/li&gt;
  &lt;li&gt;可调控可配置：毕竟要业务化，不能配配参数就能上路，整个测试环节的每一步我们要都能把握调度&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;测试流程&quot;&gt;测试流程&lt;/h1&gt;

&lt;p&gt;再来看看一个测试运行需要经过哪些步骤，来了解我们需要在哪些环节DIV&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;启动测试 - 测试环境初始化 - 被测试文件预处理 - 运行测试 - 获取测试结果 - 结束
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;测试环境初始化： 这是我们测试框架的代码能和开发者写的模块测试代码通信的一步，我们可以在这里传递测试代码需要的信息，譬如埋下一些全局变量。在这步也可以把测试环境需要做的准备工作做好，譬如源代码基础库的一些处理。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;被测试文件预处理：这里是对源代码和测试代码的处理，譬如将源代码的vue代码转化为小程序代码，处理测试文件里的alias等等，需要注意的是这部比较耗时，需要注重优化，并尽量简化&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;运行测试：提供统一的接口生成组件实例，埋好各种可能需要配置的参数，测试者调用接口运行测试时，就会在你的控制下，生成实例的过程中，可以传递内置的一些信息，处理基础库的内容，挂载实例上的mock方法等等。对于需要h5转化成小程序的模块，需要先测试h5端，再转化成小程序代码，运行小程序端测试，并且封装好的测试框架要保证h5端组件实例和小程序端组件实例上挂载的方法、属性一致，让开发者能用统一的方式测试。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;获取测试结果：拿到测试框架返回给我们的结果，视需求处理，譬如是在终端显示，还是作为pre-commit的部分阻止提交，还是在服务器上部署的失败信息回馈。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;选择工具&quot;&gt;选择工具&lt;/h1&gt;

&lt;p&gt;搞清楚测试框架的能力和我们需要做的事情，接下来就是要选一款合适的测试框架和一些方便测试的库了，需要考虑以下因素：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;可配置、可调控程度：这个非常重要，毕竟要为业务定制，而且业务不停的在发展，需要不停的加功能。可配置、可调控程度限制了你的能力，决定了你的可能性&lt;/li&gt;
  &lt;li&gt;被测试代码技术栈：这个跟测试框架关系不大，如果是node或者单纯的js，那么都不需要考虑太多。如果是vue技术栈或者react技术栈，就可以选择一些社区里现成的方便测试的库辅助，方便生成组件实例，以及操作组件实例，譬如vue的vue-test-utils, react的enzyme&lt;/li&gt;
  &lt;li&gt;常用场景：譬如我上面说的异步，如果你的源代码里异步场景比较多，那么选择一款对异步支持友好的代码绝对是开发者的福音。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;测试环境&quot;&gt;测试环境&lt;/h1&gt;

&lt;p&gt;在搭建测试环境是，对自己的测试环境要有正确的认知。大多数单元测试都是部署在node端，极少有在浏览器或者微信小程序等真实生产环境下跑的。&lt;/p&gt;

&lt;p&gt;node端的优势是一切都在掌控之中，包括测试框架的调用、测试代码和原代码的处理，不会遇到其他外来未知因素的干扰。&lt;/p&gt;

&lt;p&gt;但是也有限制，譬如node端没有dom，即使用jsdom模拟，但因为jsdom没有css layout, dom节点位置元素丢失。&lt;/p&gt;

&lt;p&gt;所以我们要扬长避短，单元测试毕竟注重的是最小代码单元，对于宏观环境的不利于测试的因素我们可以通过其他方式解决，譬如dom节点位置，缺失，我们可以mock调用dom阶段位置的函数返回结果来模拟，从而不影响到我们业务核心代码的测试。&lt;/p&gt;

&lt;h1 id=&quot;个人案例分享&quot;&gt;个人案例分享&lt;/h1&gt;

&lt;p&gt;说了这么多，落地点到我搭建的测试环境，上面所说的期望功能都实现了，方案也在上面叙述了。每个人面对的业务场景不同，还是分享点可以共通的东西，譬如测试框架。&lt;/p&gt;

&lt;p&gt;我选用的是jest，一方面是我个人比较熟悉，另一方面是上面所说的能力其都具备。&lt;/p&gt;

&lt;p&gt;先说说可圈可点之处：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;jest配置完善，如果用默认配置，就是一个大全家桶，包括测试报告、检测文件变更运行测试这些能力都帮你内置了&lt;/li&gt;
  &lt;li&gt;虽然默认配置是全家桶，但是可配置性很高，每个单元都可拆卸替换。譬如测试运行库默认用的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jasmine2&lt;/code&gt;，但是你也可以换成其他的，一个配置即可完事&lt;/li&gt;
  &lt;li&gt;jest发了22个大版本后，已经比较完善了，这一年看着facebook的这个儿子更新迭代的，还是很靠谱的。&lt;/li&gt;
  &lt;li&gt;社区繁荣，配套的react测试方案、vue测试方案资料都充足&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用下来觉得不方便的地方：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;官方文档只有命令行和配置文件的使用方式，没有任何node端调用的文档，部署环境肯定是node端，这些配置肯定是内置的，导致这些接口参数，包括暴露出的测试调用入口都是通过源码找出来的，非常不方便。&lt;/li&gt;
  &lt;li&gt;没有测试代码预处理器，只有原被测试代码预处理器，导致如果你需要对测试代码做些处理，都没法方便操作，只能处理后生成文件，再去读取生成后的文件，耗时很长&lt;/li&gt;
  &lt;li&gt;由于配置了watchman作为检测文件变更的工具，watchman不支持软链，jest内部多出逻辑也没有考虑到软链的情况，如果你的测试环境在一个项目里，被测试文件通过软链链到这个项目里，便没法操作，一定要找到原文件（这个场景比较少）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;搭建单测环境，重要的还是为写测试的开发考虑，让他们用的顺手，提供的api和写法要人性化，消除业务基础库的影响，尽量封装通用的工具函数，还要有完善详细的开发文档。后期就是宣传要给力喽，能够吸引大家来写，毕竟前端领域，非框架类和基础库类的业务代码，很少有人有测试的习惯。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2017年终总结-不易</title>
   <link href="http://echizen.github.io/think/2018/02-12-2017-summary"/>
   <updated>2018-02-12T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2018/2017-summary</id>
   <content type="html">
&lt;p&gt;特地回翻了前2年的年终总结，格调都是比较欢快高昂的，17年的格调就比较抑郁了&lt;/p&gt;

&lt;p&gt;回顾2017，不算顺利，有很多坎坷，有太多纠结。无论怎样，都还是应该总结一下，直面自己的过去，且决不粉饰太平（看到某人的年终总结粉饰太平的完全是写给别人看的）。&lt;/p&gt;

&lt;p&gt;这1年觉得很长很长……&lt;/p&gt;

&lt;h1 id=&quot;工作&quot;&gt;工作&lt;/h1&gt;

&lt;p&gt;工作上我还是取得了比较不错的结果。但是并没有达到自己期望的境界，自己的很多短板还是没能克服。不像16年，觉得只是做了自己觉得该做的事，已经在别人看来很好了，这说明我的能力满足甚至超出了当时的岗位需求。17就比较艰难，会有种被推动拔高感，随时都感到被期待，能力是在努力拔高的，而不是浑然天成的，晋升后工作范围和能力的要求都需要我去努力了。经常苦思冥想还需要做些什么，怎样做的更好，自己的各种缺点怎么去克服，其实这是一个内心非常痛苦挣扎的一年。而且经过一年，我觉得自己还是没有太大的突破，所以我总结的是这是一个蛮纠结且不够出色的一年。&lt;/p&gt;

&lt;p&gt;又发掘了自己的一些以前没意识到的缺点，譬如缺乏规划能力，缺乏技术敏感度，不能及时把技术与业务做新的结合。我明白这些都是很难短时间突破的，需要我去多看多想，有一定的沉淀，并逼迫自己去不断思考才能拥有的能力吧。&lt;/p&gt;

&lt;p&gt;以前发掘的譬如不善于沟通，对业务理解不透彻等问题。这一年下来，这些不足是在慢慢克服的，是有成长的，自己会主动去沟通，去推动有意义的事了。在业务线待久了，对业务理解的也越来越深了。&lt;/p&gt;

&lt;p&gt;工作上依旧尽心尽力，把控细节，追求质量，保证稳定性，且致力于协调所在业务线资源，一起统筹规划，推动日常迭代。但是缺乏大的突破性规划，一方面对于细节偏执，另一方面缺乏大局观。&lt;/p&gt;

&lt;p&gt;所以2017虽然过得很纠结很焦虑，也许也只是一种成长过程吧。要感谢主管，不断用新场景来锻炼我，推动我去成长，真的比较多的感受到leader们的期待，会觉得自己还没有达到要求，这种感觉给我带来很大压力，同时也在推动我。&lt;/p&gt;

&lt;p&gt;我明白，时间一点点的过，增长的不能只是工龄，1年，2年，3年，能力应该有明显的区别。也是这种意识让我过的焦虑。焦虑还来源于对自己所负责的业务的责任感，处于瓶颈期思考不出突破点时，会沮丧自己作为一线开发，业务完全交给了我，如果不能带动其进步，实在是有愧于这个平台。&lt;/p&gt;

&lt;p&gt;待的久了后会看到工作环境并没有之前理解的那么简单。但是我还是踏踏实实好好做事情，结果优先，不喜欢太多复杂的因素。&lt;/p&gt;

&lt;h1 id=&quot;技术&quot;&gt;技术&lt;/h1&gt;

&lt;p&gt;技术上，不再像以前那样能把工作和学习分离，利用业余时间学习新知识。更多的业余时间还是在学习工作上会用到的东西。我不知道这是好是坏，这一年下来，新技术所学不多，但是对于用到的东西都学的更深入透彻了。譬如前端单元测试相关，代码分析能力。&lt;/p&gt;

&lt;p&gt;单测方面，一开始是给自己负责的后台系统配置了单测环境，写了不少单测案例，虽然后来业务发展中没有持续维护案例（这是权衡稳定性和变更性后的取舍），但是这个过程算是补了一片空白，让编程之路更完整。后来在上级的要求下将这套体系推广到内部其他中后台系统，集成到脚手架&amp;amp;&amp;amp;开发工具帮助老项目一键配置环境，毕竟配置单测环境很耗时且没太大意思。如果说这只是小打小闹，12月份，为一个业务线来自近百个开发的上千个模块系统开发了单测环境才算是深入了，这个过程中无数次调试到jest的各个模块，已经真的熟悉了其运行机制，学会了将开源框架和业务场景相结合。这个过程中因业务场景比较特殊，遇到无数问题，一个个完全独立的解决下来，并且覆盖到了微信小程序端，让我内心有了底气，技术难题总是能解决的，大不了就是多花点时间想清楚问题存在的实质，多试几种解决方案。开始从调试源码找问题，不再完全依赖社区现成的解决方案&lt;/p&gt;

&lt;p&gt;特别是在开发vue转小程序的过程中，解开了ast的神秘面纱，掌握了代码分析处理方法，无论是js还是html还是css，都能有处理方案。原来抽象语法树也不是那么复杂的存在，这个开发不仅增加了自己的技能点，更是给公司带来了不小的价值。技术的沉淀在以后一定都会给我带来帮助。就像上一年业余时间系统的学习了正则相关知识，这一年也应用在了各种场景下，为我提供了一种更为方便可靠的解决方案。技术就是这样，多学点总是有用的，学的越深，用的越娴熟，能够发掘更多的场景。&lt;/p&gt;

&lt;p&gt;业务在工作中占比越来越大，很多难题不涉及到技术难度只是业务复杂度。虽然业务能力有提升，但是说句不成熟的话，我内心里还是想静静的写好一手代码，虽然技术的价值在于解决问题，还是对纯粹的技术爱的深沉…&lt;/p&gt;

&lt;p&gt;leader们会说从不担忧你的技术能力，一直想让你的业务能力能更多成长，希望你把握每次的机会。我明白这其中的意义，但是内心真的是渴望能静静的好好写代码，总是觉得给业务场景想方案，很多方案都没啥技术难度，倒是被历史包袱和业务线场景牵绊着才会很复杂，虽然这就是现实，但是总觉得没有太大意义，做了也没有多大成就感，可能是境界还不够吧。&lt;/p&gt;

&lt;p&gt;我感觉这一年技术有深入的方向，有从业务实战中得到的磨炼成长。可总是不踏实，还是有很多东西了解的不透彻，特别是面不够广，和社区联系太少了，太多业余时间其实还是在解决工作上遇到的问题，虽然也是技术问题，但是这就牺牲了本来用来和外界交流的时间，这点需要好好平衡。&lt;/p&gt;

&lt;p&gt;比较自豪的是这一年在近百人的大前端团队做过3次大分享~算是挑战了自己害怕众人前演讲的缺陷。不过演讲能力还是要好好练练，需要生动有趣点，考虑受众最想了解的而不是自己最想描述的。&lt;/p&gt;

&lt;h1 id=&quot;感情&quot;&gt;感情&lt;/h1&gt;

&lt;p&gt;如16年所愿，第一段感情有了结果，虽然4年多还是结束有很多不舍，但是其中饱含我太多的委屈，夹杂一些没法忽略的伤痕，生活习惯上也有很多不合适。所以这个决定是对的，感谢自己终于勇敢了一回。从不后悔这段感情，但是也不后悔这个决定。&lt;/p&gt;

&lt;p&gt;也许我再也遇不到更好的，但是这一步都走出去了，未来也一定不将就。&lt;/p&gt;

&lt;p&gt;结束的很平静，后来的几个月也没有什么异样，甚至和室友小姐姐各种撒欢。只是夹杂了太多委屈，从不想再回头，坚信自己的决定是正确的&lt;/p&gt;

&lt;p&gt;然后还随心所欲了一次，6月份表白了旅行途中结识的小哥哥，这位属于看到就非常有感觉，被个人气质所吸引。我觉得这件事我也不会后悔，趁着年轻，表达自己的感情，是一件多么勇敢又有趣的事啊。虽然各种差距，最终我们可能属于不同的生活。但是曾经因为怦然心动而在一起，就很美好，算是认真谈了一场恋爱，只不过可能这个感情更适合学校那种象牙塔吧。经历过后也不会再那么追求感觉了，毕竟深挖下去发现有感觉=对方长的好看，但是好看的面孔千篇一律，有趣的灵魂万里挑一，所以其实一见钟情挺肤浅的。但是也没法和看着就反感的人在一起，最好的状态应该是始于颜值，陷于才华，忠于人品吧。&lt;/p&gt;

&lt;p&gt;抛去了太多世俗观点，譬如房子、车子、未来、孩子等等，会觉得感情很纯粹。&lt;/p&gt;

&lt;p&gt;还是鼓励自己勇敢多尝试，经历多了会看的更透彻。&lt;/p&gt;

&lt;h1 id=&quot;生活&quot;&gt;生活&lt;/h1&gt;

&lt;p&gt;自认为过的更精致了，搬了新家，室友们都很nice，我会每周打扫，把家里弄得干干净净，也做一些简单的装扮。不忙的时候都会自己做饭，同事说麻烦，可是觉得给自己和室友做饭，吃的好也极大增强幸福感。&lt;/p&gt;

&lt;p&gt;去医院解决了一系列历史问题，就是痘痘只能靠中药压制，而且容易反弹，泪目ing。&lt;/p&gt;

&lt;p&gt;天气好的周末会和室友一起出门，去西溪遛遛狗，去西湖爬爬山，西湖周边的一圈山都爬遍了，出门走走，周末才算是真的周末。&lt;/p&gt;

&lt;p&gt;最最最重要的是入了一台sony微单，极大满足，没事就可以拍拍拍，特别喜欢选好景按下快门定格的一瞬间。在这一年，相机让我乏味的生活带来了色彩，给我带来了很多欢乐，梦里都是美景和拍片，占据了最大的愉快。也会去了解些摄影相关的知识，并尝试，乐趣无穷。就像沉迷于自己的菜一样，也沉迷于自己拍的照。拍了西北的湖、雪山、沙漠，也拍了夜晚的灯光，拍了秋色，拍了西湖的雪景……这一年最开心的时刻是手里有相机，身后有帮我拿设备的人，无论是小姐姐还是小哥哥。&lt;/p&gt;

&lt;p&gt;买东西不再杂开始追求品质了，但是剁手太多，得收敛点了。。。&lt;/p&gt;

&lt;p&gt;想学学理财，但是没啥进展，还没找到好的入门途径，进行中…18要有长进，开始投资。&lt;/p&gt;

&lt;h1 id=&quot;旅行&quot;&gt;旅行&lt;/h1&gt;

&lt;p&gt;一年那么多个日日夜夜的辛苦，追求的不过是出去看世界。旅行于我意义非凡。&lt;/p&gt;

&lt;p&gt;17年大的地方去了西北，青海、敦煌大环线，西北的景粗犷奔放，孤独寂寥。车子驰骋在无人区，一群人在车上欢歌，这么美的体验也定格在了我的脑海里，成为一种特殊的感觉和印象。同时体验了团队（我们是6人）旅行的乐趣，我终究不是真的喜欢孤单的。&lt;a href=&quot;http://www.mafengwo.cn/i/7710209.html&quot;&gt;念天地之悠悠 独怆然而涕下&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;公司outing拉去舟山，本身是要徒步25公里的，没想到舟山的海景不错，徒步的日子天气很好，蓝天白云，还算蓝的海，虽然累，但甚是惬意。&lt;/p&gt;

&lt;p&gt;18年希望能去心心念念的西藏，想去看林芝的桃花，想去看纳木错，想看雅鲁藏布江…也想去看稻城亚丁、新都桥，想走最美的318国道…对高原向往的深沉，能找到同伴不易，一定会好好试试。希望能成行。&lt;/p&gt;

&lt;h1 id=&quot;结语&quot;&gt;结语&lt;/h1&gt;

&lt;p&gt;17年抑郁的很严重，约有4个月的失眠期，深受折磨，17没受啥打击，却过得如此艰难，都是因为抑郁。依旧拒绝药物，害怕损伤大脑，毕竟靠脑子混饭。。。但是我正式自己的问题，并积极寻找缓解方式。其实自己并没有遇到什么大事，自己和家人的身体都还健康，工作稳定，没什么变故。这个抑郁从何而来我也不知道，所以很难化解。只能希望周期性的东西，在18年是过了这个周期了，能够开开心心，快快乐乐的好好生活。&lt;/p&gt;

&lt;p&gt;觉得自己过于焦虑，过于压力大，希望好好体味生活，调节心境，能平和的面对各种问题，宠辱不惊。&lt;/p&gt;

&lt;p&gt;经过17的折磨，惟愿自己18过得开心。至于进步什么的，我知道付出后一定不会差。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>微信小程序性能二三事</title>
   <link href="http://echizen.github.io/tech/2017/10-29-xcx-performance"/>
   <updated>2017-10-29T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/xcx-performance</id>
   <content type="html">
&lt;p&gt;小程序代码在ios上很溜，但是安卓机上性能问题很多，低端安卓机对性能调优更是要求贼高，稍不注意，页面就能卡死。&lt;/p&gt;

&lt;p&gt;性能问题基本都是cpu不够用导致的卡顿，这种卡顿可能是1）dom太大，内存爆满  2）js操作频繁，js进程繁忙 3）渲染问题，可能是被js阻塞了，也有可能是所在环境的渲染机制不够优化&lt;/p&gt;

&lt;p&gt;在本次为小程序开发的双十一会场页面时，总结出以下经验&lt;/p&gt;

&lt;h3 id=&quot;存在的性能问题及解决方案&quot;&gt;存在的性能问题及解决方案&lt;/h3&gt;

&lt;p&gt;在开发中发现在低端机上几个小程序端需要注意的事：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;dom大了后，bindtap事件会不灵敏，短时间连续触发的bindtap事件后续的会丢失。原生小程序测试时，3000个dom节点就有丢失现象。后来尝试了使用touchstart和touchend去模拟，发现虽然touchstart很灵敏，但是touchend会丢失（个人猜测小程序的tap事件也是通过touchstart和touchend模拟的）。我们的大促会场600个商品时我算了下大约13000个dom节点，低端安卓机事件丢的很严重，间隔几乎是秒级别才能触发下一次事件。所以解决方案只能减小dom量，添加dom懒加载和动态移除的逻辑，经过多次测试，比较理想的效果是预加载2屏&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2）滚动时setState卡顿严重，如果滚动时有触发setState操作，那么setState成功可能发生在几秒后，即使滚动操作在几百ms内就结束了。这就导致导航类功能的滚动跟随效果在安卓机上反应特别慢，滚停到某个商品楼层后，过了1-2s才导航条才突然动一下，所以最终取消了安卓的滚动跟随效果。（不根据版本取消效果，是因为发现即使是安卓最新的7.1版本在性能不好的机器上也会这样）&lt;/p&gt;

&lt;p&gt;3）白屏问题&lt;/p&gt;

&lt;p&gt;快速疯狂滚动页面，前面的页面没渲染出就不停的往后滚，发现小程序不是优先渲染可视区域，而是一定要把曾经滚过的区域按顺序渲染了。所以如果快速疯狂滚动，后面的内容白屏等待时间会很久。考虑到正常用户不会有这种需求，想看后面的内容可以通过楼层导航过去，而且这个问题我们也找不到解决方案，所以这个现象没有处理。&lt;/p&gt;

&lt;h3 id=&quot;注意事项&quot;&gt;注意事项&lt;/h3&gt;

&lt;p&gt;1）懒加载+动态移除非可视范围内的内容，让dom小下去&lt;/p&gt;

&lt;p&gt;2）耗时的js操作异步化，不要阻塞主线程。落地一点说，小程序里不要做频繁的setState操作，不在state里放跟视图层无关的内容。譬如我之前为了代码清晰，把导航功能模块里的楼层位置信息放到了视图层也用的一个变量里，其实视图层并不直接用到这个信息，这个信息为了准确，又会在每次滚动后重新计算，导致频繁setState，且set的是跟视图层无关的数据，优化后，性能提升很明显。&lt;/p&gt;

&lt;p&gt;3）还有跟小程序本身相关的，wx.createSelectorQuery系列接口都是异步的，会受主进程影响，如果主进程繁忙，这个接口返回时间会延迟很久也是s级别的，对于楼层导航和懒加载这种需要页面各模块位置信息的功能，不能每次操作都等这个接口返回，可以缓存数据，取缓存，然后用户的操作触发调接口更新缓存，在缓存有更新时，为了更准确，也可以主动触发下需要位置信息的后续处理函数。&lt;/p&gt;

&lt;p&gt;4）少用scroll-view，这个组件对性能影响实在太大，单纯的只是需要一块可滚动区域，请用css+view解决&lt;/p&gt;

&lt;p&gt;5）不知道微信的小程序做了什么，滚动操作时进程异常繁忙，滚动停止后很久才是可操作和执行js状态，所以尽量少的触发在滚动时的回调函数，节流函数必须合理用起来。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;性能调优是个漫长的取舍过程，需要不断测试来获取最优效果。cpu只有那么多，一段时间只能干那么多事，那就要干效果最好最重要的事。&lt;/strong&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>vue转小程序</title>
   <link href="http://echizen.github.io/tech/2017/10-29-vue2wx"/>
   <updated>2017-10-29T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/vue2wx</id>
   <content type="html">
&lt;h3 id=&quot;vue转小程序&quot;&gt;vue转小程序&lt;/h3&gt;

&lt;h4 id=&quot;背景&quot;&gt;背景&lt;/h4&gt;

&lt;p&gt;在普通移动端h5页面和小程序端页面内容因业务相同有大量相同内容时,需要一个工具能够将h5端的代码转成小程序端的，来提高开发效率，也便于日后的维护。这个转换其实是语法的映射，最终确定了h5端选择vue框架的的代码，一方面是因为可行性，特别是js部分，结构类似，处理起来比较方便；另一方面vue目前比较通用。&lt;/p&gt;

&lt;h4 id=&quot;方案&quot;&gt;方案&lt;/h4&gt;

&lt;p&gt;针对vue的sfc结构组件，将template中的模板部分转化到小程序的wxml，style里的css部分转化为小程序的wxss，script里的js部分转化为小程序的js。&lt;/p&gt;

&lt;p&gt;模板层是比较复杂的，需要对vue进行语法分析得到抽象语法树，然后再一一映射到小程序的语法，就是一次把字符串拆成ast，再转化成另一种语法的字符串的过程&lt;/p&gt;

&lt;p&gt;css部分主要是单位转化，把px、rem单位转成小程序里的rpx。具体一点的实施方案：利用postcss拿到css的ast，拿到decl、comment等节点信息。转化：1rem -&amp;gt; 100rpx; 1px -&amp;gt; 1rpx; 有 /* no2rem */标识的px单位不转化。import文件的处理：对于import引入的公用文件，为了体积不能像less处理器那样合成到所引入的文件里，所以使用postcss-less处理器分析ast，去除import语句后处理剩下的语句，并记录import的文件，将原文件补上import wxss文件语句信息，将import的文件转成wxss并引入到小程序目录&lt;/p&gt;

&lt;p&gt;js部分是核心，虽然不需要语法上的转化，但是需要实现一套标准，在vue和小程序端都能跑起来，其实是实现一个框架。但是为了方便，我们并没有重新构造一套框架，而是将vue的核心部分除了模板渲染搬到了小程序里，模板渲染依旧使用小程序内置的。&lt;/p&gt;

&lt;p&gt;当然，这不是万能的，不是每个vue语句都能被转成小程序语句，譬如小程序里不允许在模板里使用函数。这种属于小程序本身的限制我们是绕不开的。只能提供一份说明文档，让大家开发时写的是有一定限制的vue子集。&lt;/p&gt;

&lt;p&gt;而且，这不仅仅是开发个转化工具就能无痕的实现vue转小程序的，还需要在h5端和小程序端都封装一套基础库，以实现js里的很多功能，下面会详细介绍&lt;/p&gt;

&lt;h4 id=&quot;难点亮点&quot;&gt;难点&amp;amp;亮点&lt;/h4&gt;
&lt;h5 id=&quot;模板的转化&quot;&gt;模板的转化&lt;/h5&gt;

&lt;p&gt;模板部分的处理主要是转化，从vue的语法映射到小程序的语法。&lt;/p&gt;

&lt;h6 id=&quot;获取sfc&quot;&gt;获取sfc&lt;/h6&gt;

&lt;p&gt;vue源码里就有将template字符串转化成ast的部分，在compiler/parser部分，我们可以直接搬过来使用。稍微做了点改动，譬如在ast中记录行号，以抛出错误时精确到具体的行数。&lt;/p&gt;

&lt;p&gt;如果不改动vue源码，获取ast可以直接这么做：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const compiler = require(&apos;vue-template-compiler&apos;)
const getsfc = function(content) {
  let output = compiler.parseComponent(content)
  return output
}
const compileTpl = function(tpl) {
  let output = compiler.compile(tpl, { comments: true, preserveWhitespace: false, shouldDecodeNewlines: true })
  return output
}
const sfc = getsfc(vueFileContent)
const astRes = compileTpl(sfc.template.content).ast
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;vue里的这部分将template进行compileToFunctions是为了得到render以及staticRenderFns。生成的ast结构是为了这个目的设计的，所以有些地方不一定跟我的需求完全一致，还有一些地方在我的角度看上去会觉得很怪异，譬如else不是一个独立的ast节点，而是存在if的ast节点信息的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ifCondition&lt;/code&gt;里。不过虽然这种结构对于我们来说不是最方便的，但是所有信息都是完整够用的（行号这种没有的信息就只能自己改改源码记录下了），考虑到以后升级维护，vue自己解析的ast才是最准确的，所以还是利用了vue源码中的这部分，得到ast。&lt;/p&gt;

&lt;p&gt;我们来看下vue解析出来的ast结构：&lt;/p&gt;

&lt;p&gt;譬如这样的template：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;section class=&quot;app&quot; :class=&quot;dynCls&quot;&amp;gt; 
    &amp;lt;h1 @click=&quot;handleClick&quot;&amp;gt;vue转小程序&amp;lt;span :style=&quot;style.headerTip&quot;&amp;gt;(test demo)&amp;lt;/span&amp;gt;&amp;lt;/h1&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;生成的ast结构是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_307l49adk5g36d49jd2lbbh2agad4_1590x1200.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_13j808857ge1dl44k9f6dd512k52k_1554x584.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h6 id=&quot;vue-ast转化为小程序语句&quot;&gt;vue ast转化为小程序语句&lt;/h6&gt;

&lt;p&gt;不断遍历ast，取出有意义的部分，再分别做映射。&lt;/p&gt;

&lt;p&gt;ast的子元素放在父元素的children里需要遍历取出处理。&lt;/p&gt;

&lt;p&gt;如上图的结构，对于我比较有意义的几个数据是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;tag: 标签&lt;/li&gt;
  &lt;li&gt;type: 类型，1-标签；2-表达式节点(Mustache)；3-纯文本节点和comment节点&lt;/li&gt;
  &lt;li&gt;attrsMap: 标签上的属性集合&lt;/li&gt;
  &lt;li&gt;children: 元素的子元素，需要递归遍历处理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;还有一些特定场景会出现的有意义的信息&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;classBinding、styleBinding: 当元素上有动态绑定的class、style时，会将信息存在这里&lt;/li&gt;
  &lt;li&gt;if、elseif、else: 条件语句中的条件&lt;/li&gt;
  &lt;li&gt;ifConditions: 条件语句的else、elseif的节点信息都放在ifConditions的block里了&lt;/li&gt;
  &lt;li&gt;for、alias、iterator1: v-for中的相关信息&lt;/li&gt;
  &lt;li&gt;text: 文本节点的文本内容&lt;/li&gt;
  &lt;li&gt;isComment:是否是注释（注释是不转化的）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;首先针对转化的内容的不同，分成3种处理的方式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;注释： 不转化，直接拼装成字符串。我们还支持注释的block，特定的注释里的（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;!-- xcx-begin --&amp;gt;...&amp;lt;!-- xcx-end --&amp;gt;&lt;/code&gt;）内容，会在h5中移除，直接不转化送到小程序里。&lt;/li&gt;
  &lt;li&gt;组件元素：需要分析生成组件注册的js语句。需要将props、event、控制语句都分析出来；模板转化成小程序&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;template&lt;/code&gt;的引用，并将跟js关联的data传进去，详情下面有介绍。组件元素相对于普通元素还有区别，譬如事件就不会去将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@、v-on&lt;/code&gt;转成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bind、catch&lt;/code&gt;；属性会进行中划线转驼峰的处理&lt;/li&gt;
  &lt;li&gt;普通元素，就是分析ast的有用信息，转化后再拼接成字符串。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;映射这部分需要细致的对比vue的api和小程序的api。举个v-for的例子。&lt;/p&gt;

&lt;p&gt;譬如vue中：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_7155ld3l0j8db0dgj26h4cigcdjhi_862x182.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;拿到的ast结构是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_54g8ga76hjdjidlld42e9210879ih_796x1110.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们先要将标签做映射，从第一层开始，ul转成view。再取出ul这层的children，是个数组，里面只有一项，就是li这一层，li先转成view，再分析含有ast.for，需要处理循环语句，循环的对象是ast.for，循环赋值的个体是ast.alias，循环的索引是ast.iterator1，这样需要的信息都有了，我们就能映射到小程序端的语法了，小程序里循环对象语法是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wx:for=&lt;/code&gt;，循环个体是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wx:for-item=&quot;xx&quot;&lt;/code&gt;，循环索引是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wx:for-index=&quot;x&quot;&lt;/code&gt;，为了代码最佳，我们加上key值，即&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wx:key=&quot;x&quot;&lt;/code&gt;，如果指定了索引变量，则&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wx:key&lt;/code&gt;使用索引变量，否则由系统根据时间戳+递增变量值，生成一个唯一值。
在看li这一层的children，是个type为2的数据绑定表达式，表达式内容是ast.text，vue中的数据绑定语法与小程序的一致，都是使用 Mustache 语法，直接搬过去就好，所以最终结果是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_46297d38agfk5bhc0cb391k7ge0j1_1132x228.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;组件的支持&quot;&gt;组件的支持&lt;/h5&gt;

&lt;p&gt;我们在开发这一套时，微信是还没有对外开放小程序的组件化方案的，但是对组件化已经在开发阶段预计将在4个月后支持了。&lt;/p&gt;

&lt;p&gt;但是如果要h5端跟小程序一样不支持组件，这是接受不了的事情。所以我们实现了一套模板转化和js基础库配合的组件化方案。&lt;/p&gt;

&lt;p&gt;组件实现的难点在于要让模板中的组件和js中的组件实例建立联系。所以我们的方案是在解析模板，分析ast时，抓取到组件，生成组件注册函数，然后在js实例化时运行这个函数，将组件挂在到页面或者父组件上去。（小程序端实际组件的联系都是将子组件的数据挂载在了父组件上，然后利用小程序的template去渲染）&lt;/p&gt;

&lt;p&gt;譬如这样一段template:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;demo-component :parent-msg=&quot;demoComponentData.parentMsgData&quot; :img-class=&quot;classObj.imgClass&quot; data-name=&quot;demo-component&quot; @parentFnEvent=&quot;demoComponentFn&quot; @parentFnEvent2=&quot;demoComponentFn2&quot;&amp;gt;&amp;lt;/demo-component&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解析ast时我们会生成这样的js语句：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default function render(renderComponent) {
renderComponent(&apos;demo-component&apos;, { &quot;parentMsg&quot;: this.demoComponentData.parentMsgData, &quot;imgClass&quot;: this.classObj.imgClass, &quot;dataName&quot;: &quot;demo-component&quot; }, { &quot;parentFnEvent&quot;: this.demoComponentFn, &quot;parentFnEvent2&quot;: this.demoComponentFn2 }, &apos;15075388741760&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后在小程序端运行时基础库会去调用这个函数注册组件，采用跟最外层模块相同的方式注册这个组件。&lt;/p&gt;

&lt;p&gt;这里的renderComponnet函数有模板分析出来的4个参数：组件名、组件的props数据、组件上传递的事件event，组件的唯一key（防止在for循环里同名组件出现问题）。&lt;/p&gt;

&lt;p&gt;模板wxml生成的结构是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template is=&quot;demo-component&quot; data=&quot;{{ $parent:$parent[&apos;$&apos;+label],...$parent[&apos;$&apos;+label][&apos;$demo-component_15075388741760&apos;]}}&quot;&amp;gt;&amp;lt;/template&amp;gt;&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;label是当前组件的组件名标识，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$parent[&apos;$&apos;+label][&apos;$demo-component_15075373766070&apos;]&lt;/code&gt;其实就是将相应子组件的数据传递进去了。可以在微信端看到数据结构是这样的：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_7087bkfjflbg27jbla58cec1ljffb_1016x886.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里面比较复杂的是条件语句、循环语句和组件混合的情况，分析模板时不仅要关注组件是否出现，还要关注其对应的上下文环境，是否在逻辑语句中，需要有个栈去记录其上下文信息，当走到某个ast分支的最底层时如果存在组件，则将这一条线路的语句和组件都记录下来生成相应的js语句，然后再将这个分支对应的上下文逻辑语句信息出栈；如果不存在组件，则将该分支记录的上下文信息都出栈。循环往复，直到分析完整个模板。&lt;/p&gt;

&lt;p&gt;譬如某段含有逻辑语句上下文的模板：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_4e7i92dh9e9i7gg8f3i5b9edfbf39_1298x802.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;生成的wxml：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_5i11ji8jb63339040ace78b343a25_1322x1022.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;生成的组件注册函数：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171009_770al88klkj0fi6l14k3k32d77e56_1226x892.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;npm包的支持&quot;&gt;npm包的支持&lt;/h5&gt;

&lt;p&gt;小程序不支持npm包，这给我们的开发带来了很多不便，既然支持了组件，就要支持组件对应的npm包，方便大家复用代码。&lt;/p&gt;

&lt;p&gt;对npm包的支持，处理了以下几个方面&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;读取模块中的package.json，将需要的npm包下载到小程序项目的特定目录下。某些内置默认加载的npm包也会被下载&lt;/li&gt;
  &lt;li&gt;由于微信的import是不会分析npm包的，对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import xxx form &apos;npmlocation&apos;&lt;/code&gt;这种是分析不出来位置会抛出错误的，所以我们要将js代码在转化时，处理掉这个路径，变成小程序端下载的npm包文件相对于转化后小程序端的js文件的相对路径&lt;/li&gt;
  &lt;li&gt;小程序包大小限制，原始下载的npm包目录是做ignore处理的。会在分析模块js文件时，记录下依赖的文件，只将引用到的文件移出到工作目录下。做到按需引入&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;价值&quot;&gt;价值&lt;/h4&gt;

&lt;p&gt;vue转小程序只是个场景，这套方案背后更大的价值是在掌握了js\css\html的ast后，能够将一种语法转化成另一种语法的能力。这对于一些迁移改造可以节省很大人力成本。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>开发postcss插件</title>
   <link href="http://echizen.github.io/tech/2017/10-29-develop-postcss-plugin"/>
   <updated>2017-10-29T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/develop-postcss-plugin</id>
   <content type="html">
&lt;p&gt;有时候，我们有处理css的需求，可能需要个工具来转化我们的css代码，譬如把正常的移动端web css代码里的rem、px单位转化成小程序里的rpx单位。&lt;/p&gt;

&lt;p&gt;这时候写个postcss插件是个不错的选择。&lt;/p&gt;

&lt;h3 id=&quot;css-ast&quot;&gt;css ast&lt;/h3&gt;

&lt;p&gt;postcss会帮我们分析出css的抽象语法树，要想写插件去处理css，需要先了解一下它透出的css ast。&lt;/p&gt;

&lt;h4 id=&quot;类型&quot;&gt;类型&lt;/h4&gt;

&lt;p&gt;css ast主要有3种父类型&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AtRule: @xxx的这种类型，如@screen&lt;/li&gt;
  &lt;li&gt;Comment: 注释&lt;/li&gt;
  &lt;li&gt;Rule: 普通的css规则&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;还有几个个比较重要的子类型：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;decl： 指的是每条具体的css规则&lt;/li&gt;
  &lt;li&gt;rule：作用于某个选择器上的css规则集合&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;初次接触，可以去这里看看ast语法树结构，会清晰不少：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://astexplorer.net/#/2uBU1BLuJ1&quot;&gt;http://astexplorer.net/#/2uBU1BLuJ1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171029_0ak3ehbbke8b5e5bfk5f56f6574kj_2204x1164.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s11.mogucdn.com/mlcdn/c45406/171029_539a04idg9af916g4i5gi57fb7ibb_2026x1124.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一个ast节点基本有如下信息：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;nodes: css规则的节点信息集合
    &lt;ul&gt;
      &lt;li&gt;decl: 每条css规则的节点信息&lt;/li&gt;
      &lt;li&gt;prop: 样式名，如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;value: 样式值， 如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10px&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;type: 类型&lt;/li&gt;
  &lt;li&gt;source: 包括start和end的位置信息，start和end里都有line和column表示行和列&lt;/li&gt;
  &lt;li&gt;selector: type为rule时的选择器&lt;/li&gt;
  &lt;li&gt;name: type为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atRule&lt;/code&gt;时@紧接rule名，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@import &apos;xxx.css&apos;&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;params: type为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atRule&lt;/code&gt;时@紧接rule名后的值，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@import &apos;xxx.css&apos;&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xxx.css&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;text: type为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;comment&lt;/code&gt;时的注释内容&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;postcss操作方法&quot;&gt;postCss操作方法&lt;/h3&gt;

&lt;p&gt;postCss为我们提供了一些方便的操作方法&lt;/p&gt;

&lt;h4 id=&quot;遍历&quot;&gt;遍历&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;walk: 遍历所有节点信息，无论是atRule、rule、comment的父类型，还是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rule&lt;/code&gt;、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;decl&lt;/code&gt;的子类型&lt;/li&gt;
  &lt;li&gt;walkAtRules：遍历所有的atRule&lt;/li&gt;
  &lt;li&gt;walkComments&lt;/li&gt;
  &lt;li&gt;walkDecls&lt;/li&gt;
  &lt;li&gt;walkRules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些方法都透出了遍历对象的参数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root.walkDecls(decl =&amp;gt; {
    decl.prop = decl.prop.split(&apos;&apos;).reverse().join(&apos;&apos;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;处理&quot;&gt;处理&lt;/h4&gt;

&lt;p&gt;如同jquery给出了很多操作dom节点的方法一样，postCss给出了很多操作css规则的方法，每种类型的节点稍微不同，如root节点的&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s3.mogucdn.com/mlcdn/c45406/171029_8d7kllad227b7hl5ekjjkg801l43k_248x952.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;处理css&quot;&gt;处理css&lt;/h3&gt;

&lt;p&gt;搞清楚了postcss为我们提供的css ast结构和提供的操作函数后一切就好办了。无非是找到我们要处理的节点，对于要处理的内容做一些增减或者替换操作。&lt;/p&gt;

&lt;p&gt;处理css的方式其实有2种：编写postcss plugin，如果你的操作非常简单也可以直接利用postcss.parse方法拿到css ast后分析处理&lt;/p&gt;

&lt;h4 id=&quot;postcss-plugin&quot;&gt;postcss plugin&lt;/h4&gt;

&lt;p&gt;postcss插件如同babel插件一样，有固定的格式&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default postcss.plugin(&apos;postcss-plugin-name&apos;, function (opts) {
  opts = opts || {};
  return function (root, result) {
    // 处理逻辑
  };
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注册个插件名，并获取插件配置参数opts&lt;/p&gt;

&lt;p&gt;返回值是个函数，这个函数主体是你的处理逻辑，有2个参数，一个是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt;，css ast的根节点。另一个是result，返回结果对象，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result.css&lt;/code&gt;，获得处理结果的css字符串，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result.message&lt;/code&gt;包含组件里创建的warnings和自定义信息，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result.warn()&lt;/code&gt;创造一个warning实例并将其加入到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result.message&lt;/code&gt;中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result.warnings()&lt;/code&gt;获得所有创建过的warning。&lt;/p&gt;

&lt;p&gt;注意组件的异常信息处理，不要直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console&lt;/code&gt;，因为一些 postcss 处理器会过滤掉&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console&lt;/code&gt;的输出导致异常信息丢失，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node.warn&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node.error&lt;/code&gt;创造&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CssSyntaxError &lt;/code&gt;实例，会自动带上源码中的位置信息帮助debug，加到异常信息队列里。&lt;/p&gt;

&lt;h4 id=&quot;直接调用postcss命名空间下的方法&quot;&gt;直接调用postcss命名空间下的方法&lt;/h4&gt;

&lt;p&gt;可以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postcss.parse&lt;/code&gt;来处理一段css文本，拿到css ast，然后进行处理，再通过调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toResult().css&lt;/code&gt;拿到处理后的css输出&lt;/p&gt;

&lt;h3 id=&quot;示例&quot;&gt;示例&lt;/h3&gt;

&lt;p&gt;写了个web页面的rem\px单位转成小程序wxss里的rpx单位的小插件示例：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/echizen/postcss-unit2rpx/blob/master/index.js&quot;&gt;https://github.com/echizen/postcss-unit2rpx/blob/master/index.js&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>使用jest+enzyme进行react项目测试 - debug篇</title>
   <link href="http://echizen.github.io/tech/2017/04-28-jest-debug"/>
   <updated>2017-04-28T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/jest-debug</id>
   <content type="html">
&lt;p&gt;这套环境是node服务在跑，测试代码也会出bug，也需要完善的调试环境。&lt;/p&gt;

&lt;p&gt;在经历了借助工具&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-inspector&lt;/code&gt;，但是太慢；node 7 自带的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node --inspect&lt;/code&gt;只能定位到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--debug-brk&lt;/code&gt;指定的断点处，自己设置的debugger断点无效；以及很长一段时间的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log&lt;/code&gt;大法。&lt;/p&gt;

&lt;p&gt;后来终于从另一个角度解决了问题，借助编辑器IDE的强大功能。使用vs code强大的调试功能，简直不能更好用。简直跟chrome调试手感没啥差别，连断点编辑功能都有~&lt;/p&gt;

&lt;p&gt;如果使用的不是vs code，请先下载编辑器： &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;https://code.visualstudio.com/&lt;/a&gt;。不要害怕换了不习惯，体验还是很好的。找个风和日丽的日子，有足够时间适应一下。官网有debuggger文档，但是可以不看，因为跟chrome调试操作差不多。&lt;/p&gt;

&lt;h3 id=&quot;配置&quot;&gt;配置&lt;/h3&gt;

&lt;p&gt;点击debugger的小虫子icon，进入debug模式后，点击顶部调试后的配置icon，项目根目录下的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode&lt;/code&gt;下会出现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch.json&lt;/code&gt;文件，这是debug的配置文件。&lt;/p&gt;

&lt;p&gt;jest的配置内容如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  &quot;version&quot;: &quot;0.2.0&quot;
  , &quot;configurations&quot;: [{
      &quot;type&quot;: &quot;node&quot;
      , &quot;request&quot;: &quot;launch&quot;
      , &quot;name&quot;: &quot;jest&quot;
      , &quot;program&quot;: &quot;${workspaceRoot}/node_modules/jest-cli/bin/jest.js&quot;
      , &quot;stopOnEntry&quot;: false
      , &quot;args&quot;: [&quot;--runInBand&quot;]
      , &quot;cwd&quot;: &quot;${workspaceRoot}&quot;
      , &quot;preLaunchTask&quot;: null
      , &quot;runtimeExecutable&quot;: null
      , &quot;runtimeArgs&quot;: [
        &quot;--nolazy&quot;
      ]
      , &quot;env&quot;: {
        &quot;NODE_ENV&quot;: &quot;development&quot;
      }
      , &quot;externalConsole&quot;: false
      , &quot;sourceMaps&quot;: false
      , &quot;outDir&quot;: null
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中有几点需要注意：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;“type”: “node”。指定是node类型文件调试&lt;/li&gt;
  &lt;li&gt;“program”: “${workspaceRoot}/node_modules/jest-cli/bin/jest.js”。指定了入口文件，在这里就是jest的入口文件，平时通过终端运行jest也调用的是这个脚本。&lt;/li&gt;
  &lt;li&gt;“args”: [”–runInBand”]。命令携带的参数，jest的特点是多进程并发运行不同测试案例，达到快速的效果。但是这样对调试来说是没法进行的。这个参数保证了使用一个进程运行所有代码。&lt;/li&gt;
  &lt;li&gt;“env”: 指定了运行时的一些环境变量&lt;/li&gt;
  &lt;li&gt;“sourceMaps”: 调试时，因为被调试的文件时拼接后且通过babel转的，所以部分运行文件是压缩状态的，使用这个可以方便查看。但是我没开启也是可以的，大部分要设断点的都是未压缩状态的代码&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些配置相当于运行脚本：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node --debug-brk=13671 --nolazy node_modules/jest-cli/bin/jest.js --runInBand&lt;/code&gt;。 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--debug-brk=num&lt;/code&gt;就是你设置的断点处，编辑器给你转成了脚本参数&lt;/p&gt;

&lt;h3 id=&quot;调试单个文件&quot;&gt;调试单个文件&lt;/h3&gt;

&lt;p&gt;上面的配置其实是运行了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest&lt;/code&gt;的命令，会一股脑把所有测试文件都跑一遍。实际场景中我们大多数是要调试某个测试文件。&lt;/p&gt;

&lt;p&gt;我们发现args是命令携带的参数，如果要运行某个测试文件，命令形如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest oneFileName.spec.js&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;所以配置一下args加个具体的文件参数就好啦~&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;args&quot;: [&quot;--runInBand&quot;, &quot;oneFileName.spec.js&quot;]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;之前一直带上了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--&lt;/code&gt;符，哭晕。。。&lt;/p&gt;

&lt;h3 id=&quot;jest-vscode-plugin&quot;&gt;jest vscode plugin&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/mlcdn/c45406/170424_4kg05d62h3bhf3kfh5418a0dkfli4_1018x328.png&quot; alt=&quot;http://s2.mogucdn.com/mlcdn/c45406/170424_4kg05d62h3bhf3kfh5418a0dkfli4_1018x328.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;就是这货，这货有以下几个作用：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;能监听文件变更，可配置默认在保存时运营测试文件，这样你能及时便捷的查看到运行结果。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;高亮snapshot文件(.snap)。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;jest语句关键词的自动补全&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;语法错误的行内显示&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;配置在vscode的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setting.json&lt;/code&gt;中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Automatically starting Jest for this project.
&quot;jest.autoEnable&quot;: true,

// The path to the Jest binary, or an npm command to run tests prefixed with `--` e.g. `npm test --`
&quot;jest.pathToJest&quot;: &quot;node_modules/.bin/jest&quot;,

// The path to your Jest configuration file
&quot;jest.pathToConfig&quot;: &quot;&quot;,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;基本不用动。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest.autoEnable&lt;/code&gt;是是否在文件变更时自动运行测试文件。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest.pathToJest&lt;/code&gt;指向jest的入口文件。当我们在package.json中配置了jest时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest.pathToConfig&lt;/code&gt;是不用填写的，插件会去读&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt;中的jest配置。&lt;/p&gt;

&lt;h3 id=&quot;调试面板&quot;&gt;调试面板&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/mlcdn/c45406/170424_3ib3b00eb8f48gbkb70be4cjajb8j_2438x1536.png&quot; alt=&quot;http://s2.mogucdn.com/mlcdn/c45406/170424_3ib3b00eb8f48gbkb70be4cjajb8j_2438x1536.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;左上方的绿色播放键是开始以调试模式运行脚本。&lt;/p&gt;

&lt;p&gt;左侧有变量区、监视区、函数调用堆栈、断点列表。&lt;/p&gt;

&lt;p&gt;中部文件上方悬浮的操作条有继续、单步跳过、单步调试、单步跳出、重启、停止等。&lt;/p&gt;

&lt;p&gt;文件中的断点光标右键可以编辑断点，设置条件断点。&lt;/p&gt;

&lt;p&gt;当然有一系列快捷键可以用，方便操作。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>组件生命周期测试 - jest+enzyme</title>
   <link href="http://echizen.github.io/tech/2017/04-24-component-lifycycle-test"/>
   <updated>2017-04-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/component-lifycycle-test</id>
   <content type="html">
&lt;p&gt;react的生命周期有：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;componentWillMount&lt;/li&gt;
  &lt;li&gt;componentDidMount&lt;/li&gt;
  &lt;li&gt;componentWillReceiveProps&lt;/li&gt;
  &lt;li&gt;shouldComponentUpdate&lt;/li&gt;
  &lt;li&gt;componentWillUpdate&lt;/li&gt;
  &lt;li&gt;componentDidUpdate&lt;/li&gt;
  &lt;li&gt;componentWillUnmount&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其中1、2、7都会在组件渲染及销毁时自动执行，通过断言&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(wrapper.state().XXX)&lt;/code&gt;即可测试。当然也可以通过enzyme的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount()&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unmount&lt;/code&gt;接口手动调用。&lt;/p&gt;

&lt;p&gt;5和6可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update()&lt;/code&gt;强制调用，然后依旧通过断言&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(wrapper.state().XXX)&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(wrapper.props().XXX&lt;/code&gt;测试。&lt;/p&gt;

&lt;p&gt;不过更正常的做法是通过调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setState()&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setProps()&lt;/code&gt;来引起变化，触发3、4、5、6，再通过断言&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(wrapper.state().XXX)&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(wrapper.props().XXX&lt;/code&gt;测试。&lt;/p&gt;

&lt;h2 id=&quot;生命周期中含有异步过程如何测试&quot;&gt;生命周期中含有异步过程如何测试&lt;/h2&gt;

&lt;p&gt;这才是个难点。我们经常会在componentDidMount、componentWillMount里进行些异步操作，譬如请求数据。&lt;/p&gt;

&lt;p&gt;目前没有很官网的做法，只有比较投机取巧的方式。&lt;/p&gt;

&lt;p&gt;首先我们需要mock掉请求，而不是真的去请求。然后创建个可控的异步内容做钩子。&lt;/p&gt;

&lt;p&gt;譬如我们有以下这个组件要测试，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentDidMount&lt;/code&gt;里包含了异步请求：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default class View extends React.Component {
    render() {
      &amp;lt;div&amp;gt;{this.state.name}&amp;lt;/div&amp;gt;
    }

    componentDidMount() {
      return fetch(`http://www.example.com/page/getData`)
        .then(response =&amp;gt; response.json())
        .then(res =&amp;gt; this.setState(res))
        .catch(err =&amp;gt; console.error(&apos;SERVER REQUEST FAILED: &apos; + err))

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们可以这样测试：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function flushPromises() {
    return new Promise(resolve =&amp;gt; setImmediate(resolve));
}

it(&apos;...&apos;, () =&amp;gt; {
    const response = {
        body: {
          name: &apos;example&apos;
        }
        , status: 200
      }
    fetchMock.get(`http://www.example.com/page/getData`,response);
    const wrapper = mount(&amp;lt;View/&amp;gt;);
    return flushPromises().then(() =&amp;gt; {
        expect(wrapper.state().name).toBe(response.body.name)
    });
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因为我们mock了异步请求，所以并不会真的依据网络情况要等一段时间才会有返回，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchMock&lt;/code&gt;转化了请求过程为异步过程，返回的是promise，因为调用时间是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount()&lt;/code&gt;后的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentDidMount&lt;/code&gt;，比我们调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flushPromises&lt;/code&gt;早，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flushPromises&lt;/code&gt;也是返回了个promise，实现的和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchMock&lt;/code&gt;一致，所以我们在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flushPromises.then()&lt;/code&gt;的状态下，请求的promise已经是resolve状态，处理函数已经完成，我们就可以顺利断言了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>动态插入的script脚本执行时间</title>
   <link href="http://echizen.github.io/tech/2017/04-22-script-exec"/>
   <updated>2017-04-22T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/script-exec</id>
   <content type="html">
&lt;p&gt;在一些场景我们会动态插入script标签加载js。&lt;/p&gt;

&lt;p&gt;譬如某个js文件不是很重要，并不是整个页面需要的脚本，可能只是某个功能需要的，这个功能可能是用户点击了某个按钮才触发，入口比较深。且和你页面本身的结构不同类，譬如你是基于react的页面，这个功能的js是jquery插件。这种js文件我一般采用动态加载方式引入。&lt;/p&gt;

&lt;p&gt;如果你用js动态插入script，那么它什么时候执行呢，如果插入多个script，且之间有依赖关系，是否先插入的script先执行呢？&lt;/p&gt;

&lt;p&gt;答案是： 不是&lt;/p&gt;

&lt;h2 id=&quot;demo案例&quot;&gt;demo案例&lt;/h2&gt;

&lt;h3 id=&quot;代码&quot;&gt;代码&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;js-exec.js&lt;/code&gt;:动态插入2个script到页面中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test1.js&lt;/code&gt;中定义了一个全局变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obj&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test2.js&lt;/code&gt;加载完成后的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onload&lt;/code&gt;事件中会去使用这个变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obj&lt;/code&gt;。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test1.js&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test2.js&lt;/code&gt;都在打印了信息方便查看执行顺序&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var getReadyForEditor = () =&amp;gt; {
  console.log(obj.foo)
}

var editorJs = document.createElement(&quot;script&quot;)
editorJs.src = &quot;./test1.js&quot;
document.body.appendChild(editorJs)

var editorJs2 = document.createElement(&quot;script&quot;)
editorJs2.src = &quot;./test2.js&quot;
editorJs2.onload = getReadyForEditor
document.body.appendChild(editorJs2) 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test1.js&lt;/code&gt;: 控制台打印1，并且定义了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obj&lt;/code&gt;变量&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;console.log(1)
var obj = {
  foo: &apos;foo&apos;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test2.js&lt;/code&gt;: 控制台打印2。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;console.log(2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;就是这么简洁的demo&lt;/p&gt;

&lt;h3 id=&quot;执行&quot;&gt;执行&lt;/h3&gt;

&lt;p&gt;通过不断刷新，发现大概率是按照test1、test2的顺序执行，但是也有一部分是先执行test2再执行test1:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/mlcdn/c45406/170420_8g0gk91ajad3b65lhcfibaj418dg4_1422x718.png&quot; alt=&quot;http://s2.mogucdn.com/mlcdn/c45406/170420_8g0gk91ajad3b65lhcfibaj418dg4_1422x718.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;由截图可见，网络请求顺序是按照script插入的顺序，先插入到dom的先请求，但是请求时间不一样，test2比test1的请求时间短，内容先返回。看现象貌似结论是：&lt;strong&gt;资源加载完成时执行，因此资源加载先完成的先执行&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;猜测&quot;&gt;猜测&lt;/h2&gt;

&lt;p&gt;我们都知道如果是非动态插入的script，是按照在html里出现的顺序执行的，但是现在动态插入的脚本，虽然先插入的script位于html的前面，后插入的在后面，但是执行顺序却没有按这个顺序来。&lt;/p&gt;

&lt;p&gt;是不是因为浏览器不知道在一个script标签插入后还有没有下一个要插入，所以没法按顺序执行呢？那么我们一次性插入这2个标签会怎样？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var getReadyForEditor = () =&amp;gt; {
  console.log(obj.foo)
}

var editorJs = document.createElement(&quot;script&quot;)
editorJs.src = &quot;./test1.js&quot;
// document.body.appendChild(editorJs)

var editorJs2 = document.createElement(&quot;script&quot;)
editorJs2.src = &quot;./test2.js&quot;
editorJs2.onload = getReadyForEditor
// document.body.appendChild(editorJs2) 

var docFrag = document.createDocumentFragment()
docFrag.appendChild(editorJs) // Note that this does NOT go to the DOM
docFrag.appendChild(editorJs2)
document.body.appendChild(docFrag) 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createDocumentFragment&lt;/code&gt;创造文档片段，然后一次插入，这样浏览器该知道顺序了吧。&lt;/p&gt;

&lt;p&gt;但是结果依旧没变。猜想错误！&lt;/p&gt;

&lt;h2 id=&quot;文档&quot;&gt;文档&lt;/h2&gt;

&lt;p&gt;猜不出来，只能找理论支撑了。先去翻了W3C规范，但是抱歉，实在没看明白，找到想要的答案：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/html5/scripting-1.html#attr-script-src&quot;&gt;https://www.w3.org/TR/html5/scripting-1.html#attr-script-src&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If the element has a src attribute, does not have an async attribute, and does not have the “force-async” flag set
The element must be added to the end of the list of scripts that will execute in order as soon as possible associated with the Document of the script element at the time the prepare a script algorithm started.
    The task that the networking task source places on the task queue once the fetching algorithm has completed must run the following steps:
    If the element is not now the first element in the list of    scripts that will execute in order as soon as possible to which it was added above, then mark the element as ready but abort these steps without executing the script yet.
    Execution: Execute the script block corresponding to the first script element in this list of scripts that will execute in order as soon as possible.
    Remove the first element from this list of scripts that will execute in order as soon as possible.
    If this list of scripts that will execute in order as soon as possible is still not empty and the first entry has already been marked as ready, then jump back to the step labeled execution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;然后翻MDN的文档：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;有这么一段：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;script-inserted scripts execute asynchronously in IE and WebKit, but synchronously in Opera and pre-4.0 Firefox. In Firefox 4.0, the async DOM property defaults to true for script-created scripts, so the default behavior matches the behavior of IE and WebKit. To request script-inserted external scripts be executed in the insertion order in browsers where the document.createElement(“script”).async evaluates to true (such as Firefox 4.0), set .async=false on the scripts you want to maintain order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;真相&quot;&gt;真相&lt;/h2&gt;

&lt;p&gt;这就是我要找的！&lt;/p&gt;

&lt;p&gt;原来是因为浏览器对动态插入的script标签，默认设置的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;。（各浏览器有区别）&lt;/p&gt;

&lt;p&gt;我们知道&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;作用的js脚本时没有顺序的，异步加载，加载后执行。&lt;/p&gt;

&lt;p&gt;因此特性，所以还有个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;是异步加载，按script在文档中的顺序执行。&lt;/p&gt;

&lt;p&gt;那我们的测试demo试一下，打印出来的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt;果真是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/mlcdn/c45406/170420_0bgkg16l2ljc3341444c96c32241h_632x188.png&quot; alt=&quot;http://s2.mogucdn.com/mlcdn/c45406/170420_0bgkg16l2ljc3341444c96c32241h_632x188.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;如何让动态插入的script标签按插入顺序执行&quot;&gt;如何让动态插入的script标签按插入顺序执行&lt;/h2&gt;

&lt;p&gt;既然问题出在async上，那么创建script标签时把他设置为false就好。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var editorJs = document.createElement(&quot;script&quot;)
editorJs.src = &quot;./test1.js&quot;
editorJs.async = false
document.body.appendChild(editorJs)

var editorJs2 = document.createElement(&quot;script&quot;)
editorJs2.src = &quot;./test2.js&quot;
editorJs2.onload = getReadyForEditor
editorJs2.async = false
document.body.appendChild(editorJs2) 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再观察，即使test2比test1先加载完，也会等待test1执行完在执行了~&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/mlcdn/c45406/170420_262g6cce4kd6b6ajg60d152fl64f0_1440x690.png&quot; alt=&quot;http://s2.mogucdn.com/mlcdn/c45406/170420_262g6cce4kd6b6ajg60d152fl64f0_1440x690.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;多说一句&quot;&gt;多说一句&lt;/h2&gt;

&lt;p&gt;这个问题的场景其实是不友好的。良好的编程，不应该再2个分离的文件里设置全局变量，让2个文件通信。&lt;/p&gt;

&lt;p&gt;所以会有浏览器端的AMD譬如requirejs去保证顺序和依赖。&lt;/p&gt;

&lt;p&gt;也有CMD或es6 module直接引入依赖文件的变量，再通过webpack等打包工具将他们压缩成一个脚本。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>web component漫谈</title>
   <link href="http://echizen.github.io/tech/2017/04-16-web-component"/>
   <updated>2017-04-16T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/web-component</id>
   <content type="html">
&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;组件化&lt;/li&gt;
  &lt;li&gt;隔离：样式、js脚本&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;内容&quot;&gt;内容&lt;/h2&gt;

&lt;p&gt;2011就有相关文档了&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Shadow DOM&lt;/li&gt;
  &lt;li&gt;Custom Elements&lt;/li&gt;
  &lt;li&gt;HTML Imports&lt;/li&gt;
  &lt;li&gt;HTML Templates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;原始的组件&quot;&gt;原始的组件&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://codepen.io/robdodson/pen/rCGvJ&quot;&gt;http://codepen.io/robdodson/pen/rCGvJ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/echizen/web-component-demo/blob/master/old/slider.html&quot;&gt;https://github.com/echizen/web-component-demo/blob/master/old/slider.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;需要写html、css、js代码。&lt;/p&gt;

&lt;p&gt;引用组件：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;最不便捷的方式：copy paste&lt;/li&gt;
  &lt;li&gt;使用js模板语言，通过js动态插入html，css单独引入或者通过js动态插入&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;web-components&quot;&gt;web components&lt;/h2&gt;

&lt;h3 id=&quot;template&quot;&gt;template&lt;/h3&gt;

&lt;p&gt;作用入名，模板标签，不会被浏览器展现成用户可见的内容，内部内容可以被后续使用。内部可以放置css、html、js&lt;/p&gt;

&lt;p&gt;譬如这个slider的template：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;style&amp;gt;
    * {
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      -ms-box-sizing: border-box;
      box-sizing: border-box;
    }
    
    #slider {
      max-width: 600px;
      text-align: center;
      margin: 0 auto;
    }
    
    #overflow {
      width: 100%;
      overflow: hidden;
    }
    
    #slides .inner {
      width: 400%;
    }
    
    #slides .inner {
      -webkit-transform: translateZ(0);
      -moz-transform: translateZ(0);
      -o-transform: translateZ(0);
      -ms-transform: translateZ(0);
      transform: translateZ(0);
    
      -webkit-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
      -moz-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
      -o-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
      -ms-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
      transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
    
      -webkit-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
      -moz-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
      -o-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
      -ms-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
      transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
    }
    
    #slides img {
      width: 25%;
      float: left;
    }
    
    #slide1:checked ~ #slides .inner {
      margin-left: 0;
    }
    
    #slide2:checked ~ #slides .inner {
      margin-left: -100%;
    }
    
    #slide3:checked ~ #slides .inner {
      margin-left: -200%;
    }
    
    #slide4:checked ~ #slides .inner {
      margin-left: -300%;
    }
    
    input[type=&quot;radio&quot;] {
      display: none;
    }
    
    label {
      background: #CCC;
      display: inline-block;
      cursor: pointer;
      width: 10px;
      height: 10px;
      border-radius: 5px;
    }
    
    #slide1:checked ~ label[for=&quot;slide1&quot;],
    #slide2:checked ~ label[for=&quot;slide2&quot;],
    #slide3:checked ~ label[for=&quot;slide3&quot;],
    #slide4:checked ~ label[for=&quot;slide4&quot;] {
      background: #333;
    }
    #slides ::content img {
      width: 25%;
      float: left;
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;div id=&quot;slider&quot;&amp;gt;
    &amp;lt;input checked=&quot;&quot; type=&quot;radio&quot; name=&quot;slider&quot; id=&quot;slide1&quot; selected=&quot;false&quot;&amp;gt;
    &amp;lt;input type=&quot;radio&quot; name=&quot;slider&quot; id=&quot;slide2&quot; selected=&quot;false&quot;&amp;gt;
    &amp;lt;input type=&quot;radio&quot; name=&quot;slider&quot; id=&quot;slide3&quot; selected=&quot;false&quot;&amp;gt;
    &amp;lt;input type=&quot;radio&quot; name=&quot;slider&quot; id=&quot;slide4&quot; selected=&quot;false&quot;&amp;gt;
    &amp;lt;div id=&quot;slides&quot;&amp;gt;
      &amp;lt;div id=&quot;overflow&quot;&amp;gt;
        &amp;lt;div class=&quot;inner&quot;&amp;gt;
          &amp;lt;content select=&quot;img&quot;&amp;gt;&amp;lt;/content&amp;gt;
        &amp;lt;/div&amp;gt; &amp;lt;!-- .inner --&amp;gt;
      &amp;lt;/div&amp;gt; &amp;lt;!-- #overflow --&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;label for=&quot;slide1&quot;&amp;gt;&amp;lt;/label&amp;gt;
    &amp;lt;label for=&quot;slide2&quot;&amp;gt;&amp;lt;/label&amp;gt;
    &amp;lt;label for=&quot;slide3&quot;&amp;gt;&amp;lt;/label&amp;gt;
    &amp;lt;label for=&quot;slide4&quot;&amp;gt;&amp;lt;/label&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;shadow-dom&quot;&gt;Shadow DOM&lt;/h3&gt;

&lt;p&gt;如何将template的内容渲染到页面上呢，就需要借助shadow dom。&lt;/p&gt;

&lt;p&gt;shadow dom的内部的内容默认不会展现在chrome的element里，需要在settings中勾选’Show user agent shadow DOM’。&lt;/p&gt;

&lt;p&gt;想想我们的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;video&amp;gt;&lt;/code&gt;标签，在element里只能看到这个标签，但是实际上展示的界面有播放键、进度条，这些元素都被隐藏了，其实都是shadow dom。
&lt;a href=&quot;http://www.bilibili.com/video/av9606649/&quot;&gt;http://www.bilibili.com/video/av9606649/&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Add the template to the Shadow DOM
var tmpl = document.querySelector(&apos;template&apos;);
var host = document.querySelector(&apos;.img-slider&apos;);
var root = host.createShadowRoot();
root.appendChild(document.importNode(tmpl.content, true));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Shadow Host： createShadowRoot方法作用的元素，是能被用户在元素审查器里能看到的元素，也是shadow dom插入的父元素&lt;/li&gt;
  &lt;li&gt;Shadow Root：createShadowRoot方法返回的dom片段和它的子元素，对用户隐藏，但是是浏览器实际渲染的元素。&lt;/li&gt;
  &lt;li&gt;Shadow Boundary：能够实现私有作用域，得益于这个标准，Shadow Root内的html、css都被Shadow Boundary保护为私有状态，与外届环境隔离。（其实还是有接口可以通信的）&lt;/li&gt;
  &lt;li&gt;Insertion Points：通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;content&amp;gt;&lt;/code&gt;，我们可以把外界的内容插入到shadow DOM中，譬如slider组件内的img内容应该由用户自定义，就像给组件传参一样。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;content&amp;gt;&lt;/code&gt;标签使用css选择器来选择来自shadow host和外部的元素插入到shadow DOM中。（但是我发现去除&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select=&quot;img&quot;&lt;/code&gt;这个选择器在chrome 57下依旧能工作）。其实仅通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;content&amp;gt;&lt;/code&gt;这个标签扩展性不足，没法处理多种类型多个位置的子元素的情况，所以还有更强大的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slot&lt;/code&gt;规范：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;譬如模板：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;template id=&quot;contact-template&quot;&amp;gt;
  &amp;lt;b&amp;gt;Name&amp;lt;/b&amp;gt;: &amp;lt;slot name=&quot;fullName&quot;&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;br&amp;gt;
  &amp;lt;b&amp;gt;Email&amp;lt;/b&amp;gt;: &amp;lt;slot name=&quot;email&quot;&amp;gt;Unknown&amp;lt;/slot&amp;gt;&amp;lt;br&amp;gt;
  &amp;lt;b&amp;gt;Address&amp;lt;/b&amp;gt;: &amp;lt;slot name=&quot;address&quot;&amp;gt;Unknown&amp;lt;/slot&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;ul id=&quot;contacts&quot;&amp;gt;
  &amp;lt;li&amp;gt;
    &amp;lt;span slot=&quot;fullName&quot;&amp;gt;Commit Queue&amp;lt;/span&amp;gt;
    (&amp;lt;a slot=&quot;email&quot; href=&quot;mailto:commit-queue@webkit.org&quot;&amp;gt;commit-queue@webkit.org&amp;lt;/a&amp;gt;)&amp;lt;br&amp;gt;
    &amp;lt;span slot=&quot;address&quot;&amp;gt;One Infinite Loop, Cupertino, CA 95014&amp;lt;/span&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最终渲染：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;ul id=&quot;contacts&quot;&amp;gt;
  &amp;lt;li&amp;gt;
    &amp;lt;!--shadow-root-start--&amp;gt;
    &amp;lt;b&amp;gt;Name&amp;lt;/b&amp;gt;:
    &amp;lt;slot name=&quot;fullName&quot;&amp;gt;
      &amp;lt;!--slot-content-start--&amp;gt;
        &amp;lt;span slot=&quot;fullName&quot;&amp;gt;Commit Queue&amp;lt;/span&amp;gt;
      &amp;lt;!--slot-content-end--&amp;gt;
    &amp;lt;/slot&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;b&amp;gt;Email&amp;lt;/b&amp;gt;:
    &amp;lt;slot name=&quot;email&quot;&amp;gt;
      &amp;lt;!--slot-content-start--&amp;gt;
        &amp;lt;a slot=&quot;email&quot; href=&quot;mailto:commit-queue@webkit.org&quot;&amp;gt;commit-queue@webkit.org&amp;lt;/a&amp;gt;
      &amp;lt;!--slot-content-end--&amp;gt;
    &amp;lt;/slot&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;b&amp;gt;Address&amp;lt;/b&amp;gt;:
    &amp;lt;slot name=&quot;address&quot;&amp;gt;
      &amp;lt;!--slot-content-start--&amp;gt;
        &amp;lt;span slot=&quot;address&quot;&amp;gt;One Infinite Loop, Cupertino, CA 95014&amp;lt;/span&amp;gt;
      &amp;lt;!--slot-content-end--&amp;gt;
    &amp;lt;/slot&amp;gt;
    &amp;lt;!--shadow-root-end--&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;custom-elements&quot;&gt;Custom Elements&lt;/h3&gt;

&lt;p&gt;接下来就是要定义自定义标签元素，将我们的组件封装成一个含义丰富的标签，供使用方调用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var tmpl = document.querySelector(&apos;template&apos;);

// Create a prototype for a new element that extends HTMLElement
var ImgSliderProto = Object.create(HTMLElement.prototype);

// Setup our Shadow DOM and clone the template
ImgSliderProto.createdCallback = function() {
  var root = this.createShadowRoot();
  root.appendChild(document.importNode(tmpl.content, true));
};

// Register our new element
var ImgSlider = document.registerElement(&apos;img-slider&apos;, {
  prototype: ImgSliderProto
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Custom Elements有2点要求：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;名字含有连接符&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;，用来和原生标签区分&lt;/li&gt;
  &lt;li&gt;必须继承HTMLElement的prototype&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;html-import&quot;&gt;html import&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;import&quot; href=&quot;myfile.html&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;chrome 49开始已支持。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;slider&amp;lt;/title&amp;gt;
  &amp;lt;link rel=&quot;import&quot; href=&quot;./slider.html&quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;

    var sliderDom = document.querySelector(&apos;link[rel=&quot;import&quot;]&apos;).import
    // // Grab our template full of slider markup and styles
    var tmpl = sliderDom.querySelector(&apos;template&apos;);

    // Create a prototype for a new element that extends HTMLElement
    var ImgSliderProto = Object.create(HTMLElement.prototype);

    // Setup our Shadow DOM and clone the template
    ImgSliderProto.createdCallback = function() {
      var root = this.createShadowRoot();
      root.appendChild(document.importNode(tmpl.content, true));
    };

    // Register our new element
    var ImgSlider = document.registerElement(&apos;img-slider&apos;, {
      prototype: ImgSliderProto
    });
  &amp;lt;/script&amp;gt;

    &amp;lt;img-slider&amp;gt;
      &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/rock.jpg&quot; alt=&quot;an interesting rock&quot;&amp;gt;
      &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/grooves.jpg&quot; alt=&quot;some neat grooves&quot;&amp;gt;
      &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/arch.jpg&quot; alt=&quot;a rock arch&quot;&amp;gt;
      &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/sunset.jpg&quot; alt=&quot;a dramatic sunset&quot;&amp;gt;
    &amp;lt;/img-slider&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;示例&quot;&gt;示例&lt;/h2&gt;

&lt;p&gt;完整demo：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/echizen/web-component-demo/blob/master/new-web-components/index.html&quot;&gt;https://github.com/echizen/web-component-demo/blob/master/new-web-components/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/echizen/web-component-demo/blob/master/new-web-components/slider-component.html&quot;&gt;https://github.com/echizen/web-component-demo/blob/master/new-web-components/slider-component.html&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;调用&quot;&gt;调用&lt;/h2&gt;

&lt;p&gt;相当的简洁&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;img-slider&amp;gt;
  &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/rock.jpg&quot; alt=&quot;an interesting rock&quot;&amp;gt;
  &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/grooves.jpg&quot; alt=&quot;some neat grooves&quot;&amp;gt;
  &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/arch.jpg&quot; alt=&quot;a rock arch&quot;&amp;gt;
  &amp;lt;img src=&quot;https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/sunset.jpg&quot; alt=&quot;a dramatic sunset&quot;&amp;gt;
&amp;lt;/img-slider&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;生命周期&quot;&gt;生命周期&lt;/h2&gt;

&lt;p&gt;在这个 API 的基础上，Web Components 标准提供了一系列控制自定义元素的方法。我们来一一看下：&lt;/p&gt;

&lt;p&gt;一个自定义元素会经历以下这些生命周期：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;注册前创建&lt;/li&gt;
  &lt;li&gt;注册自定义元素定义&lt;/li&gt;
  &lt;li&gt;在注册后创建元素实例&lt;/li&gt;
  &lt;li&gt;元素插入到 document 中&lt;/li&gt;
  &lt;li&gt;元素从 document 中移除&lt;/li&gt;
  &lt;li&gt;元素的属性变化时&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个是很重要的内容，开发者可以在注册新的自定义元素时指定对应的生命周期回调来为自定义元素添加各种自定义的行为，这些生命周期回调包括了：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;createdCallback 自定义元素注册后，在实例化之后会调用，通常多用于做元素的初始化，如插入子元素，绑定事件等。&lt;/li&gt;
  &lt;li&gt;attachedCallback 元素插入到 document 时触发。&lt;/li&gt;
  &lt;li&gt;detachedCallback 元素从 document 中移除时触发，可能会用于做类似 destroy 之类的事情。&lt;/li&gt;
  &lt;li&gt;attributeChangedCallback 元素属性变化时触发，可以用于从外到内的通信。外部通过修改元素的属性来让内部获取相关的数据并且执行对应的操作。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;兼容性&quot;&gt;兼容性&lt;/h2&gt;

&lt;p&gt;兼容性不是很乐观，5年过去了，但是只有chrome和opera实现的最好，其他浏览器还是没有开始支持。&lt;/p&gt;

&lt;p&gt;Firefox默认禁用Web Components。启用的步骤是，前往 about:config 页面，注意取消出现的所有警告，搜索名为 dom.webcomponents.enabled 的配置，设置为 true。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/mlcdn/c45406/170410_72a8866g7kc8f6dg3ke97eal3jfa0_2236x1076.png&quot; alt=&quot;http://s2.mogucdn.com/mlcdn/c45406/170410_72a8866g7kc8f6dg3ke97eal3jfa0_2236x1076.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/mlcdn/c45406/170410_13ei0agaj8acf1g49bl81ae41ibad_2232x936.png&quot; alt=&quot;http://s2.mogucdn.com/mlcdn/c45406/170410_13ei0agaj8acf1g49bl81ae41ibad_2232x936.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是Mozilla 和 Google 开发了强大的polyfill库，来实现主流浏览器的支持：&lt;a href=&quot;https://www.polymer-project.org/&quot;&gt;https://www.polymer-project.org/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;问题&quot;&gt;问题&lt;/h2&gt;

&lt;p&gt;为啥发展的这么慢？虽然已被w3c指定为标准，但是只有chrome和opera支持的最好，5年过去了，其他浏览器还是没有开始真正支持，是否被抛弃&lt;/p&gt;

&lt;h1 id=&quot;参考&quot;&gt;参考&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/modular-future-web-components/&quot;&gt;https://css-tricks.com/modular-future-web-components/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/w3c/webcomponents&quot;&gt;https://github.com/w3c/webcomponents&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.html5rocks.com/zh/tutorials/webcomponents/imports/&quot;&gt;https://www.html5rocks.com/zh/tutorials/webcomponents/imports/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以下我还未认真看：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/Web_Components&quot;&gt;https://developer.mozilla.org/zh-CN/docs/Web/Web_Components&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000006745770&quot;&gt;https://segmentfault.com/a/1190000006745770&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>react合成事件和原生事件的阻止冒泡</title>
   <link href="http://echizen.github.io/tech/2017/04-02-reactjquery-event-system"/>
   <updated>2017-04-02T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/reactjquery-event-system</id>
   <content type="html">
&lt;p&gt;以下材料多来源于小伙伴们讨论时给出。&lt;/p&gt;

&lt;p&gt;我们知道，react拥有自己的事件系统，所使用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SyntheticEvent&lt;/code&gt;（合成事件）是和原生事件有区别的。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Your event handlers will be passed instances of SyntheticEvent, a cross-browser wrapper around the browser’s native event. It has the same interface as the browser’s native event, including stopPropagation() and preventDefault(), except the events work identically across all browsers.
If you find that you need the underlying browser event for some reason, simply use the nativeEvent attribute to get it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://facebook.github.io/react/docs/events.html&quot;&gt;https://facebook.github.io/react/docs/events.html&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;由阻止事件冒泡引发的问题&quot;&gt;由阻止事件冒泡引发的问题&lt;/h2&gt;

&lt;p&gt;合成事件不能阻止原生事件: &lt;a href=&quot;https://jsbin.com/veqofetoni/edit?html,js,output&quot;&gt;https://jsbin.com/veqofetoni/edit?html,js,output&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原生事件可以阻止合成事件: &lt;a href=&quot;https://jsbin.com/paqawokeho/edit?html,js,console,output&quot;&gt;https://jsbin.com/paqawokeho/edit?html,js,console,output&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;合成事件不能阻止原生事件原生事件可以阻止合成事件&quot;&gt;合成事件不能阻止原生事件，原生事件可以阻止合成事件&lt;/h2&gt;

&lt;p&gt;react 和jQuery 一样，都是封装了自己的事件系统。 react 默认事件代理的方式，实际上没有任何冒泡的过程，需要程序自己模拟冒泡的操作。&lt;/p&gt;

&lt;p&gt;在jQuery 和React 中事件代理都会自己维护一个从下到上的  Queue的监听队列，一个元素事件触发时，会从下往上执行这个队列中的监听函数，模拟冒泡的过程。&lt;/p&gt;

&lt;p&gt;另外React是维护自己的事件队列， React 的 stopPropagation 只会处理自己的队列冒泡，因为代理到最外层节点上如document，真正点击的节点并触发任何事件，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event.nativeEvent.stopPropagation()&lt;/code&gt; 实际上是在最外层节点上调用了原生的 stopPropagation， 只组织了body的冒泡。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;所以在react事件回调函数中调用stopPropagation，react自己维护的事件队列里阻止了余下队列里回调函数的执行，进而成功阻止了react合成事件的冒泡，但是在调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event.nativeEvent.stopPropagation&lt;/code&gt;时，由于nativeEvent是原生事件对象，react是将事件代理到在最外层元素上的，所以调用这个只能阻止最外层元素如document上的事件冒泡，且由于只能阻止当前事件的当前监听器冒泡执行，也就是react合成事件的冒泡，几乎没有实际意义。调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event.nativeEvent.stopImmediatePropagation&lt;/code&gt;，由于其能组织该元素绑定的后序相同类型事件的监听函数的执行，所以对于代理到document上的原生事件也会被组织执行，达到了组织合成事件和原生事件冒泡的作用，但是这个要求是大家都是事件代理到最外层元素如document上&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;但是如果用原生的事件的 stopPropagation， 会阻止事件向上冒泡到最外层节点，这样React的事件代理就无法工作了，最外层节点接受不到冒泡上来的事件，所以原生的stopPropagation 可以阻止React的 事件，但是原生事件也必须绑在作用元素上，而不是通过事件代理机制。&lt;/p&gt;

&lt;h2 id=&quot;react事件回调函数中event上的阻止冒泡作用&quot;&gt;react事件回调函数中event上的阻止冒泡作用&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event.stopPropagation&lt;/code&gt;：在react事件回调函数中调用能阻止react合成事件的冒泡&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event.nativeEvent.stopPropagation&lt;/code&gt;： 基本没啥实际作用，阻止的是代理到根元素（如document）的事件。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event.nativeEvent.stopImmediatePropagation&lt;/code&gt;： 阻止调用相同事件的其他侦听器，除了该事件的冒泡行为被阻止之外(event.stopPropagation方法的作用),该元素绑定的后序相同类型事件的监听函数的执行也将被阻止.所以可以阻止和react一样代理在document上的事件，如jquery中的
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(document).on(&apos;click&apos;, function(){});&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;源码解读&quot;&gt;源码解读：&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/b1b4a2fb252f26fe10d29ba60d85ff89a85ff3ec/src/renderers/shared/shared/event/SyntheticEvent.js#L136-L153&quot;&gt;https://github.com/facebook/react/blob/b1b4a2fb252f26fe10d29ba60d85ff89a85ff3ec/src/renderers/shared/shared/event/SyntheticEvent.js#L136-L153&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/b1b4a2fb252f26fe10d29ba60d85ff89a85ff3ec/src/renderers/shared/shared/event/EventPluginUtils.js#L101-L125&quot;&gt;https://github.com/facebook/react/blob/b1b4a2fb252f26fe10d29ba60d85ff89a85ff3ec/src/renderers/shared/shared/event/EventPluginUtils.js#L101-L125&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/a749f4fb63657a3af1502047a5af876ebabb0e64/src/renderers/shared/shared/event/EventPropagators.js#L63-L88&quot;&gt;https://github.com/facebook/react/blob/a749f4fb63657a3af1502047a5af876ebabb0e64/src/renderers/shared/shared/event/EventPropagators.js#L63-L88&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/b1b4a2fb252f26fe10d29ba60d85ff89a85ff3ec/src/renderers/shared/shared/ReactTreeTraversal.js#L97-L110&quot;&gt;https://github.com/facebook/react/blob/b1b4a2fb252f26fe10d29ba60d85ff89a85ff3ec/src/renderers/shared/shared/ReactTreeTraversal.js#L97-L110&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;所以在使用过程中，一定要清晰意识到原生事件和合成事件，分开管理。&lt;/p&gt;

&lt;p&gt;react合成事件的stopPropagation只对react合成事件有效，可能干扰到代理在最外层元素上的其他事件系统的事件。&lt;/p&gt;

&lt;p&gt;原生事件中非代理到最外层元素的事件模式调用stopPropagation会组织react合成事件的冒泡。另外，原生事件的监听需要在组件销毁时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentDidUnmount&lt;/code&gt;里去除监听，以免发生内存泄露。&lt;/p&gt;

&lt;p&gt;参考：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://dlinau.wordpress.com/2015/09/16/avoid-mixing-reacts-event-system-with-native-dom-event-handling/&quot;&gt;https://dlinau.wordpress.com/2015/09/16/avoid-mixing-reacts-event-system-with-native-dom-event-handling/&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>for in 循环js对象的顺序</title>
   <link href="http://echizen.github.io/tech/2017/03-12-js-object-forin-order"/>
   <updated>2017-03-12T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/js-object-forin-order</id>
   <content type="html">
&lt;p&gt;猜猜以下内容的结果&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var obj = {
  city: &quot;Beijing&quot;,
  12: 12,
  7: 7,
  0: 0,
  &quot;-2&quot;: -2,
  &quot;age&quot;: 15,
  &quot;-3.5&quot;: -3.5,
  7.7: 7.7,
  _: &quot;___&quot;,
  online: true,
  3: 3,
  &quot;23&quot;: &quot;23&quot;,
  &quot;44&quot;: 44
}

for(key in obj){
  console.log(key)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;chrome&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/p2/170312/114994688_3ah91hefjk43la5abl4979gg6bc9g_400x1008.png&quot; alt=&quot;http://s2.mogucdn.com/p2/170312/114994688_3ah91hefjk43la5abl4979gg6bc9g_400x1008.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;safari&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/p2/170312/114994688_141ih5lj2hja279cgk7hhd0i4i116_388x1092.png&quot; alt=&quot;http://s2.mogucdn.com/p2/170312/114994688_141ih5lj2hja279cgk7hhd0i4i116_388x1092.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;firefox&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/p2/170312/114994688_6akjbaej9g3bc33da6fledljha18l_1224x596.png&quot; alt=&quot;http://s2.mogucdn.com/p2/170312/114994688_6akjbaej9g3bc33da6fledljha18l_1224x596.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;IE&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/p2/170312/114994688_4jh5f28f2ef5e15kehi3585j1j8he_336x1022.png&quot; alt=&quot;http://s2.mogucdn.com/p2/170312/114994688_4jh5f28f2ef5e15kehi3585j1j8he_336x1022.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;标准&quot;&gt;标准&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;根据 ECMA-262（ECMAScript）第三版中描述，for-in 语句的属性遍历的顺序是&lt;strong&gt;由对象定义时属性的书写顺序决定的&lt;/strong&gt;。&lt;a href=&quot;http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf&quot;&gt;ECMA-262 3rd Edition&lt;/a&gt;【参考12.6.4 The for-in Statement。】&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://s17.mogucdn.com/p1/170312/idid_ifqwkm3fmuytkyjzmuzdambqgyyde_1980x1392.png&quot; alt=&quot;http://s17.mogucdn.com/p1/170312/idid_ifqwkm3fmuytkyjzmuzdambqgyyde_1980x1392.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;ECMA-262（ECMAScript）第五版规范中，对 for-in 语句的遍历机制又做了调整，&lt;strong&gt;属性遍历的顺序是没有被规定的&lt;/strong&gt;。&lt;a href=&quot;http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf&quot;&gt;ECMA-262 5rd Edition&lt;/a&gt;
【参考13.7.5 The for‑in and for‑of Statements】&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;http://s16.mogucdn.com/p1/170312/idid_ifqtknrqgmytkyjzmuzdambqgyyde_2004x1406.png&quot; alt=&quot;http://s16.mogucdn.com/p1/170312/idid_ifqtknrqgmytkyjzmuzdambqgyyde_2004x1406.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;各大浏览器的行为&quot;&gt;各大浏览器的行为&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Chrome、 sarari、 firebox、 Opera 中使用 for-in     语句遍历对象属性时会遵循一个规律，它们会先提取所有 key 的 parseFloat 值为&lt;strong&gt;非负整数&lt;/strong&gt;的属性， 然后&lt;strong&gt;根据数字顺序&lt;/strong&gt;对属性排序首先遍历出来，然后按照&lt;strong&gt;对象定义的顺序遍历余下的所有属性&lt;/strong&gt;。order is preserved with the major exception of keys like “7” that parse as integers and are handled differently by Chrome/V8&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;其它浏览器则完全按照对象定义的顺序遍历属性。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chrome等现代浏览器较新版本 的 JavaScript 解析引擎遵循的是新版 ECMA-262 第五版规范（看到11年stackoverflow上的讨论）。因此，使用 for-in 语句遍历对象属性时遍历书序并非属性构建顺序。而 IE6 IE7 IE8 Firefox Safari 的 JavaScript 解析引擎遵循的是较老的 ECMA-262 第三版规范，属性遍历顺序由属性构建的顺序决定。&lt;/p&gt;

&lt;h2 id=&quot;有顺序的遍历&quot;&gt;有顺序的遍历&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;4.3.3 Object
    An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;for-in、object.keys(objectX).map 语句无法保证对象的遍历顺序，应尽量避免编写依赖对象属性顺序的代码。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;使用数组&lt;/li&gt;
  &lt;li&gt;使用es6 的Map类型:
    &lt;blockquote&gt;
      &lt;p&gt;A Map iterates its elements in insertion order, whereas iteration order is not specified for Objects.&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;譬如之前的例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var myMap = new Map();
myMap.set(&quot;city&quot;, &quot;Beijing&quot;);
myMap.set(12, 12);
myMap.set(7, 7);
myMap.set(0, 0);
myMap.set(&quot;-2&quot;, -2);
myMap.set(&quot;age&quot;, 15);
myMap.set(&quot;-3.5&quot;, -3.5);
myMap.set(7.7, 7.7);
myMap.set(&quot;_&quot;, &quot;___&quot;);
myMap.set(&quot;online&quot;, true);
myMap.set(3, 3);
myMap.set(&quot;23&quot;, &quot;23&quot;);
myMap.set(&quot;44&quot;, 44);
//不支持for in 遍历
for (var [key, value] of myMap) {
  console.log(key + &apos; = &apos; + value);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/p2/170312/114994688_44h919l168kg3d4gfc6k4ff138gk9_548x942.png&quot; alt=&quot;http://s2.mogucdn.com/p2/170312/114994688_44h919l168kg3d4gfc6k4ff138gk9_548x942.png&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>react 项目中的css处理</title>
   <link href="http://echizen.github.io/tech/2017/03-09-css-in-react"/>
   <updated>2017-03-09T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/css-in-react</id>
   <content type="html">
&lt;h2 id=&quot;css加载&quot;&gt;css加载&lt;/h2&gt;

&lt;h3 id=&quot;style-loader&quot;&gt;style-loader&lt;/h3&gt;

&lt;p&gt;一般性配置：&lt;/p&gt;

&lt;p&gt;js file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import &apos;./message.css&apos;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;webpack:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; module: {
    loaders: [
        {
            test: /\.(scss|css)$/,
            loaders: [&apos;style&apos;, &apos;css&apos;, &apos;postcss&apos;]
        }
	 ]
},

postcss: function () {
    return [require(&apos;autoprefixer&apos;), require(&apos;precss&apos;), require(&apos;postcss-calc&apos;)];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;先引入postcss-loader处理，譬如precss将sass转成css，autoprefixer添加兼容性前缀。再由css-loader加载require的样式文件，最后style-loader将这些css转变成会插入到页面&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;style&amp;gt;&lt;/code&gt;里的样式代码。&lt;/p&gt;

&lt;p&gt;最终&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;标签下会有非常多的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;style&amp;gt;&lt;/code&gt;标签，来安放这些css文件里的样式代码。&lt;/p&gt;

&lt;p&gt;style-loader是每次遇到import或者require css类文件的语句时，就向head里添加一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;style&lt;/code&gt;标签，来安放css代码。这样频繁操作dom很低效，且新的样式规则的插入会导致浏览器频繁的重绘。并且一开始没有css样式，这样一点点插入，首屏渲染时间也会加长&lt;/p&gt;

&lt;p&gt;对于我这种之前在项目中都是单独在html文件里通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt;引入css文件的人来说，这种方式简直逆天。&lt;/p&gt;

&lt;p&gt;不过后来我用Timeline查看了下各项性能数据指标，之能说理论如前所述，但是对于pc页面在强大的chrome下的渲染来看，这种性能损耗差别比较小。&lt;/p&gt;

&lt;h3 id=&quot;extract-text-webpack-plugin&quot;&gt;extract-text-webpack-plugin&lt;/h3&gt;

&lt;p&gt;anyway，我还是要说一说可以自动打包css成单独文件的extract-text-webpack-plugin，这个不止是用在react项目中，用在vue\angular都可以：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/webpack-1/README.md&quot;&gt;https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/webpack-1/README.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果希望样式通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt;引入，而不是放在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;style&amp;gt;&lt;/code&gt;标签内。使用extract-text-webpack-plugin插件可以帮我们独立出css文件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var ExtractTextPlugin = require(&quot;extract-text-webpack-plugin&quot;); 
module.exports = {    
// ...   
  module: {         
    loaders: [                        
        {
          test: /\.css$/, loader: ExtractTextPlugin.extract(&quot;style-loader&quot;, &quot;css-loader&quot;)
        },
        {
            test: /\.less$/,
            loader: ExtractTextPlugin.extract(&quot;style-loader&quot;, &quot;css-loader!less-loader&quot;)
        }
  },     
  plugins: [         
    new ExtractTextPlugin(&quot;[name].css&quot;)     
  ] 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ExtractTextPlugin.extract可以有三个参数。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;第一个参数是可选参数，传入一个loader，当css样式没有被抽取的时候可以使用该loader。&lt;/li&gt;
  &lt;li&gt;第二个参数则是用于编译解析的css文件loader，很明显这个是必须传入的，就像上述例子的css-loader。&lt;/li&gt;
  &lt;li&gt;第三个参数是一些额外的备选项。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;插件也支持所有独立样式打包成一个css文件。增加多一个参数，指定allChunks为true。&lt;/p&gt;

&lt;p&gt;new ExtractTextPlugin(“style.css”, {allChunks: true})&lt;/p&gt;

&lt;p&gt;引入时，只需要在html里引入合并后的css文件，注意并不需要在js里再require或者import这个文件了。&lt;/p&gt;

&lt;h4 id=&quot;优点&quot;&gt;优点&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;css是单独文件，放到head里优先加载生效&lt;/li&gt;
  &lt;li&gt;js文件会变小&lt;/li&gt;
  &lt;li&gt;减少了js操作dom插入style标签带来的损耗&lt;/li&gt;
  &lt;li&gt;不频繁修改的css能够利用缓存获取&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;css-模块化&quot;&gt;css 模块化&lt;/h2&gt;

&lt;p&gt;那么在模块化的项目开发里如何处理css呢？&lt;/p&gt;

&lt;p&gt;项目中遇到很多css相关的头疼的问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;样式覆盖，多人协作更严重，css样式默认是全局作用域的。&lt;/li&gt;
  &lt;li&gt;为了尽量减少模块样式相互覆盖，我们可能采用更深层级的样式选择器，这样css渲染效率变低了，而且权值这个没法保证，可能有些class权值一致，因为引入先后顺序，后面的定义生效了，但是下次打包css顺序变了，还是会出现问题，风险存在。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;看看业内处理这个问题主要有2种方案：1.css in js. 2.css module&lt;/p&gt;

&lt;h3 id=&quot;css-in-js&quot;&gt;css in js&lt;/h3&gt;

&lt;p&gt;彻底抛弃 CSS，使用 JS 或 JSON 来写样式。Radium，jsxstyle，react-style 属于这一类。&lt;/p&gt;

&lt;h4 id=&quot;react-style&quot;&gt;react-style&lt;/h4&gt;

&lt;p&gt;譬如&lt;a href=&quot;https://github.com/js-next/react-style&quot;&gt;react-style&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;定义样式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var StyleSheet = require(&apos;react-style&apos;) 
var styles = StyleSheet.create({
    foo: {
        color: &apos;red&apos;,       
        backgroundColor: &apos;white&apos;     
      } 
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;引入样式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var React = require(&apos;react&apos;) 
class HelloWorld extends React.Component{   
  render() {     
    var dynamicStyles = {color: this.props.color}     
    return &amp;lt;div styles={[styles.foo, dynamicStyles]}&amp;gt;Hello, world!&amp;lt;/div&amp;gt;   
  } 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最终样式会转成dom的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline style&lt;/code&gt;。&lt;/p&gt;

&lt;h4 id=&quot;组件js代码里自行处理&quot;&gt;组件js代码里自行处理&lt;/h4&gt;

&lt;p&gt;还有一种不能算css 模块化的内容，但是是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;css in js&lt;/code&gt;的一种做法:&lt;/p&gt;

&lt;p&gt;譬如material-ui，直接在组件内定义内容为css样式的变量，然后将其运用在组件的style标签上。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/callemall/material-ui/blob/master/src/Checkbox/Checkbox.js&quot;&gt;https://github.com/callemall/material-ui/blob/master/src/Checkbox/Checkbox.js&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;radium&quot;&gt;radium&lt;/h4&gt;

&lt;p&gt;radium相对于material-ui那种自行处理，做了一些封装，来提高开发效率&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/FormidableLabs/radium&quot;&gt;https://github.com/FormidableLabs/radium&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var Radium = require(&apos;radium&apos;);
var React = require(&apos;react&apos;);
var color = require(&apos;color&apos;);

@Radium
class Button extends React.Component {
  static propTypes = {
    kind: React.PropTypes.oneOf([&apos;primary&apos;, &apos;warning&apos;]).isRequired
  };

  render() {
    return (
      &amp;lt;button
        style={[
          styles.base,
          styles[this.props.kind]
        ]}&amp;gt;
        {this.props.children}
      &amp;lt;/button&amp;gt;
    );
  }
}

var styles = {
  base: {
    color: &apos;#fff&apos;,
    &apos;:hover&apos;: {
      background: color(&apos;#0074d9&apos;).lighten(0.2).hexString()
    }
  },

  primary: {
    background: &apos;#0074D9&apos;
  },

  warning: {
    background: &apos;#FF4136&apos;
  }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;优点：是能给 CSS 提供 JS 同样强大的模块化能力；&lt;/li&gt;
  &lt;li&gt;缺点是不能利用成熟的 CSS 预处理器（或后处理器） Sass/Less/PostCSS，:hover 和 :active 伪类处理起来复杂&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;css-module&quot;&gt;css module&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/css-modules/css-modules&quot;&gt;https://github.com/css-modules/css-modules&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CSS Modules 内部通过 ICSS 来解决样式导入和导出这两个问题。分别对应 :import 和 :export 两个新增的伪类。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:import(&quot;path/to/dep.css&quot;) {
    localAlias: keyFromDep;
    /* ... */
}
:export {
    exportedKey: exportedValue;
    /* ... */
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但直接使用这两个关键字编程太麻烦，实际项目中很少会直接使用它们，我们需要的是用 JS 来管理 CSS 的能力。结合 Webpack 的 css-loader 后，就可以在 CSS 中定义样式，在 JS 中导入&lt;/p&gt;

&lt;p&gt;webpack配置：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// webpack.config.js
css?modules&amp;amp;localIdentName=[name]__[local]-[hash:base64:5]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;给css-loader加上 modules参数 即为启用，localIdentName 是设置生成样式的命名规则。&lt;/p&gt;

&lt;p&gt;定义css：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* components/Button.css */ 
.normal { /* normal 相关的所有样式 */ } 
.disabled { /* disabled 相关的所有样式 */ }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* components/Button.js */ 
import styles from &apos;./Button.css&apos;; 
console.log(styles); 
buttonElem.outerHTML = `&amp;lt;button class=${styles.normal}&amp;gt;Submit&amp;lt;/button&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;生成的 HTML 是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;button class=&quot;button--normal-abc53&quot;&amp;gt;Submit&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;命名空间&quot;&gt;命名空间&lt;/h5&gt;

&lt;p&gt;css module里默认样式都是局部的，相当于给每个 class 名外加加了一个 :local，以此来实现样式的局部化。如果想切换到全局模式，需要指定 :global。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.normal {
  color: green;
}

/* 以上与下面等价 */
:local(.normal) {
  color: green; 
}

/* 定义全局样式 */
:global {
  .link {
    color: green;
  }
  .box {
    color: yellow;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;样式复用&quot;&gt;样式复用&lt;/h5&gt;

&lt;p&gt;css module使用composes来复用样式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.className {
  color: green;
  background: red;
}

.otherClassName {
  composes: className;
  color: yellow;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以复用多个class的样式： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composes: classNameA classNameB&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;可以compose一个来源于其他文件的class:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.otherClassName {
    composes: className from &quot;./style.css&quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;composes 其实不是复用了具体的样式属性和值，而是会编译成多个class&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import styles from &apos;./styles.css&apos;;

buttonElem.outerHTML = `&amp;lt;button class=${styles.otherClassName}&amp;gt;Submit&amp;lt;/button&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这段代码会转化成：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;button class=&quot;button--className-daf62 button--otherClassName-abc53&quot;&amp;gt;Submit&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;cssjs变量共享&quot;&gt;CSS，JS变量共享&lt;/h5&gt;

&lt;p&gt;使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:export&lt;/code&gt; 关键字可以把 CSS 中的 变量输出到 JS 中。&lt;/p&gt;

&lt;p&gt;譬如在js中读取sass的变量：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* config.scss */
$primary-color: #f40;

:export {
  primaryColor: $primary-color;
}
/* app.js */
import style from &apos;config.scss&apos;;

// 会输出 #F40
console.log(style.primaryColor);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;优势&quot;&gt;优势&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;样式默认都是局部的，解决了命名冲突和全局污染问题&lt;/li&gt;
  &lt;li&gt;只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS&lt;/li&gt;
  &lt;li&gt;css代码依旧是css，学习成本低，符合长期形成的开发习惯&lt;/li&gt;
  &lt;li&gt;可以与预处理器共同使用&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;shadow-dom&quot;&gt;shadow dom&lt;/h3&gt;

&lt;p&gt;https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM&lt;/p&gt;

&lt;h2 id=&quot;npm包组件里的css输出&quot;&gt;npm包组件里的css输出&lt;/h2&gt;

&lt;p&gt;把组件做成npm提供给其他人使用，参考现有的一些组件。主要有2种方式：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;使用webpack将css合并成一个文件，js也合并成一个文件输出&lt;/li&gt;
  &lt;li&gt;仍按原组件结构，不拆分文件，对外提供入口文件供引入。这时候js和jsx文件会用babel编译一下。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我更喜欢2，这样源码能够被查看调试。但是途中遇到一些需要解决的问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;sass、less文件的处理：用相应的编译器转成css。可以用webpack配置多入口（每个入口就一个css文件）的方式转成css，这其实不是在把webpack当合并打包工具使用了。。。我嫌麻烦，用了gulp。&lt;/li&gt;
  &lt;li&gt;用了gulp转成css后，原文件中的引用语句： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import &apos;xxx.scss&apos;&lt;/code&gt;会找不到。只能用gulp-replace在将文件里的’.scss’替换成’.css’。&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>使用jest+enzyme进行react项目测试 - 搭建篇</title>
   <link href="http://echizen.github.io/tech/2017/02-12-jest-enzyme-setup"/>
   <updated>2017-02-12T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/jest-enzyme-setup</id>
   <content type="html">
&lt;p&gt;以自己的项目为例，介绍一下这套结构的搭建，项目使用了jest、enzyme、enzyme-to-json。&lt;/p&gt;

&lt;h3 id=&quot;安装所需依赖&quot;&gt;安装所需依赖&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install jest --save-dev
npm install enzyme --save-dev
npm install enzyme-to-json --save-dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;配置&quot;&gt;配置&lt;/h3&gt;

&lt;p&gt;package.json可以配置些基础配置，我的配置有：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;jest&quot;: {
  &quot;moduleFileExtensions&quot;: [
    &quot;js&quot;,
    &quot;jsx&quot;,
    &quot;json&quot;
  ],
  &quot;testRegex&quot;: &quot;.*\\.spec\\.js$&quot;,
  &quot;collectCoverageFrom&quot;: [
    &quot;src/**/*.{js,jsx}&quot;,
    &quot;src/**/**/*.{js,jsx}&quot;,
    &quot;!**/node_modules/**&quot;
  ],
  &quot;moduleNameMapper&quot;: {
    &quot;\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/spec/__mocks__/fileMock.js&quot;,
    &quot;\\.(css|scss)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/spec/__mocks__/styleMock.js&quot;
  },
  &quot;setupFiles&quot;: [
    &quot;&amp;lt;rootDir&amp;gt;/spec/setup.js&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;moduleFileExtensions： 被测试的文件里依赖的文件后缀，我的项目使用webpack打包，也配置了文件后缀，这样依赖的时候只用写&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require 文件名&lt;/code&gt;，工具会根据的你配置去找相应后缀的文件。&lt;/li&gt;
  &lt;li&gt;testRegex：正则表示的测试文件，测试文件的目录有很多种规划：1.单独放在测试文件夹。2.放在原文件夹，和原文件相同的位置。我是组件都放在测试文件夹，但是业务代码的测试还放在原文件附近，所以我通过文件名去匹配，我所有测试文件格式都是xxx.spec.js。&lt;/li&gt;
  &lt;li&gt;collectCoverageFrom：生成测试覆盖报告时检测的覆盖文件，我的都位于src下的js和jsx文件，但是要去除node_modules下所有文件，因为其会被作为依赖在原文件里处处引入。&lt;/li&gt;
  &lt;li&gt;moduleNameMapper： mock了图片、css、字体、音视频文件。&lt;/li&gt;
  &lt;li&gt;setupFiles：配置文件，在运行测试案例代码之前，jest会先运行这里的配置文件来初始化你指定的测试环境。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;setupFiles是个非常有用的项目，可以解决很多问题，在这里你可以为全局的window或global对象上绑东西，配置自己代码要运行的环境。譬如我在setup.js里配置了全局的react，mock了localstorage，随着测试的进行，以后应该还会有其他需求加在这里。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import React from &apos;react&apos;;
if (typeof window !== &apos;undefined&apos;) {
  window.React = React;
  window.localStorage = ( function storageMock() {
    var storage = {};
    return {
      setItem: function(key, value) {
        storage[key] = value || &apos;&apos;;
      },
      getItem: function(key) {
        return key in storage ? storage[key] : null;
      },
      removeItem: function(key) {
        delete storage[key];
      },
      get length() {
        return Object.keys(storage).length;
      },
      key: function(i) {
        var keys = Object.keys(storage);
        return keys[i] || null;
      }
    }
  })()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;至此，我的测试环境就搭好了。剩下的就是单元测试代码的编写了。&lt;/p&gt;

&lt;h3 id=&quot;组件测试代码结构&quot;&gt;组件测试代码结构&lt;/h3&gt;

&lt;p&gt;你要测试的组件或者函数必须在原文件里export出来，测试文件才能引用到，置于原文件的依赖，只要不是被mock的文件，都会真实的加载执行。所以一个react组件测试的结构类似这样：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import React from &apos;react&apos;;
import { render, mount, shallow } from &apos;enzyme&apos;;
import toJson from &apos;enzyme-to-json&apos;;
import { component } from &apos;component_path&apos;;


describe(&apos;component test&apos;, () =&amp;gt; {

  it(&apos;test one aspect&apos;, () =&amp;gt; {
    
  });
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>使用jest+enzyme进行react项目测试 - 疑难杂症篇</title>
   <link href="http://echizen.github.io/tech/2017/02-12-jest-enzyme-qa"/>
   <updated>2017-02-12T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/jest-enzyme-qa</id>
   <content type="html">
&lt;p&gt;恩，最要命的是配置环境的时候。虽然几乎所有库都会在官网首页介绍他们的配置多么多么简单，just one step，就可以跑起来。恩，确实按照套路，demo是能跑起来，但是要能不再配置其他内容，达到能随心所欲跑自己的任何一个测试文件，还是路漫漫啊。&lt;/p&gt;

&lt;h2 id=&quot;1解决在webpack的resolvealias中配置的内容jest找不到&quot;&gt;1.解决在webpack的resolve.alias中配置的内容jest找不到&lt;/h2&gt;

&lt;h3 id=&quot;报错&quot;&gt;报错&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReferenceError: React is not defined&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;原因&quot;&gt;原因&lt;/h3&gt;

&lt;p&gt;原文件和测试文件里都引入了react:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import { Component } from &apos;react’; ,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;组件是这样定义的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class FileUploadInput extends Component { render(){}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;并没有显式的使用React变量，但是经过babel编译后会成为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;React.createElement&lt;/code&gt;，使用了React对象。&lt;/p&gt;

&lt;p&gt;而我的webpack配置里，为了提高打包和编译速度、减小压缩包体积，将react作为外部变量引入的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;externals: {
    &apos;react&apos;: &apos;React&apos;,
    &apos;react-dom&apos;: &apos;ReactDOM&apos;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以我也要在jest的配置里将React作为外部的一个全局变量引入。&lt;/p&gt;

&lt;h3 id=&quot;解决方案&quot;&gt;解决方案&lt;/h3&gt;

&lt;p&gt;在package.json里jest的配置里配置”setupFiles”，指向setup.js。setup.js中声明React。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import React from &apos;react&apos;;
if (typeof window !== &apos;undefined&apos;) {
  window.React = React;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;2-调试&quot;&gt;2. 调试&lt;/h2&gt;

&lt;p&gt;恩，测试代码也是需要调试的，需要知道测试代码本身哪里有问题。&lt;/p&gt;

&lt;h3 id=&quot;node-inspect&quot;&gt;node –inspect&lt;/h3&gt;

&lt;p&gt;我先是将node升级到7，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node --inspect&lt;/code&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node --inspect --debug-brk ./node_modules/jest-cli --runInBand&lt;/code&gt;，速度很快，浏览器秒开并断点定位到首行。但是后续进入不了自己打的debugger断点，因为还没有进入到编译打包的步骤，测试文件和原文件都还没在浏览器source里出现，没法手动加断点。也就是没法打自己需要的断点。后来查了下这是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node --inspect&lt;/code&gt;的已知问题：&lt;a href=&quot;https://github.com/facebook/jest/issues/1652&quot;&gt;https://github.com/facebook/jest/issues/1652&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;node-inspector&quot;&gt;node-inspector&lt;/h3&gt;

&lt;p&gt;安装node-inspector，运行
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-debug --nodejs --harmony ./node_modules/jest-cli/bin/jest.js —runInBand&lt;/code&gt;，然后运行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-inspector&lt;/code&gt;，再按终端提示操作。&lt;/p&gt;

&lt;p&gt;可以成功设置断点调试，但是浏览器定位、打包速度超级慢，都是分钟级别的。最终受不了这速度，放弃。&lt;/p&gt;

&lt;h3 id=&quot;不完美的终篇consolelog&quot;&gt;不完美的终篇，console.log&lt;/h3&gt;

&lt;p&gt;最后还是使用原始的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log&lt;/code&gt;调试的。这个方式并不满意，但是确是这3种方式中操作起来最快的。&lt;/p&gt;

&lt;h2 id=&quot;3改变input值&quot;&gt;3.改变input值：&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wrapper.find(&apos;input&apos;).simulate(&apos;change&apos;, {target: {value: &apos;My new value&apos;}});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4mock-fetch-和-jsonp&quot;&gt;4.mock fetch 和 jsonp&lt;/h3&gt;

&lt;p&gt;坑爹的nock 只能mock http(s).request, 不能mock fetch。被&lt;a href=&quot;http://cn.redux.js.org/docs/recipes/WritingTests.html&quot;&gt;http://cn.redux.js.org/docs/recipes/WritingTests.html&lt;/a&gt;带到沟里去了。&lt;/p&gt;

&lt;p&gt;我引用的第三方mock请求的组件是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch-mock&lt;/code&gt;：&lt;a href=&quot;http://www.wheresrhys.co.uk/fetch-mock/&quot;&gt;http://www.wheresrhys.co.uk/fetch-mock/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;先按照自己请求规范标准，封装统一的mock方法，譬如我项目，所有的成功请求都是如下格式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;status:{
    code: &quot;0&quot;,
    detail: &quot;成功&quot;,
    msg: &quot;success&quot;
},
result: res
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以我封装了个如下的统一方法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const fetchMock = require(&apos;fetch-mock&apos;);
import { HOST } from &apos;../src/util/api.js&apos;;

export function mockRequest(path,res){
  let reg = new RegExp(`${HOST}${path}.*`);
  return fetchMock.get( reg, {
    body: {
      status:{
        code: &quot;0&quot;,
        detail: &quot;成功&quot;,
        msg: &quot;success&quot;
      },
      result: res
    },
    status: 200
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import { mockRequest } from &apos;../mockRequest.js&apos;;
import { multiData } from &apos;./mockModuleData.js&apos;;

describe(&apos;DataEdit render&apos;, () =&amp;gt; {
  afterEach(fetchMock.restore)
  it(&apos;renders DataEdit correctly&apos;, () =&amp;gt; {
    mockRequest(&apos;/pageModule/getData&apos;, multiData);
    return wrapper.node.fetchData(moduleInfo.moduleId).then(()=&amp;gt;{
      expect(toJson(wrapper)).toMatchSnapshot();
    })
  });
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;jsonp我原文件使用的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch-jsonp&lt;/code&gt;库提供的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchJsonp&lt;/code&gt;方法，由于jsdom不是真实的浏览器不能真的插入script请求js，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch-mock&lt;/code&gt;也mock不了jsonp。&lt;/p&gt;

&lt;p&gt;所以我先将fetchJsonp mock 成fetch，再由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch-mock&lt;/code&gt;统一Mock。&lt;/p&gt;

&lt;h2 id=&quot;5-存储html格式的snapshot&quot;&gt;5. 存储html格式的Snapshot&lt;/h2&gt;

&lt;p&gt;有些组件如antd 和 rc的modal，调用click事件后才会出来真正的ui结构，但是这个dom并不是打印在Modal组件这层作为子元素，而是作为body的直接子元素，就是这段ui结构直接插在body下。&lt;/p&gt;

&lt;p&gt;这时候我们只能通过选择器选到modal。但是jest的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toMatchSnapshot&lt;/code&gt;本身只是检查这次的快照和上次有没有区别，并不关心快照内容是什么和快照是怎么生成的，所以我们完全可以将选择器选中内容的innerHTML作为快照。需要做的就是格式化这段html，这样有改动时方便精确定位到变更的位置。&lt;/p&gt;

&lt;p&gt;我使用了一个开源的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beautifyHtml&lt;/code&gt;组件来格式化html。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import style_html from &apos;../beautifyHtml.js&apos;;
let ele = document.querySelector(&apos;.module-data-modal&apos;).innerHTML;
expect(style_html(ele)).toMatchSnapshot();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;6-enzyme获取非root的state&quot;&gt;6. enzyme获取非root的state&lt;/h2&gt;

&lt;p&gt;enzyme官方文档说明state()方法只能在mount的根组件上调用，但是我们经常通过根组件选择子组件，需要获取子组件的state，这是后可以通过node节点来获取，譬如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const wrapper = mount(InputEditView);
const InputEditCom = wrapper.find(&apos;InputEdit&apos;).first();
expect(InputEditCom.node.state.validateStatus).toBe(&apos;&apos;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实神奇的node获得的是ReactComponent，可以获取组件上所有内容，可以打印出来看一下，能做很多事。&lt;/p&gt;

&lt;h2 id=&quot;7触发antd-checkbox值的改变&quot;&gt;7.触发antd checkbox值的改变&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wrapper.find(&apos;input[type=&quot;checkbox&quot;]&apos;).first().simulate(&apos;change&apos;, { target: { checked: true } });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;并不是想象中的simulate(‘click’)&lt;/p&gt;

&lt;h2 id=&quot;8清除antd-的timepicker选择器值&quot;&gt;8.清除antd 的timePicker选择器值&lt;/h2&gt;

&lt;p&gt;清除时间选择器是mouseDown事件而不是click事件，还得去看rc-time-picker源码：&lt;a href=&quot;https://github.com/react-component/time-picker/blob/master/tests/Header.spec.jsx&quot;&gt;https://github.com/react-component/time-picker/blob/master/tests/Header.spec.jsx&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;9动态插入的script标签引入的文件处理&quot;&gt;9.动态插入的script标签引入的文件处理&lt;/h2&gt;

&lt;p&gt;我的项目里还有一种需要mock的场景，就是我为了保证线上页面js体积最小化，有些在特定场景下需要的用到的第三方库，我是使用动态插入script标签到页面中的方式来加载这些文件的。这些第三方库多是抛出了个全局变量供我的代码使用。但是在测试环境中，jsdom并不是浏览器，动态插入script加载个js文件并不能成功。&lt;/p&gt;

&lt;p&gt;这时候我需要先引入这个会被动态插入的js，先抛出来作为全局变量。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import MEditor from &apos;./mockEditor.js&apos;;
describe(&apos;Editor&apos;, () =&amp;gt; {
  const id = &apos;testEditor123&apos;;
  function creat(){
    return (&amp;lt;Editor value = { &apos;初始化内容&apos; }
              handleChange={ jest.fn() }
              id = { id }
            /&amp;gt;)
  }

  // insert editor js to window
  beforeEach(() =&amp;gt; {
    window.MEditor = MEditor;
  })

  it(&apos;call handleChange fn&apos;, () =&amp;gt; {
    const view = creat()
    const wrapper = mount(view);

    const newValue = &quot;呵呵那个呵呵&quot;;
    const inputArea = document.querySelector(`#${id}`);
    expect(inputArea.length).toBe(1);
    inputArea.innerHTML = newValue;
    expect(wrapper.props().handleChange).toBeCalledWith( newValue );
  });

})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>使用jest+enzyme进行react项目测试 - 测试手法篇</title>
   <link href="http://echizen.github.io/tech/2017/02-12-jest-enzyme-method"/>
   <updated>2017-02-12T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/jest-enzyme-method</id>
   <content type="html">
&lt;p&gt;基于jest和enzyme的特性，我们对一个react项目，可以进行以下几类测试。&lt;/p&gt;

&lt;h2 id=&quot;react项目测试方案&quot;&gt;react项目测试方案&lt;/h2&gt;

&lt;p&gt;(由于自己实践在私有项目上不方便贴代码，以下所有代码将来源于对开源库的测试和其他demo代码)&lt;/p&gt;

&lt;h3 id=&quot;组件ui测试snapshot&quot;&gt;组件UI测试：snapshot&lt;/h3&gt;

&lt;p&gt;snapshot可以测试到组件的渲染结果是否符合预期，预期就是指你上一次录入保存的结果，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toMatchSnapshot&lt;/code&gt;方法会去帮你对比这次将要生成的结构与上次的区别&lt;/p&gt;

&lt;p&gt;感谢jest改方法的强大，这是最便捷的测试，每个组件都该覆盖到。&lt;/p&gt;

&lt;p&gt;snapshot的测试案例形如调用这个组件，传入依赖的props。譬如对antd的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToolTip&lt;/code&gt;组件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import { Tooltip } from &apos;antd&apos;;
import { render } from &apos;enzyme&apos;;
import toJson from &apos;enzyme-to-json&apos;;

describe(&apos;FileUploadInput render&apos;, () =&amp;gt; {

  it(&apos;basic use&apos;, () =&amp;gt; {
    const wrapper = render(
      &amp;lt;Tooltip title=&quot;prompt text&quot;&amp;gt;
        &amp;lt;span&amp;gt;Tooltip will show when mouse enter.&amp;lt;/span&amp;gt;
      &amp;lt;/Tooltip&amp;gt;
    );
    expect(toJson(wrapper)).toMatchSnapshot();
  })

  it(&apos;use arrowPointAtCenter&apos;, () =&amp;gt; {
    const wrapper = render(
      &amp;lt;div&amp;gt;
        &amp;lt;Tooltip placement=&quot;topLeft&quot; title=&quot;Prompt Text&quot;&amp;gt;
          &amp;lt;Button&amp;gt;Align edge / 边缘对齐&amp;lt;/Button&amp;gt;
        &amp;lt;/Tooltip&amp;gt;
        &amp;lt;Tooltip placement=&quot;topLeft&quot; title=&quot;Prompt Text&quot; arrowPointAtCenter&amp;gt;
          &amp;lt;Button&amp;gt;Arrow points to center / 箭头指向中心&amp;lt;/Button&amp;gt;
        &amp;lt;/Tooltip&amp;gt;
      &amp;lt;/div&amp;gt;
    );
    expect(toJson(wrapper)).toMatchSnapshot();
  })

  it(&apos;use placement&apos;,() =&amp;gt; {
    const wrapper = render(&amp;lt;div&amp;gt;
          &amp;lt;div style={{ marginLeft: 60 }}&amp;gt;
            &amp;lt;Tooltip placement=&quot;topLeft&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;TL&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;top&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;Top&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;topRight&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;TR&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div style={{ width: 60, float: &apos;left&apos; }}&amp;gt;
            &amp;lt;Tooltip placement=&quot;leftTop&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;LT&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;left&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;Left&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;leftBottom&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;LB&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div style={{ width: 60, marginLeft: 270 }}&amp;gt;
            &amp;lt;Tooltip placement=&quot;rightTop&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;RT&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;right&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;Right&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;rightBottom&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;RB&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div style={{ marginLeft: 60, clear: &apos;both&apos; }}&amp;gt;
            &amp;lt;Tooltip placement=&quot;bottomLeft&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;BL&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;bottom&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;Bottom&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
            &amp;lt;Tooltip placement=&quot;bottomRight&quot; title={text}&amp;gt;
              &amp;lt;a href=&quot;#&quot;&amp;gt;BR&amp;lt;/a&amp;gt;
            &amp;lt;/Tooltip&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;)
    expect(toJson(wrapper)).toMatchSnapshot();
  })
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意的是一个足够健壮的测试应该覆盖到所有的渲染请况，也就是如果你的组件根据props传入的参数渲染结果可能不同，这样的情况必须写多个测试案例都覆盖到。&lt;/p&gt;

&lt;p&gt;譬如还有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToolTip&lt;/code&gt;组件，还有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;placement&lt;/code&gt;参数表示箭头方向，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arrowPointAtCenter&lt;/code&gt;参数表示将tooltip的箭头位于指示框中间位置。所以测试案例应该加上它们。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;it(&apos;use arrowPointAtCenter&apos;, () =&amp;gt; {
  const wrapper = render(
    &amp;lt;div&amp;gt;
      &amp;lt;Tooltip placement=&quot;topLeft&quot; title=&quot;Prompt Text&quot;&amp;gt;
        &amp;lt;Button&amp;gt;Align edge / 边缘对齐&amp;lt;/Button&amp;gt;
      &amp;lt;/Tooltip&amp;gt;
      &amp;lt;Tooltip placement=&quot;topLeft&quot; title=&quot;Prompt Text&quot; arrowPointAtCenter&amp;gt;
        &amp;lt;Button&amp;gt;Arrow points to center / 箭头指向中心&amp;lt;/Button&amp;gt;
      &amp;lt;/Tooltip&amp;gt;
    &amp;lt;/div&amp;gt;
  );
  expect(toJson(wrapper)).toMatchSnapshot();
})

it(&apos;use placement&apos;,() =&amp;gt; {
  const wrapper = render(&amp;lt;div&amp;gt;
        &amp;lt;div style={{ marginLeft: 60 }}&amp;gt;
          &amp;lt;Tooltip placement=&quot;topLeft&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;TL&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;top&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;Top&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;topRight&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;TR&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div style={{ width: 60, float: &apos;left&apos; }}&amp;gt;
          &amp;lt;Tooltip placement=&quot;leftTop&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;LT&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;left&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;Left&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;leftBottom&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;LB&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div style={{ width: 60, marginLeft: 270 }}&amp;gt;
          &amp;lt;Tooltip placement=&quot;rightTop&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;RT&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;right&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;Right&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;rightBottom&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;RB&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div style={{ marginLeft: 60, clear: &apos;both&apos; }}&amp;gt;
          &amp;lt;Tooltip placement=&quot;bottomLeft&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;BL&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;bottom&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;Bottom&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
          &amp;lt;Tooltip placement=&quot;bottomRight&quot; title={text}&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;BR&amp;lt;/a&amp;gt;
          &amp;lt;/Tooltip&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;)
  expect(toJson(wrapper)).toMatchSnapshot();
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;dom交互测试-jestenzyme&quot;&gt;dom交互测试: jest+enzyme&lt;/h3&gt;

&lt;p&gt;enzyme有3种渲染方式：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shallow&lt;/code&gt;，先了解下区别。&lt;/p&gt;

&lt;h4 id=&quot;rendermountshallow的区别&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shallow&lt;/code&gt;的区别&lt;/h4&gt;

&lt;p&gt;render采用的是第三方库&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cheerio&lt;/code&gt;的渲染，渲染结果是普通的html结构，对于snapshot使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt;比较合适。&lt;/p&gt;

&lt;p&gt;shallow和mount对组件的渲染结果不是html的dom树，而是react树，如果你chrome装了react devtool插件，他的渲染结果就是react devtool tab下查看的组件结构，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt;函数的结果是element tab下查看的结果。&lt;/p&gt;

&lt;p&gt;这些只是渲染结果上的差别，更大的差别是shallow和mount的结果是个被封装的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReactWrapper&lt;/code&gt;，可以进行多种操作，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find()、parents()、children()&lt;/code&gt;等选择器进行元素查找；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state()、props()&lt;/code&gt;进行数据查找，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setState()、setprops()&lt;/code&gt;操作数据；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simulate()&lt;/code&gt;模拟事件触发。&lt;/p&gt;

&lt;p&gt;shallow只渲染当前组件，只能能对当前组件做断言；mount会渲染当前组件以及所有子组件，对所有子组件也可以做上述操作。一般交互测试都会关心到子组件，我使用的都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount&lt;/code&gt;。但是mount耗时更长，内存啥的也都占用的更多，如果没必要操作和断言子组件，可以使用shallow。&lt;/p&gt;

&lt;h4 id=&quot;交互测试&quot;&gt;交互测试&lt;/h4&gt;

&lt;p&gt;主要利用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simulate()&lt;/code&gt;接口模拟事件，实际上simulate是通过触发事件绑定函数，来模拟事件的触发。触发事件后，去判断props上特定函数是否被调用，传参是否正确；组件状态是否发生预料之中的修改；某个dom节点是否存在是否符合期望。&lt;/p&gt;

&lt;p&gt;譬如antd的table组件的pagination分页功能的测试：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  import React from &apos;react&apos;;
  import { render, mount } from &apos;enzyme&apos;;
  import { renderToJson } from &apos;enzyme-to-json&apos;;
  import { Table } from &apos;antd&apos;;

  describe(&apos;Table.pagination&apos;, () =&amp;gt; {
    const columns = [{
      title: &apos;Name&apos;,
      dataIndex: &apos;name&apos;,
    }];

    const data = [
      { key: 0, name: &apos;Jack&apos; },
      { key: 1, name: &apos;Lucy&apos; },
      { key: 2, name: &apos;Tom&apos; },
      { key: 3, name: &apos;Jerry&apos; },
    ];

    const pagination = { pageSize: 2 };

    function createTable(props) {
      return (
        &amp;lt;Table
          columns={columns}
          dataSource={data}
          pagination={pagination}
          {...props}
        /&amp;gt;
      );
    }

    function renderedNames(wrapper) {
      return wrapper.find(&apos;TableRow&apos;).map(row =&amp;gt; row.props().record.name);
    }

    it(&apos;paginate data&apos;, () =&amp;gt; {
      const wrapper = mount(createTable());

      expect(renderedNames(wrapper)).toEqual([&apos;Jack&apos;, &apos;Lucy&apos;]);
      wrapper.find(&apos;Pager&apos;).last().simulate(&apos;click&apos;);
      expect(renderedNames(wrapper)).toEqual([&apos;Tom&apos;, &apos;Jerry&apos;]);
    });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过触发最后一页的click事件达到页面改变，去判断table中渲染的数据是否符合预期。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    it(&apos;repaginates when pageSize change&apos;, () =&amp;gt; {
      const wrapper = mount(createTable());

      wrapper.setProps({ pagination: { pageSize: 1 } });
      expect(renderedNames(wrapper)).toEqual([&apos;Jack&apos;]);
    });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个是直接用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setProps&lt;/code&gt;操作了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pagination&lt;/code&gt;参数，再去判断table中渲染的数据是否符合预期。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    it(&apos;fires change event&apos;, () =&amp;gt; {
      const handleChange = jest.fn();
      const noop = () =&amp;gt; {};
      const wrapper = mount(createTable({
        pagination: { ...pagination, onChange: noop, onShowSizeChange: noop },
        onChange: handleChange,
      }));

      wrapper.find(&apos;Pager&apos;).last().simulate(&apos;click&apos;);

      expect(handleChange).toBeCalledWith(
        {
          current: 2,
          onChange: noop,
          onShowSizeChange: noop,
          pageSize: 2,
        },
        {},
        {}
      );
    });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个触发click事件，断言&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handleChange&lt;/code&gt;是否以预期参数被调用。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    it(&apos;should display pagination as prop pagination changed&apos;, () =&amp;gt; {
      const wrapper = mount(createTable());
      expect(wrapper.find(&apos;.ant-pagination&apos;)).toHaveLength(1);
      expect(wrapper.find(&apos;.ant-pagination-item&apos;)).toHaveLength(2);
      wrapper.setProps({ pagination: false });
      expect(wrapper.find(&apos;.ant-pagination&apos;)).toHaveLength(0);
      wrapper.setProps({ pagination });
      expect(wrapper.find(&apos;.ant-pagination&apos;)).toHaveLength(1);
      expect(wrapper.find(&apos;.ant-pagination-item&apos;)).toHaveLength(2);
      wrapper.find(&apos;.ant-pagination-item-2&apos;).simulate(&apos;click&apos;);
      expect(renderedNames(wrapper)).toEqual([&apos;Tom&apos;, &apos;Jerry&apos;]);
      wrapper.setProps({ pagination: false });
      expect(wrapper.find(&apos;.ant-pagination&apos;)).toHaveLength(0);
      wrapper.setProps({ pagination: true });
      expect(wrapper.find(&apos;.ant-pagination&apos;)).toHaveLength(1);
      expect(wrapper.find(&apos;.ant-pagination-item&apos;)).toHaveLength(1); // pageSize will be 10
      expect(renderedNames(wrapper)).toEqual([&apos;Jack&apos;, &apos;Lucy&apos;, &apos;Tom&apos;, &apos;Jerry&apos;]);
    });
  });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个先判断渲染结果中的子节点’.ant-pagination’、’.ant-pagination-item’数量是否符合预期，以达到渲染分页情况是否正确的判断，然后再通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setProps&lt;/code&gt;操作&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pagination&lt;/code&gt;参数，判断子节点是否符合预期。&lt;/p&gt;

&lt;p&gt;从以上案例也可看出，断言时既可以通过获取props()、state()中的数据是否符合预期，也可以通过dom selector查询找到特定节点再通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text()&lt;/code&gt;接口拿到数据，判断是否符合预期&lt;/p&gt;

&lt;h3 id=&quot;功能函数测试&quot;&gt;功能函数测试&lt;/h3&gt;

&lt;p&gt;功能函数除了普通的工具处理类函数，直接引入函数，传入特定参数调用，判断函数返回值是否符合预期。要注意的也是全面性，保证函数的每个逻辑判断都能在所有测试案例跑完后被覆盖到。&lt;/p&gt;

&lt;p&gt;这时候纯函数和函数式编程的优势确实体现出来了，很方便测试。不扯了，我还有很多没有return 返回值的函数需要去改。不适合测试的代码就要去改改再测，还有那种函数功能不清的，一个函数干了一堆事情，这种也得拆拆。&lt;/p&gt;

&lt;p&gt;接入了redux的项目，比较特殊的是action、reducer函数的测试。&lt;/p&gt;

&lt;h2 id=&quot;action&quot;&gt;action&lt;/h2&gt;

&lt;p&gt;其实也是调用函数判断返回值是否符合预期。&lt;/p&gt;

&lt;p&gt;如下action:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	export function addTodo(text) {
	  return {
	    type: &apos;ADD_TODO&apos;,
	    text
	  }
	}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;测试代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	import * as actions from &apos;../../actions/TodoActions&apos;
	import * as types from &apos;../../constants/ActionTypes&apos;

	describe(&apos;actions&apos;, () =&amp;gt; {
	  it(&apos;should create an action to add a todo&apos;, () =&amp;gt; {
	    const text = &apos;Finish docs&apos;
	    const expectedAction = {
	      type: types.ADD_TODO,
	      text
	    }
	    expect(actions.addTodo(text)).toEqual(expectedAction)
	  })
	})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;异步action&quot;&gt;异步action&lt;/h3&gt;

&lt;p&gt;jest对异步提供了很方便的3种测试方式：&lt;a href=&quot;http://facebook.github.io/jest/docs/asynchronous.html#content&quot;&gt;http://facebook.github.io/jest/docs/asynchronous.html#content&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;但是异步action还是要借助第三方库&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configureMockStore&lt;/code&gt;，将redux-thunk这种异步中间件传入进去处理，获得封装后的store.dispatch来派发action。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	import fetch from &apos;isomorphic-fetch&apos;;

	function fetchTodosRequest() {
	  return {
	    type: FETCH_TODOS_REQUEST
	  }
	}

	function fetchTodosSuccess(body) {
	  return {
	    type: FETCH_TODOS_SUCCESS,
	    body
	  }
	}

	function fetchTodosFailure(ex) {
	  return {
	    type: FETCH_TODOS_FAILURE,
	    ex
	  }
	}

	export function fetchTodos() {
	  return dispatch =&amp;gt; {
	    dispatch(fetchTodosRequest())
	    return fetch(&apos;http://example.com/todos&apos;)
	      .then(res =&amp;gt; res.json())
	      .then(json =&amp;gt; dispatch(fetchTodosSuccess(json.body)))
	      .catch(ex =&amp;gt; dispatch(fetchTodosFailure(ex)))
	  }
	}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;测试代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	import configureMockStore from &apos;redux-mock-store&apos;
	import thunk from &apos;redux-thunk&apos;
	import * as actions from &apos;../../actions/TodoActions&apos;
	import * as types from &apos;../../constants/ActionTypes&apos;
	import nock from &apos;nock&apos;

	const middlewares = [ thunk ]
	const mockStore = configureMockStore(middlewares)

	describe(&apos;async actions&apos;, () =&amp;gt; {
	  afterEach(() =&amp;gt; {
	    nock.cleanAll()
	  })

	  it(&apos;creates FETCH_TODOS_SUCCESS when fetching todos has been done&apos;, () =&amp;gt; {
	    nock(&apos;http://example.com/&apos;)
	      .get(&apos;/todos&apos;)
	      .reply(200, { body: { todos: [&apos;do something&apos;] }})

	    const expectedActions = [
	      { type: types.FETCH_TODOS_REQUEST },
	      { type: types.FETCH_TODOS_SUCCESS, body: { todos: [&apos;do something&apos;]  } }
	    ]
	    const store = mockStore({ todos: [] })

	    return store.dispatch(actions.fetchTodos())
	      .then(() =&amp;gt; { // 异步 actions 的返回
	        expect(store.getActions()).toEqual(expectedActions)
	      })
	  })
	})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;异步的测试案例一定要 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return&lt;/code&gt; 异步promise，return不能丢，这样才能让jest明白这是个异步过程，才会去等待异步执行结果，否则会立即触发expect断言导致测试失败。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;对于请求的mock可能需要第三方库，这是redux官网的示例，但是fetch请求并不能用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nock&lt;/code&gt;库来mock，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nock&lt;/code&gt;官方说了他只改写了node里的http模块，&lt;a href=&quot;https://github.com/node-nock/nock&quot;&gt;https://github.com/node-nock/nock&lt;/a&gt;。并不能mock fetch接口，所以这里使用有错误&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;善用mock&quot;&gt;善用mock&lt;/h2&gt;

&lt;p&gt;很多对于测试没有价值的东西可以mock掉，这并不会影响测试的准确性。&lt;/p&gt;

&lt;h3 id=&quot;mock函数&quot;&gt;mock函数&lt;/h3&gt;

&lt;p&gt;对于一个react组件，测试这个组件时并不需要关心注入他的action函数的行为，你所要做的是特定场景下，这个action函数被正确调用，置于调用结果是action函数的单元测试该验证的事。&lt;/p&gt;

&lt;p&gt;所以你可以定义&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const handler = jest.fn()&lt;/code&gt;，后续可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(handler).toBeCalled()&lt;/code&gt;检查mock的函数是否如期被调用，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(handler).toBeCalledWith(&apos;arg&apos;)&lt;/code&gt;检查mock的函数是否调用时传入的参数是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arg&lt;/code&gt;等。&lt;/p&gt;

&lt;h3 id=&quot;mock文件&quot;&gt;mock文件&lt;/h3&gt;

&lt;p&gt;诸如css、image等于逻辑测试无关的资源文件可以在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json中&lt;/code&gt;的配置里被统一mock掉:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;jest&quot;: {
  &quot;moduleNameMapper&quot;: {
    &quot;\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/spec/__mocks__/fileMock.js&quot;,
    &quot;\\.(css|scss)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/spec/__mocks__/styleMock.js&quot;,
    &quot;^component_path$&quot;: &quot;&amp;lt;rootDir&amp;gt;/src/components&quot;,
    &quot;^root_path$&quot;: &quot;&amp;lt;rootDir&amp;gt;/src&quot;
  }
}

这样`import`时就不会真的去引入这些对于测试无用的文件了。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;mock请求&quot;&gt;mock请求&lt;/h3&gt;

&lt;p&gt;我们并不想在测试时真的去发一个请求，一方面是会耗时很长，另一方面可能会用测试数据污染线上数据库。&lt;/p&gt;

&lt;p&gt;所以我们需要mock掉真正的http请求，模拟返回值。不用担心不准确，你只用保证请求时的参数符合期望就好，mock的返回值按预期编写就好，置于这些请求是否真的能返回这些结果，是接口测试改干的活。&lt;/p&gt;

&lt;p&gt;手动一个个mock请求实在效率低，所以我们需要一个组件帮我们直接拦截掉所有请求，我们的项目代码不需改变，再传入要mock的请求返回值，组件帮我们封装成请求的返回值返回给我们的测试代码拿到。&lt;/p&gt;

&lt;p&gt;我引用的第三方mock请求的组件是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch-mock&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;先封装个统一的mock方法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const fetchMock = require(&apos;fetch-mock&apos;);
import { HOST } from &apos;../src/util/api.js&apos;;

export function mockRequest(path,res){
  let reg = new RegExp(`${HOST}${path}.*`);
  return fetchMock.get( reg, {
    body: {
      status:{
        code: &quot;0&quot;,
        detail: &quot;成功&quot;,
        msg: &quot;success&quot;
      },
      result: res
    },
    status: 200
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import { mockRequest } from &apos;../mockRequest.js&apos;;
import { multiData } from &apos;./mockModuleData.js&apos;;

describe(&apos;DataEdit render&apos;, () =&amp;gt; {
  afterEach(fetchMock.restore)
  it(&apos;renders DataEdit correctly&apos;, () =&amp;gt; {
    mockRequest(&apos;/pageModule/getData&apos;, multiData);
    return wrapper.node.fetchData(moduleInfo.moduleId).then(()=&amp;gt;{
      expect(toJson(wrapper)).toMatchSnapshot();
    })
  });
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;测试注意事项&quot;&gt;测试注意事项&lt;/h2&gt;

&lt;p&gt;可能第一次接触测试，开始时有很多错误的思维，贴出来。&lt;/p&gt;

&lt;p&gt;1.拆分单元，关注输入输出，忽略中间过程。dom测试时只用确保正确调用了action函数，传参正确，而不用关注函数调用结果，置于action处理结果，reducer中对state的改变这些都留给action和reducer自己的单元测试区测。不要想着测试整个大功能的流程，不要有闭环的思想，单元测试需要保证的当前单元正常，对于每个单元模块输入输出都正确，理论串联后一起使用闭环时也会正确。&lt;/p&gt;

&lt;p&gt;2.多种情况的测试覆盖，如果不能保证测试的全面性，每种情况都覆盖到，那么这个测试就是个不敢依靠的不全面的测试。当然在实际项目中，可能因为时间、资源等问题，无法保证每种情况都测试到，而只测试主要的内容，这时候要做到心里有数，反正我是对于每个测试都写注释的，交代清楚测试覆盖了哪些，还有哪些没有覆盖，需要其他手段保持稳定性。&lt;/p&gt;

&lt;p&gt;3.关注该关注的，无关紧要的mock掉。css、图片这种mock掉，http请求mock掉&lt;/p&gt;

&lt;p&gt;4.原本不利于测试的代码还是需要修改的，并不能为了原代码稳定不变，在测试时不敢动原代码。譬如函数不纯，没有返回值等。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>使用jest+enzyme进行react项目测试 - 介绍篇</title>
   <link href="http://echizen.github.io/tech/2017/02-12-jest-enzyme-intro"/>
   <updated>2017-02-12T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2017/jest-enzyme-intro</id>
   <content type="html">
&lt;p&gt;在真正了解到jest和enzyme之前，选择jest作为我react项目的测试框架，仅仅从以下几个方面：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;facebook开发，他们自己在用，经得起考验，质量有保障&lt;/li&gt;
  &lt;li&gt;社区活跃，出了问题应该容易找解决方案&lt;/li&gt;
  &lt;li&gt;antd用的也是这个，这样我可以参考一下人家怎么写的: 后来发现是antd的测试基本只是snapshot这方面的。。。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是了解和使用jest+enzyme之后，从未接触过测试相关知识的我仿佛打开了新世界的大门~&lt;/p&gt;

&lt;h2 id=&quot;jest&quot;&gt;jest&lt;/h2&gt;

&lt;p&gt;jest 作为一款测试框架，拥有测试框架该有的一套体系，丰富的断言库，大多数api与老牌的测试框架如jasmine、mocha，譬如常用的expect、test(it)、toBe等，都非常好用。内部也是使用了jasmine作为基础，在其上封装。但是因为Snapshot这个特色功能，非常适合react项目的测试。&lt;/p&gt;

&lt;h3 id=&quot;特色&quot;&gt;特色&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Snapshot组件快照&lt;/p&gt;

    &lt;p&gt;借用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;react-test-renderer&lt;/code&gt;库的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;renderer&lt;/code&gt;获得react组件渲染成的React树，调用toJSON接口格式化，在使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(tree).toMatchSnapshot()&lt;/code&gt;将快照与上一次的快照作对比，首次生成的某个测试案例的快照将会被保存下来，以后每次运行时都会与上一次对比，如果发现不匹配会抛出错误，需要自己去查看差异是否是合法的需要更新的内容。&lt;/p&gt;

    &lt;p&gt;这种方式对比react组件渲染后的内容，在静态ui上，非常高效的找出差别，维护稳定性。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;多线程同时跑测试案例，速度快&lt;/p&gt;

    &lt;p&gt;这个我观察了一下我写的不多的几个测试，5个测试脚本一起跑和只跑1个时间基本相同，5s左右，环境初始化还是挺慢的，看单个脚本的分析，每个测试单元都只耗了几百毫秒，环境的初始化时秒级别的。但是确实是多线程同时跑速度大大提升。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;可配置的coverage reports&lt;/p&gt;

    &lt;p&gt;生成测试覆盖报告无需再下其他依赖，已被内置，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest --coverage&lt;/code&gt;就能生成coverage文件夹保存，方便分析。形如：&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://s10.mogucdn.com/p2/170211/114994688_0kj0fjh6gb5c6j79i1bf95l6d244a_2432x1348.png&quot; alt=&quot;https://s10.mogucdn.com/p2/170211/114994688_0kj0fjh6gb5c6j79i1bf95l6d244a_2432x1348.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;强大的mocking库&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以mock值、函数、文件多种类型&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- mock函数：mock的函数可以被追踪到，`const handler = jest.fn()`，譬如后续可以通过`expect(handler).toBeCalled()`检查mock的函数是否如期被调用，`expect(handler).toBeCalledWith(&apos;arg&apos;)`检查mock的函数是否调用时传入的参数是`arg`等。
- mock文件：诸如css、image等于逻辑测试无关的资源文件可以在配置时就被mock掉`&quot;\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/spec/__mocks__/fileMock.js&quot;,
  &quot;\\.(css|scss)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/spec/__mocks__/styleMock.js&quot;,`，这样`import`时就不会真的去引入这些无用的文件了。
- time mock：`setTimeout, setInterval, clearTimeout, clearInterval`默认是被mock的，这样就不会再执行是真的等待这些函数获取的时间参数，影响测试执行速度了，但是内部特殊的mock让其依旧能保证异步进行和执行顺序。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;内置jsdom，提供了dom依存的环境&lt;/p&gt;

    &lt;p&gt;jest 配合enzyme,enzyme可以在jsDom里渲染出虚拟dom，然后我们可以操作它，进行交互测试。依旧有window、document等对象，但是无法往这个dom中插入script标签进行其他资源文件的加载。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;enzyme&quot;&gt;enzyme&lt;/h2&gt;

&lt;p&gt;enzyme提供了几种方式中将react组件渲染成真实的dom，提供了类似jquery的api来获取dom；提供了simulate函数来模拟事件触发；提供接口让我们获取到组件的state和props并且能对其进行操作。&lt;/p&gt;

&lt;p&gt;enzyme其实是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;react-test-renderer&lt;/code&gt;的封装，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;react-test-renderer&lt;/code&gt;的api非常不友好，但是enzyme开发的api跟jquery一致。如：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://s2.mogucdn.com/p2/170211/114994688_189fl06338afj5k0fg49e7g86iha4_548x1300.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;还可以接入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enzyme-to-json&lt;/code&gt;，这样在将enzyme生成的react树生成snapshot时就可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toJson&lt;/code&gt;进行格式化，提高生成的snapshot的可读性，代替了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;react-test-renderer&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toJSON&lt;/code&gt;接口。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2016年终总结</title>
   <link href="http://echizen.github.io/think/2017/01-27-2016-summary"/>
   <updated>2017-01-27T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2017/2016-summary</id>
   <content type="html">
&lt;h2 id=&quot;2016---改变&quot;&gt;2016 - 改变&lt;/h2&gt;

&lt;p&gt;正值除夕，坐下来写点东西。&lt;/p&gt;

&lt;p&gt;代码写着写着，1年就过去了，有时候真的觉得，会不会有一天敲着敲着，发现，我去，自己居然老了。毕竟码代码的日子，时间过得是那么快。&lt;/p&gt;

&lt;h3 id=&quot;那些第一次&quot;&gt;那些第一次&lt;/h3&gt;

&lt;p&gt;2016真的是很精彩，经历了好多第一次，所以我觉得2016年一个词总结时改变。&lt;/p&gt;

&lt;p&gt;毕业旅行，去了我心心念念的云南一带，和好朋友一起，2个妹子自由行，游了昆明、大理、丽江、香格里拉，从此爱上高原。天天都想去。。。&lt;a href=&quot;http://www.mafengwo.cn/i/5506537.html&quot;&gt;http://www.mafengwo.cn/i/5506537.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;从大学毕业，正式入职工作。这里面的故事就说不完了~&lt;/p&gt;

&lt;p&gt;第一次出国。去了韩国济州岛，抱着游山玩水去的，结果还是沦陷的买买买，买了一大批护肤品，化妆品回来了。。。去另一个国家体验不同的文化，还是蛮有趣的，而且这是公司组织的，第一次团队游，团队游远比我想象的有趣多了，一群人有说不完的故事，倒腾不完的花样。&lt;/p&gt;

&lt;p&gt;第一次化妆，恩，学会了化妆，虽然化的粗糙，但总是在20多年裸奔后学会涂点东西了。&lt;/p&gt;

&lt;p&gt;活动类的第一次，更是不少，譬如第一次攀岩、第一次坐快艇、第一次去做“大保健”、第一次泡温泉……&lt;/p&gt;

&lt;p&gt;食物类的第一次也很多，开始尝试很多之前不愿意尝试的食物，然后发现居然不是很难吃，还很好吃，譬如鸡蛋饼、生鱼片、各种叫不出名字的海鲜…..&lt;/p&gt;

&lt;p&gt;因为改变，因为愿意尝试，才有了这么多的第一次，才发现生活这么精彩，真的，很多东西初次尝试会觉得非常有意思。生活不断尝试新东西，才不枉此生。&lt;/p&gt;

&lt;p&gt;而且，&lt;strong&gt;你如果不去尝试，都不知道自己可以做的很好&lt;/strong&gt;，譬如攀岩，那个云梯啥的看着我都觉得没戏，去尝试一下，我居然爬上去了。&lt;/p&gt;

&lt;h3 id=&quot;工作&quot;&gt;工作&lt;/h3&gt;

&lt;p&gt;还记得大一在学校时，没出过学校，一脸惊恐的问已经工作了的初中同学，社会是不是很险恶，勾心斗角。估计是狗血电视剧看多了。。。&lt;/p&gt;

&lt;p&gt;其实工作后，处处是感动，与学校并没有太大的区别。所在的公司已经实习过大半年，所以更是很熟悉，没什么不适。&lt;/p&gt;

&lt;p&gt;工作所接触到的大多数是很正面的能量。譬如付出是会有回报，踏实做事会有人看到，能够解决问题，会有人感谢你。但是总体还是一个竞争环境，多劳多得，不要怕同等待遇下自己付出的更多会不会不值得啥的，回报最终回来的。&lt;strong&gt;机会留给有准备的人&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;我对自己的工作环境整体还是比较满意的。周围的小伙伴们都是很单纯的互帮互助，并没有在竞争氛围下发生不愉快的事，有些同事是研究生毕业，更是照顾我这种本科小娃，这一届校招生很多，大家一起学习交流即使是吐槽也是很欢脱。学长是个很低调但是很牛逼的人，非常好的引导，不仅工作学习中是楷模，也确实教了我跟多落地的知识，帮我解决问题。还有团队的各个小组长都很和蔼善良，对我们这些刚毕业的校招生非常照顾。leader虽然私聊接触的不多，但是人很好，找他一定会和你沟通，也教会我很多东西，毕竟leader就是leader，眼界格局是超越我们很多的，教我们从大的方向上看问题，为公司和业务着想，而且默默关注着大家，尽力保证公平公正，给认真工作的员工谋福利，而且非常注重我们的个人成长和技术上的修为。蛮幸运机缘巧合来到这家公司，和身边其他公司员工对比，简直业界良心，这样的规模和状况下，福利待遇非常好，工作环境也很赞，公司文化也很闹腾有趣，ceo真的是乐意和员工分享财富的。&lt;/p&gt;

&lt;p&gt;当然也会有不是很舒服的情况，譬如有些人不干活总bb，对别人的工作指手划脚，一起的工作自己的部分也不完成留给你然后再指出一坨你做的不好的地方。也有些比较“大牌”，怎么请都请不动，联调接口简直求爷爷告奶奶，各种恳求，各种联系还不理你。然后刚出来时，因为是新人，还是很多时候会不被正视，你发出的声音总是被忽视。有些情况确实改变不了，因为毕竟在公司还是有层级的，你不爽也不能指着他们破口大骂他们的不是，这种就只能改变自己的心态去面对了，对于喜欢bb的，就看他bb的是否有道理，有道理的就去按他说的改，没道理就只是口头回应一下。比较“大牌”的就不厌其烦的去骚扰吧，他终归会不好意思的，我还直接私聊说出过我有点不开心对方这不配合的态度，效果也很好，摊开沟通后对方表示了歉意，而且后来真的合作顺利多了，不会再不理你。置于“人微言轻”，那就觉得对的不停的提出，知道他们听到了，这种情况出现多了，大家发现你是有道理的，即使依旧“人微”，也不会“言轻”了，大家会慢慢重视你的声音。我胆子比较小，不敢发脾气，虽然我会默默回去和非公司的人抱怨来舒缓一下情绪，但是我不会一直抱怨或者放弃，我会去想办法让自己适应环境，而且有自己的原则。&lt;/p&gt;

&lt;p&gt;正式入职后肩上的责任和工作量都和实习时不是一个量级，才是真正的工作的感觉，很累很刺激。收获也很多。主要是学会了思考业务，不再那么技术化，只考虑功能的实现方式，而不考虑需求本身。要想走得更远，必须学会做好业务，不仅是技术，这样才能为一家商业化的公司创造价值，自己也能得到更多。不过虽然考虑业务并不难，转变一下思考方式和行事方式就可以了，但是做好业务和开拓创新却是要一直去学习的能力。&lt;/p&gt;

&lt;p&gt;去年觉得自己没有将技术和工作结合再一起给公司创造价值，今年可以很开心的说我已经能够结合技术和业务，我为公司创造了价值了。&lt;/p&gt;

&lt;p&gt;工作得到的回报也超出了我的想象，譬如得奖、晋升、绩效。并没有刻意追求这些，但是他们来了。很幸福，也很惶恐自己有足够实力匹配这些么，所以还有继续加油增强底气。这些东西虽是荣誉，但是给我工作带来了很多便利，因为你被重视了，沟通会变得轻松一些，不需要一而再再而三的去声明自己的观点，带着标签，发现身边的人会主动听你的声音了。。。这种现象也是没办法的事。&lt;/p&gt;

&lt;h3 id=&quot;技术&quot;&gt;技术&lt;/h3&gt;

&lt;p&gt;学的没有在学校多而丰富，但是更深入，且都跟工作相关，所以有比较好的实践。&lt;/p&gt;

&lt;p&gt;大的方向上，是react+redux及其全家桶的学习应用，并且5月份在自己的项目中使用，后推广给团队内的其他项目。使用过程中解决了很多很落地的问题，也看了react-router、redux、react-redux和一群中间件的源码，了解了整个体统的结构和运行机制，现在的深度完全跟以前学angular时停留在应用层不一样了。我关注并去了解库的底层实现。&lt;/p&gt;

&lt;p&gt;前端实在是发展太快，花样太多，因为业务需要，我又要去学vue系列了。。。&lt;/p&gt;

&lt;p&gt;还学了正则表达式系列，耐着性子看完了《精通正则表达式》，语法上没学到啥（语法其实可以随时查），但是了解了很多原理层和性能上的东西，能够有根据的在写正则表达式时为了效率做调整。最大的收获还不是技术上的，而是从心理上减轻了对正则的恐惧，后来工作中很多地方会用的上正则表达式，我不再上网查，开始手写，这样能够写出结合业务特色的正则表达式。我明白了正则表达式可以简单可以繁琐，没有什么是绝对的准确，完全看需求，需求粒度要求的细就要写出更细粒度的正则表达式，需求关注点如果只是某个方面，就可以在这个方面细化其他地方粗糙点。&lt;/p&gt;

&lt;p&gt;也研究了下函数式编程风格，恩，这个比较有争议。函数式在js里肯定不能全面推行，只用函数式去写，但是函数式的风格在很多场景很适用：日志、容错校验、中间件等，柯里化、部分应用、管道拼接起来的函数式风格代码都很美。纯函数的思想更是有利于测试。&lt;/p&gt;

&lt;p&gt;测试，恩，最近在折腾自动化单元测试，这是我的空白区，还在摸索中，对于如何写出靠谱的测试案例，测试应该关注什么，模拟什么，这个还需要好好思考。不说了，泪奔，都过年了，我的测试大业还么定格。。。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;多学学没有坏处&lt;/strong&gt;，反正时间都是会过去，出去休闲娱乐提升生活幸福感的时间，其他时间都拿来学习，丰富自己，未来总会用的上，总是有收获的。现在想想，实习时没啥中心，啃了不少基础知识，譬如浏览器渲染机制，js的单线程，异步的本质等等。这些基础知道了，对于我今年还是很有帮助的，今年我很少去查基础知识，很少因为不了解某个基础知识而掉进沟里。&lt;/p&gt;

&lt;h3 id=&quot;性格&quot;&gt;性格&lt;/h3&gt;

&lt;p&gt;性格最大的是改变。在丽江旅游时，我突然感觉到自己有点固执，不愿意接受甚至倾听跟自己不一样的观点。我觉得很可怕，这种固执会导致我不再进步，小时候我们成长很快，就是因为我们很谦虚，勤于学习身边的一切，有一颗包容学习的心，如果太固执真的会提前变老，难以接受新世界。&lt;/p&gt;

&lt;p&gt;所以我开始改变，我愿意接受倾听不同的声音，甚至认真思考合理性，其实我改变了很多观点，我发现我之前对很多东西在不是很了解的情况下就把它给否定了。幸好意识到为时不晚。我尽量不再轻易下结论，不再片面。&lt;/p&gt;

&lt;p&gt;我会去和阳光的人交朋友加微信，让阳光更多一点，毫不客气的屏蔽鸡汤、悲情、虚荣自拍的朋友状态。。。这是一种心向阳光的追求吧。同事中有的妹子真的很阳光，大大咧咧的性格很是可爱。朋友性格很好，自己也不会差。&lt;/p&gt;

&lt;p&gt;我觉得自己性格还可以吧，除了总和男票吵架。后来我发现不能全怪我，我总是大吼大叫是因为我不吼不叫对方就会忽视我的话，轻言细语根本不能引起重视，不吵架就不能解决问题一下。对于生活细节上譬如卫生方面我坚持我的主张没有错，所以我觉得这方面不能太怪自己，对方有更多责任。但是无论怎样，不应该牺牲自己变成又吼又闹的形象，我可以选择忽视、不接受，但是不再通过大吼大叫让对方就范。这方面我也克制很多了。&lt;/p&gt;

&lt;p&gt;跟同事有什么想法我现在做法都是直接说，虽然也可以找她们的leader去施压，但是我觉得能简单点还是简单点，有什么不快都直接当面说出来，目前这种方式很有效，但是我也不敢保证以后这么直接会不会得罪人，所幸程序员都比较单纯。&lt;/p&gt;

&lt;h3 id=&quot;生活&quot;&gt;生活&lt;/h3&gt;

&lt;p&gt;热爱旅行，16走过很向往的地方，玩的很精彩，及时是2个妹子去那么远且偏僻的地方，也上路了，觉得自己很酷。2017也想到处浪，但是我要存钱买房付首付。。。好纠结。。。&lt;/p&gt;

&lt;p&gt;对于男票，16我做了我不会再后悔的事，我真的用心接纳并真心喜欢对方，真的去付出，去包容。还是有很多不快，很多东西没有成长变好，我不知道17会怎样，这不是我一个人努力就能变好的，我希望自己不在被4年的时间和初恋这2个头衔击败，如果爱仍然没法一起愉快生活，那么不如痛快分了，让大家都解放，而且因为16我真的尝试付出和爱，所以不会再遗憾后悔。当然，分开是我最不愿意的结果，我更希望我们都能作出改变，变得更好，变得能让对方接受，能够一起愉快生活。&lt;/p&gt;

&lt;p&gt;开始用心做菜，研究怎样更好吃，模仿下厨房上的菜谱，再也不随意糊弄自己的嘴了，吃的有追求。&lt;/p&gt;

&lt;h3 id=&quot;2017寄语&quot;&gt;2017寄语&lt;/h3&gt;

&lt;p&gt;17年，希望活的精致。&lt;/p&gt;

&lt;p&gt;工作一如既往的用心，能够多思考，多沉淀，提出自己的合理化好想法，推动业务发展，增强推动力。&lt;/p&gt;

&lt;p&gt;技术更深入，刨根问底。沉淀出更多成果，多写写小工具，能够输出自己的学习成果，能够有拿的出手的内容去分享。写出精致的代码，继续做一个有追求的程序员。更加踏实，不要bb，不要装逼，一步一个脚印，走稳走远。关注社区动态，多尝试，了解前端发展。其实技术上真的还要&lt;/p&gt;

&lt;p&gt;性格上也是要精致，做个有修养的阳光的女孩。向坏自己说再见，改掉坏习惯，譬如懒惰，譬如犹豫，譬如克制发火。热爱生活，会寻找乐趣，广交有相同爱好的朋友，不抑郁。&lt;/p&gt;

&lt;p&gt;然后要锻炼哦，练出好身体，练出好身材，每晚坚持做瑜伽哦，每周都去跑跑步哦。听医生的话，少吃辛辣食品，不要再喝中药了，而是养成良好的饮食、作息习惯，每天喝金银花茶降火，养脸啊，消痘啊。&lt;/p&gt;

&lt;p&gt;认真化妆，认真穿搭，做个有品位的精致的女生。不要瞎买买了，少买但是要买精品。活的有品位有追求。�&lt;/p&gt;

&lt;p&gt;2016是改变，希望2017是精致。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>javascript函数式编程</title>
   <link href="http://echizen.github.io/tech/2016/12-19-js-fn"/>
   <updated>2016-12-19T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/js-fn</id>
   <content type="html">
&lt;p&gt;由于react和redux以及一群中间件的火起，越来越多的人关注函数式编程。我也去了解了下，我看了《javascript函数式编程》一书，也看了redux源码，总结一下自己的理解。文中会有书中的理论，也会有自己从redux中对这些理论的实践的思考。&lt;/p&gt;

&lt;h2 id=&quot;javascript-与函数式编程&quot;&gt;javascript 与函数式编程&lt;/h2&gt;

&lt;p&gt;老实说，我的感受是js不是一门很适合函数式编程的语言，这个语言的设计模式更适合面向对象编程，因为从语言层面上，他并没有不可变数据，还是弱类型的数据。但是拥有强大的原型及原型链体系，加上es6、es7规范出来后从语言层面逐渐引入了类和继承。&lt;/p&gt;

&lt;p&gt;如果真的想深入了解函数式编程，社区建议去看看&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Haskell&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClojureScript&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Elm&lt;/code&gt;，这些一开始就将函数式思想嵌入到语言层面的语言。&lt;/p&gt;

&lt;p&gt;但是函数在js中是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;first class citizen&lt;/code&gt;，函数可以被当做参数传入函数，也能被函数返回，这就给函数式编程带来了环境。而且闭包的方式也能创造私有变量，保证不受污染。&lt;/p&gt;

&lt;p&gt;函数式编程与面向对象编程各有优势，当面对一些场景使用函数式模型更方便时，多一个工具在面前，倒是也值的去研究一番。&lt;/p&gt;

&lt;h2 id=&quot;函数式编程的常见手法&quot;&gt;函数式编程的常见手法&lt;/h2&gt;

&lt;h3 id=&quot;curry柯里化&quot;&gt;curry柯里化&lt;/h3&gt;

&lt;p&gt;柯里化函数为每一个逻辑参数返回一个新函数。柯里化函数逐渐返回消耗参数的函数，直到所有参数耗尽。&lt;/p&gt;

&lt;h4 id=&quot;实现&quot;&gt;实现&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function curry(fun){
    return function(arg){
        return fun(arg)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这是简单的2个参数函数的柯里化实现，还可以实现3个、4个参数，譬如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function curry3(fun){
    return function(last){
        return function(middle){
            return function(first){
                return fun(first,middle,last)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ps:还是箭头函数更优雅：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curr3 = fun =&amp;gt; last =&amp;gt; middle =&amp;gt; first =&amp;gt; fun(first,middle,last)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;实践&quot;&gt;实践&lt;/h4&gt;

&lt;p&gt;使用柯里化实现一个将色值rgb转化为16进制值的函数&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function toHex(n){
	var hex = n.toString(16);
	return (hex.length&amp;lt;2) ? [0,hex].json(&apos;&apos;):hex;
}

function rgbToHexString(r, g, b){
	return [&quot;#&quot;,toHex(r),toHex(g),toHex(b)].join(&apos;&apos;);
}

var blueGreenish = curry3(rgbToHexString)(255)(200);
blueGreenish(0);
// #00c8ff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;redux的applyMiddleware串联的中间件的结构就是典型的柯里化手法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;({ dispatch ,  getState })  =&amp;gt;  next  =&amp;gt;  action  =&amp;gt;  { 
  //middleware content here
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如著名的库&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redux-thunk&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thunkMiddleware&lt;/code&gt;中间件的实现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function thunkMiddleware(_ref) {
  var dispatch = _ref.dispatch;
  var getState = _ref.getState;

  return function (next) {
    return function (action) {
      return typeof action === &apos;function&apos; ? action(dispatch, getState) : next(action);
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样在我们使用异步action时调用的dispatch(actionFun)就是thunkMiddleware封装的dispatch，actionFun能够取到dispatch和getState；如果是同步action，也就是action对象时，则直接运行next(action)，这个next，是上一个中间件封装吐出来的dispatch方法。&lt;/p&gt;

&lt;h4 id=&quot;作用&quot;&gt;作用&lt;/h4&gt;

&lt;p&gt;柯里化可以逐步接受参数进行“懒执行”，能按步骤处理参数，保存参数，为未来的调用做准备。&lt;/p&gt;

&lt;p&gt;可以为每一个参数进行一定的处理操作，而且这种处理是“懒处理”，调用一次处理一个参数。这种方式对同一个函数实现部分定制化，譬如blueGreenish函数利用柯里化的方式将色值确定在蓝绿色段，再传入第4个参数得到蓝绿色段的不同色值。当然，我们还可以通过控制第2、3个参数得到红黄色段的处理函数。&lt;/p&gt;

&lt;p&gt;使用柯里化比较容易产生流利的函数式API。Haskell中，函数式默认柯里化的。柯里化的用法比直接使用匿名函数要容易阅读，代码阅读起来越像他的行为描述越好。&lt;/p&gt;

&lt;h4 id=&quot;缺陷&quot;&gt;缺陷&lt;/h4&gt;

&lt;p&gt;js的函数不是默认柯里化的，对于不能参数个数的函数实现柯里化需要大量手动转换，或者借助于Underscore这样的库。柯里化对于函数参数数目不确定的情况更是束手无策，灵活性不够。&lt;/p&gt;

&lt;h3 id=&quot;部分应用&quot;&gt;部分应用&lt;/h3&gt;

&lt;p&gt;部分应用是一个“部分”执行，等待接收剩余的参数立即执行的函数。&lt;/p&gt;

&lt;p&gt;部分应用不是一个个消耗参数，而是先处理一部分参数，等到另一部分参数传入调用后，再处理这部分剩下的参数。&lt;/p&gt;

&lt;h4 id=&quot;实现-1&quot;&gt;实现&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function partical(fun,/*,pargs*/){
	var pargs = _.rest(arguments);
	return function(/*arguments*/){
		var args = cat(pargs,_.toArray(arguments));
		return fun.apply(fun,args);
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(使用了Underscore)&lt;/p&gt;

&lt;h3 id=&quot;高阶函数&quot;&gt;高阶函数&lt;/h3&gt;

&lt;p&gt;这个概念比较基础，就不说了，就是函数的返回值也是函数，可嵌套任意层。&lt;/p&gt;

&lt;h4 id=&quot;实践-1&quot;&gt;实践&lt;/h4&gt;

&lt;p&gt;react的高阶组件。&lt;/p&gt;

&lt;p&gt;高阶组件就是一个高阶函数，通过传入的组件参数，对组件做一层封装和参数预处理，实现组件差异化。用高阶组件来说高阶函数有点不合形式，但是思想是一样的（其实react组件也可以用纯函数生成，而不是这种对象模式啊。）&lt;/p&gt;

&lt;p&gt;当你有几个组件非常相似，但是又有部分不同时，这时可以封装成高阶组件来处理，传入的组件参数带有差异化的props，甚至是不同的组件，高阶组件内会做一些通用化的处理。&lt;/p&gt;

&lt;p&gt;譬如搜索框组件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const AsyncSelectDecorator = Wrapper =&amp;gt; {
  return  class WrapperComponent extends Component {
    componentDidMount() {
      const { url } = this.props;
      
      fetch(url)
      .then(response =&amp;gt; response.json())
      .then(data =&amp;gt; {
        this.setState({
          data,
        });
      });
    }

    render() {
      const { data } = this.state;
      return (
        &amp;lt;Wrapper
          {...this.props}
          data={data}
        /&amp;gt;
      );
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;获取数据接口是通用的，但是搜索框ui样式可能各异，这时候我们可以通过传入的Wrapper组件来定制化搜索样式，而高阶组件WrapperComponent则完成取数据这种通用化操作。&lt;/p&gt;

&lt;h3 id=&quot;作用-1&quot;&gt;作用&lt;/h3&gt;

&lt;p&gt;高阶函数可以实现函数的复合，达到更多的组合目的。&lt;/p&gt;

&lt;h3 id=&quot;函数组合&quot;&gt;函数组合&lt;/h3&gt;

&lt;p&gt;compose函数能够实现函数的管道连接，简单说就是按一定顺序执行传入的函数参数，通常是从右至左，前一个函数的返回结果将成为下一个函数的参数。&lt;/p&gt;

&lt;h4 id=&quot;实现-2&quot;&gt;实现&lt;/h4&gt;

&lt;h5 id=&quot;compose从右到左&quot;&gt;compose：从右到左&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function compose(...funcs) {
  funcs = funcs.filter(func =&amp;gt; typeof func === &apos;function&apos;)

  if (funcs.length === 0) {
    return arg =&amp;gt; arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) =&amp;gt; (...args) =&amp;gt; a(b(...args)))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;相当于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compose(a,b,c)(...args)&lt;/code&gt;的结果是&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function(...args) {
  a(b(c(...args)))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;pipe从左到右&quot;&gt;pipe:从左到右&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const pipe = (fns) =&amp;gt; (x) =&amp;gt; fns.reduce((v, f) =&amp;gt; f(v), x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;eg:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const add1 = (a) =&amp;gt; a + 1
const times2 = (a) =&amp;gt; a * 2
const times2add1 = pipe([times2, add1])
times2add1(5) // =&amp;gt; 11
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;实践-2&quot;&gt;实践&lt;/h4&gt;

&lt;p&gt;这也是applyMiddleware，redux丰富的中间件连接得以运转的核心模式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function applyMiddleware(...middlewares) {
  return (createStore) =&amp;gt; (reducer, preloadedState, enhancer) =&amp;gt; {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) =&amp;gt; dispatch(action)
    }
    chain = middlewares.map(middleware =&amp;gt; middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当我们使用中间件，如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const store = applyMiddleware(
  thunkMiddleware,
  logger
)(createStore)(rootReducer, initialState);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;applyMiddleware中的compose函数从右至左执行传入的函数，函数的运行结果作为参数传给下一个函数，在applyMiddleware就是不断逐层封装dispatch，使中间件在action派发时能对其做相应处理，实现强大的功能。&lt;/p&gt;

&lt;h4 id=&quot;作用-2&quot;&gt;作用&lt;/h4&gt;

&lt;p&gt;compose实现了一种函数组合方式，这种端至端的管道连接，能够有序的完成一系列操作，保证了函数的输入和输出规范后，还可以自由组合符合规则的函数，实现丰富的功能。&lt;/p&gt;

&lt;h3 id=&quot;管道&quot;&gt;管道&lt;/h3&gt;

&lt;p&gt;管道是将一群函数链接起来，上一个函数的执行结果作为下一个函数的参数&lt;/p&gt;

&lt;p&gt;链接模式有利于给对象的方法创建流畅的API。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function pipeline(seed /*,args*/ ){
    return _.reduce(_.rest(arguments),
      function(l,r) {
        return r(l)
      },
      seed
    )
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在js中使用管道，再加上柯里化和部分应用，提供了强有力的方式来组合流畅的函数。&lt;/p&gt;

&lt;p&gt;但当数据从一个函数流到下一个时，经常会被间接和深嵌套函数阻碍。用管道可以使得数据流更加明确。但是管道并不适合所有情况，譬如带有副作用的I/O操作、Ajax调用或突变，因为他们数据流出不明确。&lt;/p&gt;

&lt;p&gt;管道要求每个函数输入输出数据格式一致，这样才能让数据继续流到下一个函数被处理，这就要求我们做好控制流。抽象出一个通用的数据格式作为数据流。譬如redux中的action对象，抽出来就是{type:ACTION_TYPE, payload:data}，这个action是每一个中间件的输入，也是输出，每个中间间内部通过getState接口获得最新的state，并能处理state暂存后用。&lt;/p&gt;

&lt;h2 id=&quot;从redux中看函数式编程的实践&quot;&gt;从redux中看函数式编程的实践&lt;/h2&gt;

&lt;p&gt;redux以及其连接的中间件都是非常棒的函数式编程的实践。&lt;/p&gt;

&lt;p&gt;redux很精简，源码加起来也不超1000行，拥有几个核心函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createStore,
combineReducers,bindActionCreators,applyMiddleware&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;createstore&quot;&gt;createStore&lt;/h3&gt;

&lt;p&gt;除去容错代码，核心的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch,subscribe,getState&lt;/code&gt;的实现代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default function createStore(reducer, preloadedState, enhancer) {
  
  if (typeof enhancer !== &apos;undefined&apos;) {
    return enhancer(createStore)(reducer, preloadedState)
  }

  var currentReducer = reducer
  var currentState = preloadedState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  function getState() {
    return currentState
  }

  function subscribe(listener) {

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    var listeners = currentListeners = nextListeners
    for (var i = 0; i &amp;lt; listeners.length; i++) {
      var listener = listeners[i]
      listener()
    }

    return action
  }

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;比较有趣的是tsubscribe的实现，它返回了一个函数用来解除绑定，使用姿势：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let unsubscribe = store.subscribe(handleChange)
unsubscribe()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种方式去除了冗余代码，非常优雅简洁。&lt;/p&gt;

&lt;p&gt;如果传入enhancer 传进createStore 里面的话，那基本上得到的store 就会是enhancer 包装出来的结果。&lt;/p&gt;

&lt;p&gt;ps官方使用示例：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const store = createStore(
  reducer,
  preloadedState,
  applyMiddleware(...middleware)
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;仔细一看，跟我之前提到的接入了redux-thunk的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thunkMiddleware&lt;/code&gt;其实是一样的，thunkMiddleware的方式更直接。所以函数式还是蛮绕的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const store = applyMiddleware(
  thunkMiddleware,
  logger
)(createStore)(rootReducer, initialState);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;combinereducers&quot;&gt;combineReducers&lt;/h3&gt;

&lt;p&gt;去除容错代码，核心代码是一个高阶函数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default function combineReducers(reducers) {
  
  var finalReducerKeys = Object.keys(reducers)

  return function combination(state = {}, action) {
    var hasChanged = false
    var nextState = {}
    for (var i = 0; i &amp;lt; finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;combineReducers在creatStore前被调用，譬如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const rootReducer = combineReducers({a,b,c});
const store = applyMiddleware(
  thunkMiddleware,
  logger
)(createStore)(rootReducer, initialState);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;返回函数在dispatch中被调用用于获得新状态。&lt;/p&gt;

&lt;h3 id=&quot;bindactioncreator&quot;&gt;bindActionCreator&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function bindActionCreator(actionCreator, dispatch) {
  return (...args) =&amp;gt; dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === &apos;function&apos;) {
    return bindActionCreator(actionCreators, dispatch)
  }

  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i &amp;lt; keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === &apos;function&apos;) {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;bindActionCreator返回对象或者函数，对象的属性也是函数，这个函数返回是dispatch调用的actionCreator函数调用结果，所以actionCreator的调用结果能够被继续dispatch下去。也就是通过bindActionCreator可以注入dispatch，且通过connect将bindActionCreator返回结果注入到view层中，这样我们在view层调用action只用使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.props.actions.funA()&lt;/code&gt;，而不用处处&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import {funA} from actionA&lt;/code&gt; ，然后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch(funA())&lt;/code&gt;了。&lt;/p&gt;

&lt;p&gt;这种语法糖带来了便捷。&lt;/p&gt;

&lt;p&gt;applyMiddleware已分析过。&lt;/p&gt;

&lt;p&gt;总的来看，函数是一个功能的单元，函数的作用域是隔离的，高阶函数的调用让我们既能保护函数内容私有变量，又能获得函数的引用，被多处调用。&lt;/p&gt;

&lt;h2 id=&quot;核心&quot;&gt;核心&lt;/h2&gt;

&lt;p&gt;数据流、纯度&lt;/p&gt;

&lt;p&gt;纵观函数式编程得益点多是纯函数链接成管道，数据流流入流出，保持不变性。所以函数式编程的要素是保持函数的纯度。&lt;/p&gt;

&lt;p&gt;因此，函数式系统努力减少可见的状态修改。向一个遵循函数式原则的系统添加新功能，需要在一个局限的隔离的上下文环境中（独立作用域），进行无破坏性的数据转换，来实现新函数。&lt;/p&gt;

&lt;p&gt;函数式编程以命令式的方式构建系统，并通过将显性的状态改变缩减到最小来变得更加模块化。实际应用中，想要完全消除状态改变可能不太现实，但是我们要将任何已知系统中的突变尽量压缩到最少，突变影响范围做到可控。&lt;/p&gt;

&lt;h2 id=&quot;适用场景&quot;&gt;适用场景&lt;/h2&gt;

&lt;p&gt;函数式编程风格非常适合开发一些中间件和独立的模块。譬如日志统计、安全控制、异常处理。&lt;/p&gt;

&lt;p&gt;把一些跟核心业务逻辑模块无关的功能抽离出来。把这些功能抽离出来之后，再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性，其次是可以很方便地复用日志统计等功能模块。&lt;/p&gt;

&lt;h3 id=&quot;面向对象编程-vs-函数式编程&quot;&gt;面向对象编程 vs 函数式编程&lt;/h3&gt;

&lt;h4 id=&quot;面向对象编程&quot;&gt;面向对象编程&lt;/h4&gt;

&lt;p&gt;面向对象编程的主要目标是问题分解，每个小模块是一个类，多个类组合在一起形成更大的功能集群。基于每个部件和他们之间的组合关系，我们可以从部件之间的交互和值来描述一个系统。&lt;/p&gt;

&lt;h5 id=&quot;缺陷-1&quot;&gt;缺陷&lt;/h5&gt;

&lt;p&gt;在一个面向对象系统的内部，我们发现对象建的交互会引起各个对象内容状态的变化，而整个系统的状态转变则是由许许多多小的，细微的变化混合来形成的，这些相互关联的状态变化形成了一个变化网，系统越大越难搞清楚究竟变化源在哪里，影响面积有多大，总是给系统的维护和继续开发带来麻烦。&lt;/p&gt;

&lt;h4 id=&quot;函数式编程&quot;&gt;函数式编程&lt;/h4&gt;

&lt;p&gt;函数式编程的方法解决问题，也会将一个问题分成几部分来解决。与面向对象方法将问题分解成多组“名词”或对象不同，函数式方法将相同的问题分解成多组“动词”或函数。组合单个函数来构建更大的函数，实现更加抽象的系统。&lt;/p&gt;

&lt;p&gt;一种将函数式部件构成一个完整系统的方法是取一个值，逐渐的将它“改变”成另一个值，也就是形成数据流。&lt;/p&gt;

&lt;h5 id=&quot;优势&quot;&gt;优势&lt;/h5&gt;

&lt;p&gt;函数式编程相对于面向对象编程，会尽量减少可见的状态修改。&lt;/p&gt;

&lt;p&gt;因为数据流和函数纯度的保证，对于测试非常友好&lt;/p&gt;

&lt;h5 id=&quot;劣势&quot;&gt;劣势&lt;/h5&gt;

&lt;p&gt;保持函数式纯函数需要做很多额外的努力，因为js语言层面上的几种基本类型数据都是可变的。&lt;/p&gt;

&lt;p&gt;团队的接受度和认可度。大多数人习惯的是面向对象的风格，函数式的代码初读会很懵逼（譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;({ dispatch ,  getState })  =&amp;gt;  next  =&amp;gt;  action  =&amp;gt;  { //middleware}）&lt;/code&gt;，高阶函数的内容并不那么直观，对调用也有一定要求。所以如果多人合作的团队只有你一人热衷于函数式，这种风格肯定是不能在项目中发展的，因为会给其他人带来困惑和维护合作上的困难。&lt;/p&gt;

&lt;h3 id=&quot;选择最合适的方式&quot;&gt;选择最合适的方式&lt;/h3&gt;

&lt;p&gt;在实践中我们不应该追求纯粹的函数式编程或者面向对象编程方式，而是哪种方式更合适就选择哪种，需要参考具体应用场景和团队习惯。&lt;/p&gt;

&lt;p&gt;甚至我们可以结合2者，来实现一个功能丰富又足够灵活的系统，譬如Mixins的手法。&lt;/p&gt;

&lt;h3 id=&quot;番外&quot;&gt;番外&lt;/h3&gt;

&lt;h3 id=&quot;javascript函数式编程&quot;&gt;《javascript函数式编程》&lt;/h3&gt;

&lt;p&gt;这本书收到的赞扬在我看来有点过了，里面很多结论来的比较突然，研究好几遍还是不明白结论怎么来的，甚至一些结论并没有给出依据。当然也有可能我读的是翻译版，比较生硬。&lt;/p&gt;

&lt;p&gt;使用Underscore作为示例也是一个不太友好的地方，没接触过这个库看示例会很不习惯，总是会不由自主脑补js原生函数的写法。当然作者写这本书的时候可能这个库比较火。这个库也是函数式规范的一个很好实践，我看了3本与函数式编程相关的书籍的示例都是依赖这个库的。。。&lt;/p&gt;

&lt;p&gt;书的前几章比较基础的介绍概念，一群名词，比较生硬，很多解释也很唐突，还不如去搜一些其他的资料了解下基础。但是从第七章开始，开始有很棒的函数式思想贯穿，示例也变得完整和系统性了，7、8、9章还是很值得读的，介绍了纯度、不变性和对变化的控制，介绍了基于流的链接、管道，数据流和控制流，以及无类编程，重点给出很好的示例体现了Mixins的强大。书中有一些示例很不错，譬如错误验证，前置条件和后置条件的逐步添加，很好的体现了函数式控制流的灵活性。&lt;/p&gt;

&lt;h3 id=&quot;关于流行模式&quot;&gt;关于“流行”模式&lt;/h3&gt;

&lt;p&gt;看完《javascript函数式编程》，会觉得redux并不是什么新东西，里面的模式都是函数式编程的常见手法，几个关键实现譬如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thunk&lt;/code&gt;都在书中有基础示例，而这本书比redux诞生的早的多。&lt;/p&gt;

&lt;p&gt;所以前端的很多新流行起来的东西其实在一些老玩家面前并不新鲜，可能就是某些经典模式的实践，或者某些后端已有的模式譬如之前的mvvm，前端起步比较晚，正在快速吸收其他程序领域的经典实践，新手才感觉变化大吧，估计老玩家不会有我这种风起云涌的感受。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>正则引擎与效率</title>
   <link href="http://echizen.github.io/tech/2016/10-28-reg-engine"/>
   <updated>2016-10-28T21:52:21+00:00</updated>
   <id>http://echizen.github.io/tech/2016/reg-engine</id>
   <content type="html">
&lt;h2 id=&quot;引擎种类及特点&quot;&gt;引擎种类及特点&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;NFA（非确定型有穷自动机）：表达式主导。移动正则表达式筛选文本。同一个字符会被检查多次，即使针对同一段子表达式。表达式的书写方式对匹配效率有很大的影响&lt;/li&gt;
  &lt;li&gt;DFA（确定型有穷自动机）：文本主导。移动文本筛选可能匹配的正则表达式，所扫描的字符串中的每个字符都对引擎进行了控制。并且在扫描字符时，会记录当前有效的所有匹配可能，每个字符只会被检测一次。效率更高&lt;/li&gt;
  &lt;li&gt;POSIX NFA:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;判断是NFA: 忽略优先量词（*?,+?,??,{num,num}?）被支持
判断是DFA: 捕获型括号和回溯不被支持&lt;/p&gt;

&lt;h3 id=&quot;编译&quot;&gt;编译&lt;/h3&gt;
&lt;p&gt;在正则表达式搜索之前，两种引擎都会编译表达式，得到一套内化形式，适应各自的匹配算法。NFA的编译过程通常要快一点，需要的内存也更少一点。&lt;/p&gt;

&lt;h3 id=&quot;匹配结果&quot;&gt;匹配结果&lt;/h3&gt;
&lt;p&gt;DFA返回最左边最长的匹配结果&lt;/p&gt;

&lt;h2 id=&quot;匹配原理&quot;&gt;匹配原理&lt;/h2&gt;

&lt;h3 id=&quot;优先选择最左端的匹配结果&quot;&gt;优先选择最左端的匹配结果&lt;/h3&gt;
&lt;p&gt;“传动装置”会从字符串的第一个位置开始尝试正则表达式体现的所有可能情况，如果不符合再从第二个位置开始尝试匹配，直到匹配成功或者最后一个位置都不能成功&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;起始位置和结束位置锚点^$能够提高效率&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;标准的匹配量词numnum是匹配优先的&quot;&gt;标准的匹配量词（*,+,?,{num,num}）是匹配优先的&lt;/h3&gt;
&lt;p&gt;标准匹配量词在匹配成功之前进行尝试的次数是有上下限的，这些尝试总是希望获得最长的匹配&lt;/p&gt;

&lt;p&gt;匹配优先的注意点：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;不滥用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(.*)&lt;/code&gt;，譬如匹配双引号之间的文本，不使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;(.*)&quot;/&lt;/code&gt;,而是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;(^&quot;*)&quot;/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;善用忽略优先，譬如要匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;a&amp;gt;&amp;lt;/a&amp;gt;&lt;/code&gt;之间的元素，不使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&amp;lt;a&amp;gt;.*&amp;lt;/a&amp;gt;/&lt;/code&gt;，而是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&amp;lt;a&amp;gt;.*?&amp;lt;/a&amp;gt;/&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;忽略优先量词&quot;&gt;忽略优先量词&lt;/h3&gt;

&lt;p&gt;*?, +?, ?? , {num,num}?&lt;/p&gt;

&lt;h3 id=&quot;占有优先量词&quot;&gt;占有优先量词&lt;/h3&gt;

&lt;p&gt;*+，++，?+，{num,num}+&lt;/p&gt;

&lt;p&gt;占有优先量词与匹配优先量词很类似，只是从不交还已匹配的字符。加号在匹配之前保存了很少的状态，有利于提升性能&lt;/p&gt;

&lt;h3 id=&quot;匹配优先和忽略优先都为全局服务&quot;&gt;匹配优先和忽略优先都为全局服务&lt;/h3&gt;

&lt;p&gt;如果只有一条可能的路径，那么使用匹配优先量词或者忽略优先量词都能找到这个结果，只是他们尝试路径的次序不同，并不影响结果，只是影响效率。&lt;/p&gt;

&lt;h3 id=&quot;多选分支&quot;&gt;多选分支&lt;/h3&gt;
&lt;p&gt;多选分支既不是匹配优先，也不是忽略优先，而是对于每一个字符按顺序尝试匹配。&lt;/p&gt;

&lt;p&gt;用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tour|to|tourenament/&lt;/code&gt;匹配’tourenaments’，得到的结果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tour&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;合理安排多选分支的结构：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;针对匹配对象，将匹配成功可能性最大的子分支放在最前&lt;/li&gt;
  &lt;li&gt;如果一个分支是另一个分支的子集，将父集置于最先，以免不必要的检查。如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/\.\d\d|\.\d\d[1-9]/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;尽可能合并多选分支，减少子分支长度。任何多选分支匹配失败都会带来回溯影响性能。&lt;/p&gt;

&lt;h2 id=&quot;引擎的构造单元&quot;&gt;引擎的构造单元&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;文本文字&lt;/li&gt;
  &lt;li&gt;字符组、点号、unicode属性及其他&lt;/li&gt;
  &lt;li&gt;捕获型括号&lt;/li&gt;
  &lt;li&gt;锚点：^,$,顺序环视,逆序环视&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;回溯&quot;&gt;回溯&lt;/h2&gt;

&lt;p&gt;NFA引擎的重要特性：它会依次处理各个子表达式或组成元素，遇到需要在2个可能成功的可能中进行选择的时候，它会选择其中一个，同时记住另一个，以备稍后可能的需要。需要作出选择的情况包括量词和多选结构。&lt;/p&gt;

&lt;h3 id=&quot;回溯时使用的选择&quot;&gt;回溯时使用的选择&lt;/h3&gt;

&lt;p&gt;距离当前最近存储的选项就是当本地失败强制回溯时返回的。使用的原则是LIFO（后进先出）。&lt;/p&gt;

&lt;h3 id=&quot;量词的回溯&quot;&gt;量词的回溯&lt;/h3&gt;
&lt;p&gt;量词匹配优先，并不受其后元素的影响，但是回溯会受其后元素的影响&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;？
 保存?前的回溯&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;*
 保存*匹配的元素的每一个位置&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;使用技巧&quot;&gt;使用技巧&lt;/h2&gt;

&lt;p&gt;表达式的复杂和精确程度根据需求调整，越复杂精确一般效率越低，需要权衡。&lt;/p&gt;

&lt;p&gt;所以可以：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;采用比较模糊的匹配，然后对匹配结果进行进一步校验&lt;/li&gt;
  &lt;li&gt;根据匹配对象生成表达式，如是否需要开始结束锚点&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果回溯会导致不期望，与多选机构有关的匹配结果，问题很可能在于，任何成功的匹配都不过是多选分支的排列顺序造成的偶然结果。这说明多个多选分支的内容发生了重叠。&lt;/p&gt;

&lt;h3 id=&quot;排除子集父集模式&quot;&gt;排除子集|父集模式&lt;/h3&gt;

&lt;p&gt;匹配连续行：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/[^\n\\]|\./&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;组合可能性&quot;&gt;组合可能性&lt;/h3&gt;

&lt;p&gt;匹配IP地址：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^([01]?\d\d?|2[0-4]\d|25[0-5])\.{3,3}([01]?\d\d|2[0-4]\d|25[0-5])/&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;使用锚点提高效率&quot;&gt;使用锚点提高效率&lt;/h3&gt;

&lt;p&gt;匹配路径和文件名：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^(.*)/([^/])*$/&lt;/code&gt;, path=$1, file=$2&lt;/p&gt;

&lt;h3 id=&quot;匹配对称的符号&quot;&gt;匹配对称的符号&lt;/h3&gt;

&lt;p&gt;正则表达式可以匹配特定深度的嵌套结构，但是无法匹配任意深度的的嵌套结构。&lt;/p&gt;

&lt;p&gt;匹配单层对称的括号： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/\[^()]*(\([^()]*\)[^()]*)*\)/&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;防备不期望的匹配&quot;&gt;防备不期望的匹配&lt;/h3&gt;

&lt;p&gt;错误的浮点数匹配：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/-?[0-9]*\.?[0-9]*/&lt;/code&gt;，每个部分都不是必须的，所以可以匹配任意字符。&lt;/p&gt;

&lt;p&gt;匹配浮点数：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/-?([0-9]+(\.[0-9]*)?|\.[0-9]+)/&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;匹配分隔符之内的文本&quot;&gt;匹配分隔符之内的文本&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;匹配起始分隔符&lt;/li&gt;
  &lt;li&gt;匹配正文：匹配正文的时候不要超越结束分隔符&lt;/li&gt;
  &lt;li&gt;匹配结束分隔符&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;匹配引文字符串，但是允许其中包含转义的引号：
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/*(\\.|[^&quot;])*+&quot;/&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;为了效率将复杂表达式拆成多个简单高效的表达式&quot;&gt;为了效率，将复杂表达式拆成多个简单高效的表达式&lt;/h3&gt;

&lt;p&gt;譬如匹配文本首尾字符用：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^\s+/&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/\s+$/&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;而不是：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/\s*(.*?)\s*$/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^\s+|\s+$/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;拆分单元组合情况&quot;&gt;拆分单元,组合情况&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;匹配html 的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&lt;/code&gt;标签：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&amp;lt;(?:&quot;[^&quot;]*+&quot;|&apos;[^&apos;]*+&apos;|[^&apos;&quot;&amp;gt;])*&amp;gt;/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;匹配a标签：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&amp;lt;a([^&amp;gt;]+)&amp;gt;(.*?)&amp;lt;/a&amp;gt;/ig&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;匹配href的值：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/href\s*=\s*(?:&quot;([^&quot;]*)&quot;)|&apos;([^&apos;]*)&apos;|([^&apos;&quot;&amp;gt;\S]+)/xi&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;获取url的主机名、端口号、路径：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^http://([^/:]+)(:(\d+))?(/.*)?$/i&lt;/code&gt;，$1是主机名，$3如果存在是端口号，$4是路径&lt;/li&gt;
  &lt;li&gt;匹配url：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^(ftp|https?)://(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+(?:com|edu|biz|gov|in(?:t|fo)|mil|net|org|[a-z][a-z])(:\d+)?(/[^.!,?;&quot;&apos;&amp;lt;&amp;gt;()\[\]{}\s\x7F-\xFF]*(?:[.!,?]+[^.!,?;&quot;&apos;&amp;lt;&amp;gt;()\[\]{}\s\x7F-\xFF]+)*)?$/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;不依赖传动装置的驱动过程保持数据的协调性案例&quot;&gt;不依赖传动装置的驱动过程，保持数据的协调性案例&lt;/h3&gt;

&lt;p&gt;案例：需要处理一系列5位数的邮政编码，从中跳过以44开头的邮政编码&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/(?:[^4]\d\d\d\d|\d[^4]\d\d\d)*(44\d\d\d)/&lt;/code&gt;。注意不能使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/(?:[^4][^4]\d\d\d)*/&lt;/code&gt;，他无法匹配43210这样的邮政编码&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/(?:\d\d\d\d\d)*?(44\d\d\d)/&lt;/code&gt;，忽略优先量词&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这并没有解决问题，因为一旦本轮匹配失败后还是会进行驱动过程和重试，这样就会从邮政编码字符串的某个位置开始。解决方法是将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;44\d\d\d&lt;/code&gt;改为匹配优先的可选项，这样就不会发生匹配失败和驱动过程的回溯了：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/(?:[^4]\d\d\d\d|\d[^4]\d\d\d)*(44\d\d\d)?/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;效率&quot;&gt;效率&lt;/h2&gt;

&lt;h3 id=&quot;常见优化措施&quot;&gt;常见优化措施：&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;加速某些操作：某些类型的匹配，例如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\d+&lt;/code&gt;即为常见，引擎对此有特殊的处理方案，执行速度比通用的处理机制要快。&lt;/li&gt;
  &lt;li&gt;避免冗余操作。尽量用锚点，且将锚点独立出来。譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^(?:abc|123)/&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^abc|^123/&lt;/code&gt;虽然是等效的，但是大多数正则引擎只会对第一个表达式使用开头字符/字符串识别优化。&lt;/li&gt;
  &lt;li&gt;从量词中提取必须的元素，例如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/xx*/&lt;/code&gt;比&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/x+/&lt;/code&gt;能适用的优化措施更多&lt;/li&gt;
  &lt;li&gt;主导引擎的匹配，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/th(?:is|at)/&lt;/code&gt;而不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/this|that/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;正则表达式应用原理&quot;&gt;正则表达式应用原理&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;http://moguimg.u.qiniudn.com/p1/161106/idid_ifqweoddmmzwczrqmmzdambqgyyde_796x666.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://moguimg.u.qiniudn.com/p1/161106/idid_ifrgcmjtgy2wczrqmmzdambqmeyde_812x302.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;编译缓存内置&quot;&gt;编译缓存(内置)&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;引擎内置
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;通过传动装置进行优化内置&quot;&gt;通过传动装置进行优化(内置)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;行锚点优化&lt;/li&gt;
  &lt;li&gt;开头字符/字符组/子串识别优化&lt;/li&gt;
  &lt;li&gt;内嵌文字字符串检查优化&lt;/li&gt;
  &lt;li&gt;长度识别优化传动&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;优化表达式本身&quot;&gt;优化表达式本身&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;文字字符串连续优化，如字符组&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[abc]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;消除无作用的括号，使用非捕获型括号&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?:)&lt;/code&gt;，而且括号会组织某些优化措施，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(.*)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;消除不需要的字符组，如单个字符的字符组&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[a]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;“过度”回溯检测，避免指数级： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(.+)*&lt;/code&gt;会在引擎超限时停止匹配。&lt;strong&gt;使用占有优先量词削减状态&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;先迈最好使的腿&quot;&gt;先迈最好使的腿&lt;/h3&gt;

&lt;p&gt;将最容易匹配的子表达式放最前面。&lt;/p&gt;

&lt;p&gt;例如匹配双引号字符串： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;([^&quot;\\]|\\.)*&quot;/&lt;/code&gt;比&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;(\\.|[^&quot;\\])*&quot;/&lt;/code&gt;更高效，但是降低了准确性，譬如会错误匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;You need a 2\&quot;3\&quot; photo.&quot;&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;You need a 2\&quot;&lt;/code&gt;部分？。&lt;/p&gt;

&lt;p&gt;将结尾部分分散到多选分支内，这样任意一个包含结尾部分的分支匹配失败来的更快。&lt;/p&gt;

&lt;h3 id=&quot;限制匹配优先的作用范围&quot;&gt;限制匹配优先的作用范围&lt;/h3&gt;

&lt;p&gt;对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;([^&quot;\\]|\\.)*&quot;/&lt;/code&gt;如果对更通用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[^&quot;\\]&lt;/code&gt;使用占有优先量词+*作用后，能达到更高效率的效果。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;([^&quot;\\]+|\\.)*&quot;/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;新增的加号大大减少了多选结构回溯的次数，以及星号的迭代次数。星号量词作用于括号内的子表达式，每次迭代都需要进入后再退出括号，这都需要成本，因为引擎需要记录括号内的子表达式匹配的文本。&lt;/p&gt;

&lt;h4 id=&quot;指数级匹配&quot;&gt;指数级匹配&lt;/h4&gt;

&lt;p&gt;但是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;的排列组合对于无法匹配的元素会造成“指数级匹配”，回溯次数指数级上涨。&lt;/p&gt;

&lt;p&gt;为了避免&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;(\\.|[^\\&quot;]+)*&quot;/&lt;/code&gt;的指数级匹配，可以使用：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;[^\\&quot;]+(\\.[^\\&quot;]+)*&quot;/&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;避免指数级匹配，消除循环常见解法：&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opening normal*(special nomal*)*closeing&lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;注意：&lt;/p&gt;
    &lt;ol&gt;
      &lt;li&gt;special部分和normal部分匹配的开头不能重合&lt;/li&gt;
      &lt;li&gt;normal部分必须匹配至少一个字符&lt;/li&gt;
      &lt;li&gt;special部分是固化的&lt;/li&gt;
    &lt;/ol&gt;

    &lt;p&gt;使用“自顶向下的视角”分析这个问题：
 如果我们认为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[^\\&quot;]&lt;/code&gt;能够处理字符串中的绝大部分字符，就知道如果匹配停止一定是遇到了闭引号或者时转义字符。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用占有优先量词
 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;(\\.|[^\\&quot;]++)*+&quot;/&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;无法匹配时的回溯效率&quot;&gt;无法匹配时的回溯效率&lt;/h3&gt;
&lt;p&gt;排除字符时确定对象，减少使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;。使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[^&quot;]*&lt;/code&gt;取代&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;[^&quot;]*&quot;/&lt;/code&gt;在无法匹配时回溯的次数比&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&quot;.*&quot;/&lt;/code&gt;少很多&lt;/p&gt;

&lt;h3 id=&quot;多选结构代价很高&quot;&gt;多选结构代价很高&lt;/h3&gt;

&lt;p&gt;尽量使用字符组而不是多选结构。&lt;/p&gt;

&lt;h3 id=&quot;预查必须字符子字符串优化&quot;&gt;预查必须字符/子字符串优化&lt;/h3&gt;

&lt;p&gt;相比正则表达式的完整应用，在字符串中搜索某个字符是更加“轻量级”的操作。譬如检查&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/subject:(.*)/&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&lt;/code&gt;。譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/this|that|other/&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;th&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;忽略优先还是匹配优先&quot;&gt;忽略优先还是匹配优先&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^.*:/&lt;/code&gt;能匹配到最后的冒号,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^.*?:/&lt;/code&gt;能匹配到第一个冒号。&lt;/p&gt;

&lt;p&gt;根据需求决定。如果冒号比较接近字符串开头，使用忽略优先量词，反之使用匹配优先量词。如果不知道数据规则，那使用匹配优先量词。因为引擎对匹配优先量词的优化一般比其他量词要好。&lt;/p&gt;

&lt;p&gt;另外，排除型字符组&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^[^:]*:/&lt;/code&gt;比&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^.*?:/&lt;/code&gt;效率要高&lt;/p&gt;

&lt;h3 id=&quot;使用占有优先量词&quot;&gt;使用占有优先量词&lt;/h3&gt;

&lt;p&gt;占有优先量词能极大的提高匹配速度而不改变匹配结果。使用占有优先量词能够让引擎抛弃没有意义得备用状态。但是占有优先量词要注意和排除型字符组组合使用，如果和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/./&lt;/code&gt;使用会造成不期望的匹配。&lt;/p&gt;

&lt;p&gt;譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^[^:]+:/&lt;/code&gt;中的冒号第一次尝试无法匹配，那么无论如何回溯都无法匹配。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>animation动画初步</title>
   <link href="http://echizen.github.io/tech/2016/10-23-animation"/>
   <updated>2016-10-23T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/animation</id>
   <content type="html">
&lt;p&gt;小白初次做动画，涨了一批基础知识。拿到的需求稿的效果就是跟ppt一样，文字嗖嗖嗖的淡入、平移、左侧渐出、右侧渐出等这些基础效果。&lt;/p&gt;

&lt;h2 id=&quot;元素分析&quot;&gt;元素分析&lt;/h2&gt;

&lt;p&gt;动画不是刷一下都蹦出来的，是要根据行为方式和出现时间拆分一个个动画元素。看到的可能是各种物体，实际都可以做成一个个div元素，通过background-image来展现多种样子。&lt;/p&gt;

&lt;h2 id=&quot;动起来animation--keyframes&quot;&gt;动起来：animation + keyframes&lt;/h2&gt;

&lt;p&gt;例如一个文字从上至下滑落淡出的效果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;keyframes fadeInDown{
    0% {
        transform: translate3d(0, -1.6rem, 0) scale(0,0);
        opacity: 0;
    }
    100% {
        transform: translate3d(0, 0, 0) scale(1,1);
        opacity: 1;
    }
}

animation: fadeInDown 0.4s linear 0.2s forwards;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;整个动画的基本效果都可以用animation调出来，第一个参数指定动效，具体效果可以在keyframes中定义；第二个参数是执行时间；第三个参数是动画效果函数，线性还是渐入渐出等；第四个参数很关键，表示动画开始执行的时间，正是这个参数让我们能让一系列元素按照设定的顺序进行动画播放；第5个参数是指定动画完成后停留的状态，譬如forwards指定保持结束时的状态，默认是保持开始时的状态。&lt;/p&gt;

&lt;h2 id=&quot;注意点&quot;&gt;注意点&lt;/h2&gt;

&lt;h3 id=&quot;性能&quot;&gt;性能&lt;/h3&gt;
&lt;p&gt;由于transform和opacity可以避免触发layout，而是使用javascript - style - composite的流程渲染，且会创建新的渲染层，每个渲染层的纹理都是上传到GPU处理，性能更好。&lt;/p&gt;

&lt;p&gt;所以一般对于以下常见的动画效果，可以用这些方式优化性能：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;位移：不要使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;left&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;top&lt;/code&gt;的改变来实现，而是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform: translate3d(-0.2rem, -1.6rem, 0)&lt;/code&gt;来实现，或者不使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translate3d&lt;/code&gt;,使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform: translateX(-0.2rem);transform: translateY(-1.6rem)&lt;/code&gt;的方式。&lt;/li&gt;
  &lt;li&gt;大小：不要直接改变&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt;，而是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform:scale(0.8,0.8)&lt;/code&gt;这种方式。&lt;/li&gt;
  &lt;li&gt;显隐：不要使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:none&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:block&lt;/code&gt;，而是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opacity:0&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opacity:1&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个性能的提升不仅仅是从chrome的timeline上的曲线得知，到了低端一点的安卓机上会有明显感受，使用前一种方式的会有卡顿感，使用后一种方式会感觉到明显的流畅了。卡顿的强烈与否取决于动画的复杂度。&lt;/p&gt;

&lt;h3 id=&quot;渲染时间&quot;&gt;渲染时间&lt;/h3&gt;

&lt;p&gt;如果你的动画元素使用了背景图，在图片没加载完成前，你的动画都不能正常渲染，而是等到图片加载完成后，之前的动画刷的一下全蹦出来了。&lt;/p&gt;

&lt;p&gt;所以需要确保所依赖的图片加载完成后再进行动画。&lt;/p&gt;

&lt;p&gt;这是后可能需要js来起点作用了，现将有动画效果的class放到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.animation&lt;/code&gt;下。暴力一点的办法是在dom中直接添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:none&lt;/code&gt;的&lt;img /&gt;加载出需要的图片，然后在图片上绑上onload事件来触发动画执行的js。当然有可能img都onload了，你的js还没加载或解析完，所以需要双向通知，譬如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
    function imgReady(){
        if(typeof window.__initAnimation === &quot;function&quot;){
            window.___initAnimation();
        }else{
            window.__imgLoading = true;  
        }
    }
&amp;lt;/script&amp;gt;
&amp;lt;img class=&quot;pre-img&quot; src=&quot;http://s16.mogucdn.com/p2/161016/upload_74h3i068d0e8f345a2fjdldg7aa8k_750x621.png&quot; onload=&quot;imgReady()&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;js中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    // 如果img已经加载好，直接执行动画
	if(window.__imgLoading === true){
		initAnnimation();
	}else{
		// 否则注入动画执行函数，由img得onload调用
		window.__initAnimation = initAnnimation;
	}
	
	function initAnnimation(){
		$ele.addClass(&apos;animation&apos;);
	}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;兼容性&quot;&gt;兼容性&lt;/h3&gt;

&lt;p&gt;低端机上不出现动画，低端机怎么判断呢？一般不支持几个关键css动画属性的机器我们就不出现动画了，譬如我这里是transform、animation，可以在动画执行前进行检查当前机器的div的style下有没有这些属性：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 检查兼容性
var supports = (function() {
   var div = document.createElement(&apos;div&apos;),
       vendors = &apos;Khtml Ms O Moz Webkit&apos;.split(&apos; &apos;),
       len = vendors.length;

   return function( prop ) {
      if ( prop in div.style ) return true;

      prop = prop.replace(/^[a-z]/, function(val) {
         return val.toUpperCase();
      });
      var i = len;
      while( i-- ) {
         if ( vendors[i] + prop in div.style ) {
            return true;
         }
      }
      return false;
   };
})();

var supportTransform = supports(&apos;transform&apos;);
var supportAnimation = supports(&apos;animation&apos;);
if(!supportTransform || !supportAnimation){
	$ele.remove();
	return;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这些都是很基础的动画，做过了简单总结下。深入的动画不是我的方向啦。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>git 常用命令小记</title>
   <link href="http://echizen.github.io/tech/2016/08-21-git"/>
   <updated>2016-08-21T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/git</id>
   <content type="html">
&lt;h3 id=&quot;关联本地分支和远程分支&quot;&gt;关联本地分支和远程分支&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone&lt;/code&gt;命令会自动在本地建立一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;分支，它是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin/master&lt;/code&gt;的‘追踪分支’。使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull / git push&lt;/code&gt;就可以将本地&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;分支和远程&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin/master&lt;/code&gt;通信。但是如果你本地新建立了个develop分支，想提交时却要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push origin develop&lt;/code&gt;才能送到远程develop分支上，push也是要指明&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orgin develop&lt;/code&gt;。git 不会自动关联，这会很不方便。所以需要通过命令一次性建立本地分支和远程分支的关联&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;set-upstream&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git branch --set-upstream my_branch origin/my_branch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;–set-upstream 将被废弃并删除. 使用 –track 或者 –set-upstream-to来替代。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;track&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git branch --track experimental origin/experimental
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;set-upstream-to&lt;/p&gt;

    &lt;p&gt;在develop分支上&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git branch --set-upstream-to=origin/develop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;或简写：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git branch -u origin/dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;也可以在&lt;strong&gt;第一次将本地分支提交到远程时可以直接建立联系&lt;/strong&gt;：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git push -u origin my_branch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;忽略掉已经被误添加到git库中的文件&quot;&gt;忽略掉已经被误添加到git库中的文件&lt;/h3&gt;

&lt;p&gt;首先要讲忽略文件补到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt;中，然后重置git库信息：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git rm -r --cached .
 	git add .
 	git reset HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果你已经将不该被版本控制的文件添加到远程了，要手动删除远程的这些文件。&lt;/p&gt;

&lt;p&gt;顺便提下：忽略所在库根目录下的文件
package.json会忽略库下所有package.json文件，包括子目录下的文件，如package/package/package.json
/package.json才只会忽略库根目录下的package.json文件&lt;/p&gt;

&lt;h3 id=&quot;git-rebase&quot;&gt;git rebase&lt;/h3&gt;

&lt;p&gt;merge是将2个commit对象合并生成新的commit对象。rebase在base commit后创建新commit对象，将待合并的commit移到这里。结果就二十rebase的提交历史永远是线形的。&lt;/p&gt;

&lt;p&gt;水太深，好好读官方文档：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Branching-Rebasing&quot;&gt;Git Branching - Rebasing
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull —rebase&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase -i commitID&lt;/code&gt;后若存在冲突：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add &amp;lt;some-file&amp;gt;
git rebase --continue
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;修改密码&quot;&gt;修改密码&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/160821-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;根据版本新建分支&quot;&gt;根据版本新建分支&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git branch branchName HEAD~3
git branch branchName commitId
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;撤销修改与回退&quot;&gt;撤销修改与回退&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;撤销工作目录（未add,未commit）修改&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git checkout -- fileName
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;撤销缓存区修改（已经add，未commit)的最新修改&lt;/p&gt;

    &lt;p&gt;只是移除暂存区的内容，而不是撤销对工作目录的修改。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git reset HEAD -- fileName
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;撤销版本库（已经commit）修改【回退版本】&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git reset --hard HEAD~3
  git reset --hard versionId
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;git reset 参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;–soft ： 缓存区和工作目录都不会被改变&lt;/li&gt;
  &lt;li&gt;–mixed ： 默认选项。缓存区和你指定的提交同步，但工作目录不受影响&lt;/li&gt;
  &lt;li&gt;–hard ： 缓存区和工作目录都同步到你指定的提交&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;修改commit-信息&quot;&gt;修改commit 信息&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;最新的commit&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;会生成新的commit id,需要其他合作者也更新&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git commit --amend -m &quot;New commit message&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;commit合并到上一次&amp;amp;本次提交不会有commit 信息&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git commit --amend
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;还未提交到远程的本地的commit信息（可能多条）&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase -i versionId&lt;/code&gt; 后在终端vim里改，将你需要修改的commit 选择r模式&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/160821-2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;将分支上所有commit 作为一条commit信息合并&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git checkout master
  git merge --squash branchName
  git commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;暂存&quot;&gt;暂存&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git stash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;用于将当前修改存暂存区但不提交，新建一个分支进行下一步可能提交的修改&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;查看stash文件：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git stash list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;恢复暂存区文件到工作目录&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git stash apply
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;将文件从暂存区移除：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git stash apply //恢复
  git stash drop //删除
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;或者&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git stash pop //恢复的同时把stash内容也删了
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;将cherry-pick合并某个提交到自己的分支中&quot;&gt;将cherry-pick合并某个提交到自己的分支中&lt;/h3&gt;

&lt;p&gt;如果只想将远程仓库的一个特定提交合并到自己的分支中该怎么做呢？可以使用git cherry-pick 来选择给定SHA值的提交，然后将其合并到当前分支中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	git cherry-pick versionId
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也将本地分支的某个commit 版本合并到主支&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout new-feature
git log   找到版本号
git checkout master
git cherry-pick versionId
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;标签相关&quot;&gt;标签相关&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;推送指定标签：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git push origin v1.5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;推送多个标签：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git push origin --tags
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;查看git标签列表&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git tag -l
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;查看某个标签下的代码&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  git checkout tag_name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在 tag 代码的基础上做修改&lt;/p&gt;

    &lt;p&gt;checkout切换到某个标签上，git 会提示你当前处于一个“detached HEAD” 状态，因为 tag 相当于是一个快照，是不能更改它的代码的，如果要在 tag 代码的基础上做修改，你需要一个分支
      git checkout -b branch_name tag_name&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;查看文件每一行最近修改信息&quot;&gt;查看文件每一行最近修改信息&lt;/h3&gt;
&lt;p&gt;能查看到文件的每个部分是谁修改&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git blame 文件名
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;git-bisect开启debug&quot;&gt;git bisect开启debug&lt;/h3&gt;

&lt;p&gt;git bisect：debug，通过测试找出出问题的版本（二分查找）&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;开始bisect&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; git bisect start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;告诉git当前版本是有bug的&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; git bisect bad
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;告诉git 你所知道的正常的版本&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; git bisect good versionId
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;（如果你不清楚具体的版本号，小项目就设最开始的版本吧，返回初始版本: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rev-list --max-parents=0 HEAD&lt;/code&gt;）&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;运行测试。你要提前写好测试代码或者能找到测试特征&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; make test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;告诉git测试结果，并重复4&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; git bisect bad|good
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>代码规范-从空格到代码格式化</title>
   <link href="http://echizen.github.io/tech/2016/08-07-code-space-standard"/>
   <updated>2016-08-07T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/code-space-standard</id>
   <content type="html">
&lt;p&gt;最近被大大吐槽空格键坏了。。。我的代码里空格时有时无，一般都是加上的（手动加的。。。），写的high了，为了追求速度，也会某些片段忽略了。。。&lt;/p&gt;

&lt;p&gt;我就来扒扒空格。其实是安利代码格式化工具。&lt;/p&gt;

&lt;h1 id=&quot;代码格式规范&quot;&gt;代码格式规范&lt;/h1&gt;

&lt;p&gt;先谈谈我对代码格式规范的理解，代码格式规范还真的没有一个统一的标准，每家公司的具体细则可能都不一样，但是一个团队内部的人应该一样，这样互相阅读代码时才能有最佳体验。&lt;/p&gt;

&lt;h3 id=&quot;具体的格式规范是有争议的&quot;&gt;具体的格式规范是有争议的&lt;/h3&gt;
&lt;p&gt;譬如tab=2 space or4 space就让人说不出来到底哪个更优，来个讨论都能让双方打起来。&lt;/p&gt;

&lt;h3 id=&quot;规范条例的初衷&quot;&gt;规范条例的初衷&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;为了减少错误。如在函数开头声明变量，防止内部变量声明提升带来的bug&lt;/li&gt;
  &lt;li&gt;方便阅读。换行，空格都有这个原因。&lt;/li&gt;
  &lt;li&gt;为了浏览器等解析更高效。譬如避免定义css原子类。&lt;/li&gt;
  &lt;li&gt;为了高效。譬如js语句结尾不加分号&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;规范不是统一的，一些矛盾的规范条例是分不出来谁好谁差，只要满足上面这些优点都是有益的，但有时候可能会满足其中一条又违背另一条。譬如空格的存在方便了阅读，但是如果手动加就影响了效率。一个团队遵从同一规范就好，对于有争议的条款规范的制定可能会遵从团队大多数人的习惯风格。&lt;/p&gt;

&lt;h1 id=&quot;哪里该有空格常见规范中&quot;&gt;哪里该有空格（常见规范中）&lt;/h1&gt;

&lt;h2 id=&quot;js&quot;&gt;js&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;if / else / for / while / function / switch / do / try / catch / finally 关键字后。如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (condition) { ... }&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;函数声明名称与参数之间。如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;function name (arg) { ... }&lt;/code&gt;(此条有争议)&lt;/li&gt;
  &lt;li&gt;二元运算符两侧。如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;var a = !arr.length;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;代码块起始的左花括号 { 前。如&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  if (condition) {
  }
	
  while (condition) {
  }
	
  function funcName() {
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在对象创建时，属性中的 : 之后&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  var obj = {
      a: 1,
      b: 2,
      c: 3
  };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;不位于行尾的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;,&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;;&lt;/code&gt; 后。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let a, b, c;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;generator的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;之后&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  function* foo() {
  }
	
  const foo = function* () {
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;css&quot;&gt;css&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;选择器 与 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&lt;/code&gt; 之间&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  .selector {
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;属性名 后的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&lt;/code&gt; 与 属性值 之间。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;margin: 0;&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;列表型属性值 书写在单行时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;,&lt;/code&gt; 后。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-family: Arial, sans-serif;&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;工具的力量&quot;&gt;工具的力量&lt;/h1&gt;

&lt;p&gt;我觉得如果每一个该有空格的地方都自己手动加空格，效率真的有点低。所以我去求助工具了。不仅可以帮你在该加空格的地方加空格，还可以一起把规范中其他关于格式的条例的格式化做掉。&lt;/p&gt;

&lt;h2 id=&quot;sublime-plugin---html-css-js-prettify&quot;&gt;sublime plugin - HTML-CSS-JS Prettify&lt;/h2&gt;

&lt;h3 id=&quot;功能&quot;&gt;功能&lt;/h3&gt;

&lt;p&gt;此插件可以格式化html、css、js，可以抛弃jsFormat了。基于node，所以要有node环境，安装node。&lt;/p&gt;

&lt;h3 id=&quot;使用&quot;&gt;使用&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;默认shift+cmd+H快捷键调起对当前窗口文件的格式化。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;右键选择HTML/CSS/JS prettify -&amp;gt; prettify code。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;还可以配置保存时即格式化，preferences -&amp;gt; package setting -&amp;gt; HTML/CSS/JS prettify -&amp;gt; set plugin options。HTMLPrettify.sublime-settings文件里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format_on_save:true&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;也可以为每个库配置独特的格式化文件，在库的根目录下建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.jsprettifyrc&lt;/code&gt;，配置规则，他的优先级比全局设置的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.jsprettifyrc&lt;/code&gt;优先级更高。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;规则&quot;&gt;规则&lt;/h3&gt;

&lt;p&gt;shift+cmd+p -&amp;gt; 输入prettify -&amp;gt; 选择HTMLPretiffy:set prettify preference(或者preferences -&amp;gt; package setting -&amp;gt; HTML/CSS/JS prettify -&amp;gt; set Prettify perferences）,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.jsprettifyrc&lt;/code&gt;里配置规则，默认规则是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  // Details: https://github.com/victorporof/Sublime-HTMLPrettify#using-your-own-jsbeautifyrc-options
  // Documentation: https://github.com/einars/js-beautify/
  &quot;html&quot;: {
    &quot;allowed_file_extensions&quot;: [&quot;htm&quot;, &quot;html&quot;, &quot;xhtml&quot;, &quot;shtml&quot;, &quot;xml&quot;, &quot;svg&quot;],
    &quot;brace_style&quot;: &quot;collapse&quot;, // [collapse|expand|end-expand|none]Put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are（没理解。。。）
    &quot;end_with_newline&quot;: false, // 结尾空出一行
    &quot;indent_char&quot;: &quot; &quot;, // 缩进字符类型（空格或者tab）
    &quot;indent_handlebars&quot;: false, // e.g. {{#foo}}, {{/foo}}
    &quot;indent_inner_html&quot;: false, // 缩进 &amp;lt;head&amp;gt; 和 &amp;lt;body&amp;gt; 片段
    &quot;indent_scripts&quot;: &quot;keep&quot;, // [keep|separate|normal]
    &quot;indent_size&quot;: 4, // 缩进单位（这里是4个空格的意思）
    &quot;max_preserve_newlines&quot;: 0, // 在一个chunk中允许的最大换行数（0表示不允许）
    &quot;preserve_newlines&quot;: true, // 元素前的换行是否被允许存在（仅仅对于elemnet起作用，对tags和text都不起作用）
    &quot;unformatted&quot;: [&quot;a&quot;, &quot;span&quot;, &quot;img&quot;, &quot;code&quot;, &quot;pre&quot;, &quot;sub&quot;, &quot;sup&quot;, &quot;em&quot;, &quot;strong&quot;, &quot;b&quot;, &quot;i&quot;, &quot;u&quot;, &quot;strike&quot;, &quot;big&quot;, &quot;small&quot;, &quot;pre&quot;, &quot;h1&quot;, &quot;h2&quot;, &quot;h3&quot;, &quot;h4&quot;, &quot;h5&quot;, &quot;h6&quot;], // 不被format的标签（没懂为何有这个选项）
    &quot;wrap_line_length&quot;: 0 // Lines should wrap at next opportunity after this number of characters (0 disables)
  },
  &quot;css&quot;: {
    &quot;allowed_file_extensions&quot;: [&quot;css&quot;, &quot;scss&quot;, &quot;sass&quot;, &quot;less&quot;],
    &quot;end_with_newline&quot;: false, // 结尾空出一行
    &quot;indent_char&quot;: &quot; &quot;, // 缩进字符方式（空格:` `;tab:`\t`）
    &quot;indent_size&quot;: 4, // 缩进单位（这里是4个空格的意思）
    &quot;newline_between_rules&quot;: true, // 每一条css规则之间插入一个空行
    &quot;selector_separator&quot;: &quot; &quot;,
    &quot;selector_separator_newline&quot;: true // 是否用空行分割选择器 (e.g. &quot;a,\nbr&quot; or &quot;a, br&quot;)
  },
  &quot;js&quot;: {
    &quot;allowed_file_extensions&quot;: [&quot;js&quot;, &quot;json&quot;, &quot;jshintrc&quot;, &quot;jsbeautifyrc&quot;],

    // Set brace_style
    //  collapse: (old default) 将`{`、`}`置于控制语句同一行
    //  collapse-preserve-inline: (new default) 与collapse规则一直但是对es6 import结构支持`{`不分行。 https://github.com/victorporof/Sublime-HTMLPrettify/issues/231
    //  expand: 将`{`、`}`置于新一行(Allman / ANSI style)
    //  end-expand: 将`}` 置于新一行
    //  none: 不去处理，与原位置一直
    &quot;brace_style&quot;: &quot;collapse-preserve-inline&quot;,

    &quot;break_chained_methods&quot;: false, // 将链式调用的函数置于新一行
    &quot;e4x&quot;: false, // Pass E4X xml literals through untouched
    &quot;end_with_newline&quot;: false, //结尾空出一行
    &quot;indent_char&quot;: &quot; &quot;, // 缩进字符方式（空格:` `;tab:`\t`）
    &quot;indent_level&quot;: 0, // Initial indentation level
    &quot;indent_size&quot;: 4, // 缩进单位（这里是4个空格的意思）
    &quot;indent_with_tabs&quot;: false, // 使用tab缩进，会覆盖 `indent_size` 和`indent_char`的设置
    &quot;jslint_happy&quot;: false, // 是否强制使用`jslint-stricter`的模式
    &quot;keep_array_indentation&quot;: false, // 保留数组缩进
    &quot;keep_function_indentation&quot;: false, // 保留函数缩进
    &quot;max_preserve_newlines&quot;: 0, // 在一个chunk中允许的最大换行数（0表示不允许）
    &quot;preserve_newlines&quot;: true, // 是否保留换行
    &quot;space_after_anon_function&quot;: false, // 匿名函数function关键字和括号之间的空格是否需要, &quot;function()&quot; vs &quot;function ()&quot;
    &quot;space_before_conditional&quot;: true, // 是否添加条件语句关键词和括号之间的空格, &quot;if(true)&quot; vs &quot;if (true)&quot;
    &quot;space_in_empty_paren&quot;: false, // 函数空参数时是否添加padding值, &quot;f()&quot; vs &quot;f( )&quot;
    &quot;space_in_paren&quot;: false, // 是否在函数参数与括号之间插入空格, ie. f( a, b )
    &quot;unescape_strings&quot;: false, // Should printable characters in strings encoded in \xNN notation be unescaped, &quot;example&quot; vs &quot;\x65\x78\x61\x6d\x70\x6c\x65&quot;
    &quot;wrap_line_length&quot;: 0 // Lines should wrap at next opportunity after this number of characters (0 disables)
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;不足之处&quot;&gt;不足之处&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allowed_file_extensions&lt;/code&gt;指定文件类型，发现不支持jsx，这就尴尬了，react的jsx文件都没法格式化，会将类似html格式的jsx按js语法格式化。&lt;/li&gt;
  &lt;li&gt;css格式化有些Bug，设置了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newline_between_rules:true&lt;/code&gt;，还是会删除规则之间的空行。我觉得有空行可读性更高。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;sublime-plugin-sublimelintersublimelinter-contrib-eslint&quot;&gt;sublime plugin: Sublimelinter&amp;amp;&amp;amp;Sublimelinter-contrib-eslint&lt;/h2&gt;

&lt;p&gt;毕竟HTML-CSS-JS Prettify只是代码格式化工具，还有很多代码规范不是格式化就能解决的，这时候将上述HTML-CSS-JS Prettify代码格式化工具与eslint代码规范插件配合使用，利用eslint检查其他非格式的规范。&lt;/p&gt;

&lt;h3 id=&quot;安装&quot;&gt;安装&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install eslint -g
npm install babel-eslint -g
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再通过package control安装Sublimelinter和Sublimelinter-contrib-eslint。&lt;/p&gt;

&lt;h3 id=&quot;配置&quot;&gt;配置&lt;/h3&gt;

&lt;p&gt;修改Sublimelinter 配置，Preferences-&amp;gt;Package Settings-&amp;gt;SublimeLinter-&amp;gt;Settings-User。&lt;/p&gt;

&lt;p&gt;debug: true 开启 debug 模式 paths: 根据具体环境，设置 ESLint 路径，我的mac下是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/bin&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;user&quot;: {
        &quot;debug&quot;: true,
        &quot;linters&quot;: {
            &quot;eslint&quot;: {   
                &quot;excludes&quot;: [&quot;*.html&quot;]
            }
        },
        &quot;paths&quot;: {
            &quot;osx&quot;: [
                &quot;/usr/local/bin&quot;
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;重启 Sublime Text。&lt;/p&gt;

&lt;h3 id=&quot;规则配置&quot;&gt;规则配置&lt;/h3&gt;

&lt;p&gt;在项目根目录下新建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt; .eslintrc&lt;/code&gt;文件配置。具体规则参考官网，非常详细。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://eslint.org/docs/rules/&quot;&gt;http://eslint.org/docs/rules/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然后保存时就能看到提示，不合格的行开头会有红点标识。&lt;/p&gt;

&lt;h2 id=&quot;standard&quot;&gt;standard&lt;/h2&gt;

&lt;p&gt;号称”JavaScript Standard Style”,能够替代eslinc和jshint，不需要配置文件。&lt;/p&gt;

&lt;p&gt;但是他按照自己的规范检查你的文件，且他的规范可能和你团队指定的不同，所以使用它必须遵从他的规范。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/feross/standard&quot;&gt;https://github.com/feross/standard&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;参考&quot;&gt;参考&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/fex-team/styleguide/blob/master/javascript.md&quot;&gt;https://github.com/fex-team/styleguide/blob/master/javascript.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://airbnb.io/javascript/&quot;&gt;http://airbnb.io/javascript/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/victorporof/Sublime-HTMLPrettify#using-your-own-jsbeautifyrc-options&quot;&gt;https://github.com/victorporof/Sublime-HTMLPrettify#using-your-own-jsbeautifyrc-options&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/eslint-config-sm&quot;&gt;https://www.npmjs.com/package/eslint-config-sm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这里还有个14年sideeffect.kr做的github代码风格统计，可见大多数代码是没有遵从现在的主流规范的。。。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://sideeffect.kr/popularconvention#javascript&quot;&gt;http://sideeffect.kr/popularconvention#javascript&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Immutable试用</title>
   <link href="http://echizen.github.io/tech/2016/07-31-immutable"/>
   <updated>2016-07-31T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/immutable</id>
   <content type="html">
&lt;p&gt;之前有把一个项目想接入Immutable，试用了一下，谈点想法。改造了1天半，最终放弃了改造，因为改造成本实在太高太高了。。。&lt;/p&gt;

&lt;h1 id=&quot;immutable的适应&quot;&gt;Immutable的适应&lt;/h1&gt;

&lt;p&gt;使用Immutable，除了要清楚他定义的数据都是是不可变数据类型。还要清楚Immutable定义的数据类型与js的数据类型不一样。&lt;/p&gt;

&lt;p&gt;Immutable拥有：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List, Stack, Map, OrderedMap, Set, OrderedSet, Record&lt;/code&gt;这几种数据类型。List数据部分结构与js中的array结构类似，map与object类似。&lt;/p&gt;

&lt;p&gt;可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;formJs()&lt;/code&gt;接口将js格式的数据很方便转成Immutable中的类型，Immutable中的数据也可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toJs()、toArray()、toObject&lt;/code&gt;这些接口很方便的转成js数据类型的数据。formJs是个深层转换，会将你原本js数据结构中的每一层都转成相应的Immutable类型。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let jsObj = {
	name:&apos;Immutable&apos;,
	structure:[List, Stack, Map, OrderedMap, Set, OrderedSet, Record]
}

let ImuData = formJs(jsObj);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;得到的结构是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Map{
	name:&apos;Immutable&apos;,
	structure: List [List, Stack, Map, OrderedMap, Set, OrderedSet, Record]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;immutable的诱惑&quot;&gt;Immutable的诱惑&lt;/h1&gt;

&lt;p&gt;如果读react官方文档，会看到很多地方都在推荐使用Immutable，Immutable是facebook力荐的一个库，号称与react组合使用能提升10倍性能，github star数至16.7.31已至14000+了。&lt;/p&gt;

&lt;p&gt;由于react要求我们不要改变原始state，所有数据的变更都通过setState(newstateObj)的方式，redux也是要求不改变原始state，所有对数据的更新都放到reducer中来做。但是实际生产中，处处注意深拷贝以不改变原始数据实在太麻烦了。&lt;/p&gt;

&lt;p&gt;Immutable的所有数据都是不可改变型，而且提供了一系列接口供我们创建数据、更新数据、合并数据，每次操作的结果都是个新数据，不修改源数据。这些接口也非常方便使用，譬如合并数据和更新数据：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}});
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
var nested2 = nested.mergeDeep({a:{b:{d:6}}});
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
var nested3 = nested2.updateIn([&apos;a&apos;, &apos;b&apos;, &apos;d&apos;], value =&amp;gt; value + 1);
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }

var nested4 = nested3.updateIn([&apos;a&apos;, &apos;b&apos;, &apos;c&apos;], list =&amp;gt; list.push(6));
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;判断数据是否相等用的也是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is()&lt;/code&gt;接口。&lt;/p&gt;

&lt;p&gt;还提供了Seq这种懒类型数据，下雨消耗性能的链式查找方法例如map、fileter，Seq不保存中间层数据，对最终结果没有影响的数据都被忽略，非常高效。&lt;/p&gt;

&lt;h1 id=&quot;immutable在reactredux结构的项目中的使用&quot;&gt;Immutable在react+redux结构的项目中的使用&lt;/h1&gt;

&lt;h2 id=&quot;combinereducers&quot;&gt;combineReducers&lt;/h2&gt;

&lt;p&gt;combineReducers接受的参数和返回的产物都是object，而Immutable作用的reducer不是对象，所以需要包装，可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redux-immutable&lt;/code&gt;库。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// store.js
import { createStore, applyMiddleware } from &apos;redux&apos;;
import thunkMiddleware from &apos;redux-thunk&apos;;
import createLogger from &apos;redux-logger&apos;;

import { combineReducers } from &apos;redux-immutable&apos;;
import bookList from &apos;../reducers/bookList&apos;;
import bookDes from &apos;../reducers/bookDes&apos;;		

const rootReducer =  combineReducers({
	bookList,
	bookDes
});

const logger = createLogger();
const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  logger
)(createStore);

const configureStore = configureStore(initialState) {
  const store = createStoreWithMiddleware(rootReducer, initialState);
  return store;
}


// index.js
import React from &apos;react&apos;;
import { render } from &apos;react-dom&apos;;
import { Router, browserHistory } from &apos;react-router&apos;;
import { Provider } from &apos;react-redux&apos;;

import routes from &apos;./routes&apos;;
import configureStore from &apos;./store&apos;;


const store = configureStore();

render((
  &amp;lt;Provider store={store}&amp;gt;
      &amp;lt;Router history={browserHistory}&amp;gt;
        {routes}
      &amp;lt;/Router&amp;gt;
  &amp;lt;/Provider&amp;gt;
), document.getElementById(&apos;app&apos;));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connect&lt;/code&gt;的state参数能接受的也是纯object，所以传给connnect的state需要外包一层，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ item:Map itemContent }&lt;/code&gt;的结构。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let connectRedux = function(component, model, actions = null) {
  return connect(
    (state) =&amp;gt; {
      let needState = {};
      needState[model] = state.get(model);	      
      return needState;
    },
    actions ? (dispatch) =&amp;gt; {
      return {
        actions:bindActionCreators(Object.assign({},actions), dispatch)
      } 
    }:null
  )(component);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在reducer中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import Immutable from &apos;immutable&apos;;
const { Map, List, fromJS } = Immutable;
import * as ActionTypes from &apos;../constants/ActionTypes&apos;
const initialState = fromJS({
  name:&apos;book&apos;,
	content:{
		section:[
			{
				title:&apos;first&apos;
			},{
				title:&apos;second&apos;
			}
		],
		page:200
	}
});

export default function(state = initialState, action) {
  switch (action.type) {
    case ActionTypes.CHANGE_NAME:
        return state.update([&apos;content&apos;,&apos;page&apos;],value =&amp;gt; action.page);

    case ActionTypes.ERROR:
    default:
        return state;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后譬如你的bookDes component获得props格式为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bookDes:{
	name:&apos;book&apos;,
	content:Map {
		section:List [
			Map {
				title:&apos;first&apos;
			},
			Map {
				title:&apos;second&apos;
			}
		],
		page:200
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;获取prop下的Immutable类型的数据需要通过get接口，例如获取name需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.props.bookDes.get(&apos;name&apos;)&lt;/code&gt;;获取section则要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.props.bookDes.get(&apos;content&apos;).get(&apos;section)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这种接口用起来挺麻烦的，然后所有跟state相关的地方，只要你使用了Immutable类型的数据，就需要使用它的接口获得数据、操作数据，它的语法还是挺不友好的。但是也没办法，毕竟改变了数据类型，且没办法覆盖重写js的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;操作符，不然会带来更多副作用。我去看了lodash的接口也是类似。&lt;/p&gt;

&lt;h1 id=&quot;immutable的痛点&quot;&gt;Immutable的痛点&lt;/h1&gt;

&lt;p&gt;最终我在尝试后还是放弃了改造，因为成本实在太大，所有数据操作的地方，因为数据类型变了，都得换用Immutable的接口重写。&lt;/p&gt;

&lt;p&gt;Immutable与react、redux并没有完美融合，刚嵌入时你会看见无数的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Uncaught Error: Invariant Violation: mapStateToProps must return an object&lt;/code&gt;，不能将Immutable类型的数据直接传给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapStateToProps&lt;/code&gt;挂载到props上，所以还得将Immutable类型的state放置到对象中作为属性值。&lt;/p&gt;

&lt;p&gt;项目后期接入非常麻烦，改造成本太大。&lt;/p&gt;

&lt;p&gt;但是Immutable在标准化和性能方面都很有优势，并不能因为接口不友好就被放弃使用，只是建议在项目开始阶段就接入。&lt;/p&gt;

&lt;h1 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/immutable-js/&quot;&gt;https://github.com/facebook/immutable-js/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://facebook.github.io/immutable-js/docs/#/&quot;&gt;https://facebook.github.io/immutable-js/docs/#/&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>npm对包版本的管理</title>
   <link href="http://echizen.github.io/tech/2016/07-30-npm-version-control"/>
   <updated>2016-07-30T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/npm-version-control</id>
   <content type="html">
&lt;p&gt;使用npm时是否会有这些疑惑：为什么package.json里写的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;repo&quot;: &quot;^x.x.x&quot;&lt;/code&gt;，执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm update repo&lt;/code&gt;却不能更新到最新版？各种包之间互相依赖，每个依赖版本限制都不一样，那么最终到底会下载哪个版本？各种包依赖版本有冲突时怎么处理？&lt;/p&gt;

&lt;h1 id=&quot;packagejson中的版本指定&quot;&gt;package.json中的版本指定&lt;/h1&gt;

&lt;p&gt;package.json形如:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;repo-name&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;dependencies&quot;: {
    &quot;react&quot;: &quot;^15.2.1&quot;,
    &quot;react-dom&quot;: &quot;^15.2.1&quot;,
    &quot;react-redux&quot;: &quot;^4.0.0&quot;,
  },
  &quot;devDependencies&quot;: {
    &quot;autoprefixer&quot;: &quot;^6.1.2&quot;,
    &quot;babel-core&quot;: &quot;~5.8.20&quot;,
    &quot;babel-eslint&quot;: &quot;~4.0.5&quot;,
    &quot;babel-loader&quot;: &quot;~5.3.2&quot;,
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependencies&lt;/code&gt;是该库必备依赖包，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devDependencies&lt;/code&gt;是开发环境依赖包，多是一些工具包。每个依赖包都对版本有指定，常见的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;对于版本&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x1.x2.x3&lt;/code&gt;，举例版本为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.3&lt;/code&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^1.2.3&lt;/code&gt;表示大于等于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.3&lt;/code&gt;形如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.x.x&lt;/code&gt;的版本，注意虽然是大于指定版本，但是x1值是指定值，他能匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.5&lt;/code&gt;，也能匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.3.1&lt;/code&gt;，但是不能匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.0.0&lt;/code&gt;。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~1.2.3&lt;/code&gt;表示大于等于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.3&lt;/code&gt;形如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.x&lt;/code&gt;的版本，注意这里x1,x2的值都等于指定值，可以匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.5&lt;/code&gt;，但是不可以匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.3.1&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.0.0&lt;/code&gt;。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以最通用的写法&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^1.2.3&lt;/code&gt;并不是所有表示大于1.2.3的版本都可以匹配。如果要表示所有大于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2.3&lt;/code&gt;的版本都匹配的话得用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;=1.2.3&lt;/code&gt;。但是一般不这么做，这样有很大风险。&lt;strong&gt;大版本的发布一般伴随着api的变更&lt;/strong&gt;，x1值的改变如果都去更新的话，向下兼容没做好的库，对于使用者来说会带来bug。&lt;/p&gt;

&lt;h1 id=&quot;npm3的扁平化方案&quot;&gt;npm3的扁平化方案&lt;/h1&gt;

&lt;p&gt;npm3与npm2的对依赖包的解决方案有很大不同。我最近才更新至npm3。。。要及时更新工具啊。&lt;/p&gt;

&lt;p&gt;npm2 采用严格树形嵌套的形式组织依赖模块的目录，而 npm3 则尽量扁平化，将依赖模块提升至顶层目录：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://docs.npmjs.com/images/npm3deps4.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm安装包的顺序由字符顺序决定，最先被装到顶层目录的依赖包版本由最先依赖他的包决定&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;于此同时，npm3也会在终端输出包的结构：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://docs.npmjs.com/images/tree.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果你想看原始依赖关系，可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm ls&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://docs.npmjs.com/images/npmls.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;npm3如何抉择各个库对各种版本包的需求&quot;&gt;npm3如何抉择各个库对各种版本包的需求&lt;/h1&gt;

&lt;p&gt;在上图的例子中，如果此时来了个D v1.0，它依赖的是B v2.0，结构会变成怎样呢？这时候B v2.0不会被安装在顶级目录下，而是继续留在B的node_modules下。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://docs.npmjs.com/images/npm3deps6.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果再来个E v1.0，他依赖的是B v1.0。由于B v1.0已经在顶层目录了，这时候B v1.0不会再被下载，而是共享顶层目录的B v1.0。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://docs.npmjs.com/images/npm3deps8.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;扁平化后，顶层node_modules目录下的包会被共享&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;对于包升级的处理&quot;&gt;对于包升级的处理&lt;/h1&gt;

&lt;p&gt;如果此时我们将A v1.0升级到v2.0，A v2.0依赖的是B v2.0，会发生什么呢？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;npm先删除A v1.0&lt;/li&gt;
  &lt;li&gt;下载A v2.0&lt;/li&gt;
  &lt;li&gt;Bv1.0 不会被删除因为 E v1.0 仍然依赖它。&lt;/li&gt;
  &lt;li&gt;下载Bv2.0至A的node_modules底下。因为Bv2.0已经占有了顶层目录的位置。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是注意，此时，对于一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules&lt;/code&gt;为空的刚加入该项目开发的成员来说，第一次&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install&lt;/code&gt;后他的目录结构是不同的，他的根目录下是Bv2.0，因为按字母顺序下载，A2.0依赖的是B2.0。所以这样会出现项目成员目录不一致的情况，不过并没有什么关系。不影响项目的运行和最终产物。&lt;/p&gt;

&lt;p&gt;如果此时我们再更新E v1.0至E v2.0，它依赖的是B v2.0，这回发生什么呢？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;npm先删除Ev1.0&lt;/li&gt;
  &lt;li&gt;下载Ev2.0&lt;/li&gt;
  &lt;li&gt;删除Bv1.0&lt;/li&gt;
  &lt;li&gt;下载Bv2.0至顶层目录&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这时就尴尬了，大家依赖的都是Bv2.0，Bv2.0却处处存在。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://docs.npmjs.com/images/npm3deps12.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;此时就需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm dedupe&lt;/code&gt;了&lt;/p&gt;

&lt;h1 id=&quot;npm-dedupe&quot;&gt;npm dedupe&lt;/h1&gt;

&lt;p&gt;执行后npm会检查所有包，重新按规则整理，按包的字母顺序整理依赖，对于依赖包指定的包，删除其他包中node_modules私有文件夹下的包。最终产物就是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://docs.npmjs.com/images/npm3deps13.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;扁平化方案的优势&quot;&gt;扁平化方案的优势&lt;/h1&gt;

&lt;p&gt;资源共享，减少体积。对于webpack这样的打包工具能减少最终产物体积。&lt;/p&gt;

&lt;p&gt;对于a依赖&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;b&quot;:&quot;^1.2.3&quot;&lt;/code&gt;,b最新包是1.9.0。此时npm会在a的node_modules下装上1.9.0的b。如果后继开发中我们引入了c，c的依赖是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;b&quot;:&quot;^1.x.x&quot;&lt;/code&gt;这种，但此时b的最先版已经更新到1.9.2了，此时npm会在下载个b1.9.2至c的node_modules下，最终webpack打包时，打了2份不同版本的b。。。&lt;/p&gt;

&lt;p&gt;同样情况发生在npm3下，引入c后检查到bv1.9.0满足c对b的依赖条件，便不会再下载b，最终只会打包一份b。&lt;/p&gt;

&lt;h1 id=&quot;扁平化方案的吐槽&quot;&gt;扁平化方案的吐槽&lt;/h1&gt;

&lt;p&gt;这个听Node程序员吐槽过包被扁平化后，不方便调试和查找了。而且为了避免同一资源被多次下载，其实可以通过软链接的方式，而不必要改变原本的包结构。&lt;/p&gt;

&lt;h1 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.npmjs.com/how-npm-works/npm3&quot;&gt;https://docs.npmjs.com/how-npm-works/npm3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.npmjs.com/how-npm-works/npm3-dupe&quot;&gt;https://docs.npmjs.com/how-npm-works/npm3-dupe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/dedupe&quot;&gt;https://docs.npmjs.com/cli/dedupe&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>打入react&redux(4) - shouldComponentUpdate初步性能优化</title>
   <link href="http://echizen.github.io/tech/2016/07-26-shouldcomponentupdate1"/>
   <updated>2016-07-26T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/shouldcomponentupdate1</id>
   <content type="html">
&lt;h1 id=&quot;性能优化的必要性&quot;&gt;性能优化的必要性&lt;/h1&gt;

&lt;p&gt;使用react如果不手动使用shouldComponentUpdate，性能会有很大问题，如果你曾在render函数上打过断点就会知道事情的严重性。&lt;/p&gt;

&lt;p&gt;拿官方的一张图来看：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.com/assets/blog-img/should-component-update.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;先不管红绿，图注啥的，先假设我们有这种树形组件结构。&lt;/p&gt;

&lt;p&gt;任由react本身的话，父组件的重绘一定会导致子组件的重绘，也就是说如果c3有数据更新，无论这个被更新的数据有没有传入到c6,c7,c8，都会导致c6,c7,c8跟着c3发生rerender。这是不合理的低效行为。如果c1更新了，大家就都跟着更新了。。。&lt;/p&gt;

&lt;p&gt;再看如果c6组件数据props.c6Data有更新，引起c6、c3、c1重绘不可避免，但是也会引起c7、c8重绘，即使c7、c8没有用到c6组件更新的数据,props上就没有挂载c6Data。这就及其不合理了。&lt;/p&gt;

&lt;p&gt;不要惊讶这个现象，如果你不敢相信我的言论，你可以在c7这类子组件的render函数上打断点，然后触发一个只更新c6组件使用的数据的操作，你会看到c7也会再次render的。第一次知道真相的我内心也是崩溃的。。。&lt;/p&gt;

&lt;p&gt;所以如果不用shouldComponentUpdate做性能优化，redux中通过combineReducers组合的属于你的根组件上的state中的数据任何一部分有更新，你的根组件及其子组件就跟着一起重绘。。。其实深切的觉得react内部应该做这件事，如果依赖的数据及props上的数据没有更新的，组件就不应该去重绘。不过我觉得没用，我还觉得js应该内置深拷贝方法呢。。。&lt;/p&gt;

&lt;p&gt;所以知道真相得我开始性能优化。&lt;/p&gt;

&lt;h1 id=&quot;shouldcomponentupdate---提高性能的救兵&quot;&gt;shouldComponentUpdate - 提高性能的救兵&lt;/h1&gt;

&lt;p&gt;shouldComponentUpdate函数在componentWillUpdate、render函数之前执行，默认返回true，表示组件应该更新，返回false是阻止更新，此时render将不再发生。&lt;/p&gt;

&lt;p&gt;因此可以在shouldComponentUpdate中进行判断，只有组件自身关心的数据发生了变化才return true，否则return false.减少重绘次数。&lt;/p&gt;

&lt;h2 id=&quot;purerendermixin&quot;&gt;PureRenderMixin&lt;/h2&gt;

&lt;p&gt;react提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PureRenderMixin&lt;/code&gt;，可以统一帮我们处理&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shouldComponentUpdate&lt;/code&gt;的返回值。避免手动判断的繁琐，它会判断&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextprops&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.props&lt;/code&gt;以及&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextState&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.state&lt;/code&gt;的每个属性是否发生变化，发生变化就&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return true&lt;/code&gt;,否则&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return false&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;因为不确定它是如何比较的，我特地去看了代码，贴出来分享一下。&lt;/p&gt;

&lt;p&gt;ReactComponentWithPureRenderMixin.js：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&apos;use strict&apos;;
var shallowCompare = require(&apos;shallowCompare&apos;);
var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  },
};

module.exports = ReactComponentWithPureRenderMixin;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;shallowCompare.js：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&apos;use strict&apos;;
var shallowEqual = require(&apos;shallowEqual&apos;);
function shallowCompare(instance, nextProps, nextState) {
  return (
    !shallowEqual(instance.props, nextProps) ||
    !shallowEqual(instance.state, nextState)
  );
}

module.exports = shallowCompare;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;react.js&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function is(x, y) {
  // SameValue algorithm
  if (x === y) {
    // Steps 1-5, 7-10
    // Steps 6.b-6.e: +0 != -0
    return x !== 0 || 1 / x === 1 / y;
  } else {
    // Step 6.a: NaN == NaN
    return x !== x &amp;amp;&amp;amp; y !== y;
  }
}

function shallowEqual(objA, objB) {
  if (is(objA, objB)) {
    return true;
  }

  if (typeof objA !== &apos;object&apos; || objA === null || typeof objB !== &apos;object&apos; || objB === null) {
    return false;
  }

  var keysA = Object.keys(objA);
  var keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A&apos;s keys different from B.
  for (var i = 0; i &amp;lt; keysA.length; i++) {
    if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false;
    }
  }

  return true;
}

module.exports = shallowEqual;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shallowEqual&lt;/code&gt;函数可见它是先判断objA和objB是否相同，然后通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is(objA[keysA[i]], objB[keysA[i]]&lt;/code&gt;循环逐个比较2各对象的一级属性是否相等（使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object.keys&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;is函数只是做简单的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt;判断，又指判断了2个需要比较对象的一级属性（我所说的的一级属性是指：objA.a的a属性，objA.a.b中的b属性即为更深层级的属性）。这个判断是否准确就得看对象是深拷贝还是浅拷贝了。&lt;/p&gt;

&lt;h1 id=&quot;深拷贝和浅拷贝&quot;&gt;深拷贝和浅拷贝。&lt;/h1&gt;

&lt;h3 id=&quot;浅拷贝类似指针&quot;&gt;浅拷贝类似指针&lt;/h3&gt;

&lt;p&gt;浅拷贝，譬如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let obj1 = {
	name:&apos;obj&apos;
}
let obj2 = obj1
let arr1 = [1,2,3]
let arr2 = arr1;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;浅拷贝的操作并不会开辟新的内存地址，obj2的地址会指向obj1，只是建立了个类似指针引用的关系。改变obj2会导致obj1的改变&lt;/p&gt;

&lt;h3 id=&quot;深拷贝会分配新的内存地址&quot;&gt;深拷贝会分配新的内存地址&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let obj1 = {
	name:&apos;obj&apos;
}
let obj2 = Object.assign({},obj1)

let arr1 = [1,2,3]
let arr2 = arr1.concat();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时，obj2和arr2都已被分配了新的内存地址，并将obj1、arr1的值复制了过去，已经获得独立，改变obj2和arr2并不会影响obj1和arr1的值。&lt;/p&gt;

&lt;p&gt;但是注意的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object.assign&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;concat&lt;/code&gt;方法只是对当前操作对象进行深拷贝，如果当前操作的是一个复杂对象，并不会自动对他的子属性都做深层拷贝:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20160726-0@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这就尴尬了。导致如果我们如果想要做深拷贝就得一层层的手动&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object.assign&lt;/code&gt;，这是非常不方便的，我们需要一个库来提高生产力。&lt;/p&gt;

&lt;h2 id=&quot;reactaddonsupdate&quot;&gt;react.addons.update&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;注意这个是低层次的深拷贝&lt;/strong&gt;，虽然官方文档把他归为Immutability Helpers，但是他跟Object.assign并没有什么太大区别，都是最要复制的元素深拷贝，但是对元素的复杂类型的子属性还是地址引用。&lt;/p&gt;

&lt;p&gt;可以看一下实现react.addons.update的核心方法&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shallowCopy&lt;/code&gt;（方法名就叫浅拷贝）：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function shallowCopy(x) {
  if (Array.isArray(x)) {
    return x.concat();
  } else if (x &amp;amp;&amp;amp; typeof x === &apos;object&apos;) {
    return Object.assign(new x.constructor(), x);
  } else {
    return x;
  }
}
var nextValue = shallowCopy(value);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以不要把react.addons.update当救星，根本没啥卵用，跟自己使用object.assign、concat并无区别，甚至都没有简化代码。只能说如果你不知道object.assign({},value)实现拷贝的话它算不彻底的救了你一下。&lt;/p&gt;

&lt;h1 id=&quot;purerendermixin的局限&quot;&gt;PureRenderMixin的局限&lt;/h1&gt;

&lt;p&gt;说了这么多，&lt;strong&gt;由于PureRenderMixin的shallowEqual只是简单的检查直接属性是否相等，因此问题的关键是你是否能实现纯粹的深拷贝&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;我们来看看PureRenderMixin能做什么，不能做什么。分3种情况：&lt;/p&gt;

&lt;h2 id=&quot;浅拷贝&quot;&gt;浅拷贝&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;this.state.name=&apos;changed&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;类似obj2=obj1这种复制，那么PureRenderMixin的shallowEqual函数（浅比较）无疑会得出&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextState.name=this.state.name&lt;/code&gt;，然后return false，那么你将得不到更新！！！所有改动都不会导致重新渲染。&lt;/p&gt;

&lt;p&gt;不过我觉得稍微看过react文档的人都知道这种直接改state的方式是不对的，这种错误还是不会犯得。但是冷不防就会出现第二种情况。&lt;/p&gt;

&lt;h2 id=&quot;不完全的深拷贝&quot;&gt;不完全的深拷贝&lt;/h2&gt;

&lt;p&gt;整个复杂点的数据作为事例：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let state = {
	name:&apos;book&apos;,
	content:{
		section:[
			{
				title:&apos;first&apos;
			},{
				title:&apos;second&apos;
			}
		],
		page:200
	}
};

let firTitle = &apos;fir-sec&apos;;

let newContent = Object.assign({},state.content);
newContent.section[0].title = firTitle;

let newState = Object.assign({},state,{
	content:newContent
});

console.log(state.content === newState.content);//true
console.log(state.content.section === newState.content.section);//false
console.log(state.content.section[0] === newState.content.section[0]);//false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;判断是否相等全都用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt;简单处理了，因为react.js里的is方法其实也是这样，只是针对各种情况做了容错。&lt;/p&gt;

&lt;p&gt;把这个数据结构和上面的图做个对应:&lt;/p&gt;

&lt;p&gt;c1.props = state
c3.props = c1.props.content
c6.props = c3.props.section&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state.content === newState.content&lt;/code&gt;被判为true，导致的严重后果就是PureRenderMixin的shallowEqual函数在这一层上不更新组件，在这个例子里就是本该更新的c3组件不更新了，导致bug。&lt;/p&gt;

&lt;h2 id=&quot;纯粹的深拷贝&quot;&gt;纯粹的深拷贝&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let state = {
	name:&apos;book&apos;,
	content:{
		section:[
			{
				title:&apos;first&apos;
			},{
				title:&apos;second&apos;
			}
		],
		page:200
	}
};

let firTitle = &apos;fir-sec&apos;;

let newFirSec = Object.assign({},state.content.section[0]);
let newSection = state.content.section.concat();
newSection[0] = newFirSec;

let newContent = Object.assign({},state.content,{
	section:newSection
});

let newState = Object.assign({},state,{
	content:newContent
});

console.log(state.content === newState.content);//false
console.log(state.content.section === newState.content.section);//false
console.log(state.content.section[0] === newState.content.section[0]);//false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样万事皆好，一切正常。&lt;/p&gt;

&lt;p&gt;但是这个结果并不能让人开心，因为你得手动这么保证每一层数据都是深拷贝，4次数据就得这样操作4次，太麻烦，即使抽个方法递归调用深拷贝，性能也会不高，而且很麻烦。&lt;/p&gt;

&lt;h1 id=&quot;结论&quot;&gt;结论&lt;/h1&gt;

&lt;p&gt;shouldComponentUpdate用来优化性能，但是，react提供的PureRenderMixin组件判断&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextprops&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.props&lt;/code&gt;以及&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextState&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.state&lt;/code&gt;是否相等用的是简单的浅层次的比较，只比较直接属性是否&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt;，因此需要做到深拷贝。&lt;/p&gt;

&lt;p&gt;手动递归利用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object.assign&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;concat&lt;/code&gt;实现深拷贝既麻烦又性能低下，所以对于数据结构复杂层级深的项目，终极方案是结合&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Immutable&lt;/code&gt;使用。&lt;/p&gt;

&lt;p&gt;但是，Immutable体积庞大，语法不友好，我在将一个原本未引用改库的项目中改成用这库，改动非常大。Immutable已完成项目的接入成本高，适合一开始就接入，（使用方案我折腾完再分享。。。）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;对于数据结构扁平化，层级浅（3层以下），数据不复杂的项目，做到深拷贝，使用PureRenderMixin就能对性能做好良好的优化，且复杂度和接入成本都很低&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://facebook.github.io/react/docs/pure-render-mixin.html&quot;&gt;https://facebook.github.io/react/docs/pure-render-mixin.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://facebook.github.io/react/docs/advanced-performance.html&quot;&gt;https://facebook.github.io/react/docs/advanced-performance.html&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>打入react&redux（3）- reducer的拆分与组合</title>
   <link href="http://echizen.github.io/tech/2016/07-25-split-reducer"/>
   <updated>2016-07-25T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/split-reducer</id>
   <content type="html">
&lt;p&gt;react机制下一切皆组件，没有页面的概念。但是在使用redux划分state时还是有页面倾向的，说页面可能并不准确，更准确的是state是按功能集合划分的。&lt;/p&gt;

&lt;h1 id=&quot;reducer中state的划分&quot;&gt;reducer中state的划分&lt;/h1&gt;

&lt;p&gt;state总体上是按页面划分的，譬如现在有个列表页和详情页，还有一些用于多个组件的公用信息如个人信息（或者嵌入多个页面不同地方的组件，总之是公用信息）。我会这么划分：&lt;/p&gt;

&lt;h2 id=&quot;在各页面的reducer中维护各自的state&quot;&gt;在各页面的reducer中维护各自的state&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;list页的reducer:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  // list.js
  import * as ActionTypes from &apos;../constants/ActionTypes&apos;;
  const initialState = {
      totalPage: 1,
      pageIndex: 0, 
      list:[]
  };
	
  export default function handler(state = initialState, action) {
      switch (action.type) {
          case ActionTypes.APPEND_LIST:
              //...
          default:
              return state;
      }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;detail页的reducer:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  //detail.js
  import * as ActionTypes from &apos;../constants/ActionTypes&apos;;
  const initialState = {
      title: 1,
      author: 0, 
      content:&apos;&apos;,
      comment:[]
  };
	
  export default function handler(state = initialState, action) {
      switch (action.type) {
          case ActionTypes.APPEND_COMMENT:
              //...
          default:
              return state;
      }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;userInfo组件的reducer:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  //userInfo.js
  import * as ActionTypes from &apos;../constants/ActionTypes&apos;;
  const initialState = {
      userName:&apos;&apos;,
      userId:0,
      avator:&apos;&apos;
  };
	
  export default function handler(state = initialState, action) {
      switch (action.type) {
          case ActionTypes.GET_INFO:
              //...
          default:
              return state;
      }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;使用combinereducers将state合并&quot;&gt;使用combineReducers将state合并&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// reducers.js
import { combineReducers } from &apos;redux&apos;;

import list from &apos;../reducers/list&apos;;
import detail from &apos;../reducers/detail&apos;;
import userInfo from &apos;../reducers/userInfo&apos;;

export default combineReducers({
	list,
	detail,
    userInfo
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;connect-redux&quot;&gt;connect redux&lt;/h2&gt;

&lt;p&gt;封装一个connectRedux函数将state的分支按需分发给组件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import React from &apos;react&apos;;
import { connect } from &apos;react-redux&apos;;

/* components */
var List = require(&apos;ROOT_PATH/pages/ListView&apos;);
var Detail = require(&apos;ROOT_PATH/pages/DetalView&apos;);

var connectRedux = function(component, model = [], actions = null) {
  return connect(
    (state) =&amp;gt; {
      let needState = {};
      if(model.length){
        model.map((item)=&amp;gt;{
          Object.assign(needState, state[item]);
        })
      }else{
        needState = state;
      }
      return needState;
    }
  )(component);
}

List = connectRedux(List, [&apos;list&apos;,&apos;userInfo&apos;]);
Detail = connectRedux(Detail, [&apos;detail&apos;,&apos;userInfo&apos;]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样List组件能访问到：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const {totalPage, pageIndex, list, userName, userId, avator} = this.props
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Detail组件同理。&lt;/p&gt;

&lt;h1 id=&quot;action的处理&quot;&gt;action的处理&lt;/h1&gt;

&lt;p&gt;项目中会遇到这种情景。用户的某个操作可能导致需要更新属于state不同部分的数据。依旧拿上文中的数据举例，例如用户列表包括自己，假想有个弹窗可以更改自己头像，用户的更改将导致&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state.list[0].avator&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state.userInfo.avator&lt;/code&gt;都被更新，我们该如何派发action更新2部分数据呢？&lt;/p&gt;

&lt;h2 id=&quot;不能同时dispatch多个action&quot;&gt;不能同时dispatch多个action&lt;/h2&gt;

&lt;p&gt;我以前的做法是同时dispatch2个action，每个action更新state的不同部分，原则是以数据源区分action。随着深入了解，这是很不科学的，dispatch action引发state更新，state更新引发组件rerender，这个过程是个”类异步”（不是真正的异步）过程，你没办法保证哪个action先更新state触发rerender。有可能一前一后，也有可能，a先要被更新，但是组件的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isBatchingUpdates&lt;/code&gt;为false,处于等待更新状态，这时b的更新需求来了，也等待了一阵直到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isBatchingUpdates&lt;/code&gt;为true，最后2个state部分引发的组件更新被合并更新了，也就是值触发一个rerender。这种不确定性是可怕的，尤其是当2个state之间有依赖或者逻辑关联时，那就会出bug了。如果action中有异步操作，顺序就更不能保证了。&lt;/p&gt;

&lt;h2 id=&quot;通过actiontype分发&quot;&gt;通过action.type分发&lt;/h2&gt;

&lt;p&gt;这种情况比较靠谱的方案是通过相同的action.type执行2部分的state更新。&lt;/p&gt;

&lt;p&gt;ListView中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function changeAvator(avator,index){
    let myInfo = Object.assign({},this.props.list[index],{
        avator:avator
    });
    this.props.dispatch(changeUserInfo(userInfo,index));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;list页的action：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// list.js
export function changeUserInfo(userInfo,index){
    return {
        type: CHANGE_MY_INFO,
        userInfo: userInfo,
        index:index
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;list页的reducer:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import * as ActionTypes from &apos;../constants/ActionTypes&apos;;
const initialState = {
    totalPage: 1,
    pageIndex: 0, 
    list:[]
};

export default function handler(state = initialState, action) {
    let list = state.list.concat();
    switch (action.type) {
        case ActionTypes.CHANGE_MY_INFO:
            list[action.index] = action.userInfo;
            return Object.assign({},state,{
                list:list
            })

        default:
            return state;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;userInfo的reducer：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import * as ActionTypes from &apos;../constants/ActionTypes&apos;;
const initialState = {
    userName:&apos;&apos;,
    userId:0,
    avator:&apos;&apos;
};

export default function handler(state = initialState, action) {
    switch (action.type) {
        case ActionTypes.CHANGE_MY_INFO:
            return Object.assign({},state,{
                userName:action.userInfo.userName,
                userId:action.userInfo.userId,
                avator:action.userInfo.avator
            })

        default:
            return state;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到通过在不同reducer中对同一action.type处理，更新各自管理的数据，可以达到需求。&lt;/p&gt;

&lt;p&gt;以上内容只是我在项目中使用的方式，不一定是最好的，特别是通过action.type分发state的不同部分。如果你有更好的方法，欢迎留言指导。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>打入react&redux（2）- 神秘的dispatch</title>
   <link href="http://echizen.github.io/tech/2016/07-23-dispatch"/>
   <updated>2016-07-23T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/dispatch</id>
   <content type="html">
&lt;h1 id=&quot;action中dispatch的使用疑惑&quot;&gt;action中dispatch的使用疑惑&lt;/h1&gt;

&lt;p&gt;当在项目中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createStore&lt;/code&gt;时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyMiddleware&lt;/code&gt;了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redux-thunk&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thunkMiddleware&lt;/code&gt;后，action里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;就变得很魔性，加也可以，不加也可以，譬如以下2种写法都能达到预期的效果。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export function fetchPosts(reddit) {
  return {
    type: REQUEST_POSTS,
    reddit
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;或者&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export function fetchPosts(reddit) {
  return dispatch =&amp;gt; {
    return dispatch({     
      type: REQUEST_POSTS,
      reddit
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么在派发action的函数里用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;和不使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch&lt;/code&gt;有什么区别&lt;/p&gt;

&lt;h1 id=&quot;从函数调用栈挖出dispatch干了啥&quot;&gt;从函数调用栈挖出dispatch干了啥&lt;/h1&gt;

&lt;h2 id=&quot;情形1return-dispatchaction&quot;&gt;情形1：return dispatch(action)&lt;/h2&gt;

&lt;p&gt;先看加了dispatch的函数调用栈&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export function fetchPosts(reddit) {
  return dispatch =&amp;gt; {
    return dispatch({     
      type: REQUEST_POSTS,
      reddit
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2016072301.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;按图中标注我们来看看每个核心步骤都在干什么。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;view层调用dispatch分发action&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; componentDidMount() {
     const { dispatch, selectedReddit } = this.props
     dispatch(fetchPostsIfNeeded(selectedReddit))     //执行点
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;进入thunkMiddleware&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; function thunkMiddleware(_ref) {
   var dispatch = _ref.dispatch;
   var getState = _ref.getState;
	
   return function (next) {
     return function (action) {
       return typeof action === &apos;function&apos; ? action(dispatch, getState) : next(action); //  执行点，action为
     };
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;这个函数在全过程中执行了2次，这一次执行时，action的值为：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   dispatch =&amp;gt; {
     return dispatch({
         type: REQUEST_POSTS,
         reddit
       })
    }
   };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;可见action为一个函数，	&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thunkMiddleware&lt;/code&gt;将dispatch和getState方法注入到这个函数中并执行该函数。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;return dispatch(action)。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; export function fetchPostsIfNeeded(reddit) {
   return dispatch =&amp;gt; {
     return dispatch({     //执行点
       type: REQUEST_POSTS,
       reddit
     })
   }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;进入applyMiddleware中执行封装的供中间件使用的dispatch方法。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; function applyMiddleware() {
   for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key &amp;lt; _len; _key++) {
     middlewares[_key] = arguments[_key];
   }
	
   return function (createStore) {
     return function (reducer, initialState, enhancer) {
       var store = createStore(reducer, initialState, enhancer);
       var _dispatch = store.dispatch;
       var chain = [];
	
       var middlewareAPI = {
         getState: store.getState,
         dispatch: function dispatch(action) {
           return _dispatch(action);     //执行点
         }
       };
       chain = middlewares.map(function (middleware) {
         return middleware(middlewareAPI);
       });
       _dispatch = _compose2[&quot;default&quot;].apply(undefined, chain)(store.dispatch);
	
       return _extends({}, store, {
         dispatch: _dispatch
       });
     };
   };
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;同2中一致，再次进入thunkMiddleware，这次action为action对象，将直接调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next(action)&lt;/code&gt;:&lt;/p&gt;

    &lt;p&gt;{
    type: REQUEST_POSTS,
    reddit
 }&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;这是因为加了redux-logger，调用redux-logger中间件处理&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;真正执行store中的dispatch函数，并去调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;currentReducer&lt;/code&gt;获取已最新的state状态。以下是redux中createStore.js里dispatch方法的源码。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; function dispatch(action) {
     if (!(0, _isPlainObject2[&quot;default&quot;])(action)) {
       throw new Error(&apos;Actions must be plain objects. &apos; + &apos;Use custom middleware for async actions.&apos;);
     }
	
     if (typeof action.type === &apos;undefined&apos;) {
       throw new Error(&apos;Actions may not have an undefined &quot;type&quot; property. &apos; + &apos;Have you misspelled a constant?&apos;);
     }
	
     if (isDispatching) {
       throw new Error(&apos;Reducers may not dispatch actions.&apos;);
     }
	
     try {
       isDispatching = true;
       currentState = currentReducer(currentState, action);     //执行点
     } finally {
       isDispatching = false;
     }
	
     var listeners = currentListeners = nextListeners;
     for (var i = 0; i &amp;lt; listeners.length; i++) {
       listeners[i]();
     }
	
     return action;
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;combineReducer将action分发到相应的reducer的state分支进行更新。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;reducer中更新state&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; function postsByReddit(state = { }, action) {
   switch (action.type) {
     case INVALIDATE_REDDIT:
     case RECEIVE_POSTS:
     case REQUEST_POSTS:
       return Object.assign({}, state, {
         [action.reddit]: posts(state[action.reddit], action)
       })
     default:
       return state
   }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;情形二直接return-action&quot;&gt;情形二：直接return action&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2016062302.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种使用方式会从1直接执行到5，没有thunkMiddleware中调用action(dispatch, getState)一步，因为action不是一个函数，只是一个对象。也不会执行applyMiddleware中封装的dispacth方法。&lt;/p&gt;

&lt;h1 id=&quot;dispatch解密&quot;&gt;dispatch解密&lt;/h1&gt;

&lt;p&gt;从以上函数调用栈的追踪可以解开dispatch的谜团了。&lt;/p&gt;

&lt;p&gt;dispatch分为2种：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type BaseDispatch = (a: Action) =&amp;gt; Action&lt;/code&gt;，由redux createStore.js提供的store.dispatch。处理结果返回action对象&lt;/li&gt;
  &lt;li&gt;
    &lt;table&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;type Dispatch = (a: Action&lt;/td&gt;
          &lt;td&gt;AsyncAction) =&amp;gt; any，由applyMiddleware封装的能够处理同步action和异步action的函数，并将返回值传递给其他中间件使用。他的返回值类型没有任何限制。&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Base dispatch function 总是&lt;strong&gt;同步&lt;/strong&gt;地把 action 与上一次从 store 返回的 state 发往 reducer，然后计算出新的 state。它期望 action 会是一个可以被 reducer 消费的普通对象。&lt;/p&gt;

&lt;p&gt;Middleware 封装了 base dispatch function，允许 dispatch function 处理 普通action 之外的异步 action。 &lt;strong&gt;Middleware 可以改变、延迟、忽略 action 或异步 action，也可以在传递给下一个 middleware 之前对它们进行解释&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;上述例子中的情形一就是调用了Middleware封装的dispacth对action做了处理（虽然我没有真的写处理代码），而情形二是直接调用store实例的base dispatch.&lt;/p&gt;

&lt;h2 id=&quot;action-creator&quot;&gt;action creator&lt;/h2&gt;

&lt;p&gt;这里要引入action creator的概念。&lt;/p&gt;

&lt;p&gt;Action Creator 就是一个创建 action 的函数。Action 是一个信息的负载，而 action creator 是一个创建 action 的工厂。&lt;/p&gt;

&lt;p&gt;调用 action creator 只会生产 action，但不分发。还需要调用 store 的 dispatch function 才会引起变化。&lt;/p&gt;

&lt;h1 id=&quot;什么时候要在action-creator调用dispatch&quot;&gt;什么时候要在action creator调用dispatch&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;如果 action creator 需要读取当前的 state、调用 API、或引起诸如路由变化等副作用，那么它应该返回一个异步 action而不是 action&lt;/strong&gt;。这时候就应该使用Middleware封装的dispacth了。&lt;/p&gt;

&lt;p&gt;最常见的2种情形：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;需要获取state，通过读取state的内容结合view层传过来的参数生成action的内容，就需要使用Middleware封装的方法，得到getState接口来获得state，然后dispatch action&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  function actionCreator(params) {
      (dispatch, getState) =&amp;gt; {
          let state = getState();
          //获取state内容，并结合params等进行逻辑处理
          return dispatch({
              type: types.TEST
              data: data
          })
      }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;异步action。譬如需要异步请求的结果作为action的内容，需要和Promise搭配使用等。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  function fetchData(data){
      fetch(url, {
          body: data
      })
      .then((response) =&amp;gt; {
      	if(response.status &amp;gt;= 200 &amp;amp;&amp;amp; response.status &amp;lt; 300){
              return response.json()
          } else {
              message.error(&quot;服务器错误，请联系技术哥哥&quot;);
          }
      })
      .then((response) =&amp;gt; {
        return new Promise((resolve, reject) =&amp;gt; {
            response &amp;amp;&amp;amp; resolve(response);
        })
      })
      .catch(()=&amp;gt;{
        errorCallback();
      });
  }
	
  export function asynchronousActionCreator(params){
	
    return (dispatch, getState) =&amp;gt; {
      let promiseRes;
	    
      promiseRes = fetchData(params); // fetchData函数返回的是promise对象
      if(promiseRes &amp;amp;&amp;amp; promiseRes.then){
        promiseRes.then((data)=&amp;gt;{ 
          return  dispatch({
                  type: types.CHANGE_DATA,
                  data: data
                })    
      });
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>打入react&redux（1） - 从jsx语法的疑惑追寻虚拟dom的渲染过程</title>
   <link href="http://echizen.github.io/tech/2016/07-21-jsx"/>
   <updated>2016-07-21T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/jsx</id>
   <content type="html">
&lt;p&gt;新手上路总想在jsx的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt;里写正常的js语句，特别是赋值语句。js模板引擎带来的习惯。。。然而jsx不想理你并向你抛来了一堆错误。最近有妹子问我为什么不能用赋值语句，这下还真问倒我了，只知道不能却还真不知道为什么，于是去研究了一番。&lt;/p&gt;

&lt;h1 id=&quot;jsx是什么&quot;&gt;jsx是什么？&lt;/h1&gt;

&lt;p&gt;JSX只是函数调用和对象创建的语法糖。&lt;/p&gt;

&lt;p&gt;他跟js模板引擎不一样，不是把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;}&lt;/code&gt;看成标识符，标识符内的内容当成js执行。&lt;/p&gt;

&lt;p&gt;在jsx中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt;内的内容是要被转换成dom标签内的内容的。&lt;/p&gt;

&lt;h1 id=&quot;jsx会变成什么&quot;&gt;jsx会变成什么？&lt;/h1&gt;

&lt;p&gt;jsx毫无疑问是被转化成js，这些js的功能是创建虚拟dom，最终是要填充到页面上的。&lt;/p&gt;

&lt;p&gt;譬如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;return (
    &amp;lt;ul&amp;gt;
      {
        testArr.map(function(item) {
          return &amp;lt;li&amp;gt;{item}&amp;lt;/li&amp;gt;
        })
      }
    &amp;lt;/ul&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;是要被转成：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_react3.default.createElement(
   &apos;ul&apos;,
   null,
   testArr.map(function (item) {
     return _react3.default.createElement(
       &apos;li&apos;,
       null,
       item
     );
   })
 )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然不同的编译器转换结果略有差异，不过结构都一样。&lt;/p&gt;

&lt;h2 id=&quot;jsx如何转换呢&quot;&gt;jsx如何转换呢？&lt;/h2&gt;

&lt;p&gt;常使用的方式是通过编译器，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;babel&lt;/code&gt;，babel有专门的插件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jsx&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;babel-preset-react&lt;/code&gt;等。现在react太火了，babel自身就集成了编译jsx的功能。&lt;/p&gt;

&lt;h2 id=&quot;jsx是函数调用和对象创建的语法糖&quot;&gt;JSX是函数调用和对象创建的语法糖&lt;/h2&gt;

&lt;p&gt;在编写JSX时，在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ }&lt;/code&gt; 中不能使用语句（if语句、for语句等等），但可以使用求值表达式，它是会返回值的表达式。因为他是要被转成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createElement&lt;/code&gt;的参数的，稍后会讲到，这些参数是明确的值。&lt;/p&gt;

&lt;h3 id=&quot;jsx里可以有什么&quot;&gt;jsx里可以有什么&lt;/h3&gt;

&lt;p&gt;我们不能直接使用语句，但可以把语句包裹在函数求值表达式中运用。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;立即执行函数，这个方式有点挫。。。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;div&amp;gt;
      {
          function(){
              let a;
              //操作a
              return a;
          }
      }
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;三目运算符&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;div&amp;gt; { a&amp;gt;b?a:b } &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;最好的方式是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt;中先处理好需要的值，在return的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt;里只包含简单变量或者函数。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;抛砖---里合法内容是有返回值的js语句&quot;&gt;抛砖 - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt;里合法内容是有返回值的js语句&lt;/h1&gt;

&lt;p&gt;1）jsx中直接声明赋值会报错：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2016072101.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2) 但是这样却可以：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2016072102.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;会被编译成：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2016072103.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最终显示在页面上是：&lt;/p&gt;

&lt;p&gt;我是&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;我是1&lt;/li&gt;
  &lt;li&gt;我是2&lt;/li&gt;
  &lt;li&gt;我是3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1）中包含了申明语句，其实是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let testStr;testStr=&apos;我是&apos;&lt;/code&gt;，关于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let testStr&lt;/code&gt;jsx编译器内心是懵逼的，这个语句没有返回值啊！但是直接使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testStr=&apos;我是&apos;&lt;/code&gt;获取的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testStr&lt;/code&gt;的返回值,这个值会被直接打印在页面中。&lt;/p&gt;

&lt;p&gt;这就跟你在函数中直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return let testStr=&apos;我是&apos;&lt;/code&gt;会报错，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return testStr=&apos;我是&apos;&lt;/code&gt;获得的返回值是’我是’一样。jsx会将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt;当做返回值处理。所有不能放在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return&lt;/code&gt;语句里的内容都会报错。&lt;/p&gt;

&lt;h1 id=&quot;引玉---翻出reactelement核心源码装波逼&quot;&gt;引玉 - 翻出ReactElement核心源码装波逼&lt;/h1&gt;

&lt;p&gt;ReactElement.js&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * React Element构造器
 */
var ReactElement = function (type, key, ref, self, source, owner, props) {
  // 定义element的基本属性
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  };

  if (process.env.NODE_ENV !== &apos;production&apos;) {
    
    if (canDefineProperty) {
      Object.defineProperty(element._store, &apos;validated&apos;, {
        configurable: false,
        enumerable: false,
        writable: true,
        value: false
      });
      
      Object.defineProperty(element, &apos;_self&apos;, {
        configurable: false,
        enumerable: false,
        writable: false,
        value: self
      });
      
      Object.defineProperty(element, &apos;_source&apos;, {
        configurable: false,
        enumerable: false,
        writable: false,
        value: source
      });
    } else {
      element._store.validated = false;
      element._self = self;
      element._source = source;
    }
    Object.freeze(element.props);
    Object.freeze(element);
  }
  // 定义了element的完整属性后返回
  return element;
};

ReactElement.createElement = function (type, config, children) {
  var propName;

  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  if (config != null) {
    ref = config.ref === undefined ? null : config.ref;
    key = config.key === undefined ? null : &apos;&apos; + config.key;
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    
    // 将config的内容挂到props下
    for (propName in config) {
      if (config.hasOwnProperty(propName) &amp;amp;&amp;amp; !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }

  //如果children不知一个，就放到props.children数组里，留给接下来递归渲染
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength &amp;gt; 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i &amp;lt; childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type &amp;amp;&amp;amp; type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (typeof props[propName] === &apos;undefined&apos;) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 创建element
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ReactElement.createElement的功能也就是处理一下获取到的type，config，将锁哥children（子组件）平行放入props属性中，并调用ReactElement获取最终的element。&lt;/p&gt;

&lt;p&gt;这个element跟浏览器中dom结构不是一个东西，要看他如何发挥作用，还得看&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render&lt;/code&gt;函数如何操作。下面上的都是简化版的代码了。&lt;/p&gt;

&lt;p&gt;render函数简化版：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;React = {
    nextReactRootIndex:0,
    render:function(element,container){

        var componentInstance = instantiateReactComponent(element);
        var markup = componentInstance.mountComponent(React.nextReactRootIndex++);
        $(container).html(markup);
        //触发完成mount的事件
        $(document).trigger(&apos;mountReady&apos;);    
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;instantiateReactComponent.js能够根据不同的类型的node返回相应类型的实例。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//component工厂  用来返回一个component实例
function instantiateReactComponent(node){
    //文本节点的情况
    if(typeof node === &apos;string&apos; || typeof node === &apos;number&apos;){
        return new ReactDOMTextComponent(node);
    }
    //浏览器默认节点的情况
    if(typeof node === &apos;object&apos; &amp;amp;&amp;amp; typeof node.type === &apos;string&apos;){
        //注意这里，使用了一种新的component
        return new ReactDOMComponent(node);

    }
	...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ReactDOMTextComponent.js生成text类型的component。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//component类，用来表示文本在渲染，更新，删除时应该做些什么事情
function ReactDOMTextComponent(text) {
    //存下当前的字符串
    this._currentElement = &apos;&apos; + text;
    //用来标识当前component
    this._rootNodeID = null;
}

//component渲染时生成的dom结构
ReactDOMTextComponent.prototype.mountComponent = function(rootID) {
    this._rootNodeID = rootID;
    return &apos;&amp;lt;span data-reactid=&quot;&apos; + rootID + &apos;&quot;&amp;gt;&apos; + this._currentElement + &apos;&amp;lt;/span&amp;gt;&apos;;
} 这里很好的解释了为何我们能看到react渲染出来的dom总会加上`&amp;lt;span&amp;gt;`标签，纯文本的类型的node都会交给ReactDOMTextComponent来渲染，ReactDOMTextComponent会用`&amp;lt;span&amp;gt;`包裹文本。	
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ReactDOMComponent.js：核心，将react的element转化成真正的虚拟dom结构的地方。对于虚拟dom的渲染逻辑，本质上还是递归渲染，reactElement会递归渲染自己的子节点。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//component类，用来表示文本在渲染，更新，删除时应该做些什么事情
function ReactDOMComponent(element){
    //存下当前的element对象引用
    this._currentElement = element;
    this._rootNodeID = null;
}

//component渲染时生成的dom结构
ReactDOMComponent.prototype.mountComponent = function(rootID){
    //赋值标识
    this._rootNodeID = rootID;
    var props = this._currentElement.props;
    var tagOpen = &apos;&amp;lt;&apos; + this._currentElement.type;
    var tagClose = &apos;&amp;lt;/&apos; + this._currentElement.type + &apos;&amp;gt;&apos;;

    //加上reactid标识
    tagOpen += &apos; data-reactid=&apos; + this._rootNodeID;

    //拼凑出属性
    for (var propKey in props) {

        //这里要做一下事件的监听，就是从属性props里面解析拿出on开头的事件属性的对应事件监听
        if (/^on[A-Za-z]/.test(propKey)) {
            var eventType = propKey.replace(&apos;on&apos;, &apos;&apos;);
            //针对当前的节点添加事件代理,以_rootNodeID为命名空间
            $(document).delegate(&apos;[data-reactid=&quot;&apos; + this._rootNodeID + &apos;&quot;]&apos;, eventType + &apos;.&apos; + this._rootNodeID, props[propKey]);
        }

        //对于children属性以及事件监听的属性不需要进行字符串拼接
        //事件会代理到全局。这边不能拼到dom上不然会产生原生的事件监听
        if (props[propKey] &amp;amp;&amp;amp; propKey != &apos;children&apos; &amp;amp;&amp;amp; !/^on[A-Za-z]/.test(propKey)) {
            tagOpen += &apos; &apos; + propKey + &apos;=&apos; + props[propKey];
        }
    }
    //获取子节点渲染出的内容
    var content = &apos;&apos;;
    var children = props.children || [];

    var childrenInstances = []; //用于保存所有的子节点的componet实例，以后会用到
    var that = this;
    $.each(children, function(key, child) {
        //这里再次调用了instantiateReactComponent实例化子节点component类，拼接好返回
        var childComponentInstance = instantiateReactComponent(child);
        childComponentInstance._mountIndex = key;

        childrenInstances.push(childComponentInstance);
        //子节点的rootId是父节点的rootId加上新的key也就是顺序的值拼成的新值
        var curRootId = that._rootNodeID + &apos;.&apos; + key;
        //得到子节点的渲染内容
        var childMarkup = childComponentInstance.mountComponent(curRootId);
        //拼接在一起
        content += &apos; &apos; + childMarkup;

    })

    //留给以后更新时用的这边先不用管
    this._renderedChildren = childrenInstances;

    //拼出整个html内容
    return tagOpen + &apos;&amp;gt;&apos; + content + tagClose;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;网上找了张图很好的表达了虚拟dom的渲染过程&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/2016072104.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>webpack-dev-server使用react-router browserHistory的配置</title>
   <link href="http://echizen.github.io/tech/2016/07-05-webpack-dev-server-react-router-config"/>
   <updated>2016-07-05T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/webpack-dev-server-react-router-config</id>
   <content type="html">
&lt;p&gt;本来这玩意不值得写一篇博客的，但是坑爹的是我在网上搜了一圈解决方案都不可行，webpack-dev-sever官网的文档使用说明还有误导性。最后还是自己慢慢拨出来的。&lt;/p&gt;

&lt;h2 id=&quot;问题的产生&quot;&gt;问题的产生&lt;/h2&gt;

&lt;h3 id=&quot;browserhistory如何设置路由&quot;&gt;browserHistory如何设置路由&lt;/h3&gt;

&lt;p&gt;react-router有3种路由方式：贴近实际路由的browserHistory，我们熟悉的兼容性很好的hashHistory，还有我还没了解的createMemoryHistory。&lt;/p&gt;

&lt;p&gt;browserHistory使用的是h5 history api，给用户看到的浏览器url与普通的path没有区别，其实路由都是交给js来处理的，后端只用把所有根路径下子path的路由都指向同一个文件，交由js根据path处理展示内容，js通过history.push、history.repalce等api处理链接跳转，其实用户看到的只是url被repalce，页面并没有刷新，js做了动态替换了dom，获取数据等等，但是视觉上感觉页面跳转了，速度更快，除了数据展示部分页面没啥闪烁，当然你也可以做一些渐入渐出的特效让“页面跳转”看起来更自然，扯远了。。。&lt;/p&gt;

&lt;h3 id=&quot;webpack-dev-sever如何定位文件&quot;&gt;webpack-dev-sever如何定位文件&lt;/h3&gt;

&lt;p&gt;browserHistory的路由跟普通url path部分没啥2样，通过’/’分割路径。&lt;/p&gt;

&lt;p&gt;webpack-dev-sever是静态资源服务器，他会通过你的output配置去读取文件，通过’/’分割以文件查找的模式匹配文件。这样自然就产生问题了，因为你配置的路由并不是实际存在的文件，根据文件查找的方式是找不到的，只会404。&lt;/p&gt;

&lt;h2 id=&quot;解决方案&quot;&gt;解决方案&lt;/h2&gt;

&lt;h3 id=&quot;webpackconfigjs配置&quot;&gt;webpack.config.js配置&lt;/h3&gt;

&lt;p&gt;webpack有一个选项devServer用于协助wepack-dev-server支持h5 history api的路由。于是你可以类似这样的配置：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module.exports = {
    entry: &quot;./src/app/index.js&quot;,
    output: {
        path: path.resolve(__dirname, &apos;build&apos;),
        publicPath: &apos;build&apos;,
        filename: &apos;bundle-main.js&apos;
    },
    devServer: {
        historyApiFallback:{
            index:&apos;build/index.html&apos;
        },
    },
    //其他的配置省略
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;output.publicPath告诉webpack从该路径下寻找文件，在这里也就是把build当成文件系统入口，根路径。&lt;/p&gt;

&lt;p&gt;devServer.historyApiFallback的意思是当路径匹配的文件不存在时不出现404,而是取配置的选项historyApiFallback.index对应的文件。&lt;/p&gt;

&lt;p&gt;这样开启webpack-dev-server后会看到终端：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160705.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ps:我就是修改配置项看终端信息才知道这2个参数是这个意思，坑爹的官网解释就是个坑，遇到问题已经不能只靠官网和stackoverflow了，还是多自己研究原理和现象吧。&lt;/p&gt;

&lt;h3 id=&quot;同一个项目配置多个单页面系统入口&quot;&gt;同一个项目配置多个单页面系统入口&lt;/h3&gt;

&lt;p&gt;我有需求要在同一个项目中开启2套单页面体系，一套是给用户用的平台，一套是给管理员用的管理后台。并不想每次启动2个服务器去开发，所以需要搞定一套webpack配置，能运行2个入口：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module.exports = {
    entry: {
        &quot;main&quot;: &quot;./src/app/index.js&quot;,
        &quot;admin&quot;: &quot;./src/admin/index.js&quot;
    },
    output: {
        path: path.resolve(__dirname, &apos;build&apos;),
        publicPath: &apos;build&apos;,
        filename: &apos;bundle-[name].js&apos;
    },
    devServer: {
        historyApiFallback:{
      index:&apos;build/index.html&apos;,
      rewrites: [
          { from: /^\/admin/, to: &apos;build/admin.html&apos; }
      ],
    },
    },
    //其他的配置省略
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个配置entry配置了2个入口。&lt;/p&gt;

&lt;p&gt;output配置生成了2个文件，[name]是定义的entry的键名.&lt;/p&gt;

&lt;p&gt;historyApiFallback中的配置依旧是404页面导入到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/index.html&lt;/code&gt;这个用户系统的主入口，但是rewrites配置了重定向规则，from指定如果路径能匹配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/^\/admin/&lt;/code&gt;，则指向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/admin.html&lt;/code&gt;入口html文件。&lt;/p&gt;

&lt;h3 id=&quot;坑爹的官网&quot;&gt;坑爹的官网&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://webpack.github.io/docs/webpack-dev-server.html#the-historyapifallback-option&quot;&gt;http://webpack.github.io/docs/webpack-dev-server.html#the-historyapifallback-option&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20160705-0@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当然这也有可能不是官网文档的信息有问题，有可能是我webpack-dev-server是1.10.1，而官网最新版是1.13的问题。&lt;/p&gt;

&lt;p&gt;我按官网这种将historyApiFallback.index和output.publicPath配置为同一个路径，webpack-dev-server是不知道去找这个文件夹下的index.html或者其他html文件的。&lt;/p&gt;

&lt;p&gt;还有一个地方看官网文档纳闷了一下，顺便解释。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;module.exports透出配置是使用终端CLI启动（或者类似npm script配置利用npm run启动这类命令行启动）webpack-dev-server。&lt;/p&gt;

    &lt;p&gt;module.exports = {
      // configuration
  };&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;webpack({})这种方式是给node.js api使用的。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  var WebpackDevServer = require(&quot;webpack-dev-server&quot;);
  var webpack = require(&quot;webpack&quot;);
	
  var compiler = webpack({
    // configuration
  });
  var server = new WebpackDevServer(compiler, {
    // webpack-dev-server options
	
  });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>嵌套数据的扁平化设计</title>
   <link href="http://echizen.github.io/tech/2016/04-25-data-design"/>
   <updated>2016-04-25T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/data-design</id>
   <content type="html">
&lt;p&gt;前端写了2年，感觉到复杂度来源于数据源，也就是redux中的state。如果数据结构设计的不方便操作，后继的状态改变，交互过程都会变得很复杂。&lt;/p&gt;

&lt;p&gt;##扁平化的数据设计&lt;/p&gt;

&lt;p&gt;通常我们的数据是嵌套的，按自然关系划分一层层嵌套，数组里含对象，对象里嵌数组或数组。然后查找时一层层的map，效率又低又难懂。&lt;/p&gt;

&lt;p&gt;有一种数据设计是扁平化的设计，避免嵌套，只有一层，自然关系用添加一个关联字段表示。这样增查改删都会方便很多。玩过数据库会知道，这类似于关联表的设计。&lt;/p&gt;

&lt;p&gt;举个别人家的官方例子：&lt;/p&gt;

&lt;p&gt;自然关系表示的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;articles: [{
    id: 1,
    title: &apos;Some Article&apos;,
    author: {
      id: 7,
      name: &apos;Dan&apos;
    },
    ...
  },
  ...
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;扁平化设计：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;articles: {
  1: { author: 7, ... }, // &amp;lt;--- Same happens for references to other entities in the schema
  2: { ... },
  ...
},
users: {
  7: { ... },
  ..
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;数据结构是想表示一个文章列表的数组对象，每个文章都有对应的作者，作者信息又是个对象，自然关系的表示方法是将作者信息挂在每个文章下面的author字段，形成自然又复杂的嵌套结构，很容易看懂关系，但是不容易操作。&lt;/p&gt;

&lt;p&gt;扁平化的设计是讲文章列表和作者列表分开，各自是个对象，每个内容都有唯一id，然后在文章&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;articles&lt;/code&gt;中用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;author&lt;/code&gt;字段的id值表述关联关系。&lt;/p&gt;

&lt;p&gt;可能扁平化的结构看上去不是一目了然的知道数据之间的关系（其实习惯了也很自然）），但是操作一下深有感触会方便很多。&lt;/p&gt;

&lt;h2 id=&quot;适用场景&quot;&gt;适用场景&lt;/h2&gt;

&lt;p&gt;如果数据结构很简单，没必要设计成扁平化的。&lt;/p&gt;

&lt;p&gt;扁平化的数据适用于原数据层级嵌套很深的情况，2层以上会有很大便利。且你所要操作的数据不仅在第一层，更多的增查改删发生在更深的层级。&lt;/p&gt;

&lt;h2 id=&quot;对比&quot;&gt;对比&lt;/h2&gt;

&lt;p&gt;为了简便，我这里对比一个2层的列子：&lt;/p&gt;

&lt;h3 id=&quot;嵌套式数据的增查改删&quot;&gt;嵌套式数据的增查改删：&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var book = [{
  id: 1,
  title: &apos;first day&apos;,
  author: [{
    id: 6,
    name: &apos;kelly&apos;
  },{
    id: 7,
    name: &apos;cindy&apos;
  }]
},{
	id: 2,
  title: &apos;grass&apos;,
  author: [{
    id: 8,
    name: &apos;blue&apos;
  },{
    id: 5,
    name: &apos;yellow&apos;
  }]
}];

// 增
var getDdata = {
    id: 3,
    title: &apos;third aticle&apos;,
    author: [{
      id: 9,
      name: &apos;Dan&apos;
    },{
      id: 10,
      name: &apos;buffer&apos;
    }],
};

book.push(getDdata);

//查：get article which author id=8 all info
var queryAuId = 8;
var queryResult = book.find(function(item) {
  var isQuery = false;
  item.author.map(function(author){
    if(author.id === queryAuId){
      isQuery = true;
    }
  })
  if(isQuery){
    return item;
  }
})

// 改：modify article which author id=8 from title &apos;grass&apos; to &apos;sky&apos;
var modifyAuId = 8;
book.map(function(item) {
  var isQuery = false;
  item.author.find(function(author){
    if(author.id === modifyAuId){
      isQuery = true;
    }
  })
  if(isQuery){
    item.title = &apos;sky&apos;;
  }
})

// 删：delete article which author id = 7
var deleteAuId = 7;
var deleteIndex;
book.map(function(item,index) {
  var isQuery = false;
  item.author.map(function(author){
    if(author.id === deleteAuId){
      isQuery = true;
    }
  })
  if(isQuery){
    deleteIndex = index;
  }
})

book.splice(deleteIndex,1);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;嵌套式数据的操作基本依赖于循环map查找，有几层一般就得套几层map。&lt;/p&gt;

&lt;h3 id=&quot;扁平化数据的增查改删&quot;&gt;扁平化数据的增查改删：&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var book = {
	articles: {
	  1: { 
	  		title: &apos;first day&apos;
	  	 }, // &amp;lt;--- Same happens for references to other entities in the schema
	  2: { 
	  		title: &apos;grass&apos;
	  },
	},
	authors: {
		5: {
    	article: 2,
    	name: &apos;yellow&apos;
  	},
		6: {
	    article: 1,
	    name: &apos;kelly&apos;
	  },
	  7: {
	  	article: 1,
	  	name:&apos;cindy&apos;
	  },
	  8: {
	  	article: 2,
	  	name:&apos;blue&apos;
	  }
	}
}

// 增
var getData = {
    id: 3,
    title: &apos;third article&apos;,
    author: [{
      id: 9,
      name: &apos;Dan&apos;
    },{
      id: 10,
      name: &apos;buffer&apos;
    }],
};

book.articles[getData.id]={
	title: getData.title
};

getData.author.map(function(item) {
	book.authors[item.id] = {
		article: getData.id,
		name: item.name
	}
})

//查：get article which author id=8  info
var queryAuId = 8;
var resAuthor = book.authors[queryAuId];
var resArticle = book.articles[resAuthor.article];
var queryResult = {
	title: resArticle.title,
	author: [{
		id: queryAuId,
		name: resAuthor.name
	}] 
}

// 改：modify article which author id=8 from title &apos;grass&apos; to &apos;sky&apos;
var modifyAuId = 8;
book.articles[book.authors[modifyAuId].article].title = &apos;sky&apos;;

// 删：delete article which author id = 7
var deleteAuId = 7;
var toDeleteAuthor = book.authors[deleteAuId];
delete book.articles[toDeleteAuthor.article];
delete book.authors[deleteAuId];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;扁平化数据操作复杂度与层级没有必然联系，2层和n层复杂度相同。&lt;/p&gt;

&lt;p&gt;可能这个2层数据的例子中扁平化的优势还不是很明显，这就是个抛砖引玉的例子，你可以假设一下他是3层根据例子的情景在脑海中过一下增查改删操作，会发现嵌套式循环都多加一层，扁平化方式无变动，优势立即体现出来了。&lt;/p&gt;

&lt;h2 id=&quot;实例场景-一个多级联动select&quot;&gt;实例场景-一个多级联动select&lt;/h2&gt;

&lt;p&gt;下面通过我上周遇到的一个多级联动的select，比较一下2种设计。（为了方便我直接搬运过来了就不抽象了）&lt;/p&gt;

&lt;p&gt;开发环境是react+redux，这种select的个数不确定，下一级select由上一级选中的内容确定，且上级select选中后，如果状态里没有它的子select内容，需要去拉取数据填充。所以涉及到的操作有增查。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20160425-0@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;嵌套结构&quot;&gt;嵌套结构&lt;/h3&gt;

&lt;p&gt;数据结构，这是一个树状的结构，每个不在末枝的类目都有自己的子支（这里假设他有四层）。&lt;/p&gt;

&lt;p&gt;searchPattern中的pageBranch是要搜索的select选中值的数组（业务需要）。deepIndex是被选中的值在所在数组中的索引，是为了查找方便设置的一个变量。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const initialState = {

    searchPattern:{
        keyword: &quot;&quot;,
        pageBranch:[&apos;&apos;]   
    },

    branches:[{
        name:&apos;团购&apos;,
        child: [{
            name:&apos;剁手团&apos;,
            child:[{
                name:&apos;子分支1&apos;
            },{
                name:&apos;子分支2&apos;
            }]
        },{
            name:&apos;大牌&apos;
        }]
    },{
        name:&apos;女装&apos;,
        child: [{
            name:&apos;春装&apos;,
            child:[{
                name:&apos;针织衫&apos;
            },{
                name:&apos;毛衣&apos;
            }]
        },{
            name:&apos;夏装&apos;,
            child:[{
                name:&apos;裙子&apos;,
                child:[{
                	name:&apos;长裙&apos;
            	},{
                	name:&apos;短裙&apos;
            	}]   
            },{
                name:&apos;T恤&apos;
            }]
        }]
    }],

    deepIndex:[]    //记录select中分支层级被选中的值的索引
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;（以下部分没注意对象的引用浅拷贝的问题，不小心改变了原状态，由于这部分代码被舍弃了，所以也没为了例子去改，不是重点不要太在意。。。）	
reducer中对state的更新部分：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;case ActionTypes.GET_GROUPS:
   return Object.assign({},state,{
        branches: action.branches
    });

case ActionTypes.CHANGE_SELECT:
    let newState = Object.assign({},state);
    let deepLen = newState.deepIndex.length;
    if(action.deep != deepLen-1){
        for(let i=deepLen-1;i&amp;gt;action.deep;i--){
            newState.deepIndex.pop();
        }
    }
    newState.deepIndex[action.deep]=action.valueIndex;
    return newState;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;View部分，通过循环变量deepIndex来确定展示几个select,通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tempBranch = tempBranch.child[item]&lt;/code&gt;，让tempBranch这个临时变量再循环中不断的一层层取子支内容展示。还得有个deep变量传给action告诉自己当前操作的是第几层select。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default class GroupSelectView extends Component {
  constructor() {
    super();
  }

  handleSelectChange(dataKey,deep,value,valueIndex){
    this.props.dispatch(changeSelect(deep,valueIndex-1));
    this.props.dispatch(getGroups(deep));

    let newPattern = Object.assign({},this.props.searchPattern);

    let deepLen = this.props.searchPattern[dataKey].length;
    if(deep != deepLen-1){
        for(let i=deepLen-1;i&amp;gt;deep;i--){
            newPattern[dataKey].pop();
        }
    }
    newPattern[dataKey][deep] = value;
    this.props.dispatch(changeSearchPattern(newPattern));
  }

  render() {
    let tempBranch = this.props.branches.slice();
    let firOptions = tempBranch.map(function(firBranchItem){
      return {
          text:firBranchItem.name,
          value:firBranchItem.name
        }
    });
    firOptions.unshift({
      text:&apos;选择业务&apos;,
      value:&apos;&apos;
    });

    let GroupSelect = this.props.deepIndex.map(function(item,index) {
      if(index==0){
        tempBranch = tempBranch[item];
      }else{
        tempBranch = tempBranch.child[item];
      }
      let options = tempBranch.child&amp;amp;&amp;amp;tempBranch.child.map(function(branchItem){
        return {
          text:branchItem.name,
          value:branchItem.name
        }
      })

      if(!options){
        return;
      }

      options.unshift({
        text:&apos;选择业务&apos;,
        value:&apos;&apos;
      });

      return (
          &amp;lt;div className=&quot;fl pure-form select-pagebranch&quot; key={index}&amp;gt;
            &amp;lt;SelectItem label=&quot;&quot; value={this.props.searchPattern.pageBranch[index+1]||&apos;&apos;} 
              options={options} onSelectChange={this.handleSelectChange.bind(this,&apos;pageBranch&apos;,index+1)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;
        )
      
    }.bind(this))

    return (
      &amp;lt;div className=&quot;fl&quot;&amp;gt;
          &amp;lt;div className=&quot;fl pure-form select-pagebranch&quot;&amp;gt;
            &amp;lt;SelectItem label=&quot;&quot; value={this.props.searchPattern.pageBranch[0]} 
              options={firOptions} 
              onSelectChange={this.handleSelectChange.bind(this,&apos;pageBranch&apos;,0)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;
          {GroupSelect}
      &amp;lt;/div&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Action中的操作，查找基本靠deepIndex中的索引。这里更变态，由于层级不确定，非常不方便把请求拉取到的select列表内容添加到state中，借用了对象是引用模式，将层级根据deep深度一层层拨开添加到childBranch中，数组childBranch的第一项是总树state.branch，第二项是选中的第一个select内容及其子树，第二项是选中的第二个select内容及其子树。直到当前select的最后一项也就是childBranch的最后一个值，然后获取他的子支内容后添加到他的child属性上，由于对象的赋值是引用复制，改变childBranch的最后一个值的内容也就是改变了第一项的内容。再将新的branch传给reducer更新状态。这变态方法无可避免的改变了原state，因为利用了对象引用赋值的特性。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//deep:当前操作的select层级
export function getGroups(deep){
  return (dispatch, getState) =&amp;gt; {
    let state = getState(),
        deepIndex = state.deepIndex,
        childBranch = [state.branches.slice()],
        resBranch =  Object.assign({},state.branches);
    
    // 取根节点
    if(deep==-1){
      resBranch = [{
        name:&apos;团购&apos;
      },{
        name:&apos;女装&apos;
      }];
    }else{
      for(var i = 0 ; i &amp;lt;= deep ; i++){
        if(i==0){
          childBranch.push(childBranch[i][deepIndex[i]]);
        }else{
          childBranch.push(childBranch[i].child[deepIndex[i]]);
        }
      }

      if(childBranch[deep+1].child&amp;amp;&amp;amp;childBranch[deep+1].child.length){

      }else{
        //获取数据data = [{name:&apos;裙子&apos;},{name:&apos;T恤&apos;}]
        //正式
        //childBranch[deep+1].child = data;
      }
      resBranch = childBranch[0];
    }

    return dispatch({
      type:types.GET_GROUPS,
      branches:resBranch
    })
  }
}

export function changeSelect(deep,valueIndex) {
  return {
    type:types.CHANGE_SELECT,
    deep:deep,
    valueIndex:valueIndex
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;扁平结构&quot;&gt;扁平结构&lt;/h3&gt;

&lt;p&gt;数据设计，每个层级都是个单独的对象数组，通过parentId相关联。虽然一眼看上去没有树状结构那么清晰，但是好管理很多。由于branches现在是个对象，所以deepIds存的是键值，这里用了数字键值是为了好累加和操作。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const initialState = {

    searchPattern:{
        keyword: &quot;&quot;,
        group:[&apos;&apos;] 
    },
    branches:{
        0:[{
            id:&apos;001&apos;,
            name:&apos;团购&apos;
        },{
            id:&apos;002&apos;,
            name:&apos;女装&apos;
        }],
        1:[{
            id:&apos;011&apos;,
            parentId:&apos;001&apos;,
            name:&apos;剁手团&apos;
        },{
            id:&apos;012&apos;,
            parentId:&apos;001&apos;,
            name:&apos;大牌&apos;
        },{
            id:&apos;013&apos;,
            parentId:&apos;002&apos;,
            name:&apos;春装&apos;
        },{
            id:&apos;014&apos;,
            parentId:&apos;002&apos;,
            name:&apos;夏装&apos;
        }],
        3:[{
            id:&apos;101&apos;,
            parentId:&apos;011&apos;,
            name:&apos;子分支1&apos;
        },{
            id:&apos;102&apos;,
            parentId:&apos;011&apos;,
            name:&apos;子分支2&apos;
        },{
            id:&apos;101&apos;,
            parentId:&apos;013&apos;,
            name:&apos;针织衫&apos;
        },{
            id:&apos;102&apos;,
            parentId:&apos;013&apos;,
            name:&apos;毛衣&apos;
        },{
            id:&apos;103&apos;,
            parentId:&apos;014&apos;,
            name:&apos;裙子&apos;,
        },{
            id:&apos;104&apos;,
            parentId:&apos;014&apos;,
            name:&apos;T恤&apos;,
        }]
    },

    deepIds:[&apos;&apos;]   //记录当前选中了的树枝层级的id
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;reducer中state转换：&lt;/p&gt;

 	case ActionTypes.GET_GROUPS:
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    return Object.assign({},state,{
        branches: action.branches
    });

case ActionTypes.CHANGE_SELECT:
    let newDeepIds = state.deepIds.concat();
    let deepLen = newDeepIds.length;
    if(action.deep != deepLen-1){
        for(let i=deepLen-1;i&amp;gt;action.deep;i--){
            newDeepIds.pop();
        }
    }
    newDeepIds[action.deep] = action.id;
    
    return Object.assign({},state,{
        deepIds:newDeepIds
    });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;View中，依旧是遍历deepIds，通过parentId查找branches[deep+1]中是当前select选中状态内容的子支，保存到tempChildBranch中，再渲染页面。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default class GroupSelectView extends Component {
  constructor() {
    super();
  }
 
  handleSelectChange(deep,oneBranch,value,index){
    var id = oneBranch[index-1].id;//第一个为提示语
    this.props.dispatch(changeSelect(deep,id));
    this.props.dispatch(getGroups(deep,id));

    let newBranch = this.props.searchPattern.group.slice();

    let deepLen = newBranch.length;
    if(deep != deepLen-1){
        for(let i=deepLen-1;i&amp;gt;deep;i--){
            newBranch.pop();
        }
    }
    newBranch[deep] = value;
    let newPattern = Object.assign({},this.props.searchPattern,{
      group:newBranch
    })
    this.props.dispatch(changeSearchPattern(newPattern));
  }

  render() {
    let tempBranch = Object.assign({},this.props.branches);
    let tempChildBranch = [];
    let firOptions = tempBranch[0].map(function(firBranchItem){
      return {
          text:firBranchItem.name,
          value:firBranchItem.name
        }
    });
    firOptions.unshift({
      text:&apos;选择业务&apos;,
      value:&apos;&apos;
    });

    let GroupSelect = this.props.deepIds.map(function(parentId,index){
      
      tempChildBranch = tempBranch[index+1] &amp;amp;&amp;amp; tempBranch[index+1].slice().filter(function(item){
        return item.parentId == parentId;
      })
    
      if(tempChildBranch&amp;amp;&amp;amp;tempChildBranch.length==0){
        return;
      }

      let options = tempChildBranch&amp;amp;&amp;amp;tempChildBranch.length&amp;amp;&amp;amp;tempChildBranch.map(function(branchItem){
        return {
          text:branchItem.name,
          value:branchItem.name
        }
      })

      if(!options){
        return;
      }

      options.unshift({
        text:&apos;选择业务&apos;,
        value:&apos;&apos;
      });

      return (
          &amp;lt;div className=&quot;fl pure-form select-branch&quot; key={index}&amp;gt;
            &amp;lt;SelectItem label=&quot;&quot; value={this.props.searchPattern.group[index+1]||&apos;&apos;} 
              options={options} 
              onSelectChange={this.handleSelectChange.bind(this,index+1,tempChildBranch)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;
        )
    }.bind(this))

    return (
      &amp;lt;div className=&quot;fl group-select-wrap&quot;&amp;gt;
           &amp;lt;div className=&quot;fl pure-form select-branch&quot;&amp;gt;
            &amp;lt;SelectItem label=&quot;&quot; value={this.props.searchPattern.group[0]||&apos;&apos;} 
              options={firOptions} 
              onSelectChange={this.handleSelectChange.bind(this,0,tempBranch[0])}
            /&amp;gt;
          &amp;lt;/div&amp;gt;

          {GroupSelect}
      &amp;lt;/div&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Action,由于每个层级的树枝select内容都是一个单独的对象数组，且branches是数字键值，通过代表层级深度的deep很容易获得到下一级树枝state.branches[deep+1]，需要获取的数据也是往这一层的对象数组里添加，注意的就是用深拷贝方式不要修改原数组。如果有需要更新，更新的都是下一层级&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;branches[deep+1]&lt;/code&gt;的内容。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export function getGroups(deep,parentId){
  return (dispatch, getState) =&amp;gt; {
    let state = getState(),
        resBranch =  {},
        childBranch = state.branches[deep+1],
        newBranchChild = [],
        isNew = true,
        testDate = [];
     
    // 取根节点
    if(parentId==null){
    	//根节点特殊处理，测试数据
      resBranch = {
        0:[{
            id:&apos;001&apos;,
            name:&apos;团购&apos;
        },{
            id:&apos;002&apos;,
            name:&apos;女装&apos;
        }]
      };
    }else{
      childBranch&amp;amp;&amp;amp;childBranch.map(function(item){
        if(item.parentId==parentId){
          isNew = false;
        }
      });

      if(isNew){
        // get data from server
        //获取数据testData = [{
                id:&apos;013&apos;,
                parentId:&apos;002&apos;,
                name:&apos;春装&apos;
            },{
                id:&apos;014&apos;,
                parentId:&apos;002&apos;,
                name:&apos;夏装&apos;
            }];
                   
        if(childBranch&amp;amp;&amp;amp;childBranch.length){
          newBranchChild = [...childBranch,
                            ...testDate
                          ];
        }else{
          newBranchChild = testDate;
        }
        
        resBranch = Object.assign({},state.branches,{
          [deep+1]:newBranchChild
        })
      }else{
        return;
      }
    }

    return dispatch({
      type:types.GET_GROUPS,
      branches:resBranch
    })
  }
}

export function changeSelect(deep,id) {
  return {
    type:types.CHANGE_SELECT,
    deep:deep,
    id:id
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;体验&quot;&gt;体验&lt;/h3&gt;

&lt;p&gt;之前的嵌套形似为了完成这个需求用了变态的对象引用一改都改的奇技淫巧，并不是十分的科学，我没有想到科学的方式。使用扁平化设计后操作变得简单清晰多了，而且扁平化设计完成功能的时间只有嵌套方式的1/5，不到一小时。嵌套太绕了，容易把自己弄晕。&lt;/p&gt;

&lt;h2 id=&quot;安利&quot;&gt;安利&lt;/h2&gt;

&lt;p&gt;虽然扁平化方式的数据设计看上去数据关系没有那么清晰，但是相信我，如果你的数据关系够复杂，嵌套层级够深，扁平化设计绝对让你在增查改删的操作上大占优势。&lt;/p&gt;

&lt;p&gt;然后推荐个repo工具，我第一次看到扁平化数据设计在前端的运用是通过这个repo的，虽然看了一眼觉得其实用的就是关联数据表的思想，而且自己设计的好，完全可以抛开这个库自己实现，但是对于处理自己没办法掌握的接口数据时确实这个库能带来便利。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/gaearon/normalizr&quot;&gt;normalizr&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>写个小轮解决繁琐的touch事件</title>
   <link href="http://echizen.github.io/tech/2016/04-10-touch-init"/>
   <updated>2016-04-10T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/touch-init</id>
   <content type="html">
&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;

&lt;p&gt;对于touch我的内心是抵触的，一方面是因为太灵敏，常导致误操作，另一方面是太繁琐了，为了模仿scroll，要算坐标，进行一群操作。&lt;/p&gt;

&lt;p&gt;所以能用浏览器原生scroll的，我是绝对不用touch。&lt;/p&gt;

&lt;p&gt;但是最近接了个需求，要做拖拽效果的滚动的导航，不得不上touch家族，先辈们弄了个超级大的组件（2300多行），然后我的需求现有组件实现不了，要改动，改时实在是牵一发动全身，还是自己折腾个吧，不过只能和拒绝多时的touch握手了。&lt;/p&gt;

&lt;p&gt;写完之后还想吐槽，touch系列真的是太麻烦了，所以还是抽成一个类，方便以后沿用吧。&lt;/p&gt;

&lt;h1 id=&quot;代码&quot;&gt;代码&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/echizen/js-littleClass/blob/master/touchInit/touchInit.js&quot;&gt;戳我，我是代码-touchInit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;好像也没啥可解释的，就说一下一些收获和注意事项吧。&lt;/p&gt;

&lt;h3 id=&quot;touchmove和touchend的绑定时间&quot;&gt;touchmove和touchend的绑定时间&lt;/h3&gt;

&lt;p&gt;一直监听touch事件是很耗性能的。（但是不停的绑事件和删事件也挺耗性能的）。&lt;/p&gt;

&lt;p&gt;如果touchstart发生后，touchend发生前,touch过的元素有变更，譬如被删除，想要的滚动效果会发生错乱。&lt;/p&gt;

&lt;p&gt;所以一般是在一开始就绑定touchstart事件，但是在touchstart里绑定touchmove和touchend事件，在touchend事件里删除掉touchmove和touchend事件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   _onStart: function(event) {

      event = event.originalEvent || event;

      addEventListener(doc.body, moveEvent, this._onMove);
      addEventListener(doc.body, endEvent, this._onEnd);

      this.startCoords = getCoords(event); // for performance , store start coords

    },

    _onMove: function( event ) {
      var distance = {
        x: 0,
        y: 0
      };

      event = event.originalEvent || event;

      // ensure swiping with one touch and not pinching
      if ( event.touches &amp;amp;&amp;amp; event.touches.length &amp;gt; 1 || event.scale &amp;amp;&amp;amp; event.scale !== 1) return;

      event.preventDefault();

      distance = this._getDistance(event);
      this.coordinates = this._getCoordinate(distance.direction, distance.x, distance.y);
      this._scroll( this.coordinates );
       
      this.onTouchMove(); 
    },

    _onEnd: function( event ) {

      event = event.originalEvent || event;

      this.startCoords = { x: 0, y: 0 };

      this._resetScrollBorder();

      this._scroll( this.scrollBorder );
      
      // do what you want to do
      this.onTouchEnd();

      removeEventListener(doc.body, moveEvent, this._onMove);
      removeEventListener(doc.body, endEvent, this._onEnd);

    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;touch实现滚动的边界处理&quot;&gt;touch实现滚动的边界处理&lt;/h3&gt;

&lt;p&gt;因为touch实现滚动的原理，我们会记录每次touch事件结束后的元素左上角坐标值scrollBorder，再在touchmove发生时通过event对象下的坐标值计算和touchstart时的坐标值的差值，将之前的scrollBorder加上这个差值，得出新的元素左上角位置值，再让作用区域通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translateX&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translateY&lt;/code&gt;变换成相应的值。每次touchend发生后更新scrollBorder的值。&lt;/p&gt;

&lt;p&gt;因为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translateX&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translateY&lt;/code&gt;可以接收你滚动距离的任何大的值，这就导致没有滚到底的概念，能一直滚下去。所以我在配置中让传递border的边界宽高（默认为窗口screen宽高），在touchmove和touchend中做处理：判断当前坐标是否已经导致目标区域完全滚出了边界区域，如果是，减缓滚动速度，其实就是减少&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translateX&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translateY&lt;/code&gt;的值，我将真实差值/5，再在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touchend&lt;/code&gt;时将元素的scrollBorder重置为区域边界值。&lt;/p&gt;

&lt;p&gt;touchmove获取坐标值时对边界的处理：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; _getCoordinate: function(direction, x, y ) {
      var coordinates = {
        x: x,
        y: y
      };

      switch ( direction ) {

        case &quot;right&quot;:
          if ( this.scrollBorder.x&amp;gt;=0 ) {
            coordinates.x = Math.round((x - this.scrollBorder.x) / 5 );
            return coordinates;
          }
          break;

        case &quot;left&quot;:
          // scroll after right border,divide by 5 is to slow speed
          if ( this.container.offsetWidth - this.wrapWidth &amp;lt;= Math.abs(this.scrollBorder.x) ) {
            coordinates.x = Math.round( -(this.container.offsetWidth - this.wrapWidth) + x / 2 );
            return coordinates;
          }
          break;

        case &quot;down&quot;:
          if ( this.scrollBorder.y &amp;gt;= 0 ) {
            coordinates.y = Math.round( (y - this.scrollBorder.y) / 5 );
            return coordinates;
          }
          break;

        case &quot;up&quot;:
          if ( this.container.offsetHeight- this.wrapHeight &amp;lt;= Math.abs(this.scrollBorder.y) ) {
            coordinates.y = Math.round( -(this.container.offsetHeight - this.wrapHeight) + y / 5 );
            return coordinates;
          }
        break;
      }

      return {
        x: this.scrollBorder.x + x,
        y: this.scrollBorder.y + y
      };
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;touchend对边界的处理：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_resetScrollBorder: function(){
      var coordinates = this.coordinates;

      if(coordinates.x&amp;gt;0){
        this.scrollBorder.x = 0;
      }else if (-coordinates.x &amp;gt;= this.container.offsetWidth - this.wrapWidth){
        this.scrollBorder.x = -(this.container.offsetWidth - this.wrapWidth);
      }else{
        this.scrollBorder.x = this.coordinates.x;
      }

      if(coordinates.y&amp;gt;0){
        this.scrollBorder.y = 0;
      }else if (-coordinates.y &amp;gt;= this.container.offsetHeight - this.wrapHeight){
        this.scrollBorder.y = -(this.container.offsetHeight - this.wrapHeight);
      }else{
        this.scrollBorder.y = this.coordinates.y;
      }
      
   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;touch存在时导致的click点透&quot;&gt;touch存在时导致的click点透&lt;/h3&gt;

&lt;p&gt;我遇到2种场景，一种是用touch做拖拽效果的滚动会有动画效果的延时，只要保证动画设置时间不小于300ms就不会影响click。
   一种是用touch做自然滚动时，这时滚动没有延时很快，所以我去掉了click事件，在touch中判断又没有发生touchMove并且move的值是否超过一定值来决定是执行touch的滚动效果，还是click效果。&lt;/p&gt;

&lt;p&gt;touchMove中：&lt;/p&gt;

 		// 滚动或者点击的断定
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      if(this.config.direction == &apos;horizontal&apos;){
        this.ismove = Math.abs(distance.x) &amp;gt; 10?true:false;
      }else{
        this.ismove = Math.abs(distance.y) &amp;gt; 10?true:false;
      } 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;touchEnd中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	  if(this.ismove){
        this._resetScrollBorder();
        this._scroll( this.scrollBorder );
        this.onTouchEnd();
      }else{
        this.onClick(event.target);
      }
      this.ismove = false;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;this的绑定&quot;&gt;this的绑定&lt;/h3&gt;

&lt;p&gt;是否原型链里处处&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bind(this)&lt;/code&gt;显得很繁琐呢，不如把这些操作都扔到一个函数里吧，然后在构造器中调用一下。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  function proxy( fn, context ) {

    return function() {
      return fn.apply( context, Array.prototype.slice.call(arguments) );
    };

  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实在调用时直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call(this)&lt;/code&gt;也可以。&lt;/p&gt;

&lt;h3 id=&quot;扩展对象&quot;&gt;扩展对象&lt;/h3&gt;

&lt;p&gt;如果没有jquery，改怎么替代&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$.extend&lt;/code&gt;呢？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  function extend( destination, source ) {

    var property;

    for ( property in source ) {
      destination[property] = source[property];
    }

    return destination;

  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;为了兼容性做好降级&quot;&gt;为了兼容性，做好降级&lt;/h3&gt;

&lt;p&gt;譬如是否支持touch，是否支持动画。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var doc = document,
  win = window;
  
 var isTouch = &apos;ontouchstart&apos; in win,
  startEvent = isTouch ? &apos;touchstart&apos; : &apos;mousedown&apos;,
  moveEvent = isTouch ? &apos;touchmove&apos; : &apos;mousemove&apos;,
  endEvent = isTouch ? &apos;touchend&apos; : &apos;mouseup&apos;,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;根据判断结果选择不同的处理。&lt;/p&gt;

  	this._scroll = supportTransform ? this._scrollWithTransform : 	this._scrollWithoutTransform;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  function getCoords(event) {
    // touch move and touch end have different touch data
    var touches = event.touches,
        data = touches &amp;amp;&amp;amp; touches.length ? touches : event.changedTouches;

    return {
      x: isTouch ? data[0].pageX : event.pageX,
      y: isTouch ? data[0].pageY : event.pageY
    };
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;尾声&quot;&gt;尾声&lt;/h3&gt;

&lt;p&gt;好水啊,写不下去了…就是个简易的touch事件通用化操作，需要时拉过去用就好。宝宝再也不怕touch模拟滚动了&lt;/p&gt;

&lt;p&gt;然后会把拖拽效果的滚动条代码和踩坑经历也放出来，地址占坑：&lt;a href=&quot;https://github.com/echizen/dragNav&quot;&gt;https://github.com/echizen/dragNav&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>使用requestAnimationFrame提高性能</title>
   <link href="http://echizen.github.io/tech/2016/03-26-requestanimationframe"/>
   <updated>2016-03-26T23:12:43+00:00</updated>
   <id>http://echizen.github.io/tech/2016/requestanimationframe</id>
   <content type="html">
&lt;h2 id=&quot;requestanimationframe是干什么的&quot;&gt;requestAnimationFrame是干什么的&lt;/h2&gt;

&lt;p&gt;根据MDN的介绍，window.requestAnimationFrame()这个方法是用来在页面重绘之前，通知浏览器调用一个指定的函数，以满足开发者操作动画的需求。如果你想做逐帧动画的时候，你应该用这个方法。这就要求你的动画函数执行会先于浏览器重绘动作。通常来说，被调用的频率是每秒60次，但是一般会遵循W3C标准规定的频率。&lt;/p&gt;

&lt;h2 id=&quot;为什么使用requestanimationframe&quot;&gt;为什么使用requestAnimationFrame&lt;/h2&gt;

&lt;p&gt;如果你有连续的动画效果需要实现，不使用requestAnimationFrame会有卡顿出现。requestAnimationFrame能在浏览器在页面重绘之前执行你的动画函数，这样你的动画每秒渲染60次，不会有视觉上的卡顿出现。&lt;/p&gt;

&lt;p&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestAnimationFrame&lt;/code&gt;出现之前，大家都用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setTimeOut&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setInterval&lt;/code&gt;，但是这2个函数受到js引擎时间处理器的影响，如果时间到了，但是执行栈中还有队列排在动画函数之前，动画函数依旧不能执行，而每个间隔执行栈中的队列是不确定的也是程序员未知被浏览器管理的，所以这个很难保证动画连贯。&lt;/p&gt;

&lt;p&gt;requestAnimationFrame不一样，使用它是将动画执行时间交给了浏览器，浏览器管理系统能够保证每帧都执行传递的函数参数。&lt;/p&gt;

&lt;h2 id=&quot;使用&quot;&gt;使用&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;requestAnimationFrame(function(){
     // write you code
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;举个&quot;&gt;举个🌰&lt;/h2&gt;
&lt;p&gt;之前写过一个功能就是通过touch事件模拟滚动，这时候当监听到touchMove到一定范围后，通过translateX来改变元素位置模拟滚动。因为touchMove持续被触发，所以改变translateX的函数持续被触发，要的是一个连贯的滚动动画效果。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;requestAnimationFrame(function(){
     $(element).css(styles);
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不使用requestAnimationFrame：
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/requestAnimationFrame1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;使用requestAnimationFrame：
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/requestAnimationFrame2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过chrome timeLine的分析可以看到，红色长帧明显减少，性能提升明显。&lt;/p&gt;

&lt;p&gt;关于chrome timeLine的小tips：
分析导致红色长帧的代码是，从调用栈最顶层看起，如果使用的是别人的库，一般都是顶层的调用语句有问题，左键右键切换移动每一个函数调用位置查看语句定位。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/requestAnimationFrame3.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>chrome timeline的简单使用</title>
   <link href="http://echizen.github.io/tech/2016/03-20-chrome-timeline"/>
   <updated>2016-03-20T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/chrome-timeline</id>
   <content type="html">
&lt;p&gt;非常遗憾，折腾了一个双休日的chrome timeline,还是没玩溜，不想再继续研究了，先把所了解的记录下来。其实在chrome控制台那么小的区域里展示了那么多张图标还是让人眼花缭乱的，而且事件进度条层层叠叠也不是很直观，在一个比较大的文件里还是挺难定位到性能问题究竟出在那些脚本上。&lt;/p&gt;

&lt;p&gt;（附：本来想用测试一下字符串的拼接方式谁更高效，1）字符串直接用连接符『+』连接。2）使用数组&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[].join(&apos;&apos;)&lt;/code&gt;的方式。循环拼接了一段长长的dom字符串，大家timeline后发现只有scrpting的时间区别，例子不具有代表性。不过结论是，chrome下，方式1比方式2确实更高效，方式1是40多毫秒，方式2是60多毫秒）&lt;/p&gt;

&lt;p&gt;基本用法可以查看谷歌开发文档。&lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/timeline-tool?hl=en&quot;&gt;https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/timeline-tool?hl=en&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;友情提醒：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 在启用timeline之前做好使用隐身模式并保证所以扩展程序和插件不可用，避免受干扰。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 每次timeline的结果数据稍有差别，如果需要准确点的值，需要多观察几次求平均值&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;overview-panel---找到有问题的帧&quot;&gt;overview panel - 找到有问题的帧&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个panel包含的信息有帧信息（FPS）、内存信息（CPU）、网络信息（NET）。&lt;/p&gt;

&lt;p&gt;注意FPS有红色长条的区域，那是有性能问题的地方，你可以逐一分析每个红色长条提示的性能问题，点击该区域，拖拽激活区域以缩放或扩大，将性能分析定位到这个红色长条上，然后再在底下的面板中查看详细信息。&lt;/p&gt;

&lt;p&gt;CPU我还不会看如何体现是有性能问题的地方。。。net一栏可以从network那边获得更直观详细的信息。&lt;/p&gt;

&lt;h1 id=&quot;flame-chart-panel---定位到导致性能问题的调用过程&quot;&gt;Flame Chart panel - 定位到导致性能问题的调用过程&lt;/h1&gt;

&lt;p&gt;CPU 堆栈调用追踪区域。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最上面一行是每个任务集合执行所消耗的时间，如果某个集合消耗的的时间过长（&amp;gt;16.6ms）导致性能问题，左上角会有红色三角形警告，summary也会提示帧长度过长导致页面卡顿。&lt;/p&gt;

&lt;p&gt;下方还有n多平行的长条，有性能问题的事件右上角依旧有红色三角形，具体的问题提示可以在summary看到。&lt;/p&gt;

&lt;p&gt;譬如我打开的这个页面的性能问题是每次都发生了recalculate style，然后layout，且重复多次。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-3.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-4.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;details-panel&quot;&gt;Details panel&lt;/h1&gt;

&lt;p&gt;detail面板针对不同的事件有不同的tab，一般都有summary、Bottom-Up、call-tree、Event-Log。如果你点击的是Flame Chart每帧所耗时间的区域条，面板里还会出现layers选项。&lt;/p&gt;

&lt;h2 id=&quot;summary---饼形图展示各阶段耗时情况&quot;&gt;summary - 饼形图展示各阶段耗时情况&lt;/h2&gt;

&lt;p&gt;展示了你所选中的区域各部分工作（loading、scripting、rendering、painting、other、idle中的几项）消耗时间的饼形图。&lt;/p&gt;

&lt;p&gt;对于js函数，他还能显示函数调用链方便定位有问题的脚本行数，如上面那张layer阶段导致重绘的call stacks:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-5.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;bottom-up---找到最耗时的时间&quot;&gt;Bottom-Up - 找到最耗时的时间&lt;/h2&gt;

&lt;p&gt;主要是时间分析，每个渲染阶段耗时，每个事件的耗时。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-6.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;call-tree&quot;&gt;call-tree&lt;/h2&gt;

&lt;p&gt;详细列出了每个事件调用链中各个事件的耗时情况。&lt;/p&gt;

&lt;h2 id=&quot;event-log&quot;&gt;event-log&lt;/h2&gt;

&lt;p&gt;展示所有阶段包括loading、javascripting、rendering、painting中各事件的耗时情况，并提供了filter输入框和按钮供你快速过滤查找你感兴趣的东西。&lt;/p&gt;

&lt;h2 id=&quot;layers&quot;&gt;layers&lt;/h2&gt;

&lt;p&gt;比较有趣的layers，你可以看到真实的没帧执行后浏览器页面的样子，这是个实时截图，虽然调试时你能看到js执行顺序，但是你看不到浏览器运行你的js指令和自己的指令和整合的效果。这里可以让你感受一下，你可以一帧帧的点进去看，也能非常直观的反映出渲染慢在哪个区域。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-7.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;console-rendering-panel&quot;&gt;console rendering panel&lt;/h1&gt;

&lt;p&gt;EST键调起console面板，点击面板左侧竖的”…“选出展示rendering面板，然后打开rendering面板，可以看到可展示的选项有：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Enable paint flashing：开启显示重绘区域的功能，重绘区域将以绿色高亮显示（如下图中倒计时区域就在不停的随着刷新重绘）&lt;/li&gt;
  &lt;li&gt;show layer borders：显示渲染层，以矩形框显示了每个渲染层的边界&lt;/li&gt;
  &lt;li&gt;show FPS meter：动态展示及时的帧数&lt;/li&gt;
  &lt;li&gt;show scrolling perf issues：能够显示滚动时监听的的事件类型&lt;/li&gt;
  &lt;li&gt;emulate print media&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-8.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;刚刚逮到个鲜活的案例，有个抽奖的动画效果，在iphone6p ios 8.4 下频繁导致crash，而且一打开页面就会crash。&lt;/p&gt;

&lt;p&gt;看代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 『点击换新装』按钮的动画
.change_new_clothing .cnc_btn img {
  width: 100%;
  height: auto ;
  -webkit-animation: btnzoom 1s linear infinite alternate ;
          animation: btnzoom 1s linear infinite alternate ;
}
@-webkit-keyframes btnzoom {
  from {
    -webkit-transform: scale3d(1, 1, 1);
            transform: scale3d(1, 1, 1);
  }
  to {
    -webkit-transform: scale3d(0.9, 0.9, 1);
            transform: scale3d(0.9, 0.9, 1);
  }
}
@keyframes btnzoom {
  from {
    -webkit-transform: scale3d(1, 1, 1);
            transform: scale3d(1, 1, 1);
  }
  to {
    -webkit-transform: scale3d(0.9, 0.9, 1);
            transform: scale3d(0.9, 0.9, 1);
  }
}

// 『换新装』图片的动画

.change_new_clothing .cnc_clothing .new_clothing {
  -webkit-transform: rotateZ(-180deg);
      -ms-transform: rotateZ(-180deg);
          transform: rotateZ(-180deg);
}
.change_new_clothing .cnc_clothing .move-out {
  -webkit-animation: rotationout 1s forwards ;
          animation: rotationout 1s forwards ;
  -webkit-transform-origin: bottom ;
      -ms-transform-origin: bottom ;
          transform-origin: bottom ;
}
@-webkit-keyframes rotationout {
  from {
    -webkit-transform: rotateZ(0deg);
            transform: rotateZ(0deg);
    display: block ;
  }
  to {
    -webkit-transform: rotateZ(-180deg);
            transform: rotateZ(-180deg);
    display: block ;
  }
}
@keyframes rotationout {
  from {
    -webkit-transform: rotateZ(0deg);
            transform: rotateZ(0deg);
    display: block ;
  }
  to {
    -webkit-transform: rotateZ(-180deg);
            transform: rotateZ(-180deg);
    display: block ;
  }
}
.MCUBE_MOD_ID_7084 .change_new_clothing .cnc_clothing .move-in {
  -webkit-animation: rotationin 1s forwards ;
          animation: rotationin 1s forwards ;
  -webkit-transform-origin: bottom ;
      -ms-transform-origin: bottom ;
          transform-origin: bottom ;
}
@-webkit-keyframes rotationin {
  from {
    -webkit-transform: rotateZ(180deg);
            transform: rotateZ(180deg);
    display: block ;
  }
  to {
    -webkit-transform: rotateZ(0deg);
            transform: rotateZ(0deg);
    display: block ;
  }
}
@keyframes rotationin {
  from {
    -webkit-transform: rotateZ(180deg);
            transform: rotateZ(180deg);
    display: block ;
  }
  to {
    -webkit-transform: rotateZ(0deg);
            transform: rotateZ(0deg);
    display: block ;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;光看代码会发现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform&lt;/code&gt;中加了很多&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rotateZ&lt;/code&gt;，这样每一个都会创建一个渲染层，会导致cpu增长。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scale3d&lt;/code&gt;调用了GPU硬件加速，配合&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;animation&lt;/code&gt;也很消耗性能。其实在现代浏览器中都没问题，但是在iphone6plus 8.3中还是会导致crash，去掉其中一个dom的动画效果，都还是会crash。可能iphone6p 屏幕大GPU、CPU消耗的更厉害，而8.3版本太低，软硬件不是很匹配，webview中的内存管理的不是很好，便导致了这个crash的发生。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-9.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;截了张rendering的图，可以看出帧数饱和频率很高，持续在60fps，导致动画会有卡顿【解释错误！！！频率越高动画越流畅，啪啪啪，打脸】。『点击换新装』按钮独自占据着一个渲染层。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160321-11.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;也截了张页面加载时timeline的图，1000ms-1800ms之间，CPU陡增后居高不下，1800ms之后fps稳定在一个很高的值，这都是性能不太好的表现。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/20160320-10.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当我们把按钮动画效果除去，把所有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rotateZ&lt;/code&gt;转换为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rotate&lt;/code&gt;以避免创建新的渲染层，之后，fps下降了很多【这并不表示性能好了，再次打脸！】，也没有再出现crash。（但是我并没有发现CPU和GPU下降，不知道怎么解释）。最终解决方案就是针对版本降级动画方案。&lt;/p&gt;

&lt;h1 id=&quot;something-else&quot;&gt;something else&lt;/h1&gt;

&lt;p&gt;我是因为好奇才研究了这些功能，但是在日常大多数情况下，通过修改脚本对程序性能的改善不大。现代浏览器内存管理优化都很好，性能都很好足以撑住；浏览器引擎自己对我们的脚本会有优化，我们看到的脚本和他真正执行的脚本并不是完全一样，引擎会自己转化为性能好的脚本；脚本再耗时，也比不上网络io的耗时，两者差的是百万级别，所以优化网站第一步还是尽量用文件压缩合并等来减少网络请求和加速数据返回时间。优化网络请求带来的成效更大。&lt;/p&gt;

&lt;p&gt;但是毕竟移动端和老的ie浏览器没那么强大，所以还是会出现性能问题，当我们的页面出现问题时，能够通过chrome timeline系列工具发现问题所在或者提前检测问题发生的可能性，不至于出了问题后手足无措。&lt;/p&gt;

&lt;h2 id=&quot;疑惑&quot;&gt;疑惑&lt;/h2&gt;

&lt;p&gt;CPU状况不会看，引入了jquery等大型库后，函数调用连太长，不能很快定位到出问题的脚本在那里。&lt;/p&gt;

&lt;h1 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/timeline-tool?hl=en&quot;&gt;How to Look at Performance&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>从浏览器渲染看页面性能优化</title>
   <link href="http://echizen.github.io/tech/2016/03-18-page-performance"/>
   <updated>2016-03-18T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/page-performance</id>
   <content type="html">
&lt;p&gt;经常看到高性能的js该怎么怎么写，各种建议，却不知道如何证实这些说法，如何观测自己的js,css是否高效。遇到页面渲染缓慢（其实现代的chrome下也感觉不到慢，但是ie和手机上对于页面渲染速度就有感受了），也不知道问题出在哪一环节。于是这周看了一下chrome 的timeline使用，顺便了解了一些与性能相关的东西。&lt;/p&gt;

&lt;h1 id=&quot;性能感知-rail模型&quot;&gt;性能感知-RAIL模型&lt;/h1&gt;

&lt;p&gt;用户对页面性能的感知来源于四方面：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;对于用户操作的响应&lt;/li&gt;
  &lt;li&gt;动画效果&lt;/li&gt;
  &lt;li&gt;闲置时间&lt;/li&gt;
  &lt;li&gt;加载速度&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/rail.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;时间控制&quot;&gt;时间控制&lt;/h2&gt;

&lt;p&gt;为了让用户体验良好，对于一些时间节点需要了解。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Delay	User&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Reaction&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;0 - 16ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;屏幕更新每帧动画的时间（1000/60），不可能再16ms以内更新屏幕显示内容&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;0 - 100ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;用户操作响应敏感时间，必须在100ms以内给用户的操作以反馈，才能让用户感觉流畅&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;300 - 1000ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;加载页面或改变视图的可接受时间&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;原则&quot;&gt;原则&lt;/h2&gt;

&lt;h3 id=&quot;response-少于100ms&quot;&gt;response: 少于100ms&lt;/h3&gt;

&lt;h3 id=&quot;animation-每16ms更新frames&quot;&gt;Animation: 每16ms更新frames&lt;/h3&gt;

&lt;p&gt;页面渲染过程包括：javascript、style、layout、paint、composite&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/render-frame.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;触发JavaScript。一般来说，我们会使用JavaScript来实现一些视觉变化的效果。比如用jQuery的animate函数做一个动画、对一个数据集进行排序、或者往页面里添加一些DOM元素等。当然，除了JavaScript，还有其他一些常用方法也可以实现视觉变化效果，比如：CSS Animations, Transitions和Web Animation API。&lt;/li&gt;
  &lt;li&gt;计算样式。这个过程是根据CSS选择器，比如.headline或.nav &amp;gt; .nav_item，对每个DOM元素匹配对应的CSS样式。这一步结束之后，就确定了每个DOM元素上该应用什么CSS样式规则。&lt;/li&gt;
  &lt;li&gt;布局。上一步确定了每个DOM元素的样式规则，这一步就是具体计算每个DOM元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的，因此一个元素的布局发生变化，会联动地引发其他元素的布局发生变化。比如，&amp;lt;body&amp;gt;元素的宽度的变化会影响其子元素的宽度，其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说，布局过程是经常发生的。&lt;/li&gt;
  &lt;li&gt;绘制。本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等，也就是一个DOM元素所有的可视效果。一般来说，这个绘制过程是在多个层上完成的。&lt;/li&gt;
  &lt;li&gt;渲染层合并。由上一步可知，对页面中DOM元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后，浏览器会将所有层按照合理的顺序合并成一个图层，然后显示在屏幕上。对于有位置重叠的元素的页面，这个过程尤其重要，因为一旦图层的合并顺序出错，将会导致元素显示异常。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;每一帧动画都要经历以上5个过程。&lt;/p&gt;

&lt;p&gt;动画并不仅限与js和css动画，浏览器原生的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scroll&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touchmoves&lt;/code&gt;事件都会导致浏览器动画渲染。&lt;/p&gt;

&lt;p&gt;浏览器对每一帧画面的渲染工作需要在16毫秒（1秒 / 60 = 16.66毫秒）之内完成，由于浏览器在每帧动画执行期间都需要一些时间来执行自己的一些操作（比如渲染队列的管理，渲染线程与其他线程之间的切换等等），因此单纯的渲染工作要控制在10ms以内。&lt;/p&gt;

&lt;h3 id=&quot;idle好好利用&quot;&gt;idle：好好利用&lt;/h3&gt;

&lt;p&gt;可以利用空闲时间进行网络请求、异步操作。优化好的页面首屏数据都是很小的，接下来的数据都是异步获取后渲染。&lt;/p&gt;

&lt;p&gt;良好交互体验的做法还要控制每个异步操作在50ms以内完成，然后将优先处理用户操作应有的反馈。&lt;/p&gt;

&lt;h3 id=&quot;load数据传输要少于1000ms&quot;&gt;Load：数据传输要少于1000ms&lt;/h3&gt;

&lt;p&gt;如果有数据请求、接口响应的时间超过这个，需要告诉用户，譬如转个菊花告诉用户『正在加载中』。&lt;/p&gt;

&lt;h1 id=&quot;渲染性能&quot;&gt;渲染性能&lt;/h1&gt;

&lt;h2 id=&quot;渲染模式&quot;&gt;渲染模式&lt;/h2&gt;

&lt;p&gt;虽然理论上每一帧的渲染都要经历以上5个过程，但是对于我们js触发改变的css属性不同，其实这个过程并不是都会发生的。&lt;/p&gt;

&lt;h3 id=&quot;1javascript---style---layout---paint---composite&quot;&gt;1.javascript - style - layout - paint - composite&lt;/h3&gt;

&lt;p&gt;如果修改一个DOM元素的”layout”属性，也就是改变了元素的样式（比如宽度、高度或者位置等），那么浏览器会检查哪些元素需要重新布局，然后对页面激发一个reflow(重绘)过程完成重新布局。被reflow的元素，接下来也会激发绘制过程，最后激发渲染层合并过程，生成最后的画面。&lt;/p&gt;

&lt;h3 id=&quot;2javascript---style---paint---composite&quot;&gt;2.javascript - style - paint - composite&lt;/h3&gt;

&lt;p&gt;如果修改一个DOM元素的“paint only”属性，比如背景图片、文字颜色或阴影等，这些属性不会影响页面的布局，因此浏览器会在完成样式计算之后，跳过布局过程，只做绘制和渲染层合并过程。&lt;/p&gt;

&lt;h3 id=&quot;3javascript---style---composite&quot;&gt;3.javascript - style - composite&lt;/h3&gt;

&lt;p&gt;如果你修改一个非样式且非绘制的CSS属性，那么浏览器会在完成样式计算之后，跳过布局和绘制的过程，直接做渲染层合并。&lt;/p&gt;

&lt;p&gt;第三种方式在性能上是最理想的，对于动画和滚动这种负荷很重的渲染，我们要争取使用第三种渲染流程。&lt;/p&gt;

&lt;h2 id=&quot;javascript提升执行效率&quot;&gt;javascript:提升执行效率&lt;/h2&gt;

&lt;p&gt;页面里的动画效果大多是通过JavaScript触发的。有些是直接修改DOM元素样式属性而产生的，有些则是由数据计算而产生的，比如搜索或排序。错误的执行时机和太长的时间消耗，是常见的导致JavaScript性能低下的原因。&lt;/p&gt;

&lt;h3 id=&quot;使用requestanimationframe&quot;&gt;使用requestAnimationFrame&lt;/h3&gt;

&lt;p&gt;如果你想在动画刚刚发生的那一刻就运行一段JavaScript。那么能保证这个运行时机的，就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestAnimationFrame&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//
function updateScreen(time) {
  // 你要干的
}

requestAnimationFrame(updateScreen);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果你的js代码是要改变样式，制作动画效果，那么&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestAnimationFrame&lt;/code&gt;能保证你的代码在浏览器下一帧中触发，让你的动画流畅起来。&lt;/p&gt;

&lt;h3 id=&quot;耗时长的javascript代码使用web-workers&quot;&gt;耗时长的JavaScript代码使用Web Workers&lt;/h3&gt;

&lt;p&gt;JavaScript代码是运行在浏览器的主线程上的。与此同时，浏览器的主线程还负责样式计算、布局，甚至绘制（多数情况下）的工作。可以想象，如果JavaScript代码运行时间过长，就会阻塞主线程上其他的渲染工作，很可能就会导致帧丢失。&lt;/p&gt;

&lt;p&gt;因此，你需要认真规划一下你的JavaScript程序的运行时机和运行耗时。比如，如果你要在一个动画（比如页面滚动）执行过程中运行JavaScript程序，那么理想情况是把这段JavaScript程序的运行耗时控制在3-4毫秒以内。如果长于这个时间，那么就有帧丢失的风险。另一方面，在浏览器空闲的时候，你可以有更多时间来运行JavaScript程序。&lt;/p&gt;

&lt;p&gt;大多数情况下，你可以把纯计算工作放到Web Workers中做（如果这些计算工作不会涉及DOM元素的存取）。一般来说，JavaScript中的数据处理工作，比如排序或搜索，一般都适合这种处理方式。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var dataSortWorker = new Worker(&quot;sort-worker.js&quot;);
dataSortWorker.postMesssage(dataToSort);

// 现在主线程是空闲的可以用来干其他事

dataSortWorker.addEventListener(&apos;message&apos;, function(e) {
   var sortedData = e.data;
   // 一些操作，如更新页面中的数据
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;划分dom更新再多个frame中完成&quot;&gt;划分DOM更新，再多个frame中完成&lt;/h3&gt;

&lt;p&gt;由于Web Workers无法访问DOM元素。如果你的JavaScript代码需要存取DOM元素，也就是说必须在主线程上运行，那么可以考虑批处理的方式：把任务细分为若干个小任务，每个小任务耗时很少，各自放在一个requestAnimationFrame中回调运行。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) {
  var taskFinishTime;

  do {
   	var nextTask = taskList.pop();

    processTask(nextTask);

    // 获取本次任务的执行结束时间
    taskFinishTime = window.performance.now();
    // 如果这一帧中任务执行超过3ms，就将任务队列中的任务扔到下一帧中
  } while (taskFinishTime - taskStartTime &amp;lt; 3);

  if (taskList.length &amp;gt; 0)
    requestAnimationFrame(processTaskList);

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;layout尽可能避免触发&quot;&gt;layout：尽可能避免触发&lt;/h2&gt;

&lt;p&gt;对于DOM元素的“几何属性”的修改，比如width/height/left/top等，都需要重新计算布局。几乎所有的layout都是在整个文档范围内发生的。 如果页面中含有很多元素，那么计算这些元素的位置和维度的工作将耗费很长时间。&lt;/p&gt;

&lt;h3 id=&quot;使用flexbox布局&quot;&gt;使用flexbox布局&lt;/h3&gt;

&lt;p&gt;如果必须修改元素的位置和尺寸属性，必须触发layout，而设备允许的情况下，使用flexbox布局能改善性能。&lt;/p&gt;

&lt;p&gt;但是在任何情况下，不管是是否使用Flexbox，你都应该努力避免同时触发所有布局，特别在页面对性能敏感的时候（比如执行动画效果或页面滚动时）。&lt;/p&gt;

&lt;h3 id=&quot;减少需要执行样式计算的元素的个数&quot;&gt;减少需要执行样式计算的元素的个数&lt;/h3&gt;

&lt;p&gt;在过去，如果你修改了body元素的class属性，那么页面里所有元素都要重新计算样式。幸运的是，在某些现代的浏览器中不再这样做了。他们会对每个DOM元素维护一个独有的样式规则小集合，如果这个集合发生改变，才重新计算该元素的样式。也就是说，某个元素样式的改变不一定会导致对其他所有元素重新计算样式，得看这个元素在DOM树中的位置、具体是什么样式发生改变。&lt;/p&gt;

&lt;p&gt;虽然现代浏览器没那么傻了，样式计算是直接对那些目标元素执行，而不是对整个页面执行。但是如果你考虑兼容性要照顾上一辈浏览器，还是尽量最小区域内改变样式。&lt;/p&gt;

&lt;p&gt;以BEM (Block, Element, Modifier)的方式编写CSS代码，能达到最好的样式计算的性能。然而在没有web Components化由多人协同开发的项目中很容易导致样式冲突。&lt;/p&gt;

&lt;h3 id=&quot;避免强制同步布局事件&quot;&gt;避免强制同步布局事件&lt;/h3&gt;

&lt;p&gt;在JavaScript脚本运行的时候，它能获取到的元素样式属性值都是上一帧画面的，都是旧的值。因此，如果你想在这一帧开始的时候，读取一个元素box的height属性，你可以会写出这样的JavaScript代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;requestAnimationFrame(changeBox);
function changeBox() {
  var boxOffsetHeight = box.offsetHeight;
  var boxColor = box.style.color;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是如果你一边改样式，一边读样式就会很耗性能了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;requestAnimationFrame(changeBox);
function changeBox() {
  var boxOffsetHeight = box.offsetHeight;
  box.offsetHeight = 3000;
  var boxColor = box.style.color;
  box.style.color = &apos;#333&apos;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样浏览器每次都得先读再写，又读再写，每一次写都是一次重绘，发生了2次重绘太耗性能。我们应该先读后写，读出上一帧的值，再改变样式。&lt;/p&gt;

&lt;p&gt;如果这种读写混搭风发生再循环里就更可怕了，譬如说这样：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for (var i = 0; i &amp;lt; length; i++) {
  container[i].style.height = box.height + &apos;px&apos;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样每次循环，都得先读取box的高度值再去改变对应container的高度，这个过程每次都必须发生重绘让上一次的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;style.height&lt;/code&gt;的样式改变生效，以保证取到的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;box.height&lt;/code&gt;是及时性高的准确值，然而我们如果对container的改变不影响box，其实这个过程就是没必要的。可以先缓存起box的值：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var boxHeight = box.height;
for (var i = 0; i &amp;lt; length; i++) {
  container[i].style.height = boxHeight + &apos;px&apos;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;paint简化复杂度减小区域&quot;&gt;paint:简化复杂度、减小区域&lt;/h2&gt;

&lt;p&gt;如果layout被触发，那么接下来paint一定会被触发。因为改变一个元素的几何属性就意味着该元素的所有像素都需要重新渲染。除此之外，如果改变元素的非几何属性，也可能触发paint，比如背景、文字颜色或者阴影效果，尽管这些属性的改变不会触发layout。&lt;/p&gt;

&lt;p&gt;绘制并非总是在内存中的单层画面里完成的。实际上，浏览器在必要时将会把一帧画面绘制成多层画面，然后将这若干层画面合并成一张图片显示到屏幕上。&lt;/p&gt;

&lt;p&gt;这种绘制方式的好处是，使用tranforms来实现移动效果的元素将会被正常绘制，同时不会触发对其他元素的绘制。这种处理方式和思想跟图像处理软件（比如Sketch/GIMP/Photoshop）是一致的，它们都是可以在图像中的某个单个图层上做操作，最后合并所有图层得到最终的图像。&lt;/p&gt;

&lt;p&gt;在页面中创建一个新的渲染层的最好方式就是使用CSS属性will-change，Chrome/Opera/Firefox都支持该属性。同时再与transform属性一起使用，就会创建一个新的组合层。对于那些目前还不支持will-change属性、但支持创建渲染层的浏览器，比如Safari和Mobile Safari，你可以使用一个3D transform属性来强制浏览器创建一个新的渲染层。&lt;/p&gt;

&lt;p&gt;但是每创建一个新的渲染层，就意味着新的内存分配和更复杂的层的管理。所以创建前后应该使用chrome DevTools来分析其实际性能表现，以确认是否创建。&lt;/p&gt;

&lt;p&gt;浏览器会把两个相邻区域的渲染任务合并在一起进行，这将导致整个屏幕区域都会被绘制。在DPI较高的屏幕上，固定定位的元素会自动地被提升到一个它自有的渲染层中。但在DPI较低的设备上却并非如此，因为这个渲染层的提升会使得字体渲染方式由子像素变为灰阶，我们需要手动实现渲染层的提升。减少绘制区域通常需要对动画效果进行精密设计，以保证各自的绘制区域之间不会有太多重叠，或者想办法避免对页面中某些区域执行动画效果。&lt;/p&gt;

&lt;p&gt;对于各种样式效果，各种css属性导致的绘制复杂度是不一样的。比如，background: red就比box-shadow: 0, 4px, 4px, rgba(0,0,0,0.5)更复杂，所以在实现一个效果时，我们需要思考是否有其它方式来达到同样效果。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;paint是整个渲染流水线中耗时最长的一环，因此也是最需要避免发生的一环。特别是在动画效果中。每帧10毫秒的时间预算一般来说是不足以完成绘制工作的，尤其是在移动设备上&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;composite渲染层合并控制层数量&quot;&gt;composite:渲染层合并,控制层数量&lt;/h3&gt;

&lt;p&gt;我们可以优化的是避免触发layout，而是使用第三种类型的渲染（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;javascript - style - composite&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;css属性中能够直接使用第三种类型渲染模式的只有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opacity&lt;/code&gt;和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transform&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://csstriggers.com/&quot;&gt;https://csstriggers.com/&lt;/a&gt;：css属性是否能触发布局、绘制或渲染层合并的清单。&lt;/p&gt;

&lt;p&gt;对于要进行动画操作的元素：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.moving-element {
  will-change: transform;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;或者，对于旧版本或不支持will-change属性的浏览器：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.moving-element {
  transform: translateZ(0);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用这个CSS属性能提前告知浏览器：这个元素将会执行动画效果。从而浏览器可以提前做一些准备，比如为这个元素创建一个新的渲染层。&lt;/p&gt;

&lt;p&gt;但是，&lt;strong&gt;应用了transforms/opacity属性的元素必须_独占一个渲染层&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;创建一个新的渲染层并不是免费的，它得消耗额外的内存和管理资源。每个渲染层的纹理都需要上传到GPU处理，因此使用它们时，我们还需要考虑CPU和GPU之间的带宽问题、以及有多大内存供GPU处理这些纹理的问题。&lt;/p&gt;

&lt;p&gt;所以在使用渲染层合并属性时，也要控制层数量。&lt;/p&gt;

&lt;h2 id=&quot;用户操作响应防抖动&quot;&gt;用户操作响应防抖动&lt;/h2&gt;

&lt;p&gt;在理想情况下，当用户在设备屏幕上触摸了页面上某个位置时，页面的渲染层合并线程将接收到这个触摸事件并作出响应，比如移动页面元素。这个响应过程是不需要浏览器主线程的参与的，也就是说，不会导致JavaScript、布局和绘制过程的发生，不会导致卡顿。&lt;/p&gt;

&lt;p&gt;但是，如果你对这个被触摸的元素绑定了输入事件处理函数，比如touchstart、touchmove或者touchend，那么渲染层合并线程必须等待这些被绑定的处理函数执行完毕之后才能被执行。因为你可能在这些处理函数中调用了类似preventDefault()的函数，这将会阻止输入事件（touch/scroll等）的默认处理函数的运行。事实上，即便你没有在事件处理函数中调用preventDefault()，渲染层合并线程也依然会等待，也就是用户的滚动页面操作被阻塞了，表现出的行为就是滚动出现延迟或者卡顿（帧丢失）。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/ontouchmove.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以基于这种机制，你必须确保对用户输入事件绑定的任何处理函数都能够快速执行完毕，以便腾出时间来让渲染层合并线程来完成它的工作。&lt;/p&gt;

&lt;p&gt;输入事件处理函数，比如scroll/touch事件的处理，都会在requestAnimationFrame之前被调用执行。因此，如果你在上述输入事件的处理函数中做了修改样式属性的操作，那么这些操作会被浏览器暂存起来。然后在调用requestAnimationFrame的时候，如果你在一开始做了读取样式属性的操作，那么将会触发浏览器的强制同步布局过程！&lt;/p&gt;

&lt;p&gt;有一个方法能同时解决上面的两个问题：对样式修改操作去抖动，控制其仅在下一次requestAnimationFrame中发生：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function onScroll (evt) {

  // Store the scroll value for laterz.
  lastScrollY = window.scrollY;

  // Prevent multiple rAF callbacks.
  if (scheduledAnimationFrame)
    return;

  scheduledAnimationFrame = true;
  requestAnimationFrame(readAndUpdatePage);
}

window.addEventListener(&apos;scroll&apos;, onScroll);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h1&gt;

&lt;p&gt;谷歌开发文档系列：&lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en&quot;&gt;https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>浏览器的刷新和缓存</title>
   <link href="http://echizen.github.io/tech/2016/03-05-refresh-and-cashe"/>
   <updated>2016-03-05T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/refresh-and-cashe</id>
   <content type="html">
&lt;p&gt;这个问题来源于一次再IE上调试时，先上传了一份无bug版本用ie打开后正常，接下来不小心上传了一份bug版本，偶然发现浏览器回车刷新页面看到的是正常版（缓存），按刷新按钮和f5看到是Bug版。不禁心生疑惑。&lt;/p&gt;

&lt;p&gt;浏览器向服务器发送请求时是会带上头信息的，用来告诉服务器一些附加信息，其中一些参数与缓存有关，这些信息再chrome中的network中可以看到。&lt;/p&gt;

&lt;h1 id=&quot;回车cmdr和cmdshiftf5浏览器都发了什么头信息&quot;&gt;回车、cmd+r和cmd+shift+f5，浏览器都发了什么头信息&lt;/h1&gt;

&lt;p&gt;对于一个网址中的js资源（我是拿正打开的csdn页面测试的）。&lt;/p&gt;

&lt;h3 id=&quot;地址栏回车&quot;&gt;地址栏回车&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;status code:304&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;request header:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/blog2016030501.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;特点：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if-modify-since&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;刷新按钮cmdrwindows为f5&quot;&gt;刷新按钮/cmd+R(windows为f5)&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;status code:304&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;request header:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/blog2016030502.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;特点：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache-control:max-age=0&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if-modify-since&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;cmdshiftrwindows为ctrlf5&quot;&gt;cmd+shift+R(windows为ctrl+f5)&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;status code:200&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;request header:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/blog2016030503.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;特点：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cache-Control:no-cache&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pragma:no-cache&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;request-headers中与缓存有关的字段&quot;&gt;request headers中与缓存有关的字段&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;If-Modified-Since&lt;/p&gt;

    &lt;p&gt;用于GET和HEAD请求，它的值是文档的最后改动时间，值来源于客户端当前时间或者该资源上次被加载的时间。客户端可以通过If-Modified-Since请求头提供一个日期，该请求将被视为一个返回资源的条件，只有改动时间迟于指定时间时服务器才会返回资源，否则会返回一个304（Not Modified）状态。当&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;If-None-Match&lt;/code&gt;存在时，改参数失效。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Cache-Control&lt;/p&gt;

    &lt;p&gt;值可以为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max-age=秒数&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no-cache&lt;/code&gt;，max-age定义文件在指定的时间内无需去服务端检查是否有更新，单位是秒；no-cache指定浏览器每次都要去服务端检查文件是否有更新。&lt;/p&gt;

    &lt;p&gt;max-age=0和no-cache区别：max-age系列是由浏览器判断的，如果缓存时间超过设定的秒数就去服务器取，max-age=0和If-Modified-Since共同使用后还会出现不从服务器重新获取文件的情况，因为If-Modified-Since告诉浏览器文件还未过期。而no-cache告诉浏览器一律不走缓存，一定会去取服务器端数据。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Pragma&lt;/p&gt;

    &lt;p&gt;HTTP/1.0 caches协议没有实现Cache-Control（在http1.1实现）， 实现了 Pragma: no-cache的功能。 请求头中如果包含pragma头的时候，服务器端会将该请求转发给源服务器。 指定“no-cache”值表示服务器必须返回一个刷新后的文档，即使它是代理服务器而且已经有了页面的本地拷贝。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If-None-Match&lt;/p&gt;

    &lt;p&gt;用于GET和HEAD请求，对于拥有标签标记的资源，值为标签内容在这个标签未被修改的情况下获取缓存，返回304（ 304 Not Modified ）。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;response-headers中对应的缓存相关字段&quot;&gt;response headers中对应的缓存相关字段&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Last-Modified&lt;/p&gt;

    &lt;p&gt;与请求头中的If-Modified-Since配合使用。&lt;/p&gt;

    &lt;p&gt;在浏览器第一次请求某一个URL时，服务器端的返回状态会是200，内容是你请求的资源，同时有一个Last-Modified的属性标记(HttpReponse Header)此文件在服务期端最后被修改的时间。&lt;/p&gt;

    &lt;p&gt;客户端第二次请求此URL时，根据HTTP协议的规定，浏览器会向服务器传送If-Modified-Since报头(HttpRequest Header)，询问该时间之后文件是否有被修改过。如果服务器端的资源没有变化，则自动返回HTTP304（NotChanged.）状态码，内容为空，这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时，则重新发出资源，返回和第一次请求时类似。从而保证不向客户端重复发出资源，也保证当服务器有变化时，客户端能够得到最新的资源。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Expires&lt;/p&gt;

    &lt;p&gt;与请求头中Cache-Control配合使用。HTTP 1.0 版本，在给出的日期/时间后，资源的响应被认为是过时。需和Last-Modified结合使用。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Etag&lt;/p&gt;

    &lt;p&gt;与请求头中的If-None-Match配合使用。&lt;/p&gt;

    &lt;p&gt;HTTP协议规格说明定义ETag为“被请求变量的实体标记”。简单点即服务器响应时给请求URL标记，并在HTTP响应头中将其传送到客户端，类似服务器端返回的格式。客户端的使用If-None-Match查询更新。
如果ETag没改变，则返回状态304。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Cache-Control&lt;/p&gt;

    &lt;p&gt;通知从服务器到客户端内的所有缓存机制，表示它们是否可以缓存这个对象及缓存有效时间。其单位为秒&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;各缓存相关设置优先级&quot;&gt;各缓存相关设置优先级&lt;/h1&gt;

&lt;h3 id=&quot;last-modifiedetag-与-cache-controlexpires&quot;&gt;Last-Modified/ETag 与 Cache-Control/Expires&lt;/h3&gt;

&lt;p&gt;配置 Last-Modified/ETag 的情况下，浏览器再次访问统一 URI 的资源，还是会发送请求到服务器询问文件是否已经修改，如果没有，服务器会只返回影响吗 304 （图第二张图所示）回给浏览器，代表资源没有变化，告诉浏览器直接读取缓存数据&lt;/p&gt;

&lt;p&gt;Cache-Control/Expires 不同，如果检测到本地的缓存还是有效的时间范围内，浏览器直接使用本地副本，不会发送任何请求。两者一起使用时，&lt;strong&gt;Cache-Control/Expires的优先级要高于Last-Modified/ETag&lt;/strong&gt;。即当本地副本根据Cache-Control/Expires发现还在有效期内时，则不会再次发送请求去服务器询问修改时间（Last-Modified）或实体标识（Etag）了。&lt;/p&gt;

&lt;h3 id=&quot;cache-control-与-expires&quot;&gt;Cache-Control 与 Expires&lt;/h3&gt;

&lt;p&gt;Cache-Control 与 Expires 作用一致，都是指当前资源的有效期，控制浏览器是否直接从浏览器获取数据还是重新发送请求到服务器取数据。Cache-Control 的选择更多，设置更细致，如果响应头中同时存在，&lt;strong&gt;Cache-Control优先级高于 Expires&lt;/strong&gt;。&lt;/p&gt;

&lt;h1 id=&quot;不会被缓存的请求&quot;&gt;不会被缓存的请求&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;常规post请求不能被缓存，get请求可以&lt;/li&gt;
  &lt;li&gt;HTTP 信息头中包含Cache-Control:no-cache，pragma:no-cache，或Cache-Control:max-age=0 等告诉浏览器不用缓存的请求&lt;/li&gt;
  &lt;li&gt;需要根据Cookie，认证信息等决定输入内容的动态请求&lt;/li&gt;
  &lt;li&gt;经过HTTPS安全加密的请求&lt;/li&gt;
  &lt;li&gt;HTTP 响应头中不包含 Last-Modified/Etag，也不包含 Cache-Control/Expires 的请求&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7232#section-3&quot;&gt;https://tools.ietf.org/html/rfc7232#section-3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers&quot;&gt;Increasing Application Performance with HTTP Cache Headers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.mnot.net/cache_docs/&quot;&gt;Caching Tutorial&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>js中的异步执行时间探究</title>
   <link href="http://echizen.github.io/tech/2016/03-05-asynchronous"/>
   <updated>2016-03-05T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/asynchronous</id>
   <content type="html">
&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;

&lt;p&gt;最近调了一份代码，是A文件里发送了一个ajax请求，在回调中触发了aevent事件，即trigger(‘aevent’),b文件中监听了这个事件,即on(‘aevent’，function(){}),a文件先于b文件被引用，结果发现在本地调试时，b文件中on语句先调用，a文件中trigger后调用，到了服务器上却成了b文件中on先调用，a文件中trgger语句后调用。&lt;/p&gt;

&lt;p&gt;我觉得并不奇怪，因为我的观念里异步请求中的回调函数是在异步请求返回后执行，学长看后怀疑，他说js是异步一定晚于同步语句执行，还给我画了个同步是一条主支，异步是个分支，会被同步主支的末端被合并的图，让我开始深深怀疑异步是否真的一定晚于同步语句执行的问题。&lt;/p&gt;

&lt;p&gt;虽说当时那个bug解决了，跟这个问题没关，因为的的语句在requirejs的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;aModule&apos;,function(){})&lt;/code&gt;的回调函数中，而requirejs本身就是一个异步加载，2个异步回调函数谁先执行自然是不确定的。但是我还是想挖一挖同步异步的坑。&lt;/p&gt;

&lt;h2 id=&quot;单线程多进程&quot;&gt;单线程多进程&lt;/h2&gt;

&lt;p&gt;都说js是一门单线程多进程的语言，其实是Js是单线程的，一个浏览器进程中只有一个JS的执行线程，同一时刻内只会有一段代码在执行。而异步机制是浏览器的两个或以上常驻线程共同完成的，例如异步请求是由两个常驻线程：JS执行线程和事件触发线程共同完成的，JS的执行线程发起异步请求（这时浏览器会开一条新的HTTP请求线程来执行请求，这时JS的任务已完成，继续执行线程队列中剩下的其他任务），然后在未来的某一时刻事件触发线程监视到之前的发起的HTTP请求已完成，它就会把完成事件插入到JS执行队列的尾部等待JS处理。&lt;/p&gt;

&lt;p&gt;JS的主进程调用栈是同步的（单线程，一次只能做一件事），异步调用把回调加入一个队列，等待主栈空闲，Event触发，Event Loop机制把队列里排在前面的回调压入主栈。&lt;/p&gt;

&lt;h2 id=&quot;异步是什么&quot;&gt;异步是什么&lt;/h2&gt;

&lt;p&gt;异步不仅仅包括ajax、setTimeout等。我所认识的异步还有：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;回调函数&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; function fun1(callback) {
     callback();
 }
	
 function fun2() {
     console.log(&apos;我是异步执行函数&apos;)
 }
	
 fun1(fun2);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;事件机制&lt;/p&gt;

    &lt;p&gt;譬如jquery的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tigger/on&lt;/code&gt;,node中的emitEvent模块的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emit/on&lt;/code&gt;。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;订阅模式&lt;/p&gt;

    &lt;p&gt;各种框架实现的sub/pub。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;异步请求&lt;/p&gt;

    &lt;p&gt;原生js的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XMLHttpRequest&lt;/code&gt;。jquery中的ajax。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;promise&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;setTimeout、setInterval。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;“同步模式”就是上一段的模式，后一个任务等待前一个任务结束，然后再执行，程序的执行顺序与任务的排列顺序是一致的、同步的；&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;“异步模式”则完全不同，每一个任务有一个或多个回调函数（callback），前一个任务结束后，不是执行后一个任务，而是执行回调函数，后一个任务则是不等前一个任务结束就执行，所以程序的执行顺序与任务的排列顺序是不一致的、异步的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;异步的执行时间&quot;&gt;异步的执行时间&lt;/h2&gt;

&lt;p&gt;对于普通的回调函数、事件机制、订阅模式，异步的执行时间都是确定的。&lt;/p&gt;

&lt;p&gt;但是异步请求和setTimout、setTimeInterval的执行时间却是不确定的，原因是他们会让浏览器新开一个进程来完成网络资源请求或者计时任务，完成时回调函数不是立即调用，而是插入到当前任务队列的尾部,等待当前队列中的其他任务执行完毕才执行，至于究竟何时执行是不确定的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;console.log(&apos;start&apos;);
setTimeout(function(){
	console.log(&apos;asynchronous&apos;)
},0);

for(var i = 0;i&amp;lt;1000;i++){
	$(&apos;.zm-item-answer&apos;).append(&apos;good&apos;);

}
console.log(&apos;end&apos;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;见到的打印结果仍然是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;start
end
asynchronous
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;虽然setTimeout的延迟参数是0，但是他插入不了当前的循环队列中间，只能排到最后，即使循环队列很复杂，耗时长，异步的回调函数也会比它后执行。更不用说耗时长的网络请求了，身边有人测试了网络IO请求消耗时间是内存读取时间的百万倍。网络再好的ajax也快不过同步代码。&lt;/p&gt;

&lt;h2 id=&quot;关于ajax剩下的思考和疑惑&quot;&gt;关于ajax剩下的思考和疑惑&lt;/h2&gt;

&lt;p&gt;我仍然没办法证明ajax这种异步请求是在请求的网络资源返回后就立即将要执行的回调函数插入到当前任务队列的末端的，也有可能是js有种轮询机制，过了一定时间就会查看一下自己打开的各浏览器进程的执行情况，发现ajax资源返回了再将其回调中的任务插入到任务队列的末端。其实我还是想用某种办法知道究竟是什么情况。&lt;/p&gt;

&lt;p&gt;但是我不认同异步代码一定在同步代码执行之后执行的说法，譬如&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function fun1(callback) {
	callback();
}

function fun2() {
	console.log(&apos;我是异步执行函数&apos;);
}

fun1(fun2);

console.log(&apos;同步代码&apos;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;会打印：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;我是异步执行函数
同步带吗
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;只是ajax和setTimeout这种需要浏览器新开进程的异步比较特殊，他们不在当前线程中执行，而是要在某个时间点被插入到js任务队列的末端。总是会被同步代码后执行，特别是ajax请求。这就造成我们会看到这些异步语句总是比所有同步语句后执行。&lt;/p&gt;

&lt;p&gt;但是仍有一种说法：js代码中有帧的概念，对于同步代码是在当前帧运行的，异步代码是在下一帧运行的。&lt;/p&gt;

&lt;p&gt;我并不清楚js中的当前任务队列里会有哪些内容，也无法知道ajax、setTimeout中回调函数的确切执行时间。但是有一个原则，不能依赖异步执行的时间。&lt;/p&gt;

&lt;p&gt;如果同一个js文件中存在2个ajax、setTimeout类的异步语句，这2个异步语句的执行时间总是不确定，完全取决于浏览器先将哪个push到任务队列里，而且每次运行结果都有可能不同。&lt;/p&gt;

&lt;h2 id=&quot;应用实例&quot;&gt;应用实例&lt;/h2&gt;

&lt;p&gt;看到一个异步运用挺好的例子。&lt;/p&gt;

&lt;p&gt;JavaScript是一种单线程执行的脚本语言（这可能是由于历史原因或为了简单而采取的设计）。它的单线程表现在任何一个函数都要从头到尾执行完毕之后，才会执行另一个函数，界面的更新、鼠标事件的处理、计时器（setTimeout、setInterval等）的执行也需要先排队，后串行执行。假如有一段JavaScript从头到尾执行时间比较长，那么在执行期间任何UI更新都会被阻塞，界面事件处理也会停止响应。这种情况下就需要异步编程模式，目的就是把代码的运行打散或者让IO调用（例如AJAX）在后台运行，让界面更新和事件处理能够及时地运行。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;output&quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;button onclick=&quot;updateSync ()&quot;&amp;gt;Run Sync&amp;lt;/button&amp;gt;

&amp;lt;button onclick=&quot;updateAsync ()&quot;&amp;gt;Run Async&amp;lt;/button&amp;gt;

&amp;lt;script&amp;gt;

function updateSync() {
    for (var i = 0; i &amp;lt; 1000; i++) {
        document.getElementById(&apos;output&apos;).innerHTML = i;
    }
}

function updateAsync() {
    var i = 0;

    function updateLater() {
        document.getElementById(&apos;output&apos;).innerHTML = (i++);
        if (i &amp;lt; 1000) {
            setTimeout(updateLater, 0);
        }
    }

    updateLater();
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;点击”Run Sync”按钮会调用updateSync的同步函数，逻辑非常简单，循环体内每次更新output结点的内容为i。如果在其他多线程模型下的语言，你可能会看到界面上以非常快的速度显示从0到999后停止。但是在JavaScript中，你会感觉按钮按下去的时候卡了一下，然后看到一个最终结果999，而没有中间过程，这就是因为在updateSync函数运行过程中UI更新被阻塞，只有当它结束退出后才会更新UI。如果你让这个函数的运行时间增加一下（例如把上限改为1 000 000），你会看到更明显的停顿，在停顿期间点击另一个按钮是没有任何反应的，只有结束之后才会处理另一个按钮的点击事件。&lt;/p&gt;

&lt;p&gt;另一个按钮”Run Async”会调用updateAsync函数，它是一个异步函数，乍一看逻辑比较复杂，函数里先声明了一个局部变量i和嵌套函数updateLater（关于内嵌函数的介绍请看JavaScript世界的一等公民-函数），然后调用了updateLater，在这个函数中先是更新output结点的内容为i，然后通过setTimeout让updateLater函数异步执行。这个函数的运行后，你会看到UI界面上从0到999快速地更新过程，这就是异步执行的结果。&lt;/p&gt;

&lt;p&gt;可见，在JavaScript中异步编程甚至是一种必要的编程模式。&lt;/p&gt;

&lt;h2 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://blog.whyun.com/posts/js/&quot;&gt;js异步之惑&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://software.intel.com/zh-cn/articles/asynchronized-javascript-programming&quot;&gt;JavaScript异步编程&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>2016规划</title>
   <link href="http://echizen.github.io/think/2016/02-08-2016-plan2016"/>
   <updated>2016-02-08T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2016/2016-plan2016</id>
   <content type="html">
&lt;p&gt;规划只是用来定大体的方向，并不一定能按规划执行，因为世界变化的太快，我们总是在不停的调整着自己的规划来跟上时代的变化，否则一直按着年初的计划生活，进步幅度太小。&lt;/p&gt;

&lt;h1 id=&quot;技术&quot;&gt;技术&lt;/h1&gt;

&lt;p&gt;厚积薄发，2016应该仍是积累的一年。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;至少看透一个大型的优秀库的源码（暂定react）。&lt;/li&gt;
  &lt;li&gt;继续务实基础，把买的积累的书有价值的都看完，每晚下班后看书一小时。&lt;/li&gt;
  &lt;li&gt;写一些自己的小组件，可以通用的放到github上。时常看优秀插件的源码，吸取精华，为己所用，为公司贡献精致组件。慢慢从用组件向写组件发展。&lt;/li&gt;
  &lt;li&gt;了解计算机基础知识：http协议和编译原理。&lt;/li&gt;
  &lt;li&gt;通过自己的技术，为公司创造价值，从页面君的角色中切换出去。&lt;/li&gt;
  &lt;li&gt;每个月至少一篇有质量的博客。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为解放生产力，成为优秀的程序员而不竭奋斗。&lt;/p&gt;

&lt;h1 id=&quot;生活&quot;&gt;生活&lt;/h1&gt;

&lt;p&gt;热爱生活，享受生活。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;顺利毕业&lt;/li&gt;
  &lt;li&gt;保持和一群好朋友的联系&lt;/li&gt;
  &lt;li&gt;宏大的毕业旅行，要去丽江、大理、云南、昆明，还要去九寨沟~~~&lt;/li&gt;
  &lt;li&gt;和男票和谐发展，多付出少索取。并争取能获得自己家庭的认可&lt;/li&gt;
  &lt;li&gt;照顾好我亲爱的汪-小北~&lt;/li&gt;
  &lt;li&gt;考驾照&lt;/li&gt;
  &lt;li&gt;和生活中的每一个人保持良好的沟通，外向点&lt;/li&gt;
  &lt;li&gt;每个月都坚持出去走走，看看大自然，至少1次&lt;/li&gt;
  &lt;li&gt;每个月至少帮助男票打扫他和汪的窝1次&lt;/li&gt;
  &lt;li&gt;为了自己和家人的嘴，还是跟着网上教程学做菜吧&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;个人&quot;&gt;个人&lt;/h1&gt;

&lt;p&gt;槽点太多。。。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;祛痘战役已打响，药不能停（中药忒苦了），必要可以实施早睡早起、不吃辣😢&lt;/li&gt;
  &lt;li&gt;如果痘痘好了，学会女孩子的正常技能，化妆啦，装扮啦，自拍啦&lt;/li&gt;
  &lt;li&gt;学点摄影，照片拍的不能看&lt;/li&gt;
  &lt;li&gt;学瑜伽，还是应该坚持锻炼身体的（可惜不能打网球了😢）&lt;/li&gt;
  &lt;li&gt;修身养性，减少对男票发脾气的概率，遇事想更好的解决方案，杜绝大吼大叫，咱可以不漂亮，但是不能丢气质&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>2015年终回顾-改变</title>
   <link href="http://echizen.github.io/think/2016/02-05-2015"/>
   <updated>2016-02-05T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2016/2015-</id>
   <content type="html">
&lt;p&gt;2015年，真的不能再丰富。&lt;/p&gt;

&lt;p&gt;完成了学校方面的各种事宜，只等待颁发毕业证了。经历了从初创公司到成熟公司，算是真的踏入社会了。对自己认识的更深了，一方面是技术方面开始专注深入，走上正轨，对前端领域认识的清晰了，对自己所在的阶段和自己的长板和短板认识的清晰了，这得感谢面试经历；另一方面是对自己的生活和性格认识的清晰了，这得感谢感情经历。。。&lt;/p&gt;

&lt;h1 id=&quot;初创到成熟&quot;&gt;初创到成熟&lt;/h1&gt;

&lt;p&gt;从初创到成熟的不仅是所在公司，也是我自己个人技术。&lt;/p&gt;

&lt;h2 id=&quot;在初创公司的日子&quot;&gt;在初创公司的日子&lt;/h2&gt;

&lt;p&gt;14年暑假加入的初创公司，没有多少对公司的信心和展望什么的，更多的是想练自己的技术。体验了一把把想要实践的东西，玩了1年，也够了，再呆就感觉到瓶颈了，没法再继续发展了。倒是对公司对产品并没有什么信心，一开始就不觉得公司能走多远，对于产品，只是尽心尽力想要用技术最好的呈现它，做到最好的交互和体验，但是并不能给它能否推到市场带来什么帮助。&lt;/p&gt;

&lt;p&gt;15年1月到7月，都还是在这家初创公司（HW）工作，前端由我1人到5人到3人，团队一直在变动不稳定，做的很累，招进来的人会的太少，先让他们熟悉开发工具、工具流、必备技能就得半个月，熟悉后还没实践就走了，一批批实在太累。我感觉自己都还没成熟，就成了个前端团队leader的角色了，很疑惑。（这样写成流水账了。。。得理一下）&lt;/p&gt;

&lt;h3 id=&quot;无协作&quot;&gt;无协作&lt;/h3&gt;

&lt;p&gt;前端届鱼龙混杂，多是鱼，很多人只是觉得前端简单就来学，也没啥兴趣就是想混口饭吃，殊不知前端虽然门槛低，但是深入起来水很深的。跟没有追求的前端一起工作实在是蛮痛苦的，除了用jquery啥也不会写，jquery做个交互逻辑复杂一点的还不知道怎么根据用户操作映射dom树做出交互，工作量大，我也没时间一次次教，导致每个项目80%的代码都是我自己写的，后来不行就是有类似内容的项目（公司接的一些外包项目，多是微信平台开发）全给他们，新项目自己一个人上，丝毫没有团队协作感。这1年的经历真的一点没让我培养起前端之间的协作模式。&lt;/p&gt;

&lt;p&gt;其实后来进了成熟的公司，看着带着我的学长一步步引导我和其它他带的孩子，感觉到这一切是我自己不够成熟，团队意识不强，没有引导好其他人，没有激发大家的热情，一切还是自己做的不对。&lt;/p&gt;

&lt;h3 id=&quot;孤独&quot;&gt;孤独&lt;/h3&gt;

&lt;p&gt;喜欢折腾，自己做的项目都不会用重复的技术，每次都尝试新的模式，这个探索的过程会不断遇到问题，但是却没有可以交流的同事，有深深的孤独感。只能google，stackoverflow，SF，知乎，搜不到就只能去提问。虽说社区很繁荣，问题都能解决。但是远没有可以面对面讨论的快感和透彻，一个人写代码有深深的孤独感。&lt;/p&gt;

&lt;h3 id=&quot;疑惑&quot;&gt;疑惑&lt;/h3&gt;

&lt;p&gt;很多时候自己遇到的问题，自己找的解决方案，并不知道是不是最优解，自己架构的东西也不知道又没有更合理的方案。我有很多的困惑，虽说社区繁荣，我也不能拉着个大牛询问这么具体的东西（总觉得问得问题应该具有代表性是抽象出来的，业务耦合的问题没人有时间帮你），我充满疑惑，代码写的越多，以会越多。这是导致我想离开初创公司，想出去最直接的原因。&lt;/p&gt;

&lt;h3 id=&quot;累&quot;&gt;累&lt;/h3&gt;

&lt;p&gt;凭着一腔热情，不断的在公司耕耘，什么996，我是没课时早上9点到晚上10点，一周7天。。。尝试新技术时很兴奋，但是被项目进度压着又很害怕，要保证进度，又在尝试不熟悉的东西，只能加班加班加班。时间久了，1年左右就会周期性疲倦，想要度假了。。。&lt;/p&gt;

&lt;h3 id=&quot;挑战&quot;&gt;挑战&lt;/h3&gt;

&lt;p&gt;虽浮现出种种不好的方面，但是我从未后悔过来初创公司，相反非常感激这段日子。因为这里充满挑战。我可以尝试一切我想尝试的东西（虽然默默加班保证进度），我是自己的leader，随便自己折腾，我不断使用新的库和工具，体验他们的优势和不足，思考他们的设计。&lt;/p&gt;

&lt;p&gt;这些是我去了大公司后就不再拥有的快乐。&lt;/p&gt;

&lt;p&gt;犹记得最后一个项目带着2个协会的小朋友上angular时的兴奋。这是我在决定离开HW后做的最后一个项目，也是最开心的一个，这2个小朋友蛮给力，我们合作的很愉快，这个项目不是我一个人写的，我贡献的代码量只有30%不到。但是是我一个人架构选型，写基础demo，解决他们抛给我的技术难题，非常有挑战，也有意义，学到了很多。然后目前为止，angular在我心中是用的最爽的框架，选型很成功，很适合那个中小型交互多、表单多的项目，数据双向绑定，MVVM大大解放了我们的生产力，1个月就做完了（虽然还用了1个月测试修Bug，变动需求）。&lt;/p&gt;

&lt;p&gt;在做这个项目时，第一次用了angular,也是第一次用bower\gulp这些工具流,架构时花了不少时间，重复了4次，经验不够的我来选型显然有点赶鸭子上架，我知道架构做的不好会坑死项目，这也是我感到挑战最大的一次。所以我不断的测试开发流程，部署开发环境，模拟开发，和后端讨论交互，想象可能存在的问题，一次次推翻重构，来让这个结构变得可靠。&lt;/p&gt;

&lt;h2 id=&quot;在的日子&quot;&gt;在🍄的日子&lt;/h2&gt;

&lt;p&gt;后来我通过校招内推进了蘑菇街，这是一个1000+人蛮成熟的公司了，所在团队前端10几人，也符合我想要的规模了。&lt;/p&gt;

&lt;p&gt;虽然做好了进去先好好学习搬砖的准备，但是还是挺不习惯的，感觉以前是在写项目，现在是在写页面。还好我能及时告诉自己即使做最简单的事也有可以锻炼的地方，关键是自己会不会寻找和把握。这几个月在🍄的日子，更多的练就的基础和细致。&lt;/p&gt;

&lt;p&gt;现在综合看看，团队项目还有很多我可以努力改善的地方，除了搬砖似的日常需求，我还是能发挥价值的。但是进了大公司以后，任务没有那么重，责任没有那么大，人激情和精力也有些消退，开发节奏慢了，开始有双休日并且真的会休息了，贪图享乐的变化得遏制。&lt;/p&gt;

&lt;h3 id=&quot;极致体验&quot;&gt;极致体验&lt;/h3&gt;

&lt;p&gt;所在部门前端是和广大用户直接交互的，所以对于交互体验要求很高，动画的流畅，交互的合理性要求都很高。测试也很仔细，一遍一遍，上线后出交互问题也是要问责的（一直很害怕）。学到了rem的真正的适配。还有dom动态移除，图片动态加载，动画桢&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestAnimationFrame&lt;/code&gt;、无处不在的图片大小保护、文字数量保护、后端数据空白保护等等。&lt;/p&gt;

&lt;h3 id=&quot;组件库&quot;&gt;组件库&lt;/h3&gt;

&lt;p&gt;项目组有整套通用的组件库，导致运营提出的新需求，如果不是活动页，产品页其实也就是拉拉组件拼拼页面。听上去是挺没意思的，但我觉得对公司来说，这是产品成熟的标志，通用化后不需要重复劳动，对于程序员，我们这些新手可以先拿这种简单的活熟悉环境，还可以研究组件库的组件代码，熟悉原理，练功底。组件库也不是完美的，还有可以改进的用户体验和扩展的功能配置，所以自己也可以在组件库上动刀子，创造更好的组件。只要有心，能够做的还是很多的。&lt;/p&gt;

&lt;h3 id=&quot;环境&quot;&gt;环境&lt;/h3&gt;

&lt;p&gt;我们团队有很多事做，譬如活动页和产品页是不同的开发环境，然后我们自己也在开发开发环境。我对这些环境很感兴趣，其实我对一切解放程序员生产力的东西感兴趣，这些开发环境让本地开发变得方便。各种平台和开发环境都有自己的特色和适用性，我还没深入，但是蠢蠢欲动。我总是想清楚一个本地项目是如何跑起来的，了解到前后端和服务器端，才觉得清晰安心。&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;接下来说说在🍄的几件事：&lt;/p&gt;

&lt;h3 id=&quot;前端小黑屋&quot;&gt;前端小黑屋&lt;/h3&gt;

&lt;p&gt;刚来公司一个星期后进行了小黑屋训练，就是给我们4个活动页面，4个人组成一个团队（我们团3个人），3天去完成，要求纯手工，不用任何框架。&lt;/p&gt;

&lt;p&gt;作为一个开源库重度依赖者，对于要自己纯手工打造这些动画效果，是非常痛苦的。我是硬逼着自己一步步的去写，一开始心理上的排斥和不适应，让我开始写时非常慢。我去看了看魔方上学长们写模块的代码，看完一遍后觉得真的就是那么回事，思路并不难，难得是自己的心理障碍。这些动画怎么做其实我都是有思路的，但是一直不喜欢做动画，所以畏惧。但是任务当头，直得硬着头皮写。写着写着却觉得原来越顺畅，回头看看，真的没什么可畏惧的。因为向来喜欢挑战，我认领一个主会场的页面去完成，是动画和交互最多的一个页面。但是真的不能在规定时间内很好的完成，XF学长便跟我说不要急慢慢写，先把你觉得难得有意义模块写了。我便写了页面上4个难的模块，剩下的一个拼凑了队友的。最终，意外的我们因为完后度最高，被评为最优作品，还领了个电源。。。这个受之有愧。&lt;/p&gt;

&lt;p&gt;这个事件，对于个人来说，让我克服了对动画的恐惧之情，其实我们部门的业务做动画的需求还是不少的。技术上也有成长，由一直的字面量写法升到了原型。后来在做业务时也都将通用的组件抽成原型了，以前对于原型都是书上看看，并不会真的在项目里写，写了以后理解对this指针、原型链的理解和使用深入了，开始成为真正的jser了。&lt;/p&gt;

&lt;p&gt;这个事件，对于团队，是我最不满意的一个维度。因为我们一开始就想着任务划分清楚，按页面切了任务，而不是按模块。并没有很好的合作交流。做完这个后就在反思该如何团队化。&lt;/p&gt;

&lt;p&gt;对于项目，也不是让自己很满意，时间确实不充足，做的很草率，代码写的也挺乱的，细节也没有优化，对于有强迫症的人来说写这样的代码是很痛苦的，因为我总是想尽可能的优化代码，做到细致优雅，为了完成视觉上的效果而抛弃质量的做法很难做到。好在日后在项目中时间都很充足，能够将代码做到优雅。&lt;/p&gt;

&lt;h3 id=&quot;双十二bug惊魂&quot;&gt;双十二bug惊魂&lt;/h3&gt;

&lt;p&gt;双十二我和我的队友MS做了个活动页面，我做的h5，他做的pc。因为是双十二页面，又是新人，我们都诚惶诚恐，最后一星期基本每天测试一遍页面。&lt;/p&gt;

&lt;p&gt;然而最后上线时我的页面还是出bug了，剩余商品数量数据取成了库存数据，幸好一上线就发现自己数据不动，2分钟内及时改了过来。虽然这个问题不容易测试，因为测试时数据的剩余数和库存数相等，但是在写代码时，正常的人都不会犯这个错误，填错字段。&lt;/p&gt;

&lt;p&gt;这件事也着实吓到了我，让我时常活在bug的恐惧之中。后来在做日常需求时，虽然没出现过大bug，可是我的小bug不断，譬如没给金额加『￥』符号。&lt;/p&gt;

&lt;p&gt;好的程序员必定不能把bug带上线，所以我开始越来越注意这个问题。我一直不是很严谨，越小的地方越容易出错误。我开始梳理自己写代码的过程，要想不出Bug，不仅测试要仔细，写的时候更应该严谨。对于自己页面的每一句代码都要了如指掌，特别是用了别人的模块或者copy的代码，更应该在review一下。测试数据要覆盖尽可能多的情况，利用测试工具和平台造尽可能全面的测试数据。测试时，页面元素要耐心的一个个看，所有交互都要包含。&lt;/p&gt;

&lt;p&gt;团队方面，我发现我的同事MS就是一个很严谨的人，他上线的代码几乎没出过bug，我要好好学习。&lt;/p&gt;

&lt;p&gt;项目方便，当然很惭愧，把bug带上线已深深检讨。&lt;/p&gt;

&lt;h3 id=&quot;支援审理平台开发-挑战&quot;&gt;支援审理平台开发-挑战&lt;/h3&gt;

&lt;p&gt;这是我15年最后2周做的事，由于该团队只有一个前端，又年关将近，他们的审理平台项目前端还没开工，要延期了，leader便来我们团队借人，我就被借出去了。&lt;/p&gt;

&lt;p&gt;我先了解了他们的项目代码（一期的，现在是二期开发），发现虽然引入了react和redux，但是用的极度不规范。react组件拆分不够细，跟业务耦合太强，可复用的太低，结果就是页面长的区别不大，却没有公用组件夹。最可怕的是引入了redux，搭建了基础结构，内部完全没有用redux数据流，没有一个页面redux数据流闭环了，都是setState随时改状态，redux和react组合的优势尽失。&lt;/p&gt;

&lt;p&gt;我不想就这样扭曲的用react和redux，如果引用了一款框架，却没有使用他的优势，那么引入一个新框架折腾另外一种写法的意义何在。所以我非常想用真正的redux数据流来管理state，但是我之前也没有react和redux项目经验，入门门槛又不低，我害怕自己会导致项目延期，他们给了我2个模块5个页面，一周完成。我和我的学长XX沟通了，XX学长说节操还是要有的，想要做就去做，想写好代码就要花时间。我默默的决定规范的去用这些。&lt;/p&gt;

&lt;p&gt;但是支付金融那边的前端（他们团队只有一个前端。。。）还是按照一期那种不规范的写法在写项目，并且让我也这么做。因为时间很紧，害怕折腾后会延期，而以前的写法虽然不规范，但是快速。但是我默默决定就是加班也要规范的去用。第一天很不顺利，因为刚上手，写写改改，很多情况找不到标准，只有后面踩坑了或者觉得不方便了，才发觉前面的设计不合适，于是又去改之前的。我也开始害怕了，但是我还是想用规范的写法，第一天写到很晚先写了4个页面的UI，还不是很规范，但是毕竟完成了这么多页面有底进行接下来的尝试了，第二天我去和他们的前端沟通了我的想法，他也有点害怕延期，但是他还是支持我尝试。非常感谢XX学长和他的支持，我才敢实践，毕竟不是自己写着玩的，还是要考虑项目进度的。&lt;/p&gt;

&lt;p&gt;第二天我一天都在倒腾redux数据流，这玩意在这个项目里没有参考了，幸好我之前看过redux开发文档的大部分，也看过2个demo。就是边学习边尝试，但是终于把一个页面的数据流跑通了，心里也有底了，舒了口气。&lt;/p&gt;

&lt;p&gt;第三天又改造了一个页面，这个页面做的比较细致，不管是数据设计还是代码都规范优雅了很多，通用的小的react页面组件也被我拆出来扔到了公用组件文件夹中。总算是有一个拿的出手的页面了。&lt;/p&gt;

&lt;p&gt;第四天和第五天，画完了剩下的一个页面，又改写了之前设计的不好的页面。这周的完成所有页面的计划是没有延期，接下来就是调用后端接口联调了。周三时他们的前端问我跑通redux没有，我说通了，周四他看了我代码并且和我讨论了redux，他说也决定改成redux数据流。好开心，项目走上正轨了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;能通过自己的努力保住节操，规范的写代码，并且能够感染他人，这是我在🍄的3个月内，最有成就感的一件事。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;那周是真的累压力也大，毕竟在做没有做过的事，还没有自己嫡亲学长的解惑，感觉就像回到了初创公司，其实这个久违的感觉蛮亲切，也蛮激动的。这种挑战总是让我很兴奋，让我对代码保持高度热情。我在写react和redux的时候也在不断的思考他这种设计的目的和意义，发掘他所说的优势。优势确实有不少，形成redux数据流后，状态可控，只在一个地方操作数据，降低了bug的发生率，也方便和后端定接口或者接口变更。但是我也看到了redux和react的的其他特点，入门门槛高，而且为了达到redux数据流闭环，我们在设计和代码上要做很多工作，就是本来可以一步搞定的事，现在要5步的概念。这就决定了react+redux适应中大型项目，项目越大，越体现他的优势，越复杂的项目，前期为了闭环数据流所做的操作所占的比重越小，后期业务代码和状态变更占的比例越大，让前期的操作变得有意义，而且大型项目用了react后状态可控，非常方便维护和二期开发。但是小项目用react+redux就是折腾了，开发效率会降低。&lt;/p&gt;

&lt;p&gt;这次我除了尝鲜追了一下红遍大江南北的react+redux，也有了自己的思考和看法，不再只是局限于开发文档的介绍。一开始不理解为什么组件划分要那么细，并且没有按照那么细的标准来划分，等到后来发现要通用很麻烦时，才体会到组件划分细致的意义，就是要一个输入框都做成一个组件，就像搭积木，积木越小，类型越多，可堆成的成品类型越多。但是拆分设计组件也是要时间的，所以觉得react适合大型项目，这样因为重用率高，编写组件投入的精力才更有价值。&lt;/p&gt;

&lt;p&gt;项目上，我觉得我尽了最大的努力把它拉到规范化，并且自己默默写到半夜保证不延期。但是如果这个项目就目前的体积，我觉得更适合angular,而不是react。&lt;/p&gt;

&lt;h1 id=&quot;认识自己&quot;&gt;认识自己&lt;/h1&gt;

&lt;p&gt;这个要感谢阿里的面试，碰壁了一次又一次，也让我更清楚的认识自己的技术。&lt;/p&gt;

&lt;p&gt;我是框架重度依赖者，上手新框架和库比较快，自己几乎只写业务逻辑代码，从不写动画或功能组件，都是依赖开源库，这样在小公司活的很好，即能出东西，又稳靠，我觉得团队专业维护的插件肯定比我自己写的靠谱，有为何不用。我便也因为能写项目而忽视js基础，this指向傻傻分不清楚，原型原型链也只是纸上谈兵，从未用过。甚至在3月份面阿里时连一本完整的js的书都没看过。。。现在想想，被刷的毫无悬念。&lt;/p&gt;

&lt;p&gt;我开始重视基础，首先从看书开始，认真读了《js面向对象编程》，虽然还是没有实践，但是理论基础有了。&lt;/p&gt;

&lt;p&gt;7月份又面了阿里一次，这次人家不问别的，就问原生dom操作，还只是问api我就卡死了，别说性能了。。。因为平时从来不用原生js操作dom，我便彻底忽略了这块，我尝试引导面试官聊点别的，但是面试官就问这方面，后来我问面试官为何一直问这个，开发过程如果不自己写框架并不会真的用到这个，面试官说dom操作是js的一个核心，是基础检查的一个方面。我想就这个方面已经能看出一个人的js基础水平了吧。然后我自然又挂了。&lt;/p&gt;

&lt;p&gt;其实因为之前开发任务重，我也没时间准备面试，一些必备知识我都不清楚（在小公司有开源社区的贡献，这些知识也没有必备的刚需。。。），在我决定还是要换个大平台的时候，我毅然辞职了。然后8-9月就是修修以前公司存在的bug和小的需求过度（他们招不到人），一边对着网上的面试须知，各种看前端基础知识，倒不完全为了答题，也是为了补基础，记了好多印象笔记，很多东西解决了以前开发时的疑惑。开始清楚css盒模型，知道BFC和浮动到底是什么，了解了http协议和装态码，项目优化原则，js性能提升，浏览器渲染过程……这些都是花一点时间就能获益很多的知识点，很遗憾自己没在刚入门前端时由浅入深的就去了解这些。这段时间没有什么实践，但是基础补的不少。后来有时间间也试了下less,sass,jade,react之类之前没接触的东西。&lt;/p&gt;

&lt;p&gt;之后拥抱变化一出来，阿里是去不了了，但是网易、蘑菇街的面试都过的很轻松。想要找个好工作，基础还是很重要的，特别是应届生，你的公司很看重你的基础，基础是敲门砖，敲完了，才有兴趣看你潜力、学习能力什么的。&lt;/p&gt;

&lt;h3 id=&quot;方向&quot;&gt;方向&lt;/h3&gt;

&lt;p&gt;找工作及入职后，我都没以前初创公司里那么忙了，我有时间可以关注微博前端大V的动态，关注知乎相关话题，看些新兴的技术，开始看到整个行业的发展，猜测趋势（虽然不一定看清、看准）。开始明白前端是什么，优秀的前端是怎样的，自己在哪一步，自己接下来将要如何发展，前端将还会如何演变。这些问题清楚后，方向感明朗，真个人都充实有信心了。&lt;/p&gt;

&lt;h3 id=&quot;书籍&quot;&gt;书籍&lt;/h3&gt;

&lt;p&gt;落地工作也在做，为了构建良好的知识体系，书还是看了一些的，除了我的js首本《js面向对象编程》，这真是把js中的对象概念说的很清晰的一本书，很多基础还是不能只看博客，太片面，看本书会清晰很多。《javascript dom编程艺术》，但是感觉内容落后了，还是去看了阮一峰的系列博客了解了dom操作，《高性能javascript》是本收益匪浅的好书，《angular js权威教程》弥补了官网文档的零散性，从系统的角度介绍了这个框架，在我做项目时常会翻，《react 引领未来的用户界面框架》里面的demo不错，对于初学者，复杂度恰到好处。&lt;/p&gt;

&lt;p&gt;很遗憾的是一直想系统的了解css，都没找到什么好书，《css设计指南》2天看了感觉介绍的太浅了，90%都是我知道的内容，但是不能解释我的很多疑惑，声誉不菲的《css secret》介绍的是css的一个个小技巧，还是不系统。&lt;/p&gt;

&lt;h3 id=&quot;基础修炼&quot;&gt;基础修炼&lt;/h3&gt;

&lt;p&gt;除了看书，进了🍄后，基础一直在被翻炒加强，开始自己基于原型写组件了，终于和一直相依的对象字面量方式说拜拜了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apply&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bind&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prototype&lt;/code&gt;也都能玩的开了。虽说还待继续深入，但是好歹也算是个jser了。&lt;/p&gt;

&lt;h3 id=&quot;学习模式&quot;&gt;学习模式&lt;/h3&gt;

&lt;p&gt;这个还真是重要，对于新事物的学习模式，直接决定一个人的学习速度和接受新事物的能力。&lt;/p&gt;

&lt;p&gt;我会先看官方文档，特别是react\angular这类的库，但是es6，我就没耐心看w3C的文档了，看的是阮一峰的博客。看完官方文档，并跟着demo走一遍基础用法后，可以想想自己项目开发中经常遇到的一些需求和问题，如果用这款框架，该怎么实现。然后可以去git上搜搜这个框架写的优秀的项目，拜读2个，基础就差不多了。然后最好是有项目练手，否则就自己假设项目场景，一定要从0开始写一个项目，脚手架都自己搭，虽然挫点，但是能让自己明白这一切如何工作。在写的过程中会遇到很多实际的问题，再去文档中或社区里找到答案。&lt;/p&gt;

&lt;h1 id=&quot;感恩他人&quot;&gt;感恩他人&lt;/h1&gt;

&lt;p&gt;首先要感谢的是我现在的公司-蘑菇街，一家非常有意思的公司，给了我很大的幸福感。不错的offer是对我技术的肯定，工作环境的健康舒适让我工作心情很好，各种福利更是给我大大的愉悦，还有同事们的乐观向上奋斗让我觉得很亲切~~~&lt;/p&gt;

&lt;p&gt;也感谢前公司的信任，让我能够挑大梁做了那么多东西（当然可能是招不到其他人做。。。），让我有机会折腾实践新技术（当然也是挥洒廉价劳动力。。。）。&lt;/p&gt;

&lt;p&gt;感谢协会的一群小伙伴，在技术的路上耳闻目染，间接的影响也让我收益匪浅，而且对计算机领域见识比较广，毕竟协会有搞安全的、渗透的、ios/andriod开发的、java/php/node后端的、linux内核/windows内核的，海阔天空，视野要广，工作后，真的觉得这些影响让我获益良多。更多的是协会精神，对技术的无限热爱。&lt;/p&gt;

&lt;p&gt;还有协会里曾经解我困惑的plusman，廖师兄，小草，这些人都真真切切的帮助过我，解过技术疑惑。还有我的男友兼入门领路人，虽然前端的门带我入的极不专业，但是授人以渔了，教会了我学习，如何在技术的道路上不断学习。&lt;/p&gt;

&lt;p&gt;🍄里的学长XX，XF，都是货真价实的师傅啊，真的进了🍄后，才发现有人可以解惑真的太好了，遇见这2个学长很幸运，因为他们不同于旁人，他们对技术的热爱和追求和协会小伙伴一样一样的，而且是专业的前端，眼光和见解都很专业，不仅授人以渔还能授人以鱼了，有这样的学长走在前面，真的很开心，希望自己别依赖上了。&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;2015，是丰富而刺激的一年，希望未来的我感谢这一年我的所作所为。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>redux使用姿势</title>
   <link href="http://echizen.github.io/tech/2016/01-24-redux"/>
   <updated>2016-01-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/redux</id>
   <content type="html">
&lt;h2 id=&quot;状态管理&quot;&gt;状态管理&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;将关乎全局的状态放到reducer中去管理，组件中的UI状态可以使用组件自己的state来管理&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;关乎全局的状态指：不同组件公用的state，要与后端数据交互的state等。这些state请永远不要在组件内部随意的使用setState的方式来改变，一个组件的对于这种state的改变无法通知给其他组件，每个组件都维护自己的state，虽然可以实现组件内部的状态更新，但是会导致整体的状态混乱。&lt;/p&gt;

&lt;h3 id=&quot;组件可自己管理的状态类型&quot;&gt;组件可自己管理的状态类型&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;与外界状态无关的UI状态&lt;/p&gt;

    &lt;p&gt;但是每个组件与外界状态无关的UI状态可以自己维护，譬如全选按钮。自己setState，然后通过this.state获取。因为这些状态只影响这个UI组件本身，不会影响其他组件，也就没有必要通过redux管理了。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; export default class OrderTable extends Component {
   constructor(props, context) {
     super(props, context);
     this.state = {
       isCompleteShowing: false,
     }
   }
	
     switchShow(){
        this.setState({
        	 isCompleteShowing: !this.state.isCompleteShowing,
        })
     }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;单向的state对UI的影响&lt;/p&gt;

    &lt;p&gt;如果外部的state会影响组件内部的UI展示方式，但是组件内部UI的变更确不会影响外部的状态变更，这种单向的state对UI的影响也可以由组件自己内部管理state。但这时一定要启用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillReceiveProps&lt;/code&gt;来感知外界状态的变更。譬如一个订单处理列表，默认不显示已完成的订单，但是提供按钮供用户选择显示已完成的订单项。这时这个显示不显示的UI state就可以由组件自己管理。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;与其他组件无关的状态&lt;/p&gt;

    &lt;p&gt;这种即使是要与后端接口通信也可以自己维护，但是一定不能与后端接口交互，就是请求后的结果不要对组件内部状态有变更。譬如查询功能，查询的几个条件输入框的状态可以自己维护，我们要做的只是把所有输入框的查询条件组成的state传给action，作为参数，调用数据请求接口，但是请求的返回并不会影响这些输入框的状态。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;组件维护的非redux管理的state的存放位置&quot;&gt;组件维护的非redux管理的state的存放位置&lt;/h2&gt;

&lt;p&gt;比较合理的是放在左右组件组成的功能区，就是放在所有小组件的外部，所有数据段组成的一个完整的实际用处的state的层级。毕竟页面会是组件套组件，几个组件组成一个功能区。&lt;/p&gt;

&lt;p&gt;依旧拿查询的例子。查询模块是一个功能区组件，包括输入框、下拉框等小组件，这时候state应该放在查询功能区，输入框依旧是通过this.props.value的方式获取查询功能区的state参数。&lt;/p&gt;

&lt;h2 id=&quot;组件划分对着ui设计图一层层拆分到最细&quot;&gt;组件划分：对着UI设计图一层层拆分到最细&lt;/h2&gt;

&lt;p&gt;最细的意思是这个组件绝对可以通用了，不会因为外界state与设计不符而不能工作。&lt;/p&gt;

&lt;p&gt;组件拆分的越细腻，可复用性越高，可维护性越好，而且要给未来留路，未来某个页面在组件的props里要新加内容，也要支持。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>react+redux防坑技能</title>
   <link href="http://echizen.github.io/tech/2016/01-24-reactredux"/>
   <updated>2016-01-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/reactredux</id>
   <content type="html">
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;state不会立即变更&lt;/p&gt;

    &lt;p&gt;关于redux下的state变更建议使用react-logger组件。非redux的state变更在debugger时就只能靠console.info了。&lt;/p&gt;

    &lt;p&gt;但是react 的state并不是在调用this.setState后就立即变更的，react也有类似脏检查的机制（具体是什么机制我还没有研究。。。），会在定时器或者其他条件触发后一起更新state。这时想要打印更新后的state可以使用：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; setTimeout(function(){
 	console.log(this.state.param);
 }.bind(this),0);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;依赖外部状态又自建内部状态的组件一定要使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillReceiveProps&lt;/code&gt;来获取外部状态变更信息。&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillReceiveProps&lt;/code&gt;在组件关联的props变化时被调用。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; componentWillReceiveProps(nextProps){
   if(nextProps){
     this.setState({
         value: nextProps.value
     });
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillReceiveProps&lt;/code&gt;在组件第一次渲染时不会调用&lt;/p&gt;

    &lt;p&gt;这句话看似不难理解，其实很容易踩坑。第一次并不只是页面加载渲染时。当你新增了某个数据，这个新增的数据被组件渲染时也不会调用该方法。我曾做过个需求是一个table要求tr数据能够新增，并且新增的tr展示为编辑状态。我通过设置组件内部的state.editing标识是否是编辑状态，通过this.data.id是否为空来判断该数据是否为新增的数据。新增的数据为编辑状态应该在constructor中编写，而不是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;componentWillReceiveProps&lt;/code&gt;中。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;constructor(props, context) { 
     super(props, context);
     this.state = {
       editing: this.props.data.id?false:true,
       data:this.props.data || {}
     }
}
componentWillReceiveProps(nextProps){
   if(nextProps){
       this.setState({
           data:nextProps.data
       });
   }    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;永远不要改变原始的state&lt;/p&gt;

    &lt;p&gt;说起来容易，很容易犯错，你需要对数组和对象赋值是浅拷贝有深刻认识，不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copyObj=obj&lt;/code&gt;，再去改copyObj就能不会影响obj的。&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;对于react管理的state，永远不要在非&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;construcot&lt;/code&gt;函数中使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.state=XXX&lt;/code&gt;这种方式赋值，统一走&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.setState&lt;/code&gt;，这个接口的状态转换才能被react内部各机制接收到变更信息。
        &lt;ul&gt;
          &lt;li&gt;数组：可以采用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice()&lt;/code&gt;拷贝一个新数组使用。&lt;/li&gt;
          &lt;li&gt;对象：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newObj = Object.assign({},this.state.data)&lt;/code&gt;;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;对于redux管理的state，可以在reducer中处理函数里先建一个nextState，在nextState里操作。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let nextState = Object.assign({},this.state)&lt;/code&gt;，在对nextState进行任意想要的操作。
 即使是slice和Object.assign只能将顶层的深拷贝，也就是在内存中分配新的地址，如果所操作的对象的子元素还有复杂类型，这个子元素的指针仍指向之前的引用，改变他们仍然会改变原state。如下：&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/leef-copy.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;所以小心谨慎的保证slice和Object.assign用在最底层数组或对象上。如果功力不深或者觉得麻烦，还是用第三方库如&lt;a href=&quot;https://github.com/facebook/immutable-js/&quot;&gt;immutable-js&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>react+redux使用报告</title>
   <link href="http://echizen.github.io/tech/2016/01-23-reactredux"/>
   <updated>2016-01-23T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2016/reactredux</id>
   <content type="html">
&lt;p&gt;学一个东西先要正三观，本文不涉及具体技术代码，只谈观念。&lt;/p&gt;

&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;

&lt;p&gt;（本人第一次在项目中使用react+redux，理解可能有偏差）&lt;/p&gt;

&lt;p&gt;本周去支援其他组的开发去了，友组一个部门目前只有一个在职前端。。。是一个平台的二期开发，增加了四个功能模块，一期已有3个模块。&lt;/p&gt;

&lt;p&gt;我先了解了他们的项目代码，发现虽然引入了react和redux，但是用的蛮不规范的。react组件拆分不够细，跟业务耦合太强，可复用的太低，结果就是页面长的区别不大，却没有公用组件夹。最可怕的是引入了redux，搭建了基础结构，内部完全没有用redux数据流，没有一个页面redux数据流闭环了，都是setState随时改状态，redux和react组合的优势尽失。&lt;/p&gt;

&lt;p&gt;我不想就这样扭曲的用react和redux，如果引用了一款框架，却没有使用他的优势，那么引入一个新框架折腾另外一种写法的意义何在。所以我非常想用真正的redux数据流来管理state，但是我之前也没有react和redux项目经验，入门门槛又不低，我害怕自己会导致项目延期.&lt;/p&gt;

&lt;p&gt;但是该团队的前端还是按照一期那种不规范的写法在写项目，并且让我也这么做。因为时间很紧，害怕折腾后会延期，而以前的写法虽然不规范，但是快速。但是我默默决定就是加班也要规范的去用。第一天很不顺利，因为刚上手，写写改改，很多情况找不到标准，只有后面踩坑了或者觉得不方便了，才发觉前面的设计不合适，于是又去改之前的。&lt;/p&gt;

&lt;p&gt;打住。。。这样写下去就是开发经历了。。。&lt;/p&gt;

&lt;h2 id=&quot;理解reactredux&quot;&gt;理解react\redux&lt;/h2&gt;

&lt;p&gt;react的入门门槛还可以的，无非是组件拆分，就是要摆正观念，将页面的概念转化成组件的，你要写的是一个个组件，然后页面只是合并合并数个组件。&lt;strong&gt;组件拆分一定要摆脱业务逻辑&lt;/strong&gt;，保证组件能被不同的页面通用。并且&lt;strong&gt;组件拆分的越细越有利于重用&lt;/strong&gt;，这个真的是用过才有体会，最开始demo中一个表单的页面将input、select等都拆成了组件，我很不理解，并且没有按照这个标准去拆分，后来发现没法重用了，每个表单都得自己在写一遍input了，才回头去将input给拆了出来。&lt;/p&gt;

&lt;p&gt;redux是一个数据管理体系，可以回忆一下之前没有redux时，我们可能将一些被重复用到的数据存在localstorage中，再写增查改删等接口去操作数据，现在是redux为我们封装了这些处理数据的方式，并提出一套操作规范，（当然存数据的是变量而不是localstorage）。redux的核心是状态-state。&lt;/p&gt;

&lt;p&gt;数据在store中，展示方式是state，整个项目公用同一个state，保持state的一致性。&lt;/p&gt;

&lt;p&gt;状态的切换全在reducer中进行，reducer一般会定义原始的状态，以及根据action.type划分的state状态切换代码。&lt;/p&gt;

&lt;p&gt;而执行通知状态切换的函数在action中，action通过返回一个包含type的对象来告知reducer中相应的状态改变代码片段执行。&lt;/p&gt;

&lt;p&gt;在此不得不吐槽一下flux，最开始学习时别人建议我先去把flux搞清楚，flux入门很痛苦啊，文档那么简洁，第一次接触的人对于状态在各处跳动处理表示很晕，而且dispather让一切看起来好麻烦，不停发的addEventListenr，感觉flux并没有封装的很彻底，还是让开发者手动调用事件来触发和修改状态，监听状态切换并作出响应，flux更多的是指明了状态切换的开发规范。看了flux后，再看redux，世界都美好了。首先是去除了dispacher这么麻烦的机制，自己封装了接口监听状态转换，让开发者在规范下只需关注状态转换的处理逻辑，而不需要去监听状态转换。其次开发文档真的很详细，写的也比flux通俗易懂多了，开发文档对于初学者真的太重要了。。。&lt;/p&gt;

&lt;p&gt;附：&lt;a href=&quot;http://camsong.github.io/redux-in-chinese/&quot;&gt;redux中文文档&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;reactredux适用性&quot;&gt;react+redux适用性&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;react+redux适合中大型项目&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;redux和react的入门门槛还是不低的，从react来看为了拆分通用组件所要进行的状态传入、用户交互导致状态变更请求的传出都是需要一定的工作量的，特别是对设计合理的要求，一开始组件模式设计的不好，返工就会很多，而这种设计的合理要求一大部分的影响因素是经验，一开始写不能预料到会发生些哪些状况，设计的往往不是很合理。另一方面，为了达到redux数据流闭环，我们在设计和代码上要做很多工作，就是本来可以一步搞定的事，现在要5步的概念。这就决定了react+redux适应中大型项目，项目越大，越体现他的优势，越复杂的项目，前期为了闭环数据流所做的操作所占的比重越小，后期业务代码和状态变更占的比例越大，让前期的操作变得有意义，而且大型项目用了react后状态可控，非常方便维护和二期开发。但是小项目用react+redux就是折腾了，开发效率会降低。&lt;/p&gt;

&lt;p&gt;所以看项目适不适合react+redux，我觉得首先拿出设计图，看一下通用性模块多不多，如果总共也没几个页面，还都长的不怎么一样，即使拆成组件也就是能重用个2次，这种还是别折腾react了。如果你的页面多是展示性的静态页面，就没啥用户交互导致的状态变更，或者只是个一群群的表单提交，也别折腾redux了。&lt;/p&gt;

&lt;h2 id=&quot;解放生产力&quot;&gt;解放生产力&lt;/h2&gt;

&lt;p&gt;每看到一款新的框架，我总会从这个角度去审视他，如果只是玩玩语法糖，花式翻新，却不能解放程序员的生产力，让程序员觉得更麻烦，他就不是好框架。先不讨论react的virtual dom的高效（我也还不了解其实现），就为了按照这个框架的规范去写，就得花费不少的精力。redux更是，为了闭环，一个状态变更要在多个文件里操作。从这个角度看，是增加了麻烦，并不友好。&lt;/p&gt;

&lt;p&gt;但是我觉得react和redux都不能仅仅从这个角度来考虑，要从大项目复杂度来看。如果是一个大项目，组件拆分即使很费劲，但是被重用10次，这种按照框架规范去做的拆分组件消耗的精力就很值得.redux也是为了复杂而生，如果一个页面的多个地方都会同时修改一个state状态，如果不用redux这种机制确实会让状态难以管理，而且redux状态一旦闭环被管理后，是累死一次受益万年的事，以后即使业务有变更，也只用改改reducer中状态切换的业务逻辑代码而已，而且可以轻轻松松给状态添加新的内容。&lt;/p&gt;

&lt;p&gt;然而，react通过state更新组件界面，不需要程序员自己操作dom，这一点也是解放了生产力。&lt;/p&gt;

&lt;p&gt;说到这里不得不提angular，虽说react是view层，但是react+redux已是可以和MVVM并列的开发模式了，angular的双向绑定机制，数据控制视图，真的是无疑解放了程序员的生产力，对程序员非常友好。但是底层对界面刷新的实现据说没有virtual dom高效，复杂时也确实会导致状态混乱。但是小型项目这些都不是问题，依旧推荐angular，作为一个开发者至今怀恋使用angular的高效时光。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;框架本身没有优劣，只有适不适合&lt;/p&gt;
&lt;/blockquote&gt;

</content>
 </entry>
 
 <entry>
   <title>Efficient dom</title>
   <link href="http://echizen.github.io/2015/11-29-efficient-dom"/>
   <updated>2015-11-29T00:00:00+00:00</updated>
   <id>http://echizen.github.io/2015/efficient-dom</id>
   <content type="html">
</content>
 </entry>
 
 <entry>
   <title>使用rem优雅地屏幕适配</title>
   <link href="http://echizen.github.io/tech/2015/10-18-use-rem"/>
   <updated>2015-10-18T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/use-rem</id>
   <content type="html">
&lt;p&gt;可笑了我一个做了1年移动端页面的人，这周才知道有rem这等神物。&lt;/p&gt;

&lt;p&gt;这也是我这周入职最大的收获，公司移动端页面基本都是使用rem,淘宝那边的页面我看了下也是。&lt;/p&gt;

&lt;p&gt;em是相对于父元素的相对计算，rem是相对于根元素html节点的相对计算，且相对值是html元素的font-size值。&lt;/p&gt;

&lt;p&gt;这样优雅的方式就是：针对不同分辨率屏幕设定不同的合适的font-size参考值。然后页面中大多数元素的width、height、font-size甚至margin、padding值都采用rem为单位，这样设计图在不同大小屏幕上都是等比缩放或扩大，做到了真正的显示效果一致。&lt;/p&gt;

&lt;p&gt;而以前做项目都是使用流式布局，在页面布局的时候都是通过百分比来定义宽度，但是高度大都是用px来固定住，所以在大屏幕的手机下显示效果会变成有些页面元素宽度被拉的很长，但是高度还是和原来一样，实际显示非常的不协调。&lt;/p&gt;

&lt;p&gt;且使用rem后页面中大多数元素的尺寸都能定死，这样为元素的定位提供了极大的便利，如垂直居中都变得简单起来。&lt;/p&gt;

&lt;p&gt;但是对于一些属性并不适合rem，如background-position，使用rem会计算不准确，border-width也没必要使用rem。&lt;/p&gt;

&lt;p&gt;html最初的font-size值是由js设置的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var a = document.documentElement
  , b = a.clientWidth &amp;gt; 750 ? 750 : a.clientWidth;
a.style.fontSize = b / 15 + &quot;px&quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;（此博文有待进一步挖掘，写的太浅了。。。）&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>使用伪元素解决 retina屏幕下 border 是非retina屏幕2倍宽度问题</title>
   <link href="http://echizen.github.io/tech/2015/09-25-retina-border"/>
   <updated>2015-09-25T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/retina-border</id>
   <content type="html">
&lt;p&gt;经常要给一个元素画框，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;border-top:1px solid #ddd&lt;/code&gt;的方式，会导致retina屏幕下显示的是2px。这时候可以使用伪元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:before&lt;/code&gt;结合&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background-color&lt;/code&gt;来实现，这时候是不是retina屏幕就没有关系了。&lt;/p&gt;

&lt;p&gt;先看一下效果图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://echizen.github.io/assets/blog-img/QQ20150925-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;每个list都是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;li&amp;gt;
     &amp;lt;div class=&quot;item-content&quot;&amp;gt;
         &amp;lt;div class=&quot;item-inner&quot;&amp;gt;
             &amp;lt;div class=&quot;item-title label&quot;&amp;gt;邮箱&amp;lt;/div&amp;gt;
             &amp;lt;div class=&quot;item-input&quot;&amp;gt;
                 &amp;lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;请填写你的常用邮箱&quot;&amp;gt;
             &amp;lt;/div&amp;gt;
         &amp;lt;/div&amp;gt;
     &amp;lt;/div&amp;gt;
 &amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;css:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.item-inner:after {
    content: &apos;&apos;;
    position: absolute;
    left: 0;
    bottom: 0;
    right: auto;
    top: auto;
    height: 1px;
    width: 100%;
    background-color: #e8e8e8;
    display: block;
    z-index: 15;
    -webkit-transform-origin: 50% 100%;
    transform-origin: 50% 100%;
    -webkit-transform: scaleY(.5);
	transform: scaleY(.5);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种方式其实是利用伪元素创造了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height:1px;background-color: #e8e8e8;&lt;/code&gt;元素，视觉上就是一根线。且不会因为retina屏幕发生变化。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意：应该判断设备类型，当前只有苹果机有retina的机型，只应对苹果机做出如此的处理。安卓机依旧使用border方式。因为这种方式在大多数低端安卓机上，滚动时线条会抖动，安卓对伪元素的支持效果并不是很好&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;小tip:对不同机型做处理可以使用js在入口处做统一判断，然后给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;html&lt;/code&gt;加上class，如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ios&lt;/code&gt;,然后给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ios .item-inner&lt;/code&gt;做出如上处理，这个方式对单页面来说尤为有用，要避免的方式就是在每个页面的js部分判断机型动态添加这个类的傻方式。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>使用transclude在directive中获取子元素的控制权</title>
   <link href="http://echizen.github.io/tech/2015/09-25-isolate-scope"/>
   <updated>2015-09-25T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/isolate-scope</id>
   <content type="html">
&lt;p&gt;有这样一段html:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div wx-upload-img attachment=&quot;goAuth.pageObject.dataGroup.image&quot; img-status-tip=&quot;&quot; class=&quot;item item-icon-left mf_common_a flex flex_between&quot; &amp;gt;
   &amp;lt;div class=&quot;flex_item_3&quot;&amp;gt;证件审核&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;flex_item_6 align_right&quot; ng-click=&quot;addImg()&quot; &amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里有一种需求，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wx-upload-img&lt;/code&gt;是一个被多处复用的directive，他调用了微信上传图片的接口，但是每个复用他的地方ui都不一样，所以在这个directive中我们只控制功能，不控制ui，但是与它相关的ui元素需要绑定它提供的事件，使用它定义的元素。这样我们就要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wx-upload-img&lt;/code&gt;中获取它的子元素的控制权。&lt;/p&gt;

&lt;p&gt;解决方案是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transclude&lt;/code&gt;属性，获取isolate scope。&lt;/p&gt;

&lt;p&gt;关键代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;transclude(scope, function(clone, scope) {
    elm.append(clone);
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完整示例：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tool.directive(&apos;wxUploadImg&apos;,[&apos;wxUploadImg&apos;,&apos;PGgetImage&apos;,function(wxUploadImg,PGgetImage){
  return {
    restrict: &apos;EA&apos;,
    replace: false,
    scope: {
      attachment : &quot;=&quot;,
      imgStatusTip: &quot;@&quot;,
    },
    transclude:true,
    link: function(scope, elm, attrs, ctrl ,transclude){
      // 微信配置
      /**
       * 微信浏览器判断
       * @return {Boolean} 
       */
      var isWechat =function(){
          var ua = navigator.userAgent.toLowerCase();
          if(ua.match(/MicroMessenger/i)==&quot;micromessenger&quot;) {
              return true;
          } else {
              return false;
          }
      };
	      
      // 微信上传图片
      var wxAddImg = function(){
      	//...
      };

	  // app上传图片
      var appAddImg = function(){
        //...
      };

      var saveImgToServer = function(url){
        //...
      };
  
      if(isWechat()){
        scope.addImg = wxAddImg;
      }else{
        scope.addImg = appAddImg;
      }
      
      transclude(scope, function(clone, scope) {
        elm.append(clone);
      });
    }
  };
}]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>向directive中外部绑定的函数传递参数</title>
   <link href="http://echizen.github.io/tech/2015/09-25-directive-function-param"/>
   <updated>2015-09-25T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/directive-function-param</id>
   <content type="html">
&lt;p&gt;适用情景：controller中定义的函数，通过属性传递绑定到directive的scope上，在directive内部处理后调用函数并传递参数。&lt;/p&gt;

&lt;p&gt;示例：&lt;/p&gt;

&lt;p&gt;controller:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$scope.transferImgData = function(imageData){
  console.log(imageData);
  if(imageData.filePath === &apos;&apos;){
    SweetAlert.warning(&quot;请选择资讯图片，否则将使用默认资讯图片&quot;,&quot;&quot;);
  }else{
    feedbackMsg.uploadingImgMsg();

    imgUpload.post({},imageData,function(response) {
      
      if(response.code === 0){
        $scope.news.coverImage = response.data.fileUrl;
        feedbackMsg.uploadImgSuccessMsg();
      }else{
        console.log(response);
        SweetAlert.swal(response.message,&quot;&quot;, &quot;error&quot;);
      }
    });
  }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;view:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;upload-crop-img upload-imgdata=&quot;transferImgData&quot;&amp;gt;&amp;lt;/upload-crop-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;directive:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 图片上传裁剪处理插件
plugin.directive(&apos;uploadCropImg&apos;,[function() {

  return {
    restrict: &apos;E&apos;,
    replace: true,
    scope: {
      uploadImgdata : &quot;&amp;amp;&quot;
    },
    template: &apos;...&apos;,
    link: function (scope, element) {
    
      scope.uploadImg = function(){
      	    //要传输的图片数据
            var imgData = {
              &apos;filePath&apos;:$(&apos;.cropit-image-input&apos;).val(),
              &apos;base64ImgData&apos;:$(&apos;.image-cropper&apos;).cropit(&apos;export&apos;)
            };

            // 执行传输图片数据的函数（由外部controller定义的函数）
            scope.uploadImgdata()(imgData);
      };
    }
  };
}]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt;操作符保证了scope.uploadImgdata指代一个函数，scope.uploadImgdata()才是对这个函数的引用，等于controller中的$scope.transferImgData，然后需要传递参数。这里容易误解然后写成了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope.uploadImgdata(imgData)&lt;/code&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>jquery中props和attr的不同</title>
   <link href="http://echizen.github.io/tech/2015/09-24-props-and-attr"/>
   <updated>2015-09-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/props-and-attr</id>
   <content type="html">
&lt;p&gt;又是一段long long time没写博客了，要写的东西又堆积如山了。。。&lt;/p&gt;

&lt;p&gt;我们经常会有在代码中改变checkbox的checked属性的需求，如全选这种，也有要及时获取checkbox的checked属性值的需求。可能会遇到：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;checkbox&quot; /&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
    $(&apos;#checkbox&apos;).click(function() {
        console.log($(this).attr(&apos;checked&apos;));
        console.log($(this).prop(&apos;checked&apos;));
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;点击checkbox,你会发现打印的是：undefined  、 true&lt;/p&gt;

&lt;p&gt;再点击checkbox：  undefined  、 false。&lt;/p&gt;

&lt;p&gt;可以看出，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attr&lt;/code&gt;取出的是checkbox一开始的checked属性的值，不会及时改变，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prop&lt;/code&gt;取得是动态值，会随着checkbox状态改变。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;具有 true 和 false 两个值的属性，如 checked, selected 或者 disabled 使用prop()获取值，其他的使用 attr()&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;再来看看jquery源码中这2个函数的实现方式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;prop:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;return elem[ name ];
eg:document.getElementById(&apos;checkbox&apos;).checked = true||false。这是原生js提供的接口。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;attr:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; ret = jQuery.find.attr( elem, name );
 // Non-existent attributes return null, we normalize to undefined
 return ret == null ? undefined : ret;
 elem.getAttribute( name )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;看本质还是js的getAttribute方法取到的是初始值，而dom api中elem.attr方法取到的是及时值，会在调用时去扫描elem的attr及时的值。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>网页跳转app逻辑</title>
   <link href="http://echizen.github.io/tech/2015/09-24-jump-to-app"/>
   <updated>2015-09-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/jump-to-app</id>
   <content type="html">
&lt;p&gt;经常会有从网页跳到app的需求，这时会有2种情况，app已下载能跳转，或者app未下载不能跳转/app已下载但因为某种原因跳转失败。这时用户怎么面对这个不成功的页面呢？&lt;/p&gt;

&lt;p&gt;其实跳转app，是app中设置的一种协议，根据协议跳转，跳转后就不知道接下来的状态了，不会有跳转失败或者跳转成功的回调。而且跳转到app的时间也是不确定的。我们就只能设置一段合适的时间，这段时间后如果用户还停留在当前页，没有跳转到app，就认定他跳转失败，将页面导到设计的某个合适的页面，如下载页面。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   var toUrl = &apos;hw.tech.JuHappy://enterMeeting/&apos; + query.meetingId;
   location.href = toUrl;

   var downloadApp = setTimeout(function(){
      location.href = &quot;http://dwz.cn/Ebiv9&quot;;
   },1000);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>在angular 中 使用jquery插件</title>
   <link href="http://echizen.github.io/tech/2015/09-24-angular-jquery-plugin"/>
   <updated>2015-09-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/angular-jquery-plugin</id>
   <content type="html">
&lt;p&gt;由于jquery的对dom处理的强大和悠久深入人心的历史，很多绚丽的效果不用jquery插件还真是麻烦，不过现在已经有很多插件都有专门的angularjs版本了。但是对于没有的，就只能自己包装一下了。其实不包装也是可以的，因为jquery本身就是全局的，变量都直接全局暴露了。但是在angular的管理和生命周期下，强烈觉得还是应该封装一下，让所有的页面元素都能被angular的体系所管理。&lt;/p&gt;

&lt;p&gt;以我最喜欢的一款图片裁剪和伸缩放的插件&lt;a href=&quot;http://scottcheng.github.io/cropit/&quot;&gt;cropit&lt;/a&gt;为例。&lt;/p&gt;

&lt;p&gt;在普通环境下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;image-cropper&quot;&amp;gt;
  &amp;lt;div class=&quot;cropit-image-preview-container&quot;&amp;gt;
    &amp;lt;div class=&quot;cropit-image-preview&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  
  &amp;lt;input type=&quot;range&quot; class=&quot;cropit-image-zoom-input&quot; /&amp;gt;
  
  &amp;lt;input type=&quot;file&quot; class=&quot;cropit-image-input&quot; /&amp;gt;
  &amp;lt;div class=&quot;select-image-btn&quot;&amp;gt;Select new image&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
	$(&apos;#image-cropper&apos;).cropit({
	  imageBackground: true,
	  imageBackgroundBorderWidth: 15 // Width of background border
	});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在angular中这么用也没问题，但是会有卡顿。不是很清楚angular渲染和这个插件渲染的执行顺序。&lt;/p&gt;

&lt;p&gt;插件可以封装成directive，以备复用。&lt;/p&gt;

&lt;p&gt;直接摘抄我的代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var plugin = angular.module(&apos;jqueryPluginDirective&apos;, []);

// 图片上传裁剪处理插件
plugin.directive(&apos;uploadCropImg&apos;,[function() {

  return {
    restrict: &apos;E&apos;,
    replace: true,
    scope: {
      cropitConfig: &quot;=&quot;,
      uploadImgdata : &quot;&amp;amp;&quot;
    },
    template: &apos;&amp;lt;div class=&quot;image-cropper clear-padding&quot; id=&quot;coverImg&quot; style=&quot;margin-left:24px;&quot;&amp;gt;&apos;+
                  &apos;&amp;lt;div class=&quot;cropit-image-preview-container&quot; &amp;gt;&apos;+
                      &apos;&amp;lt;div class=&quot;cropit-image-preview&quot; style=&quot;width: 540px;height: 300px;&quot;&amp;gt;&amp;lt;/div&amp;gt;&apos;+
                  &apos;&amp;lt;/div&amp;gt;&apos;+
                    
                  &apos;&amp;lt;div class=&quot;slider-wrapper&quot; style=&quot;min-height: 30px;padding-top: 10px;&quot;&amp;gt;&apos;+
                      &apos;&amp;lt;span class=&quot;glyphicon glyphicon-picture&quot; style=&quot;font-size:14px;&quot;&amp;gt;&amp;lt;/span&amp;gt;&apos;+
                      &apos;&amp;lt;input type=&quot;range&quot; class=&quot;cropit-image-zoom-input custom&quot; min=&quot;0&quot; max=&quot;1&quot; step=&quot;0.01&quot;&amp;gt;&apos;+
                      &apos;&amp;lt;span class=&quot;glyphicon glyphicon-picture&quot; style=&quot;font-size:18px;&quot;&amp;gt;&amp;lt;/span&amp;gt;&apos;+
                  &apos;&amp;lt;/div&amp;gt;&apos;+
                    
                  &apos;&amp;lt;input type=&quot;file&quot; class=&quot;cropit-image-input&quot; id=&quot;trueChoseImg&quot;/&amp;gt;&apos;+

                  &apos;&amp;lt;button class=&quot;btn btn-primary select-image-btn&quot; type=&quot;button&quot; ng-click=&quot;choseImg()&quot;&amp;gt;&amp;lt;span class=&quot;glyphicon glyphicon-picture&quot;&amp;gt;&amp;lt;/span&amp;gt;选择图片&amp;lt;/button&amp;gt;&apos;+

                  &apos;&amp;lt;button class=&quot;btn btn-success js_upload_img&quot; type=&quot;button&quot; ng-click=&quot;uploadImg()&quot;&amp;gt;&amp;lt;span class=&quot;glyphicon glyphicon-cloud&quot; &amp;gt;&amp;lt;/span&amp;gt;上传图片&amp;lt;/button&amp;gt;&apos;+
                &apos;&amp;lt;/div&amp;gt;&apos;,
    link: function (scope, element) {
      $(element).cropit(scope.cropitConfig);
      scope.choseImg = function(){
          $(&apos;#trueChoseImg&apos;).trigger(&apos;click&apos;);
        };
      scope.uploadImg = function(){
		//...
      };
    }
  };
}]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用时：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;upload-crop-img upload-imgdata=&quot;transferImgData&quot; cropit-config = &quot;cropitConfig&quot;&amp;gt;&amp;lt;/upload-crop-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看出做了3件事：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;将html部分放到template里。&lt;/li&gt;
  &lt;li&gt;将配置通过调用处的属性传入，通过设置scope接受。&lt;/li&gt;
  &lt;li&gt;在link函数中初始化插件，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(element).cropit(scope.cropitConfig);&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>angularjs 中 controller as 引入的意义</title>
   <link href="http://echizen.github.io/tech/2015/09-24-and-this"/>
   <updated>2015-09-24T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/and-this</id>
   <content type="html">
&lt;p&gt;这又是一篇历史遗留博文。&lt;/p&gt;

&lt;p&gt;去年学习angularjs时controller用的都是$scope，在$scope下进行数据双向绑定，那时的angular版本号已记不清了，今年做项目时（angular v1.3.13），看到官网文档里的用法是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ng-controller = &quot;controllerName as XXX&quot;&lt;/code&gt;，然后controller.js中用了类似下面的代码:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var vm = this;
this.avalue = &apos;XXX&apos;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;觉得好奇为什么会引入这种用法，于是研究了一下。&lt;/p&gt;

&lt;h2 id=&quot;scope和this究竟是什么&quot;&gt;$scope和this究竟是什么&lt;/h2&gt;

&lt;p&gt;v1.2.0之前的angular:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// &amp;lt;div ng-controller=&quot;MainCtrl&quot;&amp;gt;&amp;lt;/div&amp;gt;
app.controller(&apos;MainCtrl&apos;, function ($scope) {
  $scope.title = &apos;Some title&apos;;
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;v1.2.0之后的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;app.controller(&apos;MainCtrl&apos;, function () {
  var vm = this;
  vm.title = &apos;Some title&apos;;
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;让我们回忆一下js中的构造函数和实例，也就是“类”：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var myClass = function () {
  this.title = &apos;Class title&apos;;
}
var myInstance = new myClass();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;现在我们可以使用myInstance示例来访问myClass的属性和方法。&lt;/p&gt;

&lt;p&gt;在angular中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// we declare as usual, just using the `this` Object instead of `$scope`
app.controller(&apos;MainCtrl&apos;, function () {
  var vm = this;
  vm.title = &apos;Some title&apos;;
});

&amp;lt;div ng-controller=&quot;MainCtrl as main&quot;&amp;gt;
  // MainCtrl doesn&apos;t exist, we get the `main` instance only
  {{ main.title }}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.controller(&apos;MainCtrl&apos;,function(){})&lt;/code&gt;就是在定义一个名称为MainCtrl的构造函数，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainCtrl as main&lt;/code&gt;就是在实例化，生成MainCtrl的实例main，然后就可以在main中访问MainCtrl里定义的变量和函数。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;但是，Controller as的语法是将controller绑定了当前的$scope，而不是都绑向同一个$scope变量。&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;注意事项&quot;&gt;注意事项&lt;/h2&gt;

&lt;p&gt;一些scope的监听器和方法，例如：$watch, $broadcast, $on等，只能在$scope下调用。&lt;/p&gt;

&lt;h2 id=&quot;controller-as应用方式&quot;&gt;controller as应用方式&lt;/h2&gt;

&lt;p&gt;除上面的方式外，还有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;directive:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  app.directive(&apos;myDirective&apos;, function () {
    return {
      restrict: &apos;EA&apos;,
      replace: true,
      scope: true,
      template: [].join(&apos;&apos;),
      controllerAs: &apos;&apos;, // woohoo, nice and easy!
      controller: function () {}, // we&apos;ll instantiate this controller &quot;as&quot; the above name
      link: function () {}
    };
  });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;$routeProvide&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  app.config(function ($routeProvider) {
    $routeProvider
    .when(&apos;/&apos;, {
      templateUrl: &apos;views/main.html&apos;,
      controllerAs: &apos;&apos;,
      controller: &apos;&apos;
    })
    .otherwise({
      redirectTo: &apos;/&apos;
    });
  });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;ui-rooter&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  app.config(function ($routeProvider) {
     $stateProvider
     .state(&apos;tab.history&apos;, {
       url: &apos;/history&apos;,
       views:{
         &apos;personInfo&apos;:{
           templateUrl: &apos;history.html&apos;,
           controller: &apos;historyCtrl as history&apos;
         }
       }
     })
  })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;controller-as让继承关系的变量之间更清晰可读&quot;&gt;controller as让继承关系的变量之间更清晰可读&lt;/h2&gt;

&lt;p&gt;感受下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;老版引用同名的各自的变量：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;div ng-controller=&quot;MainCtrl&quot;&amp;gt;
    {{ title }}
    &amp;lt;div ng-controller=&quot;AnotherCtrl&quot;&amp;gt;
      {{ title }}
      &amp;lt;div ng-controller=&quot;YetAnotherCtrl&quot;&amp;gt;
        {{ title }}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;新版引用同名的各自的变量：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;div ng-controller=&quot;MainCtrl as main&quot;&amp;gt;
    {{ main.title }}
    &amp;lt;div ng-controller=&quot;AnotherCtrl as another&quot;&amp;gt;
      {{ another.title }}
      &amp;lt;div ng-controller=&quot;YetAnotherCtrl as yet&quot;&amp;gt;
        {{ yet.title }}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在感受下&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;老版引用父变量&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;div ng-controller=&quot;MainCtrl&quot;&amp;gt;
    {{ title }}
    &amp;lt;div ng-controller=&quot;AnotherCtrl&quot;&amp;gt;
      Scope title: {{ title }}
      Parent title: {{ $parent.title }}
      &amp;lt;div ng-controller=&quot;YetAnotherCtrl&quot;&amp;gt;
        {{ title }}
        Parent title: {{ $parent.title }}
        Parent parent title: {{ $parent.$parent.title }}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;新版引用父变量&lt;/p&gt;

    &lt;div ng-controller=&quot;MainCtrl as main&quot;&gt;
    {{ main.title }}
    &lt;div ng-controller=&quot;AnotherCtrl as another&quot;&gt;
      Scope title: {{ another.title }}
      Parent title: {{ main.title }}
      &lt;div ng-controller=&quot;YetAnotherCtrl as yet&quot;&gt;
        Scope title: {{ yet.title }}
        Parent title: {{ another.title }}
        Parent parent title: {{ main.title }}
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;controller-as-方式解决了父子scope带来的混乱&quot;&gt;controller as 方式解决了父子$scope带来的混乱&lt;/h2&gt;

&lt;p&gt;具体问题可以看&lt;a href=&quot;http://codetunnel.io/angularjs-controller-as-or-scope/&quot;&gt;AngularJS: “Controller as” or “$scope”?&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;$scope方式下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div ng-controller=&quot;ParentController&quot;&amp;gt;  
  ParentController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;foo&quot; /&amp;gt;
  &amp;lt;div ng-controller=&quot;ChildController&quot;&amp;gt;
    ChildController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;foo&quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt; 

&amp;lt;script&amp;gt;
	app  
	  .controller(&apos;ParentController&apos;, function ($scope) {
	    $scope.foo = &quot;bar&quot;;
	  })
	  .controller(&apos;ChildController&apos;, function ($scope) { /*empty*/ });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;会产生父变字变，子边父不变的现象。这是由于原型链的缘故，当一个对象的原型链上有属性foo，你再给这个对象赋上一个属性foo，并不会它本来原型链上的属性foo。只是新建的一个foo，在原型链的更近端，访问这个属性时不会在去寻找更远处的foo属性。&lt;/p&gt;

&lt;p&gt;这就相当于：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var obj1 = {  
  someProp: &apos;obj1 property!&apos;,
  someMethod: function () {
    alert(&apos;obj1 method!&apos;);
  }
};
var obj2 = Object.create(obj1);  
obj2.someProp = &apos;obj2 property!&apos;;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因此在父子继承关系下的属性必须明确指出对象已防止混乱。譬如我们要访问父对象的foo属性应该：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div ng-controller=&quot;ParentController&quot;&amp;gt;  
  ParentController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;foo&quot; /&amp;gt;
  &amp;lt;div ng-controller=&quot;ChildController&quot;&amp;gt;
    ChildController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;$parent.foo&quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
	app  
	  .controller(&apos;ParentController&apos;, function ($scope) {
	    $scope.foo = &quot;bar&quot;;
	  })
	  .controller(&apos;ChildController&apos;, function ($scope) { /*empty*/ });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也可以借助对象传引用的方式来变态实现（个人觉得这个方法实在是曲线救国，但是controller as正是利用了这个原理）：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div ng-controller=&quot;ParentController&quot;&amp;gt;  
  ParentController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;obj.foo&quot; /&amp;gt;
  &amp;lt;div ng-controller=&quot;ChildController&quot;&amp;gt;
    ChildController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;obj.foo&quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt; 

&amp;lt;script&amp;gt;
	app  
	  .controller(&apos;ParentController&apos;, function ($scope) {
	    $scope.obj = {
	      foo: &quot;bar&quot;
	    };
	  })
	  .controller(&apos;ChildController&apos;, function ($scope) { /*empty*/ });
&amp;lt;/script&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用controller as方式可以简化代码，让关系更清晰：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div ng-controller=&quot;ParentController as parent&quot;&amp;gt;  
  ParentController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;parent.foo&quot; /&amp;gt;
  parent.foo: {{ parent.foo }}
  &amp;lt;div ng-controller=&quot;ChildController as child&quot;&amp;gt;
    ChildController: &amp;lt;input type=&quot;text&quot; ng-model=&quot;parent.foo&quot; /&amp;gt;
    parent.foo: {{ parent.foo }}
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt; 

&amp;lt;script&amp;gt;
	app  
	  .controller(&apos;ParentController&apos;, function () {
	    this.foo = &quot;bar&quot;;
	  })
	  .controller(&apos;ChildController&apos;, function () { /*empty*/ });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;controller-as的实质&quot;&gt;controller as的实质&lt;/h2&gt;

&lt;p&gt;上面的代码已经可以推测实质了，controller as只是语法糖，&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;app.controller(&apos;MyController&apos;, function () {  
  this.someValue = &quot;Hello!&quot;;
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;等价于：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;app.controller(&apos;MyController&apos;, function ($scope) {  
  $scope.myController = this;
  this.someValue = &quot;Hello!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;除了在父子contrllor之间，controllerAs的写法能比较方便的切换父子对象，其他的只要遵守不把变量直接挂在$scope上，而是挂在$scope的对象上，那么继续用$scope也未尝不可，就是代码量上多一些
objCtrl as obj和this.namej和this.namej和this.name = ‘lili’也是angularjs自己内部创建了obj的对象挂在$scope上，即：$scope.obj = { name=’lili’ }&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;以上是我初次接触controller as时的想法，当初并不当回事。但是后来我将所有业务代码都用了controller as方式，语法糖也是很必要的，当代码量大的时候，语法糖节省的是你的时间，提高的是你的效率，理清的是你的思路。&lt;/p&gt;

&lt;h2 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://toddmotto.com/digging-into-angulars-controller-as-syntax/&quot;&gt;Digging into Angular’s “Controller as” syntax&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://codetunnel.io/angularjs-controller-as-or-scope/&quot;&gt;AngularJS: “Controller as” or “$scope”?&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>谈谈学习</title>
   <link href="http://echizen.github.io/think/2015/09-09-learn-framework"/>
   <updated>2015-09-09T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2015/learn-framework</id>
   <content type="html">
&lt;p&gt;一个程序员的学习能力、接受新事物的能力绝对是非常重要的影响人的发展的一个因素，特别是前端程序员。最近有看一本2011年的书《JavaScript DOM编程艺术》，目的只是想熟悉一下自己的短板-原生js dom操作，大家都推荐这本。看了100多页的感觉就是看历史书一样，那个年代不是所有浏览器都支持javascript，兼容性多是指如何在不支持js的浏览器上优雅退化，绑事件还都是onEvent的方式…3年过去了，变化太大了，导致3年前的书都没法看下去了。&lt;/p&gt;

&lt;p&gt;新兴的东西更是多，先不说html5 API中如webSocket、canvas之类以及css3的动画（前一阵看到张鑫旭老师1年前用css3写出了轮播图，非常流畅），nodejs的兴起带来了前端的模块化，AMD、CMD模式，requirejs、seajs的代表库。手机的兴起带来了单页面应用和web app，backbonejs,angularjs,reactjs这些框架/库更是与传统的库差别很大，刚学时可能无法适从，因为他们不仅是语法糖的实现，更是编程思想的变格。开发上从纯手工化到工具的兴起，bower、gulp、grunt等等等。小小的前端和js从写页面到nodejs写后台到react native写app。我相信还有很大的一部分前端没有跟上这些发展。&lt;/p&gt;

&lt;p&gt;最近在知乎上看到徐飞老师的一句话：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这几年我看到不少人，从Backbone追到Angular，又追到React，每次出一个比较新的东西都立刻追进，你问他：这个好在哪里？他从网上复制几句话，然后语焉不详，再追问下去，就是：反正我用的东西就是好。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;说中了多少 少年，还是积极向上热爱学习的少年，估计还有一大部分少年这3个都没接触。&lt;/p&gt;

&lt;p&gt;最近我也在学习react，初衷很简单，太火了，我想看看这么火的东西的先进之处。我很乐于追求新东西，倒不是赶时髦，新东西总是有它的适用场景和先进之处，我总是想了解这些先进的技术和思想。&lt;/p&gt;

&lt;p&gt;对于前端新库和框架的学习，我都是首先看他的设计思想和设计模式，关注适用场景。设计思想体现的是每个库的特色和存在的必要性，然后你再学习具体的api和demo时，你会发现就是他的原始的设计思想指导着你去这么用它。理解了设计思想，你才能它适用与什么场景，该怎么用它。有个和我一起用angular写项目的学弟，看到他的代码确实有点后悔没早点让他研究思想的，因为对于用户操作带来页面的动态内容时，他还是从dom操作触发，而不是从数据源出发，这样人家辛辛苦苦搞出的双向绑定却没被正确用上。&lt;/p&gt;

&lt;p&gt;由于技术的发展，规范的变迁，浏览器环境的变迁，一个框架和库可能被淘汰被取代，但是那些背后的设计思想将很久都不会落后。&lt;/p&gt;

&lt;p&gt;我理解的前端说到底，首先是底子够硬，能徒手写的一手优雅的js，而不要闹出那么多笑话：&lt;a href=&quot;http://www.zhihu.com/question/30776912&quot;&gt;你在公司项目里面看到过哪些操蛋的代码&lt;/a&gt;，能使用最恰当的css。然后可能一开始我们都是使用别人的库，这并没有什么坏处，但是不能只是学学api，知道怎么写，这样库和框架一直在翻新，你每天都像重头学习一样。你要先了解其设计模式和思想，还要又能力透过封装好的api看出底层的js实现的到底是什么，你可以推测，可以查网上大牛们的研究结论，最好的是自己看源码（看源码方面我很汗颜，几乎没看过。。。能力还不够，看的吃力）。&lt;/p&gt;

&lt;p&gt;用过framework7，学到的是mvc分层的思想，每一层都专注于自己的工作，不混搭，配合requirejs，让依赖关系变得清晰，且再也不污染全局变量了。&lt;/p&gt;

&lt;p&gt;用过angular，学到的是mvvm，viewmodel是个神奇的存在，双向绑定带来的是福音，因为dom操作是耗性能的，而大多数前端并没有掌握高效操作dom的技能。双向绑定让我们的关注点从dom移到了数据源上，同样的譬如说评论区添加了一条评论，以前我们想的是动态添加一段包函数据的dom进页面，即使用framework7也顶多只能暴力的重新渲染一遍添加了新数据的模板。而angular让我们把注意力从视图和dom中移出，dom依赖数据的动态更新框架帮我们做了，而我们应该相信写angular的人是有能力高效操作dom的。我们只要向表示评论的数据变量里添加一条新数据，视图就自动更新添加了一条新评论。然后angular的contoller、service、directive、filter更是各司其职，高效组合。contoller负责主要的业务代码，组合其他部分，这是每个页面独有的，service是单例，负责如与后台数据交互和一些只与js相关的可复用的功能性代码。而directive是angular核心，每个directive都像是一个组件，包含相关的html和js，复用性特别高，你随时可以拿它到其他项目中组合，所需的代码量非常小，仅仅是加个依赖。filter是过滤器，你可以为你项目中很特色的转换写filter。还有依赖注入这个特色，不仅防止了全局变量污染，也是用一种不同于requirejs的方式，让依赖变得清晰，每段代码只需要自己依赖的模块。angular带给我最深的是知道什么是组建，如何复用，还有从立足于数据而不是dom。&lt;/p&gt;

&lt;p&gt;最近刚看react，说不出什么高大上的东西。他不同于前面2个，它有点类似与angular的directive部分，强调的是组件化，而且不同于angular以功能区分代码，而是以功能界面划分代码块，一个页面会被划分成很多个组件，最难得是你有划分好的组件的思想，每个组件要能复用，只关注自己独立的功能。组件和组件之间循环嵌套，以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.props&lt;/code&gt;传递变量，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.ref&lt;/code&gt;传递标签属性。最有特色的是state，react首创了单向的状态机，每个state下的变量都在整个组件中统一，且是单向绑定，目的是为了避免多处操作它带来的状态混乱（譬如angular需要$apply和$digest来解决），组件之间通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.state&lt;/code&gt;传递状态变量。还有一个特色是virtual DOM，据说非常高效，因为他没有直接操作dom，操作的是virtual DOM，而virtual DOM存储在与内存中，速度非常快，每次数据变化都是刷新virtual DOM,然后跟上一版的virtual DOM对比，在通过diff算法只映射变化的dom部分到真正的dom树上，而且jsx允许在node服务器渲染，这既解决了SEO的问题，也减少了浏览器渲染的压力。这个模式是angular没有的。我觉得非常新颖。至于组件化，暂时的感受是特别繁琐，因为拆分的太多，嵌套的太多，而且我对于这种牵涉到state、props的组件能够有多大的复用可能和每个页面拆的那么多的组件的复用需求表示怀疑。react的语法糖开始也会有点不习惯。相比angular，由于react只是在视图层，且不是真正的框架，而是一种模式，学习起来成本比较高。以上感悟仅是2天学习后的感悟，可能不准确。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;对于新事物，关注设计思想和模式。&lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>从sizzle源码看选择器性能</title>
   <link href="http://echizen.github.io/tech/2015/08-21-juery-selector-efficiency"/>
   <updated>2015-08-21T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/juery-selector-efficiency</id>
   <content type="html">
&lt;p&gt;本来只是好奇getElementsByClassName兼容性解决方案，结果又是一脚踩了深水泥潭，一路看到了性能问题。&lt;/p&gt;

&lt;p&gt;以我的经验和见识还没有资格谈性能问题，以下内容多是从网上看到的大牛们的分析，结合了自己的见解和对源码执行顺序的跟踪对这些观点的验证，更是一份笔记。&lt;/p&gt;

&lt;p&gt;所谓jquery选择器，其实大多数是css选择器的包装。&lt;/p&gt;

&lt;h2 id=&quot;html渲染解析过程&quot;&gt;html渲染解析过程&lt;/h2&gt;

&lt;p&gt;浏览器从下载文档到显示页面的过程是个复杂的过程，这里包含了重绘和重排。各家浏览器引擎的工作原理略有差别，但也有一定规则。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;简单讲，通常在文档初次加载时，浏览器引擎会解析HTML文档来构建DOM树，之后根据DOM元素的几何属性构建一棵用于渲染的树。渲染树的每个节点都有大小和边距等属性，类似于盒子模型（由于隐藏元素不需要显示，渲染树中并不包含DOM树中隐藏的元素）。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;当渲染树构建完成后，浏览器就可以将元素放置到正确的位置了，再根据渲染树节点的样式属性绘制出页面。由于浏览器的流布局，对渲染树的计算通常只需要遍历一次就可以完成&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;为什么排版引擎解析-css-选择器时一定要从右往左解析&quot;&gt;为什么排版引擎解析 CSS 选择器时一定要从右往左解析&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;HTML 经过解析生成 DOM Tree（这个我们比较熟悉）；而在 CSS 解析完毕后，需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree，最终用来进行绘图。Render Tree 中的元素（WebKit 中称为「renderers」，Firefox 下为「frames」）与 DOM 元素相对应，但非一一对应：一个 DOM 元素可能会对应多个 renderer，如文本折行后，不同的「行」会成为 render tree 种不同的 renderer。也有的 DOM 元素被 Render Tree 完全无视，比如 display:none 的元素。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在建立 Render Tree 时（WebKit 中的「Attachment」过程），浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果（Style Rules）来确定生成怎样的 renderer。对于每个 DOM 元素，必须在所有 Style Rules 中找到符合的 selector 并将对应的规则进行合并。选择器的「解析」实际是在这里执行的，在遍历 DOM Tree 时，从 Style Rules 中去寻找对应的 selector。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;因为所有样式规则可能数量很大，而且绝大多数不会匹配到当前的 DOM 元素（因为数量很大所以一般会建立规则索引树），所以有一个快速的方法来判断「这个 selector 不匹配当前元素」就是极其重要的。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果正向解析，例如「div div p em」，我们首先就要检查当前元素到 html 的整条路径，找到最上层的 div，再往下找，如果遇到不匹配就必须回到最上层那个 div，往下再去匹配选择器中的第一个 div，回溯若干次才能确定匹配与否，效率很低。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;逆向匹配则不同，如果当前的 DOM 元素是 div，而不是 selector 最后的 em，那只要一步就能排除。只有在匹配时，才会不断向上找父节点进行验证。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;但因为匹配的情况远远低于不匹配的情况，所以逆向匹配带来的优势是巨大的。同时我们也能够看出，在选择器结尾加上「*」就大大降低了这种优势，这也就是很多优化原则提到的尽量避免在选择器末尾添加通配符的原因。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我的理解： 按照标准css选择器效率的几个方面：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;层级越少效率越高&lt;/li&gt;
  &lt;li&gt;下一层应该比上一层更精确&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;依照这个标准，可以推测css选择器算法的大概模型，也就知道上面的解困是对的。&lt;/p&gt;

&lt;p&gt;css会先读最右边，左边的层级是为了验证右边的层并筛选。&lt;/p&gt;

&lt;p&gt;按照css选择器规范，一个良好的css选择器可以长这样： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div .class&lt;/code&gt;，而不应该是这样&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.class div&lt;/code&gt;。
第一种情况下，具体的.class元素节点少于div节点的概率远远大于相反情况。所以对于符合css规范的选择器，从右向左读显然更友好。&lt;/p&gt;

&lt;h2 id=&quot;sizzle的词法解析&quot;&gt;sizzle的词法解析&lt;/h2&gt;

&lt;p&gt;所谓词法解析，就是将用户传进来的一个复杂的选择器&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(selector)&lt;/code&gt;中的selector拆分成一个个简单的元素，并匹配类型，这样才能遍历寻找。&lt;/p&gt;

&lt;p&gt;sizzle中有一个叫做tokenize的函数就是干这个的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + &quot; &quot; ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || (match = rcomma.exec( soFar )) ) {
			if ( match ) {
				// Don&apos;t consume trailing commas as valid
				soFar = soFar.slice( match[0].length ) || soFar;
			}
			groups.push( (tokens = []) );
		}

		matched = false;

		// Combinators
		if ( (match = rcombinators.exec( soFar )) ) {
			matched = match.shift();
			tokens.push({
				value: matched,
				// Cast descendant combinators to space
				type: match[0].replace( rtrim, &quot; &quot; )
			});
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( (match = matchExpr[ type ].exec( soFar )) &amp;amp;&amp;amp; (!preFilters[ type ] ||
				(match = preFilters[ type ]( match ))) ) {
				matched = match.shift();
				tokens.push({
					value: matched,
					type: type,
					matches: match
				});
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we&apos;re just parsing
	// Otherwise, throw an error or return tokens
	return parseOnly ?
		soFar.length :
		soFar ?
			Sizzle.error( selector ) :
			// Cache the tokens
			tokenCache( selector, groups ).slice( 0 );
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sizzle的词法解析后的Token格式如下 ：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Token：{  
   value:&apos;匹配到的字符串&apos;, 
   type:&apos;对应的Token类型&apos;, 
   matches:&apos;正则匹配到的一个结构&apos;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;type可能是：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ID,TAG,CLASS,ATTR,CHILD,PSEUDO&lt;/code&gt;以及表示层级关系的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+,&amp;gt;, ,~&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;一个组合式的selector经过tokenize后会变成一个数组，每个值都是token格式的对象。&lt;/p&gt;

&lt;p&gt;然后sizzle按照一定的顺序去匹配筛选这个数组中的元素，找到正确匹配的元素。&lt;/p&gt;

&lt;h2 id=&quot;sizzle匹配顺序内容待更新&quot;&gt;sizzle匹配顺序（内容待更新）&lt;/h2&gt;

&lt;p&gt;当sizzle处理多层选择器，按照从右到左的原则，特殊选择符会从左到右。&lt;/p&gt;

&lt;p&gt;分词操作之后并没有直接开始组装匹配方法，而是先做了一些find的操作。这里的find操作就可以对应到Expr里面的find，它执行的是查询操作，返回的是结果集。&lt;/p&gt;

&lt;p&gt;可以这样理解，select利用“分词”得到的选择符根据它的type先将可以用find方法查找的结果集查出来。做find操作的时候，是按照选择符的顺序从左到右缩小结果集范围的。如果一个遍历下来，selector中的所有选择符都可以执行find操作，则直接将结果返回。否则，就进入前面介绍的“编译”执行过滤的流程了。&lt;/p&gt;

&lt;p&gt;到这里，也可以顺过来，基本上理清楚Sizzle的工作流程了。前面留下的疑问到此时其实也不算疑问了，因为执行反向匹配过滤的时候，它的查找范围已经是经过层层过滤的最小集合了。而反向匹配过滤的方法对于它所对应的那些选择符，比如伪类之类的，其实也已经是一个高效的选择。&lt;/p&gt;

&lt;p&gt;Sizzle.find主查找函数和Sizzle.filter过滤函数实现原理：&lt;/p&gt;

&lt;p&gt;对js原生的4大查找函数，getElementById（针对id），getElementsByName（针对name），getElementsByTagName（针对标签名tagName，比如div,p），getElementsByClassName(针对class)，进行一层封装，浏览器支持的话，就返回数组或者NodeList，不支持的，就返回undefined。&lt;/p&gt;

&lt;p&gt;这里需要讲一下种子集。&lt;/p&gt;

&lt;h3 id=&quot;种子集&quot;&gt;种子集&lt;/h3&gt;

&lt;p&gt;种子集就是通过最右边的选择器组得到的元素集合。比如：”div.aaa span.bbb”，最右边的选择器组就是”span.bbb”，这时引擎会根据浏览器的支持情况选择getElementsByTagName(span)或getElementsByClassName(bbb)得到一组元素，然后再通过class(bbb)或tagName(span)进行过滤，这时得到的集合就是种子集。&lt;/p&gt;

&lt;p&gt;种子集是分两步筛选出来的，首先，通过Sizzle.find得到一个大体的结果，然后通过Sizzle.filter过滤。那我们是先取span，还是.bbb呢？这里有一个准则，要确保我们后面的映射集（当我们取得种子集后，会将种子集一份，这就是映射集）最小。&lt;/p&gt;

&lt;p&gt;为了达到此目的，这里有一个优化，原生选择器的调用顺序被放在一个Sizzle.selectors.order的数组中，对于低版本浏览器，其顺序为id,name,tagName，对于支持getElementsByClassName的浏览器，顺序为&lt;strong&gt;id,class,name,tagName&lt;/strong&gt;。因为id只返回一个元素，class与样式相关，不是每个元素都有这个类名的，name属性使用到的几率比较少，而tagName排除的元素比较少。&lt;/p&gt;

&lt;p&gt;所以Sizzle.find就会根据Sizzle.selectors.order数组，依次调用正则，从最右的选择器中切下需要的部分，找到粗糙的节点集合。(针对”span.bbb”，id调用正则时，找不到，然后class，调用正则，找到.bbb，因此就调用getElementsByClassName(bbb)得到一组数据，最后通过Sizzle.filter过滤取到的数据，过滤条件是tagName(span))&lt;/p&gt;

&lt;h3 id=&quot;映射集&quot;&gt;映射集&lt;/h3&gt;

&lt;p&gt;当我们取得种子集后，会将种子集一份，这就是映射集。&lt;/p&gt;

&lt;p&gt;种子集是由一个选择器组选出来的，这时如果选择符不为空（前面是”div.aaa”），必然往左就是关系选择器（父亲，兄弟，后代），关系选择器会让引擎去选取其兄长或父亲，把这些元素置换到映射集对等的位置上（个数不变，因此映射集和种子集的数量总是相当）。然后到下一个选择器组时（”div.aaa”），就是过滤操作了。主过滤函数Sizzle.filter会调用Sizzle.selectors下的N个过滤函数对这些元素进行检测，将不符合的元素替换为false。因此到最后要去重排时，映射集是一个包含布尔值与元素节点的数组。&lt;/p&gt;

&lt;h2 id=&quot;sizzle高效原因&quot;&gt;sizzle高效原因&lt;/h2&gt;

&lt;p&gt;首先，从处理流程上，它总是先使用最高效的原生方法来做处理。前面一直在介绍的还只是Sizzle自身的选择器实现方法，真正Sizzle执行的时候，对于css选择器，它还会先判断当前浏览器是否支持querySelectorAll原生方法。如果支持的话，则优先选用此方法，浏览器原生支持的方法，效率肯定比Sizzle自己js写的方法要高，优先使用也能保证Sizzle更高的工作效率。（关于querySelectorAll可以上网查阅更多资料）。在不支持querySelectorAll方法的情况下，Sizzle也是优先判断是不是可以直接使用getElementById、getElementsByTag、getElementsByClassName等方法解决问题。&lt;/p&gt;

&lt;p&gt;其次，相对复杂的情况，Sizzle总是选择先尽可能利用原生方法来查询选择来缩小待选范围，然后才会利用前面介绍的“编译原理”来对待选范围的元素逐个匹配筛选。进入到“编译”这个环节的工作流程有些复杂，效率相比前面的方法肯定会稍低一些，但Sizzle在努力尽量少用这些方法，同时也努力让给这些方法处理的结果集尽量小和简单，以便获得更高的效率。&lt;/p&gt;

&lt;p&gt;再次，即便进入到这个“编译”的流程，Sizzle还做了我们前面为了优先解释清楚流程而暂时忽略、没有介绍的缓存机制。“编译”入口，会调用第三个核心方法superMatcher。跟踪进去看，compile方法将根据selector生成的匹配函数缓存起来了。还不止如此，再看tokenize方法，它其实也将根据selector做的分词结果缓存起来了。也就是说，当我们执行过一次Sizzle (selector)方法以后，下次再直接调用Sizzle (selector)方法，它内部最耗性能的“编译”过程不会再耗太多性能了，直接取之前缓存的方法就可以了。我在想所谓“编译”的最大好处之一可能也就是便于缓存，所谓“编译”在这里可能也就可以理解成是生成预处理的函数存储起来备用&lt;/p&gt;

&lt;p&gt;在Sizzle中，当浏览器支持querySelectorAll方法时，会重写Sizzle。但是在重写时，会根据不同情况提出各种提速方案：&lt;/p&gt;

&lt;p&gt;（1）getElementById还是比querySelectorAll速度快，因为getElementById只返回一个元素，而且内部做了缓存，但是querySelectorAll会返回拥有这个id值的多个元素，尽管页面id一般是唯一的，但如果出现了多个同样id的情况下，getElementById还是只返回一个元素，而querySelectorAll会返回多个。&lt;/p&gt;

&lt;p&gt;（2）getElementsByTagName内部也使用了缓存，而且返回的是NodeList对象，querySelectorAll返回的是一个StaticNodeList对象，前面是动态的，后面是静态的。区别在于：document.getElementsByTagName(“div”) == document.getElementsByTagName(“div”),返回真，document.querySelectorAll(“div”) == document.querySelectorAll(“div”)，返回false.返回true的，意味着它们拿到的同是cache引用。返回false意味着每次返回都是不一样的object。数据表明：创建一个动态的NodeList对象比创建一个静态的StaticNodeList对象快90%.&lt;/p&gt;

&lt;h2 id=&quot;使用选择器的几点建议&quot;&gt;使用选择器的几点建议&lt;/h2&gt;

&lt;p&gt;正确使用选择器引擎对于提高页面性能起了至关重要的作用。使用合适的选择器表达式可以提高性能、增强语义并简化逻辑。在传统用法中，最常用的简单选择器包括ID选择器、Class选择器和类型标签选择器。其中ID选择器是速度最快的，这主要是因为它使用JavaScript的内置函数getElementById()；其次是类型选择器，因为它使用JavaScript的内置函数getElementsByTag()；速度最慢的是Class选择器，其需要通过解析 HTML文档树，并且需要在浏览器内核外递归，这种递归遍历是无法被优化的。&lt;/p&gt;

&lt;p&gt;Class选择器在文档中使用频率靠前，这无疑会增加系统的负担，因为每使用一次Class选择器，整个文档就会被解析一遍，并遍历每个节点。&lt;/p&gt;

&lt;p&gt;对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(&quot;input[type=checkbox]&quot;)&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(&quot;input:checkbox&quot;)&lt;/code&gt;,对于现代浏览器，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(&quot;input[type=checkbox]&quot;)&lt;/code&gt;可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;querySelectorAll&lt;/code&gt;这种内置方来查找,$(‘input:text’)，采用了jQuery自定义的选择器表达式:text，.querySelectorAll()方法无法解析。所以前一种效率明显高于后一种。&lt;/p&gt;

&lt;h3 id=&quot;多用id选择器--总是从id选择器来继承&quot;&gt;多用ID选择器 , 总是从#id选择器来继承&lt;/h3&gt;

&lt;p&gt;多用ID选择器，这是一个明智的选择。即使选择的元素没有ID，也可以从父级元素中添加一个ID选择器，这样就会缩短节点访问的路程。这是jQuery选择器的一条黄金法则。jQuery选择一个元素最快的方法就是用ID来选择了&lt;/p&gt;

&lt;h3 id=&quot;使一个选择器的右边更具有特征相对而言选择器的左边可以少一些特征性&quot;&gt;使一个选择器的右边更具有特征，相对而言，选择器的左边可以少一些特征性&lt;/h3&gt;

&lt;p&gt;由于css选择器引擎的从右到左解析的特性，右边约具有特征，下一步循环筛选的元素的基数越少，速度越快。&lt;/p&gt;

&lt;h3 id=&quot;对于class选择器尽可能使用tagclass类型的选择符&quot;&gt;对于class选择器，尽可能使用”tag.class”类型的选择符&lt;/h3&gt;

&lt;p&gt;每使用一次Class选择器，如果不加tag,整个文档就会被解析一遍，并遍历每个节点，因为这时候sizzle需要用getElementsByTagName(*)，取出搜索域中的所有节点。但是加了tag后getElementsByTagName(tag)会大大缩小搜索范围。&lt;/p&gt;

&lt;h3 id=&quot;避免过度的约束让层级更简单&quot;&gt;避免过度的约束，让层级更简单&lt;/h3&gt;

&lt;p&gt;由于从右到左的解析原则，找到最右边seed子集后，左边的层级都用来筛选了，验证子集中哪些是符合的。层级越复杂，验证筛选的流程越多。&lt;/p&gt;

&lt;h3 id=&quot;避免使用全局选择器&quot;&gt;避免使用全局选择器*&lt;/h3&gt;

&lt;p&gt;无论你将*使用在哪一级，都会极大的降低性能，需要遍历每一层元素。&lt;/p&gt;

&lt;h3 id=&quot;多用父子关系少用嵌套关系&quot;&gt;多用父子关系，少用嵌套关系&lt;/h3&gt;

&lt;p&gt;使用parent&amp;gt;child代替parent child。因为”&amp;gt;”是child选择器，只从子节点里匹配，不递归。而” “是后代选择器，递归匹配所有子节点及子节点的子节点，即后代节点。&lt;/p&gt;

&lt;p&gt;下面六个选择器，都是从父元素中选择子元素。你知道哪个速度最快，哪个速度最慢吗?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$(&apos;.child&apos;, $parent)
$parent.find(&apos;.child&apos;)
$parent.children(&apos;.child&apos;)
$(&apos;#parent &amp;gt; .child&apos;)
$(&apos;#parent .child&apos;)
$(&apos;.child&apos;, $(&apos;#parent&apos;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;$(‘.child’, $parent)：给定一个DOM对象，然后从中选择一个子元素。jQuery会自动把这条语句转成$.parent.find(‘child’)，这会导致一定的性能损失。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;$parent.find(‘.child’)： 这条是最快的语句。.find()方法会调用浏览器的原生方法（getElementById，getElementByName，getElementByTagName等等），所以速度较快。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;parent.children(‘.child’)：这条语句在jQuery内部，会使用$.sibling()和javascript的nextSibling()方法，一个个遍历节点。它比最快的形式大约慢50%&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;$(‘#parent &amp;gt; .child’)： jQuery内部使用Sizzle引擎，处理各种选择器。Sizzle引擎的选择顺序是从右到左，所以这条语句是先选.child，然后再一个个过滤出父元素#parent，这导致它比最快的形式大约慢70%。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;$(‘#parent .child’)：这条语句与上一条是同样的情况。但是，上一条只选择直接的子元素，这一条可以于选择多级子元素，所以它的速度更慢，大概比最快的形式慢了77%。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;$(‘.child’, $(‘#parent’))： jQuery内部会将这条语句转成$(‘#parent’).find(‘.child’)，比最快的形式慢了23%。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;缓存jquery对象&quot;&gt;缓存jQuery对象&lt;/h3&gt;

&lt;p&gt;如果选出结果不发生变化的话，不妨缓存jQuery对象，这样就可以提高系统性能。养成缓存jQuery对象的习惯可以让你在不经意间就能够完成主要的性能优化。如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var a= $( &apos; .aaron&apos; );   
for (i = 0 ; i &amp;lt; 10000 ; i ++ ) ... {   
     a.append(i);   
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;多使用链式调用&quot;&gt;多使用链式调用&lt;/h3&gt;

&lt;p&gt;如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$(&apos;#news&apos;).find(&apos;tr.alt&apos;).removeClass(&apos;alt&apos;).end().find(&apos;tbody&apos;).each(function() {
        $(this).children(&apos;:visible&apos;).has(&apos;td&apos;).filter(&apos;:group(3)&apos;).addClass(&apos;alt&apos;);
   });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;链式调用优于性能的原因是链式将选择器选择结果缓存了。&lt;/p&gt;

&lt;p&gt;采用find(),end(),children(),has,filter()等方法，来过滤结果集，减少$()查找方法调用，提升性能。&lt;/p&gt;

&lt;h3 id=&quot;尽量使用符合css语法规范的css选择器表达式以此来避免使用jquery自定义的选择器表达式&quot;&gt;尽量使用符合CSS语法规范的CSS选择器表达式，以此来避免使用jQuery自定义的选择器表达式&lt;/h3&gt;

&lt;p&gt;CSS选择器表达式可以使用js内置方法querySelectorAll来查找，而jquery自定义选择器需要sizzle去遍历dom树根据修饰器过滤确定最后结果。&lt;/p&gt;

&lt;h2 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.cnblogs.com/aaronjs/p/3300797.html&quot;&gt;jQuery 源码分析Sizzle引擎 - 词法解析&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.lxway.com/8005986.htm&quot;&gt;Sizzle引擎详解&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://tid.tenpay.com/?p=4007&quot;&gt;Sizzle的“编译原理”&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>从jquery源码-sizzle css选择器引擎看兼容的js选择器</title>
   <link href="http://echizen.github.io/tech/2015/08-21-js-selector"/>
   <updated>2015-08-21T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/js-selector</id>
   <content type="html">
&lt;p&gt;为什么我会研究选择器？因为之间面试遇到一个问题：我们知道，getElementsByClassName的兼容性不好，你来写一个函数，使其拥有getElementsByClassName相同的功能，你会怎么写？&lt;/p&gt;

&lt;p&gt;。。。由于个人经历，以前写php的，大二去了创业公司写前端，从此夜以继日，用的都是封好的接口，我对原生js操作dom这块非常非常的不熟，首先我不知道getElementsByClassName的兼容性不好。。。其次我以为他只能作用在document上。。。所以这个问题，兼容性不好的替代方案，我还真答不上来。&lt;/p&gt;

&lt;p&gt;面完后，我想这个问题应该不简单，想到jquery选择器兼容性那么好，于是想去看jquery源码。&lt;/p&gt;

&lt;p&gt;getElementsByClassName在IE9及以上才可用。&lt;/p&gt;

&lt;h2 id=&quot;看源码的建议&quot;&gt;看源码的建议&lt;/h2&gt;

&lt;p&gt;本人首次看这么大的框架源码，一路踩坑，看了2天才把选择器部分勉强看完。。。总结几条看源码的建议：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;拆分源码，带着目的性的看。不然源码那么长，没有清晰的目的，你就像走入了浩瀚的宇宙。之前就想有时间看看源码级的东西，可是一打开大一点的框架，看着几万行的代码，整个人就不好了。这次因为是好奇jquery的选择器，目的性非常强，就是想看选择器部分，所以看的还是津津有味。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;拆分模块后，只研究该模块下有关的函数，不要每次看到一个函数都跟踪进去看源码，这样一环套一环，你一个模块估计就要看一个框架的代码量了，对于辅助性的函数，把它当做已知就好，去官网看看api，这个函数是什么总用，参数是什么，来哦接这些就好，抓住重点。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;调试模式开起来啊。一定要借助chrome强大的断点和逐行跟踪能力。说来惭愧，第一天我真的不靠工具靠眼力看，那么多逻辑判断，文件切换，看的我都快吐了也没明白逻辑。后来下了个为压缩的jquery.js，在浏览器下一行行跟着源码跑，才搞懂逻辑。有什么不明白的地方，在去模块中的源代码里慢慢钻研。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;这一条应该都知道吧，源码要看开发版的，不会有人傻的看发行版的一个jquey.js文件吧。。。jquery已经引用了requirejs，模块划分非常清晰。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150821-1@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;jquery-选择器设计&quot;&gt;jquery 选择器设计&lt;/h2&gt;

&lt;p&gt;jquery选择器的核心是sizzle CSS选择器引擎。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jQuery.find = Sizzle;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所有调用find方法的其实都是用了Sizzle选择器引擎。当然简单的选择id是会直接调用getElementById接口，而不用这么麻烦的。&lt;/p&gt;

&lt;p&gt;sizzle和jquery是同一个作者，sizzle号称是最高效的css选择器引擎。精华我觉得是正则匹配。。。令我头疼的各种正则表达式。&lt;/p&gt;

&lt;p&gt;我并没有真的读懂每一个细节，也不想叙述细节，关于jquery源码的研究网上一抓一大把。我这里只讲一下他的实现思路。也不讲复杂组合的选择器，先将最简单的单个选择器，形如：#id，.class这类。&lt;/p&gt;

&lt;h3 id=&quot;拆分选择对象&quot;&gt;拆分选择对象&lt;/h3&gt;

&lt;p&gt;sizzle根据正则表达式划分出选择器的类型，进行词法分析，对于复杂的组合型选择器，会拆分成数组，一个个的元素匹配。随便贴一个属性的选择器感受一下他的强大。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// http://www.w3.org/TR/css3-selectors/#whitespace
whitespace = &quot;[\\x20\\t\\r\\n\\f]&quot;,

// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
identifier = &quot;(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+&quot;,

// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
attributes = &quot;\\[&quot; + whitespace + &quot;*(&quot; + identifier + &quot;)(?:&quot; + whitespace +
	// Operator (capture 2)
	&quot;*([*^$|!~]?=)&quot; + whitespace +
	// &quot;Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]&quot;
	&quot;*(?:&apos;((?:\\\\.|[^\\\\&apos;])*)&apos;|\&quot;((?:\\\\.|[^\\\\\&quot;])*)\&quot;|(&quot; + identifier + &quot;))|)&quot; + whitespace +
	&quot;*\\]&quot;,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;idtag&quot;&gt;id、tag&lt;/h3&gt;

&lt;p&gt;getElementById、getElementsByTagName&lt;/p&gt;

&lt;h3 id=&quot;class&quot;&gt;class&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;支持getElementsByClassName的浏览器使用getElementsByClassName（IE9及以上及现代浏览器）&lt;/li&gt;
  &lt;li&gt;如果不支持getElementsByClassName的浏览器，支持querySelectorAll的浏览器使用querySelectorAll(IE8以上及现代浏览器)&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果不支持getElementsByClassName和querySelectorAll，那就先通过context.getElementsByTagName(*)选择出选区所有节点（context是你要搜索的element范围区），然后通过getAttribute(“class”)来取得class值，匹配筛选，getAttribute兼容性非常好，IE6以上和所有主流浏览器，基本不存在兼容性问题了。具体代码有这么一段：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &quot;CLASS&quot;: function( className ) {
     var pattern = classCache[ className + &quot; &quot; ];

     return pattern ||
         (pattern = new RegExp( &quot;(^|&quot; + whitespace + &quot;)&quot; + className + &quot;(&quot; + whitespace + &quot;|$)&quot; )) &amp;amp;&amp;amp;
         classCache( className, function( elem ) {
             return pattern.test( typeof elem.className === &quot;string&quot; &amp;amp;&amp;amp; elem.className || typeof elem.getAttribute !== &quot;undefined&quot; &amp;amp;&amp;amp; elem.getAttribute(&quot;class&quot;) || &quot;&quot; );
         });
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;至此，大的思路介绍完毕，至于细节，各种情况的考虑等等，得真的拜读源码才能感受到作为一款框架的严谨程度之可怕。&lt;/p&gt;

&lt;h2 id=&quot;sizzle组成&quot;&gt;sizzle组成&lt;/h2&gt;

&lt;p&gt;Sizzle的整体结构如下：&lt;/p&gt;

&lt;p&gt;（1）Sizzle主函数，里面包含选择符的切割，内部循环调用主查找函数，主过滤函数，最后是去重过滤。&lt;/p&gt;

&lt;p&gt;（2）其他辅助函数，如 uniqueSort, matches ,matchesSelector。&lt;/p&gt;

&lt;p&gt;（3）Sizzle.find主查找函数&lt;/p&gt;

&lt;p&gt;（4）Sizzle.filter主过滤函数&lt;/p&gt;

&lt;p&gt;（5）Sizzle.selectors 包含各种匹配用的正则，过滤用的正则，分解用的正则，预处理函数，过滤函数。&lt;/p&gt;

&lt;p&gt;（6）根据浏览器的特征设计makeArray，sortOrder，contains等方法。&lt;/p&gt;

&lt;p&gt;（7）根据浏览器的特征重写Sizzle.selectors中的部分查找函数，过滤函数，查找次序。&lt;/p&gt;

&lt;p&gt;（8）若浏览器支持querySelectorAll,那么用它重写Sizzle，将原来的Sizzle作为后备方案包裹在新的Sizzle里面。&lt;/p&gt;

&lt;p&gt;（9）其他辅助函数，如：isXML,posProcess。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>认识baseline</title>
   <link href="http://echizen.github.io/tech/2015/08-13-baseline"/>
   <updated>2015-08-13T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/baseline</id>
   <content type="html">
&lt;p&gt;baseline是一个容易让人忽略的东西，但是对它的忽略会造成一些奇怪的现象，就如同对z-index不熟悉一样。&lt;/p&gt;

&lt;p&gt;最开始发现baseline存在是一个2列并排的元素里，对右边的元素用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-size:16px;line-height:30px;height:30px;&lt;/code&gt;，然而元素内的文字在安卓机居中了，在苹果机却没有，而是和他左边的元素底部保持平齐。&lt;/p&gt;

&lt;p&gt;baseline带来的问题，总是在使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;line-height&lt;/code&gt;属性时被发现。&lt;/p&gt;

&lt;h2 id=&quot;baseline-是vertical-align的默认值&quot;&gt;baseline 是vertical-align的默认值&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;vertical-align 指定了行内（inline）元素或表格单元格（table-cell）元素的垂直对齐方式。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;vertical-align指定的是垂直方向上的对其方式，他有参照，一般是父级元素，也会受兄弟元素影响。&lt;/p&gt;

&lt;p&gt;取值有以下几种：&lt;/p&gt;

&lt;p&gt;行内元素&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;baseline&lt;/p&gt;

    &lt;p&gt;元素基线与父元素的基线对齐。一些 可替换元素，比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; ， HTML标准没有说明它的基线，这意味着使用这个关键字，各浏览器表现可能不一样。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;sub&lt;/p&gt;

    &lt;p&gt;元素基线与父元素的下标基线对齐。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;super&lt;/p&gt;

    &lt;p&gt;元素基线与父元素的上标基线对齐。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;text-top&lt;/p&gt;

    &lt;p&gt;元素顶端与父元素字体的顶端对齐。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;text-bottom&lt;/p&gt;

    &lt;p&gt;元素底端与父元素字体的底端对齐。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;middle&lt;/p&gt;

    &lt;p&gt;元素中线与父元素的基线对齐。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;length&gt;

  元素基线超过父元素的基线指定高度。可以取负值。

&lt;/length&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;percentage&gt;

  同 &lt;length&gt; , 百分比相对于 line-height 。
下面两个属性不像上面的属性相对于父元素，而是相对于整行：

&lt;/length&gt;&lt;/percentage&gt;
  &lt;/li&gt;
  &lt;li&gt;top&lt;/li&gt;
&lt;/ul&gt;

 	元素及其后代的顶端与整行的顶端对齐。

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;bottom&lt;/p&gt;

    &lt;p&gt;元素及其后代的底端与整行的底端对齐。
如果元素没有基线baseline，则以它的外边距的下边缘为基线。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;table-cell元素&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;baseline (and sub, super, text-top, text-bottom, &lt;length&gt;, and &lt;percentage&gt;)&lt;/percentage&gt;&lt;/length&gt;&lt;/p&gt;

    &lt;p&gt;与同行单元格的基线对齐。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;top&lt;/p&gt;

    &lt;p&gt;单元格的内边距的上边缘与行的顶端对齐。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;middle&lt;/p&gt;

    &lt;p&gt;单元格垂直居中。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;bottom&lt;/p&gt;

    &lt;p&gt;单元格的内边距的下边缘与行的底端对齐。
可以取负值。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;不同浏览器对baseline解读不一样&quot;&gt;不同浏览器对baseline解读不一样&lt;/h2&gt;

&lt;p&gt;我们常会纠结baseline表现的是span、input、img、button&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150813-1@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;处理方案&quot;&gt;处理方案&lt;/h2&gt;

&lt;p&gt;既然这么多浏览器对baseline理解不一致，规范没统一的东西还是不用为好，也没必要记住每个浏览器对每个支持baseline的元素的渲染方式。反正用到baseline的多是为了对齐，不如重置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align:baseline&lt;/code&gt;，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align:bottom&lt;/code&gt;，有不对齐的，用padding补救。&lt;/p&gt;

&lt;h2 id=&quot;漠大的知乎回答&quot;&gt;漠大的知乎回答&lt;/h2&gt;

&lt;p&gt;结合 CSS 2.1 规范中行内格式化上下文、行高计算、差异半差异、’line-height’ 和 ‘vertical-align’ 特性中相关说明，可以总结出行框顶边与行框底边位置的计算方式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;每一个行内元素会产生一个行内框；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;行内框会在行框内横向排列；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;‘line-height’ 特性值指定了每个行内非替换元素生成的行内框的 确切 高度；行内替换元素的高度由 ‘height’ 特性值决定；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;文字在行内框中垂直排列，上下空隙用半差异填补；如果字号大于行内框则文字从上下方向上溢出行内框，并可能渗入到其他行框内（行框是永远不会重叠的）；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;‘vertical-align’ 特性值指定了每个行内框的垂直对齐方式；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;行框的顶边界是这一系列垂直对齐的行内框最高的顶边框，底边界是最低的底边框。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;行框的高度是顶边界到底边界的距离。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/baseline.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://w3help.org/zh-cn/causes/RD1016&quot;&gt;http://w3help.org/zh-cn/causes/RD1016&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.zhangxinxu.com/wordpress/2010/05/%E6%88%91%E5%AF%B9css-vertical-align%E7%9A%84%E4%B8%80%E4%BA%9B%E7%90%86%E8%A7%A3%E4%B8%8E%E8%AE%A4%E8%AF%86%EF%BC%88%E4%B8%80%EF%BC%89/&quot;&gt;http://www.zhangxinxu.com/wordpress/2010/05/%E6%88%91%E5%AF%B9css-vertical-align%E7%9A%84%E4%B8%80%E4%BA%9B%E7%90%86%E8%A7%A3%E4%B8%8E%E8%AE%A4%E8%AF%86%EF%BC%88%E4%B8%80%EF%BC%89/&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>less 学习笔记</title>
   <link href="http://echizen.github.io/tech/2015/08-11-learn-less"/>
   <updated>2015-08-11T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/learn-less</id>
   <content type="html">
&lt;p&gt;乘热打铁，今天把less也看了。&lt;/p&gt;

&lt;h2 id=&quot;安装&quot;&gt;安装&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install -g less
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;运行&quot;&gt;运行&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lessc less/style.less &amp;gt; css/style.css
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里必须指定生成的css路径，否则会在终端输出结果。。。&lt;/p&gt;

&lt;p&gt;压缩的话&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lessc -x less/style.less &amp;gt; css/style.css
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;更科学的是装clean-css插件。&lt;/p&gt;

&lt;p&gt;你也可以选择不将less转化成css，把这个工作交给客户端浏览器。（个人觉得增加了浏览器负担，使渲染速度变慢）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;stylesheet/less&quot; type=&quot;text/css&quot; href=&quot;styles.less&quot; /&amp;gt;
&amp;lt;script src=&quot;less.js&quot; type=&quot;text/javascript&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;安装sublime-less插件&quot;&gt;安装sublime less插件&lt;/h2&gt;

&lt;p&gt;shift+cmd+p -&amp;gt; install package -&amp;gt; less&lt;/p&gt;

&lt;p&gt;装完要重新打开文件或重启sublime才能生效&lt;/p&gt;

&lt;p&gt;这是为了语法高亮，要是想保存时能自动把less转化成css，也有插件可装&lt;/p&gt;

&lt;h2 id=&quot;less-plugin-clean-css&quot;&gt;less-plugin-clean-css&lt;/h2&gt;

&lt;p&gt;css不压缩还是不能接受的。&lt;/p&gt;

&lt;p&gt;-x的用法是被遗弃的，所以就要装插件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo npm install -g less-plugin-clean-css
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lessc less/style.less &amp;gt; css/style.css --clean-css
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;实时编译&quot;&gt;实时编译&lt;/h2&gt;

&lt;p&gt;less的实时编译需要借助其他工具，如gulp的watch模块，koala…&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>jade学习笔记</title>
   <link href="http://echizen.github.io/tech/2015/08-11-learn-jade"/>
   <updated>2015-08-11T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/learn-jade</id>
   <content type="html">
&lt;p&gt;很早就想用jade了，最近辞去工作，在家进修，可以好好研究一下想学的东西了。环境最麻烦，所以这次有时间记录一下从0开始使用工具的过程。至于具体写法，官网说的很详细。&lt;/p&gt;

&lt;h1 id=&quot;安装&quot;&gt;安装&lt;/h1&gt;

&lt;p&gt;全局安装：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo npm install jade -g
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;jade-html&quot;&gt;jade-&amp;gt;html&lt;/h1&gt;

&lt;p&gt;先写一个jade文件，如demo.jade:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;doctype html
html(lang=&quot;en&quot;)
  head
    title= pageTitle
    script(type=&apos;text/javascript&apos;).
      if (foo) bar(1 + 5)
  body
    h1 Jade - node template engine
    #container.col
      if youAreUsingJade
        p You are amazing
      else
        p Get on it!
      p.
        Jade is a terse and simple templating language with a
        strong focus on performance and powerful features.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后在终端运行：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jade demo.jade&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;jade_test jade demo.jade
render demo.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;会看到在同一个文件夹下生成了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;demo.html&lt;/code&gt;，且是压缩版。&lt;/p&gt;

&lt;p&gt;如果希望html具有可读性，就不要压缩，运行&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jade -P demo.jade
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;文件夹移动&quot;&gt;文件夹移动&lt;/h1&gt;

&lt;p&gt;如果是实际用的话，肯定要考虑分文件夹。&lt;/p&gt;

&lt;p&gt;如我们在jade_test下新建文件夹jade和html。jade文件夹下新建test.jade文件。运行&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jade jade/test.jade --out html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;就会在html文件夹下发现被渲染的test.html。&lt;/p&gt;

&lt;p&gt;也可以进入jade文件夹下运行&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jade test.jade --out ../html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不想压缩就运行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jade test.jade -P --out ../html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;监听文件变化&quot;&gt;监听文件变化&lt;/h1&gt;

&lt;p&gt;运行以下命令，可以让test.jade变化后,test.html随时更新&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jade test.jade -P -w --out ../html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以同时监听多个文件变化，在test_jade文件夹下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jade jade -P -w --out html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是新增文件时要重启才有效果，监听不了文件的增删&lt;/p&gt;

&lt;h1 id=&quot;subilime工具&quot;&gt;subilime工具&lt;/h1&gt;

&lt;p&gt;一开始jade文件在sublime里是惨白色，没有语法高亮肯定不合习惯。我们可以装点jade插件.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150811-1@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;发现bootstrap真强大，都有jade的snippet了。&lt;/p&gt;

&lt;p&gt;我装了第一个jade,现在有语法高亮了。&lt;/p&gt;

&lt;h1 id=&quot;使用细节&quot;&gt;使用细节&lt;/h1&gt;

&lt;h2 id=&quot;换行&quot;&gt;换行&lt;/h2&gt;

&lt;p&gt;div内内容过长时会需要换行，换行是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.+space&lt;/code&gt;,eg:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; - var divClass=[&apos;fluied&apos;,&apos;text-left&apos;]
      div(class=divClass).
        重名的class会发生什么
        后者覆盖前者。
        换行是通过&apos;.&apos;+&apos; &apos;实现
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;空格的重要性&quot;&gt;空格的重要性&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  ul#explict
    -
      list = [&quot;Uno&quot;, &quot;Dos&quot;, &quot;Tres&quot;,&quot;Cuatro&quot;, &quot;Cinco&quot;, &quot;Seis&quot;]
    each item in list
      li= item
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种东西,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list&lt;/code&gt;之间只有一个换行符，不能有空格。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;each&lt;/code&gt;必须顶格，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;li&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt;之间必须没有空格。&lt;/p&gt;

&lt;h2 id=&quot;markdown-filter&quot;&gt;markdown filter&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Transformers.markdown is deprecated, you must replace the :markdown jade filter, with :markdown-it and install jstransformer-markdown-it before you update to jade@2.0.0.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;include-和-extends同时使用&quot;&gt;include 和 extends同时使用&lt;/h2&gt;

&lt;p&gt;公用头在非单页面应用中是非常中要的，extends可以让我们继承模板，include可以引入外部模板，于是我们便可以这么干：&lt;/p&gt;

&lt;p&gt;公用基础&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//- comm.jade
doctype html
html
  head
    block title
      title 公用头

    block script
      include lib.jade
  body
    block content
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;资源：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//- lib.jade
link(href=&apos;/lib/bootstrap/css/bootstrap.min.css&apos;,rel=&apos;stylesheet&apos;)
link(href=&apos;/home/css/style.css&apos;,rel=&apos;stylesheet&apos;)
script(src=&apos;lib/angular.js&apos;)    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;具体页面&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//- index.jade
extends  comm.jade

block title
  title 首页

block content
  h3 欢迎光临

  div.fluid.
     jade用起来感觉不错
     但是貌似更适合node渲染的页面，而不是mvc的页面
  script
	include ../js/index.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;资源：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// index.js
alert(&apos;jade is awesome&apos;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;comm.jade中必须用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block script&lt;/code&gt;这种方式，将要引入的文件定义为block,才能在index.jade中正确引入。&lt;/p&gt;

&lt;p&gt;最后那个资源引入真的很爽，你可以在把js抽出一个新文件，但是渲染时依然被拉入html中作为内嵌js了。我们很多时候抽出js都只是为了开发人员方便，对计算机最友好的方式其实是都写在一个页面，这样可以避免需要再一次通过网络请求资源。&lt;/p&gt;

&lt;h1 id=&quot;感受&quot;&gt;感受&lt;/h1&gt;

&lt;p&gt;敲了半天的感受就是jade让前端像写js一样写html，能定义变量，使用变量。但是应用场景貌似更适合node模板引擎先渲染，而前后端完全分离的前端mvc模式并不显其优势。作为被node先渲染的模板，可以将一些变量先传入，再有jade语法做功能判断，可是实现动态html。而前后端完全分离后，html中不会插入任何后台代码，这样变量的作用就小了一半。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>web app开发心得</title>
   <link href="http://echizen.github.io/tech/2015/08-10-build-web-app"/>
   <updated>2015-08-10T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/build-web-app</id>
   <content type="html">
&lt;h1 id=&quot;背景&quot;&gt;背景&lt;/h1&gt;
&lt;p&gt;在朝思暮想之后，终于在7月份有机会将angularjs投入项目了！！！本来头是不乐意的，说我们之前的hybrid app已经拥有一套成型的技术了，为什么还要折腾。可我想说折腾是程序员的本性，如果不试新东西怎么会进步呢？一遍又一遍的跑同样一套系统对于我来说是无聊。。。于是我便忽悠老大，angular和ionic都是谷歌开发出来的，技术上多么多么的先进。之前的那套架构有多么多么多的漏洞。于是老大心不甘情不愿的让我去试了，我也做了担保，要日夜兼程的按期完成。&lt;/p&gt;

&lt;p&gt;说起这一次的项目，折腾的真是够多的。项目不算大，但是涉及了UI完全不同的三个平台。先是app端，架构师用phonegap封装，内部页面我也就用angular和ionic这2大红的发紫的框架了。pc端管理后台，因为想看看angular和jquery合作带来的问题怎么解决，然后考虑到另一个做这个前端css技能较为薄弱，所以采用了adminLTE+angularjs。用户展示官网，采用了angularjs+bootstrap,也就是bootstrap-ui,这也是一个相当火的bt新产品，然后由于我想写写ui了，就用了bt，毕竟bt的自定义能力很强。&lt;/p&gt;

&lt;h3 id=&quot;项目中的我&quot;&gt;项目中的我&lt;/h3&gt;
&lt;p&gt;这次非常开心，拉了2个协会的小学弟，虽然他们2没参与过正式项目，整体化意识有欠缺，但是协会的人的学习能力是毋庸置疑的。还有一个小学弟，是刚从php转前端的，pc后台交给他去完成，虽然前期慢了点，bug幼稚了点，但还是像模像样的完成了，吾心甚慰。&lt;/p&gt;

&lt;p&gt;至于我的角色。。。技术顾问+打杂的。。。我先把架构搭好，然后每个平台，抽一个典型页面，写个demo，前后端调通，再交给他们参考写其他。他们解决不了的技术问题，都扔给我，或者调bug，进度赶时或者页面复杂时，我就会操刀写页面。曾经一度十几天吧，我都处于各种解决技术问题中，因为自己也是第一次用这些，没经验，很多问题也没头绪，就去看angular官网，了解原理，按自己的理解试一下，真调不出来就去stackoverflow上找别人的参考。天天深陷Bug和难题中比较绝望，但是解决很有质量的问题很有意义。在每天解决难题的煎熬和没什么新的问题之后，我就去写官网了。阔别多月之后再次感叹bt真的是世界上最好的前端框架，既有规范，又很自由，这套框架没有多写一句没用的代码，你完全可以在他的基础上构建自己的UI样式，它提供的只有便捷。&lt;/p&gt;

&lt;h1 id=&quot;脚手架&quot;&gt;脚手架&lt;/h1&gt;

&lt;p&gt;第一次自己架构一个项目，架构完后才知道为什么有yoman这种一体化的脚手架。&lt;/p&gt;

&lt;p&gt;这起源一个问题：&lt;/p&gt;

&lt;h2 id=&quot;angularjs如何按需加载&quot;&gt;angularjs如何按需加载&lt;/h2&gt;

&lt;p&gt;之前的一个项目是用requirejs按需加载的，深感按需加载很必要。&lt;/p&gt;

&lt;p&gt;但是用angularjs就会发现，无论是用&lt;ng-include&gt;还是ui-router,所有html和js在你打开第一个页面时就一股脑加载进来了。。。这是多么的不可接受。&lt;/ng-include&gt;&lt;/p&gt;

&lt;p&gt;我也试过用requirejs和angulrAMD/oclazyload这些东西尝试动态加载，但是不是只能动态加载html就是只能动态加载js，要不就是没法和ionic整合。&lt;/p&gt;

&lt;p&gt;还去知乎提了问题。其中有人回答我不需要动态加载，压缩后一次性加载也没问题。然后我开始向着这个方向进军。但是在还不知道gulp、grunt工具的我很难想象所有人在同一个js中写文件，冲突什么的有多可怕。。。&lt;/p&gt;

&lt;h2 id=&quot;文件和网络请求数谁更耗时&quot;&gt;文件和网络请求数，谁更耗时&lt;/h2&gt;

&lt;p&gt;这个问题要认真想，对于我们的项目，每个页面都不是很复杂，js代码500行之内绝对能搞定，但是页面多，如果你给每个页面都写一个contrllor.js的话，可知跑完一个app，用户得请求多少次网络。而我们将所有js文件分类打包成一个文件并压缩，这样不同类的js文件加起来才不到50k，而服务器再使用gzip压缩这些文件，就更小了，根本不恐怖。那么问题是如何让开发过程顺利，总不能所有人真的往一个文件里写东西吧。。。&lt;/p&gt;

&lt;h2 id=&quot;工具时代&quot;&gt;工具时代&lt;/h2&gt;

&lt;h3 id=&quot;gulp&quot;&gt;gulp&lt;/h3&gt;

&lt;p&gt;然后我找到了grunt,不久因为灵活性差，又换成了gulp。gulp能在这个项目里起很重要的作用：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;从bower下载的资源文件抽出开发要用的文件，打包到开发目录。&lt;/li&gt;
  &lt;li&gt;将多个js合并成一个js&lt;/li&gt;
  &lt;li&gt;将js文件压缩&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;而gulp的用法是简洁明了。非常方便。&lt;/p&gt;

&lt;p&gt;如资源文件移动：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 前端资源文件打包
 */
var BOWER_COMPONENTS_PATH = {
  &apos;src&apos;:&apos;./bower_components/&apos;,
  &apos;dest&apos;:&apos;./static/lib/&apos;
};
gulp.task(&apos;bower-ionic&apos;,function(){
  var tempPath = BOWER_COMPONENTS_PATH.src + &apos;ionic/release/&apos;;
  return gulp.src([tempPath + &apos;**/+(*.min.*|*.eot|*.svg|*.ttf|*.woff)&apos;])
    .pipe(gulp.dest(BOWER_COMPONENTS_PATH.dest + &apos;ionic/&apos;));
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再如文件合并压缩&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 控制器文件编译打包
 */
var ANGULAR_BUILD_PATH = {
  src:&apos;./build/&apos;,
  dest:&apos;./static/&apos;
};
gulp.task(&apos;pa-angular-ctrl-app&apos;,function(){
  return gulp.src([ANGULAR_BUILD_PATH.src + &apos;app/controllers/**/*_ctrl.js&apos;])
    .pipe(plugins.jshint())
    .pipe(plugins.jshint.reporter(&apos;jshint-stylish&apos;))
    .pipe(plugins.uglify())
    .pipe(plugins.concat(&apos;app_ctrl.js&apos;))
    .pipe(gulp.dest(ANGULAR_BUILD_PATH.dest + &apos;app/js/&apos;));
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还可以将多个任务合成一个任务，因为你不可能一次只合并一类文件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gulp.task(&apos;pa-angular&apos;,[&apos;pa-angular-ctrl-app&apos;,&apos;pa-angular-sev-app&apos;,&apos;pa-angular-ctrl-pc&apos;,&apos;pa-angular-resource-services&apos;,&apos;pa-angular-directives-app&apos;,&apos;pa-angular-directives-pc&apos;,&apos;pa-angular-directives-home&apos;,&apos;pa-angular-filters-app&apos;,&apos;pa-angular-filters-pc&apos;,&apos;pa-angular-ctrl-home&apos;]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;bower&quot;&gt;bower&lt;/h3&gt;

&lt;p&gt;现在UI越来越重要，那么多界面效果，所需的插件肯定不少。而bower就是必需品。没有bower，你能想象更新插件是多么麻烦吗？&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;下载资源文件&lt;/li&gt;
  &lt;li&gt;更新资源文件&lt;/li&gt;
  &lt;li&gt;删除资源文件&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;将bower和gulp一起运用，可以在插件有更新时使用’bower update XXX’一键更新，然后运行gulp命令，将新的插件资源移动到开发目录。&lt;/p&gt;

&lt;p&gt;此时若更新后，插件目录有变化，gulp命令就要重写，不能忽略了检验。&lt;/p&gt;

&lt;h1 id=&quot;架构&quot;&gt;架构&lt;/h1&gt;
&lt;p&gt;现在谈谈核心内容了。&lt;/p&gt;

&lt;p&gt;angular架构app分成5个部分：页面html，控制器controller，服务service，指令directive，过滤器filter.&lt;/p&gt;

&lt;h2 id=&quot;路由&quot;&gt;路由&lt;/h2&gt;

&lt;p&gt;ionic使用的是ui-router,我便把pc官网也引入了ui-router。由于单页面，只有一个ng-app注入模块，所以我们可以将其他模块作为ng-app注册模块的依赖。如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;user strict&quot;;
 // Declare app level module which depends on views, and components
 var jbj = angular.module(&apos;jbj&apos;, [
    &apos;ui.router&apos;,
    &apos;jbj.news&apos;,
    &apos;jbj.newsDetail&apos;
   ]);

jbj.config([&apos;$stateProvider&apos;, &apos;$urlRouterProvider&apos;,function($stateProvider, $urlRouterProvider) {

  //angular路由配置
  $stateProvider

  .state(&apos;news&apos;, {
    url: &apos;/news&apos;,
    templateUrl: &apos;news.html&apos;,
    controller: &apos;newsCtrl as news&apos;
  })

  .state(&apos;newsDetail&apos;, {
    url: &apos;/newsDetail/:objectId&apos;,
    templateUrl: &apos;news_detail.html&apos;,
    controller: &apos;newsDetailCtrl as newsDetail&apos;
  })
	
  $urlRouterProvider.otherwise(&apos;/index&apos;);

}]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后在页面上只用注入 ` &amp;lt;body ng-app=&quot;jbj&quot; ng-cloak&amp;gt;`即可。&lt;/p&gt;

&lt;h2 id=&quot;页面&quot;&gt;页面&lt;/h2&gt;
&lt;p&gt;这个没什么可介绍的，就是分平台建文件夹，一个页面一个文件的传统做法。&lt;/p&gt;

&lt;h2 id=&quot;controller&quot;&gt;controller&lt;/h2&gt;
&lt;p&gt;也就是普通的contoller，相关的部分放在一个模块中，其他的是每个页面自己的特色化js部分，调用directive、service、filter时，记得引入模块，需要注入依赖的就注入依赖。&lt;/p&gt;

&lt;h2 id=&quot;service&quot;&gt;service&lt;/h2&gt;
&lt;p&gt;由于接口要统一，三个平台使用了angularjs，都是数据双向绑定模式，所以，seviece采用统一的。service有2类，一类是数据接口，一类是工具。&lt;/p&gt;

&lt;p&gt;数据接口的service没有使用$http服务，而是使用了更适合RESTful风格的$resource。&lt;/p&gt;

&lt;p&gt;你看到的将会是这样，一个url指定所有增查改删的操作。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  var apiUrls = {
    news: WEB_HOST_URL + &apos;/api/1.0/News/:id&apos;,
    imgUploadUrl:  WEB_HOST_URL + &apos;/api/1.0/File/uploadBase64Img&apos;,
  };

  var newsService = angular.module(&apos;newsService&apos;,[&apos;ngResource&apos;]);

  newsService.factory(&apos;News&apos;,[&apos;$resource&apos;,function($resource){
    return $resource(apiUrls.news,{id:0},{
        get:{method:&quot;GET&quot;},
        post:{method:&quot;POST&quot;},
        delete:{method:&quot;DELETE&quot;},
        put:{method:&quot;PUT&quot;}
    });
  }]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;工具类的接口我放的都是为了能复用的但又跟dom无关的功能性代码，如phonegap和微信的调取图片的接口。&lt;/p&gt;

&lt;h2 id=&quot;directivefilter&quot;&gt;directive、filter&lt;/h2&gt;

&lt;p&gt;通用的跟ui和js都有关系的部分，我是将所有directive都放在同一个文件夹内，通过后缀区分。譬如只给app用的叫做XXX_directive.app.js。给pc后台用的叫XXX_directive.pc.js，通用的叫XXX_directive.js。然后根据后缀名合并文件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gulp.task(&apos;pa-angular-directives-app&apos;,function(){
  return gulp.src([ANGULAR_BUILD_PATH.src + &apos;directives/*_directive.js&apos;,ANGULAR_BUILD_PATH.src + &apos;directives/*_directive.app.js&apos;])
    .pipe(plugins.jshint())
    .pipe(plugins.jshint.reporter(&apos;jshint-stylish&apos;))
    .pipe(plugins.uglify())
    .pipe(plugins.concat(&apos;jbj_app_directives.js&apos;))
    .pipe(gulp.dest(ANGULAR_BUILD_PATH.dest + &apos;directives/&apos;));
});

gulp.task(&apos;pa-angular-directives-pc&apos;,function(){
  return gulp.src([ANGULAR_BUILD_PATH.src + &apos;directives/*_directive.js&apos;,ANGULAR_BUILD_PATH.src + &apos;directives/*_directive.pc.js&apos;])
    .pipe(plugins.jshint())
    .pipe(plugins.jshint.reporter(&apos;jshint-stylish&apos;))
    .pipe(plugins.uglify())
    .pipe(plugins.concat(&apos;jbj_pc_directives.js&apos;))
    .pipe(gulp.dest(ANGULAR_BUILD_PATH.dest + &apos;directives/&apos;));
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;filter的处理方式相同。&lt;/p&gt;

&lt;h2 id=&quot;发行版&quot;&gt;发行版&lt;/h2&gt;

&lt;p&gt;要把开发目录的js内容打包到发行目录下，譬如所有app端controller合成一个app_ctrl.js文件，在index.html引入的也是发行目录下打包后的app_ctrl.js。&lt;/p&gt;

&lt;p&gt;为了方便开发人员实时看到效果，还要实时更新合并文件。gulp中有watch服务，负责监听文件变化，注入一下就好：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gulp.task(&apos;pa-angular&apos;,[&apos;pa-angular-ctrl-app&apos;,&apos;pa-angular-sev-app&apos;,&apos;pa-angular-ctrl-pc&apos;,&apos;pa-angular-resource-services&apos;,&apos;pa-angular-directives-app&apos;,&apos;pa-angular-directives-pc&apos;,&apos;pa-angular-directives-home&apos;,&apos;pa-angular-filters-app&apos;,&apos;pa-angular-filters-pc&apos;,&apos;pa-angular-ctrl-home&apos;]);
gulp.task(&apos;watch-pa&apos;,function(){
  plugins.livereload.listen();
  gulp.watch([ANGULAR_BUILD_PATH.src + &apos;**&apos;],[&apos;pa-angular&apos;]);
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gulp pa-angular&lt;/code&gt;后，被监听的目录发生变化，pa-angular的任务就会被触发，一群纳入控制的文件就讲被合并打包，我建议为了开发时调试，开发时打包不要压缩。&lt;/p&gt;

&lt;h1 id=&quot;给力的node端-本地服务器&quot;&gt;给力的node端-本地服务器&lt;/h1&gt;
&lt;p&gt;我不玩node的，你的node小伙伴需要给力的给你一个本地跑的起来的服务器，你才能方便的开发。如果node小伙伴不给力，就自己折腾一个服务吧，现在也有不少这种的，真不行，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python -m SimpleHTTPServer 8888&lt;/code&gt;通过文件访问也可以啊&lt;/p&gt;

&lt;h1 id=&quot;我的项目结构&quot;&gt;我的项目结构&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--bower_components
--build
	--app
		--**_ctrl.js:app端各个页面的controller文件
	--pc
		--**_ctrl.js:pc后台各个页面的controller文件
	--home
		--**_ctrl.js:官网各个页面的controller文件
	--directives
		--**_directive.js
		--**_directive.app.js
		--**_directive.pc.js
		--**_directive.home.js
	--filters
		--**_filters.js
		--**_filters.app.js
		--**_filters.pc.js
		--**_filters.home.js
	--services
		--api：接口服务
			--**_service.js
		--util：工具服务
			--**_service.js
--static
	--app
		--css
		--img
		--icon
		--js
			--router.js:路由和模块初始化
			--app_ctrl.js:gulp合并而来
	--pc
		--同上
	--home
		--同上
	--directives:打包而来
		--app_directives.js
		--pc_directives.js
		--home_directives.js
	--filters:打包而来
		--app_filters.js
		--pc_filters.js
		--home_filters.js
	--services:打包而来
		--service.js
	--lib
		--**:各种经gulp分包后的插件
--views
	--app
		--**.html
	--pc
		--**.html
	--home
		--**.html
--bower.json
--gulpfile.js
--package.json
--.gitignore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;待优化&quot;&gt;待优化&lt;/h1&gt;

&lt;p&gt;如果你时间充裕的话，应该试试karma这种测试工具，写测试，毕竟angular的原则是测试先行。只是我还没有领悟其中奥妙，service、directive、controller都可以写测试，时间跟不上。&lt;/p&gt;

&lt;p&gt;css方面试试less,可以节省不少时间&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>异步处理-强大的promise</title>
   <link href="http://echizen.github.io/tech/2015/08-08-learn-promise"/>
   <updated>2015-08-08T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/learn-promise</id>
   <content type="html">
&lt;p&gt;使用了angular之后，经常会遇到异步带来的问题，譬如directive要等到controller取到服务器数据后再渲染什么的。逼得我不得不去了解promise.（话说之前还一直以为他是node的一个API，汗颜。。。）&lt;/p&gt;

&lt;p&gt;promise已被纳入ES6中，不过浏览器对其的支持性还不是特别好。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150808-1@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;硬伤是安卓4.4.4之后才支持，这显然不能商用，还得借住库。&lt;/p&gt;

&lt;p&gt;jquery、commonjs、dojo、Q、when 都实现了自己的promise部分。名称略有不同，有叫promise的，有叫q的，有叫deffer的。但大体用法是一样的。他们都遵从了同一套规范 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Promises/A+&lt;/code&gt;，但是jquery有点特殊。&lt;/p&gt;

&lt;h2 id=&quot;应用方式&quot;&gt;应用方式&lt;/h2&gt;

&lt;p&gt;先来假设一个应用场景：函数b需要在函数a执行完后执行。而函数a中有异步请求，譬如ajax,我们需要异步请求完成后根据异步请求成功或者失败来确定函数b执行内容。&lt;/p&gt;

&lt;p&gt;那么我们需要在a函数中设置个promise对象，他相当于一个触发器，在你将要运行ajax之前，先设个promise标志，说明接下来要干异步的事了。当a函数中异步操作进行完后，立即通知b函数你可以干你该干的事了。&lt;/p&gt;

&lt;p&gt;a函数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function a(){
  var promise = new Promise( function (resolve, reject) {
  // 在这里做你想做的事，譬如异步请求ajax。
  // 成功时（譬如ajax请求返回数据）：
  resolve(value);
  // 失败时（譬如ajax请求返回错误码）：
  reject(reason);
  return promise;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;b函数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function b(){
  //...初始化信息
  a.then(function(value){
    // 成功时的操作，value是你传入的值
  }, function(reason){
    //失败时的操作
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;angularjs中的promise&quot;&gt;angularjs中的promise&lt;/h2&gt;

&lt;p&gt;angularjs 中的promise使用的是$q服务，只是换了个名字，本质还是一样的。特别的是他还有notify方法，用来在异步运行途中，不停的告诉对方我还在运行中。&lt;/p&gt;

&lt;h3 id=&quot;使用方法&quot;&gt;使用方法&lt;/h3&gt;

&lt;p&gt;a函数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function a(){
  var deferred = $q.defer();
  // 在这里做你想做的事，譬如异步请求ajax。
  // 成功时（譬如ajax请求返回数据）：
  if(success){
    deferred.resolve(value);
  }else{
    // 失败时（譬如ajax请求返回错误码）：
    deferred.reject(reason);
  }
  return deferred.promise;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;b函数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function b(){
  //...初始化信息
  promise = a();
  promise.then(function(value){
    // 成功时的操作
  },function(reason){
    //失败时的操作
  },function(update){
    //进行中的提示
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;angular对promise的调用方式相对更清晰。&lt;/p&gt;

&lt;h3 id=&quot;我的运用&quot;&gt;我的运用&lt;/h3&gt;

&lt;p&gt;测试时测出了我之前写的发送验证码的bug，就是验证码请求失败，提示错误时，我依然在倒计时。。。。然后我将返回值移至异步回调中，然后就永远不会倒计时了。。。&lt;/p&gt;

&lt;p&gt;以前的代码是这样写的：&lt;/p&gt;

&lt;p&gt;directive中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scope.getCode = function(){
  var canSend =scope.getCodeFun();
  if(canSend){
    minuteClock();
  }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;controller中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vm.getCode = function(){
  if(vm.form.mobilePhoneNumber.$error.phone){
    feedbackMsg.phoneNumberError();
    return false;
  }else{
    getSmsCode.get({&apos;mobilePhoneNumber&apos;:vm.phone.mobilePhoneNumber},function(data){
      if(data.code===0){
        if(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem){
          localStorage.setItem(&apos;userPhone&apos;,vm.phone.mobilePhoneNumber);
        }
        feedbackMsg.smsCodeSuccess();
        return true;
      }else{
        feedbackMsg.smsCodeError(data.message);
        return false;
      }    
    }); 
  }
  
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很显然由于$resource的使用，是的异步部分的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return true&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return false&lt;/code&gt;执行有延时，函数如果通过了phone的验证，立即返回的是undefined,undefined在if中被转换成了布尔值false。&lt;/p&gt;

&lt;p&gt;使用了promise之后，一切都变得很精确。&lt;/p&gt;

&lt;p&gt;directive中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  scope.getCode = function(){
    var promise = scope.getCodeFun();
    promise.then(function(value) {
      scope.notSubmit = true;
      minuteClock();
    });
  };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;controller中：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vm.getCode = function(){
  var deferred = $q.defer();
  if(vm.form.mobilePhoneNumber.$error.phone){
    feedbackMsg.phoneNumberError();
    deferred.reject(&apos;error&apos;);
  }else{
    getSmsCode.get({&apos;mobilePhoneNumber&apos;:vm.phone.mobilePhoneNumber},function(data){
      if(data.code===0){
        if(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem){
          localStorage.setItem(&apos;userPhone&apos;,vm.phone.mobilePhoneNumber);
        }
        feedbackMsg.smsCodeSuccess();
        deferred.resolve(&apos;success&apos;);
      }else{
        feedbackMsg.smsCodeError(data.message);
        deferred.reject(&apos;error&apos;);
      }    
    }); 
  }
  return deferred.promise;
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;promise的优点&quot;&gt;promise的优点&lt;/h2&gt;

&lt;p&gt;如果你认真看，会发现promise能实现的回调函数都能实现，所以为什么还要引入promise?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;promise更清晰，回调函数容易陷入回调黑洞&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;promise更适合错误处理，他专门提供了成功时的接口，失败时的接口。回调的话，你只能传入2个回调函数参数了&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;promise解耦关系变得可能。这是我最大的感悟。如果用回调，上面的示例，是需要在a函数中植入成功或失败的回调函数，代码变得臃肿，而且维护时你必须对照着b函数看a函数中执行的回调函数是什么用处。而promise给出了通用的接口resolve和reject，a、b2个函数可以真正的分开，也方便a函数的复用。而且你无法想象在directive和controller这2个解耦的组件中（连通信都借用scope）使用回调这种紧密关联的东西是一件多么糟糕的事情。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;示例代码来自mdn&quot;&gt;示例代码（来自MDN）&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&apos;use strict&apos;;

// A-&amp;gt; $http function is implemented in order to follow the standard Adapter pattern
function $http(url){
 
  // A small example of object
  var core = {

    // Method that performs the ajax request
    ajax : function (method, url, args) {

      // Creating a promise
      var promise = new Promise( function (resolve, reject) {

        // Instantiates the XMLHttpRequest
        var client = new XMLHttpRequest();
        var uri = url;

        if (args &amp;amp;&amp;amp; (method === &apos;POST&apos; || method === &apos;PUT&apos;)) {
          uri += &apos;?&apos;;
          var argcount = 0;
          for (var key in args) {
            if (args.hasOwnProperty(key)) {
              if (argcount++) {
                uri += &apos;&amp;amp;&apos;;
              }
              uri += encodeURIComponent(key) + &apos;=&apos; + encodeURIComponent(args[key]);
            }
          }
        }

        client.open(method, uri);
        client.send();

        client.onload = function () {
          if (this.status == 200) {
            // Performs the function &quot;resolve&quot; when this.status is equal to 200
            resolve(this.response);
          } else {
            // Performs the function &quot;reject&quot; when this.status is different than 200
            reject(this.statusText);
          }
        };
        client.onerror = function () {
          reject(this.statusText);
        };
      });

      // Return the promise
      return promise;
    }
  };

  // Adapter pattern
  return {
    &apos;get&apos; : function(args) {
      return core.ajax(&apos;GET&apos;, url, args);
    },
    &apos;post&apos; : function(args) {
      return core.ajax(&apos;POST&apos;, url, args);
    },
    &apos;put&apos; : function(args) {
      return core.ajax(&apos;PUT&apos;, url, args);
    },
    &apos;delete&apos; : function(args) {
      return core.ajax(&apos;DELETE&apos;, url, args);
    }
  };
};
// End A

// B-&amp;gt; Here you define its functions and its payload
var mdnAPI = &apos;https://developer.mozilla.org/en-US/search.json&apos;;
var payload = {
  &apos;topic&apos; : &apos;js&apos;,
  &apos;q&apos;     : &apos;Promise&apos;
};

var callback = {
  success : function(data){
     console.log(1, &apos;success&apos;, JSON.parse(data));
  },
  error : function(data){
     console.log(2, &apos;error&apos;, JSON.parse(data));
  }
};
// End B

// Executes the method call 
$http(mdnAPI) 
  .get(payload) 
  .then(callback.success) 
  .catch(callback.error);

// Executes the method call but an alternative way (1) to handle Promise Reject case 
$http(mdnAPI) 
  .get(payload) 
  .then(callback.success, callback.error);

// Executes the method call but an alternative way (2) to handle Promise Reject case 
$http(mdnAPI) 
  .get(payload) 
  .then(callback.success)
  .then(undefined, callback.error);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.oschina.net/translate/whats-so-great-about-javascript-promises&quot;&gt;http://www.oschina.net/translate/whats-so-great-about-javascript-promises&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.angularjs.org/api/ng/service/$q&quot;&gt;https://docs.angularjs.org/api/ng/service/$q&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.html5rocks.com/en/tutorials/es6/promises/&quot;&gt;http://www.html5rocks.com/en/tutorials/es6/promises/&lt;/a&gt;关于链式调用promise、错误处理catch、forEach处理异步内容部分很精彩&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>angularjs directives</title>
   <link href="http://echizen.github.io/tech/2015/07-26-angularjs-directives"/>
   <updated>2015-07-26T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/angularjs-directives</id>
   <content type="html">
&lt;p&gt;指令应用场景很广，多是与dom和js都有关联的部分。&lt;/p&gt;

&lt;p&gt;在没有接触到angular之前并不懂得什么是真正的通用，顶多是把可复用的东西抽成函数，放到一个单独的文件里，在再需要的地方引入。但是angular的模块封装、依赖载入的方式让复用变得真正可行。不同项目中的组件都可以复用，将directive文件引入，再在需要调用的模块中依赖复用组建的模块即可。&lt;/p&gt;

&lt;h2 id=&quot;通用组件&quot;&gt;通用组件&lt;/h2&gt;

&lt;p&gt;譬如手机验证码发送的按钮，需要在注册和找回密码时都用到。它有获取验证码的功能，并拥有重新获取验证码倒计时的UI效果。这个另外一篇博客详述了，就不再赘述了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;作为一个程序员，能偷懒就偷懒，能写一遍复用的东西绝不写两遍。而且要做到通用，就得独立，尽可能不对调用环境产生依赖，考虑到调用环境的多样性。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;jquery插件的封装&quot;&gt;jquery插件的封装。&lt;/h2&gt;

&lt;p&gt;我相信很多初学者在同时使用jquery和angularjs时都是非常的纳闷，我到底该把jquery插件的初始化代码写在哪里。。。&lt;/p&gt;

&lt;p&gt;由于jquery很长一段时间的统治地位，和jquery对ui效果dom操作的天然优势，进行一个大项目时完全不依赖jquey插件是很难办到的，而jquery是全局的，$污染了全局作用域，angular在每个controller下都是一个独立的作用域，然后angular和jquery的渲染时间不一样，jquery和原生js（都写在全局时，而不是在angular的模块中）的执行时间先于angular渲染时间。最友好的jquery插件的使用方式，就是封装成directive，在angular的控制下触发。&lt;/p&gt;

&lt;p&gt;最主要的是在link函数下使用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	$(element).pluginName(configData);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这段代码放在link中，当angular解析到页面的指令的element标识符时，开始获取template,并调用link方法链接，此时调用与这段dom相关的插件的初始化操作最合适不过了。&lt;/p&gt;

&lt;p&gt;譬如我一直挚爱的特别好用的图片上传裁剪插件&lt;a href=&quot;http://scottcheng.github.io/cropit/&quot;&gt;cropit&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var plugin = angular.module(&apos;jqueryPluginDirective&apos;, []);

// 图片上传裁剪处理插件
plugin.directive(&apos;uploadCropImg&apos;,function() {

  return {
    restrict: &apos;E&apos;,
    replace: true,
    scope: {
      cropitConfig: &quot;=&quot;,
      renderTime : &quot;=&quot;,
      uploadImgdata : &quot;&amp;amp;&quot;
    },
    template: &apos;&amp;lt;div class=&quot;image-cropper clear-padding&quot; id=&quot;coverImg&quot; style=&quot;margin-left:24px;&quot;&amp;gt;&apos;+
                  &apos;&amp;lt;div class=&quot;cropit-image-preview-container&quot; &amp;gt;&apos;+
                      &apos;&amp;lt;div class=&quot;cropit-image-preview&quot; style=&quot;width: 540px;height: 300px;&quot;&amp;gt;&amp;lt;/div&amp;gt;&apos;+
                  &apos;&amp;lt;/div&amp;gt;&apos;+
                    
                  &apos;&amp;lt;div class=&quot;slider-wrapper&quot; style=&quot;min-height: 30px;padding-top: 10px;&quot;&amp;gt;&apos;+
                      &apos;&amp;lt;span class=&quot;glyphicon glyphicon-picture&quot; style=&quot;font-size:14px;&quot;&amp;gt;&amp;lt;/span&amp;gt;&apos;+
                      &apos;&amp;lt;input type=&quot;range&quot; class=&quot;cropit-image-zoom-input custom&quot; min=&quot;0&quot; max=&quot;1&quot; step=&quot;0.01&quot;&amp;gt;&apos;+
                      &apos;&amp;lt;span class=&quot;glyphicon glyphicon-picture&quot; style=&quot;font-size:18px;&quot;&amp;gt;&amp;lt;/span&amp;gt;&apos;+
                  &apos;&amp;lt;/div&amp;gt;&apos;+
                    
                  &apos;&amp;lt;input type=&quot;file&quot; class=&quot;cropit-image-input&quot; id=&quot;trueChoseImg&quot;/&amp;gt;&apos;+

                  &apos;&amp;lt;button class=&quot;btn btn-primary select-image-btn&quot; type=&quot;button&quot; ng-click=&quot;choseImg()&quot;&amp;gt;&amp;lt;span class=&quot;glyphicon glyphicon-picture&quot;&amp;gt;&amp;lt;/span&amp;gt;选择图片&amp;lt;/button&amp;gt;&apos;+

                  &apos;&amp;lt;button class=&quot;btn btn-success js_upload_img&quot; type=&quot;button&quot; ng-click=&quot;uploadImg()&quot;&amp;gt;&amp;lt;span class=&quot;glyphicon glyphicon-cloud&quot; &amp;gt;&amp;lt;/span&amp;gt;上传图片&amp;lt;/button&amp;gt;&apos;+
                &apos;&amp;lt;/div&amp;gt;&apos;,
    link: function (scope, element) {
      $(element).cropit(scope.cropitConfig);
                                                         
      scope.choseImg = function(){
          $(&apos;#trueChoseImg&apos;).trigger(&apos;click&apos;);
       };

      scope.uploadImg = function(){
            //要传输的图片数据
            var imgData = {
              &apos;filePath&apos;:$(&apos;.cropit-image-input&apos;).val(),
              &apos;base64ImgData&apos;:$(&apos;.image-cropper&apos;).cropit(&apos;export&apos;)
            };
            // 执行传输图片数据的函数（由外部controller定义的函数）
            scope.uploadImgdata()(imgData);

        // }
      };

    }
  };

});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;噢，原谅我懒惰，这么一大段template没有抽出为文件。&lt;/p&gt;

&lt;h2 id=&quot;自定义表单验证规则&quot;&gt;自定义表单验证规则&lt;/h2&gt;

&lt;p&gt;除了angularjs内置的几种表单验证规则：required, number, pattern, minlength, maxlength, min, max,开发过程中还需要大量的表单验证规则，写入pattern的正则虽然方便，但不能复用了。所以还是directive最合适。譬如这段验证手机号码的正则。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var phoneNumberReg = /^(1[3458]\d{9})$|(17\d{9})$/;
formValidation.directive(&apos;phone&apos;, function() {
  return {
    require: &apos;ngModel&apos;,
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$validators.phone = function(modelValue, viewValue) {
        if (ctrl.$isEmpty(modelValue)) {
          return false;
        }
        if (phoneNumberReg.test(viewValue)) {
          return true;
        }
        return false;
      };
    }
  };
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>谈谈angularjs directive中的scope</title>
   <link href="http://echizen.github.io/tech/2015/07-26-angularjs-directive-scope"/>
   <updated>2015-07-26T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/angularjs-directive-scope</id>
   <content type="html">
&lt;p&gt;初创公司忙的没日没夜没周末，开发进度甩博客更新几条街。在印象笔记中这是我三周前准备要写的东西了。。。（打算8月份停一个月，留点时间给自己整理总结）。&lt;/p&gt;

&lt;p&gt;directive号称是angularjs最复杂的部分。第一个指令我写了一天，一天的时间才稍微明白了一点指令的使用。学会指令第一步就是要搞懂它的作用域。&lt;/p&gt;

&lt;p&gt;指令中template参数是一段有html意义的字符串，或是一个html片段的文件的路径。这段内容会被插入到页面dom中，由angularjs编译渲染。这段html片段与其他的片段无异，也可以进行数据绑定。这时候这段html中的数据来源于哪里就是个问题了。&lt;/p&gt;

&lt;p&gt;先上鄙人人生的第一个directive实例：一个手机端的点击发送验证码并进入重发倒计时状态的按钮。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var smsBtn = angular.module(&apos;smsBtnDirective&apos;, []);

smsBtn.directive(&apos;getCodeBtn&apos;,[&apos;$interval&apos;,function($interval) {

  return {
    restrict: &apos;E&apos;,
    replace: true,
    scope: {
      &apos;getCodeFun&apos;: &apos;&amp;amp;&apos;
    },
    template: &apos;&amp;lt;button class=&quot;button button-small button-energized wide_small_button flex_center&quot; ng-click=&quot;getCode()&quot; style=&quot;min-width:92px;&quot;&amp;gt;&amp;lt;/button&amp;gt;&apos;,
    link: function (scope,ele) {
      var timeFlag = 60;
      scope.timeTip = &apos;获取验证码&apos;;

      var minuteClock = function(){
          // 验证码重新发送倒计时
          this.clock = $interval(function(){
              if(timeFlag &amp;gt; 0 &amp;amp;&amp;amp; timeFlag &amp;lt;= 60){
                  timeFlag--;
                  scope.timeTip = timeFlag+&apos; s&apos;;
                  
              }else{
                  $interval.cancel(this.clock);
                  timeFlag = 60;
                  scope.timeTip = &apos;重新发送&apos;;
              }
          },1000);
      };

      scope.getCode = function(){
        var canSend =scope.getCodeFun();
        if(canSend){
          minuteClock();
        }
      };

      ele.on(&apos;$destroy&apos;, function() {
        $interval.cancel(this.clock);
      });

      
    }
  };
}]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用方式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;get-code-btn get-code-fun=&quot;signupStep1.getCode()&quot;&amp;gt;&amp;lt;/get-code-btn&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;相关的调用html片段及js片段&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;item item-input item-button-right&quot;&amp;gt;
    &amp;lt;input type=&quot;tel&quot; name=&quot;smsCode&quot; placeholder=&quot;请输入验证码&quot; ng-model=&quot;signupStep1.phone.smsCode&quot; required&amp;gt;
    &amp;lt;get-code-btn get-code-fun=&quot;signupStep1.getCode()&quot;&amp;gt;&amp;lt;/get-code-btn&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;controller:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;use strict&apos;;
  	var userFindPd = angular.module(&apos;jbj.findPassword&apos;,[&apos;userAuthService&apos;,&apos;smsBtnDirective&apos;]);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

  	userFindPd.controller(‘findPdStep1Ctrl’,[‘findPdSms’,function(findPdSms){

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var vm = this;

vm.getCode = function(){

  if(vm.form.mobilePhoneNumber.$error.phone){
    feedbackMsg.phoneNumberError();
    return false;
  }else{
    findPdSms.get({&apos;mobilePhoneNumber&apos;:vm.phone.mobilePhoneNumber},function(data){
      if(data.code===0){
        feedbackMsg.smsCodeSuccess();
      }else{
        feedbackMsg.smsCodeError(data.message);
      }    
    });
    return true;
  }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;}]);&lt;/p&gt;

&lt;h2 id=&quot;当前控制器的scope作用域&quot;&gt;当前控制器的scope作用域&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scope:false(default)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时，template直接访问到其所插入的控制器中的scope，可以与当前scope下的数据直接绑定。&lt;/p&gt;

&lt;h2 id=&quot;isolating-the-scope-创建指令独立的作用域&quot;&gt;Isolating the Scope ，创建指令独立的作用域。&lt;/h2&gt;

&lt;p&gt;此时我们需要给scope参数赋予新的对象。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scope: {
	newmodel: &apos;=oldmodel&apos;,
	newstring: &apos;@oldstring&apos;,
	newmethod: &apos;&amp;amp;oldmethod&apos;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;two-way-model-bindingmodel&quot;&gt;‘=’Two-way model binding(model)&lt;/h3&gt;

&lt;p&gt;将父scope中的数据与指令scope的变量双向绑定。也就是说oldmodel会和newmodel同步变化。&lt;/p&gt;

&lt;p&gt;可能有些疑惑，这和直接使用父作用域而不创建独立作用域有什么区别？为何还要这番折腾。&lt;/p&gt;

&lt;p&gt;这是为了组件的复用，当你需要在directive里的link或是compile甚至controller函数中对数据进行预处理，在绑到template中展示时就有意义了。因为你不可能要求每一个复用你的组建的controller都使用相同的变量名。而是创建指令自己的作用域和变量名比较科学。这只是我发觉的一个应用场景。&lt;/p&gt;

&lt;h3 id=&quot;attribute-string-bindingstring&quot;&gt;‘@’Attribute string binding(string)&lt;/h3&gt;

&lt;p&gt;用于在子作用域中获取父作用域中的变量。可以获取形式的变量值，获取到的是被渲染后的变量值，但是并不会引起变量的双向绑定。&lt;/p&gt;

&lt;h3 id=&quot;callback-method-bindingmethod&quot;&gt;‘&amp;amp;’Callback method binding(method)&lt;/h3&gt;

&lt;p&gt;用于在指令作用域中获取父作用域中的方法。&lt;/p&gt;

&lt;p&gt;这是很常见的做法，比如回调函数。比如我的示例中的发送验证码的函数，并不能写在directive的link中，因为每一个调用组件的地方获取验证码的接口和处理逻辑都可能不同。为了通用，应该将获取验证码的函数从父scope中引进到子scope中。&lt;/p&gt;

&lt;p&gt;在以上3种方式创建了指令自己独立的作用域后。就可以在指令自己的link、compile、controller函数中通过newmodel、newstring、newmethod的变量名对相关变量进行操作了。&lt;/p&gt;

&lt;p&gt;另外我建议都使用下面这种简写方式，否则变量太多容易头晕。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scope: {
	isolatemodel: &apos;=&apos;,
	isolatestring: &apos;@&apos;,
	isolatemethod: &apos;&amp;amp;&apos;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;##黄金外链
&lt;a href=&quot;https://docs.angularjs.org/guide/directive&quot;&gt;https://docs.angularjs.org/guide/directive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters&quot;&gt;http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>html5 date input 与angularjs ng-model数据绑定</title>
   <link href="http://echizen.github.io/tech/2015/07-19-date-type"/>
   <updated>2015-07-19T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/date-type</id>
   <content type="html">
&lt;p&gt;1、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;input type=&quot;date&quot;&amp;gt;&lt;/code&gt;取到的数据是标准的ISO 8601 格式的时间&lt;/p&gt;

&lt;p&gt;2、angularjs 对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type=&quot;date&quot;&lt;/code&gt;取到的数据必须是new Date()得到的date类型的数据。&lt;/p&gt;

&lt;p&gt;这两种类型需要相互转换，才能让ng-model的date类型的数据正确显示。&lt;/p&gt;

&lt;p&gt;eg：服务器要存储的是iso类型的时间字符串。如：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2015-07-19T11:20:38.172Z&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;展现时，先要通过new Date(‘2015-07-19T11:20:38.172Z’)将数据转换成标准的Date类型，再绑定到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input type=&apos;model&apos;&lt;/code&gt;的ng-model上。&lt;/p&gt;

&lt;p&gt;将数据提交到服务器前要将数据&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new Date(&apos;2015-07-19T11:20:38.172Z&apos;).toISOString()&lt;/code&gt;，通过Date的toISOString方法转换成ISO时间格式。&lt;/p&gt;

&lt;p&gt;（有些服务器在存储时间格式的数据时，会将字符串先转换成对象。如leanCloud的存储服务，会将传递的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;2015-07-19T11:20:38.172Z&apos;&lt;/code&gt;转换成：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
	__type: &apos;date&apos;,
	iso: &apos;2015-07-19T11:20:38.172Z&apos;
} ）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;iso-8601数据格式&quot;&gt;iso 8601数据格式&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;格式： YYYY-MM-DDTHH:mm:ss.sssZ&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;YYYY：格林尼治时间从0000到9999&lt;/li&gt;
      &lt;li&gt;MM：月（01-12）&lt;/li&gt;
      &lt;li&gt;DD：日期（01-31）&lt;/li&gt;
      &lt;li&gt;T ：时间开始标识符&lt;/li&gt;
      &lt;li&gt;HH：小时（00-24）&lt;/li&gt;
      &lt;li&gt;mm：分钟（00-59）&lt;/li&gt;
      &lt;li&gt;ss：秒（00-59）&lt;/li&gt;
      &lt;li&gt;sss： 毫秒数&lt;/li&gt;
      &lt;li&gt;Z：时区标识符，Z代表UTC 0时区，我们实在东8区。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;时区参数非常重要，如果你不指定时区，默认是Z，0时区，但是转化给用户看时是当前客户端所在的时区对应的时间。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;比较奇葩的是00:00和24：00这2个分界点是相同的时间，也就是说2015-07-19T24:00 和 2015-07-19T00:00时间相同。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;黄金外链&quot;&gt;黄金外链&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date&quot;&gt;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://tools.ietf.org/html/rfc2822#page-14&quot;&gt;http://tools.ietf.org/html/rfc2822#page-14&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>html解析换行符</title>
   <link href="http://echizen.github.io/tech/2015/07-19-breakline-show"/>
   <updated>2015-07-19T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/breakline-show</id>
   <content type="html">
&lt;p&gt;遇到简单的textarea用户输入内容的展示时，总是避免不了要展示换行符。而换行符会随其他数据一起存储在服务器中。但是在源码中是看不到的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/AE4DF29E-874D-46DB-822E-95BC69768547.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在测试中发现换行符是存在的，只是没有在代码里显示出来而已，存在于字符数组中，可以通过charCodeAt函数取出看到它的值是10，查一下10是换行符&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/n&lt;/code&gt;的编码。&lt;/p&gt;

&lt;p&gt;知道这些就可以用正则匹配来实现换行了。&lt;/p&gt;

 	(notifications[i][‘messageBody’]).replace(/\r?\n/g,’&lt;br /&gt;’)
</content>
 </entry>
 
 <entry>
   <title>手机端禁止滚动</title>
   <link href="http://echizen.github.io/tech/2015/05-09-mobile-disable-scroll"/>
   <updated>2015-05-09T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/mobile-disable-scroll</id>
   <content type="html">
&lt;p&gt;更新纠正：
bootstrap的模态框在手机端依旧能滚动，是因为它使用了position:fixed属性，改属性在手机上支持性很差，特别是iphone。bootsrap使用的是position:fixed和display:none的结合改变来达到不能滚动。framework7使用的是position:absolute和visibility: hidden来达到效果的，在手机端更有效。&lt;/p&gt;

&lt;p&gt;我在做PC端模态框弹出时，禁止背景内容滚动的方式是在页面中加入一个div(最好作为body的直接子元素)，假设他的class为oveflow-bg，然后给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.oveflow-bg&lt;/code&gt;以下样式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;position: absolute;
  	left: 0;
  	top: 0;
  	width: 100%;
  	height: 100%;
  	background: rgba(0,0,0,.4);
  	z-index: 9999;
  	visibility: hidden;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当模态框弹起时给body&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overflow:hidden&lt;/code&gt;的属性，将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.oveflow-bg&lt;/code&gt;的改为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;visibility: visible;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;这种方式在页面结构简单时有效。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;或者和bootstrap一样，给模态框的最外层div以下属性：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1050;
  display: none;
  overflow: hidden;
  -webkit-overflow-scrolling: touch;
  outline: 0;
  display: none;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当模态框调出时，给body&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overflow:hidden&lt;/code&gt;的属性，给modal添加属性：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;display: block;
  	overflow-x: hidden;
  	overflow-y: auto;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种方式在手机端一点作用也没有&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;今天用framework7时它的做法非常有效。如果我说他是利用颜色来做的（其实是透明度）结合visibility。&lt;/p&gt;

&lt;p&gt;也是一个body的直接子元素：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.popup-overlay, .preloader-indicator-overlay {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,.4);
  z-index: 10600;
  visibility: hidden;
  opacity: 0;
  z-index: 10200;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;模态框打开时添加以下属性：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;visibility: visible;
  	opacity: 1;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我把开始见感到非常神奇啊，透明度可以影响这个，当我在chrome下审查元素时才发现，.popup-overlay的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opacity: 1;&lt;/code&gt;覆盖在元素上时，获取不到其他元素了，也就是没有用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;point-event:none&lt;/code&gt;，也导致鼠标事件无效。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;结论：元素的透明度为0时，相当于不存在，鼠标事件对其失效。（鼠标事件和移动端触摸事件相同）&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这就很好解释原理了，这种方式是一开始就在屏幕上放上了一个满屏的popup-overlay，但是它的opacity=0,visibility: hidden;不会影响其他元素正常被用户点击，但是当他的visibility: visible;时，用户的触摸事件全都在该元素上生效，屏幕便无法滚动了。&lt;/p&gt;

&lt;p&gt;另外：操作过程中，我把该元素.popup-overlay的颜色值调为：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clolor:transparent&lt;/code&gt;,他下面所有的元素都可已被点击了，这其实和opacity=0同理。&lt;/p&gt;

&lt;p&gt;小小颜色，大大妙用。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>css 居中裁剪图片、背景图片</title>
   <link href="http://echizen.github.io/tech/2015/04-30-css-img-center-crop"/>
   <updated>2015-04-30T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/css-img-center-crop</id>
   <content type="html">
&lt;p&gt;对于用户传的图片，或许没有按照要求的规格传，我们有时候会有按我们需要的规格展示的必要，这时候会为了美观裁剪，最科学的裁剪是居中裁剪。&lt;/p&gt;

&lt;p&gt;通常我们会借助第三方插件裁剪，这时是真的将图片裁剪了才存储的。而css的“裁剪”只是展示时的选择性区域处理，并没有真正的裁剪原图片。&lt;/p&gt;

&lt;h2 id=&quot;背景图片展示&quot;&gt;背景图片展示&lt;/h2&gt;

&lt;p&gt;CSS&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;i.icon.contactsLogo{
    width: 100px;
    height: 100px;
    -webkit-transition-duration: 300ms;
    transition-duration: 300ms;
    background-position: center center;
    background-size: cover;
    background-repeat: no-repeat;
    background-image: url(&apos;http://www.huiyouxing.com/image/meeting.png&apos;);
    display: inline-block;
    vertical-align: middle;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;html&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;item-media&quot;&amp;gt;
     &amp;lt;i class=&quot;icon contactsLogo contactsLogo4&quot; id=&quot;logoPreview&quot;&amp;gt;&amp;lt;/i&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;示例：
&lt;a href=&quot;http://jsfiddle.net/echizen/0oer38w8/2/&quot;&gt;http://jsfiddle.net/echizen/0oer38w8/2/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;图片展示&quot;&gt;图片展示&lt;/h2&gt;

&lt;p&gt;这个有缺陷，你得先知道你的图片是横屏还是竖屏模型（width&amp;gt;height还是width&amp;lt;height）。2种情况分别使用不同的样式，我还没有研究一种样式解决所有情况的。&lt;/p&gt;

&lt;p&gt;CSS:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.thumbnail {
  position: relative;
  width: 200px;
  height: 200px;
  overflow: hidden;
}

&amp;lt;!--width &amp;gt; height--&amp;gt;
.thumbnail img {
  position: absolute;
  left: 50%;
  top: 50%;
  height: 100%;
  width: auto;
  -webkit-transform: translate(-50%,-50%);
      -ms-transform: translate(-50%,-50%);
          transform: translate(-50%,-50%);
}

&amp;lt;!--width &amp;lt; height--&amp;gt;
.thumbnail img.portrait {
  width: 100%;
  height: auto;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;HTML:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;!--width &amp;gt; height--&amp;gt;
&amp;lt;div class=&quot;thumbnail&quot;&amp;gt;
  &amp;lt;img src=&quot;landscape-img.jpg&quot; alt=&quot;Image&quot; /&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;!--width &amp;lt; height--&amp;gt;
&amp;lt;div class=&quot;thumbnail&quot;&amp;gt;
  &amp;lt;img src=&quot;portrait-img.jpg&quot; class=&quot;portrait&quot; alt=&quot;Image&quot; /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;示例：
&lt;a href=&quot;http://jonathannicol.com/blog/2014/06/16/centre-crop-thumbnails-with-css/&quot;&gt;http://jonathannicol.com/blog/2014/06/16/centre-crop-thumbnails-with-css/&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>flexbox 使用注意事项</title>
   <link href="http://echizen.github.io/tech/2015/04-29-layout-extend"/>
   <updated>2015-04-29T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/layout-extend</id>
   <content type="html">
&lt;ol&gt;
  &lt;li&gt;移动端使用android手机测试效果！！！iphone的支持度很好，跟chrome下模拟器的效果一致。但是android就差的大了。毕竟模拟器只是模拟了屏幕尺寸和头信息。估计是chrome和iPhone的浏览器都用的是webkit内核，效果一致。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;为了兼容性，将能加的前缀都加上：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; display: -webkit-box;
 display: -ms-flexbox;
 display: -webkit-flex;
 display: flex;
	
 -webkit-justify-content: space-between;
 justify-content: space-between;
 webkit-box-pack: justify;
 -ms-flex-pack: justify;
	
 -webkit-box-align: center;
 -ms-flex-align: center;
 -webkit-align-items: center;
 align-items: center;
	
 -webkit-flex-wrap: nowrap;
 flex-wrap: nowrap;
	
 -webkit-flex-shrink: 0;
 -ms-flex: 0 0 auto;
 flex-shrink: 0;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;&lt;img src=&quot;http://echizen.github.io/assets/blog-img/QQ20150429-1@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;接下来说说我用flex实现这个页面效果所发现的要注意的几点。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flex-shrink = 0 &lt;/code&gt;防止dom塌陷。
 以文件下载那一条为例，这是3栏布局，中间的文件名是多行，而前后的文件标示图和下载链接是有固定大小的，如果不加这个属性会导致当中间的文件名超过一行时会挤压2边的内容，即使你给文件标示图定了固定的大小，还是会被中间的内容压小。所以要给文件标示图和下载链接以下style:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; -webkit-flex-shrink: 0;
 -ms-flex: 0 0 auto;
 flex-shrink: 0;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;flex下的如果有确定的尺寸的元素，则每个子元素都应该被明确指定flex属性，否则安卓下会破相。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; flex: flex-grow flex-shrink flex-basis|auto|initial|inherit;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;3中我们给左右2个元素指定了flex的flex-shrink属性，现在为了防止中间的文件名挤压左右元素，要给他flex-grow属性，防止他膨胀，也可以写成：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; -webkit-box-flex: 1;
 -ms-flex: 1;
 flex: 1; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flex-wrap&lt;/code&gt;属性安卓4.4以上才支持！！！即使加了前缀。解决方案是给允许换行的子元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:inline-block&lt;/code&gt;属性。&lt;/p&gt;

    &lt;p&gt;图片展示区域即使在外部容器里加了：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   display: -webkit-box;
   display: -moz-box;
   display: -webkit-flex;
   display: -moz-flex;
   display: -ms-flexbox;
   display: flex;
   -webkit-flex-wrap: wrap;
   -moz-flex-wrap: wrap;
   -ms-flex-wrap: wrap;
   flex-wrap: wrap;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;给子元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:inline-block&lt;/code&gt;属性要注意的是，空格和换行符在这里会占位置，譬如你5个子元素，每个宽度20%，这时候会换行！！！因为你的源码里的空格和换行符占了位置。解决方案是给容器加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-size: 0px;&lt;/code&gt;，&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;display为flex的元素text-overflow: ellipsis失效&lt;/p&gt;

    &lt;p&gt;貌似是flex目前还不支持这个。一般解决方案都是曲线救国，在flex的元素里放上子元素，给子元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline-block&lt;/code&gt;的display。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;黄金资料&quot;&gt;黄金资料&lt;/h3&gt;

&lt;p&gt;别人整理的bug合集：&lt;a href=&quot;https://github.com/philipwalton/flexbugs&quot;&gt;https://github.com/philipwalton/flexbugs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;flex虽美，但请慎用！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>富文本编辑器中内容在移动端适配问题</title>
   <link href="http://echizen.github.io/tech/2015/04-26-richtext-style"/>
   <updated>2015-04-26T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/richtext-style</id>
   <content type="html">
&lt;p&gt;我们经常遇到一种场景，管理者在电脑端后台的文本编辑器下编辑了一段内容，然后要在手机端给用户展现出这段内容。譬如说最近开发的会友行的会议详情部分，手机端会议内容部分包括时间地点、人数等等，还有富文本编辑器内的会议详情部分，这部分尝尝包含图片、表格等多重样式，在手机端如果不把这部分内容的容器的样式设置好，经常会破相，出现滚屏啦、内容被截啦。&lt;/p&gt;

&lt;p&gt;在容器div下加上这些样式就可以避免这个问题了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.rich_media_content {
  position: relative;
  overflow: hidden;
}

.rich_media_content * {
  max-width: 100%!important;
  box-sizing: border-box!important;
  -webkit-box-sizing: border-box!important;
  word-wrap: break-word!important;
}

.rich_media_content table {
    margin-bottom: 10px;
    border-collapse: collapse;
    display: table;
    width: 100%!important
}

.rich_media_content td,th {
    word-wrap: break-word;
    word-break: break-all;
    padding: 5px 10px;
    border: 1px solid #DDD
}

.rich_media_content caption {
    border: 1px dashed #DDD;
    border-bottom: 0;
    padding: 3px;
    text-align: center
}

.rich_media_content th {
    border-top: 2px solid #BBB;
    background: #f7f7f7
}

.rich_media_content td p {
    margin: 0;
    padding: 0
}

.rich_media_content form {
    display: none!important
}

@media screen and (min-width: 0\0) and (min-resolution:72dpi) {
    .rich_media_content table {
        table-layout:fixed!important
    }

    .rich_media_content td,.rich_media_content th {
        width: auto!important
    }
}

.rich_media_content .tc {
    text-align: center
}

.rich_media_content .tl {
    text-align: left
}

.rich_media_content .tr {
    text-align: right
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;html结构：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;rich_media_content&quot;&amp;gt;
  	&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
  	&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>flexbox layout</title>
   <link href="http://echizen.github.io/tech/2015/04-26-flexbox-layout"/>
   <updated>2015-04-26T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/flexbox-layout</id>
   <content type="html">
&lt;h2 id=&quot;兼容性&quot;&gt;兼容性&lt;/h2&gt;

&lt;p&gt;用framework7的时候学习了flex布局：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;display: -webkit-box; /* Chrome 4+, Safari 3.1, iOS Safari 3.2+ */
display: -moz-box; /* Firefox 17- */
display: -webkit-flex; /* Chrome 21+, Safari 6.1+, iOS Safari 7+, Opera 15/16 */
display: -moz-flex; /* Firefox 18+ */
display: -ms-flexbox; /* IE 10 */
display: flex; /* Chrome 29+, Firefox 22+, IE 11+, Opera 12.1/17/18, Android 4.4+ */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;box布局是flex布局的前身，为了兼容性，一般写成上面的格式。在safari和ios系统上必须加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-webkit&lt;/code&gt;前缀。&lt;/p&gt;

&lt;p&gt;兼容性：各大系统、浏览器都表现了很好的兼容性&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150426-1@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;使用场景&quot;&gt;使用场景&lt;/h2&gt;

&lt;p&gt;flex是为了更加灵活的布局。flex容器里的元素分配的是相对空间。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;有利于屏幕适配。flex布局不依赖于固定尺寸。wrap和justify-content属性让我们在不使用媒体查询的时候也能优雅的适配屏幕布局。&lt;/li&gt;
  &lt;li&gt;可以让不知道大小的元素在容器里按我们想要的方式布局。这解决了很多因为动态大小元素带来的问题。&lt;/li&gt;
  &lt;li&gt;可以很好的处理元素之间的空隙问题，可以防止overflow现象。&lt;/li&gt;
  &lt;li&gt;flex布局可以设置元素方向，水平垂直，左向右都可以，然而float是垂直定位，inline是水平定位。&lt;/li&gt;
  &lt;li&gt;如同float带来的流式布局一样，flex系列属性能完成任意的布局要求，只是相比其他布局的简易而已。所以遇到问题时我们要寻找属性，知道flex什么都可以做。当然我们可以在一个页面中结合多种布局。&lt;/li&gt;
  &lt;li&gt;但是flex布局对方向改变、元素大小改变、竖屏变横屏这些方面有需求的布局不适合，也不适合复杂的元素嵌套关系的布局。flex布局适合小而美的页面，而栅格布局才适合复杂页面。&lt;/li&gt;
  &lt;li&gt;不确定大小的元素垂直居中变得可行。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;属性&quot;&gt;属性&lt;/h2&gt;

&lt;p&gt;flex 布局涉及到2方面，父容器（container）和子元素(item)，配合使用才能完成flex布局。(以下图片来源于网络，非原创)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clear&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-align&lt;/code&gt; 在flex元素上无效.&lt;/p&gt;

&lt;h3 id=&quot;container属性&quot;&gt;container属性&lt;/h3&gt;

&lt;h3 id=&quot;1-display&quot;&gt;1. display&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.container {
  	display: -webkit-box;
  		display: -moz-box;
  		display: -ms-flexbox;
  		display: -webkit-flex;
  		display: flex;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-justify-content单行元素左右空隙分布&quot;&gt;2. justify-content：单行元素左右空隙分布&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.container {
  justify-content: flex-start | flex-end | center | space-between | space-around;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;flex-start (default)&lt;/strong&gt;: 一行子元素相对于容器左对齐&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;flex-end&lt;/strong&gt;: 行元素右对齐&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;center&lt;/strong&gt;: 行元素居中&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;space-between&lt;/strong&gt;: 第一个元素不留空隙左对齐，最后一个元素不留空隙右对齐，空隙均匀分布在元素之间&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;space-around&lt;/strong&gt;: 空隙被均匀的分布在元素之间，但由于第一个元素与边界只有左空隙，而元素与元素之间既有前一个元素的右空隙又有后一个元素的左空隙，所以结果是首尾元素距离边界的空隙只有元素与元素之间空隙的一半。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150426-4@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-align-items-单行元素上下空隙分布&quot;&gt;3. align-items： 单行元素上下空隙分布&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.container {
  align-items: flex-start | flex-end | center | baseline | stretch;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;flex-start&lt;/strong&gt;: 子元素与容器上边界对齐&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;flex-end&lt;/strong&gt;: 下对齐&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;center&lt;/strong&gt;: 中心线对其&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;baseline&lt;/strong&gt;: 依据元素内的字下方对齐&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;stretch&lt;/strong&gt; (default): 子元素被拉长至与父容器等高，子元素和父元素上下都对齐。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150426-5@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;4-align-content-多行元素上下空隙分布&quot;&gt;4. align-content： 多行元素上下空隙分布&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.container {
  align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;flex-start&lt;/strong&gt;: 多行子元素与容器上边界对齐，下方留空隙。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;flex-end&lt;/strong&gt;: 下对齐。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;center&lt;/strong&gt;: 居中对齐，上下留白。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;space-between&lt;/strong&gt;: 多行平均分布，第一行与容器上边界对其，最后一行与容器下边界对其，行于行之间留相同的空隙&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;space-around&lt;/strong&gt;: 多行平均分布，但是上下边界与元素之间也留等高的空隙。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-flex-direction布局方向&quot;&gt;5. flex-direction：布局方向&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.container {
  	flex-direction: row | row-reverse | column | column-reverse;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150426-2@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;6-flex-wrap子元素换行方式&quot;&gt;6. flex-wrap：子元素换行方式&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.container{
  flex-wrap: nowrap | wrap | wrap-reverse;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;nowrap (default): 不换行，单行排完 / left to right in ltr; right to left in rtl&lt;/li&gt;
  &lt;li&gt;wrap: 多行，允许换行，从左向右排列 / left to right in ltr; right to left in rtl&lt;/li&gt;
  &lt;li&gt;wrap-reverse: 多行，从右向左排列 / right to left in ltr; left to right in rtl	
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150426-3@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;7-flex-flow-flex-direction-和flex-wrap的简洁写法&quot;&gt;7. flex-flow :flex-direction 和flex-wrap的简洁写法&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;flex-flow: &amp;lt;‘flex-direction’&amp;gt; || &amp;lt;‘flex-wrap’&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;==============================&lt;/p&gt;

&lt;h3 id=&quot;item属性&quot;&gt;item属性&lt;/h3&gt;

&lt;h3 id=&quot;1-order元素排列顺序&quot;&gt;1. order：元素排列顺序&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.item {
  order: &amp;lt;integer&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;设置元素排列顺序，这可能在js中动态操作元素时有用。默认是按元素元素书写前后顺序排列。&lt;/p&gt;

&lt;h3 id=&quot;2-flex-grow相对于其他元素扩大宽度倍数&quot;&gt;2. flex-grow：相对于其他元素扩大宽度倍数。&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.item {
  flex-grow: &amp;lt;number&amp;gt;; /* default 0 */
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150426-6@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;测试地址：&lt;/p&gt;

&lt;h3 id=&quot;3-flex-shrink相对于其他元素缩小宽度倍数&quot;&gt;3. flex-shrink：相对于其他元素缩小宽度倍数。&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.item {
  flex-shrink: &amp;lt;number&amp;gt;; /* default 1 */
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;测试地址：&lt;a href=&quot;http://www.w3schools.com/cssref/playit.asp?filename=playcss_flex-shrink&amp;amp;preval=1&quot;&gt;http://www.w3schools.com/cssref/playit.asp?filename=playcss_flex-shrink&amp;amp;preval=1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;若(代码不完整，仅为说明关键点)：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;style type=&quot;text/css&quot;&amp;gt;
    .item1{
        flex-shrink:1;
    }
    .item2{
        flex-shrink:2;
    }
&amp;lt;/style&amp;gt;

&amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;div class=&quot;item1&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;item2&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：以上示例是把container分成了3份，item1占2/3，item2占1/3。但是实际上浏览器往往计算结果有偏差。（也可能是我理解有偏差，但是琢磨了很久，一直没找到倍数关系）&lt;/p&gt;

&lt;h3 id=&quot;4-flex-basis-元素初始宽度&quot;&gt;4. flex-basis ：元素初始宽度。&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.item {
  flex-basis: &amp;lt;length&amp;gt; | auto; /* default auto */
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;5-align-self元素排列靠齐方向&quot;&gt;5. align-self：元素排列靠齐方向。&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.item {
  align-self: auto | flex-start | flex-end | center | baseline | stretch;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;属性值含义同align-items。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150426-7@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;6-flexflex-grow-flex-shrink-和-flex-basis的简写&quot;&gt;6. flex：flex-grow, flex-shrink 和 flex-basis的简写&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.item {
  flex: none | [ &amp;lt;&apos;flex-grow&apos;&amp;gt; &amp;lt;&apos;flex-shrink&apos;&amp;gt;? || &amp;lt;&apos;flex-basis&apos;&amp;gt; ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;疑惑点：flex-shrink、flex-grow计算。flex-basis为0px和auto的含义&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;黄金参考资料&quot;&gt;黄金参考资料&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/snippets/css/a-guide-to-flexbox/&quot;&gt;https://css-tricks.com/snippets/css/a-guide-to-flexbox/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.w3schools.com/cssref/css3_pr_flex.asp&quot;&gt;http://www.w3schools.com/cssref/css3_pr_flex.asp&lt;/a&gt;
w3schools的play it部分改变列子参数很直观。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://philipwalton.github.io/solved-by-flexbox/&quot;&gt;http://philipwalton.github.io/solved-by-flexbox/&lt;/a&gt;
这是最具体的demo了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>初识原型和构造器</title>
   <link href="http://echizen.github.io/tech/2015/04-02-prototype"/>
   <updated>2015-04-02T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/prototype</id>
   <content type="html">
&lt;p&gt;首先，阐述一下，为了弄清楚原型、闭包，我看了很多博客文章，最终真正有感悟的是这篇：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://dmitrysoshnikov.com/ecmascript/javascript-the-core/&quot;&gt;http://dmitrysoshnikov.com/ecmascript/javascript-the-core/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;感谢原作者阐述的这么详细。&lt;/p&gt;

&lt;p&gt;首先解释一下，ECMAScript是一套规范，javascript是实现这套规范的语言。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原型&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一个对象的prototype属性在chrome控制台下可以在__proto__里看到，是以内部的[[Prototype]]属性来引用的。&lt;/p&gt;

&lt;p&gt;这篇文章整理的最精彩的是这些图片，清晰的阐述了逻辑。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var foo = {
  		x: 10,
  		y: 20
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150412-1@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到定义了一个对象后，他除了拥有自己显式定义的属性和方法外，还拥有隐式的__proto__属性,指向他的原型prototype属性。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原型链&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var a = {
  x: 10,
  calculate: function (z) {
    return this.x + this.y + z
  }
};

var b = {
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate(30); // 60
c.calculate(40); // 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150412-2@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果一个属性或者一个方法在对象自身中无法找到，然后它会尝试在原型链中寻找这个属性/方法。如果这个属性在原型中没有查找到，那么将会查找这个原型的原型，以此类推，遍历整个原型链（当然这在类继承中也是一样的）。第一个被查找到的同名属性/方法会被使用。因此，一个被查找到的属性叫作继承属性。如果在遍历了整个原型链之后还是没有查找到这个属性的话，返回undefined值。&lt;/p&gt;

&lt;p&gt;注意，继承方法中所使用的this的值被设置为原始对象，而并不是在其中查找到这个方法的（原型）对象。也就是，在上面的例子中this.y取的是b和c中的值，而不是a中的值。但是，this.x是取的是a中的值，并且又一次通过原型链机制完成。&lt;/p&gt;

&lt;p&gt;如果没有明确为一个对象指定原型，那么它将会使用__proto__的默认值－Object.prototype。Object.prototype对象自身也有一个__proto__属性，这是原型链的终点并且值为null。&lt;/p&gt;

&lt;p&gt;也就是说通过原型指定的方式建立起的一群对象会形成一个原型链，支部的对象能访问根部对象的属性，这个属性的访问是通过原型链的向上查找定位到的。根部对象的原型指向Object,Object的原型指向null。&lt;/p&gt;

&lt;h2 id=&quot;构造函数&quot;&gt;构造函数&lt;/h2&gt;

&lt;p&gt;构造函数自动地为新创建的对象设置一个原型对象。这个原型对象存储在ConstructorFunction.prototype属性中。&lt;/p&gt;

&lt;p&gt;我们可以使用构造函数来重写上一个拥有对象b和对象c的例子。因此，对象a（一个原型对象）的角色由Foo.prototype来扮演：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// a constructor function
function Foo(y) {
  // which may create objects
  // by specified pattern: they have after
  // creation own &quot;y&quot; property
  this.y = y;
}

// also &quot;Foo.prototype&quot; stores reference
// to the prototype of newly created objects,
// so we may use it to define shared/inherited
// properties or methods, so the same as in
// previous example we have:

// inherited property &quot;x&quot;
Foo.prototype.x = 10;

// and inherited method &quot;calculate&quot;
Foo.prototype.calculate = function (z) {
  return this.x + this.y + z;
};

// now create our &quot;b&quot; and &quot;c&quot;
// objects using &quot;pattern&quot; Foo
var b = new Foo(20);
var c = new Foo(30);

// call the inherited method
b.calculate(30); // 60
c.calculate(40); // 80

// let&apos;s show that we reference
// properties we expect

console.log(

  b.__proto__ === Foo.prototype, // true
  c.__proto__ === Foo.prototype, // true

  // also &quot;Foo.prototype&quot; automatically creates
  // a special property &quot;constructor&quot;, which is a
  // reference to the constructor function itself;
  // instances &quot;b&quot; and &quot;c&quot; may found it via
  // delegation and use to check their constructor

  b.constructor === Foo, // true
  c.constructor === Foo, // true
  Foo.prototype.constructor === Foo // true

  b.calculate === b.__proto__.calculate, // true
  b.__proto__.calculate === Foo.prototype.calculate // true

);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150412-3@2x.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;js中的一个函数在创建的时候，会自动被分配到一个prototype属性，这个属性是指向自己本身的一个对象。拥有自己本身拥有的所有属性（上面的示例中Foo.prototype.y存在）。&lt;/li&gt;
  &lt;li&gt;函数的prototype属性又有一个constructor属性，这个属性指向函数本身。&lt;/li&gt;
  &lt;li&gt;我们可以通过拓展函数的prototype属性，让它的实例（继承自他的函数）拥有这些属性。（这时候，该函数本身并不带有这些属性，其实这个设计模式就是要该函数作为公共函数，并不需要这些私有、留给实例拓展的属性。）&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;混淆点&quot;&gt;混淆点&lt;/h2&gt;

&lt;p&gt;最开始看这个时很纳闷__proto__和propotype的关系的.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;对于对象来说，__proto__是对象原型prototype的引用。在chrome控制台下，是无法直接访问对象propotype属性的，只能访问__proto__属性，打印出来是对象本身。&lt;/li&gt;
  &lt;li&gt;函数的__proto__指向空函数function Empty()
，函数的prototype属性指向函数的同名的空对象。在chrome控制台下可访问函数的__proto__属性和prototype属性。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;还是之前的例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var a = {
  x: 10,
  calculate: function (z) {
    return this.x + this.y + z
  }
};

a.__proto__;  // Object {}
a.property;   // undefined 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;=============&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function Foo(y) {
  this.y = y;
}
Foo.__proto__; // function Empty() {}
Foo.prototype; //Foo {}

Foo.prototype.y; //undefined
Foo.prototype.constructor(1);
Foo.prototype.y; // 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;困惑&quot;&gt;困惑&lt;/h2&gt;

&lt;p&gt;对于上面为什么调用了构造函数之后，就可以通过原型访问函数内部变量我表示不理解。&lt;/p&gt;

&lt;p&gt;============&lt;/p&gt;

&lt;p&gt;new一个已存在的函数会继承他的原型，拥有他的属性。&lt;/p&gt;

&lt;h2 id=&quot;prototype是什么&quot;&gt;prototype是什么&lt;/h2&gt;

&lt;p&gt;开始我看了很多实例，越看越郁闷prototype是什么。“原型是一个对象，其他对象可以通过它实现属性继承。”&lt;/p&gt;

&lt;p&gt;这么说吧，你定义了一个函数,并且对他进行了实例化：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var baseObj = function(){};
var extendObj = new baseObj();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时，extendObj拥有了所有baseObj的属性和方法，并且通过prototype属性可以拓展。baseObj称为extendObj的构造器，prototype称为原型。extendObj称为对象。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseObj.prototype.functionA&lt;/code&gt;拓展的属性和方法只对实例化后的对象存在，baseObj本身不带有functionA.且再次实例化baseObj，baseObj的对象仍带有属性functionA&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;通过这个特征，那么使用原型拓展，而不是直接拓展对象的属性的作用是，拓展了原型，就一次性拓展了所有原型的对象。这在很多情况下都是很有用的。&lt;/p&gt;

&lt;h2 id=&quot;测试代码&quot;&gt;测试代码&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var baseObj = function(){
    this.name = &apos;echizen&apos;;
    this.getName = function() {
        console.log(&apos;the name in baseObj:&apos;+this.name);
    };
};

var extendObj = new baseObj();

//对象可以继承原型的属性
extendObj.getName();
//console: the name in baseObj:echizen

//通过prototype原型的改变会体现在对象上
baseObj.prototype.age = 0;
extendObj.age;
//console:0

//对象的改变不影响原型
extendObj.age = 1;
extendObj.age;
//console:1
baseObj.prototype.age;
//console:0
baseObj.age;
//undefined

baseObj.sex = &apos;女&apos;;
extendObj.sex;
//undefined

var extendAnother = new baseObj();
extendAnother;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后的这个结果特别有代表性：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;原型扩展的属性可被所有对象共享，不管这个对象是先于原型的这个属性创建还是晚于：&lt;/strong&gt;extendAnother已拥有所有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseObj&lt;/code&gt;及其所拓展的属性。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;构造器原型（prototype）的控制器（constructor）指向构造器本身&lt;/strong&gt;：函数的constructor指向的是函数的引用。这里可以看到，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseObj.prototype.constructor&lt;/code&gt;指向了baseObj本身，并且拥有我们后来给baseObj赋的属性sex.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;每个新创建的函数的prototype都指向function Empty() {}&lt;/strong&gt;:&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseObj.constructor.prototype&lt;/code&gt;指向了function Empty() {}&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;函数的内部原型__proto__指向其构造器的原型constructor.prototype&lt;/strong&gt;:所以baseObj.&lt;strong&gt;proto&lt;/strong&gt;== baseObj.constructor.prototype; extendObj.&lt;strong&gt;proto&lt;/strong&gt;==extendObj.constructor.prototype。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;所有新创建的构造器/函数的__proto__都指向（Empty function），他是Function.prototype&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;函数的内部原型(&lt;strong&gt;proto&lt;/strong&gt;),构造器的原型（prototype）。&lt;/p&gt;

&lt;h2 id=&quot;作用&quot;&gt;作用&lt;/h2&gt;
&lt;p&gt;其实我看一个东西最关注的是他能干什么，如果不是最近笔试遇到这个，我估计不会关注这个。&lt;/p&gt;

&lt;p&gt;现在在我看来，原型的存在是为了对象的继承和属性扩展，毕竟js不是面向对象的语言，没有类的概念。利用原型我们可以是开发模块化，把共同的部分抽出来作为一个对象，再来拓展这个对象完成功能多样化。&lt;/p&gt;

&lt;p&gt;作用就是我们可以将js内置的一些对象函数实例化，然后扩展他们，这样我们就能在已有的东西上做开发，就能使用内置的一些函数对象的属性和方法了。&lt;/p&gt;

&lt;p&gt;参考：
&lt;a href=&quot;http://www.cnblogs.com/snandy/archive/2012/09/01/2664134.html&quot;&gt;http://www.cnblogs.com/snandy/archive/2012/09/01/2664134.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://blog.csdn.net/hikvision_java_gyh/article/details/8937157&quot;&gt;http://blog.csdn.net/hikvision_java_gyh/article/details/8937157
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://weizhifeng.net/javascript-the-core.html&quot;&gt;http://weizhifeng.net/javascript-the-core.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://bonsaiden.github.io/JavaScript-Garden/zh/&quot;&gt;https://bonsaiden.github.io/JavaScript-Garden/zh/&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>js实现模板替换</title>
   <link href="http://echizen.github.io/tech/2015/04-01-make-template"/>
   <updated>2015-04-01T23:12:43+00:00</updated>
   <id>http://echizen.github.io/tech/2015/make-template</id>
   <content type="html">
&lt;p&gt;js实现模板替换：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String.prototype.render = function(arr){
	var replaceKey = [];
	var str = [];

	for(var key in arr){
   		replaceKey.push(key);
    	str.push(arr[key]);
	}

	var format = this;

	for(var i=0; i&amp;lt;replaceKey.length;i++){
    	var reg = new RegExp(&quot;\\${&quot; +replaceKey[i]+ &quot;}&quot;, &quot;g&quot;);
    	format = format.replace(reg,str[i]);
    	console.log(format);
	}
	return format;
}

var greeting = &apos;my name is ${name}, age ${age}&apos;;
var result = greeting.render({&apos;name&apos;:&apos;jack&apos;,&apos;age&apos;:16});
console.log(result);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这是阿里2015年实习生招聘的一个笔试题。主要考查了原型继承、正则匹配替换。细节有考查了如何遍历获取json中的见和值，如何在正则中使用变量。当时我是花了20分钟还没写出来，瞎弄了个死版本，觉得这题有意思，于是晚上又思考了下，弄出来了比较灵活的版本。&lt;/p&gt;

&lt;p&gt;关于原型继承我是昨天才去看了2篇博客，平时没用过。看到这题后我还是想到是prototype的用法，拓展某个原型，看到题给的是字符串，我在控制台下试出是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;折腾了我比较久的是由于’$’在正则中是结束符，平时写正则是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/\$\{name\}/&lt;/code&gt;就好，但是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new RegExp()&lt;/code&gt;不行，因为它的第一个参数是字符串，\在字符串中需要转义为\，所以需要双重转义符&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\\&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;在遇到了如何自己用js实现路由，如何自己用js实现数据双向绑定后，我又遇到了如何自己用js实现模板。。。对于我这个长期用框架的人来说，可算是折腾。我也感觉到自己的学习方式有误，不能一贯用框架，应该有自己的思考，才不至于沦陷为搬运工。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原型闭包什么的高阶js还是要看看，虽然平时不懂也可以写出东西，但是那些是很原始很低级的写法。了解了这些你可以让你的代码变得高质，而且掌握了基础之后，才会发现还可以这么写，方便高效&lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>浅谈hybrid APP前端架构</title>
   <link href="http://echizen.github.io/tech/2015/03-29-hybrid-app"/>
   <updated>2015-03-29T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/hybrid-app</id>
   <content type="html">
&lt;p&gt;随着这个hybrid 项目接近发版，我想写点什么。这个项目最大的收获是亲身实验了mvc的架构吧，其他的都是搬运工，搬运F7封装好的一些功能效果。不过架构还是有可取之处的。&lt;/p&gt;

&lt;p&gt;先不说hybrid和mvc的好处了，网上一片一片的。我只说最显著的，hybrid app很灵活，网页部分你可以天天更新，每天优化，免去了发版升级。mvc架构更清晰，与后台交互成本变低，缓存也变得可能，网页性能上升。&lt;/p&gt;

&lt;h2 id=&quot;感受&quot;&gt;感受&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;单页面：像Framework7、Iconic 都是单页面应用框架，但是做hybrid APP时，不可能是单页面，与原生页面的切换结合注定我们不可能是单一入口的单页面，这些页面有多个入口，但对于每个入口进入的一系列web页面，都是单页面。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;缓存：手机端加载页面很慢，架构时一定要考虑到缓存，js的通用资源要放一起。能确定不变的东西要剥出来。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;requiredJS：影响页面速度的主要是js的加载和渲染，所以js一定不要手写，一便又一遍的请求。单页面时，所有“页面”都留在dom中，返回事件或者js端的逻辑跳转页面会让我们一遍又一遍的访问同一个页面，或者前后页面有公用的js，用requiredJS可以保证资源只被加载一次，不会重复请求加载。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;刷新：缓存的矛盾体。我的原则是用户的刷新行为永远只刷新数据，所以在controller里我会剥离出dataInit独立的请求数据资源，刷新时只调用他。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;modal&quot;&gt;modal&lt;/h2&gt;

&lt;p&gt;这是nodeJs在做，nodeJS可以跟服务器沟通，又与前端JS无缝连接，是一座很好的桥梁，用它来封装数据、处理逻辑是极好的，很遗憾这块不是我写的。&lt;/p&gt;

&lt;h2 id=&quot;view&quot;&gt;view&lt;/h2&gt;

&lt;p&gt;结合F7的框架结构，我抽出来了公用的头和尾，其它的页面仍然是一张一张的，其实单也页面应用并不是真的全都写在一个文件里，只是在浏览器下页面间不是通过url跳转，而是通过ajax请求后放在同一页面内。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html class=&quot;&amp;lt;%=ua.Android != undefined?&apos;android android-4&apos;:&apos;&apos;%&amp;gt;&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui&quot;&amp;gt;
    &amp;lt;meta name=&quot;apple-mobile-web-app-capable&quot; content=&quot;yes&quot;&amp;gt;
    &amp;lt;meta name=&quot;apple-mobile-web-app-status-bar-style&quot; content=&quot;black&quot;&amp;gt;
    &amp;lt;meta name=&quot;format-detection&quot; content=&quot;telephone=no&quot; &amp;gt;
    &amp;lt;title&amp;gt;XXX&amp;lt;/title&amp;gt;

    &amp;lt;!-- Path to Framework7 Library CSS--&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;f7/css/framework7.min.css&quot;&amp;gt;
    &amp;lt;!-- Path to your custom app styles--&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;f7/css/my-app.css&quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;!-- Status bar overlay for fullscreen mode--&amp;gt;
    &amp;lt;div class=&quot;statusbar-overlay&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- Views--&amp;gt;
    &amp;lt;div class=&quot;views&quot;&amp;gt;
        &amp;lt;!-- Your main view, should have &quot;view-main&quot; class--&amp;gt;
        &amp;lt;div class=&quot;view view-main&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;头文件的特色就是不加JS！！！，对，js严重影响加载和渲染速度，在手机端很明显。css我都写到了一个文件里，不过也不是很大，这个我自己也不知道这是不是个好的行为。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;       		&amp;lt;/div&amp;gt;  
		&amp;lt;/div&amp;gt;
  		&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&amp;lt;!-- Path to Framework7 Library JS--&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;f7/lib/framework7.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;!-- js-sdk 文件路径 --&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;f7/lib/jhyx-1.0.0.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;!-- 本地存储文件 --&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;f7/lib/store.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;!-- Path to your require js--&amp;gt;
&amp;lt;script data-main=&quot;f7/app&quot; src=&quot;f7/require.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;尾的特色是只加载框架js，通用js和requiredJS，其他的零部件都交给requiredJS去按需加载吧。&lt;/p&gt;

&lt;p&gt;每个页面为了能解析必要的nodeJS(毕竟咱不是webapp)，都是ejs文件，但是用户在浏览器上看到的url是html,在node端的app.js替换掉html,变成静态页面路由。并且对设备类型进行了检测。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Js-Bridge , 静态页面路由
 */
app.get(&apos;/*.html&apos;,function(req,res){
var curUrl = req.protocol + &apos;://&apos; +req.host + req.url;
var query = req.query;

if (query.layout == undefined || query.layout == &apos;false&apos;) {
    layout = false;
}else{
    layout = true;
}

// 浏览器头检测
var ua = req.headers[&apos;user-agent&apos;],$ = {};

if (/mobile/i.test(ua))
    $.Mobile = true;

if (/like Mac OS X/.test(ua)) {
    $.iOS = /CPU( iPhone)? OS ([0-9\._]+) like Mac OS X/.exec(ua)[2].replace(/_/g, &apos;.&apos;);
    $.iPhone = /iPhone/.test(ua);
    $.iPad = /iPad/.test(ua);
}



if (/Android/.test(ua))
    $.Android = /Android ([0-9\.]+)[\);]/.exec(ua)[1];

if (/webOS\//.test(ua))
    $.webOS = /webOS\/([0-9\.]+)[\);]/.exec(ua)[1];

if (/(Intel|PPC) Mac OS X/.test(ua))
    $.Mac = /(Intel|PPC) Mac OS X ?([0-9\._]*)[\)\;]/.exec(ua)[2].replace(/_/g, &apos;.&apos;) || true;

if (/Windows NT/.test(ua))
    $.Windows = /Windows NT ([0-9\._]+)[\);]/.exec(ua)[1];



if (/MicroMessenger/.test(ua) &amp;amp;&amp;amp; query.weixin == &quot;true&quot;) {
    var mou_sign = require(&apos;cloud/mou_sign&apos;);
    weixin = true;

    mou_sign.signJson(curUrl).then(function(wxconfig){
        wxconfig = JSON.stringify(wxconfig);
        res.render(&apos;f7&apos; + req.path.replace(&apos;.html&apos;,&apos;&apos;),{layout:layout,ua:$,weixin:weixin,wxconfig:wxconfig});
    });

}else{

    weixin = false;
    wxconfig = JSON.stringify({});
    res.render(&apos;f7&apos; + req.path.replace(&apos;.html&apos;,&apos;&apos;),{layout:layout,ua:$,weixin:weixin,wxconfig:wxconfig});
}

});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;其实像Framework7、Iconic 都是单页面应用框架，但是做hybrid APP时，不可能是单页面，与原生页面的切换结合注定我们不可能是单一入口的单页面，这些页面有多个入口，但对于每个入口进入的一系列web页面，都是单页面。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这样的逻辑，加上微信端对页面的应用，让我们的一些页面可能是入口页面也有可能是非入口页面，入口页面是要加载头和尾的，非入口页面却不能，所以我们在app.js设置了layout 变量，通过url赋值。然后在每个页面做判断。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;% if(layout)/{/%&amp;gt;
&amp;lt;% include comm/header %&amp;gt;
&amp;lt;% /}/%&amp;gt;
......
&amp;lt;% if(layout)/{/%&amp;gt;
&amp;lt;% include comm/footer %&amp;gt;
&amp;lt;% /}/%&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(由于与jekyll模板冲突，所以我将大括号+百分号转义了)&lt;/p&gt;

&lt;h2 id=&quot;controller&quot;&gt;controller&lt;/h2&gt;

&lt;p&gt;最最精华的controller部分来了，它处理页面动效、用户交互、模板渲染、路由导航。&lt;/p&gt;

&lt;p&gt;我是给每个页面都有独立的js模块的，然后用requiredJS调用公用模块的js.每个页面的js加载规则在route.js中配置。&lt;/p&gt;

&lt;p&gt;js文件的结构部分截图：
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150329-2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;虽然有点繁，我给每个页面都建了个文件夹，里面就一个controller文件，其实这是考虑，可能我们后期会把modal部分也从nodeJS端迁到js来做。由于约定了文件夹和js文件与页面文件同名，所以规则就很好配置了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Load (or reload) controller from js code (another controller) - call it&apos;s init function
 * @param controllerName
 * @param query
 */
function load(controllerName, query) {
    if (ignoreList.indexOf(controllerName) == -1) {
        require([&apos;js/&apos; + controllerName + &apos;/&apos;+ controllerName + &apos;Controller&apos;], function(controller) {
            controller.init(query);
        });
    };
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;先看看静态资源的构成。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150329-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;-require.js:不用说，requireJS&lt;/p&gt;

&lt;p&gt;-app.js:从尾部文件的代码里也可看到，这是js入口，它有公用模块的config信息。有载入router.js模块并调用他的入口，有初始化f7框架、模板编译函数、mainView初始化.&lt;/p&gt;

&lt;p&gt;-JS文件夹
	-router.js:监听f7的pageBeforeInit()状态，完成对url参数的解析判定，调用load函数，加载相应的js controller,并执行controller。
	-pagename文件夹
		-pagenameController.js:依据页面需求完成数据请求、页面模板解析、事件绑定。&lt;/p&gt;

&lt;p&gt;-lib文件夹：装一些库和插件，如jquery,验证插件什么的，是公用通用静态资源。lib文件夹将在app中做本地缓存。&lt;/p&gt;

&lt;h2 id=&quot;关于缓存&quot;&gt;关于缓存&lt;/h2&gt;

&lt;p&gt;为了速度，静态资源都做本地缓存，譬如css文件、img文件、lib文件夹下的资源，以及一些没有任何数据的静态页面。&lt;/p&gt;

&lt;h2 id=&quot;关于路由&quot;&gt;关于路由&lt;/h2&gt;

&lt;p&gt;其实这个是F7做好的，具体的不清楚，但是可以肯定的是js路由就是：&lt;/p&gt;

&lt;p&gt;1.监听用户行为,如点击链接，或者js端逻辑调用&lt;/p&gt;

&lt;p&gt;2.发送ajax请求获取一个一个的页面内容按照一定的结构规则添置到入口页面。这些页面依旧有自己的url，但是你在浏览器上看到url确实不变的，其实他是hash导航，通过hash参数判断自己当前是那个文件。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>投票调研系统</title>
   <link href="http://echizen.github.io/show/2015/03-22-survey-and-vote"/>
   <updated>2015-03-22T00:00:00+00:00</updated>
   <id>http://echizen.github.io/show/2015/survey-and-vote</id>
   <content type="html">
&lt;p&gt;我负责所有前端页面。此项目拥有大量用户自定义内容，所以基于用户操作，网页会有大量动态变动，这个系统使用的是jquery+bootstrap,这个项目锻炼了我jquery使用能力，使用了大量jquery函数。也正是jquey大量的DOM操作让我感到繁琐，我才去学了angularJS，尝试viewmodel模式，使用数据双向绑定。（因为这套系统用iframe内嵌在另一个平台上。需要登录，就不贴链接了）&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>爱家园</title>
   <link href="http://echizen.github.io/show/2015/03-22-ihome"/>
   <updated>2015-03-22T00:00:00+00:00</updated>
   <id>http://echizen.github.io/show/2015/ihome</id>
   <content type="html">
&lt;p&gt;微信公众帐号：hwijiayuan&lt;/p&gt;

&lt;p&gt;在微信平台上开发的社区交流平台。我负责手机端和PC端所有页面，这个项目中我第一次接触到适配问题，也是第一次完整的跑完手机端一套界面。各种型号的手机屏幕适配，还有html5和一些js函数在android系统、ios系统不同的表现，都在这次项目中收获到。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>会友行</title>
   <link href="http://echizen.github.io/show/2015/03-22-fullpage-test"/>
   <updated>2015-03-22T00:00:00+00:00</updated>
   <id>http://echizen.github.io/show/2015/fullpage-test</id>
   <content type="html">
&lt;p&gt;小而美的会议应用&lt;/p&gt;

&lt;p&gt;一款hybrid App，采用了web页面与原生应用混合的方式，利用JS bridge调用原生应用功能接口。我负责了所有手机端页面和网页发布会议页，采用了Framework7框架，实现了MVC结构，由前端完成了路由、缓存、数据模板；与此同时，我们还开发了一个微信平台，将整个APP的功能迁徙到了微信端。这是我第一次将MVC运用到项目中，让我在前端实现交互逻辑、界面展现。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2015-3:plan-to change at the first step</title>
   <link href="http://echizen.github.io/think/2015/03-01-2015-3-plan"/>
   <updated>2015-03-01T00:00:00+00:00</updated>
   <id>http://echizen.github.io/think/2015/2015-3-plan</id>
   <content type="html">
&lt;p&gt;真是可怕，恍惚间春节刚结束，15年却走到了3月了。&lt;/p&gt;

&lt;p&gt;春节14天假期，没码什么代码，但是看了很多零零碎碎的小东西，也思考了很多。&lt;/p&gt;

&lt;p&gt;思考的结论是：我需要改变，学习习惯、生活习惯都要改变。从小事做起，才能一步步前进。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;plan for life：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1.
利用碎片时间，不在乱逛社交网站。每天至少读3篇技术性文章，知乎时间自由支配，3月份刷朋友圈次数将至2次/天，QQ空间将至1次/天。每周关注一次招聘信息。&lt;/p&gt;

&lt;p&gt;2.
坚决不上无聊无用的课程！！！如果为了应付，一定要带技术性的书籍去，不准玩手机！！！实验还是要好好做的。&lt;/p&gt;

&lt;p&gt;3.
无特殊情况晚上12点之前睡觉，早晨8点之前起床（睡眠保证7.5-8.5小时），每周至少锻炼一次（带小北跑步也算锻炼了，剩下的就是打网球了）。&lt;/p&gt;

&lt;p&gt;4.
友好的与大部分人相处，多认识几个同学，与半生不熟的人见面尝试打完招呼之后还能找到共同话题聊起来。&lt;/p&gt;

&lt;p&gt;5.
心平气和，多听取别人意见，3月抱怨别人次数减至3次以下，不和设计师争论，不和架构师+后端大神+男朋友争论技术性问题，无论怎样，他比我懂得多，多听他的，一次也不准争！！！&lt;/p&gt;

&lt;p&gt;6.
还有好多好多，慢慢培养，先努力做好上面的。&lt;/p&gt;

&lt;p&gt;了解了平时一些没有了解的小知识后，深深的感受到前端发展之快。不学习写出来的东西就老了，前端已经自动化、工具化、模块化发展，接手了很多后台的东西。刚刚琢磨了个大概web app、single page的架构，要用起来。&lt;/p&gt;

&lt;p&gt;之前由于没有认真了解，反而让架构大师替我做了前端架构，用framework 7框架，接触后各种不喜欢，不喜欢这种功能、dom混搭风。但是为时已晚，我推崇angularJS的模块化，它简介犀利的像一门后台语言一样充满逻辑美。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;plan for tech:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1.
日夜兼程，完成公司的项目，做好这hybird app的处女秀。不仅让它跑起来，更要炫起来。既已选定F7，那就好好用起来。&lt;/p&gt;

&lt;p&gt;2.
F7的UI效果不错，要弄清楚一些，尝试看源码研究。&lt;/p&gt;

&lt;p&gt;3.
如果公司没有大项目的话，3、4、5三个月完成一个自己的小app，为了练angularJS。选择yeoman架构，结合一个棒的第三方UI框架。&lt;/p&gt;

&lt;p&gt;4.
一些习惯：学习新东西带入环境，不能光看，写不来也要找个demo体会一下。多看看好的框架的源码。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>click事件在label标签的checkbox、radio中执行2次</title>
   <link href="http://echizen.github.io/tech/2015/02-28-click-event-on-labels"/>
   <updated>2015-02-28T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/click-event-on-labels</id>
   <content type="html">
&lt;p&gt;这是2个月前遇见的事，前一阵有人问我，便想着要记下来。以下叙述不仅针对于angularJs的ng-click事件，对普通的click事件也适用。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;示例一&lt;/em&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;label class=&quot;mul_col check_area&quot; ng-click=&quot;checkGoods($index)&quot;&amp;gt;
    &amp;lt;span class=&quot;wait_check&quot;  ng-class=&quot;{&apos;checked&apos;: item.isChecked}&quot; &amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;input type=&quot;checkbox&quot; name=&quot;isChosed[]&quot; class=&quot;isChose_checkbox&quot; value=&quot;,&quot; /&amp;gt;
&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;js中是这么写的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$scope.checkGoods = function(index){
    $scope.items[index].isChecked = !$scope.items[index].isChecked;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;视觉感受是click事件无效了，因为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.wait_check&lt;/code&gt;的class没有变化，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;item.ischecked&lt;/code&gt;的值也没有变化。分布调试后，发现其实是执行了2次checkGoods()，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ng-click&lt;/code&gt;事件被执行了2次。&lt;/p&gt;

&lt;h2 id=&quot;元素默认绑定click事件&quot;&gt;元素默认绑定click事件&lt;/h2&gt;

&lt;p&gt;一些元素如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;a&amp;gt;、&amp;lt;button&amp;gt;、&amp;lt;input type=&quot;checkbox&quot;&amp;gt;&lt;/code&gt;本生就默认绑定了click事件，即使你不绑定，click事件发生时他们也会接收到。&lt;/p&gt;

&lt;p&gt;当label标签包含input时，label也自动监听原来input默认监听的事件。&lt;/p&gt;

&lt;h2 id=&quot;事件冒泡&quot;&gt;事件冒泡&lt;/h2&gt;

&lt;p&gt;我们知道事件传递方式有2种：事件捕获和事件冒泡。事件会从父元素传到子元素，再从子元素传到父元素，如果事件绑定发生在父元素传到子元素的过程中，则称为事件捕获传递，如果事件绑定发生在子元素传到父元素的阶段，则称为事件冒泡传递。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;parent&quot;&amp;gt;
    &amp;lt;div id=&quot;child&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于父元素包含子元素，子元素属于父元素，所以在子元素上的操作就相当于在父元素上的操作，譬如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#parent&lt;/code&gt;绑定了click事件，则点击子元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#child&lt;/code&gt;，会自然的触发原本绑在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#parent&lt;/code&gt;上的click事件。准确的说这其实是子元素继承了父元素的事件，使子元素父元素绑定了相同的事件。&lt;/p&gt;

&lt;p&gt;什么时候会发生事件传递呢？&lt;strong&gt;父元素和子元素必须绑定了相同的事件&lt;/strong&gt;。我们会觉得子元素继承父元素的事件很正常，这是浏览器规则帮我们做的，但往往忽视自己绑定的事件，如果我们这么做：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$(&apos;#child&apos;).click(function(){
    callChildEvt();
})

$(&apos;#parent&apos;).click(function(){
    callParentEvt();
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当我们点击了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#child&lt;/code&gt;后，会发现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;callChildEvt()&lt;/code&gt;先执行，随后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;callParentEvt()&lt;/code&gt;也执行了。其实这就是事件冒泡，传递给了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#parent&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;lable-control&quot;&gt;lable control&lt;/h2&gt;

&lt;p&gt;label标签的功能不用多说，它把所包含的input的用户交互区域扩展了.&lt;strong&gt;注意的是label和所包含的input都开始绑定默认事件，此时会发生事件冒泡现象&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在label上的click事件的处理函数会触发2次就是由于：第一次是label自己接收到事件，执行处理函数，第二次是input接受到事件后冒泡传递给label，再次触发处理函数。&lt;/p&gt;

&lt;p&gt;再看一例：&lt;/p&gt;

&lt;p&gt;&lt;em&gt;示例二&lt;/em&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
        &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jquery-2.0.3.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;label&amp;gt;标题：
            &amp;lt;input type=&quot;checkbox&quot; &amp;gt;
        &amp;lt;/label&amp;gt;

        &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
            $(&apos;label&apos;).click(function(){
                console.log(&apos;label_click&apos;);
            })

            $(&apos;input&apos;).click(function(){
                console.log(&apos;input_click&apos;);
            })
        &amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;点击label区域运行结果：（不要直接点击input元素）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150301-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;点击label后，label本身绑定的click事件执行一次，同时input本身也绑定有click事件，冒泡传递给了label，在出发一次函数。&lt;/p&gt;

&lt;p&gt;点击input区域：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150301-2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果直接点击input区域，会将click事件冒泡传递给label，执行一次label的处理函数。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;稍作修改，阻止input的冒泡事件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$(&apos;label&apos;).click(function(){
    console.log(&apos;label_click&apos;);
})

$(&apos;input&apos;).click(function(e){
    console.log(&apos;input_click&apos;);
    e.stopPropagation();
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再点击label区域运行结果：（不要直接点击input元素）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150301-4.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可见掐断事件通过input冒泡传递后，label便不能再接收第二次由input传递过来的事件。&lt;/p&gt;

&lt;p&gt;点击input区域：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150301-3.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意的是，无论我们有没有人为的给label绑定click事件，其实你点击label后，其上的click事件处理函数都会发生2遍。我们绑定的处理函数和浏览器默认绑定事件的处理函数是同时进行的。所以label被触发的2次click事件并不是由于默认的和我们绑定的&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;关于本例ng-click的分析&quot;&gt;关于本例ng-click的分析&lt;/h2&gt;

&lt;p&gt;在本例中，点击label区域后，click事件执行一次，触发一次checkGoods()，由于checkbox本身默认也有click事件监听器，所以checkbox又触发一次click，并且将事件冒泡传递给了label，checkGoods()再次被触发。&lt;/p&gt;

&lt;h2 id=&quot;解决方案&quot;&gt;解决方案。&lt;/h2&gt;

&lt;h3 id=&quot;在label内部元素上绑定ng-click&quot;&gt;在label内部元素上绑定ng-click&lt;/h3&gt;

&lt;p&gt;将ng-click绑定到input上，便只会触发一次。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;label class=&quot;mul_col check_area&quot;&amp;gt;
    &amp;lt;span class=&quot;wait_check&quot;  ng-class=&quot;{&apos;checked&apos;: item.isChecked}&quot; &amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;input type=&quot;checkbox&quot; name=&quot;isChosed[]&quot; class=&quot;isChose_checkbox&quot; value=&quot;,&quot; ng-click=&quot;checkGoods($index)&quot; /&amp;gt;
&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;阻止事件冒泡&quot;&gt;阻止事件冒泡。&lt;/h3&gt;
&lt;p&gt;虽然不符合我示例一的功能要求，但是如果你仅仅是想阻止label执行2遍事件，可以在子元素input上阻止事件冒泡，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt; e.stopPropagation()&lt;/code&gt;,很多情况下避免冒泡都是这样做。但是对于label和input组合这么做伤害了初衷，这样点击input后，对于label来说是无效的，点击input不会触发label的click事件了，不符合常识，有一种子元素在该事件上不属于父元素的感觉，这个是要注意的。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>从angularJs的ajax发觉post 请求方式</title>
   <link href="http://echizen.github.io/tech/2015/02-26-the-method-of-http-post"/>
   <updated>2015-02-26T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/the-method-of-http-post</id>
   <content type="html">
&lt;h2 id=&quot;缘由&quot;&gt;缘由&lt;/h2&gt;

&lt;p&gt;前端：angularJs发出ajax请求，提交表单数据。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$scope.remove = function(index){
    var sureDel=confirm(&quot;确定删除该商品？&quot;);
    if (sureDel==true){
        var  deleteData = { cartId:$scope.items[index].cartId};
        $http.post(&quot;{:U(&apos;App/Order/delCartGoods&apos;)}&quot;,deleteData).success(function(result){
            console.log(result);
            if(result == &apos;1&apos;){
                $scope.items.splice(index,1);
            }else{
                alert(&apos;删除失败，请再次操作&apos;);
            }
        }).error(function(){
            alert(&apos;删除失败，请再次操作&apos;);
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;后台：php，ThinkPHP框架&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?
	$id = $_POST[&apos;cartId&apos;];
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果php取到的$id为空。查看网络请求，chrome下的控制台显示&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request Method:POST
Status Code:200 OK&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request Method:POST
Status Code:200 OK&lt;/code&gt;，数据发送出去了啊，发送之前打印一遍也是有值的。&lt;/p&gt;

&lt;p&gt;仔细把header看了一遍，发现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Type:application/json&lt;/code&gt;,平时用form直接提交数据是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Type:application/x-www-form-urlencoded&lt;/code&gt;。google一下，才知道post请求也有4种方式。&lt;/p&gt;

&lt;h2 id=&quot;post请求的4种方式&quot;&gt;post请求的4种方式&lt;/h2&gt;

&lt;h3 id=&quot;applicationx-www-form-urlencoded&quot;&gt;application/x-www-form-urlencoded&lt;/h3&gt;

&lt;p&gt;普通的表单提交，如果不设置enctype 属性，都会以这种方式提交。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
 
title=test&amp;amp;sub%5B%5D=1&amp;amp;sub%5B%5D=2&amp;amp;sub%5B%5D=3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;提交的数据按照 key1=val1&amp;amp;key2=val2 的方式进行编码，key 和 val 都进行了 URL 转码。大多数常用框架如jquery的ajax请求方式就是这种方式。&lt;/p&gt;

&lt;h3 id=&quot;applicationjson&quot;&gt;application/json&lt;/h3&gt;

&lt;p&gt;这种方式用来告诉服务端消息主体是序列化后的 JSON 字符串。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
 
{&quot;title&quot;:&quot;test&quot;,&quot;sub&quot;:[1,2,3]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;angularJs的Ajax默认就是这种方式。这种方式php 就无法通过 $_POST 对象从上面的请求中获得内容。&lt;/p&gt;

&lt;h3 id=&quot;multipartform-data&quot;&gt;multipart/form-data&lt;/h3&gt;

&lt;p&gt;我们使用表单上传文件、图片时，会设定让form 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enctyped=multipart/form-data&lt;/code&gt;，其实就是声明用这种方式提交数据。&lt;/p&gt;

&lt;h3 id=&quot;textxml&quot;&gt;text/xml&lt;/h3&gt;

&lt;p&gt;它是一种使用 HTTP 作为传输协议，XML 作为编码方式的远程调用规范。常用作搜索引擎的 ping 服务，不过个人认为格式比较繁琐，没用过。&lt;/p&gt;

&lt;h2 id=&quot;php接收以applicationjson方式post的数据&quot;&gt;php接收以application/json方式post的数据&lt;/h2&gt;

&lt;h3 id=&quot;从-phpinput-里获得原始输入流再-json_decode-成对象&quot;&gt;从 php://input 里获得原始输入流再 json_decode 成对象&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?
	$postdata = file_get_contents(&quot;php://input&quot;);
	$request = json_decode($postdata);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;声明header&quot;&gt;声明header&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?
	header(&apos;Content-Type: application/json&apos;);
	$request = json_encode($_POST);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>移动开发的几个坑</title>
   <link href="http://echizen.github.io/tech/2015/02-25-the-different-performance-in-moble-page"/>
   <updated>2015-02-25T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/the-different-performance-in-moble-page</id>
   <content type="html">
&lt;p&gt;在写移动端界面时遇到了一些坑，很多是移动端浏览器本身就不同的表现，但是因为没经验，一直以为是自己代码的问题。记录自己遇到的几个坑，防止二度跳坑。&lt;/p&gt;

&lt;h2 id=&quot;positonfixed&quot;&gt;positon:fixed&lt;/h2&gt;

&lt;p&gt;可以说这是一个老大难的问题，不仅ios系统有，某些安卓机也有。ios6及以下肯定有这个问题，ios7的一些版本修复了这个bug，但是到了ios8至ios8.1.3（写这篇时的版本）都还有这个问题。andriod的情况比ios好，andriod2.X以上版本基本都支持了。&lt;/p&gt;

&lt;p&gt;这其实是浏览器的bug，但是浏览器不支持这个是有理由的。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fixed&lt;/code&gt;属性实在太消耗浏览器体力：我们所看到的页面其实viewport区域是不变的，滑动是是让内容在viewport区域刷过，就相当于page是一张纸，viewport是一个带有一定面积透明可见的黑盒子，我们在盒子里抽纸，纸在移动，我们只能从盒子的透明部分看到纸上的内容，纸本身是一个整体，然而fixed的部分属于纸这个整体却要脱离这个整体粘在盒子的透明部分上。。。只要你在抽纸（滑动），浏览器只好不辞辛苦的实时计算，将fixed的部分在纸上移动，挪到合适的部位。我们经常看到fixed的部分在滑动时乱窜。这在性能比不上电脑的手机上是很消耗的一种行为，所以很多系统的浏览器都对fixed支持不好。&lt;/p&gt;

&lt;p&gt;这个问题的解决方案一般是一些小技巧，譬如说由于ios下fixed错位发生在键盘弹起时，所以一般在input、textarea的focus事件发生时，让fixed的元素”display:none”掉或变成absolutely的定位，等到输入框发生blur事件时再变回原样，但是absolutely不是那么好用的，此时你可能为了可视区域不跳动，还得设元素的offsetTop的值。&lt;/p&gt;

&lt;p&gt;如果你的fixed的元素中包含输入框，就像这样，那你就悲催了，小技巧不能用了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150225-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其实iscroll和 Sencha Touch等一些框架已经做到了fixed的真正的效果，是通过js实时计算模拟的，但是就为了fixed元素引入这么重的框架，显然是不合适的。惭愧的是我也没有认真研究透这是怎么工作的。&lt;/p&gt;

&lt;h2 id=&quot;overflowhidden&quot;&gt;overflow:hidden&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&amp;lt;html&amp;gt;
	    &amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;
	    &amp;lt;body&amp;gt;
	        &amp;lt;section class=&quot;fullScreen&quot;&amp;gt;&amp;lt;/section&amp;gt;
	    &amp;lt;/body&amp;gt;
	&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种结构的DOM，如果配上一下css，overflow:hidden是生效的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;html,body,#fullScreen{
    width: 100%;
    height: 100%;
    overflow: hidden;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是如果这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overflow:hidden&lt;/code&gt;是js动态增添的，则无效，手机端浏览器会对动态增添的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overflow:hidden&lt;/code&gt;属性熟视无睹。即使注明框架bootstrap的modal后的遮罩层也没有解决这个问题。&lt;/p&gt;

&lt;p&gt;这个问题不容易发现，因为在电脑端即使是chrome的模拟器里都是不存在这种现象的。&lt;/p&gt;

&lt;h2 id=&quot;scroll&quot;&gt;scroll&lt;/h2&gt;

&lt;p&gt;iscroll的存在就是因为手机端的滑动没有电脑端流畅。浏览器在我们滚动滚动条放开后，仍会将滚动条的滚动事件延时一段时间，以达到更流畅的效果，但是手机的这个滚动效果明显不那么流畅，演示滚动较短。&lt;/p&gt;

&lt;p&gt;如果你试着把页面中可见的一个超过屏幕高度的区域的css设置成&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;width: 100%;
height: 100%;
position:fixed;
overflow:scroll;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你会发现连延时滚动的效果都没有了，手指离开屏幕时，屏幕就停止滚动了，感觉就像卡在那儿。。。这也是电脑中浏览器不存在的现象。&lt;/p&gt;

&lt;h2 id=&quot;touch-vs-click&quot;&gt;touch VS click&lt;/h2&gt;

&lt;p&gt;之前由于对touch事件使用不当，导致产品经理对我的效果不满意，禁止我用touch，说要的就是click那种停顿感，用户习惯了这种延迟。。。你妹！！！哪有习惯,click的300ms延时只是在手机端出现好不，pc端根本没有延时，哪来的用户习惯。。。好吧，都是我使用不当，才让touch事件蒙冤。&lt;/p&gt;

&lt;p&gt;其实使用touch与click对比一下，明显touch带给人更多的流畅感。但是使用得谨慎：&lt;/p&gt;

&lt;p&gt;1、跟click同意的点击事件应绑在touchend上，而不是touchstart或touchmove，否则用户一碰到，手指都还没离开屏幕就触发什么按钮、导航条了，岂不是很奇怪。&lt;/p&gt;

&lt;p&gt;2、使用touch事件代替click时，一定要对移动距离做判断，看一下touchstart和touchend发生时的横纵坐标差，做一层判断,就是看用户有没有横向或者纵向滑动。因为touch很奇怪，即使你在A元素上发生touchstart事件，然后手指滑动到B元素上发生touchend事件，仍会触发A元素上的touchend事件。所以如果不做判断，可能就会发生，明明是在滑动页面浏览详情，结果碰到了某个button或tap,你就莫名其妙的提交了表单、跳转了页面。。。&lt;/p&gt;

&lt;h2 id=&quot;html5-validation&quot;&gt;html5 validation&lt;/h2&gt;

&lt;p&gt;required直到目前都没有被ios系统支持，目前版本是8.1.3&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>网页调取APP</title>
   <link href="http://echizen.github.io/tech/2015/02-23-map-api-for-web-page"/>
   <updated>2015-02-23T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/map-api-for-web-page</id>
   <content type="html">
&lt;p&gt;其实高德地图、百度地图的JS API写的很清楚，但是对于如何调用，没玩过的会觉得有点迷茫，最近项目要调用地图，我也是看了半天，才理解为何网页能有如此的能耐调用手机内安装的APP。&lt;/p&gt;
&lt;h1 id=&quot;基础篇&quot;&gt;基础篇&lt;/h1&gt;

&lt;p&gt;我们能在浏览器内跳转网页是http、https协议，能打开本地文件是file协议，调用APP用的是scheme协议或者intent协议，其实都是发送请求，获得响应，只是网页和APP是使用这两个协议通信的。scheme协议不仅仅用于ios端，也适用于安卓。但intent只适用于安卓。&lt;/p&gt;

&lt;h2 id=&quot;1scheme协议&quot;&gt;1、scheme协议&lt;/h2&gt;
&lt;p&gt;在 iOS 里，程序之间都是相互隔离，目前并没有一个有效的方式来做程序间通信，幸好 iOS 程序可以很方便的注册自己的 URL Scheme，这样就可以通过打开特定 URL 的方式来传递参数给另外一个程序。原生的APP和网页通过这种协议可以互相通信，safari或者APP的webView都可以识别该协议（一些浏览器如微信内置浏览器和QQ浏览器不支持，据后台技术哥哥介绍这是腾讯有意屏蔽，默认的webview控件是支持的）.&lt;/p&gt;

&lt;p&gt;如果是自己的APP想被网页调用，需要自己在app中注册scheme，这不是我研究的范畴。。。&lt;/p&gt;

&lt;p&gt;我们一般是调用别人的APP，会使用scheme协议就行,其实特简单，&lt;strong&gt;通过链接就可以&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;常用app的scheme&quot;&gt;常用APP的scheme&lt;/h3&gt;
&lt;p&gt;QQ   mqq:// &lt;br /&gt;
微信  weixin:// &lt;br /&gt;
淘宝  taobao:// &lt;br /&gt;
大众点评  dianping:// dianping://search &lt;br /&gt;
新浪微博  sinaweibo:// &lt;br /&gt;
支付宝 alipay:// &lt;br /&gt;
豆瓣fm    doubanradio://  &lt;br /&gt;
美团  imeituan://   &lt;br /&gt;
1号店 wccbyihaodian:// &lt;br /&gt;
有道词典yddictproapp:// &lt;br /&gt;
知乎  zhihu://&lt;br /&gt;
优酷  youku://&lt;/p&gt;

&lt;p&gt;见知乎上的整理：&lt;a href=&quot;http://www.zhihu.com/question/19907735&quot;&gt;http://www.zhihu.com/question/19907735&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;2intent协议&quot;&gt;2、intent协议&lt;/h2&gt;
&lt;p&gt;“在一个Android应用中，主要是由四种组件组成的，这四种组件可参考“Android应用的构成”。而这四种组件是独立的，它们之间可以互相调用，协调工作，最终组成一个真正的Android应用。在这些组件之间的通讯中，主要是由Intent协助完成的。Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述，Android则根据此Intent的描述，负责找到对应的组件，将Intent传递给调用的组件，并完成组件的调用。因此，Intent在这里起着一个媒体中介的作用，专门提供组件互相调用的相关信息，实现调用者与被调用者之间的解耦。”所以说intent是协议是不准确的，intent实际上是一种在不同组件之间传递的请求信息。&lt;/p&gt;

&lt;h1 id=&quot;示例网页调取百度地图高德地图&quot;&gt;示例：网页调取百度地图、高德地图&lt;/h1&gt;

&lt;p&gt;官方文档：&lt;/p&gt;

&lt;p&gt;1、高德地图：&lt;a href=&quot;http://lbs.amap.com/api/uri-api/ios-uri-explain/&quot;&gt;http://lbs.amap.com/api/uri-api/ios-uri-explain/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2、百度地图：&lt;a href=&quot;http://developer.baidu.com/map/index.php?title=uri/api/ios&quot;&gt;http://developer.baidu.com/map/index.php?title=uri/api/ios&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;例如调用常规线路规划导航：&lt;/p&gt;

&lt;h2 id=&quot;scheme调用&quot;&gt;scheme调用&lt;/h2&gt;

&lt;p&gt;高德地图：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;iosamap://path?sourceApplication=applicationName&amp;amp;backScheme=applicationScheme&amp;amp;sid=BGVIS1&amp;amp;slat=39.92848272&amp;amp;slon=116.39560823&amp;amp;sname=A&amp;amp;did=BGVIS2&amp;amp;dlat=39.98848272&amp;amp;dlon=116.47560823&amp;amp;dname=B&amp;amp;dev=0&amp;amp;m=0&amp;amp;t=0&quot;&amp;gt;公交导航&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;百度地图：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;baidumap://map/direction?origin=34.264642646862,108.95108518068&amp;amp;destination=40.007623,116.360582&amp;amp;mode=driving&amp;amp;src=yourCompanyName|yourAppName&quot;&amp;gt;驾车导航&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;intent调用&quot;&gt;intent调用&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;高德地图：&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;androidamap://route?sourceApplication=softname&amp;amp;slat=36.2&amp;amp;slon=116.1&amp;amp;sname=abc&amp;amp;dlat=36.3&amp;amp;dlon=116.2&amp;amp;dname=def&amp;amp;dev=0&amp;amp;m=0&amp;amp;t=1&amp;amp;showType=1&quot;&amp;gt;线路规划&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;百度地图：&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;intent://map/direction?origin=latlng:34.264642646862,108.95108518068|name:我家&amp;amp;destination=大雁塔&amp;amp;mode=driving&amp;amp;region=西安&amp;amp;src=yourCompanyName|yourAppName&quot;&amp;gt;线路规划&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意：有些安卓机型上面2种方式都不适用。。。&lt;/p&gt;

&lt;h1 id=&quot;习惯性贴代码&quot;&gt;习惯性贴代码：&lt;/h1&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &amp;lt;script type=&quot;text/javascript&quot; src=&quot;js/map.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
        var mapName = [amap,baidumap];
        var iosScheme = [];
        var androidIntent = [];
        iosScheme[0] = &apos;iosamap://path?sourceApplication=huiyouxing&amp;amp;backScheme=applicationScheme&amp;amp;sid=BGVIS1&amp;amp;slat=&amp;amp;slon=&amp;amp;sname=&amp;amp;did=BGVIS2&amp;amp;dlat=&apos;+initPosition.lat+&apos;&amp;amp;dlon=&apos;+initPosition.lng+&apos;&amp;amp;dname=&apos;+positionName+&apos;&amp;amp;dev=0&amp;amp;m=0&amp;amp;t=0&apos;;

        iosScheme[1] = &apos;baidumap://map/direction?origin=&apos;+myLocation.lat+&apos;,&apos;+myLocation.lng+&apos;&amp;amp;destination=&apos;+initPosition.lat+&apos;,&apos;+initPosition.lng+&apos;&amp;amp;mode=driving&amp;amp;src=huiyouxing&apos;;

        androidIntent[0] = &apos;androidamap://route?sourceApplication=huiyouxing&amp;amp;slat=&apos;+myLocation.lat+&apos;&amp;amp;slon=&apos;+myLocation.lng+&apos;&amp;amp;sname=起点&amp;amp;dlat=&apos;+initPosition.lat+&apos;&amp;amp;dlon=&apos;+initPosition.lng+&apos;&amp;amp;dname=&apos;+positionName+&apos;&amp;amp;dev=0&amp;amp;m=0&amp;amp;t=1&amp;amp;showType=1&apos;;
        androidIntent[1] = &apos;intent://map/direction?origin=latlng:&apos;+myLocation.lat+&apos;,&apos;+myLocation.lng+&apos;|name:起点&amp;amp;destination=&apos;+initPosition.lat+&apos;,&apos;+initPosition.lng+&apos;&amp;amp;mode=driving&amp;amp;src=huiyouxing&apos;;

        window.mapCallNode=document.querySelectorAll(&apos;.callMap&apos;);

        if (typeof mapApp !== &apos;object&apos;) {
            mapApp = {};
        }

        (function () {
            var ua = navigator.userAgent.toLowerCase(),
            locked = false,
            domLoaded = document.readyState===&apos;complete&apos;,
            delayToRun;

            function getAndroidVersion() {
                var match = ua.match(/android\s([0-9\.]*)/);
                return match ? match[1] : false;
            }

            var noIntentTest = /360 aphone|weibo|windvane/.test(ua);
            var hasIntentTest = /chrome|samsung/.test(ua);
            var isAndroid = /android|adr/.test(ua);
            var canIntent = !noIntentTest &amp;amp;&amp;amp; hasIntentTest &amp;amp;&amp;amp; isAndroid;
            var openInIfr = /weibo|m353/.test(ua);
            var inWeibo = ua.indexOf(&apos;weibo&apos;)&amp;gt;-1;

            //魅族
            if (ua.indexOf(&apos;m353&apos;)&amp;gt;-1 &amp;amp;&amp;amp; !noIntentTest) {
                canIntent = false;
            }

            mapApp.open = function (params, mapDom) {
                if (!domLoaded &amp;amp;&amp;amp; (ua.indexOf(&apos;360 aphone&apos;)&amp;gt;-1 || canIntent)) {
                    var arg = arguments;
                    delayToRun = function () {
                        AlipayWallet.open.apply(null, arg);
                        delayToRun = null;
                    };
                    return;
                }

                //问题来了：小米使用mapScheme,但是不能调用高德地图
                // 通过scheme协议唤起地图
                if (!canIntent) {
                    var mapScheme = iosScheme[params];
                    var openSchemeLink = mapDom;
                    openSchemeLink.href = mapScheme;
                    // 执行click
                    //openSchemeLink.dispatchEvent(customClickEvent());
                    

                } else {
                    // android 下 通过 intent 协议唤起钱包
                    var mapIntent = androidIntent[params];
                    var openSchemeLink = mapDom;
                    openIntentLink.href = intentUrl;
                    // 执行click
                    //openIntentLink.dispatchEvent(customClickEvent());
                }

            }

            if (!domLoaded) {
                document.addEventListener(&apos;DOMContentLoaded&apos;, function () {
                    domLoaded = true;
                    if (typeof delayToRun === &apos;function&apos;) {
                        delayToRun();
                    }
                }, false);
            }
        })();
    &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>js回调函数</title>
   <link href="http://echizen.github.io/tech/2015/01-24-callback-function"/>
   <updated>2015-01-24T21:02:32+00:00</updated>
   <id>http://echizen.github.io/tech/2015/callback-function</id>
   <content type="html">
&lt;p&gt;感觉这是我写不好的一篇博客，本人认识实在是太浅了，最近才意识到回调函数。&lt;/p&gt;

&lt;h2 id=&quot;感受回调函数&quot;&gt;感受回调函数&lt;/h2&gt;

&lt;p&gt;其实我们使用回调函数的场景蛮多的，不过我们不一定意识到我们在使用回调函数。&lt;/p&gt;

&lt;h3 id=&quot;场景一插件回调函数使用&quot;&gt;场景一：插件回调函数使用&lt;/h3&gt;

&lt;p&gt;eg:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var n =  $(&apos;#noteContainer&apos;).noty({
    layout      : &apos;center&apos;,
    closeWith   : [&apos;click&apos;],
    theme       : &apos;relax&apos;,
    maxVisible  : 20,
    timeout: 2000,
    animation   : {
        open  : &apos;animated flipInX&apos;,
        close : &apos;animated flipOutX&apos;,
        easing: &apos;swing&apos;,
        speed : 500
    },
    callback: {
        onShow: function() {
            $(&apos;#noteContainer&apos;).css({&apos;pointer-events&apos;:&apos;auto&apos;});
        },
        afterClose: function() {
            $(&apos;#noteContainer&apos;).css({&apos;pointer-events&apos;:&apos;none&apos;});
        },
    },
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里的onshow和afterClose函数在插件内部就是当做回调函数使用的。一般插件都会有callback API，那就是回调函数接口。&lt;/p&gt;

&lt;h3 id=&quot;场景二jquery函数&quot;&gt;场景二：jquery函数&lt;/h3&gt;

&lt;p&gt;譬如说事件绑定：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$(&apos;#coverImageContainer&apos;).on(&apos;click&apos;,&apos;.cover_image&apos;,function(){
	showModal($(this));
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;譬如说ajax请求，参数有回调函数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$.post(&apos;&apos;,formData,function(result){
    if (result.errorCode == 0) {
        generate(&apos;success&apos;, result.errorMessage);
    }else{
        $(&apos;#save&apos;).removeAttr(&apos;disabled&apos;);
    };
},&apos;json&apos;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再譬如说某些内部函数如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;each&lt;/code&gt;，它的参数也是回调函数:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$(&apos;.cover_image&apos;).each(function(){
    var thisId = $(this).attr(&apos;data-imgId&apos;);
    if(id == thisId){
        $(this).remove();
        return false;
    } 
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;什么是回调函数&quot;&gt;什么是回调函数：&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;回调函数是一个被作为参数传递给另一个函数的函数&lt;/strong&gt;。从定义来讲，回调函数一点也不神秘，他就是其他函数的参数，只不过它这个参数本身就是函数。&lt;/p&gt;

&lt;h2 id=&quot;回调函数独特之处&quot;&gt;回调函数独特之处&lt;/h2&gt;

&lt;p&gt;因为函数在Javascript中是第一类对象，我们像对待对象一样对待函数，因此我们能像传递变量一样传递函数，在函数中返回函数，在其他函数中使用函数。当我们将一个回调函数作为参数传递给另一个函数是，我们仅仅传递了函数定义。我们并没有在参数中执行函数。我们并不传递像我们平时执行函数一样带有一对执行小括号()的函数。&lt;/p&gt;

&lt;p&gt;需要注意的很重要的一点是回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”，这个特定时间点因函数而异。&lt;/p&gt;

&lt;h3 id=&quot;回调函数是闭包&quot;&gt;回调函数是闭包&lt;/h3&gt;

&lt;p&gt;我们将一个回掉函数作为变量传递给另一个函数时，这个回掉函数在包含它的函数内的某一点执行，就好像这个回调函数是在包含它的函数中定义的一样。这意味着回调函数本质上是一个闭包。&lt;/p&gt;

&lt;p&gt;正如我们所知，闭包能够进入包含它的函数的作用域，因此&lt;strong&gt;回调函数能获取包含它的函数中的变量&lt;/strong&gt;，以及全局作用域中的变量。&lt;/p&gt;

&lt;p&gt;因此，你不能随便给回调函数传参，否则会发生undefined的错误。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function callTwoFun(fun){
    fun();
}
function oneFun(param){
    callTwoFun(function(param){
        console.log(param);
    })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这里，我们向callTowFun传递了回调函数作为参数，且&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;callTwoFun(function(param){
	        console.log(param);
	    })&lt;/code&gt;里的function的写法只是定义，不是执行。我们给这个回调函数声明了形参param，但是这种写法，我们却无法在执行时传递param实参。只会抛出&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;正确的写法应该是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function callTwoFun(fun){
    fun();
}
function oneFun(param){
    callTwoFun(function(){
        console.log(param);
    })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不用担心回调函数中的param取不到值，我们已经解释过了，回调函数能获取包含它的函数中的变量，也就是callTowFun能获取param参数，那么它的回调函数也能获取这个参数。&lt;/p&gt;

&lt;p&gt;另外你可能疑惑&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;callTwoFun&lt;/code&gt;的定义中，执行fun()时并没有携带参数，要知道&lt;strong&gt;在js中形参有映射的argument参数，所以我们在掉用一个带有形参的函数时，即可以不传递参数，也可以传递多于形参数量的实参，argument会按照实际情况处理&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;回调函数使用注意&quot;&gt;回调函数使用注意&lt;/h2&gt;

&lt;h3 id=&quot;在执行之前确保回调函数是一个函数&quot;&gt;在执行之前确保回调函数是一个函数&lt;/h3&gt;

&lt;p&gt;如果没有适当的检查，如果参数中没有一个回调函数或者传递的回调函数事实上并不是一个函数，我们的代码将会导致运行错误。虽然这不是必须的，但是作为良好习惯，我们应该处理这种错误。&lt;/p&gt;

&lt;p&gt;上面的例子应加上判断：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function callTwoFun(fun){
    if(typeof fun === &quot;function&quot;){
    	//调用它，既然我们已经确定了它是可调用的
        fun();
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;避免回调地狱&quot;&gt;避免回调地狱&lt;/h2&gt;

&lt;p&gt;虽然回调函数有很多方便之处，好吧，我没有提他的优点。。。但是回调函数要是层层嵌套太多，增加了阅读和代码维护成本，是不可取的。&lt;/p&gt;

&lt;p&gt;譬如，你能读懂下面这段的代码的意境吗。。。这可是node-mongodb-native，一个适用于Node.js的MongoDB驱动中的一个例子。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; var p_client = new Db(&apos;integration_tests_20&apos;, new Server(&quot;127.0.0.1&quot;, 27017, {}), {&apos;pk&apos;:CustomPKFactory});
    p_client.open(function(err, p_client) {
        p_client.dropDatabase(function(err, done) {
            p_client.createCollection(&apos;test_custom_key&apos;, function(err, collection) {
                collection.insert({&apos;a&apos;:1}, function(err, docs) {
                    collection.find({&apos;_id&apos;:new ObjectID(&quot;aaaaaaaaaaaa&quot;)}, function(err, cursor) {
                        cursor.toArray(function(err, items) {
                            test.assertEquals(1, items.length);

                            // Let&apos;s close the db
                            p_client.close();
                        });
                    });
                });
            });
        });
    });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;福利&quot;&gt;福利&lt;/h2&gt;

&lt;p&gt;又一个例子，我的入门案例：&lt;a href=&quot;http://http://jsfiddle.net/echizen/pfucjhck/&quot;&gt;http://jsfiddle.net/echizen/pfucjhck/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一篇写的不错的博客，我的入门导读：&lt;a href=&quot;http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/&quot;&gt;http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>ios 风格checkbox/radio</title>
   <link href="http://echizen.github.io/tech/2015/01-21-ios-style-button"/>
   <updated>2015-01-21T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2015/ios-style-button</id>
   <content type="html">
&lt;p&gt;再做web app或hybird app时，为了达到一致的风格，常需要模仿原生的样式，比如说ios的checkbox样式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20150121-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;简单的内容，直接上代码&lt;/p&gt;

&lt;h3 id=&quot;css&quot;&gt;CSS&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.checkbox {
    height: 28px;
    width: 50px;
    position: absolute;
    top: -3px;
    right: 6px;
    z-index: 9997;
    -moz-border-radius: 35px;
    -webkit-border-radius: 35px;
    border-radius: 35px;
    /*background-color: #efefef;*/
}
.checkbox:after {
    height: 28px;
    width: 25px;
    position: absolute;
    top: 0;
    left: 0;
    content: &apos;&apos;;
    z-index: 9996;
    -moz-border-radius: 35px 0 0 35px;
    -webkit-border-radius: 35px;
    border-radius: 35px 0 0 35px;
    /*background-color: #00CC00;*/
}

.control {
    height: 25px;
    width: 25px;
    outline: 0;
    position: absolute;
    top: 8px;
    right: 7px;
    z-index: 9998;
    -webkit-appearance: none;
    -moz-border-radius: 36.5px;
     -webkit-border-radius: 36.5px; 
    border-radius: 36.5px;
    background-color: #fff;
    border: 0;
}
.control:checked {
  right: 30px;
}

.control + .checkbox{
    background-color: #ccc;
}
.control + .checkbox:after{
    background-color: #ccc;
}

.control:checked + .checkbox{
    background-color: #00CC00;
}
.control:checked + .checkbox:after{
    background-color: #00CC00;
}

.control:after {
  height: 40px;
  width: 40px;
  position: absolute;
  right: 15px;
  top: 15px;
  /*content: &apos;&apos;;*/
  z-index: 9998;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
  background-color: #c2c0be;
  *zoom: 1;
  background-image: -moz-linear-gradient(top, #c2c0be 0%, #d7d7d7 72%);
  background-image: -webkit-linear-gradient(top, #c2c0be 0%, #d7d7d7 72%);
  background-image: linear-gradient(to bottom, #c2c0be 0%, #d7d7d7 72%);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;html&quot;&gt;html&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;section class=&quot;col-xs-12 top_line&quot;&amp;gt;
    &amp;lt;div class=&quot;mul_col title_icon&quot;&amp;gt;
        &amp;lt;img src=&quot;image/test.png&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;mul_col blue&quot;&amp;gt;会议是否公开&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;chose_button&quot; style=&quot;float:right&quot;&amp;gt;
        &amp;lt;input type=&quot;checkbox&quot; name=&quot;isPublic&quot; id=&quot;isPublic&quot; value=&quot;0&quot; style=&quot;display:none&quot; checked&amp;gt; 
        &amp;lt;div class=&quot;full_col switch&quot;&amp;gt;
            &amp;lt;input type=&quot;checkbox&quot; id=&quot;control&quot; class=&quot;control&quot; name=&quot;isPublic&quot; id=&quot;isPublic&quot; value=&quot;1&quot; &amp;lt;%=meetDetail.get(&apos;isPublic&apos;)?meetDetail.get(&apos;isPublic&apos;)==1?&apos;checked&apos;:&apos;&apos;:&apos;&apos;%&amp;gt; &amp;gt;
            &amp;lt;label for=&quot;control&quot; class=&quot;checkbox&quot;&amp;gt;&amp;lt;/label&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;分析&quot;&gt;分析&lt;/h3&gt;
&lt;p&gt;没有用js,全是css &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:checked&lt;/code&gt;选择器的功劳。
将input的checkbox框设置成圆，在利用label扩大其作用的点击范围（话说label真是一个对表单项有特殊要求样式的一个很实用的标签），将label的样式设置成椭圆。为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:checked&lt;/code&gt;样式设置不同的背景色。&lt;/p&gt;

&lt;p&gt;此方法虽简单，但值得一记，用这种思路可以做出很多样式。有时候善于用css,真的可以避免很多的臃肿的js代码。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:checked&lt;/code&gt;是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pseudo-classes&lt;/code&gt;的用法，类似的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pseudo-classes&lt;/code&gt;还有很多，详见：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>触屏事件与鼠标事件</title>
   <link href="http://echizen.github.io/tech/2014/12-23-touch-vs-click"/>
   <updated>2014-12-23T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/touch-vs-click</id>
   <content type="html">
&lt;h2 id=&quot;故事来源可忽略&quot;&gt;故事来源（可忽略）&lt;/h2&gt;

&lt;p&gt;最近做了一个购物车的页面，既有滑动删除又有点击增减数量，同一区域发生了2件事，发现2个事件没法同时共存。由于这个页面用的是angularjs,开始以为是angularjs与原生js混用，且把原生js没有按angularjs的规则写入angularjs内部，而是写到了angularjs闭合圈的外面,初学angularjs，入门教程都还没看完就在赶项目，不是很熟悉angularjs的事件触发机制，于是跑去看了一天angularjs的事件绑定，以及directive,然后发现不关angularjs的事，测试了一下果然不是。然后又怀疑是事件冒泡的干扰，又去看了几小时事件的冒泡和补获机制，发现也不是。最后才发现是因为手机端touch事件与click事件之间的关系导致。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141223-1.png&quot; alt=&quot;image&quot; /&gt;
&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141223-2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;惨痛教训啊，以后要先试验，再怀疑，再搜索，而不是凭空怀疑导致事件出错的方向，然后google一气。不过这次了解了很多。&lt;/p&gt;

&lt;h2 id=&quot;手机端touchstarttouchmovetouchendmouseovermousemovemousedownmouseupclick事件加载顺序&quot;&gt;手机端touchstart、touchmove、touchend、mouseover、mousemove、mousedown、mouseup、click事件加载顺序&lt;/h2&gt;

&lt;h3 id=&quot;触屏事件&quot;&gt;触屏事件&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;touchstart    触摸开始（手指放在触摸屏上）&lt;/li&gt;
  &lt;li&gt;touchmove     拖动（手指在触摸屏上移动）&lt;/li&gt;
  &lt;li&gt;touchend 触摸结束（手指从触摸屏上移开）&lt;/li&gt;
  &lt;li&gt;touchcancel，是在拖动中断时候触发。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;鼠标事件&quot;&gt;鼠标事件&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;mouseover 	鼠标进入&lt;/li&gt;
  &lt;li&gt;mousemove		鼠标移动&lt;/li&gt;
  &lt;li&gt;mousedown		鼠标按下触发&lt;/li&gt;
  &lt;li&gt;mouseup		鼠标抬起触发&lt;/li&gt;
  &lt;li&gt;click			鼠标点击事件，包括mousedown和mouseup2个过程&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;触发规则&quot;&gt;触发规则&lt;/h3&gt;
&lt;p&gt;在触屏操作后，手指提起的一刹那（即发生touchend后），系统会判断接收到事件的element的内容是否被改变：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果内容被改变，会解析为touch事件，接下来的click事件都不会触发，&lt;/li&gt;
  &lt;li&gt;如果内容没有改变，则会解析为click事件，按照mousedown，mouseup，click的顺序触发事件。
特别需要提到的是，在解析为click事件时，只有再触发一个触屏事件时，才会触发上一个事件的mouseout事件。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通常click事件官网文档是说会延时200~300ms.&lt;/p&gt;

&lt;p&gt;因此有关于hover的小技巧，当点击过一个按钮之后，这个按钮就会一直处于hover的状态，此时基于这个伪类所设置的css也是起作用的，直到用手指点击另外一个地方，才会完成mouseout事件。&lt;/p&gt;

&lt;h3 id=&quot;触发顺序&quot;&gt;触发顺序&lt;/h3&gt;

&lt;p&gt;测试代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function test_touch(){
    //isTouchDevice();
    var firstEmitTime = 0;
    var aa = document.getElementById(&quot;test&quot;);
    var eventTypeArr = [&apos;touchstart&apos;, &apos;touchmove&apos;, &apos;touchend&apos;,&apos;click&apos;, &apos;mousedown&apos;, &apos;mousemove&apos;, &apos;mouseover&apos;, &apos;mouseup&apos;];
     
    for(var k = 0; k &amp;lt; eventTypeArr.length; k++){
        //利用闭包保存eventType，当回调函数触发时会访问该闭包的环境变量对象,
        (function(){
            var eventType = eventTypeArr[k];
            aa.addEventListener(eventType, function(){
                var curTime = (new Date()).getTime();
                if(firstEmitTime === 0){
                    firstEmitTime = curTime;
                }
                //打印当前事件触发时间与第一个事件触发时间的差值
                var log = eventType + &apos;: &apos; + (curTime - firstEmitTime);
                console.log(log);
            });
        })();
    }
}
window.onload = test_touch;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;正常的轻轻点击一下会触发：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141223-3.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;数字表示事件触发间隔，单位是ms，测试环境是chrome浏览器控制台下的Emulation。&lt;/p&gt;

&lt;p&gt;当你将激活的模拟器关闭，使用正常的pc网页模式，再点击一下，&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141223-3.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;移动端，点击一下会触发：&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touchstart-&amp;gt;touchend-&amp;gt;mouseover-&amp;gt;mousedown-&amp;gt; mouseup-&amp;gt;click&lt;/code&gt;;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;移动端，滑动，触发：
  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touchstart-&amp;gt;touchmove-&amp;gt;touchend&lt;/code&gt;;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;pc端，点击：
  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mouseover-&amp;gt;mousemove-&amp;gt;mousedown-&amp;gt; mouseup-&amp;gt;click&lt;/code&gt;;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;pc端，移动：
  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mouseover-&amp;gt;mousemove&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在真实的手机环境下，我不知道怎样像这样一次性输出这些事件。只能一步步通过alert跟踪，但是要注意的是alert是阻塞事件的，所以不能一次输出，不代表不出发，只能手动在每一步每一次触发一个alert。&lt;/p&gt;

&lt;p&gt;关于触发顺序，详细请参考&lt;a href=&quot;http://realwall.cn/blog/?p=162&quot;&gt;http://realwall.cn/blog/?p=162&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;关于click事件的300ms延时&quot;&gt;关于click事件的300ms延时&lt;/h3&gt;

&lt;p&gt;这篇文章介绍的很详细：&lt;a href=&quot;http://thx.github.io/mobile/300ms-click-delay/&quot;&gt;http://thx.github.io/mobile/300ms-click-delay/&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;关于我的bug&quot;&gt;关于我的bug&lt;/h3&gt;

&lt;p&gt;我根据官方API给元素绑定了滑动事件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var startX = 0, startY = 0;  
var endX = 0,endY = 0;
var moveArea;

//touchstart事件  
function touchStartFunc(evt) {  
    try  
    {  
        evt.preventDefault(); //阻止触摸时浏览器的缩放、滚动条滚动等  

        var touch = evt.touches[0]; //获取第一个触点  
        var x = Number(touch.pageX); //页面触点X坐标  
        var y = Number(touch.pageY); //页面触点Y坐标  
        //记录触点初始位置  
        startX = x;  
        startY = y;  

    }  
    catch (e) {  
        console(&apos;touchSatrtFunc：&apos; + e.message);  
    }  
}  

//touchmove事件，这个事件无法获取坐标  
function touchMoveFunc(evt) {  
    try  
    {  
        evt.preventDefault(); //阻止触摸时浏览器的缩放、滚动条滚动等  
        var touch = evt.touches[0]; //获取第一个触点  
        var x = Number(touch.pageX); //页面触点X坐标 
        endX = x; 
        leftX = x-startX;
        if(leftX &amp;lt; 0){
            //var y = Number(touch.pageY); //页面触点Y坐标
            moveArea = evt.target.parentElement;
            while(moveArea.getAttribute(&apos;move-area&apos;)!=(&apos;yes&apos;)) {
                moveArea = moveArea.parentElement;
            } 
            moveArea.style.marginLeft=+&quot;px&quot;;
        }
    }  
    catch (e) {  
        console(&apos;touchMoveFunc：&apos; + e.message);  
    }  
}  

//touchend事件  
function touchEndFunc(evt) { 
    if(moveArea) { 
        try {  
            evt.preventDefault(); //阻止触摸时浏览器的缩放、滚动条滚动等 

            if(endX-startX &amp;lt;= -10){
                moveArea.style.marginLeft=&quot;-112px&quot;;
            }else{
                moveArea.style.marginLeft=&quot;0px&quot;;
            }

            //var text = &apos;TouchEnd事件触发&apos;;  
            //document.getElementById(&quot;result&quot;).innerHTML = text;  
        }  
        catch (e) {  
            console(&apos;touchEndFunc：&apos; + e.message);  
        }  
    }
}  

//绑定事件  
function bindEvent() {  
    var touch_view = document.getElementsByClassName(&quot;move_area&quot;);
    for(var i=0;i&amp;lt;touch_view.length;i++){
        
            touch_view[i].addEventListener(&apos;touchstart&apos;, touchStartFunc, false);  
            touch_view[i].addEventListener(&apos;touchmove&apos;, touchMoveFunc, false);  
            touch_view[i].addEventListener(&apos;touchend&apos;, touchEndFunc, false);  
        }
}  

//判断是否支持触摸事件  
function isTouchDevice() {  
    try {  
        document.createEvent(&quot;TouchEvent&quot;);  
        console.log(&quot;支持TouchEvent事件！&quot;);  

        bindEvent(); //绑定事件  
    }  
    catch (e) {  
        console.log(&quot;不支持TouchEvent事件！&quot; + e.message);  
    }  
}  

window.onload = isTouchDevice; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;同时给.move_area中的.nub_opt绑定了angularjs的ng-click也就是click事件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;container-fluid cart_item&quot; ng-repeat=&apos;item in items&apos;&amp;gt;
    &amp;lt;div class=&quot;col-xs-12 clear_padding move_area&quot; move-area=&quot;yes&quot;&amp;gt;
    &amp;lt;div class=&quot;touch_area&quot;&amp;gt;
        &amp;lt;div class=&quot;mul_col check_area&quot; style=&quot;width:11%&quot; ng-click=&quot;checkGoods($index)&quot;&amp;gt;
            &amp;lt;span class=&quot;wait_check&quot;  ng-class=&quot;{&apos;checked&apos;: item.isChecked}&quot;&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
        
        &amp;lt;div class=&quot;mul_col goods_thumb &quot; style=&quot;width:30%&quot;&amp;gt;
            &amp;lt;img src=&quot;{{item.img}}&quot;&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;mul_col&quot; style=&quot;width:59%&quot;&amp;gt;
            &amp;lt;div class=&quot;goods_name&quot;&amp;gt;
                {{item.name}}
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;goods_price_wrap&quot;&amp;gt;
                单价：&amp;lt;span class=&quot;goods_price&quot;&amp;gt;￥{{item.price}}&amp;lt;/span&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;goods_nub_wrap&quot;&amp;gt;
                &amp;lt;div class=&quot;nub_opt glyphicon glyphicon-plus&quot; ng-click=&quot;addNub($index)&quot;&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;span class=&quot;goods_nub&quot;&amp;gt;
                    &amp;lt;input type=&quot;number&quot; class=&quot;nub_input&quot; value=&quot;1&quot; placehover=&quot;&quot; value=&quot;&quot; ng-model=&apos;item.nub&apos;&amp;gt;
                &amp;lt;/span&amp;gt;
                &amp;lt;div class=&quot;nub_opt glyphicon glyphicon-minus&quot; ng-click=&quot;minusNub($index)&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    
        &amp;lt;div class=&quot;delete_btn&quot; ng-click=&quot;remove($index)&quot;&amp;gt;
            &amp;lt;img src=&quot;__MOBILE_PUBLIC__/Static/images/delete.png&quot;&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;关键是一个很小的问题。。。却导致了很严重的后果。我在touchstart、touchmove、touchend事件绑定的函数中都用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preventDefault()&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;对于touch事件：&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preventDefault()&lt;/code&gt;不仅会阻止触摸时浏览器的缩放、滚动条滚动等，还会导致鼠标事件被禁止&lt;/strong&gt;,我只关注了前一点。。。我的网页已针对移动端适配，在head中已加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;meta name=&quot;viewport&quot;
    content=&quot;width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no&quot;&amp;gt;&lt;/code&gt;，preventDefault()已的禁止缩放滚动已不需要。&lt;/p&gt;

&lt;h2 id=&quot;加料区&quot;&gt;加料区&lt;/h2&gt;
&lt;h3 id=&quot;手机端click事件又一特性点透效果&quot;&gt;手机端click事件又一特性–点透效果&lt;/h3&gt;

&lt;p&gt;都说手机端建议使用touch事件，少用touch事件，但是偶尔也会出问题。&lt;/p&gt;

&lt;p&gt;典型事例是网页遮罩层绑有touch事件，而遮罩层下有button元素或a标签或某元素绑有click事件，这时当你触发遮罩层的touch事件后，引发的click事件会“穿透”遮罩层，导致下面元素绑有的click事件被触发。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;总结一下，点透产生的条件：&lt;/p&gt;

    &lt;p&gt;1.A/B两个层上下z轴重叠。&lt;/p&gt;

    &lt;p&gt;2.上层的A点击后消失或移开。（这一点很重要）&lt;/p&gt;

    &lt;p&gt;3.B元素本身有默认click事件（如a标签和button） 或 B绑定了click事件。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;点透出现原因
  click事件的300ms延时，当上层元素A的touch触发至click触发的这300ms中，上层元素消失，待click出发时就传递到了它重叠的B元素（不是很明白，事件会通过冒泡或捕获方式传播，为何还能在元素重叠时传播？）&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;解决方案&lt;/p&gt;

    &lt;p&gt;1.对于B元素本身没有默认click事件的情况（无a标签等），应统一使用touch事件，统一代码风格，并且由于click事件在移动端的延迟要大很多，不利于用户体验，所以关于触摸事件应尽量使用touch相关事件。&lt;/p&gt;

    &lt;p&gt;2.对于B元素本身存在默认click事件的情况,应及时取消A元素的默认点击事件，从而阻止click事件的产生。即应在上例的handle函数中添加代码：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if(eve == &quot;touchend&quot;) e.preventDefault();&lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;3.对于遮盖浮层，由于遮盖浮层的点击即使有小延迟也是没有关系的，反而会有疑似更好的用户体验，所以这种情况，可以针对遮盖浮层自己采用click事件，这样就不会出现点透问题。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;参考：&lt;a href=&quot;http://www.douban.com/note/430517401/&quot;&gt;http://www.douban.com/note/430517401/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;tap-event的比较规范的写法&quot;&gt;tap-event的比较规范的写法&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/component/tap-event/blob/master/index.js&quot;&gt;https://github.com/component/tap-event/blob/master/index.js&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;angularjs事件绑定&quot;&gt;angularJs事件绑定&lt;/h3&gt;
&lt;p&gt;angularjs数据双向绑定的强大毋庸置疑，虽说angular的理念是避免频繁操作dom元素，但是在良好交互的需求下，给元素绑事件处理还是很必要的，angualarJs的核心文档中只有ng-click、ng-hide、ng-show区区3个事件。&lt;/p&gt;

&lt;p&gt;不建议自己用在angularJs之外再用jquery，事件加载和dom解析会交替产生很多头疼的问题。方案有三：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;闭包原生Js中绑定事件
  我这类初学者只能拿这个方案暂时解燃眉之急，没有太大问题产生。要注意angurJs的解析在原生JS之后，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/{/{}}&lt;/code&gt;指令和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ng-repeat&lt;/code&gt;中的内容会在你的JS代码执行之后才解析，这时&lt;strong&gt;如果你要对AngularJs生成的dom元素绑事件，一定要window.load=yourEventFunc。&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用插件
  人笨就要学会多偷懒，angularJs的发展使基于他的插件越来越多，如大名鼎鼎的&lt;a href=&quot;http://angular-ui.github.io/bootstrap/&quot;&gt;angular-bootstrap&lt;/a&gt;。使用他们封装的事件会事半功倍。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;自己写directive
  人家写的永远不能满足自己的需求，自己牛逼时，可以自己写directive，当你定义了你的directive后，你就可以通过诸如ng-touchstart来绑定符合angularJs规则的你自己写的事件了。这方面我还没干过，等我自己会写了，再总结。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>《AngularJs》代码纠错</title>
   <link href="http://echizen.github.io/tech/2014/12-04-correct-the-code-of-angularjs"/>
   <updated>2014-12-04T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/correct-the-code-of-angularjs</id>
   <content type="html">
&lt;p&gt;angulajs入门，用的是O’REILLY的《AngularJs》一书，这是一本好书，书中解释的很到位，例子也都很典型。但是这是一本13年的书，规范在不断发展，所以书中有些代码的写法已被遗弃，按照书中的代码来写会报错，将我发现解决的错误记下来，以免其他初学者也出错。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141204-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;1-page6&quot;&gt;1. page6&lt;/h2&gt;
&lt;p&gt;原：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;html ng-app=&quot;myapp&quot;&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;&amp;gt;
        &amp;lt;title&amp;gt;示例&amp;lt;/title&amp;gt;
        &amp;lt;script type=&quot;text/javascript&quot; src=&quot;../js/angular.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body ng-controller=&apos;CartController&apos;&amp;gt;
        &amp;lt;h1&amp;gt;购物车&amp;lt;/h1&amp;gt;
        &amp;lt;div ng-repeat=&apos;item in items&apos;&amp;gt;
            &amp;lt;span&amp;gt;{{item.title}}&amp;lt;/span&amp;gt;
            &amp;lt;input ng-model=&apos;item.quantity&apos;&amp;gt;
            &amp;lt;span&amp;gt;{{item.price | currency}}	            &amp;lt;/span&amp;gt;
            &amp;lt;span&amp;gt;{{item.price * item.quantity | currency}}&amp;lt;/span&amp;gt;
            &amp;lt;button ng-click=&quot;remove($index)&quot;&amp;gt;Remove&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        
        &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
            
                $scope.items = [
                                    {title:&apos;paint pots&apos;,quantity:8,price:3.95},
                                    {title:&apos;polka dots&apos;,quantity:17,price:12.95},
                                    {title:&apos;pebbles&apos;,quantity:5,price:6.95},
                                ];

                $scope.remove = function(index){
                    $scope.items.splice(index,1);
                }
        &amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;修改的地方：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
        var myAppModule = angular.module(&apos;myapp&apos;, []);
        myAppModule.controller(&apos;CartController&apos;,function($scope){
            $scope.items = [
                                {title:&apos;paint pots&apos;,quantity:8,price:3.95},
                                {title:&apos;polka dots&apos;,quantity:17,price:12.95},
                                {title:&apos;pebbles&apos;,quantity:5,price:6.95},
                            ];

            $scope.remove = function(index){
                $scope.items.splice(index,1);
            }
        })
    &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;原因：新的版本要求必须用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;angular.module(&apos;appname&apos;, []);&lt;/code&gt;这种形式初始化自己定义的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ng-app=&apos;appname&apos;&lt;/code&gt;，如果你直接写’ng-app’而不去初始化自己的名字，则不用加这句。不过在单文本模式下，显然我们可能会一个页面多个作用域，也就是多个ng-app,不定义名称，显然不是好的做法。&lt;/p&gt;

&lt;h2 id=&quot;2-page23&quot;&gt;2. page23&lt;/h2&gt;
&lt;p&gt;原:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;		function DeathrayMenuController($scope){
            $scope.menuState.show = false;

            $scope.toggleMenu = function(){
                $scope.menuState.show = !$scope.menuState.show;
            }
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;改为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;		function DeathrayMenuController($scope){
            $scope.menuState = {&apos;show&apos;:false};

            $scope.toggleMenu = function(){
                $scope.menuState.show = !$scope.menuState.show;
            }
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;原因：典型的未定义就使用。&lt;/p&gt;

&lt;h2 id=&quot;3-page30&quot;&gt;3. page30&lt;/h2&gt;
&lt;p&gt;原：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$scope.subtotal = function(){
    return $scope.totalCart() - $scope.discount;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;改：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$scope.subtotal = function(){
    return $scope.totalCart() - $scope.bill.discount;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>操作剪切板，js 复制链接至剪切板</title>
   <link href="http://echizen.github.io/tech/2014/12-02-html-clipboard"/>
   <updated>2014-12-02T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/html-clipboard</id>
   <content type="html">
&lt;h1 id=&quot;复制链接到剪切板&quot;&gt;复制链接到剪切板&lt;/h1&gt;

&lt;p&gt;最近遇到点击复制链接按钮要将一段网址自动复制到剪切板的需求（走了一些弯路，记载一下）。这可是web与系统的交互啊，第一感是纯粹js做不到。上网一搜，果真是，都是js和flash混搭做到这个功能。&lt;/p&gt;

&lt;p&gt;不是html5兴起的时代吗，我马上想到去看看h5有类似功能的API不。&lt;/p&gt;

&lt;h2 id=&quot;html5&quot;&gt;html5&lt;/h2&gt;
&lt;p&gt;结论：浏览器处于安全的考虑，禁止web层的脚本直接主动操作系统层的剪切板，但是开放了“被动”的API，也就是说，如果用户主动操作了剪切板，发生了copy、paste的事件，你就可以在监听到这个时间后，通过clipboardData对象操作剪切板的内容。&lt;/p&gt;

&lt;p&gt;这虽不满足我的需求，但是还是了解下，毕竟按规范办事比较好，不要动不动就用flash。&lt;/p&gt;

&lt;h3 id=&quot;剪贴板事件&quot;&gt;剪贴板事件：&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;　　beforecopy：在发生复制操作前触发;
　　copy：在发生复制操作的时候触发;
　　beforecut：在发生剪切操作前触发;
　　cut：在发生剪切操作的时候触发;
　　beforepaste：在发生粘贴操作前触发;
　　paste：在发生粘贴操作的时候触发。
　　
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;不同浏览器下区别&quot;&gt;不同浏览器下区别&lt;/h3&gt;
&lt;p&gt;在Firefox、Chrome和Safari中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforecopy&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforecut&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforepaste&lt;/code&gt;事件只会在显示针对文本框的上下文菜单(预期将发生剪贴板事件)的情况下触发。但是IE则会在触发&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cut&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paste&lt;/code&gt;事件之前先触发这些事件。至于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cut&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paste&lt;/code&gt;事件，只要是在上下文菜单(右键菜单)中选择了相应选项，或者使用了相应的键盘组合键如(ctrl+v)，所有浏览器都会触发他们。要访问剪贴板中的数据，可以通过clipboardData对象：在IE中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clipboardData对象&lt;/code&gt;是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window&lt;/code&gt;对象的属性;而在Chrome、Safari和Firefox 4+中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clipboardData对象&lt;/code&gt;是相应&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event&lt;/code&gt;对的属性。但是，在Chrome、Safari和Firefox 4+中，只有在处理剪贴板事件期间，clipboardData对象才有效，这是为了防止对剪贴板的未授权访问;在IE中，则可以随时访问clipboardData对象。为了确保跨浏览器兼容，最好只在发生剪贴板事件期间使用这个对象。&lt;/p&gt;

&lt;h3 id=&quot;函数&quot;&gt;函数&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getData(text/url)&lt;/code&gt;:在IE下，text和url参数代表将要从剪切板里获得的数据的保存类型,在Chrome、Safari和Firefox 4+中，实际上是MIME类型&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setData(text/url,data)&lt;/code&gt;:text和url参数代表数据类型,在Chrome、Safari和Firefox 4+中，仍是MIME类型。data是要放到剪切板的内容。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clearData()&lt;/code&gt;:清空剪切板。&lt;/p&gt;

&lt;h3 id=&quot;测试&quot;&gt;测试&lt;/h3&gt;
&lt;p&gt;W3C标准：&lt;a href=&quot;http://www.w3.org/TR/clipboard-apis/&quot;&gt;http://www.w3.org/TR/clipboard-apis/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;现在浏览器支持度不是很好。&lt;/p&gt;

&lt;p&gt;chrome下这个例子无效&lt;a href=&quot;http://jsfiddle.net/c8Ats/7/&quot;&gt;http://jsfiddle.net/c8Ats/7/&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$(&quot;#test&quot;).on(&apos;click&apos;, function (e) {
    var clip = new ClipboardEvent(&apos;copy&apos;);
    clip.clipboardData.setData(&apos;text/plain&apos;, &quot;test&quot;);
    clip.preventDefault();

    e.target.dispatchEvent(clip);
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是标准总有一天被执行的么，以后写这个就方便了，不用flash和插件了。&lt;/p&gt;

&lt;h2 id=&quot;jsflash&quot;&gt;js+flash&lt;/h2&gt;
&lt;p&gt;这就只能找插件了。比较靠谱的是&lt;a href=&quot;http://zeroclipboard.org/&quot;&gt;ZeroClipboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;github代码库：&lt;a href=&quot;https://github.com/zeroclipboard/zeroclipboard&quot;&gt;https://github.com/zeroclipboard/zeroclipboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;之前的版本是要自己引用的ZeroClipboard.swf的，写这篇博客时是2.2.0版本，引用这个swf文件已嵌入到插件内部了，而且支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jquery&lt;/code&gt;调用。&lt;/p&gt;

&lt;p&gt;example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;javascript:void(0)&quot; class=&quot;copy&quot; data-clipboard-text=&quot;http://zeroclipboard.org/&quot;&amp;gt;复制链接&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将将要复制的内容放到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-clipboard-text&lt;/code&gt;里。&lt;/p&gt;

&lt;script src=&quot;../ZeroClipboard/ZeroClipboard.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

&lt;p&gt;在元素之后，js调用之前引用插件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var client = new ZeroClipboard( document.getElementsByClassName(&quot;copy&quot;) );

client.on( &quot;ready&quot;, function( readyEvent ) {
     client.on( &quot;aftercopy&quot;, function( event ) {
        // `this` === `client`
        // `event.target` === the element that was clicked
        //event.data[&quot;text/plain&quot;]:复制的内容
        alert(&quot;复制成功：event.data[&quot;text/plain&quot;]&quot;);
     } );
 } );
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完结，更多内容请参考插件官网。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>js 循环给html中添加含动态数据的dom元素</title>
   <link href="http://echizen.github.io/tech/2014/11-16-make-template-insert-to-dom"/>
   <updated>2014-11-16T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/make-template-insert-to-dom</id>
   <content type="html">
&lt;h1 id=&quot;应用场景&quot;&gt;应用场景&lt;/h1&gt;
&lt;p&gt;前端经常在如ajax交互中将后台数据动态更新插入到dom中，而且插入的部分多是循环元素，只是数据不同，如做留言板是，每个评论者评论后评论模块的更新。&lt;/p&gt;

&lt;h1 id=&quot;函数&quot;&gt;函数&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;html():可以获取一段html文档（注意不是副本），但不是dom元素，不能用针对dom元素的函数如find()对其进行操作。&lt;/li&gt;
  &lt;li&gt;clone():拷贝一段dom节点的副本，可以用对dom操作的函数对其进行操作，如parent().&lt;/li&gt;
  &lt;li&gt;replace(reg,content):正则替换,将reg正则表达式匹配的内容用content元素替换。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;注&lt;/strong&gt;：用html()还是clone()就看你对模板的操作需求，如果你只是对每个模板替换掉动态显示的数据部分，用html就够了，如果你还要为每个模板操作一些特别的dom部分，如添加一段html或更改一段html部分，建议用clone()函数。&lt;/p&gt;

&lt;h1 id=&quot;思路&quot;&gt;思路&lt;/h1&gt;

&lt;p&gt;将要重复性插入的dom元素制成模板，在页面中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display:none&lt;/code&gt;后，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;html()&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clone()&lt;/code&gt;函数拷贝一份，然后用replace函数将动态数据部分进行替换。在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append()&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prepend()&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;after()&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before()&lt;/code&gt;等等插入到页面中。&lt;/p&gt;

&lt;h1 id=&quot;示例&quot;&gt;示例&lt;/h1&gt;

&lt;h2 id=&quot;1弄清需求&quot;&gt;1.弄清需求&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;col-xs-12 formItem_wrap&quot; &amp;gt;
    &amp;lt;h4&amp;gt;
        &amp;lt;span class=&quot;glyphicon glyphicon-plus&quot; style=&quot;color:#fca837&quot;&amp;gt;&amp;lt;/span&amp;gt;
        添加新表单项
    &amp;lt;/h4&amp;gt;
    &amp;lt;div class=&quot;col-xs-3 &quot; data-elementType=&quot;8&quot; data-type=&quot;0&quot; &amp;gt;
        &amp;lt;div class=&quot;col-xs-12 formItem&quot; data-fieldname=&quot;&quot;&amp;gt;日期&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;col-xs-3 &quot; data-elementType=&quot;9&quot; data-type=&quot;0&quot; &amp;gt;
        &amp;lt;div class=&quot;col-xs-12 formItem&quot; data-fieldname=&quot;&quot;&amp;gt;时间&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.formItem_wrap&lt;/code&gt;中循环输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &amp;lt;div class=&quot;col-xs-3 &quot; data-elementType=&quot;9&quot; data-type=&quot;0&quot; &amp;gt;
        &amp;lt;div class=&quot;col-xs-12 formItem&quot; data-fieldname=&quot;&quot;&amp;gt;时间&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;只是每个循环体的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-elementType&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-type&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-fieldname&lt;/code&gt;以及文字描述都不一样。&lt;/p&gt;

&lt;h2 id=&quot;2-制作模板&quot;&gt;2. 制作模板&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;form_list_template&quot; style=&quot;display:none&quot;&amp;gt;
     &amp;lt;div class=&quot;col-xs-3 &quot; data-elementType={$elementType} data-type=&quot;1&quot;&amp;gt;
          &amp;lt;div class=&quot;col-xs-12 formItem&quot; data-fieldname={$fieldname}&amp;gt;{$itemLabel}&amp;lt;/div&amp;gt;
     &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;3操作模板&quot;&gt;3.操作模板&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//要替换模板内容的json数组
var fixedFields = [
        {&quot;itemLabel&quot;: &quot;姓名&quot;, &quot;field&quot;:&quot;realname&quot;, &quot;tvalues&quot;:&quot;null&quot;, &quot;elementType&quot;:&quot;0&quot;},
        {&quot;itemLabel&quot;: &quot;性别&quot;, &quot;field&quot;:&quot;sex&quot;, &quot;tvalues&quot;:&quot;保密|,|男|,|女&quot;, &quot;elementType&quot;:&quot;11&quot;},
        {&quot;itemLabel&quot;: &quot;生日&quot;, &quot;field&quot;:&quot;birthday&quot;, &quot;tvalues&quot;:&quot;null&quot;, &quot;elementType&quot;:&quot;8&quot;},
        {&quot;itemLabel&quot;: &quot;住址&quot;, &quot;field&quot;:&quot;address&quot;, &quot;tvalues&quot;:&quot;null&quot;,  &quot;elementType&quot;:&quot;0&quot;},
        {&quot;itemLabel&quot;: &quot;邮箱&quot;, &quot;field&quot;:&quot;email&quot;, &quot;tvalues&quot;:&quot;null&quot;,&quot;elementType&quot;:&quot;3&quot;},
        {&quot;itemLabel&quot;: &quot;手机&quot;, &quot;field&quot;:&quot;mobile&quot;, &quot;tvalues&quot;:&quot;null&quot;, &quot;elementType&quot;:&quot;7&quot;},
        {&quot;itemLabel&quot;: &quot;电话&quot;, &quot;field&quot;:&quot;phone&quot;, &quot;tvalues&quot;:&quot;null&quot;, &quot;elementType&quot;:&quot;6&quot;},
        {&quot;itemLabel&quot;: &quot;qq&quot;, &quot;field&quot;:&quot;qq&quot;, &quot;tvalues&quot;:&quot;null&quot;,  &quot;elementType&quot;:&quot;2&quot;},
        {&quot;itemLabel&quot;: &quot;微博&quot;, &quot;field&quot;:&quot;weibo&quot;, &quot;tvalues&quot;:&quot;null&quot;, &quot;elementType&quot;:&quot;0&quot;},
        {&quot;itemLabel&quot;: &quot;证件类型&quot;, &quot;field&quot;:&quot;idType&quot;, &quot;tvalues&quot;:&quot;身份证|,|警官证|,|学生证|,|结婚证|,|驾驶证&quot;, &quot;elementType&quot;:&quot;14&quot;},
        {&quot;itemLabel&quot;: &quot;证件号码&quot;, &quot;field&quot;:&quot;idNumber&quot;, &quot;tvalues&quot;:&quot;null&quot;, &quot;elementType&quot;:&quot;0&quot;},
        {&quot;itemLabel&quot;: &quot;婚姻&quot;, &quot;field&quot;:&quot;married&quot;, &quot;tvalues&quot;:&quot;未婚|,|已婚|,|离异|,|丧偶&quot;, &quot;type&quot;:&quot;0&quot;, &quot;elementType&quot;:&quot;10&quot;},

    ];
    
    //获取模板
    var form_list_template = $(&quot;#form_list_template&quot;).html();
    
    //操作模板
    var form_item_init=function(){
        var form_list ;
        for ( key in fixedFields){
            //由于html()不是克隆，而是获取，为了不破坏原模板，我们将原模板拷贝给另一个变量，然后操作这个变量。
            form_list = form_list_template;
            form_list = form_list.replace(/\{\$elementType\}/gi,fixedFields[key].elementType);
            form_list = form_list.replace(/\{\$type\}/gi,fixedFields[key].type);
            form_list = form_list.replace(/\{\$itemLabel\}/gi,fixedFields[key].itemLabel);
            form_list = form_list.replace(/\{\$fieldname\}/gi,fixedFields[key].field);
            //将改造后的html输出到页面
            $(&apos;.formItem_wrap&apos;).append(form_list);
        }

    }

   form_item_init();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>动态json创建</title>
   <link href="http://echizen.github.io/tech/2014/11-09-dynamic-package-json"/>
   <updated>2014-11-09T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/dynamic-package-json</id>
   <content type="html">
&lt;h1 id=&quot;动态json创建&quot;&gt;动态json创建&lt;/h1&gt;

&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;作为一个前端，免不了要与后台攻城狮在页面上进行数据交互，话说这次的交互数据封装传输的任务都在我这边我也是吐了，用可怜的jquery在那儿dom-&amp;gt;数据、数据-&amp;gt;dom 整合，还要打包成json，再传给后台，我也是醉了，所以赶完这个项目我就去学angular.js了，数据和dom的交互jquery展现的性能太弱了，还是mvc的框架-angular.js强大。&lt;/p&gt;

&lt;h2 id=&quot;精粹&quot;&gt;精粹&lt;/h2&gt;

&lt;p&gt;json的动态创建其实就是用了push函数。
json数据格式要注意几点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;json的键值对必须先定义后使用。就是说你可以定义一个
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jsonData={&quot;name&quot;:&quot;&quot;,&quot;age&quot;:&quot;&quot;}&lt;/code&gt;，再通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jsonData.name=&quot;marry&quot;&lt;/code&gt;给这个对象赋值，但是你不能直接在未定义&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jsonData&lt;/code&gt;格式的情况下，就用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jsonData.name=&quot;marry&quot;&lt;/code&gt;的形式给它赋值。（这样你也许就纳闷了，因为多数情况下你不可能对一个很长的json数据先定义，所以我们要用push函数）&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;json数据访问时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jsonData.name&lt;/code&gt;中的name是键的名字，只能是字符串，如果你要获取一个变量key代表的键，要用jsonData[key]的方式获取。&lt;/p&gt;

    &lt;p&gt;现在举个列子：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; var prizer = {
     first: &quot;huhu&quot;,
     second: &quot;lala&quot;,
 };
 var key = &quot;first&quot;
 prizer.key
 =&amp;gt; undefined
 prizer[key]
 =&amp;gt; &quot;huhu&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;你要获得prizer底下键为first的值，直接用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prizer.key&lt;/code&gt;只会报&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;undefined&lt;/code&gt;的错误，因为它把key当成键&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;“key”&lt;/code&gt;了，这事用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prizer[key]&lt;/code&gt;可以获取”huhu”的值。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;对于一个json数组，形式类似&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[{...},{...},{...}]&lt;/code&gt;，动态创建：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var jsonArray = [];
jsonArray.push({...});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;对于一个json对象，形式类似&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ &quot;a&quot;:[{...},{...}], &quot;b&quot;:[{...},{...},{...}], &quot;c&quot;:[{...}] }&lt;/code&gt;,动态创建：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var json = {};
json.a=[];
json.a.push({...});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;以下实例内容没有整理，只是贴了自己写过的代码作为笔记，看懂上面内容后可忽略以下内容。&lt;/p&gt;
&lt;h2 id=&quot;实例json数组动态创建&quot;&gt;实例：json数组动态创建&lt;/h2&gt;
&lt;p&gt;假设你要封装一个如下所示的json：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[
  {
     &quot;itemLabel&quot;:&quot;姓名&quot;,
     &quot;field&quot;:&quot;realname&quot;,
     &quot;tvalues&quot;:[],
     &quot;priority&quot;:0,
     &quot;elementType&quot;:&quot;0&quot;
  },
  {
     &quot;itemLabel&quot;:&quot;性别&quot;,
     &quot;field&quot;:&quot;sex&quot;,
     &quot;tvalues&quot;:[
                  {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;保密&quot;},
                  {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;男&quot;},
                  {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;女&quot;}
               ],
      &quot;priority&quot;:1,
      &quot;elementType&quot;:&quot;11&quot;
  },
  {
      &quot;itemLabel&quot;:&quot;生日&quot;,
      &quot;field&quot;:&quot;birthday&quot;,
      &quot;tvalues&quot;:[],
      &quot;priority&quot;:2,
      &quot;elementType&quot;:&quot;8&quot;
  },
  {
      &quot;itemLabel&quot;:&quot;证件类型&quot;,
      &quot;field&quot;:&quot;idType&quot;,
      &quot;tvalues&quot;:[
                    {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;身份证&quot;},
                    {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;警官证&quot;},  
                    {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;学生证&quot;},  
                    {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;结婚证&quot;},
                    {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;驾驶证&quot;}
                 ],
      &quot;priority&quot;:3,
      &quot;elementType&quot;:&quot;14&quot;,
      &quot;ext&quot;:&quot;0&quot;
  },
  {
      &quot;itemLabel&quot;:&quot;标题&quot;,
      &quot;field&quot;:&quot;&quot;,
      &quot;tvalues&quot;:[
                    {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;选项A&quot;,&quot;img&quot;:&quot;../img/undefine_pic.png&quot;},
                    {&quot;oId&quot;:&quot;&quot;,&quot;text&quot;:&quot;选项B&quot;,&quot;img&quot;:&quot;../img/undefine_pic.png&quot;}
                 ],
      &quot;priority&quot;:4,
      &quot;elementType&quot;:&quot;16&quot;,
      &quot;ext&quot;:&quot;4&quot;
   }
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这是一个json数组，只是我只取了特殊的部分，只取了下标为0的部分，其他类似。&lt;/p&gt;

&lt;p&gt;这个json最多有3层。&lt;/p&gt;

&lt;p&gt;js动态生成：（这么贴代码是为了给自己留个笔记，嫌太长的请自动忽略。）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var get_formdata = function(obj,type){
    data_area = obj;
    var formdata = [];

    data_area.find(&apos;.js_userform_wrap .input_row .form-group&apos;).each(function(index){
        var itemLabel =  $(this).children(&apos;.itemLabel&apos;).text();
        itemLabel = itemLabel.substring(0, itemLabel.length - 1);
        var elementType = $(this).children(&apos;input[name=&quot;elementType&quot;]&apos;).val();
        var ext = $(this).children(&apos;input[name=&quot;ext&quot;]&apos;).val();
        if(type == &apos;edit&apos;){
            var sIId = data_area.find(&apos;input[name=&quot;itemId&quot;]&apos;).val();
            formdata.push({&quot;sIId&quot;:sIId, &quot;itemLabel&quot;:itemLabel, &quot;field&quot;:&quot;&quot;, &quot;tvalues&quot;:[],  &quot;priority&quot;:index, &quot;elementType&quot;:elementType, &quot;ext&quot;:undefined});
        }else{
            var sIId = &quot;&quot;;
            formdata.push({ &quot;itemLabel&quot;:itemLabel, &quot;field&quot;:&quot;&quot;, &quot;tvalues&quot;:[],  &quot;priority&quot;:index, &quot;elementType&quot;:elementType, &quot;ext&quot;:undefined});
        }
        
        if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;textarea_input&apos;){

            var field =  $(this).find(&apos;.js_itemName_wrap textarea&apos;).attr(&apos;name&apos;);
            var tvalues = &quot;&quot;;//$(this).find(&apos;.js_itemName_wrap input&apos;).val();
            formdata[index].tvalues = [];

        }else if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;select_input&apos;){
            
            var select = $(this).find(&apos;.js_itemName_wrap select&apos;);
            var field = select.attr(&apos;name&apos;);
            select.find(&apos;option&apos;).each(function(i){

                var oId = $(this).parent().siblings(&apos;.oId_wrap&apos;).find(&apos;input[name=&quot;oId&quot;]:eq(&apos;+i+&apos;)&apos;).val();
                if(oId==&apos;&apos; || oId == &apos;undefined&apos; || oId== &apos;{$oId}&apos; ){
                    oId = &apos;&apos;;
                }
                var text = $(this).text();
                formdata[index].tvalues.push({&quot;oId&quot;:oId,&quot;text&quot;:text});
            })
            
        }else if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;radio_input&apos;||$(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;check_input&apos;){
            var field =  $(this).find(&apos;.js_itemName_wrap input[type!=&quot;hidden&quot;]&apos;).attr(&apos;name&apos;);
            var radio = $(this).find(&apos;.js_itemName_wrap .js_choice_content&apos;);
            radio.each(function(){
                var oId = $(this).children(&apos;input[name=&quot;oId&quot;]&apos;).val();
                if(oId==&apos;&apos; || oId == &apos;undefined&apos; || oId== &apos;{$oId}&apos;){
                    oId = &apos;&apos;;
                }
                var text = $(this).find(&apos;label span&apos;).text();
                formdata[index].tvalues.push({&quot;oId&quot;:oId,&quot;text&quot;:text});
            })
            if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;check_input&apos;){
                var ext = $(this).parent(&apos;.input_row&apos;).find(&apos;.js_ext&apos;).children(&apos;option:selected&apos;).val();
                formdata[index].ext = ext;
            }
            
        }else if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;radioImg_input&apos;||$(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;checkImg_input&apos;){

            var field = $(this).find(&apos;.js_itemName_wrap input[type!=&quot;hidden&quot;]&apos;).attr(&apos;name&apos;);
            var radio = $(this).find(&apos;.js_itemName_wrap .js_choice_content&apos;);
            radio.each(function(){
                var oId = $(this).children(&apos;input[name=&quot;oId&quot;]&apos;).val();
                if(oId==&apos;&apos; || oId == &apos;undefined&apos; || oId== &apos;{$oId}&apos;){
                    oId = &apos;&apos;;
                }
                var img = $(this).find(&apos;label img&apos;).attr(&apos;src&apos;);
                formdata[index].tvalues.push({&quot;oId&quot;:oId,&quot;text&quot;:img});
            })

            if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;checkImg_input&apos;){
                var ext = $(this).parent(&apos;.input_row&apos;).find(&apos;.js_ext&apos;).children(&apos;option:selected&apos;).val();
                formdata[index].ext = ext;
            }

        }else if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;radioTextImg_input&apos;||$(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;checkTextImg_input&apos;){
            var field = $(this).find(&apos;.js_itemName_wrap input[type!=&quot;hidden&quot;]&apos;).attr(&apos;name&apos;);
            var radio = $(this).find(&apos;.js_itemName_wrap .js_choice_content&apos;);
            radio.each(function(){
                var oId = $(this).children(&apos;input[name=&quot;oId&quot;]&apos;).val();
                $(this).children(&apos;input[name=&quot;oId&quot;]&apos;).attr({&apos;data-oId&apos;:oId});
                if(oId==&apos;&apos; || oId == &apos;undefined&apos; || oId== &apos;{$oId}&apos;){
                    oId = &apos;&apos;;
                }
                var text = $(this).find(&apos;label span.js_choice_name&apos;).text();
                var img = $(this).find(&apos;label img&apos;).attr(&apos;src&apos;);
                formdata[index].tvalues.push({&quot;oId&quot;:oId,&quot;text&quot;:text,&quot;img&quot;:img});
                
            })
            if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;checkTextImg_input&apos;){
                var ext = $(this).parent(&apos;.input_row&apos;).find(&apos;.js_ext&apos;).children(&apos;option:selected&apos;).val();
                formdata[index].ext = ext;
            }
        }else if($(this).parent(&apos;.input_row&apos;).data(&apos;itemtype&apos;)==&apos;score_input&apos;){
            var field =  $(this).find(&apos;.js_itemName_wrap&apos;).attr(&apos;name&apos;);
            formdata[index].tvalues = []; 
        }else{//text_input/data_input/time_input
            var field =  $(this).find(&apos;.js_itemName_wrap input&apos;).attr(&apos;name&apos;);
            formdata[index].tvalues = [];
        }
        formdata[index].field = field;
    })
    return formdata;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这么贴代码是为了给自己留个笔记。&lt;/p&gt;

&lt;h2 id=&quot;实例json对象动态创建&quot;&gt;实例：json对象动态创建&lt;/h2&gt;

&lt;p&gt;要封装成：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   var prizer = {
		             &apos;first&apos;: [{}],
		             &apos;second&apos;: [{}],
		             &apos;third&apos;: [{}],
		        };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以下代码中生成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.get&lt;/code&gt;是获取之前函数中的返回值填充到prizer中。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataSource[nums[i]]&lt;/code&gt;为json对象。（这是个代码片段。。。）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	el.result = function(){
	            var prizer = {};
	            keycode = [&apos;first&apos;,&apos;second&apos;,&apos;third&apos;];
	            for(key in keycode){
	                var awards = keycode[key],//first\second\third
	                    locals = config.get(awards);//字符串
	                    prizer[awards] = [];

	                if( locals ){
	                    var nums = locals.split(&apos;,&apos;);//nums为数组形式
	                    for(var i = 0; i &amp;lt; nums.length; i++){
	                        prizer[awards].push(dataSource[nums[i]]);
	                    }
	                }
	            }
        return prizer ;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>跨域通信</title>
   <link href="http://echizen.github.io/tech/2014/11-08-cross-origin-communication"/>
   <updated>2014-11-08T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/cross-origin-communication</id>
   <content type="html">
&lt;h1 id=&quot;跨域通信&quot;&gt;跨域通信&lt;/h1&gt;
&lt;p&gt;网上关于跨域通信的例子不少，方法也很多，强烈建议用html5 的communication API。&lt;/p&gt;

&lt;h2 id=&quot;原由同源策略&quot;&gt;原由：同源策略&lt;/h2&gt;
&lt;p&gt;浏览器为了web安全，制定了同源策略，主要针对以下情况的网页之间不能通信。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;不同域名&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;不同端口&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;不同协议&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;可以跨域的元素&quot;&gt;可以跨域的元素&lt;/h2&gt;
&lt;p&gt;在浏览器中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link&amp;gt;&lt;/code&gt;这几个标签是可以加载跨域（非同源）的资源的，并且加载的方式其实相当于一次普通的GET请求，唯一不同的是，为了安全起见，浏览器不允许这种方式下对加载到的资源的读写操作，而只能使用标签本身应当具备的能力（比如脚本执行、样式应用等等）。&lt;/p&gt;

&lt;h2 id=&quot;html5-跨域postmessage&quot;&gt;html5 跨域：postMessage&lt;/h2&gt;

&lt;p&gt;这是一套新规范，从我个人使用角度而言，推荐他的原因是，他简洁、安全、可即时通信。
常用他来进行iframe父子页面的通信。&lt;/p&gt;

&lt;h3 id=&quot;示例wwwchildcom要向wwwparentcom传输信息&quot;&gt;示例:www.child.com要向www.parent.com传输信息。&lt;/h3&gt;

&lt;p&gt;其中www.child.com是www.parent.com中嵌套的iframe子页面的源.&lt;/p&gt;

&lt;p&gt;在www.parent.com页面中添加message事件监听器，监听子页面www.child.com传来的值&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	window.addEventListener(&quot;message&quot;, function( event ) { 
        // 判断数据来源是否是信任的源
        if(event.origin==&quot;https://www.child.com&quot;){
          // 把子窗口发送过来的数据显示在父窗口中
           $(&quot;#chosed_userform&quot;).val(event.data.form_id);
           $(&apos;#userform_iframe&apos;).removeClass(&apos;active&apos;);
           $(&quot;#chosed_outcome&quot;).text(&quot;您选择的是&quot;+event.data.form_name);
        }
    }, false );  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在www.child.com页面添加postMessage方法，向父页面www.parent.com传递信息。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; //操作父元素
function sendId(){ 
    // 通过 postMessage 向父窗口发送数据
    var form_id = $(&apos;input[name=&quot;check_userform&quot;]:checked&apos;).val();
    var form_name = $(&apos;input[name=&quot;check_userform&quot;]:checked&apos;).parent(&quot;label&quot;).text();
    var checked_form = {
        &quot;form_id&quot;: form_id,
        &quot;form_name&quot;: form_name,
    }
    window.parent.postMessage( 
            checked_form, 
            &quot;https://www.parent.com&quot;
        );
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;类似也可以由父页面向子页面发送数据。是不是很简洁。&lt;/p&gt;

&lt;h3 id=&quot;postmessage-api&quot;&gt;postMessage API&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;发送消息：postMessage(data,goal_origin)
 example:&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Window.postMessage(&quot;hello&quot;,&quot;https://www.a.com&quot;)&lt;/code&gt;
 在iframe中一般在contentWindow对象下调用该函数，如：
 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;document.getElementsByTagName(&quot;iframe&quot;).contentWindow.postMessage(&quot;hello&quot;,&quot;https://www.a.com&quot;)&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;监听消息message&lt;/p&gt;

    &lt;p&gt;` window.addEventListener(“message”, handleFunction(e),true/false)&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;
handleFunction(e)是处理函数，参数&lt;/code&gt;e.origin&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;是消息来源的URL，&lt;/code&gt;e.date`是传来的消息内容。
第三个参数是定义事件传递顺序了，true使用capture，是由父元素逐步传递到子元素，false使用bubbling，从子元素逐步传递到父元素，就是常说的冒泡。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;网传其他跨域方式&quot;&gt;网传其他跨域方式&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;服务器端代理&lt;/strong&gt;，缺点在于，默认情况下接收Ajax请求的服务端是无法获取到的客户端的IP和UA的。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;iframe&lt;/strong&gt;，使用iframe其实相当于开了一个新的网页，具体跨域的方法大致是，域A打开的母页面嵌套一个指向域B的iframe，然后提交数据，完成之后，B的服务端可以：
 	+ 返回一个302重定向响应，把结果重新指回A域；
    &lt;ul&gt;
      &lt;li&gt;在此iframe内部再嵌套一个指向A域的iframe。&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;这两者都最终实现了跨域的调用，这个方法功能上要比下面介绍到的JSONP更强，因为跨域完毕之后DOM操作和互相之间的JavaScript调用都是没有问题的，但是也有一些限制，比如结果要以URL参数传递，这就意味着在结果数据量很大的时候需要分割传递，甚是麻烦；还有一个麻烦是iframe本身带来的，母页面和iframe本身的交互本身就有安全性限制。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;JSONP&lt;/strong&gt;：利用script标签跨域，这个办法也很常见，script标签是可以加载异域的JavaScript并执行的，通过预先设定好的callback函数来实现和母页面的交互。它是一个非官方的协议，这个callback函数，对它的使用有一个典型的方式，就是通过JSON来传参，即将JSON数据填充进回调函数，这就是JSONP的JSON+Padding的含义。&lt;/p&gt;

    &lt;p&gt;在互联网上有很多JSONP的服务来提供数据，本质上就是跨域请求，并且在请求URL中指定好callback，比如callback=result，那么在获取到这些数据以后，就会自动调用result函数，并且把这些数据以JSON的形式传进去，例如（搜索“football”）：&lt;/p&gt;

    &lt;p&gt;http://ajax.googleapis.com/ajax/services/search/web?v=1.0&amp;amp;q=football&amp;amp;callback=result&lt;/p&gt;

    &lt;p&gt;使用JQuery来调用就写成：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; $.getJSON(&quot;http://ajax.googleapis.com/ajax/services/search/web?v=1.0&amp;amp;q=football&amp;amp;callback=?&quot;,function(data){
     //...
 });  总的来说，JSONP的跨域方式的局限性在于，只能使用GET请求，并且不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;4.&lt;strong&gt;Access Control&lt;/strong&gt;：
	有一些浏览器支持Access-Control-Allow-Origin这样的响应头，比如：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;header(&quot;Access-Control-Allow-Origin: http://www.a.com&quot;);&lt;/code&gt;
就指定了允许对www.a.com跨域访问。&lt;/p&gt;

&lt;p&gt;6.&lt;strong&gt;window.name&lt;/strong&gt;：这个东西其实以前被用作黑客XSS的手段，其本质是，当window的location变化的时候，页面会重新加载，但是有趣的是，这个window.name居然不发生变化，那么就可以用它来传值了。配合iframe，改变几次iframe的window对象，就完成了实用的跨域数据传递。&lt;/p&gt;

&lt;p&gt;5.&lt;strong&gt;document.domain&lt;/strong&gt;：这个方式适用于a.example.com和b.example.com这种跨域的通信，因为二者有一个共有的域，叫做example.com，只要设置document.domain为example.com就可以了，但是如果a.example1.com和b.example2.com之间要通信，它就没办法了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>mac环境下安装搭建github - jekyll blog</title>
   <link href="http://echizen.github.io/tech/2014/10-06-install-jekyll"/>
   <updated>2014-10-06T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/install-jekyll</id>
   <content type="html">
&lt;h1 id=&quot;mac环境下搭建github---jekyll-blog创建自定义风格模板&quot;&gt;mac环境下搭建github - jekyll blog，创建自定义风格模板&lt;/h1&gt;

&lt;h2 id=&quot;前篇-博客搭建&quot;&gt;前篇-博客搭建&lt;/h2&gt;
&lt;p&gt;——在头回家探亲的国庆佳节，我终于可以有时间搭建好了自己的blog，普天同庆！
先看官网教程搭建吧，如果喜欢折腾，搭个jekyll框架就好了，再自己折腾样式，如果秉着重视内容，就把jekyll-bootstrapclone下来，直接用他的默认样式.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://jekyllrb.com/docs/installation/&quot;&gt;jekyll官网：http://jekyllrb.com/docs/installation/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://jekyllcn.com/docs/installation/&quot;&gt;jekyll中文站：http://jekyllcn.com/docs/installation/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://jekyllbootstrap.com/usage/jekyll-quick-start.html&quot;&gt;jekyll-bootstrap 官网：http://jekyllbootstrap.com/usage/jekyll-quick-start.html&lt;/a&gt;（我基本是按这个教程安装的，虽然后来把它的模板全撤了，换成自己的了，这篇教程将github与jekyll博客搭建流程写的很清晰）&lt;/p&gt;

&lt;p&gt;如果不出意外，你都会遇到一些问题，遇到后就去google吧，基本都能找到答案。我就遇到了一些问题。&lt;/p&gt;

&lt;p&gt;1、其实博客搭建的时间比较早，期间本地博客一直搭不上，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem install jekyll&lt;/code&gt;一直在报错，后来静候mac更新了Ruby、下载了RubyGems·······不是很了解这些语言，也不是很了解ios系统，所以这里也没什么可分享的。就是要看教程、出问题google，&lt;/p&gt;

&lt;p&gt;2、按教程来的话，你敲&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push origin master&lt;/code&gt;，一定会报错：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    $ git push origin master
    Permission denied (publickey).
    fatal: The remote end hung up unexpectedly
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时，参考&lt;a href=&quot;https://help.github.com/articles/generating-ssh-keys/&quot;&gt;https://help.github.com/articles/generating-ssh-keys/&lt;/a&gt; &lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;主篇-风格主题搭建&quot;&gt;主篇-风格主题搭建&lt;/h2&gt;
&lt;p&gt;见我的下一篇博客吧&lt;a href=&quot;http://echizen.github.io/tech/2014/10-06-custome-blog-style/&quot;&gt;http://echizen.github.io/tech/2014/10-06-custome-blog-style/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;尾篇-发布博客&quot;&gt;尾篇-发布博客&lt;/h2&gt;
&lt;p&gt;有两种方式发布内容：&lt;/p&gt;

&lt;h3 id=&quot;1rake-post-titlehello-world&quot;&gt;1、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake post title=&quot;Hello World&quot;&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;一般写博客都是用这句，这句代码会在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_post\&lt;/code&gt; 文件夹下新建一个文件，文件名由 _config.yml 里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permalink: /:categories/:year-:month-:day-:title &lt;/code&gt;的配置决定，都说文件名不要取中文，jekyll系统会把文件名中的中文过滤掉，由于文章中会有title的设置，所以不用中文命名也不是什么痛苦的事啦。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permalink:&lt;/code&gt;的配置中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;不是真的文件夹部署，只是url上的显示，其实如果是真的文件夹分布就完美了。目前支持三种默认的配置，可以简洁的用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permalink: date&lt;/code&gt;这种形式配置。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141006-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;也就是说无论你是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permalink: /:categories/:year-:month-:day-:title &lt;/code&gt;还是什么，都只能显示三种效果，&lt;/p&gt;

&lt;h3 id=&quot;2修改头信息&quot;&gt;2、修改头信息&lt;/h3&gt;
&lt;p&gt;打开新建的那个文件，新建的文件终端都会输出新建的文件路径，所以只用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt;命令就可以很轻松的用默认的编辑器打开该文件了。（Mac下是Mou打开markdown文件）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	rake post title=&quot;jekyll blog 模板修改-创建自己的style&quot;
	Creating new post: ./_posts/2014-10-06-jekyll-blog--style.md
	➜  echizen.github.io git:(master) ✗ open ./_posts/2014-10-06-jekyll-blog--style.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;新建的~~~XXXhello-world.md的头部有一段&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YAML&lt;/code&gt; 头信息，形如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	---
	layout: post
	title: &quot;hello world&quot;
	description: &quot;&quot;
	category: 
	tags: []
	---
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;layout:决定渲染的模板，post就是post.html啦，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake post&lt;/code&gt;命令生成的默认都是post。&lt;/p&gt;

&lt;p&gt;title:真正的会在归档等地方显示的页面标题。&lt;/p&gt;

&lt;p&gt;description:为了SEO优化，方便搜索引擎搜到，最好写一点。&lt;/p&gt;

&lt;p&gt;category:文章所在的分类，像我的博客是以category分页面展示的，这时候这个参数就非常重要。&lt;/p&gt;

&lt;p&gt;tags:给文章打上标签，和category一样，这样两个都是要在页面模板上有处理时才方便。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;以上的参数冒号后一定要多加个空格不然，呵呵，就不能正确解析了&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;3发布博客&quot;&gt;3、发布博客&lt;/h3&gt;
&lt;p&gt;这一步在我的电脑上不需要，我在本地手动新建的文件，不用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git add&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit&lt;/code&gt;就能&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll server&lt;/code&gt;后在本地访问，就看到修改后的效果，很方便，满意后再&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commit&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;如果是新增：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git add XXX -&amp;gt; git commit -m &apos;add XXX&apos;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;如果是修改已存在的文件就： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git ci -am &apos;modify XXX&apos;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后一致&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push origin master&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;ps:在中文下，markdown其实挺不好用的，不断切换英文的语法标签和中文。。。&lt;/p&gt;

&lt;h3 id=&quot;4疑难杂症&quot;&gt;4、疑难杂症&lt;/h3&gt;
&lt;p&gt;markdown里插图片好不方便啊！！！还得加链接。我的做法是，在根目录下的assets下新建一个blog-img文件夹，图片基本来源于截图，我把截图的保存目录直接关联到这个目录，然后在博文中因为考虑到有可能会把.md文件粘到其他地方，我用的又是绝对地址，即https://username.github.io/assets/blog-img/图片名称.png。这样相对方便一点，但是还是没有那种直接在插入图片方便。不知大家用的是什么方法。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>自定义jekyll博客主题</title>
   <link href="http://echizen.github.io/tech/2014/10-06-custome-blog-style"/>
   <updated>2014-10-06T00:00:00+00:00</updated>
   <id>http://echizen.github.io/tech/2014/custome-blog-style</id>
   <content type="html">
&lt;h1 id=&quot;自定义jekyll博客主题&quot;&gt;自定义jekyll博客主题&lt;/h1&gt;

&lt;p&gt;你要在别人的东西上动刀子，就必须要先了解别人的东西，随便改改，后遗症是很严重的。&lt;/p&gt;

&lt;h1 id=&quot;1-yaml与liquid&quot;&gt;1. YAML与liquid&lt;/h1&gt;
&lt;p&gt;jekyll静态博客系统之所以能这么智能，首先归功于这两者，所以你得先了解他们。不需要能掌握，至少要能看懂已经写好的一些配置，能模仿着写一些头信息。&lt;/p&gt;

&lt;p&gt;YAML是会经常用到的，这个标记语言没什么符号，所以空格要特别小心，他的缩进在语法上有特殊含义。&lt;/p&gt;

&lt;h1 id=&quot;2-文件结构与关联&quot;&gt;2. 文件结构与关联&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141006-2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图是clone下来的原始结构，其中，比较重要的几个是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141006-3.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;21-_includes主要的样式文件你所有的博文内容的模板页都在这儿&quot;&gt;2.1 _includes:主要的样式文件，你所有的博文内容的模板页都在这儿。&lt;/h2&gt;

&lt;p&gt;-&lt;strong&gt;themes&lt;/strong&gt;:这里放的是模板文件。&lt;/p&gt;

&lt;p&gt;–default.html:博文公共内容的模板文件。比如头和尾。&lt;/p&gt;

&lt;p&gt;–page.html:对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout:page&lt;/code&gt;的文件的模板文件。&lt;/p&gt;

&lt;p&gt;–post.html:对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout:post&lt;/code&gt;的文件的模板文件。&lt;/p&gt;

&lt;p&gt;–settings.yml:主题的声明，一般内容是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	theme :
	  name : echizen
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于静态文件引用时的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/assets/themes/echizen&lt;/code&gt;变量很有效。该变量在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;中定义，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./_includes/JB/setup&lt;/code&gt;中有一句&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt; &lt;/code&gt;而settings.yml中的定义就是给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page.theme.name&lt;/code&gt;一个值，改值要和你主题文件夹名称相同。&lt;/p&gt;

&lt;p&gt;-&lt;strong&gt;JB&lt;/strong&gt;:主要是系统配置的插件或模块的模板文件。&lt;/p&gt;

&lt;p&gt;–comments-providers:评论模板，文件夹下还有disqus、facebook、intensedebate、livefyre四个文件，这个与_config.yml里的配置有关,如我的的这个配置使用的是disqus，那么disqus文件就生效,打开disqus文件可以看到这里其实是disqus官网上的一段嵌入的代码，他决定着评论的样式和功能，你要是喜欢折腾，也可以来这个文件动刀子。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	comments :
		provider : disqus
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;–analytics-providers:博客数据分析插件的样式,作用同上。&lt;/p&gt;

&lt;p&gt;–anlytics、comments这2个文件是使用liquid语法调用上面提的相应文件,有点MVC的感觉，这个是controller,上面提的文件是view.&lt;/p&gt;

&lt;p&gt;–剩下的几个文件，都是一些配置，变量的定义什么的，我基本没动他们，感兴趣的可以打开文件看看。文件的注释部分将其用法写的很清楚。不果setup可以看看，这里详细定义了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BASE_PATH&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HOME_PATH&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ASSET_PATH&lt;/code&gt;,对写模板时静态文件的引入很有用。&lt;/p&gt;

&lt;h2 id=&quot;22--_layouts&quot;&gt;2.2  _layouts:&lt;/h2&gt;
&lt;p&gt;这里的3个文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.html&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page.htm&lt;/code&gt;l、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post.html&lt;/code&gt;相当于contrllor用来调用view的，如default.html文件里，先是 YAML关于模板名的定义。然后再是liquid语言，先调用setup，来让页面中引用静态文件的变量能解析，然后是调用相应的模板。(本来想上代码的，无奈会被解析掉。。。)&lt;/p&gt;

&lt;h2 id=&quot;23-_plugins插件&quot;&gt;2.3 _plugins:插件&lt;/h2&gt;

&lt;h2 id=&quot;24-_posts发布的文章集&quot;&gt;2.4 _posts:发布的文章集。&lt;/h2&gt;

&lt;h2 id=&quot;25-_sites站点目录&quot;&gt;2.5 _sites:站点目录&lt;/h2&gt;
&lt;p&gt;别人能通过URL访问的文件都在这里，基本你不用操作本目录，你在其他目录下的操作，如果涉及到站点的访问，jekyll都会自动同步到该目录。&lt;/p&gt;

&lt;h2 id=&quot;26-assets静态文件资源存放&quot;&gt;2.6 assets:静态文件资源存放&lt;/h2&gt;
&lt;p&gt;css、js、img都可以存在这里。&lt;/p&gt;

&lt;h2 id=&quot;27-_configyml配置文件&quot;&gt;2.7 _config.yml:配置文件&lt;/h2&gt;
&lt;p&gt;这里有很多全局变量的配置，这里配置的变量，在整个站点都能访问，但是这里都是简单的变量声名定义，具体变量的功能性操作都在其他文件夹下具体定义，比如你尝尝会见到的灰常灰常重要的JB变量，具体的JB的内容操作都在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes/JB&lt;/code&gt;文件夹下。&lt;/p&gt;

&lt;h2 id=&quot;28-其他根目录文件&quot;&gt;2.8 其他根目录文件&lt;/h2&gt;
&lt;p&gt;.html文件是直接访问的一级页面，如默认的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;archive.html&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;categories.html&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pages.html&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tags.html&lt;/code&gt;，自己写的放在博客里的一级页面也可以扔这里，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rake page =&quot;about.md&quot;&lt;/code&gt;这种方式创建的页面也在这级上。&lt;/p&gt;

&lt;p&gt;还有一部分介绍性文件，如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.md文件&lt;/code&gt;。还有一个非常重要的文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rakefile&lt;/code&gt;，建议如果不是很了解它就不要动他，否则会挂掉。&lt;/p&gt;

&lt;h1 id=&quot;3开启自己的模板时代&quot;&gt;3.开启自己的模板时代&lt;/h1&gt;
&lt;p&gt;就按我自己写模板的过程来写吧。
先将上几幅我的博客的截图吧，我的博客其实只有3套样式的页面，主页、分类列表页、博文内容页。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141009-1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141009-2.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://echizen.github.io/assets/blog-img/QQ20141009-3.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;31-首页&quot;&gt;3.1 首页&lt;/h2&gt;
&lt;p&gt;首页是访问github page 的username.github.io直接跳转到的页面，首页的文件名一定要是index.html或index.md，因为jekyll的URL路由把域名指向了这里，而且index.md比index.html先加载，所以两者你只能取一个啦。首页保存在根目录下。
	首页是最最重要的面子工程啦。首页也要头信息，如果首页风格与其他页面都不同就不要加layout了，因为你不需要引用模板。为了引用静态文件，一定要加&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	theme :
	   name : your theme name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后别忘了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include JB/setup&lt;/code&gt;，这都是为了在页面中使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/assets/themes/&lt;/code&gt;的变量。&lt;/p&gt;

&lt;p&gt;然后你就可以像写一个普通的html页面一样去尽情的绘制你的模板吧。&lt;/p&gt;

&lt;p&gt;输出分类信息的代码，这样就可以把你的分类信息显示出来了。（为了不被jekyll解析，我将以下代码中liquid的tag符号去除了）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	 assign pages_list = site.pages 
	 assign group = &apos;navigation&apos; 
	 include JB/pages_list 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;说的深入一点，site.pages指的是所有头信息中定义了layout:pages的页面，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group = &apos;navigation&apos;&lt;/code&gt;这句是将头信息中含有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group: navigation&lt;/code&gt;的页面筛选出来了。然后调用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes/JB/pages_list&lt;/code&gt;页面输出具体的html代码，所以你如果要修改nav列表的html可以去这里。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;32-设置导航&quot;&gt;3.2 设置导航&lt;/h2&gt;
&lt;p&gt;国人习惯分类写文章，所谓的分类其实就是jekyll上的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;category&lt;/code&gt;参数。&lt;/p&gt;

&lt;p&gt;我先在根目录下新建我的分类的一级页面，我分了4类:tech、think、life、show，命名的时候我是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a_tech.html&lt;/code&gt;，加了前缀是为了在页面显示时排序，因为你输出这些分类的的代码其实是一个遍历，遍历的顺序是通常的字母排序。&lt;/p&gt;

&lt;p&gt;我的导航页面内容很简单，因为我调用了模板，我把首页当view来写的，导航的这些页面则是当controller来写的（mvc的概念是php中的，不知其他语言是否有，大家是否能意会）。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	---
	layout: page
	title: Tech
	header: Posts By Tech
	group: navigation
	theme :
	   name : echizen
	---
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里很重要的一句是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group: navigation&lt;/code&gt;，通过上面的介绍你应该已经知道，这是决定这个文件是导航文件的一个重要项。&lt;/p&gt;

&lt;p&gt;然后调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include JB/setup&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assign posts_collate = site.categories.tech&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include JB/posts_collate&lt;/code&gt;。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assign posts_collate = site.categories.tech&lt;/code&gt;：这句非常重要，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;site.categories.category_name&lt;/code&gt;就是循环获取这个category_name下的博客文章。&lt;/p&gt;

&lt;h2 id=&quot;33-导航页面样式&quot;&gt;3.3 导航页面样式&lt;/h2&gt;
&lt;p&gt;我是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes/echizen/page.html&lt;/code&gt;页面设置样式的，而调用这个page的页面是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_layouts/page.html&lt;/code&gt;，这个页面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout:default&lt;/code&gt;,所以我设置的时候导航页面和普通的博文页面的一部分是公用的，这无所谓，看个人所需。然后去_includes/JB/posts_collate这个文件修改样式。&lt;/p&gt;

&lt;h2 id=&quot;34-具体页面样式&quot;&gt;3.4 具体页面样式&lt;/h2&gt;
&lt;p&gt;其实这部分是看前端怎么划分，组织页面间样式可以多样性，你甚至可以让每个分类的样式不同。&lt;/p&gt;

&lt;p&gt;虽然我是个折腾的前端，但是我没折腾到每个页面都独具一格。我是首页一种样式，分类一种样式，博客一种样式。分享一下我的修改经验。首页完全独立，分类页面和博文内容有共用内容。&lt;/p&gt;

&lt;h3 id=&quot;defaulthtml&quot;&gt;default.html&lt;/h3&gt;
&lt;p&gt;前面已经介绍了，样式文件在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes/JB/themes/echizen&lt;/code&gt;下，echizen是我设置的主题名。主要的样式文件是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.html&lt;/code&gt;,这里放置分类页面和博文页面的公有内容。为了观者导航方便，这两个页面的导航都是应该有的，还有公用的页尾信息。&lt;/p&gt;

&lt;p&gt;加入导航条（为了不被jekyll解析，我将以下代码中liquid的tag符号去除了）：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	 assign pages_list = site.pages	  
	 assign group = &apos;navigation&apos;	  
	 include JB/pages_list ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;作为一名尊重别人劳动成果的的程序猿，我没有将页面尾部&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;footer&amp;gt;&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;/footer&amp;gt;&lt;/code&gt;之间的感谢原框架作者的内容，^_^&lt;/p&gt;

&lt;h3 id=&quot;pagehtml&quot;&gt;page.html&lt;/h3&gt;
&lt;p&gt;在我的博客主题里这其实就是分类页的模板，里面用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;liquid&lt;/code&gt;引用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt;，然后由于我在分类页的原controller页面调用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include JB/posts_collate&lt;/code&gt;，所以我去这个页面把文章列表按时间线的样式排列了下来。&lt;/p&gt;

&lt;h3 id=&quot;posthtml&quot;&gt;post.html&lt;/h3&gt;
&lt;p&gt;博文内容样式页，这里有一些文章标题、内容框架、标签样式、分页的样式。然后具体的pre、h3、p等这些样式可以写成css样式表，扔到assets/themes/theme_name/css下。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意default.html可以解析YAML，但是page.html和post.html不能解析YAML&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;35-添加comment&quot;&gt;3.5 添加comment&lt;/h2&gt;
&lt;p&gt;先从 Disqus, Intense Debate, livefyre,  Facebook Comments中选一个，去官网注册个帐号，我选的是&lt;a href=&quot;https://disqus.com/&quot;&gt;Disqus&lt;/a&gt;，在&lt;a href=&quot;https://disqus.com/admin/create/&quot;&gt;这里&lt;/a&gt;注册个号，然后将你注册的帐号名添加到_config.yml中&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   comments :
    provider : disqus
    disqus :
      short_name : your_name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;4番外篇&quot;&gt;4.番外篇&lt;/h1&gt;
&lt;p&gt;有看到 &lt;a href=&quot;http://pizn.github.io/&quot;&gt;大神&lt;/a&gt; 把jekyll默认的结构改的渣都不剩的，我也clone了他的代码看了作用,&lt;a href=&quot;https://github.com/pizn/pizn.github.com&quot;&gt;https://github.com/pizn/pizn.github.com&lt;/a&gt;，感谢他的代码的启发作用。无奈功底不深，无法改成这幅样子，我更多的参考的还是默认模板，所以我的主题是在默认模板下修改的。但是操作过一遍后我发现其实不改其原框架结构，按其原则也能达到随心所欲的页面的效果，再结合liquid和YAML，足够折腾了，所以不是很了解其内部关系时，我建议大家也按其规则修改。&lt;/p&gt;

&lt;h2 id=&quot;关于markdown解析&quot;&gt;关于markdown解析&lt;/h2&gt;
&lt;p&gt;原来markdown具有多种渲染器，这些渲染器对markdown语法的解析还不一样。jekyll使用的是 Redcarpet 的markdown渲染器，他对代码块的```不支持，代码块必须是缩进4个空格，好不方便，不过用快捷键解决的话也能接受。&lt;/p&gt;

&lt;h1 id=&quot;5主要解决的问题&quot;&gt;5.主要解决的问题&lt;/h1&gt;
&lt;h3 id=&quot;jekyll-server-本地blog运行时mac会报错一群cantfind--in-atomxml的错误&quot;&gt;jekyll server 本地blog运行时,mac会报错一群cant’find ··· in atom.xml的错误。&lt;/h3&gt;
&lt;p&gt;解决：将_config.yml中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pygments: true &lt;/code&gt;替换为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;highlighter: pygments&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;只能在_includes下的defaluthtml中使用而不能在posthtmlpagehtml中使用&quot;&gt;``只能在_includes下的defalut.html中使用，而不能在post.html、page.html中使用。&lt;/h3&gt;
&lt;p&gt;解决：在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_layout&lt;/code&gt;下的post.html和page.html中加入头信息：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	theme :
	   name : theme_name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;并将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rakefile&lt;/code&gt;中137至145行的内容注释掉，替换成（为了不被jekyll解析，我将以下代码中liquid的tag符号去除了）：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	# if File.basename(filename, &quot;.html&quot;).downcase == &quot;default&quot;
    #   page.puts &quot;---&quot;
    #   page.puts File.read(settings_file) if File.exist?(settings_file)
    #   page.puts &quot;---&quot;
    # else
    #   page.puts &quot;---&quot;
    #   page.puts &quot;layout: default&quot;
    #   page.puts &quot;---&quot;
    # end 
    page.puts &quot;---&quot;
    page.puts File.read(settings_file) if File.exist?(settings_file)
    page.puts &quot;layout: default&quot; unless File.basename(filename, &quot;.html&quot;).downcase == &quot;default&quot;
    page.puts &quot;---&quot;
    page.puts &quot;% include JB/setup %&quot;
    page.puts &quot;% include themes/#{theme_name}/#{File.basename(filename)} %&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 
</feed>