Web前端发展简史

Web前端发展简史

有人说“前端开发”是IT界最容易被误解的岗位,这不是空穴来风。如果你还认为前端只是从美工那里拿到切图, JS和CSS一番乱炖,难搞的功能就去网上信手拈来,CtrlC + Ctrl V的话,那就正中了这份误解的下怀。经过十几年的发展,web前端早已脱离了原来边缘化的形态,扮演了移动互联网开发链条中最关键的角色,是应用或产品能否打动用户的踹门砖。那么什么是前端开发,其又包含了哪些内容?

前端开发的定义

从狭义的定义来看,“前端开发”是指围绕HTML、JavaScript、CSS这样一套体系的开发技术,它的运行宿主是浏览器。从广义的定义来看,其应该包括:

l  专门为手持终端设计的类似WML这样的类HTML语言,以及类似WMLScript的类JavaScript语言。

l  VML和SVG等基于XML的描述图形的语言。

l  从属于XML体系的XML,XPath,DTD等技术。

l  用于支撑后端的ASP,JSP,ASP.net,PHP,Nodejs等语言或者技术。

l  被第三方程序打包的一种类似浏览器的宿主环境,比如Adobe AIR和使用HyBird方式的一些开发技术,如PhoneGap。

l  Adobe Flash,Flex,Microsoft Silverlight,Java Applet,JavaFx等RIA开发技术。

本文主要从“web前端”,也即狭义前端的角度出发,以人类科技进步划时代的方式,将前端开发划分为几个重要的时代,带领大家领略一下前端这十几年来的发展历程。

石器时代

最早期的Web界面基本都是在互联网上使用,人们浏览某些内容,填写几个表单并且提交。当时的界面以浏览为主,基本都是HTML代码,我们来看一个最简单的HTML文件:


<html>

<head>

<title>测试一</title>

</head>

<body>

<h1>主标题</h1>

<p>段落内容</p>

</body>

</html>

为了执行一些动作或进行一定的业务处理,有时候会穿插一些JavaScript,如作为客户端校验这样的基础功能。代码的组织比较简单,而且CSS的运用也是比较少的。譬如:下面这个文档将带有一段JavaScript代码,用于拼接两个输入框中的字符串,并且弹出窗口显示。


<html>

<head>

<title>测试二</title>

</head>

<body>

<inputid="firstNameInput"type="text"/>

<inputid="lastNameInput"type="text"/>

<inputtype="button"onclick="greet()"/>

<scriptlanguage="JavaScript">

function greet(){

var firstName = document.getElementById("firstNameInput").value;

var lastName = document.getElementById("lastNameInput").value;

alert("Hello, "+ firstName +"."+ lastName);

}

</script>

</body>

</html>

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

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


<formmethod="post"action="username.asp">

<p>First Name: <inputtype="text"name="firstName"/></p>

<p>Last Name: <inputtype="text"name="lastName"/></p>

<inputtype="submit"value="Submit"/>

</form>

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


<html>

<body>

<p>Hello world!</p>

<p>

<%response.write("Hello world from server!")%>

</p>

</body>

</html>

青铜时代

青铜时代的典型标志是出现了组件化的萌芽,着眼点主要在文件的划分上。后端组件化比较常见的做法是,把某一类后端功能单独做成片段,然后其他需要的地方来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的出现以及JS基础框架的兴起。

AJAX

AJAX其实是一系列已有技术的组合,早在这个名词出现之前,这些技术的使用就已经比较广泛了,GMail因为恰当地应用了这些技术,获得了很好的用户体验。由于Ajax的出现,规模更大,效果更好的Web程序逐渐出现,在这些程序中,JavaScript代码的数量迅速增加。出于代码组织的需要,“JavaScript框架”这个概念逐步形成,当时的主流是Prototype和Mootools,两者各有千秋,提供了各自方式的面向对象组织思路。

JavaScript基础框架

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

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

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

倚天不出,谁与争锋?除以上两者以外,还有YUI,jQuery等,JavaScript基础框架在这个时代算得上是百花齐放,但是时间已经证明,真正的王者是jQuery。

jQuery与其他的基础框架都有所不同,它着眼于简化DOM相关的代码。例如:

l  DOM的选取

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


$("*")//选取所有元素

$("#lastname")//选取id为lastname的元素

$(".intro")//选取所有class="intro"的元素

$("p")//选取所有&lt;p&gt;元素

$(".intro.demo")//选取所有 class="intro"且class="demo"的元素

l  链式表达式

在jQuery中,可以使用链式表达式来连续操作DOM,如果不使用链式表达式,可能需要这么写:


var neat = $("p.neat");

neat.addClass("ohmy");

neat.show("slow");

但是有了链式表达式,一行代码就可以搞定:


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

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

 

农业时代

这个时代的标志性事件是模块加载规范(AMD以及CMD)的出现。

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

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

AMD

在这些框架里,知名度最高的是RequireJS,遵循了AMD(Asynchronous Module Definition)的规范。比如下面这段,定义了一个动态的匿名模块,它依赖math模块:


