如何编写高质量JS代码

想写出高效的javascript类库却无从下手;

尝试阅读别人的类库,却理解得似懂给懂;

打算好好钻研js高级函数,但权威书上的内容太零散,

即使记住“用法”,但到要“用”的时候却没有想“法”。

也许你和我一样,好像有一顾无形的力量约束着我们的计划,让我们一再认为知识面的局限性,致使我们原地踏步,难以向前跨越。

这段时间,各种作业、课程设计、实验报告,压力倍增。难得挤出一点点时间,绝不睡懒觉,整理总结往日所看的书,只为了可以离写自己的类库近一点。

本文参考自《javascript语言精粹》和《Effective JavaScript》。例子都被调试过,理解过后,我想把一些“深奥”的道理说得浅显一点点。

1.变量作用域

作用域对于程序员来说就像氧气。它无处不在,甚至,你往往不会去想他。但当它被污染时(例如使用全局对象),你会感觉到窒息(例如应用响应变慢)。javascript核心作用域规则很简单,被精心设计,且很强大。有效地使用javascript需要掌握变量作用域的一些基本概念,并了解一些可能导致难以捉摸的、令人讨厌的问题的极端情况。

1.1尽量少用全局变量

javascript很容易在全局命名空间中创建变量。创建全局变量毫不费力,因为它不需要任何形式的声明,而且能被整个程序的所有代码自动地访问。

对于我们这些初学者,遇到某些需求(例如,传输的数据被记录下来、等待某时机某函数调用时使用;或者是某函数被经常使用)时,好不犹豫想到全局函数,甚至大一学到的C语言面向过程思想太根深蒂固,系统整整齐齐地都是满满函数。定义全局变量会污染共享的公共命名空间,并可能导致意外的命名冲突。全局变量也不利于模块化,因为它会导致程序中独立组件间的不必要耦合。严重地说,过多的全局(包括样式表,直接定义div或者a的样式),整合到多人开发过称将会成为灾难性错误。这就是为什么jQuery的所有代码都被包裹在一个立即执行的匿名表达式——自调用匿名函数。当浏览器加载完jQuery文件后,自调用匿名函数立即开始执行,初始化jQuery的各个模块,避免破坏和污染全局变量以至于影响到其他代码。

代码如下:

(function(window,undefined){
    var jQuery = ...
    //...
    window.jQuery = window.$ = jQuery;
})(window);

另外,你或许会认为,“先怎么怎么写,日后再整理”比较方便,但优秀的程序员会不断地留意程序的结构、持续地归类相关的功能以及分离不相关的组件,并这些行为作为编程过称中的一部分。

由于全局命名空间是javascript程序中独立的组件经行交互的唯一途径,因此,利用全局命名控件的情况是不可避免的。组件或程序库不得不定义一些全局变量。以便程序中的其他部分使用。否则最好使用局部变量。

代码如下:

this.foo ;//undefined
foo = " global foo";
this.foo ;//"global foo"
var foo = "global foo";
this.foo = "changed";
foo ;//changed

javascript的全局命名空间也被暴露在程序全局作用域中可以访问的全局对象,该对象作为this关键字的初始值。在web浏览器中,全局对象被绑定在全局window变量。这就意味你创建全局变量有两种方法:在全局作用域内使用var声明他,或者将其加入到全局对象中。使用var声明的好处是能清晰地表达全局变量在程序范围中的影响。

鉴于引用为绑定的全局变量会导致运行时错误,因此,保存作用域清晰和简洁会使代码的使用者更容易理解程序声明了那些全局变量。

由于全局对象提供了全局环境的动态反应机制,所以可以使用它查询一个运行环境,检测在这个平台下哪些特性可用。

eg.ES5引入了一个全局的JSON对象来读写JSON格式的数据。

代码如下:

if(!this.JSON){
   this.JSON = {
         parse : ..,
         stringify : ...   
    } 
}

