JavaScript作用域学习笔记

@(JS技巧)[JavaScript, 作用域]

JavaScript作用域学习笔记

概念:

作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

分类:

全局作用域(Global Scope)

定义:
在代码中任何地方都能访问到的对象拥有全局作用域

出现的情况:

  1. 最外层函数和在最外层函数外面定义的变量拥有全局作用域;
  2. 所有未定义直接赋值的变量会自动声明为全局作用域;
  3. 所有window对象的属性;
局部作用域

定义
只在固定的代码片段内可访问到,最常见的例如函数内部,所有在一些地方也会看到有人把这种作用域称为函数作用域;

作用域链

定义:
JavaScript有一个内部属性称为Scope,由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

在JS中,作用域的概念和其他语言差不多, 在每次调用一个函数的时候 ,就会进入一个函数内的作用域,当从函数返回以后,就返回调用前的作用域.

js作用域的执行过程
  1. 任何执行上下文时刻的作用域, 都是由作用域链(scope chain)来实现。
  2. 在一个函数被定义的时候, 会将它定义时刻scope chain链接到这个函数对象的[[scope]]属性.
  3. 在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.
JavaScript作用域学习笔记_第1张图片
作用域链
函数执行的赋值过程

在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。

注意: 因为函数对象的[[scope]]属性是在定义一个函数的时候决定的, 而非调用的时候,

JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里

Javascript的预编译
  1. JS是一种脚本语言, JS的执行过程, 是一种翻译执行的过程.
  2. 在JS中, 是有预编译的过程的, JS在执行每一段JS代码之前, 都会首先处理var关键字和function定义式(函数定义式和函数表达式).
  3. 在调用函数执行之前, 会首先创建一个活动对象, 然后搜寻这个函数中的局部变量定义,和函数定义, 将变量名和函数名都做为这个活动对象的同名属性, 对于局部变量定义,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined.
  4. 对于函数定义式, 会将函数定义提前. 而函数表达式, 会在执行过程中才计算.
  5. JS的预编译是以段为处理单元的…
作用域链和代码优化

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

function changeColor(){
    document.getElementById("btnChange").onclick=function(){
        document.getElementById("targetCanvas").style.backgroundColor="red";
    };
}

这个函数引用了两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。这段代码可以重写如下:

function changeColor(){
    var doc=document;
    doc.getElementById("btnChange").onclick=function(){
        doc.getElementById("targetCanvas").style.backgroundColor="red";
    };
}

这段代码比较简单,重写后不会显示出巨大的性能提升,但是如果程序中有大量的全局变量被从反复访问,那么重写后的代码性能会有显著改善。

改变作用域链

有两种方法可以改变作用域链:

  1. with:应该避免,使作用域链更长,有性能影响;
JavaScript作用域学习笔记_第2张图片
with的作用域链
  1. try-catch语句中的catch语句:
    try{
    doSomething();
}catch(ex){
    alert(ex.message); //作用域链在此处改变
}

一旦catch语句执行完毕,作用域链机会返回到之前的状态。
try-catch语句在代码调试和异常处理中非常有用,因此不建议完全避免。

参考文献

JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
JavaScript作用域链原理

你可能感兴趣的:(JavaScript作用域学习笔记)