felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 函数的三种定义方式 #47

Open felix-cao opened 6 years ago

felix-cao commented 6 years ago

JavaScript 语言里,主要有三种方式定义函数,分别是:

下面依次来聊一聊

一、函数声明

函数声明是 JavaScript 最常见的函数定义法, 函数声明必须有如下5个要素:

下面就是一个通过函数声明的方式创建名为 person 的函数

function person(name) {
  console.log(name);
  return name;
}
person('up8');

1.1、特性一、声明提升

函数声明的一个重要特性就是提升(hoisting), 指的是整个函数体的提升, 也就是说可以在函数声明前调用函数

《JavaScript 作用域》 中 4.3 节,我们有一个案例提炼出来是这样的:

console.log('start:', a);
function a() {console.log(4)}

虽然函数 a 定义在后,但是它是函数声明,根据它的提升特性,它的整个函数体 JavaScript 引擎在编译时都会提升至 console.log('start:', a);的前面,所以它的输出结果是:输出结果是 start: ƒ a() {console.log(4)}

1.2、特性二、函数声明覆盖

如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。

function add(x,y) {
  return x+y;
}
function add(x,y,z) {
  return n=x+y+z;
}
add(2, 3); // 结果是啥?

上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升,前一次声明在任何时候都是无效的,这一点要特别注意。同样再去看看 《JavaScript 作用域》 中 4.3 节 中的代码。

二、函数表达式(function expression)

var person = function(name) {
  console.log(name);
  return name;
}
person('up8');

JavaScript 语言里, 函数也是对象,对象可以赋值给一个变量, 函数表达式是将一个匿名函数(anonymous function)赋值给一个变量,function 关键词后面没有标识符

匿名函数(anonymous function), 也叫拉姆达函数:使用 function 关键字声明一个函数,但是未给函数名,所以叫匿名函数,匿名函数属于函数表达式。 匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序,或创建闭包等等

匿名函数是函数式编程一个重要的特性,它让函数与函数之间变得很灵活。

// 下面的匿名函数会导致语法错误,虽然匿名函数术语函数表达式,但是未进行赋值操作
// 所以 JavaScript 引擎将开头的 function 关键字当做函数声明
function() {
    console.log('Hello world');
}

三、Function 构造器

这种方式在实际开发中,我们是不用的,但是这里有一个知识点和一个坑,很多面试题会碰到,所以顺带聊一聊

var person = new Function('name', 'console.log(name); return name;');

Function() 构造器可以传入任意数量的字符串实参,最后一个实参所表示的文本是函数体,可以包含任意数量的 JavaScript 语句。如果构造的函数不包含任何参数,则只需传入一个函数体即可。

与前两者方式不同的是,Function() 构造器允许 JavaScript 在运行时动态地创建并翻译函数。每次调用 Function() 构造器都会解析函数体,并创建新的函数对象。因而,在循环或多次调用的函数中执行这个构造函数,执行效率会受影响。相比之下,循环中的嵌套函数和函数定义表达式则不会每次执行时都重新编译。

var name = 'up8'; // 顶层函数
function person() {
  var name = 'up8.wang' // 局部变量
  return new Function('console.log(name)'); // 无法捕获局部作用域
}
person()(); // 控制台输出up8,说明构造函数的编译在顶层函数执行  

我们可以将 Function() 构造器认为是在全局作用域中执行的 eval()。在实际编程中,Function()构造器很少用到,前两中定义方法使用比较普遍。

四、 return 语句及其坑

在函数体内,遇到 return,函数就执行完毕,并返回结果。如果没有 return ,函数执行完毕也会返回结果,只是返回的是 undefined

我们要注意的是 return 有一个坑,在 JavaScript 中,有一个自动在行末添加分号的机制,看下面的示例:

function person() {
  return {name: 'Felix Cao'};
}
person() // {name: 'Felix Cao'};

如果我们写成这样的话:

function person() {
  return  // 自动加了分好,相当于 return undefined
  {name: 'Felix Cao'}; // 因为上面已经返回,所以这句无法执行
}
person() // undefined

Reference