Js基础之作用域、this指针、闭包

84 阅读5分钟

本文主要总结js基础之作用域、this指针、闭包概念,并就一些引申出来的概念和问题进行辩析如:变量提升和函数提升;作用域链和原型链;call、apply、bind的使用和区别;手写bind函数...

Js的代码执行时期

  • 创建时
    • 作用域链:当前变量、上级变量
    • 声明判断:参数、变量、函数
    • 闭包
  • 动态运行时
    • this指针

1.作用域

1.1 全局作用域和块级作用域

  • 全局作用域

    1.全局作用域是指在所有函数外部声明的变量和函数; 2.全局作用域中声明的变量和函数可以被程序中任何地方访问; 3.全局作用域的变量和函数在程序开始执行时创建,程序结束时销毁; 4.在顶级作用域下直接声明的变量和函数,都属于是在全局作用域内的。

    var a = 1;
    let b = 2;
    const c = 3;
    function d (){};
    if(a == 8){var e = 99;}
    
  • 块级作用域 1.块级作用域指由一对花括号{}创建的作用域,通常用于控制语句块的范围,例如在if、for、while、switch等语句中。 2.在块级作用域内声明的变量只在该作用域内部可见,外部无法访问,比如:var在function中声明,在外部不可访问,funcion内部就构成一个块级作用域。 3.let、const这种块级作用域变量在其声明的位置之前是不可访问的,是暂时性死区

为什么if会形成块级作用域但变量e仍然会声明在全局作用域中?

在Js中,var声明的变量会被提升到包含它的函数作用域或全局作用域的顶部。

1.2 变量提升和函数提升

  • 变量提升

    使用var声明的变量会在当前作用域(函数作用域或全局作用域)内被提升到顶部,这意味着变量的声明会在代码实际执行之前进行。被提升的变量赋予默认值undefined,赋值语句执行前被访问为undefined,赋值语句执行后

  • 函数提升

    函数声明会在当前作用域内被提升到顶部,这意味函数可以在声明之前被提升之前使用,这种提升声明时就会保存完整的函数定义包括函数名和函数体。可以在定义函数之前使用函数。⚠️!只用函数声明会被提升,函数表达式是不会被提升的。

词法作用域和动态作用域

js属于词法(静态)作用域,其他语法如bash脚本属于动态作用域。

1.3 作用域链

1.执行顺序

从当前的作用域一直顺着作用域链向外找到全局作用域这个过程是由js引擎(v8引擎)自动执行的。如果一直往外找找到全局作用域都没找到需要的变量,抛出错误“ReferenceError”

2.作用域向上查找,向下传递

作用域链和原型链
  • 作用域链:【变量查找】js引擎自动查找;
  • 原型链:【属性查找】js中实现继承的一种机制,每个对象都有一个指向其原型对象的内部链接,这个链接称为原型链。当访问一个对象的属性时,如果对象本身没有这个属性,js就会顺着原型链向上查找,直到找到属性或原型链顶端。

原型链详解:

对象:People

【People.prototype = People.prototype】

对象原型:People.prototype

【People.proyotype.constructor = People】

对象实例:people

【people = new People()】

【people.proto = People.prototype】

2.this指针

2.1 函数直接调用中 - this指向window

全局上执行的环境,函数表达式、匿名函数、嵌套函数

2.2 隐式绑定 - this指代调用堆栈的上一级 => 对象数组等引用关系(赋值后可以改变this指向)

    function fn(){
        console.log('隐式绑定',this.a)
    }
    const obj = {
        a:1;
        fn
    }
    obj.fn = fn;
    obj.fn();

2.3 显式绑定 call()、apply()、bind()

  • call(),第一个参数作为函数内部的this,后续参数作为函数的参数列表传入;
  • apply(),第一个参数作为函数内部的this,第二个参数是一个数组,数组的元素将作为函数的参数列表传入;
  • bind(),该方法创建一个新的函数,并将指定的对象绑定为该函数内部的this,但并不立即调用该函数。后续调用新函数并传入参数。
call、apply、bind的区别?

1.call和apply传参不同,依次传入/数组传入 2.bing直接返回不同

bind原理/手写一个bind
  • 原理或者手写类的题目, 解题思路
  1. 说明原理,写下注释
  2. 根据注释补齐代码
    // 1. 需求:手写bind => bind位置(挂载在哪里)=> Function.prototype
    Function.prototype.newBind = function() {
        // 2. bind是什么?
        // 改变this
        const _this = this;
        // 接受参数args,第一项参数是新的this,第二项到最后一项是函数传参
        const args = Array.prototype.slice.call(arguments);
        const newThis = args.shift();

        // 3. 返回值
        return function() {
            return _this.newApply(newThis, args);
        }
    }

    Function.prototype.newApply = function(context) {
        context = context || window;

        // 挂载执行函数
        context.fn = this;

        let result = arguments[1]
            ? context.fn(...arguments[1])
            : context.fn();
        
        delete context.fn;
        return result;
    }
改变属性指向
    const foo = {
        bar: 10,
        fn: function() {
            console.log(1,this.bar);
            console.log(2,this);
        }
    }
    // 取出
    let fn1 = foo.fn;
    // 独立执行
    fn1();

    // 追问1: 如何改变属性指向
    const o1 = {
        text: 'o1',
        fn: function(){
            // 直接使用上下文 - 传统派活
            console.log(3,'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(4,'o1fn', o1.fn());
    console.log(5,'o2fn', o2.fn());
    console.log(6,'o3fn', o3.fn());

对应打印:

1 undefined
2 Window {window: Window, ...}
3 o1fn {text: 'o1', fn: ƒ}
4 o1fn o1
3 o1fn {text: 'o1', fn: ƒ}
5 o2fn o1
3 o1fn Window {window: Window, ...}
6 o3fn undefined

3.闭包

一个函数和他周围状态的引用捆绑在一起的组合

    // 函数作为返回值的场景
    function mail() {
        let content = '信';
        return function() {
            console.log(content);
        }
    }
    const envelop = mail()
    envelop()

    // 函数作为参数的时候
    let content;
    function envelop(fn) {
        content = 1;

        fn();
    }

    function mail() {
        console.log(content);
    }
    envelop(mail);

    // 函数嵌套
    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);
            }
        }
    }