define(["math"],function(math){

return{

addTen :function(x){

return math.add(x,10);

}

};

});

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


<scriptsrc="require.js"></script>

<script>

require(["adder"],function(adder){

//使用这个adder

});

</script>

RequireJS除了提供异步加载方式,也可以使用同步方式加载模块代码。AMD规范除了使用在前端浏览器环境中,也可以运行于NodeJS等服务端环境,但是NodeJS内置的模块机制是基于CMD规范定义的。

 

CMD

值得一提的是,在浏览器端,除了RequireJS以外,国内的牛人淘宝玉伯开发了SeaJS异步模块加载器,其遵循CMD规范,目前已经有超过300家大型web应用或站点采用,SeaJS同样简单易学:


// 所有模块都通过 define 来定义

define(function(require, exports, module){

// 通过 require 引入依赖

var $ =require(‘jquery‘);

var Spinning =require(‘./spinning‘);

// 通过 exports 对外提供接口

exports.doSomething =...

// 或者通过 module.exports 提供整个接口

module.exports =...

});

工业时代

“这是一个最好的时代,也是一个最坏的时代。”前端自动化和MV*框架真正让前端迎来了春天,但是这个时代框架插件众多、体系繁复,让前端新手无所适从。在这个时代,Web端功能日益复杂,人们不得不开始考虑这样一些问题:

l  如何更好地模块化开发

l  业务数据如何组织

l  界面和业务数据之间通过何种方式进行交互

在这种背景下,前端MVC、MVP、MVVM框架如雨后春笋,我们暂且把这些框架都统称为MV*框架。这些框架的出现,正是为了解决上述这些问题,具体的实现思路各有不同,主流的有Backbone,AngularJS,Ember三大剑客,本文主要选用Backbone和AngularJS来讲述以下场景。

数据模型

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

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


varTodo=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.

varTodoList=Backbone.Collection.extend({

// Reference to this collection‘s model.

model :Todo,

// Save all of the todo items under the ‘"todos-backbone"‘ namespace.

localStorage :newBackbone.LocalStorage("todos-backbone"),

// Filter down the list of all todo items that are finished.

done:function(){

returnthis.filter(function(todo){

return todo.get(‘done‘);

});

},

// Filter down the list to only todo items that are still not finished.

remaining :function(){

returnthis.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)

return1;

returnthis.last().get(‘order‘)+1;

},

// Todos are sorted by their original insertion order.

comparator :function(todo){

return todo.get(‘order‘);

}

});

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

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

控制器

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

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


functionTodoCtrl($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三个方法,然后,可以在视图中对他们进行绑定。

视图

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


// The DOM element for a todo item...

varTodoView=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‘);

returnthis;

},

//......

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

