JS变量声明·提升·函数提升

article/2025/9/16 19:03:53

变量声明

声明变量的关键字

在ES6之前,声明变量的关键字只有var,作用域只有:全局作用域和函数作用域;
到了ES6,引入let,const两个关键字声明变量和常量,同时引入新的作用域:块级作用域。

声明变量的形式

ES5 只有两种声明变量的方法:var命令和function命令。
ES6 除了添加letconst命令,另外两种声明变量的方式:import命令和class命令。所以ES6 一共有 6 种声明变量的方法。函数也是对象。

使用varfunction声明的变量会变量提升(函数名就是变量名),其他的不会被提升(隐式声明也不会)。

局部变量和全局变量

局部变量:在函数内部显式声明的变量(包括形参) + 块级作用域内let,const声明的变量;其他的则为全局变量

在 Web 浏览器中,全局执行环境被认为是 window 对象,可通过window对象访问全局变量和方法。(并非所有的全局变量都会作为全局对象的属性或方法)

显式声明与隐式声明

显式声明:带有声明变量关键字的
隐式声明:不带有声明变量关键字的

  • 隐式声明的变量都是全局变量
  • 显式声明的变量不可以通过delete删除,而隐式声明的却可以
  • 隐式声明的变量不存在变量提升,只有通过var声明的变量才会存在变量提升
  • 如果在函数内部隐式声明变量,直到函数执行后该变量才能称为全局变量

注意:只有var声明或者隐式声明的变量才会作为window对象的属性,命名函数function xx(){}和使用var声明匿名函数var xx=function(){}也会作为window对象的方法。

<body><input type="text" onclick="this.value=window.name" value="点击看name"/><input type="text" onclick="this.value=window.age" value="点击看age"/><input type="text" onclick="this.value=window.isSingle" value="点击看status"/><input type="text" onclick="this.value=window.sex" value="点击看sex"/><input type="text" onclick="this.value=window.animal" value="点击看animal"/><input type="text" onclick="this.value=window.profile()" value="点击看profile执行"/>
</body>
<script>var name = 'Free Joe';//显示声明(全局)age = 24;//隐式声明let animal ='dog';//let 声明的全局变量function profile(){var isSingle = true//显示声明(局部){sex = 'male';//隐式声明}return `My name is ${name},a ${age}-year-old ${sex} who is ${isSingle?'single '+animal:'in couples'}`;}profile();
</script>

在这里插入图片描述

📌使用name属性作为全局变量时,需要注意的是name属性本身也是window对象的自带属性,所以直接使用或者打印这个变量也不会报错,只是空字符串而已。


练手:

function foo(){//console.log(a)a=1;
}
foo()
console.log(a)

试下解开注释会是什么效果?

var & let

let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 外不能访问。包含有let,const{}才会被提升为块级作用域。

let不允许重复声明

同一作用域下,let声明的变量不能再被显示声明,var声明的变量是可以被var重复声明

var x = 1;
let x = 2;//SyntaxError: Identifier 'x' has already been declared
console.log(x)//报错重复声明var y = 1;
var y = 2;
console.log(y);//2

let不存在变量提升

同一作用域下,let 声明的变量不可以先赋值后声明

块级作用域存在变量提升,只是let,const声明的变量不会提升而已

x=1//ReferenceError: Cannot access 'x' before initialization
let x=2
console.log(x)//报错:未初始化x前不可读取xy=1
var y=2
console.log(y)//打印:2

块级作用域

let声明的变量只在块级作用域内有效

并非所有在块级作用域内声明的变量对外部作用域都是不可见的,只有使用了let,const声明的变量对外才是不可见。

x=1
{let x=2console.log(++x)//打印:3
}
console.log(x)//打印:1

没有let,const加持的{}是毫无意义的,也不会形成块级作用域,类似if的代码块

{var z = 2;}
console.log(z)//打印:2

暂存性死区

暂存性死区是相对于某一个使用let,const声明的变量/常量而言的,在该代码块内该变量定义之前的区域就是暂存性死区。换言之,在let,const所在的块级作用域中,无法在let,const之前调用其声明的标识符。

var i = 1 
{	
//死区开始console.log(i);//Uncaught ReferenceError: Cannot access 'i' before initializationlet i = 1;
//死区结束
}

