JavaScript变量及作用域
说明:关于javascript基本类型及引用类型、执行环境及作用域等… -参考《JavaScript高级程序设计(第3版)》
1.基本类型和引用类型的值
ECMAScript变量包含两种不同数据类型的值:基本类型值和引用值。
基本类型类型:Undefined、Null、Boolean、String、Number.
[简单的数据段,按值访问,可以直接操作保存在变量中的实际的值]
引用类型:[引用类型值指由多个值构成的对象,这些对象保存在内存中,因而只能操作对象的引用,不能操作对象的内存空间]
从内存分配上看:
原始值:存储在栈(stack)中的简单数据段,即值直接存储变量访问的位置,可以直接操作。
其空间固定,将他们存储在较小的内存区域(栈)便于迅速查寻变量的值。
引用值:指复合数据类型的对象,因为其空间大小不固定,因而将其存于堆中,因而存储在变量处的值是一个指针,指向存储对象的内存地址。
内存分析图:
复制变量:
基本类型:
若变量A要复制变量B的基本类型的值,则会把变量B的副本赋值给变量A,此后AB变量完全独立,只不过拥有的值相同。
引用类型:
若变量A要复制变量B的引用类型的值,也会把存储在变量A中的值赋值给变量B,但由于存放在变量处的是一个指向对象内存地址的指针,所以它们操作的其实是同一个对象。
//基本类型值的复制
var num1 = 5;
var num2 = num1;
num1=10;
alert(num2); //5
//引用类型值的复制
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name); //"Nicholas"
可参考下图:
传递参数:
ECMAScript中所有函数的参数都是按值传递的。但基本类型和引用类型在传参时还是有差别。其主要原因主要是上述讲到的内存分配不同导致的。
对于基本类型的值传递还是一样,独立各不影响。
对于引用类型:引用类型变量存放的值仍是其对象在堆内存中的内存地址,因此它传递的值也就是这个内存地址,因此若在函数内部修改了该参数,也会体现在外部,因为它们都指向同一个对象。
//基本类型:
function addTen(num){
num+=10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20
alert(result); //30;
//引用类型:
function setName(obj){
obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
《JavaScript高级程序设计(第3版)》书中证明对象是按值传递部分:
function setName(obj){
obj.name = "Nicholas";
obj = new Object();
obj.name="Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas";
对于上次代码,书中的解释为:如果person是按引用传递的,那么person就会自动被修改为指向其name属性值为”Greg”的新对象,但实际访问person.name,得到的仍是”Nicholas”,说明即使在函数内部
改了参数的值,但原始的引用仍未改变。
上述代码中setName部分其实就是:
function setName(person){
arguments[0] = person;
arguments[0].name="Nicholas"; //即person.name="Nicholas";
arguments[0] = new Object(); //arguments[0]的值重新绑定了新对象的内存地址,已经不再同person一样指向同一个对象的内存地址,而person的值仍为原来的值。
arguments[0].name="Greg"; //与person无关
}
补充:在函数内部重写obj,其实这个变量引用即为一个局部变量,该函数执行完毕后,这个局部变量就会被销毁。
因而可以把ECMAScript函数的参数想象成局部变量。
检测类型:
typeof操作符用于检测String、Number、Boolean、Undefined类型的最佳工具。不过无法检测object和null
var n = null;
var o = new Object();
alert(n); //object
alert(o); //object
在检测引用类型时,我们可以使用instanceof操作符
如果变量是给定引用类型的实例,instanceof操作符就会返回true。
补充:所有的引用类型都是Object的实例,因此用instanceof检测一个引用类型值和Object始终为true,用instanceof检测基本类型值则始终为false.
alert(person instanceof Object); //变量person是否为Object的实例?
//举例如下:
function Animal(){};
function Pig(){};
Pig.prototype = new Animal(); //后续介绍---Pig继承Animal
alert(new Pig() instanceof Animal); //true
2.执行环境与作用域:
- 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象。我们无法通过代码访问该对象,但解析器可在后台处理数据时使用它。
- 执行环境也称为执行上下文,当Javascript解释器初始化执行代码时,会默认进入全局执行环境,执行过程中调用的每个函数都会创建一个新的执行环境,
每个函数都有自己的执行环境,当执行流进入一个函数时,会将函数的环境推入栈顶部,函数执行完,其环境从栈中退出,控制权交回原来的执行环境。
- 全局执行环境是最外围的执行环境,在Web浏览器中,全局变量被认为是window对象,所有全局变量和全局函数都作为window对象的属性和方法。
- 当代码在一个执行环境中执行时,会创建变量对象的一个作用域链,其用途是保证对执行环境有权访文的所有变量和函数的有序访问。**全局作用链的前端是当前执行代码所在的环境的变量对象。
- 内层环境可以引用外层环境的变量和函数,而外层环境则无法访问内层变量的变量和函数。
- 全局执行环境的变量对象始终都是作用域链的最后一个对象。
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color, anotherColor, 和 tempColor
}
// 这里可以访问color 和 anotherColor,但不能访问tempColor
swapColors();
}
changeColor();// 只能访问color
执行环境如图:
解释:
上述代码主要涉及三个执行环境:全局执行环境、changeColor()局部环境、swapColors()局部环境。
1.全局环境:变量color、函数changeColor().
2.changeColor()局部环境:变量anotherColor,函数swapColors().
3.swapColors()局部环境:变量tempColor.
该段代码很好地展现了内层环境可引用外层环境的变量和函数,而外层环境无法访问内层变量的变量和函数。
如图,执行环境可以向上搜索作用域链,查询变量和函数名,任何环境下都不能通过向下搜索作用域链进入另一个执行环境。
即:changeColor()执行环境不能向下搜索进入swapColors()执行环境,但swapColors()执行环境可以通过向上搜索进入changeColor()执行环境。
查询标识符:
查询过程:从作用域链前端开始,向上逐级查询与给定名字相匹配的标识符,找到便停止返回,未找到则继续向上逐级搜索,一直追溯到全局环境的变量对象,若还未找到,则该变量未声明。
延长作用域链:
执行环境共有全局和局部两种,但可通过其他方式延长作用域链。
可以通过一些语句在作用域链的前端临时增加一个变量对象,该变量对象在代码执行结束后删除。
实现上述方法的两种语句:
try-catch语句的catch块:创建一个新变量对象,其中包含被抛出的错误对象的声明.
with语句:将指定对象添加到作用域链中。
无块级作用域:
什么是无块级作用域?
在其他类C语言中,花括号{}封闭的代码(if,for,while…)都有自己的作用域,但在ECMAScript中经常会遇到下列问题:
//在javascript中,if,for语句等没有自己的执行环境,而是会把其变量添加到当前执行环境(在此为全局环境).
//if语句中:
if(true){
var color = "blue";
}
alert(color); //"blue"
//for语句中
for(var i = 0;i<10;i++) {
alert(i);
}
alert(i); //10 !!!