JavaScript 函數

1. 函數的定義和調用

函數的命名規範與變量相似,再也不贅述。數組

1.1. 函數定義

JavaScript 的函數能夠嵌套,也就是函數內部能夠再定義函數。瀏覽器

// 參數列表不是必須的,可空
function fun(a, b, c, str) {
    // do something
    return str; //沒有return,函數執行完也會返回結果,爲undefined。
}
// 第二種函數定義,匿名函數,配合事件使用
var num = function(x) {
    // do something
    return x;
};
// 在這種方式下,函數定義成匿名函數,無函數名。但函數賦值給了變量 abs,
// 因此經過變量 abs 就能夠調用該函數。
// 以上兩種定義徹底等價。

1.2. 函數調用

// 1.直接調用
function fun(a, b) {
    return a+b;
}
var result = fun(3, 5);

// 2.事件調用
<button onclick='fun(3, 5)'>點我調用方法</button>s

1.3. 自執行函數

自執行函數就是指無需手動調用,在聲明函數時自動調用的函數類型。app

// 聲明自執行函數的語法結構有三種
// 1.!function(形參列表){}(實參列表);
!function(num1, num2) {
    var sum = num1 + num2;
}(1, 2);

// 2.(function(形參列表){}(實參列表));
// 這種寫法使用圓括號將匿名函數及以後的圓括號包裹成爲一個總體。
(function(num1, num2) {
    var sum = num1 + num2;
}(1, 2));

// 3.(function(形參列表){})(實參列表);
(fuction(num1, num2) {
    var sum = num1 + num2;
})(1, 2);

1.4. arguments 參數

arguments 關鍵字只在函數內部起做用,而且永遠指向當前函數的調用者傳入的全部參數。arguments 相似 Array 但它不是一個 Arraydom

function foo(x) {
    console.log('x = ' + x); // 10
    for (var i=0; i<arguments.length; i++) {
        console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
    }
}
foo(10, 20, 30); //x = 10 arg 0 = 10 arg 1 = 20 arg 2 = 30

利用 arguments,你能夠得到調用者傳入的全部參數。也就是說,即便函數不定義任何參數,仍是能夠拿到參數的值:ide

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

實際上 arguments 最經常使用於判斷傳入參數的個數。函數

// foo(a[, b], c)
// 接收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變爲默認值
    }
    // ...
}

arguments 對象除了能夠保存實參列表外,還有一個重要的屬性 callee,用於返回 arguments 所在函數的引用。arguments.callee() 能夠調用自身函數執行,也被稱爲遞歸,所以 arguments.callee() 是遞歸調用。ui

// 在 strict 模式下 callee 不可用
var num = 1;
function func() {
    console.log(num);
    num++;
    if(num <= 5) { // 當 num<=5 時,函數遞歸調用自己
        arguments.callee(); // 表示調用函數自己,效果與 func() 相同
    }
}

要把中間的參數 b 變爲「可選」參數,就只能經過 arguments 判斷,而後從新調整參數並賦值。this

1.5. rest 參數

因爲 JavaScript 函數容許接收任意個參數,因而咱們就不得不用 arguments 來獲取全部參數:rest

