進階 Javasctipt 概念 (3)

PY
9 min readMar 31, 2019

JS 動態型別、一級與高階函式

動態型別是 JS 最讓人頭痛的部分,越來越多企業導入 TypeScript 方便維護與開發,而這篇除了來講講麻煩的動態型別以外,還有一級與高階函式如何達成 DRY 原則

JS 動態型別

JS 七大型別

  • string
  • number
  • boolean
  • object
  • undefined
  • null
  • symbol

首先動態型別代表著每個變數都沒有固定的型別,隨時可以改變,前四個在各種語言都比較常看到,而 undefined 跟 null 則需要留意,簡而言之,undefined 代表著你已經分配了記憶體位置,但未定義值,而 null 則是一個空值,空值也是值

let a;//undefined
let b = null //null

那 array 那些呢? 他是個物件

console.log(typeof []) //object

function 呢?

console.log(typeof function(){}) //function

前面幾章提到了 function 就是個物件,那顯示出來的怎是個奇怪的 function 型別,這等等再提到

我們先解釋原始型別 Primitive Type 與物件型別 Non-Primitive Type

原始型別

原始型別是 by value 的,他就是個分配好的記憶體空間,固定在那裏,你無法去改動他的記憶體位置

物件型別

物件型別是對記憶體位置做操作,他是 by reference 的,當你將一個物件賦值給一個變數,他會是參照那個物件的記憶體位置,就像是指標一樣

內建物件

你可能曾經聽過在 JS 中所有東西都是物件,上面列出了內建物件的清單,可以看到 String, Number 也是物件,到底是物件型別還是原始型別,搞得好亂,先來看看一段程式碼

true.toString() //'true'

true 不是原始型別嗎? 怎會像是物件一樣讓方法調用

此時你可以想像 true 外面包一層 Boolean

Boolean(true).toString()

Boolean() 是個內建物件,我們把它想像成他是一個方法來判斷參數的布林值,這方法裡面有個屬性叫做 toString(),讓返回的值變成 string,所以這個 Boolean, String, Number 其實是物件,用來轉換參數型別的方法

isArray

typeof array // object
array.isArray() //true

當你使用 typeof 時,因為陣列本身就是物件的關係,陣列也會被判定為物件,所以可以使用 isArray 這方法來回傳是否為陣列

Pass by value vs Pass by reference

JS 中,物件是參照記憶體位置的,所以當一個物件賦值給另一個物件,這會直接賦予它同樣的記憶體位置

var a = {a:123}
var b = a

陣列也一樣

var a = [1]
var b = a
b.push(2)
console.log(a) // [1,2]

可以用 concat 這方法來創建一個新記憶體位置的陣列在把值 concat 進去

var a = [1]
var b = [].concat(a)
b.push(2)
console.log(a) // [1]

同樣利用 assign 也可以對物件有相同的效果,{ …obj } 解構賦值也是一種方法

var a = {a:'a'}
var b = Object.assign({},a)
b.b = 5
console.log(a) // {a:'a'}
var c = {...b}
console.log(c) //{a:'a',b:5}
c.d = 6
console.log(b)

那...假如有物件裡面又有物件呢? Object.assign 與 { ...obj } ,只有賦值最上層的物件,稱為淺拷貝,屬性內的 obj 卻一樣參照原來 obj 的記憶體位置

let obj = {
a:'a',
b: {
c:'c'
}
}
var b = Object.assign({},obj)
obj.b.c = 5
console.log(obj.b.c) // 5
console.log(b.b.c) // 5

如果要深拷貝屬性內的物件,就得用到 JSON.parse + JSON.stringify 將所有物件序列化後再轉回物件

let obj = {
a:'a',
b: {
c:'c'
}
}
let superClone = JSON.parse(JSON.stringify(obj))
obj.b.c = 5
console.log(superClone.b.c) // c

判斷兩個物件是否相等

https://codertw.com/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/291301/

物件參數傳值 pass by sharing

function 外部物件與傳參數內部共享同一個物件

function a(b){
b.number++
}
var o = {number: 10}
add(o)
console.log(o.number) // 11

其實 pbv or pbr 的解釋一直都是很模糊的,在這篇文章中可以看到對不同語言的傳值傳址解釋,這邊就不贅述了

型別轉換

強制型別轉換在 JS 常常讓人踩到雷,所以才有 typesrcipt 的出現,讓型別更加穩定,維護性更高

1 == '1' //true
1 === '1' //false

比較表

TypeScript & 動態靜態與強弱型別

typescript 是將 JS 轉換為強型別的語言

動態靜態在於需不需要宣告型態,而強弱型別再於能不能轉換型態

const message: string = 'Hello World!';

一級與高階函式

Function is Object

函式就是物件,在上一篇有提到了關於 JS 的函式,分為表達跟陳述與他們各自的語彙環境

在閉包開始前先來深入探討一下函式,首先呼叫函式會有 this, 跟 arguments,我們有最簡單的命名呼叫方式,或是在物件內呼叫該方法,或是使用 call() apply() 執行

let obj = {
two(){
return 2;
}
}
function one(){
return one
}
obj.two()
one()
one.call()

還有一種常見的 new 關鍵字,用於呼叫函式的建構子

let four = new Function('num','return num')
four(4)

Function 是用來創造函式的建構子,裡面傳入參數即可創建一個function

所以既然 function 是 obj,那一樣可以在裡面創建屬性

function one(){
console.log('wooo')
}
one.yell = '123456'

這樣我們在 one 內就有了 yell 的屬性

一級函式

一級函式的意思是你可以對它做與其他型別一樣的操作

  • 可將函式賦予變數
  • 函式裡面的參數也可以帶函式
  • 可 return 函式
  • 跟物件一樣有屬性

另外 es6 有個小技巧是可以在參數裡面設置 default 值

function a(param=6){
return param
}
a() //6

高階函式 High Order Function

高階函式是種函式 Curry 化的方法,主要是將函式參數傳進函式裡的回呼函式,像這樣

const  multiplyBy = (x)=>{return function(y){return x*y;}}const multiplyByTwo = multiplyBy(2);multiplyByTwo(4) // 8//簡化版const  multiplyBy = x => y => x*y

好處是在於將一個方法封裝並接受不同的參數,我們可以針對這些參數做判斷以用來知道說該返回什麼樣的回呼函式

const giveAccessTo = (name) =>'Access Granted to ' + name;function authenticate(person) {let array = [];// you can add checks here for person.levelfor (let i = 0; i < 50000; i++) {array.push(i)}return giveAccessTo(person.name)}function letPerson(person, fn) {
// ++ We now tell the function what data to use when we call it not when we define it + tell it what to do.
if (person.level === 'admin') {return fn(person)} else if (person.level === 'user') {return fn(person)}}function sing(person) {return 'la la la my name is ' + person.name}letPerson({level: 'user', name: 'Tim'}, sing)

我們封裝 letPerson 這個方法來傳入不同的人物階級 person.level 與他們要做的事情 sing ,以達到 DRY 原則

將函式分為三個階段

普通函式 -> 傳入參數 -> 高階函式

所以當你看到函式內的動作重複了,就能一層一層往上去將函式包裝起來,就像俄羅斯娃娃一樣,這或許讓人覺得很複雜有很難理解,但是這的確能提升函式的複用性。

--

--