【青山学js】var let const有什么区别?什么是变量提升?

473 阅读7分钟

什么是变量

变量作为js中最常见也是我们最早接触的js知识点,相信大家都不陌生,变量几乎存在于所有的编程语言中,百度百科中对于变量的解释为 变数或变量,是指没有固定的值,可以改变的数。变量以非数字的符号来表达,一般用拉丁字母。 而JavaScript中的变量是松散类型(弱类型)的,所谓松散类型就是可以用来保存任何类型的数据,在声明变量时无需指定变量的类型。所以,当我们声明一个变量之后,可以存储任意类型的数据。

怎么去声明一个变量

变量名

在js中声明一个变量通常通过关键字加一个变量名的形式来声明一个变量,那对于变量名在js中是如何要求的呢?

  • 变量名必须以字母、下划线(_)或者美元符($)开头,后面可以跟字母、下划线、美元符或者数字
  • 变量名的长度不能超过255个字符
  • 变量名必须区分大小写
  • 变量名中间不可有空格换行符及其他标点符号
  • 不能使用脚本语言保留的关键字作为变量名,如true、false、function等(具体关键字列表可参考菜鸟教程JavaScript 保留关键字

除了这些js明文规定的变量命名规则之外,为了代码可读性更高,我们通常也对变量命名有行业通用的命名规范。

  • 尽量采用符合当前语义的单词进行命名,如agetime,避免使用无意义的字符组合,如aaabbb
  • 由于部分框架可能使用$作为关键字,所以我们也应尽量避免使用$作为变量名
  • 如果命名单词超过两个单词,尽量采用驼峰法命名,如userAgemyFirstName
  • 尽量避免使用中文作为变量名,尽管那样不会报错

声明变量

上面说了,js声明变量的方法为关键字加一个变量名,说完了变量名,我们就来说一下声明变量的关键字,js中声明变量的关键字有以下三种

var

var关键字是我们学习js最先接触也是早期声明变量最为常用的关键字,使用方法直接在var关键字后面直接跟变量名即可,如

var message = 'hello world'
console.log(message) // hello world

var 关键字特点

  • 可重复声明,但并没有什么卵用
  • 声明后的变量可修改
var message = 'hello'
message = 'world'
console.log(message) // world
  • 有初始值,每一个用var声明的变量初始值均为undefined
var message
console.log(message) // undefined
  • 可同时声明多个变量
var message, test, handleName
console.log(message) // undefined
console.log(test) // undefined
console.log(handleName) // undefined
  • var 关键字可以省略
message = 'hello'
console.log(message) // hello

// js 中如果省略var关键字,则会自动创建全局变量
function foo() {
    message = 'hello'
    console.log(message) // hello
}
foo()
console.log(message) // hello

// 如果使用,则会创建当前作用域的变量
function foo() {
    var message = 'hello'
    console.log(message) // hello
}
foo()
console.log(message) // message is not defined

* 注意,在严格模式下不可省略关键字,否则会报错

let

let 关键字是ES2015(ES6) 新增加的重要的 JavaScript 关键字,用法和 var 一样

let message = 'hello world'
console.log(message) // hello world

let 关键字与var不同的地方

  • 不可重复声明,已经声明过的变量再次声明会报错(在同一作用域内)
var message
let message // Identifier 'message' has already been declared

let test = 'test'
let test = 'hello' // Identifier 'test' has already been declared
  • let 所声明的变量,只在 let 命令所在的代码块内有效
{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

利用 let 的这一特性可以解决很经典的一个问题

for (var index = 0; index < 6; index++) {
  setTimeout(function() {
    console.log(index)
  }, 100)
}

这段代码的结果输出的是6个6,这是因为这里的index是用 var 定义的,每一轮循环所用的index都是全局变量,所以等到100毫秒后执行的时候,index已经循环完变成了6,那么利用 let 仅在当前代码块有效的这个特性,就可以很好的解决这个问题

for (let index = 0; index < 6; index++) {
  setTimeout(function() {
    console.log(index)
  }, 100)
}
// 0 1 2 3 4 5

因为 let 仅在当前代码块有效,所以这里每一轮循环都相当于定义了一个新的index,所以最后输出的时循环时候的值。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

const

const 关键字是ES2015(ES6) 新增加的重要的 JavaScript 关键字,用法和 var 一样,只不过 const 生成的是一个或多个常量,在有些时候,我们定义的值不希望被覆盖或者修改,我们就可以用 const

const message = 'hello world'
console.log(message) // hello world

const 关键字 和 var 关键字不同的地方

  • 不可重复声明
  • 声明时必须初始化,否则会报错
const a // Missing initializer in const declaration
const b = 'test' // 正确
  • 声明的常量不可进行赋值修改
const a = 'hello'
a = 'world' // Uncaught TypeError: Assignment to constant variable.
其实const声明的常量并非严格意义上的常量,因为当我们用const定义一个常量的值为引用类型(下面会讲基本类型和引用类型)时候,虽然我们不能进行重新赋值,但我们可以修改引用类型的值。
const a = {
    name: '小明'
}
a.name = '小豪'
console.log(a.name) // 小豪

变量的值:基本类型与引用类型

我们知道,js中的值可以保存所有数据类型,那么js中的数据类型又都有什么呢? JavaScript中的数据类型有六种,其中有五种简单数据类型(也叫基本数据类型),还有一种复杂的数据类型——对象(Object),由于本文主要讲的是变量相关知识,所以对数据类型不深入讲解,知识大概说一下。

基本类型

五种基本的数据类型:Undefined、Null、Boolean、Number和String。这5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。我们就直接按正常思路来就行了。

var a = '小明'
var b = a
a = '小豪'
console.log(b) // '小明'

引用类型

由于对象的知识相对较多,后续会有专门的文章进行介绍总结。

如果有点基础同学都知道,原始类型存储的是值,对象类型存储的是地址(指针), 那么当我们定义一个变量的值为对象的时候,由于存储的实际是这个对象在内存中的地址,相当于我们在这里只是引用了这个对象,所以在对一些变量进行复制赋值和修改时候就会出现一些意想不到的事情,比如下面的代码

var a = {
  name: '小明'
}
var b = a
b.name = '小豪'
console.log(a.name) // 小豪

在上面的例子中,我们把a赋值给b,实际上只是把a所引用的对象地址赋值给了b,这时候a和b其实指向的是一个对象,所以当我们修改其中任何一个的时候,都是在对同一对象进行修改

这样很好的解释了我们上面所说的const定义的常量可修改的问题,当我们用const定义的常量为对象时,其实我们在这个常量里保存的只是一个对象的地址,无论我们怎么修改这个对象,const定义的常量里保存的地址是没有变化的,所以上面例子中对const定义的对象进行修改其实并没有违背const定义的变量不可修改的原则,只有我们给这个常量重新赋值一个新对象(也就是新地址)的时候,才会触发const定义的常量不可修改的规则。

变量作用域

作用域,我们这里为了理解,可以简单的理解为作用域就是变量可以生效及访问的地方。JavaScript中分为全局作用域和局部作用域,全局作用域里的变量在所有的地方都可以访问,局部作用域只能在当前作用域被访问。

在一些类似于c语言的编程语言中,每一对花括号包裹的区域都有自己的作用域,我们称之为块状作用域,而在JavaScript中没有块级作用域(es6之前),取而代之的是函数作用域,所以我们通常所说的局部作用域也就是函数作用域。

var a = "hello" // 全局作用域
function foo() {
    // 在此作用域可以访问到a
    console.log(a) // 'hello'
    var b = 'world'
}
foo()
console.log(a) // 'hello'
// 由于变量b定义在函数foo内,所以在函数foo的作用域外访问不到
console.log(b) // b is not defined

在js中可以存在函数嵌套函数的情况,所以我们非常容易见到函数作用域嵌套的情况,这时候就会形成一条作用域链,在js中,当你使用一个变量时,JavaScript引擎会首先在当前作用域内寻找,如果找不到,就会到上一层作用域寻找,如果一直找到顶层作用域(也就是全局作用域)还找不到时,就会报错。

ES2015(ES6) 新增加了 let 关键字,从而可以让我们在块级作用域(大括号)中声明变量。

变量提升

在JavaScript 中,函数及变量的声明都将被提升到当前作用域的最顶部。

//var a   <------------------
console.log(a) // undefined  ↑
var a   //  ---------------->

在上面的例子中,我们虽然声明语句在打印语句的后面,但是我们打印a却并没有报错,就是因为这里的变量声明被提到了当前作用域的最上面,我们称之为变量提升。

在JavaScript中 var a = 'test' 其实是分为两步进行的,分别是变量声明 var a 和 变量赋值 a = 'test', 变量声明会被提前,但是变量赋值不会被提前,我们来看下面的例子

console.log(a) // undefined
var a = 'test'

// 上述代码的执行步骤可以用下面的代码理解

var a
console.log(a)
a = 'test'

结合上面的函数作用域,我们来看一下下面的例子

var a = 'test'
function foo() {
  console.log(a)
  var a = 'hello'
}
foo()

大家猜一下打印的结果是什么,没错,就是 undefined ,我们这里定义了全局变量a,又定义了函数局部变量a,所以我们在执行函数 foo() 时,其实是找的函数 foo() 里的局部变量a,局部变量a通过变量提升到了函数 foo() 的顶部,但是变量赋值却没有提升,所以最后打印结果为 undefined,上面的代码可以理解为下面这样

var a = 'test'
function foo() {
  var a
  console.log(a)
  a = 'hello'
}
foo()

需要注意的是 letconst 不存在变量提升,在你定义他们之前使用会报错。比较有意思的是,letconst的报错还不一样

console.log(test) // Cannot access 'test' before initialization
const test = 'hello'

console.log(message) // message is not defined
let message = 'hello'

// 如果我们在局部作用域里写,报错就一样了
function foo() {
  console.log(message) // Cannot access 'message' before initialization
  let message = 'world'
}
foo()

作者确实基础较差,写文章更多的目的是为了提高自己的能力,但是总结写下来难免有啰嗦和失误的地方,如果可以帮到别人当然十分开心,如果大家发现什么问题也欢迎随时提出,我也会持续的学习,不断的对文章新型修改,希望大家一起进步,加油