前端开发技术的发展

导语 前端开发技术,从狭义的定义来看,是指围绕html、javascript、CSS这样一套体系的开发技术,它的运行宿主是浏览器。从广义的定义来看,包括了:专门为手持终端设计的类似WML这样的类HTML语言,类似WMLScript这样的
前端开发技术的发展

前端开发技术,从狭义的定义来看,是指围绕html、javascript、CSS这样一套体系的开发技术,它的运行宿主是浏览器。从广义的定义来看,包括了:

  • 专门为手持终端设计的类似WML这样的类HTML语言,类似WMLScript这样的类JavaScript语言。
  • VML和SVG等基于XML的描述图形的语言。
  • 从属于XML体系的XML,XPath,DTD等技术。
  • 用于支撑后端的ASP,JSP,ASP.net,PHP,nodejs等语言或者技术。
  • 被第三方程序打包的一种类似浏览器的宿主环境,比如Adobe AIR和使用HyBird方式的一些开发技术,如PhoneGap(它使用Android中的WebView等技术,让开发人员使用传统web开发技术来开发本地应用)
  • Adobe Flash,Flex,Microsoft Silverlight,Java Applet,JavaFx等RIA开发技术。

本文从狭义的前端定义出发,探讨一下这方面开发技术的发展过程。 <!--more--> 从前端开发技术的发展来看,大致可以分为以下几个阶段:

一. 刀耕火种

1. 静态页面

最早期的Web界面基本都是在互联网上使用,人们浏览某些内容,填写几个表单,并且提交。当时的界面以浏览为主,基本都是HTML代码,有时候穿插一些JavaScript,作为客户端校验这样的基础功能。代码的组织比较简单,而且CSS的运用也是比较少的。

最简单的是这样一个文件:

  1. <html> 
  2.     <head> 
  3.         <title>测试一</title> 
  4.     </head> 
  5.     <body> 
  6.         <h1>主标题</h1> 
  7.         <p>段落内容</p> 
  8.     </body> 
  9. </html> 

2. 带有简单逻辑的界面

这个界面带有一段JavaScript代码,用于拼接两个输入框中的字符串,并且弹出窗口显示。

  1. <html> 
  2.     <head> 
  3.         <title>测试二</title> 
  4.     </head> 
  5.     <body> 
  6.         <input id="firstNameInput" type="text" />  
  7.         <input id="lastNameInput" type="text" />  
  8.         <input type="button" onclick="greet()" /> 
  9.         <script language="JavaScript"
  10.         function greet() { 
  11.             var firstName = document.getElementById("firstNameInput").value; 
  12.             var lastName = document.getElementById("lastNameInput").value; 
  13.             alert("Hello, " + firstName + "." + lastName); 
  14.         } 
  15.         </script>  
  16.     </body> 
  17. </html> 

3. 结合了服务端技术的混合编程

由于静态界面不能实现保存数据等功能,出现了很多服务端技术,早期的有CGI(Common Gateway Interface,多数用C语言或者Perl实现的),ASP(使用VBScript或者JScript),JSP(使用Java),PHP等等,Python和Ruby等语言也常被用于这类用途。

有了这类技术,在HTML中就可以使用表单的post功能提交数据了,比如:

  1. <form method="post" action="username.asp"
  2.     <p>First Name: <input type="text" name="firstName" /></p> 
  3.     <p>Last Name: <input type="text" name="lastName" /></p> 
  4.     <input type="submit" value="Submit" /> 
  5. </form> 

