第24题:一道关于new运算符、变量提升的经典题

题目:
		function Foo() {
            getName = function() {
                console.log(1);
            };
            return this;
        }
        Foo.getName = function() {
            console.log(2);
        };
        Foo.prototype.getName = function() {
            console.log(3);
        };
        var getName = function() {
            console.log(4);
        };
        function getName() {
            console.log(5);
        }

        //请写出以下输出结果:
        Foo.getName();
        getName();
        Foo().getName();
        getName();
        new Foo.getName();
        new Foo().getName();
        new new Foo().getName();
解析:

首先分析代码上半部分,先定义了一个Foo函数;再Foo函数添加了一个静态属性getName,并且赋值一个匿名函数;接着给Foo函数的原型创建了一个getName的静态属性,并且赋值一个匿名函数;最后分别以函数变量表达式函数声明的形式定义getName函数。

第一问

Foo.getName():
直接调用Foo上的getName函数,没什么好说的,那就是2.

第二问

getName():
这接调用getName()函数,这里涉及到变量提升的问题,从上面的代码中可以看到getName函数有两种方式定义,一种是函数变量表达式,一种函数声明。并且变量声明会提前。相当于下面的代码:

function Foo() {
    getName = function () { console.log(1); };
    return this;
}
var getName;//只提升变量声明
function getName() { console.log (5);}//提升函数声明,覆盖var的声明

Foo.getName = function () { console.log (2);};
Foo.prototype.getName = function () { console.log (3);};
getName = function () { console.log (4);};//最终的赋值再次覆盖function getName声明

getName();//最终输出4

最终的getName()还是输出4。

第三问

Foo().getName()
第一步: 执行Foo(),看一看Foo里边有什么:

	function Foo() {
            getName = function() {
                console.log(1);
            };
            return this;
        }

第一句:将一个匿名函数赋值给getName变量,注意的是:getName变量没有用var声明,现在当前Foo函数作用域内找getName变量,没有;接着到该函数作用域的上层找,找到了,即var getName = function () { alert (4);}。 那么执行Foo()时,将全局作用域的getName重新赋值为 function(){console.log(1)},并且返回this这个对象。

第二步:可以理解为this.getName():
关于this的问题,因为这里是直接调用,相当于window.getName(),即这里的this指向window。而第一步执行Foo()时,就将全局的getName()重新赋值,所以最后的结果为1.

第四问

getName():
即直接调用全局的getName(),即于上一问中的一样,返回的是1.

第五问

new Foo.getName():
这里就涉及到运算符的优先级问题了,先看一张图:
在这里插入图片描述

可以看到成员访问的优先级大于new运算符,即可以表示为:
new (Foo.getName) (), 可以理解为以Foo.getName为构造函数来执行。这里的Foo.getName()很容易看出来,就是Foo的静态属性。

回顾调用new的过程

  • 新生成了一个对象
  • 获得构造函数
  • 链接到原型
  • 绑定 this,执行构造函数
  • 返回新对象
 function create() {
    // 创建一个空对象
    let obj = new Object();
    // 获得构造函数, arguments中去除第一个参数(第一个参数为构造函数)
    let Con = [].shift.call(arguments);
    // 链接到原型
    obj.__proto__ = Con.prototype;
    // 绑定this,执行构造函数
    let result = Con.apply(obj, arguments);
    // 优先返回构造函数返回的对象
    return typeof result === 'object' ? result : obj;
}

深入理解点击:深入理解new的原理与模拟实现

回到本题,new (Foo.getName) ()返回一个对象,并且执行了Foo.getName的构造函数,所以返回2.

第六问

new Foo().getName(): 即(new Foo()).getName ()。

  • 先以Foo为构造函数执行,返回一个对象,再执行这个对象的getName()。
  • Foo函数返回一个this,而this在构造函数中本来就代表当前实例化对象,所以new Foo()返回一个实例化对象 。
  • 接着执行这个对象的getName方法,通过原型链找到了 Foo 上的 getName 函数,所以结果为 3
第七问

new new Foo().getName(): 即 new( (new Foo()).getName) )()

  • 这里就是在第六问的基础上增加了一个new,是以Foo上的getName函数为构造函数,执行并且返回一个对象。
  • 最终结果为3

参考链接:一道常被人轻视的前端Js面试题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值