JS 總結之函數、做用域鏈

在 JavaScript 中,函數其實是一個對象。前端

🏌 聲明

JavaScript 用 function 關鍵字來聲明一個函數:git

function fn () {

}
複製代碼

變體:函數表達式:es6

var fn = function () {

}
複製代碼

這種沒有函數名的函數被稱爲匿名函數表達式。github

🤾‍ return

函數能夠有返回值數組

function fn () {
  return true
}
複製代碼

位於 return 以後的任何代碼都不會執行:瀏覽器

function fn () {
  return true
  console.log(false) // 永遠不會執行
}
fn() // true
複製代碼

沒有 return 或者只寫 return,函數將返回 undefined:bash

function fn () {
}
fn() // undefined
// 或者
function fn () {
  return
}
fn() // undefined
複製代碼

⛹ 參數

函數能夠帶有限個數或者不限個數的參數閉包

// 參數有限
function fn (a, b) {
  console.log(a, b)
}
// 參數不限
function fn (a, b, ..., argN) {
  console.log(a, b, ..., argN)
}
複製代碼

沒有傳值的命名參數,會被自動設置爲 undefinedapp

// 參數有限
function fn (a, b) {
  console.log(b) // undefined
}
fn(1)
複製代碼

🚣 arguments

函數能夠經過內部屬性 arguments 這個類數組的對象來訪問參數,即使沒有命名參數函數

// 有命名參數
function fn (a, b) {
  console.log(arguments.length) // 2
}
fn(1, 2)

// 無命名參數
function fn () {
  console.log(arguments[0], arguments[1], arguments[2]) // 1, 2, 3
}
fn(1, 2, 3)
複製代碼

⛳️ 長度

arguments 的長度由傳入的參數決定,並非定義函數時決定的。

function fn () {
  console.log(arguments.length) // 3
}
fn(1, 2, 3)
複製代碼

若是按定義函數是決定個的,那麼此時的 arguments.length 應該爲 0 而不爲 3。

🏓 同步

arguments 對象中的值會自動反應到對應的命名參數,能夠理解爲同步,不過並非由於它們讀取了相同的內存空間,而只是保持值同步而已

function fn (a) {
  console.log(arguments[0]) // 1
  a = 2
  console.log(arguments[0]) // 2
  arguments[0] = 3
  console.log(a) // 3
}
fn(1)
複製代碼

嚴格模式下,重寫 arguments 的值會致使錯誤。

🏸 callee

經過 callee 這個指針訪問擁有這個 arguments 對象的函數

function fn () {
  console.log(arguments.callee) // fn
}
fn()
複製代碼

🏒 類數組

長的跟數組同樣,能夠經過下標訪問,如 arguments[0],卻沒法使用數組的內置方法,如 forEach 等:

function fn () {
  console.log(arguments[0], arguments[1]) // 1, 2
  console.log(arguments.forEach) // undefined
}
fn(1, 2)
複製代碼

經過對象那章知道,能夠用 call 或者 apply 借用函數,因此 arguments 能夠借用數組的內置方法:

function fn () {
  Array.prototype.forEach.call(arguments, function (item) {
    console.log(item)
  })
}
fn(1, 2)
// 1
// 2
複製代碼

對於如此詭異的 arguments,我以爲仍是少用爲好。

🤺 this、 prototype

具體查看總結:

🏋 按值傳遞

引用《JavaScript 高級程序設計》4.1.3 的一句話:

ECMAScript 中全部函數的參數都是按值傳遞的,也就是說,把函數外部的值複製給函數內部的參數,就和把一個變量複製到另外一個變量同樣。

🎺 基本類型的參數傳遞

基本類型的傳遞很好理解,就是把變量複製給函數的參數,變量和參數是徹底獨立的兩個個體:

var name = 'jon'
function fn (a) {
  a = 'karon'
  console.log('a: ', a) // a: karon
}
fn(name)
console.log('name: ', name) // name: jon
複製代碼

用表格模擬過程:

棧內存 堆內存
name, a jon

將 a 複製爲其餘值後:

棧內存 堆內存
name jon
a karon

🎻 引用類型的參數傳遞

