JS函數學習(2)
- JavaScript的函數不但是“頭等公民”,而且可以像變量一樣使用,具有非常強大的抽象能力
-
函數體內部的語句在執行時,一旦執行到
return
時,函數就執行完畢,並將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。如果沒有
return
語句,函數執行完畢後也會返回結果,只是結果為undefined
。 - 定義(2)
var abs = function (x) { if (typeof x !== ‘number‘) { throw ‘Not a number‘; } if (x >= 0) { return x; } else { return -x; } }; //可以通過變量abs就可以調用該函數。 abs(10); // 返回10 abs(-9); // 返回9
-
arguments
它只在函數內部起作用,並且永遠指向當前函數的調用者傳入的所有參數。arguments
類似Array
但它不是一個Array。即使函數不定義任何參數,還是可以拿到參數的值:
function abs() { if (arguments.length === 0) { return 0; } var x = arguments[0]; return x >= 0 ? x : -x; } abs(); // 0 abs(10); // 10 abs(-9); // 9 //通常用於判斷參數個數 // 接收2~3個參數,b是可選參數,如果只傳2個參數,b默認為null: function foo(a, b, c) { if (arguments.length === 2) { // 實際拿到的參數是a和b,c為undefined c = b; // 把b賦給c b = null; // b變為默認值 } // ... }
-
rest參數(ES6)
ES6標準引入了rest參數,用於接收額外的rest
參數
function foo(a, b, ...rest) { console.log(‘a = ‘ + a); console.log(‘b = ‘ + b); console.log(rest); } foo(1, 2, 3, 4, 5); // 結果: // a = 1 // b = 2 // Array [ 3, 4, 5 ] foo(1); // 結果: // a = 1 // b = undefined // Array [] 如果傳入的參數連正常定義的參數都沒填滿,也不要緊,rest參數會接收一個空數組(註意不是undefined)。
- 變量作用域
如果兩個不同的函數各自申明了同一個變量,那麽該變量只在各自的函數體內起作用。換句話說,不同函數內部的同名變量互相獨立,
由於JavaScript的函數可以嵌套,此時,內部函數可以訪問外部函數定義的變量
如果內部函數定義了與外部函數重名的變量,則內部函數的變量將“屏蔽”外部函數的變量。
‘use strict‘; function foo() { var x = 1; function bar() { var x = ‘A‘; alert(‘x in bar() = ‘ + x); // ‘A‘ } alert(‘x in foo() = ‘ + x); // 1 bar(); } //這說明JavaScript的函數在查找變量時從自身函數定義開始,從“內”向“外”查找
-
變量提升
JavaScript的函數定義有個特點,它會先掃描整個函數體的語句,把所有申明的變量“提升”到函數頂部:
function foo() { var x = ‘Hello, ‘ + y; alert(x); var y = ‘Bob‘; } //JavaScript引擎看到的代碼相當於: function foo() { var y; // 提升變量y的申明 var x = ‘Hello, ‘ + y; alert(x); y = ‘Bob‘; } //由於JavaScript的這一怪異的“特性”,我們在函數內部定義變量時,請嚴格遵守“在函數內部首先申明所有變量”這一規則。最常見的做法是用一個var申明函數內部用到的所有變量: function foo() { var x = 1, // x初始化為1 y = x + 1, // y初始化為2 z, i; // z和i為undefined // 其他語句: for (i=0; i<100; i++) { ... } }
-
全局作用域
不在任何函數內定義的變量就具有全局作用域。實際上,JavaScript默認有一個全局對象window
,全局作用域的變量實際上被綁定到window
的一個屬性。因此,直接訪問全局變量course
和訪問window.course
是完全一樣的。實際上alert等函數也是window的一個變量。
-
名字空間
全局變量會綁定到window
上,不同的JavaScript文件如果使用了相同的全局變量,或者定義了相同名字的頂層函數,都會造成命名沖突,並且很難被發現。
減少沖突的一個方法是把自己的所有變量和函數全部綁定到一個全局變量中。例如:
// 唯一的全局變量MYAPP: var MYAPP = {}; // 其他變量: MYAPP.name = ‘myapp‘; MYAPP.version = 1.0; // 其他函數: MYAPP.foo = function () { return ‘foo‘; };
-
局部作用域let(ES6)
用let
替代var
可以申明一個塊級作用域的變量:
‘use strict‘; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } i += 1; // SyntaxError }
-
常量const(ES6)
ES6標準引入了新的關鍵字const
來定義常量,const
與let
都具有塊級作用域:
‘use strict‘; const PI = 3.14; PI = 3; // 某些瀏覽器不報錯,但是無效果! PI; // 3.14
- this
JavaScript的函數內部如果調用了this,那麽這個
this
到底指向誰?答案是,視情況而定!
由於這是一個巨大的設計錯誤,要想糾正可沒那麽簡單。ECMA決定,在strict模式下讓函數的this
指向undefined;這個決定只是讓錯誤及時暴露出來,並沒有解決
this
應該指向的正確位置。
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: ‘小明‘, birth: 1990, age: getAge }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // 25, 正常結果 getAge(); // NaN 如果單獨調用函數,比如getAge(),此時,該函數的this指向全局對象,也就是window。 如果這麽寫也是不行,要保證this指向正確,必須用obj.xxx()的形式調用!: var fn = xiaoming.age; // 先拿到xiaoming的age函數 fn(); // NaN
修復的辦法也不是沒有,我們用一個that
變量首先捕獲this
:
‘use strict‘; var xiaoming = { name: ‘小明‘, birth: 1990, age: function () { var that = this; // 在方法內部一開始就捕獲this function getAgeFromBirth() { var y = new Date().getFullYear(); return y - that.birth; // 用that而不是this } return getAgeFromBirth(); } }; xiaoming.age(); // 25
-
apply與call
要指定函數的this
指向哪個對象,可以用函數本身的apply
方法,它接收兩個參數,第一個參數就是需要綁定的this
變量,第二個參數是Array
,表示函數本身的參數。
用apply
修復getAge()
調用:
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: ‘小明‘, birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 參數為空
另一個與apply()
類似的方法是call()
,唯一區別是:
-
apply()
把參數打包成Array
再傳入; -
call()
把參數按順序傳入。
Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5 對普通函數調用,我們通常把this
綁定為null
。
-
裝飾器
JavaScript的所有對象都是動態的,即使內置的函數,我們也可以重新指向新的函數,利用apply()
,我們還可以動態改變函數的行為。
現在假定我們想統計一下代碼一共調用了多少次parseInt()
,可以把所有的調用都找出來,然後手動加上count += 1
,不過這樣做太傻了。最佳方案是用我們自己的函數替換掉默認的parseInt()
:
var count = 0; var oldParseInt = parseInt; // 保存原函數 window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 調用原函數 }; // 測試: parseInt(‘10‘); parseInt(‘20‘); parseInt(‘30‘); count; // 3
JS函數學習(2)