讓天下沒(méi)有難學(xué)的js之JavaScript中變量的那些事

本篇文章面向群體:入門(mén)級(jí)</br>
難度等級(jí):★☆☆☆☆</br>
內(nèi)容較多,建議點(diǎn)贊收藏后閱讀

什么是變量

變量作為js中最常見(jiàn)也是我們最早接觸的js知識(shí)點(diǎn),相信大家都不陌生,變量幾乎存在于所有的編程語(yǔ)言中,百度百科中對(duì)于變量的解釋為 變數(shù)或變量,是指沒(méi)有固定的值,可以改變的數(shù)。變量以非數(shù)字的符號(hào)來(lái)表達(dá),一般用拉丁字母。 而JavaScript中的變量是松散類(lèi)型(弱類(lèi)型)的,所謂松散類(lèi)型就是可以用來(lái)保存任何類(lèi)型的數(shù)據(jù),在聲明變量時(shí)無(wú)需指定變量的類(lèi)型。所以,當(dāng)我們聲明一個(gè)變量之后,可以存儲(chǔ)任意類(lèi)型的數(shù)據(jù)。

怎么去聲明一個(gè)變量

變量名

在js中聲明一個(gè)變量通常通過(guò)關(guān)鍵字加一個(gè)變量名的形式來(lái)聲明一個(gè)變量,那對(duì)于變量名在js中是如何要求的呢?

  • 變量名必須以字母、下劃線(_)或者美元符($)開(kāi)頭,后面可以跟字母、下劃線、美元符或者數(shù)字
  • 變量名的長(zhǎng)度不能超過(guò)255個(gè)字符
  • 變量名必須區(qū)分大小寫(xiě)
  • 變量名中間不可有空格換行符及其他標(biāo)點(diǎn)符號(hào)
  • 不能使用腳本語(yǔ)言保留的關(guān)鍵字作為變量名,如true、false、function等(具體關(guān)鍵字列表可參考菜鳥(niǎo)教程JavaScript 保留關(guān)鍵字

除了這些js明文規(guī)定的變量命名規(guī)則之外,為了代碼可讀性更高,我們通常也對(duì)變量命名有行業(yè)通用的命名規(guī)范。

  • 盡量采用符合當(dāng)前語(yǔ)義的單詞進(jìn)行命名,如age、 time,避免使用無(wú)意義的字符組合,如aaabbb
  • 由于部分框架可能使用$作為關(guān)鍵字,所以我們也應(yīng)盡量避免使用$作為變量名
  • 如果命名單詞超過(guò)兩個(gè)單詞,盡量采用駝峰法命名,如userAge、myFirstName
  • 盡量避免使用中文作為變量名,盡管那樣不會(huì)報(bào)錯(cuò)

聲明變量

上面說(shuō)了,js聲明變量的方法為關(guān)鍵字加一個(gè)變量名,說(shuō)完了變量名,我們就來(lái)說(shuō)一下聲明變量的關(guān)鍵字,js中聲明變量的關(guān)鍵字有以下三種

var

var關(guān)鍵字是我們學(xué)習(xí)js最先接觸也是早期聲明變量最為常用的關(guān)鍵字,使用方法直接在var關(guān)鍵字后面直接跟變量名即可,如

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

var 關(guān)鍵字特點(diǎn)

  • 可重復(fù)聲明,但并沒(méi)有什么卵用
  • 聲明后的變量可修改
var message = 'hello'
message = 'world'
console.log(message) // world
  • 有初始值,每一個(gè)用var聲明的變量初始值均為undefined
var message
console.log(message) // undefined
  • 可同時(shí)聲明多個(gè)變量
var message, test, handleName
console.log(message) // undefined
console.log(test) // undefined
console.log(handleName) // undefined
  • var 關(guān)鍵字可以省略
message = 'hello'
console.log(message) // hello

// js 中如果省略var關(guān)鍵字,則會(huì)自動(dòng)創(chuàng)建全局變量
function foo() {
    message = 'hello'
    console.log(message) // hello
}
foo()
console.log(message) // hello

// 如果使用,則會(huì)創(chuàng)建當(dāng)前作用域的變量
function foo() {
    var message = 'hello'
    console.log(message) // hello
}
foo()
console.log(message) // message is not defined

<span style="color:red">*</span> 注意,在嚴(yán)格模式下不可省略關(guān)鍵字,否則會(huì)報(bào)錯(cuò)

let

let 關(guān)鍵字是ES2015(ES6) 新增加的重要的 JavaScript 關(guān)鍵字,用法和 var 一樣

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

let 關(guān)鍵字與var不同的地方

  • 不可重復(fù)聲明,已經(jīng)聲明過(guò)的變量再次聲明會(huì)報(bào)錯(cuò)(在同一作用域內(nèi))
var message
let message // Identifier 'message' has already been declared

let test = 'test'
let test = 'hello' // Identifier 'test' has already been declared
  • let 所聲明的變量,只在 let 命令所在的代碼塊內(nèi)有效
{
  let a = 10;
  var b = 1;
}

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

利用 let 的這一特性可以解決很經(jīng)典的一個(gè)問(wèn)題

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

這段代碼的結(jié)果輸出的是6個(gè)6,這是因?yàn)檫@里的index是用 var 定義的,每一輪循環(huán)所用的index都是全局變量,所以等到100毫秒后執(zhí)行的時(shí)候,index已經(jīng)循環(huán)完變成了6,那么利用 let 僅在當(dāng)前代碼塊有效的這個(gè)特性,就可以很好的解決這個(gè)問(wèn)題

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

