JavaScript小白之快速掌握作用域

为什么要了解作用域呢?

要想深刻地掌握一门编程语言,我们不仅要会用,还要了解一些底层基础和规则,作用域便是其中的一点。

什么是作用域呢?

  • 我们又称 js 是浏览器的脚本语言,我们需要知道,我们用 js 写完一行代码之后,浏览器读取到这行代码,会不会第一时间去执行这行代码呢?
js 复制代码
var a = 1
a = 'hello'

以上代码合理吗? 显然合理,但是为什么我们定义的 a 既能是数字类型,又能是字符型呢?首先,我们要有这些概念

  1. 弱类型语言,其定义一个变量不需要声明类型
  2. 强类型语言,其定义一个变量需要声明类型

强类型语言在书写时会给你一些更严谨的逻辑提示,而弱类型的语言则很自由,这样就会出现类型给错的等一些问题,这就需要编译器先来梳理一下这些代码,再交给执行引擎去执行。而 JavaScript 是弱类型的动态语言,所以回到最上面那个问题,浏览器并不会第一时间去执行这行代码。所以对于 JavaScript 这门语言来说,执行代码之前是需要先编译的。

  • 那么编译器是如何编译的呢?
js 复制代码
var a = 1
function foo() {
  console.log(a);
}
foo()

还是这个代码,浏览器在执行之前会叫编译器先编译,编译器从上往下、从左往右梳理代码,发现代码中有变量(有效标识符) a 和 foo(),那么这个有效标识符 a 和 foo()定义在哪呢?这就引出了一个概念------作用域。如图有两个作用域:

作用域:作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性,这个可能有点难以理解,通俗来讲,作用域一个隔离带,将里面的有效标识符和外面的有效标识符分开,防止产生矛盾。

作用域的类型及作用

作用

首先,我们需要知道代码的执行有一个规则,变量的查找会先从内到外的作用域中查找,不能从外到内,如下

js 复制代码
var a = 1
function foo() {
  var a = 2
  console.log(a);
}
foo()

在上示代码中,全局下有一个变量 a 和函数 foo 。存在如下两个域,那么这里输出的结果是什么呢?是 1 ?还是 2 呢?

在执行代码前是需要先编译的,编译之后我们发现全局下有一个变量 a 和 foo() ,往下有一个函数foo()的调用,同理,在执行调用之前,我们会先编译函数体foo()里面的内容,发现里面也有一个变量 a ,于是console.log(a)就在当前所处域中找到变量 a 的值,然后输出,所以输出的结果是 1。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突

声明提升(补充)

在讲作用域的类型之前,我们先补充一个小知识。

  • 1、var声明
js 复制代码
console.log(a);
var a = 1

你们认为这两行代码输出的结果是什么呢?可能很多人都不约而同地认为是 1 。事实上,输出的结果是 undefined,为什么呢?按照正常的逻辑,我们先编译,发现全局里有一个变量 a ,然后执行 console.log(a) 语句,发现找不到 a 的值,因为代码中,赋值是在 console.log(a)后执行的。实际上,也就相当于执行了这样一段代码:

js 复制代码
var a
console.log(a);
a = 1

即var 声明的变量 存在声明提升,提升到当前作用域的顶端。

  • 2、函数声明
js 复制代码
foo()

function foo() {
  console.log(123)
}

这里输出的结果是什么呢,能输出吗?是 123 吗?没错,这里输出的结果就是 123。按照我们正常的思维,怎么能没有声明就直接调用函数呢,真的难以理解。事实上,这里也存在声明提升,即相当于执行了这样一段代码:

js 复制代码
function foo() {
  console.log(123)
}

foo()

}

函数的声明提升和var的声明提升有一点不同,var只是提升变量 ,而函数提升的是整体 ,即函数声明会整体 提升,提升到当前作用域的顶端。

  • 3、let声明
js 复制代码
console.log(a);
let a = 1

这里能输出 1 吗?还是说也是undefined,事实上都不是,这里会报错,这个才符合我们正常的思维,这里没有先声明变量 a 就直接访问了变量 a ,显然是错误的,所以这里会报错。即let不会声明提升

js 复制代码
let a = 1
let a = 2
console.log(a);