解析:{}内使用let声明了变量i,那么 {}内的 i标识符会被屏蔽掉直到遇到let i才开启。所以无论你在{}外怎么申明声明定义该标识符也无济于事。

拓展:为什么需要块级作用域?

1.用来计数的循环变量泄露为全局变量

<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
</body>
<script>var btns = document.getElementsByTagName('button')for (var i = 0; i < btns.length; i++) {btns[i].onclick = function () {console.log('第' + (i + 1) + '个')}}
</script>

无论点哪个按钮都输出 第4个

循环变量的那部分 (姑且称之为“循环变量区”)声明了变量i,可以视为var i;i=0;,第二次循环就是i=1,滴三次就是i=2,准备第四次声明,i=3,条件不符合,没有进入循环体。当我们点击按钮的时候,触发方法,访问全局变量i,此时i为3,打印i+1则为4。

如何实现点击第几个按钮就提示那个按钮呢?很简单,只要将for循环中的var改成let来声明i即可。

let声明之后,for循环还有一个特别之处,循环变量区是一个父作用域,而循环体内部是一个单独的子作用域。
循环变量区let i;i=0;之后每次循环都是给i赋值而已,但是{}每次循环,都会建立一个全新的块级作用域。

 for (let i = 0; i < btns.length; i++) {btns[i].onclick = function () {console.log('第' + (i + 1) + '个')}}

2.内层变量可能会覆盖外层变量。

var tmp = new Date();function f() {console.log(tmp);if (false) {var tmp = 'hello world';}
}f(); // undefined

同作用域下,函数提升会高于全局变量的。函数内有自己的变量对象VO,编译阶段,tmp变量声明被提升至函数顶层作用域,只有条件为false时候才对tmp赋值,所以输出undefined

const

const命令跟let命令一样:不存在变量提升、具有块级作用域、存在暂时性死区、不允许重复声明 +

声明时候就必须初始化

const y;//SyntaxError: Missing initializer in const declaration

若值为字面量,值不可修改

字面量值保存在变量所指向的那个内存地址,因此等同于常量。

const x=2
console.log(++x)//TypeError: Assignment to constant variable.

若值为引用值(对象等),对象里面的属性值是可以更改的

变量指向的是内存地址,保存的是一个指针,const只能保存这个指针地址是固定的,至于他指向的数据就无法掌控了。

var obj={name:'FreeJoe',age:24}
const x =obj
obj.age=100
console.log(obj.age)//打印:100

当然,如果不希望属性值和属性名改变,也不希望扩充新的属性名和属性值,需要用Object.freeze()方法

const person = {name:'Joe',age:25}
Object.freeze(person)
person.name='Tom';
person.tel='110';
console.log(person);//{name:'Joe',age:25}

变量提升

JS 代码执行过程分为两个阶段

  • 词法分析:词法分析主要包括:分析变量声明、分析函数声明、分析形参三个部分。
  • 执行阶段

换而言之,变量的声明和赋值对于js执行引擎而言并非一步到位,而是先提取变量声明(变量符号表)再赋值。并非读取一条var a = 1就会立马分两步走,而是读取整个<script>域的var x = xx后,把所有的变量声明剥离出来后,再赋值。

使用var关键字声明的变量 ,会在同一域(局部或全局)所有的代码执行之前被声明;但是如果声明变量时不是用var关键字,则变量不会被声明提前。

x=1;
console.log(x)//ReferenceError: Cannot access 'x' before initialization
let xconsole.log(y)//undefined
var y=1;console.log(z)//ReferenceError: z is not defined
z=1;i=1;
console.log(i)//1
var i=2
console.log(i)//2

注:JavaScript 严格模式(strict mode)不允许使用未(后)声明的变量。

var a,b;
(function(){console.log(a)//undefinedconsole.log(b)//undefinedvar a=b=1console.log(a)//1console.log(b)//1
}())
console.log(a)//undefined
console.log(b)//1

不要被var a=b=1这些连等式迷惑,拆解出来,var a=b,b=1 。此处b是全局变量,而a只是局部变量。

函数提升

具名函数的声明有两种方式:1. 函数声明式 2. 函数表达式(匿名方式)

//函数声明式
function bar () {}
//函数表达式声明; 
var foo = function () {}

