前端開發(fā)技術(shù)的發(fā)展

引自:http://blog.jobbole.com/41988/

前端開發(fā)技術(shù)魁索,從狹義的定義來看,是指圍繞HTML、JavaScript瞬雹、CSS這樣一套體系的開發(fā)技術(shù),它的運行宿主是瀏覽器刽虹。從廣義的定義來看酗捌,包括了:

專門為手持終端設(shè)計的類似WML這樣的類HTML語言,類似WMLScript這樣的類JavaScript語言涌哲。
VML和SVG等基于XML的描述圖形的語言胖缤。
從屬于XML體系的XML,XPath阀圾,DTD等技術(shù)哪廓。
用于支撐后端的ASP,JSP初烘,ASP.net涡真,PHP分俯,nodejs等語言或者技術(shù)。
被第三方程序打包的一種類似瀏覽器的宿主環(huán)境哆料,比如Adobe AIR和使用HyBird方式的一些開發(fā)技術(shù)锄码,如PhoneGap(它使用Android中的WebView等技術(shù)龙宏,讓開發(fā)人員使用傳統(tǒng)Web開發(fā)技術(shù)來開發(fā)本地應(yīng)用)
Adobe Flash妒峦,F(xiàn)lex舰攒,Microsoft Silverlight,Java Applet讥此,JavaFx等RIA開發(fā)技術(shù)拢锹。

本文從狹義的前端定義出發(fā),探討一下這方面開發(fā)技術(shù)的發(fā)展過程萄喳。

從前端開發(fā)技術(shù)的發(fā)展來看卒稳,大致可以分為以下幾個階段:

一. 刀耕火種

1.靜態(tài)頁面

最早期的Web界面基本都是在互聯(lián)網(wǎng)上使用,人們?yōu)g覽某些內(nèi)容他巨,填寫幾個表單充坑,并且提交。當時的界面以瀏覽為主染突,基本都是HTML代碼捻爷,有時候穿插一些JavaScript,作為客戶端校驗這樣的基礎(chǔ)功能份企。代碼的組織比較簡單也榄,而且CSS的運用也是比較少的。

最簡單的是這樣一個文件:

<html>
<head>
    <title>測試一</title>
</head>
<body>
    <h1>主標題</h1>
    <p>段落內(nèi)容</p>
</body>
</html>

2.帶有簡單邏輯的界面

這個界面帶有一段JavaScript代碼司志,用于拼接兩個輸入框中的字符串甜紫,并且彈出窗口顯示。

<html>
<head>
    <title>測試二</title>
</head>
<body>
    <input id="firstNameInput" type="text" /> 
    <input id="lastNameInput" type="text" /> 
    <input type="button" onclick="greet()" />
    <script language="JavaScript">
    function greet() {
        var firstName = document.getElementById("firstNameInput").value;
        var lastName = document.getElementById("lastNameInput").value;
        alert("Hello, " + firstName + "." + lastName);
    }
    </script> 
</body>
</html>

3.結(jié)合了服務(wù)端技術(shù)的混合編程

由于靜態(tài)界面不能實現(xiàn)保存數(shù)據(jù)等功能骂远,出現(xiàn)了很多服務(wù)端技術(shù)囚霸,早期的有CGI(Common Gateway Interface,多數(shù)用C語言或者Perl實現(xiàn)的)激才,ASP(使用VBScript或者JScript)拓型,JSP(使用Java),PHP等等瘸恼,Python和Ruby等語言也常被用于這類用途劣挫。

有了這類技術(shù),在HTML中就可以使用表單的post功能提交數(shù)據(jù)了钞脂,比如:

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

在這個階段揣云,由于客戶端和服務(wù)端的職責未作明確的劃分,比如生成一個字符串冰啃,可以由前端的JavaScript做邓夕,也可以由服務(wù)端語言做,所以通常在一個界面里阎毅,會有兩種語言混雜在一起焚刚,用<%和%>標記的部分會在服務(wù)端執(zhí)行,輸出結(jié)果扇调,甚至經(jīng)常有把數(shù)據(jù)庫連接的代碼跟頁面代碼混雜在一起的情況矿咕,給維護帶來較大的不便。

