JavaScript 是透過建構式來模擬物件類別,可建立實體。
若函式內已建立物件,並回傳物件,則可直接使用,不需再用 new 建立物件。
建構式最外層一定是函數,但此函數內可有屬性及方法(function)。
parameters: 指的是在函式的那些傳入參數名稱的定義
arguments: 指的是當函式被呼叫時,傳入到函式中真正的那些值。我們在文章中會以"實際傳入參數值"來說明。
函數種類
Copy //1.函式定義 - 使用有名稱的函式
function sum (a , b){
return a + b
}
//2.函式表達式 - 常數指定為匿名函式
//此處的常數就是函數,可被當成參數使用或做為一個變數的值
const sum = function (a , b) {
return a + b
}
//3.建構式(內含 this 關鍵字)
function PhoneTemplate (brand , modal , withCamera) {
this .brand = brand;
this .modal = modal;
this .withCamera = withCamera || false ;
this . takePhoto = function () {
if ( this .withCamera) {
console .log ( this .modal + ' 照相' );
} else {
console .log ( this .modal + ' 這台沒有照相功能' );
}
}
}
3.箭頭函式
傳入參數預設值
Copy //用邏輯或(||)
const link = function (point , url) {
let point = point || 10
let url = url || 'http://google.com'
...
}
在ES6中加入了函式傳入參數的預設值指定語法,現在可以直接在傳入參數時就定義這些參數的預設值,這個作法是建議的用法:
Copy const link = function (point = 10 , url = 'http://google.com' ) {
...
}
在JavaScript中function可當成參數傳入,而且也可以return function。
Copy const addOne = function (value){
return value + 1
}
const addOneAndTwo = function (value , fn){
return fn (value) + 2
}
console .log ( addOneAndTwo ( 10 , addOne)) //13
不固定傳入參數(Variadic)與其餘(rest)參數
Copy function sum ( ... value) {
let total = 0
for ( let i = 0 ; i < value . length ; i ++ ){
total += value[i]
}
return total
}
作用範圍(scope)
php不同,在外的變數無法帶入function內,除非加上use()。
或本身就是全域變數。例:$_POST, $_GET...
Copy $that = $this ;
$wrapper = function () use ($that) {
return $that -> my_function_name ( 'arg1' , 'arg2' ) ;
};
Copy // ES6中在函數外宣告的常數或變數,是會影響到函數內的變數。
// 也代表函數的參數不一定要直接賦予,也可由外部宣告的值代替。
var scope = 'var'
let scope2 = 'let'
const scope3 = 'const'
function show (){
console .log (scope)
console .log (scope2)
console .log (scope3)
}
show () //var let const
//如果是使用var來定義變數,x並不是在函式中定義的,所以會變為"全域變數"
//因此當if內的花括號已執行,x會傳出
if ( true ) {
var x = 5
}
console .log (x) //5
//函數花括號內的y不會傳出(只進不出)
function test (){
var y = 5
}
console .log (y) //y is not defined
對比使用let來宣告變數,let y位於區塊{}中,無法在外部環境獲取
Copy if ( true ) {
let y = 5
}
console .log (y) //y is not defined
靜態(Static)成員
Copy let Util = function (){}
Util .version = '1.0.0'
Util . getFunction = function (){}
不只是函數可用靜態方式來擴充成員。函式在實體化後,也可擴充成員。
提升(Hoisting)
ES6函數中的變數/常數、函式、類別都有提升(Hoisting)的特性。
非順序執行到時才影響函數內的值,與一般程式不同。
Copy const x = 1
function test () {
console .log (x)
//會提升到最上方,使x=2
x = 2
}
test () //x=2
CPS風格(確保依序執行,如CALLBACK)
延續傳遞風格(Continuation-passing style, CPS)
JavaScript中會大量使用CPS風格,主因是只有單執行緒。
優點:CPS用的是明確地移轉控制權到下一個函式中,也就是使用"延續函式"的方式
缺點:CPS風格在愈複雜的應用情況時,程式碼愈不易撰寫與組織,維護性與閱讀性也很低
Copy //CPS風格
function func (x , cb) {
cb (x)
}
回調(callback)
使用匿名函式的語法,直接寫在函式的傳入參數中。
回調(callback)提供了一種彈性的機制,讓程式開發者可以自行定義在此函式的最後完成時,要如何進行下一步。
Copy function showMessage (greeting , name , callback) {
console .log ( 'you call showMessage' )
callback (greeting , name)
}
showMessage ( 'Hello!' , 'Eddy' , function (param1 , param2) {
console .log (param1 + ' ' + param2)
})
異步回調函式(不阻塞主線程式執行)
同步是由上往下依順序執行,一個執行程序完成後才會再接著下一個,一般的程式語言都是按照這樣的流程來執行。但遇到執行的動作需要等候時,就產生阻塞的問題。
異步是為了解決阻塞,而把需等候的動作放入queue中,等候執行。最常使用的例子是用setTimeout這個內建的方法。
Callbacks(回調),常常用來呼叫異步回調函式
異步程序(函式)間沒辦法保証執行的時間順序,但原則是先進先出
事件大多是異步程序。例:在JavaScript中有一個不斷偵測事件發生的事件迴圈(Event Loop),所有在網頁上的DOM元素註冊的事件,都會進入一個佇列(queue)中,等待被觸發事件
Copy function aFunc (value , callback){
callback (value)
}
function bFunc (value , callback){
setTimeout (callback , 0 , value)
}
function cb1 (value){ console .log (value) }
function cb2 (value){ console .log (value) }
function cb3 (value){ console .log (value) }
function cb4 (value){ console .log (value) }
aFunc ( 1 , cb1)
bFunc ( 2 , cb2)
aFunc ( 3 , cb3)
bFunc ( 4 , cb4)
//最後的執行結果是1 -> 3 -> 2 -> 4
所有的同步回調函式都執行完成了,才會開始依順序執行異步的回調函式。
bFunc中使用了計時器setTimeout會把傳入的回調函式進行異步執行,也就是先移到工作佇列中,等執行主執行緒的呼叫堆疊空了,在某個時間回到主執行緒再執行。所以即使它的時間設定為0秒,裡面的回調函式並不是立即執行,而是會暫緩(延時)執行的一種回調函式,一般稱為異步回調函式。
異步回調函式還有另一個名稱是延時回調(defer callback)
閉包 closure
閉包的最大特點(賣點)就是它會記憶函式建立時的環境,取得function建立當下的預設值。
在閉包中所記憶的變數與值,通常稱為自由(free)變數或獨立(independent)變數,這些變數是在函式中使用,但被封入作用域之中。
Copy function aFunc (x){
return function (){
console .log ( x ++ )
}
}
const newFunc = aFunc ( 1 )
newFunc () //1
//x值會在aFunc呼叫後會持續保留在新的newFunc函式裡
newFunc () //2
this的分界
Copy // 函數未包在物件內時,this 指向的是 window
var bar = function () {
console .log ( this .a ); // undefined
};
var foo = function () {
var a = 123 ;
this .bar ();
};
foo (); // undefined
解決方式是要利用作用域鏈(Scope Chain)的設計,也就是說,雖然inner函式與外面的outter分屬不同函式,但inner函式具有存取得到outter函式的作用域的能力,所以可以用這樣的解決方法:
Copy const obj = {a : 1 }
function outter () {
// this 轉為 that 後可存在這一層,代表 obj
const that = this
function inner (){
console .log (that) //用作用域鏈讀取outter中的that值
}
inner ()
}
outter .call (obj) // Object {a: 1}
this (call , apply , bind)
JavaScript語言中因為在設計上並不是以類別為基礎的物件導向,所以this的指向的是目前呼叫函式或方法的擁有者(owner)物件 ,也就是說它與函式如何被呼叫或調用有關,雖然是同一函式的呼叫,因為不同的物件呼叫,也有可能是不同的this值。
call(obj) 或 bind(obj) 內的 obj 是 this 指向的物件,不論在 func 或 obj 內的 this 皆指向相同的 obj。也就是 this 的指向改為後方帶入的obj 參數。被戲稱為認賊作父。
call 和 apply
兩者皆可動態改變this的指向,區別在於call()第二個參數後放值,apply第二個參數後放陣列 。(第一個參數都放物件,this會改為指向此物件)
Copy //語法
func .call (obj , arg1 , arg2);
func .apply (obj , [arg1 , arg2])
//call範例
const apple = {
color : "red" ,
say () {
console .log ( "My color is " + this .color);
}
}
apple .say ();
//My color is red
banana = {
color : "yellow"
}
apple . say .call (banana);
//My color is yellow
//apply範例
function sum (a , b , c) {
return a + b + c;
}
var args = [ 1 , 2 , 3 ];
sum .apply ( undefined , args) ; // 6
傳入的第一個參數為this,之後傳入的參數,會依原參數的順序傳入。
Copy function funcA (param1 , param2){
console .log ( this , param1 , param2)
}
const objB = { a : 1 , b : 2 }
funcA () //undefined undefined undefined
const funcB = funcA .bind (objB , objB .a)
funcB () //Object {a: 1, b: 2} 1 undefined
funcB ( objB .b) //Object {a: 1, b: 2} 1 2
匿名函式與IIFE
匿名函式實現只執行一次的函式,也就是IIFE結構。
IIFE在執行環境一讀取到時,就會立即執行,而不像一般的函式需要呼叫才會執行
Copy //兩種寫法
( function () { … })()
( function () { … }())
//會鎖住函式裡面的變數值的閉包,這個樣式通常會用來模擬靜態變數。
//例1
const counter = ( function () {
let i = 1
//回傳此函數執行的結果
return function () {
console .log (i ++ )
}
}())
counter () //1
counter () //2
//例2
var feature = ( function () {
// Private variables and functions
var privateThing = "secret" ;
var publicThing = "not secret" ;
var changePrivateThing = function () {
privateThing = "super secret" ;
};
var sayPrivateThing = function () {
console .log ( privateThing );
changePrivateThing ();
};
// Public API
return {
publicThing : publicThing ,
sayPrivateThing : sayPrivateThing
};
})();
feature .publicThing; // "not secret"
箭頭函數的限制
this 指向的是 callback 函數,而非物件,所以箭頭函式可當成無法用 this。
Copy const calculate = {
array : [ 1 , 2 , 3 ] ,
sum : () => {
return this . array .reduce ((result , item) => result + item)
}
}
//TypeError: Cannot read property 'array' of undefined
calculate .sum ()
箭頭函式無法用於建構式(constructor),使用new會產生錯誤。
Copy const Message = (text) => {
this .text = text;
}
// Throws "TypeError: Message is not a constructor"
const helloMessage = new Message ( 'Hello World!' );
Copy //無花括號,自動return
const sum = (a , b) => a + b
//一個傳入參數可無括號
const func = x => x + 1