如果你提供了JSON的实现,你当然可以简单无条件地使用自己的实现。但是由宿主环境提供的内置实现几乎更适合的,因为它们是用C语言写进浏览器的。因为它们按照一定的标准对正确性和一致性进行了严格检查,并且普遍来说比第三方实现提供更好的性能。

当初数据结构课程设计模拟串的基本操作,要求不能使用语言本身提供的方法。javascript对数组的基本操作实现得很好,如果只是出于一般的学习需要,模拟语言本身提供的方法的想法很好,但是如果真正投入开发,无需考虑第一时间选择使用javascript内置方法。

1.2避免使用with

with语句提供任何“便利“,让你的应用变得不可靠和低效率。我们需要对单一对象依次调用一系列方法。使用with语句可以很方便地避免对对象的重复引用:

代码如下:

function status(info){
    var widget = new Widget();
    with(widget){
           setBackground("blue");
           setForeground("white");
           setText("Status : "+info);
           show();
    } 
}

使用with语句从模块对象中”导入“(import)变量也是很有诱惑力的。

代码如下:

function f(x,y){
   with(Math){
         return min(round(x),sqrt(y));//抽象引用
   }
}

事实上,javascript对待所有的变量都是相同的。javascript从最内层的作用域开始向外查找变量。with语言对待一个对象犹如该对象代表一个变量作用域,因此,在with代码块的内部,变量查找从搜索给定的变量名的属性开始。如果在这个对象中没有找到该属性,则继续在外部作用域中搜索。with块中的每个外部变量的引用都隐式地假设在with对象(以及它的任何原型对象)中没有同名的属性。而在程序的其他地方创建或修改with对象或其原型对象不一定会遵循这样的假设。javascript引擎当然不会读取局部代码来获取你使用了那些局部变量。javascript作用域可被表示为高效的内部数据结构,变量查找会非常快速。但是由于with代码块需要搜索对象的原型链来查找with代码里的所有变量,因此,其运行速度远远低于一般的代码块。

替代with语言,简单的做法,是将对象绑定在一个简短的变量名上。

代码如下:

function status(info){
    var w = new Widget();
   
     w.setBackground("blue");
     w.setForeground("white");
     w.setText("Status : "+info);
     w.show();
  
}

其他情况下,最好的方法是将局部变量显式地绑定到相关的属性上。

代码如下:

function f(x,y){
    var    min    = Math.min,
           round  = Math.round,
           sqrt   = Math.sqrt; 
     return min(round(x),sqrt(y));
}

1.3熟练掌握闭包

理解闭包有单个概念:

a)javascript允许你引用在当前函数以外定义的变量。

代码如下:

function makeSandwich(){
   var magicIngredient = "peanut butter";
   function make(filling){
        return magicIngredient + " and " + filling;
   }
   return make("jelly"); 
}
makeSandwich();// "peanut butter and jelly"

b)即使外部函数已经返回,当前函数仍然可以引用在外部函数所定义的变量

代码如下:

function makeSandwich(){
   var magicIngredient = "peanut butter";
   function make(filling){
        return magicIngredient + " and " + filling;
   }
   return make; 
}
var f = sandwichMaker();
f("jelly");                      // "peanut butter and jelly"
f("bananas");               // "peanut butter and bananas"
f("mallows");               // "peanut butter and mallows"

javascriptd的函数值包含了比调用它们时所执行所需要的代码还要多的信息。而且,javascript函数值还在内部存储它们可能会引用的定义在其封闭作用域的变量。那些在其所涵盖的作用域内跟踪变量的函数被称为闭包。

make函数就是一个闭包,其代码引用了两个外部变量:magicIngredient和filling。每当make函数被调用时,其代码都能引用这两个变量,因为闭包存储了这两个变量。

函数可以引用在其作用域内的任何变量,包括参数和外部函数变量。我们可以利用这一点来编写更加通用的sandwichMaker函数。

代码如下:

function makeSandwich(magicIngredient){
   function make(filling){
        return magicIngredient + " and " + filling;
   }
   return make; 
}
var f = sandwichMaker(”ham“);
f("cheese");                      // "ham and cheese"
f("mustard");               // "ham and mustard"

