理解JavaScript的变量,作用域和提升机制

前言

变量是编程中最基础的概念,也是我们学习编程中最早接触且最重要的知识点。在JavaScript中我们有三种方式声明一个变量,通过关键词var, let, const

在这篇文章中,我们将理解变量是什么?怎么去声明和命名变量?var, let, const三者之间的区别?全局变量和局部变量的含义,区别和使用场景?

懒人包

ECMAScript 2015(ES6)引入的letconst是为了解决var,只有函数作用域无块作用域声明提升会产生异常,可被重新声明,不分常量变量的现实问题。所以现在我们尽量要使用letconst,摒弃var的声明方式。尽可能多地使用const,在循环重新分配的情况下使用let。 通常可以在处理遗留代码之外避免使用var

理解变量

抽象点说,变量是储存值的命名容器。我们将未来有可能多次引用的值存储在变量中。在JavaScript中变量包含的值不只是数字。它可以是任何JavaScript数据类型,例如对象和字符串。

因为在JavaScript基于ECMAScript 2015(ES6)语言规范之前,只有一种方法来声明变量-使用关键字var,所以市面上很多陈旧的代码和资料还是会只将var用作变量,但是在新关键词let,const引入的ES6时代下,如何理解和使用letconst十分必要。

我们可以通过var演示变量本身的概念,下图列子,我们会声明一个变量,并为其分配一个值。

// Assign the string value Sammy to the username identifier
var username = 'sammy_shark'

该语句包含以下部分:

  • 使用var关键字声明变量
  • 变量名称(标识符)用户名
  • 赋值操作,用 = 语法表示
  • 分配值 sammy_shark

那现在我们可以在代码中使用username,JavaScript会记住username携带的String类型的值sammy_shark

// Check if variable is equal to value
if (username === 'sammy_shark') {
  console.log(true)
}
true

变量可以被用来代表JavaScript中任何类型。在下面例子中我们可以把string, number, object, Boolean和 null 赋值到变量中。

// Assignment of various variables
var name = 'Sammy'
var spartans = 300
var kingdoms = ['mammals', 'birds', 'fish']
var poem = { roses: 'red', violets: 'blue' }
var success = true
var nothing = null

使用console.log,我们可以特定变量中包含的值内容。

// Send spartans variable to the console
console.log(spartans)
300

变量在内存中存储数据,以后就可以直接访问和修改。变量也可以重新分配和赋予新值。在下面简化的例子中,我们将演示如何把password存储到一个变量中,并且更新。

// Assign value to password variable
var password = 'hunter2'

// Reassign variable value with a new value
password = 'hunter3'

console.log(password)
'hunter3'

在实际程序中,password大多被安全的存储在数据库中,但是某些情况下,我们可能需要更新变量值 ,如上面例子password 的原值是 hunter2, 但是我们要把 hunter3赋予它 。

命名变量

变量名称在JavaScript中被称为 标识符。我们在了解JavaScript的语法和代码结构中讨论了标识符命名的一些规则。以下是必须遵循的一些规则。

  • 变量名可以由字母(a-z),数字(0-9),美元符号($)和下划线(_)组成。
  • 变量名称不得包含空格(制表符或空格)
  • 变量名称不能以数字开头
  • 命名的变量不能包含任何保留关键字
  • 变量名称区分大小写

JavaScript还具有使用驼峰大小写的约定(有时称为camelCase),这是写第一个单词为小写,随后所有单词都大写的惯例。除某些例外,大多数标识符将遵循此约定。

看上去要遵循的规则有点多,其实驼峰式写法就可以满足正常工作需求。

作用域

作用域 scope在JavaScript中指当前代码的上下文,它决定了变量对JavaScript的访问性。在JavaScript中有两种作用域 局部全局

全局变量被声明在块block外部。局部变量被声明在块中。在下面的例子中,我们将创建一个全局变量。

// Initialize a global variable
var creature = 'wolf'

我们学过变量可以被重新赋值。使用局部作用域,实际我们可以在不改变初始值情况下新建一个和外部作用域同样名称的变量。

在下面的示例中,我们将创建一个全局species变量。 而在函数内部有个具有相同名称的局部species变量。 在控制台打印它们,我们可以看到变量的值根据作用域而有所不同,并且原始值没有更改。

// Initialize a global variable
var species = 'human'

function transform() {
  // Initialize a local, function-scoped variable
  var species = 'werewolf'
  console.log(species)
}

// Log the global and local variable
console.log(species)
transform()
console.log(species)
human
werewolf
human

在这个例子中局部变量是 函数作用域。用var关键词声明的变量是函数作用域,意味着它们仅将函数识别为一个单独的作用域。局部作用域的变量不能被全局作用域变量访问。

新关键字 let and const块状作用域,这意味着不仅在函数块中创建了局部作用域,而且还从任何其他块创建作用域。JavaScript中其他类型的代码块由关键字组成,例如if,forwhile

为了说明函数作用域变量和块作用域变量之间的区别,我们将使用letif块中分配一个新变量。

var fullMoon = true

// Initialize a global variable
let species = 'human'

if (fullMoon) {
  // Initialize a block scoped variable
  let species = 'werewolf'
  console.log(`It is a full moon. Lupin is currently a ${species}.`)
}

console.log(`It is not a full moon. Lupin is currently a ${species}.`)
It is a full moon. Lupin is currently a werewolf.
It is not a full moon. Lupin is currently a human.

在此示例中,species变量在全局范围内具有一个值(human),在局部具有另一个值(werewolf)。但是如果我们使用var,将会有不同的结果。

var fullMoon = true

