JavaScript -- 变量

577 阅读7分钟

再JavaScript中,变量是用来保存数据的一种占位符,它是松散类型的。它可以保存任意类型的数据,也可以再任意时间点去改变它所保存数据的类型。再ECMAScript中有三个关键字可以声明变量,var、let、const。var是ECMAScript6之前唯一声明变量的关键字,而let和const是ECMAScript6之后出来的两种新的声明变量的关键字

var关键字

声明一个变量,是一个关键字 var 后面再跟上一个变量名(标识符)

// 一个声明变量的关键字(如var)后面跟着一个标识符(或者称为变量名)
var a = 10

上面的例子中相当于定义了一个变量a,然后再初始化的时候给它赋值了一个Number类型的数据10,如果不给它初始化的值,那么JavaScript会给变量保存一个值undefined

因为再js中变量是松散类型的,所以再之后也可以更改变量的数据和数据类型。

// 一个声明变量的关键字(如var)后面跟着一个标识符(或者称为变量名)
var a = 10
a = 'h1'

但是这样是不推荐的,当我们再项目中定义一个变量,最好就是将这个变量就定下来之后它是使用什么数据类型,这也算是一种开发规范

var的声明作用域

var声明的变量,会成为一个函数的局部变量。再函数外部是无法访问的。访问了就会报错

function fun () {
  var a = 10
}

fun()
console.log(a) // 报错

声明了一个函数,调用这个函数会创建一个变量a。但是再执行完这个函数的时候,也会销毁这个变量a。如果再函数中没有用关键字声明的变量,这个变量会变成全局的变量

function fun () {
  a = 10
}

fun()
console.log(a) // 10

var声明提升

使用var声明的变量,js会将它拉到到该作用域的顶部。

console.log(a) // undefined
var a = 10

上面的也不会报错,js再解析的时候会将上面一段代码进行改造,等价于下面这个例子

var a
console.log(a) // undefined
a = 10

不管你再这个作用域的哪里声明一个var的变量,js都会将这个变量拉到该作用域的顶部,这种效应叫做变量的提升

let关键字

let的作用跟var差不多,也是声明一个变量。但是let跟var有很多不一样的地方

let的作用域

没错,let有自己独特的作用域。let声明的范围叫做块级作用域

{
  let a = 10
}
console.log(a) // 报错
{
  var b = 10
}
console.log(b) // 10

let只用声明再带有{}的地方,会形成一个块状的作用域。再外部就无法访问到这个变量。但是var是没有这种作用域,他只有函数作用域。而块级作用域也是函数作用域的子集,所以这个作用域也适用于let声明的变量

let不能重复声明

再一个作用域里面不可以声明两个变量名一样的let变量。如果声明了会报错,但是var则不会

let a = 10
let a = 'hq' // 报错

// 哪怕你是使用了混合声明,var加let也会报错
var a = 10
let a = 'hi' // 报错

// JavaScript引擎会根据声明变量的作用域来判断是否是重复声明,所以嵌套了作用域来重复声明就不会报错
if (true) {
  let a = 10
  console.log(a) // 10
}

let a = 'h1'
console.log(a) // h1

暂时性死区

使用let声明的变量,不会像var一样进行会被提升

// let声明不可以再它之前使用
console.log(a) // 报错
let a = 10
// var声明可以使用,不会报错
console.log(b) // 10
var b = 10

再JavaScript引擎解析的时候也会注意let声明的变量,但是再这之前是不允许再前面使用这个变量的,关于这种情况被称为 ”暂时性死区“,在此阶段引用任何后面才声明的变量都会抛出 ReferenceError

全局声明

let声明的变量跟var声明的变量再全局声明的情况也有所不也一样。使用var声明的变量会变成window的属性,但是let不会

let a = 10
var b = 20

console.log(window.b) // 20
console.log(window.a) // undefined

const关键字

const关键字和let关键字基本相同,但是使用它的时候必须同时初始化好要报错的数据,并且再之后不能修改const的数据

// 必须初始化要保存的数据
const a // 报错
// 初始化好了以后不能进行修改
const b = 10
b = 20 // 报错

但是有一点,如果const声明的是一个对象,那么你可以进行修改对象里面的数据。因为变量保存的是一个指针,而不是数据本身。所以只要你不将这个变量重新赋值另一个对象,就不会报错

// 这样操作不会报错
const obj = { a: 10 }
obj.a = 20 // 不会报错
console.log(obj) // { a: 20 }
// 但是这样操作就不行
const obj = { a: 10 }
obj = { a: 20 } // 报错,改变了指针的指向

声明的方式

不要使用var声明

再声明变量的方式上面,es6为我们提供了let和const可以有助于我们提供代码的质量。这两种变量声明让变量有了更明确的作用域、声明位置,以及不变的值。相比于var声明变量的一些怪异行为,let和const提高了我们代码的质量。所以再声明变量的时候不要使用var声明,多使用let和const声明

先使用cosnt 再使用let

再些代码的时候,我们应该优先考虑使用const来声明变量。只有再知道后期变量会发生改变的时候再使用let。这样可以避免因意外赋值导致的非预期行为。这样可以让开发者更有信心地推断某些变量的值永远不会变,提高我们代码的质量

数据保存的差异

我们都知道JavaScript的数据类型分为7种数据类型,其中六种原始类型 一种引用类型

原始类型(简单类型)

  • String
  • Number
  • Boolean
  • Undefined
  • Null
  • Symbol

引用类型(复杂类型)

  • Object

而变量就是用来保存这些数据类型,便于开发者去使用。每一个变量只不过是保存一个任意数据类型的占位符

变量在保存和复制原始类型和引用类型有着区别

保存值

  • 变量保存原始类型的数据,是保存这个类型的值。所以我们可以直接通过变量来操作这个值

  • 变量保存引用类型的数据,是保存的这个类型的引用,不是保存这个类型的值。因为在JavaScript中Object是保存在内存里面 的,但是JavaScript是不允许开发者直接去访问这个内存位置。所以保存的是这个类型的引用 复制值

  • 保存了原始类型的变量,在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。在内存中会多出一份数据

// str和str2是两个独立的互相不干扰
const num1 = 1
let num2 = num1
num2 += 1
console.log(num2) // 2
console.log(num1) // 1
  • 保存了引用类型的变量,通过它来赋值到另一个变量,也会把这个变量的值赋值到新变量。因为引用类型的变量保存的是一个指针,所以在这个过程中实际上两个变量指向的是一个数据。内存中不会再创建出一份数据
// 因为obj1只不过是保存的一个引用(指针),所以obj2被赋值了一个指针。这个指针指向了一个对象
// 所以它们两个修改对象里面的属性,都会互相影响
const obj1 = { a: 1 }
const obj2 = obj1
console.log(obj1 === obj2) // true
obj2.a = 2
console.log(obj2) // { a: 2 }
console.log(obj1) // { a: 2 }