前端開發技術的發展

民工精髓發表於2013-02-05

前端開發技術的發展

前端開發技術,從狹義的定義來看,是指圍繞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開發技術。

本文從狹義的前端定義出發,探討一下這方面開發技術的發展過程。

從前端開發技術的發展來看,大致可以分為以下幾個階段:

一. 刀耕火種

1. 靜態頁面

最早期的Web介面基本都是在網際網路上使用,人們瀏覽某些內容,填寫幾個表單,並且提交。當時的介面以瀏覽為主,基本都是HTML程式碼,有時候穿插一些JavaScript,作為客戶端校驗這樣的基礎功能。程式碼的組織比較簡單,而且CSS的運用也是比較少的。

最簡單的是這樣一個檔案:

<html>
    <head>
        <title>測試一</title>
    </head>
    <body>
        <h1>主標題</h1>
        <p>段落內容</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. 結合了服務端技術的混合程式設計

由於靜態介面不能實現儲存資料等功能,出現了很多服務端技術,早期的有CGI(Common Gateway Interface,多數用C語言或者Perl實現的),ASP(使用VBScript或者JScript),JSP(使用Java),PHP等等,Python和Ruby等語言也常被用於這類用途。

有了這類技術,在HTML中就可以使用表單的post功能提交資料了,比如:

<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>

在這個階段,由於客戶端和服務端的職責未作明確的劃分,比如生成一個字串,可以由前端的JavaScript做,也可以由服務端語言做,所以通常在一個介面裡,會有兩種語言混雜在一起,用<%和%>標記的部分會在服務端執行,輸出結果,甚至經常有把資料庫連線的程式碼跟頁面程式碼混雜在一起的情況,給維護帶來較大的不便。

<html>
    <body>
        <p>Hello world!</p>
        <p>
        <%
            response.write("Hello world from server!")
        %>
        </p>
    </body>
</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提供了一系列選擇器用於選取介面元素,在其他一些框架中也有類似功能,但是一般沒有它的簡潔、強大。

$("*")                //選取所有元素
$("#lastname")        //選取id為lastname的元素
$(".intro")            //選取所有class="intro"的元素
$("p")                //選取所有&lt;p&gt;元素
$(".intro.demo")    //選取所有 class="intro"且class="demo"的元素
  • 鏈式表示式:

在jQuery中,可以使用鏈式表示式來連續操作dom,比如下面這個例子:

如果不使用鏈式表示式,可能我們需要這麼寫:

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

但是有了鏈式表示式,我們只需要這麼一行程式碼就可以完成這些:

$("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模組

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

假設上面的程式碼存放於adder.js中,當需要使用這個模組的時候,通過如下程式碼來引入adder:

<script src="require.js"></script>
<script>
    require(["adder"], function(adder) {
        //使用這個adder
    }); 
</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的方式是這樣:

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的選中狀態。

除了物件,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');
    }
});

資料模型也可以包含一些方法,比如自身的校驗,或者跟後端的通訊、資料的存取等等,在上面兩個例子中,也都有體現。

AngularJS的模型定義方式與Backbone不同,可以不需要經過一層封裝,直接使用原生的JavaScript簡單資料、物件、陣列,相對來說比較簡便。

2. 控制器

在Backbone中,是沒有獨立的控制器的,它的一些控制的職責都放在了檢視裡,所以其實這是一種MVP(Model View Presentation)模式,而AngularJS有很清晰的控制器層。

還是以這個todo為例,在AngularJS中,會有一些約定的注入,比如$scope,它是控制器、模型和檢視之間的橋樑。在控制器定義的時候,將$scope作為引數,然後,就可以在控制器裡面為它新增模型的支援。

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這個陣列,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方法中引用了另外一些元素,通常,需要直接放置到介面的頂層試圖會採用這種方式,而“部件”檢視一般由主檢視來建立、佈局。

// 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陣列,然後使用其中的單個todo物件建立了一些HTML元素,把相應的值填到裡面。這種做法和ng-model一樣,都創造了雙向繫結,即:

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

