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