深入理解JS - 变量声明、this、闭包 | 青训营笔记

77 阅读5分钟

var,let,const 区别

var

在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量

注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象,但是Node环境并不会挂载到global

var a = 10;
console.log(window.a); // 10// 变量提升
console.log(b); // undefined 执行过程:var b -> console.log -> b=20 
var b = 20;
​
// var可以重复定义
var c = 30;
var c = 40;
console.log(c); // 40 var d = 10;
function fun() {
    d = 20;
}
fun();
console.log(d); // 20// 如果改为以下,则函数内的变量变为局部变量
var d = 10;
function fun2() {
    var d = 20;
}
fun2();
console.log(d); // 10

let

letES6新增的命令,用来声明变量

用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

// 只在所处代码块有效
{
    let a = 10;
}
console.log(a); // ReferenceError: a is not defined. // 不存在变量提升
console.log(b);
let b = 20; // ReferenceError: Cannot access 'b' before initialization// 作用域内存在let时,作用域不受外界影响 —— 暂时性死区
var c = 123;
if (true) {
    c = 'abc'; // ReferenceError: Cannot access 'c' before initialization
    let c;
}
​
// 【同一个作用域内】不允许重复定义
let d = 30;
let d = 40;
console.log(d); // SyntaxError: Identifier 'd' has already been declared// 这种情况没有问题,因为不是同一个作用域内
let d = 20;
{
    let d = 30;
    console.log(d); //30
}
console.log(d); // 20

const

const声明一个只读的常量,一旦声明,常量的值就不能改变(针对基础类型)

const a = 10;
a = 20; // TypeError: Assignment to constant variable.// 声明的时候必须定义值
const b; // SyntaxError: Missing initializer in const declaration// 定义对象
const c = {};
​
// 对对象重新赋值是不允许的,但是可以对对象的值进行赋值
c = {}; // TypeError: Assignment to constant variable.
c.prop = 30;
console.log(c.prop); // 30

能够为const的对象添加新的值的原因

const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量

对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变

总结

  • 变量提升
  • 暂时性死区
  • 块级作用域
  • 重复声明
  • 修改声明的变量
  • 使用 尽量使用const,减少使用var

this指向

  1. 普通函数

    • this指向window
  2. 对象

    • this在对象调用的时候指向对象
    • 先赋值再调用,看调用的地方
  3. new

    • 创建临时对象
    • this指向临时对象
    • 执行构造函数
    • 返回临时对象
function fun() {
  console.log(this); // 普通函数
}
fun(); // 浏览器环境-Window,Node环境-globalconst Obj = {
  name: "Kevin",
  showName: function () {
    console.log(this.name, this);
  },
};
​
Obj.showName(); // Kevin Obj
const getName = Obj.showName; // undefined Window(Node环境 - global)
getName();
​
function ShowName() {
    this.name = "Kevin";
}
​
const GetName = new ShowName();
console.log(GetName.name); // Kevin
console.log(ShowName.name); // ShowName 函数的属性

闭包

闭包是指在函数内部创建的函数,它可以访问并持有其所在作用域内的变量,即使在其所在作用域外部被调用时仍然有效。换句话说,闭包是函数以及其相关的引用环境的组合体。

闭包通常在以下情况下出现:

  • 在一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量。
  • 内部函数被返回,或者被传递给其他函数作为参数,从而在外部函数的作用域之外被调用。
// 举个🌰
function outerFunction() {
    let outerVariable = 'Kevin';
    return function innerFunction() {
        console.log(outerVariable);
    }
}
​
let closure = outerFunction();
closure(); // Kevin

闭包的特点包括:

  1. 可以访问和持有外部函数的变量和参数,即使外部函数已经执行完毕。
  2. 外部函数的变量在闭包中被保存,形成了一个闭包环境。
  3. 闭包可以在外部函数的作用域之外被调用,延长了变量的生命周期。
  4. 闭包可以访问不同实例中的不同变量,每个闭包都有独立的引用环境。

通过使用闭包,我们可以创建私有变量、实现函数柯里化、实现模块化等功能。闭包在JavaScript中是一个非常强大和常用的特性,它使得函数可以拥有状态并且能够保持对其相关变量的访问。

const myModule = (function() {
    let privateVar = "private Variable";
​
    function printPrivate() {
        console.log(privateVar);
    }
​
    return {
        printPublic: function() {
            printPrivate();
        },
        publicVar: 'public Var'
    }
})();
​
/**
* 立即执行函数(Immediately Invoked Function Expression,IIFE)在闭包模块化中的使用是为了创建一个独立的作用域。
* 为什么要使用立即执行函数呢?主要有以下几个原因:
*   1. 创建独立的作用域:立即执行函数可以创建一个私有的作用域,避免全局命名空间污染和变量冲突。
*   2. 封装实现细节:通过将模块的实现细节封装在立即执行函数内部,可以隐藏模块的内部实现,只暴露需要对外公开的接口。
*   3. 避免变量提升问题:立即执行函数可以防止变量和函数在外部被意外访问或修改,保持模块的封装性。
*/
​
myModule.printPublic(); // "private Variable"
console.log(myModule.privateVar, myModule.publicVar); // undefined "public Variable"
const curry = function(fn) {
    return function curried(...args) {
        // 如果当前柯里化函数参数个数多于或等于fn的参数个数(fn.length),说明已经成功柯里化
        if(args.length >= fn.length) {
            // 为fn入参
            return fn.apply(this, args);
        } else {
            return function(...args2) {
                // 否则,链接上一次的参数与新的参数,并且继续调用curried函数
                return curried.apply(this, args.concat(args2));
            }
        }
    }
}
​
const add = function(a, b, c) {
    return a+b+c;
}
​
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2,3)); // 6