JavaScript 概念三明治(二)

物件型別

   「物件」指的就是物件。恩,但其實有很多東西本身也算是物件,例如陣列和函式,不相信嗎?讓我們繼續看下去。你可以在JavaScript裡面宣告一個陣列,然後用typeof去得到這個陣列的型別,就可以看出一些端倪:
JavaScript提供了Array這個所有地方都可以使用全域物件,而在這個Array.isArray的方法能夠讓我們使用,來知道某數值是不是一個陣列。
好,那麼剛剛說函式也是物件型別,是這樣嗎?你會發現,當你一樣用typeof去觀察一個function的型別時,所得到的結果不是object,而是function。
先別急著反駁,讓我先用一個最簡單的方式證明你看。前面應該提到過,一個物件型別的數值,可以用.或是[]來做存取。那麼首先我宣告一個函式,並把這個函式當作物件使用,在上面新增一個屬性之後,再取出這個屬性來看看,是不是得到同樣的值:
    函式真的是像物件一樣的東西,事實上,它真的就是。在JavaScript裡面,函式算是一個比較物件,稱為「函式物件」(Function Object),它除了可以被當作函式來呼叫,也能夠當作物件使用。

一級函式  First-Class Function

    當我們說一個語言具有一級函式的特性時,代表這個語言把函式當作變數值一樣看待,也因此可以把函式當作參數一樣傳入另外一個函式裡面,也能夠以函式作為另一個函式的回傳值。在Function Programing 裡面,也是因為這個特性,才有辦法做到複合函式(Function Composition)。
在JavaScript內,函式本身也是一個特殊的物件(就是函式物件),所以才有辦法把函式當作參數傳給另外一個函式來做使用。

高階函式 High Order Function

    高階函式有人稱為HOF。只要是可以接收函式作為參數,或是回傳函式作為輸出的函式,我們就能夠把它稱為高階函式,例如,JavaScript裡面的陣列這個類別,上面有許多讓你很方便能夠對陣列做操作的方法,例如:map方法讓你能夠依序對每個陣列做一些修改、而filter則讓你能夠過濾掉不符合條件的元素。
我們可以試著思考看看他們是怎麼被實做出來的,下面就以Array.map為例,一起來模擬這個方法、邊思考它的運作方式。

試著實作陣列上的map方法

map是在任何陣列上都可以取用的一個方法,它接收一個能夠修改陣列元素的函式。並在運算之後將每個元素被修改後的數值,以另外一個全新陣列最為回傳值。
   由於map其實就是對陣列元素個別巡訪,然後做某些操作之後回傳,所以可以推敲出可能的步驟如下:
1.將包含開發者修改的邏輯函式傳入map方法內。
2.先創造一個全新的陣列,用以存放運算之後的結果。
3.執行一個以陣列長度為執行次數的迴圈。
4.利用迴圈的計數值作為陣列的索引,來找到每個陣列元素。
5.取得陣列元素、逐個取得修改函式運算完的結果。
6.逐個放入運算結果值的陣列並待迴圈節結束後回傳。
有了這些運作的邏輯,我們也能夠嘗試自己實作類似map方法的邏輯,看起來可能會像是這樣:
function arrayMap(fn, array){
 let newArray = [];
  for (let i=0; i < array.length; i++){
   let result = fn(array[i]);
    newArray.push(result);
   }
   return newArray;
}
let array =[1,2,3];
let newArray = arrayMap(function(item){
   return item*2;
},array)
console.log(newArray);

試著實作陣列上的filter方法

    既然都講到這裡了,我們就來看看另一類似的filter方法。filter一樣接受一個函式來進行運算,只不過相對於map是變更陣列的個別元素,filter的目的是過濾不需要的元素,你應該也能從字面上看出來。
傳進filter的函式,會以回傳值的條件判斷,來作為陣列元素會不會被過濾掉的依據,若回傳值與布林值的true等價、可被轉型為true,那麼就保留這個元素,反之這個元素就不會出現在運算結果的陣列裡面。
而我們同樣的能夠推測filter的運作流程,預期上會與map相似:
1.將包含開發者判斷邏輯的函式傳入filter方法內。
2.先創造一個全新的陣列,用以存放運算的結果。
3.執行一個以陣列長度為執行次數的迴圈。
4.利用迴圈的計數值作為陣列的索引,來找到每個陣列元素。
5.取得陣列元素、並逐個取得判斷函式運算文成的結果。
6.依照判斷函式的結果決定是否要放入結果值的陣列,並待迴圈結束後回傳。
function arrayFilter(fn, array){
   let newArray=[];

   for (let i=0; i < array.length; i++){
      let arrayElement = array[i];
      let shouldkeep = fn(arrayElement);

      if (Boolean(shouldkeep)){
         newArray.push(arrayElement);
      }
   }

   return newArray;
}

