主体 分为以下三部分,能力、经验有限,欢迎拍砖。

1.低效的代码

2.面向对象的重构重复利用代码

3.调试的经验总结

第一部分 日常中低效的代码

  • 加载和运行
<html>

<head>
<title>Script Example</title> </head> <body> <p> <script type="text/javascript"> document.write("The date is " + (new Date()).toDateString()); </script> </p> </body> </html>

当浏览器遇到一个<script>标签时,正如上面 HTML 页面中那样,无法预知 JavaScript 是否在<p>标签中 添加内容。因此,浏览器停下来,运行此 JavaScript 代码,然后再继续解析、翻译页面。同样的事情发生 在使用 src 属性加载 JavaScript 的过程中。浏览器必须首先下载外部文件的代码,这要占用一些时间,然后 解析并运行此代码。此过程中,页面解析和用户交互是被完全阻塞的。

一个<script>标签可以放在 HTML 文档的<head>或<body>标签中,可以在其中多次 出现。传统上,<script>标签用于加载外部 JavaScript 文件

第一个 JavaScript 文件开始下载,并阻塞了其他文件的下载过程。进 一步,在 file1.js 下载完之后和 file2.js 开始下载之前有一个延时,这是 file1.js 完全运行所需的时间。每个文件必须等待前一个文件下载完成并运行完之后,才能开始自己的下载过程。当这些文件下载时,用户面 对一个空白的屏幕。这就是几年前(现在当网速较慢时,仍可重现这个问题)大多数浏览器的行为模式。

因为脚本阻塞其他页面资源的下载过程,所以推荐的办法是:将所有<script>标签放在尽可能接近<body> 标签底部的位置,尽量减少对整个页面下载的影响。例如:

<html>

<head>
<title>Script Example</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head>
<body> <p>Hello world!</p>
<-- Example of recommended script positioning --> <script type="text/javascript" src="file1.js"></script> <script type="text/javascript" src="file2.js"></script> <script type="text/javascript" src="file3.js"></script> </body> </html>
  • 数据访问

数据存储在哪里, 关系到代码运行期间数据被检索到的速度。在 JavaScript 中,此问题相对简单,因为数据存储只有少量方 式可供选择。正如其他语言那样,数据存储位置关系到访问速度。在 JavaScript 中有四种基本的数据访问 位置:

直接量(Literal values)

直接量仅仅代表自己,而不存储于特定位置。

JavaScript 的直接量包括:

字符串(string),数字(Number),布尔值(Boolean),对象(Object), 数组(Array),函数(Function),正则表达式(RegExp),具有特殊意义的空值(null),以及未定义(undefined)。

变量(Variables)

我们使用 var 关键字创建用于存储数据值。

数组项(Array items)

具有数字索引,存储一个 JavaScript 数组对象。

对象成员(Object members)

具有字符串索引,存储一个 JavaScript 对象。

每一种数据存储位置都具有特定的读写操作负担。大多数情况下,对一个直接量和一个局部变量数据访问的性能差异是微不足道的。

管理作用域(Managing Scope)

作用域概念是理解 JavaScript 的关键,不仅从性能的角度,而且从功能的角度。作用域对 JavaScript 有 许多影响,从确定哪些变量可以被函数访问,到确定 this 的值,首先要理解作用域的工作原理。

作用域链和标识符解析(Scope Chains and Identifier Resolution)

每一个 JavaScript 函数都被表示为对象。进一步说,它是一个函数实例。函数对象正如其他对象那样, 拥有你可以编程访问的属性,和一系列不能被程序访问,仅供 JavaScript 引擎使用的内部属性。

内部[Scope]属性包含一个函数被创建的作用域中对象的集合。此集合被称为函数的作用域链,它决定哪些数据可由函数访问。此函数作用域链中的每个对象被称为一个可变对象,每个可变对象都以“键值对”

的形式存在。当一个函数创建后,它的作用域链被填充以对象,这些对象代表创建此函数的环境中可访问的数据。例如下面这个全局函数:

function add(num1, num2)

{ 

  var sum = num1 + num2; 

  return sum;

}

