菜鳥筆記--函數基礎


一 定義方法

1. 靜態方法(函數聲明表達式)

function 函數名([虛參列表]){
函數體;
[return[函數返回值]]
}
//這是最典型的函數聲明,以關鍵字function開始,其後跟隨函數名稱標識符、一對圓括號(包含由0個或多個逗號隔開的參數名稱)和一對花括號(包含0條或多條JS語句,構成函數體)。這種函數定義方式須要顯式的指定函數名稱,在代碼執行前就被解釋器加載到做用域中,這個特性可讓咱們在函數定義以前就調用該函數。咱們能夠經過代碼來驗證這一點。

javascript

console.log(sum); //控制檯輸出sum函數的源代碼,此時函數還未定義
function sum(a,b){
return a+b;
}
console.log(sum(2,3));//5


函數做用域:前端

既然提到函數聲明,就要提到函數的做用域。函數做用域是指在函數內聲明的全部變量在函數體內始終是可見的,這意味着,變量在聲明以前已經可用。這個特性能夠被稱爲聲明提早,即在函數體內聲明的全部變量,在聲明以前已經有定義,但只有在執行到這個變量時纔會被真正賦值。從以代碼能夠清晰地看到這一點java

var scope = "global";
function f(){
console.log(scope); //輸出「undefined」,而不是「global」
var scope = "local"; //變量在這裏賦初始值,但變量自己在函數體內任何地方均是有定義的
console.log(scope); //輸出「local」
}
f();

等價於編程

var scope = "global";
function f() {
var scope; //在函數頂部聲明瞭局部變量,即聲明提早
console.log(scope); //變量存在,輸出「undefined」,而不是「global」
var scope = "local"; //變量在這裏賦初始值
console.log(scope); //輸出「local」
}
f();

 

2. 動態匿名方法(構造函數)

var 函數名 = new function(["虛參列表"],「函數體」);

var f = new Function("x","y","return x+y"); //Function()構造函數
var f = function(x,y){return x+y}; //這兩條代碼是等價的

Function()構造函數能夠傳入任意數量的字符串實參,最後一個實參所表示的文本是函數體,能夠包含任意數量的JavaScript語句。若是構造的函數不包含任何參數,則只需傳入一個函數體便可。與前二者方式不一樣的是,Function()構造函數容許JavaScript在運行時動態地建立並翻譯函數。每次調用Function()構造函數都會解析函數體,並建立新的函數對象。於是,在循環或屢次調用的函數中執行這個構造函數,執行效率會受影響。相比之下,循環中的嵌套函數和函數定義表達式則不會每次執行時都從新編譯。

Function()構造函數還有值得注意的一點就是它所建立的函數並非使用詞法做用域,函數體代碼的編譯總在頂層函數執行。以下代碼所示:數組

var a = 3; //在頂層函數中聲明變量a
function f(){
var a = 2; //在函數體內聲明局部變量a
return new Function("return a*a;"); //沒法捕獲局部做用域
}
console.log(f()()); //控制檯輸出9而非4,說明構造函數的編譯在頂層函數執行

//咱們能夠將Function()構造函數認爲是在全局做用域中執行的eval()。在實際編程中,Function()構造函數不多用到,前兩中定義方法使用比較廣泛。

 

3. 直接量方法

函數名 = function([虛參列表]){函數體;}

與函數定義語句同樣,函數直接量表達式也是用到了關鍵字function。通常這種定義方式適用於將它做爲一個大的表達式的一部分,好比在賦值和調用過程當中定義函數。經過函數直接量生成的函數,函數名稱能夠省略,此時就是一個匿名函數。以下例所示:這樣可使代碼更爲緊湊。函數定義表達式特別適合用來定義那些只會用到一次的函數。app

var f=function(x){ //省略函數名的匿名函數
return x*x;
}

等價於函數

var f;
f=function(x){ //省略函數名的匿名函數
return x*x;
}

//與函數聲明表達式不一樣的是,函數直接量表達式是在執行到代碼時才加載函數的,咱們能夠用下面的代碼來講明。this

console.log(f); //控制檯輸出undefined,此時函數f還未加載
var f=function(x){ //開始加載函數
return x*x;
}
console.log(f); //控制檯輸出函數的源代碼

 

二 調用方法

1. 直接調用 函數名(實參列表)

2. 在連接中調用 //<a href="javascript:函數名()"></a>

3. 在事件中調用 事件類型 = "函數名()"

4. 遞歸調用

(1) 定義 -- 在函數總體內部調用函數自身

(2) 格式

function 函數名(){
代碼
函數名();
}


三 方法

1. apply -- 將函數做爲對象的方法來調用
將參數以數組形式傳遞給該方法

spa