<html>
<body>
    <p>Hello world!</p>
    <p>
    <%
        response.write("Hello world from server!")
    %>
    </p>
</body>
</html>

4.組件化的萌芽

這個時代狼钮,也逐漸出現(xiàn)了組件化的萌芽碳柱。比較常見的有服務(wù)端的組件化,比如把某一類服務(wù)端功能單獨做成片段熬芜,然后其他需要的地方來include進來莲镣,典型的有:ASP里面數(shù)據(jù)庫連接的地方,把數(shù)據(jù)源連接的部分寫成conn.asp涎拉,然后其他每個需要操作數(shù)據(jù)庫的asp文件包含它瑞侮。

上面所說的是在服務(wù)端做的,瀏覽器端通常有針對JavaScript的鼓拧,把某一類的Javascript代碼寫到單獨的js文件中半火,界面根據(jù)需要,引用不同的js文件季俩。針對界面的組件方式钮糖,通常利用frameset和iframe這兩個標簽。某一大塊有獨立功能的界面寫到一個html文件酌住,然后在主界面里面把它當作一個frame來載入店归,一般的B/S系統(tǒng)集成菜單的方式都是這樣的。

此外赂韵,還出現(xiàn)了一些基于特定瀏覽器的客戶端組件技術(shù)娱节,比如IE瀏覽器的HTC(HTML Component)。這種技術(shù)最初是為了對已有的常用元素附加行為的祭示,后來有些場合也用它來實現(xiàn)控件肄满。微軟ASP.net的一些版本里,使用這種技術(shù)提供了樹形列表质涛,日歷稠歉,選項卡等功能。HTC的優(yōu)點是允許用戶自行擴展HTML標簽汇陆,可以在自己的命名空間里定義元素怒炸,然后,使用HTML毡代,JavaScript和CSS來實現(xiàn)它的布局阅羹、行為和觀感勺疼。這種技術(shù)因為是微軟的私有技術(shù),所以逐漸變得不那么流行捏鱼。

Firefox瀏覽器里面推出過一種叫XUL的技術(shù)执庐,也沒有流行起來。

二. 鐵器時代

這個時代的典型特征是Ajax的出現(xiàn)导梆。

1.AJAX

AJAX其實是一系列已有技術(shù)的組合轨淌,早在這個名詞出現(xiàn)之前,這些技術(shù)的使用就已經(jīng)比較廣泛了看尼,GMail因為恰當?shù)貞?yīng)用了這些技術(shù)递鹉,獲得了很好的用戶體驗。

由于Ajax的出現(xiàn)藏斩,規(guī)模更大躏结,效果更好的Web程序逐漸出現(xiàn),在這些程序中灾茁,JavaScript代碼的數(shù)量迅速增加窜觉。出于代碼組織的需要,“JavaScript框架”這個概念逐步形成北专,當時的主流是prototype和mootools禀挫,這兩者各有千秋,提供了各自方式的面向?qū)ο蠼M織思路拓颓。

2.JavaScript基礎(chǔ)庫

Prototype框架主要是為JavaScript代碼提供了一種組織方式语婴,對一些原生的JavaScript類型提供了一些擴展,比如數(shù)組驶睦、字符串砰左,又額外提供了一些實用的數(shù)據(jù)結(jié)構(gòu),如:枚舉场航,Hash等缠导,除此之外,還對dom操作溉痢,事件僻造,表單和Ajax做了一些封裝。

Mootools框架的思路跟Prototype很接近孩饼,它對JavaScript類型擴展的方式別具一格髓削,所以在這類框架中,經(jīng)常被稱作“最優(yōu)雅的”對象擴展體系镀娶。

從這兩個框架的所提供的功能來看立膛,它們的定位是核心庫,在使用的時候一般需要配合一些外圍的庫來完成梯码。

jQuery與這兩者有所不同宝泵,它著眼于簡化DOM相關(guān)的代碼好啰。 例如:

DOM的選擇

jQuery提供了一系列選擇器用于選取界面元素,在其他一些框架中也有類似功能鲁猩,但是一般沒有它的簡潔坎怪、強大罢坝。
$("*")                         //選取所有元素
$("#lastname")      //選取id為lastname的元素
$(".intro")              //選取所有class="intro"的元素
$("p")                   //選取所有<p>元素
$(".intro.demo")     //選取所有 class="intro"且class="demo"的元素

