JavaScript原理篇——深入理解作用域、作用域链、闭包、this指向

执行上下文描述了代码执行时的环境,包括变量对象、作用域链和 this 值;而作用域则决定了变量和函数的可访问性范围,分为全局作用域和局部作用域。

变量对象用于存储变量和函数声明:是与执行上下文相关联的数据结构,用于存储在上下文中定义的变量、函数声明和形参等信息。

作用域链用于解析标识符的查找路径:是 JavaScript 中用于解析标识符(变量名)的机制,它由多个执行上下文的变量对象组成的链表结构。当代码在某个执行上下文中查找变量时,会首先查找当前上下文的变量对象,如果找不到,则会沿着作用域链向上一级上下文查找,直到找到为止。作用域链的形成是由函数的嵌套关系所决定的,内部函数可以访问外部函数的变量,但外部函数无法访问内部函数的变量。

this 值取决于函数的调用方式:this 是 JavaScript 中的一个关键字,它在函数被调用时绑定到函数的执行环境。this 的值取决于函数的调用方式。在全局作用域中,this 指向全局对象(在浏览器中通常是 window 对象)。在函数内部,this 的值取决于函数被调用时的上下文。可以通过函数的调用方式(作为方法、通过 call、apply、bind 方法等)来改变 this 的值。

测试你对作用域的掌握程度

请你输出下面代码的执行结果

  

如果你毫无头绪的话,请尝试阅读本文。在本文最后的挑战作用域笔试题中给出答案。

 执行上下文与作用域

行上下文(Execution Context)和作用域(Scope)是 JavaScript 中重要的概念。理解这两个概念对于编写和调试 JavaScript 代码至关重要,因为它们直接影响了代码中变量和函数的可见性和生命周期。

执行上下文(Execution Context)

执行上下文是 JavaScript 中一个抽象的概念,它定义了代码执行时的环境。每当 JavaScript 代码执行时,都会创建一个执行上下文,并将其推入执行上下文栈(Execution Context Stack)中。执行上下文在js中很重要。变量或函数的上下文决定了它们能访问哪些数据,以及行为。每个上下文都关联一个变量对象,在这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。

执行上下文包括三个重要的组成部分:

  • 变量对象(Variable Object):用于存储变量、函数声明和函数参数。在全局上下文中,它被称为全局对象(Global Object),在函数上下文中,它被称为活动对象(Activation Object)。
  • 作用域链(Scope Chain):用于查找变量的链条,它由当前执行上下文的变量对象和所有包含(父级)执行上下文的变量对象组成。
  • this 值:指向函数执行的当前对象,在全局上下文中通常指向全局对象,在函数上下文中取决于函数的调用方式。

this是什么

this 是一个关键字,其工作原理是根据函数的调用方式动态确定其指向的对象。具体来说:

  • 当函数作为普通函数调用时,this 指向全局对象(在浏览器中通常是 window 对象)。
  • 当函数作为对象的方法调用时,this 指向调用该方法的对象。
  • 当函数作为构造函数调用时(使用 new 关键字),this 指向新创建的实例对象。
  • 当函数作为事件处理函数绑定到 DOM 元素上时,this 指向触发事件的 DOM 元素。

全局作用域

// 全局作用域
console.log(this); // 指向全局对象(window)

 对象方法中的this

const obj = {
  name: "Alice",
  greet() {
    console.log(this.name);
  },
};
obj.greet();

构造函数方法的this

function Person(name) {
  this.name = name;
}
const person1 = new Person("Bob");
console.log(person1.name);

this的值是在执行的时候才能确认,定义的时候不能确认

上下文栈

当函数被调用时,函数的上下文会被推到一个上下文栈上。在函数执行完后,上下文栈会弹出该函数上下文。将控制权返回给之前的上下文。

闭包怎么产生的

上下文所在代码都执行完毕后才会销毁。这也是闭包存在的作用,如果有代码还没执行完某些变量无法被销毁。闭包是指函数和函数内部能访问到的其外部作用域的变量的组合。在 JavaScript 中,函数可以访问其定义时所处的词法作用域,即使函数是在其定义的作用域之外执行的。

通常来说,栈的内存小,基本数据类型用栈存储,引用数据类型用堆存储。引用类型在栈中国存了查找引用对象在堆内存的地址。但是闭包中的变量存在哪里呢?

如果变量存在栈中,那函数调用完栈顶空间销毁,闭包变量不就没了吗?

闭包变量是存在堆内存中的。

function outerFunction() {
  let outerVar = 'I am from outer function';
  function innerFunction() {
    console.log(outerVar);
  }
  return innerFunction;
}
const innerFunc = outerFunction();
innerFunc(); // 输出:I am from outer function
  • 上面的代码输出:I am from outer function。因为 innerFunction 是在 outerFunction 内部定义的,可以访问 outerFunction 的变量 outerVar

  • 闭包的优点包括可以实现数据封装、延长变量的生命周期、实现模块化等;缺点包括可能导致内存泄漏(如果不注意释放闭包)、影响性能(因为变量未被及时释放)等。

作用域(Scope)

作用域是指变量和函数的可访问性范围,它决定了在代码中的哪些位置可以访问到某个变量或函数。变量的作用域是在定义时就决定了。JavaScript 中有三种主要类型的作用域:

  • 全局作用域(Global Scope):在整个代码中都可访问的作用域,任何在全局作用域中声明的变量或函数都可以被任何地方的代码访问。
  • 局部作用域(Local Scope):在特定代码块(通常是函数)中可访问的作用域,局部作用域可以嵌套,内部作用域可以访问外部作用域的变量,但外部作用域不能访问内部作用域的变量。
  • 块级作用域(Block Scope):块级作用域指的是由一对花括号 {} 包裹起来的代码块内部所创建的作用域。在 JavaScript 中,使用 letconst 关键字声明的变量具有块级作用域,即只在声明它们的代码块内部可见。块级作用域可以帮助我们避免变量污染和提供更好的封装性。

全局作用域

根据js实现的宿主环境,在浏览器中,全局上下文就是window对象。所有通过var定义的全局变量和函数都会成为window对象的属性和方法。 全局上下文只有在应用程序退出前才会被销毁。比如关闭网页或退出浏览器

局部/函数作用域

每个函数都有自己的上下文。注意是function定义的函数才有自己的上下文。不是用了大括号的都有上下文,像for循环和if是不会创建自己的上下文的。

作用域链

作用域链的创建是在函数定义时确定的,它与函数的定义位置有关。当函数被调用时,会创建一个新的执行环境,其中包含一个新的变量对象,并将其添加到作用域链的前端。这样,函数内部就可以访问其所在作用域以及其外部作用域中的变量和函数,形成了一个作用域链。

作用域链的作用

对于作用域链,可以把它理解成包含自身变量对象和上级变量对象的列表,通过 [[Scope]]属性查找上级变量。作用域链是一种用于查找变量和函数的机制,它是由当前执行环境和其所有父级执行环境的变量对象组成的链式结构。当在一个执行环境中访问变量或函数时,会首先在当前执行环境的变量对象中查找,如果找不到,则会沿着作用域链向上查找,直到找到对应的变量或函数,或者达到最外层的全局对象(如window)。

简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

像素棱镜

你的鼓励将是我前进的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值