[Effective JavaScript 笔记] 第10条:避免使用with

with特性,提供的任何“便利”都更让其变得不可靠和低效率。javascript

with语句的用法,能够很方便地避免对对象的重复引用。上面的代码整理成下面的形式java

function status(info){

      var widget=new Widget();

      with(widget){

             setBackground(‘blue’);

             setForeground(‘white’);

             setText(‘Status:’+info);

            show()

       }

}

使用with语句从模块对象中导入变量也颇有诱惑力。数据结构

function f(x,y){

      with(Math){

            return min(round(x),sqrt(y));

       }

}

在上面的这两种状况,使用with使得提取对象的属性,并将这些属性绑定到块的局部变量中变得很是诱人且容易。函数

颇有吸引力,但没作它们应该作的事。性能

这两个例子里有两种不一样类型的变量。优化

  • 一种是但愿引用with对象的属性的变量,如setBackground,round。
  • 另外一种是但愿引用外部变量绑定的变量,如info,x,y。
  • 语法上并无区分这两种类型的变量,都只是看起来像变量。

js中全部变量都是相同的

js从最内层的做用域开始向外查找变量。with语句对待一个对象犹如该对象表明一个变量做用域,所以在with代码块的内部,变量查找从搜索给定的变量名的属性开始。若是在这个对象中没有找到该属性,则继续在外部做用域中搜索。prototype

image

在ES5规范中这称为词法环境(在旧版本标准中称为做用域链)。

该词法环境的最内层做用域由widget对象提供。接下来的做用域用来绑定该函数的局部变量info和widget。接下来一层绑定到status函数。注意在一个正常的做用域中,会有与局部做用域中的变量一样多的做用域绑定存储在与之对应的环境层级中。可是对于with做用域,绑定集合依赖于碰巧在给定时间点时的对象。3d

with块中的每一个外部变量的引用都隐式地假设在with对象(以及它的任何原型对象)中没有同名的属性。而在程序的其余地方建立或修改with对象或其原型对象不必定会遵循这样的假设。js引擎固然也不会读取局部代码来获取你使用了哪些局部变量。对象

变量做用域和对象命名空间之间的冲突使得with代码块异常脆弱。如:with对象得到了一个名为info的属性,那么status函数的行为就会被当即改变。status函数将使用这个属性而不是info参数。在源代码的功能进行扩展时,后面有可能决定全部的widget对象都应该有一个info属性。在原型对象上添加了info属性。这将使status函数变得不可预测。blog

status(‘connecting’);//Status:connecting

Widget.prototype.info=”[widget info]”;

status(‘connected’);//Status:[widget info]

一样的,若是某人添加名为x或y的属性到Math对象上,那么f函数也悲剧了。

Math.x=0;

Math.y=0;

f(2,9);//0

  

老是很难预测一个特定的对象是否已被修改,或是否可能拥有你不知道的属性。事实证实,人力不可预测的特性对于优化编译器一样不可预测。

一般状况下,js做用域可被表示为高效的内部数据结构,变量查找会很是快速。但with代码块须要搜索对象的原型链来查找with代码块里的全部变量,所以,其运行速度远远低于通常的代码块

在js中没有单个特性能做为一个更好的选择直接替代with语句。在某些状况下,最好的替代方法是简单地将对象绑定到一个简短的变量名上

function status(info){

    var w=new Widget();

    w.setBackground(‘blue’);

    w.setForebacground(‘white’);

    w.addText(‘Status:’+info);

    w.show();

}

  

没有任何变量对于w对象的内容是敏感的。因此即便一些代码修改了Widget的原型对象,status函数的行为依旧是可预期的。

status(‘connecting’);//Status:connecting

Widget.prototype.info=”[widget info]”;

status(‘connected’);//Status:connected

  

在其余状况下,最好的方法是显示地将局部变量显式地绑定到相关的属性上

function f(x,y){

     var min=Math.min,round=Math.round,sqrt=Math.sqrt;

    return min(round(x),sqrt(y));

}

  

再次一旦消除with语句,函数的行为变得能够预测了。

Math.x=0;

Math.y=0;

f(2,9);//2

  

提示

  • 避免使用with语句
  • 使用简短的变量名代替重复访问的对象
  • 显式地绑定局部变量到对象属性上
相关文章
相关标签/搜索