js 作用域、this、闭包

129 阅读3分钟

javascript 作用域、this、闭包

1、作用域

  • 全局作用域 window 全局声明的变量作用域为 window
    var a = "javascript";
    console.log(a);

在全局作用域下声明的变量和函数在页面的任何位置都可以访问到

    var a = "javascript";
    function log() {
    console.log(a);
    }
    log(); // javascript
  • 函数作用域
    var b = "frontend";
    function foo() {
        var c = "course";
        console.log(c);
        console.log(b);
    }

    foo(); // course frontend
    console.log(c); // c is not defined

// 在函数作用域中声明的变量,在外部无法访问,全局作用域下声明的变量 任何位置都可以访问
  • 块级作用域 花括号包裹的部分形成块级作用域 只用 let const 关键字能形成块级作用域
    if (true) {
        let a = "block";
        var b = "prop";
    }
    console.log(b); // prop
    console.log(a); // ReferenceError:  a is not defined
  • 变量和函数声明提升 变量提升优先
// 1. 执行顺序
    let a = "global";
    console.log(a);

    function course() {
        let b = "js";
        console.log(b);

        session();
        function session() {
            let c = "this";
            console.log(c);

            teacher();
            // 2. 函数提升 - 作用域之内
            function teacher() {
            // 2.1 let不支持提升
                // 2.2 变量通过var支持提升,声明提升
                // var e = undefined
                console.log(e);
                let d = "yunyin";
                var e = "yy";
                // e = 'yy';
                console.log(d);

                console.log("test1", b); // 3. 作用域向上查找,向下传递
            }
        }
    }
    course();

    // 提升优先级
    console.log("yunyin", yunyin); // f()
    function yunyin() {
        this.course = "js";
    }
    yunyin = "course";
// 变量优先 => 函数需要变量

2、this 上下文

this 是在执行时动态读取上下文决定的,而不是创建时

  • 全局上执行的环境 => 函数表达式、匿名函数、嵌套函数
    function foo() {
        console.log(this); //
    }
    foo()// window
    
    (function () {
        console.log(this);
    })();

    function a() {
        function b() {
            console.log(this);
        }
        return b();
    }
    a();
  • 隐式绑定 - this 指代调用堆栈的上一级 => 对象、数组等引用关系逻辑
    function fn() {
        console.log("隐式绑定", this);
    }
    var a = {
        text: "a",
        fn,
    };
    a.fn();

面试题

    const foo = {
        bar: 10,
        fn: function () {
            console.log(this.bar);
            console.log(this);
        },
    };
    // 取出
    let fn1 = foo.fn;
    // 独立执行
    fn1();

    // 追问1: 如何改变属性指向
    const o1 = {
        text: "o1",
        fn: function () {
            // 直接使用上下文 - 传统派活
            console.log("o1fn", this);
            return this.text;
        },
    };

    const o2 = {
        text: "o2",
        fn: function () {
            // 呼叫领导执行 —— 部门协作
            return o1.fn();
        },
    };

    const o3 = {
        text: "o3",
        fn: function () {
            // 直接内部构造 —— 公共人
            let fn = o1.fn;
            return fn();
        },
    };

    console.log("o1fn", o1.fn()); // this 为 o1
    console.log("o2fn", o2.fn()); // this 为 o1
    console.log("o3fn", o3.fn()); // this 为 window

改变this指向

   // 1、人为干涉改变this - bind / call / apply
    o1.fn.call(o2)
    //2、不需要人为改变
    const o1 = {
        text: 'o1',
        fn: function(){
            // 直接使用上下文 - 传统派活
            console.log('o1fn', this);
            return this.text;
        }
    }
    
    const o2 = {
        text: 'o2',
        fn: o1.fn
    }
    console.log('o2fn', o2.fn());

  • 显示绑定 (bind | apply | call) 三者区别: 都可以改变this 第一个参数都是要更改的this,后面的参数中call 是逐个传入而apply 需要传一个数组,bind 传参和call 一致 但是bind 返回一个函数

面试题 手写apply bind

    // apply
    Function.prototype.myApply = function(contxt) {
        // 获取要更改的this
        context = context || window
        // 获取执行函数 this 就是执行函数
        context.fn = this
        let result = arguments[1] ? context.fn(...arguments[1]) : context.fn()
        delete context.fn
        return result
    }

    function add(num1, num2) {
        console.log(num1 + num2)
    }
    add.myApply(null, [1,2])


    // bind

    Function.prototype.myBind(context) {
        context = context || window
        var _this = this
        let arg = Array.prototype.slice.call(arguments).slice(1)
        return function() {
            _this.myApply(context,arg)
        }
    }

    //call
    Function.prototype.myCall(context) {
        context = context || window
        context.fn = this
        let args = [...arguments].slice(1)
        let result = args.length > 0 ?  context.fn(...args) : context.fn()
        delete context.fn
        return result
    }

3、闭包

一个函数 A 中返回一个函数 B B 中引用 A 中声明的变量,从而导致函数 A 及变量无法释放 场景:

  • 函数作为返回值
    function main(){
        let a = 'b'
        return function() {
            console.log(b)
        }
    }
    const envelop = mail()
    envelop()
  • 函数作为参数的时候
    function main(fn) {
        let content
        fn()
    }
    function fn1() {
        content = 1
    }

    const envelop = main(fn1)
    envelop()
  • 函数嵌套
    let counter = 0;

    function outerFn() {
        function innerFn() {
            counter++;
            console.log(counter);
        }
        return innerFn;
    }
    outerFn()();
  • 立即执行函数 => js模块化的基石
    let count = 0;
    (function immediate(args) {
        if (count === 0) {
            let count = 1;

            console.log(count);
        }
    })(args);

    // 实现私有变量
    function createStack() {
        return {
            items: [],
            push(item) {
                this.item.push(item);
            }
        }
    }

    const stack = {
        items: [],
        push: function() {}
    }

    function createStack() {
        const items = [];
        return {
            push(item) {
                items.push(item);
            }
        }
    }