那么这里会输出 2 吗?事实上也不会,这里也会报错,声明已经存在不能重复声明。即let不能重复声明同一变量

  • 4、const声明
js 复制代码
console.log(a);
const a = 1

同let,这里也会报错。即const不会声明提升

js 复制代码
const a = 1
const a = 2
console.log(a);

同let,报错。即const不能重复声明同一变量

js 复制代码
const a = 1
a = 'hello'
console.log(a);

这里也会报错,用const 声明的变量不允许修改值

全局作用域

变量在函数或者代码块{}外定义的即为全局作用域。

js 复制代码
var a = 1
console.log(a);

在上面的代码中,函数体外有一个变量 a ,它是全局变量,在全局发挥作用。

函数作用域

顾名思义,在函数体内部定义的变量即为函数作用域。

js 复制代码
var a = 1
function foo() {
  var a = 2
  console.log(a);
}
foo()
  • 在上示代码中,全局下有一个变量 a 和函数 foo 。而函数 foo() 有一个也有变量 a ,它只能在函数体内发挥作用,即函数作用域。
块级作用域

let/const + {} 会形成块级作用域。

js 复制代码
if(1) {
  var a = 1
}
console.log(a);

这里if语句形成了作用域吗?假设形成了作用域,根据代码执行的规则,我们执行console.log()语句时,便会找不到 a 的值,而事实上,输出的结果是 1 。所以if语句并没有形成作用域。那么将i语句中的var改成了let/const呢?

js 复制代码
if(1) {
  let a = 1
}
console.log(a);

输出结果:ReferenceError: a is not defined,报错了,说明这里形成了作用域。换成for语句:

js 复制代码
for(var i = 0 ; i < 5 ; i++) {
  let a = 1
}
console.log(a);

同样,输出结果:ReferenceError: a is not defined,说明这里形成了作用域。这里将let换成const也是一样的。我们把let/const + {} 形成的作用域叫做块级作用域。再看:

js 复制代码
let a = 1
if(true) {
  console.log(a);
  let a = 2
}

按照我们上面所说,let不能声明提升,所以console.log(a)访问不到自己所处作用域 a 的值,然后去外层作用域找,所以这里是不是应该输出 1,实际上是不对的,这里会报错,let+{}形成的作用域有一个规则:自己作用域有,但是访问不到,也不允许去访问外面的,这叫做暂时性死区

欺骗词法作用域

  • 1、
js 复制代码
function foo() {
  var a = 1
  console.log(a,b);
}
foo()

显然,这里会报错,找不到 b 的值,那么我们这样改:

js 复制代码
function foo(str) {
  eval(str)
  var a = 1
  console.log(a,b);
}
foo('var b = 2')

输出结果:1 2 为什么呢?这里eval的作用相当于把原本不属于这个作用域的代码搬过来,即相当于如下代码

js 复制代码
function foo() {
  var b = 2
  var a = 1
  console.log(a,b);
}
foo()

eval把编译器也"骗过了",

  • 2、

with函数可以批量修改声明的对象中的某一些属性。

js 复制代码
function foo(obj) {
  with(obj){
  a = 2
  }
}
var o1 = { b : 4}
foo(o1)
console.log(o1);

这里输出的结果是 2 ,这里是把对象o1中的 a 改成 2,但实际上对象o1中并不存在属性 a,当with修改对象中的属性时,当对象中不存在这个属性,with就会将这个属性泄漏到全局,让其变成全局变量,所以这里输出的结果是 2。对于编译器来说,"一头雾水",因为全局实际不存在这个变量,with将它"骗过了"。

  • 3、

当不写关键字声明对象时,不管写在哪都认为是全局变量

第一篇文章,如有错误,请大家指正,感谢!

相关推荐
子非鱼92121 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
想退休的搬砖人34 分钟前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
清汤饺子1 小时前
实践指南之网页转PDF
前端·javascript·react.js
蒟蒻的贤1 小时前
Web APIs 第二天
开发语言·前端·javascript
清灵xmf1 小时前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
蘑菇头爱平底锅1 小时前
十万条数据渲染到页面上如何优化
前端·javascript·面试
2301_801074151 小时前
TypeScript异常处理
前端·javascript·typescript