JavaScript-变量和作用域(12)

一、变量

JS变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已;
由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的声明周期内改变

1.基本类型和引用类型

基本类型
保存在栈内存中的简单数据段;即这种值完全保存在内存中的一个位置;
基本类型值包含:Undefined|Null|Boolean|Number|String

引用类型
保存在堆内存中的对象(可能由多个值构成),即变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
引用类型的值的大小不固定,因此不能保存在栈内存,必须保存在堆内存中;但可以将引用类型的值的内存地址保存在栈内存中;
当查询引用类型的变量时,先从栈内存中读取内存地址,然后通过地址找到堆内存中的值;=>按引用访问

2.动态属性

定义基本类型值和引用类型值的方式相似:创建一个变量并为该变量赋值
但当这个值保存到变量中以后,对不同类型值可以执行的操作就不一样了

1
2
3
4
5
6
7
var box = new Object();                     // 创建引用类型;
box.name = 'lee'; // 新增一个属性;
console.log(box.name); // =>lee;

var box = 'lee'; // 创建基本类型
box.age = 15; // 给基本类型添加属性;
console.log(box.age); // =>undefined;

3.复制变量值

在变量复制方面,基本类型和引用类型也有所不同;

1
2
3
4
5
6
7
8
9
10
11
// 基本类型赋值的是值本身;
var box = 'lee'; // 在栈内存中生成一个box'lee';
var box2 = box; // 在栈内存中再生成一个box2'lee';
// box和box2完全独立;两个变量分别操作时互不影响;

// 引用类型赋值的是地址;
var box = new Object(); // 创建一个引用类型;box在栈内存中;而Object在堆内存中;
box.name = 'lee'; // 新增一个属性;
var box2 = box; // 把引用地址赋值给box2;box2在栈内存中;
// box2=box,因为它们指向的是同一个对象;
// 如果这个对象中的name属性被修改了,box.name和box2.name输出的值都会被修改掉;

4.传递参数

JS中所有函数的参数都是按值传递的,即参数不会按引用传递;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function box(num){                         // 按值传递,传递的参数是基本类型;
num +=10; // 这里的num是局部变量,全局无效;
return num;
}
var num = 50;
var result = box(num);
console.log(result); // 60;
console.log(num); // 50;

function box(num){
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.检测类型

要检测一个变量的类型,通过typeof运算符来判断;多用来检测基本类型;

1
2
var box = 'lee';
console.log(typeof box); // =>string;

要检测变量是什么类型的对象,通过instanceof运算符来查看

1
2
3
4
5
6
7
8
9
10
11
12
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.执行环境及作用域

执行环境:定义了变量或函数有权访问的其他数据,决定了他们各自的行为
在Web浏览器中,全局执行环境 = window对象;因此所有的全局变量和函数都是作为window对象的属性和方法创建的;

1
2
3
4
5
6
7
8
9
10
var box = 'blue';                         // 声明一个全局变量;
function setBox(){
console.log(box); // 全局变量可以在函数里访问;
}
setBox(); // 执行函数;
// 全局的变量=window对象的属性;
// 全局的函数=window对象的方法;

// PS:当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁;
// 如果是在全局环境下,需要程序执行完毕,或者网页被关闭才会销毁;

每个执行环境都有一个与之相关联的变量对象,就好比全局的window可以调用全局变量和全局方法一样;
局部的环境也有一个类似window的变量对象,环境中定义的所有变量和函数都保存在这个对象中;我们无法访问这个对象,但解析器会处理数据时后台使用它;

1
2
3
4
5
6
7
var box = 'blue';
function setBox(){
var box = 'red'; // 这里是局部变量,在当前函数体内的值是'red';出了函数体就不被认知;
console.log(box);
}
setBox();
console.log(box);

通过传参可以替换函数体内的局部变量,但作用域仅限在函数体内这个局部环境;

1
2
3
4
5
6
var box = 'blue';
function setBox(box){ // 通过传参,将局部变量替换成了全局变量;
alert(box); // 此时box的值是外部调用时传入的参数;=>red;
}
setBox('red');
alert(box);

如果函数体内还包含着函数,只有这个内函数可以访问外一层的函数的变量;
内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境中的任何变量和函数;

1
2
3
4
5
6
7
8
9
10
var box = 'blue';
function setBox(){
function setColor(){
var b = 'orange';
alert(box);
alert(b);
}
setColor(); // setColor()的执行环境在setBox()内;
}
setBox();

PS:每个函数被调用时都会创建自己的执行环境;当执行到这个函数时,函数的环境就会被推到环境栈中去执行,而执行后又在环境栈中弹出(退出),把控制权交给上一级的执行环境。
PS:当代码在一个环境中执行时,就会形成一种叫做作用域链的东西;它的用途是保证对环境中有访问权限的变量和函数进行有序访问;作用域链的前端,就是执行环境的变量对象;

7.延长作用域链

有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除;
with语句:会将指定的对象添加到作用域链中;
catch语句:会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明

1
2
3
4
5
6
7
function buildUrl(){
var qs = '?debug=true';
with(location){ // with语句接收的是location对象,因此变量对象中就包含了location对象的所有属性和方法;
var url = href+qs; // 而这个变量对象被添加到了作用域链的前端;
};
return url;
}

8.没有块级作用域

块级作用域:表示诸如if语句等有花括号封闭的代码块,所以,支持条件判断来定义变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
if(true){                                 // if语句代码块没有局部作用域;
var box = 'lee'; // 变量声明会将变量添加到当前的执行环境(在这里是全局环境);
}
alert(box);

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:变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜索作用域链;