《javascript高級程序設計》學習筆記 | 5.5.Function類型

歡迎關注前端小謳的github,閱讀更多原創技術文章

Function 類型

  • 函數是對象,每一個函數都是 Function 類型的實例,都與其餘引用類型同樣具備屬性方法
  • 函數名是指向函數對象的指針,不會與某個函數綁定(一個函數可能會有多個名字)

相關代碼 →javascript

3 種聲明方式

// 1.函數聲明定義
function sum(num1, num2) {
  return num1 + num2
}

// 2.函數表達式定義
var sum = function (num1, num2) {
  return num1 + num2
}

// 3.Function構造函數定義
/* 不推薦使用構造函數定義,由於會形成解析2次代碼(1次解析常規js代碼,1次解析傳入構造函數中的字符串) */
var sum = new Function('num1', 'num2', 'return num1+num2')

訪問函數指針

function sum(num1, num2) {
  return num1 + num2
}
console.log(sum(10, 10))

var anotherSum = sum // 使用不帶圓括號的函數名是訪問函數指針,而非調用函數
console.log(anotherSum(10, 10))

/* sum 和 anotherSum 同時指向同一個函數 */

sum = null // sum與函數斷絕關係
console.log(anotherSum(10, 10)) // 但anotherSum()仍可正常調用
console.log(sum(10, 10)) // 會報錯,sum is not a function

沒有重載

function addSomeNumber(num) {
  return num + 100
}
// 建立第二個函數
function addSomeNumber(num) {
  return num + 200
}
// 第二個函數等價於下列代碼 -> 覆蓋了引用第一個函數的變量addSomeNumber
addSomeNumber = function (num) {
  return num + 200
}
var result = addSomeNumber(100)
console.log(result) // 300

函數聲明與函數表達式

js 引擎在代碼開始執行以前,解析器經過函數聲明提高(function declaration hoisting)的過程,將聲明函數放到源代碼樹的頂部,使其在執行任何代碼以前可用(能夠訪問);而函數表達式則必須等到解析器執行到所在代碼行才被解釋執行。前端

函數聲明函數表達式的惟一區別就是何時能夠經過變量訪問函數java

console.log(sumDeclare(10, 10)) // 函數聲明會提早
function sumDeclare(num1, num2) {
  return num1 + num2
}
console.log(sumExpression(10, 10)) // 函數表達式不會提早,會報錯,sumExpression is not a function
var sumExpression = function (num1, num2) {
  return num1 + num2
}

做爲值的函數

像傳遞參數同樣,把一個函數傳遞給另外一個函數,也能夠將一個函數做爲另外一個函數的結果返回node

function callSomeFunction(someFunction, someArgument) {
  return someFunction(someArgument)
}

function add10(num) {
  return num + 10
}
var result1 = callSomeFunction(add10, 10) // 訪問函數的指針而不是執行函數
console.log(result1) // 20

function getGreeting(name) {
  return 'Hello,' + name
}
var result2 = callSomeFunction(getGreeting, 'Nicholas') // 訪問函數的指針而不是執行函數
console.log(result2) // Hello,Nicholas

【例】想要根據數組對象的某個對象屬性進行排序:git

/**
 * 按照對象數組的某個object key,進行數組排序
 * @param {String} key 要排序的key
 * @param {String} sort 正序/倒序:asc/desc,默認爲asc
 */
function arraySort(key, sort) {
  return function (a, b) {
    if (sort === 'asc' || sort === undefined || sort === '') {
      // 正序:a[key] > b[key]
      if (a[key] > b[key]) return 1
      else if (a[key] < b[key]) return -1
      else return 0
    } else if (sort === 'desc') {
      // 倒序:a[key] < b[key]
      if (a[key] < b[key]) return 1
      else if (a[key] > b[key]) return -1
      else return 0
    }
  }
}
var userList = [
  { name: 'Tony', id: 3 },
  { name: 'Tom', id: 2 },
  { name: 'Jack', id: 5 },
]
console.log(userList.sort(arraySort('id'))) // 按 id 正序排列
console.log(userList.sort(arraySort('id', 'desc'))) // 按 id 倒序排列

函數內部屬性

函數內部有 2 個特殊對象 argumentsthis,1 個內部屬性 callergithub

arguments 是一個類數組對象,保存着函數的全部參數。對象有 callee 屬性,該屬性是一個指針,指向擁有這個 arguments 對象的函數數組

// 遞歸函數:計算階乘
function factorial(num) {
  if (num <= 1) return 1
  else return num * factorial(num - 1)
}
// 內部再也不引用函數名,不管函數名是什麼,均可以保證完成遞歸調用
function factorial(num) {
  if (num <= 1) return 1
  else return num * arguments.callee(num - 1) // callee指向擁有這個arguments對象的函數
}