闭包是javascript最优雅、最有表现力的特性之一,也是许多习惯用法的核心。

c)闭包可以更新外部变量的值。事实上,闭包存储的是外部变量的引用,而不是它们的值的副本。因此,对于任何具有访问这些外部变量的闭包,都可以进行更新。

代码如下:

function box(){
    var val = undefined;
    return {
         set : function(newval) {val = newval;},
         get : function (){return val;},
         type : function(){return typeof val;}
    };
}
var b = box();
b.type(); //undefined
b.set(98.6);
b.get();//98.6
b.type();//number

该例子产生一个包含三个闭包的对象。这三个闭包是set,type和get属性,它们都共享访问val变量,set闭包更新val的值。随后调用get和type查看更新的结果。

1.4理解变量声明提升

javascript支持此法作用域(对变量foo的引用会被绑定到声明foo变量最近的作用域中),但不支持块级作用域(变量定义的作用域并不是离其最近的封闭语句或代码块)。

不明白这个特性将会导致一些微妙的bug:

代码如下:

function isWinner(player,others){
    var highest = 0;
    for(var i = 0,n = others.length ;i<n;i++){
          var player = others[i];
          if(player.score > highest){
                   highest = player.score;
          }
    }
    return player.score > highest;
}

1.5 当心命名函数表达式笨拙的作用域

代码如下:

function double(x){ return x*2; }
var f = function(x){ return x*2; }

同一段函数代码也可以作为一个表达式,却具有截然不同的含义。匿名函数和命名函数表达式的官方区别在于后者会绑定到与其函数名相同的变量上,该变量作为该函数的一个局部变量。这可以用来写递归函数表达式。

代码如下:

var f = function find(tree,key){
  //....
  return find(tree.left , key) ||
             find(tree.right,key);   
}

值得注意的是,变量find的作用域只在其自身函数中,不像函数声明,命名函数表达式不能通过其内部的函数名在外部被引用。

代码如下:

find(myTree,"foo");//error : find is not defined;
var constructor = function(){ return null; }
var f= function(){
    return constructor();
};
f();//{}(in ES3 environments)

该程序看起来会产生null,但其实会产生一个新的对象。

因为命名函数变量作用域内继承了Object.prototype.constructor(即Oject的构造函数),就像with语句一样,这个作用域会因Object.prototype的动态改变而受到影响。在系统中避免对象污染函数表达式作用域的办法是避免任何时候在Object.prototype中添加属性,以避免使用任何与标准Object.prototype属性同名的局部变量。

在流行的javascript引擎中另外一个缺点是对命名函数表达式的声明进行提升。

代码如下:

var f = function g(){return 17;}
g(); //17 (in nonconformat environment)

一些javascript环境甚至把f和g这两个函数作为不同的对象,从而导致不必要的内存分配。

1.6 当心局部块函数声明笨拙的作用域

代码如下:

function f() {return "global" ; }
function  test(x){
    function f(){return "local";}
    var result = [];
    if(x){
         result.push(f());
    }     
     result.push(f());
     result result;
}
test(true);   //["local","local"]
test(false);  //["local"]

代码如下:

function f() {return "global" ; }
function  test(x){
    var result = [];
    if(x){
         function f(){return "local";}
         result.push(f());
    }     
     result.push(f());
     result result;
}
test(true);   //["local","local"]
test(false);  //["local"]

javascript没有块级作用域,所以内部函数f的作用域应该是整个test函数。一些javascript环境确实如此,但并不是所有javascript环境都这样,javascript实现在严格模式下将这类函数报告为错误(具有局部块函数声明的处于严格模式下的程序将报告成一个语法错误),有助于检测不可移植代码,为未来的标准版本在给局部块函数声明给更明智和可以的语义。针对这种情况,可以考虑在test函数内声明一局部变量指向全局函数f。

(0)

