变量的作用域
通常来说,指一段代码中所用到的名字的可用性的代码范围就是该名字的作用域。(代码名字(变量)在某个范围内起作用和效果)
作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
JavaScript(es6前)中的作用域分为两种:
- 全局作用域:作用于整个script标签或者是一个单独的js文件
- 局部作用域(函数作用域):代码名字只在函数内部起作用和效果,跟函数有关系。
JS没有块级作用域(在ES6之前):块作用域由 { } 包括,比如 if { } ; for { } 。因此 if 和 for 中定义的变量在外侧可以随意使用
if(true){
var num = 123;
console.log(num); //123
}
console.log(num); //123
全局变量:(在函数外部定义的变量)
- 在代码任何位置都能使用
- 在全局作用域下var声明的变量
- 特殊情况下,在函数内不使用var声明的变量也是全局变量(不建议使用)
局部变量:(在函数内部定义的变量)
- 只能在函数内部使用
- 在函数内部var声明的变量
- 函数的形参实际上就是局部变量
全局变量和局部变量的区别:
- 全局:任何地方都能使用,只有在浏览器关闭时才会被销毁,比较占内存
- 局部:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块结束运行后,会被销毁,更节省内存空间
作用域链:采取就近原则来查找变量最终的值
- 只要是代码,就至少有一个作用域
- 写在函数内部的局部作用域
- 如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
- 根据内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链
闭包
所谓“闭包”指的就是有权访问另一个函数作用域内变量(局部变量)的函数
主要用途:
- 可以在函数外部读取函数内部的变量
- 可以让变量的值始终保持在内存中
- 可以避免使用全局变量,防止全局变量污染
- 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,并且互不干扰。
闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。大概意思就是说当外部函数运行结束甚至销毁时,局部的变量key=value,尽管key被垃圾回收机制给回收了,但是value仍不会被回收,会变成一个自由变量留下引用的指针。
注意:由于闭包会使得函数的变量一直被保存在内存中,内存消耗很大,所以闭包的滥用可能会降低程序的处理速度,造成内存消耗等问题
一句话概括:闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放
示例1
var age = 10;
function foo(){
console.log(age);//-----------------1
var name = "hunt_bo";
return function(){
console.log(name);
}
}
console.log(name);//---------------2
var bar = foo();
bar();
首先,是函数内部可以访问到全局变量的值,所以在foo()函数中去打印age是可以打印出来的,就像代码中的标记1,但是在标记2处打印的函数内部的变量name是打印不出来的。
但是当我们用到闭包的时候,在foo函数内部返回一个函数,在返回的函数中去用到局部变量name,最后在外部调用,这时候的bar仍然在函数外部,但是完全可以拿到局部变量的name值。这就是闭包的定义,同样也是闭包的第一个特点。
示例2
function addCount(){
var count = 0;
return function(){
count += 1;
console.log(count);
}
}
var fun1 = addCount();
var fun2 = addCount();
fun1();//1
fun1();//2
fun1();//3
fun2();//1
fun2();//2
这个例子跟第一个差不多的,在返回函数中做了一个递增并打印的操作,把addCount的返回函数给到fun1和fun2,当像上面代码一样调用的时候,就会发现替他打印的不是12345,而是12312,原因就回到了上边介绍的那句话:每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。所以说虽然fun1和fun2都是addCount(),但是都创建了新地址,都是自己的,互不干扰。
示例3
function fnnn(){
var arr = [];
for(var i = 0;i < 5;i ++){
arr[i] = function(){
return i;
}
}
return arr;
}
var list = fnnn();
for(var i = 0,len = list.length;i < len ; i ++){
console.log(list[i]());
}
预解析
JS引擎/解释器运行JS代码时分为两步:预解析和代码执行
- 预解析:执行代码前,JS引擎会把JS里所有var和function声明的变量提升到当前作用域的最前面
- 代码执行:按照书写顺序从上往下执行
预解析只发生在通过 var 定义的变量和 function 上。学习预解析就能知道为什么在变量声明之前访问变量的值是 undefined,为什么在函数声明之前就可以调用函数。
预解析也叫变量、函数提升。
- 变量预解析(变量提升):就是把所有的变量声明提升到当前作用域最前面,不提升赋值操作
- 函数预解析(函数提升):就是把所有的函数声明提升到当前作用域的最前面,不调用函数
预解析案例
变量预解析:
console.log(num)
var num=10
//以上两行相当于执行以下代码
var num
console.log(num)
num=10
执行结果为:undefined
fun()
var fun=function(){
console.log(22)
}
//相当于执行以下代码
var fun
fun()
fun=function(){
console.log(22)
}
//函数表达式 调用必须写在函数表达式下面
执行报错:fun is not a function
函数预解析:
fn()
function fn(){
console.log(11)
}
//相当于执行了
function fn(){
console.log(11)
}
fn()
执行结果为:11
变量预解析和函数预解析综合:
var num=10
fun()
function fun() {
console.log(num)
var num=20
}
//相当于执行以下代码
var num
function fun(){
var num
console.log(num) //根据作用域链就近原则,输出结果为undefined
num=20
}
num=10
fun()
执行结果为:undefined 20
f1()
console.log(c)
console.log(b)
console.log(a)
function f1() {
var a = b = c = 9 //与var a=9,b=9,c=9 集体声明不同
console.log(a)
console.log(b)
console.log(c)
}
//相当于执行以下代码
function f1() {
var a
a=b=c=9
// b和c直接赋值,没有var声明,相当于 全局变量
//如果要集体声明,则应该var a = 9, b = 9, c = 9;
console.log(a) //输出结果:9
console.log(b) //9
console.log(c) //9
}
f1()
console.log(c) //9
console.log(b) //9
console.log(a) //报错:a is not defined
执行结果为:9 9 9 9 9 报错