[
  {
    "path": "README.md",
    "content": "blog\n====\n\nmy personal blog\n\n# 目录\n\n## Angular系列\n\n#### - [AngularJS实例教程（一）——数据绑定与监控](https://github.com/xufei/blog/issues/14)\n#### - [AngularJS实例教程（二）——作用域与事件](https://github.com/xufei/blog/issues/18)\n\n#### - [Angular沉思录（一）数据绑定](https://github.com/xufei/blog/issues/10)\n#### - [Angular沉思录（二）视图模型的层次](https://github.com/xufei/blog/issues/11)\n#### - [Angular沉思录（三）Angular中的模块机制](https://github.com/xufei/blog/issues/17)\n\n#### - [Angular的变革](https://github.com/xufei/blog/issues/25)\n#### - [浴火重生的Angular](https://github.com/xufei/blog/issues/9)\n#### - [后Angular时代二三事](https://github.com/xufei/blog/issues/21)\n#### - [优化Angular应用的性能](https://github.com/xufei/blog/issues/23)\n\n#### - [［翻译］有关Angular 2.0的一切](https://github.com/xufei/blog/issues/8)\n#### - [［翻译］Angular的问题](https://github.com/xufei/blog/issues/15)\n\n## Web应用\n  \n#### - [构建单页Web应用](https://github.com/xufei/blog/issues/5)\n#### - [Web应用组件化的权衡](https://github.com/xufei/blog/issues/22)\n#### - [Web应用的组件化（一）——基本思路](https://github.com/xufei/blog/issues/6)\n#### - [Web应用的组件化（二）——管控平台](https://github.com/xufei/blog/issues/7)\n#### - [从HTML Components的衰落看Web Components的危机](https://github.com/xufei/blog/issues/3)\n\n## 随笔系列\n  \n#### - [老码农的技术理想](https://github.com/xufei/blog/issues/16)\n#### - [未来Web应用的前端技术选型畅想](https://github.com/xufei/blog/issues/24)\n#### - [2015前端组件化框架之路](https://github.com/xufei/blog/issues/19) \n#### - [今年搞的一些活动的意图](https://github.com/xufei/blog/issues/13)\n#### - [企业文化与价值观 — 给新员工的一封信](https://github.com/xufei/blog/issues/12)\n#### - [给一位打算从事前端，但是又有疑惑的在校大学生的回信](https://github.com/xufei/blog/issues/4)\n  \n  \n"
  },
  {
    "path": "gitpress.json",
    "content": "{\n        \"docs\"      : {\"all\": \"posts\"},\n        \"perpage\"   : 5,\n        \"types\"     : {\n                \"\\\\.(md||markdown)$\"   : \"markdown\", \n                \"\\\\.(js||css||json)$\"  : \"code\",\n                \"\\\\.html?$\"            : \"html\",\n                \".*\"                   : \"text\"                \n        },\n        \"title\"  : \"民工的砖墙\",\n        \"comment\"     : {\"type\": \"duoshuo\", \"short_name\": \"xufei\"},\n        \"friends\"  : [\n                {\n                        \"name\" : \"月影MM\",\n                        \"title\": \"十年踪迹的博客\",\n                        \"url\"  :  \"http://blog.silverna.org/\"\n                },\n                {\n                        \"name\" : \"JerryQu的小站\",\n                        \"title\": \"屈屈的博客\",\n                        \"url\"  : \"http://imququ.com\"\n                },\n                {\n                        \"name\" : \"dh20156\",\n                        \"title\": \"dh20156踪迹\",\n                        \"url\"  :  \"http://www.w3cgroup.com/\"\n                },\n                {\n                        \"name\"  : \"doyoe\",\n                        \"title\"  : \"CSS探索之旅\",\n                        \"url\"  : \"http://blog.doyoe.com/\"                  \n                }\n        ] \n}\n"
  },
  {
    "path": "posts/2013-01-14-前端开发技术的发展.md",
    "content": "前端开发技术的发展\n==\n\n前端开发技术，从狭义的定义来看，是指围绕HTML、JavaScript、CSS这样一套体系的开发技术，它的运行宿主是浏览器。从广义的定义来看，包括了：\n\n- 专门为手持终端设计的类似WML这样的类HTML语言，类似WMLScript这样的类JavaScript语言。\n- VML和SVG等基于XML的描述图形的语言。\n- 从属于XML体系的XML，XPath，DTD等技术。\n- 用于支撑后端的ASP，JSP，ASP.net，PHP，nodejs等语言或者技术。\n- 被第三方程序打包的一种类似浏览器的宿主环境，比如Adobe AIR和使用HyBird方式的一些开发技术，如PhoneGap（它使用Android中的WebView等技术，让开发人员使用传统Web开发技术来开发本地应用）\n- Adobe Flash，Flex，Microsoft Silverlight，Java Applet，JavaFx等RIA开发技术。\n\t\n本文从狭义的前端定义出发，探讨一下这方面开发技术的发展过程。\n<!--more-->\n从前端开发技术的发展来看，大致可以分为以下几个阶段：\n\n#一. 刀耕火种\n##1. 静态页面\n\n最早期的Web界面基本都是在互联网上使用，人们浏览某些内容，填写几个表单，并且提交。当时的界面以浏览为主，基本都是HTML代码，有时候穿插一些JavaScript，作为客户端校验这样的基础功能。代码的组织比较简单，而且CSS的运用也是比较少的。\n\t\n最简单的是这样一个文件：\n\n\t<html>\n\t\t<head>\n\t\t\t<title>测试一</title>\n\t\t</head>\n\t\t<body>\n\t\t\t<h1>主标题</h1>\n\t\t\t<p>段落内容</p>\n\t\t</body>\n\t</html>\n\n##2. 带有简单逻辑的界面\n这个界面带有一段JavaScript代码，用于拼接两个输入框中的字符串，并且弹出窗口显示。\n\n\t<html>\n\t\t<head>\n\t\t\t<title>测试二</title>\n\t\t</head>\n\t\t<body>\n\t\t\t<input id=\"firstNameInput\" type=\"text\" /> \n\t\t\t<input id=\"lastNameInput\" type=\"text\" /> \n\t\t\t<input type=\"button\" onclick=\"greet()\" />\n\t\t\t<script language=\"JavaScript\">\n\t\t\tfunction greet() {\n\t\t\t\tvar firstName = document.getElementById(\"firstNameInput\").value;\n\t\t\t\tvar lastName = document.getElementById(\"lastNameInput\").value;\n\t\t\t\talert(\"Hello, \" + firstName + \".\" + lastName);\n\t\t\t}\n\t\t\t</script> \n\t\t</body>\n\t</html>\n\n##3. 结合了服务端技术的混合编程\n\n由于静态界面不能实现保存数据等功能，出现了很多服务端技术，早期的有CGI（Common Gateway Interface，多数用C语言或者Perl实现的），ASP（使用VBScript或者JScript），JSP（使用Java），PHP等等，Python和Ruby等语言也常被用于这类用途。\n\n有了这类技术，在HTML中就可以使用表单的post功能提交数据了，比如：\n\n\t<form method=\"post\" action=\"username.asp\">\n\t\t<p>First Name: <input type=\"text\" name=\"firstName\" /></p>\n\t\t<p>Last Name: <input type=\"text\" name=\"lastName\" /></p>\n\t\t<input type=\"submit\" value=\"Submit\" />\n\t</form>\n\n在这个阶段，由于客户端和服务端的职责未作明确的划分，比如生成一个字符串，可以由前端的JavaScript做，也可以由服务端语言做，所以通常在一个界面里，会有两种语言混杂在一起，用<%和%>标记的部分会在服务端执行，输出结果，甚至经常有把数据库连接的代码跟页面代码混杂在一起的情况，给维护带来较大的不便。\n\n\t<html>\n\t\t<body>\n\t\t\t<p>Hello world!</p>\n\t\t\t<p>\n\t\t\t<%\n\t\t\t\tresponse.write(\"Hello world from server!\")\n\t\t\t%>\n\t\t\t</p>\n\t\t</body>\n\t</html>\n\n##4.组件化的萌芽\n\n这个时代，也逐渐出现了组件化的萌芽。比较常见的有服务端的组件化，比如把某一类服务端功能单独做成片段，然后其他需要的地方来include进来，典型的有：ASP里面数据库连接的地方，把数据源连接的部分写成conn.asp，然后其他每个需要操作数据库的asp文件包含它。\n\n上面所说的是在服务端做的，浏览器端通常有针对JavaScript的，把某一类的Javascript代码写到单独的js文件中，界面根据需要，引用不同的js文件。针对界面的组件方式，通常利用frameset和iframe这两个标签。某一大块有独立功能的界面写到一个html文件，然后在主界面里面把它当作一个frame来载入，一般的B/S系统集成菜单的方式都是这样的。\n\n此外，还出现了一些基于特定浏览器的客户端组件技术，比如IE浏览器的HTC（HTML Component）。这种技术最初是为了对已有的常用元素附加行为的，后来有些场合也用它来实现控件。微软ASP.net的一些版本里，使用这种技术提供了树形列表，日历，选项卡等功能。HTC的优点是允许用户自行扩展HTML标签，可以在自己的命名空间里定义元素，然后，使用HTML，JavaScript和CSS来实现它的布局、行为和观感。这种技术因为是微软的私有技术，所以逐渐变得不那么流行。\n\nFirefox浏览器里面推出过一种叫XUL的技术，也没有流行起来。\n\n#二. 铁器时代\n\n这个时代的典型特征是Ajax的出现。\n\n##1. AJAX\nAJAX其实是一系列已有技术的组合，早在这个名词出现之前，这些技术的使用就已经比较广泛了，GMail因为恰当地应用了这些技术，获得了很好的用户体验。\n\n由于Ajax的出现，规模更大，效果更好的Web程序逐渐出现，在这些程序中，JavaScript代码的数量迅速增加。出于代码组织的需要，“JavaScript框架”这个概念逐步形成，当时的主流是prototype和mootools，这两者各有千秋，提供了各自方式的面向对象组织思路。\n\n##2. JavaScript基础库\n\nPrototype框架主要是为JavaScript代码提供了一种组织方式，对一些原生的JavaScript类型提供了一些扩展，比如数组、字符串，又额外提供了一些实用的数据结构，如：枚举，Hash等，除此之外，还对dom操作，事件，表单和Ajax做了一些封装。\n\nMootools框架的思路跟Prototype很接近，它对JavaScript类型扩展的方式别具一格，所以在这类框架中，经常被称作“最优雅的”对象扩展体系。\n\n从这两个框架的所提供的功能来看，它们的定位是核心库，在使用的时候一般需要配合一些外围的库来完成。\n\njQuery与这两者有所不同，它着眼于简化DOM相关的代码。\n例如：\n\n- DOM的选择\n\njQuery提供了一系列选择器用于选取界面元素，在其他一些框架中也有类似功能，但是一般没有它的简洁、强大。\n\n\t$(\"*\")\t\t\t\t//选取所有元素\n\t$(\"#lastname\")\t\t//选取id为lastname的元素\n\t$(\".intro\")\t\t\t//选取所有class=\"intro\"的元素\n\t$(\"p\")\t\t\t\t//选取所有&lt;p&gt;元素\n\t$(\".intro.demo\")\t//选取所有 class=\"intro\"且class=\"demo\"的元素\n\n- 链式表达式：\n\n在jQuery中，可以使用链式表达式来连续操作dom，比如下面这个例子：\n\n如果不使用链式表达式，可能我们需要这么写：\n\n\tvar neat = $(\"p.neat\");\n\tneat.addClass(\"ohmy\");\n\tneat.show(\"slow\");\n\n但是有了链式表达式，我们只需要这么一行代码就可以完成这些：\n\n\t$(\"p.neat\").addClass(\"ohmy\").show(\"slow\");\n\n除此之外，jQuery还提供了一些动画方面的特效代码，也有大量的外围库，比如jQuery UI这样的控件库，jQuery mobile这样的移动开发库等等。\n\n##3. 模块代码加载方式\n\n以上这些框架提供了代码的组织能力，但是未能提供代码的动态加载能力。动态加载JavaScript为什么重要呢？因为随着Ajax的普及，jQuery等辅助库的出现，Web上可以做很复杂的功能，因此，单页面应用程序（SPA，Single Page Application）也逐渐多了起来。\n\n单个的界面想要做很多功能，需要写的代码是会比较多的，但是，并非所有的功能都需要在界面加载的时候就全部引入，如果能够在需要的时候才加载那些代码，就把加载的压力分担了，在这个背景下，出现了一些用于动态加载JavaScript的框架，也出现了一些定义这类可被动态加载代码的规范。\n\n在这些框架里，知名度比较高的是RequireJS，它遵循一种称为AMD（Asynchronous Module Definition）的规范。\n\n比如下面这段，定义了一个动态的匿名模块，它依赖math模块\n\n\tdefine([\"math\"], function(math) {\n\t\treturn {\n\t\t\taddTen : function(x) {\n\t\t\t\treturn math.add(x, 10);\n\t\t\t}\n\t\t};\n\t}); \n\n假设上面的代码存放于adder.js中，当需要使用这个模块的时候，通过如下代码来引入adder：\n\n\t<script src=\"require.js\"></script>\n\t<script>\n\t\trequire([\"adder\"], function(adder) {\n\t\t\t//使用这个adder\n\t\t}); \n\t</script>\n\nRequireJS除了提供异步加载方式，也可以使用同步方式加载模块代码。AMD规范除了使用在前端浏览器环境中，也可以运行于nodejs等服务端环境，nodejs的模块就是基于这套规范定义的。（修订，这里弄错了，nodejs是基于类似的CMD规范的）\n\n#三. 工业革命\n\n这个时期，随着Web端功能的日益复杂，人们开始考虑这样一些问题：\n\n- 如何更好地模块化开发\n- 业务数据如何组织\n- 界面和业务数据之间通过何种方式进行交互\n\n在这种背景下，出现了一些前端MVC、MVP、MVVM框架，我们把这些框架统称为MV*框架。这些框架的出现，都是为了解决上面这些问题，具体的实现思路各有不同，主流的有Backbone，AngularJS，Ember，Spine等等，本文主要选用Backbone和AngularJS来讲述以下场景。\n\n##1. 数据模型\n\n在这些框架里，定义数据模型的方式与以往有些差异，主要在于数据的get和set更加有意义了，比如说，可以把某个实体的get和set绑定到RESTful的服务上，这样，对某个实体的读写可以更新到数据库中。另外一个特点是，它们一般都提供一个事件，用于监控数据的变化，这个机制使得数据绑定成为可能。\n\n在一些框架中，数据模型需要在原生的JavaScript类型上做一层封装，比如Backbone的方式是这样：\n\n\tvar Todo = Backbone.Model.extend({\n\t\t// Default attributes for the todo item.\n\t\tdefaults : function() {\n\t\t\treturn {\n\t\t\t\ttitle : \"empty todo...\",\n\t\t\t\torder : Todos.nextOrder(),\n\t\t\t\tdone : false\n\t\t\t};\n\t\t},\n\n\t\t// Ensure that each todo created has `title`.\n\t\tinitialize : function() {\n\t\t\tif (!this.get(\"title\")) {\n\t\t\t\tthis.set({\n\t\t\t\t\t\"title\" : this.defaults().title\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\t// Toggle the 'done' state of this todo item.\n\t\ttoggle : function() {\n\t\t\tthis.save({\n\t\t\t\tdone : !this.get(\"done\")\n\t\t\t});\n\t\t}\n\t});\n\n上述例子中，defaults方法用于提供模型的默认值，initialize方法用于做一些初始化工作，这两个都是约定的方法，toggle是自定义的，用于保存todo的选中状态。\n\n除了对象，Backbone也支持集合类型，集合类型在定义的时候要通过model属性指定其中的元素类型。\n\n\t// The collection of todos is backed by *localStorage* instead of a remote server.\n\tvar TodoList = Backbone.Collection.extend({\n\t\t// Reference to this collection's model.\n\t\tmodel : Todo,\n\n\t\t// Save all of the todo items under the '\"todos-backbone\"' namespace.\n\t\tlocalStorage : new Backbone.LocalStorage(\"todos-backbone\"),\n\n\t\t// Filter down the list of all todo items that are finished.\n\t\tdone : function() {\n\t\t\treturn this.filter(function(todo) {\n\t\t\t\treturn todo.get('done');\n\t\t\t});\n\t\t},\n\n\t\t// Filter down the list to only todo items that are still not finished.\n\t\tremaining : function() {\n\t\t\treturn this.without.apply(this, this.done());\n\t\t},\n\n\t\t// We keep the Todos in sequential order, despite being saved by unordered \n\t\t//GUID in the database. This generates the next order number for new items.\n\t\tnextOrder : function() {\n\t\t\tif (!this.length)\n\t\t\t\treturn 1;\n\t\t\treturn this.last().get('order') + 1;\n\t\t},\n\n\t\t// Todos are sorted by their original insertion order.\n\t\tcomparator : function(todo) {\n\t\t\treturn todo.get('order');\n\t\t}\n\t});\n\n\n数据模型也可以包含一些方法，比如自身的校验，或者跟后端的通讯、数据的存取等等，在上面两个例子中，也都有体现。\n\nAngularJS的模型定义方式与Backbone不同，可以不需要经过一层封装，直接使用原生的JavaScript简单数据、对象、数组，相对来说比较简便。\n\n##2. 控制器\n\n在Backbone中，是没有独立的控制器的，它的一些控制的职责都放在了视图里，所以其实这是一种MVP（Model View Presentation）模式，而AngularJS有很清晰的控制器层。\n\n还是以这个todo为例，在AngularJS中，会有一些约定的注入，比如$scope，它是控制器、模型和视图之间的桥梁。在控制器定义的时候，将$scope作为参数，然后，就可以在控制器里面为它添加模型的支持。\n\n\tfunction TodoCtrl($scope) {\n\t\t$scope.todos = [{\n\t\t\ttext : 'learn angular',\n\t\t\tdone : true\n\t\t}, {\n\t\t\ttext : 'build an angular app',\n\t\t\tdone : false\n\t\t}];\n\n\t\t$scope.addTodo = function() {\n\t\t\t$scope.todos.push({\n\t\t\t\ttext : $scope.todoText,\n\t\t\t\tdone : false\n\t\t\t});\n\t\t\t$scope.todoText = '';\n\t\t};\n\n\t\t$scope.remaining = function() {\n\t\t\tvar count = 0;\n\t\t\tangular.forEach($scope.todos, function(todo) {\n\t\t\t\tcount += todo.done ? 0 : 1;\n\t\t\t});\n\t\t\treturn count;\n\t\t};\n\n\t\t$scope.archive = function() {\n\t\t\tvar oldTodos = $scope.todos;\n\t\t\t$scope.todos = [];\n\t\t\tangular.forEach(oldTodos, function(todo) {\n\t\t\t\tif (!todo.done)\n\t\t\t\t\t$scope.todos.push(todo);\n\t\t\t});\n\t\t};\n\t}\n\n本例中为$scope添加了todos这个数组，addTodo，remaining和archive三个方法，然后，可以在视图中对他们进行绑定。\n\n##3. 视图\n在这些主流的MV*框架中，一般都提供了定义视图的功能。在Backbone中，是这样定义视图的：\n\n\t// The DOM element for a todo item...\n\tvar TodoView = Backbone.View.extend({\n\t\t//... is a list tag.\n\t\ttagName : \"li\",\n\n\t\t// Cache the template function for a single item.\n\t\ttemplate : _.template($('#item-template').html()),\n\n\t\t// The DOM events specific to an item.\n\t\tevents : {\n\t\t\t\"click .toggle\" : \"toggleDone\",\n\t\t\t\"dblclick .view\" : \"edit\",\n\t\t\t\"click a.destroy\" : \"clear\",\n\t\t\t\"keypress .edit\" : \"updateOnEnter\",\n\t\t\t\"blur .edit\" : \"close\"\n\t\t},\n\n\t\t// The TodoView listens for changes to its model, re-rendering. Since there's\n\t\t// a one-to-one correspondence between a **Todo** and a **TodoView** in this\n\t\t// app, we set a direct reference on the model for convenience.\n\t\tinitialize : function() {\n\t\t\tthis.listenTo(this.model, 'change', this.render);\n\t\t\tthis.listenTo(this.model, 'destroy', this.remove);\n\t\t},\n\n\t\t// Re-render the titles of the todo item.\n\t\trender : function() {\n\t\t\tthis.$el.html(this.template(this.model.toJSON()));\n\t\t\tthis.$el.toggleClass('done', this.model.get('done'));\n\t\t\tthis.input = this.$('.edit');\n\t\t\treturn this;\n\t\t},\n\n\t\t//......\n\n\t\t// Remove the item, destroy the model.\n\t\tclear : function() {\n\t\t\tthis.model.destroy();\n\t\t}\n\t});\n\n上面这个例子是一个典型的“部件”视图，它对于界面上的已有元素没有依赖。也有那么一些视图，需要依赖于界面上的已有元素，比如下面这个，它通过el属性，指定了HTML中id为todoapp的元素，并且还在initialize方法中引用了另外一些元素，通常，需要直接放置到界面的顶层试图会采用这种方式，而“部件”视图一般由主视图来创建、布局。\n\n\t// Our overall **AppView** is the top-level piece of UI.\n\tvar AppView = Backbone.View.extend({\n\t\t// Instead of generating a new element, bind to the existing skeleton of\n\t\t// the App already present in the HTML.\n\t\tel : $(\"#todoapp\"),\n\n\t\t// Our template for the line of statistics at the bottom of the app.\n\t\tstatsTemplate : _.template($('#stats-template').html()),\n\n\t\t// Delegated events for creating new items, and clearing completed ones.\n\t\tevents : {\n\t\t\t\"keypress #new-todo\" : \"createOnEnter\",\n\t\t\t\"click #clear-completed\" : \"clearCompleted\",\n\t\t\t\"click #toggle-all\" : \"toggleAllComplete\"\n\t\t},\n\n\t\t// At initialization we bind to the relevant events on the `Todos`\n\t\t// collection, when items are added or changed. Kick things off by\n\t\t// loading any preexisting todos that might be saved in *localStorage*.\n\t\tinitialize : function() {\n\t\t\tthis.input = this.$(\"#new-todo\");\n\t\t\tthis.allCheckbox = this.$(\"#toggle-all\")[0];\n\n\t\t\tthis.listenTo(Todos, 'add', this.addOne);\n\t\t\tthis.listenTo(Todos, 'reset', this.addAll);\n\t\t\tthis.listenTo(Todos, 'all', this.render);\n\n\t\t\tthis.footer = this.$('footer');\n\t\t\tthis.main = $('#main');\n\n\t\t\tTodos.fetch();\n\t\t},\n\n\t\t// Re-rendering the App just means refreshing the statistics -- the rest\n\t\t// of the app doesn't change.\n\t\trender : function() {\n\t\t\tvar done = Todos.done().length;\n\t\t\tvar remaining = Todos.remaining().length;\n\n\t\t\tif (Todos.length) {\n\t\t\t\tthis.main.show();\n\t\t\t\tthis.footer.show();\n\t\t\t\tthis.footer.html(this.statsTemplate({\n\t\t\t\t\tdone : done,\n\t\t\t\t\tremaining : remaining\n\t\t\t\t}));\n\t\t\t} else {\n\t\t\t\tthis.main.hide();\n\t\t\t\tthis.footer.hide();\n\t\t\t}\n\n\t\t\tthis.allCheckbox.checked = !remaining;\n\t\t},\n\n\t\t//......\n\t});\n\n对于AngularJS来说，基本不需要有额外的视图定义，它采用的是直接定义在HTML上的方式，比如：\n\n\t<div ng-controller=\"TodoCtrl\">\n\t\t<span>{{remaining()}} of {{todos.length}} remaining</span>\n\t\t<a href=\"\" ng-click=\"archive()\">archive</a>\n\t\t<ul class=\"unstyled\">\n\t\t\t<li ng-repeat=\"todo in todos\">\n\t\t\t\t<input type=\"checkbox\" ng-model=\"todo.done\">\n\t\t\t\t<span class=\"done-{{todo.done}}\">{{todo.text}}</span>\n\t\t\t</li>\n\t\t</ul>\n\t\t<form ng-submit=\"addTodo()\">\n\t\t\t<input type=\"text\" ng-model=\"todoText\"  size=\"30\"\n\t\t\tplaceholder=\"add new todo here\">\n\t\t\t<input class=\"btn-primary\" type=\"submit\" value=\"add\">\n\t\t</form>\n\t</div>\n\n在这个例子中，使用ng-controller注入了一个TodoCtrl的实例，然后，在TodoCtrl的$scope中附加的那些变量和方法都可以直接访问了。注意到其中的ng-repeat部分，它遍历了todos数组，然后使用其中的单个todo对象创建了一些HTML元素，把相应的值填到里面。这种做法和ng-model一样，都创造了双向绑定，即：\n\n- 改变模型可以随时反映到界面上\n- 在界面上做的操作（输入，选择等等）可以实时反映到模型里。\n\n而且，这种绑定都会自动忽略其中可能因为空数据而引起的异常情况。\n\n##4. 模板\n\n模板是这个时期一种很典型的解决方案。我们常常有这样的场景：在一个界面上重复展示类似的DOM片段，例如微博。以传统的开发方式，也可以轻松实现出来，比如：\n\n\tvar feedsDiv = $(\"#feedsDiv\");\n\n\tfor (var i = 0; i < 5; i++) {\n\t\tvar feedDiv = $(\"<div class='post'></div>\");\n\t\n\t\tvar authorDiv = $(\"<div class='author'></div>\");\n\t\tvar authorLink = $(\"<a></a>\")\n\t\t\t.attr(\"href\", \"/user.html?user='\" + \"Test\" + \"'\")\n\t\t\t.html(\"@\" + \"Test\")\n\t\t\t.appendTo(authorDiv);\n\t\tauthorDiv.appendTo(feedDiv);\n\t\n\t\tvar contentDiv = $(\"<div></div>\")\n\t\t\t.html(\"Hello, world!\")\n\t\t\t.appendTo(feedDiv);\n\t\tvar dateDiv = $(\"<div></div>\")\n\t\t\t.html(\"发布日期：\" + new Date().toString())\n\t\t\t.appendTo(feedDiv);\n\t\n\t\tfeedDiv.appendTo(feedsDiv);\n\t}\n\n但是使用模板技术，这一切可以更加优雅，以常用的模板框架UnderScore为例，实现这段功能的代码为：\n\n\tvar templateStr = '<div class=\"post\">'\n\t\t+'<div class=\"author\">'\n\t\t+\t'<a href=\"/user.html?user={{creatorName}}\">@{{creatorName}}</a>'\n\t\t+'</div>'\n\t\t+'<div>{{content}}</div>'\n\t\t+'<div>{{postedDate}}</div>'\n\t\t+'</div>';\n\tvar template = _.template(templateStr);\n\ttemplate({\n\t\tcreateName : \"Xufei\",\n\t\tcontent: \"Hello, world\",\n\t\tpostedDate: new Date().toString()\n\t});\n\n也可以这么定义：\n\n\t<script type=\"text/template\" id=\"feedTemplate\">\n\t<% _.each(feeds, function (item) { %>\n\t\t<div class=\"post\">\n\t\t\t<div class=\"author\">\n\t\t\t\t<a href=\"/user.html?user=<%= item.creatorName %>\">@<%= item.creatorName %></a>\n\t\t\t</div>\n\t\t\t<div><%= item.content %></div>\n\t\t\t<div><%= item.postedData %></div>\n\t\t</div>\n\t<% }); %>\n\t</script>\n\t\n\t<script>\n\t$('#feedsDiv').html( _.template($('#feedTemplate').html(), feeds));\n\t</script>\n\n除此之外，UnderScore还提供了一些很方便的集合操作，使得模板的使用更加方便。如果你打算使用BackBone框架，并且需要用到模板功能，那么UnderScore是一个很好的选择，当然，也可以选用其它的模板库，比如Mustache等等。\n\n如果使用AngularJS，可以不需要额外的模板库，它自身就提供了类似的功能，比如上面这个例子可以改写成这样：\n\n\t<div class=\"post\" ng-repeat=\"post in feeds\">\n\t\t<div class=\"author\">\n\t\t\t<a ng-href=\"/user.html?user={{post.creatorName}}\">@{{post.creatorName}}</a>\n\t\t</div>\n\t\t<div>{{post.content}}</div>\n\t\t<div>\n\t\t\t发布日期：{{post.postedTime | date:'medium'}}\n\t\t</div>\n\t</div>\n\n主流的模板技术都提供了一些特定的语法，有些功能很强。值得注意的是，他们虽然与JSP之类的代码写法类似甚至相同，但原理差别很大，这些模板框架都是在浏览器端执行的，不依赖任何服务端技术，即使界面文件是.html也可以，而传统比如JSP模板是需要后端支持的，执行时间是在服务端。\n\n##5. 路由\n\n通常路由是定义在后端的，但是在这类MV*框架的帮助下，路由可以由前端来解析执行。比如下面这个Backbone的路由示例：\n\n\tvar Workspace = Backbone.Router.extend({\n\t\troutes: {\n\t\t\t\"help\":\t\t\t\t \"help\",\t// #help\n\t\t\t\"search/:query\":\t\t\"search\",  // #search/kiwis\n\t\t\t\"search/:query/p:page\": \"search\"   // #search/kiwis/p7\n\t\t},\n\t\t\n\t\thelp: function() {\n\t\t\t...\n\t\t},\n\t\t\n\t\tsearch: function(query, page) {\n\t\t\t...\n\t\t}\t\n\t});\n\n在上述例子中，定义了一些路由的映射关系，那么，在实际访问的时候，如果在地址栏输入\"#search/obama/p2\"，就会匹配到\"search/:query/p:page\"这条路由，然后，把\"obama\"和\"2\"当作参数，传递给search方法。\n\nAngularJS中定义路由的方式有些区别，它使用一个$routeProvider来提供路由的存取，每一个when表达式配置一条路由信息，otherwise配置默认路由，在配置路由的时候，可以指定一个额外的控制器，用于控制这条路由对应的html界面：\n\n\tapp.config(['$routeProvider',\n\tfunction($routeProvider) {\n\t\t$routeProvider.when('/phones', {\n\t\t\ttemplateUrl : 'partials/phone-list.html',\n\t\t\tcontroller : PhoneListCtrl\n\t\t}).when('/phones/:phoneId', {\n\t\t\ttemplateUrl : 'partials/phone-detail.html',\n\t\t\tcontroller : PhoneDetailCtrl\n\t\t}).otherwise({\n\t\t\tredirectTo : '/phones'\n\t\t});\n\t}]); \n\n注意，在AngularJS中，路由的template并非一个完整的html文件，而是其中的一段，文件的头尾都可以不要，也可以不要那些包含的外部样式和JavaScript文件，这些在主界面中载入就可以了。\n\n##6. 自定义标签\n\n用过XAML或者MXML的人一定会对其中的可扩充标签印象深刻，对于前端开发人员而言，基于标签的组件定义方式一定是优于其他任何方式的，看下面这段HTML：\n\n\t<div>\n\t\t<input type=\"text\" value=\"hello, world\"/>\n\t\t<button>test</button>\n\t</div>\n\n即使是刚刚接触这种东西的新手，也能够理解它的意思，并且能够照着做出类似的东西，如果使用传统的面向对象语言去描述界面，效率远远没有这么高，这就是在界面开发领域，声明式编程比命令式编程适合的最重要原因。\n\n但是，HTML的标签是有限的，如果我们需要的功能不在其中，怎么办？在开发过程中，我们可能需要一个选项卡的功能，但是，HTML里面不提供选项卡标签，所以，一般来说，会使用一些li元素和div的组合，加上一些css，来实现选项卡的效果，也有的框架使用JavaScript来完成这些功能。总的来说，这些代码都不够简洁直观。\n\n如果能够有一种技术，能够提供类似这样的方式，该多么好呢？\n\n\t<tabs>\n\t\t<tab name=\"Tab 1\">content 1</tab>\n\t\t<tab name=\"Tab 2\">content 2</tab>\n\t</tabs>\n\n回忆一下，我们在章节1.4 组件化的萌芽 里面，提到过一种叫做HTC的技术，这种技术提供了类似的功能，而且使用起来也比较简便，问题是，它属于一种正在消亡的技术，于是我们的目光投向了更为现代的前端世界，AngularJS拯救了我们。\n\n在AngularJS的首页，可以看到这么一个区块“Create Components”，在它的演示代码里，能够看到类似的一段：\n\n\t<tabs>\n\t\t<pane title=\"Localization\">\n\t\t\t...\n\t\t</pane>\n\t\t<pane title=\"Pluralization\">\n\t\t\t...\n\t\t</pane>\n\t</tabs>\n\n那么，它是怎么做到的呢？秘密在这里：\n\t\n\tangular.module('components', []).directive('tabs', function() {\n\t\treturn {\n\t\t\trestrict : 'E',\n\t\t\ttransclude : true,\n\t\t\tscope : {},\n\t\t\tcontroller : function($scope, $element) {\n\t\t\t\tvar panes = $scope.panes = [];\n\t\n\t\t\t\t$scope.select = function(pane) {\n\t\t\t\t\tangular.forEach(panes, function(pane) {\n\t\t\t\t\t\tpane.selected = false;\n\t\t\t\t\t});\n\t\t\t\t\tpane.selected = true;\n\t\t\t\t}\n\t\n\t\t\t\tthis.addPane = function(pane) {\n\t\t\t\t\tif (panes.length == 0)\n\t\t\t\t\t\t$scope.select(pane);\n\t\t\t\t\tpanes.push(pane);\n\t\t\t\t}\n\t\t\t},\n\t\t\ttemplate : '<div class=\"tabbable\">'\n\t\t\t\t+ '<ul class=\"nav nav-tabs\">' \n\t\t\t\t+ '<li ng-repeat=\"pane in panes\" ng-class=\"{active:pane.selected}\">' \n\t\t\t\t+ '<a href=\"\" ng-click=\"select(pane)\">{{pane.title}}</a>' \n\t\t\t\t+ '</li>' \n\t\t\t\t+ '</ul>' \n\t\t\t\t+ '<div class=\"tab-content\" ng-transclude></div>' \n\t\t\t\t+ '</div>',\n\t\t\treplace : true\n\t\t};\n\t}).directive('pane', function() {\n\t\treturn {\n\t\t\trequire : '^tabs',\n\t\t\trestrict : 'E',\n\t\t\ttransclude : true,\n\t\t\tscope : {\n\t\t\t\ttitle : '@'\n\t\t\t},\n\t\t\tlink : function(scope, element, attrs, tabsCtrl) {\n\t\t\t\ttabsCtrl.addPane(scope);\n\t\t\t},\n\t\t\ttemplate : '<div class=\"tab-pane\" ng-class=\"{active: selected}\" ng-transclude>' + '</div>',\n\t\t\treplace : true\n\t\t};\n\t})\n\n这段代码里，定义了tabs和pane两个标签，并且限定了pane标签不能脱离tabs而单独存在，tabs的controller定义了它的行为，两者的template定义了实际生成的html，通过这种方式，开发者可以扩展出自己需要的新元素，对于使用者而言，这不会增加任何额外的负担。\n\n#四. 一些想说的话\n\n###关于ExtJS\n\n注意到在本文中，并未提及这样一个比较流行的前端框架，主要是因为他自成一系，思路跟其他框架不同，所做的事情，层次介于文中的二和三之间，所以没有单独列出。\n\n###写作目的\n\n在我10多年的Web开发生涯中，经历了Web相关技术的各种变革，从2003年开始，接触并使用到了HTC，VML，XMLHTTP等当时比较先进的技术，目睹了网景浏览器的衰落，IE的后来居上，Firefox和Chrome的逆袭，各类RIA技术的风起云涌，对JavaScript的模块化有过持续的思考。未来究竟是什么样子？我说不清楚，只能凭自己的一些认识，把这些年一些比较主流的发展过程总结一下，供有需要了解的朋友们作个参考，错漏在所难免，欢迎大家指教。\n\n个人邮箱：xu.fei@outlook.com  \n新浪微博：http://weibo.com/sharpmaster\n"
  },
  {
    "path": "posts/2013-07-01-从零开始编写自己的JavaScript框架（一）.md",
    "content": "从零开始编写自己的JavaScript框架（一）\n====\n\n#1. 模块的定义和加载\n\n##1.1 模块的定义\n\n一个框架想要能支撑较大的应用，首先要考虑怎么做模块化。有了内核和模块加载系统，外围的模块就可以一个一个增加。不同的JavaScript框架，实现模块化方式各有不同，我们来选择一种比较优雅的方式作个讲解。\n\n先问个问题：我们做模块系统的目的是什么？如果觉得这个问题难以回答，可以从反面来考虑：假如不做模块系统，有什么样的坏处？\n\n我们经历过比较粗放、混乱的前端开发阶段，页面里充满了全局变量，全局函数。那时候要复用js文件，就是把某些js函数放到一个文件里，然后让多个页面都来引用。\n\n考虑到一个页面可以引用多个这样的js，这些js互相又不知道别人里面写了什么，很容易造成命名的冲突，而产生这种冲突的时候，又没有哪里能够提示出来。所以我们要有一种办法，把作用域比较好地隔开。\n<!--more-->\nJavaScript这种语言比较奇怪，奇怪在哪里呢，它的现有版本里没package跟class，要是有，我们也没必要来考虑什么自己做模块化了。那它是要用什么东西来隔绝作用域呢？\n\n在很多传统高级语言里，变量作用域的边界是大括号，在{}里面定义的变量，作用域不会传到外面去，但我们的JavaScript大人不是这样的，他的边界是function。所以我们这段代码，i仍然能打出值：\n\n    for (var i=0; i<5; i++) {\n        //do something\n    }\n    alert(i);\n\n那么，我们只能选用function做变量的容器，把每个模块封装到一个function里。现在问题又来了，这个function本身的作用域是全局的，怎么办？我们想不到办法，拔剑四顾心茫然。\n\n我们有没有什么可参照的东西呢？这时候，脑海中一群语言飘过：\nC语言飘过：“我不是面向对象语言哦~不需要像你这么组织哦~”，“死开！”\nJava飘过：“我是纯面向对象语言哦，连main都要在类中哦，编译的时候通过装箱清单指定入口哦~”，“死开！”\nC++飘过：“我也是纯面向对象语言哦”，等等，C++是纯面向对象的语言吗？你的main是什么？？？main是特例，不在任何类中！\n\n啊，我们发现了什么，既然无法避免全局的作用域，那与其让100个function都全局，不如只让一个来全局，其他的都由它管理。\n\n本来我们打算自己当上帝的，现在只好改行先当个工商局长。你想开店吗？先来注册，不然封杀你！于是良民们纷纷来注册。店名叫什么，从哪进货，卖什么的，一一登记在案，为了方便下面的讨论，我们连进货的过程都让工商局管理起来。\n\n店名，指的就是这里的模块名，从哪里进货，代表它依赖什么其他模块，卖什么，表示它对外提供一些什么特性。\n\n好了，考虑到我们的这个注册管理机构是个全局作用域，我们还得把它挂在window上作为属性，然后再用一个function隔离出来，要不然，别人也定义一个同名的，就把我们覆盖掉了。\n\n    (function() {\n        window.thin = {\n            define: function(name, dependencies, factory) {\n                //register a module\n            }\n        };\n    })();\n\n在这个module方法内部，应当怎么去实现呢？我们的module应当有一个地方存储，但存储是要在工商局内部的，不是随便什么人都可以看到的，所以，这个存储结构也放在工商局同样的作用域里。\n\n用什么结构去存储呢？工商局备案的时候，店名不能跟已有的重复，所以我们发现这是用map的很好场景，考虑到JavaScript语言层面没有map，我们弄个Object来存。\n\n    (function() {\n        var moduleMap = {};\n        window.thin = {\n            define: function(name, dependencies, factory) {\n\t\t\t\tif (!moduleMap[name]) {\n\t\t\t\t\tvar module = {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\tdependencies: dependencies,\n\t\t\t\t\t\tfactory: factory\n\t\t\t\t\t};\n\t\t\t\t\tmoduleMap[name] = module;\n\t\t\t\t}\n\t\t\t\treturn moduleMap[name];\n\t\t\t}\n        };\n    })();\n\n现在，模块的存储结构就搞好了。\n\n##1.2 模块的使用\n\n存的部分搞好了，我们来看看怎么取。现在来了一个商家，卖木器的，他需要从一个卖钉子的那边进货，卖钉子的已经来注册过了，现在要让这个木器厂能买到钉子。现在的问题是，两个商家处于不同的作用域，也就是说，它们互相不可见，那通过什么方式，我们才能让他们产生调用关系呢？\n\n个人解决不了的问题还是得靠政府，有困难要坚决克服，没有困难就制造困难来克服。现在困难有了，该克服了。商家说，我能不能给你我的进货名单，你帮我查一下它们在哪家店，然后告诉我？这么简单的要求当然一口答应下来，但是采用什么方式传递给你呢？这可犯难了。\n\n我们参考AngularJS框架，写了一个类似的代码：\n\n\tthin.define(\"A\", [], function() {\n\t\t//module A\n\t});\n\n\tthin.define(\"B\", [\"A\"], function(A) {\n\t\t//module B\n\t\tvar a = new A();\n\t});\n\n看这段代码特别在哪里呢？模块A的定义，毫无特别之处，主要看模块B。它在依赖关系里写了一个字符串的A，然后在工厂方法的形参写了一个真真切切的A类型。嗯？这个有些奇怪啊，你的A类型要怎么传递过来呢？其实是很简单的，因为我们声明了依赖项的数组，所以可以从依赖项，挨个得到对应的工厂方法，然后创建实例，传进来。\n\n\tuse: function(name) {\n\t\tvar module = moduleMap[name];\n\n\t\tif (!module.entity) {\n\t\t\tvar args = [];\n\t\t\tfor (var i=0; i<module.dependencies.length; i++) {\n\t\t\t\tif (moduleMap[module.dependencies[i]].entity) {\n\t\t\t\t\targs.push(moduleMap[module.dependencies[i]].entity);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\targs.push(this.use(module.dependencies[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmodule.entity = module.factory.apply(noop, args);\n\t\t}\n\n\t\treturn module.entity;\n\t}\n\n我们可以看到，这里面递归获取了依赖项，然后当作参数，用这个模块的工厂方法来实例化了一下。这里我们多做了一个判断，如果模块工厂已经执行过，就缓存在entity属性上，不需要每次都创建。以此类推，假如一个模块有多个依赖项，也可以用类似的方式写，毫无压力：\n\n\tthin.define(\"D\", [\"A\", \"B\", \"C\"], function(A, B, C) {\n\t\t//module D\n\t\tvar a = new A();\n\t\tvar b = new B();\n\t\tvar c = new C();\n\t});\n\n注意了，D模块的工厂，实参的名称未必就要是跟依赖项一致，比如，以后我们代码较多，可以给依赖项和模块名称加命名空间，可能变成这样：\n\n\tthin.define(\"foo.D\", [\"foo.A\", \"foo.B\", \"foo.C\"], function(A, B, C) {\n\t\t//module D\n\t\tvar a = new A();\n\t\tvar b = new B();\n\t\tvar c = new C();\n\t});\n\n这段代码仍然可以正常运行。我们来做另外一个测试，改变形参的顺序：\n\n\tthin.define(\"A\", [], function() {\n\t\treturn \"a\";\n\t});\n\n\tthin.define(\"B\", [], function() {\n\t\treturn \"b\";\n\t});\n\n\tthin.define(\"C\", [], function() {\n\t\treturn \"c\";\n\t});\n\n\tthin.define(\"D\", [\"A\", \"B\", \"C\"], function(B, A, C) {\n\t\treturn B + A + C;\n\t});\n\n\tvar D = thin.use(\"D\");\n\talert(D);\n\n试试看，我们的D打出什么结果呢？结果是\"abc\"，所以说，模块工厂的实参只跟依赖项的定义有关，跟形参的顺序无关。我们看到，在AngularJS里面，并非如此，实参的顺序是跟形参一致的，这是怎么做到的呢？\n\n我们先离开代码，思考这么一个问题：如何得知函数的形参名数组？对，我们是可以用func.length得到形参个数，但无法得到每个形参的变量名，那怎么办呢？\n\nAngularJS使用了一种比较极端的办法，分析了函数的字面量。众所周知，在JavaScript中，任何对象都隐含了toString方法，对于一个函数来说，它的toString就是自己的实现代码，包含函数签名和注释。下面我贴一下AngularJS里面的这部分代码：\n\n\tvar FN_ARGS = /^function\\s*[^\\(]*\\(\\s*([^\\)]*)\\)/m;\n\tvar FN_ARG_SPLIT = /,/;\n\tvar FN_ARG = /^\\s*(_?)(\\S+?)\\1\\s*$/;\n\tvar STRIP_COMMENTS = /((\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/))/mg;\n\tfunction annotate(fn) {\n\t  var $inject,\n\t\t  fnText,\n\t\t  argDecl,\n\t\t  last;\n\n\t  if (typeof fn == 'function') {\n\t\tif (!($inject = fn.$inject)) {\n\t\t  $inject = [];\n\t\t  fnText = fn.toString().replace(STRIP_COMMENTS, '');\n\t\t  argDecl = fnText.match(FN_ARGS);\n\t\t  forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){\n\t\t\targ.replace(FN_ARG, function(all, underscore, name){\n\t\t\t  $inject.push(name);\n\t\t\t});\n\t\t  });\n\t\t  fn.$inject = $inject;\n\t\t}\n\t  } else if (isArray(fn)) {\n\t\tlast = fn.length - 1;\n\t\tassertArgFn(fn[last], 'fn');\n\t\t$inject = fn.slice(0, last);\n\t  } else {\n\t\tassertArgFn(fn, 'fn', true);\n\t  }\n\t  return $inject;\n\t}\n\n可以看到，这个代码也不长，重点是类型为function的那段，首先去除了注释，然后获取了形参列表字符串，这段正则能获取到两个结果，第一个是全函数的实现，第二个才是真正的形参列表，取第二个出来split，就得到了形参的字符串列表了，然后按照这个顺序再去加载依赖模块，就可以让形参列表不对应于依赖项数组了。\n\nAngularJS的这段代码很强大，但是要损耗一些性能，考虑到我们的框架首要原则是简单，甚至可以为此牺牲一些灵活性，我们不做这么复杂的事情了。\n\n##1.3 模块的加载\n\n到目前为止，我们可以把多个模块都定义在一个文件中，然后手动引入这个js文件，但是如果一个页面要引用很多个模块，引入工作就变得比较麻烦，比如说，单页应用程序（SPA）一般比较复杂，往往包含数以万计行数的js代码，这些代码至少分布在几十个甚至成百上千的模块中，如果我们也在主界面就加载它们，载入时间会非常难以接受。但我们可以这样看：主界面加载的时候，并不是用到了所有这些功能，能否先加载那些必须的，而把剩下的放在需要用的时候再去加载？\n\n所以我们可以考虑万能的AJAX，从服务端获取一个js的内容，然后……，怎么办，你当然说不能eval了，因为据说eval很evil啦，但是它evil在哪里呢？主要是破坏全局作用域啦，怎么怎么，但是如果这些文件里面都是按照我们规定的模块格式写，好像也没有什么在全局作用域的……，好吧。\n\n算了，我们还是用最简单的方式了，就是动态创建script标签，然后设置src，添加到document.head里，然后监听它们的完成事件，做后续操作。真的很简单，因为我们的框架不需要考虑那么多种情况，不需要AMD，不需要require那么麻烦，用这框架的人必须按照这里的原则写。\n\n所以，说真的我们这里没那么复杂啦，要是你们想看更详细原理的不如去看这个，解释得比我好哎：http://coolshell.cn/articles/9749.html#jtss-tsina\n\n我也偷懒了，只是贴一下代码，顺便解释一下，界面把所依赖的js文件路径放在数组里，然后挨个创建script标签，src设置为路径，添加到head中，监听它们的完成事件。在这个完成时间里，我们要做这么一些事情：在fileMap里记录当前js文件的路径，防止以后重复加载，检查列表中所有文件，看看是否全部加载完了，如果全加载好了，就执行回调。\n\n\trequire: function (pathArr, callback) {\n\t\tfor (var i = 0; i < pathArr.length; i++) {\n\t\t\tvar path = pathArr[i];\n\n\t\t\tif (!fileMap[path]) {\n\t\t\t\tvar head = document.getElementsByTagName('head')[0];\n\t\t\t\tvar node = document.createElement('script');\n\t\t\t\tnode.type = 'text/javascript';\n\t\t\t\tnode.async = 'true';\n\t\t\t\tnode.src = path + '.js';\n\t\t\t\tnode.onload = function () {\n\t\t\t\t\tfileMap[path] = true;\n\t\t\t\t\thead.removeChild(node);\n\t\t\t\t\tcheckAllFiles();\n\t\t\t\t};\n\t\t\t\thead.appendChild(node);\n\t\t\t}\n\t\t}\n\n\t\tfunction checkAllFiles() {\n\t\t\tvar allLoaded = true;\n\t\t\tfor (var i = 0; i < pathArr.length; i++) {\n\t\t\t\tif (!fileMap[pathArr[i]]) {\n\t\t\t\t\tallLoaded = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (allLoaded) {\n\t\t\t\tcallback();\n\t\t\t}\n\t\t}\n\t}\n\n##1.4 小结\n\n到此为止，我们的简易框架的模块定义系统就完成了。完整的代码如下：\n\n\t(function () {\n\t\tvar moduleMap = {};\n\t\tvar fileMap = {};\n\n\t\tvar noop = function () {\n\t\t};\n\n\t\tvar thin = {\n\t\t\tdefine: function(name, dependencies, factory) {\n\t\t\t\tif (!moduleMap[name]) {\n\t\t\t\t\tvar module = {\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\tdependencies: dependencies,\n\t\t\t\t\t\tfactory: factory\n\t\t\t\t\t};\n\n\t\t\t\t\tmoduleMap[name] = module;\n\t\t\t\t}\n\n\t\t\t\treturn moduleMap[name];\n\t\t\t},\n\n\t\t\tuse: function(name) {\n\t\t\t\tvar module = moduleMap[name];\n\n\t\t\t\tif (!module.entity) {\n\t\t\t\t\tvar args = [];\n\t\t\t\t\tfor (var i=0; i<module.dependencies.length; i++) {\n\t\t\t\t\t\tif (moduleMap[module.dependencies[i]].entity) {\n\t\t\t\t\t\t\targs.push(moduleMap[module.dependencies[i]].entity);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\targs.push(this.use(module.dependencies[i]));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tmodule.entity = module.factory.apply(noop, args);\n\t\t\t\t}\n\n\t\t\t\treturn module.entity;\n\t\t\t},\n\n\t\t\trequire: function (pathArr, callback) {\n                for (var i = 0; i < pathArr.length; i++) {\n                    var path = pathArr[i];\n\n                    if (!fileMap[path]) {\n                        var head = document.getElementsByTagName('head')[0];\n                        var node = document.createElement('script');\n                        node.type = 'text/javascript';\n                        node.async = 'true';\n                        node.src = path + '.js';\n                        node.onload = function () {\n                            fileMap[path] = true;\n                            head.removeChild(node);\n                            checkAllFiles();\n                        };\n                        head.appendChild(node);\n                    }\n                }\n\n                function checkAllFiles() {\n                    var allLoaded = true;\n                    for (var i = 0; i < pathArr.length; i++) {\n                        if (!fileMap[pathArr[i]]) {\n                            allLoaded = false;\n                            break;\n                        }\n                    }\n\n                    if (allLoaded) {\n                        callback();\n                    }\n                }\n            }\n\t\t};\n\n\t\twindow.thin = thin;\n\t})();\n\n测试代码如下：\n\n\tthin.define(\"constant.PI\", [], function() {\n\t\treturn 3.14159;\n\t});\n\n\tthin.define(\"shape.Circle\", [\"constant.PI\"], function(pi) {\n\t\tvar Circle = function(r) {\n\t\t\tthis.r = r;\n\t\t};\n\n\t\tCircle.prototype = {\n\t\t\tarea : function() {\n\t\t\t\treturn pi * this.r * this.r;\n\t\t\t}\n\t\t}\n\n\t\treturn Circle;\n\t});\n\n\tthin.define(\"shape.Rectangle\", [], function() {\n\t\tvar Rectangle = function(l, w) {\n\t\t\tthis.l = l;\n\t\t\tthis.w = w;\n\t\t};\n\n\t\tRectangle.prototype = {\n\t\t\tarea: function() {\n\t\t\t\treturn this.l * this.w;\n\t\t\t}\n\t\t};\n\n\t\treturn Rectangle;\n\t});\n\n\tthin.define(\"ShapeTypes\", [\"shape.Circle\", \"shape.Rectangle\"], function(Circle, Rectangle) {\n\t\treturn {\n\t\t\tCIRCLE: Circle,\n\t\t\tRECTANGLE: Rectangle\n\t\t};\n\t});\n\n\tthin.define(\"ShapeFactory\", [\"ShapeTypes\"], function(ShapeTypes) {\n\t\treturn {\n\t\t\tgetShape: function(type) {\n\t\t\t\tvar shape;\n\n\t\t\t\tswitch (type) {\n\t\t\t\t\tcase \"CIRCLE\": {\n\t\t\t\t\t\tshape = new ShapeTypes[type](arguments[1]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"RECTANGLE\":  {\n\t\t\t\t\t\tshape = new ShapeTypes[type](arguments[1], arguments[2]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn shape;\n\t\t\t}\n\t\t};\n\t});\n\n\tvar ShapeFactory = thin.use(\"ShapeFactory\");\n\talert(ShapeFactory.getShape(\"CIRCLE\", 5).area());\n\talert(ShapeFactory.getShape(\"RECTANGLE\", 3, 4).area());\n\n在这个例子里定义了四个模块，每个模块只需要定义自己所直接依赖的模块，其他的可以不必定义。也可以来这里看测试链接：http://xufei.github.io/thin/demo/demo.0.1.html\n"
  },
  {
    "path": "posts/2013-07-10-从零开始编写自己的JavaScript框架（二）.md",
    "content": "从零开始编写自己的JavaScript框架（二）\n====\n\n#2. 数据绑定\n\n##2.1 数据绑定的原理\n\n数据绑定是一种很便捷的特性，一些RIA框架带有双向绑定功能，比如Flex和Silverlight，当某个数据发生变更时，所绑定的界面元素也发生变更，当界面元素的值发生变化时，数据也跟着变化，这种功能在处理表单数据的填充和收集时，是非常有用的。\n\n在HTML中，原生是没有这样的功能的，但有些框架做到了，它们是怎么做到的呢？我们来做个简单的试试，顺便探讨一下其中原理。\n\n先看数据到界面上的的绑定，比如：\n\n    <input vm-value=\"name\"/>\n    var person = {\n        name: \"Tom\"\n    };\n\n如果我们给name重新赋值，person.name = \"Jerry\"，怎么才能让界面得到变更？\n<!--more-->\n从直觉来说，我们需要在name发生改变的时候，触发一个事件，或者调用某个指定的方法，然后才好着手做后面的事情，比如：\n\n    var person = {\n        name: \"Tom\",\n        setName: function(newName) {\n            this.name = newName;\n            //do something\n        }\n    };\n\n这样我们可以在setName里面去给input赋值。推而广之，为了使得实体包含的多个属性都可以运作，可以这么做：\n\n    var person = {\n        name: \"Tom\",\n        gender: 5\n        set: function(key, value) {\n            this[key] = value;\n            //do something\n        }\n    };\n\n或者合并两个方法，只判断是否传了参数：\n\n    Person.prototype.name = function(value) {\n        if (arguments.length == 0) {\n            return this._name;\n        }\n        else {\n            this._name = value;\n        }\n    }\n\n这种情况下，赋值的时候就是person.name(\"Tom\")，取值的时候就是var name = person.name()了。\n\n有一些框架是通过这种方式来变通实现数据绑定的，对数据的写入只能通过方法调用。但这种方式很不直接，我们来想点别的办法。\n\n在C#等一些语言里，有一种东西叫做存取器，比如说：\n\n    class Person\n    {\n        private string name;\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n            set\n            {\n                name = value;\n            }\n        }\n    }\n\n用的时候，person.Name = \"Jerry\"，就会调用到set里，相当于是个方法。\n\n这一点非常好，很符合我们的需要，那JavaScript里面有没有类似存取器的特性呢？老早以前是没有的，但现在有了，那就是Object.defineProperty，它的第三个参数就是可选的存取函数。比如说：\n\n    var person = {};\n\n    // Add an accessor property to the object.\n    Object.defineProperty(person, \"name\", {\n        set: function (value) {\n            this._name = value;\n            //do something\n        },\n        get: function () {\n            return this._name;\n        },\n        enumerable: true,\n        configurable: true\n    });\n\n赋值的时候，person.name = \"Tom\"，取值的时候，var name = person.name，简直太美妙了。注意这里define的时候，是定义在实例上的，如果想要定义到类型里面，可以在构造器里面定义。\n\n现在我们从数据到DOM的绑定可以解决掉了，至少我们能够在变量被更改的时候去做一些自己的事情，比如查找这个属性被绑定到哪些控件了，然后挨个对其赋值。框架怎么知道属性被绑定到哪些控件了呢？这个直接在第二部分的实现过程中讨论。\n\n再看控件到数据的绑定，这个其实很好理解。无非就是给控件添加change之类的事件监听，在这里面把关联到的数据更新掉。到这里，我们在原理方面已经没有什么问题了，现在开始准备把它写出来。\n\n##2.2 数据绑定的实现\n\n我们的框架启动之后，要先把前面所说的这种绑定关系收集起来，这种属性会分布于DOM的各个角落，一个很现实的做法是，递归遍历界面的每个DOM节点，检测该属性，于是我们代码的结构大致如下所示。\n\n    function parseElement(element) {\n        for (var i=0; i<element.attributes.length; i++) {\n            parseAttribute(element.attributes[i]);\n        }\n\n        for (var i=0; i<element.children.length; i++) {\n            parseElement(element.children[i]);\n        }\n    }\n\n但是我们这时候面临一个问题，比如你的输入框绑定在name变量上，这个name应该从属于什么？它是全局变量吗？\n\n我们在开始做这个框架的时候强调了一个原则：业务模块不允许定义全局变量，框架内部也尽量少有全局作用域，到目前为止，我们只暴露了thin一个全局入口，所以在这里不能破坏这个原则。\n\n因此，我们要求业务开发人员去定义一个视图模型，把变量包装起来，所包装的不限于变量，也可以有方法。比如下面，我们定义了一个实体叫Person，带两个变量，两个方法，后面我们来演示一下怎么把它们绑定到HTML界面。\n\n    thin.define(\"Person\", [], function() {\n        function Person() {\n            this.name = \"Tom\";\n            this.age = 5;\n        }\n\n        Person.prototype = {\n            growUp: function() {\n                this.age++;\n            }\n        };\n\n        return Person;\n    });\n\n模型方面都准备好了，现在来看界面：\n\n    <div vm-model=\"Person\">\n        <input type=\"text\" vm-value=\"name\"/>\n        <input type=\"text\" vm-value=\"age\"/>\n        <input type=\"button\" vm-click=\"growUp\" value=\"Grow Up\"/>\n    </div>\n\n为了使得结构更加容易看，我们把界面的无关属性比如样式之类都去掉了，只留下不能再减少的这么一段。现在我们可以看到，在界面的顶层定义一个vm-model属性，值为实体的名称。两个输入框通过vm-value来绑定到实例属性，vm-init绑定界面的初始化方法，vm-click绑定按钮的点击事件。\n\n好了，现在我们可以来扫描这个简单的DOM结构了。想要做这么一个绑定，首先要考虑数据从哪里来？在绑定name和code属性之前，毫无疑问，应当先实例化一个Person，我们怎么才能知道需要把Person模块实例化呢？\n\n当扫描到一个DOM元素的时候，我们要先检测它的vm-model属性，如果有值，就取这个值来实例化，然后，把这个值一直传递下去，在扫描其他属性或者下属DOM元素的时候都带进去。这么一来，parseElement就变成一个递归了，于是它只好有两个参数，变成了这样：\n\n    function parseElement(element, vm) {\n        var model = vm;\n\n        if (element.getAttribute(\"vm-model\")) {\n            model = bindModel(element.getAttribute(\"vm-model\"));\n        }\n\n        for (var i=0; i<element.attributes.length; i++) {\n            parseAttribute(element, element.attributes[i], model);\n        }\n\n        for (var i=0; i<element.children.length; i++) {\n            parseElement(element.children[i], model);\n        }\n    }\n\n看看我们打算怎么来实例化这个模型，这个bindModel方法的参数是模块名，于是我们先去use一下，从工厂里生成出来，然后new一下，先这么return出去吧。\n\n    function bindModel(modelName) {\n        thin.log(\"model\" + modelName);\n\n        var model = thin.use(modelName, true);\n        var instance = new model();\n\n        return instance;\n    }\n\n现在我们开始关注parseAttribute函数，可能的attribute有哪些种类呢？我列举了一些很常用的：\n\n- init，用于绑定初始化方法\n- click，用于绑定点击\n- value，绑定变量\n- enable和disable，绑定可用状态\n- visible和invisible，绑定可见状态\n\n然后就可以实现我们parseAttribute函数了：\n\n    function parseAttribute(element, attr, model) {\n        if (attr.name.indexOf(\"vm-\") == 0) {\n            var type = attr.name.slice(3);\n\n            switch (type) {\n                case \"init\":\n                    bindInit(element, attr.value, model);\n                    break;\n                case \"value\":\n                    bindValue(element, attr.value, model);\n                    break;\n                case \"click\":\n                    bindClick(element, attr.value, model);\n                    break;\n                case \"enable\":\n                    bindEnable(element, attr.value, model, true);\n                    break;\n                case \"disable\":\n                    bindEnable(element, attr.value, model, false);\n                    break;\n                case \"visible\":\n                    bindVisible(element, attr.value, model, true);\n                    break;\n                case \"invisible\":\n                    bindVisible(element, attr.value, model, false);\n                    break;\n                case \"element\":\n                    model[attr.value] = element;\n                    break;\n            }\n        }\n    }\n\n注意到最后还有个element类型，本来可以不要这个，但我们考虑到将来，一切都是组件化的时候，界面上打算不写id，也不依靠选择器，而是用某个标志来定位元素，所以加上了这个，文章最后的示例中使用了它。\n\n这么多绑定，不打算都讲，用bindValue函数来说明一下吧：\n\n    function bindValue(element, key, vm) {\n        thin.log(\"binding value: \" + key);\n\n        vm.$watch(key, function (value, oldValue) {\n            element.value = value || \"\";\n        });\n\n        element.onkeyup = function () {\n            vm[key] = element.value;\n        };\n\n        element.onpaste = function () {\n            vm[key] = element.value;\n        };\n    }\n\n我们假定每个模型实例上带有一个$watch方法，用于监控某变量的变化，可以传入一个监听函数，当变量变化的时候，自动调用这个函数，并且把新旧两个值传回来。\n\n在这个代码里，我们使用$watch方法给传入的key添加一个监听，监听器里面给监听元素赋值。我们这里偷懒了一下，假定所有的绑定元素都是输入框，所以直接给element.value设置值，为了防止值为空导致显示undefined，把值跟空字符串用短路表达式做了个转换。\n\n接下来，也对element的几个可能导致值变化的事件进行了监听，在里面把模型上对应的值更新掉。这样双向绑定就做好了。\n\n然后回头来看$watch的实现。很显然这里也要一个map，我们给它取名为$watchers，存放属性的绑定关系，对于每个属性，它的值需要保存一份，供getter获取，同时还有一个数组，存放了该属性绑定的处理函数。当属性发生变更的时候，去挨个把它们调用一下。\n\n    var Binder = {\n        $watch: function (key, watcher) {\n            if (!this.$watchers[key]) {\n                this.$watchers[key] = {\n                    value: this[key],\n                    list: []\n                };\n\n                Object.defineProperty(this, key, {\n                    set: function (val) {\n                        var oldValue = this.$watchers[key].value;\n                        this.$watchers[key].value = val;\n\n                        for (var i = 0; i < this.$watchers[key].list.length; i++) {\n                            this.$watchers[key].list[i](val, oldValue);\n                        }\n                    },\n\n                    get: function () {\n                        return this.$watchers[key].value;\n                    }\n                });\n            }\n\n            this.$watchers[key].list.push(watcher);\n        }\n    };\n\n但是vm怎么就有$watch呢，每个地方都去判断一下非空然后再去创建其实挺麻烦的，所以，这个属性我们可以直接在实例化模型的时候创建出来。\n\n    function bindModel(name) {\n        thin.log(\"binding model: \" + name);\n\n        var model = thin.use(name, true);\n        var instance = new model().extend(Binder);\n        instance.$watchers = {};\n\n        return instance;\n    }\n\n看看这里的写法，为什么$watchers要额外设置，而$watch就可以放在Binder里面来extend呢？\n\n先解释extend干了什么，它做的是一个对象的浅拷贝，也就是说，把Binder的属性和方法都复制给了创建出来的model实例，注意，这个所谓的复制，如果是简单类型，那确实复制了，如果是引用类型，那复制的其实只是一个引用，所以如果$watchers也放在Binder里，不同的instance就共享一个$watchers，逻辑就是错误的。那为什么$watch又可以放在这里复制呢？因为它是函数，它的this始终指向当前的执行主体，也就是说，如果放在instance1上执行，指向的就是instance1，放在instance2上执行，指向的就是instance2，我们利用这一点，就可以不用让每个实例都创建一份$watch方法，而是共用同一个。\n\n同理，我们可以把enable，visible，init，click这些都做起来，init的执行时间放在扫描完vm-model那个element之下的所有DOM节点之后。\n\n嗯，我们是不是可以试一下了？来写个代码：\n\n    <!DOCTYPE html>\n    <html>\n    <head>\n        <title>Simple binding demo</title>\n        <meta charset=\"utf-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n        <meta name=\"description\" content=\"binding\">\n        <meta name=\"author\" content=\"xu.fei@outlook.com\">\n        <script type=\"text/javascript\" src=\"../js/thin.js\"></script>\n    </head>\n    <body>\n    <div vm-model=\"test.Person\">\n        <input type=\"text\" vm-value=\"name\"/>\n        <input type=\"text\" vm-value=\"age\"/>\n        <input type=\"text\" vm-value=\"age\"/>\n        <input type=\"button\" vm-click=\"growUp\" value=\"Grow Up\"/>\n    </div>\n\n    <div vm-model=\"test.Person\" vm-init=\"init\">\n        <input type=\"text\" vm-value=\"name\"/>\n        <input type=\"text\" vm-value=\"age\"/>\n        <input type=\"button\" vm-click=\"growUp\" value=\"Grow Up\"/>\n    </div>\n    <script type=\"text/javascript\">\n        thin.define(\"test.Person\", [], function () {\n            function Person() {\n                this.name = \"Tom\";\n                this.age = 5;\n            }\n\n            Person.prototype = {\n                init: function () {\n                    this.name = \"Jerry\";\n                    this.age = 3;\n                },\n\n                growUp: function () {\n                    this.age++;\n                }\n            };\n\n            return Person;\n        });\n    </script>\n    </body>\n    </html>\n\n或者访问这里：http://xufei.github.io/thin/demo/simple-binding.html\n\n以刚才文章提到的内容，还不能完全解释这个例子的效果，因为没看到在哪里调用parseElement的。说来也简单，就在thin.js里面，直接写了一个thin.ready，在那边调用了这个函数，去解析了document.body，于是测试页面里面才可以只写绑定和视图模型。\n\n我们还有一个更实际一点的例子，结合了另外一个系列里面写的简单DataGrid控件，做了一个很基础的人员管理界面：http://xufei.github.io/thin/demo/binding.html\n\n##2.3 小结\n\n到此为止，我们的绑定框架勉强能够运行起来了！虽然很简陋，而且要比较新的浏览器才能跑，但毕竟是跑起来了。\n\n注意Object.defineProperty仅在Chrome等浏览器中可用，IE需要9以上才比较正常。在司徒正美的avalon框架中，巧妙使用VBScript绕过这一限制，利用vbs的property和两种语言的互通，实现了低版本IE的兼容。我们这个框架的目标不是兼容，而是为了说明原理，所以感兴趣的朋友可以去看看avalon的源码。 \n"
  },
  {
    "path": "posts/2013-10-01-企业软件领域前端开发的困境.md",
    "content": "企业软件领域前端开发的困境\n====\n\n前一段时间，看到阿里几位前端大师的讨论：[阿里前端的困局与突围][1]，对这个职业的发展方向有一些思考，我上次跟winter和dh一起吃饭，也简单聊到这个话题。\n\nwinter问了一个问题，如果在互联网企业跟游戏开发的企业同时进行一次针对前端开发的大裁员，对这个企业的核心价值而言，哪种影响更大？\n\n这个问题问得很有意思，在每个行业里，前端开发的侧重点是不一样的，重要性也有所不同，简单来说可以分为3个大类：互联网、企业应用、游戏，分别侧重于：交互、架构、算法。\n\n在这三个大类里，互联网方向的前端开发最为正统，算是根正苗红，所以在这个领域的人，对标准研究得最为透彻，对交互理解得最为深刻，目前前端方向的高手大多集中在这个领域。这个领域的人最关注的问题是兼容性，对一些细节的优化把握得炉火纯青。因为这个领域的业务特点，前端做的事情过于扁平，整个可发挥的余地不够，虽然高手众多，但就像很多龙挤在浅水里，常常有无用武之地的感叹。刚才玉伯这篇文章，讲述的就是这个领域中前端的困惑。\n\n企业应用方向的前端开发其实很多时候并不在意他们用的是Web还是其他类似技术，比如Flex，Silverlight等，对他们来说，即使是C/S的系统，也能够发挥出很大价值。这个领域的人最关注的问题是组件化和快速业务开发。\n<!--more-->\n从企业软件的方向来说，它的业务是很丰富的，对各种前端技术的应用也都很广泛，一个大型的企业应用，几乎什么特性都能用上。相对于互联网系统，它的客户相对比较专业，可以排除一些低端过时的浏览器，所以少了很多兼容的负担，在框架选型上，也可以接受很复杂的框架，比如ExtJS、AngularJS等，因为他们的业务特性，往往需要很复杂框架的支持。\n\n用我所在的电信行业软件举例，业务复杂度非常高，一套全业务系统会有两千左右的数据库表，两千个左右的业务菜单，其中有些业务界面的复杂度非常惊人，而且经常会根据需求有较大变动，性能也有较高要求。\n\n理论上来说，前端在这个领域可以领悟到很多事情，但这个领域有个最大的问题，盈利太低，不足以支撑很深入的研究，另一个无奈的问题是，由于历史原因，前端开发人员在这个领域并不容易受到重视，比如说资深技术人员多数是做后端开发的，认为前端很小儿科，在应届生入职筛选的时候，也会把能力较高的弄去做后端开发，剩下的留给前端。这些原因，造成了企业应用的前端领域就像一个又大又深的湖，里面小鱼小虾众多，却很少有大鱼。\n\n我在企业软件前端开发做了很多年，经常思考其中的一些问题，在这个领域做，总是有一种寂寞的感觉。我们这种行业，为了保证交付的及时，倾向于划分业务开发和技术平台开发，业务开发人员并不在难解决的问题上花时间，遇到问题的时候向技术平台团队寻求支持，把问题转移给更专业的人，避免耽误自己的交付时间，在他们开工之前，也由技术平台团队预先搭建框架，他们直接在这个上面以固定的模式进行开发。两个团队，前者的特点是多而泛，后者的特点是少而精。\n\n这么做，效率比较高，但带来一个问题，业务开发团队的技术水平很难提升，因为他总是忙碌赶工，很少有时间去思考很多问题的前因后果，即使你帮他解决了问题，告诉他，他也不一定有心情去关注一遍，因为他确实很忙。可能有些有激情的人会自己花点时间研究一下，但多数人很难有这样的心境。\n\n这就造成了业务开发团队和平台开发团队的技术实力严重脱节。从另外一个角度看，技术平台团队长期专门给别人解决问题，自己却很少全职参与某个业务项目的开发，他也很难有成就感。这还不是最大的问题，最大问题是，不管从哪个团队，都很难成长出能够设计最适合这些业务的前端架构的人，这恰恰是这个领域前端开发最重要的部分。\n\n当出现各种新技术的时候，平台团队比较容易去快速跟进，但投入通常不会很大，当取得一些进展的时候，会逐步向业务开发团队推广，但这个推广的难度是很大的，因为人数的比例会比较大，当技术从一个人向三个人推进的时候，是相对还算容易的，如果从一个向十个或者二十个人去推进，难度就大多了。而由于传统企业盈利规模的限制，没办法在每个技术方向都有较大投入，所以往往就是一两个人去折腾，他们在探索的过程中遇到问题，是很难找到能够交流的人的，如果自己解决不了问题，就会持续苦闷，非常寂寞。前端这个领域更是如此，现在客户端技术这么多，各种终端，各种浏览器，各种前端框架，每个上面投入一个人，就已经是个很大团队了，这种模式很明显就碰到瓶颈了，因为它很直接地跟人员编制产生了冲突。扩编直接对利润产生冲击，但是不扩编的话，技术平台团队的压力就会进一步加大，除了要探索新技术，还要对越来越庞大的业务开发团队作技术支持，每个人都痛不欲生。\n\n这种困境怎么解决呢，我想了很久，无计可施，也许，是时候要效仿互联网企业的开发模式了？但是积重难返，而且传统企业招聘的门槛远比互联网企业低，人员的能力有差距，也很难有互联网企业那么蓬勃而广泛的技术研究气氛，可能就更难做下去了。 \n\n可能这个领域的出路是寻找更为简单快速的开发方式，并且把相关的外围工具也做大做强，在业务领域中，把组件也积累沉淀出来，这时候能够用更少的业务开发人员来实现同等规模的系统，把更多人力节约出来做技术探索和改进吧？\n\n  [1]: https://github.com/lifesinger/lifesinger.github.com/issues/141\n"
  },
  {
    "path": "posts/2013-10-22-前端MV☆框架的意义.md",
    "content": "前端MV*框架的意义\n====\n\n经常有人质疑，在前端搞MV*有什么意义？也有人提出这样的疑问：以AngularJS，Knockout，BackBone为代表的MV*框架，它跟jQuery这样的框架有什么区别？我jQuery用得好好的，有什么必要再引入这种框架？\n\n回答这些问题之前，先要理清一些历史，前端从什么时候开始有框架的？\n\n早期前端都是比较简单，基本以页面为工作单元，内容以浏览型为主，也偶尔有简单的表单操作，这个时期每个界面上只有很少的JavaScript逻辑，基本不太需要框架。随着AJAX的出现，Web2.0的兴起，人们可以在页面上可以做比较复杂的事情了，然后前端框架才真正出现了，以jQuery为代表，针对界面上常见的DOM操作，远程请求，数据处理等作了封装，也有专注于处理数据的Underscore，严格来说，这些都不能算框架，而是算库。\n\n库和框架是有一些区别的：库是一种工具，我提供了，你可以不用，即使你用了，也没影响你自己的代码结构。框架则是面向一个领域，提供一套解决方案，如果你用我，就得按照我的方式办事。按照这个定义，jQuery和Underscore都只能算是库，ExtJS和dojo算框架。\n\nMV*框架又是为什么兴起的呢？它的出现，伴随着一些Web产品逐渐往应用方向发展，遇到了在C/S领域相同的问题：由于前端功能的增强、代码的膨胀，导致不得不做“前端的架构”这个事情了。\n<!--more-->\n很多做后端开发的人对前端架构很不屑，认为前端只是很薄的一层东西，做架构干什么？什么，不但要搞架构，还要搞MVC？Java Struts的MVC中，整个前端都只能算是View而已，你还要在这个View里面划分模型和控制器等其他东西？他们中的多数对这个很不屑，但Web前端随着复杂度的增加，很多地方跟客户端已经没有本质区别了。\n\njQuery的思维方式是：以DOM操作为中心  \nMV*框架的思维方式是：以模型为中心，DOM操作只是附加\n\n所以回到那个问题上，jQuery满足了你的业务需要，你还有什么必要引入MV*框架？\n\n这个是要看产品类型的，如果是页面型产品，多数确实不太需要它，因为页面中的JavaScript代码，处理交互的绝对远远超过处理模型的，但是如果是应用软件类产品，这就太需要了。\n\n长期做某个行业软件的公司，一般都会沉淀下来一些业务组件，主要体现在数据模型、业务规则和业务流程，这些组件基本都存在于后端，在前端很少有相应的组织。在以往的经验里，他们是有做MVC的，也尝试做了一些界面组件，但做法比较过时，比如说使用JSF或者GWT这样的方式。\n\nJSF的问题是什么？它的问题并不在于界面跟逻辑混合，所谓的纵向切分组件，Polymer这种纯前端框架也是这么切分的，它问题在于组件的生成和渲染不在同一个地方。所以，逻辑代码的位置很尴尬，如果这个界面简单还好说，复杂起来就很麻烦了，就是很多明明是前端逻辑代码，却需要通过后端去生成。\n\nGWT这种方式相对要好一些，它的问题是留给UI调节的余地太小了，比较缺乏灵活性。\n\n这类基于某种服务端技术的组件化方式有一些局限性，比如它较大程度限制了前端的发挥，在早一些的时候，这种方式可能还不错，但是现在随着时代发展，用户对前端用户体验要求越来越高，需要我们把很大一部分精力继续放回前端来。JSF等方案的另外一个问题是绑定了某种服务端环境，很难切换到另外一种后端上，如果碰上要用Hybird方式开发，想复用一些前端逻辑，几乎毫无可能。\n\n那么，我们看看纯前端的框架，看看都是怎么解决这些问题的。以Google为例，它推出了两个框架，Polymer和Angular，而且处于并行发展的阶段，这两者理念还有不小的差别，给不少人带来了困惑。\n\nPolymer切分组件的方式有点类似JSF，它跟HTML5标准中的Shadow DOM和Element有很大联系，这种切分组件的方式非常直观，每个组件都是端到端的，包含UI和逻辑，直接放置到某个界面上就能用，这种方式很容易被业务开发人员接受，但里面的时序比较难处理。\n\n比如说，有两个组件，里面各包含一个下拉框，有数据的联动关系，因为它们处在两个不同的组件里，联动的处理代码就很难写，考虑到组件的特点，要尽量隐藏自己的内部实现，所以从外部获取组件内部的某个元素要绕一层，而组件不能依赖其他外部的东西，所以到最后只有通过事件去实现，这个联动代码写好了应当放在哪里，也是个大问题。我们的例子仅仅是这么简单，就要绕这么个大圈子才能保证时序，如果场景比较复杂，非常难以控制。\n\n如果同样的组件在某个界面被复用多次，数据的一致性也很难保证，设想一下某个界面存在两个一样的下拉框，分别处于不同组件中，两者的数据都需要分别去加载，这个过程是有浪费的，更严重的是，如果这个下拉框对应的数据有更新，很难把每个实例都更新一遍，这个处理过程是非常麻烦的。\n\nAngular框架处理问题的方式跟它有所不同，它是水平分层，所有这些数据访问逻辑都跟UI彻底分离，所以可以很轻松地把这个逻辑代码写出来，这么一来，前面所述端到端的组件就彻底退化，变成只有界面展现了。\n\n看看刚才碰到的两个问题，第一个，模型代码按照业务领域进行划分，获取的数据放在两个不同的数组，然后通过双向绑定跟UI产生关联，如果UI上一个下拉框选中项发生变更，只需要监控这个取值项，然后更新另一个下拉框的取值列表即可，完全不需要绕弯子。即使这两个处于不同模型中，也可以用类似后端的方式，采用事件总线等机制去完成通信。\n\n第二个更简单了，复用的组件其实只有UI，也就是说，只有UI是多实例的，模型其实只有一份，比如说一个地区的树形结构，即使一个界面上同时有维护和使用两种功能，都可以共享同一份模型，当维护这边对数据进行了更新，就实时反馈到模型中，然后由双向绑定再把这个模型同步到界面上的使用方去，整个过程清晰可控。\n\n从协作关系上讲，很多前端开发团队每个成员的职责不是很清晰，有了前端的MV*框架，这个状况会大有改观。MV*框架的理念是把前端按照职责分层，每一层都相对比较独立，有自己的价值，也有各自发挥的余地。\n\n为什么多数做互联网前端开发的同学们感受不到MV*框架的重要性呢，因为在这个协作体系里，Model的这一块不够复杂，在传统软件领域，Model的部分是代码最多的，View的相对少一些，而互联网领域里，基本是相反的，所以Model这块沦为附加，如果主要在操作View和Controller，那当然jQuery这类框架比较好用了。\n\n所以，经常看到有互联网产品的同学们讲前端MVC，但举例的时候，都比较牵强，很多时候，他们举出来的那个Model，其实都不能算真正的Model，而是在操作View的过程中一些辅助的模型，真正的Model是贯穿前后端的。\n\n归根结底，前端MV*框架带来的是一整套工作流程的变更，后端工程师也可以编写前端的模型代码，把它跟后端彻底打通，交互工程师处理UI跟模型的互动关系，UI工作人员可以专注、无障碍地处理HTML源码，把它们以界面模版的形式提供给交互工程师。这一整套协作机制能够大大提高B/S架构系统的开发效率，如果再有外围的管控平台，生产效率将真正踏进工业化的阶段。\n\n到这个阶段，前端开发人员的出路是什么呢？我认为有两种。拿服装行业来对比，如果你要的是普通的，就使用工业手段批量生产，使用MV*框架，做好架构和组件重用，做得快，细节不是很讲究。如果你想要更好的，有特色的，就需要名家设计，手工打造，非常精巧，高端大气上档次。所以，这也就代表着前端开发的两种发展方向。\n \n"
  },
  {
    "path": "posts/2013-11-20-Web应用的组件化开发（一）.md",
    "content": "Web应用的组件化开发（一）\n====\n\n基本思路\n----\n\n#1. 为什么要做组件化？\n\n无论前端也好，后端也好，都是整个软件体系的一部分。软件产品也是产品，它的研发过程也必然是有其目的。绝大多数软件产品是追逐利润的，在产品目标确定的情况下，成本有两个途径来优化：减少部署成本，提高开发效率。\n\n减少部署成本的方面，业界研究得非常多，比如近几年很流行的“去IOE”，就是很典型的，从一些费用较高的高性能产品迁移到开源的易替换的产品集群，又比如使用Linux + Mono来部署.net应用，避开Windows Server的费用。\n\n提高开发效率这方面，业界研究得更多，主要途径有两点：加快开发速度，减少变更代价。怎样才能加快开发速度呢？如果我们的开发不是重新造轮子，而是每一次做新产品都可以利用已有的东西，那就会好很多。怎样才能减少变更代价呢？如果我们能够理清模块之间的关系，合理分层，每次变更只需要修改其中某个部分，甚至不需要修改代码，仅仅是改变配置就可以，那就更好了。\n<!--more-->\n我们先不看软件行业，来看一下制造行业，比如汽车制造业，他们是怎么造汽车的呢？造汽车之前，先设计，把整个汽车分解为不同部件，比如轮子，引擎，车门，座椅等等，分别生产，最后再组装，所以它的制造过程可以较快。如果一辆汽车轮胎被扎破了，需要送去维修，维修的人也没有在每个地方都修一下，而是只把轮胎拆下来修修就好了，这个轮胎要是实在坏得厉害，就干脆换上个新的，整个过程不需要很多时间。\n\n席德梅尔出过一款很不错的游戏，叫做《文明》（Civilization），在第三代里面，有一项科技研究成功之后，会让工人工作效率加倍，这项科技的名字就叫做：可替换部件（Replacement Parts）。所以，软件行业也应当引入可替换的部件，一般称为组件。\n\n#2. 早期的前端怎么做组件化的？\n\n在服务端，我们有很多组件化的途径，像J2EE的Beans就是一种。组件建造完成之后，需要引入一些机制来让它们可配置，比如说，工作流引擎，规则引擎，这些引擎用配置的方式组织最基础的组件，把它们串联为业务流程。不管使用什么技术、什么语言，服务端的组件化思路基本没有本质差别，大家是有共识的，具体会有服务、流程、规则、模型等几个层次。\n\n早期展示层基本以静态为主，服务端把界面生成好，浏览器去拿来展示，所以这个时期，有代码控制的东西几乎全在服务端，有分层的，也有不分的。如果做了分层，大致结构就是下图这样：\n\n![web1.0.png](https://raw.github.com/xufei/blog/master/assets/web-components/web1.0.png \"Web 1.0\")\n\n这个图里，JSP（或者其他什么P，为了举例方便，本文中相关的服务端技术都用Java系的来表示）响应浏览器端的请求，把HTML生成出来，跟相关的JavaScript和CSS一起拿出去展示。注意这里的关键，浏览器端对界面的形态和相关业务逻辑基本都没有控制权，属于别人给什么就展示什么，想要什么要先提申请的尴尬局面。\n\n这个时期的Web开发，前端的逻辑是基本可忽略的，所以前端组件化方式大同小异，无论是ASP还是JSP还是其他什么P，都可以自定义标签，把HTML代码和行间逻辑打包成一个标签，然后使用者直接放置在想要的地方，就可以了。\n\n在这一时代，所谓的组件化，基本都是taglib这样的思路，把某一块界面包括它的业务逻辑一起打成一个端到端的组件，整个非常独立，直接一大块从界面到逻辑都有，而且逻辑基本上都是在服务端控制，大致结构如下图所示。\n\n![components in web1.0.png](https://raw.github.com/xufei/blog/master/assets/web-components/components1.0.png \"Components in Web 1.0\")\n\n#3. SPA时代，出现了新问题\n\n自从Web2.0逐渐流行，Web前端已经不再是纯展示了，它逐渐把以前在C/S里面做的一些东西做到B/S里面来，比如说Google和微软的在线Office，这种复杂度的Web应用如果还用传统那种方式做组件化，很显然是行不通的。\n\n我们看看之前这种组件化的方式，本质是什么？是展现层跟业务逻辑层的隔离，后端在处理业务逻辑，前端纯展现。如果现在还这么划分，就变成了前端有界面和逻辑，后端也有逻辑，这就比较乱了。我们知道，纯逻辑的分层组件化还是比较容易的，任何逻辑如果跟展现混起来，就比较麻烦了，所以我们要把分层的点往前推，推到也能把单独的展现层剥离出来。\n\n如下图所示，因为实际上HTML、CSS、JavaScript这些都逐渐静态化，所以不再需要把它们放在应用服务器上了，我们可以把它们放在专门的高性能静态服务器上，再进一步发展，就可以是CDN（Content Delivery Network，内容分发网络）。前端跟后端的通信，基本都是通过AJAX来，也会有一些其他的比如WebSocket之类，总之尽量少刷新了。\n\n![web2.0.png](https://raw.github.com/xufei/blog/master/assets/web-components/web2.0.png \"Web 2.0\")\n\n在这张图里面可以看到，真正的前端已经形成了，它跟应用服务器之间形成了天然的隔离，所以也能够很独立地进行一些发展演进。\n\n现在很多Web程序在往SPA（单页面程序，Single Page Application）的方向发展，这类系统通常比较类似传统的C/S程序，交互过程比较复杂，因此它的开发过程也会遇到一些困难。\n\n那为什么大家要做SPA呢？它有很多明显的好处，最核心的优势就是高效。这个高效体现在两个方面：一是对于用户来说，这种方式做出来的东西体验较好，类似传统桌面程序，对于那些需要频繁操作的行业用户，有很大优势。二是运行的效率较高，之前集成一些菜单功能，可能要用iframe的方式引入，但每个iframe要独立引入一些公共文件，服务器文件传输的压力较大，还要初始化自己的一套内存环境，比较浪费，互相之间也不太方便通信，一般要通过postMessage之类的方式去交互。\n\n有了SPA之后，比如一块界面，就可以是一个HTML片段，用AJAX去加载过来处理之后放到界面上。如果有逻辑的JavaScript代码，也可以用require之类的异步加载机制去运行时加载，整体的思路是比较好的。\n\n很多人说，就以这样的需求，用jQuery再加一个异步js加载框架，不是很足够了吗？这两个东西用得好的话，也是能够解决一些问题的，但它们处理的并不是最关键的事情。在Web体系中，展现层是很天然的，因为就是HTML和CSS，如果只从文件隔离的角度，也可以做出一种划分的方式，逻辑放在单独的js文件里，html内部尽量不写js，这就是之前比较主流的前端代码划分方式。\n\n刚才我们提到，SPA开发的过程中会遇到一些困难，这些困难是因为复杂度大为提升，导致了一些问题，有人把这些困难归结为纯界面的复杂度，比如说，控件更复杂了之类，没有这么简单。问题在于什么呢？我打个比方：我们在电脑上开两个资源管理器窗口，浏览到同一个目录，在一个目录里把某个文件删了，你猜猜另外一个里面会不会刷新？\n\n毫无疑问，也会刷新，但是你看看你用的Web页面，如果把整个复杂系统整合成单页的，能保证对一个数据的更新就实时反馈到所有用它的地方吗？怎么做，是不是很头疼？代码组织的复杂度大为提高，所以需要做一些架构方面的提升。\n\n#4. 架构的变更\n\n提到架构，我们通常会往设计模式上想。在著名的《设计模式》一书中，刚开始就讲了一种典型的处理客户端开发的场景，那就是MVC。\n\n传统的MVC理念我们并不陌生，因为有Struts，所以在Web领域也有比较经典的MVC架构，这里面的V，就负责了整个前端的渲染，而且是服务端的渲染，也就是输出HTML。如下图所示：\n\n![struts-mvc.png](https://raw.github.com/xufei/blog/master/assets/web-components/struts-mvc.png \"Struts MVC\")\n\n在SPA时代，这已经不合适了，所以浏览器端形成了自己的MVC等层次，这里的V已经变成客户端渲染了，通常会使用一些客户端的HTML模版去实现，而模型和控制器，也相应地在浏览器端形成了。\n\n![struts-mvc.png](https://raw.github.com/xufei/blog/master/assets/web-components/spa.png \"Single Page Application\")\n\n我们有很多这个层面的框架，比如Backbone，Knockout，Avalon，Angular等，采用了不同的设计思想，有的是MVC，有的是MVP，有的是MVVM，各有其特点。\n\n以Angular为例，它推荐使用双向绑定去实现视图和模型的关联，这么一来，如果不同视图绑定在同一模型上，就解决了刚才所说的问题。而模型本身也通过某种机制，跟其他的逻辑模块进行协作。\n\n这种方式就是依赖注入。依赖注入的核心理念就是通过配置来实例化所依赖的组件。使用这种模式来设计软件架构，会牺牲一些性能，在跟踪调试的便利性等方面也会有所损失，但换来的是无与伦比的松耦合和可替代性。\n\n比如说，这些组件就可以单独测试，然后在用的时候随手引入，毫无压力。对于从事某一领域的企业来说，光这一条就足以吸引他在上面大量投入，把所有不常变动领域模型的业务代码都用此类办法维护起来，这是一种财富。\n\n#5. MV*框架的基本原理\n\n如果我们来设计Angular这么一个前端框架，应当如何入手呢？很显然，逻辑的控制必须使用JavaScript，一个框架，最本质的事情在于它的逻辑处理方式。\n\n我们的界面为什么可以多姿多彩？因为有HTML和CSS，注意到这两种东西都是配置式的写法，参照后端的依赖注入，如果把这两者视为跟Spring框架中一些XML等同的配置文件，思路就豁然开朗了。\n\n与后端不同的是，充当前端逻辑工具的JavaScript不能做入口，必须挂在HTML里才能运行，所以出现了一个怪异的状况：逻辑要先挂在配置文件（HTML）上，先由另外的容器（浏览器或者Hybird的壳）把配置文件加载起来，然后才能从某个入口开始执行逻辑。好消息是，过了这一步，逻辑层就开始大放异彩了。\n\n从这个时候开始，框架就启动了，它要做哪些事情呢？\n\n- 初始化自身（bootstrap）\n- 异步加载可能尚未引入的JavaScript代码（require）\n- 解析定义在HTML上的规则（template parser）\n- 实例化模型（scope）\n- 创建模型和DOM的关联关系（binding, injection）\n\n这些是主线流程，还有一些支线，比如：\n\n- 解析url的search字符串，恢复状态（route）\n- 加载HTML部件模板（template url）\n- 部件模板和模型的关联（binding）\n\n#6. 如何做组件化\n\n##6.1. HTML的组件化\n\nSPA的一个典型特征就是部分加载，界面的部件化也是其中比较重要的一环。界面片段在动态请求得到之后，借助模版引擎之类的技术，经过某种转换，放置到主界面相应的地方。所以，从这个角度来看，HTML的组件化非常容易理解，那就是界面的片段化和模板化。\n\n##6.2. JavaScript的组件化\n\nJavaScript这个部分有好几个发展阶段。\n\n- 早期的共享文件，把公共功能的代码提出出来，多个页面共用\n- 动态引用，消灭全局变量\n- 在某些框架上进一步划分，比如Angular里面又分为provider，service，factory，controller\n\nJavaScript组件化的目标是什么呢，是清晰的职责，松耦合，便于单元测试和重复利用。这里的松耦合不仅体现在js代码之间，也体现在js跟DOM之间的关系，所以像Angular这样的框架会有directive的概念，把DOM操作限制到这类代码中，其他任何js代码不操作DOM。\n\n![componentsinspa.png](https://raw.github.com/xufei/blog/master/assets/web-components/componentsinspa.png \"Components in SPA\")\n\n如上图所示，总的原则是先分层次，层内再作切分。这么做的话，不再存在之前那种端到端组件了，使用起来没有原先那么方便，但在另外很多方面比较好。\n\n##6.3. CSS的组件化\n\n这方面，业界也有很多探索，比如LESS，SASS，Stylus等。为什么CSS也要做组件化呢？传统的CSS是一种扁平的文本结构，变更成本较高，比如说想要把结构从松散改紧凑，需要改动很多。如果把实际使用的CSS只当作输出结果，而另外有一种适合变更的方式当作中间过程，这就好多了。比如说，我们把一些东西定义成变量，每个细节元素使用这些变量，当需要整体变更的时候，只需修改这些变量然后重新生成一下就可以了。\n\n以上，我们讨论了大致的Web前端开发的组件化思路，后续将阐述组件化之后的协作过程和管控机制。"
  },
  {
    "path": "posts/2013-12-02-一些JS题目的解答.md",
    "content": "一些JS题目的解答\n====\n\n在[这里](http://davidshariff.com/quiz/)看到一些测试题，我HTML和CSS比较一般，尝试把里面的JS题目都解答一下：\n\n#1.\n    \"1\" + 2 + \"3\" + 4\n\n- 10\n- 1234\n- 37\n\n答案：1234，加法优先级等同，从左往右，数字与字符串相加，数字转换成字符串进行运算，结果等同于：\"12\"+\"3\"+4 = \"123\"+4 = \"1234\"。\n<!--more-->\n#2.\n\n    4 + 3 + 2 + \"1\"\n\n- 10\n- 4321\n- 91\n\n答案：91，优先级同上，从左往右，等同于：7+2+\"1\" = 9+\"1\" = \"91\"。\n\n#3.\n    var foo = 1;\n    function bar() {\n    \tfoo = 10;\n    \treturn;\n    \tfunction foo() {}\n    }\n    bar();\n    alert(foo);\n\n- 1\n- 10\n- Function\n- undefined\n- Error\n\n答案：1，function的定义会提前到当前作用域之前，所以等同于：\n\n    var foo = 1;\n    function bar() {\n    \tfunction foo() {}\n    \tfoo = 10;\n    \treturn;\n    }\n    bar();\n    alert(foo);\n\n所以，在foo=10的时候，foo是有定义的，属于局部变量，影响不到外层的foo。\n\n参见：https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FFunctions_and_function_scope\n\n> Unlike functions defined by function expressions or by the Function constructor, a function defined by a function declaration can be used before the function declaration itself.\n\n#4.\n\n    function bar() {\n        return foo;\n        foo = 10;\n        function foo() {}\n        var foo = 11;\n    }\n    alert(typeof bar());\n\n- number\n- function\n- undefined\n- Error\n\n答案：function，与上题类似，等同于：\n\n    function bar() {\n        function foo() {}\n        return foo;\n        foo = 10;\n        var foo = 11;\n    }\n    alert(typeof bar());\n\n在return之后声明和赋值的foo都无效，所以返回了function。\n\n参见：https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return\n\n> A function immediately stops at the point where return is called.\n\n补充，这个解答有问题：\n\n> @尤里卡Eureka：JS中function声明和var声明都会被提前，最终得到结果为function，是因为*名称解析顺序-Name Resolution Order*(http://t.cn/8kcIRts导致的function声明优先级大于var声明，而不是由return语句退出导致最后的结果~\n\n#5.\n\n    var x = 3;\n\n    var foo = {\n        x: 2,\n        baz: {\n            x: 1,\n            bar: function() {\n                return this.x;\n            }\n        }\n    }\n\n    var go = foo.baz.bar;\n\n    alert(go());\n    alert(foo.baz.bar());\n\n- 1,2\n- 1,3\n- 2,1\n- 2,3\n- 3,1\n- 3,2\n\n答案：3,1\nthis指向执行时刻的作用域，go的作用域是全局，所以相当于window，取到的就是window.x，也就是var x=3;这里定义的x。而foo.baz.bar()里面，this指向foo.baz，所以取到的是这个上面的x，也就是1。\n\n参见：https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FOperators%2Fthis\n\n#6.\n\n    var x = 4,\n        obj = {\n            x: 3,\n            bar: function() {\n                var x = 2;\n                setTimeout(function() {\n                    var x = 1;\n                    alert(this.x);\n                }, 1000);\n            }\n        };\n    obj.bar();\n\n- 1\n- 2\n- 3\n- 4\n- undefined\n\n答案：4，不管有这个setTimeout还是把这个函数立即执行，它里面这个function都是孤立的，this只能是全局的window，即使不延时，改成立即执行结果同样是4。\n\n#7.\n\n    x = 1;\n    function bar() {\n        this.x = 2;\n        return x;\n    }\n    var foo = new bar();\n    alert(foo.x);\n\n- 1\n- 2\n- undefined\n\n答案：2，这里主要问题是最外面x的定义，试试把x=1改成x={}，结果会不同的。这是为什么呢？在把函数当作构造器使用的时候，如果手动返回了一个值，要看这个值是否简单类型，如果是，等同于不写返回，如果不是简单类型，得到的就是手动返回的值。如果，不手动写返回值，就会默认从原型创建一个对象用于返回。\n\n参见：https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new\n\n#8.\n\n    function foo(a) {\n        alert(arguments.length);\n    }\n    foo(1, 2, 3);\n\n- 1\n- 2\n- 3\n- undefined\n\n答案3，arguments取的是实参的个数，而foo.length取的是形参个数。\n\n参见：\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments/length?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FFunctions_and_function_scope%2Farguments%2Flength\n\n> arguments.length provides the number of arguments actually passed to a function. This can be more or less than the defined parameter count (See Function.length).\n\n#9.\n\n    var foo = function bar() {};\n    alert(typeof bar);\n\n- function\n- object\n- undefined\n\n答案：undefined，这种情况下bar的名字从外部不可见，那是不是这个名字别人就没法知道了呢？不是，toString就可以看到它，比如说alert(foo)，可以看看能打出什么。\n\n参见：\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FFunctions_and_function_scope\n\n> The function name can be used only within the function's body. Attempting to use it outside the function's body results in an error (or undefined if the function name was previously declared via a var statement).\n\n#10.\n\n    var arr = [];\n    arr[0]  = 'a';\n    arr[1]  = 'b';\n    arr.foo = 'c';\n    alert(arr.length);\n\n- 1\n- 2\n- 3\n- undefined\n\n答案：2，数组的原型是Object，所以可以像其他类型一样附加属性，不影响其固有性质。\n\n#11.\n\n    function foo(a) {\n        arguments[0] = 2;\n        alert(a);\n    }\n    foo(1);\n\n- 1\n- 2\n- undefined\n\n答案：2，实参可以直接从arguments数组中修改。\n\n参见：\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FFunctions_and_function_scope%2Farguments\n\n> The arguments can also be set\n\n#12.\n\n    function foo(){}\n    delete foo.length;\n    alert(typeof foo.length);\n\n- number\n- undefined\n- object\n- Error\n\n答案：number，foo.length是无法删除的，它在Function原型上，重点它的configurable是false。\n\n参见：https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete\n\n> delete can't remove certain properties of predefined objects (like Object, Array, Math etc). These are described in ECMAScript 5 and later as non-configurable\n"
  },
  {
    "path": "posts/2013-12-09-Web应用的组件化（二）.md",
    "content": "Web应用的组件化开发（二）\n====\n\n管控平台\n----\n\n在上一篇中我们提到了组件化的大致思路，这一篇主要讲述在这么做之后，我们需要哪些外围手段去管控整个开发过程。从各种角度看，面对较大规模前端开发团队，都有必要建立这么一个开发阶段的协作平台。\n\n在这个平台上，我们要做哪些事情呢？\n<!--more-->\n#1. HTML片段\n\n我们为什么要管理HTML片段？因为有界面要用它们，当这些片段多了之后，需要有个地方来管理起来，可以检索、预览它们，还能看到大致描述。\n\n这应该是整个环节中一个相对很简单的东西，照理说，有目录结构，然后剩下的就是单个的HTML片段文件了，这就可以解决存储和检索的问题了，但我们还要考虑更多。\n\n已有的HTML片段，如何被使用呢？这肯定是一种类似include的方式，通过某种特殊标签（不管是前端还是后端的方式）把这些片段引用进来，这时候就有了第一个问题：\n\n假设有界面A和界面B同时引用了片段C，在某个开发人员修改片段C内容的时候，他如何得知将会影响到界面A和B呢？一个比较勉强的方式是全项目查找，但这在很多情况下是不够的。\n\n如果我们的HTML片段是作为独立的公共库存在的，它已经不能通过项目内查找去解决这一问题了，因为不管A还是B，只要他不处于片段C的项目空间，就无从追寻。\n\n这时候很多人会问两个问题：\n\n1. 跨项目的界面片段重用，意义在哪里？\n\n    如果我们的产品是针对一个小领域，它的复杂度根本不需要划分多个项目部分来协作完成。设想场景是面对很大的行业，各项目都是子产品，将来可能是其中若干个联合部署，这时候，保持其中的一致性是非常重要的。比如我们有个基本配置界面，在多个子产品中都要用，如果各自开发一个，其操作风格很可能就是不一致的，给人的印象就是不专业。所以会需要把常见的界面片段都归集起来，供业务方挑选使用。\n\n2. 修改C，只提供说明，但是不通知A和B，不实时更新他们的版本，然后自行决定怎样升级，如何？\n\n    这会有一个问题，每次有小功能升级的时候，代码是最容易同步合并的，所以才会有“持续集成”这个概念，如果是一直伴随升级，总要比隔一个大阶段才升级好，升级成本应尽量分摊到平时，就像农妇养小猪，小猪每天长一点，每天都抱来抱去，不觉得吃力，即使长大了也还能抱得动。\n\n现在问题就很明确了，一定要有一种方式来把这个依赖关系管理起来，很显然，已有的版本库是肯定管不了这些的，所以只能在外围做一些处理。\n\n我们建立一个管理平台，除了管理实体文件的版本，还管它们之间的关系。具体这个关系如何收集整理，有两种方式：手动配置，代码分析。\n\n手动配置是比较土的方式，开发人员每提交一个文件，就去这系统上手动配置它的依赖关系。代码分析的话，要在每次提交文件的时候解析文件的包含规则，找出确切的文件。这两者各有利弊，前者比较笨，但容易做，后者对代码格式的要求比较高，要考虑的情况较多。\n\n我们的界面往往不是那么简单，HTML片段也可能有层次的，举例来说：\n\n界面A里面包含了片段B，但是片段B自身又包含了片段C，所以这个依赖关系也是有层级的，需要在设计的时候一并考虑。\n\n#2. JavaScript模块\n\nJavaScript代码的管理，比HTML片段的状况好一些，因为业界很多这方面的解决方案。但它们还是没有解决当依赖项产生变更的时候反向通知的问题。\n\n所以我们还是得像HTML片段一样，把它们的依赖关系都管理到平台里。于是，每个JavaScript模块都显式配置了自己所依赖的其他模块，通过这种单向关系，形成了一套完整的视图。\n\n在JavaScript模块的代码实现中，我们是不提倡直接写依赖关系的。很多通用规范，比如AMD，往往建议我们这样写模块：\n\n    define(['dep1', 'dep2'], function (dep1, dep2) {\n        var moduleA = function () {};\n        return moduleA;\n    });\n\n但我们的系统是面向行业的，比这种通用解决方案要苛刻一些。比如说，如果有一天重构代码，JavaScript模块们调整了目录或者名字，这么写的就痛苦了，他必须把所有影响到的都去调整一遍，这是要搜索替换的。况且，就像上面HTML模板的部分提到的，影响了处于其他项目中依赖它的代码，缺少合适的方式去通知他们修改。\n\n所以我们期望的是，在每个编写的JavaScript模块中只存放具体实现，而把依赖关系放在我们的平台上管理，这样，即使当前模块作了改名之类的重构处理，处于外部项目中依赖它的那些代码也不必修改，下一次版本发布的生成过程会自动把这些事情干掉。\n\n对应到上面的这段代码，我们需要开发人员做的只是其中的实现，也就是moduleA的那个部分，外面这些依赖的壳子，是会在发布阶段根据已配置的依赖关系自动生成的。\n\n如果需要，JavaScript模块还可以细分，比如类似Angular里面那样，把factory，controller和directive分离出来，这会对后续有些处理提供方便。\n\n现在我们有必要讨论一下模块的粒度了，我们这里提到的都是基本的粒度，每个JavaScript模块中存放的应该只有一个很具体东西的实现。那么，有个问题，在我们发布的时候，是不是就按照这个粒度发布出去呢？\n\n很显然不行，如果这么做，很可能会出现复杂界面一次要用10多个HTTP请求才能加载完它所需要的所有JavaScript代码的情况，所以需要做一些合并。\n\n那么，合并的策略是什么？在我们这个平台上，开发人员又是要怎样定义这个合并关系的呢？我们需要在模块之上定义一个更大粒度的组织方式，这个方式与模块的关系，就好比Java里面，jar文件与class的关系。如果开发人员不显式配置，也可以通过全局策略，比如按最下层目录来合并。\n\n这个时候，在实际使用这些代码的时候，需要带两个配置信息过去，一个是要动态载入的JavaScript文件（合并之后的），二是每个JavaScript文件中包含的原始模块。\n\n#3. 单元测试\n\n如果JavaScript模块都已经被良好有序管理起来，就可以为它们考虑单元测试的事情了。单元测试对于提高基础单元的可靠度，是有非常重要意义的。\n\n在我们这个平台里，可以把单元测试跟JavaScript模块关联起来，每个JavaScript模块可以挂一组单元测试代码，这些代码可以在线编写，在线运行。\n\n单元测试的本质就是编写模拟代码来调用已有模块，考虑到我们的模块是JavaScript，所以很多思路都倾向于在浏览器端执行它们，对于单个模块的单元测试，这不是个问题。\n\n如果要批量执行整个系统的单元测试，那就不一样了。把JavaScript代码先加载到浏览器中，然后再执行，很多时候并不需要这么复杂。我们完全可以在服务端把它们做了。\n\n借助Node.js的能力，我们可以在服务端执行JavaScript代码，也就意味着能够把绝大多数JavaScript模块的单元测试在服务端就执行掉。当然，我们为此可能要多做不少事情，比如说，有些库需要移植一份node版的，常见的有AJAX调用等等。\n\n注意了，能够在服务端做JavaScript单元测试是有先决条件的，代码的分层必须很良好，除了视图层，其他任何层面都不能操作DOM。所以我们这里主要测试的也正是除了视图层之外的所有JavaScript业务逻辑。至于视图层怎么办？这个真的很难解决，这世界上不是所有东西都能自动做的，只能先把可做的做了，以后再来考虑这些。\n\n#4. 文档和示例管理\n\n##4.1. 文档\n\n现在我们有HTML片段和JavaScript模块了，需要给它们多一些描述信息。简单描述显然是不够的，我们还要详细文档。\n\n这种详细文档可以通过某种方式生成，也可以由开发人员手动编写。与传统的离线文档不同，在线的文档更实时，并且，每当一个开发人员变更了他的文档之后，不需要经过全量构建，访问者可以实时访问到他的最新版本。\n\n熟悉GitHub的朋友们可能早已习惯这种方式，在项目库里面存在一些以md格式结尾的文本文件，使用markdown语法来编写一些说明文档。\n\n毫无疑问，这类格式很适合在线协作，所以我们也会在平台上集成这么一种编写文档的方式，无论是针对HTML模板还是JavaScript模块，或者是其他什么类型，甚至还可以用来当博客，就像[月影](http://blog.silverna.org/ \"\")同学的gitpress平台，能直接从GitHub上拉取文本或者HTML文件形成博客。\n\n文档除了以集成的形式浏览之外，应当也可以以单独链接的方式发出去，这时候用户就可以像看一个新闻网页一样去浏览。如果再进一步做下去，还可以做电子书的生成，提供打包的离线文档。\n\n##4.2. 示例\n\n在编写代码文档的过程中，可能免不了要插入示例，示例有两种形态，一种是纯文本，类似gist这样，一种是可在线运行，类似jsfiddle和jsbin这样。\n\n这两种都有各自的优点，所以可以都做，示例的存放可以与文档类似，也应当能通过一个链接独立运行。\n\n##4.3. 幻灯片\n\n有时候我们看到一些在线的幻灯片，觉得效果很帅，比如[reveal.js](http://lab.hakim.se/reveal-js/ \"reveal.js - The HTML Presentation Framework\")，我们的开发人员有时候作代码分析或者走查的时候也不免要写一些演示，如果能把这些东西也随项目管理起来，能在线查看，会是很不错的一件事。所以我们也可以考虑给它们加个存储界面，甚至做个简易的在线编写器。\n\n#5. 项目与目录管理\n\n说到现在，我们似乎还遗漏了一点什么。那就是以上提到的这些东西，以什么为组织单位来存储？\n\n考虑到我们的这个平台是要管理一整个大产品的全部前端内容的，它里面应该分了很多项目，对应到子产品上，这么一来，很自然地，项目就成了第一级组织单位。项目之下，没有悬念地，只有目录了。\n\n对于一个项目而言，它有哪些要做的事情呢？首先要能配置其实体存储位置。前面提到的这么多代码、文档之类，最终都是要实体存储的，怎么存？我们当然可以自己搞一套，在文件系统上做起来，但是还要考虑它们的版本管理，非常麻烦，所以不如直接对接某个版本库，调用它的接口去存取文件，这里配置的就是版本库的路径。\n\n其次，要考虑从已有项目复制，类似GitHub里面的fork功能，不过内部处理机制可以略有不同，fork的项目默认未必要有实体文件，只有当产生了修改或者新增操作的时候才创建，剩下的还引用原来的就可以了。我们这里的项目复制功能是为项目化版本而考虑的，经常出现一个产品版本支持多个客户项目的情况，所以可能会用得着这个特性。\n\n然后，也要考虑项目的依赖关系。依赖一个项目，意思是需要用到它里面的组件，所以实质是组件的依赖。提供项目依赖这个视图，只是为了未来变更的一些考虑。\n\n#6. 评论管理\n\n之前提到，我们整个平台的目的是为了提高大型前端团队的协作能力，协作是离不开交流的。上述的任何功能，都应当带有交流沟通的能力。\n\n比如说，如果开发人员A使用了其他人写的一个代码组件a，对其中一些细节有疑问，他应当可以对它进行评论。在他评论的时候，任何参与维护过这个组件的人员都能收到一个提醒，这时候他可以选择过来看看，回复这个疑问。同理，在文档、示例下也可以如此操作。\n\n在互联网上有这类产品，用于在任意URL下挂接评论交流系统，比较有名的就是[Disqus](http://disqus.com/ \"Disqus - The Web's Community of Communities\")，我们可以看到很多网站下面挂着它，用于做交流评论，这样用户可以用一个账号在多个网站之间交流。国内也有同类的，比如[多说](http://duoshuo.com/ \"多说 - 社会化评论系统\")，能够用微博、QQ等账号登录进行交流。\n\n从我们这个平台本身看，如果是部署在企业内部作流程提升，引入外部评论系统的可能性就比较小了。因为在企业内部用，一定是希望这个员工的账号信息跟工号挂钩，也能够跟版本服务器账号等模块作集成，权限也便于控制。\n\n从另外一个角度讲，某个人员登录这个系统的时候，他可能收到很多消息，来自不同的代码或文档位置，挨个点过去回复也有些麻烦，我们应当给他提供一个全局视图，让他能在一个统一的界面把这些问题都答复掉，如果他需要的话，也是可以点进去到实际的位置。\n\n#7. 用户和权限控制\n\n从以上部分我们已经看到，这个系统是一个比较复杂的开发过程管控平台。这样的话，每个使用的人就应当可以登录，然后分配不同的权限等级。\n\n未登录用户应当有一些东西的查看权限，但是不能发表评论。已登录的用户根据权限级别，可以控制能否创建、修改项目，创建、修改目录，代码，单元测试，文档等。\n\n#8. 国际化字符串管理\n\n一个跨语言区域的Web应用不可避免要跟国际化打交道，这个事情通常是在服务端做，比如通过在界面代码中嵌入类似<% =getRes(key, lan) %>这样的代码，去获取相应的字符串，替换到界面里来。\n\n这个事情是要占用应用服务器资源的，而且国际化本身其实是一个在运行之前就已经确定的事，完全可以把这个过程放在发布阶段就做掉。比如说，我们给每种语言预先就把代码生成多份，只是部署在一起，根据需要的情况来动态加载特定的那一份。\n\n有不少客户端的国际化方案，是把资源文件拆细，以页面为单位存储，但这其实是不太合理的。第一个原因就是在Web2.0时代，“页面”这个概念本身就已经弱化了，到了单页应用里，整个应用都只是一个页面，这个时候，资源文件以什么粒度来组织呢？\n\n我们提到过，采用MV*框架去做Web应用的架构，有一个目标是做组件化。组件化的意图就是某个组件可以尽可能随心所欲地放在需要的地方用。如果把资源文件的粒度弄小到对应HTML片段和JavaScript模块这一级，灵活性倒是有了，带来的问题就是管理成本增大。\n\n做一个行业应用，最重要的就是业务一致性，这包括逻辑的一致性，也包括了术语的一致性。某一个词，可能在多个资源文件中都出现，这就增加了不一致的可能性。\n\n所以，应当有一个统一的术语管理平台，一切界面上出现的文字或者提示，都必须来自这个平台。\n\n#9. 静态资源的管理\n\n在发布系统的时候，除了需要发布代码，还需要发布图片等静态资源，这些东西也应当被管理起来。\n\n静态资源在两种情况下可用：随产品发布，在本平台被引用。比如说有一个图片，在这个平台上作了管理，它可以被配置到某个项目上，在发布的时候导出。这个图片还可以被用链接的方式查看或者下载，如果本平台内部的一个文档或者示例要引用它，也是可以的。\n\n#10. 样式与主题管理\n\n在Web系统里，样式和主题是很重要的一环。样式的管理和发布一直是一个比较复杂的话题，早几年一般都是分块写，然后组合合并，最近这些年有LESS，SASS和Stylus这类技术，解决了编写和发布的分离问题。\n\n我们看看发布的最大问题是什么？是不同部分的合并。为了追求灵活性，不得不把东西拆得很细，之前HTML片段和JavaScript模块的处理方式都是这样。这么做，我们就需要另外一件事：这些细小的东西，尽可能要覆盖全面。\n\n对应到CSS里面，我们要做的是把每种在系统中可能出现的元素、类别都作为单独的规则维护起来，生成一个全局的规则列表。不同项目间，实现可以不同，但规则的名字是固定的，定制只允许修改实现，不允许修改规则。如果要新增之前没有的规则，也必须在全局规则列表里先添加，再作实现。\n\n样式规则被管理之后，可以在界面组件上对它作关联，也可以不做。做的好处是发布的时候能只把用到的那些样式规则生成发布出去，如果能接受每次发布全量CSS，那也无所谓。\n\n除了规则，也需要考虑一些变量的管理，在CSS中合理使用变量，会大为减轻定制化所导致的工作量。\n\n#11. 一键发布\n\n我们引入了这么一堆东西，其实是增加了发布的复杂度。为什么呢？\n\n之前不管HTML、JavaScript还是CSS，都是手写出来，最多经过一个minify的工作，就发布了，整个过程很简单，两句脚本搞定。\n\n现在可复杂了，先要分析依赖关系，然后提取文件，然后国际化字符串替换，然后合并，然后代码压缩，整个过程很折腾，不给配置管理员一个解释的话，他一定过来砍人。\n\n我们有个原则：解决问题的过程中，如果引入了新的问题，要求负责解决原问题的人也一起解决掉。现在为了一些意图，增加了版本发布的复杂度，那也要有个办法再把这事摆平，至少不能比原来复杂。\n\n所以我们就要把这些过程都集成到管控平台里，做一个一键发布的过程，把所有的这些操作都集成起来，配置管理员发布版本的时候只要点一下就可以把所有这些事情做掉。甚至说，这些流程还可以配置，能够加减环节。\n\n这时候我们做到了跟之前发版本一样方便，能不能多做点什么呢？\n\n可以把JavaScript单元测试集成到版本发布阶段。因为我们已经把JavaScript按照职责做了分层，并且把UI部分做了隔离，就可以在浏览器之外把这个单元测试做掉，平时提交代码的时候也可以做，最终在版本发布阶段再全量做一下，也是很有意义的。\n\n代码依赖关系管理的另一个目的是什么呢？是最小化发布，既然我们都管理了文件之间的关系，那么，从根出发，显然是能够得出哪些代码文件在本项目中使用的，就可以每次从我们的全量代码库中取得确切需要的一部分来发布。这也是我们整个管控平台带来的优势。\n\n#12. 小结\n\n我们这一篇比较复杂，提出了一整套解决大规模前端协作的管控机制。这套理论的本质是在开发和版本发布之间加了一个环节，把Web体系中除了服务之外的一切静态资源都纳入其中，强化了现有主流的一些基于命令行的前端工程化组织模式。\n\n相比于传统行业，比如汽车制造，我们这个环节相当于生产流水线的设计，其中一些组件的存储就类似仓储机制，发布就类似出厂过程。\n\n这个平台本身还有不少其他的可做的东西，比如甚至可以在上面做界面的可视化定制等，这些是长远的终极目标，在后面的文章里会谈谈一些考虑。\n\n后续文章中，我们会展望有了这个平台之后，整个前端的协作流程是怎样的。"
  },
  {
    "path": "posts/2014-01-06-影响企业应用前端开发效率的因素.md",
    "content": "影响企业应用前端开发效率的因素\n====\n\n原先是在知乎上回答一个[问题](http://www.zhihu.com/question/22426434/answer/21433867 \"\")的，整理了放这里：\n\n我们来分析一下究竟哪些因素让企业应用的前端开发这么困扰。\n\n先看看界面部分吧。\n\n#1. 命令式还是声明式\n毫无疑问，就写界面来说，声明式的代码编写效率远高于命令式：\n\n\t<Panel title=\"Test\">\n\t  <Button label=\"Click me\"/>\n\t</Panel>\n\n\tPanel p = new Panel();\n\tp.title = \"Test\";\n\tButton b = new Button();\n\tb.label = \"Click me\";\n\tp.add(b);\n\n第一种容易写，容易理解。\n<!--more-->\n#2. 控件标签集\n不管你的软件面向什么行业，至少都要一些控件，或者是基本的表单输入，或者是复杂的比如树形表格，里面还可以跨行跨列渲染的。\n\n如果我们有一套映射到控件的标签，那么写代码是肯定会简单很多的，比如说，在HTML里面没有原生的Panel，那么，刚才第一段代码可能就要变成：\n\n\t<div class=\"panel panel-default\">\n\t  <div class=\"panel-heading\">\n\t    <h3 class=\"panel-title\">Simple HTML Loader</h3>\n\t  </div>\n\t  <div class=\"panel-body\">\n\t    <button>Click me</button>\n\t  </div>\n\t</div>\n\n我们为了使得界面代码编写更高效，毫无疑问会倾向于把这么一堆东西简化成一个Panel标签，这样就会逐步建立一套面向自己行业的标签集。\n\n#3. 带逻辑的控件\n刚才这个例子为什么简单呢，因为它只是一个普通容器，静态的，不带逻辑，所以即使你用什么静态模板也能解决问题。如果复杂一点，是一个TabNavigator，就要考虑切换的事件，再复杂一些是个树形表格，那就更麻烦了。\n\n我们来看jQuery提供的插件方式实现TabNaviator：\n\n\t<div id=\"tabs\">\n\t  <ul>\n\t    <li><a href=\"#tabs-1\">Nunc tincidunt</a></li>\n\t    <li><a href=\"#tabs-2\">Proin dolor</a></li>\n\t    <li><a href=\"#tabs-3\">Aenean lacinia</a></li>\n\t  </ul>\n\t  <div id=\"tabs-1\">\n\t  </div>\n\t  <div id=\"tabs-2\">\n\t  </div>\n\t  <div id=\"tabs-3\">\n\t  </div>\n\t</div>\n\n\t  <script>\n\t  $(function() {\n\t    $( \"#tabs\" ).tabs();\n\t  });\n\t  </script>\n\n从我个人的角度看，这种代码很愚蠢。蠢在何处呢？HTML这类声明式的界面描述语言，写起来本来应当直观一些的，但是被这么一搞，又往命令式的方向去了。而且两种东西混杂，声明和渲染居然分了两处，又增加了维护的成本。\n\n难道就没有别的办法来解决这个问题吗？\n\n我们看看其他语言和框架，比如Flex和Silverlight。\n\n\t<mx:TabNavigator id=\"tn\"  width=\"100%\" height=\"100%\">\n\t  <!-- Define each panel using a VBox container. -->\n\t  <mx:VBox label=\"Panel 1\">\n\t    <mx:Label text=\"TabNavigator container panel 1\"/>\n\t  </mx:VBox>\n\n\t  <mx:VBox label=\"Panel 2\">\n\t    <mx:Label text=\"TabNavigator container panel 2\"/>\n\t  </mx:VBox>\n\n\t  <mx:VBox label=\"Panel 3\">\n\t    <mx:Label text=\"TabNavigator container panel 3\"/>\n\t  </mx:VBox>\n\t</mx:TabNavigator>\n\n上面这段是Flex里面的TabNavigator，在这个链接底部有运行结果：TabNavigator\n\n为什么它可以看不到逻辑的代码，但是又确实能有动作呢，因为它的实现类是mx.containers.TabNavigator，在这个代码里，可以自己手动去处理一切内部实现，但是暴露给业务开发人员的就是这么简单的标签。\n\n我们看看在HTML和JS这个体系里用什么办法去解决。不要提JSF这类服务端技术，因为它的思路也是不好的，展示代码的生成和渲染都不在一个地方，会有很多问题。\n\n#4. Polymer与Angular\n\n早期IE里有HTC，也就是HTML Components，因为别的浏览器厂商不喜欢，所以快要消亡了。在W3C新的HTML规范里，有一个Web Components，参见这里：Introduction to Web Components\n\n这个东西跟HTC的思想本出同源，它引入了Custom Elements和Shadow DOM这两个概念，也就是说，我可以自定义一个标签，然后在内部随便怎么折腾，用这个标签的人可以很方便。\n\n很美好，是不是，但是只适用于比较新的浏览器，基于这个理念架构的框架Polymer的目标也只是支持一些比较新的浏览器。Polymer\n\n那么怎么办呢？我们还有Angular，它也可以自定义标签，然后用directive的方式写内部实现。\n\n\t<tabs>\n\t  <pane title=\"Localization\">\n\t  </pane>\n\t  <pane title=\"Pluralization\">\n\t  </pane>\n\t</tabs>\n\n\t<script id=\"components.js\">\n\t  angular.module('components', [])\n\n\t    .directive('tabs', function() {\n\t      return {\n\t        restrict: 'E',\n\t        transclude: true,\n\t        scope: {},\n\t        controller: function($scope, $element) {\n\t          var panes = $scope.panes = [];\n\n\t          $scope.select = function(pane) {\n\t            angular.forEach(panes, function(pane) {\n\t              pane.selected = false;\n\t            });\n\t            pane.selected = true;\n\t          }\n\n\t          this.addPane = function(pane) {\n\t            if (panes.length == 0) $scope.select(pane);\n\t            panes.push(pane);\n\t          }\n\t        },\n\t        template:\n\t          '<div class=\"tabbable\">' +\n\t            '<ul class=\"nav nav-tabs\">' +\n\t              '<li ng-repeat=\"pane in panes\" ng-class=\"{active:pane.selected}\">'+\n\t                '<a href=\"\" ng-click=\"select(pane)\">{{pane.title}}</a>' +\n\t              '</li>' +\n\t            '</ul>' +\n\t            '<div class=\"tab-content\" ng-transclude></div>' +\n\t          '</div>',\n\t        replace: true\n\t      };\n\t    })\n\n\t    .directive('pane', function() {\n\t      return {\n\t        require: '^tabs',\n\t        restrict: 'E',\n\t        transclude: true,\n\t        scope: { title: '@' },\n\t        link: function(scope, element, attrs, tabsCtrl) {\n\t          tabsCtrl.addPane(scope);\n\t        },\n\t        template:\n\t          '<div class=\"tab-pane\" ng-class=\"{active: selected}\" ng-transclude>' +\n\t          '</div>',\n\t        replace: true\n\t      };\n\t    })\n\t</script>\n\n这么一来，也就有些接近我们的目标了，看到现在，我们还记得目标是什么吗？是尽可能精简的面向领域的容器和控件标签集，有了这个，写界面代码才能更简单。\n\n#5. 为什么HTML默认标签集这么小\n\n事情结束了吗？没有呢。我们的HTML体系为什么标签集这么小？因为他要解决的是通用领域的东西，怎样才能通用呢？要的是尽可能无歧义。\n\n怎样的东西会没有歧义？那就是它的含义尽可能少，比如说单行文本输入框，总没人对它有歧义吧，它无非就是可以设置最大最小长度，是否只读，是否禁用，最多通过某种规则来限制输入字符，最多最多，也就这些可做的了，大家都认同。\n\nButton就不同了，一开始他是\n\n    <input type=\"button\" value=\"Click\"/>\n    \n后来大家想要各种各样的button，于是开放了\n\n    <button></button>\n\n这样的标签，可以在里面写各种HTML，我记得当时很多人在中间加上下和左右两层marquee，简直玩坏了。\n\n现在HTML里面又有了数字输入，日期时间输入这样的东西，数字的没什么疑问，就是最大最小值，步进值等等，日期时间这个就复杂了，它怎么做，都有人不满意。有人要日期排左边，有人要时间排上面，有人只要年和月，有人只要分和秒。有人要点空白表示选中，有人要双击日期表示选中，还有人想用农历、波斯历、尼泊尔历，简直没完了，还不如不做，谁要谁自己做……\n\n所以，面向各领域的人们，自己动手，丰衣足食吧。\n\n#6. 界面修饰\n\n好了，控件集的问题解决了，我们来看看界面的修饰。\n\n你们发现没有，不管用什么非HTML的标签体系，可能写代码会很快，但是有时候要修饰界面，比如只是调整一下所有容器的边距，某些按钮的圆角之类，就会生不如死。\n\n这时候你会发现，HTML里面的CSS真是神器，什么都能干，而且是面向切面的，只要你的HTML结构是良好的，完全不需要调整这个层面的代码。为什么其他体系的CSS没有这么强呢？比如说Flex也可以写CSS，QT也可以写CSS。\n\n因为CSS的部分实在是太复杂了，复杂到整个浏览器里面绝大部分的代码都在处理这方面的东西，像Google的Chrome团队有1000多人，别的体系没法有这么大投入，只能看着羡慕。\n\n上次看到一个问题，近30年来软件开发体系有哪些本质的改进？我觉得CSS真的可以入选，这是一个把结构和展现完全分离的典范，并且实现得很好。\n\n我们的前端开发一般都是面向某个领域的，不管什么领域，CSS方向都可以有一个很独立的规划，因为它可以不影响界面的结构。所以这个方面，其实不太会对前端开发造成太多压力，压力只集中在维护CSS的人群身上。\n\n好了，上面扯了那么多，其实到现在还在界面的层次，一直没有去谈到真正的逻辑。那么，最让我们困扰的部分是哪里呢？\n\n#7. 模块化和加载\n\nWeb前端开发有个最苦闷的事情就是选型，因为HTML这个体系很开放，提供的默认能力又不是很足够，如果要做复杂交互的东西，会需要很多额外的工作。有各种框架从各种角度来解决问题，但怎么把这些东西整合到正好符合自己的需要，是一个很花精力的事情，很多时候恨不得自己把全部轮子都造一遍。\n\n真正的开发工作中，跨浏览器，踩各种坑应该是最烦闷的事，其他部分，如果有做好自己领域里标签的定义，或者不用标签用其他方式，应该不算特别困难。\n有人说JavaScript语言本身比较松散，所以写业务逻辑比较头疼，这不算大问题。基于B/S的开发，有一个大坑是你在运行的时候要先把代码加载过来，然后才能跑。你看那些C/S软件，有这困扰吗？再看看后端程序员，谁还要关心自己的代码执行之前要做的事情？\n\n所以后端程序员写前端代码，都情不自禁地会引入一大堆库。我们形象一点来描述一下这个过程：\n\n嗯，大家都用jQuery，我也引入，抄了两段代码发现真不错。咦，我要个树控件，网上逛了一圈，拿了个zTree回来。再埋头苦干半个小时，缺数据表格控件，于是过了一会，jQuery UI被整体引入了。再埋头苦干，上网乱点了点，浏览器跳出个广告，一看叫做Kendo UI，看看发现不错，引进来再说，用里面的某个控件。又过了一阵，听说最近Angular很火啊，看了看例子，表单功能怎么那么强，我也要用！捣鼓捣鼓又加进去了。项目里又要用图表库，看了半天眼睛都花了，百度的ECharts不错哦，引进来。哎呀我界面怎么那么丑，人家的怎么那么清爽，查看源码，一看，Bootstrap，去官网一看，真乃神器，不用简直对不起自己。\n\n没多久之后，这个界面已经融合了各种主流框架，代码写法五花八门，依赖了几M的JS库，更要命的是里面某些JS有冲突，某些样式也互相覆盖，快疯了。\n\n这里有哪些问题呢？\n\n- JS代码要先加载到界面才能执行，而这么几M的代码加载过来就要好久了，然后每个框架还要把自己初始化，又耗不少时间，半分钟之后自己写的JS才开始执行，用户等得都快怀孕了。\n- 不管是JS还是CSS，都应当控制基准的代码，这件事的主要意义是避免冲突，因为整个体系都比较松散，如果不加控制，就会造成冲突。即使在服务端写Java，也有类签名一致性之类的问题，所以这个部分必须要重视。\n\n刚才这两点，第二点暂时不是我们要探讨的范围，第一点，引出的话题就是异步加载，这是一个可以展开说很多的话题，也不再说了。异步加载和缓存是面对复杂场景必做的优化措施。\n\n但是这个里面规范就有好几种，具体实现方式就更多了。ES6的module也许可以解决这个问题。harmony:modules [ES Wiki]\n\n#8. 逻辑的分层\n\n网站型和应用型Web程序对分层的需求是不一样的。网站型的逻辑大部分都在处理UI，而应用型可能有很多业务逻辑，这部分需要更好的组织，以便复用，或者即使我们的目标不包括复用，为了这个代码的可维护性，也需要有比较好的组织方式。\n\n本质上这些组织方式与传统的客户端软件开发没什么不同，主要要做的无非就是UI层的隔离，或者模板化，或者别的什么方式。纯逻辑的代码大家都会写，但这个逻辑怎么跟界面产生关系，这是个问题。\n\n有些框架通过在HTML元素上设置额外属性，然后启动的时候读取，在框架内部做一些相关的事情，比如Angular、Avalon和Knockout。有的框架在视图层中让开发人员手动去处理界面，就像未引入框架的那样，比如Backbone，两者是各有利弊的。\n\n前面这种，一般功能是会很强大，但是它自身所做的东西必须足够多，多得帮你做掉绝大部分本来该自己做的事，你才会特别爽。所以，用这类框架来做表单型应用的时候，是会非常舒服的，因为这些需求他做框架的时候能预见，所以比如校验、联动、存取之类的都会处理掉。假如你要做一个绘图类应用，这就麻烦了，不管你是用Canvas还是SVG，它所能帮到的都不多。这时候，后面这类可能反而适合一些。\n\n这些数据分层框架的原理是什么呢？是要做一层表单与数据的对应关系，所以他要检测数据的变动，比如一个Object，它某个值变更了，要去把对应的界面更改之类。这里面也有很多的坑，可以一步一步踩过来。。。\n\n到现在，我大致可以回答你的问题，什么情况下前端开发会比较轻松呢？\n- 针对自己领域的界面标签库比较完善，或者易于扩展\n- 样式容易调整，并且独立于界面元素\n- 逻辑模块化，层次分明，在某种统一规范上存在大量可用库\n咦，我这三点好像在说微软的WPF体系吗？"
  },
  {
    "path": "posts/2014-01-20-为什么企业应用这么“钟情”于IE6.md",
    "content": "为什么企业应用这么“钟情”于IE6\n====\n\n企业内部的信息化，很多起步都很早，但B/S化基本都是从2000年以后开始的，之前有各种客户端，比如基本的Win32 API开发，或者Delphi体系（VCL等，C++ Builder也算在内），微软的几个封装化的体系（ATL，MFC，还有昙花一现的JFC），Java体系（AWT、Swing，SWT等），这些技术本身都比较成熟，IDE的支持也不错，但最终，很大一部分迁移到Web上了。\n\n迁移到Web的最直接因素是部署成本，B/S架构有天然的部署优势，无需分发，这一点人所共知，但另外还有个重要因素是开发成本。很多人对这一点不相信，为什么呢，因为现在前端码农也不便宜啊，怎么就他降低开发成本了？\n<!--more-->\n早期的Web界面并不花哨，所以对一些技巧的要求远不如现在高，而且只存在一个主流浏览器（IE6），更是无需更多的知识，逻辑基本都放在后端，前端要做的事情基本只有写表单和表格类的HTML，从这个角度看，门槛确实低得可以，写标签的难度远远低于用高级语言写界面。\n\n在这种情况下，大量的表单类应用就被迁移到B/S模式了，其中有时候会出现复杂一些的需求，比如流程的建模，在对VML不熟悉的情况下，很多这种东西会被开发成一个独立的客户端，跟B/S系统连到同样的数据库，变通解决这个问题。但其实在IE6里面，VML本身已经非常可用了，所以这时候也有很多图形化的东西用VML做，比如我自己05年写的这个，抓了个录像在这里：http://xufei.github.io/assets/iom.swf\n\n作为企业应用，另有个重要的事情就是代码的组件化复用，比如说，上面我这个录像里面的树型结构和选项卡，其实给开发人员用的时候很简单：\n\n    <z:tree id=\"tacheTree\"></z:tree>\n    \n    document.getElementById(\"tacheTree\").loadXML(xml);\n\n这两句分别是界面中的声明跟JS代码中的调用，为什么可以这么写呢，是因为IE中有HTC（HTML Component），可以自己用HTML和JS定义标签，只需要用命名空间引入就可以。这个其实是非常有用的功能，一般的企业都必定会在此基础上有所积累，即使现在的Web Components，也没有比它高端到哪里去。\n\n以上这些是从开发视角看的，作为一个运行平台来说，IE6已经非常足够，布局有些小问题，大家其实无所谓，用点技巧也可以摆得比较整齐。如果有大模块的集成，就来个iframe，也都工作得很好。\n\n我们再来看看为什么很多企业软件固守IE体系。提到企业软件，大家经常有个误会，认为企业软件的前端就都是照着IE6来开发的，其实不是这样，企业软件产品是有延续性的，在已有产品上做升级的代价非常大，比如说一套很深入使用了IE only特性的系统，想要迁移到跨浏览器上，这个代价有多大？首先要构建同等复杂度的控件库，然后把功能逐个迁移，最后还要考虑一些已经被废弃了但是暂时没有替代方案的东西，整个过程是非常痛苦的。\n\n很多时候，并不是开发人员意识不到这些问题，而是他没有解决办法。技术上的困难，经过一些努力都是可以克服的，更大的困难在于软件升级的成本。比如说，你评估了把代码修改成跨浏览器，需要50个人做一年，谁为这个过程买单？如果你站在企业决策者的角度，是会搞50个人来做一年，还是让你的2000个员工都稍微克服一下，只使用IE来访问系统？\n\n即使你说这次升级完了就解决了现在的跨浏览器问题，如何确保以后再出个什么浏览器也能支持？如何保证再也不会伤筋动骨地修改代码？谁也说不准未来是什么样，所以，他只有到了不得不升级的时候，才会考虑做这件事。会是现在吗？不一定，因为即使很新版本的IE，也还有兼容模式，他只要还能用下去，就不太有动力去折腾。\n\n那么，我们再看看企业软件开发商是如何处理这个问题的。毫无疑问，企业软件开发商是希望你每年都升级的，因为传统的软件都是一次买断，然后收点维护费，客户是卖一个少一个，但你想升级，那就得交钱，谁会跟钱过不去呢？从这些企业长久的发展来说，它也会让自己的产品逐步去贴近标准，老的产品就这样了，你总不能现在开发个东西还说IE only吧，那估计都不好意思拿出去卖。所以说，这类企业在开发新系统的时候，反而会采用更激进的策略，比如说我就从某个基线往上支持，IE10+，Chrome 27+之类。在江苏电信的CRM系统里，从2011年开始就是推荐客户使用Firefox来访问系统的，大家去电信营业厅办业务，可以观察一下营业员使用的浏览器，其实在很多方面，传统软件厂商也不像外界猜测的那么固步自封。 \n\n很多时候，从企业软件开发商的角度看，新的浏览器标准并未带来多少比IE6为代表的“低端浏览器”更有价值的特性，SVG取代了VML，Web Components取代了HTML Components，多了一些储存、文件之类的本地接口，还有摄像头之类偶尔用得上的东西，之前大家用Flash的Socket，现在变成WebSocket，该XMLHTTP的还是XMLHTTP。HTML5体系中提升最大的标签语义化、布局和样式等在这个领域带来的震撼并不强烈，最有价值的反倒是JS性能上的大幅提升。"
  },
  {
    "path": "posts/2014-04-27-基于AngularJS的企业软件前端架构.md",
    "content": "基于AngularJS的企业软件前端架构\n====\n\n这篇是我参加QCon北京2014的演讲内容：\n\n## 提纲： ##\n \n企业应用在软件行业中占有很大的比重，而这类软件多数现在也都采用B/S的模式开发，在这个日新月异的时代，它们的前端开发技术找到了什么改进点呢？\n \nB/S企业软件前端开发模式大体上与桌面软件类似，都是偏重量级的，在前端可能会有较多的业务逻辑，这些业务逻辑如何被合理模块化，与界面分离，以便测试，成为这个领域的一个重要挑战。另一方面，由于企业应用的界面相对规整，偏重的是数据存取，没有太多花哨的东西，所以常见的界面控件也是可枚举的，如何让开发界面的工作能更快完成，甚至由不擅长编写代码的业务设计人员来做，与界面原型的工作合二为一，能提高不少开发效率。\n \n在AngularJS等MV*框架出现之后，给这个领域带来一些契机，架构师们能够有机会去重新规划前端的架构，甚至是开发流程，从而让整个软件的生产更为高效。\n \n本文将探讨它给这个领域带来的变化。\n<!--more-->\n## 正文： ##\n \n### 企业应用前端的特点\n \n企业应用系统是一种很常见的软件系统，这类系统的特点是面向某个行业，功能较复杂，对界面的要求一般是整齐，不追求花哨。这类系统通常有C/S和B/S两个流派，其中的B/S方式因为部署和集成的便利，使用得较为普遍。\n \n同样是在浏览器中做东西，写企业应用和网站的差别也很明显。企业应用的业务逻辑较重，前端有一定的厚重性，但是对效果并不追求很多，主要是各类控件的使用，表单的存取值等等。\n \n企业应用产品的一些特点如下：\n \n- 独占模式。\n \n一般用户使用互联网产品，都是片段时间使用，比如购物或者阅读，做完之后就刷新或者关闭浏览器了，而企业应用往往是工作的全部，从早上上班开始打开，到下班才关掉，一天绝大部分工作都在上面完成，比如一个呼叫中心的操作员。\n \n- 重业务，轻视觉\n \n企业应用对视觉的追求是比较低的，一般不会要求花哨效果，以业务操作的流畅性为第一目标。\n \n- 界面规整，模式单一\n \n企业应用的界面布局相对有模式可循，可以用很少的场景来穷举，界面横平竖直，比较规整，使用到的控件元素也是可穷举的，基本没有什么特效。\n \n- 键盘操作\n \n由于企业应用的用户都相对比较专业，在上岗之前需要经过统一培训，而且每个用户使用的频度较高，很多时候他们会用尽量快捷的方式来做操作，比如键盘，这一点在互联网产品中比较少见。所以，有时候大家为了追求好看，把系统原生的select用div来替换，在这种情况下反而增加了用户的麻烦。\n \n- 逻辑复杂\n \n我之前所在的行业中，业务逻辑很复杂，前端可能会需要写很多复杂的逻辑，JS代码大部分是在处理逻辑，而不是界面交互。\n \n- 加载速度的侧重不同\n \n互联网产品往往很重视首屏优化，但是其策略可能与企业应用不同。比如说，3个200k的模块，在网站型产品中可能优化成一个100k加三个150k的模块，但在企业应用中，很可能优化成一个400k加三个50k的模块。为什么会这样呢？因为内容型的网站讲究的优化策略是分摊，如果首次加载太慢，会很影响用户的信心，但企业应用用户的容忍度是较高的，他并不在乎刚打开的时候慢一些，因为打开了之后就要用一天，对于之后每步操作的模块加载速度倒是要求很高。另外，对于内存泄露的处理，也要求得比较高一些。整个这些策略，其实是来源于C/S系统的影响。\n \n- 浏览器版本相对宽松\n \n很多时候提到企业应用，大家的想法就是低端，IE6，但其实这个的原因是客户只购买软件，运维一般自己做，每年不会有很多持续的投入来改进，所以导致很多老系统不能持续升级。软件厂商其实反倒可以用更激进的策略去升级浏览器，用户对这个的接受度还是比较高的，使用系统的群体也是比互联网用户小很多的，抛弃老旧浏览器的事情也确实可以干，比如我就见过几年前某电信营业系统预装的都是Firefox。\n \n### 企业应用常见的前端框架\n \n在开发B/S企业应用前端的人群中，有很大一部分群体选择了服务端的组件化方式，比如JSF之类，它的弊端是与异构服务端的第三方系统集成比较麻烦。也有不少人使用Bindows和ExtJS这样的框架，最近的KendoUI也是个不错的选择。\n \n每种类型选一个有代表性的来说说：\n \n- HTC 在浏览器端扩展标签\n \n早期有些团队采用的方式，一般会跟XMLHTTP等结合使用，易于使用，界面代码整洁，但已被主流浏览器抛弃。\n \n- JSF等 在服务端生成界面\n \n以后端为主的架构师最推崇的方式，受Struts的MVC模型影响很深，弱化了前端，使得前端蜕化为后端的一种附属。\n \n- GWT 编译阶段生成界面\n \n写其他语言来生成HTML和JS，一般会依赖于一种前端UI库。这种方式也比较受后端架构师喜欢，因为他们觉得写JS很头疼，宁可写Java。\n \n- ExtJS 用JS封装界面组件，干脆就不要HTML了\n \n这是另外一种极端，从Bindows开始，使用纯逻辑代码来描述界面，走着跟Java Swing一样的道路，也有不少人喜欢。但这种方式在没有好用的界面设计器的情况下非常痛苦。\n \n- Flex等 脱离HTML体系，另辟蹊径\n \n这条路其实是对Java Applet的一种延续，好处是可以不受HTML体系的制约，独立发展，所以其实这些体系在企业应用领域的成熟度远超HTML体系。\n \n### 曾经的企业B/S应用几件宝\n \n有一段时间，我们几乎只有IE6，所以那个时候的前端开发人员很快乐，没有兼容的压力。那时候，我们如何构建前端应用呢？\n \n参见[http://weibo.com/1858846672/B1fL3vuYN?mod=weibotime](http://weibo.com/1858846672/B1fL3vuYN?mod=weibotime \"这条微博\")\n \n- HTC\n \n这是最好用的声明控件的方式。\n \n- XMLHTTP\n \n尽管还没有AJAX的概念，但我们已经可以用它做前后端分离的传输机制了。\n \n- VML\n \n在IE里面画矢量图，不使用插件，有其他选择吗？\n \n- XSLT\n \n把XML数据转换成HTML，跟现在的前端模板像吗？\n \n- popup\n \n创建右键菜单最好的方式。\n \n[用这些技术构建的一个典型企业应用](http://xufei.github.io/assets/iom.swf \"用这些技术构建的一个典型企业应用\")\n \n### 单页应用和前端分层\n \n当时这些系统的构建方式也可以算单页应用，我们用iframe来集成菜单，每个菜单有自己独立的功能，整个主界面是始终不会刷新的。\n \n时光飞逝，这些年，前端有了什么本质的改变，产生了翻天覆地的变化吗？\n \n有时候我们回顾一下，却发现多数都是在增加完善一些细节，真正有颠覆性的有比如以RequireJS和SeaJS为代表的模块定义和加载库，npm这样的包管理器，grunt，gulp，百度fis这样的集成开发模式。为什么它们算是本质改进呢？\n \n因为这些标志着前端开发从粗放的模式，逐渐变化到精确控制的形态。比如我们再也不能不管代码的依赖关系，也不能一打开界面就不分青红皂白把所有可能要用到的代码都立刻加载过来，那个时代已经过去了，从任何角度讲，现代的前端开发都在精细化，从代码的可控，到界面体验的精细优化，到整个团队甚至公司甚至互联网上的组件共享，以及前端团队协作流程的改进，这已经是一个很成规模的产业了。\n \n我们把眼光放到2013年，在这一年里最火的前端技术莫过于NodeJS和AngularJS，前者给我们带来的是一种开发方式的改变，后者是一种典型的前端分层方案。Angular是前端MV*框架的一个流派，用过的人都会觉得很爽。它爽在什么地方呢？因为它帮我们做的事情太多了，一个双向绑定，无所不包，凡是存取值相关的操作，基本都不用自己写代码。在企业应用前端功能里，表单的存取值和校验占据了很大的比例，这些事都不用干了，那简直太好了。\n \n如果就因为这个用Angular，那还有些早。有一些第三方代码被称为库，另外一些称为框架，Angular是框架而不是库。框架的含义是，有更强的约束性，并非作为辅助功能来提供的。\n \n先看一下企业应用的通常形态吧，会有一个可配置的菜单，然后多半会采用MDI的形式，能打开多个业务功能，用选项卡的形式展示起来，可以随时切换操作。每个人每天常用的功能是可以穷举的，他进入系统之后，一般要用到下班才关掉。所以这种系统非常适合做成单页应用，开始的时候加载一个总体框架，每点击一个菜单，就加载这个菜单对应的功能模块，放在一个新的选项卡或者别的什么地方展示出来。\n \n在早期做这种系统的时候，一般都会用iframe来集成菜单，这种方式很方便，但是每个菜单页都要载入共同的框架文件，初始化一个环境，数据之间也不能精确共用。\n \n所以现在我们做企业信息系统，不再适合用iframe来集成菜单，所有菜单的业务代码，会在同一个页面的作用域中共存。这在某些方面是便利，比如数据的共享，一个选择全国城市的下拉框，在多个功能中都存在，意味着这些城市的数据我们可以只加载一次。但从另外一个角度来说，也是一种挑战，因为数据之间产生干扰的可能性大大增加了。\n \n我们回顾一下在传统的客户端开发中是怎么做的，早在经典的《设计模式》一书中，就提到了MVC模式，这是一种典型的分层模式。长期以来，在Web开发人员心中的MVC，指的都是Struts框架的那张图，但我们单页应用中的MVC，其实更接近最原始的《设计模式》书中概念。所以我们要在前端分层，而不仅仅把整个前端都推到视图层。\n \n做单页应用，前端不分层是很难办的，当规模扩大的时候，很难处理其中一些隐患。分层更重要的好处是能够从全盘考虑一些东西，比如说数据的共享。跨模块的数据共享是一个比较复杂的话题，搞得不好就会导致不一致的情况，如果考虑到在分层的情况下，把各种数据来源都统一维护，就好办多了。\n \n所以，以AngularJS为代表的前端MV*框架最重要的工作就是做了这些对于分层的指导和约束性工作，在此基础上，我们可以进一步优化单页应用这类产品。\n \n### 前端的自定义标签体系\n \n构建一个大型企业应用，最重要的是建立整套组件体系。一般针对某行业的软件，长期下来都会有很多固定的模式，可以提炼成组件和规则，从前端来看，体现为控件库和前端逻辑。控件库这个是老生常谈，在很多框架里都有这个概念，但各自对应的机制是不同的。\n \n从写一个界面的角度来讲，最为便利的方式是基于标签的声明式代码，比如我们常见的HTML，还有微软的XAML，Flex中的MXML等，都很直接，设想一下在没有可视化IDE的情况用类似Java Swing和微软WinForm这样的方式编写界面，毫无疑问写XML的方式更易被接受。所以，我们可以得出初步的结论，界面的部分应该写标签。\n \n很遗憾，HTML自带的标签是不足的，它有基本表单输入控件，但是缺乏DataGrid，Tree之类更富有表现性的控件。所以绝大多数界面库，都采用某种使用JavaScript的方式来编写这类控件，比如：\n \n```HTML\n<div id=\"tabs\">\n  <ul>\n    <li><a href=\"#tabs-1\">Nunc tincidunt</a></li>\n    <li><a href=\"#tabs-2\">Proin dolor</a></li>\n    <li><a href=\"#tabs-3\">Aenean lacinia</a></li>\n  </ul>\n  <div id=\"tabs-1\">\n  </div>\n  <div id=\"tabs-2\">\n  </div>\n  <div id=\"tabs-3\">\n  </div>\n</div>\n```\n \n```JavaScript\n$(function() {\n    $( \"#tabs\" ).tabs();\n});\n```\n \n如果这样，这些复杂控件就都要通过JavaScript来创建和渲染了，这与我们刚才提到的原则是违背的。那我们寻找的是什么呢，是一种能扩展已有HTML体系的东西。在早期，IE浏览器中有HTC，可以通过引入命名空间来声明组件，现在的标准浏览器中又引入了Web Components，在Polymer这个框架中可以看到更多的细节。说到底，这类方式要做些什么事情呢？\n \n- 隔离组件的实现，让使用变得简单\n- 支持自行扩展新的组件\n- 作一些作用域上的隔离，比如Web Components里面，style标签上可以加作用域，表示这个样式只生效于组件内部\n \n从另外一个角度讲，为什么我们非要这么做不可？最大好处来自哪里？对于大型项目而言，管理成本和变更成本都是需要认真考虑的。如果一个组件，需要在DOM中声明一个节点， 然后再用一个js去获取DOM，把DOM渲染出来，再填充数据的话，这个过程的管理成本是很大的，因为HTML和JS这两个部分丢了一个都会有问题，无论在什么时候，维护一个文件总是比维护多个文件要强的，我们看HTC那种方式，为什么它的使用成本很低，因为它可以把控件自身的DOM、逻辑、样式全部写在自己内部，整个一个文件被人引用就可以了。在现在这个阶段不存在这么好用的技术了，只能退而求其次。\n \n所以，在这个点上，Angular带来的好处是可扩展的标签体系，这也就是标签的语义化。Angular的主打功能之一是指令，使用这种方式，可以很容易扩展标签或者属性。比如，业务开发人员可以直接写：\n \n```JavaScript\n<panel>\n     <tree data=\"{{data}}\"></tree>\n</panel>\n```\n \n这样多么直观，而且可以跟原有的HTML代码一起编写，不造成任何负担。语义化的标签是快速编写界面的不二法门。\n \n### 业务逻辑\n \n有了语义化标签之后，如果我们只写界面不写逻辑，那也够了，但现实往往没有这么美好，我们还要来考虑一下业务逻辑怎么办。\n \n企业应用一般都是面向某行业的，在这个行业内部，会有一些约定俗成的业务模型和流程，这些东西如何复用，一直是一个难题。以往的做法，会把这些东西都放在服务端，用类似Java这样的语言来实现业务元素、业务规则和业务流程的管理。\n \n这种做法所带来的一个缺点就是对界面层的忽视，因为他只把界面层当作展示，对其中可能出现的大量JavaScript逻辑感到无所适从。很多从事这一领域的架构师不认同界面层的厚度，他们认为这一层只应当是很薄的，纯展示相关的，但在这个时代，已经不存在真正轻量级的界面了。\n \n前面提到，我们在前端作分层，把展现层跟业务逻辑层完全隔离，带来的好处就是逻辑层不存在对DOM的操作，只有纯粹的逻辑和远程调用，这么一来，这一层的东西都可以很容易做测试。对于一个大型产品来说，持续集成是很有必要的，自动化测试是持续集成中不可缺少的一环。如果不做分层，这个测试可能就比较难做，现在我们能把容易的先做掉，而且纯逻辑的代码，还可以用更快的方式来测试。\n \n之前我们做前端的单元测试，都需要把代码加载到浏览器来执行，或者自行封装一些“无头浏览器”，也就是不打开实际的展示，模拟这个测试过程。这个过程相对来说还是有些慢，因为它还有加载的这个网络传输的过程，如果我们能在服务端做这个事情呢？\n \n我们看到，最近很火的NodeJS，它从很多方面给了前端工程师一个机会，去更多地把控整个开发流程，在我们这个场景下，如果能把针对前端逻辑的单元测试都放在node里做，那效率就会更高。\n \n### 二次开发平台\n \n我们来看看，有了这么一套分层机制，又有了界面标签库之后，该做些什么呢？\n \n做企业软件的公司，有不少会做二次开发平台，这个平台的目标是整合一些已有的行业组件，让业务开发人员甚至是不懂技术的业务人员通过简单的拖拉、配置的形式，组合生成新的业务功能。\n \n从界面的角度看，拖拽生成很容易，很多界面原型工具都可以做，但要如何整合数据和业务？因为你要生成的这个功能，是实实在在要拿去用，不是有个样子看就可以，所以要能跟真实数据结合起来。 但这事情谈何容易！\n \n就比如说，界面上有一个选择所属行业的下拉框，里面数据是配置出来的，对这个数据的查询操作在后端，作为一个查询服务或者是业务对象管理起来，有些传统的方式可能是在后端作这个关联，Angular框架可以把这个事情推到前端来。相比Backbone这样的框架来说，Angular由于有双向绑定，这个过程会变得特别省事。一个界面片段想要和数据关联起来，要做的事情就是各种属性的设置，所以动态加载和动态绑定都会比较容易。\n \n比如：\n \npartial.html\n```HTML\n<ul>\n     <li ng-repeat=\"item in items\">{{item.name}}</li>\n</ul>\n```\n \nmain.html\n```HTML\n...\n<div ng-include=\"'partial.html'\" ng-controller=\"CtrlA\"></div>\n...\n```\n \na.js\n```JavaScript\nfunction CtrlA($scope) {\n    $scope.items = [{name:\"Tom\"}, {name:\"Jerry\"}];\n}\n```\n \nb.js\n```JavaScript\nfunction CtrlB($scope) {\n    $scope.items = [{name:\"Donald\"}, {name:\"Micky\"}];\n}\n```\n \n在上面的例子里，这个列表显示什么，完全取决于ng-controller=\"CtrlA\"这句，如果我们把这句搞成配置的，就很容易把数据源换成另外一个CtrlB，甚至说，即使在同一版本上做项目化，引入另外一个包含CtrlA其他版本的js文件，也基本无需更改其他代码，这就达到了二次开发的一个目的：尽可能以配置而不是编码去新增、维护新功能。\n \n### 移动开发\n \n现在的企业软件已经不能只考虑PC的浏览器了，很多客户都会有移动办公的需求。响应式设计是一种常见的解决方案，但是在企业应用领域，想要把复杂的业务功能设计成响应式界面的代价太大了，况且界面设计本身就是开发企业软件的这些公司的短板，所以我们的比较简单的办法是对PC和移动终端单独设计界面，这样就有了一个问题了，这两种界面的业务逻辑并没有差别，如果我们要维护两套代码，代价是非常大的，能有什么办法共用一些东西呢？\n \n如果不采用分层的形式，那这个很麻烦，我们注意到两种系统的差异只在UI层，如果我们用分层的模式，可以共用UI层以外的东西。具体到Angular里面来说，比如service，factory，甚至controller都是可以共用的，只有directive和HTML模板随设备产生差异就可以了。\n \n之前我们很少看到有基于Angular的移动端开发框架，但现在有了，比如Ionic，使用这样的框架，可以直接引用已有的业务逻辑代码，只在展示上作一些调整。这么做有很多好处，同时也对代码的架构水准有一定要求，需要把业务逻辑跟界面展示完全切割开。\n \n这样带来的好处也是很明显的，独立的业务逻辑，因为它不依赖于界面了，所以很容易控制，做单元测试，集成测试，打桩等等，总之它是纯逻辑的东西，在后端可以用什么方式保证代码质量，在前端的业务逻辑也一样可以用，业务逻辑可以因此而清晰稳定。\n \n对于企业应用而言，这么做可以极大程度地复用以往的业务逻辑，只在负责最终展示的代码部分作差异化。\n \n### 工程化\n \n上面这些技术性的问题都解决了，剩下的都是规模带来的边际效应，这需要我们从工程化角度去考虑很多问题：\n \n- 某个JS模块被修改，如何得知会影响谁？\n- 某个界面片段被调整，会影响什么界面？\n- 如何最小化发布？\n- 如何一键测试、打包、压缩？\n- 。。。。。。\n \n这些话题，篇幅所限，不在本文中叙述，可以查看我另外的关于Web应用组件化的文章。 \n"
  },
  {
    "path": "posts/2014-04-27-清华故地重游.md",
    "content": "清华故地重游\n====\n\n2014年4月底在北京参加QCon，27号抽空回清华看了一遍，10多年没回来，很感慨，没想到正好还碰上校庆了。学校里面的路居然还记得，打车从东门进，到主楼附近下车了往里走，那些年的回忆涌上心头。\n\n这是四教，入学时候英语分级考试就在这。第一节正式的课程：王致勇老师的《无机化学》也是在这里上。\n\n![四教](http://ww1.sinaimg.cn/mw690/6ecbbfd0gw1efwu1fxhokj21kw166qti.jpg)\n\n这是四教和五教中间的过道。有次下雪，同学提醒我小心点，我说，你见过龙王被水呛的吗？话音未落立刻摔在地上，被群嘲了。\n\n![四教五教中间的过道](http://ww1.sinaimg.cn/mw690/6ecbbfd0gw1efwu1h45ayj21kw1667wh.jpg)\n\n这是刚才的路口。有一次骑车路过，右前方的一个人急速左拐，我没刹得住车，撞在他后座，从他车上面飞了过去，手心都是血。\n\n![五教路口](http://ww2.sinaimg.cn/mw690/6ecbbfd0gw1efwu0qk3msj21mg17ce81.jpg)\n\n这是9号楼，计算机系的，后面是10号楼。\n\n![9号楼](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu0v6l8hj21mg17cnpd.jpg)\n\n入学的时候在这个地方，有志愿者迎接新生。\n![入学迎新](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu1ot39kj21mg17cx6p.jpg)\n\n体育课一般在这上。\n![体育课](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu1jhoa5j21mg17cu0x.jpg)\n\n到东面来了，左边是9号楼，右边是10号楼，在这个楼下丢过好几辆自行车。\n![9号楼10号楼中间](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu0tre1sj21mg17cu0x.jpg)\n\n10号楼的门口，变成办公楼了。\n![10号楼](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu0x1gglj21mg17ckjl.jpg)\n\n10号楼427，从98年住到99年，进门左手边靠窗的上铺，现在是办公室了，没进去。\n![427](http://ww2.sinaimg.cn/mw690/6ecbbfd0gw1efwu14qf6uj21mg17c1br.jpg)\n\n宿舍斜对面的洗手间，亮亮在这里一边洗衣服一边欢快唱歌，现在他是海归副教授了，当年也有唱ws歌的一面，哈哈。\n![wc](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu13pyk9j21mg17cne6.jpg)\n\n那时候宿舍没电话，家里打电话过来的时候，宿管喊427徐飞电话，然后一边答应说来了来了，一边飞奔下来。\n![楼梯口](http://ww2.sinaimg.cn/mw690/6ecbbfd0gw1efwu15mv34j21mg17ch7k.jpg)\n\n这个地方以前是8食堂，在这吃饭次数很多。\n![原来的8食堂](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu0rxegtj21mg17ckjl.jpg)\n\n那时候这里是个小店，有卖汽水的，我这种乡下孩子从来没喝过芬达和苹果汁，西瓜汁，不知道那个叫什么，只见过可乐和雪碧，后来听到站在我前面的来自深圳的赵铌同学说，才学着说要苹果汁。\n![小卖部](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu1e49v3j21mg17ckjl.jpg)\n\n在这学排球的，学不会，被老师训，标语很震撼人心，每个人入学的时候都默默算了一下吧？\n![体育系](http://ww1.sinaimg.cn/mw690/6ecbbfd0gw1efwu1i2esaj21mg17c7wh.jpg)\n\n那时候这里是平地，在这军训，很大一片操场。\n![东操场](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu1bret3j21mg17cb29.jpg)\n\n10食堂，做化学实验回来一般会在这吃饭，河对面的树林里当时有练某某功的，好奇围观了一次。\n![10食堂](http://ww1.sinaimg.cn/mw690/6ecbbfd0gw1efwu0ybl2xj21mg17ce81.jpg)\n\n28号楼，机械系在里面，某师兄的宿舍在这。\n![28号楼](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu126j1ej21mg17ckjl.jpg)\n\n我们材料系在这，23号楼，住306，现在要刷卡进去，没能进。\n![23号楼](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu0znzibj21mg17ce81.jpg)\n\n23号楼背后,99年短学期有一次整个宿舍回来晚了,没喊宿管,武涛从这爬上去,然后挨个拉我们上去,那时候没有栏杆.\n![23号楼背后](http://ww1.sinaimg.cn/mw690/6ecbbfd0gw1efwu10wt2qj21mg17chdt.jpg)\n\n这是14还是15食堂？经常跟材81的吴光麟一起在这吃。那会旁边有一个店卖饼干，3块一斤，很划算。\n![丁香园](http://ww2.sinaimg.cn/mw690/6ecbbfd0gw1efwu17ypvdj21mg17c1kx.jpg)\n\n二校门\n![二校门](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu18z6i6j21mg17c7wh.jpg)\n\n一教，在这里上CAD课程，学autocad14\n![一教](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu1nkp5bj21mg17ce81.jpg)\n\n日晷和大礼堂\n![日晷](http://ww2.sinaimg.cn/mw690/6ecbbfd0gw1efwu1d0ejpj21mg17c7wh.jpg)\n\n学堂，制图课程好像在这，也有在水利馆的\n![学堂](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu1m74a1j217c1mghdt.jpg)\n\n同方部\n![同方部](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu1l0pwzj217c1mge81.jpg)\n\n大礼堂西面的池塘\n![池塘](http://ww4.sinaimg.cn/mw690/6ecbbfd0gw1efwu16t420j21mg17ce81.jpg)\n\n自清亭\n![自清亭](http://ww1.sinaimg.cn/mw690/6ecbbfd0gw1efwu1r09ucj21mg17ce81.jpg)\n\n朱自清雕像\n![雕像](http://ww3.sinaimg.cn/mw690/6ecbbfd0gw1efwu1pxaxkj21mg17ce81.jpg)\n\n水木清华\n![水木清华](http://ww1.sinaimg.cn/mw690/6ecbbfd0gw1efwu1f2ygjj21mg17c4po.jpg)\n\n荷塘月色\n![荷塘月色](http://ww2.sinaimg.cn/mw690/6ecbbfd0gw1efwu1ahyv7j21mg17chdt.jpg)\n\n其实这一片我很少来，太文艺了。。。\n\n下午还去亮亮的办公室坐了会，大家都跟以前有些不一样了，他送我们下楼的时候，在电梯碰到个女生，叫他李老师好，我就在犹豫要不要把李老师当年的糗事说出来，哈哈哈哈。\n"
  },
  {
    "path": "posts/2014-05-20-前端架构那些事儿.md",
    "content": "前端架构那些事儿\n====\n\n在谈前端架构之前，需要先探讨一下不同人群对前端产生的困惑。前端这个职业最近几年才逐渐被认可，之前一直是低端的代名词，所以多数高手很不屑搞这个。之前的很多项目，人们对前端这块的要求也只是能用就行，所以很少会在上面去细致、深入地建立一套完善体系。而多数产品的技术经理也会是后端出身的，往往对前端的认识还停留在Java Struts那个原始的MVC模型上，或者首先想到的就是GWT和JSF，这是从后端角度出发的一种视角。用这类思维方式做出来的产品，一般用户体验都不会很好。\n\n另一方面，从界面层上手的人群，他对用户体验这方面会把控得比较好，但通常缺架构意识，或者说是软件工程的意识。在界面层比较复杂的情况下，很可能会有失控的趋势。对整个系统结构的认知程度通常不够深入，也缺乏设计模式等方面的知识。\n\n开发人员会有一些困惑：\n\n- 创建项目的时候，一般没有人作前端的技术选型\n\n    - 拿到项目之后，没有直接可复制的基础版本\n\n- 习惯于引用第三方组件\n\n    - 赶功能，需要某个组件或者特效\n    - 上网搜到一个合适的，加进来\n    - 它还依赖一些别的库\n    - 文件大还是次要的\n    - 可能会产生冲突，样式也不一致\n\n开发经理也会有一些困惑：\n\n- 协作过程感觉有问题\n\n    - 前端人员写原始界面，包含静态界面和特效\n    - 开发人员接着改，加逻辑\n    - 发现有问题要返工了\n    - 在谁的代码基础上继续改？如何合并？\n\n- 2014年了，为什么还有这么多人工环节？\n\n    - 能自动单元测试吗？\n    - 能自动发布打包吗？\n\n用户会对这些事情感到烦恼：\n\n- 长得丑\n\n    - 界面老土\n    - 风格不一致\n\n- 速度慢\n\n    - 加载慢\n    - 渲染慢\n    - 执行慢\n\n- 出错\n\n架构的本质是什么？其实也是一种管理。通常我们所说的管理，都是指对于任务和人员的管理，而架构管的是机器和代码。比如说，机器的部署属于运维的物理架构，SOA属于服务架构，那么，前端的架构指什么呢？\n\n长期以来，前端所处的位置是比较偏应用层，而且是很薄的一层，而架构又要求深度和广度，所以之前在前端里面做架构，好比在小水塘里游泳，稍微扑腾两下就到处碰壁。但最近这几年来，前端的范围被大大拓展了，所以这一层逐渐变得大有可为。\n\n怎样去理解架构呢？在早期的文字MUD游戏里，有这么一句话：“你感觉哪里不对，但是又说不上来。”在我们开发和使用软件系统的过程中，或多或少会遇到这样的感觉，有这种感觉就说明架构方面可能有些问题。\n\n在狭义的前端领域，架构要处理的很重要的事情是组件的集成。由于JavaScript本身缺乏命名空间这样的机制，多数框架都倾向于自己搞一套，所以这方面的碎片化是很严重的。如果一个公司的实力不足以自研所有用到的组件，就会一直面临这方面的问题。\n\n比如说，在做某个功能的过程中，发现需要一个组件，时间来不及做，就到网上搜了个，加到代码里面，先运行起来再说。一不小心，等功能做完的时候，已经引入了无数种组件了，有很多代码是重叠的，可能有的还有冲突，外观也不一致。\n\n环顾四周的大型互联网公司，基本上都有自己的前端框架，比如阿里的Kissy和Arale，腾讯的JX，百度的Tangram，360的QWrap等，为什么？因为要整合别的框架，并且在此基础上发展适合自己的组件库，代价非常大，初期没办法的时候只能凑合，长期来说，所有代码都可控的意义非常重要。\n\n那么，是不是一套框架可以包打天下呢，这个真的很难。对于不同的产品形态，如果想要用一套框架去适应，有的会偏轻，有的又偏重，有的要兼容低端浏览器，有的又不要，很难取舍。\n\n常见的前端产品形态包括：\n\n- 内容型Web站点 侧重渲染方面的优化，前端逻辑比重小\n- 操作型B/S系统 以数据和逻辑为中心，界面较规整\n- 内嵌Web的本地应用 要处理缓存和一些本地接口，包括PC客户端和移动端\n\n另外有Web游戏，因为跟我们的企业形态关系不大，而且也比较独特，所以不包含在内。这三种产品的前端框架要处理的事情显然是不太一样的，所以可以细分成2-3种项目模板，整理出对应的种子项目，供同类产品初始化用。\n\n最近我们经常在前端领域听说两个词：全端、全栈。\n\n全端的意思是，原来的只做在浏览器中运行的Web程序不够，还要做各种终端，包括iOS，Android等本地应用，甚至PC桌面应用。\n\n为什么广义的前端应当包含本地应用呢？因为现在的本地应用，基于很多考虑，都变成了混合应用，也就是说，开发这个应用的技术，既包含原生的代码，也包含了嵌入的HTML5代码。这么一来，就造成了开发本地应用的人技能要求较广，能够根据产品的场景，合理选择每个功能应当使用的技术。\n\n现在有一些PC端的混合应用开发技术，比如node-webkit和hex，前者的典型应用是Intel® XDK，后者的典型应用是有道词典，此外，豌豆荚的PC客户端也是采用类似技术的，也有一些产品是用的qt-webkit。这类技术可以方便做跨平台，极大减少开发工作量。\n\n所以，我们可以看到，在很多公司，开发安卓、iOS应用的人员跟Web前端的处于同一个团队中，这很大程度上就是考虑到这种情况。\n\n全栈的意思是，除了只做在浏览器中运行的代码，还写一些服务端的代码，这个需求又是从哪里来的呢？\n\n这个需求其实来自优化。我们要优化一个系统的前端部分，有这么一些事情可以做：\n\n- HTML结构的优化，减少DOM树的层次等等\n- CSS渲染性能的优化，批量写入DOM变更之类\n- 资源文件的优化，比如小图片的合并，图像格式的处理，图标字体的使用等\n- JavaScript逻辑的优化，模块化，异步加载，性能优化\n- 加载字节量的优化，主要是分摊的策略\n- HTTP请求的优化\n\n这里面，除了前三条，其他都可能跟后端有些关系，尤其是最后一条。但是前端的人没法去优化后端的东西，这是不同的协作环节，所以就很麻烦。\n\n那么，如果有了全栈，这个问题可以怎么解决呢？\n\n比如说，我们要做最原始的小文件合并，可以在服务器做一些配置，把多个合并成一个请求，比如天猫的某个url：\n\nhttp://g.tbcdn.cn/kissy/k/1.4.1/??dom/base-min.js,event-min.js,event/dom/base-min.js,event/base-min.js,event/dom/touch-min.js,event/dom/shake-min.js,event/dom/focusin-min.js,event/custom-min.js,cookie-min.js?t=1.js\n\n这个就很明显是多个文件合并而成的，9个小文件的请求，合并成了一个64k的文件请求。\n\n这种简单的事情可以在静态代理服务器上配置出来，更复杂的就比较难了，需要一定的服务端逻辑。比如说，我们有多个ajax请求，请求不同的服务，每个请求的数据量都非常少，但因为请求数很多，可能会影响加载性能，如果能把它们在服务端就合并成一个就好了。但这个优化是前端发起的，传统模式下，他的职责范围有限，优化不到服务端去，而这多个服务很可能是跨产品模块的，想要合并，放在哪个后端团队都很怪异。\n\n这可真难办，就像老虎追猴子，猴子上了树，老虎只能在下面干瞪眼。但是如果我们能让老虎上树，这就不是个问题了。如果有这么一层NodeJS，这一层完全由前端程序员控制，他就可以在这个地方做这种合并，非常的合理。\n\n除此之外，我们常常会用到HTML模板，但使用它的最佳位置是随着产品的场景而不同的，可能某个地方在前端更好，可能某个地方在后端好些。到底放在哪合适，只有前端开发人员才会知道，如果前端开发人员不能参与一部分后端代码的开发，优化工作也还是做不彻底。有NodeJS之后会怎样呢，因为不管前端模板还是后端模板，都是JavaScript的，可以使用同一套库，这样在做调整的时候不会有代码迁移的烦恼，直接把模板换地方即可。\n\n现在，也有很多业务场景有实时通信的需求，目前来说最合适的方案是Socket.io，它默认使用NodeJS来当服务端，这也是NodeJS的一个重要使用场景。\n\n这样，前端开发人员也部分参与了运行在服务端的代码，他的工作范围从原先客户端浏览器，向后拓展了一个薄层，所以就有了全栈的称呼。至于说这个称呼还继续扩展，一个前端开发人员从视觉到交互到静态HTML到JavaScript包办的情况，这个就有些过头了。\n\n以上这些，主要解决的都是代码层面的事情。另外有一个方面，也是需要关注，但却常常不能引起重视的，那就是前端的工程化问题。\n\n早期为什么没有这些问题？因为那时候前端很简单，复杂度不高，现在整个很复杂了，就带来了很多管理问题。比如说整个系统的前端都组件化了之后，HTML会拆分成各种模板，JavaScript会拆分成各种模块，而CSS也通过LESS或者SASS这种方式，变成了一种编译式的语言。\n\n这时候，我们考虑一个所谓的组件，它就比较麻烦了。它可能是一个或者多个HTML模板，加上一个或者多个JavaScript模块，再包含CSS中的一部分构成的，而前两者都可能有依赖项，三个部分也都要避免与其他组件的冲突。\n\n这些东西都需要管理，并且提供一种比较好的方案去维护。在JavaScript被模块化之后，也可以通过单元测试来控制它们的质量，并且把这个过程自动化，每次版本有变更之前，保证它们最基本的正确性。最终，需要有一种自动化的发布机制，把这几类代码提取，打包合并，压缩，发布。\n\n这个主题展开可以讲很多，所以我们不在本次分享中涉及。在我之前的几篇文章中，也阐述过观点。\n\n目前这方面研究最深入的是之前百度FIS团队的张云龙，他的几篇文章在[这里](https://github.com/fouber/blog \"这里\")，强烈推荐阅读。\n\n后记：\n\n这篇文章是我入职苏宁之后第一次公开分享，目标受众主要是后端出身的技术经理，目的是让这个群体能有更多的前端意识。现在公司的项目基本都有前端模块，但人员专职程度较低，水平也参差不齐。苏宁的战略口号之一是提升用户体验，从产品角度看，用户体验的提升并非是UI做几个图，搞一些花哨效果就可以了，它是一个系统工程，涉及从用户习惯调研、产品设计、前端开发、甚至后端服务等一系列环节，需要从易用度、观感、加载性能、流畅度等各方面共同提升。\n\n这些东西都需要从全局角度作规划，从源头控制起，否则只能是头疼医头，脚痛医脚。为此，基础技术中心会逐步整合几套适合不同场景的基础前端框架，作为种子项目供今后的技术选型使用。此外，还会从前端开发的各种主题组织一些技术分享，并且逐步形成一套制度化，流程化的培训体系。\n"
  },
  {
    "path": "posts/2014-10-01-From HTC to Web Components.md",
    "content": "从HTML Components的衰落看Web Components的危机\n====\n\n搞前端时间比较长的同学都会知道一个东西，那就是HTC（HTML Components），这个东西名字很现在流行的Web Components很像，但却是不同的两个东西，它们的思路有很多相似点，但是前者已是昨日黄花，后者方兴未艾，是什么造成了它们的这种差距呢？\n\n## HTML Components的一些特性\n\n因为主流浏览器里面只有IE支持过HTC，所以很多人潜意识都认为它不标准，但其实它也是有标准文档的，而且到现在还有链接，注意它的时间！\n\n[http://www.w3.org/TR/NOTE-HTMLComponents](http://www.w3.org/TR/NOTE-HTMLComponents \"HTML Components\")\n\n我们来看看它主要能做什么呢？\n\n它可以以两种方式被引入到HTML页面中，一种是作为“行为”被附加到元素，使用CSS引入，一种是作为“组件”，扩展HTML的标签体系。\n\n### 行为\n\n行为（Behavior）是在IE5中引入的一个概念，主要是为了做文档结构和行为的分离，把行为通过类似样式的方式隔离出去，详细介绍在这里可以看：\n\n[http://msdn.microsoft.com/en-us/library/ms531079(v=vs.85).aspx](http://msdn.microsoft.com/en-us/library/ms531079(v=vs.85).aspx)\n\n行为里可以引入HTC文件，刚才的HTC规范里就有，我们把它摘录出来，能看得清楚一些：\n\n*engine.htc*\n```HTML\n<HTML xmlns:PUBLIC=\"urn:HTMLComponent\">\n<PUBLIC:EVENT NAME=\"onResultChange\" ID=\"eventOnResultChange\" />\n\n<SCRIPT LANGUAGE=\"JScript\">\n\nfunction doCalc()\n{\n   :\n   oEvent = createEventObject();\n   oEvent.result = sResult;\n   eventOnResultChange.fire (oEvent);\n\n}\n```\n\n```HTML\n<HTML xmlns:LK=\"urn:com.microsoft.htc.samples.calc\">\n<HEAD>\n<STYLE>\n   LK\\:CALC    { behavior:url(engine.htc); } \n</STYLE>\n</HEAD>\n\n<LK:CALC ID=\"myCalc\" onResultChange=\"resultWindow.innerText=window.event.result\">\n<TABLE>\n<TR><DIV ID=\"resultWindow\" STYLE=\"border: '.025cm solid gray'\" ALIGN=RIGHT>0.</DIV></TR>\n<TR><TD><INPUT TYPE=BUTTON VALUE=\" 7 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" 8 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" 9 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" / \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" C \"></TD>\n</TR>\n<TR><TD><INPUT TYPE=BUTTON VALUE=\" 4 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" 5 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" 6 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" * \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" % \" DISABLED></TD>\n</TR>\n<TR><TD><INPUT TYPE=BUTTON VALUE=\" 1 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" 2 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" 3 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" - \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\"1/x\" DISABLED></TD>\n</TR>\n<TR><TD><INPUT TYPE=BUTTON VALUE=\" 0 \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\"+/-\"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" . \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" + \"></TD>\n    <TD><INPUT TYPE=BUTTON VALUE=\" = \"></TD>\n</TR>\n\n</TABLE>\n</LK:CALC>\n</HTML>\n```\n\n这是一个计算器的例子，我们先大致看一下代码结构，是不是很清晰？再看看现在用jQuery，我们是怎么实现这种东西的：是用选择器选择这些按钮，然后添加事件处理函数。注意你多了一步选择的过程，而且，整个过程混杂了声明式和命令式两种代码风格。如果按照它这样，你所有的JS基本都丢在了隔离的不相关的文件中，整个是一个配置的过程，分离得很干净。\n\n除了这种计算器，还有规范文档中举例的改变界面展示，或者添加动画之类，注意它们的切入点，都是相当于附加在特定选中元素上的行为，即使DOM不给JS暴露任何选择器，也毫无影响，因为它们直接就通过CSS的选择器挂到元素上了。\n\n这种在现在看来，意义不算明显，现在广为使用的先选择元素再添加事件，也是不错的展现和行为分离方式。\n\n但另外一种使用方式就不同了。\n\n### 组件\n\n狭义的HTML5给我们带来了什么？是很多新增的元素标签，比如section，nav，acticle，那这些东西跟原先直接用div实现的，好处在哪里呢？在于语义化。\n\n所谓语义化，就是一个元素能清晰表达自己是干什么的，不会让人有歧义，像div那种，可以类比成是一个Object，它不具体表示什么东西，但可以当成各种东西来用。而nav一写，就知道，它是导航，它就像有class定义的一个实体类，能表达具体含义。\n\n那么，原有的HTML元素显然是不够的，因为实际开发过程中要表达的东西显然远远超出这些元素，比如日历，这种东西就没有一个元素用来描述它，更不用说在一些企业应用中可能会出现的树之类复杂控件了。\n\n不提供原生元素，对开发造成的困扰是代码写起来麻烦，具体可以看之前我在知乎的一个回复，第三点：\n\n[http://www.zhihu.com/question/22426434/answer/21433867](http://www.zhihu.com/question/22426434/answer/21433867)\n\n所以，大家都想办法去提供自己的扩充元素的方式，现在我们是知道典型的有angularjs，polymer，但很早的时候也不是没有啊：\n\n[http://msdn.microsoft.com/en-us/library/ms531076(v=vs.85).aspx](http://msdn.microsoft.com/en-us/library/ms531076(v=vs.85).aspx)\n\n看，这就是HTC的添加自定义元素的方式，每个元素可以定义自己对外提供的属性、方法，还有事件，自己内部可以像写一个新页面一样，专注于实现功能。而且你发现没有，它考虑得很长远，提供了命名空间，防止你在一个页面引入两个不同组织提供的同名自定义元素。\n\n这个东西就可以称为组件了，它跟外界是完全隔离的，外界只要把它拿来就可以用，就像用原生元素一样，用选择器选择，设置属性，调用方法，添加事件处理等等，而且，注意到没有，它的属性是带get和set的，这是多么梦寐以求的东西！\n\n正是因为它这么好用，所以在那个时代，我们用它干了很多东西，封装了各种基础控件，比如树，数据表格，日期选择，等等，甚至当时也有人嫌弃浏览器原生select和radio不好看，用这么个东西，里面封装了图片来模拟功能，替换原生的来用。\n\n当时也有人，比如我在04年就想过，能不能把这些扩大化，扩展到除了基础控件之外的地方，把业务的组件也这么搞一下，一切皆组件，多好？\n\n但有些事情我直到后来很久以后才想明白，基于业务的端到端组件虽然写起来很方便，却是有致命缺陷的。\n\n到这里为止，对HTML Components的回顾告一段落，也不讨论它为什么就没了之类，这里面争议太大，我只想谈谈从这里面，能看到Web Components这么个大家寄予厚望的新标准需要面对一些什么问题。\n\n## Web Components的挑战\n\n以下逐条列出，挨个说明，有的已经有了，有的差一些，有的没有，不管这么多，总之谈谈我心目中的这个东西应当是怎样的。\n\n### 自定义元素标签支持命名空间\n\n原因我前面已经说了，可能会有不同组织实现同类功能的组件，存在于同一个页面内，引起命名歧义，所以我想了很久，还是觉得有前缀比较好：\n\n```HTML\n<yours:ComponentA></yours:ComponentA>\n<his:ComponentA></his:ComponentA>\n```\n\n甚至，这里的前缀还可以是个简称别名，比如yours=com.aaa.productA，这可能只有复杂到一定程度才会出现，大家不要以为这太夸张，但总有一天Web体系能构建超大型软件，到那时候你就知道是不是可能了。\n\n### 样式的局部作用域\n\n这个前一段时间有的浏览器实现过，在组件内部，style上加一个scoped属性，这是正确的方向。为什么要这么干呢，所谓组件，引入成本越小越好，在无约定的情况下都能引入，不造成问题，那是最佳的结果。\n\n如果你一个组件的样式不是局部的，很可能就跟主界面的冲突了，就算你不跟主界面的冲突，怎么保证不跟主界面中包含的其他组件的样式冲突？靠命名约定是不现实的，看长远一些，等你的系统够大，这就是大问题了。\n\n### 跟主文档的通讯\n\n一个自定义组件，应当能够跟主文档进行通讯，这个过程包括两个方向，分别可以有多种不同的方式。\n\n#### 从内向外\n\n除了事件，真没有什么好办法可以做这个方向的通讯，但事件也可以有两种定义方式，一种是类似onclick那种，主文档应当能够在它上面直接添加对应的事件监听函数，就像对原生元素那样，每个事件都能单独使用。另一种是像postMessage那样，只提供一个通道，具体怎么处理，自己去定义消息格式和处理方式。\n\n这两种实现方式都可行，后者比较偷懒，但也够用了，前者也没有明显优势。\n\n#### 从外向内\n\n这个也可以有两种方式，一种是组件对外暴露属性或者方法，让主文档调用，一种是外部也通过postMessage往里传。前者用起来会比较方便，后者也能凑合用用。\n\n所以，如果特别偷懒，这个组件就变得像一个iframe那样，跟外部基本都通过postMessage交互。\n\n### JavaScript\n\n写到这里我是很纠结的，因为终于来到争议最大的地方了。按照很多人的思路，我这里应该也写隔离成局部作用域的JavaScript才对，但真不行，我们可以先假设组件内部的所有JavaScript都跑在局部作用域，它不能访问主文档中的对象。\n\n我这里解释一下之前那个坑，为什么端到端组件是有缺陷的。\n\n先解释什么叫端到端组件。比如说，我有这么一个组件，它封装了对后端某接口的调用，还有自身的一些展示处理，跟外界通过事件通信。它整个是不需要依赖别人的，初始加载数据都是自己内部做，别人要用它也很简单，直接拿来放在页面里就可以了。\n\n照理说，这东西应当非常好才对，使用起来这么方便，到底哪里不对？我来举个场景。\n\n在页面上同时存在这个组件的多个实例，每个组件都去加载了初始数据，假设它们是不带参数的，每个组件加载的数据都一样，这里是不是就有浪费的请求了？有人可能觉得一点点浪费不算问题，那么继续。\n\n假设这个组件就是一个很普通的下拉列表，用于选取人员的职业，初始可能有医生，教师，警察等等，我把这个组件直接放在界面上，它一出现，就自己去加载了所需的列表信息并且展示了。有另外一个配置界面，用于配置这些职业信息，这时候我在里面添加了一个护士，并且提交了。假设为了数据一致性，我们把这个变更推回到页面，麻烦就出现了。\n\n界面只有一个职业下拉列表的时候可能还好办，有多个的时候，这个更新的策略就有问题了。\n\n如果在组件的内部做这个推送的对接，就会出现要推送多份一致的数据给组件的不同实例的问题。如果把这个放在外面，那我们也有两种方式：\n\n- 订阅发布模式，组件订阅某个数据源，数据源跟服务端对接，当数据变更的时候，发给每个订阅者\n- 观察者模式，组件观察某个数据源，当数据变更的时候，去取回来\n\n这两种很类似，不管哪种，都面临一个问题：\n\n数据源放在哪？\n\n很明显不能放在组件内部了，只能放在某个“全局”的地方，但刚才我们假设的是，组件内部的JavaScript代码不能访问外界的对象，所以……\n\n但要是让它能访问，组件的隔离机制等于白搭。最好的方式，也许是两种都支持，默认是局部作用域，另外专门有一个作用域放给JS框架之类的东西用，但浏览器实现的难度可能就大了不少。\n\n可能有人会说，你怎么把问题搞这么复杂，用这么BT的场景来给我们美好的未来出难题。我觉得问题总是要面对的，能在做出来之前就面对问题，结果应该会好一些。\n\n我注意观察了很多朋友对Web Components的态度，大部分都是完全叫好，但其中有一部分，主要是搞前端MV*的同学对它的态度很保守，主要原因应该是我说的这几点。因为这个群体主要都在做单页型的应用，这个里面会遇到的问题是跟传统前端不同的。\n\n那么，比如Angular，或者React，它们跟Web Components的协作点在哪里呢？我个人觉得是把引擎保留下来，上层部分逐步跟Web Components融合，所以它们不是谁吃掉谁的问题，而是怎样去融合。最终就是在前端有两层，一层是数据和业务逻辑层，一层是偏UI的，在那个层里面，可以存在像Web Components那样的垂直切分，这样会很适宜。\n\n最后说说自己对Polymer的意见，我的看法没有@司徒正美 那么粗暴，但我是认同他的观点的，因为Polymer的根本理念就是在做端到端组件，它会面临很多的挑战。虽然它是一个组件化框架，组件化最适宜于解决大规模协作问题，但是如果是以走向大型单页应用这条路来看，它比Angular和React离目标的距离还远很多。\n"
  },
  {
    "path": "posts/2014-10-04-to-be-a-fe.md",
    "content": "给一位打算从事前端，但是又有疑惑的在校大学生的回信\n====\n\n抱歉这么晚才回复这个邮件，主要是觉得你的问题有典型性，想要详细一点给出答复。\n\n所谓的前端，在不同的公司，定义是不同的，工作内容也会有差异，有的还很大。比如有很多公司，没有专门的前端分类，所有的都属于开发人员，一些比较传统的公司，还有一些人数较少的小公司会是这样。又比如有些公司，前端人员的职责仅限于静态页面和交互效果，然后把这些东西交给业务开发人员去编写业务的JS代码。还有一些公司，前端除了PC和移动端的Web，还包括各种移动终端的开发。\n\n这些种种不同，都是各公司自身的业务特点决定的，大体上比较适合各自的业务场景，越大的公司，内部的分工可能越明确，所以也就有了你看到的，有比较偏向JS的，有比较偏向CSS的。\n\n个人选择什么方向，我觉得需要问自己两个问题：\n\n1. 你是一个怎样的人\n\n这个的意思是，你觉得自己学js和css的时候，哪种觉得更轻松愉快，容易领悟。一个人选择自己最容易领悟的方面去学习，会事半功倍。\n\n2. 你希望成为一个怎样的人\n\n人的一生，实际上很大程度是职业细分的过程，每个人在他工作的前10年，都可能会逐步深入到某些领域，他的知识广度可能会逐步增加，但能够深入的，往往在一两个分支上。\n\n从大的方面看，最初的软件体系基本都是以服务端为主，客户端通过字符界面去进行操作，后来桌面程序迅速发展，再后来Web兴起，最近各种终端的流行，更加促使广义的“前端”这个领域有更多的发挥空间。整体来说，后端的发展趋势是服务化，前端的发展趋势是多样化。因为消费者的促进，前端的需求和发展会是非常乐观的，无论在其中选择哪个细分方向，只要努力下去，成为这个领域的专家，肯定都会有所成就。\n\n目前，在很多公司，搞CSS一般还没有独立职位，或者即使有，暂时比搞JS的还是稍微弱势一些，正如前端部门一般比后端部门弱势，但这种状况会好转的，每个领域都会得到适合自己的发挥。\n\n关于原生JS和某些库的学习，我的观点是这样，除了一些很特别很怪异的点，对于语言本身的常规用法是需要都掌握的，其实也不多，常用到的就那么些。一般说的原生JS，是包括JS语言本身，还有它对DOM和BOM的操作，比如元素的创建移除，事件的添加等等，这些应该都需要懂。至于说对于某个库的学习，更重要的是学习它的思维方式，每看一个例子，就先想一想如果自己写，会把代码写成怎样，再与真实的例子进行对照，举一反三，这样的学习会是很快的过程。\n\n现在这个时代，各种浏览器还在混战，但低版本IE的淘汰已经成为了必然，如果是现在开始学习，一定要着眼于将来，多看看CSS3各子集的规范，了解ES新版本的特性，因为世界迟早是它们的。对于低版本浏览器的兼容，一般都会有成熟的解决方案，当遇到具体问题的时候再去看也可以。\n\n很多人看待前端，是把它当作一个很浅的层面来看的，其实前端的人多了解一些别的领域也是有好处的，从中能得到很多领悟，比如软件工程，设计模式，它们对不管什么方面的开发人员而言，都是很好的指导。\n\n一个成熟的前端开发人员，他应当有比较宽的知识面，同时至少在某一两个细分领域有专注的研究和见解。平时在日常生活中，也可以多注意观察一些产品，对自己正在做的整个产品有深刻认识，对生活常识有充分了解，有时候也会有助于减少开发过程中走的弯路。\n\n能够对自己的未来有所预期，并且主动寻找学习的途径，这说明你有很好的开始，在前端这条道路上认真走下去，相信会有美好的未来。\n"
  },
  {
    "path": "posts/2014-10-21-build-single-page-application.md",
    "content": "构建单页Web应用\n====\n\n## 单页应用是什么？\n\n让我们先来看几个网站：\n\n[coding](https://coding.net/)\n\n[teambition](https://www.teambition.com/)\n\n[cloud9](https://c9.io/)\n\n注意这几个网站的相同点，那就是在浏览器中，做了原先“应当”在客户端做的事情。它们的界面切换非常流畅，响应很迅速，跟传统的网页明显不一样，它们是什么呢？这就是单页Web应用。\n\n所谓单页应用，指的是在一个页面上集成多种功能，甚至整个系统就只有一个页面，所有的业务功能都是它的子模块，通过特定的方式挂接到主界面上。它是AJAX技术的进一步升华，把AJAX的无刷新机制发挥到极致，因此能造就与桌面程序媲美的流畅用户体验。\n\n其实单页应用我们并不陌生，很多人写过ExtJS的项目，用它实现的系统，很天然的就已经是单页的了，也有人用jQuery或者其他框架实现过类似的东西。用各种JS框架，甚至不用框架，都是可以实现单页应用的，它只是一种理念。有些框架适用于开发这种系统，如果使用它们，可以得到很多便利。\n\n## 开发框架\n\nExtJS可以称为第一代单页应用框架的典型，它封装了各种UI组件，用户主要使用JavaScript来完成整个前端部分，甚至包括布局。随着功能逐渐增加，ExtJS的体积也逐渐增大，即使用于内部系统的开发，有时候也显得笨重了，更不用说开发以上这类运行在互联网上的系统。\n\njQuery由于偏重DOM操作，它的插件体系又比较松散，所以比ExtJS这个体系更适合开发在公网运行的单页系统，整个解决方案会相对比较轻量、灵活。\n\n但由于jQuery主要面向上层操作，它对代码的组织是缺乏约束的。如何在代码急剧膨胀的情况下控制每个模块的内聚性，并且适当在模块之间产生数据传递与共享，就成为了一种有挑战的事情。\n\n为了解决单页应用规模增大时候的代码逻辑问题，出现了不少MV*框架，他们的基本思路都是在JS层创建模块分层和通信机制。有的是MVC，有的是MVP，有的是MVVM，而且，它们几乎都在这些模式上产生了变异，以适应前端开发的特点。\n\n这类框架包括Backbone，Knockout，AngularJS，Avalon等。\n\n## 组件化\n\n这些在前端做分层的框架推动了代码的组件化，所谓组件化，在传统的Web产品中，更多的指UI组件，但其实组件是一个广泛概念，传统Web产品中UI组件占比高的原因是它的厚度不足，随着客户端代码比例的增加，相当一部分的业务逻辑也前端化，由此催生了很多非界面型组件的出现。\n\n分层带来的一个优势是，每层的职责更专一了，由此，可以对其作单元测试的覆盖，以保证其质量。传统UI层测试最头疼的问题是UI层和逻辑混杂在一起，比如往往会在远程请求的回调中更改DOM，当引入分层之后，这些东西都可以分别被测试，然后再通过场景测试来保证整体流程。\n\n## 代码隔离\n\n与开发传统页面型网站相比，实现单页应用的过程中，有一些比较值得特别关注的点。\n\n从单页应用的特点来看，它比页面型网站更加依赖于JavaScript，而由于页面的单页化，各种子功能的JavaScript代码聚集到了同一个作用域，所以代码的隔离、模块化变得很重要。\n\n在单页应用中，页面模板的使用是很普遍的。很多框架内置了特定的模板，也有的框架需要引入第三方的模板。这种模板是界面片段，我们可以把它们类比成JavaScript模块，它们是另一种类型的组件。\n\n模板也一样有隔离的需要。不隔离模板，会造成什么问题呢？模板间的冲突主要存在于id属性上，如果一个模板中包含固定的id，当它被批量渲染的时候，会造成同一个页面的作用域中出现多个相同id的元素，产生不可预测的后果。因此，我们需要在模板中避免使用id，如果有对DOM的访问需求，应当通过其他选择器来完成。如果一个单页应用的组件化程度非常高，很可能整个应用中都没有元素id的使用。\n\n## 代码合并与加载策略\n\n人们对于单页系统的加载时间容忍度与Web页面不同，如果说他们愿意为购物页面的加载等待3秒，有可能会愿意为单页应用的首次加载等待5-10秒，但在此之后，各种功能的使用应当都比较流畅，所有子功能页面尽量要在1-2秒时间内切换成功，否则他们就会感觉这个系统很慢。\n\n从这些特点来看，我们可以把更多的公共功能放到首次加载，以减小每次加载的载入量，有一些站点甚至把所有的界面和逻辑全部放到首页加载，每次业务界面切换的时候，只产生数据请求，因此它的响应是非常迅速的，比如青云的控制台就是这么做的。\n\n通常在单页应用中，无需像网站型产品一样，为了防止文件加载阻塞渲染，把js放到html后面加载，因为它的界面基本都是动态生成的。\n\n当切换功能的时候，除了产生数据请求，还需要渲染界面，这个新渲染的界面部件一般是界面模板，它从哪里来呢？来源无非是两种，一种是即时请求，像请求数据那样通过AJAX获取过来，另一种是内置于主界面的某些位置，比如script标签或者不可见的textarea中，后者在切换功能的时候速度有优势，但是加重了主页面的负担。\n\n在传统的页面型网站中，页面之间是互相隔离的，因此，如果在页面间存在可复用的代码，一般是提取成单独的文件，并且可能会需要按照每个页面的需求去进行合并。单页应用中，如果总的代码量不大，可以整体打包一次在首页载入，如果大到一定规模，再作运行时加载，加载的粒度可以搞得比较大，不同的块之间没有重复部分。\n\n## 路由与状态的管理\n\n我们最开始看到的几个在线应用，有的是对路由作了管理的，有的没有。\n\n管理路由的目的是什么呢？是为了能减少用户的导航成本。比如说我们有一个功能，经历过多次导航菜单的点击，才呈现出来。如果用户想要把这个功能地址分享给别人，他怎么才能做到呢？\n\n传统的页面型产品是不存在这个问题的，因为它就是以页面为单位的，也有的时候，服务端路由处理了这一切。但是在单页应用中，这成为了问题，因为我们只有一个页面，界面上的各种功能区块是动态生成的。所以我们要通过对路由的管理，来实现这样的功能。\n\n具体的做法就是把产品功能划分为若干状态，每个状态映射到相应的路由，然后通过pushState这样的机制，动态解析路由，使之与功能界面匹配。\n\n有了路由之后，我们的单页面产品就可以前进后退，就像是在不同页面之间一样。\n\n其实在Web产品之外，早就有了管理路由的技术方案，Adobe Flex中，就会把比如TabNavigator，甚至下拉框的选中状态对应到url上，因为它也是单“页面”的产品模式，需要面对同样的问题。\n\n当产品状态复杂到一定程度的时候，路由又变得很难应用了，因为状态的管理极其麻烦，比如开始的时候我们演示的c9.io在线IDE，它就没法把状态对应到url上。\n\n## 缓存与本地存储\n\n在单页应用的运作机制中，缓存是一个很重要的环节。\n\n由于这类系统的前端部分几乎全是静态文件，所以它能够有机会利用浏览器的缓存机制，而比如动态加载的界面模板，也完全可以做一些自定义的缓存机制，在非首次的请求中直接取缓存的版本，以加快加载速度。\n\n甚至，也出现了一些方案，在动态加载JavaScript代码的同时，把它们也缓存起来。比如Addy Osmani的这个[basket.js](https://github.com/addyosmani/basket.js)，就利用了HTML5 localStorage作了js和css文件的缓存。\n\n在单页产品中，业务代码也常常会需要跟本地存储打交道，存储一些临时数据，可以使用[localStorage](https://github.com/mortzdk/localStorage)或者[localStorageDB](https://github.com/knadh/localStorageDB)来简化自己的业务代码。\n\n## 服务端通信\n\n传统的Web产品通常使用JSONP或者AJAX这样的方式与服务端通信，但在单页Web应用中，有很大一部分采用WebSocket这样的实时通讯方式。\n\nWebSocket与传统基于HTTP的通信机制相比，有很大的优势。它可以让服务端很便利地使用反向推送，前端只响应确实产生业务数据的事件，减少一遍又一遍无意义的AJAX轮询。\n\n由于WebSocket只在比较先进的浏览器上被支持，有一些库提供了在不同浏览器中的兼容方案，比如socket.io，它在不支持WebSocket的浏览器上会降级成使用AJAX或JSONP等方式，对业务代码完全透明、兼容。\n\n## 内存管理\n\n传统的Web页面一般是不需要考虑内存的管理的，因为用户的停留时间相对少，即使出现内存泄漏，可能很快就被刷新页面之类的操作冲掉了，但单页应用是不同的，它的用户很可能会把它开一整天，因此，我们需要对其中的DOM操作、网络连接等部分格外小心。\n\n## 样式的规划\n\n在单页应用中，因为页面的集成度高，所有页面聚集到同一作用域，样式的规划也变得重要了。\n\n样式规划主要是几个方面：\n\n### 基准样式的分离\n\n这里面主要包括浏览器样式的重设、全局字体的设置、布局的基本约定和响应式支持。\n\n### 组件样式的划分\n\n这里面是两个层面的规划，首先是各种界面组件及其子元素的样式，其次是一些修饰样式。组件样式应当尽量减少互相依赖，各组件的样式允许冗余。\n\n### 堆叠次序的管理\n\n传统Web页面的特点是元素多，但是层次少，单页应用会有些不同。\n\n在单页应用中，需要提前为各种UI组件规划堆叠次序，也就是z-index，比如说，我们可能会有各种弹出对话框，浮动层，它们可能组合成各种堆叠状态。新的对话框的z-index需要比旧的高，才能确保盖在它上面。诸如此类，都需要我们对这些可能的遮盖作规划，那么，怎样去规划呢？\n\n了解通信知识的人，应当会知道，不同的频率段被划分给不同的通信方式使用，在一些国家，领空的使用也是有划分的，我们也可以用同样的方式来预先分段，不同类型的组件的z-index落到各自的区间，以避免它们的冲突。\n\n## 单页应用的产品形态\n\n我们在开始的时候提到，存在着很多新型Web产品，使用单页应用的方式构建，但实际上，这类产品不仅仅存在于Web上。点开Chrome商店，我们会发现很多离线应用，这些产品都可以算是单页应用的体现。\n\n除了各种浏览器插件，借助node-webkit这样的外壳平台，我们可以使用Web技术来构建本地应用，产品的主要部分仍然是我们熟悉的单页应用。\n\n单页应用的流行程度正在逐渐增加，大家如果关注了一些初创型互联网企业，会发现其中很大一部分的产品模式是单页化的。这种模式能带给用户流畅的体验，在开发阶段，对JavaScript技能水平要求较高。\n\n单页应用开发过程中，前后端是天然分离的，双方以API为分界。前端作为服务的消费者，后端作为服务的提供者。在此模式下，前端将会推动后端的服务化。当后端不再承担模板渲染、输出页面这样工作的情况下，它可以更专注于所提供的API的实现，而在这样的情况下，Web前端与各种移动终端的地位对等，也逐渐使得后端API不必再为每个端作差异化设计了。\n\n## 部署模式的改变\n\n在现在这个时代，我们已经可以看到一种产品的出现了，那就是“无后端”的Web应用。这是一种什么东西呢？基于这种理念，你的产品很可能只需要自己编写静态Web页面，在某种BaaS（Backend as a Service）云平台上定制服务端API和云存储，集成这个平台提供的SDK，通过AJAX等方式与之打交道，实现注册认证、社交、消息推送、实时通信、云存储等功能。\n\n我们观察一下这种模式，会发现前后端的部署已经完全分离了，前端代码完全静态化，这意味着可以把它们放置到CDN上，访问将大大地加速，而服务端托管在BaaS云上，开发者也不必去关注一些部署方面的繁琐细节。\n\n假设你是一名创业者，正在做的是一种实时协同的单页产品，可以在云平台上，快速定制后端服务，把绝大部分宝贵的时间花在开发产品本身上。\n\n## 单页应用的缺陷\n\n单页应用最根本的缺陷就是不利于SEO，因为界面的绝大部分都是动态生成的，所以搜索引擎很不容易索引它。\n\n## 产品单页化带来的挑战\n\n一个产品想要单页化，首先是它必须适合单页的形态。其次，在这个过程中，对开发模式会产生一些变更，对开发技能也会有一些要求。\n\n开发者的JavaScript技能必须过关，同时需要对组件化、设计模式有所认识，他所面对的不再是一个简单的页面，而是一个运行在浏览器环境中的桌面软件。\n"
  },
  {
    "path": "posts/2015-02-26-components-in-webapp.md",
    "content": "2015前端组件化框架之路\n====\n\n# 1. 为什么组件化这么难做\n\nWeb应用的组件化是一个很复杂的话题。\n\n在大型软件中，组件化是一种共识，它一方面提高了开发效率，另一方面降低了维护成本。但是在Web前端这个领域，并没有很通用的组件模式，因为缺少一个大家都能认同的实现方式，所以很多框架/库都实现了自己的组件化方式。\n\n前端圈最热衷于造轮子了，没有哪个别的领域能出现这么混乱而欣欣向荣的景象。这一方面说明前端领域的创造力很旺盛，另一方面却说明了基础设施是不完善的。\n\n我曾经有过这么一个类比，说明某种编程技术及其生态发展的几个阶段：\n\n- 最初的时候人们忙着补全各种API，代表着他们拥有的东西还很匮乏，需要在语言跟基础设施上继续完善\n- 然后就开始各种模式，标志他们做的东西逐渐变大变复杂，需要更好的组织了\n- 然后就是各类分层MVC，MVP，MVVM之类，可视化开发，自动化测试，团队协同系统等等，说明重视生产效率了，也就是所谓工程化\n\n那么，对比这三个阶段，看看关注这三种东西的人数，觉得Web发展到哪一步了？\n\n细节来说，大概是模块化和组件化标准即将大规模落地（好坏先不论），各类API也大致齐备了，终于看到起飞的希望了，各种框架几年内会有非常强力的洗牌，如果不考虑老旧浏览器的拖累，这个洗牌过程将大大加速，然后才能释放Web前端的产能。\n\n但是我们必须注意到，现在这些即将普及的标准，很多都会给之前的工作带来改变。用工业体系的发展史来对比，前端领域目前正处于蒸汽机发明之前，早期机械（比如《木兰辞》里面的机杼，主要是动力与材料比较原始）已经普及的这么一个阶段。\n\n所以，从这个角度看，很多框架/库是会消亡的（专门做模块化的AMD和CMD相关库，专注于标准化DOM选择器铺垫的某些库），一些则必须进行革新，还有一些受的影响会比较小（数据可视化等相关方向），可以有机会沿着自己的方向继续演进。\n\n# 2. 标准的变革\n\n对于这类东西来说，能获得广泛群众基础的关键在于：对将来的标准有怎样的迎合程度。对前端编程方式可能造成重大影响的标准有这些：\n\n- module\n- Web Components\n- class\n- observe\n- promise\n\nmodule的问题很好理解，JavaScript第一次有了语言上的模块机制，而Web Components则是约定了基于泛HTML体系构建组件库的方式，class增强了编程体验，observe提供了数据和展现分离的一种优秀方式，promise则是目前前端最流行的异步编程方式。\n\n这里面只有两个东西是绕不过去的，一是module，一是Web Components。前者是模块化基础，后者是组件化的基础。\n\nmodule的标准化，主要影响的是一些AMD/CMD的加载和相关管理系统，从这个角度来看，正如seajs团队的@afc163 所说，不管是AMD还是CMD，都过时了。\n\n模块化相对来说，迁移还比较容易，基本只是纯逻辑的包装，跟AMD或者CMD相比，包装形式有所变化，但组件化就是个比较棘手的问题了。\n\nWeb Components提供了一种组件化的推荐方式，具体来说，就是：\n\n- 通过shadow DOM封装组件的内部结构\n- 通过Custom Element对外提供组件的标签\n- 通过Template Element定义组件的HTML模板\n- 通过HTML imports控制组件的依赖加载\n\n这几种东西，会对现有的各种前端框架/库产生很巨大的影响：\n\n- 由于shadow DOM的出现，组件的内部实现隐藏性更好了，每个组件更加独立，但是这使得CSS变得很破碎，LESS和SASS这样的样式框架面临重大挑战。\n- 因为组件的隔离，每个组件内部的DOM复杂度降低了，所以选择器大多数情况下可以限制在组件内部了，常规选择器的复杂度降低，这会导致人们对jQuery的依赖下降。\n- 又因为组件的隔离性加强，致力于建立前端组件化开发方式的各种框架/库（除Polymer外），在自己的组件实现方式与标准Web Components的结合，组件之间数据模型的同步等问题上，都遇到了不同寻常的挑战。\n- HTML imports和新的组件封装方式的使用，会导致之前常用的以JavaScript为主体的各类组件定义方式处境尴尬，它们的依赖、加载，都面临了新的挑战，而由于全局作用域的弱化，请求的合并变得困难得多。\n\n# 3. 当下最时髦的前端组件化框架/库\n\n在2015年初这个时间点看，前端领域有三个框架/库引领时尚，那就是Angular，Polymer，React（排名按照首字母），在知乎的这篇[2014 年末有哪些比较火的 Web 开发技术？](http://www.zhihu.com/question/26644904/answer/33634518)里，我大致回答过一些点，其他几位朋友的答案也很值得看。关于这三者的细节分析，**侯振宇**的这篇讲得很好：[2015前端框架何去何从](http://www.cnblogs.com/sskyy/p/4264371.html)\n\n我们可以看到，Polymer这个东西在这方面是有先天优势的，因为它的核心理念就是基于Web Components的，也就是说，它基本没有考虑如何解决当前的问题，直接以未来为发展方向了。\n\nReact的编程模式其实不必特别考虑Web标准，它的迁移成本并不算高，甚至由于其实现机制，屏蔽了UI层实现方式，所以大家能看到在native上的使用，canvas上的使用，这都是与基于DOM的编程方式大为不同的，所以对它来说，处理Web Components的兼容问题要在封装标签的时候解决，反正之前也是要封装。\n\nAngular 1.x的版本，可以说是跟同时代的多数框架/库一样，对未来标准的兼容基本没有考虑，但是重新规划之后的2.0版本对此有了很多权衡，变成了激进变更，突然就变成一个未来的东西了。\n\n这三个东西各有千秋，在可以预见的几年内将会鼎足三分，也许还会有新的框架出现，能不能比这几个流行就难说了。\n\n此外，原Angular 2.0的成员Rob Eisenberg创建了自己的新一代框架[aurelia](http://aurelia.io/)，该框架将成为Angular 2.0强有力的竞争者。\n\n# 4. 前端组件的复用性\n\n看过了已有的一些东西之后，我们可以大致来讨论一下前端组件化的一些理念。假设我们有了某种底层的组件机制，先不管它是浏览器原生的，或者是某种框架/库实现的约定，现在打算用它来做一个大型的Web应用，应该怎么做呢？\n\n所谓组件化，核心意义莫过于提取真正有复用价值的东西。那怎样的东西有复用价值呢？\n\n- 控件\n- 基础逻辑功能\n- 公共样式\n- 稳定的业务逻辑\n\n对于控件的可复用性，基本上是没有争议的，因为这是实实在在的通用功能，并且比较独立。\n\n基础逻辑功能主要指的是一些与界面无关的东西，比如underscore这样的辅助库，或者一些校验等等纯逻辑功能。\n\n公共样式的复用性也是比较容易认可的，因此也会有bootstrap，foundation，semantic这些东西的流行，不过它们也不是纯粹的样式库了，也带有一些小的逻辑封装。\n\n最后一块，也就是业务逻辑。这一块的复用是存在很多争议的，一方面是，很多人不认同业务逻辑也需要组件化，另一方面，这块东西究竟怎样去组件化，也很需要思考。\n\n除了上面列出的这些之外，还有大量的业务界面，这块东西很显然复用价值很低，基本不存在复用性，但仍然有很多方案中把它们“组件化”了，使得它们成为了“不具有复用性的组件”。为什么会出现这种情况呢？\n\n组件化的本质目的并不一定是要为了可复用，而是提升可维护性。这一点正如面向对象语言，Java要比C++纯粹，因为它不允许例外情况的出现，连main函数都必须写到某个类里，所以Java是纯面向对象语言，而C++不是。\n\n在我们这种情况下，也可以把组件化分为：全组件化，局部组件化。怎么理解这两个东西的区别呢，有人问过js框架和库的区别是什么，一般来说，有某种较强约定的东西，称为框架，而约定比较松散的，称为库。框架很多都是有全组件化理念的，比如说，很多年前就出现的ExtJS，它是全组件化框架，而jQuery和它的插件体系，则是局部组件化。所以用ExtJS写东西，不管写什么都是差不多一样的写法，而用jQuery的时候，大部分地方是原始HTML，哪里需要有些不一样的东西，就只在那个地方调用插件做一下特殊化。\n\n对于一个有一定规模的Web应用来说，把所有东西都“组件化”，在管理上会有较大的便利性。我举个例子，同样是编写代码，短代码明显比长代码的可读性更高，所以很多语言里会建议“一个方法一般不要超过多少行，一个类最好不要超过多少行”之类。在Web前端这个体系里，JavaScript这块是做得相对较好的，现在入门水平的人，也已经很少会有把一堆js都写在一起的了。CSS这块，最近在SASS，LESS等框架的引领下，也逐步往模块化方面发展，否则直接编写bootstrap那种css，会非常痛苦。\n\n这个时候我们再看HTML的部分，如果不考虑模板等技术的使用，某些界面光布局代码写起来就非常多了，像一些表单，都需要一层套一层，很多简单的表单元素都需要套个三层左右，更不必说一些有复杂布局的东西了。尤其是整个系统单页化之后，界面的header，footer，各种nav或者aside，很可能都有一定复杂性。如果这些东西的代码不作切分，那么主界面的HTML一定比较难看。\n\n我们先不管用什么方式切分了，比如用某种模板，用类似Angular中的include，或者Polymer，React中的标签，或者直接使用原生Web Components，总之是把一块一块都拆开了，然后包含进来。从这个角度看，这些拆出去的东西都像组件，但如果从复用性的角度看，很可能多数东西，每一块都只有一个地方用，压根没有复用度。这个拆出去，纯粹是为了使得整个工程易于管理，易于维护。\n\n这时候我们再来关注不同框架/库对UI层组件化的处理方式，发现有两个类型，模板和函数。\n\n模板是一种很常见的东西，它用HTML字符串的方式表达界面的原始结构，然后通过代入数据的方式生成真正的界面，有的是生成目标HTML，有的还生成各种事件的自动绑定。前者是静态模板，后者是动态模板。\n\n另外有一些框架/库偏爱用函数逻辑来生成界面，早期的ExtJS，现在的React（它内部还是可能使用模板，而且对外提供的是组件创建接口的进一步封装——jsx）等，这种实现技术的优势是不同平台上编程体验一致，甚至可以给每种平台封装相同的组件，调用方轻松写一份代码，在Web和不同Native平台上可用。但这种方式也有比较麻烦的地方，那就是界面调整比较繁琐。\n\n本文前面部分引用**侯振宇**的那篇文章里，他提出这些问题：\n\n> 如何能把组件变得更易重用? 具体一点:\n> - 我在用某个组件时需要重新调整一下组件里面元素的顺序怎么办?\n> - 我想要去掉组件里面某一个元素怎么办?\n> 如何把组件变得更易扩展? 具体一点:\n> - 业务方不断要求给组件加功能怎么办?\n\n为此，还提出了“模板复写”方案，在这一点上我有不同意见。\n\n我们来看看如何把一个业务界面切割成组件。\n\n有这么一个简单场景：一个雇员列表界面包括两个部分，雇员表格和用于填写雇员信息的表单。在这个场景下，存在哪些组件？\n\n对于这个问题，主要存在两种倾向，一种是仅仅把“控件”和比较有通用性的东西封装成组件，另外一种是整个应用都组件化。\n\n对前一种方式来说，这里面只存在数据表格这么一个组件。\n对后一种方式来说，这里面有可能存在：数据表格，雇员表单，甚至还包括雇员列表界面这么一个更大的组件。\n\n这两种方式，就是我们之前所说的“局部组件化”，“全组件化”。\n\n我们前面提到，全组件化在管理上是存在优势的，它可以把不同层面的东西都搞成类似结构，比如刚才的这个业务场景，很可能最后写起来是这个样子：\n\n```XML\n<Employee-Panel>\n\t<Employee-List></Employee-List>\n\t<Employee-Form></Employee-Form>\n</Employee-Panel>\n```\n\n对于UI层，最好的组件化方式是标签化，比如上面代码中就是三个标签表达了整个界面。但我个人坚决反对滥用标签，并不是把各种东西都尽量封装就一定好。\n\n全标签化的问题主要有这些：\n\n第一，语义化代价太大。只要用了标签，就一定需要给它合适的语义，也就是命名。但实际用的时候，很可能只是为了把一堆html简化一下而已，到底简化出来的那东西应当叫什么名字，光是起名也费不知多少脑细胞。比如你说雇员管理的表单，这个表单有heading吗，有footer吗，能折叠吗，等等，很难起一个让别人一看就知道的名字，要么就是特别长。这还算简单的，因为我们是全组件化，所以很可能会有组合了多种东西的一个较复杂的界面，你想来想去也没法给它起个名字，于是写了个：\n\n\n```XML\n<Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right>\n</Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right>\n```\n\n这尼玛……可能我夸张了点，但很多时候项目规模够大，你不起这么复杂的名字，最后很可能没法跟功能类似的一个组件区分开，因为这些该死的组件都存在于同一个命名空间中。如果仅仅是当作一个界面片段来include，就不存在这种心理负担了。\n\n比如Angular里面的这种：\n\n```HTML\n<div ng-include=\"'aaa/bbb/ccc.html'\"></div>\n```\n\n就不给它什么名字，直接include进来，用文件路径来区分。这个片段的作用可以用其目录结构描述，也就是通过物理名而非逻辑名来标识，目录层次充当了一个很好的命名空间。\n\n现在的一些主流MVVM框架，比如knockout，angular，avalon，vue等等，都有一种“界面模板”，但这种模板并不仅仅是模板，而是可以视为一种配置文件。某一块界面模板描述了自身与数据模型的关系，当它被解析之后，按照其中的各种设置，与数据建立关联，并且反过来再更新自身所对应的视图。\n\n不含业务逻辑的UI（或者是业务逻辑已分离的UI）基本不适合作为组件来看待，因为即使在逻辑不变的情况下，界面改版的可能性也太多了。比如即使是换了新的CSS实现方式，从float布局改成flex布局，都有可能把DOM结构少套几层div，因此，在使用模板的方案中，只能把界面层视为配置文件，不能看成组件，如果这么做，就会轻松很多。\n\n部队行军的时候讲究“逢山开路，遇水搭桥”，这句话的重点在于只有到某些地形才开路搭桥，使用MVVM这类模式解决的业务场景，多数时候是一马平川，横着走都可以，不必硬要造路。所以从整个方案看的话，UI层实现应该是模板与控件并存，大部分地方是模板，少数地方是需要单独花时间搞的路和桥。\n\n第二，配置过于复杂。有很多东西其实不太适合封装，不但封装的代价大，使用的代价也会很大。有时候会发现，调用代码的绝大部分都是在写各种配置。\n\n就像刚才的雇员表单，既然你不从标签的命名上去区分，那一定会在组件上加配置。比如你原来想这样：\n\n```XML\n<EmployeeForm heading=\"雇员表单\"></EmployeeForm>\n```\n\n然后在组件内部，判断有没有设置heading，如果没有就不显示，如果有，就显示。过了两天，产品问能不能把heading里面的某几个字加粗或者换色，然后码农开始允许这个heading属性传入html。没多久之后，你会惊奇地发现有人用你的组件，没跟你说，就在heading里面传入了折叠按钮的html，并且用选择器给折叠按钮加了事件，点一下之后还能折叠这个表单了……\n\n然后你一想，这个不行，我得给他再加个配置，让他能很简单地控制折叠按钮的显示，但是现在这么写太不直观，于是采用对象结构的配置：\n\n```XML\n<EmployeeForm>\n\t<Option collapsible=\"true\">\n\t\t<Heading>\n\t\t\t<h4><strong>雇员</strong>表单</h4>\n\t\t</Heading>\n\t</Option>\n</EmployeeForm>\n```\n\n然后又有一天，发现有很多面板都可以折叠，然后特意创建了一个可折叠面板组件，又创建了一种继承机制，其他普通业务面板从它继承，从此一发不可收拾。\n\n我举这例子的意思是为了说明什么呢，我想说，在规模较大的项目中，企图用全标签化加配置的方式来描述所有的普通业务界面，是一定事倍功半的，并且这个规模越大就越坑，这也正是ExtJS这类对UI层封装过度的体系存在的最大问题。\n\n这个问题讨论完了，我们来看看另外一个问题：如果UI组件有业务逻辑，应该如何处理。\n\n比如说，性别选择的下拉框，它是一个非常通用化的功能，照理说是很适合被当做组件来提供的。但是究竟如何封装它，我们就有些犯难了。这个组件里除了界面，还有数据，这些数据应当内置在组件里吗？理论上从组件的封装性来说，是都应当在里面的，于是就这么造了一个组件：\n\n```XML\n<GenderSelect></GenderSelect>\n```\n\n这个组件非常美好，只需直接放在任意的界面中，就能显示带有性别数据的下拉框了。性别的数据很自然地是放在组件的实现内部，一个写死的数组中。这个太简单了，我们改一下，改成商品销售的国家下拉框。\n\n表面上看，这个没什么区别，但我们有个要求，本公司商品销售的国家的信息是统一配置的，也就是说，这个数据来源于服务端。这时候，你是不是想把一个http请求封装到这组件里？\n\n这样做也不是不可以，但存在至少两个问题：\n\n- 如果这类组件在同一个界面中出现多次，就可能存在请求的浪费，因为有一个组件实例就会产生一个请求。\n- 如果国家信息的配置界面与这个组件同时存在，当我们在配置界面中新增一个国家了，下拉框组件中的数据并不会实时刷新。\n\n第一个问题只是资源的浪费，第二个就是数据的不一致了。曾经在很多系统中，大家都是手动刷新当前页面来解决这问题的，但到了这个时代，人们都是追求体验的，在一个全组件化的解决方案中，不应再出现此类问题。\n\n如何解决这样的问题呢？那就是引入一层Store的概念，每个组件不直接去到服务端请求数据，而是到对应的前端数据缓存中去获取数据，让这个缓存自己去跟服务端保持同步。\n\n所以，在实际做方案的过程中，不管是基于Angular，React，Polymer，最后肯定都做出一层Store了，不然会有很多问题。\n\n# 5. 为什么MVVM是一种很好的选择\n\n我们回顾一下刚才那个下拉框的组件，发现存在几个问题：\n\n- 界面不好调整。刚才的那个例子相对简单，如果我们是一个省市县三级联动的组件，就比较麻烦了。比如说，我们想要把水平布局改成垂直的，又或者，想要把中间的label的字改改，都会非常麻烦。按照传统的做组件的方式，就要加若干配置项，然后组件里面去分别判断，修改DOM结构。\n- 如果数据的来源不是静态json，而是某个动态的服务接口，那用起来就很麻烦。\n- 我们更多地需要业务逻辑的复用和纯“控件”的复用，至于那些绑定业务的界面组件，复用性其实很弱。\n\n所以，从这些角度，会尽量期望在HTML界面层与JavaScript业务逻辑之间，存在一种分离。\n\n这时候，再看看绝大多数界面组件存在什么问题：\n\n有时候我们考虑一下DOM操作的类型，会发现其实是很容易枚举的：\n\n- 创建并插入节点\n- 移除节点\n- 节点的交换\n- 属性的设置\n\n多数界面组件封装的绝大部分内容不过是这些东西的重复。这些东西，其实是可以通过某些配置描述出来的，比如说，某个数组以什么形式渲染成一个select或者无序列表之类，当数组变动，这些东西也跟着变动，这些都应当被自动处理，如果某个方案在现在这个时代还手动操作这些，那真的是一种落伍。\n\n所以我们可以看到，以Angular，Knockout，Vue，Avalon为代表的框架们在这方面做了很多事，尽管理念有所差异，但大方向都非常一致，也就是把大多数命令式的DOM操作过程简化为一些配置。\n\n有了这种方式之后，我们可以追求不同层级的复用：\n\n- 业务模型因为是纯逻辑，所以非常容易复用\n- 视图模型基本上也是纯逻辑，界面层多数是纯字符串模板，同一个视图模型搭配不同的界面模板，可以实现视图模型的复用\n- 同一个界面模板与不同的视图模型组合，也能直接组合出完全不同的东西\n\n所以这么一来，我们的复用粒度就非常灵活了。正因为这样，我一直认为Angular这样的框架战略方向是很正确的，虽然有很多战术失误。我们在很多场景下，都是需要这样的高效生产手段的。\n\n# 6. 组件的长期积累\n\n我们做组件化这件事，一定是一种长期打算，为了使得当前的很多东西可以作为一种积累，在将来还能继续使用，或者仅仅作较小的修改就能使用，所以必须考虑对未来标准的兼容。主要需要考虑的方面有这几点：\n\n- 尽可能中立于语言和框架，使用浏览器的原生特性\n- 逻辑层的模块化（ECMAScript module）\n- 界面层的元素化（Web Components）\n\n之前有很多人对Angular 2.0的激进变更很不认同，但它的变更很大程度上是对标准的全面迎合。这不仅仅是它的问题，其实是所有前端框架的问题。不面对这些问题，不管现在多么好，将来都是死路一条。这个问题的根源是，这几个已有的规范约束了模块化和元素化的推荐方式，并且，如果要对当前和未来两边做适配的话，基本就没法干了，导致以前的都不得不做一定的迁移。\n\n模块化的迁移成本还比较小，无论是之前AMD还是CMD的，都可以根据一些规则转换过来，但组件化的迁移成本太大了，几乎每种框架都会提出自己的理念，然后有不同的组件化理念。\n\n还是从三个典型的东西来说：Polymer，React，Angular。\n\nPolymer中的组件化，其实就是标签化。这里的标签，并不只是界面元素，甚至逻辑组件也可以这样，比如这个代码：\n\n```HTML\n<my-panel>\n\t<core-ajax id=\"ajax\" url=\"http://url\" params=\"{{formdata}}\" method=\"post\"></core-ajax>\n</my-panel>\n```\n\n注意到这里的core-ajax标签，很明显这已经是纯逻辑的了，在大多数前端框架或者库中，调用ajax肯定不是这样的，但在浏览器端这么干也不是它独创，比如flash里面的WebService，比如早期IE中基于htc实现的webservice.htc等等，都是这么干的。在Polymer中，这类东西称为非可见元素（non-visual-element）。\n\nReact的组件化，跟Polymer略有不同，它的界面部分是标签化，但如果有单纯的逻辑，还是纯JavaScript模块。\n\n既然大家的实现方式都那么不一致，那我们怎么搞出尽量可复用的组件呢？问题到最后还是要绕到Web Components上。\n\n在Web Components与前端组件化框架的关系上，我觉得是这么个样子：\n\n各种前端组件化框架应当尽可能以Web Components为基石，它致力于组织这些Components与数据模型之间的关系，而不去关注某个具体Component的内部实现，比如说，一个列表组件，它究竟内部使用什么实现，组件化框架其实是不必关心的，它只应当关注这个组件的数据存取接口。\n\n然后，这些组件化框架再去根据自己的理念，进一步对这些标准Web Components进行封装。换句话说，业务开发人员使用某个组件的时候，他是应当感知不到这个组件内部究竟使用了Web Components，还是直接使用传统方式。（这一点有些理想化，可能并不是那么容易做到，因为我们还要管理像import之类的事情）。\n\n# 7. 我们需要关注什么\n\n目前来看，前端框架/库仍然处于混战期，可比中国历史上的春秋战国，百家齐放，作为跟随者来说，这是很痛苦的，因为无所适从，很可能你作为一个企业的前端架构师或者技术经理，需要做一些选型工作，但选哪个能保证几年后不被淘汰呢？基本没有。\n\n虽然我们不知道将来什么框架会流行，但我们可以从一些细节方面去关注，某个具体的方面，将来会有什么，也可以了解一下在某个具体领域存在什么样的方案。一个完整的框架方案，无非是以下多个方面的综合。\n\n## 7.1 模块化\n\n这块还是不讲了，支付宝seajs还有百度ecomfe这两个团队的人应该都能比我讲得好得多。\n\n## 7.2 Web Components\n\n本文前面讨论过一些，也不深入了。\n\n## 7.3 变更检测\n\n我们知道，现代框架的一个特点是自动化，也就是把原有的一些手动操作提取。在前端编程中，最常见的代码是在干什么呢？读写数据和操作DOM。不少现代的框架/库都对这方面作了处理，比如说通过某种配置的方式，由框架自动添加一些关联，当数据变更的时候，把DOM进行相应修改，又比如，当DOM发生变动的时候，也更新对应的数据。\n\n这个关联过程可能会用到几种技术。首先我们看怎么知道数据在变化，这里面有三种途径：\n\n一、存取器的封装。这个的意思也就是对数据进行一层包装，比如：\n\n```JavaScript\nvar data = {\n\tname: \"aaa\",\n\tgetName: function() {\n\t\treturn this.name;\n\t},\n\tsetName: function(value) {\n\t\tthis.name = value;\n\t}\n}\n```\n\n这样，不允许用户直接调用data.name，而是调用对应的两个函数。Backbone就是通过这样的机制实现数据变动观测的，这种方式适用于几乎所有浏览器，缺点就是比较麻烦，要对每个数据进行包装。\n\n这个机制在稍微新一点的浏览器中，也有另外一种实现方式，那就是defineProperty相关的一些方法，使用更优雅的存取器，这样外界可以不用调用函数，而是直接用data.name这样进行属性的读写。\n\n国产框架avalon使用了这个机制，低版本IE中没有defineProperty，但在低版本IE中不止有JavaScript，还存在VBScript，那里面有存取器，所以他巧妙地使用了VBS做了这么一个兼容封装。\n\n基于存取器的机制还有个麻烦，就是每次动态添加属性，都必须再添加对应的存取器，否则这个属性的变更就无法获取。\n\n二、脏检测。\n\n以Angular 1.x为代表的框架使用了脏检测来获知数据变更，这个机制的大致原理是：\n\n保存数据的新旧值，每当有一些DOM或者网络、定时器之类的事件产生，用这个事件之后的数据去跟之前保存的数据进行比对，如果相同，就不触发界面刷新，否则就刷新。\n\n这个方式的理念是，控制所有可能导致数据变更的来源（也就是各种事件），在他们可能对数据进行操作之后，判断新旧数据是否有变化，忽略所有中间变更，也就是说，如果你在同一个事件中，把某个数据任意修改了很多次，但最后改回来了，框架会认为你什么都没干，也就不会通知界面去刷新了。\n\n不可否认的是，脏检测的效率是比较低的，主要是不能精确获知数据变更的影响，所以当数据量更大的情况下，浪费更严重，需要手动作一些优化。比如说一个很大的数组，生成了一个界面上的列表，当某个项选中的时候，改变颜色。在这种机制下，每次改变这个项的数据状态，就需要把所有的项都跟原来比较一遍，然后，还要再全部比较一次发现没有关联引起的变化了，才能对应刷新界面。\n\n三、观察机制。\n\n在ES7里面，引入了Object的observe方法，可以用于监控对象或数组的变动。\n\n这是目前为止最合理的观测方案。这个机制很精确高效，比如说，连长跟士兵说，你去观察对面那个碉堡里面的动静。这个含义很复杂，包括什么呢？\n\n- 是不是加人了\n- 是不是有人离开了\n- 谁跟谁换岗了\n- 上面的旗子从太阳旗换成青天白日了\n\n所谓观察机制，也就是观测对象属性的变更，数组元素的新增，移除，位置变更等等。我们先思考一下界面和数据的绑定，这本来就应当是一个外部的观察，你是数据，我是界面，你点头我微笑，你伸手我打人。这种绑定本来就应当是个松散关系，不应当因为要绑定，需要破坏原有的一些东西，所以很明显更合理。\n\n除了数据的变动可以被观察，DOM也是可以的。但是目前绝大多数双向同步框架都是通过事件的方式把DOM变更同步到数据上。比如说，某个文本框绑定了一个对象的属性，那很可能，框架内部是监控了这个文本框的键盘输入、粘贴等相关事件，然后取值去往对象里写。\n\n这么做可以解决大部分问题，但是如果你直接myInput.value=\"111\"，这个变更就没法获取了。这个不算大问题，因为在一个双向绑定框架中，一个既被监控，又手工赋值的东西，本身也比较怪，不过也有一些框架会尝试从HTMLInputELement的原型上去覆盖value赋值，尝试把这种东西也纳入框架管辖范围。\n\n另外一个问题，那就是我们只考虑了特定元素的特定属性，可以通过事件获取变更，如何获得更广泛意义上的DOM变更？比如说，一般属性的变更，或者甚至子节点的增删？\n\nDOM4引入了MutationObserver，用于实现这种变更的观测。在DOM和数据之间，是否需要这么复杂的观测与同步机制，目前尚无定论，但在整个前端开发逐步自动化的大趋势下，这也是一种值得尝试的东西。\n\n复杂的关联监控容易导致预期之外的结果：\n\n- 慕容复要复国，每天读书练武，各种谋划\n- 王语嫣观察到了这种现象，认为表哥不爱自己了\n- 段誉看到神仙姐姐闷闷不乐，每天也茶饭不思\n- 镇南王妃心疼爱子，到处调查这件事的原委，意外发现段正淳还跟旧爱有联系\n- ……\n\n总之这么下来，最后影响到哪里了都不知道，谁让丘处机路过牛家村呢？\n\n所以，变更的关联监控是很复杂的一个体系，尤其是其中产生了闭环的时候。搭建整个这么一套东西，需要极其精密的设计，否则熟悉整套机制的人只要用特定场景轻轻一推就倒了。灵智上人虽然武功过人，接连碰到欧阳锋，周伯通，黄药师，全部都是上来就直接被抓了后颈要害，大致就是这意思。\n\npolymer实现了一个[observe-js](https://github.com/Polymer/observe-js)，用于观测数组、对象和路径的变更，有兴趣的可以关注。\n\n在有些框架，比如aurelia中，是混合使用了存取器和观察模式，把存取器作为观察模式的降级方案，在浏览器不支持observe的情况下使用。值得一提的是，在脏检测方式中，变更是合并后批量提交的，这一点常常被另外两种方案的使用者忽视。其实，即使用另外两种方式，也还是需要一个合并与批量提交过程。\n\n怎么理解这个事情呢？数据的绑定，最终都是要体现到界面上的，对于界面来说，其实只关注你每一次操作所带来的数据变更的始终，并不需要关心中间过程。比如说，你写了这么一个循环，放在某个按钮的点击中：\n\n```JavaScript\nfor (var i=0; i<10000; i++) {\n\tobj.a += 1;\n}\n```\n\n界面有一个东西绑定到这个a，对框架来说，绝对不应当把中间过程直接应用到界面上，以刚才这个例子来说，合理的情况只应当存在一次对界面DOM的赋值，这个值就是对obj.a进行了10000次赋值之后的值。尽管用存取器或者观察模式，发现了对obj上a属性的这10000次赋值过程，这些赋值还是都必须被舍弃，否则就是很可怕的浪费。\n\nReact使用虚拟DOM来减少中间的DOM操作浪费，本质跟这个是一样的，界面只应当响应逻辑变更的结束状态，不应当响应中间状态。这样，如果有一个ul，其中的li绑定到一个1000元素的数组，当首次把这个数组绑定到这个ul上的时候，框架内部也是可以优化成一次DOM写入的，类似之前常用的那种DocumentFragment，或者是innerHTML一次写入整个字符串。在这个方面，所有优化良好的框架，内部实现机制都应当类似，在这种方案下，是否使用虚拟DOM，对性能的影响都是很小的。\n\n## 7.4 Immutable Data\n\nImmutable Data是函数式编程中的一个概念，在前端组件化框架中能起到一些很独特的作用。\n\n它的大致理念是，任何一种赋值，都应当被转化成复制，不存在指向同一个地方的引用。比如说：\n\n```JavaScript\nvar a = 1;\nvar b = a;\nb = 2;\n\nconsole.log(a==b);\n```\n\n这个我们都知道，b跟a的内存地址是不一致的，简单类型的赋值会进行复制，所以a跟b不相等。但是：\n\n```JavaScript\nvar a = {\n\tcounter : 1\n};\nvar b = a;\n\nb.counter++;\nconsole.log(a.counter==b.counter);\n```\n\n这时候因为a和b指向相同的内存地址，所以只要修改了b的counter，a里面的counter也会跟着变。\n\nImmutable Data的理念是，我能不能在这种赋值情况下，直接把原来的a完全复制一份给b，然后以后大家各自变各自的，互相不影响。光凭这么一句话，看不出它的用处，看例子：\n\n对于全组件化的体系，不可避免会出现很多嵌套的组件。嵌套组件是一个很棘手的问题，在很多时候，是不太好处理的。嵌套组件所存在的问题主要在于生命周期的管理和数据的共享，很多已有方案的上下级组件之间都是存在数据共享的，但如果内外层存在共享数据，那么就会破坏组件的独立性，比如下面的一个列表控件：\n\n```HTML\n<my-list list-data=\"{arr}\">\n\t<my-listitem></my-listitem>\n\t<my-listitem></my-listitem>\n\t<my-listitem></my-listitem>\n</my-list>\n```\n\n我们在赋值的时候，一般是在外层整体赋值一个类似数组的数据，而不是自己挨个在每个列表项上赋值，不然就很麻烦。但是如果内外层持有相同的引用，对组件的封装性很不利。\n\n比如在刚才这个例子里，假设数据源如下：\n\n```JavaScript\nvar arr = [\n\t{name: \"Item1\"}, \n\t{name: \"Item2\"}, \n\t{name: \"Item3\"}\n];\n```\n\n通过类似这样的方式赋值给界面组件，并且由它在内部给每个子组件分别进行数据项的赋值：\n\n```JavaScript\nlist.data = arr;\n```\n\n赋值之后会有怎样的结果呢？\n\n```JavaScript\nconsole.log(list.data == arr);\nconsole.log(listitem0.data == arr[0]);\nconsole.log(listitem1.data == arr[1]);\nconsole.log(listitem2.data == arr[2]);\n```\n\n这种方案里面，后面那几个log输出的结果都会是true，意思就是内层组件与外层共享数据，一旦内层组件对数据进行改变，外层中的也就改变了，这明显是违背组件的封装性的。\n\n所以，有一些方案会引入Immutable Data的概念。在这些方案里，内外层组件的数据是不共享的，它们的引用不同，每个组件实际上是持有了自己的数据，然后引入了自动的赋值机制。\n\n这时候再看看刚才那个例子，就会发现两层的职责很清晰：\n\n- 外层持有一个类似数组的东西arr，用于形成整个列表，但并不关注每条记录的细节\n- 内层持有某条记录，用于渲染列表项的界面\n- 在整个列表的形成过程中，list组件根据arr的数据长度，实例化若干个listitem，并且把arr中的各条数据赋值给对应的listitem，而这个赋值，就是immutable data起作用的地方，其实是把这条数据复制了一份给里面，而不是把外层这条记录的引用赋值进去。内层组件发现自己的数据改变之后，就去进行对应的渲染\n- 如果arr的条数变更了，外层监控这个数据，并且根据变更类型，添加或者删除某个列表项\n- 如果从外界改变了arr中某一条记录的内容，外层组件并不直接处理，而是给对应的内层进行了一次赋值\n- 如果列表项中的某个操作，改变了自身的值，它首先是把自己持有的数据进行改变，然后，再通过immutable data把数据往外同步一份，这样，外层组件中的数据也就更新了。\n\n所以我们再看这个过程，真是非常清晰明了，而且内外层各司其职，互不干涉。这是非常有利于我们打造一个全组件化的大型Web应用的。各级组件之间存在比较松散的联系，而每个组件的内部则是封闭的，这正是我们所需要的结果。\n\n说到这里，需要再提一个容易混淆的东西，比如下面这个例子：\n\n```HTML\n<outer-component>\n\t<inner-component></inner-component>\n</outer-component>\n```\n\n如果我们为了给inner-component做一些样式定位之类的事情，很可能在内外层组件之间再加一些额外的布局元素，比如变成这样：\n\n```HTML\n<outer-component>\n\t<div>\n\t\t<inner-component></inner-component>\n\t</div>\n</outer-component>\n```\n\n这里中间多了一级div，也可能是若干级元素。如果有用过Angular 1.x的，可能会知道，假如这里面硬造一级作用域，搞个ng-if之类，就可能存在多级作用域的赋值问题。在上面这个例子里，如果在最外层赋值，数据就会是outer -> div -> inner这样，那么，从框架设计的角度，这两次赋值都应当是immutable的吗？\n\n不是，第一次赋值是非immutable，第二次才需要是，immutable赋值应当仅存在于组件边界上，在组件内部不是特别有必要使用。刚才的例子里，依附于div的那层变量应当还是跟outer组件在同一层面，都属于outer组件的人民内部矛盾。\n\n这里是facebook实现的[immutable-js库](http://facebook.github.io/immutable-js/)\n\n## 7.5 Promise与异步\n\n前端一般都习惯于用事件的方式处理异步，但很多时候纯逻辑的“串行化”场景下，这种方式会让逻辑很难阅读。在新的ES规范里，也有yield为代表的各种原生异步处理方案，但是这些方案仍然有很大的理解障碍，流行度有限，很大程度上会一直停留在基础较好的开发人员手中。尤其是在浏览器端，它的受众应该会比node里面还要狭窄。\n\n前端里面，处理连续异步消息的最能被广泛接受的方案是promise，我这里并不讨论它的原理，也不讨论它在业务中的使用，而是要提一下它在组件化框架内部所能起到的作用。\n\n现在已经没有哪个前端组件化框架可以不考虑异步加载问题了，因为，在前端这个领域，加载就是一个绕不过去的坎，必须有了加载，才能有执行过程。每个组件化框架都不能阻止自己的使用者规模膨胀，因此也应当在框架层面提出解决方案。\n\n我们可能会动态配置路由，也可能在动态加载的路由中又引入新的组件，如何控制这些东西的生命周期，值得仔细斟酌，如果在框架层面全异步化，对于编程体验的一致性是有好处的。将各类接口都promise化，能够在可维护性和可扩展性上提供较多便利。\n\n我们之前可能熟知XMLHTTP这样的通信接口，这个东西虽然被广为使用，但是在优雅性等方面，存在一些问题，所以最近出来了替代方案，那就是fetch。\n\n细节可以参见月影翻译的这篇[【翻译】这个API很“迷人”——(新的Fetch API)](http://www.w3ctech.com/topic/854)\n\n在不支持的浏览器上，也有github实现的一个polyfill，虽然不全，但可以凑合用[window.fetch polyfill](https://github.com/github/fetch)\n\n大家可以看到，fetch的接口就是基于promise的，这应当是前端开发人员最容易接受的方案了。\n\n# 7.6 Isomorphic JavaScript\n\n这个东西的意思是前后端同构的JavaScript，也就是说，比如一块界面，可以选择在前端渲染，也可以选择在后端渲染，值得关注，可以解决像seo之类的问题，但现在还不能处理很复杂的状况，持续关注吧。\n\n# 8. 小结\n\n很感谢能看到这里，以上这些是我近一年的一些思考总结。从技术选型的角度看，做大型Web应用的人会很痛苦，因为这是一个青黄不接的年代，目前已有的所有框架/库都存在不同程度的缺陷。当你向未来看去，发现它们都是需要被抛弃，或者被改造的，人最痛苦的是在知道很多东西不好，却又要从中选取一个来用。@严清 跟@寸志 @题叶讨论过这个问题，认为现在这个阶段的技术选型难做，不如等一阵，我完全赞同他们的观点。\n\n选型是难，但是从学习的角度，可真的是挺好的时代，能学的东西太多了，我每天路上都在努力看有可能值得看的东西，可还是看不完，只能努力去跟上时代的步伐。\n\n以下一段，与诸位共勉：\n\n> It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way--in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only."
  },
  {
    "path": "temp/01.md",
    "content": "基于AngularJS构建Web应用门户\n====\n\n我们常常遇到有构建Web应用门户的需求，\n\n大型单页应用要做的第一件事就是分层架构。分层架构有利于把职责清晰化，每块可以单独做测试。\n\n数据模型层必须独立出来，所以有些Angular文章推荐给所有service建一个module，然后把一切包含数据模型的service都放进去。可以这么做，也可以再细分。\n\n分层做完，就要考虑懒加载的问题了。\n\n我们看看一个典型的场景，一个工作台，或者说门户界面，上面能够放很多小部件，类似iGoogle那样，用户可以任意加已有的部件，这些部件都是基于某种约定，由第三方开发人员完成。这种如果在单页应用里，该怎么实现呢？\n\n企业应用门户的设计，其实是一个很考验规划水准的事。因为它首先要集成别人，还要考虑如何被别人集成。它的设计思路直接影响到一大堆代码按照什么规范进行开发。挂在这个门户上的业务模块，有的很简单，不会影响别人，有的可能会影响别人，要想个办法把它隔离起来，还有的本身就要跟别人通讯。\n\n先来看部件有些什么可以做的事。他可以有界面，有逻辑，有样式。这些都可以分别动态加载出来，比如HTML片段可以ng-include或者$get过来append，js文件可以require，css可以行间也可以动态加rule，因为这些部件是要跟我们主界面在同一个页面作用域内，所以要尽量营造隔离的环境。\n\n#1. 无逻辑的界面部件\n\n如果是纯HTML话很好办，它直接拿来放在某容器里就可以了，互相影响不到，直接搞个ng-include把它包含到主界面就可以了。\n\n    <div ng-include src=\"'partial/simple.html'\"></div>\n\n我们来写个directive专门做部件加载器吧。先来个最简单的：\n\n    portal.directive(\"htmlLoader\", function ($http) {\n    \treturn function (scope, element, attrs) {\n    \t\tvar url = attrs.url;\n    \t\t$http.get(url).success(function (result) {\n    \t\t\telement.html(result);\n    \t\t});\n    \t};\n    });\n\n用的时候也很简单：\n\n    <div html-loader url=\"partial/simple.html\"></div>\n    \n可以看到，纯HTML模版加载非常简单，只要取过来放置到元素上就好了。\n\n#2. 带行间逻辑的界面部件\n\n什么是行间逻辑呢？意思是这一段JavaScript逻辑只作用于当前界面片段，出于某些原因，这些逻辑必须紧跟当前的界面，需要在全页面加载出来之前就能执行，比如某些搜索，只要搜索框一出来就应当能操作，这就是一种典型的需求。\n\n我们看看刚才的这种加载器，里面用了element.html(result)，这么做显然不能执行这个result中含有的JavaScript代码，怎么办呢？\n\n我们想个办法来把js分离出来：\n\n\n\n#3. 有独立命名空间的界面部件\n\nJavaScript的话，最基本的就是避免全局变量，在Angular体系中，还需要作些特殊的考虑。我们知道，Angular里面，第一级组织单元是module，但它这个module的概念跟AMD那种module的不同，如果说AMD的module相当于Java Class的级别，Angular的要相当于package了。\n\n这就有了我们的第一个问题：部件里面的JavaScript代码跟主界面的在不在一个module内？如果是别的框架，这不是个问题，但这货是Angular，有些纠结，这里面还分两种情况：\n\n- 部件很独立，相互无任何协作关系\n- 部件之间有协作，包括可能共享代码或者有通信\n\n第一种很好办，我们可以让这个部件自己定义ng-app，这样它就是一个独立隔离的环境了。来写一下代码：\n\n    portal.module(\"mis\").directive(\"appLoader\", function ($http) {\n    \treturn function (scope, element, attrs) {\n    \t\tvar module = attrs.module;\n    \t\tvar url = attrs.url;\n    \t\tvar scripts = attrs.scripts.split(\",\") || [];\n    \n    \t\t$script(scripts, function () {\n    \t\t\tscope.$apply(function () {\n    \t\t\t\t$http.get(url).success(function (result) {\n    \t\t\t\t\tvar elem = angular.element(result);\n    \t\t\t\t\tangular.bootstrap(elem, [module]);\n    \t\t\t\t\telement.append(elem);\n    \t\t\t\t});\n    \t\t\t});\n    \t\t});\n    \t};\n    });\n    \n部件代码：\n\nclock.html\n\n    <div ng-controller=\"ClockCtrl\">\n    \t<span ng-bind=\"now\"></span>\n    </div>\n\nclock.js\n    \n    angular.module(\"widgets\", []);\n    \n    angular.module(\"widgets\").controller(\"ClockCtrl\", function($timeout, $scope) {\n    \t$scope.now = new Date();\n    \tupdateLater();\n    \n    \tvar timeoutId;\n    \tfunction updateLater() {\n    \t\t$scope.now = new Date();\n    \t\ttimeoutId = $timeout(function() {\n    \t\t\tupdateLater();\n    \t\t}, 1000);\n    \t}\n    });\n\n使用的时候：\n\n    <div app-loader url=\"partial/clock.html\" module=\"widgets\" scripts=\"js/widgets/clock.js\"></div>\n    \n分析这段代码，可以看到，这里要做两件事：加载依赖项的js文件，加载html片段，然后简单粗暴地调用了Angular的bootstrap方法，这是什么意思呢？\n\nAngular可以用两种方式来初始化，第一种是通过在标签上写ng-app，然后页面加载的时候会自动做这个事情，第二种是直接调用angular.bootstrap方法，手动初始化。\n\n我们新加载的这个部件因为拥有独立命名空间，所以它的js代码里面也会很独立，在clock.js里面新定义了一个module叫做widgets，整个部件都运行在这个命名空间下。\n\n到这里，事情也没结束，因为我们这里只演示了一个\n\n#4. 跟主界面共享命名空间的部件\n\n第二种很麻烦，因为Angular的module依赖关系想要动态加很复杂，所以我们只能约定已有的module，然后让部件的js从这个module上加自己的代码。\n\n    portal.directive(\"partialLoader\", function ($http, $compile) {\n    \treturn function (scope, element, attrs) {\n    \t\tvar module = attrs.module;\n    \t\tvar url = attrs.url;\n    \t\tvar scripts = attrs.scripts.split(\",\") || [];\n    \n    \t\t$script(scripts, function () {\n    \t\t\tscope.$apply(function () {\n    \t\t\t\t$http.get(url).success(function (result) {\n    \t\t\t\t\telement.html(result);\n    \t\t\t\t\t$compile(element.contents())(scope);\n    \t\t\t\t});\n    \t\t\t});\n    \t\t});\n    \t};\n    });\n\n使用的时候：\n\n    <div partial-loader url=\"partial/goods.html\" scripts=\"js/order/goods.js\"></div>\n    \n有时候会有\n\n#5. 已载入文件的缓存\n\n#6. 样式的隔离\n\nCSS的隔离也比麻烦，因为样式是全局生效的，我们必须约定某种规则，让第三方开发者的样式只能挂在他这个部件下，无论是行间样式还是引入的外部样式，都不能起到这个限制的作用。所以，必须使用更苛刻的手段。\n\n思路肯定是要从某种元素往下写的，我们给他一个特定元素，他以这个为基准，所有自己的CSS都定义在这个元素的选择符之下。那么，这些不同的部件之间，又要如何区分呢？\n\n想想，部件之间其实只有很少东西能用于区分，比如说全局唯一的部件名，所以，我们以此为依据，这样来约定：\n\n- 在主界面中包含部件的容器，生成一个如下的元素：\n\n这样\n\n    <div data-appname=\"sampleApp\"></div>\n\n\n真实的部件HTML代码会被放置在其中，举例来说，这个部件的HTML是：\n\n    <ul>\n        <li>Apple</li>\n        <li>Pear</li>\n    </ul>\n\n它的CSS就可以这么写：\n\n    div[data-appname=\"sampleApp\"] ul {\n    \n    }"
  },
  {
    "path": "temp/02.md",
    "content": "Web应用的组件化开发（四）\n====\n\n团队建设\n----\n\n一个良好的前端工业化体系也离不开良好的团队。如果可以随意期望，当然是团队里的每个人水平都很高，相处又融洽，对整个前端组件体系的思想也非常熟悉，刘备带着一万个赵子龙，曹操吓得转身就跑，这个太不切实际了。\n\n现在我们的问题就变成了：如果有一个普通的团队，怎么去提升它？凝聚力方面的事情我不说，只尝试从提升团队技能方面谈些想法。\n\n有不少公司，因为创办之初缺少前端规划高手，很多事情都是由后端架构代劳的，慢慢做着，就发现前端的部分做得比较散乱，很难控制到有序的状态，于是很苦恼，想着怎么去解决这些问题。\n\n设想一下我们进入这么一个团队，将会通过什么途径去改变他们现有的做事方式呢？\n\n对于任何一家企业，产品和技术方面想要有延续性，必须建设知识库。知识库的形式可以是文档的集合，也可以有代码、示例等东西，知识库的规划是一个很复杂的事情，因为它要让这些知识点易学习，还要易查询，在新员工融入团队的过程中，一个友好的知识库体系将发挥极大的作用。\n\n对前端新手来说，首先应当为他提供观念方面的培训，即：前端做事的思维方式。其次，需要让他知道他每次做东西的时候，遇到的一些固有模式能够在什么地方找到参考，甚至直接就可以照搬用。对于一个新人来说，必须先要能在不明白原理的情况下先能做事，然后在做事过程中慢慢体会有些东西是为什么这样。我们没有时间等到他每个东西都弄懂了才干活，现在这个世界是个短平快的世界，给不了你先学习再干活的空间，只能推着你一边干一边学。\n\n那么，这些固有的模式，就需要先总结出来。从无到有是一件很困难的事情，但不能因为它困难就不做。可以聚集团队中经验最丰富的同事，花一些时间来总结和规划在自身行业中一些很常见的代码模板，维护到知识库系统上，供新人参阅。\n\n后端出身的人，他对系统的规划一般会怎样呢？他通常倾向于引入后端模板，比如freemarker这种，或者引入类似jsf这样的后端标签库的封装机制。这么做对前端程序员来说，影响倒不是特别大，最苦闷的会是那些UI设计师，他想要改一块界面就非常麻烦了。"
  },
  {
    "path": "temp/03.md",
    "content": "前几天因为上班时候在知乎写了一篇比较长的回答，有位前同事开玩笑说上班写这个领导不找谈话吗？\n\n这句话引出了我一些想法，作为一名架构师，他的工作职责应当包含哪些内容呢？"
  },
  {
    "path": "temp/04.md",
    "content": "那些年我们错过的前端技术\n====\n\n\n#1. HTC\n\nHTC（HTML Component）首次出现于微软IE5.0，作为自定义元素附加行为（Behaviors）的一种技术，它\n\nHTC有两种使用方式，一种是附加在样式里，给使用这个样式的元素添加额外的行为\n\n    <style>\n       ns\\:elem {\n           behavior:url(foo.htc);\n       } \n    </style>\n\n"
  },
  {
    "path": "temp/05.md",
    "content": "Web应用的组件化开发（三）\n====\n\n架构与协作\n----\n\n在前面两篇文章中，我们叙述了组件化开发的基本原理和管控机制，这就相当于工厂引入了新的生产流水线。光有设备不行啊，还需要有对产品的一些规划过程，然后才能去生产。\n\n开发一个大型系统，最重要的是什么呢？是可控性。大系统最害怕开发过程失控，首先就要求整体架构是良好的，如果部件出问题，比较容易调整，整体出问题就麻烦了。所以，作为大型系统的架构师，最重要的感觉就是一切尽在掌控之中。那么，如何解决可控性的问题呢？\n\n高手下棋，首重棋势。什么是棋势呢？这是整个一盘棋的灵魂，起手数子，布出全盘脉络，从此金角银边，皆不出掌握矣。对软件而言，一个好的系统，必定先有好架构，把架构掌控住，也就控制了整个开发过程。\n\nWeb前端这个领域有些像很多年前的客户端软件开发，最开始，大家先搞各种基础框架，从API的层面完善底层建筑，近几年，就纷纷在谈论模块和组件了，然后各种模式层出不穷，令人目不暇接。\n\n之前这些，一般都是小规模的团队开发，如果是很大规模的团队，比如50人以上共同做一套东西呢？这一阵我想了很多，如果产品规模扩大，怎样让多个团队进行协同编程呢？他们共享一些组件，一起完善整个系统，让代码变得可控，变更成本可预测，重构风险尽可能降低，自动化的测试和发布过程。\n\n这个方面，目前关注的人还不是很多，但在一些大公司，比如阿里和百度，已经很多有识之士看到了这里面可做的事情，并且有一些成功的案例了，比如百度的FIS。\n\n#1. 逻辑代码的构建\n\nWeb应用系统中，JavaScript是一切交互的核心，但JavaScript本身是一个比较松散的语言，这样的语言来做大型系统，可靠性方面需要做很多事情来保障。传统的构建大型系统的语言，比如Java和C#，都有很多反向工程的工具，比如根据代码生成UML图，这就从一个方面让架构师能随时了解自己项目的结构和依赖关系，有机会去调整一些不合理的地方，而这些调整又可以反过来作用到代码上，这些过程很大程度上能让项目的整体稳定，因为它的重构成本是比较小的，这些重构可以不太打断开发过程，在一次项目开发过程中，能够有机会多次重构，从而降低了很多风险，消除掉绝大多数隐患。\n\n想要达到这样的效果，就必须在“拼装性”上做文章。一个大型系统，整体结构如何，模块关系如何，事件如何交互，这些都可以预先设计，在此之前，人们通常关注到的一般都是逻辑单元的隔离和依赖关系的定义。就如AMD这类规范所定义的，JavaScript代码按模块定义，并指定各自的依赖关系，这就有了良好架构的基础。\n\n但同时我们也看到，如果仅以此种方式进行开发，在过程中，架构师难以直观地了解整个代码的逻辑关系，更难做逻辑上的重构。\n\n如果在构建一个大型系统之前就有机会把它的模块依赖关系和交互都描述出来，并且以图形的方式展现，每个开发人员都有机会看到系统的全貌，然后从这些图形化的描述再生成代码的基本骨架，填充内容，逐渐看着这个系统有血有肉，活泼生动起来，每个人的成就感都会非常强烈。\n\n很多时候，架构师并不是一开始就能把所有东西都搞对的，如果做了一半，发现开始有些不合理的地方，想要做些调整，该怎么办呢？举例来说，项目做到一半，发觉在逻辑上，两个并列的目录其实应该是父子关系，想要把其中一个整体下沉一级，移动到另一个下面，这个改动还要尽可能不影响业务的开发过程。\n\n想要达到这些目的，我们的难处在哪里呢？需要把真实的代码和抽象的描述建立这么一种对应关系，能够实时反映彼此的变化。这种对应关系很难在代码中体现，因为代码是一个线性的东西，难以表达立体的结构。所以，我倾向于把这些关系放到外面，逻辑结构跟代码的物理文件分离，互相有联系。\n\n我们先看一个在线UML的Demo：[Online software modeling](http://www.genmymodel.com/ \"\")，注册之后，可以打开已有的示例，比如Online Shopping Cart，能够查看UML图形。\n\n![online-shopping-cart-diag.jpg](https://raw.github.com/xufei/blog/master/assets/web-components/online-shopping-cart-diag.jpg \"\")\n\n这个Demo可以大致说明我们的意图，但还是有差别。在这个在线Demo里面，可以直接生成Java代码，我们的思路也大致是要这样。\n\n考虑到JavaScript是一种灵活的语言，我们其实并不需要把类关系整得这么正式，要整出来的实际上是模块的关系，这里的模块可以暂时先理解成AMD那样的。\n\n所以，从模型到代码的这个方向，大致就是先绘制这么一种图，然后从图形生成最初的代码结构。\n\n在很多工具里，也提供反向的功能，就是从模块依赖关系反过来生成依赖图。在Chrome的插件Angular Batarang中，可以对正在运行的Angular系统分析模块依赖关系，生成一个图形。这个图形不太直观，但大致能说明意思了。\n\n我们来看这正反两面，如果合二为一的话，就非常好了，这也就像双向绑定，改数据能同步UI，在UI上操作能影响数据。用这里做比喻，代码就好比数据，逻辑关系图就好比UI，所以我们只要直接把依赖关系抓住，从始至终一直维护起来，就能解决所有的问题了。\n\n举例来说，如果有三个模块，分别是portal.User，portal.Goods，portal.Cart，表示用户、商品、购物车，它们所对应的模块，如果按传统方式，可能是这样：\n\nportal/user.js\n\n    define(\"portal.User\", [], function() {\n        //User\n    });\n\nportal/goods.js\n\n    define(\"portal.Goods\", [], function() {\n        //Goods\n    });\n    \nportal/cart.js\n\n    define(\"portal.Cart\", [\"portal.User\"], function(User) {\n        //Cart\n    });\n\n注意到这里，购物车Cart模块对用户User有依赖，而且三个代码存储的目录也是有所讲究的。\n\n我们打算把它变成怎样呢？\n\n首先，js文件里不再存放这些模块的名称和依赖关系：\n\nportal/user.js\n    \n    function User() {\n        //User\n    }\n\nportal/goods.js    \n\n    function Goods() {\n        //Goods\n    }\n    \nportal/cart.js\n\n    function Cart(User) {\n        //Cart\n    }       \n\n然后，新建一个文件，用于专门保存路径配置：\n    \n    var modules = [{\n        id: \"portal.User\", location: \"portal/user.js\"\n    }, {\n        id: \"portal.Goods\", location: \"portal/goods.js\"\n    }, {\n        id: \"portal.Cart\", location: \"portal/cart.js\", dependencies:[\"portal.User\"]\n    }];\n\n这时候我们完全可以任意更改某模块的物理路径，也可以对模块重命名，只需要同时查找各依赖项中相同的，也一起改掉即可，这就实现了物理存储和逻辑结构的分离。\n\n在整个项目中，这个文件至关重要，架构师需要凭借它来掌握代码结构，开发人员需要它来跑单元测试，配置管理员需要用它发布版本。\n\n很多时候，这么一个配置文件还是不够的，为什么呢，因为很多情况下有合并文件的需求，我们可以在这个文件基础上加一些结构，也可以额外再建一个配置文件，专门用于处理合并的信息。\n\n#2. 界面的整合\n\n我们构建一个Web系统，免不了有时候要集成别人，也可能被别人集成。\n\n#3. 样式的隔离\n\n\n\n整个系统的复用原则是：\n\n除视图以外的所有部分，在PC浏览器和移动端共用。\n如果存在可直接复用的视图层，可以拿出来共用，不强求。\n\n逻辑代码具有通用性的前提是跟DOM层彻底分离，而视图层的通用性其实很小，所以这一块不用特别去强求，即使是响应式设计，在很多地方带来的也可能是更多的负担。"
  }
]