当 add()函数创建后,它的作用域链中填入一个单独的可变对象,此全局对象代表了所有全局范围定义的变量。此全局对象包含诸如窗口(window)、浏览器(browser)和文档(DOM)之类的访问接口。(注意: 此图中只画出全局变量中很少的一部分,其他部分还很多)。

add()函数的作用域链

add 函数的作用域链将会在运行时用到。假设运行下面的代码: var total = add(5, 10);

运行此 add 函数时建立一个内部对象,称作“运行期上下文”。一个运行期上下文定义了一个函数运行时的环境。对函数的每次运行而言,每个运行期上下文都是独一的,所以多次调用同一个函数就会导致多次创建运行期上下文。当函数执行完毕,运行期上下文就被销毁。

一个运行期上下文有它自己的作用域链,用于标识符解析。当运行期上下文被创建时,它的作用域链被 初始化,连同运行函数的[[Scope]]属性中所包含的对象。这些值按照它们出现在函数中的顺序,被复制到 运行期上下文的作用域链中。这项工作一旦完成,一个被称作“激活对象”的新对象就为运行期上下文创建 好了。此激活对象作为函数执行期的一个可变对象,包含访问所有局部变量,命名参数,参数集合,和 this 的接口。然后,此对象被推入作用域链的前端。当作用域链被销毁时,激活对象也一同销毁。下图显示 了前面实例代码所对应的运行期上下文和它的作用域链。

在函数运行过程中,每遇到一个变量,标识符识别过程要决定从哪里获得或者存储数据。此过程搜索运 行期上下文的作用域链,查找同名的标识符。搜索工作从运行函数的激活目标之作用域链的前端开始。如 果找到了,那么就使用这个具有指定标识符的变量;如果没找到,搜索工作将进入作用域链的下一个对象。 此过程持续运行,直到标识符被找到,或者没有更多对象可用于搜索,这种情况下标识符将被认为是未定 义的。函数运行时每个标识符都要经过这样的搜索过程,例如前面的例子中,函数访问 sum,num1,num2 时都会产生这样的搜索过程。正是这种搜索过程影响了性能。

在运行期上下文的作用域链中, 一个标识符所处的位置越深,它的读写速度就越慢。所以,函数中局部变量的访问速度总是最快的,而全 局变量通常是最慢的(优化的 JavaScript 引擎在某些情况下可以改变这种状况)。请记住,全局变量总是 处于运行期上下文作用域链的最后一个位置,所以总是最远才能触及的。

最好尽可能使用局部变量。一个好的经验法则 是:用局部变量存储本地范围之外的变量值,如果它们在函数中的使用多于一次。考虑下面的例子:

function initUI(){
  var
    bd = document.body,
    links = document.getElementsByTagName_r("a"),
     i = 0,
    len = links.length;
  
  
   while(i < len){     update(links[i++]); }     document.getElementById("go-btn").onclick = function(){ start();    };    bd.className = "active";
}

此函数包含三个对 document 的引用,document 是一个全局对象。搜索此变量,必须遍历整个作用域链, 直到最后在全局变量对象中找到它。你可以通过这种方法减轻重复的全局变量访问对性能的影响:首先将 全局变量的引用存储在一个局部变量中,然后使用这个局部变量代替全局变量。例如,上面的代码可以重 写如下:

function initUI(){

    var doc = document,
bd = doc.body,
links = doc.getElementsByTagName_r("a"),
i = 0,
   len = links.length;
  
   while(i < len){      update(links[i++]);
   }    doc.getElementById("go-btn").onclick = function(){
    start();
   };   bd.className = "active";
}

DOM 编程(DOM Scripting)

对 DOM 操作代价昂贵,在富网页应用中通常是一个性能瓶颈。

ECMAScript 需要访 问 DOM 时,你需要过桥,交一次“过桥费”。你操作 DOM 次数越多,费用就越高。一般的建议是尽量减 少过桥次数,努力停留在 ECMAScript 岛上。本章将对此问题给出详细解答,告诉你应该关注什么地方, 以提高用户交互速度。

为了给你一个关于 DOM 操作问题的量化印象,考虑下面的例子:

