關注前端小謳,閲讀更多原創技術文章
相關代碼 →
10.9 函數內部
- ES5 中函數內部有 2 個特殊對象
arguments和this,1 個內部屬性caller - ES6 新增
new.target屬性
10.9.1 arguments
arguments是一個類數組對象,包含調用函數時傳入的所有參數- 只有以
funciton關鍵字定義函數時才會有arguments對象(箭頭函數沒有) -
對象有一個
callee屬性,指向arguments所在函數的指針(注意:是指針即函數名,而非函數)- 嚴格模式下,訪問
arguments.callee會報錯
- 嚴格模式下,訪問
// 遞歸函數:計算階乘
function factorial(num) {
if (num <= 1) return 1
else return num * factorial(num - 1)
}
// 使用arguments.callee解耦函數邏輯與函數名
function factorial(num) {
if (num <= 1) return 1
else return num * arguments.callee(num - 1) // callee指向arguments對象所在函數
}
let trueFactorial = factorial // 保存函數的指針
// 重寫factorial函數,trueFactorial指針不變
factorial = function () {
return 0
}
console.log(trueFactorial(5)) // 120,已用arguments.callee解除函數體內代碼與函數名的耦合,仍能正常計算
console.log(factorial(5)) // 0,函數已被重寫
10.9.2 this
- 在標準函數中,
this指向調用函數的上下文對象,即函數執行的環境對象(全局作用域指向window)
window.color = 'red' // vscode是node運行環境,無法識別全局對象window,測試時將window改為global
let o = { color: 'blue' }
function sayColor() {
console.log(this.color)
}
sayColor() // 'red',this指向全局對象
o.sayColor = sayColor
o.sayColor() // 'blue',this指向對象o
- 在箭頭函數中,
this指向定義函數的上下文對象,即該函數外部的環境對象
let sayColor2 = () => {
console.log(this.color) // this指向定義sayColor2的上下文,即全局對象
}
sayColor2() // 'red',this指向全局對象
o.sayColor2 = sayColor2
o.sayColor2() // 'red',this指向全局對象
- 在事件回調或定時回調中調用某個函數時,
this指向的並非想要的對象,將回調函數寫成箭頭函數可解決問題
function King() {
this.royaltyName = 'Henry'
setTimeout(() => {
console.log(this.royaltyName) // 箭頭函數,this指向定義函數的上下文,即King()的函數上下文
}, 1000)
}
function Queen() {
this.royaltyName = 'Elizabeth'
setTimeout(function () {
console.log(this.royaltyName) // 標準函數,this指向調用函數的上下文,即setTimeout()的函數上下文
}, 1000)
}
new King() // 'Henry',1秒後打印
new Queen() // undefined,1秒後打印
10.9.3 caller
- ES5 定義了
caller屬性,指向調用當前函數的函數(全局作用域中為 null)
function callerTest() {
console.log(callerTest.caller)
}
callerTest() // null,在全局作用域種調用
function outer() {
inner()
}
function inner() {
console.log(inner.caller)
}
outer() // [Function: outer],在outer()調用
// 解除耦合
function inner() {
console.log(arguments.callee.caller) // arguments.callee指向arguments所在函數的指針,即inner
}
outer() // [Function: outer],在outer()調用
arguments.caller的值始終是undefined,這是為了區分arguments.caller和函數的caller- 嚴格模式下,訪問
arguments.caller和為函數的caller屬性賦值會報錯
function inner2() {
console.log(arguments.caller) // undefined
console.log(arguments.callee) // [Function: inner2]
}
inner2()
10.9.4 new.target
-
ES6 在函數內部新增
new.target屬性,檢測函數是否使用new關鍵字調用- 未使用
new調用,new.target的值是undefined - 使用
new調用,new.target的值是被調用的構造函數
- 未使用
function King2() {
if (!new.target) {
console.log(new.target, 'King2 must be instantiated using "new"')
} else {
console.log(new.target, 'King2 instantiated using "new"')
}
}
new King2() // [Function: King2] 'King2 instantiated using "new"'
King2() // undefined 'King2 must be instantiated using "new"'
10.10 函數屬性與方法
-
函數包含 2 個屬性:
length和prototypelength保存函數希望接收的命名參數的個數
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 0prototype指向函數的原型對象,保存函數所有實例方法且不可枚舉(使用for-in無法發現)
console.log(Array.prototype) // 在瀏覽器中查看Array的原型對象,包含sort()等方法 console.log(Object.keys(Array)) // [],Array構造函數自身所有可枚舉的屬性 console.log(Object.getOwnPropertyNames(Array)) // [ 'length', 'name', 'prototype', 'isArray', 'from', 'of' ],Array構造函數自身的所有屬性 -
函數有 3 個方法:
apply()、call()和bind()function sumPrototype(num1, num2) { return num1 + num2 }apply()和call()都會以指定的this值調用函數,即設置調用函數時函數體內this的指向apply()接收 2 個參數:① 運行函數的作用域(指定 this);② 參數數組(實例或 arguments 對象均可)
function applySum1(num1, num2) { return sum.apply(this, arguments) // 傳入arguments對象 } function applySum2(num1, num2) { return sum.apply(this, [num1, num2]) // 傳入數組實例 } console.log(applySum1(10, 10)) // 20 console.log(applySum2(10, 10)) // 20call()接收若干參數:① 運行函數的作用域(指定 this);剩餘參數逐個傳入
function callSum(num1, num2) { return sum.call(this, num1, num2) // 逐個傳入每個參數 } console.log(callSum(10, 10)) // 20apply()和call()真正強大的地方在於能夠擴充函數運行的作用域,即控制函數體內this值
window.color = 'red' // vscode是node運行環境,無法識別全局對象window,測試時將window改為global let o2 = { color: 'blue' } function sayColor3() { console.log(this.color) } sayColor3() // 'red',this指向全局對象 sayColor3.call(this) // 'red',this指向全局對象 sayColor3.call(window) // 'red',this指向全局對象,測試時將window改為global sayColor3.call(o2) // 'blue',this指向對象o2Function.prototype.apply.call(),將函數原型的apply方法利用call()進行綁定(可通過Reflect.apply()簡化代碼)
let f1 = function () { console.log(arguments[0] + this.mark) } let o3 = { mark: 95, } f1([15]) // '15undefined',this指向f1的函數上下文,this.mark為undefined f1.apply(o3, [15]) // 110,將f1的this綁定到o3 Function.prototype.apply.call(f1, o3, [15]) // 110,函數f1的原型對象的apply方法,利用call進行綁定 Reflect.apply(f1, o3, [15]) // 110,通過指定的參數列表發起對目標函數的調用,三個參數(目標函數、綁定的this對象、實參列表)bind()創建一個新的函數實例,其this被綁定到傳給bind()的對象
let o4 = { color: 'blue' } function sayColor4() { console.log(this.color) } let bindSayColor = sayColor4.bind(o4) // 創建實例bindSayColor,其this被綁定給o4 sayColor4() // 'red',this指向全局對象 bindSayColor() // 'blue',this被綁定給對象o4
10.11 函數表達式
- 函數聲明的關鍵特點是函數聲明提升,即函數聲明會在代碼執行之前獲得定義
sayHi() // 'Hi',先調用後聲明
function sayHi() {
console.log('Hi')
}
-
函數表達式必須先賦值再使用,其創建一個匿名函數(
function後沒有標識符)再把它賦值給一個變量- 匿名函數的
name屬性是空字符串
- 匿名函數的
sayHi2() // ReferenceError: Cannot access 'sayHi2' before initialization,不能先調用後賦值
let sayHi2 = function sayHi() {
console.log('Hi')
}
- 函數聲明與函數表達式的區別在於提升,在條件塊中避免使用函數聲明,可以使用函數表達式
let condition = false
if (condition) {
function sayHi3() {
console.log('true')
}
} else {
function sayHi3() {
console.log('false')
}
}
sayHi3() // 不同瀏覽器的結果不同,避免在條件塊中使用函數聲明
let sayHi4
if (condition) {
sayHi4 = function () {
console.log('true')
}
} else {
sayHi4 = function () {
console.log('false')
}
}
sayHi4() // false,可以在條件塊中使用函數表達式
- 創建函數並賦值給變量可用於在一個函數中把另一個函數當作值返回
/**
* 按照對象數組的某個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'))) // [{ name: 'Tom', id: 2 },{ name: 'Tony', id: 3 },{ name: 'Jack', id: 5 }],按 id 正序排列
console.log(userList.sort(arraySort('id', 'desc'))) // [{ name: 'Jack', id: 5 },{ name: 'Tony', id: 3 },{ name: 'Tom', id: 2 }],按 id 倒序排列
console.log(userList.sort(arraySort('name'))) // [{ name: 'Jack', id: 5 },{ name: 'Tom', id: 2 },{ name: 'Tony', id: 3 }],按 name 正序排列
總結 & 問點
- arguments 是什麼?arguments.callee 指向哪裏?寫一段代碼,表示函數名與函數邏輯解耦的階乘函數
- this 在標準函數和箭頭函數的指向有什麼不同?在事件回調或定時回調中,為什麼更適合使用箭頭函數?
- 函數的 caller 屬性指向哪裏?arguments.caller 的值是什麼?嚴格模式下 caller 有哪些限制?
- new.target 的作用和值分別是什麼?
- 函數有哪些屬性?其指向和用法分別是什麼?
- 請用代碼證明 apply()、call()、bind()是如何擴充函數作用域的,並解釋 Function.prototype.apply.call()的含義
- 函數聲明和函數表達式最大的區別是什麼?如何理解聲明提升?
- 寫一段代碼,根據對象數組的某個對象屬性進行排序,可根據參數決定排序屬性及升/降序