JS基础:变量、作用域、闭包
1 作用域
- 在 JavaScript 中, 对象和函数同样也是变量。
- 在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
作用域分类:
- 全局作用域
- 局部作用域
1.1 全局作用域
全局变量有 全局作用域: 网页中所有脚本和函数均可使用
函数内部可以直接读取全局变量
全局变量是 window 对象: 所有数据变量都属于 window 对象
1.2 局部作用域
变量在函数内声明,只能在函数内部访问,即局部变量
函数参数也是局部变量
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量
1:函数内部可以读取全局变量
var n=999;
function f1(){
alert(n);
}
f1(); // 999
2:在函数外部自然无法读取函数内的局部变量
function f1(){
var n=999;
}
alert(n); // error
3:如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量
function f1(){
n=999;
}
f1();
alert(n); // 999
1.3 无块级作用域
4:可能导致内部变量可能覆盖外层变量
var i = 5;
function func(){
console.log(i);
if(true){
var i = 6;
}
}
func();//undefined
5:局部变量可能泄露为全局变量
for(var i = 0; i < 10; i++){
console.log(i);
}
console.log('i',i);//10
1.4 什么是变量
变量包括两种,普通变量和函数变量。
-
普通变量:凡是用var标识的都是普通变量
比如下面 :
var x=1; var object={}; var getA=function(){}; //以上三种均是普通变量,但是这三个等式都具有赋值操作。所以,要分清楚声明和赋值。声明是指 var x; 赋值是指 x=1;
-
函数变量:函数变量特指的是下面的这种,fun就是一个函数变量。
function fun(){} ;// 这是指函数变量. 函数变量一般也说成函数声明。
类似下面这样,不是函数声明,而是函数表达式
var getA=function(){} //这是函数表达式 var getA=function fun(){}; //这也是函数表达式,不存在函数声明。关于函数声明和函数表达式的区别,详情见javascript系列---函数篇第二部分
什么是变量声明?
- 变量有普通变量和函数变量,所以变量的声明就有普通变量声明和函数变量声明。
-
普通变量声明
var x=1; //声明+赋值 var object={}; //声明+赋值
上面的两个变量执行的时候总是这样的
var x = undefined; //声明 var object = undefined; //声明 x = 1; //赋值 object = {}; //赋值
关于声明和赋值,请注意,声明是在函数第一行代码执行之前就已经完成,而赋值是在函数执行时期才开始赋值。所以,声明总是存在于赋值之前。而且,普通变量的声明时期总是等于undefined.
-
函数变量声明
函数变量声明指的是下面这样的:
function getA(){}; //函数声明
声明提前到什么时候?
- 所有变量的声明,在函数内部第一行代码开始执行的时候就已经完成。见1.5
1.5 函数作用域
一个函数在执行时所用到的变量无外乎来源于下面三种:
-
函数的参数----来源于函数内部的作用域
-
在函数内部声明的变量(普通变量和函数变量)----也来源于函数内部作用域
-
来源于函数的外部作用域的变量,放在1.3中讲。
var x = 1;
function add(num) () {
var y = 1;
return x + num + y; //x来源于外部作用域,num来源于参数(参数也属于内部作用域),y来源于内部作用域。
}
函数作用域的创建步骤:
-
函数形参的声明。
-
函数变量的声明
-
普通变量的声明。
-
函数内部的this指针赋值
-
.....函数内部代码开始执行!
需要强调:
-
函数形参在声明的时候已经指定其形参的值
function add(num) { var num; console.log(num); //1 } add(1);
-
在第二步函数变量的生命中,函数变量会覆盖以前声明过的同名声明
function add(num1, fun2) { function fun2() { var x = 2; } console.log(typeof num1); //function console.log(fun2.toString()) //functon fun2(){ var x=2;} } add(function () { }, function () { var x = 1 });
-
在第三步中,普通变量的声明,不会覆盖以前的同名参数
function add(fun,num) { var fun,num; console.log(typeof fun) //function console.log(num); //1 } add(function(){},1);
其实这是因为js允许重复声明,新声明的东西没赋值,就直接忽略了,可以参考以下
function add(fun,num) { var fun=1,num; console.log(typeof fun) //number console.log(num); //1 } add(function(){},1);
-
一个小例子,检查一下理解了吗?
function getA() { if (false) { var a = 1; } console.log(a); //undefined } getA();
2 作用域链
当声明一个函数时,局部作用域一级一级向上包起来,就是作用域链
-
函数中,变量先从局部作用域找,未找到,则去上一层局部作用域找,没有,则去全局作用域找,全局未找到,则报错;
-
当前作用域没有定义的变量,即为‘自由变量’
var a = 100;
function fn(){
var b =201;
console.log('a',a);//a 自由变量
console.log('b',b);
console.log('c',c);
}
fn();
3 闭包
通过作用域链,我们知道内部变量可以很容易地访问外部变量,但是外部变量有没有办法访问内部变量呢?——当然是有的,通过闭包!
3.1 闭包的概念
闭包的概念:有权访问另一个作用域的函数。
这句话就告诉我们,第一,闭包是一个函数。第二,闭包是一个能够访问另一个函数作用域。
也就是解决了,如何从外部访问局部变量
构建闭包3步骤:
-
使用外层函数封装受保护的局部变量 和 专门操作变量的内层函数
-
外层函数将内层函数返回(return)到外部
-
在全局调用外层函数,获得内部函数的对象,保存在全局变量中反复使用
例子:
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
3.2 闭包的作用
- 实现私有成员
- 保护命名空间
- 避免污染全局变量
- 变量需要长期驻存在内存
3.3 闭包的缺点
3.3.1 变量污染
var funB,
funC;
(function() {
var a = 1;
funB = function () {
a = a + 1;
console.log(a);
}
funC = function () {
a = a + 1;
console.log(a);
}
}());
funB(); //2
funC(); //3.
对于 funB和funC两个闭包函数,无论是哪个函数在运行的时候,都会改变匿名函数中变量a的值,这种情况就会污染了a变量。
两个函数的在运行的时候作用域如下图:
解决方法:
既然外部作用域链上的变量时静态的,那么将外部作用域链上的变量拷贝到内部作用域不就可以啦!! 具体怎么拷贝,当然是通过函数传参的形式啊。
var funB,funC;
(function () {
var a = 1;
(function () {
funB = function () {
a = a + 1;
console.log(a);
}
}(a));
(function (a) {
funC = function () {
a = a + 1;
console.log(a);
}
}(a));
}());
funB()||funC(); //输出结果全是2 另外也没有改变作用域链上a的值。
3.3.2 内存泄漏
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除
4 几道例题
例1:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(name)
alert(object.getNameFunc()());
//The Window
//The Window
相当于在全局作用域下调用return this.name
,返回的是The Window
例2:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
name = "My Object"
return this.name;
};
}
};
alert(name)
alert(object.getNameFunc()());
//The Window
//My Object
name = "My Object"
修改了全局变量name的值
例3:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
//My Object
object.getNameFunc()
时,this是object,that变量被赋值了object,最后在全局调用时return object.name
例4:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
var that = this;
return that.name;
};
}
};
alert(object.getNameFunc()());
// The Window
在例3的基础上改了一下,这时候that是调用时候的this,也就是window,所以返回的是全局变量
例5:
var name = "The Window";
function f1(){
var name = "My Function"
return function(){
alert(name);
}
}
var res = f1()
res()
// My Function
简单的局部变量