var trueFactorial = factorial // 保存函數的指針
factorial = function () {
  return 0
}
console.log(trueFactorial(5)) // 120,已用arguments.callee解除函數體內代碼與函數名的耦合,仍能正常計算
console.log(factorial(5)) // 0

this 引用的是函數執行的環境對象,即 this 值(全局做用域調用函數時引用的是 windowapp

// vscode是node運行環境,沒法識別全局對象window,測試時需作微調
window.color = 'red'
var o = { color: 'blue' }
function sayColor() {
  console.log(this.color)
}
sayColor() // red,此時this指向對象window
o.sayColor = sayColor
o.sayColor() // blue,此時this指向對象o

ES5 定義了 caller 屬性,保存着調用當前函數的函數引用—— 誰調用了當前函數,caller 就是誰(全局做用域中調用當前函數 caller 值爲 null)函數

function callerTest() {
  console.log(callerTest.caller)
}
callerTest() // 在全局做用域種調用當前函數,caller 的值爲 null

function outer() {
  inner()
}
function inner() {
  console.log(inner.caller)
}
outer() // outer()調用了inner,所以打印outer()函數的源代碼

function inner() {
  console.log(arguments.callee.caller) // 能夠解除緊密耦合
}
outer() // 結果不變,打印outer()函數的源代碼
  • 函數在嚴格模式下運行時,訪問 arguments.callee 會報錯
  • ES5 定義了 arguments.caller 屬性,嚴格模式會報錯,非嚴格模式始終是 undefined,該屬性是爲了區分 arguments.caller 和 函數的 caller
  • 嚴格模式不能爲函數的 caller 屬性賦值,會報錯

函數屬性和方法

每一個函數都包含 2 個屬性:lengthprototype測試

length 表示函數但願接收的命名參數的個數

function nameLength(name) {
  return name
}
function sumLength(sum1, sum2) {
  return sum1 + sum2
}
function helloLength() {
  return 'Hello'
}
console.log(nameLength.length, sumLength.length, helloLength.length) // 1,2,0

prototype 保存着函數全部實例方法不可枚舉(使用for-in沒法發現),每一個函數都包含 3 個非繼承而來的方法apply()call()bind()

apply()call()的用途和結果相同 —— 都是在特定的做用域中調用函數,apply() 接收 arguments 對象或數組實例,call() 接收每個參數

function sumPrototype(num1, num2) {
  return num1 + num2
}

apply()接收 2 個參數:① 運行函數的做用域;② 參數數組(實例或 arguments 對象都可)

function applySum1(num1, num2) {
  return sumPrototype.apply(this, arguments) // 傳入arguments對象
}
function applySum2(num1, num2) {
  return sumPrototype.apply(this, [num1, num2]) // 傳入數組實例
}
console.log(applySum1(10, 10))
console.log(applySum2(10, 10))

call()接收若干參數:① 運行函數的做用域;剩餘參數分別傳入

function callSum(num1, num2) {
  return sumPrototype.call(this, num1, num2) // 分別傳入每一個參數
}
console.log(callSum(10, 10))

apply()call() 真正強大的地方在於可以擴充函數運行的做用域

// vscode是node運行環境,沒法識別全局對象window,測試時需作微調
window.color = 'red'
var o = { color: 'blue' }
function sayColor() {
  console.log(this.color)
}
sayColor() // red,此時this指向對象window
sayColor().call(this) // red,此時this指向對象window
sayColor().call(window) // red,此時this指向對象window
sayColor().call(o) // blue,此時this指向對象o

ES5 追加 bind() 方法,其建立一個函數實例,其 this 被綁定到傳給 bind() 函數的值

// vscode是node運行環境,沒法識別全局對象window,測試時需作微調
window.color = 'red'
var o = { color: 'blue' }
function sayColor() {
  console.log(this.color)
}
var bindColor = sayColor.bind(o)
bindColor() // blue,此時this被綁定給對象o

總結 & 問點

  • 函數是什麼?函數名是什麼?
  • 函數分別有哪幾種聲明方式?
  • js 的函數有重載嘛?爲何?
  • 函數聲明與函數表達式有什麼區別?
  • 什麼是函數的聲明提早?
  • 請寫出 function:根據對象數組的某個對象進行排序
  • 函數內部有哪些對象和屬性?這些對象和屬性分別有哪些特色或用法?
  • 嚴格模式下,函數內部的對象或屬性分別會有哪些限制?
  • 函數有哪些屬性?其實例方法都保存在哪裏?
  • 函數有哪些非繼承而來的方法?分別有什麼用法和做用?
相關文章
相關標籤/搜索