前言
農曆2019即將過去,趁著年前幾天上班事情少,整理了一下javascript的基礎知識,在此給大家做下分享,喜歡的大佬們可以給個小贊。本文在github也做了收錄。
本人github: github.com/Michael-lzg
JS 作用域
Javascript 變數的作用域無非就是兩種:全域性變數和區域性變數。Javascript 語言的特殊之處,就在於函式內部可以直接讀取全域性變數。
全域性作用域(Global Scope)
在程式碼中任何地方都能訪問到的物件擁有全域性作用域,一般來說一下幾種情形擁有全域性作用域:
- 最外層函式和在最外層函式外面定義的變數擁有全域性作用域
var name = 'Jack' // 全域性定義
function foo() {
var age = 23 // 區域性定義
function inner() {
// 區域性函式
console.log(age) //age 23
}
inner()
}
console.log(name) // yuan
console.log(age) // Uncaught ReferenceError: age is not defined,在外部沒有這個變數
foo() // 內嵌函式的列印23
inner() // Uncaught ReferenceError: inner is not defined 因為內嵌函式,找不到這個函式
複製程式碼
- 所有末定義直接賦值的變數自動宣告為擁有全域性作用域
var name = 'yuan'
function foo() {
age = 23 // 全域性定義
var sex = 'male' // 區域性定義
}
foo()
console.log(age) // 23
console.log(sex) // sex is not defined
複製程式碼
- 所有 window 物件的屬性擁有全域性作用域 一般情況下,window 物件的內建屬性都都擁有全域性作用域,例如 window.alert()、window.location、window.top 等等。
作用域鏈
當程式碼在一個環境中執行時,會建立變數物件的一個作用域鏈(作用域形成的鏈條)
- 作用域鏈的前端,始終都是當前執行的程式碼所在環境的變數物件
- 作用域鏈中的下一個物件來自於外部環境,而在下一個變數物件則來自下一個外部環境,一直到全域性執行環境
- 全域性執行環境的變數物件始終都是作用域鏈上的最後一個物件
當在內部函式中,需要訪問一個變數的時候,首先會訪問函式本身的變數物件,是否有這個變數,如果沒有,那麼會繼續沿作用域鏈往上查詢,直到全域性作用域。如果在某個變數物件中找到則使用該變數物件中的變數值。
內部環境可以通過作用域鏈訪問所有外部環境,但外部環境不能訪問內部環境的任何變數和函式。
變數提升
ES6 之前我們一般使用 var 來宣告變數,變數提升如下例子:
function test() {
console.log(a) //undefined
var a = 123
}
// 它的實際執行順序如下
function test() {
var a
console.log(a)
a = 123
}
test()
複製程式碼
函式提升
javascript 中不僅僅是變數宣告有提升的現象,函式的宣告也是一樣。具名函式的宣告有兩種方式:
- 函式宣告式
- 函式字面量式
//函式宣告式
function bar() {}
//函式字面量式
var foo = function() {}
複製程式碼
函式提升是整個程式碼塊提升到它所在的作用域的最開始執行
console.log(bar)
function bar() {
console.log(1) //ƒ bar () { console.log(1)}
}
// 實際執行順序
function bar() {
console.log(1)
}
console.log(bar)
複製程式碼
閉包
閉包就是能夠讀取其他函式內部變數的函式,函式沒有被釋放,整條作用域鏈上的區域性變數都將得到保留。由於在 javascript 語言中,只有函式內部的子函式才能讀取區域性變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。
產生一個閉包
建立閉包最常見方式,就是在一個函式內部建立另一個函式。閉包的作用域鏈包含著它自己的作用域,以及包含它的函式的作用域和全域性作用域。
function fn() {
var a = 1,
b = 2
function f1() {
return a + b
}
return f1
}
複製程式碼
上面例子中的 f1 就是一個閉包
閉包的應用
- 設計私有的方法和變數。 任何在函式中定義的變數,都可以認為是私有變數,因為不能在函式外部訪問這些變數。私有變數包括函式的引數、區域性變數和函式內定義的其他函式。把有權訪問私有變數的公有方法稱為特權方法(privileged method)。
function Animal() {
// 私有變數
var series = '哺乳動物'
function run() {
console.log('Run!!!')
}
// 特權方法
this.getSeries = function() {
return series
}
}
複製程式碼
- 匿名函式最大的用途是建立閉包。減少全域性變數的使用。從而使用閉包模組化程式碼,減少全域性變數的汙染。
var objEvent = objEvent || {}
(function() {
var addEvent = function() {
// some code
}
function removeEvent() {
// some code
}
objEvent.addEvent = addEvent
objEvent.removeEvent = removeEvent
})()
複製程式碼
addEvent 和 removeEvent 都是區域性變數,但我們可以通過全域性變數 objEvent 使用它,這就大大減少了全域性變數的使用,增強了網頁的安全性。
- 定義模組,我們將操作函式暴露給外部,而細節隱藏在模組內部。
function module() {
var arr = [];
function add(val) {
if (typeof val == 'number') {
arr.push(val);
}
}
function get(index) {
if (index < arr.length) {
return arr[index]
} else {
return null;
}
}
return {
add: add,
get: get
}
}
var mod1 = module();
mod1.add(1);
mod1.add(2);
mod1.add('xxx');
console.log(mod1.get(2));
複製程式碼
使用閉包的注意點
- 由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在 IE 中可能導致記憶體洩露。解決方法時,在退出函式之前,將不使用的區域性變數全部刪除。
function makeAdder(x) {
return function(y) {
return x + y
}
}
var add5 = makeAdder(5)
var add10 = makeAdder(10)
console.log(add5(2)) // 7
console.log(add10(2)) // 12
// 釋放對閉包的引用
add5 = null
add10 = null
複製程式碼
add5 和 add10 都是閉包。它們共享相同的函式定義,但是儲存了不同的環境。在 add5 的環境中,x 為 5。而在 add10 中,x 則為 10。最後通過 null 釋放了 add5 和 add10 對閉包的引用。
- 閉包會在父函式外部,改變父函式內部變數的值。所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法,把內部變數當作它的私有屬性,這時一定要小心,不要隨便改變父函式內部變數的值。
推薦文章
關注的我的公眾號不定期分享前端知識,與您一起進步!