资讯专栏INFORMATION COLUMN

一道颇有难度的JavaScript题

alexnevsky / 1325人阅读

摘要:所以,这次再分享一道稍微有难度的题目。首先运行全局的被覆盖成输出并且返回的此时代表的是。随后相当于执行的那么输出的实际就是被覆盖。首先带参数的操作符优先级最高,第一步划分为第二步划分为所以执行这个函数是对应的所以执行肯定输出的是。

  上次分享了一道题,大家反响不错,很开心自己写的东西有人愿意花时间去看,也给了自己莫大的鼓舞,其实做题虽然不比真正的编程,但是也能够让你发现一些你之前没有注意到的语言层面的问题。所以,这次再分享一道稍微有难度的JavaScript题目。

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();        

   请问上述代码在浏览器环境下,输出结果是多少?
   揭晓一下最终答案:

2 4 1 1 2 3 3

  前四道难度不是很大,主要是后三道,基本是全军覆没,感叹实在是太绕了了。后面慢慢分析了一下,逐个讲一下吧。
  首先必须注意一个问题

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

  在函数内部声明的getName变量,前面是不带有varlet,const的,所以其实根据LHS(这个的介绍可以去的我博客看一下关于LHS和RHS的总结),声明的getName是在全局范围内(也是就window)。
  其次需要明确你是否知道下面代码在浏览器中的执行结果:

var getName = function () { 
    console.log("4");
};
function getName() { 
    console.log(5);
}
getName();

  上述代码的执行结果是:4。原因是这样的,var声明的变量和函数声明function都会被提升,但是函数声明的提升的级别是比
var要高的,所以上面的代码的实际执行结果是:

function getName() { 
    console.log(5);
}
var getName = function () { 
    console.log("4");
};
getName();

  后一个函数表达式getName覆盖了前面的函数声明getName,实际执行的是函数表达式(也就是是为什么JavaScript永远不会有函数重载这么一说了),所以输出的是4
  首先我给下面的代码添加一下必要的注释:

//函数声明
function Foo() {
    //全局变量
    getName = function () { 
        console.log("1");
    };
    return this;
}
//为函数添加属性getName,其类型是Function,所以这里也可以看出来,Function也是一种Object
Foo.getName = function () {
    console.log("2");
};
//为Foo的原型添加方法getName
Foo.prototype.getName = function () { 
    console.log("3");
};
var getName = function () { 
    console.log("4");
};
function getName() { 
    console.log(5);
}

  下面执行第一条语句:

Foo.getName();  

  函数Foo本身并没有执行,执行的是函数的属性getName,当然输出的是:2.
  接下来执行:

getName();    

  这是在全局范围内执行了getName(),有两条对应的getName的声明,根据前面我们所提到的提升的级别来看实际执行是函数表达式:

var getName = function () { 
    console.log("4");
};

  所以输出的是4
  接下来执行

Foo().getName(); 

首先看一下JavaScript的操作符优先级,从高到低排序

  从上面可以看出来().优先级相同,所以Foo().getName()从左至右执行。首先运行Foo(),全局的getName被覆盖成输出console.log("1"),并且返回的this此时代表的是window。随后相当于执行的window.getName(),那么输出的实际就是1(被覆盖)。
  下面到了

getName();  

  这个不用说了,执行的还是:1(和上面一毛一样)。
  下面到了三个最难的部分:

new Foo.getName();

对于这条语句的执行,有两种可能:

(new Foo).getName()

new (Foo.getName)()

  但是我们根据操作符优先级表可以得知,其实上.操作符要比new优先级要高,所以实际执行的是第二种,所以是对

Foo.getName = function () {
    console.log("2");
};

函数执行了new操作,当然输出的是2
下面到了执行

new Foo().getName();   

  这个语句的可能性也有两种:

(new Foo()).getName();

或者

new (Foo().getName)();

  那么应该是那种的呢?原来我以为会是第二种的执行方式,后面通过浏览器调试发现真实的执行的方式是第一种。我看到题目的作者是这么解释的:

