JavaScript 函数优先和函数提升

本文详细探讨了JavaScript中的函数优先级,即函数可以像变量一样被分配、作为参数传递和作为返回值。此外,还深入解析了函数和变量的提升,包括函数声明方式、函数表达式提升与变量提升的规则,并通过多个示例进行说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当一种编程语言被称为函数优先(First-class functions)的编程语言时,是指该语言中函数可以和其他任何变量一样对待。例如,一个函数可以作为参数传递给另一个函数,可以作为返回值被另一个函数返回,可以作为一个值分配给一个变量。

分配一个函数给一个变量

const  foo = function() {
  console.log("foobar");
}
// Invoke this function using the variable: foo
foo();
  • 首先定义一个匿名函数,并把它赋值给一个变量:foo
  • 在变量后边添加小括号() parentheses 来通过变量调用该函数
  • 即使函数被命名,仍然可以使用变量名调用,但是不能使用函数名直接调用。使用变量命名后在 debug 代码的时候是有帮助的,对调用方式没有影响
const foo2 = function foo1() {
  console.log("foobar1");
}
//right to invoke
foo2();
//error to invoke
foo1();

将函数作为参数进行传递

function sayHello() {
  return "hello,";
}
function sayHi = function() {
  return "hi,";
}
function greeting(helloMessage, name) {
  console.log(helloMessage() + name;
}

//Pass `sayHello` as an argument to `greeting` function
greeting(sayHello, "JavaScript!");
//Pass `sayHi` as an argument to `greeting` function
greeting(sayHi, "codepen");
  • 通过直接命名或者匿名定义了两个函数
  • 直接将 sayHello() 函数和 sayHi 作为参数传递给函数 greeting(),这里就解释可以把函数当做一个值来对待
  • 把一个函数 A 作为参数传递给另一个函数 B,那么函数 A 我们称为回调函数 Callback function,在这里 sayHello 就是回调函数

返回一个函数(将函数作为返回值)

function sayHello() {
  return function() {
    console.log("hello!");
  }
}

//we have two ways to invoke
//1.using a variable
const myFunc = sayHello();
myFunc();

//2.using double parentheses
sayHello()();
  • 在这个例子中,我们在函数 sayHello 中返回了另外一个函数:之所以能这么做,是因为在JavaScript 中函数(function)也被当做一个值(value)
  • 一个函数 A 返回一个函数 B,那么函数 A 叫做高阶函数 Higher-Order Function

在例子中,我们有两种方式来调用 sayHello 函数以及其返回的匿名函数(Anonymous Function):

  1. 使用变量
const myFunc = sayHello();

那么此时 myFunc 其实就是一个匿名函数了,要调用这个匿名函数,那么就在变量名后边加上小括号就好了。

myFunc();

也就是这里要调用 sayHello 函数返回的匿名函数,需要定义一个变量。因为直接调用 sayHello 函数返回的就是这个匿名函数,但是这个匿名函数并没有调用。

  1. 使用两个括号
sayHello()();

可以如上使用两个括号()()来调用返回的函数。

函数和变量的提升

变量和函数声明从它们在代码中出现的位置被“移动”到了最上面,这叫变量的提升。分为两个部分,第一部分是将所有的变量声明和函数声明放在了代码的最上方,第二部分的代码运行到指定位置时再执行。

函数的声明方式

方式一:函数声明

function 函数名(形参列表) {
    //方法体
}

方式二:函数表达式(又称函数字面量声明)

var 变量名 = function 函数名(形参列表) {
    //方法体
}

注意: 这个函数名有点特别,它只能在自己这个函数体内被调用,不能被外部访问。

方式三:函数对象的声明

var 方法名 = new Function("形参1","形参2","形参3", "方法体");

只有函数声明才会函数声明提升,其他两种不会函数声明提升。

变量的提升
console.log(a);
var a = 3;

等价于

var a;
console.log(a);
a = 3;
函数的提升
foo();
function foo() {
    console.log(a);
    var a=2;
}

等价于

function foo() {
    var a;
    console.log(a);
    a=2;
}
foo();

注意:
函数声明会整个被提升,函数表达式不会整个被提升。

foo();//会报错,但是是 TypeError 而不是 ReferenceError
var foo = function bar() {
    //代码
}

因为其中的函数表达式被当成了变量来进行提升,所以上述代码等价于

var foo;
foo();
foo = function bar() {
    //代码
}

所以在执行 foo 函数的时候,能找到 foo 这个变量,只是没有被赋值,所以是 TypeError

知识点
var a = 2;
var a;
console.log(a);

//结果 2    

处理上述程序会分为两个部分,一部分是由编译器在编译时处理,另一部分则由引擎在运行时处理。

编译器做如下处理:

  1. 遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。
    如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a
  • 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2这个赋值操作。
示例 1
foo();
var foo;
function foo() {
    console.log(1);
}
foo = function() {
    console.log(2);
}

//结果:1

因为上述代码会被引擎理解为如下形式

function foo() {//函数声明首先会被提升
    console.log(1);
}
var foo;//接着提升变量声明
foo();//1
foo = function() {
    console.log(2);
}
示例 2
foo();
function foo() {
    console.log(1);
}
var foo = function() {
    console.log(2);
}
function foo() {
    console.log(3);
}

//结果:3

JS 中没有函数重载的概念,同名函数或变量会相互覆盖

示例 3
var foo = function() {
    console.log(1);
}
function foo() {
    console.log(2);
}
foo();

//结果:1

首先会提升函数声明,接着再提升变量声明

示例 4
foo();
var foo = 0;
function foo() {
    console.log(1);
}
foo();
foo = function() {
    console.log(2);
};
foo();

//结果:
1
TypeError: foo is not a function
示例 5
foo();
var a = true;
if (a) {
    function foo() { console.log('a'); }
} else {
    function foo() { console.log('b'); }
}

//结果:b

因为 JS 并不是以代码段为作用域,而是以函数为作用域的。所以上述代码会被引擎理解为如下形式:

function foo() {
    console.log('a');
}
function foo() {
    console.log('b');
}
foo();
var a = true;
if (a) {
	foo();
} else {
    foo();
}
示例 6
var a = 10;
function fn() {
    if (!a) {
      var a = 20
    }
    console.log(a);
}
fn();

//结果:20
示例 7
var x = foo();
var foo = function() {
   return 2;
};
console.log(x);

//结果:TypeError: foo is not a function
示例 8
var tmp = new Date();
function f() {
    console.log(tmp);
    if(false) {
     var tmp = 'hello world';
    }
}
f(); 

//结果:undefined

ES6 之前只有函数作用域和全局作用域这两个概念。所以在函数 f() 中 var tmp; 会被提升到函数顶部,此时输出的 tmp 并没有被赋值。

示例 9
var a = 10;
function fn(a, b) {
    console.log(a);
    var a = 10;
    console.log(a);
    function a() {}
    console.log(a);
}
fn(15);

//结果:
[Function: a]
10
10

变量或函数提升的顺序可以归结为一下这个规律:

函数形参声明--->函数声明---->变量声明

参考

  • https://www.jianshu.com/p/9e1e7d0abadf
  • https://blog.youkuaiyun.com/baidu_36065997/article/details/80103100
  • https://blog.youkuaiyun.com/cauchy6317/article/details/81148017
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值