因?yàn)?let 僅在當(dāng)前代碼塊有效,所以這里每一輪循環(huán)都相當(dāng)于定義了一個(gè)新的index,所以最后輸出的時(shí)循環(huán)時(shí)候的值。你可能會(huì)問(wèn),如果每一輪循環(huán)的變量i都是重新聲明的,那它怎么知道上一輪循環(huán)的值,從而計(jì)算出本輪循環(huán)的值?這是因?yàn)?JavaScript 引擎內(nèi)部會(huì)記住上一輪循環(huán)的值,初始化本輪的變量i時(shí),就在上一輪循環(huán)的基礎(chǔ)上進(jìn)行計(jì)算。

const

const 關(guān)鍵字是ES2015(ES6) 新增加的重要的 JavaScript 關(guān)鍵字,用法和 var 一樣,只不過(guò) const 生成的是一個(gè)或多個(gè)常量,在有些時(shí)候,我們定義的值不希望被覆蓋或者修改,我們就可以用 const

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

const 關(guān)鍵字 和 var 關(guān)鍵字不同的地方

  • 不可重復(fù)聲明
  • 聲明時(shí)必須初始化,否則會(huì)報(bào)錯(cuò)
const a // Missing initializer in const declaration
const b = 'test' // 正確
  • 聲明的常量不可進(jìn)行賦值修改
const a = 'hello'
a = 'world' // Uncaught TypeError: Assignment to constant variable.
其實(shí)const聲明的常量并非嚴(yán)格意義上的常量,因?yàn)楫?dāng)我們用const定義一個(gè)常量的值為引用類(lèi)型(下面會(huì)講基本類(lèi)型和引用類(lèi)型)時(shí)候,雖然我們不能進(jìn)行重新賦值,但我們可以修改引用類(lèi)型的值。
const a = {
    name: '小明'
}
a.name = '小豪'
console.log(a.name) // 小豪

變量的值:基本類(lèi)型與引用類(lèi)型

我們知道,js中的值可以保存所有數(shù)據(jù)類(lèi)型,那么js中的數(shù)據(jù)類(lèi)型又都有什么呢?
JavaScript中的數(shù)據(jù)類(lèi)型有六種,其中有五種簡(jiǎn)單數(shù)據(jù)類(lèi)型(也叫基本數(shù)據(jù)類(lèi)型),還有一種復(fù)雜的數(shù)據(jù)類(lèi)型——對(duì)象(Object),由于本文主要講的是變量相關(guān)知識(shí),所以對(duì)數(shù)據(jù)類(lèi)型不深入講解,知識(shí)大概說(shuō)一下。

基本類(lèi)型

五種基本的數(shù)據(jù)類(lèi)型:Undefined、Null、Boolean、Number和String。這5種基本數(shù)據(jù)類(lèi)型是按值訪問(wèn)的,因?yàn)榭梢圆僮鞅4嬖谧兞恐械膶?shí)際的值。我們就直接按正常思路來(lái)就行了。

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

引用類(lèi)型

由于對(duì)象的知識(shí)相對(duì)較多,后續(xù)會(huì)有專(zhuān)門(mén)的文章進(jìn)行介紹總結(jié)。

如果有點(diǎn)基礎(chǔ)同學(xué)都知道,原始類(lèi)型存儲(chǔ)的是值,對(duì)象類(lèi)型存儲(chǔ)的是地址(指針),
那么當(dāng)我們定義一個(gè)變量的值為對(duì)象的時(shí)候,由于存儲(chǔ)的實(shí)際是這個(gè)對(duì)象在內(nèi)存中的地址,相當(dāng)于我們?cè)谶@里只是引用了這個(gè)對(duì)象,所以在對(duì)一些變量進(jìn)行復(fù)制賦值和修改時(shí)候就會(huì)出現(xiàn)一些意想不到的事情,比如下面的代碼

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

在上面的例子中,我們把a(bǔ)賦值給b,實(shí)際上只是把a(bǔ)所引用的對(duì)象地址賦值給了b,這時(shí)候a和b其實(shí)指向的是一個(gè)對(duì)象,所以當(dāng)我們修改其中任何一個(gè)的時(shí)候,都是在對(duì)同一對(duì)象進(jìn)行修改