首先看运算符优先级括号高于new。实际执行为(new Foo()).getName()。遂先执行Foo函数。

  我觉得上面的解释是有问题的,对比上面两种执行方式,第一种是先执行new,然后执行的是.操作符,然后执行的是()。第二种是先执行了(),再执行的是.,最后执行new操作符。如果真的按照引用所说的用优先级的方式判别,其实恰恰应该执行的是第二种而不是第一种。
  后来总算找到原因了,原来之前那个出现的比较多的JavaScript优先级的表并不完整,万能的MDN给出了最权威的JavaScript优先级表运算符优先级
  我列举出最重要的部分(由高到低):
  
  所以带参数的new操作符是优先级最高的,这下就没有问题了,执行顺序确实应该是第一种。
  那么按照(new Foo()).getName();来执行,情况就就很简单了,(new Foo())返回了新生成的对象,该对象没有getName()方法,所以在prototype中找到了getName()方法。所以输出的是3
  胜利就在眼前,我们看一下最后一问。

new new Foo().getName();        

  和上一步一样的方法,我们按照优先级表给分析一下这个语句到底是怎么执行的。
  首先带参数的new操作符优先级最高,第一步划分为:

new (new Foo().getName)();

  第二步划分为:

new ((new Foo()).getName)();

  所以执行(new Foo()).getName这个函数是对应的Foo.prototype.getName,所以执行new (Foo.prototype.getName)()肯定输出的是3
  哈哈哈,这么难得题终于解决了,开心~总结一下吧,首先JavaScript知识最好去MDN去查,万一别的地方写错了真的是害人不浅。其次,如果在写代码的时候还是少利用操作符优先级这种东西,一旦不明确的地方就立刻用(),代码的可阅读性真的是很重要!很重要!很重要!毕竟代码还是给人看~
  如果有写的不正确的地方,欢迎大家指出,资历深浅,请多指教。欢迎大家去围观我的博客呀~~http://mrerhu.github.io
  

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86736.html

相关文章

  • 一道面试谈谈函数柯里化(Currying)

    摘要:忍者秘籍一书中,对于柯里化的定义如下在一个函数中首先填充几个参数然后再返回一个新函数的技术称为柯里化。回到我们的题目本身,其实根据测试用例我们可以发现,函数的要求就是接受单一函数,例如但是与柯里化不同之处在于,柯里化返回的一个新函数。   欢迎大家再一次来到我的文章专栏:从面试题中我们能学到什么,各位同行小伙伴是否已经开始了悠闲的春节假期呢?在这里提前祝大家鸡年大吉吧~哈哈,之前有人说...

    cppprimer 评论0 收藏0
  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函数式编程前端掘金引言面向对象编程一直以来都是中的主导范式。函数式编程是一种强调减少对程序外部状态产生改变的方式。 JavaScript 函数式编程 - 前端 - 掘金引言 面向对象编程一直以来都是JavaScript中的主导范式。JavaScript作为一门多范式编程语言,然而,近几年,函数式编程越来越多得受到开发者的青睐。函数式编程是一种强调减少对程序外部状态产生改变的方式。因此,...

    cfanr 评论0 收藏0
  • 从简历被拒到收割今日头条 offer,我用一年时间破茧成蝶!

    摘要:正如我标题所说,简历被拒。看了我简历之后说头条竞争激烈,我背景不够,点到为止。。三准备面试其实从三月份投递简历开始准备面试到四月份收,也不过个月的时间,但这都是建立在我过去一年的积累啊。 本文是 无精疯 同学投稿的面试经历 关注微信公众号:进击的java程序员K,即可获取最新BAT面试资料一份 在此感谢 无精疯 同学的分享 目录: 印象中的头条 面试背景 准备面试 ...

    tracymac7 评论0 收藏0
  • 从简历被拒到收割今日头条 offer,我用一年时间破茧成蝶!

    摘要:正如我标题所说,简历被拒。看了我简历之后说头条竞争激烈,我背景不够,点到为止。。三准备面试其实从三月份投递简历开始准备面试到四月份收,也不过个月的时间,但这都是建立在我过去一年的积累啊。 本文是 无精疯 同学投稿的面试经历 关注微信公众号:进击的java程序员K,即可获取最新BAT面试资料一份 在此感谢 无精疯 同学的分享目录:印象中的头条面试背景准备面试头条一面(Java+项目)头条...

    wdzgege 评论0 收藏0
  • LeetCode 攻略 - 2019 年 7 月上半月汇总(55 攻略)

    摘要:微信公众号记录截图记录截图目前关于这块算法与数据结构的安排前。已攻略返回目录目前已攻略篇文章。会根据题解以及留言内容,进行补充,并添加上提供题解的小伙伴的昵称和地址。本许可协议授权之外的使用权限可以从处获得。 Create by jsliang on 2019-07-15 11:54:45 Recently revised in 2019-07-15 15:25:25 一 目录 不...

    warmcheng 评论0 收藏0

发表评论

0条评论

alexnevsky

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<