前言
農曆2019已經過去,趁著上班前這段時間,整理了一下javascript的基礎知識,在此給大家做下分享,喜歡的大佬們可以給個小贊。 本人github: github.com/Michael-lzg
相關文章:
js 是一門單執行緒語言
。 js 引擎有一個主執行緒(main thread)用來解釋和執行 js 程式,實際上還存在其他的執行緒。例如:處理 ajax 請求的執行緒、處理 DOM 事件的執行緒、定時器執行緒、讀寫檔案的執行緒(例如在 node.js 中)等等。這些執行緒可能存在於 js 引擎之內,也可能存在於 js 引擎之外,在此我們不做區分。不妨叫它們工作執行緒。
JS 執行上下文
當程式碼執行時,會產生一個對應的執行環境,在這個環境中,所有變數會被事先提出來(變數提升),有的直接賦值,有的為預設值 undefined,程式碼從上往下開始執行,就叫做執行上下文。
例子 1: 變數提升
foo // undefined
var foo = function() {
console.log('foo1')
}
foo() // foo1,foo賦值
var foo = function() {
console.log('foo2')
}
foo() // foo2,foo重新賦值
複製程式碼
例子 2:函式提升
foo() // foo2
function foo() {
console.log('foo1')
}
foo() // foo2
function foo() {
console.log('foo2')
}
foo() // foo2
複製程式碼
例子 3:宣告優先順序,函式 > 變數
foo() // foo2
var foo = function() {
console.log('foo1')
}
foo() // foo1,foo重新賦值
function foo() {
console.log('foo2')
}
foo() // foo1
複製程式碼
執行環境
在 JavaScript 的世界裡,執行環境有三種,分別是:
- 全域性環境:程式碼首先進入的環境
- 函式環境:函式被呼叫時執行的環境
- eval 函式:(不常用)
執行上下文特點
- 單執行緒,在主程式上執行
- 同步執行,從上往下按順序執行
- 全域性上下文只有一個,瀏覽器關閉時會被彈出棧
- 函式的執行上下文沒有數目限制
- 函式每被呼叫一次,都會產生一個新的執行上下文環境
執行上下文棧
執行全域性程式碼時,會產生一個執行上下文環境,每次呼叫函式都又會產生執行上下文環境。當函式呼叫完成時,這個上下文環境以及其中的資料都會被消除,再重新回到全域性上下文環境。處於活動狀態的執行上下文環境只有一個。
其實這是一個壓棧出棧的過程——執行上下文棧。
var // 1.進入全域性上下文環境
a = 10,
fn,
bar = function(x) {
var b = 20
fn(x + b) // 3.進入fn上下文環境
}
fn = function(y) {
var c = 20
console.log(y + c)
}
bar(5) // 2.進入bar上下文環境
複製程式碼
執行上下文生命週期
1、建立階段
- 生成變數物件
- 建立作用域鏈
- 確定 this 指向
2、執行階段
- 變數賦值
- 函式引用
- 執行其他程式碼
3、銷燬階段
- 執行完畢出棧,等待回收被銷燬
javascript 事件迴圈
- 同步和非同步任務分別進入不同的執行"場所",同步的進入主執行緒,非同步的進入
Event Table
並註冊函式。 - 當指定的事情完成時,
Event Table
會將這個函式移入Event Queue
。 - 主執行緒內的任務執行完畢為空,會去
Event Queue
讀取對應的函式,進入主執行緒執行。 - 上述過程會不斷重複,也就是常說的
Event Loop
(事件迴圈)。
同步任務和非同步任務,我們對任務有更精細的定義:
macro-task(巨集任務):
可以理解是每次執行棧執行的程式碼就是一個巨集任務(包括每次從事件佇列中獲取一個事件回撥並放到執行棧中執行)。
瀏覽器為了能夠使得 JS 內部(macro)task
與 DOM 任務能夠有序執行,會在一個(macro)task
執行結束後,在下一個(macro)task
執行開始前,對頁面進行重新渲染。
(macro)task 主要包含:script
(整體程式碼)、setTimeout
、setInterval
、I/O、UI 互動事件、postMessage
、MessageChannel
、setImmediate(Node.js 環境)
micro-task(微任務):
可以理解是在當前 task 執行結束後立即執行的任務。也就是說,在當前 task 任務後,下一個 task 之前,在渲染之前。所以它的響應速度相比 setTimeout(setTimeout 是 task)會更快,因為無需等渲染。也就是說,在某一個 macrotask 執行完後,就會將在它執行期間產生的所有 microtask 都執行完畢(在渲染前)。
microtask 主要包含:Promise
.then
、MutaionObserver
、process.nextTick
(Node.js 環境)
舉個例子
我們來分析一段較複雜的程式碼,看看你是否真的掌握了 js 的執行機制:
console.log('1')
setTimeout(function() {
console.log('2')
process.nextTick(function() {
console.log('3')
})
new Promise(function(resolve) {
console.log('4')
resolve()
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6')
})
new Promise(function(resolve) {
console.log('7')
resolve()
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9')
process.nextTick(function() {
console.log('10')
})
new Promise(function(resolve) {
console.log('11')
resolve()
}).then(function() {
console.log('12')
})
})
// 1,7,8,2,4,5,6,3,9,11,12,10
複製程式碼
再來一段
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
複製程式碼
解決非同步問題的方法
- 回撥函式
ajax('XXX1', () => {
// callback 函式體
ajax('XXX2', () => {
// callback 函式體
ajax('XXX3', () => {
// callback 函式體
})
})
})
複製程式碼
- 優點:解決了同步的問題
- 缺點:回撥地獄,不能用 try catch 捕獲錯誤,不能 return
2、Promise 為了解決 callback 的問題而產生。
Promise 實現了鏈式呼叫,也就是說每次 then 後返回的都是一個全新 Promise,如果我們在 then 中 return ,return 的結果會被 Promise.resolve() 包裝。
- 優點:解決了回撥地獄的問題
- 缺點:無法取消 Promise ,錯誤需要通過回撥函式來捕獲
3、Async/await
- 優點是:程式碼清晰,不用像 Promise 寫一大堆 then 鏈,處理了回撥地獄的問題
- 缺點:await 將非同步程式碼改造成同步程式碼,如果多個非同步操作沒有依賴性而使用 await 會導致效能上的降低。
總結
- javascript 是一門單執行緒語言
- Event Loop 是 javascript 的執行機制
node 環境和瀏覽器的區別
1、全域性環境下 this 的指向
- node 中 this 指向 global
- 瀏覽器中 this 指向 window
- 這就是為什麼 underscore 中一上來就定義了一 root;
瀏覽器中的 window 下封裝了不少的 API 比如 alert 、document、location、history 等等還有很多, 我門就不能在 node 環境中 xxx();或 window.xxx();了。因為這些 API 是瀏覽器級別的封裝,存 javascript 中是沒有的。當然 node 中也提供了不少 node 特有的 API。
2、js 引擎
-
在瀏覽器中不同的瀏覽器廠商提供了不同的瀏覽器核心,瀏覽器依賴這些核心解釋折我們編寫的 js。但是考慮到不同核心的少量差異,我們需要對應相容性好在有一些優秀的庫幫助我們處理這個問題。比如 jquery、underscore 等等。
-
nodejs 是基於 Chrome's JavaScript runtime,也就是說,實際上它是對 GoogleV8 引擎(應用於 Google Chrome 瀏覽器)進行了封裝。V8 引 擎執行 Javascript 的速度非常快,效能非常好。
3、DOM 操作
- 瀏覽器中的 js 大多數情況下是在直接或間接(一些虛擬 DOM 的庫和框架)的操作 DOM。因為瀏覽器中的程式碼主要是在表現層工作。
- node 是一門服務端技術。沒有一個前臺頁面,所以我們不會再 node 中操作 DOM。
4、I/O 讀寫
與瀏覽器不同,我們需要像其他服務端技術一樣讀寫檔案,nodejs 提供了比較方便的元件。而瀏覽器(確保相容性的)想在頁面中直接開啟一個本地的圖片就麻煩了好多(別和我說這還不簡單,相對路徑。。。。。。試試就知道了要麼找個庫要麼二進位制流,要麼上傳上去有了網路地址在顯示。不然人家為什麼要搞一個 js 庫呢),而這一切 node 都用一個元件搞定了。
5、模組載入
-
javascript 有個特點,就是原生沒提供包引用的 API 一次性把要載入的東西全執行一遍,這裡就要看各位閉包的功力了。所用東西都在一起,沒有分而治之,搞的特別沒有邏輯性和複用性。如果頁面簡單或網站當然我們可以通過一些 AMD、CMD 的 js 庫(比如 requireJS 和 seaJS)搞定事實上很多大型網站都是這麼幹的。
-
nodeJS 中提供了 CMD 的模組載入的 API,如果你用過 seaJS,那麼應該上手很快。node 還提供了 npm 這種包管理工具,能更有效方便的管理我們飲用的庫
推薦文章
關注的我的公眾號不定期分享前端知識,與您一起進步!