4. JavaScript 環境與規則原理 - ZoeHYH/mentor-program-4th GitHub Wiki
過去 JavaScript 只能在瀏覽器上運行,在開發人員工具的 console 裡輸入程式碼。
使用 Node.js 可以在電腦運行 JavaScript 。
語言也會進行更新,設定新的語法與型別,目前 JavaScript 更新到 ES6 ,更新後還要一段時間,瀏覽器才會跟上,語法才能普及。
vim 檔案名稱.html #儲存即可直接建立檔案
<script>
debuuger #啟用開發人員工具 source 頁面 debug 功能
#內容
</script>
open 檔案
#在開發人員工具裡檢視
#下載 installer ,並安裝程式
node -v #在 CLI 輸入,顯示當前版本表示安裝成功
vim 檔案名稱.js #直接輸入 JavaScript
node 檔案 #在 CLI 印出結果
node # Enter 進入JavaScript 環境
#輸入 JavaScript
# Ctrl+c 跳出
process.argv #放在 JavaScript 裡可印出 node 運行時傳入的參數
process.argv[0] # node 路徑
process.argv[1] # JavaScript 檔案路徑
process.argv[其他數字] #後續參數
-
JavaScript 程式撰寫
var readline = require('readline'); //執行後手動輸入資料 var lines = [] var rl = readline.createInterface({ input: process.stdin //使用文件就會直接輸入到 input }); rl.on('line', function (line) { lines.push(line) //將每行內容存到陣列 }); rl.on('close', function() { solve(lines) //關閉前輸出 }) function solve(lines) { //撰寫 JavaScript 函式 }
var readline = require('readline'); //執行後手動輸入資料 var lines = [] var rl = readline.createInterface({ input: process.stdin //使用文件就會直接輸入到 input }); rl.on('line', function (line) { lines.push(line) //將每行內容存到陣列 }); rl.on('close', function() { aolve(lines) //關閉前輸出 }) function solve(lines) { //撰寫 JavaScript 函式 }
-
CLI
node 檔案 #輸入 # Windows Crtl+z 結束輸入,其他 Crtl+d
cat input.txt | env node code.js
cat input.txt | command node code.js
cat input.txt | node code.js # windows 可能無法使用
console.log('字串') //印出字串,同時也是重要的 debug 工具
//註解一行
/*註解多行*/
執行動作, if
、switch
、 for
等等。
if (a === 3) { // if 本身是 Statement
console.log('Hello');
}
輸入後會回傳值的陳述句,可被簡化為某種值,最基本的就是用變數與運算子構成。
2 + 4
一個放資料位置的箱子。
調用變數時,電腦會一層一層往外尋找變數宣告的作用域,相對於動態作用域 Dynamic Scope ,靜態作用域在宣告時就固定了。
一個物件清單。
進入function
會產生一個EC,儲存 AO, Scope Chain 也會被建立並初始化,傳入global
的 VO 或上層function
的 VO 。
//作用域模擬
函式EC: {
AO:{
變數
},
scopeChain: [函式EC.AO, global[[Scope]]]
= [函式EC.AO, globalEC.VO] //變數溯源的過程
}
globalEC: {
VO: {
變數
},
scopeChain: [globalEC.VO]
}
函式.[[Scope]] = globalEC.scopeChain //函式被呼叫
- Execution Context :
global
具有 EC,每當進入一個函式也會產生一個 EC 。儲存 VO 、 AO 與傳入的 Scope Chain 等相關資訊,並放到 stack 裡,函式執行完畢就會刪除回傳。 - Variable Object :每個 EC 都有對應的 VO ,儲存變數與函式,以及函式的參數。global VO 存有全域變數。
- Activation Object :VO 的特別型態,在函式的 EC 中建立,並當成 VO 使用,存有參數、變數,
arguments
也存這裡。
function func(a) {
i = 3;
}
for (var i = 0; i < 10; i++) {
b(i * 2); // 6 , i 在函式裡被重新賦值了
}
在全域命名空間 Global Namespace 裡宣告,任何地方都能用。
a = 1 //在非嚴格模式下直接賦值等於建立全域變數
實際上,全域變數不是變數,而是全域物件(底層物件)window
, node 環境稱global
,的屬性。
console.log(window.a) // 1
在函式裡宣告,只在函式裡能用。
只在大括弧{}
的範圍內有效,解決迴圈內的變數會被誤改的問題,只有let
與const
可以宣告。
分配記憶體空間。
var 變數名稱 = 值 //宣告並賦值
var a // 宣告未賦值
a // undefined
b // 錯誤: not defined
var firstVar //駝峰式命名 Camel Case
var second_var //底線命名 Underscore
// Case Sensitive:大小寫有差
var var //不可使用保留字
var 1var //不能用數字開頭
var %^ //特殊符號有些也不行
-
var
:視所在位置建立全域或函式作用域,可重新宣告、賦值,不再建議使用。 -
const
:建立區塊作用域,不可重新宣告,可重新賦值,用於宣告常數與物件,在 JavaScript 中所宣告物件仍是可變的。 -
let
:建立區塊作用域,宣告時必須賦值,不可重新宣告與賦值,用於for
迴圈或需要再指定值的情況,它的特性會讓迴圈每跑一次就產生新的作用域。
let [a, b] = [1, 2] //可以傳值或傳變數
const obj = {
key1: '1',
key2: 2,
}
let {key1, key2} = obj //變數名稱必須對應 key
key1 // '1'
let [a, ...b] = [1, 2, 3] //搭配展開運算子
let [a, b, c=3] = b
function sum(a, b = 2) {}
sum(1) //設了預設值,只傳入一個值也可行
宣告變數時不需要宣告型別,但執行跨型別的運算時會自動轉換型別。
console.log(數字+字串) //字串
- 原始型別或都傳值 pass by value:當宣告新變數並賦予原本變數的值,電腦傳遞的是新增的拷貝位置。
- 物件型別傳址 pass by reference :當宣告新變數並賦值為原本變數,電腦傳遞的是原本變數的記憶體位置。
- 還是物件型別或都 pass by sharing ? 又或者傳遞的是原本的位置,只是記憶體內容不可改變。
查詢變數位置,例如對變數賦值時。
查詢變數值,例如呼叫變數時。
宣告變數的程式碼會先被編譯器讀取,寫入 EC 裡的 VO 或 AO。所以可在宣告前就使用變數。
編譯是把原始碼編譯成目的碼,並保證兩者執行的結果相同;直譯則是直接執行原始碼的語義,並輸出結果,但做法是個黑盒子。
-
function
最優先,連同賦值也就是內容也會一起寫入。所以var
的宣告會被忽略,但變數仍可在逐行執行時被覆寫。 - 接著是
function
傳入的引數Argument
。 -
var
最後,且宣告變數的值是undefined
。
由於let
與const
的宣告會被提升,但不會被初始化為undefined
,在提升後與賦值前存取變數會拋出錯誤,這段時間被稱為 TDZ。
由函式和與其相關的參照環境組合而成的實體。由於回傳的函式的 Scope Chain 會存取上層函式的 AO,因此即使上層函式的資料已從記憶體釋放,也可以藉由回傳的函式讀取。
function a() {
return function b() {
//如果用到了 a 的變數,該變數就會被存進 b 的 EC
}
**}**
//兩種 return 選一種
function a() {
function b() {
}
return b
}
在let
與const
還沒出現時,迴圈的var
必須包在function
裡避免被存取修改。
for(var i=0; i<n; i++) {
function() {
函式(i) //執行時迴圈已經跑完了
})
}
//使用閉包
function 回傳函式(參數) {
return function() {
函式(參數)
}
}
for(var i=0; i<n; i++) {
回傳函式(i) //每一圈都回傳函式(i)
}
//用 IIFE(Immediately Invoked Function Expression)包起函式
for(var i=0; i<n; i++) {
(function(參數) {
function(i) {
函式(i)
})
})(i)
}
function a(num) {
//複雜運算
return num
}
function b(func) {
var ans = {} //記起值
return function (num) { //直接回傳函式結果
if (ans[num]) return ans[num]
ans[num] = func(num)
}
return ans[num]
}
const c = b(a)
console.log(c(20)) //在 b 裡呼叫 a 運算
console.log(c(20)) //在 b 裡呼叫出上一次的結果
ES6 才出現的語法糖 Syntactic sugar (對語言功能沒有影響,但更方便設計而發明的語法),讓模擬物件導向的過程更簡潔可讀。
class 類別名稱 { //類別應首字大寫
constructor {
this.參數 = 參數 //儲存建構時傳入的引數
}
名稱(參數) { //初始化設定用 setter
this.參數 = 參數 // this 指的是剛剛呼叫函式的變數,用 this.參數儲存引數
}
名稱() {
//撰寫要做的事
}
名稱() { //為這個類別設置可用方法
console.log(this.參數) //呼叫函式就會印出參數
}
名稱() {
return 參數 //呼叫方法就回傳參數
}
}
var 變數名稱 = new 類別()
var 變數名稱 = new 類別(引數)
變數.初始化(引數) //初始化設定
變數.方法
值只關乎呼叫時的this
究竟代表什麼,與作用域和程式碼位置完全無關。
a.b.c.hello.call(a.b.c) //觀察 a.b.c.hello() this 值的方法
- 在 class 中代表呼叫這個方法的變數
- 在針對瀏覽器操作的函式中,指的是操作的對象
- node.js 預設為
global
- 瀏覽器預設為
window
- 在嚴格模式會變成
undefined
,用'use strict'
放在程式開頭開啟 - 呼叫在箭頭函式作用域裡宣告的函式,
this
就是箭頭函式的this
-
'use strict'
:非嚴格模式下,傳入的this值會轉成物件
函式.call(this的值, 參數, 參數) //印出
函式.apply(this的值, [參數的陣列])
const 新函式 = 原函式.bind(this的值) //一旦 bind , call apply 就無用了
在 ES5 時,透過建立function
設計類似class
的機制。
function 類別名稱(參數) { //建構式 constructor
this.參數 = 參數;
this.方法 = function () { //功能
//但每新增一個 instance 就會佔據多一份記憶體位置
}
}
類別.prototype.方法 = function () {
//這樣就可以共用這個功能,只佔據一份記憶體位置
}
Array.prototype.方法 = function () {
//這樣會在陣列這個類別上新增功能,但不建議
}
var instance = new 類別(引數)
instance.方法()
原型鏈可以做到類似繼承的效果,電腦透過原型鏈尋找所呼叫方法的函式,從 instance 本身的原型prototype
找到類別.prototype
,找不到就會繼續去找物件型別的原型,直到找到null
都沒有。
-
prototype
Object.getPrototypeOf(instance) //回傳 instance 的 prototype .__proto__ //可用但比較不推薦,在這裡用來解說 instance.__proto__ === 類別.prototype 類別.prototype.__proto__ === Function.prototype //類別就是函式類別的 instance Function.prototype.__proto__ === Object.prototype Object.prototype.__proto__ //指向 null 原型.hasOwnProperty('方法') //原型是否存有這個方法
-
instance
a instanceof b //可以判斷兩者關係 Function instanceof Object === Object instanceof Function //互為 instance Function.__proto__ === Function.prototype // true Function.__proto__.__proto__ === Object.prototype // true
-
constructor :每個 prototype 都有的屬性,指向構造函數
instance.constructor === 類別 // true 任一類別.prototype.constructor === 任一類別 // true 類別.prototype.hasOwnProperty('constructor') // true
-
new
:創造新的物件,把物件的__proto__
指向類別的prototype,
所謂模組就是各式各樣具特定功能的程式,由不同開發者寫好並分享到網路上,供需要這些功能的人下載引用,省去每個人都要開發同一個功能的浪費。一個模組可以是一個資料夾裡放著幾個檔案,同時包含 package.json 。
var 模組 = require('模組名稱')
var 我的模組 = require('./模組.js') //也可只傳模組讓它搜尋
模組.方法() //使用模組裡的方法
module.exports = 輸出資料 //可以輸出任何東西
- 安裝 Babel
-
npm install — save-dev @babel/core @babel/node @babel/preset-env
-
資料夾裡新增
.babelrc
檔案 -
檔案裡新增
{ "presets": ["@babel/preset-env"] }
-
npx babel-node
就如同node
模式,正常運作表示成功
-
- 普通模組
- 添加在在宣告語法前
export function等宣告語法 模組名稱
或是export {模組, 模組}
另外放置 import {模組, 模組} from '檔案路徑'
- 添加在在宣告語法前
-
default
預設集export default function等宣告語法
-
import default的模組, { 模組 }
或import { default as 預設集名稱, 模組}
-
*
所有模組:import * as 名稱 from 'export 的檔案路徑'
- 別名語法
as
export {模組 as 別名, 模組}
import {模組 as 別名, 模組} from 'export 的檔案路徑
- CLI 執行:
npx babel-node import 的檔案名稱
ypm 是 npm 的替代選項,由 Facebook 主導開發,更快且安全,同時兼容 npm 。
npm 是其中一種供人上傳分享並下載各式 JavaScript 模組的公共平台,方便我們利用別人的模組。
npm 依附於 node.js 下,會一起安裝好,但更新頻率較高,可隨時下載最新版。
-
安裝 npm
npm -v #可以檢視安裝的版本 npm install npm@latest -g #也拿來可以下載其他模組
-
安裝專案使用的套件
npm install #下載別人專案用到的所有模組 npm install --production #只下載使用別人專案會用到的模組, dependencies npm install 模組名稱 #建立 node_modules 目錄,將指定模組載進去 npm i 模組名稱 #縮寫 #如果專案中沒有 package.json 就會下載最新版本 npm install 模組名稱@版本 #指定安裝版本 npm update 模組 #查詢遠端最新版本,比對本地,檢視 package.json 中的語意版本規則決定是否下載 npm outdated #回傳目前本地版本、 package.json 宣告版本、遠端最新版本
-
安裝要在 CLI 介面使用的套件
npm install -g 模組名稱 #全域性安裝模組 npm ls -g --depth=0 #檢視全域的模組 npm update -g 模組 npm outdated -g --depth=0 npm uninstall -g <package>
管理安裝的 npm 模組最好的方式就是建立 packege.json 檔案。
npm init #在當前目錄建立檔案,回答問題或使用預設後輸入 yes
npm init --yes #省略回答步驟
npm set init.key名稱 "內容" # author.email、author.name、license
npm install 模組名稱 --save #新增模組到 dependencies
npm install 模組名稱 --save-dev #新增模組到 devDependencies
- 描述你的專案依賴哪些模組
- 允許使用語意化版本規則指明模組版本
- 更易分享、重複使用模組
{ //前兩項是基本要有的
"name": "模組名稱", //全小寫、無空格、可用橫線或下劃線
"version": "數字.數字.數字", //符合“語義化版本規則”
"description": "描述資訊", //幫助搜尋,沒寫會使用 README.md 第一行
"main": 入口檔案, //一般都是 index.js
"scripts": {"代號": "指令", "test": "為預設且指令為空", "start": "開啟專案"},
"keywords": "關鍵字", //幫助 npm search 的搜尋
"author": {作者資訊},
"license": "MIT", //預設
"bugs": "當前專案的錯誤資訊",
"dependencies": {"模組": "^版本"}, //使用時依賴的模組,供使用專案者下載
"devDependencies": {"模組": "^版本"} //開發測試時依賴的模組
}
npm 制定的版本號規則。
大版本號.小版本號.補丁版本號 //提供者會更新不同數字,標明升級幅度
大版本.小版本 大版本.小版本.x ~大版本.小版本.補丁版本
1.0 1.0.x ~1.0.4 //只接受補丁版本的更新
大版本 大版本.小版本 ^大版本.小版本.補丁版本
1 1.x ^1.0.4 //接受小版本更新
* x //接受大版本更新
呼叫 package.json 裡的 script ,直接執行代碼,例如安裝特定套件或執行專案。
{
"script": {"start": "node index.js"}
}
npm run start
由 Facebook 開發的套件,專門測試 JavaScript 程式碼的框架,可用 npm 或 ypm 安裝。
針對function
輸出入做測試。
module.export = 被測物 //在測試檔案輸出
//新增測試用檔案 test.js
const 被測物 = require('./被測物') //引入
describe('描述這個區塊測試', () => { //說明 test 測試同一段程式碼,非必要,但輸出後會有 sum 測試的總結
test('描述測試', () => { //測試不同輸入輸出結果
expect(被測物(引數)).toBe(預期輸出結果);
})
})
- 使用做好的測試檔
- 使用 package.json 的
"scripts"
,在 CLI 輸入npm run test
。"test" : "jest"
"test" : "jest test.js"
- 新版 npm 可以直接輸入
npx jest test.js
。
- 使用 package.json 的
先建立test()
的架構再開發,在 debug 幫助很大。