Knows JavaScript === ## Types of JavaScript: - string - number - boolean - null - undefined - object There are two types: - Primitive Type - string - number - boolean - null - undefined - symbol (Starting in ES6) - Object Type **string** using `''` or `""` literals to declare string type. ``` const myString = 'Hello World!'; const myStringObj = String('Hello World!'); console.log(myString[0]); // "H" console.log(myString[5]); // " " console.log(myString[11]); // "d" ``` **number** - Positive value - Negative value - Float value - Octal value - Decimal value - Hexadecimal value - Scientific notation - Infinity - NaN (Not an Number) ``` const myPositiveValue = 123; const myNegativeValue = -123; const myFloatValue = 123.3; const myOctal = 012; const myHex = 0xFF; const myScientificNotation = 513e-5; const myInfinity = Infinity; const myNaN = NaN; ``` *Casting to decimal* - Number() - +() ``` console.log(Number('0100')); // 100 console.log(Number('0100.1')); // 100.1 console.log(Number('100abc')); // NaN console.log(Number(undefined)); // NaN console.log(+'0100'); // 100 console.log(+'0100.1'); // 100.1 console.log(+'100abc'); // NaN console.log(+undefined); // NaN ``` *Casting to integer* - parseInt() - The second argument used to choose what types of carry to convert to decimal - Cannot parse scientific notation ``` console.log(parseInt('0100')); // 100 console.log(parseInt('0100', 8)); // 64 console.log(parseInt('0100.1')); // 100 console.log(parseInt('100abc')); // 100 console.log(parseInt(undefined)); // NaN ``` *Casting to float* - parseFloat() - Can parse scientific notation ``` console.log(parseFloat('0100')); // 100 console.log(parseFloat('0100.1')); // 100.1 console.log(parseFloat('100abc')); // 100 console.log(parseFloat(undefined)); // NaN ``` **JavaScript 浮點數運算會有誤差值, 因為是依據 [IEEE 754的雙精度(64位元)](https://zh.wikipedia.org/wiki/IEEE_754),因此有做浮點數運算的時候最好搭配`parseFloat()`與`toFixed()`來確保正確性。** ``` console.log(0.1 + 0.2); // 0.30000000000000004 console.log(0.1 + 0.2 === 0.3); // false console.log( parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true ); ``` *NaN* ``` console.log(typeof NaN); // number console.log(NaN === NaN); // false console.log(NaN == NaN); // false ``` **boolean** ``` console.log(Boolean('hello world')); // true console.log(!!'hello world'); // true console.log(!'hello world'); // false ``` *falsy* ``` console.log(!!false); // false console.log(!!0); // false console.log(!!''); // false console.log(!!null); // false console.log(!!undefined); // false console.log(!!NaN); // false ``` *truthy* ``` console.log(!!' '); // true console.log(!!1); // true console.log(!!-Infinity); // true console.log(!!{}); // true console.log(!![]); // true ``` **善用`truthy`&`falsy`技巧可以讓你的程式更簡潔明瞭** **undefined** It means no value, and it's the defualt value of JavaScript. ``` var nothing = undefined; console.log(nothing); // undefined var nothingInside; console.log(nothingInside); // undefined ``` **As `undefined` is the unique value of JavaScript, it is not that JSON-Safe.** Primitive Type: - It's a value, no attributes. - It's immutable. - 必要時,JavaScript 引擎會將原始型別強制轉型為 對應的物件型別 (除了 null & undefined) **null** In JavaScript, null means none. ``` const nothing = null; console.log(typeof nothing); // "object" // It's the bug of JavaScript. // So do not use typeof on null type ``` - null is assigned by human, not JavaScript's default value. - null is JSON-Safe ### Object Type - Built-in Objects: - String - Number - Boolean - Object - Function - Array - Date - Error - RegExp **How to create a Built-in object:** - using `new` to create. - using literal to create. `new` example: ``` const myString = new String('Hello'); const myNumber = new Number(123); const myArray = new Array(1, 2, 3) ``` `literal` example: ``` const myString = 'Hello'; // string literal const myNumber = 123; // number literal const myFloatNumber = 123.4; // float number literal const myBoolean = true; // boolean literal const myNull = null; // null literal const myArray = [1, 2, 3]; // array literal const myObject = { a: 1, b: 2, c: 3 }; // object literal const myRegExp = /123/; // regExp literal ``` **Declaring recommendation:** - Only `Date` and `Error` use `new` - Others of Initialization use to `literal` for readable, short and performance considered. **Check type using `typeof`** ``` typeof myString // object typeof myArray // object ``` **How to check the child type of object** - Object.prototype.toString.call(); - Using `instanceof` to check the object's type equals to the specific type. Return `true` if they are equal. ``` Object.prototype.toString.call(myString); // [object String] Object.prototype.toString.call(myArray); // [object Array] myString instanceof String // true myArray instanceof Object // false ``` **Get value using `valueOf()`** ``` myString.valueOf() // Hello myNumber.valueOf() // 123 ``` ## Function **IIFE(Immediately Invoked Function Expression) or Self-Invoking Functions** - IIFE will excute automatically when program runs the position of declaration of IIFE. - It's a Function Expression. ``` (function() { console.log('This is IIFE'); // This is IIFE }()); (function() { console.log('Is this IIFE?'); // nothing happened. }); const myString = (function() { return 'This is IIFT'; }()); console.log(myString); // This is IIFE ``` ### Function Scope - Lexical Scope (語彙範疇) - Dynamic scope (動態範疇) **JavaScript 與大多數語言相同是採用 Lexical Scope。** *Lexical Scope* 代表著區塊間的包裏關係,**被包裏在內層的區塊可以保護自己的變數不被外層取用,相反的外層區塊的變數還是可以被內層區塊使用。** ``` var outer = 'Outer'; function myFunction() { var inner = 'Inner'; console.log(outer); // Outer console.log(inner); // Inner } myFunction(); console.log(outer); // Outer console.log(inner); // Uncaught ReferenceError: inner is not defined ``` ### Function call The `call` is a built-in function of Function. - Run function, can use `()` but if using `()` can not specify `this` - Explicitly specify `this` - `fn.call(this, arg1, arg2, ..., argn)` ``` function add(a, b) { return a + b; } console.log(add(1, 2)); // 3 console.log(add.call(null, 1, 2)); // 3 ``` ### Function apply It is same as `call`. Only different in passing arguments. - `fn.apply(this, [arg1, arg2, ..., argn])` ``` function add(a, b) { return a + b; } console.log(add(1, 2)); // 3 console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3 console.log(add.appy(null, 1, 2)); // Uncaguth TypeError: CreateListFromArrayLike called on non-object ``` ### Function arguments ``` const list = [1, 5, 8]; function add() { return Array.from(arguments).reduce((sum, num) => sum + num, 0); } console.log(add.call(null, 1, 5)); // 6 console.log(add.apply(null, list)); // 14 ``` **`arugemnts` is all passing arguments of the function. These arguments are in the `array-like` so this `array-like` doesn't have the bulit-in functions of Array Object.** ### Function bind - `new_fn = fn.bind(this, arg1, arg2, ..., argn)` ``` function add(a, b) { return a + b; } const add1 = add.bind(null, 2); console.log(add1(1)); // 3 console.log(add1(5)); // 7 ``` *Simple Polyfill* ``` function add(a, b) { return a + b; } function addNew() { return Array.from(arguments).reduce((sum, num) => sum + num, 0); } function bind(t, callback) { const outerArguments = Array.from(arguments).splice(2); return function () { const innerArguments = Array.from(arguments); return callback(t, outerArguments.concat(innerArguments)); } } const simpleAddBind = bind(null, add, 1); const simpleAddNewBind = bind(null, addNew, 1); console.log(simpleAddBind(1, 2, 3)); // 2 console.log(simpleAddNewBind(1, 2, 3)); // 7 ``` ## this - Default Binding - Implicit Binding - Explicit Binding - `new` Binding - Semantic Binding ### Default Binding Default binding refers to how `this` is the global context whenever a function is invoked without any of these other rules. If *we aren't using a dot and we aren't using call(), apply(), or bind(),* our `this` will be our global object. Your global context depends on where you're working. If you're in browser, `this` will be the `windown`. When programming in `strict mode`, the global context is `undefined`. ``` // without strict mode function printMe() { console.log(this); // prints your 'Window Object' } ``` ``` 'use strict'; function printMe() { console.log(this); // undefined } ``` Eyeballing `This` You'll notice that none of these rules require too much work. 1. Is there a dot? Look to the left. That's `this`. 2. Do you see .call() or .apply()? What's passed in before the first comma? That's `this`. 3. Does the function stand alone when it's invoked? Then what's your global context? That's `this`. These three rules-of-thumb point to the most important rule of all: `this` refers to a function's *callsite* (where it is invoked). **.bind() - The Exception** When called on a function, .bind() sets a `this` context and returns a **new function** of the same name with a bound `this` context. ``` var sayMyName = function () { console.log('My name is ' + this.name); } const ctx = { name: 'Jones' }; var sayMyName = sayMyName.bind(ctx); sayMyName(); // My name is Jones ``` ### Implicit Binding Implicit binding occurs when dot notation is used to invoke a function. ``` const obj = { name: 'MyObjectName', hello: sayHello }; function sayHello() { console.log('Hello!!', this.name); } obj.hello(); // Hello!! MyObjectName ``` ### Explicit Binding Explicit binding of `this` occurs when `.call()`, `.apply()`, or`.bind()` are used on a function. We call these explicit because you are explicitly passing in a `this` context to call() or apply(). ``` function sayHelloToEveryone(no) { console.log(this.name + ' says: Hello everyone! I\'m No. '+ no + ' guest.'); } const firstGuestObj = { name: 'Jones', no: 1 }; const secondGuestObj = { name: 'Mary', no: 2 }; // Jones says: Hello everyone! I'm No. 1 guest. sayHelloToEveryone.call(firstGuestObj, firstGuestObj.no); // Mary says: Hello everyone! I'm No. 2 guest. sayHelloToEveryone.apply(secondGuestObj, secondGuestObj.no); ``` ## callback(回調) 回調(callback)是一種特別的函式結構,也因為 JavaScript 具有`高階函式(Higher-order function)`的特性,意思說是在函式中可以用另一個函式當作傳入參數值,最後也可以回傳函式。 一般而言,函式使用回傳值`return`作為最後的執行語句。但回調不是,回調結構首先會定義一個函式類型的傳入參數,在此函式的最後執行語句,即是呼叫這個函式傳入參數,通常我們稱它為`回調(callback)`函式。回調函式經使用匿名函式的語法,直接寫在函式的傳入參數中。 ``` function showMessage(greeting, name, callback) { console.log('you call showMessage') callback(greeting, name) } showMessage('Hello!', 'Jones', function(param1, param2) { console.log(`${param1} ${param2}`) }) ``` 由於回調函式是一個函式類型,通常會在使用它的函式中,作一個基本檢查,以免造成程式錯誤,在 JavaScript 中函式是一個特別的類型,因此,函式的`typeof`回傳值會是'function'。以下為在函式中使用回調(callback)作為回傳值語句的基本 pattern。 ``` function showMessage(greeting, name, callback) { console.log('you call showMessage') if (callback && typeof(callback) === 'function') { callback(greeting, name) } } ``` ## Hoisting(提升) `提升(hoisting)`是 JavaScript 語言中的一種執行階段時的特性,也是一種隱性機制。不過,沒先定義與指定值就使用,這絕對是個壞習慣是吧?變數/常數沒指定好就使用,結果一定不會是你要的。 `var`、`let`和`const`會被提升其定義,但**指定的值不會一併提升上去**。 ``` console.log(x) // undefined var x = 5 console.log(y) // ReferenceError: y is not defined let y = 2 ``` 以上的程式碼因在同一個function scope中,但是變數的定義則皆在使用參數語句之後,因此 JavaScript 在執行階段時會將變數做提升(hoisting),會定義的值不會一併做提升。 ``` var x console.log(x) x = 5 let y console.log(y) y = 2 ``` function hoisting ``` foo() // hello function foo() { console.log('hello') } ``` 關於宣告的提升(hoisting),有些許的不同,以下為解釋。 **All declarations(`var`, `let`, `const`, `function`, `function*`, `class`) are "hoisted" in JavaScript.** This means that if a name is declared in a scope, in that scope the identifier will always reference that particular variable. ``` x = `global` // function scope: (function() { x // not "global" var/let/... x }()); // block scope: { x // not "global" let/const/... x } ``` This is true both for function and block scopes. The difference between `var / function / function*` declarations and `let / const / class` declarations is the **initialisation**. The former are initialised with `undefined` or the (generator) function right when the binding is created at the top of the scope. The lexically declared variables however stay **uninitialised**. This means that a `ReferenceError` exception is thrown when you try to access it. It will only get initialised when the `let / const / class` statement is evaluated, everything before (above) that is called the *temporal dead zone*. The *temporal dead zone* is nost a syntactic location, but rather the *time* between the variable (scope) creation and the initialisation. It's not an error to reference the variable in code above the declaration as long as that code is not executed (e.g. a function body or simply dead code), and it will throw an exception if you access the variable before the initialisation even if the accessing code is below the declaration (e.g. in a hoisted function declaration that is called too early). - 所有的定義`var`, `let`, `const`, `function`, `function*`, `class`都會被提升(hoisted) - 使用函式定義時,在函式區塊中的這些定義也會被提升到該區塊的最前面 - 當函式與變數/常數同名稱而提升時,函式的優先程度高於變數/常數 - 遵守好的風格習慣可以避免掉變數提升的問題 ## 匿名函式與IIFE 匿名函式還有一另一個被使用的情況,就是實現只執行一次的函式,也就是IIFE結構。**IIFE (Immediately invoked function expression)**,中文稱之為**立即呼叫函式表達式**,IIFE可以算是 JavaScript 中獨特的一種 design pattern,它是被某些聰明的程式設計師研究出來的一種結構,它與表達式的強制執行有關,有兩種語法長得很像但功能一樣,這兩種都有人在使用。 ``` (function () {...})() (function () {...}()) ``` IIFE在執行環境一讀取到定義時,就會立即執行,而不像一般的函式需要呼叫才會執行,這也是它的名稱的由來 - 立即呼叫,唯一的例外當然是它如果在一個函式的內部中,那只有呼叫那個函式才會執行。 ``` (function () { console.log('IIFE test1') }()) function test2() { (function () { console.log('IIFE test2') }()) } test() ``` IIFE的主要用途,例如分隔作用範圍,避免全域作用範圍的污染,避免在區塊中的變數提升(hoisting)。IIFE也可以再進而形成一種 Module Pattern(模組模式),用來封裝物件的公用與私有成員。許多知名的函式庫例如: JQuery、Underscore、Backbone 一開始發展時,都使用模組模式作為擴充結構。 不過,模組模式的結構存在於 JavaScript 已有很久一段時間,算是前一代主要的設計模式與程式碼組織方式,現在網路上毒到的教學文,大概都是很有歷史了。而現今的 JavaScript 已經改為另一種更具彈性的、更全面化的 Module System(模組系統)的作法,例如: AMD、CommonJS與Harmony(ES6標準的代號)。 ## 純粹函式與副作用(Side Effect) 對函式來說,具有副作用代表著可能會更動到外部環境,或是更動到傳入的參數值。函式的區分是以`純粹(pure)函式`與`不純粹(impure)函式`兩者來區分,這不光只有無副作用的差異,還有其他的條件。 純粹函式(pure function)即滿足以下定義的函式: - 給定相同的輸入(傳入值),一定會回傳相同轉出值結果(回傳值) - 不會產生 Side Effect - 不依賴任何外部的狀態 ``` const sum = function(value1, value2) { return value1 + value2 } ``` 套用上面說的定義,你可以用下面這樣理解它是不是一個純粹函式: - 只要每次給定相同的輸入(如:1與2),就一定會得到相同的輸出值(如:3) - 不會改變原始輸入參數或是外部的環境,所以沒有 Side Effect - 不依賴其他外部的狀態(變數之類...) 不純粹函式,以下範例,需要依賴外部的狀態值(變數值) ``` let count = 1 const increaseAge = function(value) { return count += value } ``` 在 JavaScript 中不純粹函式很常見,像我們一直用作為輸出的`console.log`函式,這類函式因為沒有回傳值,都是用來作某件事而已。像`console.log`會更動瀏覽器的主控台(外部環境)的輸出,也算是一種 Side Effect。 純粹函式具有以下的優點: - 程式碼可以簡單化,閱讀性提高 - 較為封閉與固定,可重覆使用性高 - 易於單元測試(Unit Test)、除錯 不過許多內建的或常用的函式都是免不了有副作用,如以下應用: - 會改變傳入參數變數(物件、陣列)的函式 - 時間性質的函式 - I/O相關 - 資料庫相關 - AJAX ## 函式定義的選擇 ``` // Function declaration (函式定義) function sum(a, b) { return a + b } // Function expression (函式表達式) const sum = function (a, b) { return a + b } // Arrow function (箭頭函式) const sum = (a, b) => a + b ``` 首先,由於第二種方式(函式表達式)完全可以被箭頭函式取代,箭頭函式又有另外的好處(綁定`this`),所以它幾乎可以不用了。 而`函式定義`有一些特點,所以它會被用以下的情況: - 全域作用範圍 - 模組作用範圍 - Object.prototype的屬性值 函式定的的優點: - 函式定義名稱可以加入到執行期間的呼叫堆疊(call stack)中,除錯方便 - 函式定義可以被提升,也就是可以在定義前呼叫 除此之外,都可以使用`Arrow Function` ## 原型基礎物件導向 > If you don't understand prototypes, you don't understand JavaScript. > 如果你沒搞懂原型,你就不算真的懂 JavaScript。 ## ES6 Features ### let & const & var - let & const are block scope - var is function scope - const is constant variable, cannot reassign value - let can reassign value 如果是使用`var`來定義變數,程式碼中的變數x並不是在函式中定義的,所以會變成"全域變數" ``` if (true) { var x = 10 } console.log(x) // 10 ``` 對比使用`let`來宣告變數,程式碼中的y位於區塊中,無法在外部環境獲取得到 ``` if (true) { let y = 10 } console.log(y) // ReferenceError: y is not defined ``` ### Template strings(樣版字串) 使用重音符號backtick`(``)`來描述字串。樣版字串可以多行、加入字串變數,還有其他延伸的用法,在`Angular`被大量使用。 ``` const aString = `hello world` const aString = `hello! world!` ``` 在樣版字串中可以嵌入變數/常數,也可以作運算 嵌入的符號是使用錢字號與花括號的組合`${}` ``` const firstName = 'Jones' console.log(`Hello ${firstName}`) // Hello Jones const x = 5 console.log(`5 + 3 = ${x + 3}`) // 5 + 3 = 8 ``` ### for...of 可以用於可迭代物件上、取出其中的值,可迭代物件包含陣列、字串、Map物件、Set物件等等。 ``` const aArray = [1, 2, 3] for (let value of aArray) { console.log(value) // 1 // 2 // 3 } const aString = 'abc' for (let value of aString) { console.log(value) // a // b // c } const aMap = new Map([['a', 1], ['b', 2], ['c', 3]]) for (let value of aMap) { console.log(value) // ['a', 1] // ['b', 2] // ['c', 3] } const aSet = new Set(['a', 'b', 'c']) for (let value of aSet) { console.log(value) // a // b // c } ``` ### Parameter Default Value(參數預設值) 可以直接在傳入參數時就定義這些參數的預設值,這個作法是建議的用法。 ``` function sayHello(name = 'nobody') { console.log(`Hello! ${name}`) } ``` ### Rest Parameters(其餘參數) 使用省略符號(ellipsis)`...`加在傳入參數名稱前面,其餘參數的傳入值是一個標準的陣列值。 ``` function sum(...value) { let total = 0 for (let i = 0, len = value.length; i < len; i++) { total += value[i] } return total } // writing a readable sum function function sumNew(...value) { return value.reduce((sum, value) => sum += value, 0) } console.log(sumNew(1, 2, 3)) // 6 console.log(sumNew(1, 2)) // 3 console.log(sumNew(1, 2, 3, 4)) // 10 console.log(sumNew('1', '2', '3')) // 123 console.log(sumNew('1', '2')) // 12 console.log(sumNew('1', '2', '3', '4')) // 1234 ``` 其餘參數只是扮演好參數的角色,代表不確定的其他參數名稱,所以如果一個函式中的參數值有其他的確定傳入參數名稱,其餘參數名稱應該要寫在最後一個位子,而且一個函式只能有一個其餘參數名稱。 ``` function(a, b, ...theArgs) { // ... } ``` rest parameters vs arguments 物件的簡單比較 - 其餘參數僅代表其餘的傳入參數值,而`arguments`則代表所有傳入的參數值。 - 其餘參數的傳入值為一個**標準陣例**,可以使用所有的陣列方法。而`arguments`物件是**偽陣列**物件類型,則不能使用陣列的大部份內建方法。 - 其餘參數需要定義參數才能使用,如:`...參數名稱`,而`arguments`物件不需要定義即可使用,它是**隱藏機制**。 ### Arrow Function(箭頭函式) ### Promise Promise States - pending(等待中) - fulfilled(已實現) - rejected(已拒絕) ## npm packages security audit Using `npm aduit` to check packages for `CI` stage. [npm audit](https://docs.npmjs.com/auditing-package-dependencies-for-security-vulnerabilities)