函数表达式中的函数不会被提升,函数声明会被提升。
声明式函数会提升到做顶层作用域

console.log(sub)//undefined
console.log(sub(1))//TypeError: sub is not a function
var sub =function(a){return --a}console.log(add(1))//2
function add(a){return ++a}

如果有变量和函数同名,则会忽略掉变量,只提升函数

console.log(foo)
var foo=1
function foo(){}//[Function: foo]
console.log(foo)//1

使用变量注意项

尽可能少用全局变量
容易造成命名污染,也一定程度影响内存释放。

优先推荐使用const -> let 最不推荐var
letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。

JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率。
const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。

他们没有明确说明为什么要这样做,但我认为这样做有以下好处:
1.加强语义化 (常量与变量) 更好的可读性
2.避免for(var ) 循环计数变量泄露为全局变量类似的问题。


http://chatgpt.dhexx.cn/article/NcWdtZgy.shtml

相关文章

js的变量提升理解

先来看一段代码 showName() console.log(myname) var myname 极客时间 function showName() {console.log(函数showName被执行); }思考一下它的输出 为什么myname在声明之前就可以输出到控制台>这就是因为变量提升 上面的代码等价于下边的代码 /* * 变量提升部分 */ //…

【JS】变量提升的本质

在开始之前&#xff0c;我们先来看一看如下几个现象&#xff1a; console.log(str); //undefined console.log(fn); //ƒ fn() {}var str "fantasy"; function fn() {} 如果对上述输出结果不感到陌生&#xff0c;那么你或许对变量提升有了一定了了解。 没错&…

js作用域和变量提升

作用域 作用域的分类 全局作用域 在一个js脚本中&#xff0c;最外层的作用域就是全局作用域&#xff0c;在此范围内声明的任何变量都会是全局变量&#xff0c;可以在程序的任意位置访问 局部作用域 函数作用域 var locales { school: function() { // 这里的school…

js之变量提升

首先 javascript 是一种弱类型、动态的、解释型的脚本语言。 弱类型&#xff1a;类型检查不严格&#xff0c;偏向于容忍隐式类型转换。 强类型&#xff1a;类型检查严格&#xff0c;偏向于不容忍隐式类型转换。 动态类型&#xff1a;运行的时候执行类型检查。 静态类型&…

JS中的变量提升总结

1.JS代码执行顺序 我们直觉上会认为JS的代码在执行时是由上到下一行一行执行的&#xff0c;但实际并不完全正确&#xff0c;下面的例子会证明&#xff1a; a haha var a console.log(a)上面的代码会输出什么呢&#xff1f; 如果按照我们认为的由上到下一行一行执行&#xf…

Flink Table 和 DataStream 转换

文章目录 Flink Table 和 DataStream 转换1. 表(Table) 转换为 流(DataStream)1.1 处理&#xff08;仅插入&#xff09;流1.1.1 fromDataStream()方法&#xff1a;1.1.1.1 fromDataStream(DataStream var1)1.1.1.2 fromDataStream(DataStream var1, Expression... var2)1.1.1.3…

数据流—DataStreamAPI

Hello Flink 1&#xff1a;构建一个典型的Flink流式应用需要一下几步&#xff1a; 1&#xff1a;设置执行环境。 2&#xff1a;从数据源中读取一条或多条流 3&#xff1a;通过一系列流式转换来实现应用逻辑。 4&#xff1a;选择性的将结果输出到一个或多个数据汇&#xff08;用…

【Flink】DataStream API使用之转换算子(Transformation)

转换算子&#xff08;Transformation&#xff09; 数据源读入数据之后&#xff0c;就是各种转换算子的操作&#xff0c;将一个或者多个DataSream转换为新的DataSteam&#xff0c;并且Flink可以针对一条流进行转换处理&#xff0c;也可以进行分流或者河流等多流转换操作&#xf…

Flink-DataStream执行环境和数据读取

​编辑执行环境 创建执行环境 执行模式 触发程序执行 源算子&#xff08;Source&#xff09; 读取有界数据流 读取无界数据 读取自定义数据源&#xff08;源算子&#xff09; DataStream是一个 Flink 程序&#xff0c;其实就是对 DataStream 的各种转换。具体来说&#xff0c…

