javascript做用域链学习笔记

做用域链

  1. ”JavaScript中的函数运行在它们被定义的做用域里,而不是它们被执行的做用域里.” ——权威指南javascript

  2. 在JavaScript中,一切皆对象,包括函数。函数对象和其它对象同样,拥有能够经过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被建立的做用域中对象的集合,这个集合被称为函数的做用域链,它决定了哪些数据能被函数访问。html

  3. 在一个函数被定义的时候, 会将它定义时刻的scope chain连接到这个函数对象的[[scope]]属性.前端

  4. 在一个函数对象被调用的时候,会建立一个活动对象(也就是一个对象), 该对象包含了函数的全部局部变量、命名参数、参数集合以及this,而后此对象会被推入做用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。java

  5. 在每次调用一个函数的时候 ,就会进入一个函数内的做用域,当从函数返回之后,就返回调用前的做用域.git

实例解析

var sayHello = function(l,s){
    var word = "hello world";
}

sayHello();
  • 在执行sayHello定义语句的时候, 会建立一个这个函数对象的[[scope]]属性。github

  • 将这个[[scope]]属性, 连接到定义它的做用域链上。此时由于func定义在全局环境, 因此此时的[[scope]]只是指向全局活动对象window active object.app

  • 在调用sayHello的时候, 会建立一个活动对象(假设为fObj),并建立arguments属性。而后会给这个对象添加俩个命名属性fObj.l, fObj.s; 对于每个在这个函数中申明的局部变量和函数定义, 都做为该活动对象的同名命名属性。对于局部变量,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined.函数

  • 将调用参数赋值给形参,对于缺乏的调用参数,赋值为undefined。优化

  • 将这个活动对象作为scope chain的最前端, 并将func的[[scope]]属性所指向的,定义sayHello时候的顶级活动对象, 加入到scope chain.this

  • 在发生标识符解析的时候, 就会逆向查询当前scope chain列表的每个活动对象的属性,若是找到同名的就返回。找不到,那就是这个标识符没有被定义。

做用域链全过程解析:

function factory() {
     var name = 'laruence';
     var intro = function(){
          alert('I am ' + name);
     }
     return intro;
}

function app(para){
     var name = para;
     var func = factory();
     func();
}

app('eve');

首先当调用app的时候, scope chain是由: {window活动对象(全局)}->{app的活动对象} 组成。此时的scope chain以下:(对于局部变量,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined.

[[scope chain]] = [
{
     para : 'eve',
     name : undefined,
     func : undefined,
     arguments : []
}, {
     window call object
}
]

当调用进入factory的函数体的时候, 此时的factory的scope chain为:

[[scope chain]] = [
{
     name : undefined,
     intor : undefined
}, {
     window call object
}
]

注意: 此时的做用域链中, 并不包含app的活动对象.(JavaScript中的函数运行在它们被定义的做用域里,而不是它们被执行的做用域里.)

在定义intro函数的时候, intro函数的[[scope]]为:

[[scope chain]] = [
{
     name : 'laruence',
     intor : undefined
}, {
     window call object
}
]

从factory函数返回之后,在app体内调用intor的时候, 发生了标识符解析, 而此时的sope chain是:

[[scope chain]] = [
{
     intro call object
}, {
     name : 'laruence',
     intor : undefined
}, {
     window call object
}
]

因此, name标识符解析的结果(在上面的做用域链中一层层往上匹配)应该是factory活动对象中的name属性, 也就是’laruence’。

标识符解析过程:
该过程从做用域链头部,也就是从活动对象开始搜索,查找同名的标识符,若是找到了就使用这个标识符对应的变量,若是没找到继续搜索做用域链中的下一个对象,若是搜索完全部对象都未找到,则认为该标识符未定义。函数执行过程当中,每一个标识符都要经历这样的搜索过程。

利用做用域链的代码优化

  1. 把全局变量存储到局部变量里再使用

从做用域链的结构能够看出,在运行期上下文的做用域链中,标识符所在的位置越深,读写速度就会越慢。全局变量老是存在于运行期上下文做用域链的最末端,所以在标识符解析的时候,查找全局变量是最慢的。因此,在编写代码的时候应尽可能少使用全局变量,尽量使用局部变量。一个好的经验法则是:若是一个跨做用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例以下面的代码:

function changeColor(){
    var doc=document;
    doc.getElementById("btnChange").onclick=function(){
        doc.getElementById("targetCanvas").style.backgroundColor="red";
    };
}
  1. 避免改变做用域链
    函数每次执行时对应的运行期上下文都是独一无二的,因此屡次调用同一个函数就会致使建立多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每个运行期上下文都和一个做用域链关联。通常状况下,在运行期上下文运行的过程当中,其做用域链只会被 with 语句和 catch 语句影响。
function initUI(){
    with(document){
        var bd=body,
            links=getElementsByTagName("a"),
            i=0,
            len=links.length;
        while(i < len){
            update(links[i++]);
        }
        getElementById("btnInit").onclick=function(){
            doSomething();
        };
    }
}

with 语句的做用是将代码的做用域设置到一个特定的对象中

当代码运行到with语句时,运行期上下文的做用域链临时被改变了。一个新的可变对象被建立,它包含了参数指定的对象的全部属性。这个对象将被推入做用域链的头部,这意味着函数的全部局部变量如今处于第二个做用域链对象中,所以访问代价更高了。

参考文章:Javascript做用域原理理解 JavaScript 做用域和做用域链

个人博客地址:http://bigdots.github.iohttp://www.cnblogs.com/yzg1/

相关文章
相关标签/搜索