JS 命令式 宣告式 函式式 程式設計?

煙雨恐龍發表於2020-12-05

JS 命令式 宣告式 函式式 程式設計?

——煙雨仔的讀書筆記, 並沒有很深奧~

1. 命令式 和 宣告式 是啥?

命令式和宣告式是按照程式設計風格來劃分的.

宣告式有一個突出特點: 對執行結果的描述遠勝於執行過程.

相對地, 命令式的特點是: 關注達成目標的具體過程.

或許你還是很懵, 看下面的指令式程式設計例子.

	const arr = ['h', 'e', 'l', 'l', 'o' ]
	for(let i = 0; i < arr.length; i++) {
		arr[i] = arr[i].toUpperCase()
	}
	console.log(arr)

在上面的例子中, 包含了完成這個任務的整個過程, 即遍歷整個陣列並轉換成大寫.

接下來看宣告式程式設計如何完成.

	const arr = ['h', 'e', 'l', 'l', 'o']
	const arrNew = arr.reduce((prev, cur) => 
		prev += cur.toUpperCase(), '')
	console.log(arrNew)

對於上述程式碼, 我們之所以明白它發生了什麼是由於看到 reduce 方法, 但具體遍歷處理的細節被抽象封裝到 reduce 內部, 我們並沒有看到它迴圈遍歷的過程.

簡單理解就是命令式程式碼的過程相當細節: 切塊, 去籽, 扔進攪拌機, 而宣告式程式碼描述的是具體結果: 西瓜, 榨成西瓜汁.

因此宣告式程式設計程式碼是可讀性更強的, 因為我們並不在意榨成西瓜汁的細節. React 框架採用的就是宣告式程式設計, render 函式呼叫來構建 DOM, 但 DOM 渲染的具體細節是被封裝的. 雖然看不到細節, 但我們看到 render 方法就清楚地知道這是在渲染元件.

2. JS 中的函式

JS 可以進行函數語言程式設計, 這意味著變數可以做的事情, 函式也可以.

是的, 接下來你需要謹記: “函式就是變數!

  1. 可以像宣告變數那樣去宣告一個函式(函式表示式)
    let fn = function(msg) {
    	console.log(msg)
    }
    
  2. 函式就是變數! 因此可以新增到物件
    const obj = {
    	msg: 'hello watermelon',
    	foo (msg) {
    		console.log(msg)
    	}
    }
    
  3. 函式就是變數! 因此可以新增到陣列
    const arr = [
    	'hello watermelon',
    	msg => console.log(msg),
    	'hello world'
    ]
    
    arr[1](arr[0])	// hello watermelon
    
  4. 函式就是變數! 因此可以作為其他函式的引數傳遞
    let foo = fn => fn('hello watermelon')
    foo(msg => console.log(msg))
    // hello watermelon
    
  5. 函式就是變數! 因此可以作為其他函式的執行結果返回
    const foo = arg1 => arg2 => console.log(arg1 + ' ' + arg2)
    const log = foo('hello')// log 是 foo 返回的函式
    log('watermelon')
    

經過以上的瞭解, 煙雨仔相信你開始有一丁點了解了.

函數語言程式設計的核心概念

這部分偏理論, 以至於煙雨仔也有些迷惑…

不可變性

在函數語言程式設計中, 資料不可變, 資料是無法被修改的. 這意味著不能修改原始資料, 只能進行拷貝編輯. 關於如何拷貝這裡就不進行贅述.

純函式

純函式是一個返回結果只依賴輸入引數的函式. 我們需要明確以下幾點.

  1. 純函式至少需要接收一個引數, 並將引數當做不可變資料.
  2. 總是返回一個值或其他函式.
  3. 不會產生副作用, 即不會修改作用域外的變數.

以下是一個非純函式.

	const person = {
		name: 'mistrain',
		age: 18,
		hobby: 'watermelon'
	}
	function changeName() {
		person.name = 'zongzi'
		return person
	}
	changeName()
	console.log(person)
	// {name: 'zongzi', age: 18, hobby: 'watermelon'}

以上 changeName 函式是非純函式. 它並沒有接收引數, 也沒有返回一個值或其他函式, 並且修改了作用域外資料 person.

接下來看純函式.

	const person = {
  		name: 'mistrain',
  		age: 18,
  		hobby: 'watermelon'
	}
	const newPerson = info => ({...info, name: 'zongzi'})
	// 擴充套件運算子, 並修改部分物件屬性, 煙雨仔第一次用這種寫法, 妙~
	console.log(newPerson(person))
	// {name: 'zongzi', age: 18, hobby: 'watermelon'}
	console.log(person)
	// {name: 'mistrain', age: 18, hobby: 'watermelon'}

以上是一個純函式, 它實現了不改變 person, 得到一個新的 newPerson 物件, 因此沒有產生副作用.

資料轉換

由於函數語言程式設計中資料不可變性的存在, 有時往往需要將一種資料轉換成另一種資料副本來使用. ES6提供了一些高階函式使轉換副本的程式碼更加簡單.

高階函式

  1. 第一類是 reduce, map, filter等, 它們都將函式作為引數進行傳遞.
  2. 第二類是將函式作為返回值. 如柯里化(currying)就是通過在一個函式內返回另一個函式實現的.

這裡放一個問爛了的經典柯里化面試題, 在瞭解函數語言程式設計後, 重新來看這個題相信你會有不一樣的理解.

實現一個函式, 呼叫得到:
fn(1) = 1
fn(1, 2, 3) = 6
fn(1)(2, 3)(4, 5, 6) = 21

遞迴

魯迅先生說: “不懂資料結構的前端也可以是好前端.”(煙雨仔瞎編的)相信你懂遞迴是怎麼一回事, 即"自己呼叫自己". 這裡煙雨仔也不贅述了.

一些廢話

本篇是煙雨仔在學習《React 學習手冊》時的一些筆記. 畢竟理論的東西就是很容易忘記, 但確實很有用~

寫得很淺, 不正確的地方望諸位大佬指正.

相關文章