var obj = {
  name: 'jon'
}
function fn (a) {
  a.name = 'karon'
  console.log('a: ', a) // a: { name: 'karon' }
}
fn(obj)
console.log(obj) // name: { name: 'karon' }
複製代碼

嗯?說好的按值傳遞呢?咱們嘗試把 a 賦值爲其餘值,看看會不會改變了 obj 的值:

var obj = {
  name: 'jon'
}
function fn (a) {
  a = 'karon'
  console.log('a: ', a) // a: karon
}
fn(obj)
console.log(obj) // name: { name: 'jon' }
複製代碼

🎸 真相浮出水面

參數 a 只是複製了 obj 的引用,因此 a 能找到對象 obj,天然能對其進行操做。一旦 a 賦值爲其餘屬性了,obj 也不會改變什麼。

用表格模擬過程:

棧內存 堆內存
obj, a 引用值 { name: 'jon' }

參數 a 只是 複製了 obj 的引用,因此 a 能找到存在堆內存中的對象,因此 a 能對堆內存中的對象進行修改後:

棧內存 堆內存
obj, a 引用值 { name: 'karon' }

將 a 複製爲其餘值後:

棧內存 堆內存
obj 引用值 { name: 'karon' }
a 'karon'

所以,基本類型和引用類型的參數傳遞也是按值傳遞的

🚴 做用域鏈

理解做用域鏈以前,咱們須要理解執行環境變量對象

🍗 執行環境

執行環境定義了變量或者函數有權訪問的其它數據,能夠把執行環境理解爲一個大管家。

執行環境分爲全局執行環境函數執行環境,全局執行環境被認爲是 window 對象。而函數的執行環境則是由函數建立的。

每當一個函數被執行,就會被推入一個環境棧中,執行完就會被推出,環境棧最底下一直是全局執行環境,只有當關閉網頁或者推出瀏覽器,全局執行環境纔會被摧毀。

🍖 變量對象

每一個執行環境都有一個變量對象,存放着環境中定義的全部變量和函數,是做用域鏈造成的前置條件。但咱們沒法直接使用這個變量對象,該對象主要是給 JS 引擎使用的。具體能夠查看《JS 總結之變量對象》

🍤 做用域鏈的做用

做用域鏈屬於執行環境的一個變量,做用域鏈收集着全部有序的變量對象,函數執行環境中函數自身的變量對象(此時稱爲活動對象)放置在做用域鏈的最前端,如:

scope: [函數自身的變量對象,變量對象1,變量對象2,..., 全局執行環境的變量對象]
複製代碼

做用域鏈保證了對執行環境有權訪問的全部變量和函數的有序訪問

var a = 1
function fn1 () {
  var b = 2
  console.log(a,b) // 1, 2
  function fn2 () {
    var c = 3
    console.log(a, b, c) // 1, 2, 3
  }
  fn2()
}
fn1()
複製代碼

對於 fn2 來講,做用域鏈爲: fn2 執行環境fn1 執行環境全局執行環境 的變量對象(全部變量和函數)。

對於 fn1 來講,做用域鏈爲: fn1 執行環境全局執行環境 的變量對象(全部變量和函數)。

總結爲一句:函數內部能訪問到函數外部的值,函數外部沒法範圍到函數內部的值。引出了閉包的概念,查看總結:《JS 總結之閉包》

🏇 箭頭函數

ES6 新語法,使用 => 定義一個函數:

let fn = () => {}
複製代碼

當只有一個參數的時候,能夠省略括號:

let fn = a => {}
複製代碼

當只有一個返回值沒有其餘語句時,能夠省略大括號:

let fn = a => a

// 等同於
let fn = function (a) {
  return a
}
複製代碼

返回對象而且沒有其餘語句的時候,大括號須要括號包裹起來,由於 js 引擎認爲大括號是代碼塊

let fn = a => ({ name: a })

// 等同於
let fn = function (a) {
  return { name: a }
}
複製代碼

箭頭函數的特色:

  1. 沒有 this,函數體內的 this 是定義時外部的 this
  2. 不能被 new,由於沒有 this
  3. 不可使用 arguments,可使用 rest 代替
  4. 不可使用 yield 命令,所以箭頭函數不能用做 Generator 函數。

🚀 參考

相關文章
相關標籤/搜索