鏈式表達式:

在jQuery中廓握,可以使用鏈式表達式來連續(xù)操作dom,比如下面這個例子:

如果不使用鏈式表達式嘁酿,可能我們需要這么寫:
var neat = $("p.neat");
neat.addClass("ohmy");
neat.show("slow");

但是有了鏈式表達式隙券,我們只需要這么一行代碼就可以完成這些:
$("p.neat").addClass("ohmy").show("slow");

除此之外,jQuery還提供了一些動畫方面的特效代碼闹司,也有大量的外圍庫娱仔,
比如jQuery UI這樣的控件庫,jQuery mobile這樣的移動開發(fā)庫等等游桩。

3.模塊代碼加載方式

以上這些框架提供了代碼的組織能力牲迫,但是未能提供代碼的動態(tài)加載能力。動態(tài)加載JavaScript為什么重要呢借卧?因為隨著Ajax的普及盹憎,jQuery等輔助庫的出現(xiàn),Web上可以做很復雜的功能铐刘,因此陪每,單頁面應(yīng)用程序(SPA,Single Page Application)也逐漸多了起來镰吵。

單個的界面想要做很多功能檩禾,需要寫的代碼是會比較多的,但是疤祭,并非所有的功能都需要在界面加載的時候就全部引入盼产,如果能夠在需要的時候才加載那些代碼,就把加載的壓力分擔了勺馆,在這個背景下戏售,出現(xiàn)了一些用于動態(tài)加載JavaScript的框架,也出現(xiàn)了一些定義這類可被動態(tài)加載代碼的規(guī)范谓传。

在這些框架里蜈项,知名度比較高的是RequireJS,它遵循一種稱為AMD(Asynchronous Module Definition)的規(guī)范续挟。

比如下面這段紧卒,定義了一個動態(tài)的匿名模塊,它依賴math模塊

 define(["math"], function(math) {
   return {
    addTen : function(x) {
        return math.add(x, 10);
    }
 };
});

假設(shè)上面的代碼存放于adder.js中诗祸,當需要使用這個模塊的時候跑芳,通過如下代碼來引入adder:

<script src="require.js"></script>
<script>
require(["adder"], function(adder) {
    //使用這個adder
}); 
</script>

RequireJS除了提供異步加載方式轴总,也可以使用同步方式加載模塊代碼。AMD規(guī)范除了使用在前端瀏覽器環(huán)境中博个,也可以運行于nodejs等服務(wù)端環(huán)境怀樟,nodejs的模塊就是基于這套規(guī)范定義的。(修訂盆佣,這里弄錯了往堡,nodejs是基于類似的CMD規(guī)范的)

三. 工業(yè)革命

這個時期,隨著Web端功能的日益復雜共耍,人們開始考慮這樣一些問題:

如何更好地模塊化開發(fā)
業(yè)務(wù)數(shù)據(jù)如何組織
界面和業(yè)務(wù)數(shù)據(jù)之間通過何種方式進行交互

在這種背景下虑灰,出現(xiàn)了一些前端MVC、MVP痹兜、MVVM框架穆咐,我們把這些框架統(tǒng)稱為MV*框架。這些框架的出現(xiàn)字旭,都是為了解決上面這些問題对湃,具體的實現(xiàn)思路各有不同,主流的有Backbone遗淳,AngularJS拍柒,Ember,Spine等等洲脂,本文主要選用Backbone和AngularJS來講述以下場景斤儿。

1.數(shù)據(jù)模型

在這些框架里,定義數(shù)據(jù)模型的方式與以往有些差異恐锦,主要在于數(shù)據(jù)的get和set更加有意義了往果,比如說,可以把某個實體的get和set綁定到RESTful的服務(wù)上一铅,這樣陕贮,對某個實體的讀寫可以更新到數(shù)據(jù)庫中。另外一個特點是潘飘,它們一般都提供一個事件肮之,用于監(jiān)控數(shù)據(jù)的變化,這個機制使得數(shù)據(jù)綁定成為可能卜录。

在一些框架中戈擒,數(shù)據(jù)模型需要在原生的JavaScript類型上做一層封裝,比如Backbone的方式是這樣:

