第5章 精通函数:闭包和作用域

1. 理解闭包

  • 闭包允许函数访问并操作函数外部的变量。只要变量或函数存在于声明函数时的作用域内,闭包即可使函数能够访问这些变量或函数
var outerName = "Wango";
var fn;

function outerFn() {
    var innerName = "Lily";

    function innerFn() {
        // 函数定义时可以访问innerName
        // 那么在外部调用的时候也可以访问innnerName
        console.log(outerName);     // Wango
        console.log(innerName);     // Lily
    }
    // 将内部函数暴露给外部变量
    fn = innerFn;
}

outerFn();

fn();

只要内部函数一直存在,内部函数的闭包就一直保存着该函数的作用域中的变量和函数

所以闭包的存在会一直占据内存空间,导致内存泄露

2. 使用闭包

2.1 封装私有变量

function Counter() {
    // 在构造函数内定义私有变量,外部无法直接访问
    var num = 0;
    
    // 用this定义公共变量,外部可通过对象访问
    this.another = 1000;
    
    // 通过闭包让外部可以读取私有变量
    this.getCount = function() {
        return num;
    }

    this.count = function() {
        num++;
    }
}

var counter = new Counter();
counter.count();

// 通过闭包读取私有变量
console.log(counter.getCount());  // 1
// 无法直接访问私有变量
console.log(counter.num);         // undefined
// 可以通过对象访问公共变量
console.log(counter.another);         // 1000

2.2 回调函数

<div id="box" style="position: absolute">This is a box</div>

<script>
    function animateIt(elemIid) {
        // 这三个控制动画的变量定义在函数内部和外部都能达到同样的效果
        // 但是定义在外部会污染全局作用域,需要为每个动画都定义三个变量来控制
        var elem = document.getElementById(elemIid);
        var tick = 0;
        var timer = setInterval(function() {
            if(tick < 100) {
                elem.style.top = elem.style.left = tick + "px";
                tick++;
            }
            else {
                clearInterval(timer);
                console.log(tick);      // 100
                console.log(timer);     // 1
            }
        }, 10);
    }

    animateIt("box");
</script>

闭包不是在创建的那一刻的状态的快照,而是一个真实的状态封装

只要闭包存在,就可以对变量进行修改

2.3 通过执行上下文来跟踪代码

调用函数可以通过关键字this访问函数上下文

执行上下文是内部的JavaScript概念,JS引擎使用执行上下文来跟踪函数的执行

  • JS中使用执行上下文栈(或称调用栈)来跟踪执行上下文

2.4 使用词法环境跟踪变量的作用域

  • 词法环境:是JavaScript引擎内部用来跟踪标识符与特定变量之间的映射关系,是JavaScript作用域的内部实现机制,通常称为作用域

3. 变量与作用域

3.1 JS的变量类型

  • JS中有三个关键字可以定义变量:var、let、const。三者有两点不同:可变性、与词法环境的关系(即作用域不同)

3.2 变量可变性

  • 通过const定义的变量不可变(即不允许重新赋值),通过var或let声明的变量可变
  • const声明的变量在声明时需要写初始值
  • const变量不可变是指标识符(栈)到数据(堆)的指向不可变,而数据(堆)内其他数据同样可以增删查改(如对象内或者数组内的数据)
const PERSON = {};
PERSON.name = "Wango";
console.log(PERSON.name);       // Wango
PERSON.name = "Lily";
console.log(PERSON.name);       // Lily
delete PERSON.name;
console.log(PERSON.name);       // undefined

PERSON = {};        // 指向不可变
// Uncaught TypeError: Assignment to constant variable.

3.3 作用域

  • var函数作用域
  • let、const块级作用域
function fn() {
    var lastName = "Wango";
}
{
    var weight = 50;    // 代码块外可以访问
    let age = 24;       // 代码块外不可访问
    const GENDER = "male";  // 代码块外不可访问 
}

console.log(lastName);
// Uncaught ReferenceError: lastName is not defined
console.log(weight);
// 50
console.log(age);
// Uncaught ReferenceError: age is not defined
console.log(GENDER);
// Uncaught ReferenceError: GENDER is not defined

3.4 var变量和函数声明的提升

  • 对于使用var声明的变量和使用函数声明定义的函数会在代码执行前提升到其所在作用域的顶部进行声明(提前在词法环境中注册),var声明的变量的值为undefined,函数可以正常使用
check(num);

var num = 100;
function check(num) {
    console.log("Checked: " + num);     // Checked: undefined
}

// 上面代码等价于
function check(num) {       // 函数声明会被是提升到var变量声明之前
    console.log("Checked: " + num);     // Checked: undefined
}
var num;


check(num);
num = 100;

使用let或const声明的变量、使用函数表达式(非var声明)和箭头函数没有这种现象,需要先声明后使用

原文地址:https://www.cnblogs.com/hycstar/p/14008547.html