[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"list-\u002Farticles\u002F7":3},{"items":4,"total":124},[5,20,28,41,49,57,64,73,80,90,98,113],{"id":6,"type":7,"slug":8,"title":9,"date":10,"category":11,"tags":12,"body_markdown":14,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},171,"article","ios-disable-hotfix-and-the-future-of-web","【蹭热点】从iOS禁用热更新扯到web的未来","2017-03-09 09:24","web",[13,11],"热更新","\n昨天发生了一件不大不小的事情：有相当多的iOS开发者收到了来自苹果的警告邮件，大意是“你的app有动态修改应用逻辑的行为”。说得更通俗一些，就是能绕过app store的发版审核机制，直接进行更新的app基本都收到了警告。\n\n按理说，这其实是一件于情于理都没什么好争辩的事情，毕竟发版本要经过审核是白纸黑字定下的规则。但是这件事情却掀起了轩然大波，甚至连React-Native和Weex都一瞬间有种人人自危的感觉。这到底是为什么呢？\n\n## 审核之痛\n\n首先，为什么有这么多人在明知冒险的情况下仍然会选择使用热更新的方式呢？主要的原因还是苹果的版本发布审核太慢，据说7天以上是家常便饭。试想，一个app发出去，发现有一个bug，用户居然需要一周以上的时候才能更新到修复版本，这个在当前中国互联网环境下确实是不太适用的，所以热更新才能大行其道。\n\n当然，也有一部分人完全是动了歪心思，先合法地上架app，然后再修修修改已上架的app行为，偷偷摸摸做些见不得人的事情。\n\n## web缺席\n\n既然热更新是如此迫切的需求，那难道就没有更好的方案吗？比如……web？天生就热更新，不好么？\n\n当然好。可是web的性能和体验这么差，大部分情况下属于不得已的情况下才会考虑的方案。所以在移动互联网的崛起中，web基本是缺席的。而且这个现象并没有任何好转的迹象。\n\n\u003C!-- more -->\n\n## web在互联网的地位\n\n这一段标题党了，既然都缺席了，还谈何地位？曾经有一段时间，web在移动互联网的应用是很被看好的，也就是HTML5红遍大江南北那几年，随着各种native API（定位、加速计、电池等）的到位和各种hybrid方案的成熟，web一度有要将Android和iOS直接干下马的气势。\n\n然而，时间终将会说明一切。事实证明，一个大而全的web体系并不适合在移动互联网使用。\n\n在《To a Dark Future》一文（见公众号菜单）中，我曾经说过，web标准体系是一个非常繁杂的体系，即使是想在页面上显示一个文本，也会面临一堆的事务需要处理。 同时，由于web标准关注点的迷失，一些长久以来无法很好在web中完成的东西现在仍然缺位。这导致web在移动互联网时代有着天然的性能和体验缺陷，而且短期内看不到有利好的趋势。\n\n## A New Web?\n\n既然大而全的web体系并不适合在移动互联网使用，那是否有替代方案呢？\n\nGoogle给了一个方向，就是PWA。但是思路仍然是在web上做加法。如果这个东西推广成功了，那么至少web可以解决一个老大难的资源加载问题。然而，由于标准制订牵涉到各方利益，目前来看，这个标准基本上不可能推广到iOS中。从昨天的苹果警告热更新应用中更可以明显的感觉到这一信号：苹果不希望自己的应用生态被侵入，而如入PWA能落地，无疑是一个巨大的威胁。\n\n微信也给了一个方向，就是小程序。很多人看完小程序都会有一个感觉：这和web不是同一个东西吗？从技术的角度上来说，小程序之所以和web撇清关系，就是希望抛开繁杂的web体系，重建一个微信自己的新web，而这个新web中什么特性保留什么特性抛弃完全是微信自己掌控。控件不满足需求时新建一种，性能有问题时改一下实现，甚至改一下API也不是什么大事。关于小程序和web的技术考量，我在知乎《微信小程序为什么不用HTML5、CSS，自己搞了个WXML、WXSS，很多框架用不了，好处一点不知道？》这个问题下有详细回答，可[点击查看](\u002Farticle\u002Ftech\u002F2017\u002Fwhy-mini-program-use-self-developed-tech-stack.html)。\n\n## Future\n\n未来会怎样？我在[Dark Future](\u002Farticle\u002Fweb\u002F2017\u002Fto-a-dark-future.html)一文中表示对web前端持悲观态度。\n\n但是……（对，按套路，肯定有但是的……）热更新的需求仍然无比旺盛。如果苹果继续保持审核时的傲骄姿势的话，新web还一定会出现，但既然叫新web，也就跟现有的web没有太大关系了，但这也许是web唯一的出路。\n",null,0,"2026-08-28 04:37:17","published","",{"id":21,"type":7,"slug":22,"title":23,"date":24,"category":25,"tags":26,"body_markdown":27,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},81,"five-years-plan","【问答】五年职业规划","2017-03-01 09:36","life",[],"\n## 问\n\n请问八哥五年内的职业规划是什么？（来自小密圈）\n\n## 答\n\n这个问题让我犹豫了好久好久啊。因为5年确实很难预料会发生什么。先回顾一下过去的几年职业路径吧。\n\n工作前两年基本在学习，怒补基本功，以及作为一个工程师，在职场上的职责是什么，怎么样去参与项目做事情。\n\n接下来几年有幸开始慢慢主导一些项目的开发工作，慢慢学会了如何以产品思维来思考技术工作，明白技术为产品服务。这期间也有去充当一半产品经理的角色，去参与需求的规划，去关注用户的反馈，去解答用户的疑问。当然期间也在一直在补充前端技术并开始接触新技术和开源社区，对前端的知识体系有了更深刻的认知。\n\n如果说工作到现在学到了什么核心竞争力的话，可能是两个方面。一方面是清楚地知道前端的能力、职责和限制，有什么需求能清楚知道能不能做，以及如何做，代价如何。另一方面则是积累了一些产品研发全流程的经验，知道研发过程各个角色的立场和思维模式，尤其是产品思维。\n\n\u003C!-- more -->\n\n未来5年怎么做。实话说，并不容易。在“每18到24个月前端都会难一倍”这样的高速技术更新下，5年以后前端在哪里都很难说，这个趋势一下子很难看透。之前在小密圈也有跟部分资深的同行聊过一些，大家的看法也都不尽相同。\n\n回到技术工种，如果但凡还有一点上进心，不是那种“我会写几行代码，我想一直写这几行代码，把自己减少就行”的，那么基本上3到5年就会在技术上达到一个比较大的瓶颈，换句话说，这个阶段比较基础的知识和能力都已经掌握，很难再出现觉得特别为难无法完成的需求。这个阶段之后接下来的技术能力提升对产出的贡献比较有限。\n\n可能的几个发展方向：\n\n1. 全栈工程师，即持续在后端开发 服务器运维 或者客户端方向发展，甚至连设计产品也有必要深入接触，深入掌握产品研发全流程\n2. 技术leader 即带领技术团队，这个方向需要更多关注团队和成员情况，是一个完全不同的发展方向，技术深度的重要性反而没那么重要\n\n至于这些方向能做到什么程度，确实很难预测，跟行业发展、个人定位、工作机遇等都有很大关系。但不管怎样，保持积极向上的心态是永远不会错的，不管什么时候，尽人事，听天命。\n",{"id":29,"type":7,"slug":30,"title":31,"date":32,"category":11,"tags":33,"body_markdown":39,"permalink":40,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},179,"usage_of_weakmap","ES2015 WeakMap的学习和使用","2017-02-27 13:15:00",[34,35,36,37,38],"JavaScript","ES2015","ES6","WeakMap","数据结构","\nES2015（ES6）中新增了几种数据类型，包括Map WeakMap Set WeakSet等。其中Map可以与我们熟悉的对象Object进行对照，他们的功能都是提供一个键值对集合，主要的区别在于Object的key只能是字符串，而Map的key可以是任意类型。关于Map的大致用法可以参考[MDN](https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FMap)，我在前一篇文章[《也谈JavaScript数组去重》](https:\u002F\u002Fwww.toobug.net\u002Farticle\u002Farray_unique_in_javascript.html)中也有提及。\n\n今天要讨论的主角是WeakMap。\n\n按照[MDN](https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap)上的说明\n\n> WeakMap 对象是键\u002F值对的集合，且其中的键是弱引用的。其键只能是对象，而值则可以是任意的。\n\n从这段描述来看，我们可以大致推断出，WeakMap与Map的主要区别在于两点：\n\n1. WeakMap对key的引用是弱引用\n2. WeakMap的key只能是对象\n\n这两点意味着什么呢？反正我第一眼看到的时候不是拉格良日懵就是三元二次懵的状态。于是围绕WeakMap去查阅了一些资料，渐渐地有了一些更深入的认识，记录成本文。\n\n\u003C!-- more -->\n\n## WeakMap的特性\n\n具体而言，WeakMap大致有如下一些明显的特性：\n\n### 1. key只能使用对象\n\n例如：\n\n```javascript\nvar m = new WeakMap();\nvar k = {};\n\n\u002F\u002F 设置键值对\nm.set(k, 1);\n\n\u002F\u002F 取值\nm.get(k);   \u002F\u002F1\n\n\u002F\u002F 非对象的key会报错\nm.set('k', 1);  \u002F\u002F报错\n```\n\n在上例中，我们使用了对象`k`作为WeakMap的key，设置了value为`1`。到下面取值的时候，只能使用同一个对象`k`去取。也就是说WeakMap是按照key的引用来对value进行存取的。\n\n> 关于“key只能使用对象”和“value的查找是通过比较key的引用”这两个命题，其实是互为因果的，本质上是一个先有鸡还是先有蛋的问题：\n>\n> 正因为WeakMap只能使用对象作为key，所以取值的时候对key进行查找也只能按对象引用进行查找。\n>\n> 正因为WeakMap在查找的时候只能按对象引用进行查找，所以只能使用对象作为key，否则存进去的值根本无法查找取出。\n\n### 2. key中的对象保持弱引用\n\n弱引用正是WeakMap中“Weak”的含义。熟悉JavaScript的朋友都知道引用是怎么回事，简单地说，当一个对象被引用的时候，往往意味着它正在被使用，或者在将来有可能会被使用。此时对象不会被垃圾回收机制回收掉。\n\n```javascript\nvar obj = {};\n......\nobj = null;\n```\n\n这段代码中，一开始`obj`引用了使用字面量创建的空对象，因此这个空对象不会被回收。很久以后`obj`被指向了`null`，不再引用空对象，此时这个空对象就不再能从任何代码中被访问到，将被回收掉。\n\n> 为简单起见，这里不讨论循环引用的垃圾回收问题。\n\n而弱引用则可以理解为“引用了对象，但是不影响它的垃圾回收”。假设，请注意，是假设，仅仅是假设，假设上例中第一句`obj = {}`是一个弱引用的关系，那么因为这个空对象没有其它的引用，它将很快被垃圾回收，在下方无法访问到。\n\n具体到WeakMap而言，大概是这样：\n\n```javascript\nvar obj = {};\nvar wm = new WeakMap();\nwm.set(obj, 1);\nwm.get(obj);\t\u002F\u002F 1\n......\nobj = null;\nwm.get(obj);\t\u002F\u002F 这句没有意义\n```\n\n在这个例子中，WeakMap实例`wm`（弱）引用了`obj`对象（空对象），接着下方代码释放了对空对象的引用（`obj = null`），此时和上例一样，空对象将被垃圾回收。也即`wm`中持有的空对象（弱）引用并不影响对对象本身的垃圾回收。这就是WeakMap中“弱引用”的含义。\n\n值得注意的是最后一行代码（`wm.get(obj)`），因为`obj`的引用已经被修改，所以这里无法访问到原来`obj`关联的值`1`，同时，会不会因为空对象已经被垃圾回收，所以`wm`中其实也已经没有了这个值。那么，到底是因为我们找不到路所以取不到值，还是因为它的值本来就不存在了呢？答案应该是Both吧，不过这个问题不翻规范的话，有点像哲学问题了，挺好玩。\n\n### 3. 无法遍历\n\nWeakMap和Map\u002FObject等有一个很大的不同，即它是不可遍历的。你无法使用`for...in`或者`for...of`等语句知道WeakMap中的内容。\n\n至于具体的原因……其实我不知道。MDN上是这样介绍的：\n\n> 正由于这样的弱引用，WeakMap 的 key 是非枚举的 (没有方法能给出所有的 key)。如果key 是可枚举的话，其列表将会受垃圾回收机制的影响，从而得到不确定的结果.\n\n从这段描述中，可以大致验证下我们上面的问题，当对象引用消失后，到底是因为我们缺少了引用所以无法从WeakMap中取到值还是WeakMap中的值本身也会消失？稍微了解一些JS垃圾回收知识的朋友都清楚，JS的垃圾回收会在一些条件（剩余内存、CPU负荷情况）下触发，也就是说可能会有一定延时。而上文说如果可以遍历，结果会受垃圾回收机制影响，大致可以理解为：如果可以遍历，那么在垃圾回收之前，将遍历到已经没有引用的对象和对应的值，在垃圾回收之后，则对象和值一起消失。因此可以大概猜出结论：WeakMap中的值会在垃圾回收时才消失。\n\n## WeakMap的使用场景\n\n客观地说，WeakMap的使用场景并不是很多，而且在条件不是非常苛刻的前提下，一般都可以有替代解决方案。不过很多情况下使用WeakMap来解决问题会更简单可靠一些。\n\n### 一个例子\n\n看完前面又臭又长的理论，不知道你会不会已经急得抓耳挠腮了：这么个key只能是对象，又不能遍历的东东，到底有什么用啊？反正我当时是急得想跺脚了，几乎所有的文章都只说这是个什么东西，并不告诉你它有什么用。经过多方求证，最后终于了解到它的核心思想：\n\n**在不改变对象本身的情况下扩展对象。**\n\n怎么理解呢？假如有100只鸡，现在要对每只鸡称重并记录。那么，鸡的体重记录到哪里就成了一个问题。我们有两种选择：\n\n1. 记录到一个本本上\n2. 想办法用笔写到鸡身上\n\n```javascript\n\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 方法1:记录到本本上\nvar notebook = [];\nchickenList.forEach(function(chickenItem, index){\n\tnotebook[index] = getWeight(chickenItem);\n});\n\n\u002F\u002F 方法2:记录到鸡身上\nchickenList.forEach(function(chickenItem, index){\n\tchickenItem.weight = getWeight(chickenItem);\n});\n```\n\n首先我们看一下第2种方法，记录到鸡身上。这种方法的好处在于我们不需要额外的笔记本（变量）。但同时它也有很明显的缺点：\n\n1. 破坏了鸡的卖相，有时候这是很严重的事情，比如你想把一只5斤的鸡当成6斤卖出去，结果鸡身上直接写“我只有5斤”（修改了原有对象，可能导致意外的行为）\n2. 可能碰到一些战斗鸡，一个字都写不上去（对象冻结了或者有不可覆盖的属性）\n3. 可能写到一些本来就写了字的地方，导致根本看不清（与对象原有属性冲突）\n4. 如果鸡的生产线上有光学设备，有可能破坏生产线的生产，因为这只鸡变成了一只非标品，只能使用人工生产，降低效率（破坏JS引擎的hidden class优化机制）\n\n我们再来看一下第1种方法。这种方法的好处在于完全不用改动原来的对象，但它有比较明显的问题：\n\n1. 需要一个专门的本本来记录结果（额外变量）\n2. 本本无法和鸡精准地一一对应，只能靠一些索引或者标记（例如给每只鸡起一个名字）去（不可靠）地记录对应关系（无法精准地对比到是哪一个对象）\n3. 本本上的结果可以随时被别人修改\n\n针对上述第2个问题，一般我们在日常开发中，都会给对象加上一些标识，例如`id`属性，去与`notebook`记录的数据做一一对应。这也是我们在处理这一类需求的时候，一般不会觉得有困扰的原因。但这只适用于你能控制`chicken`对象结构的情况。如果你并不知道你要处理是一个什么样的对象，那么这种方法就会面临上方提到的第2个问题。\n\n这样的需求用Map是否可以解决呢？答案是肯定的：\n\n```javascript\n\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 记录到另一个本本上\nvar notebook = new Map();\nchickenList.forEach(function(chickenItem, index){\n\tnotebook.set(chickenItem, getWeight(chickenItem));\n});\n```\n\n这里将本本`notebook`换成了一个Map，而Map可以保留对`chicken`的引用，从而解决上面说的使用数组或者对象来记录时无法精准对应的问题。\n\n这里用Map解决了上述第2个问题，但仍然存在两个问题：\n\n1. 需要额外变量`notebook`来存储所有的重量数据\n2. `notebook`中的数据可能随时被别人修改\n\n此时，我们终于可以看看WeakMap在这个问题上是如何表现的了：\n\n```javascript\n\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 记录到WeakMap\nvar notebook = new WeakMap();\nchickenList.forEach(function(chickenItem, index){\n\tnotebook.set(chickenItem, getWeight(chickenItem));\n});\n```\n\n咦？怎么感觉跟Map的例子一样一样的？没错。Map和WeakMap的作用和用法非常相似。但是因为WeakMap弱引用和不可遍历，这里有一些不同的事情：\n\n1. 当你拿到`chicken`引用的时候，获取重量并记录到WeakMap `notbook`中，但是一旦释放`chicken`，则`notebook`中对应的数据也无法再访问，这可以很好地节省内存\n2. 因为`notebook`不可遍历，也就意味着你只有在需要使用`chicken`（有引用）时，才可以访问`notebook`，可以有效防止被外部修改\n\n打个比方，你的本本上，关于这只鸡的记录，只有当鸡来了的时候才存在，而且只有鸡在场的时候才能查到，当鸡走了，在本本上关于鸡的记录是不存在的，也就不存在被别人偷看、修改一说。也就是说，这个本本上关于鸡的记录好像根本就是这只鸡自己带来的一样。这就是上方所说的**在不改变对象本身的情况下扩展对象**的含义。\n\n同样的原理和写法，可以应用到其它的场景中，比如标记对象的状态（用于任务调度、错误处理等），比如为DOM元素添加额外的关联数据等等。\n\n### 应用1：事件系统\n\n在Node中，如果我们要使用事件系统的话，一般会将让自己的Class（构造函数）继承`EventEmitter`。而如果要为任意对象添加事件机制的话，就不那么容易了。有了`WeakMap`，就可以比较容易地处理这件事情了：\n\n```javascript\nvar listeners = new WeakMap();\n\n\u002F\u002F 监听事件\nfunction on(object, event, fn){\n\tvar thisListeners = listeners.get(object);\n\tif(!thisListeners) thisListeners = {};\n\tif(!thisListeners[event]) thisListeners[event] = [];\n\tthisListeners[event].push(fn);\n\tlisteners.set(object, thisListeners);\n}\n\n\u002F\u002F 触发事件\nfunction emit(object, event){\n\tvar thisListeners = listeners.get(object);\n\tif(!thisListeners) thisListeners = {};\n\tif(!thisListeners[event]) thisListeners[event] = [];\n\tthisListeners[event].forEach(function(fn){\n\t\tfn.call(object, event);\n\t});\n}\n\n\u002F\u002F 使用\nvar obj = {};\n\non(obj, 'hello', function(){\n\tconsole.log('hello');\n});\n\nemit(obj, 'hello');\n```\n\n### 应用2：私有变量\n\n```javascript\nfunction Constructor() {\n    var data = new WeakMap();\n\n \t\u002F\u002F 重写构建函数\n    Constructor = function() {\n    \t\u002F\u002F 挂一个私有变量存储\n        data.set(this, {});\n    }\n\n \t\u002F\u002F 方法\n    Constructor.prototype.doSth = function () {\n    \tvar privateVar = data.get(this);\n    \t......\n    };\n\n    return new Constructor();\n};\n```\n\n我们使用`data`来扩展`this`对象，用来存储私有变量，这个私有变量在外部无法被访问，而且随`this`对象的销毁和消失，简直完美。\n\n你可能会说，私有变量不是都已经有现成的方案了吗？\n\n```javascript\nfunction Constructor() {\n    var data = {};\n\n \t\u002F\u002F 方法\n    this.doSth = function () {\n    \tvar privateVar = data;\n    \t......\n    };\n};\n```\n\n没错，这正是经典的私有变量解决方案。但是这个方案有一个问题，即所有访问私有变量的方法（如`doSth()`）都只能挂在实例上，即每个实例中除了私有变量外，还有自己的特权方法。而使用WeakMap实现私有变量的方案中，方法可以挂在原型上。这两者在性能上会有一些差异。\n\n> ES2015虽然有Class，但是仍然不支持私有变量的定义，要使用私有变量的话，实现方法和ES5没有太大差异。\n\n## WeakMap的模拟\n\n如果你有关注过ES2015的特性在ES5中的模拟降级情况的话，应该会发现很多人说过，WeakMap是无法模拟的。事实的确是这样，但是如果我们稍微放宽一些条件，还是有办法模拟出一个可用的WeakMap的。\n\n首先，我们看一个[web components polyfill](https:\u002F\u002Fgithub.com\u002Fwebcomponents\u002Fwebcomponentsjs)中[对WeakMap的模拟](https:\u002F\u002Fgithub.com\u002Fwebcomponents\u002Fwebcomponentsjs\u002Fblob\u002Fmaster\u002Fsrc\u002FWeakMap\u002FWeakMap.js)：\n\n```javascript\nif (typeof WeakMap === 'undefined') {\n  (function() {\n    var defineProperty = Object.defineProperty;\n    var counter = Date.now() % 1e9;\n\n    var WeakMap = function() {\n      \u002F\u002F 记录一个唯一的name属性\n      this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');\n    };\n\n    WeakMap.prototype = {\n      \u002F\u002F 在WeakMap的key对象中添加与WeakMap实例name相同的属性\n      \u002F\u002F 利用这个属性来保存value\n      \u002F\u002F 下面的API原理类似\n      set: function(key, value) {\n        var entry = key[this.name];\n        if (entry && entry[0] === key)\n          entry[1] = value;\n        else\n          defineProperty(key, this.name, {value: [key, value], writable: true});\n        return this;\n      },\n      get: function(key) {\n        var entry;\n        return (entry = key[this.name]) && entry[0] === key ?\n            entry[1] : undefined;\n      },\n      delete: function(key) {\n        var entry = key[this.name];\n        if (!entry || entry[0] !== key) return false;\n        entry[0] = entry[1] = undefined;\n        return true;\n      },\n      has: function(key) {\n        var entry = key[this.name];\n        if (!entry) return false;\n        return entry[0] === key;\n      }\n    };\n\n    window.WeakMap = WeakMap;\n  })();\n}\n```\n\n上面的代码中我们加了一点注释，这个模拟的核心在于，`weakMap.set(key, value)`时，将`value`直接写入`key`这个对象中，作为对象的一个属性。因此它的生命周期与对象本身完全一致，也不会影响对象的垃圾回收，基本达到WeakMap的核心诉求。\n\n而它也有一些不足之处，最大的问题就是会修改对象本身。我们前文说过，WeakMap的核心思想是“在不改变对象本身的情况下扩展对象”，而我们的模拟却刚好违背了这一思想，将值扩展到了对象本身。如上文所说，总有一些对象是不可修改的，在这种情况下就会出现问题。这也是刚刚提到的必须在放宽一些条件的情况下，才可以模拟WeakMap的意思。\n\n基于同样的原理，还有一个模拟WeakMap的库\u003Chttps:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap>。这个库的源码可见这里\u003Chttps:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap\u002Fblob\u002Fmaster\u002Fweakmap.js>，它将数据以及对数据的操作封装到了`Data()`中，然后将它挂到了对象的一个属性`globalId`上。详细的关系大概如下：\n\n```\nobject:{\n\t[globalId]: data{\n\t\t[puid1]: value1\n\t\t[puid2]: value2\n\t}\n}\n```\n\n值得一提的是，`globalId`是全局唯一的，也就是说在一次脚本运行的过程中（不关掉页面，或者Node进程不退出），所有对象上的`globalId`都是相同的。\n\n相比web components的模拟而言，这个库的模拟要严谨细心得多，比如当对象不可写时会抛出错误，比如在产生随机ID时会进行重复判断，确保逻辑正确，比如它只在对象上写入`[globalId]`一个属性，尽量减少对对象的修改。当然这些逻辑也导致了代码可读性直线下降，解读这段代码花了我整整一个晚上的时间，有兴趣的同学可以自己看一下是否能够读懂。\n\n## 结\n\n本文简单总结了下自己在学习WeakMap过程中记录的相关知识点，包括WeakMap的特性、使用场景以及模拟实现。\n\n总体说来，WeakMap是一个有独特特性和使用场景的数据类型，它的出现使我们能更从容地应对一些开发过程中的问题。但话又说回来，在WeakMap出现之前，同样的问题我们也一样能解决，只是可能稍微麻烦一些，或者性能稍微差一些。\n\n另，本文所使用比喻仅为说明问题而设，各位看官明白要表达的意思即可，勿在比喻中钻牛角尖。\n\n参考资料：\n\n- \u003Chttps:\u002F\u002Fsanwen8.cn\u002Fp\u002F1e9XKBe.html>\n- \u003Chttps:\u002F\u002Fwww.sitepen.com\u002Fblog\u002F2015\u002F03\u002F19\u002Flegitimate-memory-efficient-privacy-with-es6-weakmaps\u002F>\n- \u003Chttp:\u002F\u002Fwww.2ality.com\u002F2016\u002F01\u002Fprivate-data-classes.html>\n- \u003Chttps:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap>\n- \u003Chttp:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F29413222\u002Fwhat-are-the-actual-uses-of-es6-weakmap>\n- \u003Chttp:\u002F\u002Ffitzgeraldnick.com\u002F2014\u002F01\u002F13\u002Fhiding-implementation-details-with-e6-weakmaps.html>\n- \u003Chttps:\u002F\u002Filikekillnerds.com\u002F2015\u002F02\u002Fwhat-are-weakmaps-in-es6\u002F>\n- \u003Chttps:\u002F\u002Fsegmentfault.com\u002Fa\u002F1190000002549235>\n- \u003Chttps:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap>\n","\u002Farticle\u002Fusage_of_weakmap.html",{"id":42,"type":7,"slug":43,"title":44,"date":45,"category":11,"tags":46,"body_markdown":48,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},178,"to-a-dark-future","To A Dark Futuer","2017-02-24 09:10",[11,47],"未来","\n开年来真的有点忙，好不容易写起来的公众号又好久没拔草了。\n\n最近忙的其中一件事是写一个slides，也就是传说中的PPT。（明明用的是keynote，你才PPT，你全家都PPT！）写的是web前端的一些普及性的介绍。\n\n在写的过程中，发现越写越悲观，写到最后竟然隐隐觉得web要完。当然，这是一件政治极其不正确的事情，所以也不敢到处乱发，只能当成呓语在公众号写一写，给为数不多的朋友诉一诉这其中的一些想法。\n\n## 标准的危机\n\n曾经有几年，大家是非常热衷于web标准的。但是如果问一问现在来面试的朋友，估计应该没有多少人还在意web标准这件事情。大家会关注 Angular \u002F React \u002F Vue ，会关注 AR \u002F VR ，但是真的没有人关注web标准。\n\n那我们关注一下可好？\n\n唉，算了。反正大概的感觉就是真正需要成为标准的东西一直拖拖拉拉，比如像 web components 至今没有定稿，导致至今没有一个像样的组件方案。最后靠框架来填补这块空白。而像 web bluetooth \u002F web usb \u002F web vr 这类的东西倒是层出不穷。\n\n除了标准本身外，厂商的跟进也是个大问题。一方面厂商对标准越来越不上心，爱理不理，另一方面，又为了各自的利益，先做非标准实现，再回头去影响标准。\n\n总而言之，标准现在就是一锅粥。\n\n\u003C!-- more -->\n\n## 性能的危机\n\n如果说浏览器是最复杂的桌面软件，不知道会不会有人打我。反正至少在常见软件中应该是这样的。就不说什么 video 相当于一个视频播放器， img 相当于一个图片查看器之类的媒体相关的了。就算只是一个简单的 span ，你大概也无法想象它到底有多复杂，光是DOM这一层就够头疼了，要暴露它的内容、位置、样式等各种各样的属性API，就意味着远远不是放一个文本这么简单的事情。DOM的复杂性为 web 性能的提升放了一个非常大的瓶颈。更何况现在的DOM简直是越来越复杂了。虽然浏览器在尽全力优化它的性能，但是复杂度摆在那，真的经不起作啊。\n\n一个性能有巨大瓶颈的平台，注定是应用受限的，因此一旦需要和其它技术同台竞技，web 往往是性能输家。\n\n## 场景的危机\n\n如果说性能的危机还不算啥，那场景的危机真的是灭顶之灾。\n\nweb 随着PC互联网而生，URL 成为它成功的重要因素。可链接、可传播，这是一个巨大的优势。也正是因为 URL 的重要性，才导致了域名生意的火爆，想想 www.jd.com 就可以卖300万，图啥？不就是图用户在输入网址的时候简单一点么？\n\n然而移动互联网改变了一切。你回想一下，从你早上起床刷朋友圈开始，到早上骑个单车，微信支付个早餐，公交车上刷刷微博和新闻，到上班时跟同事讨论些问题，中午点个外卖，下午看看美女图养养眼，到晚上约会个妹子吃吃饭，最后打个车回家，看看电视剧，刷刷朋友圈睡觉。有用到网址么？也许再过两年，该有朋友问什么是网址了吧？\n\n网址的消失让 web 失去了不可替代的优势。当然你可能会说，web 还有快速发版，不用安装等优势啊。诚然，然而当今世界，连iOS上的热更新都成熟到快焦了，快速发版还有什么优势可言？连小程序都来抢饭碗了，还有什么不安装的优势可言？\n\n想下来，如果有人跟我说“web已经没有立足之地了”，可能一开始我会拒绝，但是仔细想想，差得也并不多啊。\n\n也会有人说，web不死，你以为你每天用的app的界面里就没有web么？对，是，有，而且有好多。然而，在这些场景下的web，有什么是不可能被替代的呢？React-Native \u002F Weex 都已经快要宣战了，native代替这一点点web可能也只是一件需要时间的事情了吧。\n\n## 结\n\n嗯，刀刃上的钢并不见更好，反而让刀越来越重。场景却越来越小。这就是越写越悲观的原因。\n\n所以在小密圈有此一问：你觉得5年后的web是什么样子？\n\n希望是我想错了或者想多了。\n",{"id":50,"type":7,"slug":51,"title":52,"date":53,"category":25,"tags":54,"body_markdown":56,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},82,"happy-new-year","新年好！","2017-02-03 16:07",[55],"新年","\n眼睛一闭一睁，一个春节过去了哈？各位吃好玩好睡好了吗？听说今天很多人开工啦，于是我终于知道了原来我初八上班是很晚的，感谢公司，感谢郭嘉，感谢MTV。\n\n新年新气象，希望大家抓紧时间吹吹牛，然后用剩下的一年，哦不对，是90%年，去实现自己吹过的牛。\n\n不过新年新气象倒并不是促使我想发点东西的原因，想发文章最主要的原因还是因为昨天晚上到今天又被Docker折腾到半死。\n\n事情是这样：手上有一个小站，用Docker部署在阿里云上，操作系统选用的是无比拉风的CoreOS，就是传说中那个用Docker构建，内置Docker的系统，这个系统还有一个很NB的功能，就是会自己升级……自动升级……自动……升级。\n\n前天晚上收到告警，网站down了。不过彼时正处在调整堵车9小时后万念俱灰的情绪中，所以没有管它。昨天回深后到晚上才又想起来这事，奇怪的是所有的服务都在跑，但是容器之前的网络都不通了。于是找资料，升级docker composer，升级composer的配置文件版本，折腾Docker新的网络模型……\n\n\u003C!-- more -->\n\n算了，再讲下去估计你也看不下去了，最终的结果是我折腾了一晚上后宣布放弃，早上又折腾两个小时仍然无力回天。最终，通过阿里云的磁盘快照，将整台服务器恢复到了出故障前的版本，收工。\n\n这件事情给了我一些以前从来没有过的感受。以前，总有人在说前端步伐太快，跟不到，而我总会置之一笑，谁让你自己不抓紧学习，还怪技术了？但这次在Docker面前，我感觉自己就像那些非专业的前端工程师一样迷茫。Docker的版本一直在不断快速迭代，一直不停有大招放出，从registry升级到网络模型，再到集成swarm、composer版本升级等等。每一个版本都很先进，每一个版本都是大招，只是对于我这个只在Docker 1.5时代接触过一点的人来说，突然间感觉自己对这个生态一无所知。\n\n最后1，通过磁盘备份来解决问题，并不英雄。但也许在公司项目碰到同样问题的时候，这应该是首先的解决方案。难说是变成熟了还是变懒了。\n\n最后2，备份多么重要，Gitlab事件历历在目，自己经历的事情更刻骨铭心。\n",{"id":58,"type":7,"slug":59,"title":60,"date":53,"category":25,"tags":61,"body_markdown":63,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},85,"when-should-enter-a-startup-company","【问答】什么阶段考虑进入创业公司",[62],"创业","\n## 问\n\n什么阶段考虑进入创业公司？\n\n## 答\n\n其实我并没有认真的思考过这个问题。职业发展是一个涉及到很多方面的问题，包括职业方向上的规划、薪资水平、工作环境、团队氛围等很多方面。\n\n因为标的公司是创业公司，一般来说，从薪资、工作环境上不会有明显优势，因此个人觉得主要考虑点在于个人成长和团队氛围上。\n\n\u003C!-- more -->\n\n个人成长，即自己在技术或者非技术领域的成长空间还有多大，如果感觉自己已经在每天做同样的工作，没有任何进步了，那么可以考虑一下创业公司。一般来说换一个环境可以让你接触到不一样的工作内容，而且创业公司在分工没有那么细的情况下，更考验一个人的综合水平。如果有机会去一个好的创业公司，对技术人素质提升是大有好处的。\n\n团队氛围，即团队的氛围是否积极向上，是否有产品氛围。如果一个团队死气沉沉，或者因为分工的原因导致大家只关注手头上的事情，那有可能创业团队是一个更好的选择。一般而言，在创业团队干劲会更足一些，团队氛围也容易带动。此外，作为技术人，在创业团队可以更多关注到技术以外的东西，比如流程、项目管理、产品规划等，如果有机会进入一个产品氛围不错的团队，对技术人的视野也是有极大好处的。\n\n最后，创业团队坑也多，活多钱少，慎重。\n",{"id":65,"type":7,"slug":66,"title":67,"date":68,"category":69,"tags":70,"body_markdown":72,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},120,"what-about-mini-program","【问答】小程序上线后市场反应如何","2017-01-19 09:04","tech",[71],"小程序","\n来自知乎问题[小程序上线后市场反应如何](https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F54884655\u002Fanswer\u002F141701284)\n\n尘归尘，土归土。正在回归正常。\n\n小程序一直在强调自己线下场景的定位，奈何开发者、媒体总是不信邪，非要赌小程序会有流量红利，会给自己带来线上流量。甚至在微信明确说微信中没有入口，没有应用商店之后，仍然有一大波人围着不愿散去。\n\n现在打脸了吧。线上流量红利真的一点没给。于是这波围观的人一哄而散，甚至还会留下一堆唱衰的评价，“你看，我就说这玩意没鸟用吧”，殊不知，正是自己的期望过高导致现在的巨大落差。\n\n回归到小程序自己的定位来说，其实非常明确，就是线下场景。具体的微信公开课Pro上已经详细阐述过，这里就不复制了，有兴趣的自己找。\n\n\u003C!-- more -->\n\n而线下场景的铺开，是需要一个漫长的过程的，所以现在看起来很冷。这其实应该是微信预料中的事情，甚至可以说是微信刻意安排的，微信不希望小程序变成纯线上产品，因此只能顶过第一波舆论洪峰，挡掉大家对线上流量的期待，接下来踏实地推动线下场景，最终让小程序变成“一个生活方式”。\n\n\n我个人比较看好这件事情，拭目以待。\n\n## 评论的回应\n\n### 小程序竞争不过APP\n\n别老站在开发者的立场看问题嘛。站在用户的立场来看，当你在某个店里想要看看店里的服务，你愿意去下载一个app吗？还是选择用微信扫描一下就能立马获得高质量的服务产品？开发者是跟随产品走的，产品是跟随用户走的，用户在哪动力就在哪。\n\n### 小程序和web相比没有本质区别\n\n网页慢，加载慢，运行也慢，另外能力也比较受限。张小龙亲口说，小程序比web的优势就是体验好很多很多。\n\n我就是做web前端开发的……你自己试试写写小程序，再写写web，体验真的不一样。开发起来小程序恶心死，但是用起来小程序真的秒杀web。当然，不排除有很少量的做得确实很好的web，但是这个量太少太少了，而且往往最后还是由网速来决定到底好不好用。小程序通过框架直接拉高了最低标准，写得再烂的小程序用起来也不会烂到哪去，而且加载全部是从微信的服务器（甚至有可能是私有协议不是HTTP，猜测），比较好地保证了体验的平均水准。\n\n具体为什么快可以有很多分析，比如从微信的CDN是不是比你自己的服务器加载快？打包的资源是不是比一个个css\u002Fjs加载快？私有协议是不是比HTTP快？native的canvas是不是比web的快？jscore是不是比webview的js引擎快？数据驱动ui更新是不是比手动更新DOM快？因为实现方式上有巨大的差距，这样去猜到底是哪里快了意义不大。就算如你所说，只是预加载让它显得快，那用起来比web畅快也是事实，用户并不关心你是怎么快起来的。\n\n对不起，我的立场是站在用户这边的，并没有要证明小程序比web先进的意见，你可能误会我了。我也是web开发者，我当然知道web能做的事情很多，我当然知道有顶尖的开发者和顶尖的作品，但是从我扫过的后在微信中打开的页面来看，基本上都是乱七八糟。\n\n### 线下不需要小程序\n\n问：我想知道店里的服务直接问服务员不就好了么?\n\n答：线下不等于商店。ktv 演唱会 桌游 客运站 候机室 自助店 饭店 麦当劳……都是线下。\n\n问：在火车站本来随处可见最新时刻表，点餐加菜直接叫服务员比用手机方便，叫DJ房间有按钮，演唱会门票加印曲目信息比额外开发小程序成本低，投票互动这个属线上场景，不是线下场景。\n\n如果只是线下场景才能用，小程序定位太尴尬了，和公众号功能重合，小程序可以做的，公众号都可以做到，要小商家再次投入成本重新开发个和公众号功能差不多的东西，谁会去做。\n\n张小龙如果实现让公众号不用修改直接转换为小程序，或者简化小程序开发，或许会有人用。\n\n答：是的。火车站随处可见时刻表，但还是有人用手机查对吗？还是有人用12306买票对吗？叫服务员是方便，但还是有人喜欢用手机慢慢点菜是吗？叫DJ是方便，但总得点歌是吗？演唱会门票印东西是方便，但黑得啥都看不见的时候还是有人喜欢在手机上看节目单是吗？\n\n并不是线下手段已经用不了了，小程序来拯救世界的。微信支付普及前去商店不是也可以付现刷卡吗？\n\n大规模的社会变革从来不是断崖式的，而是渐进式的，一开始都会有无数人怀疑，现在不是挺好的吗，你们干嘛要瞎折腾。\n\n问：对于火车站，已经有独立全功能的12306app，再开发一个小程序对火车站有什么好处，对于用户，我手机直接打开12306，除了查时刻还能订票改签，用功能阉割微信小程序的意义在哪里？\n\n我没有否定手机应用在逐渐改变生活的趋势，只是觉得小程序在现阶段只是个失败的尝试。\n\n现阶段小程序的所有线下应用场景，公众号都能实现，商家在已经有公众号的情况下不会额外投资转用小程序。\n\n现在小程序都开放一个月了，到底有几个用户在用小程序了，原来是怎么用手机还是怎么用，线下没有入口，线上全面被app碾压，所以张小龙才被迫开放线上模糊查询。\n\n如果腾讯真的有魄力利用小程序在安卓版微信做一个应用市场，放开应用程序大小的限制，在国内管住流氓行为，那才是革命性的改变，微信甚至可能影响iOS的份额，成为系统级的存在，可惜腾讯现在还在打擦边球，所以半死不活。\n\n问：我觉得你有必要去看下微信公开课pro的场景分析，不想再一遍一遍重复了。另外也不想讨论，已经有xxx，所以xxx是否必要的问题了。另外你也不要臆断有几个用户在用，前几天了解到百万级用户量小程序已经不是一个两个了。还有其他问题吗？\n",{"id":74,"type":7,"slug":75,"title":76,"date":77,"category":69,"tags":78,"body_markdown":79,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},121,"why-mini-program-use-self-developed-tech-stack","【问答】为什么小程序自己做了一套开发体系","2017-01-13 13:37",[71],"\n来自知乎问题[微信小程序为什么不用HTML5、CSS，自己搞了个WXML、WXSS，很多框架用不了，好处一点不知道？](https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F51809406\u002Fanswer\u002F140747129)\n\n## 引子\n\n假设我们把问题稍微改一下哈：\n\n> iOS应用为什么不用HTML5、CSS，自己搞了个OC \u002F swift \u002F autolayout \u002F storyboard，很多框架用不了，好处一点不知道，以前项目根本没法移植，而且我们习惯的jquery、auicss、图标等完全用不了，也没见OC \u002F swift \u002F autolayout \u002F storyboard有什么好处，完全理解不了。。。\n\n嗯。我只是改一下题目，不回答。可自行思考。\n\n## 用web做小程序的可能性和限制\n\n为什么没有人问上面改过的问题，而小程序就有人问呢？只是因为小程序和web长得很像，所以就觉得要用web来做吗？那长得像的话是不是就能直接用web做呢？\n\n答案是，大部分是可以的。比如文本、图片、输入框等等，都可以。\n\n但是，也有一部分小程序的功能是web完全不具备的，例如扫码、获取设备信息、获取罗盘信息、等等。\n\n此外，还有一小部分是web做起来很困难的。比如上面有人提到的地图、fixed的文本输入、视频相关、sticky定位等。\n\n除了能力上的限制以外，还有相当一部分是来自于性能上的限制，也即虽然很多东西用web确实可以做，但是性能是很差的。\n\n\u003C!-- more -->\n\n## 打补丁可以吗？\n\n如上面提到的一些能力缺失（以及半缺失），如果使用web的话，是不是打补丁也可以解决呢？答案是肯定的。\n\n但转念一想，如果要靠打补丁的方式去做，那为什么还要选择使用web呢？如果你深入使用过微信公众号的JS SDK就会发现，其实大部分你需要认真用JS SDK的时候（只做分享信息设置的不算），这个页面就已经没法在其它环境中复用了。也就是说，不管怎样，你已经是为微信专门写了一套代码。\n\n## 抛弃标准\n\n（这是一段政治不正确的文字）\n\n对微信来说，如果使用web来做小程序，就意味着要照顾庞大的web标准体系。虽然对大部分的前端工程师来说，使用到的web能力并不是太多，但对浏览器来说，web标准是一套非常繁杂非常闹心的东西，要支持一套完整的web标准体系并不是一件容易的事情。\n\n当然，你可以说腾讯不是有X5了吗？那好，我们抛开实现上的复杂不说，只说支持web标准和小程序的关系。\n\n如果要在web标准的基础上来做，那么打补丁这件事情会变得不愉快。\n\n例如fixed的输入框这件事情，假设客户端可以自行改变容器的高度和定位，在focus的时候做一些hack处理，那么大部分情况下体验是不错的。但是因为web标准在这里，你就不能随意更改一个元素的定位和尺寸。\n\n再比如视频，X5中为了用(shang)户(ye)体(li)验(yi)，重写了视频元素的行为，默认情况下全屏播放，且非全屏的情况下也只能是页面最高层元素，无法被别的元素覆盖。对于看视频、看电视剧、看电影的人来说，这本来是一个不错的用户体验，但是这一棒子却将用视频做页面效果、做直播（边看边聊）的人打死了，导致X5的这一行为至今被骂到死。\n\n这样的例子非常多，如果你既要完整照顾web标准，同时还要在用户体验、性能上做优化，还要在此基础上打补丁，将是一件几乎不可能完成的事情。即使能费九牛二虎之力做得七七八八，也可能随时面临用户的投诉：“为什么这个行为和浏览器不一样？”\n\n而反观小程序的现状，在完全不管web标准之后，想不支持CSS级联就可以不支持，想改canvas API就能改，想增加wx.showToast()就能直接加。\n\n甚至在加载方面也完全不用考虑web的事，一股脑扔给微信，像app一样整体下载就可以了。\n\n这对产品和开发团队来说完全是甩开膀子随便干的节奏。因此对小程序的产品和开发团队来说，放弃使用web来做是一件性价比非常高的事情。\n\n## 重塑开发规则\n\n因为没有了web标准的束缚，小程序团队可以从头制定开发规则，从而在源头上对质量进行一定的把控。\n\n例如小程序可以强硬地要求，fixed的textarea必须添加一个特殊的class，这样小程序就能自己去解决这一场景下的技术难题，而不用绕很远的路去做各种兼容。\n\n例如小程序可以要求textarea不可以出现在scroll-view中，从而避免一些技术难题。\n\n此外，小程序还可以从框架上强制地要求必须不能动态操作页面元素。至于理由是什么，可能和运营规则有关。\n\n总而言之，因为有了一个全新的开发体系，微信相当于直接伴演了上帝的角色，以前web开发中碰到的任何问题都可以有N多方法马上解决。\n\n## 重塑运营规则\n\n上面都是作为技术人员的一些废话。小程序之所有不选用web，个人认为最重要的原因是要重塑运营规则。\n\n众所周知，web以开放互联著称，这意味着任何人可以在web上发布任何程序，并且每一个web都可以和其他web应用互相跳转。此外，web页面的内容是可以随时通过后端或者前端进行控制从而动态显示的。当然，web因为这么多年的发展，标准庞大，能力众多，实在是不能胜举。\n\n这些能力固然是非常棒的，也是web开发人员引以为豪的地方。但是对小程序来说，却未必是一件好事。\n\n看看当前公众号的现状即可知道：刷流量、跳广告、伪造各种页面、发布违规页面等行为屡禁不止。这正是微信最不爽的地方。\n\n因此，小程序的目标很明确，就是重塑一个微信规则下的web。这里的web只能提供服务，不能营销，不能引流，不能动态改内容逃避打击，不能跳转，不能违规。所以的事情只能按白名单能力（小程序开发文档）来做，白名单没有的，想都别想。而且即使白名单中有东西被坏人利用了，还有一道人工审核来进行把关。不按规则来的，对不起，全部打死。\n\n对营销狗（网上取的词，无贬义）来说，小程序毫无用处，因为它几乎将营销的口子全部堵死了。而对用户来说，这才真正是该有的体验。\n\n所以，个人以为，这才是小程序要重建一套开发体系最重大的意义。\n",{"id":81,"type":7,"slug":82,"title":83,"date":84,"category":11,"tags":85,"body_markdown":88,"permalink":89,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},168,"array_unique_in_javascript","也谈JavaScript数组去重","2017-01-05 09:00:00",[34,86,87],"数组","去重","\nJavaScript的数组去重是一个老生常谈的话题了。随便搜一搜就能找到非常多不同版本的解法。\n\n昨天在微博上看到一篇文章，也写数组去重，主要推崇的方法是将利用数组元素当作对象key来去重。我在微博转发了“用对象key去重不是个好办法…”然后作者问什么才是推荐的方法。\n\n细想一下，这样一个看似简单的需求，如果要做到完备，涉及的知识和需要注意的地方着实不少，于是诞生此文。\n\n## 定义重复（相等）\n\n要去重，首先得定义，什么叫作“重复”，即具体到代码而言，两个数据在什么情况下可以算是相等的。这并不是一个很容易的问题。\n\n对于原始值而言，我们很容易想到`1`和`1`是相等的，`'1'`和`'1'`也是相等的。那么，`1`和`'1'`是相等的么？\n\n如果这个问题还好说，只要回答“是”或者“不是”即可。那么下面这些情况就没那么容易了。\n\n\u003C!-- more -->\n\n### NaN\n\n初看`NaN`时，很容易把它当成和`null`、`undefined`一样的独立数据类型。但其实，它是数字类型。\n\n```javascript\n\u002F\u002F number\nconsole.log(typeof NaN);\n```\n\n根据规范，比较运算中只要有一个值为NaN，则比较结果为`false`，所以会有下面这些看起来略蛋疼的结论：\n\n```javascript\n\u002F\u002F 全都是false\n0 \u003C NaN;\n0 > NaN;\n0 == NaN;\n0 === NaN;\n```\n\n以最后一个表达式`0 === NaN`为例，在规范中有明确规定（\u003Chttp:\u002F\u002Fwww.ecma-international.org\u002Fecma-262\u002F6.0\u002F#sec-strict-equality-comparison>）：\n\n> 4. If Type(x) is Number, then\n>   a. If x is NaN, return false.\n>   b. If y is NaN, return false.\n>   c. If x is the same Number value as y, return true.\n>   d. If x is +0 and y is −0, return true.\n>   e. If x is −0 and y is +0, return true.\n>   f. Return false.\n\n这意味着任何涉及到`NaN`的情况都不能简单地使用比较运算来判定是否相等。比较科学的方法只能是使用`isNaN()`：\n\n```javascript\nvar a = NaN;\nvar b = NaN;\n\n\u002F\u002F true\nconsole.log(isNaN(a) && isNaN(b));\n```\n\n### 原始值和包装对象\n\n看完`NaN`是不是头都大了。好了，我们来轻松一下，看一看原始值和包装对象这一对冤家。\n\n如果你研究过`'a'.trim()`这样的代码的话，不知道是否产生过这样的疑问：`'a'`明明是一个原始值（字符串），它为什么可以直接调用`.trim()`方法呢？当然，很可能你已经知道答案：因为JS在执行这样的代码的时候会对原始值做一次包装，让`'a'`变成一个字符串对象，然后执行这个对象的方法，执行完之后再把这个包装对象脱掉。可以用下面的代码来理解：\n\n```javascript\n\u002F\u002F 'a'.trim();\nvar tmp = new String('a');\ntmp.trim();\n```\n\n这段代码只是辅助我们理解的。但包装对象这个概念在JS中却是真实存在的。\n\n```javascript\nvar a = new String('a');\nvar b = 'b';\n```\n\n`a`即是一个包装对象，它和`b`一样，代表一个字符串。它们都可以使用字符串的各种方法（比如`trim()`），也可以参与字符串运算（`+`号连接等）。\n\n但他们有一个关键的区别：类型不同！\n\n```javascript\ntypeof a; \u002F\u002F object\ntypeof b; \u002F\u002F string\n```\n\n在做字符串比较的时候，类型的不同会导致结果有一些出乎意料：\n\n```javascript\nvar a1 = 'a';\nvar a2 = new String('a');\nvar a3 = new String('a');\n\na1 == a2; \u002F\u002F true\na1 == a3; \u002F\u002F true\na2 == a3; \u002F\u002F false\na1 === a2; \u002F\u002F false\na1 === a3; \u002F\u002F false\na2 === a3; \u002F\u002F false\n```\n\n同样是表示字符串`a`的变量，在使用严格比较时竟然不是相等的，在直觉上这是一件比较难接受的事情，在各种开发场景下，也非常容易忽略这些细节。\n\n### 对象和对象\n\n在涉及比较的时候，还会碰到对象。具体而言，大致可以分为三种情况：纯对象、实例对象、其它类型的对象。\n\n**纯对象**\n\n> 纯对象（plain object）具体指什么并不是非常明确，为减少不必要的争议，下文中使用纯对象指代由字面量生成的、成员中不含函数和日期、正则表达式等类型的对象。\n\n如果直接拿两个对象进行比较，不管是`==`还是`===`，毫无疑问都是不相等的。但是在实际使用时，这样的规则是否一定满足我们的需求？举个例子，我们的应用中有两个配置项：\n\n```javascript\n\u002F\u002F 原来有两个属性\n\u002F\u002F var prop1 = 1;\n\u002F\u002F var prop2 = 2;\n\n\u002F\u002F 重构代码时两个属性被放到同一个对象中\n\nvar config = {\n    prop1: 1,\n    prop2: 2\n};\n```\n\n假设在某些场景下，我们需要比较两次运行的配置项是否相同。在重构前，我们分别比较两次运行的`prop1`和`prop2`即可。而在重构后，我们可能需要比较`config`对象所代表的配置项是否一致。在这样的场景下，直接用`==`或者`===`来比较对象，得到的并不是我们期望的结果。\n\n在这样的场景下，我们可能需要自定义一些方法来处理对象的比较。常见的可能是通过`JSON.stringify()`对对象进行序列化之后再比较字符串，当然这个过程并非完全可靠，只是一个思路。\n\n> 如果你觉得这个场景是无中生有的话，可以再回想一下断言库，同样是基于对象成员，判断结果是否和预期相符。\n\n**实例对象**\n\n实例对象主要指通过构造函数（类）生成的对象。这样的对象和纯对象一样，直接比较都是不等的，但也会碰到需要判断是否是同一对象的情况。一般而言，因为这种对象有比较复杂的内部结构（甚至有一部分数据在原型上），无法直接从外部比较是否相等。比较靠谱的判断方法是由构造函数（类）来提供静态方法或者实例方法来判断是否相等。\n\n```javascript\nvar a = Klass();\nvar b = Klass();\n\nKlass.isEqual(a, b);\n```\n\n**其它对象**\n\n其它对象主要指数组、日期、正则表达式等这类在`Object`基础上派生出来的对象。这类对象各有各的特殊性，一般需要根据场景来构造判断方法，决定两个对象是否相等。\n\n比如，日期对象，可能需要通过`Date.prototype.getTime()`方法获取时间戳来判断是否表示同一时刻。正则表达式可能需要通过`toString()`方法获取到原始字面量来判断是否是相同的正则表达式。\n\n### ==和===\n\n在一些文章中，看到某一些数组去重的方法，在判断元素是否相等时，使用的是`==`比较运算符。众所周知，这个运算符在比较前会先查看元素类型，当类型不一致时会做隐式类型转换。这其实是一种非常不严谨的做法。因为无法区分在做隐匿类型转换后值一样的元素，例如`0`、`''`、`false`、`null`、`undefined`等。\n\n同时，还有可能出现一些只能黑人问号的结果，例如：\n\n```javascript\n[] == ![]; \u002F\u002Ftrue\n```\n\n### Array.prototype.indexOf()\n\n在一些版本的去重中，用到了`Array.prototype.indexOf()`方法：\n\n```javascript\nfunction unique(arr) {\n    return arr.filter(function(item, index){\n        \u002F\u002F indexOf返回第一个索引值，\n        \u002F\u002F 如果当前索引不是第一个索引，说明是重复值\n        return arr.indexOf(item) === index;\n    });\n}\n```\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    arr.forEach(function(item){\n        if(ret.indexOf(item) === -1){\n            ret.push(item);\n        }\n    });\n    return ret;\n}\n```\n\n既然`==`和`===`在元素相等的比较中是有巨大差别的，那么`indexOf`的情况又如何呢？大部分的文章都没有提及这点，于是只好求助规范。通过规范（\u003Chttp:\u002F\u002Fwww.ecma-international.org\u002Fecma-262\u002F6.0\u002F#sec-array.prototype.indexof>），我们知道了`indexOf()`使用的是严格比较，也就是`===`。\n\n> 再次强调：按照前文所述，`===`不能处理`NaN`的相等性判断。\n\n### Array.prototype.includes()\n\n`Array.prototype.includes()`是ES2016中新增的方法，用于判断数组中是否包含某个元素，所以上面使用`indexOf()`方法的第二个版本可以改写成如下版本：\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    arr.forEach(function(item){\n        if(!ret.includes(item)){\n            ret.push(item);\n        }\n    });\n    return ret;\n}\n```\n\n那么，你猜猜，`includes()`又是用什么方法来比较的呢？如果想当然的话，会觉得肯定跟`indexOf()`一样喽。但是，程序员的世界里最怕想当然。翻一翻规范，发现它其实是使用的另一种比较方法，叫作“SameValueZero”比较（\u003Chttps:\u002F\u002Ftc39.github.io\u002Fecma262\u002F2016\u002F#sec-samevaluezero>）。\n\n> 1. If Type(x) is different from Type(y), return false.\n> 2. If Type(x) is Number, then\n>   a. If x is NaN and y is NaN, return true.\n>   b. If x is +0 and y is -0, return true.\n>   c. If x is -0 and y is +0, return true.\n>   d. If x is the same Number value as y, return true.\n>   e. Return false.\n> 3. Return SameValueNonNumber(x, y).\n\n注意`2.a`，如果`x`和`y`都是`NaN`，则返回`true`！也就是`includes()`是可以正确判断是否包含了`NaN`的。我们写一段代码验证一下：\n\n```javascript\nvar arr = [1, 2, NaN];\narr.indexOf(NaN); \u002F\u002F -1\narr.includes(NaN); \u002F\u002F true\n```\n\n可以看到`indexOf()`和`includes()`对待`NaN`的行为是完全不一样的。\n\n## 一些方案\n\n从上面的一大段文字中，我们可以看到，要判断两个元素是否相等（重复）并不是一件简单的事情。在了解了这个背景后，我们来看一些前面没有涉及到的去重方案。\n\n### 遍历\n\n双重遍历是最容易想到的去重方案：\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    var len = arr.length;\n    var isRepeat;\n    for(var i=0; i\u003Clen; i++) {\n        isRepeat = false;\n        for(var j=i+1; j\u003Clen; j++) {\n            if(arr[i] === arr[j]){\n                isRepeat = true;\n                break;\n            }\n        }\n        if(!isRepeat){\n            ret.push(arr[i]);\n        }\n    }\n    return ret;\n}\n```\n\n双重遍历还有一个优化版本，但是原理和复杂度几乎完全一样：\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    var len = arr.length;\n    for(var i=0; i\u003Clen; i++){\n        for(var j=i+1; j\u003Clen; j++){\n            if(arr[i] === arr[j]){\n                j = ++i;\n            }\n        }\n        ret.push(arr[i]);\n    }\n    return ret;\n}\n```\n\n这种方案没什么大问题，用于去重的比较部分也是自己编写实现（`arr[i] === arr[j]`），所以相等性可以自己针对上文说到的各种情况加以特殊处理。唯一比较受诟病的是使用了双重循环，时间复杂度比较高，性能一般。\n\n### 使用对象key来去重\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    var len = arr.length;\n    var tmp = {};\n    for(var i=0; i\u003Clen; i++){\n        if(!tmp[arr[i]]){\n            tmp[arr[i]] = 1;\n            ret.push(arr[i]);\n        }\n    }\n    return ret;\n}\n```\n\n这种方法是利用了对象（`tmp`）的key不可以重复的特性来进行去重。但由于对象key只能为字符串，因此这种去重方法有许多局限性：\n\n1. 无法区分隐式类型转换成字符串后一样的值，比如`1`和`'1'`\n2. 无法处理复杂数据类型，比如对象（因为对象作为key会变成`[object Object]`）\n3. 特殊数据，比如`'__proto__'`会挂掉，因为`tmp`对象的`__proto__`属性无法被重写\n\n对于第一点，有人提出可以为对象的key增加一个类型，或者将类型放到对象的value中来解决：\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    var len = arr.length;\n    var tmp = {};\n    var tmpKey;\n    for(var i=0; i\u003Clen; i++){\n        tmpKey = typeof arr[i] + arr[i];\n        if(!tmp[tmpKey]){\n            tmp[tmpKey] = 1;\n            ret.push(arr[i]);\n        }\n    }\n    return ret;\n}\n```\n\n该方案也同时解决第三个问题。\n\n而第二个问题，如果像上文所说，在允许对对象进行自定义的比较规则，也可以将对象序列化之后作为key来使用。这里为简单起见，使用`JSON.stringify()`进行序列化。\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    var len = arr.length;\n    var tmp = {};\n    var tmpKey;\n    for(var i=0; i\u003Clen; i++){\n        tmpKey = typeof arr[i] + JSON.stringify(arr[i]);\n        if(!tmp[tmpKey]){\n            tmp[tmpKey] = 1;\n            ret.push(arr[i]);\n        }\n    }\n    return ret;\n}\n```\n\n### Map Key\n\n可以看到，使用对象key来处理数组去重的问题，其实是一件比较麻烦的事情，处理不好很容易导致结果不正确。而这些问题的根本原因就是因为key在使用时有限制。\n\n那么，能不能有一种key使用没有限制的对象呢？答案是——真的有！那就是ES2015中的`Map`。\n\n> `Map`是一种新的数据类型，可以把它想象成key类型没有限制的对象。此外，它的存取使用单独的`get()`、`set()`接口。\n\n```javascript\nvar tmp = new Map();\ntmp.set(1, 1);\ntmp.get(1); \u002F\u002F 1\n\ntmp.set('2', 2);\ntmp.get('2'); \u002F\u002F 2\n\ntmp.set(true, 3);\ntmp.get(true); \u002F\u002F 3\n\ntmp.set(undefined, 4);\ntmp.get(undefined); \u002F\u002F 4\n\ntmp.set(NaN, 5);\ntmp.get(NaN); \u002F\u002F 5\n\nvar arr = [], obj = {};\n\ntmp.set(arr, 6);\ntmp.get(arr); \u002F\u002F 6\n\ntmp.set(obj, 7);\ntmp.get(obj); \u002F\u002F 7\n```\n\n由于Map使用单独的接口来存取数据，所以不用担心key会和内置属性重名（如上文提到的`__proto__`）。使用`Map`改写一下我们的去重方法：\n\n```javascript\nfunction unique(arr) {\n    var ret = [];\n    var len = arr.length;\n    var tmp = new Map();\n    for(var i=0; i\u003Clen; i++){\n        if(!tmp.get(arr[i])){\n            tmp.set(arr[i], 1);\n            ret.push(arr[i]);\n        }\n    }\n    return ret;\n}\n```\n\n### Set\n\n既然都用到了ES2015，数组这件事情不能再简单一点么？当然可以。\n\n除了`Map`以外，ES2015还引入了一种叫作`Set`的数据类型。顾名思义，`Set`就是集合的意思，它不允许重复元素出现，这一点和数学中对集合的定义还是比较像的。\n\n```javascript\nvar s = new Set();\ns.add(1);\ns.add('1');\ns.add(null);\ns.add(undefined);\ns.add(NaN);\ns.add(true);\ns.add([]);\ns.add({});\n```\n\n如果你重复添加同一个元素的话，`Set`中只会存在一个。包括`NaN`也是这样。于是我们想到，这么好的特性，要是能和数组互相转换，不就可以去重了吗？\n\n```javascript\nfunction unique(arr){\n    var set = new Set(arr);\n    return Array.from(set);\n}\n```\n\n我们讨论了这么久的事情，居然两行代码搞定了，简直不可思议。\n\n然而，不要只顾着高兴了。有一句话是这么说的“不要因为走得太远而忘了为什么出发”。我们为什么要为数组去重呢？因为我们想得到不重复的元素列表。而既然已经有`Set`了，我们为什么还要舍近求远，使用数组呢？是不是在需要去重的情况下，直接使用`Set`就解决问题了？这个问题值得思考。\n\n## 小结\n\n最后，用一个测试用例总结一下文中出现的各种去重方法：\n\n```javascript\nvar arr = [1,1,'1','1',0,0,'0','0',undefined,undefined,null,null,NaN,NaN,{},{},[],[],\u002Fa\u002F,\u002Fa\u002F]\nconsole.log(unique(arr));\n```\n\n> 测试中没有定义对象的比较方法，因此默认情况下，对象不去重是正确的结果，去重是不正确的结果。\n\n|方法      |结果                                              |说明                                     |\n|----------|--------------------------------------------------|-----------------------------------------|\n|indexOf#1 |NaN被去掉                                         |                                         |\n|indexOf#2 |NaN重复                                           |                                         |\n|includes  |正确                                              |                                         |\n|双重循环#1|NaN重复                                           |                                         |\n|双重循环#2|NaN重复                                           |                                         |\n|对象#1    |字符串和数字无法区分，对象、数组、正则表达式被去重|                                         |\n|对象#2    |对象、数组、正则表达式被去重                      |                                         |\n|对象#3    |对象、数组被去重，正则表达式被消失                |JSON.stringify(\u002Fa\u002F)结果为{}，和空对象一样|\n|Map       |正确                                              |　                                       |\n|Set       |正确                                              |　                                       |\n\n最后的最后：任何脱离场景谈技术都是妄谈，本文也一样。去重这道题，没有正确答案，请根据场景选择合适的去重方法。\n","\u002Farticle\u002Farray_unique_in_javascript.html",{"id":91,"type":7,"slug":92,"title":93,"date":94,"category":11,"tags":95,"body_markdown":97,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},166,"oauth-in-mini-program","小程序接入OAuth","2016-12-29 09:17",[71,96],"OAuth","\n> 注：该方案实现后并未提审，所以只讨论技术可行性，不讨论是否合规是否过审的问题。\n\n这个问题按不同情况和不同解决方案大致有五条路可走：\n\n## 1 密码模式的OAuth\n\n简单来说，就是用户提供用户名和密码。可能有人会说：靠，这叫毛线的OAuth？对，我也是这么想的，但是以API规范著称的Github对非web应用就是给出的这样的方案：\n\n[GitHub Developer Guide](https:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fdeveloper.github.com\u002Fv3\u002Foauth\u002F%23non-web-application-flow)\n\n> \"Use Basic Authentication to create an OAuth2 token using the interface below. With this technique, a username and password need not be stored permanently, and the user can revoke access at any time. (Make sure to understand how to work with two-factor authentication if you or your users have two-factor authentication enabled.)\"\n\n翻译一下：\n\n> （非web流程）“使用HTTP Basic Auth（译注：即用户名和密码）来创建OAuth2 token，流程见XXX。使用这种方案时，不应该永久保存用户名和密码，用户要可以随时撤销授权。（如果你或者你的用户开户了两步验证，请确保你了解如何正确处理两步验证的情况。）”\n\n但，题主所说的微博、豆瓣是否支持这种模式，存疑。另外这种方式对用户来说非常不友好，毕竟要将密码交到第三方手中，如果是我来用，我是不会输的。\n\n## 2 直接使用密码登录\n\n靠？这又TMD叫什么OAuth？没错，这不是OAuth。但是对大部分使用OAuth的网站来说，都会让用户补充一下自己的用户名和密码。简单来说就是用户除了可以选择OAuth之外，还可以使用自己的用户名和密码登录。\n\n如果网站是一个很有节操的网站，并没有自己的用户名和密码，完全依赖第三方怎么办？\n\n简单啊，让用户补充一个不就好了？！\n\n## 3 授权码\n\n回想第二种方式为什么可行呢？当然你可以说这都不涉及OAuth了，当然可行了。\n\n我们也可以换个思路，上面第二种方法可行，是因为我们将OAuth的授权与网站本身进行了绑定，换句话说，当你输入用户名，我就能找到你是谁，你对应的OAuth信息是什么。\n\n那么，同样的思路，我们可以使用一个“授权码”（名字随意取，你高兴的话叫它“红包口令”都行），这个授权码和用户信息是绑定的，然后引导用户在小程序上输入授权码即可完成。\n\n从用户体验的角度来说，这个授权码不有太长，因为需要用户手工输入。\n\n从安全性来说，有一定隐患，所以一定要加上时效限制。\n\n## 4 授权码加强版-扫码\n\n在最近更新的一个版本中，小程序终于加上了扫码的能力。所以可以将上一种方案中的授权码做成一个二维码，让用户在小程序中扫描即可。\n\n## 5 unionId机制\n\n微信小程序支持微信开放平台已有的unionId机制。简单说把小程序当成一个平台，然后去[微信开放平台](https:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fopen.weixin.qq.com\u002F)申请一个第三方网站登录。这样用户就有两套登录，一套在网站中，一套在小程序中。这两套机制中对同一个用户来说，unionId是相同的，因此可以直接将小程序中用户身份与网站身份对应起来。\n\n不过微信开放平台申请需要做一些认证，比较麻烦。\n\n## 小结\n\n严格来说，只有第一种是满足要求的，即完全依赖第三方登录。\n\n第二种依赖网站自有账号体系。后面三种都需要依赖微信的账号体系，然后将这个账号信息与已有用户进行绑定。\n\n另外这五种方案都需要后台配合，因为安全的OAuth流程中后台是不可以缺位的。\n",{"id":99,"type":7,"slug":100,"title":101,"date":102,"category":103,"tags":104,"body_markdown":107,"permalink":15,"excerpt_src":15,"media_type":108,"media_title":109,"media_author":110,"media_url":111,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":112},16,"a-crack-in-humanity","人性的一道裂缝","2016-11-08 21:11","bmm",[105,106],"观后感","电影","\n![张一曼](\u002Fassets\u002Flife\u002F2016\u002Fa-crack-in-humanity\u002F01.jpg)\n\n这个人，叫张一曼。看完电影《驴得水》，脑海中挥之不去的便是这一张面孔。\n\n她直爽、勇敢、独立，敢爱敢恨、敢做敢当、热爱生活。对看客而言，这些似乎都是很容易就能挂在嘴边的词，然而真正将人放到生活的大染缸中，还能够做到这些的，实在是凤毛麟角。而张一曼生活的环境，比我们的生活环境，大约要艰苦一千零一倍。更难的是，每天围绕在她身边的还有一群各怀鬼胎的人。在那样一种环境中，还能保持这样一种生活状态，实在难能可贵。\n\n自然，电影中也有看起来敢爱敢恨的人，然而这些人都只不过是纸做的，看起来是那么回事，实际上风一吹就倒了。仿佛他们的生活都只是表演一般，浮在水上，只有张一曼的生活是活到骨子里的。\n\n\u003C!-- more -->\n\n除了这些品质之外，可能最让人印象深刻的则是她的生活哲学了，可能归纳起来也无非是简单、快乐而已。她说，以前一直有人管她，所以跑到大山里来，不希望再有人管她。她说，她不是外表放荡而已，她就是这样的人。也许这些并非真话，然而却确确实实成为了指导她日常生活的哲学。\n\n她有对人的爱吗？恐怕是有的。被表白时的拒绝，我更愿意看作是一种为了对方好而下意识地后退。无奈之下出口伤人后的道歉，看得人心碎。而学校有钱添置基础设施后，和校长一起高兴得手舞足蹈，如小孩子一般。\n\n原以为，这是一个最好的人，会有最好的结局，然而，人都是有自己的过不去的坎，这些坎可能就是人性中逃不过去的一道裂缝。初看不甚要紧，甚至可以轻松掩盖起来，然而当压力真正到来时，这一道裂缝却能撕裂整个人。\n\n对张一曼来说，这道裂缝便是早期一些痛苦的经历。影片中并未为我们撕开这道口子，只是隐隐掀起了一角，她就已经痛不欲生了。原以为她已经过了这一道坎，能够再一次笑对生活了，然而最后发现自己永远无法过去。不管是笑言自己放荡，还是无比认真地告诉自己无牵无挂活在当下，都无法掩盖那一道深深的裂缝。\n\n一声枪响，将这道裂缝生生撕开。心痛。\n","movie","驴得水","周申","https:\u002F\u002Fmovie.douban.com\u002Fsubject\u002F25921812\u002F","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Flife\u002F2016\u002Fa-crack-in-humanity\u002F01.jpg",{"id":114,"type":7,"slug":115,"title":116,"date":117,"category":11,"tags":118,"body_markdown":121,"permalink":122,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":123},165,"capture_video_on_web","如何使用web录制视频","2016-09-25 19:58",[119,120],"Video","WebRTC","\n最近在某个需求的评审会上，产品同学脑洞大开，提出了**使用web录制视频**的想法。并兴致勃勃地说“看，XXX网站可以调用摄像头，还能聊天呢！”本着负（Zhuang）责（Bi）的原则，我们也对该方案做了认真的预研。大致结论：\n\n1. 非实时录制时（文件上传框），兼容性相对较好，且API和性能稳定\n2. 实时录制视频在Chrome for Android中可行，其它机型和浏览器均不可使用。考虑到相关标准仍处于不稳定状态，不建议在产品中使用\n3. 微信有非公开接口可以调用实时视频录制（微证券使用）\n\n详细方案如下：\n\n\u003C!-- more -->\n\n## 方案一：使用文件上传框\n\n文件上传框`\u003Cinput type=\"file\">`是前端同学非常熟悉的一个HTML控件，它的主要作用是用来上传文件。而在移动端，这个文件上传框被赋予了更多的使命，除了可以选择文件上传之外，还可以调用摄像头来拍摄照片或者视频并上传。\n\n具体的使用方式：\n\n```html\n\u003Cinput type=\"file\" accept=\"video\u002F*\"\u002F>\n```\n\n或者\n\n```html\n\u003Cinput type=\"file\" accept=\"video\u002Fmp4,video\u002Fx-m4v,video\u002F*\"\u002F>\n```\n\n这两种写法的区别在于对不同机型来说兼容性可能略有区别，但是具体的情况未做一一测试总结。经过初步测试，该方案可以在以下环境中运行：\n\n- Android 4.4+\n- iOS 6.0+\n- 微信webview\n- Chrome for Android\n\n> 注：该兼容性列表是我们简单测试一部分机器后的结论，不做任何保证。事实上我们也碰到一部分Android机器是例外。下文兼容性列表同理。\n\n该方案API简单易用，且功能由浏览器或webview原生实现，性能比较稳定。\n\n\n该方案缺点：非实时录制视频，无法确定用户是录制的还是选取的已有的视频文件。\n\n相关demo \u003Chttp:\u002F\u002Fcodepen.io\u002FTooBug\u002Fembed\u002FRRZQxr\u002F>\n\n## 方案二：视频录制\n\n要使用web录制视频，需要两个相关API，一个用于调用摄像头，一个用于录制。调用摄像头后会产生一个视频流，然后调用录制API将这个视频流压缩和保存。\n\n其中调用摄像头的API叫作`getUserMedia()`，以前属于`navigator`对象（Chrome 21-49），后来规范修改，现在属于`MediaDevices`（Chrome 49+）。该API还负责提示用户授权。\n\n视频流叫作`MediaStream`。拿到`MediaStream`后，可配合`ObjectURL`，产生一个虚拟URL，供浏览器`video`标签调用，实现视频回放（回显）。\n\n用于录制视频的API叫作`MediaRecorder`。该API在Chrome 49+可用。\n\n该方案兼容性：\n\n- Chrome 49+\n- Firefox 29+\n- Chrome for Android\n\n相关Demo地址：\u003Chttps:\u002F\u002Fsimpl.info\u002Fmediarecorder\u002F>\n\n## 方案三：WebRTC视频流远程录制\n\nWebRTC是指实现web实时通信的一系列规范，一般可以通俗地理解为“P2P视频聊天”。实现这个功能依赖于上方说的摄像头调用API `getUserMedia()`取到`MediaStream`，同时还依赖一个P2P网络连接和传输的API来实现视频流数据的传输，这个API叫作`RTCPeerConnection`。\n\n在实际运作时，需要服务额外处理两个浏览器在P2P通信之前的Session建立相关的逻辑：\n\n![WebRTC实际原理图](\u002Fassets\u002Fcapture_video_on_web\u002F1.png)\n\n同时，还需要服务端支持来完成浏览器在NAT等复杂网络环境中的通信“打洞”需求：\n\n![WebRTC实际原理图](\u002Fassets\u002Fcapture_video_on_web\u002F2.png)\n\n使用该方案录制视频的原理是通过服务端模拟一个浏览器（Peer），实现相关视频流接收解码协议以及`RTCPeerConnection`协议。\n\nSession管理的服务端和打洞的服务端实现和维护比较麻烦，但有例可循，而模拟Peer的部分则实现过于复杂，因此，虽然该方案理论上可行，且浏览器兼容性稍好，但仍然认为该方案在实际操作中不可行。\n\n这个方案的兼容性\n\n- Firefox 17+\n- Chrome 21+\n- Edge 12+\n- Chrome for Android\n\n## 方案四：截图上传\n\n该方案原理：在使用`getUserMedia()`获取视频流之后，将该视频流定时投映到一张2d画布中（`canvas`），然后将画布中的画面提取成图片数据（`base64`）。\n\n该方案原理比较简单，兼容性\n\n- Firefox 17+\n- Chrome 21+\n- Edge 12+\n- Chrome for Android\n\n但同时也有明显缺陷：\n\n- 无法获取声音数据，只能获取到图片数据\n- 需要上传后由后台转换成视频\n- 帧数多时图片可能较大，造成性能问题（比如崩溃）\n- 帧数多时图片可能较大，造成网络传输慢\n\n## 相关文档\n\n- [MDN上的navigator.getUserMedia文档](https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FAPI\u002FNavigator\u002FgetUserMedia)\n- [MDN上的MediaDevices.getUserMedia文档](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FMediaDevices\u002FgetUserMedia)\n- [MDN上的MediaStream文档](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FMediaStream)\n- [MDN上的MediaRecorder文档](https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FAPI\u002FMediaRecorder)\n- [W3C Media Capture and Streams规范（发布候选状态）](https:\u002F\u002Fwww.w3.org\u002FTR\u002Fmediacapture-streams\u002F)\n- [W3C MediaRecording规范（Working Draft草稿状态）](https:\u002F\u002Fw3c.github.io\u002Fmediacapture-record\u002FMediaRecorder.html)\n- [W3C webrtc规范（Working Draft草稿状态）](http:\u002F\u002Fw3c.github.io\u002Fwebrtc-pc\u002F)\n- [webrtc官方网站](https:\u002F\u002Fwebrtc.github.io)\n- [教程：webrtc入门](https:\u002F\u002Fcodelabs.developers.google.com\u002Fcodelabs\u002Fwebrtc-web\u002F)\n- [文章：真实世界中的webrtc](http:\u002F\u002Fwww.html5rocks.com\u002Fen\u002Ftutorials\u002Fwebrtc\u002Finfrastructure\u002F)\n","\u002Farticle\u002Fcapture_video_on_web.html","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fcapture_video_on_web\u002F1.png",190]