var Todo = Backbone.Model.extend({

// Default attributes for the todo item.
defaults : function() {
    return {
        title : "empty todo...",
        order : Todos.nextOrder(),
        done : false
    };
},


// Ensure that each todo created has `title`.
initialize : function() {
    if (!this.get("title")) {
        this.set({
            "title" : this.defaults().title
        });
    }
},


// Toggle the 'done' state of this todo item.
toggle : function() {
    this.save({
        done : !this.get("done")
    });
 }
});

上述例子中艰毒,defaults方法用于提供模型的默認值筐高,initialize方法用于做一些初始化工作,這兩個都是約定的方法,toggle是自定義的柑土,用于保存todo的選中狀態(tài)蜀肘。

除了對象,Backbone也支持集合類型稽屏,集合類型在定義的時候要通過model屬性指定其中的元素類型扮宠。

 // The collection of todos is backed by *localStorage* instead of a remote server.
 var TodoList = Backbone.Collection.extend({

// Reference to this collection's model.
  model : Todo,


// Save all of the todo items under the '"todos-backbone"' namespace.
   localStorage : new Backbone.LocalStorage("todos-backbone"),


// Filter down the list of all todo items that are finished.
done : function() {
    return this.filter(function(todo) {
        return todo.get('done');
    });
},

// Filter down the list to only todo items that are still not finished.
remaining : function() {
    return this.without.apply(this, this.done());
},

// We keep the Todos in sequential order, despite being saved by unordered 
//GUID in the database. This generates the next order number for new items.
nextOrder : function() {
    if (!this.length)
        return 1;
    return this.last().get('order') + 1;
},

// Todos are sorted by their original insertion order.
comparator : function(todo) {
    return todo.get('order');
 }
}); 

數(shù)據(jù)模型也可以包含一些方法,比如自身的校驗狐榔,或者跟后端的通訊坛增、數(shù)據(jù)的存取等等,在上面兩個例子中荒叼,也都有體現(xiàn)轿偎。

AngularJS的模型定義方式與Backbone不同,可以不需要經(jīng)過一層封裝被廓,直接使用原生的JavaScript簡單數(shù)據(jù)、對象萝玷、數(shù)組嫁乘,相對來說比較簡便。

2.控制器

在Backbone中球碉,是沒有獨立的控制器的蜓斧,它的一些控制的職責都放在了視圖里,所以其實這是一種MVP(Model View Presentation)模式睁冬,而AngularJS有很清晰的控制器層挎春。

還是以這個todo為例,在AngularJS中豆拨,會有一些約定的注入直奋,比如$scope,它是控制器施禾、模型和視圖之間的橋梁脚线。在控制器定義的時候,將$scope作為參數(shù)弥搞,然后邮绿,就可以在控制器里面為它添加模型的支持。

 function TodoCtrl($scope) {
$scope.todos = [{
    text : 'learn angular',
    done : true
}, {
    text : 'build an angular app',
    done : false
}];

$scope.addTodo = function() {
    $scope.todos.push({
        text : $scope.todoText,
        done : false
    });
    $scope.todoText = '';
};

$scope.remaining = function() {
    var count = 0;
    angular.forEach($scope.todos, function(todo) {
        count += todo.done ? 0 : 1;
    });
    return count;
};

$scope.archive = function() {
    var oldTodos = $scope.todos;
    $scope.todos = [];
    angular.forEach(oldTodos, function(todo) {
        if (!todo.done)
            $scope.todos.push(todo);
    });
 };
}

本例中為$scope添加了todos這個數(shù)組攀例,addTodo船逮,remaining和archive三個方法,然后粤铭,可以在視圖中對他們進行綁定挖胃。

3.視圖

