likes
comments
collection
share

面试问:函数与函数表达式 立即调用函数表达式怎么理解?

作者站长头像
站长
· 阅读数 26

函数与函数表达式 立即调用函数表达式

函数表达式的用处

  1. 实现代码的隐藏(代码封装)
  2. 还可以实现变量隔离(立即执行函数)

函数声明 与 函数表达式的差异

foo()
function foo() {
  console.log('foo')
}

上面的代码是声明了一个函数,然后调用了foo函数,代码正确执行了。(这里会好奇,代码不是至上而下执行的么,为什么函数在声明之前就可以调用该函数了呢?) 再看下面代码

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

这是函数表达式,这段代码执行会报错

VM130:1 Uncaught TypeError: foo is not a function
    at <anonymous>:1:1

为什么上面报错是: foo变量不是一个函数呢,接着往下看

大体可以这么说,因为这两种定义函数的方式具有不同的语义,不同的语义出发了不同的行为。第一个是函数声明,第二个是函数表达式 函数声明v8有不同语义处理,函数表达式v8有另一种处理,具体是怎么处理的我们接着往下看

v8处理函数声明

v8在执行js的过程中,会即时编译代码,然后在执行

var x = 5
function foo(){
    console.log('Foo')
}

v8执行这段代码大致流程是如下图 面试问:函数与函数表达式 立即调用函数表达式怎么理解? 上图输入一段代码,进过js解析器解析成 AST(抽象语法树)和 作用域,在编译阶段,如果解析到函数声明,那么v8会将这个函数转换为内存中 的函数对象并将其放到作用域中。同样,如果解析到了某个变量声明,也会将其放到作用域中,但是会将其设置为undefined,表示该变量还未被使用

在v8执行阶段,如果使用了某个变量,或者调用了某个函数,那么V8便会去作用域查找相关内容

所有的变量都会被v8在编译阶段被装进作用域中,基础类型最开始初始化为undefined 函数声明会将对象直接放到作用域中

在编译阶段,将所有的变量提升到作用域的过程称为变量提升

理解了变量提升,就可以解释函数声明之前调用该函数仍然可以正确执行,因为函数在编译阶段就被提升到作用域中,在执行阶段,只要是在作用域中的存在的变量或者对象,都是可以使用的 我们思考,为什么普通变量会初始值是undefing,而声明的对象在变量提升后的值则是函数对象。为什么呢,接下来就要讲表达式和语句的区别了

表达式,语句,函数(即对象)

首先简单的理解,表达式就是表示值得式子(可以表示一个值),而语句是操作值得式子

x = 5 //这就是一个表达式 这行代码执行后就会返回一个值 
var x // 这就是一个语句,v8不会返回任何值给你

举一反三,当我声明了一个函数,这个函数也是一个语句,比如下面这个函数声明

function foo(){ return 1} // 当v8执行这行代码时,它只是解析foo函数,并将函数对象存储在堆内存当中,放到作用域当中

表达式 执行完会返回一个值,语句返回时 不会返回任何值.记住这个再往下看 v8 执行这行var x = 5代码时候,会认为它是两行代码,一段是定义变量的语句,一段是赋值表达式,

var x = undefined
x = 5

关键地方到了,在变量提升阶段,V8并不会执行赋值表达式,该阶段只会分析基础的语句,比如变量定义,函数声明。

上面两行代码是在不同阶段完成的 var x 是在编译阶段执行的,也可以说是在变量提升阶段完成的,而 x = 5 是表达式,所有的表达式都是在执行阶段完成的。

在变量提升阶段,v8还会给变量赋一个默认值undefined,所以在定义一个普通变量之前,使用该变量,那么该变量值就是 undefined 表达式是不会再编译阶段执行的,那么函数声明执行没有任何输出内容,所以函数声明是一个语句 v8在变量提升阶段,如果遇上了函数声明,那么v8同样会对函数声明执行变量提升操作。这时候会将整个函数对象提升到作用域中

所以结果就是: V8在解析javaScript源码过程中,如果遇上普通变量声明,会将其提升到作用域中并且赋默认值undefined,如果遇上函数声明,那么v8会在内存中为声明成函数对象,并将对象提升到作用域中。 如果在这个阶段,就调用了函数 或者使用变量,那么拿到的是编译阶段作用域中的值。 面试问:函数与函数表达式 立即调用函数表达式怎么理解? 说了一通,应该理解了函数声明V8的执行,也知道在函数之前调用该函数也可以执行的道理了吧

关键字:编译阶段 作用域 变量提升 表达式 语句 函数声明 普通变量

函数表达式

在表达式中,使用function来顶一个函数,那么就把该表达式成为函数表达式

  • 函数表达式是在表达式语句中使用function的,最典型的表达式是“a=b”这种形式,因为函数也是一个对象,也是一等公民,可以用来赋值 我们把 a = function() {}这种方式称为函数表达式

  • 函数表达式中,可以省略函数名称,从而创建匿名函数

  • 一个函数表达式可以被作为一个即时调用的函数表达式-- IIFE

foo()
var foo = function (){ console.log('foo')}

v8解析后

var foo = undefined // 语句
foo = function (){ console.log('foo')} // 表达式

在上面知道表达式和v8解析js的过程就很容易知道 为什么这段代码会报foo不是一个方法了 因为在编译阶段,v8是不会去处理函数表达式的,所有也就不会将函数表达式提升到作用域中了。 所以只编译阶段就调用了foo 这时候的foo就是undefined 而undefined是一个原生对象并不是函数所以就会报错

这里额外掌握一个知识,立即调用的函数表达式(IIFE)

js有一个圆括号运算符,规定圆括号里面可以放一个表达式

(a=3) // 这整个语句就是一个表达式,最终输出三

举一反三,如果在括号里面放上一段函数的定义,如下图所示

(function () { //statements})

因为小括号里面必须是表达式,如果小括号里面定义一个函数,那么V8就会把这个函数看成是函数表达式,执行时返回一个函数对象. 如果我们直接在表达是后面加上调用的括号,就称为立即调用函数表达式

(function(){})()

这里我们就知道了,立即调用函数表达式也是一个表达式,所以在V8编译阶段,并不会为该表达式创建函数对象,这样就可以函数和函数内部的变量都不会被其他部分的代码访问到,就不会污染变量环境

在ES6之前,javaScript没有模块和私有作用域的概念,在多人开发项目中,你的模块的变量可能覆盖掉别人的变量,所以立即表达式就可以将我们内部变量封装起来,避免了相互之间的变量污染 20200330学习笔记 参考:

李兵《图解googleV8》

转载自:https://juejin.cn/post/6844904110110539789
评论
请登录