JavaScript中的函數

概念

函數就是封裝了一段可被重複調用執行的代碼塊javascript

函數的使用分爲兩步:聲明函數和調用函數。java

  1. 函數聲明
function fn() {
  console.log("hi")
}

注意:數組

  • function 聲明函數的關鍵字所有小寫;
  • 函數不調用本身不會執行;
  1. 調用函數
fn()

注意:調用的時候必定要加小括號。瀏覽器


函數的五種聲明方式

具名函數

function f(x, y) {
  return x + y
}
console.log(f(1, 2))    // 3

匿名函數

var fn
fn = function(x, y) {
  return x + y
}
console.log(fn(1, 2))    // 3

具名函數賦值

var x = function y(a, b) {
  return a + b
}
console.log(x(1, 2))    // 3
console.log(y)    // y is not defined

window.Function

var fn = new Function('x', 'y', 'return x+y')
console.log(fn(1, 2))    // 3

箭頭函數

var fn1 = x => n * n
var fn2 = (x, y) => x + y
var fn3 = (x, y) => {
  return x + y
}

name屬性

function.name 屬性返回函數實例的名稱。閉包

咱們來看看下面這幾種狀況:wordpress

function fn() {}
console.log(fn.name)    // fn

let fn1 = function fn2() {}
console.log(fn1.name)    // fn2

let fn = new Function('x', 'y', 'return x+y')
console.log(fn.name)    // anonymous

console.log((() => {}).name)    // ""
let fn = () => {}
console.log(fn.name)    // fn

函數的本質

函數就是一段能夠反覆調用的代碼塊。函數是一個對象,這個對象能夠執行一段代碼,能夠執行代碼的對象就是函數。函數

那爲何函數是一個對象呢?this

var f = {}
f.name = 'f'
f.params = ['x', 'y']
f.functionBody = 'console.log("1")'
f.call = function() {
  return window.eval(f.functionBody)
}
console.log(f)    // {name: "f", params: Array(2), functionBody: "window.runnerWindow.proxyConsole.log("1")", call: ƒ}
f.call()    // 1

函數的封裝

函數的封裝是把一個或多個功能經過函數的方法封裝起來,對外只提供一個簡單的函數接口。lua

下面咱們來看看幾個簡單的例子:prototype

// 計算 1 ~ 100 之間的累加和
function getNum() {
  var sum = 0
  for (let i = 1; i <= 100; i++) {
    sum += i
  }
  console.log(sum)
}
getNum()    // 5050

// 求任意兩個數的和
function getSum(num1, num2) {
  console.log(num1 + num2)
}
getSum(1, 2)    // 3

// 求任意兩個數之間的和
function getNum(start, end) {
  let sum = 0
  for (let i = start; i <= end; i++) {
    sum += i
  }
  console.log(sum)
}
getNum(0, 10)    // 55

this與arguments

this 就是 call 的第一個參數,能夠用 this 獲得。

arguments 就是 call 除了第一個之外的參數,能夠用 arguments 獲得。arguments 對象中存儲了傳遞的全部實參。
arguments 展現形式是一個僞數組。
僞數組具備如下這幾個特色:

  1. 具備 length 屬性;
  2. 按索引方式儲存數據;
  3. 不具備數組的 push、pop 等等方法;

在普通模式下,若是 this 是 undefined,瀏覽器會自動把 this 變爲 window。

在普通模式下:

let fn = function() {
  console.log(this)    // window
  console.log(this === window)    // true
}
fn.call(undefined)

在嚴格模式下:

let fn = function() {
  'use strict'
  console.log(this)    // undefined
  console.log(this === window)    // false
}
fn.call(undefined)

arguments:

let fn = function() {
  console.log(arguments)
}
fn.call(undefined, 1, 2)    // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]

// 求任意個數中的最大值
function fn() {
  let max = arguments[0]
  for (let i = 1; i < arguments.length; i++) {
    if (max < arguments[i]) {
      max = arguments[i]
    }
  }
  return max
}
console.log(fn(1, 2, 3, 4, 661, 434))    // 661

call stack調用棧

先進後出