在這些主流的MV*框架中,一般都提供了定義視圖的功能。在Backbone中冠骄,是這樣定義視圖的:

  // The DOM element for a todo item...
  var TodoView = Backbone.View.extend({

 //... is a list tag.
   tagName : "li",


// Cache the template function for a single item.
   template : _.template($('#item-template').html()),


// The DOM events specific to an item.
events : {
    "click .toggle" : "toggleDone",
    "dblclick .view" : "edit",
    "click a.destroy" : "clear",
    "keypress .edit" : "updateOnEnter",
    "blur .edit" : "close"
},


 // The TodoView listens for changes to its model, re-rendering. Since there's

// a one-to-one correspondence between a **Todo** and a **TodoView** in this

 // app, we set a direct reference on the model for convenience.
initialize : function() {
    this.listenTo(this.model, 'change', this.render);
    this.listenTo(this.model, 'destroy', this.remove);
},

// Re-render the titles of the todo item.
render : function() {
    this.$el.html(this.template(this.model.toJSON()));
    this.$el.toggleClass('done', this.model.get('done'));
    this.input = this.$('.edit');
    return this;
},


//......


// Remove the item, destroy the model.
clear : function() {
    this.model.destroy();
 }
});

上面這個例子是一個典型的“部件”視圖伪煤,它對于界面上的已有元素沒有依賴。也有那么一些視圖凛辣,需要依賴于界面上的已有元素抱既,比如下面這個,它通過el屬性扁誓,指定了HTML中id為todoapp的元素防泵,并且還在initialize方法中引用了另外一些元素,通常蝗敢,需要直接放置到界面的頂層試圖會采用這種方式捷泞,而“部件”視圖一般由主視圖來創(chuàng)建、布局寿谴。

  // Our overall **AppView** is the top-level piece of UI.
  var AppView = Backbone.View.extend({

 // Instead of generating a new element, bind to the existing skeleton of

// the App already present in the HTML.
   el : $("#todoapp"),


// Our template for the line of statistics at the bottom of the app.
   statsTemplate : _.template($('#stats-template').html()),


// Delegated events for creating new items, and clearing completed ones.
   events : {
    "keypress #new-todo" : "createOnEnter",
    "click #clear-completed" : "clearCompleted",
    "click #toggle-all" : "toggleAllComplete"
  },


// At initialization we bind to the relevant events on the `Todos`

// collection, when items are added or changed. Kick things off by

// loading any preexisting todos that might be saved in *localStorage*.
initialize : function() {
    this.input = this.$("#new-todo");
    this.allCheckbox = this.$("#toggle-all")[0];

    this.listenTo(Todos, 'add', this.addOne);
    this.listenTo(Todos, 'reset', this.addAll);
    this.listenTo(Todos, 'all', this.render);

    this.footer = this.$('footer');
    this.main = $('#main');

    Todos.fetch();
},


// Re-rendering the App just means refreshing the statistics -- the rest

// of the app doesn't change.
render : function() {
    var done = Todos.done().length;
    var remaining = Todos.remaining().length;

    if (Todos.length) {
        this.main.show();
        this.footer.show();
        this.footer.html(this.statsTemplate({
            done : done,
            remaining : remaining
        }));
    } else {
        this.main.hide();
        this.footer.hide();
    }

    this.allCheckbox.checked = !remaining;
},


//......
});

對于AngularJS來說锁右,基本不需要有額外的視圖定義,它采用的是直接定義在HTML上的方式讶泰,比如:

<div ng-controller="TodoCtrl">
<span>{{remaining()}} of {{todos.length}} remaining</span>
<a href="" ng-click="archive()">archive</a>
<ul class="unstyled">
    <li ng-repeat="todo in todos">
        <input type="checkbox" ng-model="todo.done">
        <span class="done-{{todo.done}}">{{todo.text}}</span>
    </li>
</ul>
<form ng-submit="addTodo()">
    <input type="text" ng-model="todoText"  size="30"
    placeholder="add new todo here">
    <input class="btn-primary" type="submit" value="add">
</form>
</div>

在這個例子中咏瑟,使用ng-controller注入了一個TodoCtrl的實例,然后痪署,在TodoCtrl的$scope中附加的那些變量和方法都可以直接訪問了码泞。注意到其中的ng-repeat部分,它遍歷了todos數(shù)組狼犯,然后使用其中的單個todo對象創(chuàng)建了一些HTML元素余寥,把相應(yīng)的值填到里面。這種做法和ng-model一樣悯森,都創(chuàng)造了雙向綁定宋舷,即:

改變模型可以隨時反映到界面上
在界面上做的操作(輸入,選擇等等)可以實時反映到模型里呐馆。

