一、作用域
了解作用域?qū)Τ绦驁?zhí)行的影響及作用域鏈的查找機制,使用閉包函數(shù)創(chuàng)建隔離作用域避免全局變量污染。
作用域(scope)規(guī)定了變量能夠被訪問的“范圍”贵涵,離開了這個“范圍”變量便不能被訪問,作用域分為全局作用域和局部作用域恰画。
1.1 局部作用域
局部作用域分為函數(shù)作用域和塊作用域宾茂。
函數(shù)作用域
在函數(shù)內(nèi)部聲明的變量只能在函數(shù)內(nèi)部被訪問,外部無法直接訪問拴还。
<script>
// 聲明 counter 函數(shù)
function counter(x, y) {
// 函數(shù)內(nèi)部聲明的變量
let s = x + y;
console.log(s); // 18
}
// 設(shè)用 counter 函數(shù)
counter(10, 8);
// 訪問變量 s
console.log(s); // 報錯
</script>
總結(jié):
- 函數(shù)內(nèi)部聲明的變量跨晴,在函數(shù)外部無法被訪問
- 函數(shù)的參數(shù)也是函數(shù)內(nèi)部的局部變量
- 不同函數(shù)內(nèi)部聲明的變量無法互相訪問
- 函數(shù)執(zhí)行完畢后,函數(shù)內(nèi)部的變量實際被清空了
塊作用域
在 JavaScript 中使用 {}
包裹的代碼稱為代碼塊自沧,代碼塊內(nèi)部聲明的變量外部將【有可能】無法被訪問坟奥。
<script>
{
// age 只能在該代碼塊中被訪問
let age = 18;
console.log(age); // 正常
}
// 超出了 age 的作用域
console.log(age); // 報錯
let flag = true;
if(flag) {
// str 只能在該代碼塊中被訪問
let str = 'hello world!';
console.log(str); // 正常
}
// 超出了 age 的作用域
console.log(str); // 報錯
for(let t = 1; t <= 6; t++) {
// t 只能在該代碼塊中被訪問
console.log(t); // 正常
}
// 超出了 t 的作用域
console.log(t); // 報錯
</script>
JavaScript 中除了變量外還有常量,常量與變量本質(zhì)的區(qū)別是【常量必須要有值且不允許被重新賦值】拇厢,常量值為對象時其屬性和方法允許重新賦值爱谁。
<script>
// 必須要有值
const version = '1.0.0';
// 不能重新賦值
// version = '1.0.1';
// 常量值為對象類型
const user = {
name: '小明',
age: 18
}
// 不能重新賦值
user = {};
// 屬性和方法允許被修改
user.name = '小小明';
user.gender = '男';
</script>
總結(jié):
-
let
聲明的變量會產(chǎn)生塊作用域,var
不會產(chǎn)生塊作用域 -
const
聲明的常量也會產(chǎn)生塊作用域 - 不同代碼塊之間的變量無法互相訪問
- 推薦使用
let
或const
注:開發(fā)中 let
和 const
經(jīng)常不加區(qū)分的使用孝偎,如果擔(dān)心某個值會不小被修改時访敌,則只能使用 const
聲明成常量。
1.2 全局作用域
<script>
標(biāo)簽和 .js
文件的【最外層】就是所謂的全局作用域衣盾,在此聲明的變量在函數(shù)內(nèi)部也可以被訪問寺旺。
<script>
// 此處是全局
function sayHi() {
// 此處為局部
}
// 此處為全局
</script>
全局作用域中聲明的變量,任何其它作用域都可以被訪問势决,如下代碼所示:
<script>
// 全局變量 name
let name = '小明';
// 函數(shù)作用域中訪問全局
function sayHi() {
// 此處為局部
console.log('你好' + name);
}
// 全局變量 flag 和 x
let flag = true;
let x = 10;
// 塊作用域中訪問全局
if(flag) {
let y = 5;
console.log(x + y); // x 是全局的
}
</script>
總結(jié):
- 為
window
對象動態(tài)添加的屬性默認(rèn)也是全局的阻塑,不推薦! - 函數(shù)中未使用任何關(guān)鍵字聲明的變量為全局變量果复,不推薦3旅А!!
- 盡可能少的聲明全局變量走搁,防止全局變量被污染
JavaScript 中的作用域是程序被執(zhí)行時的底層機制独柑,了解這一機制有助于規(guī)范代碼書寫習(xí)慣,避免因作用域?qū)е碌恼Z法錯誤私植。
1.3 作用域鏈
作用域鏈本質(zhì)上是底層的變量查找機制忌栅,在函數(shù)被執(zhí)行時,會優(yōu)先查找當(dāng)前函數(shù)作用域中查找變量曲稼,如果當(dāng)前作用域查找不到則會依次逐級查找父級作用域直到全局作用域索绪,如下代碼所示:
<script>
// 全局作用域
let a = 1;
let b = 2;
// 局部作用域
function f() {
let c;
// let a = 10;
console.log(a); // 1 或 10
console.log(d); // 報錯
// 局部作用域
function g() {
let d = 'yo';
// let b = 20;
console.log(b); // 2 或 20
}
// 調(diào)用 g 函數(shù)
g()
}
console.log(c); // 報錯
console.log(d); // 報錯
f();
</script>
總結(jié):
- 嵌套關(guān)系的作用域串聯(lián)起來形成了作用域鏈
- 相同作用域鏈中按著從小到大的規(guī)則查找變量
- 子作用域能夠訪問父作用域,父級作用域無法訪問子級作用域(就近原則)
1.4 閉包
閉包是一種比較特殊和函數(shù)躯肌,使用閉包能夠訪問函數(shù)作用域中的變量者春。從代碼形式上看閉包是一個做為返回值的函數(shù),如下代碼所示:
<script>
function foo() {
let i = 0;
// 函數(shù)內(nèi)部分函數(shù)
function bar() {
console.log(++i);
}
// 將函數(shù)做為返回值
return bar;
}
// fn 即為閉包函數(shù)
let fn = foo();
fn(); // 1
</script>
總結(jié):
閉包:一個作用域有權(quán)訪問另外一個作用域的局部變量清女,
好處:可以把一個變量使用范圍延伸
- 閉包本質(zhì)仍是函數(shù),只不是從函數(shù)內(nèi)部返回的
- 閉包能夠創(chuàng)建外部可訪問的隔離作用域晰筛,避免全局變量污染
- 過度使用閉包可能造成內(nèi)存泄漏
注:回調(diào)函數(shù)也能訪問函數(shù)內(nèi)部的局部變量嫡丙。
1.5 變量提升
變量提升是 JavaScript 中比較“奇怪”的現(xiàn)象,它允許在變量聲明之前即被訪問读第,
<script>
// 訪問變量 str
console.log(str + 'world!');
// 聲明變量 str
var str = 'hello ';
</script>
let和var都有提升曙博,但是let定義的變量沒有賦值之前是不可以使用、var可以使用是undefined
總結(jié):
- 變量在未聲明即被訪問時會報語法錯誤
- 變量在聲明之前即被訪問怜瞒,變量的值為
undefined
-
let
聲明的變量不存在變量提升父泳,推薦使用let
【也有人認(rèn)為具有提升但是不賦值不能使用】 - 變量提升出現(xiàn)在相同作用域當(dāng)中
- 實際開發(fā)中推薦先聲明再訪問變量
注:關(guān)于變量提升的原理分析會涉及較為復(fù)雜的詞法分析等知識,而開發(fā)中使用 let
可以輕松規(guī)避變量的提升吴汪。
二惠窄、函數(shù)
2.1 函數(shù)提升
函數(shù)提升與變量提升比較類似,是指函數(shù)在聲明之前即可被調(diào)用漾橙。
<script>
// 調(diào)用函數(shù)
foo();
// 聲明函數(shù)
function foo() {
console.log('聲明之前即被調(diào)用...');
}
// 不存在提升現(xiàn)象
bar();
var bar = function () {
console.log('函數(shù)表達式不存在提升現(xiàn)象...');
}
</script>
總結(jié):
- 函數(shù)提升能夠使函數(shù)的聲明調(diào)用更靈活
- 函數(shù)表達式不存在提升的現(xiàn)象
- 函數(shù)提升出現(xiàn)在相同作用域當(dāng)中
2.2 參數(shù)
函數(shù)參數(shù)的使用細(xì)節(jié)杆融,能夠提升函數(shù)應(yīng)用的靈活度。
默認(rèn)值
<script>
// 設(shè)置參數(shù)默認(rèn)值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好霜运,我叫${name}脾歇,我今年${age}歲了。</p>`);
}
// 調(diào)用函數(shù)
sayHi();
sayHi('小紅');
sayHi('小剛', 21);
</script>
總結(jié):
- 聲明函數(shù)時為形參賦值即為參數(shù)的默認(rèn)值
- 如果參數(shù)未自定義默認(rèn)值時淘捡,參數(shù)的默認(rèn)值為
undefined
- 調(diào)用函數(shù)時沒有傳入對應(yīng)實參時藕各,參數(shù)的默認(rèn)值被當(dāng)做實參傳入
動態(tài)參數(shù)
arguments
是函數(shù)內(nèi)部內(nèi)置的偽數(shù)組變量,它包含了調(diào)用函數(shù)時傳入的所有實參焦除。
<script>
// 求生函數(shù)激况,計算所有參數(shù)的和
function sum() {
// console.log(arguments);
let s = 0;
for(let i = 0; i < arguments.length; i++) {
s += arguments[i];
}
console.log(s);
}
// 調(diào)用求和函數(shù)
sum(5, 10); // 兩個參數(shù)
sum(1, 2, 4); // 兩個參數(shù)
</script>
總結(jié):
-
arguments
是一個偽數(shù)組 -
arguments
的作用是動態(tài)獲取函數(shù)的實參