进阶 1 - 变量提升 & 闭包函数

一. 变量提升(不正常现象)

看以下代码, 或多或少会有些问题的.

function fn(){
    console.log(name);
    var name = '大马猴';
}
fn()

发现问题了么. 这么写代码, 在其他语言里. 绝对是不允许的. 但是在js里. 不但允许, 还能执行. 为什么呢? 因为在js执行的时候. 它会首先检测你的代码. 发现在代码中会有name使用. OK. 运行时就会变成这样的逻辑:

function fn(){
    var name;
    console.log(name);
    name = '大马猴';
}
fn()
console.log(a);

看到了么. 实际运行的时候和我们写代码的顺序可能会不一样....这种把变量提前到代码块第一部分运行的逻辑被称为变量提升. 这在其他语言里是绝对没有的. 并且也不是什么好事情. 正常的逻辑不应该是这样的. 那么怎么办? 在新的ES6中. 就明确了, 这样使用变量是不完善的. es6提出. 用let来声明变量. 就不会出现该问题了.

function fn(){
    console.log(name);  // 直接报错, let变量不可以变量提升.
    let name = '大马猴'; 
}
fn()

== 结论一, 用let声明变量是新版本javascript提倡的一种声明变量的方案.==

let还有哪些作用呢?

function fn(){
    // console.log(name);  // 直接报错, let变量不可以变量提升.
    // let name = '大马猴';
    var name = "周杰伦";
    var name = "王力宏";
    console.log(name);
}
fn()

显然一个变量被声明了两次. 这样也是不合理的. var本意是声明变量. 同一个东西. 被声明两次. 所以ES6规定. let声明的变量. 在同一个作用域内. 只能声明一次.

function fn(){
    // console.log(name);  // 直接报错, let变量不可以变量提升.
    // let name = '大马猴';
    let name = "周杰伦";
    console.log(name);
    let name = "王力宏";
    console.log(name);
}
fn()

注意, 报错是发生在代码检查阶段. 所以. 上述代码根本就执行不了.

==结论二, 在同一个作用域内. let声明的变量只能声明一次. 其他使用上和var没有差别==

二. 闭包函数

我们先看一段代码.

let name = "周杰伦";
function chi(){
    name = "吃掉";
}
chi();
console.log(name);

发现没有, 在函数内部想要修改外部的变量是十分容易的一件事. 尤其是全局变量. 这是非常危险的. 试想, 我写了一个函数. 要用到name, 结果被别人写的某个函数给修改掉了. 多难受.

接下来. 我们来看一个案例:

同时运行下面两组代码:

// 1号工具人.
var name = "alex"

setTimeout(function () {
    console.log("一号工具人:" + name) // 一号工具人还以为是alex呢, 但是该变量是不安全的.
}, 5000);
// 2号工具人
var name = "周杰伦"
console.log("二号工具人", name);

两组代码是在同一个空间内执行的. 他们拥有相同的作用域. 此时的变量势必是非常非常不安全的. 那么如何来解决呢? 注意, 在js里. 变量是有作用域的. 也就是说一个变量的声明和使用是有范围的. 不是无限的. 这一点, 很容易验证.

function fn() {
    let love = "爱呀"
}

fn()
console.log(love)

直接就报错了. 也就是说. 在js里是有全局和局部的概念的.

直接声明在最外层的变量就是全局变量. 所有函数, 所有代码块都可以共享的. 但是反过来就不是了. 在函数内和代码块内声明的变量. 它是一个局部变量. 外界是无法进行访问的. 我们就可以利用这一点来给每个工具人创建一个局部空间. 就像这样:

// 1号工具人.
(function () {
    var name = "alex";
    setTimeout(function () {
        console.log("一号工具人:" + name) // 一号工具人还以为是alex呢, 但是该变量是不安全的.
    }, 5000);
})();
// 二号工具人
(function () {
    var name = "周杰伦"
    console.log("二号工具人", name);
})();

这样虽然解决了变量的冲突问题. 但是...我们想想. 如果在函数外面需要函数内部的一些东西来帮我进行相关操作怎么办...比如, 一号工具人要提供一个功能(加密). 外界要调用. 怎么办?

// 1号工具人.
let encrypt_tool = (function () {
    let log_msg = '开始加密......\n'
    // 我是一个加密函数
    let encrypt = function (data) { // 数据
        console.log(log_msg) //打印日志信息(访问外部变量)
        // 返回密文
        return atob(data);
    }
    // 外面需要用到这个功能啊. 你得把这个东东返回啊. 返回加密函数
    return encrypt;
})();

//外部调用
console.log(encrypt_tool('i love you'));

注意了. 我们如果封装一个加密js包的时候. 好像还得准备出解密的功能. 并且, 不可能一个js包就一个功能吧. 那怎么办? 我们可以返回一个对象. 对象里面可以存放好多个功能. 而一些不希望外界触碰的功能. 就可以很好的保护起来.

// 1号工具人.
let encrypt_tool = (function () {
    let log_msg_1 = '开始加密......'
    let log_msg_2 = '开始解密......'
    
    // 我是一个加密函数
    let encrypt = function (data) {// 被加密数据
        console.log(log_msg_1) //打印日志信息(访问外部变量)
        // 返回密文
        return atob(data);
    };
    //解密函数
    let decrypt = function (en_data) {// 加密后的数据
        console.log(log_msg_2) //打印日志信息(访问外部变量)
        // 返回解密后的原数据
        return btoa(en_data);
    };
    // 外面需要用到这个功能啊. 你得把这个东东返回啊. 返回加密函数
    return {'encrypt': encrypt, 'decrypt': decrypt};
})();

//外部调用
en_data = encrypt_tool.encrypt('i love you');
de_data = encrypt_tool.decrypt(en_data)
console.log(en_data);
console.log(de_data);

OK. 至此. 何为闭包? 上面这个就是闭包. 相信你百度一下就会知道. 什么内层函数使用外层函数变量. 什么让一个变量常驻内存.等等. 其实你细看. 它之所以称之为闭包~. 它是一个封闭的环境. 在内部. 自己和自己玩儿. 避免了对该模块内部的冲击和改动. 避免的变量之间的冲突问题.

闭包的特点:

  1. 内层函数对外层函数变量的使用.

  2. 会让变量常驻与内存.

Last updated