查看調用棧過程

普通調用

嵌套調用

遞歸調用


柯里化

柯里化(Currying),又稱部分求值(Partial Evaluation),是一種關於函數的高階技術。它不會調用函數,它只是對函數進行轉換。將 fn(a,b,c) 轉換爲能夠被以 fn(a)(b)(c) 的形式進行調用。它是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。

咱們先來看一個例子:

function add(a, b, c) {
  return a + b + c
}
console.log(add(1, 2, 3))    // 6

如今咱們把上面代碼修改爲柯里化版本:

function addCurry(a) {
  return function(b) {
    return function(c) {
      return a + b + c
    }
  }
}
console.log(addCurry(1)(2)(3))    // 6

咱們來把 addCurry(1)(2)(3) 換一個形式來表示:

let a = addCurry(1)    // ƒ (b) { return function(c) { return a + b + c } }
let b = a(2)    // ƒ (c) { return a + b + c }
let c = b(3)
console.log(c)    // 6

下面咱們再來看一個例子:

let handleBar = function(template, data) {
  return template.replace('{{name}}', data.name)
}
handleBar('<p>Hello,{{name}}</p>', {
  name: 'zww'
})    // <p>Hello,zww</p>

handleBar('<p>Hello,{{name}}</p>', {
  name: 'lq'
})    // <p>Hello,lq</p>

上面這段代碼致使的問題就是,若是咱們常常要使用 template 模板,那麼每次像上面這樣寫將會致使十分繁瑣。咱們能夠將代碼修改成柯里化版本:

function handleBarCurry(template) {
  return function(data) {
    return template.replace('{{name}}', data.name)
  }
}

let h = handleBarCurry('<p>Hello,{{name}}</p>')
h({ name: 'zww' })    // <p>Hello,zww</p>
h({ name: 'lq' })    // <p>Hello,lq</p>

這樣就實現了 template 模板參數複用的效果了。

張鑫旭 - JS中的柯里化(currying)
現代JavaScript 教程 - 柯里化(Currying)
Currying 的侷限性
JavaScript函數柯里化


高階函數

高階函數是至少知足下面一個條件的函數:

  1. 接受一個或多個函數做爲輸入;
  2. 輸出一個函數;
  3. 同時知足上面兩個條件;

例以下面這些就是 JS 原生的高階函數:

  1. Array.prototype.sort()
  2. Array.prototype.forEach()
  3. Array.prototype.map()
  4. Array.prototype.filter()
  5. Array.prototype.reduce()

咱們來實現找出數組中全部的偶數並相加:

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let sum = 0
for (let i = 0; i < array.length; i++) {
  if (array[i] % 2 === 0) {
    sum += array[i]
  }
}
console.log(sum)    // 20

下面咱們用高階函數來實現上面的功能:

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let sum = array.filter(function(x) {
  return x % 2 === 0
}).reduce(function(p, n) {
  return p + n
}, 0)
console.log(sum)    // 20

廖雪峯 - 高階函數


回調函數

MDN 所描述的:被做爲實參傳入另外一函數,並在該外部函數內被調用,用以來完成某些任務的函數,稱爲回調函數。

簡單的說就是被看成參數的函數就是回調。

就像 array.sort(function() {})array.forEach(function() {})這些都是回調函數。

function putMsg(msg, callback) {
  setTimeout(() => {
    console.log(msg)
    callback()
  }, 1000)
}
putMsg('hi', function() {
  console.log('msg')
})

上面代碼將在 1 秒後打印 hi、msg。


構造函數

簡單的說就是返回對象的函數就是構造函數,構造函數名字首字母通常大寫。

構造函數有兩個特色:

  1. 函數體內部使用了 this 關鍵字,表明了所要生成的對象實例;
  2. 生成對象的時候,必須使用 new 命令;
function Person(name, age) {
  this.name = name
  this.age = age
}
let person = new Person('zww', 18)
console.log(person)    // Person {name: "zww", age: 18}

做用域

做用域指的是您有權訪問的變量集合。

做用域決定了這些變量的可訪問性(可見性)。

函數內部定義的變量從函數外部是不可訪問的(不可見的)。

