js作用域 作用域链 闭包

作用域

  • 变量

var声明的变量,其作用域函数体的全部,没有块作用域

let声明的变量拥有块级作用域

1)  局部变量:

函数内声明的变量为局部变量,为局部作用域,只能在函数内访问;

function  studentnum()

{

     var nums = 10;

     console.log(nums);

}

studentnum();

console.log(nums);

2)全局变量:

函数外声明的变量为全局变量,具有全局作用域,在声明处后面的网页脚本中均可使用;

var nums = 10;

function  studentnum()

{

     console.log(nums);

}

studentnum();

console.log(nums);

//balabala

function studentnum() {

num = 50;

console.log(num);

}

studentnum();

console.log(num);

 

//balabala

     function studentnum() {

          num = 50;

          console.log(num);

          }

         num=100;

         studentnum();

         console.log(num);

如果变量在函数内没有使用var进行声明,则默认为全局变量(前提是函数必须执行了):

 

3)  变量声明周期:

变量在声明时初始化;

局部变量在函数执行完后销毁;

全局变量在页面关闭后销毁;

二、函数

1.函数基本定义:

function functionname()
{
执行代码
}

2.匿名函数

function(a){return a;}

可用来定义匿名函数来执行某些一次性任务。

3.表达式函数:

函数可以定义为一个表达式储存在变量中;

var y = function (h, q){return h%q;}

document.write(y(3, 4));

这样就定义了一个匿名函数并保存在变量中了;

4.自调用函数:

自调用的方法是

1.匿名函数自己被小括号抱起来();

2.在函数后面紧跟小括号()调用;

表达式函数可以自调用,声明的函数不能自调用;

(function (){

var h = '专业始于专注';

document.write(h);})(); //正常执行

 

function fnName(){

  console.log(123);

}() // 报错

 

var fnName = function () {

  console.log(123);

}() // 正常执行

 

5.使用关键词Function来new一个函数:

var  fun1 = new Function('var h = 3, q = 5; return h + q;');

alert(fun1());

 6.内部函数(私有函数)

function a(param){

  function b(i){

     return i*2;

};

    return b(param)+1;

};

当我们调用a()时,本地函数b()也会在内部被调用。由于b()是本地函数,它在a()以外的地方是看不见的,所以b为私有函数

使用私有函数的好处:

  1. 有助于确保全局名字空间的纯净性(命名冲突的机会减小)
  2. 私有性---可以选择只将一些必要的函数暴露给外部,并且保留属于自己的函数,使它们不被应用程序的其他部分所用

7.返回函数的函数

function func(){

var b=3;

return function(){

   console.log(b);

}

}

var newFunc=func();

newFunc();

函数的生命周期

函数的的生命周期分为创建和执行两个阶段。

  1. 在函数创建阶段,JS解析引擎进行预解析,会将函数声明提前,同时将该函数放到全局作用域中或当前函数的上一级函数的局部作用域中。
  2. 在函数执行阶段,会创建该函数的执行上下文并且JS引擎会将当前函数的局部变量和内部函数进行声明提前,然后再执行业务代码,当函数执行完退出时,释放该函数的执行上下文,并注销该函数的局部变量。

//balabala

var a=123;

function f(){

  console.log(a);

  var a=1;

  console.log(a);

}

f();

JavaScript变量提升和函数提升

1.变量提升:

把变量提升到函数的最开始位置;只是提升变量的声明,不会提升赋值;

function hoistvar()

{

console.log(h);

var h = '专业始于专注';

}

hoistvar();

2.函数提升:

把声明的函数提到前面去;只有声明形式的函数才会提到前面去(字面量形式的不会,字面量就是如何表达这个值,一般等号右边的都认为是字面量,也就是表达式函数);

hoistfun();

function  hoistfun(){

 var a=2;

var b=3;

return a+b;

}

//balala

console.log(a);

var a=2;

function a() {

    console.log(1);

}

 

a = function() {

    console.log(2);

}

a();

//balala

function studentnum() {

        var studentsnum = 50;

        console.log(studentnum);

}

    studentnum();