而且肥缔,這種綁定都會自動忽略其中可能因為空數(shù)據(jù)而引起的異常情況。

4.模板

模板是這個時期一種很典型的解決方案汹来。我們常常有這樣的場景:在一個界面上重復展示類似的DOM片段续膳,例如微博。以傳統(tǒng)的開發(fā)方式收班,也可以輕松實現(xiàn)出來坟岔,比如:

var feedsDiv = $("#feedsDiv");

for (var i = 0; i < 5; i++) {
var feedDiv = $("<div class='post'></div>");

var authorDiv = $("<div class='author'></div>");
var authorLink = $("<a></a>")
    .attr("href", "/user.html?user='" + "Test" + "'")
    .html("@" + "Test")
    .appendTo(authorDiv);
authorDiv.appendTo(feedDiv);

var contentDiv = $("<div></div>")
    .html("Hello, world!")
    .appendTo(feedDiv);
var dateDiv = $("<div></div>")
    .html("發(fā)布日期:" + new Date().toString())
    .appendTo(feedDiv);

feedDiv.appendTo(feedsDiv);
}

但是使用模板技術(shù),這一切可以更加優(yōu)雅摔桦,以常用的模板框架UnderScore為例社付,實現(xiàn)這段功能的代碼為:
var templateStr = '<div class="post">'
+'<div class="author">'
+ '<a href="/user.html?user={{creatorName}}">@{{creatorName}}</a>'
+'</div>'
+'<div>{{content}}</div>'
+'<div>{{postedDate}}</div>'
+'</div>';
var template = _.template(templateStr);
template({
createName : "Xufei",
content: "Hello, world",
postedDate: new Date().toString()
});

也可以這么定義:

 <script type="text/template" id="feedTemplate">
<% _.each(feeds, function (item) { %>
<div class="post">
    <div class="author">
        <a href="/user.html?user=<%= item.creatorName %>">@<%= item.creatorName %></a>
    </div>
    <div><%= item.content %></div>
    <div><%= item.postedData %></div>
</div>
 <% }); %>
</script>

<script>
  $('#feedsDiv').html( _.template($('#feedTemplate').html(), feeds));
</script>

除此之外承疲,UnderScore還提供了一些很方便的集合操作,使得模板的使用更加方便鸥咖。如果你打算使用BackBone框架燕鸽,并且需要用到模板功能,那么UnderScore是一個很好的選擇啼辣,當然啊研,也可以選用其它的模板庫,比如Mustache等等鸥拧。

如果使用AngularJS党远,可以不需要額外的模板庫,它自身就提供了類似的功能富弦,比如上面這個例子可以改寫成這樣:

<div class="post" ng-repeat="post in feeds">
<div class="author">
    <a ng-href="/user.html?user={{post.creatorName}}">@{{post.creatorName}}</a>
</div>
<div>{{post.content}}</div>
<div>
    發(fā)布日期:{{post.postedTime | date:'medium'}}
</div>
</div>

主流的模板技術(shù)都提供了一些特定的語法沟娱,有些功能很強。值得注意的是腕柜,他們雖然與JSP之類的代碼寫法類似甚至相同济似,但原理差別很大,這些模板框架都是在瀏覽器端執(zhí)行的媳握,不依賴任何服務(wù)端技術(shù)碱屁,即使界面文件是.html也可以,而傳統(tǒng)比如JSP模板是需要后端支持的蛾找,執(zhí)行時間是在服務(wù)端。

5.路由

通常路由是定義在后端的赵誓,但是在這類MV*框架的幫助下打毛,路由可以由前端來解析執(zhí)行。比如下面這個Backbone的路由示例:

 var Workspace = Backbone.Router.extend({
routes: {
    "help":              "help",    
// #help
    "search/:query":        "search",  
// #search/kiwis
    "search/:query/p:page": "search"   
// #search/kiwis/p7
},

help: function() {
    ...
},

search: function(query, page) {
    ...
}   
});

在上述例子中俩功,定義了一些路由的映射關(guān)系幻枉,那么,在實際訪問的時候诡蜓,如果在地址欄輸入”#search/obama/p2″熬甫,就會匹配到”search/:query/p:page”這條路由,然后蔓罚,把”obama”和”2″當作參數(shù)椿肩,傳遞給search方法。