function innerHTMLLoop() {
  for (var count = 0; count < 15000; count++) {     document.getElementById('here').innerHTML += 'a';   }
}

此函数在循环中更新页面内容。这段代码的问题是,在每次循环单元中都对 DOM 元素访问两次:一次 读取 innerHTML 属性能容,另一次写入它。

一个更有效率的版本将使用局部变量存储更新后的内容,在循环结束时一次性写入:

function innerHTMLLoop2() {
  var content = '';
  for (var count = 0; count < 15000; count++) {     content += 'a';
  }   document.getElementById('here').innerHTML += content;
}

你访问 DOM 越多,代码的执行速度就越慢。

事件托管(Event Delegation)

当页面中存在大量元素,而且每个元素有一个或多个事件句柄与之挂接(例如 onclick)时,可能会影 响性能。连接每个句柄都是有代价的,无论其形式是加重了页面负担(更多的页面标记和 JavaScript 代码) 还是表现在运行期的运行时间上。你需要访问和修改更多的 DOM 节点,程序就会更慢,特别是因为事件 挂接过程都发生在 onload(或 DOMContentReady)事件中,对任何一个富交互网页来说那都是一个繁忙的 时间段。挂接事件占用了处理时间,另外,浏览器需要保存每个句柄的记录,占用更多内存。当这些工作 结束时,这些事件句柄中的相当一部分根本不需要(因为并不是 100%的按钮或者链接都会被用户点到), 所以很多工作都是不必要的。

一个简单而优雅的处理 DOM 事件的技术是事件托管。它基于这样一个事实:事件逐层冒泡总能被父元 素捕获。采用事件托管技术之后,你只需要在一个包装元素上挂接一个句柄,用于处理子元素发生的所有 事件。

According to the DOM standard, each event has three phases: 根据 DOM 标准,每个事件有三个阶段:

  1. 捕获
  2. 到达目标
  3. 冒泡

当用户点击了“menu #1”链接,点击事件首先被<a>元素收到。然后它沿着 DOM 树冒泡,被<li>元素收 到,然后是<ul>,接着是<div>,等等,一直到达文档的顶层,甚至 window。这使得你可以只在父元素上 挂接一个事件句柄,来接收所有子元素产生的事件通知。

假设你要为图中所显示的文档提供一个逐步增强的 Ajax 体验。如果用户关闭了 JavaScript,菜单中的链 接仍然可以正常地重载页面。但是如果 JavaScript 打开而且用户代理有足够能力,你希望截获所有点击, 阻止默认行为(转入链接),发送一个 Ajax 请求获取内容,然后不刷新页面就能够更新部分页面。使用 事件托管实现此功能,你可以在 UL"menu"单元挂接一个点击监听器,它封装所有链接并监听所有 click 事 件,看看他们是否发自一个链接。

document.getElementById('menu').onclick = function(e) {

  e = e || window.event;
  var target = e.target || e.srcElement;
  var pageid, hrefparts;   if (target.nodeName !== 'A') {     return;
  }   hrefparts = target.href.split('/');
  pageid = hrefparts[hrefparts.length - 1]; pageid = pageid.replace('.html', '');   ajaxRequest('xhr.php?page=' + id, updatePageContents);   if (typeof e.preventDefault === 'function') {
    e.preventDefault();
    e.stopPropagation();   } else {
    e.returnValue = false;
    e.cancelBubble = true;   }
};

正如你所看到的那样,事件托管技术并不复杂;你只需要监听事件,看看他们是不是从你感兴趣的元素 中发出的。这里有一些冗余的跨浏览器代码,如果你将它们移入一个可重用的库中,代码就变得相当干净。

  • 算法和流 程控制

要熟悉javascript的所有循环方法,不只是单纯for

在大多数编程语言中,代码执行时间多数在循环中度过。在一系列编程模式中,循环是最常用的模式之 一,因此也是提高性能必须关注的地区之一。理解 JavaScript 中循环对性能的影响至关重要,因为死循环 或者长时间运行的循环会严重影响用户体验。

for 循环,与类 C 语言使用同样的语法:

for (var i=0; i < 10; i++){

//loop body

}

