【明白】一道 JS 面试题

最近在一个前端进修群里,有人抛出了这么一道 JS 面试题。

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

我一看,这不很简单吗?IIFE 部分的 foo 原本指向函数本身,但厥后被修正成 100 了,所以部分的 foo 打印 100。全局的 foo 照样保存本来的值,所以全局的 foo 打印 1。

然后我复制代码到控制台运转,发明先打印函数体 foo(){...},然后再打印 1

我猜测的第一个打印效果错了,一番查找材料终究搞懂了,因而有了这篇文章。

函数声明式 vs 函数表达式

以下示意情势的是函数声明式,简单说就是 function 前面没有任何运算符,实在就下面一种情势。

function name() {
    ...
}

以下示意情势的是函数表达式,有多种情势。

var fun = function name() {
    ...
}

// 函数前带有 + - * / () && || 等运算标记
(function name(){
    ...
}())

// 又或许
+function name(){
    ...
}()

能认出函数声明式与函数表达式后,我们来看看二者有什么区分。

函数提拔

轻微相识 JS 的都晓得变量提拔(variable hoisting),除此之外另有函数提拔(function hoisting),也就是说下面的代码是一般运转的。

foo(); // running

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

然则函数提拔只对函数声明式有用,对函数表达式不见效,下面的代码就会报错。

foo(); // Uncaught TypeError: foo is not a function

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

区分一:函数声明式会提拔函数定义,而函数表达式不提拔函数定义。这一区分只是想给人人温习知识点,并非本文的重点。

函数名绑定的作用域

先看看下面函数的示意情势,记着它有助于接下来的申明。

function BindingIdentifier (FormalParameters) { FunctionBody }

函数声明式和函数表达式的别的一个症结区分是,看函数名(BindingIdentifier)绑定到哪一个作用域下。

先看下 ECMAScript 是怎样形貌这一区分的。

The
BindingIdentifier in a
FunctionExpression can be referenced from inside the
FunctionExpression’s FunctionBody to allow the function to call itself recursively. However, unlike in a
FunctionDeclaration, the
BindingIdentifier in a
FunctionExpression cannot be referenced from and does not affect the scope enclosing the
FunctionExpression.

上面说 BindingIdentifier(函数的援用) 能够用于在函数表达式内递归挪用本身。而且函数表达式的 BindingIdentifier 只绑定在该函数内部,不污染外部的作用域,外部作用域也没法访问到 BindingIdentifier。

区分二:函数声明式的 BindingIdentifier 绑定在声明时的作用域下,函数表达式的 BindingIdentifier 绑定在函数内部的作用域下

背地的缘由

说了这么多,彷佛还没说的真正的缘由。是的,前面的内容只是铺垫,有了上面的内容,才更好邃晓背地的缘由。

诠释前先说缘由:

  • 函数表达式的函数名是不可修正的(ImmutableBinding)。但假如你真修正了,在非严厉形式下会寂静失利,在严厉形式下会报错(Uncaught TypeError: Assignment to constant variable)。
  • 函数声明式的函数名是可修正的(MutableBinding)。

缘由出自《You-Dont-Know-JS》的一个 issue,这一 issue 已被作者归入第二版(second edition)的编写中。

The production FunctionExpression : function Identifier ( FormalParameterListopt ) { FunctionBody } is evaluated as follows: … Call the CreateImmutableBinding concrete method of envRec passing the String value of Identifier as the argument. …

挪用 CreateImmutableBinding 建立 Immutable’s 函数名。

For each FunctionDeclaration f in code, in source text order do … If funcAlreadyDeclared is false, call env’s CreateMutableBinding concrete method passing fn and configurableBindings as the arguments. …

挪用 CreateMutableBinding 建立 Mutable’s 函数名。

固然也能够从 ECMAScript 范例中找到缘由:Runtime Semantics: Evaluation

至于言语为何要这么划定,我也没想邃晓,假如有晓得的同砚能够分享一下。

剖析下代码

那转头再剖析下一最先的示例,从每一行解释能够协助邃晓背地的缘由。

var foo = 1; // 在外部作用域声明foo=1

// IIFE是典范的函数表达式
(function foo(){ // 函数名foo,援用函数本身,绑定在函数内部,不污染外部作用域
    foo = 100; // 这里修正了foo,但范例划定不能修正,但不会报错
    console.log(foo); // 照样援用函数本身
}())

console.log(foo); // 外部作用域一直是1

一样的代码,当函数运转在严厉形式下,报错提醒说:“不能赋值给常量”。也就是说函数表达式的函数名被定义成常量,没法再修正了。

var foo = 1;

(function foo(){
    'use strict'; // 严厉形式
    foo = 100; // Uncaught TypeError: Assignment to constant variable
    console.log(foo);
}())

console.log(foo);

为了协助对照邃晓,下面给出了函数声明式的示例及诠释,下面的代码不管在非严厉形式照样严厉形式下都打印100,也就是说函数声明式的函数名能够被修正。

// foo是函数声明式
function foo(){ // 函数名foo,援用函数本身,绑定在声明时的作用域下
    foo = 100; // 修正了foo,函数声明式内能够从新修正函数名
    console.log(foo); // 100
}
foo();

假如在函数表达式内运用 var foo = 100; 来从新声明变量,那这个变量就不是不可修正的(ImmutableBinding),所以内部的 foo 打印 100。

var foo = 1;

(function foo(){
    var foo = 100; // 从新声明变量
    console.log(foo); // 100
}())

console.log(foo); // 1

经由过程上面的剖析诠释,愿望你能够控制这道面试题,闻一知十。

假如你喜好这篇文章,请关注我,我会延续输出更多原创且高质量的内容。

原文链接:【邃晓】一道 JS 面试题

    原文作者:士心
    原文地址: https://segmentfault.com/a/1190000018916381
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