JS作用域与预解析

目录

作用域:

一、什么是作用域

(1)、全局作用域

(2)、局部作用域(函数作用域)

(3)、作用域链

面试题:

二、全局执行上下文代码的流程

三、执行上下文栈

预解析:

变量提升与函数提升

变量提升

函数提升

经典面试陷阱

暂时性死区(TDZ)


作用域:

一、什么是作用域

通常来说,一段程序代码中所用到的名字(变量名和函数名)并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的 作用域

作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突

js作用域(es6)之前:全局作用域 局部(函数)作用域, es6后有 块级作用域

(1)、全局作用域

直接编写在script标签中或者一个单独的js文件内的Js代码

全局作用域在页面打开时创建,在页面关闭时销毁

在全局作用域中有一个全局对象window ,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用它

es5中 在全局作用域中:创建的变量都会作为window对象的属性保存;创建的函数都会作为window对象的方法保存

全局作用域中的变量都是全局变量;在页面的任意部分都可以访问的到

javascript 复制代码
   var a=10;
   var b=10;
  // var c="hello";
   console.log(window.c);//加window.,如果没有找到变量c,会undefined,不加的话,会报错
          
   function fun(){
       console.log("我是fun函数");
   }
   window.fun();//创建的函数都会作为window对象的方法保存
(2)、局部作用域(函数作用域)

在函数内部就是局部作用域。这个变量只在函数内部起作用

调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁

每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的

在函数作用域中可以访问到全局作用域的变量, 在全局作用域中无法访问到函数作用域的变量

es5的环境下,在函数中如果要访问全局变量,可以使用window对象

javascript 复制代码
  var a = 10;
      function fun() {
        var a = "我是fun函数中的变量a";
        //console.log("a="+a);
        function fun2() {
          // console.log(a);
          console.log("a=" + window.a);
        }
        fun2(); //10
      }
      fun();
(3)、作用域链

只要是代码,就至少有一个作用域

写在函数内部的局部作用域

如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域

当在函数作用域中操作一个变量时,它会先在自身作用域中寻找,如果有,就直接使用,如果没有,就去它上一级作用域去寻找, 如果全局作用域中依然没找到,则会报错

根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称做是作用域链

面试题:
javascript 复制代码
      //第一题
      var x = 10;
      function fn() {
        console.log(x);//10
      }
      function show(f) {
        var x = 20;
        f();
      }
      show(fn);
      
      //第二题
      /*说说它们的输出情况*/
      var fn = function () {
        console.log(fn);//输出函数本身
      };
      fn(); //函数

      var obj = {
        fn2: function () {
          // 在自己身上找,找不到fn2,然后去全局找,也找不到所以报错
          console.log(fn2); //报错
          // 通过this在自己身上找,找到了fn2
          // console.log(this.fn2)//输出函数本身
        },
      };
      obj.fn2();
二、全局执行上下文代码的流程

在执行全局代码前将window确定为全局执行上下文顶级对象

对全局数据进行预处理

var定义的全局变量==>undefined, 添加为window的属性

function声明的全局函数==>赋值(fun), 添加为window的方法

this==>赋值(window)

开始执行全局代码

三、执行上下文栈
  1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象

  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)

  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)

  4. 在当前函数执行完后,将栈顶的对象移除(出栈)

  5. 当所有的代码执行完后, 栈中只剩下window


预解析:

变量提升与函数提升

JS代码是由浏览器中的JS解析器来执行的,JS解析器在运行JS代码的时候分为两步:先预解析再代码执行

预解析: js引擎会把js里面所有的var 还有function提升到当前作用域的最前面

**代码执行:**按照代码书写的顺序从上往下执行

预解析分为变量预解析(变量提升)和函数预解析(函数提升)

变量提升就是把所有var的变量声明提升到当前的作用域最前面,不提升赋值操作

函数提升就是把所有的函数声明提升到当前作用域最前面,不调用函数


变量提升

为了理解变量提升,首先我们要将声明与赋值分开

当我们写var name="张三" ;时,JavaScript 引擎其实把这句话看成了两行代码

  1. var name;这是声明(告诉引擎有一个变量叫 name)

  2. name="张三";这是赋值(把值放进去)