for 循环大概是最常用的 JavaScript 循环结构。它由四部分组成:初始化体,前测条件,后执行体,循环 体。当遇到一个 for 循环时,初始化体首先执行,然后进入前测条件。如果前测条件的计算结果为 true, 则执行循环体。然后运行后执行体。for 循环封装上的直接性是开发者喜欢的原因。

第二种循环是 while 循环。while 循环是一个简单的预测试循环,由一个预测试条件和一个循环体构成:

var i = 0; 
while(i < 10){
//loop body i++;
}

在循环体执行之前,首先对前测条件进行计算。如果计算结果为 true,那么就执行循环体;否则循环体 将被跳过。任何 for 循环都可以写成 while 循环,反之亦然。

第三种循环类型是 do-while 循环。do-while 循环是 JavaScript 中唯一一种后测试的循环,它包括两部分: 循环体和后测试条件体:

var i = 0; 

do {

//loop body

} while (i++ < 10);

在一个 do-while 循环中,循环体至少运行一次,后测试条件决定循环体是否应再次执行。

第四种也是最后一种循环称为 for-in 循环。此循环有一个非常特殊的用途:它可以枚举任何对象的命名 属性。其基本格式如下:

for (var prop in object){

//loop body

}

每次循环执行,属性变量被填充以对象属性的名字(一个字符串),直到所有的对象属性遍历完成才返 回。返回的属性包括对象的实例属性和它从原型链继承而来的属性。

一个典型的数组处理循环,可使用三种循环的任何一种。最常用的代码写法如下:

//original loops

for (var i=0; i < items.length; i++){
   process(items[i]);
}

var j=0;
while (j < items.length){   process(items[j++]]); } var k=0;
do {

  process(items[k++]);

} while (k < items.length);

在每个循环中,每次运行循环体都要发生如下几个操作:

1. 在控制条件中读一次属性(items.length)
2. 在控制条件中执行一次比较(i < items.length)
3. 比较操作,察看条件控制体的运算结果是不是 true(i < items.length == true)

4. 一次自加操作(i++)
5.  一次数组查找(items[i])

6. 一次函数调用(process(items[i]))

在这些简单的循环中,即使没有太多的代码,每次迭代也要进行许多操作。代码运行速度很大程度上由 process()对每个项目的操作所决定,即使如此,减少每次迭代中操作的总数可以大幅度提高循环整体性能。

优化循环工作量的第一步是减少对象成员和数组项查找的次数。正如第 2 章讨论的,在大多数浏览器上, 这些操作比访问局部变量或直接量需要更长时间。前面的例子中每次循环都查找 items.length。这是一种浪 费,因为该值在循环体执行过程中不会改变,因此产生了不必要的性能损失。你可以简单地将此值存入一 个局部变量中,在控制条件中使用这个局部变量,从而提高了循环性能:

//minimizing property lookups

for (var i=0, len=items.length; i < len; i++){
   process(items[i]);
}
var j=0,
count = items.length; while (j < count){   process(items[j++]]);
} var k=0,
num = items.length; do {   process(items[k++]); } while (k < num);

以下两个部分还没有整理好,争取在周末发出来。

2.面向对象的重构重复利用代码

3.调试的经验总结