function foo(a, b) {
    var i, rest = [];
    if (arguments.length > 2) {
        for (i = 2; i<arguments.length; i++) {
            rest.push(arguments[i]);
        }
    }
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

爲了獲取除了已定義參數 a、b 以外的參數,咱們不得不用 arguments,而且循環要從索引2開始以便排除前兩個參數,這種寫法很彆扭,只是爲了得到額外的 rest 參數。ES6 標準引入了 rest 參數,上面的函數能夠改寫爲:code

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 參數只能寫在最後,前面用 ... 標識,從運行結果可知,傳入的參數先綁定 a、b,多餘的參數以數組形式交給變量 rest,因此,再也不須要 arguments 咱們就獲取了所有參數。若是傳入的參數連正常定義的參數都沒填滿,也沒關係,rest 參數會接收一個空數組(注意不是 undefined)。

2. 變量做用域

在 JavaScript 中,變量只有函數做用域,也即函數內聲明的變量只有函數內部能用。

  • 在函數外,不管是否使用 var 聲明變量,都是全局變量,在整個 JavaScript 文件可用;
  • 在函數內,使用 var 聲明的變量爲局部變量,只在當前函數可用;
  • 在函數內,不用 var 聲明的變量依然爲全局變量,在整個 JavaScript 文件可用;
  • 在 JavaScript 中,函數內部可以使用全局變量,而函數外部不能使用局部變量。

JavaScript 默認有一個全局對象 window,全局做用域的變量實際上被綁定到 window 的一個屬性:

'use strict';

var course = 'Learn JavaScript';
alert(course);         // 'Learn JavaScript'
alert(window.course);  // 'Learn JavaScript'
// 所以,直接訪問全局變量 course 和訪問 window.course 是徹底同樣的。

全局變量會綁定到 window 上,不一樣的 JavaScript 文件若是使用了相同的全局變量,或者定義了相同名字的頂層函數,都會形成命名衝突,而且很難被發現。減小衝突的一個方法是把本身的全部變量和函數所有綁定到一個全局變量中。例如:

// 惟一的全局變量MYAPP:
var MYAPP = {};

// 其餘變量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其餘函數:
MYAPP.foo = function () {
    return 'foo';
};
// 把本身的代碼所有放入惟一的名字空間 MYAPP 中,會大大減小全局變量衝突的可能。

因爲 JavaScript 的變量做用域其實是函數內部,咱們在 for 循環等語句塊中是沒法定義具備局部做用域的變量的:

'use strict';
function foo() {
    for (var i=0; i<100; i++) {
    }
    i += 100; // 仍然能夠引用變量i
}

爲了解決塊級做用域,ES6 引入了新的關鍵字 let,用 let 替代 var 能夠申明一個塊級做用域的變量:

'use strict';
function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    i += 1;// SyntaxError:
}

ES6 標準引入了新的關鍵字 const 來定義常量,constlet 都具備塊級做用域:

'use strict';
const PI = 3.14;
PI = 3; // 某些瀏覽器不報錯,可是無效果!
PI;     // 3.14

解構賦值
從 ES6 開始,JavaScript 引入瞭解構賦值,能夠同時對一組變量進行賦值。

// 1.傳統作法:把一個數組的元素分別賦值給幾個變量
var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];

// 2.在 ES6 中,可使用解構賦值,直接對多個變量同時賦值
'use strict';
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x, y, z分別被賦值爲數組對應元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);

對數組元素進行解構賦值時,多個變量要用 [...] 括起來。

若是數組自己還有嵌套,也能夠經過下面的形式進行解構賦值,注意嵌套層次和位置要保持一致:

let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

解構賦值還能夠忽略某些元素:

let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前兩個元素,只對z賦值第三個元素
z; // 'ES6'

若是須要從一個對象中取出若干屬性,也可使用解構賦值,便於快速獲取對象的指定屬性:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, age, passport} = person;
// name, age, passport分別被賦值爲對應屬性:

對一個對象進行解構賦值時,一樣能夠直接對嵌套的對象屬性進行賦值,只要保證對應的層次是一致的:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name;     // '小明'
city;     // 'Beijing'
zip;      // undefined, 由於屬性名是zipcode而不是zip
// 注意: address不是變量,而是爲了讓city和zip得到嵌套的address對象的屬性:
address;  // Uncaught ReferenceError: address is not defined

使用解構賦值對對象屬性進行賦值時,若是對應的屬性不存在,變量將被賦值爲 undefined,這和引用一個不存在的屬性得到 undefined 是一致的。若是要使用的變量名和屬性名不一致,能夠用下面的語法獲取:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// 把passport屬性賦值給變量id:
let {name, passport:id} = person;
name;     // '小明'
id;       // 'G-12345678'
// 注意: passport不是變量,而是爲了讓變量id得到passport屬性:
passport; // Uncaught ReferenceError: passport is not defined