AngularJS中定義路由的方式有些區(qū)別豺谈,它使用一個$routeProvider來提供路由的存取郑象,每一個when表達式配置一條路由信息,otherwise配置默認路由茬末,在配置路由的時候厂榛,可以指定一個額外的控制器,用于控制這條路由對應(yīng)的html界面:

 app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.when('/phones', {
    templateUrl : 'partials/phone-list.html',
    controller : PhoneListCtrl
}).when('/phones/:phoneId', {
    templateUrl : 'partials/phone-detail.html',
    controller : PhoneDetailCtrl
}).otherwise({
    redirectTo : '/phones'
});
}]);

注意,在AngularJS中击奶,路由的template并非一個完整的html文件辈双,而是其中的一段,文件的頭尾都可以不要柜砾,也可以不要那些包含的外部樣式和JavaScript文件湃望,這些在主界面中載入就可以了。

6.自定義標簽

用過XAML或者MXML的人一定會對其中的可擴充標簽印象深刻局义,對于前端開發(fā)人員而言喜爷,基于標簽的組件定義方式一定是優(yōu)于其他任何方式的,看下面這段HTML:

<div>
   <input type="text" value="hello, world"/>
   <button>test</button>
</div>

即使是剛剛接觸這種東西的新手萄唇,也能夠理解它的意思檩帐,并且能夠照著做出類似的東西,如果使用傳統(tǒng)的面向?qū)ο笳Z言去描述界面另萤,效率遠遠沒有這么高湃密,這就是在界面開發(fā)領(lǐng)域,聲明式編程比命令式編程適合的最重要原因四敞。

但是泛源,HTML的標簽是有限的,如果我們需要的功能不在其中忿危,怎么辦达箍?在開發(fā)過程中,我們可能需要一個選項卡的功能铺厨,但是缎玫,HTML里面不提供選項卡標簽,所以解滓,一般來說赃磨,會使用一些li元素和div的組合,加上一些css洼裤,來實現(xiàn)選項卡的效果邻辉,也有的框架使用JavaScript來完成這些功能∪埃總的來說值骇,這些代碼都不夠簡潔直觀。

如果能夠有一種技術(shù)缕减,能夠提供類似這樣的方式雷客,該多么好呢?

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

回憶一下桥狡,我們在章節(jié)1.4 組件化的萌芽 里面搅裙,提到過一種叫做HTC的技術(shù)皱卓,這種技術(shù)提供了類似的功能,而且使用起來也比較簡便部逮,問題是娜汁,它屬于一種正在消亡的技術(shù)兄朋,于是我們的目光投向了更為現(xiàn)代的前端世界,AngularJS拯救了我們颅和。

在AngularJS的首頁,可以看到這么一個區(qū)塊“Create Components”峡扩,在它的演示代碼里蹭越,能夠看到類似的一段:

 <tabs>
    <pane title="Localization">
    ...
    </pane>
   <pane title="Pluralization">
    ...
    </pane>
</tabs>

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

angular.module('components', []).directive('tabs', function() {
 return {
    restrict : 'E',
    transclude : true,
    scope : {},
    controller : function($scope, $element) {
        var panes = $scope.panes = [];

        $scope.select = function(pane) {
            angular.forEach(panes, function(pane) {
                pane.selected = false;
            });
            pane.selected = true;
        }

        this.addPane = function(pane) {
            if (panes.length == 0)
                $scope.select(pane);
            panes.push(pane);
        }
    },
    template : '<div class="tabbable">'
        + '<ul class="nav nav-tabs">'
        + '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'
        + '<a href="" ng-click="select(pane)">{{pane.title}}</a>'
        + '</li>'
        + '</ul>'
        + '<div class="tab-content" ng-transclude></div>'
        + '</div>',
    replace : true
  };
}).directive('pane', function() {
return {
    require : '^tabs',
    restrict : 'E',
    transclude : true,
    scope : {
        title : '@'
    },
    link : function(scope, element, attrs, tabsCtrl) {
        tabsCtrl.addPane(scope);
    },
    template : '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' + '</div>',
    replace : true
  };
})

