JavaScript 概念三明治(四)

函式陳述式與表達式

JavaScript有兩種語法分類:陳述式與表達式。而了解這兩種語法分類之後,我們才有辦法區別函式的兩種使用方法:函式宣告式、函式表達式在運用上有什麼不同。
陳述式
陳述式Statement就像生活中我們所描述事情時所說的某句話像是「今天出門天氣不好的話記得帶傘」,像是一種聲明、或是對邏輯的描寫。陳述式會影響程式的運行流程,並且一定會做某一些事情,向邏輯判斷就是一種陳述式。
但是陳述式在JavaScript並不會產生數值,可以以此看出它跟表達式的區別。所以不能放在JavaScript內預期會產生數值的地方,像是函式的參數、函式的回傳值、或是宣告變數時等號的右邊(不能被用來指派給另一變數)。舉例來說,常見的陳述式有:

*if/switch判斷式
*for/while迴圈
*變數宣告
*一般函式宣告

表達式
就像你每個月上班或打工,做完該做的事情之後會拿到薪水作為回報一樣。表達式也是一段JavaScript在執行完後會得到一個結果的程式碼。它可以是很長,也可以簡短,只要執行後會回傳結果的一段程式碼,就是表達式。
//函式呼叫
functionInvocation();
//變數指派
a = 3;
//運算式
1 + 2;
a ===3;
ture || false;
true && true;
Array.isArray([]) ? doSomeThing() : doOtherThing();
這些都是表達式。
函式陳述式與函式表達式
在JavaScript裡面,要創造一個函式,可以透過宣告式,也能透過表達式來達成。這兩種方法就很值觀則分別稱成「函式陳述式」跟「函式表達式」。
函式陳述式
函式陳述式是藉由直接給函式名稱來直接宣告一個函式,其實它就是一般的函式宣告。之前有說過變數宣告時,JavaScript會幫你保留記憶體空間,所以它屬於陳述式。而像這樣子一般的函式宣告,跟變數宣告會產生的行為是一樣的,因此當然也屬於陳述式。
//可以在宣告函式的程式碼之前就先呼叫
functionStatement();
function functionStatement(){
//do something
}
函式表達式
另外一種宣告函式的方法是函式表達式,函式表達式的寫法是直接一個把函式指派給另一個變數,之所以能夠這麼做是因為函式本身在JavaScript裡面也是一個物件,所以除了一般的宣告方式之外,函式也能夠作為一個被指派的值。
functionExpression() //functionExpression is not a function
var functionExpression = function(){
//do somthing
}

上方是 functionStatement
下方是函式表達式,有 do somthing

回呼函式

在JavaScript裡面很常聽到這個值,那麼什麼是回呼函式Callback Function?其實它指的就是在函式裡面執行的另外一個函式。它有著一種「等某段邏輯執行完畢之後,在告訴我」的意思。

其餘參數以及物件參數

這邊介紹兩種在函式上非常實用的語法:其餘參數物件作為參數時的展開。

>其餘參數
當一個函式有可能接收一個以上但不確定數量的參數,我們就能夠使用ES6之後提供的其餘參數(Rest Parameter)。其餘參數以...加上變數名稱來表示,且只能擺在最後一個位置,它會將所有後來未在函式裡面定義的變數,蒐集成一個陣列傳入函式。

function addAll(a,b,…rest){
let sum =a+b;
rest.forEach((num)=>{ //參數 rest會是一個陣列
sum = sum +num;
})
return sum;
}
AddAll=addAll(1,2,3,4,5,6);
console.log(AddAll);
>物件參數的展開
當一個物件參數傳入函式時,我們可以把這個物件「展開」,快速地取得物件裡面的屬性作為參數給函式內的邏輯使用。
var userInfo = {
name:'Anakin',
nickName: 'King'
}
function displayUser({name,nickName}){
//do something
}
displayUser(userInfo);

圖說演算法使用JavaScript(九)

5-3徹底完轉單向串列演算法

在JavaScript語言中,如果以動態配置產生鏈結串列的節點,必須先行自訂一個類別,接著在該類別中定義一個指標欄位,用意在指向下一個鏈結點,及至少一個資料欄位。例如我們宣告一學生成績串列節點的結構宣告,並且包含下面兩個資料欄位;姓名name、成績score,與一個指標欄位next。可以宣告如下:
class student{
constructor(){
this.name='';
this.score=0;
this.next=null;
}
}
當各位完成節點類別的宣告後,就可以動態建立鏈結串列中的每個節點。假設我們現在要新增一個節點至串列尾端,且ptr指向串列的第一個節點,在程式上必須設計四個步驟:
1.動態配置記憶體空間給新節點使用。
2.將原列尾端的指標欄next指向新元素所在的記憶體位置。
3.將ptr指標指向新節點的記憶體位置,表示這是新的串列尾端。
4.由於新節點目前為串列最後一個元素,所以將它的指標欄next指向null。
例如要將s1的next變數指向s2,而且s2的next變數指向null:
s1.next = s2;
s2.next = null;
由於串列的基本特性就是next變數將會指向下一個節點,這時s1節點與s2節點間的關係就如下圖所示:
5-3-1單向鏈結串列的連結

