文档章节

[JavaScript]项目优化总结

豆仔
 豆仔
发布于 2012/12/19 16:31
字数 3928
阅读 259
收藏 27

前段时间对公司已有项目JavaScript代码进行优化,本文的是对优化工作的一个总结,拿出来与大家分享。 当然我的优化方式可能并不是最优的,或者说有些不对的地方,请指教。

JavaScript优化总结分为以下几点

优化前后对比

优化前

  • 代码混乱,同样功能的函数重复出现在多个地方。如果需要修改实现,需要找到所有的地方。牵一发而动全身
  • JavaScript文件未压缩,size比较大加载消耗网络耗时,阻塞页面渲染
  • 使用时需要加载多个单独的JavaScript文件,增加了http请求数降低性能
  • 缺乏文档(让后面的开发者对已有功能不清楚,这在一定程度上造成前面说的,同样功能的函数重复出现在多个地方)

优化后

  • 模块化,提取公共接口组织为库、结构清晰、方便代码重用、并且能够有效防止变量污染问题。
  • JavaScript公共库文件使用UglifyJS压缩:
    • Size比较小优化了网络加载时间
    • 压缩混淆了代码,在一定程度上保护代码
  • 对公共库合并压缩,在减少size的同时,减少http请求数
  • 公共库中每个类、函数、属性都有说明文档

l 模块化(类编程):代码清晰、有效防止变量污染问题、代码重用方便扩展等; l JavaScript压缩混淆:减少size优化加载时间,混淆保护代码; l JavaScript文件合并:减少http请求优化网络耗时提升性能; l 生成文档:方便公共库的使用,查找接口方便。

模块化(类编程)

对于静态类来说JavaScript实现比较简单,使用Object直接量就已经够用了;但是要创建实例化、可继承经典的类需要做一番工作。因为JavaScript是**基于原型的(prototype-based)**编程语言,并没有包含内置类的实现(它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual等),但是我们通过JavaScript可以轻易地模拟出经典的类。

静态类

根据宝宝JS公共接口的特性,它们不需要实例化,所以优化使用了该方式。下面以PetConfigParser为例介绍下实现方式:

PetConfigParser类

<!-- lang: js -->
var PetConfigParser;
if(!PetConfigParser) {
  PetConfigParser = {};
}

(function() {
  //private 变量、函数
  /**
   * 宝宝所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】为key
   * @attribute petDic
   * @type {Object}
   * @private
   */
  var petDic = null; //宝宝字典
  /**
   * 根据_\_pet\_config构建一个Object字典,以cate、dex、lvl组合作为key
   * @method buildPetDic
   * @private
   * @return {void}
   */
  function buildPetDic() {
    petDic = new Object();
    for(var item in _\_pet\_config) {
      var lvl = parseInt(_\_pet\_config[item]['lvl']);
      var dex = parseInt(_\_pet\_config[item]['dex']);
      var cate = parseInt(_\_pet\_config[item]['cate']);
      var key = cate * 10000 + (lvl - 1) * 10 + dex;
      petDic[key] = _\_pet\_config[item];
    }
  }
  //public 接口
  /**
   * 根据宝宝id,读取_\_pet\_config中对应宝宝的信息
   * @method getPetById
   * @param {String/int} petId 宝宝id
   * @return {Object} pet 宝宝的所有静态信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飞天小使等级1熟练2", intro:"", skill:"护身符", skill1\_prob:"30", skill2_prob:"0"}
   */
  if(typeof PetConfigParser.getPetById !== 'function') {
    PetConfigParser.getPetById = function(petId) {
      var pet = ("undefined" == typeof(_\_pet\_config)) ? null : \_\_pet\_config["pet\_" + petId];
      return pet;
    }
  }
})();

这种方式利用了JavaScript匿名函数来创建私有作用域,这些私有作用域只能在内部访问。总结上述过程分为以下几个步骤:

  1. 定义一个全局的变量(var PetConfigParser),注意变量首字母大写与普通变量区别;
  2. 然后创建一个匿名函数并运行( (function () {/xxxx/ })(); ),在匿名函数内部创建局部变量和函数,它们只能在当前作用域中被访问到;
  1. 全局变量(var PetConfigParser)可以在任何地方访问到,在匿名函数内部操作PetConfigParser添加静态函数。

使用实例:

<!-- lang: js -->
$(function () {
  DialogManager.init();
  $('#showDialog').click(function () {
    DialogManager.show("#msgBoxTest", "#closeId");
    return false;
  });
  $('#cofirmBtn').click(function () {
    DialogManager.hide();
    return false;
  });
})
<h5 id="1-1-2">实例类</h5>

