HTML5中国

 找回密码
 立即注册

QQ登录

只需一步,快速开始

HTML5中国 首页 应用推荐 查看内容

深入理解javascript(上)

2016-9-13 10:46| 发布者: Hyukoh| 查看: 702| 评论: 0|原作者: 秋天的风,夏天的雨|来自: cnblogs

摘要: JavaScript通过函数管理作用域。在函数内部声明的变量只在这个函数内部,函数外面不可用。另一方面,全局变量就是在任何函数外面声明的或是未声明直接简单使用的。 ...

  最小的全局变量


  JavaScript通过函数管理作用域。在函数内部声明的变量只在这个函数内部,函数外面不可用。另一方面,全局变量就是在任何函数外面声明的或是未声明直接简单使用的。

  每个JavaScript环境有一个全局对象,当你在任意的函数外面使用this的时候可以访问到。你创建的每一个全部变量都成了这个全局对象的属 性。在浏览器中,方便起见,该全局对象有个附加属性叫做window,此window(通常)指向该全局对象本身。下面的代码片段显示了如何在浏览器环境 中创建和访问的全局变量:

myGlobal = 'Hello'; // 不推荐写法
console.log(myGlobal); // 'Hello'
console.log(window.myGlobal); // 'Hello'
console.log(window['myGlobal']); // 'Hello'
console.log(this.myGlobal); // 'Hello'


  全局变量的问题:


  全局变量的问题在于,你的JavaScript应用程序和web页面上的所有代码都共享了这些全局变量,他们住在同一个全局命名空间,所以当程序的两个不同部分定义同名但不同作用的全局变量的时候,命名冲突在所难免。

  要想让全局变量少最重要的还是始终使用var来声明变量。

  由于JavaScript的两个特征,不自觉地创建出全局变量是出乎意料的容易。首先,你可以甚至不需要声明就可以使用变量;第二,JavaScript有隐含的全局概念,意味着你不声明的任何变量都会成为一个全局对象属性。参考下面的代码:

function sum(x, y){
    // 不推荐使用:隐试全局变量
    result = x + y;
    return result; 
}

  此段代码中的result没有声明。代码照样运作正常,但在调用函数后你最后的结果就多一个全局命名空间,这可以是一个问题的根源。

  经验法则是始终使用var声明变量,正如改进版的sum()函数所演示的:

function sum(x, y){
    var result = x + y;
    return result;
}

  另一个创建隐式全局变量的反例就是使用任务链进行部分var声明。下面的片段中,a是本地变量但是b确实全局变量,这可能不是你希望发生的

function foo(){
    var a = b = 0;
    // 准备做些神马。。。  
}

  此现象发生的原因在于这个从右到左的赋值,首先,是赋值表达式b = 0,此情况下b是未声明的。这个表达式的返回值是0,然后这个0就分配给了通过var定义的这个局部变量a。换句话说,就好比你输入了:

var a = ( b = 0 )

  如果你已经准备好声明变量,使用链分配是比较好的做法,不会产生任何意料之外的全局变量,如:

function foo() {
   var a, b;
   // ... a = b = 0; // 两个均局部变量
}

  注:然而,另外一个避免全局变量的原因是可移植性。如果你想你的代码在不同的环境下(主机下)运行,使用全局变量如履薄冰,因为你会无意中覆盖你最初环境下不存在的主机对象(所以你原以为名称可以放心大胆地使用,实际上对于有些情况并不适用)。


  忘记var的副作用(Side Effects When Forgetting var):


  隐式全局变量和明确定义的全局变量间有些小的差异,就是通过delete操作符让变量未定义的能力。

  •   通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的。
  •   无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的。

  这表明,在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete操作符删除的,而变量是不能的:

