JavaScript 中 自执行函数&函数类型&函数表达式各有什么特点,三者又有什么区别?

748 阅读4分钟

函数&自执行函数&函数类型

1、自执行函数特点

  1. 函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定
  2. 对于一个常量进行赋值,在 strict 模式下会报错,非 strict 模式下静默失败。
  3. IIFE 中的函数是函数表达式,而不是函数声明。

2、函数类型

  1. 函数声明
  2. 函数表达式
  3. 函数构造器创建

1. 函数声明(FD

  1. 有一个特定的名称
  2. 在源码中的位置:要么处于程序级(Program level),要么处于其它函数的主体(FunctionBody)中
  3. 在进入上下文阶段创建
  4. 影响变量对象
  5. 以下面的方式声明
function funName () {}

2. 函数表达式(FE

  1. 在源码中须出现在表达式的位置
  2. 有可选的名称
  3. 不会影响变量对象
  4. 在代码执行阶段创建
// 函数表达式
var foo = function () {} // 匿名函数表达式赋值给变量foo
var foo2 = function _foo2() {} // 外部FE通过变量“foo”来访问——foo(),而在函数内部(如递归调用),有可能使用名称“_foo”。
// 圆括号(分组操作符)内只能是表达式
(function foo() {});
// 在数组初始化器内只能是表达式
[function bar() {}];
// 逗号也只能操作表达式
1, function baz() {};
// !
!function() {}();
(function foo() {})() // 自执行函数 IIFE
(function () {})() // IIFT
var foo = {
  bar: function (x) {
    return x % 2 != 0 ? 'yes' : 'no';
  }(1)
};
foo.bar // 'yes'

3. 函数构造器创建的函数

我们将它与 FDFE 区分开来。其主要特点在于这种函数的[[Scope]]属性仅包含全局对象

var x = 10;
function foo() {
  var x = 20;
  var y = 30;
  var bar = new Function('alert(x); alert(y);');
  bar(); // 10, "y" 未定义
}

3、如何创建一个函数不需要 () 就可以执行

  1. 创建对象
  2. 对象里面表达式定义自执行函数
var foo = {
  bar: function (x) {
    return x % 2 != 0 ? 'yes' : 'no';
  }(1)
};
foo.bar // 'yes'

4、为什么有些要加 () 有些可以不加?

当函数不在表达式的位置的时候,分组操作符圆括号是必须的——也就是手工将函数转化成 FE

如果解析器知道它处理的是 FE,就没必要用圆括号。

5、具名函数表达式

当函数表达式 FE 有一个名称(称为命名函数表达式,缩写为 NFE)时,将会出现一个重要的特点。

从定义(正如我们从上面示例中看到的那样)中我们知道函数表达式不会影响一个上下文的变量对象(那样意味着既不可能通过名称在函数声明之前调用它,也不可能在声明之后调用它)。 但是,FE在递归调用中可以通过名称调用自身。

(function foo(bar) {
  if (bar) {
    return;
  }
  foo(true); // "foo" 是可用的
})();

foo” 储存在什么地方?在 foo 的活动对象中?不是,因为在 foo 中没有定义任何” foo ”。在上下文的父变量对象中创建 foo?也不是,因为按照定义—— FE 不会影响 VO (变量对象)——从外部调用 foo 我们可以实实在在的看到。那么在哪里呢?

当解释器在代码执行阶段遇到命名的 FE 时,在 FE 创建之前,它创建了辅助的特定对象,并添加到当前作用域链的最前端。然后它创建了 FE,此时(正如我们在第四章 作用域链知道的那样)函数获取了[[Scope]] 属性——创建这个函数上下文的作用域链)。此后,FE 的名称添加到特定对象上作为唯一的属性;这个属性的值是引用到 FE 上。最后一步是从父作用域链中移除那个特定的对象。

6、自执行函数示例

// 例一
+function foo(){
foo=10;//我的问题代码
    console.log(foo);//方法自己
}();
console.log(typeof foo);//undefined 观察是否全局污染

// 例二
var b = 10;
(function b() {
   // 内部作用域,会先去查找是有已有变量b的声明,有就直接赋值20,确实有了呀。发现了具名函数 function b(){},拿此b做赋值;
   // IIFE的函数无法进行赋值(内部机制,类似const定义的常量),所以无效。
  // (这里说的“内部机制”,想搞清楚,需要去查阅一些资料,弄明白IIFE在JS引擎的工作方式,堆栈存储IIFE的方式等)
    b = 20;
    console.log(b); // [Function b]
    console.log(window.b); // 10,不是20
})();

// 严格模式 会报错
var b = 10;
(function b() {
  'use strict'
  b = 20;
  console.log(b)
})() // "Uncaught TypeError: Assignment to constant variable."

// 普通函数
function a () {
    a = 1
    console.log(a)
}
a() // 1
a() // a is not a function