[翻译]High Performance JavaScript(007)

来源:互联网 发布:淘宝拍卖房产税费 编辑:程序博客网 时间:2024/05/16 16:02

Dynamic Scopes  动态作用域

 

    Both the with statement and the catch clause of a try-catch statement, as well as a function containing eval_r(), are all considered to be dynamic scopes. A dynamic scope is one that exists only through execution of code and therefore cannot be determined simply by static analysis (looking at the code structure). For example:

    无论是with表达式还是try-catch表达式的catch子句,以及包含eval_r()的函数,都被认为是动态作用域。一个动态作用域只因代码运行而存在,因此无法通过静态分析(察看代码结构)来确定(是否存在动态作用域)。例如:

function execute(code) {
  eval_r(code);
  function subroutine(){
    return window;
  }
  var w = subroutine();
  //what value is w?
};

    The execute() function represents a dynamic scope due to the use of eval_r(). The value of w can change based on the value of code. In most cases, w will be equal to the global window object, but consider the following:

    execute()函数看上去像一个动态作用域,因为它使用了eval_r()。w变量的值与code有关。大多数情况下,w将等价于全局的window对象,但是请考虑如下情况:

execute("var window = {};")

    In this case, eval_r() creates a local window variable in execute(), so w ends up equal to the local window instead of the global. There is no way to know if this is the case until the code is executed, which means the value of the window identifier cannot be predetermined.

    这种情况下,eval_r()在execute()函数中创建了一个局部window变量。所以w将等价于这个局部window变量而不是全局的那个。所以说,不运行这段代码是没有办法了解具体情况的,标识符window的确切含义不能预先确定。

 

    Optimizing JavaScript engines such as Safari's Nitro try to speed up identifier resolution by analyzing the code to determine which variables should be accessible at any given time. These engines try to avoid the traditional scope chain lookup by indexing identifiers for faster resolution. When a dynamic scope is involved, however, this optimization is no longer valid. The engines need to switch back to a slower hash-based approach for identifier resolution that more closely mirrors traditional scope chain lookup.

    优化的JavaScript引擎,例如Safari的Nitro引擎,企图通过分析代码来确定哪些变量应该在任意时刻被访问,来加快标识符识别过程。这些引擎企图避开传统作用域链查找,取代以标识符索引的方式进行快速查找。当涉及一个动态作用域后,此优化方法就不起作用了。引擎需要切回慢速的基于哈希表的标识符识别方法,更像传统的作用域链搜索。

 

    For this reason, it's recommended to use dynamic scopes only when absolutely necessary.

    正因为这个原因,只在绝对必要时才推荐使用动态作用域。

 

Closures, Scope, and Memory  闭包,作用域,和内存

 

    Closures are one of the most powerful aspects of JavaScript, allowing a function to access data that is outside of its local scope. The use of closures has been popularized through the writings of Douglas Crockford and is now ubiquitous in most complex web applications. There is, however, a performance impact associated with using closures.

    闭包是JavaScript最强大的一个方面,它允许函数访问局部范围之外的数据。闭包的使用通过Douglas Crockford的著作流行起来,当今在最复杂的网页应用中无处不在。不过,有一种性能影响与闭包有关。

 

    To understand the performance issues with closures, consider the following:

    为了解与闭包有关的性能问题,考虑下面的例子:

function assignEvents(){
  var id = "xdi9592";
  document.getElementById("save-btn").onclick = function(event){
    saveDocument(id);
  };
}

    The assignEvents() function assigns an event handler to a single DOM element. This event handler is a closure, as it is created when the assignEvents() is executed and can access the id variable from the containing scope. In order for this closure to access id, a specific scope chain must be created.

    assignEvents()函数为一个DOM元素指定了一个事件处理句柄。此事件处理句柄是一个闭包,当assignEvents()执行时创建,可以访问其范围内部的id变量。用这种方法封闭对id变量的访问,必须创建一个特定的作用域链。

 

    When assignEvents() is executed, an activation object is created that contains, among other things, the id variable. This becomes the first object in the execution context's scope chain, with the global object coming second. When the closure is created, its [[Scope]] property is initialized with both of these objects (see Figure 2-7).

    当assignEvents()被执行时,一个激活对象被创建,并包含了一些应有的内容,其中包括id变量。它将成为运行期上下文作用域链上的第一个对象,全局对象是第二个。当闭包创建时,[[Scope]]属性与这些对象一起被初始化(见图2-7)。

 

 