Javascript与当前项目的思考的更多相关文章

  1. “canvas画布仿window系统自带画图软件"项目的思考

    "canvas画布仿window系统自带画图软件"项目的思考 首先贴上DEMO图,并没有美化效果.对UI有要求的,请自带补脑技术. 思考一 在做项目的过程中,我发现"工具 ...

  2. 汇总一些知名的 JavaScript 开发开源项目

    汇总一些知名的 JavaScript 开发开源项目   转自:CTOLib , www.ctolib.com/topics-107352.html ggraph - 图形可视化的凌乱数据 这是一个建立 ...

  3. Week1 Team Homework #1 from Z.XML-项目选择思路--基于对曾经大作业项目的思考

    这两天试玩了一下去年学长的满分工程<shield star>游戏,再结合了一下他们团队的博客记录,有一种非常牛逼的感觉.具体对于这款游戏的一些思考和看法,毛大神已经说的很好了.因此,这里主 ...

  4. 关于老教授之家项目的思考 && 中国互联网+大赛培训

    最近在做中国互联网+竞赛相关的项目,有一点思考在这里记录下来,算是一份经历,日后可以再回顾,这也是我真正参加的一个大型比赛,作为技术人员可能更多的是从事技术,但是在其他方面能贡献自己的一份力量也是不错 ...

  5. 关于Java 项目的思考总结

    Java 项目思考总结 前言 今天是2017年3月25日,笔者已经毕业半年,工作经验一年. 正好有心思写这个总结. 持续开发 对于Java项目,我所接触的一般就是JavaWeb项目和 Java Jar ...

  6. 大量javascript代码的项目如何改善可维护性

    项目中有点javascript文件,javascript代码行数达到7000多行,维护很费力,主要体现在以下几个方面: 1,方法没有注释,没有注释方法的作用,从上到下罗列,很难知道这个方法应该啥时候调 ...

  7. EMLS项目推进思考

    解决难度从小到大来看: 一.技术与运营层面1. 到企业级层面需要的技术与运营的支撑________前端推送__________________|________后台支撑系统_________|____ ...

  8. 对于javascript的词法作用域的思考

    曾经看到过这样一段有意思的程序: var a=3; function scopeTest(){ console.log(a); var a=2; console.log(a); } scopeTest ...

  9. JavaScript组合继承的一点思考

    今天看<JavaScript高级程序设计>一书中关于组合继承模式时.书上有这么一个Demo程序: <html> <head> </head> <b ...

随机推荐

  1. SQLServer基本查询

    条件查询 --1.比较运算符 --2.确定集合谓词 --3.确定范围谓词 , ) --4.字符匹配谓词 select * from dbo.DepartMent where dName like 'C ...

  2. 菜鸟学Linux命令:tar命令 压缩与解压缩

    tar命令可以为linux的文件和目录创建档案.利用tar,可以为某一特定文件创建档案(备份文件),也可以在档案中改变文件,或者向档案中加入新的文件. tar最初被用来在磁带上创建档案,现在,用户可以 ...

  3. Android 中this、getContext()、getApplicationContext()、getApplication()、getBaseContext() 之间的区别

    : 知之为知之,不知为不知是知也! 使用this, 说明当前类是context的子类,一般是activity application等; this:代表当前,在Activity当中就是代表当前的Act ...

  4. SQL语法中的JOIN类型

    这个要弄明白哟..CROSS JOIN, NATURAL, INNER JOIN ,LEFT OUTER JOIN(LEFT JOIN) 等等....带LEFT,RIGHT的必为OUTER,所以OUT ...

  5. poj 1195:Mobile phones(二维线段树,矩阵求和)

    Mobile phones Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 14391   Accepted: 6685 De ...

  6. PHP+Nginx环境搭配

    一.Nginx安装 nginx可以使用各平台的默认包来安装,本文是介绍使用源码编译安装,包括具体的编译参数信息. 正式开始前,编译环境gcc g++ 开发库之类的需要提前装好,这里默认你已经装好. u ...

  7. TortoiseSVN常用操作说明

    TortoiseSVN是windows下其中一个非常优秀的SVN客户端工具.通过使用它,我们可以可视化的管理我们的版本库.不过由于它只是一个客户端,所以它不能对版本库进行权限管理. TortoiseS ...

  8. 如果我可以重新学习iOS开发(转)

    在过去的几个月里,我一直在学习用Objective-C编写iOS app,最后我开始理清思绪.这比我想象中要难很多,也花了太长时间. 我经常遇到困难.感到沮丧,修复bug比实际写代码要花太多时间.但是 ...

  9. ios二维码扫描

    1.添加AVFoundation.framework框架 2,控制器中实现 //第一步添加AVFoundation.framework框架 #import "ViewController.h ...

  10. Linux常用工具之XFTP、Xshell配置

    Xftp是一个基于 MS windows 平台的功能强大的SFTP.FTP文件传输软件.使用了 Xftp 以后,MS windows 用户能安全地在UNIX/Linux 和 Windows PC 之间 ...