Javascript笔试题一则,为什么输出结果是这样?

一段代码及输出如下:

b = c;

b();
console.log(a);    //1
console.log(b);    //2
console.log(c);    //3

function c() {
    a = 1, b = 2, c = 3;
};

将上述代码稍作修改:

b = function c() {
    a = 1, b = 2, c = 3;
};

b();
console.log(a);    //1
console.log(b);    //2
console.log(c);    //Uncaught ReferenceError: c is not defined

再次将上述代码稍作修改:

b = function c() {
    a = 1, b = 2, c = 3;
    console.log(a);    //1
    console.log(b);    //2
    console.log(c);    //fuction c(){...
};
b();

不知所以然,如何才能正确解释上述三段代码中的变量c

阅读 7.2k
7 个回答

众所周知,JS的变量和函数声明都会被存储到执行上下文的变量对象(或活动对象)中,即声明提前。

函数声明 的优先级 高于 变量声明的优先级,但 不会 覆盖变量赋值。

对于 var bar = function foo(){}; 语句,其实就是一个有效的命名函数表达式,但有一点需要记住:这个名字只在新定义的函数作用域内有效,因为规范规定了标示符不能在外围的作用域内有效:

var f = function foo(){
    return typeof foo; // foo是在内部作用域内有效
};
// foo在外部用于是不可见的
typeof foo; // "undefined"
f(); // "function"

记住一点: 命名函数表达式的标示符(即函数名称)在外部作用域是无效的

var bar = function foo(){}; // 命名函数表达式,因为它是赋值表达式的一部分

参考:深入理解JavaScript系列(2):揭秘命名函数表达式

上面问题主要就是,命名函数表达式的问题

命名函数表达式

var foo = function bar(){};

规范规定:标示符不能在函数体外的作用域内有效
所以,bar只在定义的函数作用域内有效,也不能被覆盖

b = function c() {
    a = 1, b = 2, c = 3;
    console.log(typeof c); // function
};

函数声明

函数声明不存在作用域的问题,但是为污染函数定义

b = c;

b();
console.log(a);//1
console.log(b);//2
console.log(c);//3

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

function c() {
    a = 1, b = 2, c = 3;
};

第一个,是变量声明提升和变量作用域。
函数c的声明会被自动提到最前。
http://segmentfault.com/q/1010000003843499/a-1020000003843572
在函数中,一个变量定义不加var是全局变量,所以函数执行之后会覆盖初始值。也就是在函数中那几个变量是全局变量。

第二个,是 函数声明和函数表达式 的区别
参考 http://segmentfault.com/q/1010000003864900/a-1020000003866303

第三个,如上,在词法记录器中定义的那个 c 是只读变量,可以被重新定义,但是不能被重新赋值。也就是说这个c是只读的。当然你也可以重新声明一个 c。当然这c与原本那个就没有关系了。

第一段代码

b = c;

b();
console.log(a);    //1
console.log(b);    //2
console.log(c);    //3

function c() {
    a = 1, b = 2, c = 3;
}    // note:函数声明后面没有分号

要理解这段代码要知道三点

  • 不用 var 声明的变量为全局属性

  • 变量声明和函数声明会提前

  • 同名的变量声明不会覆盖函数声明(注意是声明不是赋值)

知道了这三个后,其实上面的代码真正的运行情况是这样的

// 函数声明提前
function c() {
    a = 1, b = 2, c = 3;
}

b = c;    // 这时不用 `var` 声明的 `b` 是全局属性,值为 `c` 函数
b();    // 执行 `b` 函数
// 此时 `a` 为全局属性,值为1 
// `b` 原来为函数,此时为 `b` 赋值,值为 2 (弱类型)
// `c` 原来为函数,此时为 `c` 赋值,值为 3 (同上)
console.log(a);    // 1
console.log(b);    // 2
console.log(c);    // 3

第二段代码

b = function c() {
    a = 1, b = 2, c = 3;
};

b();
console.log(a);    //1
console.log(b);    //2
console.log(c);    //Uncaught ReferenceError: c is not defined

这个和上面的类似,其实就是值的覆盖,但是有问题的是为什么 c is not defined

当我们在表达式的位置写函数的时候,不管函数是否有函数名,都为函数表达式,函数表达式若有函数名,该名字不可以在外部使用,只能在内部使用

当函数表达式有函数名,并且执行的时候,函数里就会多出一个特殊的对象,这个对象只有一个属性,属性名为该函数名,值为对这个函数的引用,所以才能在自身调用 详情看这里

所以当 b 执行的时候,a 为全局属性, b 为全局属性,c是对自身函数的引用,然而不能对 c 进行修改(详情看评论),所以 c 还是局部变量,不能在外部访问,所以报错 - 谢谢@FE的指出

第三段代码

第三段代码和第一段一样,因为 c 已经赋值为 3 typeof c = 'number' 所以就报 c is not a function

这个考的应该是具名函数表达式。

console.log(bar1, bar2); // undefined

// 一般的函数表达式
var bar1 = function () {};
console.log(bar1.name); // ''

// 具名函数表达式
var bar2 = function foo () {};
console.log(bar2.name); // 'foo'

console.log(foo); // Uncaught ReferenceError: foo is not defined

依我粗鄙的理解:
具名函数表达式和一般的函数表达式最大的区别是 在具名函数内部可以同过标识符foo(函数名)访问到函数自身。

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

bar(); // function
console.log(typeof foo); // undefined

那么第二段代码容易理解了

b = function c() {
    // 函数作用域内存在标识符 c (函数自身)
    a = 1, b = 2, c = 3; // 3没有赋值给 window.c
};

b();
console.log(a);    //1
console.log(b);    //2
console.log(c);    //Uncaught ReferenceError: c is not defined

第三段函数内 c 的值没有覆盖,我就无法解释原因了

第二段代码,和第三段代码 c 为啥没有被全局变量覆盖?

这个问题可以了解一下, 深究就没什么必要了.
只要命名的时候加个var, 就能避免很多乱七八糟的问题

推荐问题
宣传栏