下面代码的输出结果是什么?

javascript 复制代码
console.log(a); 
var a = 10;

按照正常的逻辑(从上往下运行),第一行应该报错才对,因为它这时候还没遇到a的定义

但在浏览器里运行,它不会报错,而是输出 undefined

javascript 复制代码
// --- 预解析阶段(偷偷做的事)---
var a;           // 1. 把声明提到最上面,默认值是 undefined

// --- 执行阶段(真正运行的事)---
console.log(a);  // 2. 此时 a 存在,但还没赋值,所以是 undefined
a = 10;          // 3. 赋值留在了原地

结论:var 的预解析只提升了名字 ,把丢在了原地。所以它不报错,但也记不住值。

函数提升

函数声明的待遇比var高级得多。它享有整体提升的特权

javascript 复制代码
sayHi(); // 我在定义之前就调用了!

function sayHi() {
    console.log("你好!");
}

这段代码是可以正常运行的!输出 "你好!"

浏览器眼中的代码长这样:

javascript 复制代码
// --- 预解析阶段 ---
function sayHi() {      // 整块都被提上来了
    console.log("你好!");
}

// --- 执行阶段 ---
sayHi();                // 所以这里能找到函数,正常执行

经典面试陷阱

这段代码是报错,还是输出我吃饱了?

javascript 复制代码
eat(); 

var eat = function() {
    console.log("我吃饱了");
};

报错,在在预解析(Hoisting)的眼里,变量声明赋值是被强行拆散

javascript 复制代码
// --- 1. 预解析阶段 ---
var eat;            // 变量 eat 被提升了,但默认值是 undefined
                    // 注意:后面的 function... 并没有跟着上来!

// --- 2. 执行阶段 ---
eat();              // 此时 eat 的值是 undefined。
                    // 你试图运行 undefined(),JS 引擎就会怒吼:
                    // "TypeError: eat is not a function"

eat = function() {  // 这一步才把函数赋值给 eat,但太晚了,程序上面已经挂了
    console.log("我吃饱了");
};

暂时性死区(TDZ)

var与let和const的区别:以下代码会怎样?

javascript 复制代码
console.log(name); // 这里会打印 undefined 吗?
let name = "张三";

会报错:Uncaught ReferenceError: Cannot access 'name' before initialization

(无法在初始化之前访问 'name')

为了搞懂var和let的区别,我们需要把"预解析"拆得更细一点。一个变量的诞生其实分三步:

  1. 创建 (Creation):在内存里登记名字"我有个变量叫 name"。

  2. 初始化 (Initialization):给它一个默认值(比如 undefined)。

  3. 赋值 (Assignment):真正把"张三"赋给它。

JS引擎在也会预解析对let进行创建,但不会初始化一个undefined。而是在外面加个锁(暂时性死区),在程序读到正式赋值那一行之前,谁"碰"谁"死"。

目的 :ES6想要把let 设计得更安全、更规范(不像var 那样既污染全局window,又允许在声明前乱用),所以才引入了 TDZ(死区) 机制。

相关推荐
切糕师学AI2 小时前
.NET Core Web 中的健康检查端点(Health Check Endpoint)
前端·kubernetes·.netcore
Traced back2 小时前
# C# WinForms 数据库清理系统基础知识与避坑指南
开发语言·数据库·c#
煜磊2 小时前
MD5加盐值-注册与登录
java·开发语言
蓉妹妹2 小时前
在React中使用Scroll嵌套Scroll,出现里面Scroll滚动条超出高度却滚动没反应的问题,解决方案添加nestedScrollEnabled
javascript·react native·react.js
茉莉玫瑰花茶2 小时前
C++ 17 详细特性解析(4)
开发语言·c++·算法
rosmis2 小时前
地铁病害检测系统软件改进记录-2-02
开发语言·前端·javascript
css趣多多2 小时前
解决ui组件flex1容器底部被撑开的问题
前端
欧阳x天2 小时前
STL详解(九)—— stack和queue的模拟实现
开发语言·c++
xqqxqxxq2 小时前
洛谷算法1-1 模拟与高精度(NOIP经典真题解析)java(持续更新)
java·开发语言·算法