JavaScript—深入理解函數

當程式在調用某個函數時,做了以下的工作:準備執行環境,初始函數作用域鏈和arguments參數對象。

函數概述

函數的聲明語句

function命令聲明的程式碼區塊,就是一個函數。function命令後面是函數名,函數名後面是一對圓括弧,裡面是傳 入函數的參數。函數體放在大括弧裡面。

function hello(a){
 	console.log(a);
}
// 調用:
hello('hello world');
函數表達式

採用變數賦值的寫法:將一個匿名函數賦值給變數。這時,這個匿名函數又稱函數表達式。

var hello = function(a){
	console.log(a);
}
// 將一個具名函數賦值給變數。
var hello = function fn(a){
	console.log(a);
	console.log(fn); // fn();
	console.log(fn === hello); // true;
}
console.log(fn); // ReferenceError: fn is not defined;
// 說明,具名函數fn和hello是同一個函數,但是作用範圍不一致,fn只能在函數體內使用,相當於函數的一個局部變數,hello可在函數內部,外部調用。
Function構造函數
var add = new Function(
 'x',
 'y',
 'return x + y'
);
//等同於
function add(x,y){
 return x + y;
}
// 可以傳遞任意數量的參數給Function構造函數,只有最後一個參數會被當做函數體,如果只有一個參數,該參數就是函數體。
// Function構造函數可以不使用new命令,返回結果完全一樣。
函數的返回值return

return只能出現在函數體內。
​一個函數中可以有多個return語句。
​return;(表達式的值為undefined)代表直接提出函數執行,return之後除了在finally{}中的程式碼,都不會再執行。
​return 可以返回任何數據類型的數據。
​如果函數調用時在前面加上了new前綴,且返回值不是一個對象或者沒有返回值,則返回this(該新對象)

函數調用

函數調用模式

function add(x,y){
 return x+y;
}
var sum = add(3,4);
console.log(sum)//7
// 使用函數調用模式調用函數時,非嚴格模式下,this被綁定到全局對象;在嚴格模式(use strict;)下,this是undefined

​ 方法調用模式

​ 當一個函數被保存在對象的一個屬性時,我們稱它為一個方法。當一個方法被調用時,this被綁定到該對象。如果 調用表達式包含一個提取屬性的動作,那麼它就是被當做一個方法來調用。

var p = {
	a: 1,
	fn: function(){
		this.a = 2;
	}
}
console.log(p.a);// 1
p.fn();
console.log(p.a);// 2

​ 構造器調用模式

​ 如果函數或者方法調用之前帶有關鍵字new,它就構成構造函數調用。
如果構造函數調用在圓括弧內包含一組實參列表,先計算這些實參表達式,然後傳入函數內。
如果構造函數沒有形參,javascript構造函數調用的語法是允許省略實參列表和圓括弧的。凡是沒有形參的構造函數調 用都可以省略圓括弧。(var o = new Object() 等價於 var o = new Object)

​ 間接調用模式

​ javascript中函數也是對象,函數對象也可以包含方法。call()和apply()方法可以用來間接地調用函數
​ 這兩個方法都允許顯式指定調用所需的this值,也就是說,任何函數可以作為任何對象的方法來調用,哪怕這個函數不 是那個對象的方法。兩個方法都可以指定調用的實參。call()方法使用它自有的實參列表作為函數的實參,apply()方法則 要求以數組的形式傳入參數。

var obj = {};
function sum(x,y){
	return x+y;
}
console.log(sum.call(obj,1,2));//3
console.log(sum.apply(obj,[1,2]));//3

函數參數

JS是弱類型語言,函數定義時未指定函數形參的類型,函數調用也未對傳入的實參值做任何類型檢查。實際上, javascript函數調用甚至不檢查傳入形參的個數。

參數個數

1.當實參(函數被調用時摻入的實際參數值)比函數聲明指定的形參(函數定義時的參數列表)個數要少,剩下的形參都將設置為undefined值。

function add(x,y){
 return x + y; // x:1, y: undefined
}
add(1);

2.當實參比形參個數要多時,剩下的實參沒有辦法直接獲得,需要使用arguments對象來獲取。

function add(x,y){
 console.log(arguments);
 console.log(arguments.length);
 return x + y;
}
add(1,2,3,4,5);
// 控制台:
// Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// arguments是個類數組,有數組的部分屬性,如length,可以通過索引去獲取對應的參數列表。

3.函數定義時也可以不給形參,到時直接通過arguments[索引]去獲取實參。

同名形參

在非嚴格模式下,函數中可以出現同名形參,且只能訪問最後出現的該名稱的形參。