JavaScript实现经典的类,总结有三种方法:

  • 构造函数方式;
  • 原型方式;
  • 构造函数+原型的混合方式
<h6 id="1-1-2-1">构造函数方式</h6>

构造函数用来初始化实例对象的属性和值。任何JavaScript函数都可以用作构造函数,构造函数必须使用new运算符作为前缀来创建新的实例。

<!-- lang: js -->
var Person = function (name) {
  this.name = name;
  this.sayName = function () {
    alert(this.name);
  };
}
//实例化
var tyler = new Person("tylerzhu");
var saylor = new Person("saylorzhu");
tyler.sayName();
saylor.sayName();
//检查实例
alert(tyler instanceof Person);

构造函数方式跟传统的面向对象语言是不是很相识!只不过是class关键字用function替换了。

注意:不要省略new否则Person(“tylerzhu”) //==>undefined。**当使用new关键字来调用构造函数时,执行上下文(context)从全局对象(window)变成一个空的上下文,这个上下文代表了新生成的实例。**因此,this关键子指向当前创建的实例。所以省略new时,没有进行上下文切换会在全局对象中查找name,没有找到而创建一个全局变量name返回undefined。

原型方式

构造函数方式简单,但是存在一个浪费内存的问题。如上面的例子中实例化了两个对象tyler、saylor,表面上好像没什么问题,但是实际上对于每一个实例对象,sayName()方法都是一模一样的内容,每一次生成一个实例,都必须为重复的内容申请内容。

alert(tyler.sayName == saylor.sayName) 输出false!!!

Javascript中每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例共享。

<!-- lang: js -->
var Person = function (name) {
  Person.prototype.name = name;
  Person.prototype.sayName = function(){
    alert(this.name);
  }
};
//实例化
var tyler = new Person("tylerzhu");
var saylor = new Person("saylorzhu");
tyler.sayName();
saylor.sayName();

//检查实例
alert(tyler instanceof Person);

这时tyler、saylor实例的sayName方法,都是同一个内存地址(指向prototype对象),因此原型方法更节省内存

但是看tyler.sayName();saylor.sayName();两者输出,会看出问题 —— 它们都输出“saylorzhu”。因为原型所有属性都共享,只要一个实例改变其他的都会跟着改变,所以实例化对象saylor覆盖了tyler。

构造函数+原型的混合方式

构造函数方式可以为同一个类的每一个对象分配不同的内存,这很适合写类的时候设置属性;但是设置方法的时候我们就需要让同一个类的不同对象共享同一个内存了,写方法用原型的方式最好。所以写类的时候需要把构造方法和原型两种方式混合着用(很多类库提供的创建类的方法或框架的写类方式本质上都是:构造函数+原型)。

<!-- lang: js -->
var Person = function (name) {
    this.name = name;
    Person.prototype.sayName = function(){
        alert(this.name);
    }
}
//实例化
var tyler = new Person("tylerzhu");
var saylor = new Person("saylorzhu");
tyler.sayName();
saylor.sayName();
//检查实例
alert(tyler instanceof Person);

这样即可通过构造函数构造不同name的人,对象实例也都共享sayName方法,不会造成内存浪费。

JavaScript压缩/合并

JavaScript代码压缩混淆的意义:简单的说就是为了减小js文件大小,去掉多余的注释和换行缩进等,使得下载起来更快,提高用户体验。

JavaScript压缩工具有很多,我推荐使用jQuery现在使用的工具UglifyJS(jQuery以前也使用过多种压缩工具,如Packer),因为它压缩性能很好。

“jQuery 1.5 发布的时候 john resig 大神说所用的代码优化程序从Google Closure切换到UglifyJS,新工具的压缩效果非常令人满意”

下面是官方性能对比:

We’re still a lot better than YUI in terms of compression, though slightly slower. We’re still a lot faster than Closure, and compression after gzip is comparable.

Uglifyjs安装

UglifyJS是基于NodeJS的Javascript语法解析/压缩/格式化工具,所以我们要安装NodeJS。

Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.
JavaScript最早是运行在浏览器中,然而浏览器只是提供了一个上下文,它定义了使用JavaScript可以做什么,但并没有“说”太多关于JavaScript语言本身可以做什么。事实上,JavaScript是一门“完整”的语言:它可以使用在不同的上下文中,其能力与其他同类语言相比有过之而无不及。Node.js事实上就是另外一种上下文,它允许在后端(脱离浏览器环境)运行JavaScript代码。
要实现在后台运行JavaScript代码,代码需要先被解释然后正确的执行。Node.js的原理正是如此,它使用了Google的V8虚拟机(Google的Chrome浏览器使用的JavaScript执行环境),来解释和执行JavaScript代码。
除此之外,伴随着Node.js的还有许多有用的模块,它们可以简化很多重复的劳作,比如向终端输出字符串。因此,Node.js事实上既是一个运行时环境,同时又是一个库。

