《javascript高级程序设计》学习笔记 | 10.9-10.11.函数内部
关注前端小讴,阅读更多原创技术文章
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
和prototype
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
无法发现)
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)) // 20
call()
接收若干参数:① 运行函数的作用域(指定 this);剩余参数逐个传入
function callSum(num1, num2) { return sum.call(this, num1, num2) // 逐个传入每个参数 } console.log(callSum(10, 10)) // 20
apply()
和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指向对象o2
Function.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()的含义
- 函数声明和函数表达式最大的区别是什么?如何理解声明提升?
- 写一段代码,根据对象数组的某个对象属性进行排序,可根据参数决定排序属性及升/降序