JS变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已; 由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变;
一 变量及作用域
1.基本类型和引用类型// JS变量包含两种不同的数据类型的值:基本类型值和引用类型值;
// 1.基本类型值:保存在栈内存中的简单数据段;即这种值完全保存在内存中的一个位置; // 基本类型值包含:Undefined|Null|Boolean|Number|String; // 这些类型在内存中占有固定大小的空间;它们的值保存在栈空间,我们按值来访问;
// 2.引用类型值:保存在堆内存中的对象(可能由多个值构成),即变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象; // 引用类型的值的大小不固定,因此不能保存在栈内存,必须保存在堆内存中;但可以将引用类型的值的内存地址保存在栈内存中; // 当查询引用类型的变量时,先从栈内存中读取内存地址,然后通过地址找到堆内存中的值;=>按引用访问;
2.动态属性
3.复制变量值
// 引用类型赋值的是地址;
var Box = new Object(); // 创建一个引用类型;Box在栈内存中;而Object在堆内存中;
Box.name = 'lee'; // 新增一个属性;
var Box2 = Box; // 把引用地址赋值给Box2;Box2在栈内存中;
// Box2=Box,因为它们指向的是同一个对象;
// 如果这个对象中的name属性被修改了,Box.name和Box2.name输出的值都会被修改掉;
4.传递参数
return num;
}
console.log(num); // num is not defined;
function box(obj){
obj.name = 'lee';
var obj = new Object(); // 函数内部又创建了一个对象,它是局部变量;但在函数结束时被销毁了;
obj.name = 'Mr'; // 并没有替换掉原来的obj;
}
var p = new Object();
box(p); // 变量p被传递到box()函数中之后就被复制给了obj;在函数内部,obj和p访问的是同一个对象;
console.log(p.name); // =>lee;
// JS函数的参数都将是局部变量;也就是说,没有按引用传递;
5.检测类型
// 要检测变量是什么类型的对象,通过instanceof运算符来查看;
var Box = [1,2,3];
console.log(Box instanceof Array); // =>true;
var Box2 = {};
console.log(Box2 instanceof Object);
var Box3 = /g/;
console.lgo(Box3 instanceof RegExp);
var Box4 = new String('lee');
console.log(Box4 instanceof String); // =>true;是否是字符串对象;
var Box5 = 'string';
console.log(Box5 instanceof String); // =>false;
// 当使用instanceof检查基本类型的值时,它会返回false;
6.执行环境及作用域
// 如果是在全局环境下,需要程序执行完毕,或者网页被关闭才会销毁;
// PS:每个执行环境都有一个与之关联的变量对象,就好比全局的window可以调用全局变量和全局方法一样;
// 局部的环境也有一个类似window的变量对象,环境中定义的所有变量和函数都保存在这个对象中;
// (我们无法访问这个变量对象,但解析器会处理数据时后台使用它);
var box = 'blue';
function setBox(){
var box = 'red'; // 这里是局部变量,在当前函数体内的值是'red';出了函数体就不被认知;
console.log(box);
}
setBox();
console.log(box);
// 通过传参可以替换函数体内的局部变量,但作用域仅限在函数体内这个局部环境;
var box = 'blue';
function setBox(box){ // 通过传参,将局部变量替换成了全局变量;
alert(box); // 此时box的值是外部调用时传入的参数;=>red;
}
setBox('red');
alert(Box);
// 如果函数体内还包含着函数,只有这个内函数才可以访问外一层的函数的变量;
// 内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数;
var Box = 'blue';
function setBox(){
function setColor(){
var b = 'orange';
alert(Box);
alert(b);
}
setColor(); // setColor()的执行环境在setBox()内;
}
setBox();
// PS:每个函数被调用时都会创建自己的执行环境;当执行到这个函数时,函数的环境就会被推到环境栈中去执行,而执行后又在环境栈中弹出(退出),把控制权交给上一级的执行环境;
// PS:当代码在一个环境中执行时,就会形成一种叫做作用域链的东西;它的用途是保证对执行环境中有访问权限的变量和函数进行有序访问;作用域链的前端,就是执行环境的变量对象;
7.延长作用域链
8.没有块级作用域
for(var i=0; i<10; i++){ // 创建的变量i即使在for循环执行结束后,也依旧会存在与循环外部的执行环境中;
var Box = 'lee';
}
alert(i);
alert(Box);
function Box(num1,num2){
var sum = num1+num2; // 此时sum是局部变量;如果去掉var,sum就是全局变量了;
return sum;
}
alert(Box(10,10));
alert(sum); // sum is not defined;访问不到sum;
// PS:不建议不使用var就初始化变量,因为这种方法会导致各种意外发生;
// 一般确定变量都是通过搜索来确定该标识符实际代表什么;搜索方式:向上逐级查询;
var Box = 'blue';
function getBox(){
return Box; // 此时Box是全局变量;如果是var Box='red',那就变成局部变量了;
}
alert(getBox());
// 调用getBox()时会引用变量Box;
// 首先,搜索getBox()的变量对象,查找名为Box的标识符;
// 然后,搜索继续下一个变量对象(全局环境的变量对象),找到了Box标识符;
// PS:变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜索作用域链;
二 内存问题
// JS最常用的垃圾收集方式就是标记清除;垃圾收集器会在运行的时候给存储在内存中的变量加上标记;
// 然后,它会去掉环境中正在使用的变量的标记,而没有被去掉标记的变量将被视为准备删除的变量;
// 最后,垃圾收集器完成内存清理工作,销毁那些标记的值并回收他们所占用的内存空间;
// 垃圾收集器是周期性运行的,这样会导致整个程序的性能问题;
// 比如IE7以前的版本,他的垃圾收集器是根据内存分配量运行的,比如256个变量就开始运行垃圾收集器,这样就不得不频繁地运行,从而降低了性能;
// 一般来说,确保占用最少的内存可以让页面获得更好的性能;
// 最佳方案:一旦数据不再使用,将其值设置为null来释放引用,这个做法叫做解除引用;
var o = {
name:'lee';
};
o = null; // 解除对象引用,等待垃圾收集器回收;
三 小结
1.变量 // JS变量可以保存两种类型的值:基本类型值和引用类型值;它们具有以下特点: // 1.基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中; // 2.从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本; // 3.引用类型的值是对象,保存在堆内存中; // 4.包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针; // 5.从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向用一个对象; // 6.确定一个值是哪种基本类型可以使用typeof操作符;而确定一个值是哪种引用类型可以使用instanceof操作符;
2.作用域 // 所有变量都存在于一个执行环境(作用域)中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量; // 1.执行环境有全局执行环境和函数执行环境之分; // 2.每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链; // 3.函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其父环境,乃至全局环境; // 4.变量的执行环境有助于确定应该合适释放内存;
3.内存 // JS自动垃圾收集机制 // 1.离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除; // 2.为了确保有效地回收内存,应该及时解除不再使用的全局对象/全局对象属性以及循环引用变量的引用;