Figure 2-7. Scope chains of the assignEvents() execution context and closure

图2-7  assignEvents()运行期上下文的作用域链和闭包

 

    Since the closure's [[Scope]] property contains references to the same objects as the execution context's scope chain, there is a side effect. Typically, a function's activation object is destroyed when the execution context is destroyed. When there's a closure involved, though, the activation object isn't destroyed, because a reference still exists in the closure's [[Scope]] property. This means that closures require more memory overhead in a script than a nonclosure function. In large web applications, this might become a problem, especially where Internet Explorer is concerned. IE implements DOM objects as nonnative JavaScript objects, and as such, closures can cause memory leaks (see Chapter 3 for more information).

    由于闭包的[[Scope]]属性包含与运行期上下文作用域链相同的对象引用,会产生副作用。通常,一个函数的激活对象与运行期上下文一同销毁。当涉及闭包时,激活对象就无法销毁了,因为引用仍然存在于闭包的[[Scope]]属性中。这意味着脚本中的闭包与非闭包函数相比,需要更多内存开销。在大型网页应用中,这可能是个问题,尤其在Internet Explorer中更被关注。IE使用非本地JavaScript对象实现DOM对象,闭包可能导致内存泄露(更多信息参见第3章)。

 

    When the closure is executed, an execution context is created whose scope chain is initialized with the same two scope chain objects referenced in [[Scope]], and then a new activation object is created for the closure itself (see Figure 2-8).

    当闭包被执行时,一个运行期上下文将被创建,它的作用域链与[[Scope]]中引用的两个相同的作用域链同时被初始化,然后一个新的激活对象为闭包自身被创建(参见图2-8)。

Figure 2-8. Executing the closure

图2-8  闭包运行

 

    Note that both identifiers used in the closure, id and saveDocument, exist past the first object in the scope chain. This is the primary performance concern with closures: you're often accessing a lot of out-of-scope identifiers and therefore are incurring a performance penalty with each access.

    注意闭包中使用的两个标识符,id和saveDocument,存在于作用域链第一个对象之后的位置上。这是闭包最主要的性能关注点:你经常访问一些范围之外的标识符,每次访问都导致一些性能损失。

 

    It's best to exercise caution when using closures in your scripts, as they have both memory and execution speed concerns. However, you can mitigate the execution speed impact by following the advice from earlier in this chapter regarding out-of-scope variables: store any frequently used out-of-scope variables in local variables, and then access the local variables directly.

    在脚本中最好是小心地使用闭包,内存和运行速度都值得被关注。但是,你可以通过本章早先讨论过的关于域外变量的处理建议,减轻对运行速度的影响:将常用的域外变量存入局部变量中,然后直接访问局部变量。

 

