欢迎关注前端小讴的github,阅读更多原创技术文章
Function 类型
- 函数是对象,每个函数都是
Function
类型的实例,都与其他引用类型一样具有属性和方法 - 函数名是指向函数对象的指针,不会与某个函数绑定(一个函数可能会有多个名字)
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)的过程,将声明函数放到源代码树的顶部,使其在执行任何代码之前可用(可以访问);而函数表达式则必须等到解析器执行到所在代码行才被解释执行。
函数声明和函数表达式的唯一区别就是什么时候可以通过变量访问函数
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
}
作为值的函数
像传递参数一样,把一个函数传递给另一个函数,也可以将一个函数作为另一个函数的结果返回
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
【例】想要根据数组对象的某个对象属性进行排序:
/**
* 按照对象数组的某个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 个特殊对象 arguments
和 this
,1 个内部属性 caller
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 值(全局作用域调用函数时引用的是 window
)
// 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 个属性: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
无法发现),每个函数都包含 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:根据对象数组的某个对象进行排序
- 函数内部有哪些对象和属性?这些对象和属性分别有哪些特点或用法?
- 严格模式下,函数内部的对象或属性分别会有哪些限制?
- 函数有哪些属性?其实例方法都保存在哪里?
- 函数有哪些非继承而来的方法?分别有什么用法和作用?