做用域分爲全局做用域、局部做用域。變量也能夠分爲全局變量與局部變量。
從執行效率來看全局變量與局部變量:

  1. 全局變量只有瀏覽器關閉的時候纔會銷燬,比較佔內存資源;
  2. 局部變量當咱們程序執行完畢就會銷燬,比較節約內存資源;

做用域鏈:內部函數訪問外部函數的變量,採起的是鏈式查找的方式來決定取哪一個值,這種結構稱爲做用域鏈。也就是所謂的就近原則。

咱們來看看幾個例子:

question one:

var a = 1

function f1() {
  var a = 2
  f2.call()
  console.log(a)    // 2

  function f2() {
    var a = 3
    console.log(a)    // 3
  }
}
f1.call()
console.log(a)    // 1

question two:

var a = 1

function f1() {
  f2.call()
  console.log(a)    // undefined
  var a = 2    // 變量提高!!!

  function f2() {
    var a = 3
    console.log(a)    // 3
  }
}
f1.call()
console.log(a)    // 1

question three:

var a = 1

function f1() {
  console.log(a)    // undefined
  var a = 2
  f2.call()
}

function f2() {
  console.log(a)    // 1
}
f1.call()
console.log(a)    // 1

question four:

var liTags = document.querySelectorAll('li')
for (var i = 0; i < liTags.length; i++) {
  liTags[i].onclick = function() {
    console.log(i)    // 點擊第二個li時,打印6
  }
}

以上代碼變量提高後可等價以下:

var liTags
var i
liTags = document.querySelectorAll('li')
for (i = 0; i < liTags.length; i++) {
  liTags[i].onclick = function() {
    console.log(i)
  }
}

閉包

閉包指有權訪問另外一個函數做用域中變量的函數,簡單的說就是,一個做用域能夠訪問另一個函數內部的局部變量。

閉包的主要做用:延伸了變量的做用範圍。

var a = 1

function fn() {
  console.log(a)
}

這個函數 fn 與變量 a 就造成一個閉包。

function f1() {
  var num = 10
  function f2() {
    console.log(num);
  }
  return f2
}
var f = f1()
f()    // 10

箭頭函數

ES6 中新增的定義函數的方式。

箭頭函數不綁定 this,箭頭函數中的 this,指向的是函數定義位置的上下文 this。

箭頭函數有如下這些特色:

  1. 有更加簡潔的語法;
  2. 不會綁定 this;
let fn = () => {
  console.log(this)
}
fn()    // window

// 若是使用 call,仍是不會改變 this 指向。
fn.call({
  name: 'zww'
})    // window

咱們來看看下面這段代碼的 this 是什麼:

function fn() {}
setTimeout(function(a) {
  console.log(this)    // window
}, 1000)

很明顯,上面代碼執行 1s 後將會打印 window,那麼咱們應該怎樣把 this 指向 fn 呢?

可使用 bind 來更改 this 的執行,代碼以下:

setTimeout(function(a) {
  console.log(this)    // ƒ fn() {}
}.bind(fn), 1000)

這樣 this 就指向了 fn 了,下面咱們再在 setTimeout 裏面添加個 setTimeout:

setTimeout(function(a) {
  console.log(this)    // ƒ fn() {}
  setTimeout(function(a) {
    console.log(this)    // window
  }, 1000)
}.bind(fn), 1000)

裏面這個 setTimeout 所打印的 this 仍是指向的 window,那應該怎麼指向 fn 呢?

沒錯,仍是使用 bind,只不過裏面直接傳 this 便可:

setTimeout(function(a) {
  console.log(this)    // ƒ fn() {}
  setTimeout(function(a) {
    console.log(this)    // ƒ fn() {}
  }.bind(fn), 1000)
}.bind(fn), 1000)

上面代碼,咱們還可使用箭頭函數來簡化:

setTimeout(function(a) {
  console.log(this)    // ƒ fn() {}
  setTimeout((a) => {
    return console.log(this)    // ƒ fn() {}
  }, 1000)
}.bind(fn), 1000)
相關文章
相關標籤/搜索