1.作用域
变量起作用的区域或范围:变量的生命周期和能访问到变量的区域
全局作用域:函数体外部,函数内也可以访问,当所有的程序执行完毕才释放(全局变量会污染,尽量少写)
局部作用域:函数作用域,只能在函数体内部访问,函数执行完毕就释放
JS中没有块级作用域{…}
function fn(){
var x = y = 1;
}
fn();
console.log(y);
console.log(x);
结果为1 报错,在函数内部不用var声明的是全局变量,y可以输出
var name = 'xm';
function fn(){
name = 'xh';
console.log(name);
}
fn();
console.log(name);
结果为 xh xh,函数中的name没有使用var定义,会先在函数中查找是否定义了name。如果没有定义,会向上一级作用域也就是全局下查找name。发现全局中已经定义了name,所以函数中name='xh’是为全局变量name重新赋值了。即在全局下输出和在函数中输出结果都是‘xh’。
2.变量对象
所有全局空间的属性和函数,都是window的属性和方法
var name = 'xm';//等价于window.name
function fn(){//等价于window.fn
var sex = 'male';//等价于fn.sex,局部作用域的变量对象看不见摸不着,但JS引擎会用到
function fn2(){//等价于fn.fn2
var age = 18;
}
}
console.log(person);//报错
console.log(window.person);//不存在的属性并不会报错,返回undefined
3.作用域链
var name = 'xm';
function fn(){
var name = 'xh';
var sex = 'male';
function fn2(){
var name = 'xhei';
var age = 18;
}
}
作用域链如下
当在fn2中输出属性时,会先查找自身作用域,如果找到则输出并停止查找,如果未找到则查找上一层作用域,若一直未找到最终会查找window的作用域,即内层可以调用外层的变量,但外层无法调用内层变量
同名变量越内层优先级越高
作用域链越长查询越慢,局部变量优于全局变量,内层优于外层变量
延长作用域链
var person = {};
person.name = 'xm';
person.sex = 'male';
var score = 4;
with(person){
name = 'xh';
sex = 'female';
score = 44;
}
可以通过with增加一个以person为变量对象的小作用域,其内的name相当于person.name,当没有person.score时它会查找外层作用域(with容易出问题,尽量避免使用)
<html>
<head>
<meta charset="utf-8">
<title>变量、作用域</title>
</head>
<body>
<button>1</button>
<button>2</button>
<button>3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button');
for (var i = 0; i < 3; i++) {
btns[i].onclick = function () {
alert(i + 1);
};
}
</script>
</body>
</html>
由于点击按钮触发事件函数,建立一个新的作用域,但此时循环已经结束,i值为3,所以每个按钮弹出的都是4
var btns = document.getElementsByTagName('button');
for (var i = 0; i < 3; i++) {
OOO(i);
};
function OOO(i){
btns[i].onclick = function (){
alert(i+1);
}
}
将点击事件置于单独的函数中,每一次循环都将当前的i值传入函数作用域,当点击事件发生时往上寻找,找到的都是函数内的i值,就可以正常弹出1/2/3
4.JS的解析机制
过程:先进行预解析,再逐行解读代码
预解析(解读代码时函数的声明部分直接跳过):先全局后局部进行,首先查找var,并将所有声明的变量赋值undefined,再查找function关键字,直接对函数初始化。当变量名与函数名冲突时,比如都为name,预解析只保留函数(优先级更高);当两个函数重名时,预解析保留后面那个。
firefox老版本的浏览器无法预解析{…}代码块内的函数。
补充:函数的参数与局部变量同等对待
<script>
console.log(a);
</script>
<script>
var a = 1;
</script>
由于预解析分标签进行,浏览器会报错,如果将两条语句调换位置,则不会有问题(即先声明后打印)
var a = 1;
function fn(){
console.log(a);
a = 2;
}
fn();
console.log(a);
由于函数内部没有预解析,所以执行到函数调用时打印出的是全局变量的值!但若函数带有参数,函数内部就有预解析。
5.垃圾收集机制
释放无用的数据,回收内存
分为自动、手动收集
JS有自动回收机制
原理:找出没用的数据,打上标记,释放其内存(垃圾收集器按照一定时间间隔周期性执行)
标识无用数据的策略:
标记清除:一次性为所有变量都打上标记,去掉环境中的变量(还没有离开执行环境的变量)和被这些变量所引用变量的标记,回收剩下的变量对应的内存(主流方法)
引用计数:跟踪记录每个数据被引用的次数,当值的引用次数变成0时就回收内存(由于循环引用的存在,已经放弃)–IE6-8浏览器依然存在循环引用的问题
6.内存管理
由于分配给Web浏览器的内存少于桌面应用程序的内存,内存需要优化,只保留内存中有用的数据,没用的就释放掉(手动设置为null)
补充:没用var的声明方式,不管在哪里声明,它都是全局变量(不会预解析)