JS           concatlist.js

/[示範]:單向串列的連結功能
var concatlist=(ptr1,ptr2)=>{
	ptr=ptr1;
	while (ptr.next!=null) ptr=ptr.next;
	ptr.next=ptr2;
	return ptr1;
}

class employee{
	constructor()  {
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

findword=0;
data=[];
namedata1=['Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack'];
namedata2=['May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy'];
for (i=0; i<12;i++){
	data[i]=[];
	data[i][0]=i+1;
	data[i][1]=Math.floor(51+Math.random()*50);
}
const head1=new employee();  //建立第一組串列首
if (!head1) {
	console.log('Error!! 記憶體配置失敗');
	return;
}
head1.num=data[0][0];
head1.name=namedata1[0];
head1.salary=data[0][1];
head1.next=null;
ptr=head1;

for(i=1; i<12; i++){
//建立第一組鏈結串列
	newnode=new employee();
	newnode.num = data[i][0];
	newnode.name=namedata1[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}

for(i=0; i<12; i++){
	data[i][0]=i+13;
	data[i][1]=Math.floor(51+Math.random()*50);
}

const head2=new employee();
if (!head2){
	console.log('Error!! 記憶體配置失敗!!');
	return;
}

head2.num=data[0][0];
head2.name=namedata2[0];
head2.salary=data[0][1];
head2.next=null;
ptr=head2;
for(i=1; i<12; i++){
//建立第二組鏈結串列
  newnode=new employee();
  newnode.num=data[i][0];
  newnode.name=namedata2[i];
  newnode.salary=data[i][1];
  newnode.next=null;
  ptr.next=newnode;
  ptr=ptr.next;
}

i=0;
ptr=concatlist(head1,head2);//將串列相連
console.log('兩個鏈結串列相聯的結果:');
while(ptr!=null){
	process.stdout.write('['+ptr.num+' '+ptr.name+' '+ptr.salary+']=> ');
	i=i+1;
	if(i>3){
		console.log();
		i=0;
	}
	ptr=ptr.next;
}

用一維方式來算

PHP          concatlist.php

$namedata1=array('Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack');

$namedata2=array('May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy');
$namedata=array();

function add_score($arr){
	$count_arr=count($arr);
	$new_arr=array();

	for ($i=0; $i<$count_arr; $i++){
		$score = rand(60,100);
		$temp = $arr[$i].",".$score;
		array_push($new_arr,$temp);
	}
	return $new_arr;
}

function join_arr($arr1,$arr2){
	 $count_arr=count($arr2);
	 for($i=0; $i<$count_arr; $i++){
	 	array_push($arr1,$arr2[$i]);
	 }
	 return $arr1;
}
$new_namedata1=add_score($namedata1);
$new_namedata2=add_score($namedata2);
$namedata=join_arr($new_namedata1,$new_namedata2);

foreach ($namedata as $key => $item) {
	  echo $item." ";
}

後記:

PHP
    使用二維陣列,很難去加、刪,只好用一維陣列並加上符號,來加資料。
       array_push()函數,前面不能加變數,會出錯。
           $my_arr=array_push($my_arr,$add_data);
  會出現錯誤的訊息
      array_push() expects parameter 1 to be array

PHP          concatlist.php

用二維方式來算       

$namedata1=array('Allen','Scott','Marry','Jon',
			'Mark','Ricky','Lisa','Jasica',
		   'Hanson','Amy','Bob','Jack');

$namedata2=array('May','John','Michal','Andy',
			'Tom','Jane','Yoko','Axel',
		   'Alex','Judy','Kelly','Lucy');
$namedata=array();

function add_score($arr){
	$count_arr=count($arr);
	$new_arr=array();
	$temp_arr=array();

	for ($i=0; $i<$count_arr; $i++){
		$score = rand(60,100);
		$temp = $arr[$i];
		$temp_arr=array($temp,$score);
		array_push($new_arr,$temp_arr);
	}
	return $new_arr;
}

function join_arr($arr1,$arr2){
	 $count_arr=count($arr2);
	 for($i=0; $i<$count_arr; $i++){
	 	array_push($arr1,$arr2[$i]);
	 }
	 return $arr1;
}
$new_namedata1=add_score($namedata1);
$new_namedata2=add_score($namedata2);
$namedata=join_arr($new_namedata1,$new_namedata2);

foreach ($namedata as $key => $item) {
	  foreach($item as $value){
	  echo $value." ";
	  }
	  echo "<br>";
}

後記       PHP二維參考資料

PHP      先存成陣列,再用array_push去加
for ($i=0; $i<$count_arr; $i++){
       $score = rand(60,100);
       $temp = $arr[$i];
       $temp_arr=array($temp,$score);
array_push($new_arr,$temp_arr);
}
5-3-2單向串列插入新節點
在單向鏈結串列中插入新節點,如同一列火車加入新的車廂,有三種情況:加於第1個節點之前、加於最後一個節點之後,以及加於此串列中間任一位置。

演算法如下:

newnode.next=first;
first=newnode;

演算法如下:

ptr.next=newnode;
newnode.next=null;

演算法如下:

newnods.next=x.next;
x.next=newnods;

JS          insert_node.js

class employee {
	constructor() {
		this.num=0;
		this.salary=0;
		this.name='';
		this.next=null;
	}
}

var findnode=(head,num)=>{
	ptr=head;
	while (ptr!=null){
		if (ptr.num==num) return ptr;
		ptr=ptr.next;
	}
	return ptr;
}

var insertnode=(head,ptr,num,salary,name)=>{
	InsertNode=new employee();
	if (!InsertNode) return null;
	InsertNode.num=num;
	InsertNode.salary=salary;
	InsertNode.name=name;
	InsertNode.next=null;
	if(ptr==null)  {  //插入第一個節點
		InsertNode.next=head;
		return InsertNode;
	}
	else{
		if(ptr.next==null)  //插入最後一個節點
				ptr.next=InsertNode;
			else{ //插入中間點
				InsertNode.next=ptr.next;
				ptr.next=InsertNode;
			}
	}
	return head;
}

position=0;
data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299],
			[1012,42660],[1014,25676],[1018,44145],[1043,52182],
			[1031,32769],[1037,21100],[1041,32196],[1046,25776]];
namedata=['Allen','Scott','Marry','John','Mark','Ricky',
					'Lisa','Jasica','Hanson','Daniel','Axel','Jack'];
process.stdout.write('員工編號 薪水\t員工編號 薪水\t員工編號 薪水\t員工編號 薪水\n');
process.stdout.write('-------------------------------------------------------------\n');

for (i=0; i<3; i++) {
	for(j=0; j<4; j++)
		process.stdout.write(data[j*3+i][0]+ '\t' +data[j*3+i][1]+'\t');
		console.log();
}
console.log('-------------------------------------------------------------');

head=new employee(); //建立串列首
head.next=null;

if(!head) {
	console.log('Error!! 記憶體配置失敗');
	return;
}
head.num=data[0][0];
head.name=namedata[0];
head.salary=data[0][1];
head.next=null;
ptr=head;

for (i=1; i<12; i++){//建立串列
	newnode=new employee();
	newnode.next=null;
	newnode.num=data[i][0];
	newnode.name=namedata[i];
	newnode.salary=data[i][1];
	newnode.next=null;
	ptr.next=newnode;
	ptr=ptr.next;
}

while(true) {
	process.stdout.write('請輸入要插入其後的員工編號,如輸入的編號不在此串列中,\n');
	const prompt = require('prompt-sync')();
	const position=parseInt(prompt('新輸入的員工節點將視為此串列的串列首,要結束插入的過程,請輸入-1'));
	if (position ==-1)
		break;
	else{
		ptr=findnode(head,position);
		new_num = parseInt(prompt('請輸入新插入的員工編號:'));
		new_salary=parseInt(prompt('請輸入新插入員工的薪水:'));
		new_name=prompt('請輸入新插入的員工姓名:');
		head=insertnode(head,ptr,new_num,new_salary,new_name);
	}
	console.log();
}
ptr=head;
console.log('\t員工編號     姓名\t薪水');
console.log('\t===========================');
while (ptr!=null) {
	process.stdout.write('\t['+ptr.num+' ]\t[ '+ptr.name+' ]\t['+ptr.salary+']\n');
	ptr=ptr.next;
}

JavaScript 概念三明治(三)

箭頭函式

E86之後,出現一種新的創造函式的方法,普遍被稱為箭頭函式,使得創造函式變得簡潔許多。相對於以前使用function關鍵字來宣告的用法,箭頭函式只需要用=>搭配括號跟區塊,就可以簡單地創造出函式,而因為這個等於大於的符號看起來很像箭頭,所以才被稱為箭頭函式。

const arrowFunction =() =>{
//do something.
};
箭頭函式的用法如上,可以看到這個用法已經不需要function關鍵字了,取而代之的是在括號跟區塊中間的箭頭。而你應該也可以注意到,箭頭函式的用法就與前面提到的函式表達式非常相近。沒錯,箭頭函式本身是匿名的,這意味它只可以被使用在函式表達式裡。
善用函式表達式
我們不妨試試看直接創造一個箭頭函式,但不將它指派給任何變數試試看。

()=>{
console.log("this is an arrow function");
};
你會發現,因為它是表達式,所以直接執行並不會出錯,就像我們直接在全域環境創造一個數字、一個字串或一個物件一樣。它會回傳一個函式物件,不過JavaScript看到這裡只會知道,「喔,這裡有一個函式」,所以該函式不會被執行,這段程式碼對JavaScript來講其實沒有什麼意義。
接下來,直覺敏銳的讀者應該可以猜到我想要說什麼了,其實轉個彎想一下,既然它只能用在表達式,那麼也許我也能把它用在前面立即執行函式(IIFE)的寫法上。
(()=>{
console.log("arrow function has been invoked");
})() //加上()會先去執行()的函式

一樣用括號,並把函式放進括號裡面,清楚告訴JavaScript這是一個表達式,而且我要優先運算,然後得到運算結果所回傳的函式物件之後,直接執行它。這樣一來,我就可以用簡潔的方法來撰寫IIFE了。

簡潔的回傳值
使用箭頭函式時,若所要回傳的內容單純只有一個表達式的時候,這個時候我們可以省略區塊{},而直接接在箭頭符號後面,這麼一來在函式被呼叫之後,這個表達式會直接被回傳。
當然,箭頭既然函式本身也是表達式,而JavaScript裡面函式又可以被傳給另為一個函式,那麼我應該有可以這麼做:
有看到特別的地方嗎?上方呼叫add函式的時候連續使用的兩個括號,這可不是連續呼叫兩次的意思!而是因為add函式本身回傳的是另外一個函式,會有兩次的呼叫是說:在執行第一個函式完收到第二個函式之後就直接執行了。

與function關鍵字的差異

箭頭函式相較於一般函式function關鍵字的用法,一般來說的優點都是語法簡潔、好閱讀等等,實際上也真的是這樣,它的確能夠讓開發者增加不少開發速度,不過它與使用function關鍵字創造出來的函式有些微的不同,甚至在某些點上有非常大的不一樣。這些差別不一定就是好或是壞,端看你如何使用而已,所以在這個段落的後面篇幅將會列舉一些較為明顯的差異來說明。
>>箭頭函式沒有arguments物件
什麼是arguments物件?arguments這個字代表了引數,另外一個與隻非常相近的詞稱為參數,引數用在函式宣告時,而呼叫函式時所傳進去的值則叫做參數。
換句話說,宣告一個函式時要先定義好參數,使函式時必須傳入引數。這兩個詞蠻常被搞混的,在別的地方搞錯沒有關係,不過在這邊為了介紹arguments物件,必須要釐清他們的差異才行。
   function add(a,b){  //a,b是參數parameters
return a+b;
}
add(1,3); //引數
所以函式裡面的arguments物件其實就是用來接收傳入函式的引數。一個使用function關鍵字來創造的函式,在函式被呼叫時,除了直接透過函式的參數名稱來拿到對應傳入的引數值,但是也可以用物件來取用個別的引數內容,這是JavaScript提供的預設物件。
arguments物件是一個很像陣列的資料,你可以透過索引來拿到個別的引數值。
但它沒有陣列上那些方便的方法像是foreach或是map,真的單純只是長得像陣列而已,有些人會把它稱為是一個「類陣列」Array-Liked的資料結構。而一個跟陣列比較像的屬性只有length,所以如果要依序拿到每個引數值,則必須用一般的for迴圈。
function add(a, b) {
for (var i=0; i<arguments.length; i++){
if (typeof arguments[i] !== "number"){
return false;
}
}
return a+b;
}
add(1,2); //3
add(1,"some thing else"); //false
而arguments物件在箭頭函式內可不存在的,所以如果想要利用它來組合一些邏輯的話,千萬記得這一點。
const arrowFunction =() =>{
console.log(arguments);
}
arrowFunction();
//ReferenceError:arguments is not defined