這樣很好的解釋了我們上面所說(shuō)的const定義的常量可修改的問(wèn)題,當(dāng)我們用const定義的常量為對(duì)象時(shí),其實(shí)我們?cè)谶@個(gè)常量里保存的只是一個(gè)對(duì)象的地址,無(wú)論我們?cè)趺葱薷倪@個(gè)對(duì)象,const定義的常量里保存的地址是沒(méi)有變化的,所以上面例子中對(duì)const定義的對(duì)象進(jìn)行修改其實(shí)并沒(méi)有違背const定義的變量不可修改的原則,只有我們給這個(gè)常量重新賦值一個(gè)新對(duì)象(也就是新地址)的時(shí)候,才會(huì)觸發(fā)const定義的常量不可修改的規(guī)則。

變量作用域

作用域,我們這里為了理解,可以簡(jiǎn)單的理解為作用域就是變量可以生效及訪問(wèn)的地方。JavaScript中分為全局作用域和局部作用域,全局作用域里的變量在所有的地方都可以訪問(wèn),局部作用域只能在當(dāng)前作用域被訪問(wèn)。

在一些類(lèi)似于c語(yǔ)言的編程語(yǔ)言中,每一對(duì)花括號(hào)包裹的區(qū)域都有自己的作用域,我們稱(chēng)之為塊狀作用域,而在JavaScript中沒(méi)有塊級(jí)作用域(es6之前),取而代之的是函數(shù)作用域,所以我們通常所說(shuō)的局部作用域也就是函數(shù)作用域。

var a = "hello" // 全局作用域
function foo() {
    // 在此作用域可以訪問(wèn)到a
    console.log(a) // 'hello'
    var b = 'world'
}
foo()
console.log(a) // 'hello'
// 由于變量b定義在函數(shù)foo內(nèi),所以在函數(shù)foo的作用域外訪問(wèn)不到
console.log(b) // b is not defined

在js中可以存在函數(shù)嵌套函數(shù)的情況,所以我們非常容易見(jiàn)到函數(shù)作用域嵌套的情況,這時(shí)候就會(huì)形成一條作用域鏈,在js中,當(dāng)你使用一個(gè)變量時(shí),JavaScript引擎會(huì)首先在當(dāng)前作用域內(nèi)尋找,如果找不到,就會(huì)到上一層作用域?qū)ふ?,如果一直找到頂層作用域(也就是全局作用域)還找不到時(shí),就會(huì)報(bào)錯(cuò)。

ES2015(ES6) 新增加了 let 關(guān)鍵字,從而可以讓我們?cè)趬K級(jí)作用域(大括號(hào))中聲明變量。

變量提升

在JavaScript 中,函數(shù)及變量的聲明都將被提升到當(dāng)前作用域的最頂部。

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

在上面的例子中,我們雖然聲明語(yǔ)句在打印語(yǔ)句的后面,但是我們打印a卻并沒(méi)有報(bào)錯(cuò),就是因?yàn)檫@里的變量聲明被提到了當(dāng)前作用域的最上面,我們稱(chēng)之為變量提升。

在JavaScript中 var a = 'test' 其實(shí)是分為兩步進(jìn)行的,分別是變量聲明 var a 和 變量賦值 a = 'test', 變量聲明會(huì)被提前,但是變量賦值不會(huì)被提前,我們來(lái)看下面的例子

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

// 上述代碼的執(zhí)行步驟可以用下面的代碼理解

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

結(jié)合上面的函數(shù)作用域,我們來(lái)看一下下面的例子

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

大家猜一下打印的結(jié)果是什么,沒(méi)錯(cuò),就是 undefined ,我們這里定義了全局變量a,又定義了函數(shù)局部變量a,所以我們?cè)趫?zhí)行函數(shù) foo() 時(shí),其實(shí)是找的函數(shù) foo() 里的局部變量a,局部變量a通過(guò)變量提升到了函數(shù) foo() 的頂部,但是變量賦值卻沒(méi)有提升,所以最后打印結(jié)果為 undefined,上面的代碼可以理解為下面這樣

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

需要注意的是 letconst 不存在變量提升,在你定義他們之前使用會(huì)報(bào)錯(cuò)。比較有意思的是,letconst的報(bào)錯(cuò)還不一樣

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

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

// 如果我們?cè)诰植孔饔糜蚶飳?xiě),報(bào)錯(cuò)就一樣了
function foo() {
  console.log(message) // Cannot access 'message' before initialization
  let message = 'world'
}
foo()

作者確實(shí)基礎(chǔ)較差,寫(xiě)文章更多的目的是為了提高自己的能力,但是總結(jié)寫(xiě)下來(lái)難免有啰嗦和失誤的地方,如果可以幫到別人當(dāng)然十分開(kāi)心,如果大家發(fā)現(xiàn)什么問(wèn)題也歡迎隨時(shí)提出,我也會(huì)持續(xù)的學(xué)習(xí),不斷的對(duì)文章新型修改,希望大家一起進(jìn)步,加油

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容