理解js函数(Ⅰ)

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


总结

  • 匿名函数表达式:没有名称,通常通过变量引用,适用于一次性使用或作为回调。
  • 命名函数表达式:在函数体内有名称,便于递归和调试,外部通过变量引用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值