解構賦值還可使用默認值,這樣就避免了不存在的屬性返回 undefined 的問題:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678'
};
// 若是person對象沒有single屬性,默認賦值爲true:
var {name, single=true} = person;
name; // '小明'
single; // true

有些時候,若是變量已經被聲明瞭,再次賦值的時候,正確的寫法也會報語法錯誤:

// 聲明變量:
var x, y;
// 解構賦值:
{x, y} = { name: '小明', x: 100, y: 200};
// 語法錯誤: Uncaught SyntaxError: Unexpected token =
// 這是由於 JavaScript 引擎把 { 開頭的語句看成了塊處理,
// 因而 = 再也不合法。解決方法是用小括號括起來:
({x, y} = { name: '小明', x: 100, y: 200});

解構賦值在不少時候能夠大大簡化代碼。例如,交換兩個變量 xy 的值,能夠這麼寫,再也不須要臨時變量:

var x=1, y=2;
[x, y] = [y, x];

// 快速獲取當前頁面的域名和路徑:
var {hostname:domain, pathname:path} = location;

若是一個函數接收一個對象做爲參數,那麼,可使用解構直接把對象的屬性綁定到變量中。例如,下面的函數能夠快速建立一個 Date 對象:

function buildDate({year, month, day, hour=0, minute=0, second=0}) {
    return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}
// 它的方便之處在於傳入的對象只須要 year、month 和 day 這三個屬性:
buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
// 也能夠傳入 hour、minute 和 second 屬性:
buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
// Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

3. this 關鍵字

this 關鍵字指向當前函數調用語句所在的做用域。簡記:誰調用函數,this 指向誰。

function func() {
    // 直接在 window 對象中使用 func() 調用函數,this 指向 window 對象
    console.log(this);
}
func();

4. 方法

在一個對象中綁定函數,稱爲這個對象的方法。

5. Map/Reduce

好比咱們有一個函數 \(f(x) = x^2\) ,要把這個函數做用在一個數組[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就能夠用 map 實現以下:

'use strict';

function pow(x) {
    return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
document.write(results);

6. 箭頭函數

ES6 標準新增了一種新的函數:Arrow Function(箭頭函數)。爲何叫Arrow Function?由於它的定義用的就是一個箭頭:

x => x * x
// 上面的箭頭函數至關於:
function (x) {
    return x * x;
}

箭頭函數至關於匿名函數,而且簡化了函數定義。箭頭函數有兩種格式,一種像上面的,只包含一個表達式,連 { ... }return 都省略掉了。還有一種能夠包含多條語句,這時候就不能省略 { ... }return

x => {
    if (x > 0) {
        return x * x;
    }
    else {
        return - x * x;
    }
}

若是參數不是一個,就須要用括號 () 括起來:

/ 兩個參數:
(x, y) => x * x + y * y

// 無參數:
() => 3.14

// 可變參數:
(x, y, ...rest) => {
    var i, sum = x + y;
    for (i=0; i<rest.length; i++) {
        sum += rest[i];
    }
    return sum;
}

若是要返回一個對象,就要注意,若是是單表達式,這麼寫的話會報錯:

x => { foo: x }   // SyntaxError:
// 由於和函數體的{ ... }有語法衝突,因此要改成:
x => ({ foo: x }) // ok:

7. 包裝對象

  • 不要使用new Number()new Boolean()new String() 建立包裝對象;
  • parseInt()parseFloat() 來轉換任意類型到 number
  • String() 來轉換任意類型到 string,或者直接調用某個對象的 toString() 方法;
  • 一般沒必要把任意類型轉換爲 boolean 再判斷,由於能夠直接寫 if (myVar) {...}
  • typeof 操做符能夠判斷出 numberbooleanstringfunctionundefined
  • 判斷 Array 要使用 Array.isArray(arr)
  • 判斷 null 請使用 myVar === null
  • 判斷某個全局變量是否存在用 typeof window.myVar === 'undefined';
  • 函數內部判斷某個變量是否存在用 typeof myVar === 'undefined'

image

相關文章
相關標籤/搜索