函数的声明优先于变量声明

  1. 函数提升和变量提升给我们的启示:

在编写程序时,尽量按照提升后的执行顺序写代码,避免不必要的错误和理解误差

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境(执行上下文)有权访问的所有变量和函数的有序访问。

变量对象(VO):变量对象即包含变量的对象,变量对象我们无法访问,除此之外和普通对象没什么区别。变量对象存储了在上下文中定义的变量和函数声明

活动对象(AO):是在进入函数执行环境时刻被创建的,它通过函数的 arguments 属性初始化。

变量对象和活动对象的关系

未进入执行阶段之前,变量对象(VO)中的属性都不能访问,只是声明但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。它们其实都是同一个对象,只是处于执行环境的不同生命周期。AO 实际上是包含了 VO 的。因为除了 VO 之外,AO 还包含函数的 parameters,以及 arguments 这个特殊对象。也就是说 AO 的确是在进入到执行阶段的时候被激活,但是激活的除了 VO 之外,还包括函数执行时传入的参数
和 arguments 这个特殊对象。

执行环境(执行上下文)的组成

当JavaScript代码执行的时候,会进入不同的执行环境(执行上下文),这些执行环境会构成一个执行环境栈

(执行上下文栈) (Execution context stack,ECS)。见下图:
js作用域 作用域链 闭包_第1张图片

执行环境分析

js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。

eg

  var scope = "global";

      function fn1(){

         return scope;

      }

      function fn2(){

         return scope;

      }

      fn1();

      fn2();

js作用域 作用域链 闭包_第2张图片

 

当某个函数第一次被调用时,就会创建一个执行环境(execution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([scope])。然后使用this.arguments(arguments在全局环境中不存在)和其他命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在作用域链的第0位。以上述执行环境分析的小例子为例进行图解:当第一次调用fn1时

js作用域 作用域链 闭包_第3张图片

 

解析:可以看到fn1活动对象里并没有scope变量,于是沿着作用域链(scope chain)向后寻找,结果在全局变量对象里找到了scope,所以就返回全局变量对象里的scope值

function outer(){

         var scope = "outer";

         function inner(){

            return scope;

         }

         return inner;

      }

      var fn = outer();

      fn();

js作用域 作用域链 闭包_第4张图片

 

JavaScript闭包

作用域 私有函数

垃圾回收机制 引用

  1. 函数是可以嵌套函数的,内部function可以访问外部function的变量;
  2. 闭包:通过引用访问函数内的函数,实现信息的驻留;
  • 访问函数内的函数,突破变量作用域限制

var person = function() {

      var personnum = 0; //私有privite,外部不能直接访问

      function getCount() {

           return personnum++;

      }

      return getCount; //返回函数名(public,暴露内部信息 )     

 }

 var p = person();//通过子函数引用,能访问局部变量console.log('count...' + p());

  • 引用在,空间不灭

function  fun() {

     var h = 5;

     return function() {

          console.log(h);

          return h++;

     };

var historys = fun();

historys();

historys();

var historys2 = fun();

historys2();

historys2();

historys2();

fun()();

fun()();

  • 共享封闭空间

function fn() {

     var _num = 0;

     return {

          add1: function() {

                _num++;

                console.log(_num);

           },

          add2: function() {

               _num += 2;

               console.log(_num);

            }

        }

   }

   var c = fn();

   c.add1();

   c.add2();

   c.add1();

  • 快照:创建时备份函数作用域内的所有现场信息

function fun(a) {

  return function(b){

      return function(c){ console.log(a+b+c);  };

  };

}   

var x = fun(1 );

var y = fun(5)(8); 

var z = fun(2)(4);

x(2)(1);

y(2);    //在y快照基础上再运算 5+8+2

z(2);

//

 function f1() {

            var n = 999; 

            nAdd = function () {

                n += 1;

                console.log(n);

            }

            function f2() {

                n++;

                console.log(n);

            }

            return f2;  

        }

        var n = 668;

        console.log(n);

        var result = f1();  

        result();  

        nAdd();

        result();

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

 

你可能感兴趣的:(js作用域 作用域链 闭包)