// Use var to initialize a variable
var species = 'human'

if (fullMoon) {
  // Attempt to create a new variable in a block
  var species = 'werewolf'
  console.log(`It is a full moon. Lupin is currently a ${species}.`)
}

console.log(`It is not a full moon. Lupin is currently a ${species}.`)
It is a full moon. Lupin is currently a werewolf.
It is not a full moon. Lupin is currently a werewolf.

在本示例的结果中,全局作用域变量和块作用域变量最终都具有相同的值werewolf。 这是因为您没有在var中创建新的局部变量,而是在同一作用域中重新分配了相同的变量。var不理解if是另一个新作用域的一部分。

总而言之,作用域是变量对JavaScript的可见性。 全局作用域是作用域的最外部上下文,而局部作用域是更具体的作用域。 局部作用域有两种类型-函数作用域和块作用域。var仅限于函数作用域,这意味着只能在函数内部创建新作用域。letconst具有块作用域,这意味着任何块都会创建一个新的局部作用域,例如ifforwhile。 块作用域更安全,因为它产生的代码不太可能无意间覆盖变量值。

提升机制

到目前为止,在大多数示例中,我们都使用var声明一个变量,并使用一个值对其进行了初始化。 声明和初始化后,我们可以访问或重新分配变量。

如果我们在声明和初始化变量之前尝试使用它,将返回undefined

// Attempt to use a variable before declaring it
console.log(x)

// Variable assignment
var x = 100
undefined

但是,如果省略var关键字,则不再声明该变量,而只是对其进行初始化。 它将返回ReferenceError并停止脚本的执行。

// Attempt to use a variable before declaring it
console.log(x)

// Variable assignment without var
x = 100
ReferenceError: x is not defined

这样做的原因是由于提升,这是一种JavaScript操作,其中变量和函数声明移至其作用域的顶部。 由于只提升实际的声明,而不提升初始化,因此第一个示例中的值返回undefined

为了更清楚地展示,下面是我们编写的代码以及JavaScript实际如何解释它。

// The code we wrote
console.log(x)
var x = 100

// How JavaScript interpreted it
var x
console.log(x)
x = 100

在执行脚本之前,JavaScript将x作为变量保存到内存中。 由于在定义之前调用了它,因此结果是undefined而不是100,但是它不会导致ReferenceError并暂停脚本。

我们原来预估的最可能的x的期望输出为true,结果输出是undefined

// Initialize x in the global scope
var x = 100

function hoist() {
  // A condition that should not affect the outcome of the code
  if (false) {
    var x = 200
  }
  console.log(x)
}

hoist()
undefined

在上面示例中,我们在全局中将x声明为100。 在if语句中,x可能会更改为200,但是由于条件为false,它应该不会影响x的值。 取而代之的是,将x提升到了hoist()函数的顶部,并且值变成了undefined。

这种不可预测的行为可能会导致程序中的错误。 因为letconst是块作用域的,所以它们不会以这种方式提升,如下所示。

// Initialize x in the global scope
let x = true

function hoist() {
  // Initialize x in the function scope
  if (3 === 4) {
    let x = false
  }
  console.log(x)
}

hoist()
true

变量的重复声明,在var中使用没有问题,但在在letconst中将引发错误。

// Attempt to overwrite a variable declared with var
var x = 1
var x = 2

console.log(x)
2
// Attempt to overwrite a variable declared with let
let y = 1
let y = 2

console.log(y)
Uncaught SyntaxError: Identifier 'y' has already been declared

总而言之,var允许提升,即将变量声明保存到内存中。 这会导致代码中未定义变量的意外结果。 引入letconst解决了这个问题,方法是在声明变量之前尝试使用变量或多次声明变量时抛出错误。

常量

我们已经学习了使用var创建变量的方法,并且了解了letconst如何解决与范围和提升有关的潜在问题。 因此,建议停止使用var,而使用较新的letconst。 尽管let可以做var可以做的一切,但是const还有一些其他规则可以遵循。

许多编程语言都有常量,它们是不能修改或更改的值。 const是根据常量建模的,分配给const的值不能重新分配。

// Assign value to const
const SPECIES = 'human'

// Attempt to reassign value
SPECIES = 'werewolf'

console.log(SPECIES)
Uncaught TypeError: Assignment to constant variable.

尝试重新分配SPECIES将导致错误。

由于无法重新分配const值,因此需要同时声明和初始化它们,否则还会引发错误。

// Declare but do not intialize a const
const TODO;

console.log(TODO);
Uncaught SyntaxError: Missing initializer in const declaration

通常将所有的const标识符都写成大写。 这易于与其他变量值区分开。

在编程中无法更改的值称为“不可变”,而相反的值则称为“可变”。 虽然不能重新分配const,但它们不是不变的,因为可以修改对象的属性。

// Create a CAR object with two properties
const CAR = {
  color: 'blue',
  price: 15000,
}

// Modify a property of CAR
CAR.price = 20000

console.log(CAR)
{ color: 'blue', price: 20000 }

总而言之,不能重新分配const值,必须将其及其声明一起初始化。

var, let和const区别

JavaScript具有三个不同的关键字来声明变量,这为语言增加了一层复杂性。 两者之间的差异基于范围,提升和重新分配。

关键词 作用域 提升 可以被重新赋值 可以被重新声明
var 函数作用域 Yes Yes Yes
let 块作用域 No Yes No
const 块作用域 No No No

您可能想知道应该在自己的程序中使用这三个关键词。 普遍接受的做法是,尽可能多地使用const,在循环重新分配的情况下使用let。 通常可以在处理遗留代码之外避免使用var

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356