前言:
按照ECMA-262的定义,JavaScript的变量与其他语言的变量有很大的区别。JavaScript变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须保存何种数据类型值得规则,变量的值以及数据类型可以在脚本的生命周期内发生改变。尽管从某种角度看,这可能是一个既有趣又强大,同时又容易出现问题的特性,但JavaScript变量实际的复杂程度还远不止如此。
目录大纲
一.基本类型和引用类型的值
1.赋值变量的值
2.传参
3.检测类型
1.操作符typeof
2.操作符instanceof
二.执行环境以及作用
1.arguments介绍
2.延长作用域链
1.with语句
2.try-catch语句
3.js没有块级作用域
三.垃圾收集机制
1.标记清除
2.引用计数
一.基本类型和引用类型的值
变量包括两种不同的数据类型的值
1.基本类型指的是简单的数据段,包括Undefined、Null、Boolean、Number和String。
2.引用类型指的是保存在内存中,由多个值组成的对象。
创建对象举例
var person = new Object();
person.name = "Nicholos";
注意:
1.Object的‘O’一定大写。
2.直接增加属性并且赋值。
3.只能给引用类型才能添加属性,是动态地添加属性。
1.赋值变量的值
基本类型的赋值方法与C语言相同,会增加一个新的空间用于存放副本,变量保存在栈内存中。
var num1= 5;
var num2=num1;
引用类型的值则不同,引用类型的值是保存在堆内存对象中,JS不允许直接访问内存中的位置,所以引用类型的值是按照引用类型访问的。当一个变量复制到另一个变量中,副本其实是一个指针,两个对象指的是同一个对象。
2.传参
传参就是把变量的值复制给函数内部的变量,是复制值的过程,永远满足值传递的关系。
引用类型也是复制一个新的指针,实际上指的还是同一个对象。
function setName(obj){
obj.name ="Ac";
obj = new Object();
obj.name = "Bc";
}
var person =new Object();
setName(person);
//person.name ="Ac";
一开始函数内的obj是和person指向同一个对象的,但是obj新声明了一个对象,引用类型的值不再是指向原来的对象的值,而且函数运行完后被销毁。
3.检测类型
1.操作符typeof
var s="Nicholas";
var b= true;
var i =22;
var u;
var n= null;
var o= new Object();
alert(typeof s); //string
alert(typeof b); //boolean
alert(typeof i); //number
alert(typeof u); //undefine
alert(typeof n); //object
alert(typeof o); //object
null和object都是返回object
2.操作符instanceof
alert(person instanceof Object);// person 是Object吗?
alert(colors instanceof Array );// colors 是Array吗?
alert(pattern instanceof RegExp);// pattern 是RegExp吗?
是的话,就返回一个true,不是的话就返回一个false。
二.执行环境以及作用
执行环境(简称 环境)定义了变量和函数有权访问的其他的数据。每一个环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
全局环境是一个最外围的执行环境,web中一般认为全局环境是window对象,全局环境会一直运行到应用退出它所定义的函数和变量才会被销毁。
每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,函数执行完后,栈将环境弹出,把控制权返回给之前的执行环境。
当函数在一个环境中执行的时候,会创建一个作用域链,是为了保证对执行环境有权访问的所有变量和函数有序的访问,作用域链的前端,始终都是当前执行代码所在环境中变量对象。如果环境是函数,将其活动对象作为变量对象。活动对象最开始只包含一个变量-----arguments(arguments在全局环境中不存在)对象,作用域链的下一个变量对象来自包括外部的环境,一直延续到全局执行环境。
1.arguments介绍
arguments是JS中每个函数(除全局环境外)都有的类数组对象,用于储存函数传入的参数。
- arguments.length 记录参数个数。
- arguments.callee 储存调用这个函数的代码。
- arguments可以实现函数的重载。
function add() {
var len = arguments.length,
sum = 0;
for(;len--;){
sum += arguments[len];
}
return sum;
}
console.log( add(1,2,3) ); //6
console.log( add(1,3) ); //4
console.log( add(1,2,3,5,6,2,7) ); //26
标识符解析是沿着作用域链一级一级的搜索标识符的过程,从作用域链的前端开始,逐级地向后回溯,直到找到标识符为止。
搜索标识符举例:
var scope = "global"; //全局
function fn1(){
return scope;
}
fn2();
函数嵌套的作用域链举例:
<script>
function outer(){
var scope = "outer";
function inner(){
return scope;
}
return inner;
}
var fn = outer();
fn();
</script>
说明:当调用outer函数,inner函数的作用域链已经初始化了(即复制父函数的作用域链,再在前端插入自己的活动对象)
2.延长作用域链
1.with语句:
作用:接收一个对象,将指定对象添加到作用域链中。
with语句使用方法:
with(people)
{
var str = "姓名: " + name + "<br>";
str += "年龄:" + age + "<br>";
str += "性别:" + gender;
document.write(str);
}
优点是不用多次写对象的名字,在代码块里直接使用对象的值,但是注意with语句内不能改变对象的属性,改变对象的属性还是要引用对象来改变。
2.try-catch语句:
作用:catch语句产生一个新对象添加到作用域链的前端,主要用来检测代码中的错误。
try-catch的用法:
try{
//可能会导致出错的代码
}catch(error){
alert("catch an error");
}
当try运行的代码运行出现错误的时候,就会立即退出代码进程,执行catch语块,catch接收到一个包含错误对象的信息,对象下的message是包含错误信息,对象的名字要自己起。
3.JS没有块级作用域
花括号的边界表示一个块,但是在JS中,花括号封闭的的代码中声明的变量会添加到当前的执行环境。
if(true){
var color="blue";
}
alert(color); //blue
用关键字let让生命的变量只在块作用域内有效。
for(let i=0;i<5;i++){
//操作
}
alert(i); //ReferenceError: i is not defined
三.垃圾收集机制
JS中会自动找出不再使用的变量,释放其占用的内存,按照固定是时间间隔,周期性地重复操作。
1.标记清除
当变量进入环境的时候就标记“进入环境”,当环境离开环境的时候就标记为“离开环境”。
2.引用计数
引用计数是根据每个变量使用的次数。
1.声明一个变量并且将引用类型的值赋值给该变量,这个值的引用次数加一。
2.同一个值赋值给另一个变量,该值的引用次数加一。
3.包含这个值的引用变量又取得了另外一个值,该值的引用次数减一。
4.该值的引用次数变成0时,等待垃圾收集器运行时释放该值所占的内存。
但是此机制有弊端:
function problem(){
var a =new Object();
var b =new Object();
a.some = b;
b.some = a;
}
例子中的两个对象通过各自的属性相互引用,两个对象的引用次数都是2,不会是0。
参考文献: