javascript 异步调用动画为什么是异步操作

专题:JavaScript 第二季
前端开发技术的发展
作者/ 徐飞
徐飞,05年至今就职于中兴软创,致力于企业软件前端工程化的发展。徐飞坚信:作为技术人员应当乐于助人,有好东西要主动拿出来分享,资产阶级知识分子垄断电子书的现象再也不能出现了!微博,图灵社区ID:民工精髓。
前端开发技术,从狭义的定义来看,是指围绕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的运用也是比较少的。
最简单的是这样一个文件:
&title&测试一&/title&
&h1&主标题&/h1&
&p&段落内容&/p&
2. 带有简单逻辑的界面
这个界面带有一段JavaScript代码,用于拼接两个输入框中的字符串,并且弹出窗口显示。
&title&测试二&/title&
&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").
var lastName = document.getElementById("lastNameInput").
alert("Hello, " + firstName + "." + lastName);
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" /&
在这个阶段,由于客户端和服务端的职责未作明确的划分,比如生成一个字符串,可以由前端的JavaScript做,也可以由服务端语言做,所以通常在一个界面里,会有两种语言混杂在一起,用&%和%>标记的部分会在服务端执行,输出结果,甚至经常有把数据库连接的代码跟页面代码混杂在一起的情况,给维护带来较大的不便。
&p&Hello world!&/p&
response.write("Hello world from server!")
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的出现。
AJAX其实是一系列已有技术的组合,早在这个名词出现之前,这些技术的使用就已经比较广泛了,GMail因为恰当地应用了这些技术,获得了很好的用户体验。
由于Ajax的出现,规模更大,效果更好的Web程序逐渐出现,在这些程序中,JavaScript代码的数量迅速增加。出于代码组织的需要,“JavaScript框架”这个概念逐步形成,当时的主流是prototype和mootools,这两者各有千秋,提供了各自方式的面向对象组织思路。
2. JavaScript基础库
Prototype框架主要是为JavaScript代码提供了一种组织方式,对一些原生的JavaScript类型提供了一些扩展,比如数组、字符串,又额外提供了一些实用的数据结构,如:枚举,Hash等,除此之外,还对dom操作,事件,表单和Ajax做了一些封装。
Mootools框架的思路跟Prototype很接近,它对JavaScript类型扩展的方式别具一格,所以在这类框架中,经常被称作“最优雅的”对象扩展体系。
从这两个框架的所提供的功能来看,它们的定位是核心库,在使用的时候一般需要配合一些外围的库来完成。
jQuery与这两者有所不同,它着眼于简化DOM相关的代码。
jQuery提供了一系列选择器用于选取界面元素,在其他一些框架中也有类似功能,但是一般没有它的简洁、强大。
//选取所有元素
$("#lastname")
//选取id为lastname的元素
$(".intro")
//选取所有class="intro"的元素
//选取所有&p&元素
$(".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) {
addTen : function(x) {
return math.add(x, 10);
假设上面的代码存放于adder.js中,当需要使用这个模块的时候,通过如下代码来引入adder:
&script src="require.js"&&/script&
require(["adder"], function(adder) {
//使用这个adder
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() {
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 this.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作为参数,然后,就可以在控制器里面为它添加模型的支持。
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;
$scope.archive = function() {
var oldTodos = $scope.
$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...
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');
// 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().
var remaining = Todos.remaining().
if (Todos.length) {
this.main.show();
this.footer.show();
this.footer.html(this.statsTemplate({
done : done,
remaining : remaining
this.main.hide();
this.footer.hide();
this.allCheckbox.checked = !
对于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&
&form ng-submit="addTodo()"&
&input type="text" ng-model="todoText"
placeholder="add new todo here"&
&input class="btn-primary" type="submit" value="add"&
在这个例子中,使用ng-controller注入了一个TodoCtrl的实例,然后,在TodoCtrl的$scope中附加的那些变量和方法都可以直接访问了。注意到其中的ng-repeat部分,它遍历了todos数组,然后使用其中的单个todo对象创建了一些HTML元素,把相应的值填到里面。这种做法和ng-model一样,都创造了双向绑定,即:
改变模型可以随时反映到界面上
在界面上做的操作(输入,选择等等)可以实时反映到模型里。
而且,这种绑定都会自动忽略其中可能因为空数据而引起的异常情况。
模板是这个时期一种很典型的解决方案。我们常常有这样的场景:在一个界面上重复展示类似的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&{{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&&%= item.content %&&/div&
&div&&%= item.postedData %&&/div&
$('#feedsDiv').html( _.template($('#feedTemplate').html(), feeds));
除此之外,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&{{post.content}}&/div&
发布日期:{{post.postedTime | date:'medium'}}
主流的模板技术都提供了一些特定的语法,有些功能很强。值得注意的是,他们虽然与JSP之类的代码写法类似甚至相同,但原理差别很大,这些模板框架都是在浏览器端执行的,不依赖任何服务端技术,即使界面文件是.html也可以,而传统比如JSP模板是需要后端支持的,执行时间是在服务端。
通常路由是定义在后端的,但是在这类MV*框架的帮助下,路由可以由前端来解析执行。比如下面这个Backbone的路由示例:
var Workspace = Backbone.Router.extend({
"search/:query":
// #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:
&input type="text" value="hello, world"/&
&button&test&/button&
即使是刚刚接触这种东西的新手,也能够理解它的意思,并且能够照着做出类似的东西,如果使用传统的面向对象语言去描述界面,效率远远没有这么高,这就是在界面开发领域,声明式编程比命令式编程适合的最重要原因。
但是,HTML的标签是有限的,如果我们需要的功能不在其中,怎么办?在开发过程中,我们可能需要一个选项卡的功能,但是,HTML里面不提供选项卡标签,所以,一般来说,会使用一些li元素和div的组合,加上一些css,来实现选项卡的效果,也有的框架使用JavaScript来完成这些功能。总的来说,这些代码都不够简洁直观。
如果能够有一种技术,能够提供类似这样的方式,该多么好呢?
&tab name="Tab 1"&content 1&/tab&
&tab name="Tab 2"&content 2&/tab&
回忆一下,我们在章节1.4 组件化的萌芽 里面,提到过一种叫做HTC的技术,这种技术提供了类似的功能,而且使用起来也比较简便,问题是,它属于一种正在消亡的技术,于是我们的目光投向了更为现代的前端世界,AngularJS拯救了我们。
在AngularJS的首页,可以看到这么一个区块“Create Components”,在它的演示代码里,能够看到类似的一段:
&pane title="Localization"&
&pane title="Pluralization"&
那么,它是怎么做到的呢?秘密在这里:
angular.module('components', []).directive('tabs', function() {
restrict : 'E',
transclude : true,
scope : {},
controller : function($scope, $element) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected =
pane.selected =
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&'
+ '&div class="tab-content" ng-transclude&&/div&'
+ '&/div&',
replace : true
}).directive('pane', function() {
require : '^tabs',
restrict : 'E',
transclude : true,
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,通过这种方式,开发者可以扩展出自己需要的新元素,对于使用者而言,这不会增加任何额外的负担。
四. 一些想说的话
注意到在本文中,并未提及这样一个比较流行的前端框架,主要是因为他自成一系,思路跟其他框架不同,所做的事情,层次介于文中的二和三之间,所以没有单独列出。
在我10多年的Web开发生涯中,经历了Web相关技术的各种变革,从2003年开始,接触并使用到了HTC,VML,XMLHTTP等当时比较先进的技术,目睹了网景浏览器的衰落,IE的后来居上,Firefox和Chrome的逆袭,各类RIA技术的风起云涌,对JavaScript的模块化有过持续的思考。未来究竟是什么样子?我说不清楚,只能凭自己的一些认识,把这些年一些比较主流的发展过程总结一下,供有需要了解的朋友们作个参考,错漏在所难免,欢迎大家指教。
给JavaScript初学者的24条最佳实践
作者/ Jeffrey Way
Jeffrey Way曾经是Nettuts+的编辑和网站开发课程Tuts+ Premium的校长。现在Jeffrey专注于在Laracasts!上教授关于Laravel零零总总,欢迎来看看。
1.使用 === 代替 ==
JavaScript 使用2种不同的等值运算符:===|!== 和 ==|!=,在比较操作中使用前者是最佳实践。
“如果两边的操作数具有相同的类型和值,===返回true,!==返回false。”——JavaScript:语言精粹
然而,当使用==和!=时,你可能会遇到类型不同的情况,这种情况下,操作数的类型会被强制转换成一样的再做比较,这可能不是你想要的结果。
2.Eval=邪恶
起初不太熟悉时,“eval”让我们能够访问JavaScript的编译器(译注:这看起来很强大)。从本质上讲,我们可以将字符串传递给eval作为参数,而执行它。
这不仅大幅降低脚本的性能(译注:JIT编译器无法预知字符串内容,而无法预编译和优化),而且这也会带来巨大的安全风险,因为这样付给要执行的文本太高的权限,避而远之。
3.省略未必省事
从技术上讲,你可以省略大多数花括号和分号。大多数浏览器都能正确理解下面的代码:
if(someVariableExists)
然后,如果像下面这样:
if(someVariableExists)
anotherFunctionCall();
有人可能会认为上面的代码等价于下面这样:
if(someVariableExists) {
anotherFunctionCall();
不幸的是,这种理解是错误的。实际上的意思如下:
if(someVariableExists) {
anotherFunctionCall();
你可能注意到了,上面的缩进容易给人花括号的假象。无可非议,这是一种可怕的实践,应不惜一切代价避免。仅有一种情况下,即只有一行的时候,花括号是可以省略的,但这点是饱受争议的。
if(2 + 2 === 4) return 'nicely done';
很可能,有一天你需要在if语句块中添加更多的语句。这样的话,你必须重写这段代码。底线——省略是雷区。
4.使用JSLint
是由大名鼎鼎的(Douglas Crockford)编写的调试器。简单的将你的代码粘贴进JSLint中,它会迅速找出代码中明显的问题和错误。
“JSLint扫面输入的源代码。如果发现一个问题,它返回一条描述问题和一个代码中的所在位置的消息。问题并不一定是语法错误,尽管通常是这样。JSLint还会查看一些编码风格和程序结构问题。这并不能保证你的程序是正确的。它只是提供了另一双帮助发现问题的眼睛。”——JSLing 文档
部署脚本之前,运行JSLint,只是为了确保你没有做出任何愚蠢的错误。
5.将脚本放在页面的底部
在本系列前面的文章里已经提到过这个技巧,我粘贴信息在这里。
记住——首要目标是让页面尽可能快的呈献给用户,脚本的夹在是阻塞的,脚本加载并执行完之前,浏览器不能继续渲染下面的内容。因此,用户将被迫等待更长时间。
如果你的js只是用来增强效果——例如,按钮的单击事件——马上将脚本放在body结束之前。这绝对是最佳实践。
&p&And now you know my favorite kinds of corn. &/p&
&script type="text/javascript" src="path/to/file.js"&&/script&
&script type="text/javascript" src="path/to/anotherFile.js"&&/script&
6.避免在For语句内声明变量
当执行冗长的for语句时,要保持语句块的尽量简洁,例如:
for(var i = 0; i & someArray. i++) {
var container = document.getElementById('container');
container.innerHtml += 'my number: ' +
console.log(i);
注意每次循环都要计算数组的长度,并且每次都要遍历dom查询“container”元素——效率严重地下!
var container = document.getElementById('container');
for(var i = 0, len = someArray. i &
container.innerHtml += 'my number: ' +
console.log(i);
感兴趣可以思考如何继续优化上面的代码,欢迎留下评论大家分享。
7.构建字符串的最优方法
当你需要遍历数组或对象的时候,不要总想着“for”语句,要有创造性,总能找到更好的办法,例如,像下面这样。
var arr = ['item 1', 'item 2', 'item 3', ...];
var list = '&ul&&li&' + arr.join('&/li&&li&') + '&/li&&/ul&';
我不是你心中神,但请你相信我(不信你自己测试)——这是迄今为止最快的方法!使用原生代码(如 join()),不管系统内部做了什么,通常比非原生快很多。——James Padolsey,
8.减少全局变量
只要把多个全局变量都整理在一个名称空间下,拟将显著降低与其他应用程序、组件或类库之间产生糟糕的相互影响的可能性。——Douglas Crockford
var name = 'Jeffrey';
var lastName = 'Way';
function doSomething() {...}
console.log(name); // Jeffrey -- 或 window.name
更好的做法
var DudeNameSpace = {
name : 'Jeffrey',
lastName : 'Way',
doSomething : function() {...}
console.log(DudeNameSpace.name); // Jeffrey
注:这里只是简单命名为 "DudeNameSpace",实际当中要取更合理的名字。
9.给代码添加注释
似乎没有必要,当请相信我,尽量给你的代码添加更合理的注释。当几个月后,重看你的项目,你可能记不清当初你的思路。或者,假如你的一位同事需要修改你的代码呢?总而言之,给代码添加注释是重要的部分。
// 循环数组,输出每项名字(译者注:这样的注释似乎有点多余吧).
for(var i = 0, len = array. i & i++) {
console.log(array[i]);
10.拥抱渐进增强
确保javascript被禁用的情况下能平稳退化。我们总是被这样的想法吸引,“大多数我的访客已经启用JavaScript,所以我不必担心。”然而,这是个很大的误区。
你可曾花费片刻查看下你漂亮的页面在javascript被关闭时是什么样的吗?(下载 工具就能很容易做到(译者注:chrome用户在应用商店里自行下载,ie用户在Internet选项中设置)),这有可能让你的网站支离破碎。作为一个经验法则,设计你的网站时假设JavaScript是被禁用的,然后,在此基础上,逐步增强你的网站。
11.不要给"setInterval"或"setTimeout"传递字符串参数
考虑下面的代码:
setInterval(
"document.getElementById('container').innerHTML += 'My new number: ' + i", 3000
不仅效率低下,而且这种做法和"eval"如出一辙。从不给setInterval和setTimeout传递字符串作为参数,而是像下面这样传递函数名。
setInterval(someFunction, 3000);
12.不要使用"with"语句
乍一看,"with"语句看起来像一个聪明的主意。基本理念是,它可以为访问深度嵌套对象提供缩写,例如……
with (being.person.man.bodyparts) {
而不是像下面这样:
being.person.man.bodyparts.arms =
being.person.man.bodyparts.legs=
不幸的是,经过测试后,发现这时“设置新成员时表现得非常糟糕。作为代替,您应该使用变量,像下面这样。
var o = being.person.man.
13.使用{}代替 new Ojbect()
在JavaScript中创建对象的方法有多种。可能是传统的方法是使用"new"加构造函数,像下面这样:
var o = new Object();
o.name = 'Jeffrey';
o.lastName = 'Way';
o.someFunction = function() {
console.log(this.name);
然而,这种方法的受到的诟病不及实际上多。作为代替,我建议你使用更健壮的对象字面量方法。
更好的做法
name: 'Jeffrey',
lastName = 'Way',
someFunction : function() {
console.log(this.name);
注意,如果你只是想创建一个空对象,{}更好。
var o = {};
“对象字面量使我们能够编写更具特色的代码,而且相对简单的多。不需要直接调用构造函数或维持传递给函数的参数的正确顺序,等”——
14.使用[]代替 new Array()
这同样适用于创建一个新的数组。
var a = new Array();
a[0] = "Joe";
a[1] = 'Plumber';
更好的做法:
var a = ['Joe','Plumber'];
“javascript程序中常见的错误是在需要对象的时候使用数组,而需要数组的时候却使用对象。规则很简单:当属性名是连续的整数时,你应该使用数组。否则,请使用对象”——Douglas Crockford
15.定义多个变量时,省略var关键字,用逗号代替
var someItem = 'some string';
var anotherItem = 'another string';
var oneMoreItem = 'one more string';
更好的做法
var someItem = 'some string',
anotherItem = 'another string',
oneMoreItem = 'one more string';
…应而不言自明。我怀疑这里真的有所提速,但它能是你的代码更清晰。
17.谨记,不要省略分号
从技术上讲,大多数浏览器允许你省略分号。
var someItem = 'some string'
function doSomething() {
return 'something'
已经说过,这是一个非常糟糕的做法可能会导致更大的,难以发现的问题。
更好的做法
var someItem = 'some string';
function doSomething() {
return 'something';
18."For in"语句
当遍历对象的属性时,你可能会发现还会检索方法函数。为了解决这个问题,总在你的代码里包裹在一个if语句来过滤信息。
for(key in object) {
if(object.hasOwnProperty(key) {
...then do something...
参考 JavaScript:语言精粹,道格拉斯(Douglas Crockford)。
19.使用Firebug的"timer"功能优化你的代码
在寻找一个快速、简单的方法来确定操作需要多长时间吗?使用Firebug的“timer”功能来记录结果。
function TimeTracker(){
console.time("MyTimer");
for(x=5000; x & 0; x--){}
console.timeEnd("MyTimer");
20.阅读,阅读,反复阅读
虽然我是一个巨大的web开发博客的粉丝(像这样!),午餐之余或上床睡觉之前,实在没有什么比一本书更合适了,坚持放一本web开发方面书在你的床头柜。下面是一些我最喜爱的JavaScript书籍。
读了他们……多次。我仍将继续!
21.自执行函数
和调用一个函数类似,它很简单的使一个函数在页面加载或父函数被调用时自动运行。简单的将你的函数用圆括号包裹起来,然后添加一个额外的设置,这本质上就是调用函数。
(function doSomething() {
name: 'jeff',
lastName: 'way'
22.原生代码永远比库快
JavaScript库,例如jQuery和Mootools等可以节省大量的编码时间,特别是AJAX操作。已经说过,总是记住,库永远不可能比原生JavaScript代码更快(假设你的代码正确)。
jQuery的“each”方法是伟大的循环,但使用原生"for"语句总是更快。
23.道格拉斯的 JSON.Parse
尽管JavaScript 2(ES5)已经内置了JSON 解析器。但在撰写本文时,我们仍然需要自己实现(兼容性)。道格拉斯(Douglas Crockford),JSON之父,已经创建了一个你可以直接使用的解析器。这里可以下载(链接已坏,可以在这里查看相关信息)。
只需简单导入脚本,您将获得一个新的全局JSON对象,然后可以用来解析您的json文件。
var response = JSON.parse(xhr.responseText);
var container = document.getElementById('container');
for(var i = 0, len = response. i & i++) {
container.innerHTML += '&li&' + response[i].name + ' : ' + response[i].email + '&/li&';
24.移除"language"属性
曾经脚本标签中的“language”属性非常常见。
&script type="text/javascript" language="javascript"&
然而,这个属性早已被弃用,所以请移除(译者注:html5 中已废弃,但如果你喜欢,你仍然可以添加)。
就这样吧,伙计
现在你已经学到了,24条JavaScript初学者的必备技巧。让我知道你高效技巧吧!感谢你的阅读。本系列的第三部分主题会是什么呢(思索中)?
第三部分在这里:
本文为翻译文章,原文为“”
关于#20 的补充,下面是译者认为的一些好书,有兴趣的读者可以留言讨论
javascript模式(和上面JavaScript面向对象编程指南同一作者,这本书更好)
(尼古拉斯新书)
高性能javascript(尼古拉斯 已绝版)
javascript语言精髓与编程实践
(尼古拉斯)
译者/ 颜海镜
90 后一枚,活跃在各个技术社区, 专注Web前端已有三个年头。关注HTML/CSS/JavaScript等相关技术,目前就职于北京金山软件。坚信Web赢在未来。热爱思考,热爱开源分享,常翻译些外文博客,此外还爱好读书,羽毛球,乒乓球,相声,铁杆纲丝,当然,也非常热爱写代码。个人博客:
Angular、Backbone、CanJS与Ember : JavaScript MVC框架PK
作者/ Sebastian Porto
最爱JavaScript, Ruby和Go, 没事儿爱写Blog,深度编程强迫症患者。现居于澳大利亚墨尔本。,,。
选择JavaScript MVC框架很难。一方面要考虑的因素非常多,另一方面这种框架也非常多,而要从中选择一个合适的,还真得费一番心思。想知道有哪些JavaScript MVC框架可以选择?看看吧。
我用过其中4个框架:Angular、 Backbone、 CanJS和 Ember。因此,可以对它们作一比较,供大家参考。本文会涉及框架选型过程中需要考虑的一系列因素,我们逐一讨论。
每一个因素我们都会按照1到5分来打分,1分代表很差,5分代表很好。我会尽量保持客观,但也不敢保证真能“一碗水端平”,毕竟这些分数都是根据我个人经验给出的。
作为构建应用的基础,框架必须具备一些重要的功能。比如,视图绑定、双向绑定、筛选、可计算属性(computed property)、脏属性(dirty attribute)、表单验证,等等。还能罗列出一大堆来。下面比较了一些我认为MVC框架中比较重要的功能:
功能AngularBackbone CanJSEmber
可观察对象(observable)是是是是
路由(routing)是是是是
视图绑定(view binding)是是是
双向绑定(two way binding)是--是
部分视图(partial view)是-是是
筛选列表视图(filtered list view)是-是是
可观察对象:可以被监听是否发生变化的对象。
路由:把变化通过浏览器URL的参数反映出来,并监听这些变化以便执行相应的操作。
视图绑定:在视图中使用可观察对象,让视图随着可观察对象的变化而自动刷新。
双向绑定:让视图也能把变化(如表单输入)自动推送到可观察对象。
部分视图:包含其他视图的视图。
筛选列表视图:用于显示根据某些条件筛选出来的对象的视图。
根据上述功能,我打出的分数如下:
AngularBackbone CanJSEmber
有一点必须指出,使用Backbone也能实现上述大多数功能,只是手工编码量挺大的,有时候还要借助插件。这里的打分只考虑了框架核心是否支持某一功能。
有时候,框架配合一些现成的插件和库来使用,可能要比使用框架原生同类功能效果更好,而这种插件和库几乎遍地都是(不下数百个),又各有特色。因此,能够把这些库和插件整合到MVC框架中也非常重要。
Backbone是其中最灵活的一个框架,因为它的约定和主张最少。使用Backbone需要你自己作出很多决定。
CanJS的灵活性与Backbone差不多,把它跟别的库整合起来很容易。在CanJS中甚至可以更换其他渲染引擎,我在CanJS中就一直用,没有任何问题。不过,我还是推荐框架自带的组件。
Ember和Angular也都还算灵活,可有时候你会发现,就算不喜欢它们的某些实现方法,你也只能默默忍受。 这是在选择Ember或Angular时必须考虑的。
AngularBackbone CanJSEmber
Angular一开始会让人大呼过瘾,因为可以利用它干好多意想不到的事,比如双向绑定,而且学习难度不高。乍一看让人觉得很简单。可是,进了门之后,你会发现后面的路还很长。应该说这个框架比较复杂,而且有不少标新立异之处。想看着它的文档上手并不现实,因为Angular制造的概念很多,而文档中的例子又很少。
Backbone的基本概念非常容易理解。但很快你会发现它对怎么更好地组织代码并没有太多主张。为此,你得观摩或阅读一些教程,才能知道在Backbone中怎么编码最好。而且,你会发现在有了Backbone的基础上,还得再找一个库(比如或)跟它配合才能得心应手。正因为如此,我不认为Backbone是个容易上手的框架。
CanJS相对而言是这里面最容易上手的。看看它只有一页的网站(),基本上就知道怎么做效率最高了。当然,还得找其他一些资料看,不过我个人很少有这种需求(比如看其他教程、上论坛或讨论组提问呀什么的)。
Ember的上手难度与Angular有一拼,我认为学习Ember比学习Angular总体上容易一些,但它要求你一开始就要先搞懂一批基本概念。而Angular呢,一开始不需要这么费劲也能做一些让人兴奋不已的事儿。Ember缺少这种前期兴奋点。
AngularBackbone CanJSEmber
比较全面地掌握了一个框架之后,重点就转移到了产出上。什么意思呢?约定啊、戏法啊,反正要尽可能快。
熟悉Angular之后,你的效率会非常高,这一点毋庸置疑。之所以我没给它打最高分,主要因为我觉得Ember的开发效率似乎更胜一筹。
Backbone要求你写很多样板(boilerplate )代码,而我认为这完全没必要。要我说,这是直接影响效率的一个因素。
CanJS的开发效率属于不快不慢的那种。不过,考虑到学习难度很低,因此适合早投入早产出的项目。
Ember的开发效率首屈一指。它有很多强制性约束,可以帮你自动完成的事很多。而开发人员要做的,就是学习和应用这些约定,Ember会替你处理到位。
AngularBackbone CanJSEmber
能轻易找到参考资料和专家帮忙吗?
Backbone的社区很大,这是人所共知的事实。关于Backbone的教程也几乎汗牛充栋,StackOverflow和IRC社区非常热闹。
Angular和Ember社区也相当大,教程什么的同样不少,StackOverflow和IRC也很热闹,但还是比不上Backbone。
CanJS社区呢,相对小一些,好在社区成员比较活跃,乐于助人。我倒没发现CanJS社区规模小有什么负面影响。
AngularBackbone CanJSEmber
有没有插件或库构成的生态系统?
说起插件和库,Backbone的选择是最多的,可用插件俯拾皆是,这一点让其他框架都望尘莫及。Angular的生态圈加上还是很令人瞩目的。我觉得Ember的下游生态虽然欠发达,但Ember本身很受欢迎, 所以前景十分乐观。CanJS的下游支脉比较少见。
AngularBackbone CanJSEmber
这个因素有时候很重要,特别是对于移动开发项目。
自身大小(无依赖,未压缩)
AngularBackbone CanJSEmber
80KB18KB33KB141KB
Backbone最小,这一点也是最为人们所津津乐道的。但不能只看库本身的体积。
包含依赖的大小
80KB的Angular是唯一不需要其他库就能使用的。其他三个框架则都对其他库有依赖。
Backbone至少需要和。虽然在Underscore中可以使用最小的模板来渲染视图,但多数情况下,还要借助更好的模板引擎,比如。这样它就增肥到了61KB。
CanJS至少需要Zepto,因此会达到57KB。
Ember需要和,总共是269KB。
AngularBackbone CanJSEmber
80KB61KB57KB269KB
AngularBackbone CanJSEmber
我不认为性能是选择框架的关键因素,因为这些框架在预期应用领域中的性能都不差。当然啦,具体还得看你做什么项目。要是想开发游戏,那性能是个重要因素。
虽然我见过,也亲自做过一些性能对比(比如),但我并不完全相信测试结果。很难说这种测试的方法和结果与实际项目吻合。
不过,据我所见所闻,CanJS的性能是最高的,而且在视图绑定上格外突出。相对来说,我觉得Angular性能稍差,因为它执行对象的脏检查(dirty checking),这一点就拖了它的后腿了。。
AngularBackbone CanJSEmber
这个框架成熟吗,经过实际检验了吗,有多少网站在用它呢?
使用Backbone的网站不计其数。最近两年,它的核心代码没怎么改过,这是成熟的一个重要标志。
Ember已经不是新框架了,但它的重大变更还是经常有,前几月刚刚稳定下来。因此,目前还不能说它是个成熟的框架。
Angular似乎比Ember更稳定,验证的示例也更多,但不能与Backbone相提并论。
CanJS好像还未经任何验证,因为不知道有什么网站在使用它。不过,CanJS其实也没有看起来那么弱不经风,它可是从精简来的。JavaScriptMVC是2008年就出现的一个库,因此会有很多智慧结晶留传下来。
AngularBackbone CanJSEmber
内存泄漏隐患
如果你想开发每次打开都得运行很长时间的单页应用,这是必须得考虑的问题。你当然不希望自己的应用导致内存泄漏,这个问题非常现实。不幸的是,内存泄露很容易发生,而自己编写的DOM事件监听器则是重灾区。
只要你守规矩,Angular、CanJS和Ember能把这个问题帮你解决好。Backbone则不然,它需要你自己手工来卸载。
AngularBackbone CanJSEmber
这恐怕是选择框架时最重要的一个因素了。
你喜欢声明式HTML吗?-> Angular
你喜欢使用模板引擎吗?-> Backbone、CanJS或Ember
你喜欢固执已见的框架吗?-> Ember
你希望框架与最初的模式完全吻合吗?-> 没有完全吻合的,或许CanJS最接近
你希望使用目前看来很酷的框架吗?-> Ember、Angular
本项没办法打分。
好啦,把各框架所得分数做个汇总吧。别忘了这只是我个人看法,如果你觉得哪一项打分有失偏颇,请务必告诉我。
(此处只是一个静态图表,单击可以打开)
如果每一个因素的权重都一样,那么这几个框架确实难分高下。因此,我觉得最终决定很大程度上还是取决于你的个人偏好,或者必须要给每个因素赋予不同的权重才行。
聊聊Backbone(公平到此为止!)
我在这篇文章里始终尽量做到“一碗水端平”。可是,最后针对Backbone我还想再多说两句,因为这些话如鲠在喉,不吐实在不快。
Backbone在两年前是一个不错的库,但我知道:今天已经有了更好的选择了。我也知道,很多人选择Backbone其实仅仅因为使用它的人多,这是个恶性循环。
Backbone为追求灵活性而抛弃了开发的便利性。但我觉得它实在太过了,功能严重缺乏,导致开发效率很低。没错,有一大堆插件可以实现这样那样的功能,可这样一来你不仅要学习Backbone,还得不断学习怎么使用那些插件。
Backbone的大型社区和生态系统也很诱人,但随着其他框架越来越受欢迎,这个优势也将慢慢消失。
正因为如此,我强烈建议大家选择Backbone时要三思而后行。
原文地址:
相关阅读:
译者/ 李松峰
,非计算机专业出身的技术爱好者,曾从事 5 年 Web 前后端开发工作,现为北京图灵文化发展有限公司 QA 部主任。2006 年开始涉足计算机相关图书翻译,至今翻译字数超过 458 万字,已出版译著 20 余部。图灵社区 ID:
Douglas Crockford:代码阅读和每个人都该学的编程
作者 /Peter Seibel
Peter Seibel是Common Lisp专家,Jolt生产效率大奖图书《实用 Common Lisp 编程》的作者。耶鲁大学英语专业毕业,后投身于互联网行业,曾负责“Mother Jones Magazine” 和“Organic Online”的 Perl 专栏以及 WebLogic 的 Java 专栏,并曾在加州大学伯克利分校成人教育学院教授 Java 编程。2003 年辞职专心研究 Lisp 编程,之后即有了那部 Jolt 大奖图书。现在他是 Gigamonkeys Consulting 公司的首席执行官,和家人幸福地生活在加州伯克利。
Douglas Crockford现在供职于Paypal。曾是Yahoo!的资深JavaScript架构师,他在上世纪70年代初求学期间就开始从事程序开发工作了,那时的他主修电视广播专业,但苦于无法进入演播室工作,转而学习了学校开设的Fortran课程。在其职业生涯中,Crockford曾先后供职于Atari、Lucasfilm和Electric Communities,以各种方式联姻计算机与传播媒介。深感于XML的复杂性,他发明了JSON这一广泛用于Ajax应用的数据交换格式。Crockford曾谈到如果能避免使用某些特性的话,JavaScript实际上是一门相当优雅的语言。他强调了以子集方式来管理复杂度的重要性,同时介绍了他所使用的一种代码阅读方法:从清理代码开始。
关于JavaScript
Seibel:在程序学习之路上有哪些令你后悔的事情?
Crockford:我了解一些语言,但却一直没有机会使用。我花了不少时间学习APL并了解到其衰败的原因,但这门语言真的非常优雅,可我却没有花时间使用它,这太遗憾了。除此以外,我还了解其他一些语言,知道能用它们做什么,但实际上却并没有机会用这些语言思考。
Seibel:我听说你喜欢ES3版本JavaScript的简洁性。
Crockford:嗯,最终无论怎么对语言进行修订,其要义都是希望促进语言的不断成功。语言越成功,修改的代价就越大。随着你的不断成熟,再教育的成本就会变得更大,同时还有潜在的破坏代价,而这些成本和代价也会变得难以接受。如果你确实非常成功,那就更要小心提防所做的任何变化了。反之,如果你尚未成功,那么就有更大的自由空间来改变了。
JavaScript成为世界上最流行的编程语言纯粹是偶然。目前世界上JavaScript处理器的数量要高于任何其他语言。得益于其安全模型带来的种种问题,JavaScript是唯一一门可在任何机器上编写并运行的语言。这些还嫌不够的话,再看看那么多嵌入了JavaScript的应用吧。Adobe的大多数应用都嵌入了JavaScript,这样就可以在本地编写脚本控制这些应用了。还有其他很多应用,不胜枚举。这么一看,JavaScript已经变得非常流行了。
JavaScript这门语言的问题在于推向市场以及标准化的过程都过于匆忙了。其大多数缺陷都没有出现在目前的实现当中——只存在于规范中。标准说照错的做,这听起来太吓人了,但这就是JavaScript的状态。它于1999年冻结了,接下来本应走向灭亡。但Ajax的横空出世改变了这一切,JavaScript变成了世界上最重要的编程语言。
于是,我们现在认为应该修复它。但这事应该是在2000年就开始做的,而那时并没有这么做,因为根本没人关注JavaScript。现在它已经长大了。
Web环境下的JavaScript还有一点非常怪异:如果编写服务器端应用、桌面应用或是嵌入式应用,你不仅需要选择语言,还要选择特定的编译器以及特定的运行时。但对JavaScript而言你别无选择,你必须在所有的环境下运行。
由于要在所有环境下运行,bug就没法修复了。如果某个浏览器厂商搞出个bug,他们会说“天啊,搞砸了”,下个月就会发布另一个版本,但我们却不能指望着所有用户都会升级。大多数人一旦在机器里装上IE就再也不会升级了,那些bug就会常年驻留在浏览器上。
Seibel:这就是目前的状况。你希望Web能成为更适合于应用开发的平台。除非所有浏览器都能修复这些bug,否则我们是没法修复的,如果这还不管用,那实在是没办法了。路在何方呢?
Crockford:这正是我努力争取的东西。我心中已经有一套理想的方案了,知道它要成为什么样子。我知道身处何方,也能看到前方的障碍。我正在思考如何才能前行。从某种意义上来说,我们已经身陷囹圄了,因为我们开发出了这些大型系统——我更关注经济系统、社会系统,还有技术系统——它们都依赖于这个并不完善的系统。
毫无疑问,JavaScript的鸡肋就是对全局对象的依赖。它没有链接器,无法在编译单元间隐藏信息。它将这些信息都丢到了一个普通的全局对象中。这样,所有组件都能访问一切内容,对DOM拥有相同的访问权限,对网络也拥有相同的访问权限。如果有人将脚本放到了你的页面上,它就可以访问服务器,看起来就像是你自己的脚本一样,而服务器则根本无法分辩。
这些脚本可以获取屏幕信息,可以访问用户信息,看起来就像是你自己的脚本一样,用户同样也无法分辩。在页面来自于你的服务器,而且无论脚本来自何处都拥有同样权限的情况下,用户所有新式的反钓鱼工具就都派不上用场了。
实际情况比这还要糟,因为脚本还可以通过其他方式进入你的页面。Web架构涉及几种语言——有HTTP、HTML,URL可以看作是一种语言,有CSS,还有脚本语言。它们可以彼此嵌入并且具有不同的引号、转义和注释约定。所有浏览器对这些语言的实现都不一样。加之这些实现的细节并不是在哪里都可以找到,这样恶意用户就能够轻松将脚本放到URL中,放到样式中,放到HTML中,放到其他脚本中。
Seibel:这么说来人们在ES4上所付出的仅仅是机会成本,每个人都花时间来思考这个问题而不是寻找问题的解决办法?
Crockford:没错。ES4的目标根本就不对,它想解决的是人们厌恶JavaScript这个问题。我很欣赏Brendan Eich对待这个问题的立场,他确实做得很棒,但却太着急了,没有管理好这个项目,放出了ES4这么差劲的东西。在过去的12年里,有很多人诋毁和诅咒他,觉得他是个蠢蛋,搞出来的语言也愚蠢至极,但我觉得事实并非如此。这个语言确实有闪亮的地方,而他其实是一个才华横溢的家伙。现在他在极力表明自己是无辜的,想要证明自己是个聪明人,他想要展示这门语言的优秀特性,将这些特性整合起来,这样它就能好用了。
我觉得这并非现在需要解决的问题,亟需解决的问题是:Web正变得分崩离析,我们需要对其进行修复。这要求我们清楚路在何方。我最反对Brendan的一个地方就是他的方案完全没抓住重点。
我越来越感觉到这是个问题。如果能够模块化,如果能够自己选择编程语言,我们本可以走得更远。但现在这一切都尚未实现,不过现实情况比这还好一些。现在有Caja和ADsafe这样的东西在使用目前的技术解决这些问题。时不我待啊。
ADsafe创建了JavaScript的一个安全子集,它不允许访问任何的全局变量和危险的东西。这么做的结果是这个子集依然可以构成一门有用的语言,因为有强大的lambda作为后盾,它可以完成很多事情。它是一门非传统的语言,它不支持我们今天使用原型的方式,但这个子集却是一个功能完整的lambda语言,非常强大。
Seibel:先不考虑ES4目标的正确与否,仅仅从语言的视角来看,它有没有哪些特性吸引到你呢?
Crockford:有一些bug修复我觉得很不错,值得保留。但ES4中有太多未经验证的东西了。我们使用ES3的经历表明,一旦规范中出现了错误就很难再把它剔除出去。我们并没有ES4的使用经验,没人用它开发过大型应用。
在真正应用到实际工作中之前需要对其进行标准化和部署,我觉得现在的节奏太快了。如果有多个参考实现,人们也开发出了一些有价值的应用,这才表明语言是没问题的,接下来才对其进行标准化并部署起来。但现在的做法却是反其道而行之。
Seibel:Google的GWT会将Java编译为JavaScript。还有人尝试将其他语言编译为JavaScript。这是未来之路么?
Crockford:看到JavaScript逐渐变为通用运行时还是蛮有趣的一件事,这一点倒是我们不曾想到的。
Seibel:但正如你所说,JavaScript无处不在,它确实是通用运行时。
Crockford:我认为这也进一步说明JavaScript确实该跑步前进。尤其是现在我们正在进入移动产品的时代,但摩尔定律并不适用于电池。JavaScript在解释上所花费的时间愈发显得重要,此外还有周期数。我认为这会更加督促我们来改进运行时的质量。
就GWT和其他转换工具而言,我的态度是实用至上。人们是很难融入到这个环境中的——如果能找到好的解决方案,那非常棒。我自己是害怕使用这些工具的,担心的就是抽象层泄漏(abstraction leakage)。如果你的Java代码、GWT或是其生成的代码存在问题,那可能就没法解决了。特别是在完全不了解JavaScript的情况下就贸然使用这种方法会更加危险,因为GWT对你完全隐藏了JavaScript。在这种情况下一旦出现了问题,那受伤的肯定是你自己。虽然我还没有听说发生过这种事,到目前为止这些工具还不错,但毕竟存在着风险。
Seibel:你希望JavaScript有哪些变化呢?
Crockford:我认为改进JavaScript最好的办法就是瘦身。如果我们能够取其精华,弃其糟粕,那JavaScript会变得更棒。我认为这个办法也适合于HTML、HTTP和CSS。我们应该仔细思考所用的各种标准,搞清楚需要哪些特性,遗漏了哪些特性并重新审视它们,绝不应该盲目地增加新特性。
Seibel:目前你在Yahoo!扮演着JavaScript架构师和布道者的角色,我想你的一部分工作是向Yahoo!的JavaScript开发者们传授“JavaScript应用之道”。那你的工作还涉及一般性的优秀设计实践与编码实践么?
Crockford:我一直在倡导良好的代码阅读方法。我认为这是社区中的开发者多花点儿时间阅读彼此的代码,这对每个人都非常有意义。现今的项目管理有这样一种趋势:让开发者们独立完成工作,接下来进行大规模的整合,如果没问题就发布出去,然后就大功告成了,接着就抛之脑后了。
这么做的一个后果就是一旦碰上差劲的开发者,到最后你才能发现问题,不过这时已经太晚了。项目会因此出现风险,在构建的时候才发现有些代码写得实在是糟糕,肯定会导致项目延期,而这是无法接受的。另外,项目中可能会有一些优秀的开发者,而他们却没有太多机会指导其他成员。代码阅读可以解决上面这两个问题。
Seibel:能否详细谈谈如何进行代码阅读呢?
Crockford:每次开会都让一些人阅读他们各自的代码,他们会引领我们查看其编写的所有内容,其他人则负责检查。对于团队的其他成员来说,这绝对是个学习的好机会,通过这个过程他们就可以知道自己的东西该如何与他人的相配合。
每个人都围坐在桌边,手里拿一叠纸,同时还把代码在屏幕上打出来,大家一起阅读。我们会在编写代码的过程中加上注释。有人会说“我看不懂这个注释”或是“这个注释与代码风马牛不相及”。大家的意见极具价值,因为作为开发者的你是不会阅读自己编写的注释的,你也根本没有意识到自己写的注释误导了读者。有这么多人帮助你编写整洁的代码是多么幸福的一件事啊——你会找到自己根本无法找到的缺陷。
我认为一小时的代码阅读抵得上两周的QA。这种剔除错误的手段真是很高效。如果你让能力很强的同事阅读代码,那么他们周围的新手们就会学到很多东西,而这一切是无法通过其他手段获得的;如果新手来阅读代码,那么他会得到很多极具价值的建议。
但这件事我们不能一直留到最后再做。回忆过去,我们会在项目完成之际安排代码阅读,但这个时候已经太迟了,只好取消。现在我深信代码阅读应该伴随着整个项目的生命周期。我花了很长时间才意识到这一点,这么做的好处不胜枚举。
首先,这么做有助于把控项目,我们能够真切地看到大家的进度,也能及早发现是不是有人已经偏离了轨道。
我曾经管理过一些项目,马上就到最后期限了,有人说“耶,马上就干完了”,然后我拿到了代码,发现里面什么都没有,有的也是一些垃圾,离完成还远着呢。管理层最厌恶这种事情了,我觉得代码阅读能够有效避免这种窘境的发生。
Seibel:那你需要指导别人如何进行代码阅读么?可以想象,既不想让代码编写者感觉受到侵犯,又能给出颇具价值的意见是很难的。
Crockford:没错,这需要给予团队成员充分的信任,要明确界定好边界。如果团队不和睦,那就别指望这么做了,这会导致团队分崩离析。如果还没有意识到团队的不和睦,那这么做很快就能发现。这个过程会让你学到很多,也会揭示出很多问题。起初会觉得不太自然,但一旦适应后就会觉得再自然不过了。
另外,我们要编写可读性好的代码。大家都知道,整洁很重要,而代码风格也同样如此。所有这一切会提升代码的质量并增强编程社区的能力。
Seibel:如何编写可读性好的代码呢?
Crockford:可读性有几个等级。最简单的一级是与表达保持一致,适当地保持缩进,在适当的地方使用空格。我有一个习惯来自于早年学习Fortran的时候,那就是,我往往会使用过多的单字母变量名,这个习惯可不好。我真的在很努力改掉这个坏习惯,但太难了——这么多年来还在与之斗争。
Seibel:有什么具体的举措可以提升代码的可读性呢?
Crockford:子集的想法非常重要,尤其对于JavaScript来说更是如此,因为这门语言包含了太多的糟粕,当然其他语言也一样。当还是个菜鸟时,我会翻阅语言规范并弄明白每个特性。我知道该如何使用这些特性并一直在用。但事实证明很多特性并非是深思熟虑的结果。
我现在想到的是Fortran,但其实所有语言都难逃这个宿命。有时语言设计者本身就错了。依我看来,C在设计上存在很多不妥之处。
Seibel:你如何阅读别人编写的代码呢?
Crockford:清理。我会把代码放到文本编辑器中并开始修复。首先,我会统一标点符号,适当缩进,等等这类的事情。我有些程序可以完成这些事情,但从长远来看自己完成会更加高效,因为这有助于我加深对代码的理解。Morningstar曾教会我如何完成这些事情。他非常善于重构别人的代码,并且这也是他所使用的方法,很好。
Seibel:你是否遇到过这种情况:看到代码写得一团糟,然后进行清理,但最后发现原来的代码其实写得很不错?
Crockford:还没遇到过。我认为随随便便写出来的代码肯定不好。好的代码意味着可读性要好。在某种程度上,如果我搞不懂代码的意图,那么写得再好也没用,有可能代码在我不关心的方面表现得很好,比如很高效、很紧凑,等等。
代码的可读性是我的第一要义。它比速度还重要,可以与正确性一争高下,可读性是正确性的重要前提。如果可读性不好,那就不是好代码,代码的编写者可能做出了错误的权衡。
Seibel:在阅读代码时你会先排版,那你会对代码进行多大程度上的重构呢?
Crockford:我会重新编排代码以便所有东西都会在使用前声明和创建。有些语言提供了很大的灵活性,让你无需再这么做了。但这种灵活性我不需要。
Seibel:你曾在之前的一次演讲中引用了《出埃及记》第23章第10节和第11节的内容——六年你要耕种田地,收藏土产,只是第七年你要叫地歇息,不耕不种。并建议每次第7个sprint都应该用来清理代码。那什么时候做比较好呢?
Crockford:每6个周期——不管周期间是什么都该如此。如果你是每月交付,那么我觉得每隔半年都应该跳过一个周期,专门用来清理代码。
每个人的必修课:编程
Seibel:编程是不是越来越容易了,门槛逐渐降低?
Crockford:我对编程的兴趣在于帮助其他人编程、设计特定的语言或编程工具,这样会有越来越多的人能够从事编程工作——这也是Smalltalk的初衷。Smalltalk后来的发展方向出现了变化,但最初的方向确实吸引了我。我们如何设计一门面向儿童的语言,如何为那些并非程序员的人们设计一种语言?
Seibel:你是不是认为每个人都应该学习编程,至少了解一些?
Crockford:没错。当今世界快被计算机控制了,为了保护自己或是让自己更加全面,你应该了解这些东西的工作方式。
Seibel:有些人认为通过编程可以学到一种重要的思考方式,比如阅读和数学就是不同的思考方式,但都非常重要。
Crockford:我以前也这么想。在开始编程时我就有过这种想法:一切都是那么地井然有序,我看到了之前从未接触过的结构等东西。我在想“喔,这太神奇了。每个人都应该学习学习”,因为我突然之间感觉自己变聪明了。但不久之后,在与其他程序员交流的过程中发现,他们并没有开窍。程序员其实与常人也没什么区别,有时他们也会出现误解。当认识到这一点后我觉得很难过。
Seibel:那编程只是年轻人的专利么?
Crockford:过去我是这么认为的。几年前我患有睡眠呼吸暂停的症状,但没有意识到。我想可能是太累了,年纪也有些大了吧,结果发现自己的注意力很难集中,甚至都没法编程了,因为我的大脑没法承载太多的东西。很多时候编程都需要先在脑子里想好,然后再写出来,但我却不行。
我丧失了这种能力,想当然地认为是年龄太大的缘故。幸好,病情得到了好转,于是我又开始编程了。现在的我编程水平可能比以前还要好,因为我知道如何不过多地依赖于记忆。现在的我更喜欢将代码文档化,因为我不敢保证下一周还能记得写这些代码的意图。事实上,有时我会检查自己的代码,但会惊讶于自己怎么会这么写代码:我压根就不记得自己曾经这么写过,这些代码有的非常丑陋,有的却非常优雅。我实在不知道怎么会这样。
Seibel:Knuth的大部头《计算机程序设计艺术》如何?你是从头到尾读过这本书呢,还是将其作为参考随时翻阅,抑或是把它束之高阁碰也不碰呢?
Crockford:除了你说的最后一种情形之外。在上大学时,有那么几个月我连房租都没交,就是为了买他的书。我读过这些书,从中得到了不少乐趣,比如在第一卷的索引有个关于拖车的笑话就很好玩。我到现在为止还没能把书上的内容全部搞懂。Knuth对某些地方的研究要比我深入得多,但我还是喜欢这些书并把它们当作参考资料。
Seibel:你是从头到尾逐字阅读,跳过那些不理解的数学部分?
Crockford:是的,我会很快略读过星号太多的部分。我试图将熟悉Knuth的书作为招聘标准,但结果却大失所望,根本没几个人读过他的书。依我看来,任何自称为专业程序员的人都应该读过Knuth的书,至少也应该买过他的书。
Seibel:也就是说Knuth讲述的是如何实现最根本的东西,然后才有全景。即便清理了平台,但使用理性的方式构建大型系统和设计也是非常困难的。请问你是如何设计代码的?
Crockford:编写程序与对程序的生命周期进行迭代是不同的。通常,编写软件的原因在于我们知道将要修改它,而修改任何东西都不那么容易,因为很多时候修改意味着打破旧有的东西。
你不能期望使用这种方式完成所有事情,但还是应该尽力保证足够的灵活性,这样不管做什么都能适应。这就是我的观点。如何避免误入死胡同?如何保证灵活性?
这就是我喜欢JavaScript的原因之一。我发现JavaScript可以轻松实现重构,而重构一个继承层次很深的类实在太痛苦了。
Seibel:你觉得自己是个科学家、工程师、艺术家、工匠还是什么?
Crockford:我觉得自己是个作家。有时我使用英语写作,有时使用JavaScript。归根结底,这完全取决于交流方式以及为了促进这种交流所采取的结构。人类语言与计算机语言在很多地方都是大相径庭的,我们需要阅读计算机语言,因此必须与之交流,我根据语言的这种交流能力判断计算机程序的优劣。在这个层次上,人类语言与计算机语言的差别不大。
Seibel:你对自学的程序员有什么建议呢?
Crockford:两个字:多读。现在有不少好书,去找些好书来看吧。如果从事Web开发工作,请找一些优秀的站点,看看他们的代码。话虽如此,其实我不太想这么建议。大多数Web开发者都是从“查看源代码”开始走上Web开发之路的,但直到现在,大多数源代码的质量都是非常低劣的。因此有一代程序员都被那些低劣的示例误导了,他们写的代码质量也不高。现在的情况已经有所改观,但依然有很多低质量的代码游离于你我之间,所以我是不太想给出这个建议的。
这是一本访谈笔录,记录了当今最具个人魅力的 15 位软件先驱的编程生涯。包括 Donald Knuth、Jamie Zawinski、Joshua Bloch、Ken Thompson 等在内的业界传奇人物,为我们讲述了他们是怎么学习编程的,在编程过程中发现了什么以及他们对未来的看法,并对诸如应该如何设计软件等长久以来一直困扰很多程序员的问题谈了自己的观点。本文选编自。
Node上的JavaScript性能测试实践
作者/ 朴灵
朴灵,真名田永强,文艺型码农,就职于阿里巴巴数据平台,资深工程师,Node.js布道者,写了多篇文章介绍Node.js的细节。活跃于CNode社区,是线下会议NodeParty的组织者和JSConf China(沪JS和京JS)的组织者之一。热爱开源,多个Node.js模块的作者。个人GitHub地址:。叩首问路,码梦为生。
单元测试主要用于检测代码的行为是否符合预期。在完成代码的行为检测后,还需要对已有代码的性能作出评估,检测已有功能是否能满足生产环境的性能要求,能否承担实际业务带来的压力。换句话说,性能也是功能。
性能测试的范畴比较广泛,包括负载测试、压力测试和基准测试等。由于这部分内容并非Node特有,为了收敛范畴,这里将只会简单介绍下基准测试。
除了基准测试,这里还将介绍如何对Web应用进行网络层面的性能测试和业务指标的换算。
基本上,每个开发者都具备为自己的代码写基准测试的能力。基准测试要统计的就是在多少时间内执行了多少次某个方法。为了增强可比性,一般会以次数作为参照物,然后比较时间,以此来判别性能的差距。
假如我们要测试ECMAScript5提供的Array.prototype.map和循环提取值两种方式,它们都是迭代一个数组,根据回调函数执行的返回值得到一个新的数组,相关代码如下:
var nativeMap = function (arr, callback) {
return arr.map(callback);
var customMap = function (arr, callback) {
var ret = [];
for (var i = 0; i & arr. i++) {
ret.push(callback(arr[i], i, arr));
比较简单直接的方式就是构造相同的输入数据,然后执行相同的次数,最后比较时间。为此我们可以写一个方法来执行这个任务,具体如下所示:
var run = function (name, times, fn, arr, callback) {
var start = (new Date()).getTime();
for (var i = 0; i & i++) {
fn(arr, callback);
var end = (new Date()).getTime();
console.log('Running s d times cost d ms', name, times, end
%% % - start);
最后,分别调用1 000 000次:
var callback = function (item) {
run('nativeMap', 1000000, nativeMap, [0, 1, 2, 3, 5, 6], callback);
run('customMap', 1000000, customMap, [0, 1, 2, 3, 5, 6], callback);
得到的结果如下所示:
Running nativeMap 1000000 times cost 873 ms
Running customMap 1000000 times cost 122 ms
在我的机器上测试结果显示Array.prototype.map执行相同的任务, 要花费for循环方式7倍左右的时间。
上面就是进行基准测试的基本方法。 为了得到更规范和更好的输出结果, 这里介绍benchmark这个模块是如何组织基准测试的,相关代码如下:
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite();
var arr = [0, 1, 2, 3, 5, 6];
suite.add('nativeMap', function () {
return arr.map(callback);
}).add('customMap', function () {
var ret = [];
for (var i = 0; i & arr. i++) {
ret.push(callback(arr[i]));
}).on('cycle', function (event) {
console.log(String(event.target));
}).on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
它通过suite来组织每组测试,在测试套件中调用add()来添加被测试的代码。
执行上述代码,得到的输出结果如下:
nativeMap x 1,227,341 ops/sec ±1.99 (83 runs sampled) %
customMap x 7,919,649 ops/sec ±0.57
% (96 runs sampled)
Fastest is customMap
benchmark模块输出的结果与我们用普通方式进行测试多出±1.99 (83 runs sampled) % 这么一段。事实上,benchmark模块并不是简单地统计执行多少次测试代码后对比时间,它对测试有着严密的抽样过程。执行多少次方法取决于采样到的数据能否完成统计。83 runs sampled表示对nativeMap测试的过程中,有83个样本,然后我们根据这些样本,可以推算出标准方差,即±1.99%这部分数据。
除了可以对基本的方法进行基准测试外, 通常还会对网络接口进行压力测试以判断网络接口的性能,这在6.4节演示过。对网络接口做压力测试需要考查的几个指标有吞吐率、响应时间和并发数,这些指标反映了服务器的并发处理能力。
最常用的工具是ab、siege、http_load等,下面我们通过ab工具来构造压力测试,相关代码如下:
$ ab -c 10 -t 3 http://localhost:8001/
This is ApacheBench, Version 2.3 &$Revision: 655654 $&
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 5000 requests
Completed 10000 requests
Finished 11573 requests
Server Software:
Server Hostname:
Server Port:
Document Path:
Document Length:
10240 bytes
Concurrency Level:
Time taken for tests:
3.000 seconds
Complete requests:
Failed requests:
Write errors:
Total transferred:
HTML transferred:
Requests per second:
3857.60 [#/sec] (mean)
Time per request:
2.592 [ms] (mean)
Time per request:
0.259 [ms] (mean, across all concurrent requests)
Transfer rate:
38858.59 [Kbytes/sec] received
Connection Times (ms)
mean[+/-sd] median
Processing:
Percentage of the requests served within a certain time (ms)
35 (longes % t request)
上述命令表示10个并发用户持续3秒向服务器端发出请求。下面简要介绍上述代码中各个参数的含义。
Document Path:表示文档的路径,此处为/。
Document Length:表示文档的长度,就是报文的大小,这里有10KB。
Concurrency Level:并发级别,就是我们在命令中传入的c,此处为10,即10个并发。
Time taken for tests:表示完成所有测试所花费的时间,它与命令行中传入的t选项有细微出入。
Complete requests:表示在这次测试中一共完成多少次请求。
Failed requests:表示其中产生失败的请求数,这次测试中没有失败的请求。
Write errors:表示在写入过程中出现的错误次数(连接断开导致的)。
Total transferred:表示所有的报文大小。
HTML transferred:表示仅HTTP报文的正文大小,它比上一个值小。
Requests per second:这是我们重点关注的一个值,它表示服务器每秒能处理多少请求,是重点反映服务器并发能力的指标。这个值又称RPS或QPS。
两个Time per request值:第一个代表的是用户平均等待时间,第二个代表的是服务器平均请求处理事件,前者除以并发数得到后者。
Transfer rate:表示传输率,等于传输的大小除以传输时间,这个值受网卡的带宽限制。
Connection Times:连接时间,它包括客户端向服务器端建立连接、服务器端处理请求、等待报文响应的过程。
最后的数据是请求的响应时间分布,这个数据是Time per request的实际分布。可以看到,50%的请求都在2ms内完成,99%的请求都在6ms内返回。
另外,需要说明的是,上述测试是在我的笔记本上进行的,我的笔记本的相关配置如下:
2.4 GHz Intel Core i5
8 GB 1333 MHz DDR3
基准测试驱动开发
Felix Geisend?rfer是Node早期的一个代码贡献者,同时也是一些优秀模块的作者,其中最著名的为他的几个MySQL驱动,以追求性能著称。他在“Faster than C”幻灯片中提到了一种他所使用的开发模式,简称也是BDD,全称为Benchmark Driven Development,即基准测试驱动开发,
其中主要分为如下几步其流程图如图10-9所示。
(1) 写基准测试。
(2) 写/改代码。
(3) 收集数据。
(4) 找出问题。
(5) 回到第(2)步。
图1 基准测试驱动开发的流程图
之前测试的服务器端脚本运行在单个CPU上,为了验证cluster模块是否有效,我们可以参照Felix Geisend?rfer的方法进行迭代。通过上面的测试,我们已经完成了一遍上述流程。接下来,我们回到第(2)步,看看是否有性能的提升。
原始代码无需任何更改,下面我们新增一个cluster.js文件,用于根据机器上的CPU数量启动多进程来进行服务,相关代码如下:
var cluster = require('cluster');
cluster.setupMaster({
exec: "server.js"
var cpus = require('os').cpus();
for (var i = 0; i & cpus. i++) {
cluster.fork();
console.log('start ' + cpus.length + ' workers.');
接着通过如下代码启动新的服务:
node cluster.js
start 4 workers.
然后用相同的参数测试, 根据结果判断启动多个进程是否是行之有效的方法。 测试结果如下:
$ ab -c 10 -t 3 http://localhost:8001/
This is ApacheBench, Version 2.3 &$Revision: 655654 $&
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 5000 requests
Completed 10000 requests
Finished 14145 requests
Server Software:
Server Hostname:
Server Port:
Document Path:
Document Length:
10240 bytes
Concurrency Level:
Time taken for tests:
3.010 seconds
Complete requests:
Failed requests:
Write errors:
Total transferred:
HTML transferred:
Requests per second:
4699.53 [#/sec] (mean)
Time per request:
2.128 [ms] (mean)
Time per request:
0.213 [ms] (mean, across all concurrent requests)
Transfer rate:
47339.54 [Kbytes/sec] received
Connection Times (ms)
mean[+/-sd] median
Processing:
Percentage of the requests served within a certain time (ms)
215 (longest request) %
从测试结果可以看到, QPS从原来的3857.60变成了4699.53, 这个结果显示性能并没有与CPU的数量成线性增长, 这个问题我们暂不排查, 但它已经验证了我们的改动确实是能够提升性能的。
测试数据与业务数据的转换
通常,在进行实际的功能开发之前,我们需要评估业务量,以便功能开发完成后能够胜任实际的在线业务量。如果用户量只有几个,每天的PV只有几十个,那么网站开发几乎不需要什么优化就能胜任。如果PV上10万甚至百万、千万,就需要运用性能测试来验证是否能够满足实际业务需求,如果不满足,就要运用各种优化手段提升服务能力。
假设某个页面每天的访问量为100万。根据实际业务情况,主要访问量大致集中在10个小时以内,那么换算公式就是: QPS = PV / 10h 100万的业务访问量换算为QPS,约等于27.7,即服务器需要每秒处理27.7个请求才能胜任业务量。
测试是应用或者系统最重要的质量保证手段。有单元测试实践的项目,必然对代码的粒度和层次都掌握得较好。单元测试能够保证项目每个局部的正确性,也能够在项目迭代过程中很好地监督和反馈迭代质量。如果没有单元测试,就如同黑夜里没有秉烛的行走。
对于性能,在编码过程中一定存在部分感性认知,与实际情况有部分偏差,而性能测试则能很好地斧正这种差异。
从不同的视角介绍了Node内在的特点和结构。书中并非完全按照顺序递进式介绍,首先简要介绍了Node,接着深入探讨了模块机制、异步I/O和异步编程,然后讨论了内存控制和Buffer相关的内容,接着探讨了网络编程、Node Web开发、进程、测试和产品化等内容,最后的附录介绍了Node的安装、调试、编码规范和NPM仓库搭建等内容。

我要回帖

更多关于 javascript 异步调用 的文章

 

随机推荐