Object Members  对象成员

 

    Most JavaScript is written in an object-oriented manner, either through the creation of custom objects or the use of built-in objects such as those in the Document Object Model (DOM) and Browser Object Model (BOM). As such, there tends to be a lot of object member access.

    大多数JavaScript代码以面向对象的形式编写。无论通过创建自定义对象还是使用内置的对象,诸如文档对象模型(DOM)和浏览器对象模型(BOM)之中的对象。因此,存在很多对象成员访问。

 

    Object members are both properties and methods, and there is little difference between the two in JavaScript. A named member of an object may contain any data type. Since functions are represented as objects, a member may contain a function in addition to the more traditional data types. When a named member references a function, it's considered a method, whereas a member referencing a nonfunction data type is considered a property.

    对象成员包括属性和方法,在JavaScript中,二者差别甚微。对象的一个命名成员可以包含任何数据类型。既然函数也是一种对象,那么对象成员除传统数据类型外,也可以包含一个函数。当一个命名成员引用了一个函数时,它被称作一个“方法”,而一个非函数类型的数据则被称作“属性”。


    As discussed earlier in this chapter, object member access tends to be slower than accessing data in literals or variables, and in some browsers slower than accessing array items. To understand why this is the case, it's necessary to understand the nature of objects in JavaScript.

    正如本章前面所讨论过的,对象成员比直接量或局部变量访问速度慢,在某些浏览器上比访问数组项还要慢。要理解此中的原因,首先要理解JavaScript中对象的性质。

 

Prototypes  原形

 

    Objects in JavaScript are based on prototypes. A prototype is an object that serves as the base of another object, defining and implementing members that a new object must have. This is a completely different concept than the traditional object-oriented programming concept of classes, which define the process for creating a new object. Prototype objects are shared amongst all instances of a given object type, and so all instances also share the prototype object's members.

    JavaScript中的对象是基于原形的。原形是其他对象的基础,定义并实现了一个新对象所必须具有的成员。这一概念完全不同于传统面向对象编程中“类”的概念,它定义了创建新对象的过程。原形对象为所有给定类型的对象实例所共享,因此所有实例共享原形对象的成员。


    An object is tied to its prototype by an internal property. Firefox, Safari, and Chrome expose this property to developers as __proto__; other browsers do not allow script access to this property. Any time you create a new instance of a built-in type, such as Object or Array, these instances automatically have an instance of Object as their prototype.

    一个对象通过一个内部属性绑定到它的原形。Firefox,Safari,和Chrome向开发人员开放这一属性,称作__proto__;其他浏览器不允许脚本访问这一属性。任何时候你创建一个内置类型的实例,如Object或Array,这些实例自动拥有一个Object作为它们的原形。


    Consequently, objects can have two types of members: instance members (also called "own" members) and prototype members. Instance members exist directly on the object instance itself, whereas prototype members are inherited from the object prototype. Consider the following example:

    因此,对象可以有两种类型的成员:实例成员(也称作“own”成员)和原形成员。实例成员直接存在于实例自身,而原形成员则从对象原形继承。考虑下面的例子:
var book = {
  title: "High Performance JavaScript",
  publisher: "Yahoo! Press"
};
alert(book.toString());
//"[object Object]"


    In this code, the book object has two instance members: title and publisher. Note that there is no definition for the method toString() but that the method is called and behaves appropriately without throwing an error. The toString() method is a prototype member that the book object is inheriting. Figure 2-9 shows this relationship.

    此代码中,book对象有两个实例成员:title和publisher。注意它并没有定义toString()接口,但是这个接口却被调用了,也没有抛出错误。toString()函数就是一个book对象继承的原形成员。图2-9显示出它们之间的关系。


Figure 2-9. Relationship between an instance and prototype

图2-9  实例与原形的关系


    The process of resolving an object member is very similar to resolving a variable. When book.toString() is called, the search for a member named "toString" begins on the object instance. Since book doesn't have a member named toString, the search then flows to the prototype object, where the toString() method is found and executed. In this way, book has access to every property or method on its prototype.

    处理对象成员的过程与变量处理十分相似。当book.toString()被调用时,对成员进行名为“toString”的搜索,首先从对象实例开始,如果book没有名为toString的成员,那么就转向搜索原形对象,在那里发现了toString()方法并执行它。通过这种方法,booke可以访问它的原形所拥有的每个属性或方法。

 

    You can determine whether an object has an instance member with a given name by using the hasOwnProperty() method and passing in the name of the member. To determine whether an object has access to a property with a given name, you can use the in operator. For example:

    你可以使用hasOwnProperty()函数确定一个对象是否具有特定名称的实例成员,(它的参数就是成员名称)。要确定对象是否具有某个名称的属性,你可以使用操作符in。例如:


