本篇文章面向群體:入門級</br>
難度等級:★☆☆☆☆</br>
內(nèi)容較多,建議點(diǎn)贊收藏后閱讀
什么是變量
變量作為js中最常見也是我們最早接觸的js知識(shí)點(diǎn)氧敢,相信大家都不陌生,變量幾乎存在于所有的編程語言中衬吆,百度百科中對于變量的解釋為 變數(shù)或變量秸滴,是指沒有固定的值,可以改變的數(shù)栈源。變量以非數(shù)字的符號(hào)來表達(dá)挡爵,一般用拉丁字母。 而JavaScript中的變量是松散類型(弱類型)的凉翻,所謂松散類型就是可以用來保存任何類型的數(shù)據(jù)了讨,在聲明變量時(shí)無需指定變量的類型捻激。所以,當(dāng)我們聲明一個(gè)變量之后前计,可以存儲(chǔ)任意類型的數(shù)據(jù)胞谭。
怎么去聲明一個(gè)變量
變量名
在js中聲明一個(gè)變量通常通過關(guān)鍵字加一個(gè)變量名的形式來聲明一個(gè)變量,那對于變量名在js中是如何要求的呢男杈?
- 變量名必須以字母丈屹、下劃線(_)或者美元符($)開頭,后面可以跟字母伶棒、下劃線旺垒、美元符或者數(shù)字
- 變量名的長度不能超過255個(gè)字符
- 變量名必須區(qū)分大小寫
- 變量名中間不可有空格換行符及其他標(biāo)點(diǎn)符號(hào)
- 不能使用腳本語言保留的關(guān)鍵字作為變量名,如true肤无、false先蒋、function等(具體關(guān)鍵字列表可參考菜鳥教程JavaScript 保留關(guān)鍵字)
除了這些js明文規(guī)定的變量命名規(guī)則之外,為了代碼可讀性更高宛渐,我們通常也對變量命名有行業(yè)通用的命名規(guī)范竞漾。
- 盡量采用符合當(dāng)前語義的單詞進(jìn)行命名,如
age
窥翩、time
业岁,避免使用無意義的字符組合,如aaa
寇蚊、bbb
- 由于部分框架可能使用
$
作為關(guān)鍵字笔时,所以我們也應(yīng)盡量避免使用$
作為變量名 - 如果命名單詞超過兩個(gè)單詞,盡量采用駝峰法命名仗岸,如
userAge
允耿、myFirstName
- 盡量避免使用中文作為變量名,盡管那樣不會(huì)報(bào)錯(cuò)
聲明變量
上面說了爹梁,js聲明變量的方法為關(guān)鍵字加一個(gè)變量名右犹,說完了變量名,我們就來說一下聲明變量的關(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ù)聲明积糯,但并沒有什么卵用
- 聲明后的變量可修改
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)聲明過的變量再次聲明會(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è)問題
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è)問題
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ì)問,如果每一輪循環(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
一樣,只不過 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è)常量的值為引用類型(下面會(huì)講基本類型和引用類型)時(shí)候,雖然我們不能進(jìn)行重新賦值廓俭,但我們可以修改引用類型的值云石。
const a = {
name: '小明'
}
a.name = '小豪'
console.log(a.name) // 小豪
變量的值:基本類型與引用類型
我們知道,js中的值可以保存所有數(shù)據(jù)類型研乒,那么js中的數(shù)據(jù)類型又都有什么呢汹忠?
JavaScript中的數(shù)據(jù)類型有六種,其中有五種簡單數(shù)據(jù)類型(也叫基本數(shù)據(jù)類型)雹熬,還有一種復(fù)雜的數(shù)據(jù)類型——對象(Object)宽菜,由于本文主要講的是變量相關(guān)知識(shí),所以對數(shù)據(jù)類型不深入講解竿报,知識(shí)大概說一下铅乡。
基本類型
五種基本的數(shù)據(jù)類型:Undefined、Null烈菌、Boolean阵幸、Number和String花履。這5種基本數(shù)據(jù)類型是按值訪問的,因?yàn)榭梢圆僮鞅4嬖谧兞恐械膶?shí)際的值挚赊。我們就直接按正常思路來就行了诡壁。
var a = '小明'
var b = a
a = '小豪'
console.log(b) // '小明'
引用類型
由于對象的知識(shí)相對較多,后續(xù)會(huì)有專門的文章進(jìn)行介紹總結(jié)咬腕。
如果有點(diǎn)基礎(chǔ)同學(xué)都知道欢峰,原始類型存儲(chǔ)的是值,對象類型存儲(chǔ)的是地址(指針)涨共,
那么當(dāng)我們定義一個(gè)變量的值為對象的時(shí)候纽帖,由于存儲(chǔ)的實(shí)際是這個(gè)對象在內(nèi)存中的地址,相當(dāng)于我們在這里只是引用了這個(gè)對象举反,所以在對一些變量進(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ǔ)所引用的對象地址賦值給了b室囊,這時(shí)候a和b其實(shí)指向的是一個(gè)對象,所以當(dāng)我們修改其中任何一個(gè)的時(shí)候魁索,都是在對同一對象進(jìn)行修改
這樣很好的解釋了我們上面所說的const定義的常量可修改的問題融撞,當(dāng)我們用const定義的常量為對象時(shí),其實(shí)我們在這個(gè)常量里保存的只是一個(gè)對象的地址粗蔚,無論我們怎么修改這個(gè)對象尝偎,const定義的常量里保存的地址是沒有變化的,所以上面例子中對const定義的對象進(jìn)行修改其實(shí)并沒有違背const定義的變量不可修改的原則鹏控,只有我們給這個(gè)常量重新賦值一個(gè)新對象(也就是新地址)的時(shí)候致扯,才會(huì)觸發(fā)const定義的常量不可修改的規(guī)則。
變量作用域
作用域当辐,我們這里為了理解抖僵,可以簡單的理解為作用域就是變量可以生效及訪問的地方。JavaScript中分為全局作用域和局部作用域缘揪,全局作用域里的變量在所有的地方都可以訪問耍群,局部作用域只能在當(dāng)前作用域被訪問。
在一些類似于c語言的編程語言中寺晌,每一對花括號(hào)包裹的區(qū)域都有自己的作用域世吨,我們稱之為塊狀作用域,而在JavaScript中沒有塊級作用域(es6之前)呻征,取而代之的是函數(shù)作用域耘婚,所以我們通常所說的局部作用域也就是函數(shù)作用域。
var a = "hello" // 全局作用域
function foo() {
// 在此作用域可以訪問到a
console.log(a) // 'hello'
var b = 'world'
}
foo()
console.log(a) // 'hello'
// 由于變量b定義在函數(shù)foo內(nèi)陆赋,所以在函數(shù)foo的作用域外訪問不到
console.log(b) // b is not defined
在js中可以存在函數(shù)嵌套函數(shù)的情況沐祷,所以我們非常容易見到函數(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)鍵字凌那,從而可以讓我們在塊級作用域(大括號(hào))中聲明變量。
變量提升
在JavaScript 中吟逝,函數(shù)及變量的聲明都將被提升到當(dāng)前作用域的最頂部帽蝶。
//var a <------------------
console.log(a) // undefined ↑
var a // ---------------->
在上面的例子中,我們雖然聲明語句在打印語句的后面块攒,但是我們打印a卻并沒有報(bào)錯(cuò)励稳,就是因?yàn)檫@里的變量聲明被提到了當(dāng)前作用域的最上面,我們稱之為變量提升囱井。
在JavaScript中 var a = 'test'
其實(shí)是分為兩步進(jìn)行的驹尼,分別是變量聲明 var a
和 變量賦值 a = 'test'
, 變量聲明會(huì)被提前,但是變量賦值不會(huì)被提前庞呕,我們來看下面的例子
console.log(a) // undefined
var a = 'test'
// 上述代碼的執(zhí)行步驟可以用下面的代碼理解
var a
console.log(a)
a = 'test'
結(jié)合上面的函數(shù)作用域扶欣,我們來看一下下面的例子
var a = 'test'
function foo() {
console.log(a)
var a = 'hello'
}
foo()
大家猜一下打印的結(jié)果是什么,沒錯(cuò)千扶,就是 undefined
,我們這里定義了全局變量a骆捧,又定義了函數(shù)局部變量a澎羞,所以我們在執(zhí)行函數(shù) foo()
時(shí),其實(shí)是找的函數(shù) foo()
里的局部變量a敛苇,局部變量a通過變量提升到了函數(shù) foo()
的頂部妆绞,但是變量賦值卻沒有提升,所以最后打印結(jié)果為 undefined
枫攀,上面的代碼可以理解為下面這樣
var a = 'test'
function foo() {
var a
console.log(a)
a = 'hello'
}
foo()
需要注意的是 let
和 const
不存在變量提升括饶,在你定義他們之前使用會(huì)報(bào)錯(cuò)。比較有意思的是来涨,let
和 const
的報(bào)錯(cuò)還不一樣
console.log(test) // Cannot access 'test' before initialization
const test = 'hello'
console.log(message) // message is not defined
let message = 'hello'
// 如果我們在局部作用域里寫图焰,報(bào)錯(cuò)就一樣了
function foo() {
console.log(message) // Cannot access 'message' before initialization
let message = 'world'
}
foo()
作者確實(shí)基礎(chǔ)較差,寫文章更多的目的是為了提高自己的能力蹦掐,但是總結(jié)寫下來難免有啰嗦和失誤的地方技羔,如果可以幫到別人當(dāng)然十分開心僵闯,如果大家發(fā)現(xiàn)什么問題也歡迎隨時(shí)提出,我也會(huì)持續(xù)的學(xué)習(xí)藤滥,不斷的對文章新型修改鳖粟,希望大家一起進(jìn)步,加油