javascript
JS 总结之函数、作用域链
在 JavaScript 中,函數實際上是一個對象。
? 聲明
JavaScript 用 function 關鍵字來聲明一個函數:
function fn () {} 復制代碼變體:函數表達式:
var fn = function () {} 復制代碼這種沒有函數名的函數被稱為匿名函數表達式。
?? return
函數可以有返回值
function fn () {return true } 復制代碼位于 return 之后的任何代碼都不會執行:
function fn () {return trueconsole.log(false) // 永遠不會執行 } fn() // true 復制代碼沒有 return 或者只寫 return,函數將返回 undefined:
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) } 復制代碼沒有傳值的命名參數,會被自動設置為 undefined
// 參數有限 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]) // 1a = 2console.log(arguments[0]) // 2arguments[0] = 3console.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, 2console.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
具體查看總結:
- 《關于 this 應該知道的幾個點》
- 《原型》
? 按值傳遞
引用《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 = 2console.log(a,b) // 1, 2function fn2 () {var c = 3console.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 } } 復制代碼箭頭函數的特點:
? 參考
- 《JavaScript 深入之參數按值傳遞》 by 冴羽
- 《ECMAScript 6 入門》函數的擴展 - 箭頭函數 by 阮一峰
- 《JavaScript 高級程序設計》3 基本概念、4.1.3 傳遞參數、4.2 執行環境及作用域
轉載于:https://juejin.im/post/5c2075f7e51d451611220c45
總結
以上是生活随笔為你收集整理的JS 总结之函数、作用域链的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CF788B Weird journey
- 下一篇: Linux系统检测命令有哪些