let array = [1,2,3];
let resultArray = arrayFilter(function (element){
   return element > 1;
},array);

console.log(resultArray);

圖說演算法使用JavaScript(五)

3-11最大利潤 Max Profit

    給定一系列股票價格,找出產生最大利潤的最小買入價和最大賣出價。舉例以陣列的方式提供一系列的股票價格,例如[24,27,32,36,45],這個函數會找出這一系列的股票價格的最低買入價及最高賣出價,再以陣列的方式回傳,如此一來可以計算出這支股票的最大利潤。

JS          max_prifit.js

var maxProfit=(prices)=> {
	let low = prices[0] < prices[1]? prices[0]:prices[1];
	    high = prices[0] < prices[1] ? prices[1]:prices[2];
	let maxProfit = high - low;
	let temp = low;

	for (let index = 2; index < prices.length; index++){
		const sellPrice = prices[index];

		if (low > sellPrice) temp = prices[index];
		else{
			const  profit = sellPrice - low;
			if (profit > maxProfit)
				(maxProfit = profit),
				(high = sellPrice),
				(low = temp);
		}
	}
    return [low, high];
}
console.log("產生最大歷潤的最小買入價和最大賣出價分別為:");
console.log(maxProfit([24,27,32,36,45]));

PHP          max_profit.php

$arr= array (24,27,32,36,45);
function get_max_profit($arr){

$low = ($arr[0] < $arr[1]) ? $arr[0] : $arr [1];
$high = ($arr[0] > $arr[1]) ? $arr[0] : $arr[1];
$maxProfit = $high - $low;
$temp = $low;
$len = count($arr);
$ans = array();
$i=1;
 while (  $i <= $len-1){
		$sellPrice = $arr[$i];

		if($low > $sellPrice) $temp = $arr[$i];

		else {
			$profit = $sellPrice -$low;
			if($profit > $maxProfit){
				$maxProfit = $profit;
				$high = $sellPrice;
				$low = $temp;
			}

		}
		$i++;
	}

	$ans[] =array($low,$high);
	return $ans;
}
$my_max_profix = get_max_profit($arr);
print_r($my_max_profix);

3-12費伯納序列  Fibonacci

JS          fibonacci.js

var fib=(n)=>{
	if (n==0){
		return 0;
	} else if (n==1 || n==2){
		return 1;
	} else{
		return (fib(n-1)+fib(n-2));
	}
}

const num=10;

for(i=0; i<=num; i++){
	console.log("fib("+i+")=" + fib(i));
}

PHP          fibonacci.php

$my_num = 30;  //超過38時,執行會超過30秒
function my_fibnacci ($n){

	switch ($n) {
		case ($n==0):
			return 0;
			break;
		case ($n==1 ||$n==2):
			return 1;
			break;
		default:
			return(my_fibnacci($n-1)+my_fibnacci($n-2));
			break;
	}
}
for ($i=0 ; $i<=$my_num; $i++){
	echo "fib(".$i.")=".my_fibnacci($i)."<br>";
}

3-13記憶式費伯納序列 Memoized Fibonacci

    這是一種具有記憶性質的費伯納序列的做法,它可以使得在求取費伯納序列能以較高的效率取得。也就是動態規劃法,它是一種具備記憶體功能的演算法。

JS          momoized_fibonacci.js

output =[];   //宣告陣列
var fib=(n)=>{
	if (n==0)  return 0;
	
	if (n==1 ) return 1;
	else{
		output[0]=0;
		output[1]=1;
		for (t=2; t<=n; t++){
			output[t]=output[t-1]+output[t-2];
		}
		return output[n];
	}
}
const num=9;
for(i=0; i<=num; i++){
	console.log("fib("+i+")=" + fib(i));
}

PHP           memoize_fibonacci.php

$my_num = 50; 

function my_fibnacci ($n){
   $ans=array();
	 
	 if($n==0) return 0;
	 if($n==1) return 1;
	 else{
	 	$ans[0]=0;
	 	$ans[1]=1;
	 	for ($j=2; $j<=$n; $j++){
	 		 $ans[$j]=$ans[$j-1]+$ans[$j-2];
	 	}
	 	return $ans[$n];
	 }
	}

for ($i=0 ; $i<=$my_num; $i++){
	echo "fib(".$i.")=".my_fibnacci($i)."<br>";
}

JavaScript 概念三明治(一)

物件 Object

物件是JavaScript 裡面很重要的型別,在其他語言裡的物件可能有不一樣的意思,但在JavaScript裡面,物件就是「一連串鍵與值配對(key-value pairs)的組合」。換句話說,這意味著物件是由一個個的名稱與一個個的對應內容聚集在一起後形成的結果,這個物件的鍵名又被稱為物件的屬性(property),屬性的用途跟變數有一點相像,都是讓開發者在存取時有一個獨特的名稱,用以區別不同內容。

創造一個物件