varAppView=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(){

vardone=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上的方式,比如:


<divng-controller="TodoCtrl">

<span>{{remaining()}} of {{todos.length}} remaining</span>

<ahref=""ng-click="archive()">archive</a>

<ulclass="unstyled">

<ling-repeat="todo in todos">

<inputtype="checkbox"ng-model="todo.done">

<spanclass="done-{{todo.done}}">{{todo.text}}</span>

</li>

</ul>

<formng-submit="addTodo()">

<inputtype="text"ng-model="todoText"size="30"

placeholder="add new todo here">

<inputclass="btn-primary"type="submit"value="add">

</form>

</div>

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

l  改变模型可以随时反映到界面上

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

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

模板

模板是这个时代一种很典型的解决方案。我们常常有这样的场景:在一个界面上重复展示类似的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("发布日期:"+newDate().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>‘;

vartemplate= _.template(templateStr);

template({

createName :"Xufei",

content:"Hello, world",

postedDate:newDate().toString()

});

也可以这么定义:


<scripttype="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,可以不需要额外的模板库,它自身就提供了类似的功能,比如上面这个例子可以改写成这样:


<divclass="post"ng-repeat="post in feeds">

<divclass="author">

<ang-href="/user.html?user={{post.creatorName}}">@{{post.creatorName}}</a>

</div>

<div>{{post.content}}</div>

<div>

发布日期:{{post.postedTime | date:‘medium‘}}

</div>

</div>

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

路由

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


varWorkspace=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文件,这些在主界面中载入就可以了。

自定义组件

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


<div>

<inputtype="text"value="hello, world"/>

<button>test</button>

</div>

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

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

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


<tabs>

<tabname="Tab 1">content 1</tab>

<tabname="Tab 2">content 2</tab>

</tabs>

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


<tabs>

<panetitle="Localization">

...

</pane>

<panetitle="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,通过这种方式,开发者可以扩展出自己需要的新元素,对于使用者而言,这不会增加任何额外的负担。

时间: 2024-10-28 02:17:49

Web前端发展简史的相关文章

前端发展简史

在很久以前,HTML,CSS和JavaScript - 就已经是是任何前端开发人员所涉及的主要内容. HTML第一次公开提到HTML是在1991年,Tim Berners-Lee描述了最初为标记语言提供的18个标签. 1994年他成立了一个HTML工作组 - 互联网工程任务组(IEFT).他们认为网络属于大众(这个想法今天仍然很先进),所以他们的大门向任何希望加入的人开放.到1995年,我们获得了许多新的标签和属性,例如能够设置字体和背景颜色.这引起了一些争议,因为他们认为HTML只应该用于描述

web前端工程师在移动互联网时代里地位问题

支付宝十周年推出了一个新产品:支付宝的十年账单,我也赶个时髦查看了一下我的支付宝十年账单,哎,感慨自己真是太屌丝了,不过这只是说明我使用淘宝少了,当我大规模网上购物时候,我很讨厌慢速的快递,所以我大部分消费都贡献给了像京东这样具有火箭般快递速度的电子商城了.不过在支付宝十年账单里,有个统计数据引起了我的危机意识,在中国一些偏远或者是经济欠发达的省份,电子购物在居民的全部消费里的占比比发达地区高多了,而这个的助推剂居然是移动互联网在中国的普及,在中国使用智能手机和平板电脑购物的人们已经远超使用PC

Web前端开发的前景与用处

随着时代的发展,现在从事IT方向的人有很多,所以励志要成为前端开发工程师的人有很多.当然也有很多人在犹豫不知道该从事哪个方向,我今天就是来给大家分析一下Web前端开发的前景.包括工作内容,发展前景和薪资待遇等等. 了解这个行业以及发展前景 我觉得呢,一个行业发展前景好不好,数据上的证明总要比口头上的说服要有力的多. 首先在百度词条搜索前端开发出现最多的是“培训”.各种各样的培训,什么“三天速成web大师”,“七天空手敲JavaScript”之类的等等各种培训.反正现在都是这样,什么职业赚钱搞什么

web前端入坑第四篇:你还在用 jQuery?

web前端入坑第四篇:你还在用 jQuery? 大妈都这么努力,我们有几个人回家还看书的? 先来补齐[web前端入坑系列]前三篇的连接web前端入坑系列:点击标题进入第一篇: web 前端入坑第一篇:web前端到底是什么?有前途吗第二篇: web前端入坑第二篇:web前端到底怎么学?干货资料!第三篇:web前端入坑第三篇 | 一条"不归路" - 学习路线! 再说这个话题之前,我们先来扫盲普及一下 [jquery] 到底是什么以及它火爆将近十年的重要原因. [ 重新认识 - Jquery

web前端的前景

随着时代的发展,现在从事IT方向的人有很多,所以励志要成为前端开发工程师的人有很多.当然也有很多人在犹豫不知道该从事哪个方向,我今天就是来给大家分析一下Web前端开发的前景.包括工作内容,发展前景和薪资待遇等等. 我觉得呢,一个行业发展前景好不好,数据上的证明总要比口头上的说服要有力的多. 首先在百度词条搜索前端开发出现最多的是"培训".各种各样的培训,什么"三天速成web大师","七天空手敲JavaScript"之类的等等各种培训.反正现在都是

web前端开发与页面设计的协作、区别与发展

web前端开发与页面设计的协作.区别与发展,布布扣,bubuko.com

web前端的发展态势

以前 作为一个java程序员写的代码主要还是后台的代码,虽然开始的时候前后端都写,但是也是用别人造好的轮子来用,学学html,css,js,jquery,再找一个前端ui框架学学,上手之后我们就可以写界面,写后台了,当然这只是企业的后台管理项目,界面要求不是特别高,对于网站项目还是需要前端童鞋来设计,布局,写好html给到我们,我们转成jsp,或者各种模板引擎文件.这种模式对前端童鞋要求不是特别高,会设计,切图,就差不多了,什么前端组件化,模块化,自动化这些概念都没有,可能是小公司没有接触到,大

web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史

秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知,不问自晓,古今行事,未之有也 」 我们都知道如今流行的框架:Vue.Js.AngularJs.ReactJs.已经逐渐应用到各个项目和实际应用中,它们都是MVVM数据驱动框架系列的一种. 在了解MVVM之前,我们先回想一下前端发展的历史阶段,做到心中有数,才会更好理解. 这段回想历史.由于网上就可查不少资料,

Web前端和后端的技术发展方向

今年web前端被炒得异常火热,但与此同时后端也是备受关注,这让很多web前端和后端的技术人员思考两者的发展方向,下面是小编收集的前端大牛的一些看法. 首先,兴趣是最重要的老师.个人认为除了少数意志力坚定的人,大多数人只有在自己有兴趣的领域才能发展事业. 但是是否"觉得兼容性很枯燥",就一定无法"感兴趣"下去, 这个其实不好一概而论.如果对兼容性问题感到麻烦棘手,这个恐怕很难做好前端.但是如果对探究某个兼容性问题有兴趣,但当探索完毕后,又厌烦每次都要为这个问题写额外的