变量的声明周期
- 默认作用域消失时,变量就会被回收
- 如果变量被引用了,则不会被回收
var n = 1;
fn(){
var a = {name: 'a'};
var b = {name: 'b'};
window.xxx = b ;
}
fn();
局部变量在浏览器执行的时候才会被赋值,当函数执行完成之后变量所占的内存被回收,如果函数重新被调用,则创建一个新的变量;
如上代码,a在fn执行完成之后内存就回收。全局变量n则会在页面刷新或者关闭的时候内存被回收。b在函数执行完之后内存不会被回收,因为b被引用了,当b不在被引用时(window.xxx指向别的作用域时),b所占的内存则被回收;
作用域
每个函数都有自己的作用域,如果不想一个变量被其他作用域干扰,则可以自己创造一个作用域,将变量放在里面。作用域可以避免变量冲突,所以尽量不要使用全局变量。
var a
function fn(){
var a
function fn2(){
var a;
fn3()
a = 1;
}
}
function fn3(){
a = 2;
}
这段代码中,很明显能看出a = 1
指向的是fn2这个函数的变量a,浏览器执行到a=1
这行代码时,会在当前作用域下找变量a,如果找不到,就会往它的父级去找,函数也是一样,这里的fn3()里面a=2
指的是全局的a,并不是fn2()里面的a;
立即执行函数
(function (){
var a = 1 ;
console.log
})()
//可以传递参数
var a = 100
!function (a){
console.log(a) //100
}(a) //立即执行函数传参在这里传;
这是一个立即执行函数,这样的写法主要是为了创造一个作用域来避免全局变量,立即执行函数可以使用! - + () ~
等符号,
变量提升
浏览器在执行代码之前,会先把所有的声明提升到作用域的顶部,例如以下代码:
var a = 100;
function a(){}
console.log(a)
//你觉得会打印出什么呢,是100还是function;
//手动提升一下变量
var a
function a(){}
a=100
console.log(a);
//最终的结果为100;
接下来举一个复杂点的例子
var a = 100;
function fn(){
var b = 2;
if(b === 1){
var a = 2;
}
console.log(a);
}
fn();
//上面这段代码,控制台会输出什么,100?not defined? undefined?
//让我们来手动提升一下变量
var a;
function fn(){
var b;
var a;
b = 2;
if(b === 2){
a = 2;
}
console.log(a);
}
a = 100;
//不言而喻,这里输出的是undefined;
//所以,遇到代码一定要手动提升变量!
//手动提升变量!!
//手动提升变量!!!
执行时机(异步)
btn.onclick = function (){
console.log(1)
}
console.log(2)
如上代码,控制台会先输出2
,然后在用户点击之后才会输出1
,如果用户不点击,那么永远都不会输出1
。
闭包
掌握了前面的知识,那么闭包就好理解了,
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>闭包</title>
<style>
li{
list-style:none;
border: 1px solid #ccc;
padding: 10px 7px;
cursor: pointer;
}
</style>
</head>
<body>
<ul>
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
<li>选项5</li>
<li>选项6</li>
</ul>
<script>
var items = document.querySelectorAll('li');
for(var i = 0; i < items.length; i++){
items[i].onclick = function (){
console.log(i);
}
}
</script>
</body>
</html>
这段代码本意是想点击选项1这个li
的时候控制台输出0,点击选项2这个li
的时候控制台输出1,以此类推,但是这样写不管点击哪个li
,控制台输出的都是6,为什么呢,先提升变量
var items
var i
items = document.querySelectorAll('li');
for(i = 0; i < items.length; i++){
// i == 0,1,2,3,4,5
items[i].onclick = function (){
console.log(i);
}
}
//这里我们再看一下i的值
console.log(i) //i 为 6;
//很明显,如果此时再去点击li,i就是6,因为i是一个全局变量,每次点击指向的值都是这个 i 。
既然了解了问题的所在,我们就可以着手解决这个问题了,只需要创造出一个作用域让i分别等于0,1,2,3,4,5;
var items = document.querySelectorAll('li');
for(var i = 0; i < items.length; i++){
var temp = function (j){
items[j].onclick = function (){
console.log(j);
}
}
//在循环中,每次j都等于不同的i,
temp(i);
}
上面的代码中,temp
明显是一个立即执行函数,所以我们把代码简化一下
var items = document.querySelectorAll('li');
for(var i = 0; i < items.length; i++){
(function (j){
items[j].onclick = function (){
console.log(j);
}
})(i);
}
总结一下,因为 i
是一个全局的变量,所有的点击都在指向了这个 i
而点击的执行时机(异步)很靠后,当等到点击 i
的时候 ,全局i
已经++成了6,所以每次执行就拿不到我们想要的结果 ,通过立即执行函数创造一个新的作用域,在 i 循环的时候将i的值分别赋给 j 来解决这个问题。
闭包总结
闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
一般情况下使用闭包主要是为了:1. 封装数据; 2. 暂存数据。
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露