function add (x,x,x){
	return x;
}
console.log(add(1,2,3));// 3
// 嚴格模式編譯報錯。
函數重載

在java語言中,函數的重載是這樣定義的:方法名相同,參數的個數或者類型必須不同。
javascript函數不能像Java上那樣實現重載。只能通過檢查傳入函數中參數的類型和數量並作出不同的反應,來模仿方法的重載。

function doAdd(){
  if(arguments.length == 1){
    alert(arguments[0] + 10);
  }else if(arguments.length == 2){
    alert(arguments[0] + arguments[1]);
  }
}
參數傳遞

值傳遞:對於基本數據類型的參數傳遞。比如String,Number,Boolean等。在向參數傳遞基本類型的值時,被傳遞的值會被複制到一個局部變數(命名參數或arguments對象的一個元素)。

function addTen(num){
  num += 10;
  return num;
}
var count = 20;
var result = addTen(count);
console.log(count);//20,沒有變化
console.log(result);//30

引用傳遞:參數為引用類型的數據時(Object, Array),傳遞過去的是引用數據的記憶體地址。會把這個地址複製給一個局部變數,因此這個局部變數的變化會直接改變指向該記憶體地址的引用數據。

function setName(obj){
  //obj 在函數內部是一個局部變數
  obj.name = 'test';
}
var person = new Object();
setName(person);
console.log(person.name);//'test'

函數屬性和方法

函數是javascript中特殊的對象,可以擁有屬性和方法,就像普通的對象擁有屬性和方法一樣。甚至可以用Function()構造函數來創建新的函數對象。

屬性

length屬性:arguments對象的length屬性表示實參個數,而參數的length屬性則表示形參個數。

prototype屬性:每一個函數都有一個prototype屬性,這個屬性指向了一個對象的引用,這個對象叫做原型對象(prototype object)。每一個函數都包含不同的原型對象。將函數用作構造函數時,新創建的對象會從原型對象上繼承屬性。

function fn(){};
var obj = new fn;
fn.prototype.a = 1;
console.log(obj.a);//1

name屬性:函數定義了一個非標準的name屬性,通過這個屬性可以訪問到給定函數指定的名字,這個屬性的值永遠等於跟在function關鍵字後面的標識符,匿名函數的name屬性為空。

方法

每一個函數都包含兩個非繼承而來的方法:apply()和call()方法。這兩個方法的用途都是在特定的作用域中調用函數。

call()&apply() 要想以對象o的方法來調用函數f(),可以使用call()和apply()。

f.call(o);
f.apply(o);
// 比如:
window.color = "red";
var o = {color: "blue"};
function sayColor(){
  console.log(this.color);
}
sayColor();      //red
sayColor.call(this);  //red
sayColor.call(window); //red
sayColor.call(o);   //blue
sayColor.call(o)等價於:
o.sayColor = sayColor;
o.sayColor();  //blue
delete o.sayColor;
// 調用方式:
func.apply(作用域對象, []);
func.call(作用域對象, a,b,c);
// 在非嚴格模式下,使用函數的call()或apply()方法時,null或undefined值會被轉換為全局對象。而在嚴格模式下,函數的this值始終是指定的值

應用:

找出數組中最大元素。

var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a);//15

將類數組轉成真正的數組。

var add = function(x,y){
	console.log(Array.prototype.slice.apply(arguments));
};
add(1,2);
控制台:
(2) [1, 2]

將一個數組的值push到另一個數組中。

var a = [];
Array.prototype.push.apply(a,[1,2,3]);
console.log(a);//[1,2,3]
Array.prototype.push.apply(a,[2,3,4]);
console.log(a);//[1,2,3,2,3,4]

bind()

bind()是es5新增的方法,這個方法的主要作用就是將函數綁定到某個對象。
當在函數f()上調用bind()方法並傳入一個對象o作為參數,這個方法將返回一個新的函數。以函數調用的方式調用新的函數將會把原始的函數f()當做o的方法來調用,傳入新函數的任何實參都講傳入原始函數。
 bind()方法不僅是將函數綁定到一個對象,它還附帶一些其他應用:除了第一個實參之外,傳入bind()的實參也會綁定到this,這個附帶的應用是一種常見的函數式編程技術,有時也被稱為』柯里化』(currying)。

function  getConfig(colors,size,otherOptions){
  console.log(colors,size,otherOptions);
}
var defaultConfig = getConfig.bind(null,'#c00','1024*768');
defaultConfig('123');//'#c00 1024*768 123'
defaultConfig('456');//'#c00 1024*768 456'