针对小白的js作用域详细介绍

前言

js是一门弱类型语言,弱类型指的是声明变量不需要给它定死类型,比如定义整形,String等类型都可以直接用var来进行声明。js的作用域可以分为全局作用域,函数作用域,块级作用域三大作用域,当然,我做提到的js版本是es6之后,这一版本相较于前一代es5多了上面提到的块级作用域。

全局作用域

全局作用域很好理解,就是针对全局的范围

函数作用域

在函数体内的范围

块级作用域

带有花括号{}的范围,常见为if,for后面的{},但是里面必须有let声明


在谈作用域之前我想聊聊js运行的过程,先看下下面这段非常简单的代码

scss 复制代码
var a = 1

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

foo()

运行结果:1

js运行代码会先进行编译,也就是先获取有效标识符(变量和函数的声明都算),再来执行各种操作,编译的时候需要完成的任务是从上到下获取各个变量。 比如这段代码,第一行代码知道了有a这个值,并且值为1,到了第3行代码是个函数体,我们不用管他,因为不是执行,所以第3-5行的代码都先跳过,然后到了第7行,需要执行foo函数,这个时候我们再来看函数体,进行编译,其实函数作用域就是函数体的范围,现在开始对foo函数进行编译,里面并没有任何变量的声明,于是开始执行操作console.log(a),这里需要找a这个变量,查找变量有个非常重要的规则,就是从内部作用域往外部作用域进行查找,不能从外部往内部作用域进行查找,比如,这里会先在函数体内查找a这个变量,发现并没有a,于是到全局作用域进行查找,找到a,并且值为1,于是打印a的值。

运行代码出现的defined类问题

在讲作用域之前我想聊聊大家运行代码可能会碰到的not defined ,undefined 以及NaN各类问题,还是以这个代码为例。如果第1行代码仅仅只是var a 得话,就是说a没有赋值,那么输出的会是undefined,如果是没有var a = 1这行代码,那么输出将会报错,也就是a is not defined,NaN我们后面再进行讲解。

根据刚刚我们谈到的变量查找规则我们引入下面几个案例帮助大家理解

css 复制代码
var a = 1

function foo(){
    var a = 2
    console.log(a)
}

foo()

运行结果:2

因为a在函数作用域已经存在了,不需要到外面进行查找,所以a的值就是2。这里不知道大家发现没一个小彩蛋,a这个变量为什么可以多次声明,其实var这个声明类型就是可以多次声明,甚至在js里面可以不进行声明,直接写a = 1都是可以的,js会默认给你声明成var。

再来一个

javascript 复制代码
function foo(){
    var b = 3
}

console.log(b)

运行结果: b is not defined

其实这个结果就是报错,console.log(b)这个语句在全局作用域中,因此找b需要在全局作用域来找,但是b只在函数作用域中出现了,不能从外往内找,因此找不到

下面我们再看一个比较特殊的情况

css 复制代码
var b = 1

function foo(a , b){
    console.log(a + b)
}

foo(2)

运行结果:NaN

这里先解释下NaN是什么,NaN也是个值,并且是number类型的,只不过没有值。这里的函数里面用了形参,第3行里面的a,b都是形参,第7行的2是实参,但是只传给了a这个形参,形参的传值只能通过实参,然而实参并没有b,所以哪怕全局里面有b这个值也不行,因此a + b等于2 + undefined = NaN。想必这里肯定有人又会问为什么b是undefined,而不是is not defined,因为其实形参就相当于已经给b声明了。

下面我们再来看一个情形

css 复制代码
var b 

function foo(a){
    console.log(a + b)
}

foo(2)

b = 1

运行结果:NaN

不知道大家可以理解为什么结果是NaN,不应该是3吗。代码是从上往下运行的,编译到第7行的时候,已经知道了全局变量有b这个变量,但是没有赋值,因此这个时候执行函数的时候,b是没有值的,还没执行到第9行就需要知道b的值,显然不符合常理的。因此结果就是a + b = 1 + undefined = NaN

接下来要讲块级作用域,块级作用域其实是let引出的作用域,let跟var是同样类型的东西,都是用来声明变量的,如果{}里面有let,那么这个{}就是一个块级作用域。因此块级作用域与let息息相关,所以接下来我想先给大家聊聊let这个东西

let

