作用域和闭包

164 阅读2分钟

1. 作用域

1.1 执行上下文

  • 执行上下文,作用域链用定义时的

//  执行上下文

function outer() {
     /*
        executionContext = {
            scopeChain: {},
            variableObject: {   // 初始化变量 函数 形参
                age: undefined,
                arguments:[]
            },
            this: {}
        }
    */
    var age = 18;
    function demo(num) {
        // 创建阶段,浏览器或者node会创建一个执行上下文 ,即变量提升,执行时赋值
        /*
        executionContext = {
            scopeChain: {},   // 作用域链 scope chain
            variableObject: {   // 初始化函数中声明的 变量 函数 形参
                name: undefined,
                getData: undefined,
                c: undefined,
                num: undefined,
                arguments:[]
            },
            this: {}
        }
        */
       /*
       ** 当前作用域variableObject找不到age 向上一层找, 上一层也没有,找全局,全局没有则undefined
       */
        console.log('age::', age)   // 18
        
        console.log('before::', name, num, getData, c)
        // before:: undefined 100 undefined function c() {}
        
        var name = 'paul';
        var getData = function getData() {
        
        };
        function c() {
        
        };
        
        console.log('after::', name, num, getData, c)
        // after:: paul 100 function getData() {} function c() {}
    }
    
    demo(100);
}

outer();

  • 作用域链,为,定义时而非运行时

// 作用域链为定义时,而非执行时

function demo(num){
    var getData = function getData() {};

    console.log('name::', name);
    // name is not defined
    // 向外找作用域是  定义时的, 而非执行时的作用域,定义时,demo外是window、global

    function c() {};
}

function outer(){
    var name = 'chris';
    demo(100);  // 执行
}

outer();

1.2 执行上下文栈

// 执行上下文栈

function demo (num) {
    var name = 'chris paul';
    var getData = function getData() {};
    function c() {};

    console.log('demo');

    if (num >= 1000000){
        return;
    }
    // 递归
    demo(num + 1);
    console.log('name::', name)

    // Maximum call stack size exceeded
    // 每次入栈,都存入外边的变量,存入上下文,直到执行结束才会释放,js调用栈爆
    // 递归每次入栈,都需要保证上面的变量还可以访问,所以不能删,结束才能销毁
    // 百万节点用栈模拟递归,尾递归优化(浏览器兼容性)
}

demo(1);

1.3 let(babel编译实例)

// let const

function demo (num) { 
    console.log('name::', name)  // undefined,变量提升,可以访问到但是没赋值
    console.log('name2::', name2)  // name2 is not defined
    // const let 没有变量提升 不会提升到 variableObject
    // babel 会把let直接替换成var babel有时候实现的不好, 遇到bug,有值,不报错,往变量提升上想想
    var name = 'chris paul';
    let name2 = 'kobe';
}
demo(100);
// babel编译块级作用域假象

function demo(num) {
    // 用var时i在这里
    for (let i = 0; i < 100; i++) {
        // 用let时i在这里
        setTimeout(()=>{
            console.log('i::', i)
        },1)
    }
}

demo(100);

// babel编译如下

function demo(num){
    var _loop = function _loop(i){
        setTimeout(()=>{
            console.log('i::', i)
        },1)
    }
    for (var i = 0; i < 100; i++){
        _loop(i);
    }
}

// babel 只是傻傻的把let变成var么

function demo(num){
    {
        let name = 'iverson';
        console.log('name::', name)
    }
    console.log('name::', name)   // name 访问不到
}
demo(100);

// babel编译后

function demo(num){
    {
        var _name = 'iverson';
        console.log('name::', _name)
    }
    console.log('name::', name)
}
demo(100);

2. 闭包

2.1 定义

// 闭包:通过外层函数的执行返回里层函数
function outer() {
    var top = 'backham'; // 定义时就有
    function inner() {
        console.log('top', top);
    }
    return inner;
}
var inner = outer();
inner(); 

// 因为作用域链的关系,里层函数执行时,向上找定义时候的top
// 在内部函数访问到外部函数的变量就叫闭包
function outer() {
    var top = 'backham';
    function inner() {
        console.log('top', top);
    }
    inner();  // 有权访问另一个函数作用域重的变量的函数,就算是闭包,不用非得return
}
outer();

2.2 用法

  • wife可以访问person的money
(function () {
    var Person = function () {
        this.money = 100000000;
    }
    Person.prototype.buybuybuy = function () {
        this.money -= 100;
        console.log('left money::', this.money);
    }

    let teacher = new Person();
    teacher.buybuybuy();
    function wifeSearch() {
        teacher.money = 0;
    }
    wifeSearch();
    teacher.buybuybuy();

    // left money:: 99999900
    // left money:: -100
    // 对象上的属性,外部可以直接改,不希望外部可以直接访问,用闭包解决这个问题
})(); 

  • 设置闭包私有,wife无法访问person的money

(function () {
    var Person = (function (){
        // 静态私有变量 共用了私有变量
        var _money = 100000000;
        function Person() { 
            // 公有变量放在这里
            // 私有变量提到闭包上 _money  
        };
        Person.prototype.buybuybuy = function () {    //函数内部,访问外部作用域变量
            _money -= 100;
            console.log('left money::', _money);
        };
        return Person;
    })();

    let teacher = new Person();
    teacher.buybuybuy();
    function wifeSearch() {
        teacher.money = 0;
    }
    wifeSearch();  // 改不了
    teacher.buybuybuy();
})(); 
  • 日常用法
  • 用闭包做缓存(wx.getUserInfo()可以缓存)
(function() {
    var axios = require('axios');
    // 封装了一个数据接口 利用闭包特性 缓存
    // 返回值永远类型相等 返回promise
    function apiGenerator() {
    	let menuData = null;
    	return function getInitMenu() {
        	if(meunData) {
                    return promise.resolve(menuData);  
                }
    		return axios.get('menuapi')
            	.then(res => {
                    menuData = res;
                    return menuData;
                })
    	}
    }
    
    let result = apiGenerator();
})();