js中变量,作用域

时间:2021-08-28 06:08:53

一、js中的变量

ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。js中种有5种基本数据类型Undefined、Null、Boolean、Number 和String。js对于基本数据类型和引用类型的访问是不同的。基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。
特别注意一下String,在js中它是基本类型,而在很多其他语言中它是引用类型。

1、基本类型和引用类型复制的不同

基本类型的复制:

var num1 = 5;
var num2 = num1;

js中变量,作用域

从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。在此,num1 中保存的值是5。当使用num1 的值来初始化num2 时,num2 中也保存了值5。但num2中的5 与num1 中的5 是完全独立的,该值只是num1 中5 的一个副本。此后,这两个变量可以参与任何操作而不会相互影响。

引用类型的复制:

var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name);

js中变量,作用域
当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。

2、参数传递

首先我们介绍一下什么是值传递,什么是引用传递?我们可以举个例子来说明。
值传递很简单,就是将需要传递的值作为一个副本传递出去。那么什么是引用传递?我们举个例子:

int *p,*p1;
int i = 5; *p = i; //这里将i在内存中的地址赋与了指针p
*p1 = p; //这里将p在内存中的地址赋与了指针p1.

引用传递一直传递的是赋值变量的地址。
在ECMAScript 中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。访问变量有按值和按引用两种方式,而参数只能按值传递。
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript 的概念来说,就是arguments 对象中的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。

function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,没有变化
alert(result); //30

这里的函数addTen()有一个参数num,而参数实际上是函数的局部变量。在调用这个函数时,变量count 作为参数被传递给函数,这个变量的值是20。于是,数值20 被复制给参数num 以便在addTen()中使用。在函数内部,参数num 的值被加上了10,但这一变化不会影响函数外部的count 变量。参数num 与变量count 互不相识,它们仅仅是具有相同的值。

function setName(obj) {
obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas

以上代码中创建一个对象,并将其保存在了变量person 中。然后,这个变量被传递到setName()函数中之后就被复制给了obj。在这个函数内部,obj 和person 引用的是同一个对象。换句话说,即使这个变量是按值传递的,obj 也会按引用来访问同一个对象。于是,当在函数内部为obj 添加name属性后,函数外部的person 也将有所反映;因为person 指向的对象在堆内存中只有一个,而且是全局对象。

二、js的作用域

想知道js的作用域必须要先知道js的执行环境。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。通俗的讲就是一个执行环境对应着一个我们无法使用但是后台解释器会使用的变量对象,而这个对象中保存着在这个环境中所有可以访问的变量。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments 对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。也就是作用域链是从最里面向外边搜索。
我们可以举个例子:

function compare(value1, value2){
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);

js中变量,作用域
上面图片就是例子中的作用域链,在创建compare()函数
时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。