第二章 函数高级

1 执行上下文和执行栈

1.1 执行上下文

全局执行上下文
    ① 在执行全局代码前将window确定为全局执行上下文
    ② 对全局数据进行预处理
        var定义的全局变量==>undefined, 添加为window的属性
        function声明的全局函数==>赋值(fun), 添加为window的方法
        this==>赋值(window)
     ③ 开始执行全局代码

函数执行上下文:
    ① 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
    ② 对局部数据进行预处理
        形参变量==>赋值(实参)==>添加为执行上下文的属性
        arguments==>赋值(实参列表), 添加为执行上下文的属性
        var定义的局部变量==>undefined, 添加为执行上下文的属性
        function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
        this==>赋值(调用函数的对象)
    ③ 开始执行函数体代码

1.2 执行栈

执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。

当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。

引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。

1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下全局执行上下文
<!--
1. 依次输出什么?
2. 整个过程中产生了几个执行上下文?
-->
<script type="text/javascript">
  console.log('global begin: '+ i); //globa begin undeifined
  var i = 1;
  foo(1);
  function foo(i) {
    if (i == 4) {
      return;
    }
    console.log('foo() begin:' + i);
    foo(i + 1);
    console.log('foo() end:' + i);
  }
  console.log('global end: ' + i); 
</script>

2 作用域和作用域链

① 作用域

作用域就是变量的作用范围

分类:
    全局作用域
    函数作用域(局部作用域)

特点:
    在函数声明的时候就决定了,跟函数在哪里调用没有关系

② 作用域和执行上下文的关系

1. 区别1
  * 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  * 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  * 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
2. 区别2
  * 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  * 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放
3. 联系
  * 上下文环境(对象)是从属于所在的作用域
  * 全局上下文环境==>全局作用域
  * 函数上下文环境==>对应的函数使用域

③ 作用域链

函数内嵌套函数,产生作用域链

在函数内使用某个变量,先从本函数作用域中查找;如果查找不到取上层函数作用域查找;直到全局作用域

3 闭包

① 什么是闭包

1)简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
2)MDN 上面这么说:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

② 如何产生闭包

1)在一个函数 A 内部创建另一个函数 B;
2)函数 B 中引用上层函数也就是函数 A 中的数据(函数 A 声明的变量);
3)函数A 把函数 B 作为返回值返回,或者函数 B 被其他方式引用(如作为事件监听函数)。
function A(){  
  var a = 1,b = 2;  

  function B(){  
    return a+b;  
  }  
  return B;  
}  
var fn = A(); // 返回的是函数B

闭包常见的形式:

  1. 把被嵌套的函数返回
  2. 把被嵌套的函数作为回调函数(事件的回调函数、定时器)

③ 闭包的作用(影响)

1. 函数内部的变量长时间存储在内存中(被内部的函数引用了),延长了局部变量的声明周期
2. 在函数的外部操作(读写)函数内部的数据(变量\函数)

④ 闭包与作用域

1) 函数外不能引用函数内的变量,在函数外无法使用 A 中的变量 a 和 b;
2) 函数 B 可以引用上层作用域中的变量 a 和 b;
3) 函数 A 调用结束后的返回值赋值给了 fn,通过调用 fn 我们就间接在函数外部调用了 A 中的变量 a 和 b。

⑤ 闭包与垃圾回收机制

1)在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
2)通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
3)但是,在创建了一个闭包以后,及时函数执行结束,函数作用域内的变量也不会被销毁;
4)以上面代码的函数 A 为例,由于A 最后返回了函数 B,而B中引用了 A中的变量a和变量b,在 A 调用结束后,由于返回值中引用了 a 和 b(引用关系存在不会触发垃圾回收机制),所以a和b都不会被销毁。

⑥ 闭包:定义js模块

JS模块:具有特定功能的文件
把模块内所有的数据和功能封装在一个函数内(私有的)
模块向外暴露一个对象,对象中想要对外的方法和属性

暴露方式: ①直接return  ②作为window的属性

⑦ 闭包的缺点

函数闭包之后,里面的变量会长时间存在于内存中,可能会造成内存泄漏

解决方案:
    闭包能不用不用
    减少对闭包引用次数
    及时释放,把引用闭包的对象变为null(垃圾)

⑧ 相关面试题

题目 1
/*
   说说它们的输出情况
*/

//代码片段一
var name = "The Window";
var object = {
  name: "My Object",
  getNameFunc: function () {
    return function () {
      return this.name;
    };
  }
};
console.log(object.getNameFunc()());  

//代码片段二
var name2 = "The Window";
var object2 = {
  name2: "My Object",
  getNameFunc: function () {
    var that = this;
    return function () {
      return that.name2;
    };
  }
};
console.log(object2.getNameFunc()()); //? MyObject
题目 2
/*
   说说它们的输出情况
   */

function fun(n, o) {
  console.log(o);
  return {
    fun: function (m) {
      return fun(m, n)
    }
  }
}
var a = fun(0);  
a.fun(1);           
a.fun(2);           
a.fun(3);           

var b = fun(0).fun(1).fun(2).fun(3);

var c = fun(0).fun(1);  
c.fun(2);  
c.fun(3);

results matching ""

    No results matching ""