而且,這種繫結都會自動忽略其中可能因為空資料而引起的異常情況。

4. 模板

模板是這個時期一種很典型的解決方案。我們常常有這樣的場景:在一個介面上重複展示類似的DOM片段,例如微博。以傳統的開發方式,也可以輕鬆實現出來,比如:

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("釋出日期:" + new Date().toString())
        .appendTo(feedDiv);

    feedDiv.appendTo(feedsDiv);
}

但是使用模板技術,這一切可以更加優雅,以常用的模板框架UnderScore為例,實現這段功能的程式碼為:

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>
        釋出日期:{{post.postedTime | date:'medium'}}
    </div>
</div>

主流的模板技術都提供了一些特定的語法,有些功能很強。值得注意的是,他們雖然與JSP之類的程式碼寫法類似甚至相同,但原理差別很大,這些模板框架都是在瀏覽器端執行的,不依賴任何服務端技術,即使介面檔案是.html也可以,而傳統比如JSP模板是需要後端支援的,執行時間是在服務端。

5. 路由

通常路由是定義在後端的,但是在這類MV*框架的幫助下,路由可以由前端來解析執行。比如下面這個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) {
        ...
    }    
});

在上述例子中,定義了一些路由的對映關係,那麼,在實際訪問的時候,如果在位址列輸入"#search/obama/p2",就會匹配到"search/:query/p:page"這條路由,然後,把"obama"和"2"當作引數,傳遞給search方法。

AngularJS中定義路由的方式有些區別,它使用一個$routeProvider來提供路由的存取,每一個when表示式配置一條路由資訊,otherwise配置預設路由,在配置路由的時候,可以指定一個額外的控制器,用於控制這條路由對應的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的人一定會對其中的可擴充標籤印象深刻,對於前端開發人員而言,基於標籤的元件定義方式一定是優於其他任何方式的,看下面這段HTML:

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

即使是剛剛接觸這種東西的新手,也能夠理解它的意思,並且能夠照著做出類似的東西,如果使用傳統的面嚮物件語言去描述介面,效率遠遠沒有這麼高,這就是在介面開發領域,宣告式程式設計比指令式程式設計適合的最重要原因。

但是,HTML的標籤是有限的,如果我們需要的功能不在其中,怎麼辦?在開發過程中,我們可能需要一個選項卡的功能,但是,HTML裡面不提供選項卡標籤,所以,一般來說,會使用一些li元素和div的組合,加上一些css,來實現選項卡的效果,也有的框架使用JavaScript來完成這些功能。總的來說,這些程式碼都不夠簡潔直觀。

如果能夠有一種技術,能夠提供類似這樣的方式,該多麼好呢?

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

回憶一下,我們在章節1.4 元件化的萌芽 裡面,提到過一種叫做HTC的技術,這種技術提供了類似的功能,而且使用起來也比較簡便,問題是,它屬於一種正在消亡的技術,於是我們的目光投向了更為現代的前端世界,AngularJS拯救了我們。

在AngularJS的首頁,可以看到這麼一個區塊“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,通過這種方式,開發者可以擴充套件出自己需要的新元素,對於使用者而言,這不會增加任何額外的負擔。

四. 一些想說的話

關於ExtJS

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

寫作目的

在我10多年的Web開發生涯中,經歷了Web相關技術的各種變革,從2003年開始,接觸並使用到了HTC,VML,XMLHTTP等當時比較先進的技術,目睹了網景瀏覽器的衰落,IE的後來居上,Firefox和Chrome的逆襲,各類RIA技術的風起雲湧,對JavaScript的模組化有過持續的思考。未來究竟是什麼樣子?我說不清楚,只能憑自己的一些認識,把這些年一些比較主流的發展過程總結一下,供有需要了解的朋友們作個參考,錯漏在所難免,歡迎大家指教。

個人郵箱:xu.fei@outlook.com
新浪微博:http://weibo.com/sharpmaster

相關文章