// 定义三个全局变量
var global_var = 1;
global_novar = 2; // 反面教材
(function(){
    global_fromfunc = 3; // 反面教材
})();
// 试图删除
delete global_var; // false
delete global_novar; // false
delete global_fromfunc; // false
// 测试该删除
typeof global_var; // 'number'
typeof global_novar; // 'undefined'
typeof  global_fromfunc; // 'undefined'

  在ES5严格模式下,未声明的变量(如在前面的代码片段中的两个反面教材)工作时会抛出一个错误。


  访问全局对象(Access to the Global Object)


  在浏览器中,全局对象可以通过window属性在代码的任何位置访问(除非你做了些比较出格的事情,像是声明了一个名为window的局部变量)。但是在其他环境下,这个方便的属性可能被叫做其他什么东西(甚至在程序中不可用)。如果你需要在没有硬编码的window标识符下访问全局对象,你可以在任何层级的函数作用域中做如下操作:

var global = (function(){
    return this;
})();

  这种方法可以随时获得全局对象,因为其在函数中被当做函数调用了(不是通过new构造),this总 是指向全局对象。实际上这个并不适用于ECMAScript 5严格模式,所以,在严格模式下时,你必须采取不同的形式。例如,你正在开发一个JavaScript库,你可以将你的代码包裹在一个即时函数中,然后从 全局作用域中,传递一个引用指向this作为你即时函数的参数。


  单var形式(Single var Pattern)


  在函数顶部使用单var语句是比较有用的一种形式,其好处在于:

  •   提供了一个单一的地方去寻找功能所需要的所有局部变量。
  •   防止变量在定义之前使用的逻辑错误。
  •   帮助你记住声明的全局变量,因此较少了全局变量。
  •   减少代码量。

  单var形式长得就像下面这个样子:

function func(){
    var a = 1,
          b = 2,
          sum = a + b,
          myObject = {},
          i,
          l;
     // 做些神马事情......    
}

  您可以使用一个var语句声明多个变量,并以逗号分隔。像这种初始化变量同时初始化值的做法是很好的。这样子可以防止逻辑错误(所有未初始化但声明的变量的初始值是undefined)和增加代码的可读性。在你看到代码后,你可以根据初始化的值知道这些变量大致的用途,例如是要当作对象呢还是当作整数来使。

  你也可以在声明的时候做一些实际的工作,例如前面代码中的sum = a + b这个情况,另外一个例子就是当你使用DOM(文档对象模型)引用时,你可以使用单一的var把DOM引用一起指定为局部变量,就如下面代码所示的:

function func(){
    var a = 1,
          b = 2,
          sum = a + b,
          myObject = {},
          i,
          l;
     // 做些神马事情......    
}


  预解析:var散布的问题(Hoisting: A Problem with Scattered vars):


  JavaScript中,你可以在函数的任何位置声明多个var语句,并且它们就好像是在函数顶部声明一样发挥作用,这种行为称为 hoisting(悬置/置顶解析/预解析)。当你使用了一个变量,然后不久在函数中又重新声明的话,就可能产生逻辑错误。对于JavaScript,只 要你的变量是在同一个作用域中(同一函数),它都被当做是声明的,即使是它在var声明前使用的时候。看下面这个例子:

// 反例
nyName = 'global'; // 全局变量
function func(){
    alert(myName); // undefined
    var myName = 'local';
    alert(myName); // 'local'
}
func();

  在这个例子中,你可能会以为第一个alert弹出的是”global”,第二个弹出”loacl”。这种期许是可以理解的,因为在第一个alert 的时候,myname未声明,此时函数肯定很自然而然地看全局变量myname,但是,实际上并不是这么工作的。第一个alert会弹 出”undefined”是因为myname被当做了函数的局部变量(尽管是之后声明的),所有的变量声明当被悬置到函数的顶部了。因此,为了避免这种混乱,最好是预先声明你想使用的全部变量。

  上面的代码片段执行的行为可能就像下面这样:

myName = 'global';
function func(){
    var myName;
    alert(myName); // undefined
     myName = 'local';
     alert(myName); // local
}

  注:为了完整,我们再提一提执行层面的稍微复杂点的东西。代码处理分两个阶段,第一阶段是变量,函数声明,以及正常格式的参数创建,这是一个解析和进入上下文的阶段。第二个阶段是代码执行,函数表达式和不合格的标识符(为声明的变量)被创建。但是,出于实用的目的,我们就采用了”hoisting(置顶)”这个概念, 这种ECMAScript标准中并未定义,通常用来描述行为。


  for循环(for Loops)


  在for循环中,你可以循环取得数组或是数组类似对象的值,譬如arguments和HTMLCollection对象。通常的循环形式如下:

