Git 指令筆記
Command | 用途 |
---|---|
git add . | 將所有檔案加入到工作目錄索引中 |
git add ../../file/* | 將 ../../file/ 這個路徑下所有檔案加入 |
git rebase | |
git restore |
Writing For Sharing
最近開始幫公司重新設計官方網站,同時也負責開發,觀察近期網站 UI 趨勢,Micro Interactions & Animated Illustrations 成為現代網頁的特色,而且更能夠吸引瀏覽者的眼球,於是在規劃網站的時候打算使用由 Airbnb 開源的 lottie-web Library 來產生 svg 動畫, svg 不但可以節省網路資源也比 gif 看起來更順暢。
首先我用 illustrator 繪製好基礎向量素材,再 import 進 After Effect 去製作動畫效果,並不斷調整讓它看起來更順暢。
將 illustrator 檔案 import 進去 After Effect 的時候,有幾種方式,可以參考這個連結:A Guide to Importing Adobe Illustrator Files into After Effects
但因為 Bodymovin 無法轉出 AI (.ai) 的圖層,會變成 missing image and vector ,所以必須將 AI (.ai) 圖層轉換成 shape layer,方法是選取要轉換的圖層,然後到 Layer > Create > Create Shapes from Vector Layer,他就會產生一個新的 shape layer。
-Learn how to convert Illustrator layers into shape layers in a composition.
BTW 如果不使用 illustrator 畫,直接在 AE 裡面製作 shape layer 就沒有這個困擾了。
另外,版本與系統的相符也是一個需要注意的地方,這次在使用 AE 之前先花了一段時間升級 mac 系統 和重新安裝 AE XD
目前在 After Effect 中使用 Bodymovin 時需要注意 After Effect 版本須在 15.0 以上,如果使用 mac 系統的話目前至少 要 macOS versions 10.12 (Sierra), 10.13 (High Sierra), 10.14 (Mojave)等版本之系統才支援,並需要在 After Effect 的 Preference 設定中打勾這項:Allow Scripts To Write Files And Access Network option 否則 Bodymovin 無法運作。
To allow scripts to write files and communicate over a network, choose Edit > Preferences > General (Windows) or After Effects > Preferences > General (Mac OS), and select the Allow Scripts To Write Files And Access Network option.
在 After Effect 2019 以後的版本,該選項被移到 scrtiping & expressions 的 pannel 裡了,可以參考以下連結說明:
The following existing preferences related to scripting and expressions have been moved from the General preferences panel to the new Scripting & Expressions panel.
-adobe support community
把動畫修改的差不多後就可以透過 Bodymovin 來輸出 JSON 檔案,來運用到專案裡實際跑在 Browser 中看看,我用以下方法將 Bodymovin 轉出的 JSON 檔運用到專案中:
開啟 Bodymovin (Navigate to Windows -> Extensions -> Bodymovin),選好 export 設定與指定路徑資料夾
關於如何設定 Bodymovin:How to Use Lottie ? | After Effects Plug-in ‘Bodymovin’
可以打開 demo.html 在瀏覽器中先看看 demo 是否沒有問題
在專案資料夾路徑底下執行: npm install lottie-web
安裝運行動畫的 js script
在動畫運行的 js 檔中(ex: app.js)import lottie from 'lottie-web'
並將 json 檔也 import 進來: import animationData from '../lottie/data.json'
call lottie.loadAnimation()
to start an animation
for example:
1 | let animObj = lottie.loadAnimation({ |
在 react JSX 的 html 描述部分用 react 的 ref 屬性指定要產生動畫的 dom element:
1 | <div style={{width: 300, margin: '0 auto'}} ref={ref => this.animBox = ref}></div> |
關於除了用 npm install 來安裝 package 的使用方法,airbnb 的官方 github 有詳細的文件說明其他使用方法,例如 CDN 或透過 Bodymovin getPlayer 來取得 js script 後再引用到專案中,可以參考以下連結:
lottie github documentation
Lottie animations for Web 2019
How to export an animation with Bodymovin 2017
同步(synchronous)的意思是指一次只能進行一件事、一件任務,非同步或稱異步(asynchronous)的意思則是不用等上一任務完成再執行。只看名字容易搞混,但如果想成是同一個步道和不同步道,會比較好理解。同步道因為只有一個步道所以一次只能執行一項任務,而不同步道可以多個任務一起執行。
JavaScript 這種編程語言,我們稱之為腳本(script)、直譯式語言,可以寫在 HTML 中,在頁面加載的時候會自動執行。只要瀏覽器或伺服器有搭載 JavaScript Engine 在環境中,就可以執行 JavaScript。
Scripts are provided and executed as a plain text. They don’t meed a special preparation or a compilation to run. In this aspect, JavaScript is very different from another language called JAVA. JAVA needs to be compiled into machine code.
在使用 C、C++、JAVA 之類傳統語言,執行前需要先編譯,這可以為程式碼產生機器的有效表達方式,通常可以優化執行時期的效能。早期的 JavaScript 因為直譯式語言的命令稿語言設計而在性能上表現不佳,但因其對 web 的發展重要性日益增長,許多企業資源和優秀人才的投入,現在既可收命令稿語言的便利性又可享有編譯式語言的性能。
直譯式和編譯式程式語言的差別在於:直譯式語言需要經過 直譯器 interpreter 逐行轉換,且它是在執行時才被直譯成執行碼,效能一部分取決於直譯器的速度,直譯式語言多為動態語言(dynamic language),具有靈活的型別處理,在執行時才動態生成等彈性。直譯式語言仰賴一個執行環境(execution context)語言可用的功能由這個執行環境提供。
編譯式語言經由 編譯器 compiler 轉換成目的碼(object code)再由連結器(linker)轉換成可執行的二進位碼(byte code)。編譯式語言多為靜態語言(static langauge)有事先定義的型別、型別檢查及高效能執行速度等特性。
由 Google 開發(2008)的 open source Engine:V8 是實現 JavaScript 在瀏覽器環境非同步執行的最佳功臣;在 V8 之前的 Engine 都太慢了,Google 為了讓 JavaScript 能夠在瀏覽器上跑得更快,開發了以 C++ 語言寫成的 V8。V8 將 JavaScript 在執行前編譯成了機器碼(machine code,是電腦的CPU可直接解讀的資料);而位元組碼(bytecode,是為了實現特定軟體運行、軟體環境、與硬體環境無關)或是解釋執行它,並使用了 inline-caching 行內快取等方法,提升效能,使其速度能夠媲美二進制編譯。
每個瀏覽器(Google Chrome, FireFox, IE, Safari)都有自己的 JavaScript Engine implementation and all of them have a JavaScript runtime that provide web APIs. 這些 web APIs 是種應用程式,負責許多瀏覽器運作所需的操作,包括 send HTTP Request, listen to DOM events, delay execution by using setTimeout and setInterval, caching, database storage…etc. 也可以儲存資料、暫存在瀏覽器。其他 Engine 還有 Spider Monkey(used by FireFox)以及 Chakra(used by IE) 等。
JavaScript is a single threaded language that can be non-blocking.
JavaScript 是單一執行緒(single threaded execution)的程式語言,意思是一次只能處理一個需求(任務),所有的 line code 在執行堆疊(call stack)中記錄執行情況,每次只會執行一個程式碼片段(one thing at a time = one call stack)。
執行堆疊(call stack)會紀錄目前執行到程式的哪個部分,如果進入了某個 function,便會把這個 function 添加到堆疊的最上方,如果 function 執行了 return
便會把 function 從堆疊中抽離(pop off),在堆疊中的資料是遵守 first-in-last-out 的順序。
雖然 JavaScript 執行程式是同步的,逐一執行,但因為在瀏覽器環境中,還有 Rendering Engine 和 HTTP Request,所以整個網頁在執行過程中可以達到非同步的運作。
JavaScript Run-Time Environment 就是指 JavaScript 的執行環境,下圖為執行環境的示意:
JavaScript 依靠這些機制運作執行:
在瀏覽器執行 JavaScript 時, call stack 會將不屬於 JavaScript 原生處理範圍的函式或程式碼,丟給 Web APIs 處理,再將處理結果丟到 Callback Queue,透過 Event Loop 不斷查詢是否 Call Stack is empty?如果沒有等待執行的程式碼,再將 Callback Queue 佇列中的第一項放到 Call Stack 中去執行。由於 Event Loop 運作的關係,瀏覽器的 JavaScript 可以同時執行多個需求,而不需要等待上個需求完成才進行下一個。
以下介紹幾個在 JavaScript 中實現非同步操作的語法:
setTimeout 和 setInterval 是由瀏覽器提供的 Web APIs,可以延遲任務執行時間,作為計時器使用。
為了解決 callback hell 的問題和串接 function 的需求,JavaScript 在 ES6 中加入了 Promise,Promise is saying that “hey, I am doing somthing and I promise to let you know when I have the result.” A promise is an object that may produce a single value some time in the future, either a resolved value, or a reason that it’s not resolved(rejected). Promise 讓非同步請求處理更容易,是一種非同步編程的解決方案。Promise object 是一個構造函數,需要用 new
語法來生成 promise 實例:
1 | const promise = new Promise( function(resolve, reject){ |
Promise 有三種狀態:pending、fulfilled、rejected,分別代表當前腳本任務的執行狀況。當 Promise 實例生成後,可以用 .then(function(){}, function(){})
方法分別指定 fulfilled 和 rejected 狀態時的回調函數(callback function):
1 | promise.then( |
A fetch simply return a promise.
1 | fetch('url') |
fetch 實作基於 es6 promise,在 2015 年由 google 發佈,fetch 回傳的 promise 物件不會再有收到 response 但是 http status 呈現 404 或 500 的時後變成 rejected,只會在網路出現問題或是被阻止 request 時,狀態才會變成 rejected,其他都是 fulfilled。
由於 fetch 會返回一個 promise 所以可以使用 .then
來串接,避免撰寫 callback hell 程式碼,通常使用時會用 .catch
來捕捉 promise 發生的 error。
Async/Await
is bulit on top of Promises. 目的是簡化使用 promise 的行為。我們可以使用 async ()=>{}
來宣告一個非同步函式,這個非同步函式會返回一個 promise。 Every function that returns a promise can be considered as async function. async
function 是不管怎樣都會回傳 Promise
的函式,雖然我們回傳的不是一個 Promise
,但因為它是 async
function 的關係,JS 會自動把它包成 Promise
,所以可以使用 then
:
1 | const foo = async () => { |
await
必須在 async
function 裡面才能使用,await
可以在所有 return a promise 的程式碼前使用,await
會等 promise 執行完,再執行下一行。await
也能夠把 Promise
回傳的值接起來,通常我們在呼叫 API(例如執行 fetch、axios)的時候就很好用:
1 | fetch('https://jsonplaceholder.typicode.com/users') |
改寫成 async function:
1 | async function fetchUsers() { |
使用 async/await 處理多個 非同步請求以及除錯,比使用 promise 的 then 鏈,更容易除錯:
這是使用 promise 和 try/catch 用法:
1 | function loadData() { |
這是使用 asyn/await 和 try/catch:
1 | async function loadData() { |
在錯誤發生時,如果是用 .then 返回的錯誤堆疊不提供錯誤發生在哪裡,不容易找到錯誤發生原因:
1 | function loadData() { |
使用 async/await 便可以逐行找到錯誤發生的地方:
1 | async function loadData() { |
Node.js is a JavaScript runtime bulit on Google chrome’s V8 engine. 2009 Ryan Doll decided it would be great to run it ouside the browser. So he created Node.js which is actually a C++ program. It’s an executable C++ program that provides JS runtime for us.
Node.js uses Google chrome’s V8 engine to provide JavaScript runtime but does not rely only on it’s event loop. It uses libuv library (written in c) to work along side V8 event loop to extend what can be done in background. Node follows same callback approach like Web APIs and works in similar fashion as the browser.
Node.js 的執行系統圖示:
If you compare browser diagram with above node diagram, you can see the similarities. The entire right section looks like Web API but it also contains event queue (callback queue/message queue) and the event loop. But V8, event queue and event loop runs on single thread while worker threads are responsible to provide asynchronous I/O operation. That’s why Node.js is said to have as non-blocking event driven asynchronous I/O architecture.
I/O 指 input/output,資訊處理系統與外部世界(使用者、另一個資訊處理系統)之間的通訊,包括輸入:接收訊號或資料;輸出:發送訊號或資料。任何資訊傳入或傳出 CPU/記憶體組合,例如通過磁盤驅動器讀取資料,就會被認為是 I/O。
由 Node.js 建立的後端伺服器,可以使用 Express.js 框架來處理非同步請求的操作,Express.js 是一個基於 Node.js 平台,極簡、靈活的框架,擁有強大的特性可以幫助開放網頁應用。
使用 Express.js 就是調用各種中間件(middleware)來處理各種請求、管理路由,其本身也可以視為是一種中間件(middleware)。通過 Express.js 提供的執行函數調用對應的方法,比起直接在 Node.js 中寫處理程式,會更加方便和快捷。
How JavaScript works in browser and node?
Udemy course “Advanced JavaScript Concepts”
學習Express前,都會搞懂這幾個問題
1996 年創造 JavaScript 的 NetScape 公司決定將 JavaScript 提交給標準化組織 ECMA,希望讓這種語言成為國際標準。1997 年 ECMAScript 1.0 發佈,規定了 browser 腳本語言的標準。
使用 ECMAScript 這個名稱的原因是:
以下簡要的依年份條列從 2015 年至今(2019)加入的新功能:
let
& const
& block scope 的概念:避免 var
宣告時,出現區域變數覆蓋全域變數或在 for loop
中循環變數洩露成為全域變數的副作用發生。this
值,使用來自外部的 this
,作為 object method 的時候不需要再 .bind(this)
。padStart()
,padEnd()
新增字串長度到指定長度.finally()
在 promise 完成(resovled or rejected)後執行一個指令for await(... of...)
1 | async function process(array) { |
ES10 在 2019 年 2 月中推出,在新版的 Nodejs 和 Chrome 已經可以使用。
[].flat()
,[].flatMap()
Object.fromEntries
1 | const map = new Map([ ['foo', 'bar'], ['baz', 42] ]); |
String.trimStart()
and String.trimEnd()
減少空白字串
try{} catch{}
可以省略 catch 回傳的表達參數 catch(err){} -> catch{}
revised Function#toString:
function.toString() 回傳確實的 Function 程式碼,包括空白字元和註解:
1 | function exampleFuncton() { |
Symbol Description:
在建構 symbol 時把 description 作第一個參數傳入,就可以通過 toString()
取得:
1 | const symbolExample = Symbol("Symbol description"); |
JSON ⊂ ECMAScript
The unescaped line separator U+2028 and paragraph separator U+2029 characters were not accepted in the pre-ES10 era.
well-formed JSON.stringifyJSON.stringify()
may return characters between U+D800 and U+DFFF as values for which there are no equivalent UTF-8 characters. However, JSON format requires UTF-8 encoding. The proposed solution is to represent unpaired surrogate code points as JSON escape sequences rather than returning them as single UTF-16 code units.
stable Array.sort()
The previous implementation of V8 used an unstable quick-sort algorithm for arrays containing more than 10 items. A stable sorting algorithm is when two objects with equal keys appear in the same order in the sorted output as they appear in the unsorted input.
新增數據資料的基本型別:BigInt
(stage 3)
BigInt is the 7th primitive type: an arbitrary precision integer. The variables can now represent 253 numbers and not just max out at 9007199254740992.
Dynamic import (stage 3)
Dynamic import()
returns a promise for the module namespace object of the requested module. Therefore, imports can now be assigned to a variable using async/await.
Standardized globalThis
object (stage 3)
What’s New in ES10? Javascript Features ES2019
February 16, 2019
JavaScript: What’s new in ECMAScript 2019 (ES2019)/ES10?
ES6、ES7、ES8、ES9、ES10新特性一览
Twelve ES10 Features in Twelve Simple Examples
Modules are just clusters of code. Good authors divide their books into chapters and sections; good programmers divide their programs into modules.
在進行軟體專案時,複雜性總是伴隨,妥善應用抽象、介面和他們底層的概念,將某項功能的需要注意的複雜性最小化,直到他變成單一功能的分支,也就是將一個大的 program 拆分成互相依賴的小文件、再用簡單的方法拼裝起來,透過妥善設計的介面(API)讓使用者知道如何引用一個 module ,這樣的思維就是模組化開發思維。
Good module should be highly self-contained with distinct functionally, allowing them to be shuffled, removed, or added as neccesary, without disrupting the system as a whole.
透過提供一個讓系統其他部分操作的公開介面(API),這個介面(API)裡面有元件公開的方法或屬性,那些方法和屬性也可以稱為接觸點,也就是可在介面(API)公開互動的東西。接觸點越多,需要操作的地方也越多,也有較高的彈性,因為有大量的功能,所以可能也會更難理解、使用。
介面(API)有兩種用途:幫助我們開發元件的新功能、讓使用介面的人(元件、系統)可以受惠於公開的功能,而不需考慮那項功能的背後細節如何實作。因此設計良好合適的介面可以增加功能開發的生產力,當我們持續使用類似的API外貌,就不需要每次都重新擬定新的設計,使用者也可以放心使用。
隨著專案日漸複雜,全域變量容易互相衝突,影響專案的維護性和可拓展性。使用模組化開發方法的好處有:
在 JavaScript 中,管理變量 variable 是一切 coding 的基礎,看起來似乎是越少變量越容易管理,不過透過 scope 的概念可以讓變量在單一作用域內產生作用,不同作用域的變量便不會互相干擾,然而當你想要共用變量或資料時卻只能透過全域變量的設定,或用 callback function 將變量傳出來,設定全域變量容易產生命名衝突,而且當所有變量都需要在全域中,就必須按照一定順序編程。當需要移除舊程式碼、製作新功能時,就會有困難。所以有 module 開發的必要性。
早期 JavaScript 沒有 module 體系,其他語言都有這個功能,例如:Ruby 的 require、Python 的 import,連 css 都有 @import。因為有開發上的實際需要,所以社群制定了一些 module pattern 的加載方法,最主要的有 CommonJS 和 AMD 規範,前者用於 server 後者用於 browser。
<script></script>
引入方式,但開發大型專案時容易產生弊端:在 ES6 module 中,每個檔案都是一個模組,有自己的範圍和環境,通過 export
命令輸出指定 code,再用 import
命令輸入文件。(自動採用嚴格模式)
過去 server 開發時會使用一個叫做 Browserify 的 module bundler, Browserify 使用 CommonJS 語法,會產生一個 bundle.js 檔案,讓我們能夠上傳並發佈網站。而現在我們會使用 ES6 module + Webpack + Babel,Webpack 也是一個類似於 Browserify 的 module bundler,但 Webpack 可以結合 Babel 轉譯器,讓瀏覽器也讀懂 ES6,並產生 bundle.js 檔案。
現代的模組化開發多使用以下工具(庫):
一個強大的套件管理工具、線上資料庫,擁有龐大的社群支持,一開始是為了 Node.js 而發展出來的套件工具,隨著發展變成開發者社群用來 share code、package 的資料庫,透過 npm 提供的指令,可以管理套件版本、加載套件、卸除套件等。使用套件做開發,簡化重複的程式碼,可以加快開發速度和提升程式維護性。
npm 在初始化專案時會產生一個 package.json 的檔案,裡面紀錄專案所使用的 packages 版本紀錄,透過檔案紀錄可以確保團隊工作時所有人使用的版本一致。
其他套件管理工具還有由 facebook 開發的 Yarn,目前也有部分人在使用。
Wepack is a static module bundler for morden JavaScript applications.
運行在 Node.js 環境的一個開發工具。
實作方法:在 webpack.config.js
中設定要進行 bundle 的檔案。自己編寫設定檔,透過指令去驅動的自動化工具,所有的動作都需要自己寫規則去做編譯,將我們寫好的前端框架檔案(preprocess)編譯成目前 browser 看得懂的內容並打包成一包的完整檔案,提交給 server,會在專案底下創建一個 webpack.config.js 和 index.js 檔案。
Babel is a JavaScript compiler that takes your morden JS code and returns browser compatible JS(older JS code).
有很多開發工具(ex:create-react-app)都自動搭載了 babel 作為轉譯工具。他也可以轉換 react 的 jsx 還有 typescript。
自動化工作流程的 library。可以 compile css 也可以 compile JavaScript。
官網
程式碼 coding style 檢查工具。
深入學習 JavaScript 模組化設計 - NICOLAS BEVACQUA 著 O’REILLY
搞懂為何設定 REACT、JSX、ES2015、BABEL、WEBPACK 的學習筆記
資料結構是電腦中儲存、組織資料的方式,在 JavaScript 中,資料可以分做基本型別和物件型別,基本型別儲存一個值,物件型別儲存更複雜的資料,為了更簡便的存取複雜的資料,JavaScript 使用有規則可循的結構來管理資料。
The key distinctions between primitives and objects:
資料結構:
在 Ruby 語言中,使用 index 的叫做 Array,使用 key-value-pair 的叫做 Hash。
而在 JavaScript 中,使用 index 的也叫做 Array,使用 key-value-pair 的有兩種: Object 和 Map。
JavaScript 為這些資料結構內建了一些方法來方便開發者使用、管理資料:
Array 陣列
Object Array 物件陣列
是指陣列元素為 object 或 array 的多層結構資料。針對這種類型的資料,JavaScrip也提供一些有用的方法來協助管理:
Object
Map 也是 key-value-pair 物件,但是他可以直接被 iterate、 key 可以使用任何資料型別;object的 key 只能是 string。
undefined
if keys don’t exist.Set is a collection of values, where each value may occur only once.
WeakMap & WeakSet
通常情況下,當某數據存在於內存中時,對象的屬性或者數組的元素或其他的數據結構將被認為是可以獲取的並留存於內存。在一個正常 Map 中,我們將某對象存儲為鍵還是值並不重要。它將會被一直保留在內存中,就算已經沒有指向它的引用。
WeakSet
WeakMap
There are many things one would want to do with a primitive. So JavaScript provides methods to call. JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects.
The solution is:
The “object wrappers” are different for each primitive type and are called: String, Number, Boolean and Symbol. Thus, they provide different sets of methods.
JavaScript 允許訪問字符串,數字,布爾值和符號的方法和屬性。當進行訪問時,創建一個特殊的“包裝對象”,它提供額外的功能,運行後即被銷毀。除 null 和 undefined 以外的基本類型都提供了許多有用的方法。從形式上講,這些方法通過臨時對象工作,但 JavaScript 引擎可以很好地調整以優化內部,因此調用它們並不需要太高的成本。
Number
JavaScript 有一個內置的 Math 對象,它包含了一個小型的數學函數和常量庫。
String
javascript.info/map-set-weakmap-weakset
javascript.info/primitives-methods
因為 JavaScript 中,基本型別(primitve)與物件型別(object)的值的賦予方式不同,基本型別是 pass by value,物件型別是 pass by sharing。相關介紹可看:JS-pass by value or reference
因為值賦予方式的差異,在複製物件例如 object, array 等資料類型時,根據拷貝資料的形式,可以分為淺拷貝(shallow copy)及深拷貝(deep copy)。
對淺拷貝來說,只是複製 collection structure,而不是 element,With a shallow copy, two collections now share the individual elements. Collections can be diverse data structures which stores multiple data items.
因此對於基本型別來說,淺拷貝(用等號賦值)會傳值,但對物件型別來說,淺拷貝是傳遞 reference,讓兩者可以共用一個記憶體的物件資料,這樣的話在指派物件型別的資料的第二層或更深層內容時,會同時影響兩個地方。
淺拷貝只複製指向某個物件的指標,而不複製物件本身,新舊物件還是共用同一塊記憶體。
而深拷貝是整個複製,包含element,會另外創造一個一模一樣的物件,新物件跟原物件不共用記憶體,修改新物件不會改到原物件。所以當我們在使用有多層結構的物件資料時,要盡量用深拷貝。
一般物件如果用等號賦值:
1 | var obj1 = { a: 10, b: 20, c: 30 }; |
一般而言 基本型別的拷貝方法就是用 等號賦值,而物件型別例如 array 或 object等就有很多方式,依照拷貝的層次深度可以分為淺拷貝和深拷貝:如果物件中屬性的值也是物件,只能複製到第一層物件的屬性,而無法複製到屬性值的物件(第二層),就無法達到實際的複製,而是會與舊物件一起共用同一塊記憶體;這樣的複製方法稱為「淺拷貝」。相反地,深拷貝會另外創造一個一模一樣的物件,新物件跟原物件不共用記憶體,修改新物件不會改到原物件。
淺拷貝方法
Array.concat:一般Array.concat的用法是合併兩個陣列
1 | var alpha = ['a', 'b', 'c'], |
Array.slice:一般Array.slice()的方法是複製一個新的陣列,可帶入參數 Array.slice(start, end)
,當不輸入參數值的話會直接複製一個。
1 | var animals = ['ant', 'bison', 'camel', 'duck', 'elephant']; |
手動複製
1 | var obj1 = { a: 10, b: 20, c: 30 }; |
Object.assign:用來合併物件,用法為 Object.assign(target, ...source)
若目標物件為空物件則可視為複製一個source的物件。Object.assign({}, obj1)
的意思是先建立一個空物件{},接著把obj1中所有的屬性複製過去,因為Object.assign跟我們手動複製的效果相同,所以一樣只能處理深度只有一層的物件,沒辦法做到真正的 Deep Copy,不過如果要複製的物件只有一層的話可以使用他。
展開運算子(Spread Operator) 也只能實現一層的拷貝
1 | let obj = {name:'john', age:{child: 18}} |
深拷貝方法
JSON.parse(JSON.stringify(object_array)):
1 | let obj1 = { a:{b:10} }; |
var $ = require(‘jquery’);
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
1 |
|
var _ = require(‘lodash’);
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false
1 |
|
updateBudgets = (journey, journeyId) => {
const index = this.state.journeyList.findIndex(item => item.id === journeyId);
if (index !== -1) {
this.setState({
journeys: [
...this.state.journeys.slice(0, index),
Object.assign({}, this.state.journeys[index], journey[0]),
...this.state.journeys.slice(index + 1)
]
})
}
};
關於JAVASCRIPT中的SHALLOW COPY(淺拷貝)及DEEP COPY(深拷貝)
[Javascript] 關於 JS 中的淺拷貝和深拷貝
問題:JavaScript 到底是 pass by value 還是 pass by referece?
根據 JavaScript 的資料型態,可以分為兩大類:基本型別 primitive 和 物件型別 object。
基本型別的資料以 純值的形態存在,例如:number、string、boolean、null、undefined、symbol,而 object 的資料可能為 純值 或 多種不同型別組合而成的 物件。
1 | var a = 10; |
var b = a;
表面上看起來變數 b 的內容是透過複製變數 a 而來,實際上變數 b 是去建立了一個新的值,然後將變數 a 的內容複製了一份存放到記憶體, a 和 b 其實是存在於兩個不同的記憶體位置,因此變數 a 和變數 b 彼此獨立互不相干,即使更改 a 的內容, b 的值也不會變:
1 | a + 2; |
基本型別是不可變的 (immutable),當修改、更新值時,與那個值的副本完全無關,像這種情況,我們通常稱作「傳值」 (pass by value)。
如果是物件型別的資料:
1 | var obj1 = { a: 10 }; |
「物件」這類資料型態,在 JavaScript 中是透過「引用」的方式傳遞資料的。
當建立起一個新的物件並賦值給一個變數(var obj3 = { b: 20 };
)的時候,JavaScript 會在記憶體的某處存放這個物件({ b: 20 }
),再將變數(obj3
)指向這個物件的存放位置,因此當var obj4 = obj3;
的時候,其實是將 obj4 這個變數也指向了 { b: 20 }
這個實體。
這種透過引用的方式來傳遞資料,接收的其實是引用的「參考」而不是值的副本時,
我們通常會稱作「傳址」 (pass by reference)。
1 | var coin1 = { value: 10 }; |
答案會是 10,因為當coin1指向的資料被做為 function 的參數傳入 function 時,即使資料在 function 內部被重新賦值,外部變數的內容都不會被影響。
在這種情況底下, JavaScript 會將 obj 指向一個新建的 object { value: 123 }
,不會影響到外部的 coin1 指向的位址的物件內容。
如果不是 重新賦址 而是 修改傳入 的內容:
1 | var coin1 = { value: 10 }; |
此時變數 coin1 所指向的資料內容被改變,傳入 function 作為 obj 參數的值的 coin1 在 function 內被修改,改變了 value 的值。
對於開發者來說,JavaScript 的內存管理是自動的、無形的。我們創建的原始值、對象、函數……這一切都會佔用內存。當某個東西我們不再需要時會發生什麼? JavaScript 引擎如何發現它、清理它?
JavaScript 中主要的內存管理概念是可達性 Reachability,簡言之,可達值是那些以某種方式可訪問或可用的值,它們保證存儲在內存中。
這裡列出固有的可達值基本集合,這些值明顯不能被釋放:
這些值被稱作根 root。
如果一個值可以通過 引用 或 引用鏈,從根值訪問到,則認為這個值是 可達的。
比方說,如果局部變量中有一個對象,並且該對象具有引用另一個對象的 property,則該對像被認為是可達的。而且它引用的內容也是可達的。
在 JavaScript 引擎中有一個被稱作垃圾回收器(garbage collector)的東西在後台執行。它監控著所有對象的狀態,並刪除掉那些已經不可達的。
深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?
重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?
Garbage collection
[筆記] 談談 JavaScript 中 by reference 和 by value 的重要觀念
Hoisting、Scope 和 Closure 在 JavaScript 中是很重要的觀念,因爲會影響我們如何撰寫 JavaScript。了解這三個概念可以幫助我們了解 JaveScript 在內文執行的運作原理,尤其是在創建和執行階段,JaveScript 的執行機制會如何理解我們寫的程式碼,並跑出我們想要的結果。
JavaScript 內文執行的運作方式有一個機制叫做:Hoisting 提升,在執行任何程式碼前,JavaScript 會把變數和函數的宣告在編譯階段就放入記憶體,編譯後執行時因為已經宣告了,所以如此即便我們先寫調用某一函式的程式碼,再寫該函式的內容,JavaScript 也還是可以知道這段程式碼的意義,程式碼仍然可以運作:
1 | catName("Chloe"); |
1 | num = 6; |
JavaScript 僅提升宣告的部分,而尚未賦值。如果在使用該變數後才宣告和初始化,那麼該值將是 undefined,以下範例顯示了這個特性。
1 | var x = 1; // 給予 x 值 |
上述程式碼其實是這樣運作的:
1 | var x = 1; |
函數宣告的優先權比變數宣告高,如果 function 調用時有傳參數進來,就會先宣告該參數代表的變數意義並賦值。
1 | function test(v) { |
需要注意的是,只有 declaration 宣告式的 function (ex:function func(){...}
)會被在編譯階段提升,而 expression 表達式宣告的 function (ex:let func = function(){...}
)會在執行階段才被存放到記憶體中。
當 JavaScript engine start up 程式碼準備好開始運行時,就會先建立 global execution context全域執行環境,然後建立一個 global object 和 this,在 browser 的環境中,global object 是 window, this === window,在 node.js 環境中, global object 是 global,this === global。
我們可以 assign variable、function 到 global object 中。
執行環境在建立時會經歷兩個階段,分別是 :
當 JavaScript engine 看到 function name() 函數被執行,就會創建一個 function name() execution context,新的 function execution context 會被加入到Execution stack 執行堆疊,並依序執行(Javascript 是單一執行緒,一次只能做一件事),執行環境 的堆疊過程是具有 順序性 的:first in last out。
Scope determines the accessibility (visibility) of variables. Scope is where can I access the variable where’s that variable in my code. It just defines the accessibility of variables and functions in the code. JavaScript has function scope: Each function creates a new scope. Variables defined inside a function are not accessible (visible) from outside the function.
Scope 可以說是一個變數的生存範圍,出了這個範圍就無法存取到。在 JavaScript 裡面,可以分為兩種 Scope 作用域:
在 ES6 以前,唯一產生作用域的方法就是 function,每一個 function 都有自己的作用域,在作用域外面你就存取不到這個 function 內部所定義的變數,然而 ES6 的時候引入了 let 跟 const,多了 block-scope 的概念。
延伸:ES6: let, const, Block-Level Scope
因應ES6的出現,使用上建議大家不要再用var來宣告變數,改用let與const,而且優先使用const。
因為const在宣告時必須給定值,並且不能再被更改,這可以有效降低出現錯誤的機會。
同理,如果是需要變更的數值則改用作用範圍較小的let做宣告,來減少錯誤出現的機率,Ex: for迴圈。
– JavaScript 宣告: var、let、const
「閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函式閉包(function closures),是參照了自由變數的函式。這個被參照的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。閉包是由函式和與其相關的參照環境組合而成的實體。」–wiki
每個宣告的 function 都會儲存著[[Scope]],而這個資訊裡面就是參照的環境。
「that all functions, independently from their type: anonymous, named, function expression or function declaration, because of the scope chain mechanism, are closures.
from the theoretical viewpoint: all functions, since all they save at creation variables of a parent context. Even a simple global function, referencing a global variable refers a free variable and therefore, the general scope chain mechanism is used;
from the practical viewpoint: those functions are interesting which:
scopeChain 把每一層的 AO 和 VO 記錄下來,而變數就紀錄在AO 或 VO 裡,也因為 return 才把 AO 和 VO 保留下來。
closure 其實就是因為 scopeChain 有 reference 到其他 Execution Context 的 AO(active object) 或是 VO(variable object),所以在離開之後還是可以存取到上層的變數,如果你是以會記住上層資訊的角度來看 closure,那所有的 function 其實都是 closure。
閉包是函式記得並存取 Lexical Scope 語彙範疇的能力,可說是指向特定 scope 的參考,因此當函式是在其宣告的 Lexical Scope 語彙範疇之外執行時也能正常運作。
迴圈與閉包搭配使用時的謬誤與陷阱。
1 | for (var i = 1; i <= 5; i++) { |
由於 console.log(i) 中的 i 會存取的範疇是 for 所在的範疇(目前看起來是全域範疇,因為 var 宣告的變數不具區塊範疇的特性),因此當 1 秒、2 秒…5 秒後執行 console.log(i) 時,就會去取 i 的值,而此時 for 迴圈已跑完,i 變成 6,因此就會每隔一秒印出一個「6」。
解決方法可以利用 IIFE(Immediately Invoked Function Expression)把一個 function 包起來並傳入 i 立即執行,所以迴圈每跑一圈其實就會立刻呼叫一個新的 function,因此就產生了新的作用域。不過在 ES6 裡面有了 block scope 的概念以後,你只要簡單地把迴圈裡面用的 var 改成 let 就行了:因為 let 的特性,所以其實迴圈每跑一圈都會產生一個新的作用域。
關於此題的其他參考:for迴圈 setTimeout 結合一些示例
模組模式可經由建立一個模組實體來調用內層函式,而內層函式由於具有閉包的特性,因此可存取外層的變數和函式。透過模組模式,可隱藏私密資訊,並選擇對外公開的 API。
利用模組依存性載入器或管理器或 ES6 模組來管理模組。
提升(Hoisting)
我知道你懂 hoisting,可是你了解到多深?
秒懂!JavaSript 執行環境與堆疊
W3C
所有的函式都是閉包:談 JS 中的作用域與 Closure
你懂 JavaScript 嗎?#15 閉包(Closure)
[2019-10-12] 進階 JavaScript - Closure
閉包(Closure)
閉包也會用來作為物件私用(private)的模擬,以及名稱空間的管理等
在物件導向程式語言裡面,this 概念指的是 instance 本身。
舉例:
1 | class Car { |
然而和一般物件導向的程式語言 Java 或 C++ 等不同,在 JavaScript 裡面,你在任何地方都可以存取到 this,所以在 JavaScript 裡的 this 跟其他程式語言慣用的那個 this 有了差異,這就是為什麼 this 難懂的原因。
1 | function hello(){ |
一旦脫離了物件導向,也就是在 class 外面的 this,其實沒有太大的意義。
在這種很沒意義的情況下,this 的值在瀏覽器底下就會是 window
,在 node.js 底下會是 global
,如果是在嚴格模式,this 的值就會是 undefined
。這個規則就是所謂的「預設綁定」。
可以用 call、apply 與 bind 改變 this 的值:
1 | 'use strict'; |
call 跟 apply 的差別就是 apply 在傳參數時要用 array 包起來。
1 | class Car { |
可以把原本的 this 值覆蓋掉。
1 | 'use strict'; |
使用 bind 之後,call 方法也沒有辦法覆蓋掉。
如果是在非嚴格模式底下,無論是用 call、apply 還是 bind,你傳進去的如果是 primitive 都會被轉成 object:
1 | function hello() { |
this 是在運行時求值的,可以適用於任何 function,從不同 object 調用同一個 function 可以會有不同 this 的值。
1 | const obj = { |
this 的值跟 作用域 跟 程式碼的位置 在哪裡完全無關,只跟「你如何呼叫」有關。
作用域的概念舉例:
1 | var a = 10 |
無論我在哪裡,無論我怎麼呼叫test這個 function,他印出來的 a 永遠都會是全域變數的那個 a(// 10),因為作用域就是這樣運作,test 在自己的作用域裡面找不到 a 於是往上一層找,而上一層就是 global scope,這跟你在哪裡呼叫 test 一點關係都沒有。test 這個 function 在宣告的時候就把 scope 給決定好了。
但 this 卻是完全相反,this 的值會根據你怎麼呼叫它而變得不一樣,例如使用 call、apply 跟 bind 可以用不同的方式去呼叫改變 this 的值。如果 function 是在物件下調用,那麼 this 則會指向此物件,無論 function 是在哪裡宣告;使用物件的方法調用時 this 會指向調用的物件。宣告的位置不重要,重要的是呼叫的方式。
JavaScript 的 this 到底是誰?
淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂
1 | import React from 'react'; |
this.setStateHandler().bind(this)
sets the context for the function setStateHandler()
to be the class object. This is necessary so that you could call this.setState({...})
inside the method, because setState()
is the method of React.Component. If you do not .bind(this)
you would get an error that setState()
method is undefined.
what is the usage of : this.method.bind(this)
React 與 bind this 的一些心得
當使用 extend React.Component 的方式去宣告元件的時候,React 確實會綁定 this 到元件內,但是卻有以下特定的地方才會被綁進去生命周期函式,例如 componentDidMount 等等
render 內其他自己定義的 property 就不會被綁入 this ,而且 this 會被指到 windows 這個全域上。