let的作用域变量查找的规则和var还是一样的,依旧遵循只能从内到外找,不能从外到内找。其实let的出现就是为了弥补var的缺陷,但是var的优点还是保留在let身上的。

先给大家看个var的案例

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

foo()

运行结果:undefined

正常人都不会这样写代码,但是hr会[doge],这里全局只有一个函数的声明,没有任何变量,因此执行到第7行的时候编译器开始对foo()进行编译,第3行执行语句先不管,我们先看变量的声明,于是找到了a这个变量,接下来再执行语句,编译其实就是在当前的作用域里先找到声明,再来执行语句,这里的console.log和=赋值都是执行语句,由于赋值在后,因此这里的a是没有赋值进去的。其实仔细想想这里的代码如果按照我们的思维方式,应该是报错的,所以let就解决了这一问题,如果这里用let声明,这个代码就会报错。并且我们管这个叫做暂时性死区

其实上面这个代码就等同于

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

foo()

这个其实就是var的声明提升,就是在当前作用域中,var声明一个变量是会自动放到当前作用域最前面。

好,接下来我们继续聊块级作用域。我们先看一个例子:

css 复制代码
{
    let a = 1
}

console.log(a)

运行结果:a is not defined

其实很好理解,因为let与{}相结合形成了块级作用域,并且查找a不能从外向内找,所以a这个值相当于没有声明,报错。

三大作用域到这里已经讲完了,但是其实还有一个名词叫做欺骗词法作用域,这个作用域不同于上面的作用域。

欺骗词法作用域

eval案例

scss 复制代码
function foo(str,a){
    eval(str)
    console.log(a,b)
}

foo('var b = 1',2)

这个eval函数对大家肯定非常陌生,其实这行代码就等同于var b = 1 ,也就是说将下面的代码搬到了这里,在这里定义var b = 1。起到一个欺骗的作用,将全局作用域搬到函数作用域里面。

还有一个例子就是with(obj){}这个函数,这个函数的作用是批量修改对象的键值对

比如:

ini 复制代码
var obj = {
    a: 1,
    b: 2,
    c: 3
}

with(obj){
    a = 2,
    b = 3,
    c = 4
}

console.log(obj)

运行结果:{ a: 2, b: 3, c: 4 }

with案例

css 复制代码
function foo(obj){
    with(obj){
        a = 2
    }
}

var o1 = {
    a: 3
}

var o2 = {
    b: 3
}

foo(o2)
console.log(a)

运行结果:2

这里全局并没有a这个参数,但是结果却是有的,因为在执行foo(o2)的时候,with(o2)发现o2这个对象没有a这个key,于是with会将a这个key直接抛到全局作用域中。

不声明变量案例

scss 复制代码
function foo(){
    a = 1
}
foo()

console.log(a)

运行结果:1

其实这个案例我在文章前面就有讲到,js声明变量可以不用var或者let等类型,这个时候直接赋值声明会默认声明到全局作用域中去,无论赋值在那个作用域。


好了,js作用域的分享就到这里,感谢大家的观看,另外如果觉得文章写得不错的话,可以给个免费的赞吗,大佬看到这里,发现理解有误欢迎给我进行指错。谢谢大家!

相关推荐
Rowrey1 小时前
react+typescript,初始化与项目配置
javascript·react.js·typescript
祈澈菇凉5 小时前
Webpack的基本功能有哪些
前端·javascript·vue.js
记得早睡~6 小时前
leetcode150-逆波兰表达式求值
javascript·算法·leetcode
庸俗今天不摸鱼7 小时前
Canvas进阶-4、边界检测(流光,鼠标拖尾)
开发语言·前端·javascript·计算机外设
[廾匸]7 小时前
cesium视频投影
javascript·无人机·cesium·cesium.js·视频投影
菲力蒲LY8 小时前
vue 手写分页
前端·javascript·vue.js
一丢丢@zml8 小时前
new 一个构造函数的过程以及手写 new
javascript·手写new
化作繁星9 小时前
React 高阶组件的优缺点
前端·javascript·react.js
zpjing~.~9 小时前
vue 父组件和子组件中v-model和props的使用和区别
前端·javascript·vue.js
FFF-X10 小时前
大屏自适应终极方案:基于比例缩放的完美适配实践(Vue3版)
javascript·html5