// 不太好的循环
for(var i=0; i<myArray.length; i++){
    // 使用myArray[i]做点神马?
}

  这种形式的循环的不足在于每次循环的时候数组的长度都要去获取下。这回降低你的代码,尤其当myarray不是数组,而是一个HTMLCollection对象的时候。

  HTMLCollections指的是DOM方法返回的对象,例如:

document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()

  还有其他一些HTMLCollections,这些是在DOM标准之前引进并且现在还在使用的,例如:

document.images: 页面上所有的图片元素
document.links : 所有a标签元素
document.forms : 所有表单
document.forms[0].elements : 页面上第一个表单中的所有域

  集合的麻烦在于它们实时查询基本文档(HTML页面)。这意味着每次你访问任何集合的长度,你要实时查询DOM,而DOM操作一般都是比较昂贵的。

  这就是为什么当你循环获取值时,缓存数组(或集合)的长度是比较好的形式,正如下面代码显示的:

for(var i=0,max=myArray.length; i<max; i++){
    // 使用myArray[i]做点神马?
}

  这样,在这个循环过程中,你只检索了一次长度值。

  在所有浏览器下,循环获取内容时缓存HTMLCollections的长度是更快的,2倍(Safari3)到190倍(IE7)之间。

  注意到,当你明确想要修改循环中的集合的时候(例如,添加更多的DOM元素),你可能更喜欢长度更新而不是常量。

  伴随着单var形式,你可以把变量从循环中提出来,就像下面这样:

function looper(){
    var i = 0,
          max,
          myArray = [];
    // ......
    for(i=0,max=myArray.length;i++){
        // 使用myArray[i]做点神马?
    }
}

  这种形式具有一致性的好处,因为你坚持了单一var形式。不足在于当重构代码的时候,复制和粘贴整个循环有点困难。例如,你从一个函数复制了一个循环到另一个函数,你不得不去确定你能够把i和max引入新的函数(如果在这里没有用的话,很有可能你要从原函数中把它们删掉)。

  最后一个需要对循环进行调整的是使用下面表达式之一来替换i++。

i = i + 1
i += 1

  还有两种变化的形式,其又有了些微改进,因为:

  •   少了一个变量(无max)
  •   向下数到0,通常更快,因为和0做比较要比和数组长度或是其他不是0的东西作比较更有效率

//第一种变化的形式:
var i, myarray = [];
for (i = myarray.length; i–-;) {
   // 使用myarray[i]做点什么
}
//第二种使用while循环:
var myarray = [],
    i = myarray.length;
while (i–-) {
   // 使用myarray[i]做点什么
}

  这些小的改进只体现在性能上。


  for-in循环(for-in Loops):


  for-in循环应该用在非数组对象的遍历上,使用for-in进行循环也被称为“枚举”。

  从技术上将,你可以使用for-in循环数组(因为JavaScript中数组也是对象),但这是不推荐的。因为如果数组对象已被自定义的功能增强,就可能发生逻辑错误。另外,在for-in中,属性列表的顺序(序列)是不能保证的。所以最好数组使用正常的for循环,对象使用for-in循环。

  有个很重要的hasOwnProperty()方法,当遍历对象属性的时候可以过滤掉从原型链上下来的属性。

// 对象
var man = {
    hands: 2,
    legs: 2,
    heads: 1
};
// 在代码的某个地方
// 一个方法添加给了所有对象
if( typeof Object.prototype.clone === 'undefined' ){
     Object.prototype.clone = function(){};
}

  在这个例子中,我们有一个使用对象字面量定义的名叫man的对象。在man定义完成后的某个地方,在对象原型上增加了一个很有用的名叫 clone()的方法。此原型链是实时的,这就意味着所有的对象自动可以访问新的方法。为了避免枚举man的时候出现clone()方法,你需要应用hasOwnProperty()方法过滤原型属性。如果不做过滤,会导致clone()函数显示出来,在大多数情况下这是不希望出现的。

