zoukankan      html  css  js  c++  java
  • Javascript与当前项目的思考

     

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

    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.调试的经验总结

     

  • 相关阅读:
    Maven入门:使用Nexus搭建Maven私服及上传下载jar包
    idea 破解转(肉测好用,测试2018.4.16)
    使用spring-boot-admin对spring-boot服务进行监控(转自牛逼的人物)
    eureka集群高可用配置,亲测成功配置(转)
    Apollo-open-capacity-platform 微服务能力开发平台 (转)
    导入数据到数据库表,报错[Err] [Row1] [Imp] 1153
    .mmap文件如何打开
    web端自动化——webdriver驱动
    web端自动化——Remote应用
    svn服务器端—管理员分配权限
  • 原文地址:https://www.cnblogs.com/yakun/p/3388744.html
Copyright © 2011-2022 走看看