浅谈JavaScript中的变量、参数、作用域和作用域链

时间:2024-01-16 13:52:02
  • 基本类型和引用类型

    在JavaScript中有两种数据类型值。基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指的是可能由多个值构成的对象。在JavaScript中有5种基本数据类型,分别是:Undefined、Null、Boolean、Number、String(这个和其他编程语言不一样,需要注意)。基本数据类型是按值进行访问的,一般都存储在栈中。而引用类型的值都保存在内存堆上面,在内存栈上保存一个对它的引用(这个和C#,Java等编程语言存储对象的方式类似)。在JavaScript中提供基本的引用类型数据,它们是:Object、Array、Function等。这些具体会在后面的博文介绍。

    下面我们来看下基本数据类型和引用数据类型在一些关键点上的差异。先看基本数据类型。

    例如:var num1=5;var num2=num1。基本数据类型在内存中是这样的。如图:

浅谈JavaScript中的变量、参数、作用域和作用域链

    而作为引用类型我们可以使用下面代码来看下其内存布局。代码如下:

 var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Hello";
alert(obj2.name); //输出Hello

浅谈JavaScript中的变量、参数、作用域和作用域链

    JavaScript中所有的函数参数都是值传递的。这对于基本数据类型很好理解,就是拷贝一份当前的值。函数内部使用的参数和外部传递的值互不影响。而作为引用类型的参数,这个值其实就是对象在堆内存上的引用。这个类似于上面介绍引用类型内存布局的部分。

  • 执行环境和作用域

    执行环境定义了变量或者函数有权访问的其他数据、决定了它们各自的行为。每一个环境变量都有一个与之关联的比变量对象。执行环境中定义的所有变量和函数都保存在这个变量中。

    全局执行环境是最外层的执行环境,一般在Web环境下指的是Window对象。所有全局变量和函数都是作为Window对象的属性和方法创建的。某一个执行环境的代码执行完毕以后,该环境被销毁(准确的说是从环境栈中弹出),保存在其中所有的变量和函数也都销毁了。全局执行环境需要等浏览器关闭才能退出。

    每一个函数都有自己的执行环境。当代码在一个环境中执行时,会创建变量对象的作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行环境的变量对象。作用域链中的下一个变量对象包含外部环境,再下一个变量对象则来自下一个包含环境。这样一直延续到全局执行环境。下面就通过一个例子来直观的看下这方面的内容。

    我们先来看这段简单的代码:

 function a(x,y){
var b=x+y;
return b;
}

    在函数a创建的时候它的作用域链填入全局变量对象,该变量对象中包含全局变量和函数。如图所示:

浅谈JavaScript中的变量、参数、作用域和作用域链

    如果执行环境是函数,那么将其活动对象(activation object, AO)作为作用域链第一个对象,第二个对象是包含环境,下一个是包含环境的包含环境(这就是我们上面讲的作用域链)。还是看代码:

 function a(x,y){
var b=x+y;
return b;
}
var tatal=a(5,10);

    这时候当执行到第5行代码,进入函数a进行执行时,作用域链如图:

浅谈JavaScript中的变量、参数、作用域和作用域链

  • 延长作用域链

    在JavaScript中执行环境的类型可以分成两种,全局和局部(函数)。但是有些语句在执行过程中可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。具体来说就是当执行流进入try-catch中的catch块时和with语句时会临时添加一个变量对象。但这在构建大型应该时也会造成性能损耗。

  • JavaScript参数相关知识点

    JavaScript函数中的参数和其他编程语言也是不同的。JavaScript函数不介意你到底传递多少个参数,甚至实参和形参不对应也可以,因为在函数内部JavaScript使用一个arguments对象来保存外部传递的所有参数。arguments对象是一个类似于数组的对象,它可以提供arguments[0],[1]这样的方式来访问传递过来的参数。但是它不是数组,是一个对象。

 function showArgs(arg1, arg2) {
var len1 = showArgs.length; //输出形参个数
var len2 = arguments.length;//输出实参个数
alert(len1); //输出2
alert(len2); //输出4
}
showArgs(1, 2, 3, 4);

    我们可以看到实参传递的所有参数都保存在arguments对象中,这个对象中根据下标会获取到一个个的实参。而函数对象的length只会获取该函数形参的个数。下面介绍argument的另一个重量级的属性callee。对于callee,它是一个指针,指向其函数(arguments对象所属的函数)。下面我们还是通过例子来了解这个属性。

 var sum = function sum(n) {
  var functionCode = arguments.callee.toString();
  //alert(functionCode);
  if (n <= 1) {
  return 1;
  }
  else {
  return n + arguments.callee(n - 1);
  }
  };
  alert(sum(100)); //输出5050

    这个例子是计算1+2+3+.....+99+100的。我们可以使用arguments.callee来进行递归调用。arguments.callee指向的是当前函数的指针。那一句注释掉的会输出sum函数的源代码。好了今天的博客就写到这边吧。最后再用一个例子来生动的介绍下作用域和作用域链。

  • 最后总结

  请看下面的例子

var color = "blue";
function changeColor() {
var anthorcolor = "red";
function swapColor() {
var tempColor = anthorcolor;
anthorcolor = color;
color = tempColor;
}
swapColor();
}
changeColor();

  分析如图:

浅谈JavaScript中的变量、参数、作用域和作用域链

分析:
  我们看到当JavaScript执行changeColor函数时,会创建的自己的一个执行环境,然后压入环境栈中。每一个执行环境都有一个与之对应的变量对象和作用域链。作用域链的前端(索引0处)始终是指向当前执行代码所在的环境的变量对象。作用域链的下一个指向的变量对象则来自下一个包含环境(父执行环境,图中是全局执行环境)。最后changeColor的变量对象中保存了swapColor函数的引用及其他一些信息。所以在changeColor中进行变量的查找时,会首先查找自己的变量对象,没有找到的话会去上一级的变量对象中查找,所以changeColor函数可以访问到anthorColor和color变量。

  下面我们来分析swapColor函数。当开始执行swapColor函数时,会往环境栈中压入一个swapColor的执行环境。这时候作用域链的前端指向的是swapColor的变量对象。这时候swapColor的作用域链最前端是其本身的变量对象,下一层指向的是changeColor的变量对象,最外面一层指向的是window的变量对象。所以在swapColor中查找color变量的时候需要先在swapColor的变量对象中查找,然后在changeColor变量环境中查找,最后在全局的变量环境中才找到。最后swapColor执行完毕后,其执行环境会从环境栈中弹出。当前的执行环境又变成changeColor。好了,介绍到这,你应该也能画出执行swapColor时的执行环境和作用域链的图了吧。