// 1.
// for-in循环
for(var i in man){
    if( man.hasOwnProperty(i) ){ // 过滤
          console.log(i, ':', man[i]);
     }
}
/* 
    hands: 2
    legs: 2
    heads: 1
*/

// 反面例子
for(var i in man){
    console.log(i, ':', 'man[i]');
}
/* 
    hands: 2
    legs: 2
    heads: 1
    clone: function(){}
*/

  另外一种使用hasOwnProperty()的形式是取消Object.prototype上的方法。像是:

for (var i in man) {
   if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
      console.log(i, ":", man[i]);
   }
}

  其好处在于在man对象重新定义hasOwnProperty情况下避免命名冲突。也避免了长属性查找对象的所有方法,你可以使用局部变量“缓存”它。

var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
    if (hasOwn.call(man, i)) { // 过滤
        console.log(i, ":", man[i]);
    }
}

  注:严格来说,不使用hasOwnProperty()并不是一个错误。根据任务以及你对代码的自信程度,你可以跳过它以提高些许的循环速度。但是当你对当前对象内容(和其原型链)不确定的时候,添加hasOwnProperty()更加保险些。

  格式化的变化(通不过JSLint)会直接忽略掉花括号,把if语句放到同一行上。其优点在于循环语句读起来就像一个完整的想法(每个元素都有一个自己的属性”X”,使用”X”干点什么):

// 警告: 通不过JSLint检测
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // 过滤
    console.log(i, ":", man[i]);
}

  (不)扩展内置原型((Not) Augmenting Built-in Prototypes)


  扩增构造函数的prototype属性是个很强大的增加功能的方法,但有时候它太强大了。

  增加内置的构造函数原型(如Object(), Array(), 或Function())挺诱人的,但是这严重降低了可维护性,因为它让你的代码变得难以预测。使用你代码的其他开发人员很可能更期望使用内置的 JavaScript方法来持续不断地工作,而不是你另加的方法。

  另外,属性添加到原型中,可能会导致不使用hasOwnProperty属性时在循环中显示出来,这会造成混乱。

  因此,不增加内置原型是最好的。你可以指定一个规则,仅当下面的条件均满足时例外:

  •   可以预期将来的ECMAScript版本或是JavaScript实现将一直将此功能当作内置方法来实现。例如,你可以添加ECMAScript 5中描述的方法,一直到各个浏览器都迎头赶上。这种情况下,你只是提前定义了有用的方法。
  •   如果您检查您的自定义属性或方法已不存在——也许已经在代码的其他地方实现或已经是你支持的浏览器JavaScript引擎部分。
  •   你清楚地文档记录并和团队交流了变化。

if( typeof Object.prtptype.myMethod !== 'function' ){
     Object.rototype.myMethod = function(){
           // 接下来做些神马?
     }
}


  switch模式(switch Pattern)


  你可以通过类似下面形式的switch语句增强可读性和健壮性:

var inspect_me = 0,
      result = '';
switch ( inspect_me ){
 case 0:
        result  = 'zero';
        break;
 case 1:
        result  = 'one';
        break;
 case 2:
        result  = 'two';
        break;
default:
        result = 'unKnow'
}

  这个简单的例子中所遵循的风格约定如下:

  •   每个case和switch对齐(花括号缩进规则除外)
  •   每个case中代码缩进
  •   每个case以break清除结束
  •   避免贯穿(故意忽略break)。如果你非常确信贯穿是最好的方法,务必记录此情况,因为对于有些阅读人而言,它们可能看起来是错误的。
  •   以default结束switch:确保总有健全的结果,即使无情况匹配。

原文链接:http://www.cnblogs.com/beyond-succeed/archive/2016/09/12/5864316.html

来源作者:秋天的风,夏天的雨


鲜花

握手

雷人

路过

鸡蛋
更多

相关阅读

最新评论

HTML5中国微信

小黑屋|关于我们|HTML5论坛|友情链接|手机版|HTML5中国 ( 京ICP备11006447号 京公网安备:11010802018489号  

GMT+8, 2017-4-27 05:46

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

返回顶部