foo.apply(obj, [參數1,參數2,參數3.....])
//存在上下文調用的目的就是爲了實現方法借用,且不會污染對象。

 

(1) 若是須要讓函數以函數的形式調用, 可使用

foo.apply( null ); // 上下文爲 window
--------------------------------------.net

function foo () {
console.log( this );
}
// 若是須要讓函數以函數的形式調用, 可使用
foo.apply( null ); // this => window // 或 foo.apply()

(2) 若是但願他是方法調用模式, 注意須要提供一個宿主對象
foo.apply( obj ); // 上下文 爲 傳的 obj 對象

var o = { name: 'jim' };
// 若是但願他是方法調用模式, 注意須要提供一個宿主對象
foo.apply( o ) // this => o 對象


Note:
使用 apply 進行調用, 若是函數是帶有參數的. apply 的第一個參數要麼是 null 要麼是對象
若是是 null 就是函數調用
若是是 對象就是 方法調用, 該對象就是宿主對象, 後面緊跟一個數組參數, 將函數全部的參數依次放在數組中.

For example:

函數調用:

func( '張三', 19, '男' ),
將其修改爲 apply 模式:
func.apply( null, [ '張三', 19, '男'] )//以 window 爲上下文執行 apply

方法模式:

o.func( 123, 567 )
apply
var o = { name: 'jim' };
foo.apply( o, [ 123, 567 ] ); //以 o 爲上下文執行 apply

詳情例子可參考博文 https://blog.csdn.net/qq_16415157/article/details/53033953


2. call -- 將函數做爲對象的方法來調用
將指定參數傳遞給該方法

函數調用: 函數名.call( null, 參數1,參數2,參數3… )
方法調用: 函數名.call( obj, 參數1,參數2, 參數3… )

不傳參時,apply 和 call 徹底同樣

function Person ( name, age, gender ) {
this.name = name;
this.age = age;
this.gender = gender;
}

function Student ( name, age, gender, course ) {
// 原型鏈結構不會變化,同時實現了繼承的效果
Person.call( this, name, age, gender );// 借用Person構造方法
this.course = course;
}

var p = new Student ( 'jim', 19, 'male', '前端' );
console.log( p );

3. bind 方法 (ES 5)讓函數綁定對象的一種用法

函數自己就是能夠調用, 可是其若是想要做爲方法調用, 就必須傳入宿主對象, 而且使用 call 或 apply 形式

可是 bind 使得個人函數能夠與某一個對象綁定起來, 那麼在調用函數的時候, 就好像是該對象在調用方法,就能夠直接傳參,而不須要傳宿主對象。

var write = document.write;
write("hello");//不能正確執行,由於write函數丟掉了上下文,此時this的指向global或window對象,致使執行時提示非法調用異常,因此咱們須要改變this的指向

正確的方案就是使用 bind/call/apply來改變this指向

bind方法
var write = document.write;
write.bind(document)('hello');

call方法
var write = document.write;
write.call(document,'hello');

apply方法
var write = document.write;
write.apply(document,['hello']);

4. toString -- 返回函數的字符串表示

function foo(){
return 1;
}

foo.toString()
//"function foo(){return 1;}"


四 arguments對象

1. 功能 -- 存放實參的參數列表
這個函數體內的arguments很是特殊,其實是所在函數的一個內置類數組對象,能夠用數組的[i]和.length。

js語法不支持重載!但可用arguments對象模擬重載效果。
重載的定義是指函數的方法名相同,但參數不一樣。

例如:

function add(a,b){
console.log(a + b);
}
add(1,2);

function add(c,d,e){
console.log(c + d + e);
}
add(4,5,6);

//輸出結果爲NaN 9,這就說明後面的函數把前一個同名函數覆蓋掉了,從而能夠得出js函數不存在重載,永遠調用最後一個方法。

Js 函數自己是不存在重載功能的,可是咱們能夠利用Arguments對象來進行模擬重載。Javascrip中每一個函數都會有一個Arguments對象實例arguments,它引用着函數的實參,能夠用數組下標的方式"[ ]"引用arguments的元素。
以下例子:

function cale(){
//傳入一個參數,求平方
if(arguments.length==1){
console.log(arguments[0]*arguments[0])
}else if(arguments.length == 2){
//傳入兩個參數就求和
console.log(arguments[0]+arguments[1])
}
}
cale(7);
cale(9,5)
//不管傳入幾個參數均可以求和
function add(){
//arguments:[]
//遍歷每一個元素並累加
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
console.log(add(5,6,7))

 

2. 特性

(1) 僅能在函數體內使用
函數對象內,自動建立的專門接收全部參數值的類數組對象。
arguments對象和Function是分不開的。由於arguments這個對象不能顯式建立,arguments對象只有函數開始時纔可用。

(2) 帶有下標屬性,但並不是數組
arguments[i]
arguments.length

雖然arguments對象並非一個數組,可是訪問單個參數的方式與訪問數組元素的方式相同

例如:
arguments[0],arguments[1],...arguments[n],

(3) 函數聲明時自動初始化
在js中 不須要明確指出參數名,就能訪問它們

例如:

function test(){
var s = "anna" + ",";
for (var i = 0; i < arguments.length; i++) {
s += arguments[i] + ","
}
return s;
}
console.log(test("name", "age"))//anna,name,age

 

3. 屬性

(1) length--獲取函數實參的長度

(2) callee--返回當前正在指向的函數

callee 屬性是 arguments 對象的一個成員,僅當相關函數正在執行時纔可用。
callee 屬性的初始值就是正被執行的 Function 對象,這容許匿名的遞歸函數。

例如:

var sum=function(n){

if(1==n) {

   return 1;

} else {

  return n + arguments.callee(n-1);

  }

}
alert(sum(6));
//通俗一點就是,arguments此對象大多用來針對同個方法多處調用而且傳遞參數個數不同時進行使用。根據arguments的索引來判斷執行的方法。

(3) caler--返回調用當前正在執行函數的函數名

五 函數參數

1. 參數類型

(1) 形參 -- 定義函數時使用的參數
         -- 接收調用該參數時傳遞的參數

(2) 實參 -- 調用函數時傳遞給函數的實際參數

2. 特性

(1) 參數個數沒有限制

實參<形參 -- 多餘形參=undefined

形參>實參 -- 多餘實參被忽略

(2) 參數的數據類型沒有限制
經過arguments對象訪問參數數組

(3) 參數始終按值傳遞

值傳參針對基本類型,引用傳參針對引用類型,傳參能夠理解爲複製變量值。基本類型複製後倆個變量徹底獨立,以後任何一方改變都不會影響另外一方;引用類型複製的是引用(即指針),以後的任何一方改變都會映射到另外一方。

咱們能夠把ECMAScript函數的參數想象成局部變量。在向參數傳遞基本類型的值時,被傳遞的值被複制給一個局部變量(即命名參數,或者用ECMAScript的概念來講,就是arguments對象中的一個元素)。在向參數傳遞引用類型時,會把這個值在內存中的地址(指針)複製給一個局部變量,所以這個局部變量的變化會反映在函數的外部。

a. 基本類型 -- 傳值
//由於是按值傳遞的,傳遞完後倆個變量各不相干!

function add(num){

    num += 10;
    console.log(num)
}

var count  = 20;
add(count)//30
console.log(count)//20


b. 引用類型 -- 傳值(這麼叫便於理解,其實也是按值傳遞)

function setName(obj){
    obj.name = "anna"
}

var person = new Object();
setName(person)
console.log(person.name)//anna

// 以上代碼中建立一個對象,並將其保存在變量person中。而後,這個變量被傳遞到setName(obj)函數中以後就被複制給了obj。在這個函數內部,obj和person引用的是同一個對象。換句話說,即便ECMAScript說這個變量時按值傳遞的,但obj也會按引用來訪問同一個對象。因而,在函數內部爲obj添加name屬性後,函數外部的person也將有所反應;由於這時的person和obj指向同一個堆內存地址。因此,不少人錯誤的認爲:在局部做用域中修改的對象會在全局對象中反映出來,就說明參數是按引用傳遞的。

能夠參考下面例子, 證實對象也是按值傳遞的:

function changeName(obj){
    obj.name = "Nico";
    obj = new Object();//改變obj的指向,此時obj指向一個新的內存地址,再也不和person指向同一個
    obj.name = "Jack;"
}

var people = new Object();
changeName(people);
console.log(people.name)//Nico 證實對象也是按值傳遞的

//這個例子與前一個惟一的區別,就是setName()函數中添加了兩行代碼: obj = new Object(); 用來改變obj的指向; obj.name = "Jack"; 用來給新建立的obj添加屬性。若是是按引用傳遞的,那麼person就會自動被修改成指向新建立的obj的內存地址,則person的name屬性值被修改成"Greg"。可是,當訪問person.name時,顯示的結果爲"Nicholas"。這說明即便在函數內部修改了參數的值,但原始的引用仍然保持未變。實際上,當在函數內部重寫obj時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後被當即銷燬!

六 指針標識1. this -- 指向檔期那操做對象(將會在新博文中詳細解說)2. callee -- 指向參數集合所屬函數3. prototype -- 指向函數附帶的原型對象(將會在新博文中詳細解說)4. constructor -- 指向建立對象的構造函數(將會在新博文中詳細解說)

相關文章
相關標籤/搜索