1、js函数函数的本质是什么
js中函数的本质是对象,是Function构造函数(Function是函数也是对象)的实例。
function getName(){
}
console.log(typeof getName)
console.log(getName.__proto__===Function.prototype) //作为对象
console.log(getName.prototype.__proto__===Object.prototype) //作为函数
2、函数的声明方式:
A) function关键字直接声明:
function getName(){}
B)函数表达式:
let getName=function(){}
C)箭头函数:
let getName=()=>{}
D)Function实例:
let getName=new Function('a','b','return a+b')
简单对这种方式声明函数做一个解释:
首先,Function的参数必须都是字符串。其次,最后一个参数作为函数体,其余参数认为是要声明函数的参数。使用这种方式声明函数很不方便,不推荐。
3、箭头函数
es6中箭头函数产生的意义是消除函数的二义性,他不像普通函数,既可以直接调用,又可以作为构造函数使用new关键字调用。箭头函数只能直接调用,他没有自己的this,也不能作为构造函数,也没有函数内部的argument,也没有prototype属性。
另外,箭头函数也简化了函数的书写。举个例子:
function sum(a,b){
return a+b
}
//可以简写为
let sum=(a,b)=>a+b
A)参数简写:如果只有一个参数,小括号可以省略。
B)函数体简写:如果只有一行代码或是一个表达式作为函数体,大括号和return关键字可以省略。
4、函数名
函数是对象,函数名就是指向该对象的引用。每一个函数都有一个name属性作为函数的标识。
function a(){}
console.log(a.name) //a
如果函数是一个获取函数,设置函数或者是bind()返回的函数例子,则会在标识的前面加上一个前缀。
箭头函数的name是变量名。
匿名函数表达式的name是(空字符串) (()=>{}).name
Funciton实例的name是anonymous
let getName=new Function()
console.log(getName.name) //anonymous
5、理解函数的参数:
js中函数参数的传递是没有类型和长度的限制的,这意味着你定义函数的时候要求有两个参数,但是你在调用函数的时候可以传一个,传三个,甚至不传参数都可以。因为在函数的内部会通过arguments接收传过来的参数。
另外要理解一点:arguments对象可以跟命名参数一起使用,并会和命名参数进行同步,举个例子:
function doAdd(num1,num2){
arguments[1]=10
console.log(arguments[0]+num2)
}
doAdd(10,5) //20
doAdd(10) //NaN
如上,传递两个参数调用,函数执行过程中arguments[1]的修改,将值同步给你num2,因此结果输出的是20而不是15;
这里你需要注意,命名参数和arguments和命名参数虽然可以同步,但他们指向的不是同一块内存地址。
最后一行代码,只传递了一个参数,arguments[1]修改之后并不会同步给num2,因为在函数调用时,没有给num2传值,因此num2的值为undefined。10+undefined,最后输出NaN。
箭头函数内部没有arguments,但是你可以通过外层包的函数给他提供arguments,如下:
function doAdd(num1,num2){
let bar = () =>{
console.log(arguments[0]+arguments[1]) //7
}
bar();
}
doAdd(3,4)
6、没有函数重载
在java中,如果两个函数的参数的类型或者个数有不同,那么虽然两个函数名相同,他们是可以同时存在的。但是在js中,如果两个函数同名,后定义的函数会覆盖先定义的函数。
function conFun(a,b){
console.log('先定义的函数执行了')
}
function conFun(a){
console.log('后定义的函数执行了')
}
conFun(1,2)
这和函数声明提升也有一些关系,以上代码你可以这样理解:
let conFun;
conFun=function(a,b){
console.log('先定义的函数执行了')
}
conFun=function(a){
console.log('后定义的函数执行了')
}
conFun(1,2)
7、函数的默认参数
直接举例:
function makeKing(name='Henry'){
name='test';
console.log(arguments[0],name)
}
makeKing()
makeKing('Louis')
使用默认参数时,arguments对象的值不反应参数的默认值,参数的值也不会反映给arguments。
默认参数的值既可以是原始值,对象类型的值,也可以是函数调用返回的结果。
默认参数作用域和暂时性死区:
简单解释就是:后定义的默认参数可以引用先定义的默认参数,但是先定义的参数无法引用后定义的参数。另外,参数也存在自己的作用域,参数作用域中的参数不能引用函数体中定义的变量。
8、扩展参数与收集参数
扩展参数:
想象一个场景,有以下函数:
function getSum(){
let sum=0;
for(let i=0;i<arguments.length;i++){
sum+=arguments[i];
}
return sum;
}
这个函数是实现求和的,参数是很多数字分别作为参数参进去,但是我现在有的是一个数组values,我怎么把数组拆开一个一个传进去?
方法一:借助apply
let values=[1,2,3,4]
getSum.apply(null,values)
方法二:用扩展运算符
let values=[1,2,3,4]
getSum(...values)
收集参数:
在函数调用的时候,参数是分别传进去的,但是在函数声明时,想通过一个参数(数组类型),把所有的参数收集进来,如下:
function getSum(...values){
let sum=0;
for(let i=0;i<values.length;i++){
sum+=values[i];
}
return sum;
}
console.log(getSum(1,2,3,4)) //10
函数声明时,除了收集参数,还可以定义多个参数。但是,收集参数必须是最后一个。
function getSum(a,b,...values){} //可以
function getSum(a,...values,b){} //不可以
9、函数内部
arguments:
arguments用来接收函数调用是传递的实参的,他是一个类数组的结构(不是数组,可通过Array.from转换成数组)。他有一个callee属性,指向arguments所在函数。
function fun(){
console.log(arguments.callee===fun)
}
fun() //true
this:
在标准函数中,this引用的是在函数调用时,把函数作为方法调用的上下文对象。在全局上下文中直接调用函数,this引用window。
在箭头函数中,this引用的是定义箭头函数的上下文对象。
caller:
在函数内部,给函数对象添加了一个属性caller,指向调用该函数的函数。
function outer(){
inner()
}
function inner(){
console.log(inner.caller)
}
outer()
inner()
new.target:
在函数内部,如果函数作为普通函数调用,new.target指向undefined。如果函数作为构造函数使用new关键字调用,new.target指向该构造函数。
9、函数的属性和方法
函数是对象,因此有自己的属性和方法,这里简单介绍几个。
- length属性:函数声明时,形参的个数。
- prototype属性:函数的原型对象,函数new出来的实例会继承这个原型对象。
- apply方法:接收两个参数,函数体内this的值和参数数组(arguments也可以)
- call方法:和apply相似,第一个参数也是函数内this的值,后面的参数个数不固定,要把需要给函数传递的参数一个一个列出来
- bind方法:传参和call方法是一致的,但是bind方法不仅可以改变函数内部的this指向,他本身也返回一个函数,可以对返回函数进行参数的预设,看下面的例子:
function multiply(a, b, c) {
return a * b * c;
}
const multiplyBy2And3 = multiply.bind(null, 2, 3); // 预设前两个参数
console.log(multiplyBy2And3(4)); // 输出:24 (即 2 * 3 * 4)
10、匿名函数表达式和命名函数表达式
在 JavaScript 中,函数表达式是一种定义函数的方式,其中函数被赋值给变量或对象属性。函数表达式可以是匿名的,也可以是命名的。
匿名函数表达式
匿名函数表达式是指没有名称的函数。匿名函数通常用于一次性使用或作为参数传递给其他函数(如回调函数)。
语法:
const myFunction = function(param1, param2) {
// 函数体
return param1 + param2;
};
特点:
- 没有函数名,函数名位置为空。
- 通常通过变量来引用和调用。
- 常用于回调函数或立即执行函数表达式(IIFE)。
示例:
const numbers = [1, 2, 3];
const sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log(sum); // 输出: 6
命名函数表达式
命名函数表达式是指函数表达式中包含一个名称,这个名称在函数体内是可访问的,但在函数外部不可访问。命名函数表达式通常用于递归调用或在调试时提供更清晰的函数名。
语法:
const myFunction = function myNamedFunction(param1, param2) {
// 函数体
if (param2 === 0) {
return param1; // 基本情况
}
return myNamedFunction(param1 - 1, param2 - 1) + 1; // 递归调用
};
特点:
- 函数有一个内部名称,在函数体内可访问。
- 外部无法通过该名称直接调用函数。
- 提供更好的调试信息,特别是在递归或复杂函数中。
示例:
const factorial = function calcFactorial(n) {
if (n <= 1) return 1;
return n * calcFactorial(n - 1);
};
console.log(factorial(5)); // 输出: 120
总结
- 匿名函数表达式:没有名称,通常通过变量引用,适用于一次性使用或作为回调。
- 命名函数表达式:在函数体内有名称,便于递归和调试,外部通过变量引用。