在这个阶段,由于客户端和服务端的职责未作明确的划分,比如生成一个字符串,可以由前端的JavaScript做,也可以由服务端语言做,所以通常在一个界面里,会有两种语言混杂在一起,用<%和%>标记的部分会在服务端执行,输出结果,甚至经常有把数据库连接的代码跟页面代码混杂在一起的情况,给维护带来较大的不便。

  1. <html> 
  2.     <body> 
  3.         <p>Hello world!</p> 
  4.         <p> 
  5.         <% 
  6.             response.write("Hello world from server!"
  7.         %> 
  8.         </p> 
  9.     </body> 
  10. </html> 

4.组件化的萌芽

这个时代,也逐渐出现了组件化的萌芽。比较常见的有服务端的组件化,比如把某一类服务端功能单独做成片段,然后其他需要的地方来include进来,典型的有:ASP里面数据库连接的地方,把数据源连接的部分写成conn.asp,然后其他每个需要操作数据库的asp文件包含它。

上面所说的是在服务端做的,浏览器端通常有针对JavaScript的,把某一类的Javascript代码写到单独的JS文件中,界面根据需要,引用不同的js文件。针对界面的组件方式,通常利用frameset和iframe这两个标签。某一大块有独立功能的界面写到一个html文件,然后在主界面里面把它当作一个frame来载入,一般的B/S系统集成菜单的方式都是这样的。

此外,还出现了一些基于特定浏览器的客户端组件技术,比如IE浏览器的HTC(HTML Component)。这种技术最初是为了对已有的常用元素附加行为的,后来有些场合也用它来实现控件。微软ASP.net的一些版本里,使用这种技术提供了树形列表,日历,选项卡等功能。HTC的优点是允许用户自行扩展HTML标签,可以在自己的命名空间里定义元素,然后,使用HTML,JavaScript和CSS来实现它的布局、行为和观感。这种技术因为是微软的私有技术,所以逐渐变得不那么流行。

Firefox浏览器里面推出过一种叫XUL的技术,也没有流行起来。

二. 铁器时代

这个时代的典型特征是Ajax的出现。

1. AJAX

AJAX其实是一系列已有技术的组合,早在这个名词出现之前,这些技术的使用就已经比较广泛了,GMail因为恰当地应用了这些技术,获得了很好的用户体验。

由于Ajax的出现,规模更大,效果更好的Web程序逐渐出现,在这些程序中,JavaScript代码的数量迅速增加。出于代码组织的需要,“JavaScript框架”这个概念逐步形成,当时的主流是prototype和mootools,这两者各有千秋,提供了各自方式的面向对象组织思路。

2. JavaScript基础库

Prototype框架主要是为JavaScript代码提供了一种组织方式,对一些原生的JavaScript类型提供了一些扩展,比如数组、字符串,又额外提供了一些实用的数据结构,如:枚举,Hash等,除此之外,还对dom操作,事件,表单和Ajax做了一些封装。

Mootools框架的思路跟Prototype很接近,它对JavaScript类型扩展的方式别具一格,所以在这类框架中,经常被称作“最优雅的”对象扩展体系。

从这两个框架的所提供的功能来看,它们的定位是核心库,在使用的时候一般需要配合一些外围的库来完成。

jQuery与这两者有所不同,它着眼于简化DOM相关的代码。 例如:

  • DOM的选择

jQuery提供了一系列选择器用于选取界面元素,在其他一些框架中也有类似功能,但是一般没有它的简洁、强大。

  1. $("*")             //选取所有元素 
  2. $("#lastname")     //选取id为lastname的元素 
  3. $(".intro")            //选取所有class="intro"的元素 
  4. $("p")             //选取所有<p>元素 
  5. $(".intro.demo")   //选取所有 class="intro"且class="demo"的元素 
  • 链式表达式:

在jQuery中,可以使用链式表达式来连续操作dom,比如下面这个例子:

如果不使用链式表达式,可能我们需要这么写:

  1. var neat = $("p.neat"); 
  2. neat.addClass("ohmy"); 
  3. neat.show("slow"); 

但是有了链式表达式,我们只需要这么一行代码就可以完成这些:

  1. $("p.neat").addClass("ohmy").show("slow"); 

除此之外,jQuery还提供了一些动画方面的特效代码,也有大量的外围库,比如jQuery UI这样的控件库,jQuery Mobile这样的移动开发库等等。

3. 模块代码加载方式

以上这些框架提供了代码的组织能力,但是未能提供代码的动态加载能力。动态加载JavaScript为什么重要呢?因为随着Ajax的普及,jQuery等辅助库的出现,Web上可以做很复杂的功能,因此,单页面应用程序(SPA,Single Page Application)也逐渐多了起来。

单个的界面想要做很多功能,需要写的代码是会比较多的,但是,并非所有的功能都需要在界面加载的时候就全部引入,如果能够在需要的时候才加载那些代码,就把加载的压力分担了,在这个背景下,出现了一些用于动态加载JavaScript的框架,也出现了一些定义这类可被动态加载代码的规范。

在这些框架里,知名度比较高的是RequireJS,它遵循一种称为AMD(Asynchronous Module Definition)的规范。

比如下面这段,定义了一个动态的匿名模块,它依赖math模块

  1. define(["math"], function(math) { 
  2.     return { 
  3.         addTen : function(x) { 
  4.             return math.add(x, 10); 
  5.         } 
  6.     }; 
  7. });  

假设上面的代码存放于adder.js中,当需要使用这个模块的时候,通过如下代码来引入adder:

  1. <script src="require.js"></script> 
  2. <script> 
  3.     require(["adder"], function(adder) { 
  4.         //使用这个adder 
  5.     });  
  6. </script> 

RequireJS除了提供异步加载方式,也可以使用同步方式加载模块代码。AMD规范除了使用在前端浏览器环境中,也可以运行于nodejs等服务端环境,nodejs的模块就是基于这套规范定义的。(修订,这里弄错了,nodejs是基于类似的CMD规范的)

三. 工业革命

这个时期,随着Web端功能的日益复杂,人们开始考虑这样一些问题:

  • 如何更好地模块化开发
  • 业务数据如何组织
  • 界面和业务数据之间通过何种方式进行交互

在这种背景下,出现了一些前端MVC、MVP、MVVM框架,我们把这些框架统称为MV*框架。这些框架的出现,都是为了解决上面这些问题,具体的实现思路各有不同,主流的有Backbone,AngularJS,Ember,Spine等等,本文主要选用Backbone和AngularJS来讲述以下场景。

1. 数据模型

在这些框架里,定义数据模型的方式与以往有些差异,主要在于数据的get和set更加有意义了,比如说,可以把某个实体的get和set绑定到RESTful的服务上,这样,对某个实体的读写可以更新到数据库中。另外一个特点是,它们一般都提供一个事件,用于监控数据的变化,这个机制使得数据绑定成为可能。

在一些框架中,数据模型需要在原生的JavaScript类型上做一层封装,比如Backbone的方式是这样:

  1. var Todo = Backbone.Model.extend({ 
  2.     // Default attributes for the todo item. 
  3.     defaults : function() { 
  4.         return { 
  5.             title : "empty todo..."
  6.             order : Todos.nextOrder(), 
  7.             done : false 
  8.         }; 
  9.     }, 
  10.  
  11.     // Ensure that each todo created has `title`. 
  12.     initialize : function() { 
  13.         if (!this.get("title")) { 
  14.             this.set({ 
  15.                 "title" : this.defaults().title 
  16.             }); 
  17.         } 
  18.     }, 
  19.  
  20.     // Toggle the 'done' state of this todo item. 
  21.     toggle : function() { 
  22.         this.save({ 
  23.             done : !this.get("done"
  24.         }); 
  25.     } 
  26. }); 

上述例子中,defaults方法用于提供模型的默认值,initialize方法用于做一些初始化工作,这两个都是约定的方法,toggle是自定义的,用于保存todo的选中状态。

除了对象,Backbone也支持集合类型,集合类型在定义的时候要通过model属性指定其中的元素类型。

  1. // The collection of todos is backed by *localStorage* instead of a remote server. 
  2. var TodoList = Backbone.Collection.extend({ 
  3.     // Reference to this collection's model. 
  4.     model : Todo, 
  5.  
  6.     // Save all of the todo items under the '"todos-backbone"' namespace. 
  7.     localStorage : new Backbone.LocalStorage("todos-backbone"), 
  8.  
  9.     // Filter down the list of all todo items that are finished. 
  10.     done : function() { 
  11.         return this.filter(function(todo) { 
  12.             return todo.get('done'); 
  13.         }); 
  14.     }, 
  15.  
  16.     // Filter down the list to only todo items that are still not finished. 
  17.     remaining : function() { 
  18.         return this.without.apply(thisthis.done()); 
  19.     }, 
  20.  
  21.     // We keep the Todos in sequential order, despite being saved by unordered  
  22.     //GUID in the database. This generates the next order number for new items. 
  23.     nextOrder : function() { 
  24.         if (!this.length) 
  25.             return 1; 
  26.         return this.last().get('order') + 1; 
  27.     }, 
  28.  
  29.     // Todos are sorted by their original insertion order. 
  30.     comparator : function(todo) { 
  31.         return todo.get('order'); 
  32.     } 
  33. }); 

数据模型也可以包含一些方法,比如自身的校验,或者跟后端的通讯、数据的存取等等,在上面两个例子中,也都有体现。

AngularJS的模型定义方式与Backbone不同,可以不需要经过一层封装,直接使用原生的JavaScript简单数据、对象、数组,相对来说比较简便。

2. 控制器

在Backbone中,是没有独立的控制器的,它的一些控制的职责都放在了视图里,所以其实这是一种MVP(Model View Presentation)模式,而AngularJS有很清晰的控制器层。

还是以这个todo为例,在AngularJS中,会有一些约定的注入,比如$scope,它是控制器、模型和视图之间的桥梁。在控制器定义的时候,将$scope作为参数,然后,就可以在控制器里面为它添加模型的支持。

  1. function TodoCtrl($scope) { 
  2.     $scope.todos = [{ 
  3.         text : 'learn angular'
  4.         done : true 
  5.     }, { 
  6.         text : 'build an angular app'
  7.         done : false 
  8.     }]; 
  9.  
  10.     $scope.addTodo = function() { 
  11.         $scope.todos.push({ 
  12.             text : $scope.todoText, 
  13.             done : false 
  14.         }); 
  15.         $scope.todoText = ''
  16.     }; 
  17.  
  18.     $scope.remaining = function() { 
  19.         var count = 0; 
  20.         angular.forEach($scope.todos, function(todo) { 
  21.             count += todo.done ? 0 : 1; 
  22.         }); 
  23.         return count; 
  24.     }; 
  25.  
  26.     $scope.archive = function() { 
  27.         var oldTodos = $scope.todos; 
  28.         $scope.todos = []; 
  29.         angular.forEach(oldTodos, function(todo) { 
  30.             if (!todo.done) 
  31.                 $scope.todos.push(todo); 
  32.         }); 
  33.     }; 

本例中为$scope添加了todos这个数组,addTodo,remaining和archive三个方法,然后,可以在视图中对他们进行绑定。

3. 视图

在这些主流的MV*框架中,一般都提供了定义视图的功能。在Backbone中,是这样定义视图的:

  1. // The DOM element for a todo item... 
  2. var TodoView = Backbone.View.extend({ 
  3.     //... is a list tag. 
  4.     tagName : "li"
  5.  
  6.     // Cache the template function for a single item. 
  7.     template : _.template($('#item-template').html()), 
  8.  
  9.     // The DOM events specific to an item. 
  10.     events : { 
  11.         "click .toggle" : "toggleDone"
  12.         "dblclick .view" : "edit"
  13.         "click a.destroy" : "clear"
  14.         "keypress .edit" : "updateOnEnter"
  15.         "blur .edit" : "close" 
  16.     }, 
  17.  
  18.     // The TodoView listens for changes to its model, re-rendering. Since there's 
  19.     // a one-to-one correspondence between a **Todo** and a **TodoView** in this 
  20.     // app, we set a direct reference on the model for convenience. 
  21.     initialize : function() { 
  22.         this.listenTo(this.model, 'change'this.render); 
  23.         this.listenTo(this.model, 'destroy'this.remove); 
  24.     }, 
  25.  
  26.     // Re-render the titles of the todo item. 
  27.     render : function() { 
  28.         this.$el.html(this.template(this.model.toJSON())); 
  29.         this.$el.toggleClass('done'this.model.get('done')); 
  30.         this.input = this.$('.edit'); 
  31.         return this
  32.     }, 
  33.  
  34.     //...... 
  35.  
  36.     // Remove the item, destroy the model. 
  37.     clear : function() { 
  38.         this.model.destroy(); 
  39.     } 
  40. }); 

上面这个例子是一个典型的“部件”视图,它对于界面上的已有元素没有依赖。也有那么一些视图,需要依赖于界面上的已有元素,比如下面这个,它通过el属性,指定了HTML中id为todoapp的元素,并且还在initialize方法中引用了另外一些元素,通常,需要直接放置到界面的顶层试图会采用这种方式,而“部件”视图一般由主视图来创建、布局。

  1. // Our overall **AppView** is the top-level piece of UI. 
  2. var AppView = Backbone.View.extend({ 
  3.     // Instead of generating a new element, bind to the existing skeleton of 
  4.     // the App already present in the HTML. 
  5.     el : $("#todoapp"), 
  6.  
  7.     // Our template for the line of statistics at the bottom of the app. 
  8.     statsTemplate : _.template($('#stats-template').html()), 
  9.  
  10.     // Delegated events for creating new items, and clearing completed ones. 
  11.     events : { 
  12.         "keypress #new-todo" : "createOnEnter"
  13.         "click #clear-completed" : "clearCompleted"
  14.         "click #toggle-all" : "toggleAllComplete" 
  15.     }, 
  16.  
  17.     // At initialization we bind to the relevant events on the `Todos` 
  18.     // collection, when items are added or changed. Kick things off by 
  19.     // loading any preexisting todos that might be saved in *localStorage*. 
  20.     initialize : function() { 
  21.         this.input = this.$("#new-todo"); 
  22.         this.allCheckbox = this.$("#toggle-all")[0]; 
  23.  
  24.         this.listenTo(Todos, 'add'this.addOne); 
  25.         this.listenTo(Todos, 'reset'this.addAll); 
  26.         this.listenTo(Todos, 'all'this.render); 
  27.  
  28.         this.footer = this.$('footer'); 
  29.         this.main = $('#main'); 
  30.  
  31.         Todos.fetch(); 
  32.     }, 
  33.  
  34.     // Re-rendering the App just means refreshing the statistics -- the rest 
  35.     // of the app doesn't change. 
  36.     render : function() { 
  37.         var done = Todos.done().length; 
  38.         var remaining = Todos.remaining().length; 
  39.  
  40.         if (Todos.length) { 
  41.             this.main.show(); 
  42.             this.footer.show(); 
  43.             this.footer.html(this.statsTemplate({ 
  44.                 done : done, 
  45.                 remaining : remaining 
  46.             })); 
  47.         } else { 
  48.             this.main.hide(); 
  49.             this.footer.hide(); 
  50.         } 
  51.  
  52.         this.allCheckbox.checked = !remaining; 
  53.     }, 
  54.  
  55.     //...... 
  56. }); 

对于AngularJS来说,基本不需要有额外的视图定义,它采用的是直接定义在HTML上的方式,比如:

  1. <div ng-controller="TodoCtrl"
  2.     <span>{{remaining()}} of {{todos.length}} remaining</span> 
  3.     <a href="" ng-click="archive()">archive</a> 
  4.     <ul class="unstyled"
  5.         <li ng-repeat="todo in todos"
  6.             <input type="checkbox" ng-model="todo.done"
  7.             <span class="done-{{todo.done}}">{{todo.text}}</span> 
  8.         </li> 
  9.     </ul> 
  10.     <form ng-submit="addTodo()"
  11.         <input type="text" ng-model="todoText"  size="30" 
  12.         placeholder="add new todo here"
  13.         <input class="btn-primary" type="submit" value="add"
  14.     </form> 
  15. </div> 

在这个例子中,使用ng-controller注入了一个TodoCtrl的实例,然后,在TodoCtrl的$scope中附加的那些变量和方法都可以直接访问了。注意到其中的ng-repeat部分,它遍历了todos数组,然后使用其中的单个todo对象创建了一些HTML元素,把相应的值填到里面。这种做法和ng-model一样,都创造了双向绑定,即:

  • 改变模型可以随时反映到界面上
  • 在界面上做的操作(输入,选择等等)可以实时反映到模型里。

而且,这种绑定都会自动忽略其中可能因为空数据而引起的异常情况。

4. 模板

模板是这个时期一种很典型的解决方案。我们常常有这样的场景:在一个界面上重复展示类似的DOM片段,例如微博。以传统的开发方式,也可以轻松实现出来,比如:

  1. var feedsDiv = $("#feedsDiv"); 
  2.  
  3. for (var i = 0; i < 5; i++) { 
  4.     var feedDiv = $("<div class='post'></div>"); 
  5.  
  6.     var authorDiv = $("<div class='author'></div>"); 
  7.     var authorLink = $("<a></a>"
  8.         .attr("href""/user.html?user='" + "Test" + "'"
  9.         .html("@" + "Test"
  10.         .appendTo(authorDiv); 
  11.     authorDiv.appendTo(feedDiv); 
  12.  
  13.     var contentDiv = $("<div></div>"
  14.         .html("Hello, world!"
  15.         .appendTo(feedDiv); 
  16.     var dateDiv = $("<div></div>"
  17.         .html("发布日期:" + new Date().toString()) 
  18.         .appendTo(feedDiv); 
  19.  
  20.     feedDiv.appendTo(feedsDiv); 

但是使用模板技术,这一切可以更加优雅,以常用的模板框架UnderScore为例,实现这段功能的代码为:

  1. var templateStr = '<div class="post">' 
  2.     +'<div class="author">' 
  3.     +   '<a href="/user.html?user={{creatorName}}">@{{creatorName}}</a>' 
  4.     +'</div>' 
  5.     +'<div>{{content}}</div>' 
  6.     +'<div>{{postedDate}}</div>' 
  7.     +'</div>'
  8. var template = _.template(templateStr); 
  9. template({ 
  10.     createName : "Xufei"
  11.     content: "Hello, world"
  12.     postedDate: new Date().toString() 
  13. }); 

也可以这么定义:

  1. <script type="text/template" id="feedTemplate"
  2. <% _.each(feeds, function (item) { %> 
  3.     <div class="post"
  4.         <div class="author"
  5.             <a href="/user.html?user=<%= item.creatorName %>">@<%= item.creatorName %></a> 
  6.         </div> 
  7.         <div><%= item.content %></div> 
  8.         <div><%= item.postedData %></div> 
  9.     </div> 
  10. <% }); %> 
  11. </script> 
  12.  
  13. <script> 
  14. $('#feedsDiv').html( _.template($('#feedTemplate').html(), feeds)); 
  15. </script> 

除此之外,UnderScore还提供了一些很方便的集合操作,使得模板的使用更加方便。如果你打算使用BackBone框架,并且需要用到模板功能,那么UnderScore是一个很好的选择,当然,也可以选用其它的模板库,比如Mustache等等。

如果使用AngularJS,可以不需要额外的模板库,它自身就提供了类似的功能,比如上面这个例子可以改写成这样:

  1. <div class="post" ng-repeat="post in feeds"
  2.     <div class="author"
  3.         <a ng-href="/user.html?user={{post.creatorName}}">@{{post.creatorName}}</a> 
  4.     </div> 
  5.     <div>{{post.content}}</div> 
  6.     <div> 
  7.         发布日期:{{post.postedTime | date:'medium'}} 
  8.     </div> 
  9. </div> 

主流的模板技术都提供了一些特定的语法,有些功能很强。值得注意的是,他们虽然与JSP之类的代码写法类似甚至相同,但原理差别很大,这些模板框架都是在浏览器端执行的,不依赖任何服务端技术,即使界面文件是.html也可以,而传统比如JSP模板是需要后端支持的,执行时间是在服务端。

5. 路由

通常路由是定义在后端的,但是在这类MV*框架的帮助下,路由可以由前端来解析执行。比如下面这个Backbone的路由示例:

  1. var Workspace = Backbone.Router.extend({ 
  2.     routes: { 
  3.         "help":              "help",    // #help 
  4.         "search/:query":        "search",  // #search/kiwis 
  5.         "search/:query/p:page""search"   // #search/kiwis/p7 
  6.     }, 
  7.  
  8.     help: function() { 
  9.         ... 
  10.     }, 
  11.  
  12.     search: function(query, page) { 
  13.         ... 
  14.     }    
  15. }); 

在上述例子中,定义了一些路由的映射关系,那么,在实际访问的时候,如果在地址栏输入”#search/obama/p2”,就会匹配到”search/:query/p:page”这条路由,然后,把”obama”和”2”当作参数,传递给search方法。

AngularJS中定义路由的方式有些区别,它使用一个$routeProvider来提供路由的存取,每一个when表达式配置一条路由信息,otherwise配置默认路由,在配置路由的时候,可以指定一个额外的控制器,用于控制这条路由对应的html界面:

  1. app.config(['$routeProvider'
  2. function($routeProvider) { 
  3.     $routeProvider.when('/phones', { 
  4.         templateUrl : 'partials/phone-list.html'
  5.         controller : PhoneListCtrl 
  6.     }).when('/phones/:phoneId', { 
  7.         templateUrl : 'partials/phone-detail.html'
  8.         controller : PhoneDetailCtrl 
  9.     }).otherwise({ 
  10.         redirectTo : '/phones' 
  11.     }); 
  12. }]);  

注意,在AngularJS中,路由的template并非一个完整的html文件,而是其中的一段,文件的头尾都可以不要,也可以不要那些包含的外部样式和JavaScript文件,这些在主界面中载入就可以了。

6. 自定义标签

用过XAML或者MXML的人一定会对其中的可扩充标签印象深刻,对于前端开发人员而言,基于标签的组件定义方式一定是优于其他任何方式的,看下面这段HTML:

  1. <div> 
  2.     <input type="text" value="hello, world"/> 
  3.     <button>test</button> 
  4. </div> 

即使是刚刚接触这种东西的新手,也能够理解它的意思,并且能够照着做出类似的东西,如果使用传统的面向对象语言去描述界面,效率远远没有这么高,这就是在界面开发领域,声明式编程比命令式编程适合的最重要原因。

但是,HTML的标签是有限的,如果我们需要的功能不在其中,怎么办?在开发过程中,我们可能需要一个选项卡的功能,但是,HTML里面不提供选项卡标签,所以,一般来说,会使用一些li元素和div的组合,加上一些css,来实现选项卡的效果,也有的框架使用JavaScript来完成这些功能。总的来说,这些代码都不够简洁直观。

如果能够有一种技术,能够提供类似这样的方式,该多么好呢?

  1. <tabs> 
  2.     <tab name="Tab 1">content 1</tab> 
  3.     <tab name="Tab 2">content 2</tab> 
  4. </tabs> 

回忆一下,我们在章节1.4 组件化的萌芽 里面,提到过一种叫做HTC的技术,这种技术提供了类似的功能,而且使用起来也比较简便,问题是,它属于一种正在消亡的技术,于是我们的目光投向了更为现代的前端世界,AngularJS拯救了我们。

在AngularJS的首页,可以看到这么一个区块“Create Components”,在它的演示代码里,能够看到类似的一段:

  1. <tabs> 
  2.     <pane title="Localization"
  3.         ... 
  4.     </pane> 
  5.     <pane title="Pluralization"
  6.         ... 
  7.     </pane> 
  8. </tabs> 

那么,它是怎么做到的呢?秘密在这里:

  1. angular.module('components', []).directive('tabs'function() { 
  2.     return { 
  3.         restrict : 'E'
  4.         transclude : true
  5.         scope : {}, 
  6.         controller : function($scope, $element) { 
  7.             var panes = $scope.panes = []; 
  8.  
  9.             $scope.select = function(pane) { 
  10.                 angular.forEach(panes, function(pane) { 
  11.                     pane.selected = false
  12.                 }); 
  13.                 pane.selected = true
  14.             } 
  15.  
  16.             this.addPane = function(pane) { 
  17.                 if (panes.length == 0) 
  18.                     $scope.select(pane); 
  19.                 panes.push(pane); 
  20.             } 
  21.         }, 
  22.         template : '<div class="tabbable">' 
  23.             + '<ul class="nav nav-tabs">'  
  24.             + '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'  
  25.             + '<a href="" ng-click="select(pane)">{{pane.title}}</a>'  
  26.             + '</li>'  
  27.             + '</ul>'  
  28.             + '<div class="tab-content" ng-transclude></div>'  
  29.             + '</div>'
  30.         replace : true 
  31.     }; 
  32. }).directive('pane'function() { 
  33.     return { 
  34.         require : '^tabs'
  35.         restrict : 'E'
  36.         transclude : true
  37.         scope : { 
  38.             title : '@' 
  39.         }, 
  40.         link : function(scope, element, attrs, tabsCtrl) { 
  41.             tabsCtrl.addPane(scope); 
  42.         }, 
  43.         template : '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' + '</div>'
  44.         replace : true 
  45.     }; 
  46. }) 

这段代码里,定义了tabs和pane两个标签,并且限定了pane标签不能脱离tabs而单独存在,tabs的controller定义了它的行为,两者的template定义了实际生成的html,通过这种方式,开发者可以扩展出自己需要的新元素,对于使用者而言,这不会增加任何额外的负担。

四. 一些想说的话

关于ExtJS

注意到在本文中,并未提及这样一个比较流行的前端框架,主要是因为他自成一系,思路跟其他框架不同,所做的事情,层次介于文中的二和三之间,所以没有单独列出。

写作目的

在我10多年的Web开发生涯中,经历了Web相关技术的各种变革,从2003年开始,接触并使用到了HTC,VML,XMLHTTP等当时比较先进的技术,目睹了网景浏览器的衰落,IE的后来居上,Firefox和Chrome的逆袭,各类RIA技术的风起云涌,对JavaScript的模块化有过持续的思考。未来究竟是什么样子?我说不清楚,只能凭自己的一些认识,把这些年一些比较主流的发展过程总结一下,供有需要了解的朋友们作个参考,错漏在所难免,欢迎大家指教。

http://www.aseoe.com/ true 前端开发技术的发展 http://www.aseoe.com/show-41-534-1.html report 36901 前端开发技术,从狭义的定义来看,是指围绕html、javascript、CSS这样一套体系的开发技术,它的运行宿主是浏览器。从广义的定义来看,包括了:专门为手持终端设计的类似WML这样的类HTML语言,类似WMLScript这样的
TAG:前端开发 技术
本站欢迎任何形式的转载,但请务必注明出处,尊重他人劳动成果
转载请注明: 文章转载自:爱思资源网 http://www.aseoe.com/show-41-534-1.html

[前端插件推荐] Plugin

1 2 3 4
  • jQuery实现逐字逐句显示插件l-by-l.min.js
  • jQuery带方向感知的鼠标滑过图片边框特效插件
  • jQuery HotKeys监听键盘按下事件keydown插件
  • 响应式无限轮播jQuery旋转木马插件
响应式无限轮播jQuery旋转木马插件
web前端开发
爱思资源网 Copyright 2012-2014 Www.Aseoe.Com All rights reserved.(晋ICP备13001436号-1)