var book = {
  title: "High Performance JavaScript",
  publisher: "Yahoo! Press"
};
alert(book.hasOwnProperty("title")); //true
alert(book.hasOwnProperty("toString")); //false
alert("title" in book); //true
alert("toString" in book);
//true


    In this code, hasOwnProperty() returns true when "title" is passed in because title is an object instance; the method returns false when "toString" is passed in because it doesn't exist on the instance. When each property name is used with the in operator, the result is true both times because it searches the instance and prototype.

    此代码中,hasOwnProperty()传入“title”时返回true,因为title是一个实例成员。传入“toString”时返回false,因为toString不在实例之中。如果使用in操作符检测这两个属性,那么返回都是true,因为它既搜索实例又搜索原形。


Prototype Chains  原形链


    The prototype of an object determines the type or types of which it is an instance. By default, all objects are instances of Object and inherit all of the basic methods, such as toString(). You can create a prototype of another type by defining and using a constructor. For example:

    对象的原形决定了一个实例的类型。默认情况下,所有对象都是Object的实例,并继承了所有基本方法,如toString()。你可以用“构造器”创建另外一种类型的原形。例如:

 function Book(title, publisher){
  this.title = title;
  this.publisher = publisher;
}
Book.prototype.sayTitle = function(){
  alert(this.title);
};
var book1 = new Book("High Performance JavaScript", "Yahoo! Press");
var book2 = new Book("JavaScript: The Good Parts", "Yahoo! Press");
alert(book1 instanceof Book); //true
alert(book1 instanceof Object); //true
book1.sayTitle(); //"High Performance JavaScript"
alert(book1.toString());
//"[object Object]"


    The Book constructor is used to create a new instance of Book. The book1 instance's prototype (__proto__) is Book.prototype, and Book.prototype's prototype is Object. This creates a prototype chain from which both book1 and book2 inherit their members. Figure 2-10 shows this relationship.

    Book构造器用于创建一个新的Book实例。book1的原形(__proto__)是Book.prototype,Book.prototype的原形是Object。这就创建了一个原形链,book1和book2继承了它们的成员。图2-10显示出这种关系。


Figure 2-10. Prototype chains

图2-10  原形链


    Note that both instances of Book share the same prototype chain. Each instance has its own title and publisher properties, but everything else is inherited through prototypes. Now when book1.toString() is called, the search must go deeper into the prototype chain to resolve the object member "toString". As you might suspect, the deeper into the prototype chain that a member exists, the slower it is to retrieve. Figure 2-11 shows the relationship between member depth in the prototype and time to access the member.

    注意,两个Book实例共享同一个原形链。每个实例拥有自己的title和publisher属性,但其他成员均继承自原形。当book1.toString()被调用时,搜索工作必须深入原形链才能找到对象成员“toString”。正如你所怀疑的那样,深入原形链越深,搜索的速度就会越慢。图2-11显示出成员在原形链中所处的深度与访问时间的关系。


Figure 2-11. Data access going deeper into the prototype chain

图2-11  数据访问深入原形链


    Although newer browsers with optimizing JavaScript engines perform this task well, older browsers—especially Internet Explorer and Firefox 3.5—incur a performance penalty with each additional step into the prototype chain. Keep in mind that the process of looking up an instance member is still more expensive than accessing data from a literal or a local variable, so adding more overhead to traverse the prototype chain just amplifies this effect.

    虽然使用优化JavaScript引擎的新式浏览器在此任务中表现良好,但是老的浏览器,特别是Internet Explorer和Firefox 3.5,每深入原形链一层都会增加性能损失。记住,搜索实例成员的过程比访问直接量或者局部变量负担更重,所以增加遍历原形链的开销正好放大了这种效果。

原创粉丝点击