前端笔记 - js的作用域与变量提升

118 阅读6分钟

--本笔记根据资料与自己理解整理而成,定有疏漏,烦请指正

1.什么是作用域?

首先引用专业说法

image.png

那么以上说法所述,js里的作用域有三类,分别是全局作用域模块作用域 很少用,先跳过晚点回来补函数作用域


什么是作用域
按官方说法这个东西叫做执行上下文,那什么是执行上下文呢?其实这个词在刚开始接触的时候很是恼火,A就是A,B就是B,怎么冒出来个玄而又玄的叫做执行上下文的东西,我还得好好参悟参悟。然后苦苦思索。拼尽全力依旧无法完全明悟,只能隐隐约约的感觉到大概也许可能就是那么一回事吧。
但搬砖这么久了,回过头来仔细想一想,这东西叫执行上下文没什么大毛病。为什么呢,因为这是个词组,他就是对作用域的扩展解释,作用=执行,域=上下文,对吧,这么看着很明确吧。 这不是在讲车轱辘话吗混蛋
那什么是作用呢?
讲这个前我们先回来讲讲js在做什么事情,简单来说就是我声明了一些变量,再写了一些逻辑,我希望我能通过我声明的这些变量和这些逻辑办成一些事情。比如:

    var student = {
      name: '明仔',
      studentLevel: {
        yuwen: 0,
      },
    }
    var teacher = {
      name: 'Mr.张',
      subject: 'yuwen',
    }

    function learn(student, teacher) {
      var type = teacher.subject
      student.studentLevel[type]++
    }

    learn(student, teacher)
    console.log(student)

这个代码里我声明里一个学生变量、一个老师变量和一个学习的方法(逻辑),我期望学生每次上完课成绩都能提升1分 真的可以吗?
那么在这里面,我想要办成的这个事,就离不开所涉及到的东西(声明的这些变量和逻辑),而这些东西就构成了作用。
那么什么是域呢?
简单来说就是我这套逻辑所在的边界。在我的这套逻辑里,想要实现最终每次学习成绩都喜加一,那我首先得要有一个学生,然后我还要有一个科目老师,然后还要有学习方法,最终学生还得去乖乖学习。只有上述的作用在一个逻辑边界下成立,我才能让每个学生上完课就喜加一。而这个逻辑边界,就是域。
那么作用+域=作用域=执行上下文,好理解的吧? 的吧?
但是啊但是,这个域不以作用多少为转移,只跟所处的位置有关系。
那怎么区分呢?
简单来说最外层是全局作用域,在这里声明的东西其他地方都能访问到。
而函数内部的是函数作用域,只有函数里能访问到,出了函数花括号{}这个边界大家就不认识了。
模块作用域跳过了,后面再补
而里面有一内内特殊的地方,就是let、const声明的是块级作用域。
那块级作用域特殊在哪呢?
那就得讲讲为什么会有let和const了,因为一开始是只用var来声明变量的。但是吧var这个小同志处理简单任务害行,任务稍微复杂一些,稍微不那么规范一些,用var就有些不太合适了。比如

    function createTask(name, type, taskObj) {
      taskObj.name = name
      taskObj.type = type
    }
    var taskObj = {
      name: '',
    }
    createTask('本月牛马使用(榨取)计划', '.xlsx', taskObj)
    console.log(taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }


代码我先声明再执行,一点毛病没有嗷。
但是吧但是,我今天想要骚操作一下,我想先瞅一眼有没有我再声明,你样不样吧

    console.log(taskObj) // undefined
    function createTask(name, type, taskObj) {
      taskObj.name = name
      taskObj.type = type
    }
    var taskObj = {
      name: '',
    }
    createTask('本月牛马使用(榨取)计划', '.xlsx', taskObj)
    console.log(taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }


诶,可以喔,但是这样对吗?

    console.log(taskObj)  // ReferenceError: taskObj is not defined
    function createTask(name, type, taskObj) {
      taskObj.name = name
      taskObj.type = type
    }


这次我就瞅瞅,我不声明了,诶,为什么报错了?我就只瞅一眼还不行了吗?
那为什么声明可以,不声明不行,这就是js存在的一个叫做变量提升的机制。

image.png


再来回头看一眼

image.png

    console.log(taskObj)  // undefined
    var taskObj = {}
    createTask('本月牛马使用(榨取)计划', '.xlsx', taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }
    function createTask(name, type, taskObj) {
      taskObj.name = name
      taskObj.type = type
    }
    console.log(taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }

等价于

    var taskObj
    function createTask(name, type, taskObj) {
      taskObj.name = name
      taskObj.type = type
    }
    console.log(taskObj)  // undefined
    taskObj = {}
    createTask('本月牛马使用(榨取)计划', '.xlsx', taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }
    console.log(taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }


js把var和function的顺序提升到作用域的最顶端,先保证可被访问。而且相比function整体提升,var就只是占位。
你先别管有妹有值,我给你占住坑了,你只管跑啊,就是这还卡了那就只怪你自己了。
但是吧但是,这东西简单写写还可以,要是任务复杂起来,这样写就让人很难琢磨了。虽然代码可以东一块西一块的写,但是正常来说大家还是喜欢聚集在一起,从上到下一步步写,这样看起来才有条理才舒服。
所以const和let就应时而生了。被const和let所声明的变量不能被提前访问,不然就会报错

    console.log(taskObj)
    const taskObj = {}  // ReferenceError: Cannot access 'taskObj' before initialization
    // let taskObj = {}  // ReferenceError: Cannot access 'taskObj' before initialization


这样一来就能保证逻辑的连贯性了,毕竟不一步步来就报错了啊。
那么优化一下

    const taskObj = {}
    function createTask(name, type, taskObj) {
        taskObj.name = name
        taskObj.type = type
    }
    createTask('本月牛马使用(榨取)计划', '.xlsx', taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }
    console.log(taskObj) // { name: '本月牛马使用(榨取)计划', type: '.xlsx' }

噔噔,大功告成,虽然看起来只是把var换成了const,但对于可读性来说大大的提高了,const保证我的变量只在当前及往后有效,前面同级作用域一定妹有,不然就报错。毕竟代码不是写出来就完了,还要考虑后续维护的。

更多的作用域类型可以参考其他作者链接:juejin.cn/post/717512…

写在最后
好了,周更计划(1/1)完成了,就是第一篇写完读起来不那么专业 咋办呢咋办,后面再想想写的又专业又好阅读吧。