這段代碼里案训,定義了tabs和pane兩個標簽买置,并且限定了pane標簽不能脫離tabs而單獨存在,tabs的controller定義了它的行為强霎,兩者的template定義了實際生成的html城舞,通過這種方式,開發(fā)者可以擴展出自己需要的新元素椿争,對于使用者而言熟嫩,這不會增加任何額外的負擔。

四. 一些想說的話

關(guān)于ExtJS

注意到在本文中椅邓,并未提及這樣一個比較流行的前端框架昧狮,主要是因為他自成一系,思路跟其他框架不同合住,所做的事情,層次介于文中的二和三之間笨使,所以沒有單獨列出僚害。

寫作目的

在我10多年的Web開發(fā)生涯中,經(jīng)歷了Web相關(guān)技術(shù)的各種變革靶草,從2003年開始岳遥,接觸并使用到了HTC奕翔,VML寒随,XMLHTTP等當時比較先進的技術(shù),目睹了網(wǎng)景瀏覽器的衰落互艾,IE的后來居上讯泣,F(xiàn)irefox和Chrome的逆襲,各類RIA技術(shù)的風起云涌昨稼,對JavaScript的模塊化有過持續(xù)的思考拳锚。未來究竟是什么樣子?我說不清楚匾荆,只能憑自己的一些認識杆烁,把這些年一些比較主流的發(fā)展過程總結(jié)一下兔魂,供有需要了解的朋友們作個參考,錯漏在所難免析校,歡迎大家指教铜涉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绰播,一起剝皮案震驚了整個濱河市蠢箩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谬泌,老刑警劉巖掌实,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宴卖,居然都是意外死亡邻悬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛾扇,“玉大人,你說我怎么就攤上這事坟漱「澹” “怎么了竖瘾?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵花颗,是天一觀的道長。 經(jīng)常有香客問我庸论,道長,這世上最難降的妖魔是什么域携? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任秀鞭,我火速辦了婚禮扛禽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘豆巨。我一直安慰自己掐场,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布萍膛。 她就那樣靜靜地躺著卦羡,像睡著了一般麦到。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拟赊,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天粹淋,我揣著相機與錄音桃移,去河邊找鬼。 笑死借杰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的纤虽。 我是一名探鬼主播逼纸,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼菠发!你這毒婦竟也來了专缠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤哥力,失蹤者是張志新(化名)和其女友劉穎吩跋,沒想到半個月后渔工,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡梁丘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年氛谜,在試婚紗的時候發(fā)現(xiàn)自己被綠了区端。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡杨何,死狀恐怖危虱,靈堂內(nèi)的尸體忽然破棺而出唐全,到底是詐尸還是另有隱情,我是刑警寧澤捌蚊,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布近弟,位于F島的核電站,受9級特大地震影響窗宦,放射性物質(zhì)發(fā)生泄漏二鳄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望欺殿。 院中可真熱鬧,春花似錦程拭、人聲如沸棍潘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽资锰。三九已至阶祭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鞭盟,已是汗流浹背瑰剃。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粤剧,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓焕议,卻偏偏與公主長得像盅安,于是被迫代替她去往敵國和親世囊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,860評論 25 707
  • 發(fā)現(xiàn)麥子學院的課程還是感覺很好的。以后就按這個寫文集了号胚。希望能堅持下去猫胁。 HTML+CSS基礎(chǔ)入門 1.課程介紹 ...
    biggerworld閱讀 1,078評論 0 1
  • 某天, 我醒來届惋, 頭上長顆樹菠赚。 我搖搖頭, 怕長滿怪異的果實瘩欺。 戴上一頂帽拌牲, 怕世人皆知。 一陣風拂過拍埠, 落滿手足...
    字微閱讀 545評論 8 15
  • 祁同偉與候亮平涩堤,生死搏殺卻又惺惺相惜分瘾,曾經(jīng)同是一樣的才華出眾,精明能干的熱血青年,不拘一格垮抗,輕視清規(guī)戒律炮姨,互相欣賞...
    luckystar_d212閱讀 955評論 0 1
  • 人生在世照藻,總會有各種各樣的煩惱魔策,解決煩惱的方式千千萬,但終究不外乎是要結(jié)合到事情的具體解決方式上栋烤。 想要擁有內(nèi)心的...
    片羽飛章之墨語軒襲閱讀 388評論 0 0