相关推荐

  • 深入理解javascript学习笔记(一) 编写高质量代码

    一.变量 •全局变量 JavaScript的两个特征,不自觉地创建出全局变量是出乎意料的容易.首先,你可以甚至不需要声明就可以使用变量:第二,JavaScript有隐含的全局概念,意味着你不声明的任何变量都会成为一个全局对象属性(不是真正意义上的全局变量,可以用delete删除) 复制代码 代码如下: function sum(x,y) { // result 未声明,为隐式全局变量 result = x + y; return result; } function foo() { // 使用任

  • 在iframe里的页面编写js,实现在父窗口上创建动画效果展开和收缩的div(不变动iframe父窗口代码)

    复制代码 代码如下: <%@ page contentType="text/html; charset=GBK" language="java"%> <%@ page import="com.jstrd.mm.business.sysmgr.monitor.logic.MMStock2BudgetLogic" %> <% String query = request.getParameter("query&

  • 编写高质量JavaScript代码的基本要点

    才华横溢的Stoyan Stefanov,在他写的由O'Reilly初版的新书<JavaScript Patterns>(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会是件很美妙的事情.具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以

  • 编写跨浏览器的javascript代码必备[js多浏览器兼容写法]

    序号 操作 分类 IE(6.0) FireFox(2.0) Mozilla(1.5) 当前浏览器 备注 1 "." 访问tag的固有属性 OK OK OK OK 2 "." 访问tag的用户定义属性eg: <input type="checkbox" myattr="test"> OK NO NO OK 可以用getAttribute函数 替代 3 obj.getAttribute 访问tag的固有属性 OK OK

  • 最佳JS代码编写的14条技巧

    写任何编程代码,不同的开发者都会有不同的见解.但参考一下总是好的,下面是来自Javascript Toolbox发布的14条最佳JS代码编写技巧. 1. 总是使用 var 在javascript中,变量不是全局范围的就是函数范围的,使用var关键词将是保持变量简洁明了的关键.当声明一个或者是全局或者是函数级(function-level)的变量,需总是前置var关键词,下面的例子将强调不这样做潜在的问题. 不使用 Var 造成的问题 var i=0; // This is good - crea

  • 如何编写高质量JS代码(续)

    继续上一篇文章<如何编写高质量JS代码>今次整理一下javascript函数知识点. 2.使用函数 函数给程序员提供了主要的抽象功能,又提供实现机制.函数可以独立实现其他语言中的多个不同的特性,例如,过程.方法.构造函数,甚至类或模块. 2.1 理解函数调用.方法调用以及构造函数调用之间的不同 针对面向对象编程,函数.方法和类的构造函数是三种不同的概念. 使用模式: 1,函数调用 复制代码 代码如下: function hello(username){     return "hel

  • 编写可维护面向对象的JavaScript代码[翻译]

    Writing maintainable Object-Oriented (OO) JavaScript will save you money and make you popular. Don't believe me? Odds are that either you or someone else will come back and work with your code. Making that as painless an experience as possible will s

  • 深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点

    具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以帮助你写出更好的,更易于理解和维护的代码,这些代码在几个月或是几年之后再回过头看看也是会觉得很自豪的. 书写可维护的代码(Writing Maintainable Code ) 软件bug的修复是昂贵的,并且

  • 编写Js代码要注意的几条规则

    1.不要大量使用document.write() 2.检查客户端支持对象的能力(渐进式)而不是检查其客户端,测试要使用的对象. 3.访问既有HTML中的内容而不是通过Js添加HTML(行为层与结构层分离) 4.不要使用专有DOM对象(例如IE的document.all) 5.将脚本放进一个.js文件而不是在HTML中到处可见. 6.对运行良好而且不用客户端编程的网站进行改进,而不是首先添加脚本然后添加非脚本的备用方案. 7.代码要保持独立,不要使用可能与其他脚本冲突的全局变量.(可用对象字面量)

  • 如何编写高质量JS代码

    想写出高效的javascript类库却无从下手: 尝试阅读别人的类库,却理解得似懂给懂: 打算好好钻研js高级函数,但权威书上的内容太零散, 即使记住"用法",但到要"用"的时候却没有想"法". 也许你和我一样,好像有一顾无形的力量约束着我们的计划,让我们一再认为知识面的局限性,致使我们原地踏步,难以向前跨越. 这段时间,各种作业.课程设计.实验报告,压力倍增.难得挤出一点点时间,绝不睡懒觉,整理总结往日所看的书,只为了可以离写自己的类库近一点.

  • 如何编写高质量 JavaScript 代码

    目录 一.易阅读的代码 1.统一代码格式 2.去除魔术数字 3.单一功能原则 二.高性能的代码 1.优化算法 2.使用内置方法 3.减少作用域链查找 4.避免做重复的代码 三.健壮性的代码 1.使用新语法 2.随时可扩展 3.避免副作用 4.整合逻辑关注点 前段时间有一个叫做"人类高质量男性"的视频火了,相信很多同学都刷到过.所以今天给大家分享下,什么叫做"人类高质量代码",哈哈,开个玩笑. 其实分享的都是一些自己平时总结的小技巧,算是抛砖引玉吧,希望能给大家带来一

  • 12条写出高质量JS代码的方法

    书写出高质量的JS代码不仅让程序员看着舒服,更加能够提高程序的运行速度,以下就是我们的小编整理方法: 一.如何书写可维护性的代码 当出现bug的时候如果你能立马修复它是最好的,此时解决问题的四路在你脑中还是很清晰的.否则,你转移到其他任务或者bug是经过一定的时间才出现的,你忘了那个特定的代码,一段时间后再去查看这些代码就 需要: 1.花时间学习和理解这个问题 2.化时间是了解应该解决的问题代码 还有个问题,特别对于大的项目或是公司,修复bug的这位伙计不是写代码的那个人(且发现bug和修复bu

  • 编写高质量代码改善C#程序——使用泛型集合代替非泛型集合(建议20)

    软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类.不管是数组还是集合类,它们都有各自的优缺点.如何使用好集合是我们在开发过程中必须掌握的技巧.不要小看这些技巧,一旦在开发中使用了错误的集合或针对集合的方法,应用程序将会背离你的预想而运行. 建议20:使用泛型集合代替非泛型集合 在建议1中我们知道,如果要让代码高效运行,应该尽量避免装箱和拆箱,以及尽量减少转型.很遗憾,在微软提供给我们的第一代集合类型中没有做到这一点,下面我们看ArrayList这个类的使用情况: Array

  • 编写高质量代码的30条黄金守则(首选隐式类型转换)

    编写高质量代码的30条黄金守则-Day 01(首选隐式类型转换),本文由比特飞原创发布,转载务必在文章开头附带链接:https://www.byteflying.com/archives/6455 该系列文章由比特飞原创发布,计划用三个月时间写完全30篇文章,为大家提供编写高质量代码的一般准则. 1.概述 隐式类型转换是微软为了 C# 支持匿名类型而加入的,使用 var 通常可以使代码的可读性更强,甚至是帮我们解决一些严重的性能问题.为了清楚的明白 var 的作用机制,我们首先来看看编译器为 v

  • 高质量PHP代码的50个实用技巧必备(下)

    接着上篇<高质量PHP代码的50个实用技巧必备(上)>继续研究. 26. 避免直接写SQL, 抽象之 不厌其烦的写了太多如下的语句: <span style="color:#333333;font-family:''Helvetica, Arial, sans-serif'';">$query = "INSERT INTO users(name , email , address , phone) VALUES('$name' , '$email' ,

  • 高质量PHP代码的50个实用技巧必备(上)

    50个高质量PHP代码的实用技巧,希望大家喜欢. 1.不要使用相对路径 常常会看到: require_once('../../lib/some_class.php'); 该方法有很多缺点: 它首先查找指定的php包含路径, 然后查找当前目录.因此会检查过多路径.如果该脚本被另一目录的脚本包含, 它的基本目录变成了另一脚本所在的目录. 另一问题, 当定时任务运行该脚本, 它的上级目录可能就不是工作目录了.因此最佳选择是使用绝对路径: view sourceprint? define('ROOT'

随机推荐