Flink数据流类型之间的转换(WindowedStream、DataStream、KeyedStream、AllWindowStream之间的转换)

Flink提供了一些流API&#xff0c;其中包括WindowedStream、DataStream、KeyedStream和AllWindowStream。 &#x1f34a;WindowedStream是一种特殊的流&#xff0c;其中数据已按时间或数据元素的键进行分组&#xff0c;并且每个分组的数据都在窗口中按时间划分。这意味着&…

DataStream API

目录 原算子 准备工作&#xff0c;环境搭建 读取数据 从文件中读取数据 从集合中读取数据 从元素中读取数据 从source文件中读取数据 从kafka中读取数据 自定义source类型输出 转换算子 map转换 Filter转换 FlatMap转换 原算子 准备工作&#xff0c;环境搭建 为…

Flink学习——DataStream API

一个flink程序&#xff0c;其实就是对DataStream的各种转换。具体可以分成以下几个部分&#xff1a; 获取执行环境&#xff08;Execution Environment&#xff09;读取数据源&#xff08;Source&#xff09;定义基于数据的转换操作&#xff08;Transformations&#xff09;定义…

大数据开发-Flink-数据流DataStream和DataSet

文章目录 一、DataStream的三种流处理Api1.1 DataSource1.2 Transformation1.3 Sink 二、DataSet的常用Api2.1 DataSource2.2 Transformation2.3 Sink Flink主要用来处理数据流&#xff0c;所以从抽象上来看就是对数据流的处理&#xff0c;正如前面大数据开发-Flink-体系结构 &…

Flink DataStream API 介绍

Flink DataStream API 介绍 StreamExecutionEnvironment #mermaid-svg-JKeWa22W2vWA4zBS {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JKeWa22W2vWA4zBS .error-icon{fill:#552222;}#mermaid-svg-JKeWa22W2vWA4z…

DataStream API介绍与使用(一)

详细API参考官网 DataStream编程模型 在Flink整个系统架构中&#xff0c;对流计算的支持是其最重要的功能之一&#xff0c;Flink基于Google提出的DataFlow模型&#xff0c;实现了支持原生数据流处理的计算引擎。Flink中定义了DataStream API让用户灵活且高效地编写Flink流式应…

DataStream API(一)

Flink 有非常灵活的分层 API 设计&#xff0c;其中的核心层就是 DataStream/DataSet API。由于新版 本已经实现了流批一体&#xff0c; DataSet API 将被弃用&#xff0c;官方推荐统一使用 DataStream API 处理流数 据和批数据。由于内容较多&#xff0c;我们将会用几章的篇幅来…

DataStream(二)

目录 5.3.2 聚合算子&#xff08;Aggregation&#xff09; 5.3.3 用户自定义函数&#xff08;UDF&#xff09; 3. 扁平映射&#xff08;flatMap&#xff09; flatMap 操作又称为扁平映射&#xff0c;主要是将数据流中的整体&#xff08;一般是集合类型&#xff09;拆分成一个 …

Flink DataStream API

Flink DataStream API 编程指南 概览前言什么是DataStreamFlink程序剖析程序样例 Data SourcesDataStream Transformations算子数据流转换算子物理分区算子链和资源组 Data Sinks迭代执行参数 概览 前言 Flink中的DataStream程序是常规程序&#xff0c;可对数据流进行转换&am…

DataStream API(三)

目录 5.3.4 物理分区&#xff08;Physical Partitioning&#xff09; 5.4 输出算子&#xff08;Sink&#xff09; 5.4.1 连接到外部系统 5.4.2 输出到文件 5.4.3 输出到 Kafka 5.4.4 输出到 MySQL&#xff08;JDBC&#xff09; 5.4.5 自定义 Sink 输出 5.5 本章总结 5.3.…

流式数据采集和计算(十):Flink的DataStream学习笔记

Flink的DataStream学习笔记.. 1 Flink 基础.. 3 Flink特性.. 3 Flink和Spark对比.. 3 设计思路.. 3 状态管理.. 3 Flink 初探.. 4 设计架构.. 4 Flink on yarn. 5 流程分析.. 6 DataStream. 7 API程序结构.. 7 DataSource 8 Transformation. 9 Sink. 13 Time 14…