一個物件最簡單的宣告方式,就是在賦予變數時,用兩個大括號,將即將宣告的物件包裹起來,並用冒號來區分物件內的屬性名稱與想要存放的數值內容。範例如下:

物件屬性的存取

一般的變數在宣告並被賦值後,我們就用該變數名稱來表示要存取對應內容。而這個變數如果存取的內容是物件,則可以搭配中括號「[]」,或是小數點符號「.」來拿到物件裡面某個屬性的內容:

anObject["key1"] --> 用中括號來存取物件屬性的內容
abObject.key1 --> 用小數點符號來存取物件屬性的內容

陣列 Array

陣列在許多程式語言都會出現。它跟物件有一點點像,但是使用的方式有些差異。如同前面提到的,如果物件是一連串的名稱與對應內容組合而成的型別,那麼陣列就是「把一系列的數值放在一起形成的組合」。意即可以先把陣列看成沒有屬性的物件,只是陣列並非像物件那樣是用大括號來宣告。
創造一個陣列
使用中括號就能簡單宣告出一個陣列,並在中括號內用逗點區隔想要存放的各個值,與物件一樣,陣列可以存放多於一個以上的數值。

var array = ["內容一", "內容二"];

與物件屬性可以存放的內容相同,對於陣列裡面可以存放的內容,沒有型別上的限制,想要在陣列裡面放數字、字串、布林、物件或甚至是另外一個陣列,都是可以的。通常存放陣列裡面的數值,我們會用「元素」(element)來稱呼。
存取陣列的元素
存取陣列元素的方法跟物件一樣,可以使用中括號來表示。但是陣列不像物件那樣有明確的屬性可以知道要對哪一個內容作存取,該可麼辦呢?在陣列裡面的處理方式是,既然沒有明確的名稱,那麼就從第一個元素開始,給每一個元素一個足以區別內容的號碼,我們稱之為「索引」(index),陣列的這個號碼,是從0開始算的。
存取陣列的長度
陣列元素的長度可以用.length來取得,它的用法就跟存取物件屬性一模一樣。事實上,陣列背後的實作也是以物件來達成,只是使用的方式不一樣而已。

var array = ["1", "2", "3"];
console.log(array.length);
//3

函式宣告

函式最簡單的使用方式是透過function這個關鍵字來告訴JavaScript你想要宣告的函式。與變數一樣,函式在一般情況下也必須具有名稱才能讓JavaScript知道你想呼叫(執行)哪一個函式。
至於函式執行後回傳的結果則透過return來決定要回傳內容是什麼,而就像前面提到的一樣,return不一定要存在於函式的內容中,只不過如果在函式內沒有任何return被使用,或是有return但後面沒有任何要回傳的值,JavaScript都會自動幫你將回傳的值設置為undefined,所以可以把一個函式回傳的預設值當作是undefined

變數宣告

ES6出現了兩個新的變數宣告的關鍵字,也是目前實際產品開發中最常使用到的兩個語法,它們分別是let以及const。在前面則是用var來處理。
let
let其實與var差不多,與var不同是宣告變數的範疇、以及不能被重複宣告。  var是函式範疇,而let是區塊範疇 

let 是區塊範疇
區塊範疇相比於var的函式範疇,究竟有什麼優缺點呢?一個很明顯的好處就是,能夠更有效避免變數意外地被修改或覆蓋。
var是函式範疇
當我在全域宣告一個變數i,並在後面使用了for迴圈,for迴圈裡面的條件判斷也用到另外一個新的宣告的i,但是你會發現在for條件判斷用i其實在迴圈進行時也無意間對外面的全域變數進行了修改,最後,外面的變數i的內容就跟著變成10。而若使用const和let,這個問題就不會發生。
const和let的範疇則是一樣的,那麼const和let相較起來又有什麼差異呢?
const
const這個字本身意味著常數。它的範疇與let相同,都屬於區塊範疇,但是與let布一樣的是,使用const宣告變數在第一次宣告,並被指派後,就不能修改。也就是說,使用const所宣告的變數,只能夠被讀取。
使用const宣告變數時,一定要跟著一起指派內容。它與var或let不一樣,可以指宣告變數名稱而不給值。若你使用const時這麼做的話,就會出現錯誤。
而上面講到const所宣告的變數無法再重新指派的規則,其實有一個看起來像是例外的情況,那就是當const與物件、陣列一起使用時,會有這樣的情形發生:
看到了嗎?使用const所宣告的變數內容若是物件,則該物件裡的屬性還是能夠被存取跟修改,這看來很像是在對該變數內容作修改,其實並沒有。當變數被宣告並且存放的數值是一個物件時,所帶給變數的並不是物件本身的內容,而是物件存放的記憶體位置。
所以只要該變數所參考的記憶體位置保持一致,就不會有問題。而換另外一個角度來看,若我重新對const所宣告的變數指派另一個物件,那是會報錯的。