l Windows下面直接下载exe文件执行即可。(http://nodejs.org/)

l 设置代理(公司网络不设置代理无法下载,外网环境不需要)

“npm,全称是"node packagemanager",它是node包管理器,第三方的package全是通过npm去安装的。”

为npm设置代理

npm config set proxy=http://proxy.tencent.com:8080

为npm默认选择http方式,不选用https

npm config set registry http://registry.npmjs.org

l npm安装uglify-js

npm install uglify-js

l 验证安装是否成功

C:\Users\tyler>npm -v
1.1.36

C:\Users\tyler>node -v
v0.8.2
<h5 id="1-2-2">UglifyJS使用</h5>

(注:建议看下官方文档)

uglifyjs [options] [input files]

uglifyjs会读取文件中的javascript代码进行处理。如果你不指定输出的文件名,那么他会把处理后的内容输出到命令行中。

支持的选项:

  • -b 或 --beautify - 输出格式化代码,当传入该参数,下面的附加选项用于更美观的控制格式化:
    • -i N 或 --indent N - 缩进级别(空格数量)
    • -q 或 --quote-keys - 是否用引号引起字符串对象的键(默认只会引起不能被正确标志的键名)
  • --ascii - 默认UglifyJS不处理字符编码而直接输出 Unicode字符,通过传入该参数将非ASCII编码的字符转化为\cXXXX的序列(输出总按照UTF8编码,但传入该选项能得到ASCII编码的输出)。
  • -nm 或 --no-mangle - 不改变变量名称
  • -ns 或 --no-squeeze - 不调用ast_squeeze()函数(该函数会做多种优化使得结果更小,可读性略有降低)
  • -mt 或 --mangle-toplevel - 在顶级作用域打乱变量名称(默认不开启)
  • --no-seqs - 当调用ast_squeeze()将会合并多个语句块为一个语句块,如 "a=10; b=20; foo()" 将被转换为 "a=10,b=20,foo()"
  • --no-dead-code - 默认UglifyJS将会删除不被用到的代码,传入该参数禁用此功能。
  • -nc 或 --no-copyright - 默认 uglifyjs 会在输出后的代码中添加版权信息等注释代码,传入该参数禁用此功能。
  • -o 文件名 或 --output 文件名 - 指定输出文件名,如果不指定,则打印到标准输出(STDOUT)
  • --overwrite - 如果传入的JS代码来自文件而不是标准输入,传入该参数,输出会覆盖该文件。
  • --ast - 传入该参数会得到抽象的语法树而不是Javascript,对调试或了解内部代码很有用。
  • -v 或 --verbose - 在标准错误输出一些信息(目前的版本仅输出操作用时)
  • --extra - 开启附加优化,这些优化并未得到全面的测试。
  • --unsafe - 开启其他附加优化,这些优化已知在特定情况下并不安全,目前仅支持:
    • foo.toString() ==> foo + ""
  • --max-line-len (默认32K字节) - 在32K字节出增加换行符,传入0禁用此功能。
  • --reserved-names - 一些类库会依赖一些变量,该参数指定的名称不会被混淆掉,多个用逗号隔开

下面是我们使用uglifyjs压缩,PetConfigParser.js的例子:

uglifyjs -nc -mt PetConfigParser.js > PetConfigParser.min.js

<h5 id="1-2-3">JavaScript文件合并</h5>

规则1——减少HTTP请求(Minimize HTTP Requests) Yahoo前端优化性能规则[5]

只有10%~20%的最终用户响应时间花在接收请求的HTML文档上,剩下的80%~90%时间都花在HTML文档所引用的所有组件(图片、脚本、样式表、Flash等)进行的HTTP请求上。因此,改善响应时间最简单的办法就是减少组件数量并由此减少HTTP请求数。

对公共库合并压缩在减少size的同时,减少http请求优化网络耗时提升性能。

文档生成

YUIDoc 是一个基于Node.js的应用程序,用来根据JavaScript的注释生成API文档,类似JavaDoc、ASDoc,这也是当前YUI用来生成文档的工具。

YUIDoc安装与使用

l YUIDoc安装

与UglifyJS一样,YUIDoc也是基于Nodejs的一个应用程序,使用npm安装即可。

npm install yuidocjs -g

校验安装是否成功

C:\Users\tyler>yuidoc -v
0.3.15

l 生成文档(一次性生成)

yuidoc .

一次性生成该目录及其子目录下所有JS的文档,默认在不配置的情况下会生成在当前目录的out目录中。

-o, --out <directory path> Path to put the generated files (defaults to ./out)

l 生成文档(实时生成)

YUIDoc还提供了一种实时文档生成的方式,有利于团队协作开发.比如在SVN上部署YUIDoc实时文档,递交到SVN的代码都会及时生成文档提供团队使用查阅

yuidoc --server

默认开放监听当前目录文件变动,开放3000端口,可以通过

http://127.0.0.1:3000/

来访问文档,如果3000端口被占用,也可以指定特定端口号

yuidoc --server 5000

来通过开放5000端口提供文档访问

<h5 id="1-3-1">YUIDoc标签</h5>

要使用YUIDoc,那么所有注释都得按照YUIDoc的标准来,否则不能正确解析出文档。YUIDoc使用的标签和其它语言类同,比较容易理解。下面不详细说明每个标签,只列举几个例子,具体可参加官方文档。例如:

对PetConfigParser类进行注释:

/**
 * 宝宝配置文件解析,及提供查询宝宝配置相关的操作方法<br/>
 * 1. getPetById 根据宝宝id获取对应宝宝的信息<br/>
 * 2. getPetName 根据宝宝的id,读取宝宝信息,然后拼接出宝宝的名字,如3+10天蝎宝宝<br/>
 * 等等<br/>
 * @author tylerzhu
 * @class PetConfigParser
 * @static
 */

对类中的变量进行注释:

/**
 * 宝宝所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】为key
 * @attribute petDic
 * @type {Object}
 * @private
 */

对类中函数进行注释:

/**
 * 根据宝宝id,读取__pet_config中对应宝宝的信息
 * @method getPetById
 * @param {String/int} petId 宝宝id
 * @return {Object} pet 宝宝的所有静态信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飞天小使等级1熟练2", intro:"", skill:"护身符", skill1_prob:"30", skill2_prob:"0"}
 */

参考链接、进一步阅读

[1] NodeJS

[2] UglifyJS

[3] 用UglifyJS解析/压缩/格式化你的Javascript

[4] Yahoo前端优化性能规则

[5] YUIDoc官方

[6] 用YUIDoc文档化JavaScript代码

作者:吴秦

出处:http://www.cnblogs.com/skynet/

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名吴秦(包含链接).

补充链接

UglifyJS2

UglifyJS2: compression beats version 1

本文转载自:http://www.cnblogs.com/skynet/

豆仔
粉丝 54
博文 54
码字总数 24065
作品 0
南京
程序员
私信 提问
将webpack打包优化到极致_20180619

背景: 项目上线前是专门针对 webpack 打包做了优化的,但是在之后做网络优化的时候通过这个插件发现一些公共的js文件重复打包进了业务代码的js中。这些代码体积虽然很小,但是为了将优化做到...

zhiqiang21
06/20
0
0
前端开发规范手册(命名、HTML、CSS、JS、ES6、React)

简介 前端开发规范手册(命名、HTML、CSS、JS、ES6、React)完整链接,欢迎给出您的宝贵意见; GitHub源码地址,要是觉得文档还能凑合着看,欢迎 ~ 不以规矩,不能成方圆。 对于团队而言,统...

baldwin
02/19
0
0
WebAssembly和Emscripten工作整理

写在前面 之前做过一段时间的WebAssembly的研究,写过几个WebAssembly Demo,并且阅读并翻译了很多篇Emscripten官方文档,今天对这些东西做一个总结。 如果有需要对这块知识了解的同学,就可...

云荒杯倾
2018/01/10
0
0
jQuery、ajax、JSON

最近在做项目的过程中,对于jQuery、ajax、JSON这三者的关系总是理不清楚,于是,在简单是使用,有了丁点的经验,于是对这三者的关系做了下研究: 1、jQuery : 以下来自百度百科 jQuery是一个...

伊人心
2018/12/28
0
0
WebAssembly 不完全指北

背景:从 JavaScript 说起 JavaScript 占据着统治地位,不管是公开还是私有的项目、任何组织、世界任何地区,JavaScript 都是第一。 -GitHub 2018 年度报告 随着JavaScript的快速发展,目前它...

腾讯IVWEB团队
07/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
昨天
6
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
昨天
6
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
昨天
6
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
昨天
7
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部