本文是 重溫基礎 系列文章的第四篇。
今日感受:常懷感恩之心,對人對己。
系列目錄:
本章節複習的是JS中的基礎組件之一,函數,用來複用特定執行邏輯。
1.定義函數
定義函數有兩種方法:函數聲明 和 函數表達式 :
1.1 函數聲明
也成爲函數聲明,通常格式爲:
function f (a){
return a + 1;
}
解釋:這裏聲明一個函數 f
,並傳入一個參數 a
,當函數執行以後,通過 return
關鍵字返回了 a+1
的值。
參數:
當傳入的參數是一個數字/字符串等具體的值的時候,若參數的值被改變,不會影響到全局或調用函數。
但如果參數是個對象,若函數內改變的這個參數的屬性,則函數外部的這個參數原始的值會被修改。
var leo = {
age:20
}
function f(obj){
obj.age = 15;
obj.name = 'leo';
}
f(leo);
console.log(leo); //{age: 15, name: "leo"}
1.2函數表達式
通過定義一個匿名的函數,來賦值給一個變量,通過這個變量來調用這個函數。
var f = function (a){
return a + 1;
}
但是函數表達式也可以提供函數名,用於函數內部調用,並指代本身,也可以作爲調試器堆棧跟蹤中識別該函數。
var f = function g(a){
return n < 2 ? 1 : a*g(a-1);
}
另外,函數表達式聲明可以用來根據不同條件,來定義一個函數:
var f;
if(a == 1){
f = function (){
return 'when a == 1';
}
}else {
f = function (){
return 'when a != 1';
}
}
2.函數調用
函數定義完成後不會自動執行,需要我們通過函數名稱來調用,才能真正執行:
var f = function (){
console.log('ok');
}
f(); // 'ok'
另外,函數也可以調用自身,這就是遞歸過程:
function f (n){
if( n==0 || n==1) {
return 1;
}else {
return n * f(n-1);
}
}
// 三目運算
function f (n){
return (n==0 || n==1)?1: n*f(n-1);
}
3.函數作用域
由於函數只在函數的內部有定義,所以函數內部定義的變量在函數外部不能訪問,函數內部就是這個函數的作用域。
當一個父級函數內,還定義了一個子級函數,則這個子級函數可以訪問父級函數定義的變量。
// 全局作用域 global scope
var a = 1, b = 2;
function f (){
return a + b;
}
f(); // 3
function g(){
var a1 = 'leo', b1 = 'pingan';
function hi (){
return a1 + '和' + b1
}
return hi();
}
g(); // 'leo和pingan'
3.1 閉包
閉包是 JavaScript 中最強大的特性之一,並且JS允許函數嵌套。
在一個函數內部在嵌套一個函數,而嵌套的這個函數對外面的函數是私有的,則形成一個閉包,閉包是一個可以自己擁有獨立的環境和變量的表達式,通常是函數。
理解一下,前面說的內部函數可以調用外部函數的變量和方法,那麼可以這麼理解:閉包的函數繼承了父級容器函數的參數和變量,即內部函數包含外部函數的作用域。
總結一下:
- 內部函數只能在外部函數中訪問;
- 內部函數形成閉包:可以訪問外部函數的參數和變量,但外部函數卻不能使用這個內部函數的參數和變量;
function f(a) {
function g(b){
return a + b;
}
return g;
}
var a1 = f(5); // ƒ g(b){ return a + b; }
var a2 = a1(6); // 11
var a3 = f(5)(6); // 11
閉包可以給內部函數的變量提供一定的安全保障。
另外,閉包還有複雜的用法:
var f = function (name){
var age ;
return {
setName : function (newName){
name = newName;
},
getName : function (){
return name;
},
getAge : function (){
return age;
},
setAge : function (newAge){
age = newAge;
}
}
}
var leo = f('leo');
leo.setName('pingan');
leo.setAge(20);
leo.getName(); // 'pingan'
leo.getAge(); // 20
3.2命名衝突
在同一個閉包作用域下若參數或變量名相同,產生衝突,則優先使用作用域最近:
function f(){
var a = 1;
function g(a){
return a + 1;
}
return g;
}
f()(3); // 4
4.arguments對象
函數的實際參數會被保存在一個類數組對象 arguments
對象中,通過索引訪問具體的參數:
var a = arguments[i]
arguments
的索引從0開始,也有arguments.length
屬性獲取長度。
當我們不知道參數的數量的時候,可以使用arguments.length
來獲取實際傳入參數的數量,再用arguments
對象來獲取每個參數。
例如:
// 拼接所有參數爲一個字符串
// 參數 s 爲分隔符
function f( s ){
var text = '';
for(var i = 0;i<= arguments.length; i++){
text += arguments[i] + s ;
}
return text;
}
f('--','leo','pingan','robin');
// "----leo--pingan--robin--undefined--"
f('**','leo','pingan','robin');
// "****leo**pingan**robin**undefined**"
5.函數參數
ES6開始,新增兩個類型的參數:默認參數和剩餘參數:
5.1默認參數
若函數沒有傳入參數,則參數默認值爲undefined
,通常設置參數默認值是這樣做的:
// 沒有設置默認值
function f(a, b){
b = b ? b : 1;
return a * b;
}
f(2,3); // 6
f(2); // 2
// 設置默認值
function f(a, b = 1){
return a * b;
}
f(2,3); // 6
f(2); // 2
5.2剩餘參數
可以將參數中不確定數量的參數表示成數組,如下:
function f (a, ...b){
console.log(a, b);
}
f(1,2,3,4); // a => 1 b => [2, 3, 4]
6.箭頭函數
函數箭頭表達式是ES6新增的函數表達式的語法,也叫胖箭頭函數,變化:更簡潔的函數和this
。
- 更簡潔的函數
// 有1個參數
let f = v => v;
// 等同於
let f = function (v){return v};
// 有多個參數
let f = (v, i) => {return v + i};
// 等同於
let f = function (v, i){return v + i};
// 沒參數
let f = () => 1;
// 等同於
let f = function (){return 1};
let arr = [1,2,3,4];
arr.map(ele => ele + 1); // [2, 3, 4, 5]
- this
注意這幾點:- 箭頭函數內的
this
總是指向定義時所在的對象,而不是調用時。 - 箭頭函數不能當做構造函數,即不能用
new
命令,否則報錯。 - 箭頭函數不存在
arguments
對象,即不能使用,可以使用rest
參數代替。 - 箭頭函數不能使用
yield
命令,即不能用作Generator函數。
- 箭頭函數內的
一個簡單的例子:
function Person(){
this.age = 0;
setInterval(() => {
this.age++;
}, 1000);
}
var p = new Person(); // 定時器一直在執行 p的值一直變化
參考資料
本部分內容到這結束
Author | 王平安 |
---|---|
[email protected] | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787/Leo_Reading/issues |
ES小冊 | es.pingan8787.com |