JS基础:一文搞懂变量、作用域和闭包

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. 函数的参数----来源于函数内部的作用域

  2. 在函数内部声明的变量(普通变量和函数变量)----也来源于函数内部作用域

  3. 来源于函数的外部作用域的变量,放在1.3中讲。

var x = 1;
function add(num) () {
  var y = 1; 
  return x + num + y;   //x来源于外部作用域,num来源于参数(参数也属于内部作用域),y来源于内部作用域。
}

函数作用域的创建步骤:

  1. 函数形参的声明。

  2. 函数变量的声明

  3. 普通变量的声明。

  4. 函数内部的this指针赋值

  5. .....函数内部代码开始执行!

需要强调:

  • 函数形参在声明的时候已经指定其形参的值

    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 作用域链

当声明一个函数时,局部作用域一级一级向上包起来,就是作用域链

  1. 函数中,变量先从局部作用域找,未找到,则去上一层局部作用域找,没有,则去全局作用域找,全局未找到,则报错;

  2. 当前作用域没有定义的变量,即为‘自由变量’

    var a = 100;
    function fn(){
      var b =201;
      console.log('a',a);//a 自由变量
      console.log('b',b);
      console.log('c',c);
    }  
    fn();

image-20210107103039977

3 闭包

通过作用域链,我们知道内部变量可以很容易地访问外部变量,但是外部变量有没有办法访问内部变量呢?——当然是有的,通过闭包!

3.1 闭包的概念

闭包的概念:有权访问另一个作用域的函数。

这句话就告诉我们,第一,闭包是一个函数。第二,闭包是一个能够访问另一个函数作用域。

也就是解决了,如何从外部访问局部变量

构建闭包3步骤:

  1. 使用外层函数封装受保护的局部变量 和 专门操作变量的内层函数

  2. 外层函数将内层函数返回(return)到外部

  3. 在全局调用外层函数,获得内部函数的对象,保存在全局变量中反复使用

例子:

      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变量。

两个函数的在运行的时候作用域如下图:

image-20210107103926056

解决方法:

既然外部作用域链上的变量时静态的,那么将外部作用域链上的变量拷贝到内部作用域不就可以啦!! 具体怎么拷贝,当然是通过函数传参的形式啊。

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的值。

image-20210107104026779

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

简单的局部变量

参考

学习Javascript闭包(Closure)

JS基础-作用域和作用域链以及闭包

JavaScript系列----作用域链和闭包

JS作用域和作用域链

原文地址:https://www.cnblogs.com/cpaulyz/p/14245210.html