资讯专栏INFORMATION COLUMN

90%面试都不会问的题,因为...

church / 747人阅读

摘要:函数的形参和函数体就是两个不同的作用域。这里只声明了还是形参这里改变了形参的值,所以返回是这题还有一个坑点,我拿到里面去转一下得到的结果是这里结果是这种神奇的代码还是尽量不要写呀如果有理解错误的地方,欢迎指正

把话说完:90%面试官都不会问的题,因为面试官也不一定懂。

直接来看一看今天要说的题目:

// 问题:foo.x的值是什么?bar.x?
var foo = { n: 1 };
var bar = foo;
foo.x = foo = { n: 2 };
console.log(foo.x) // ?
console.log(bar.x) // ? 

// 问题:下面两题的结果是?

(function(x, f = () => x) {
  var x;
  var y = x;
  x = 2;
  return [x, y, f()]; // ?
})(1)

(function(x, f = () => x) {
  var y = x;
  x = 2;
  return [x, y, f()]; // ?
})(1)

这两题是我最近在一个讨论群里看到的,发出来的时候还是引起了大家非常热烈的讨论。不过大家最后都觉得这种题目没有什么意义,实际做项目的时候不会也不建议这么写(ps: 我要是在项目发现谁这样写,直接从三楼丢下去),不过从学习的角度其实还是可以研究一下的。

第一个问题:
// 问题:foo.x的值是什么?bar.x?
var foo = { n: 1 };
var bar = foo;
foo.x = foo = { n: 2 };
console.log(foo.x) // ?
console.log(bar.x) // ? 

我一看到这个问题,第一个反应的结果是:

foo.x = {n: 2};
bar.x = {n: 2};

当时我的内心独白是这样的: So easy !! 这种题也有什么好问的!
然而结果是:

bar.x = {n: 2};
foo.x = undefined;

Why!!!!!!?????? 我表示很郁闷

然后我果断去调研了一番,下面大概总结一下~
针对这题其实要明白两点:

对于对象赋值,传递的都是引用,都是引用调用

对于赋值语句,总是先对lhs求值,再对rhs求值,然后PutValue。

可以参考一下ECMAScript标准,下面来看一下上面代码的执行。

1.第一第二行代码很简单,就是把一个对象({n: 2})赋给 foo, 然后通过 foo 再把对象赋值给 bar。这时候 bar 和 foo 存的都是对象 {n: 2} 的引用。

2.接下来重点看 foo.x = foo = { n: 2 }。我们就按照 [ 对于赋值语句,总是先对lhs求值,再对rhs求值,然后PutValue。 ] 来解析这行代码。

第一步,首先对 foo.x 进行求值,foo 指向的是对象 { n: 2 }(下面称为:ObjectF ), ObjectF 没有属性 x ,那么为 ObjectF 添加属性x,左值的求值结果就是对刚才添加的属性 x 的引用(某个内存地址X)。

第二步, 对右值进行求值,右值是 foo = {n : 2}。递归向下,先对左值求值,得到 foo,foo 还是 ObjectF 引用,然后对右值{a : 2}求值,得到 ObjectE ,接着PutValue将改变 foo 的指向到 ObjectE,赋值表达式foo = {n : 2}返回得到 ObjectE引用。

这个时候 foo 和 ObjectF 已经解绑,而且重新指向了 ObjectE,ObjectE上没有 x 这个属性,所以 foo.x 这个时候是undefined。

第三步, PutValue将左值指向 ObjectE,也就是说第一步中的内存地址X存的是ObjectE的引用。

到这里整个赋值过程就完成了。

第二个问题:
// 问题:下面两题的结果是?
(function(x, f = () => x) {
    var x;
    var y = x;
    x = 2;
    return [x, y, f()]; // [2, 1, 1]
})(1)

(function(x, f = () => x) {
    var y = x;
    x = 2;
    return [x, y, f()]; // [2, 2, 1]
})(1)

对于这个问题,第二个函数相信大家都不会有啥疑问。应该集中在第一个上。

要理解这题也需要明白两个点:

1.函数体内和函数体外是两个不同的命名空间或者说作用域,函数体外的作用域是不能访问函数体内的变量的。函数的形参(x, f) 和 函数体 { } 就是两个不同的作用域。

(function(a, f = () => x) {
    var x = 2;
    return [ a, f()];
})(1) // Uncaught ReferenceError: x is not defined

2.函数中的默认参数可用于后面的默认参数(已经遇到的参数可用于以后的默认参数)

怎么理解 【函数中的默认参数可用于后面的默认参数(已经遇到的参数可用于以后的默认参数)】,看下面的例子:

function singularAutoPlural(singular, plural = singular+"s", rallyingCry = plural + " ATTACK!!!") {
    return [singular, plural, rallyingCry ]; 
}

//["Gecko","Geckos", "Geckos ATTACK!!!"]
singularAutoPlural("Gecko");

//["Fox","Foxes", "Foxes ATTACK!!!"]
singularAutoPlural("Fox","Foxes");

//["Deer", "Deer", "Deer ... change."]
singularAutoPlural("Deer", "Deer", "Deer peaceably and respectfully
   petition the government for positive change.")

Demo来自MDN

看懂了这个,接下来就直接来解释一下这个题目~

(function(x, f = () => x) { // 首先这里给参数 f 默认赋值了一个匿名函数,根据我们之前说的第二个知识点这里的 x 就是形参 x。由于作用域的关系 函数f 是不能访问到函数内的 x 的。
    var x; // !!! 注意,这里进行了变量声明,会分配新的内存地址。但是因为只进行了声明而没有赋值,所以在作用域链还会找到 形参x
    var y = x; // 这里 y 的值取的还是形参 x 的值
    x = 2; // 这里 对上面的 var x 进行赋值而形参x 的值是不受影响的(console.log(arguments[0])试一下, 所以 f()  返回是1),此时作用域链上会先找到函数内声明的 x。
    return [x, y, f()]; // [2, 1, 1]
})(1)

(function(x, f = () => x) {
    var y = x; // 这里只声明了y, x 还是形参x
    x = 2; // 这里改变了形参x的值,所以 f() 返回是 2
    return [x, y, f()]; // [2, 1, 2]
})(1)

这题还有一个坑点,我拿到babel里面去转一下得到的结果是

"use strict";

(function (x) {
   var f = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
       return x;
   };

   var x;
   var y = x;
   x = 2;
   return [x, y, f()]; // !!!  这里结果是 [2, 1, 2]
})(1);

这种神奇的代码还是尽量不要写呀!

如果有理解错误的地方,欢迎指正!

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

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

相关文章

  • 金三银四背后,一个 Android 程序员的面试心得

    摘要:到十二月份,公司开始第二波裁员,我决定主动拿赔偿走人。加一个小插曲上面的题是饿了吗面试问到的。想去的公司没有面试好,不要气馁,继续加油准备。避免打击自信心。 回顾一下自己这段时间的经历,九月份的时候,公司通知了裁员,我匆匆忙忙地出去面了几家,但最终都没有拿到offer,我感觉今年的寒冬有点冷。到十二月份,公司开始第二波裁员,我决定主动拿赔偿走人。后续的面试过程我做了一些准备,基本都能走...

    Achilles 评论0 收藏0
  • 寒冬一年经验前端面试总结

    摘要:不过幸运的是所有面试的公司都给了,在这里总结下经验吧。这里推荐下我当时看的一篇的面经,木易杨老师写的大厂高级前端面试题汇总。 前言 本人毕业一年,最近陆续面试了头条、瓜子、360、猿辅导、中信银行、老虎等公司,由于最近比较寒冬而且招1-3年的并不多,再加上自己对公司规模和位置有一定要求,所以最后合适的也就这几家了。不过幸运的是所有面试的公司都给了offer,在这里总结下经验吧。掘金:h...

    Scott 评论0 收藏0
  • 记一次百度的二面的面试

    摘要:闭包加作用域问题打印结果是函数声明了两次,有一次覆盖,最后的覆盖了前面的,要是只声明一遍,那么打印的就是作用域问题打印反直觉自带坑的题找鼠标最近的标签链接最美的不是下雨天是你链接最美的不是链接最美的不是下雨链接最美的不是下雨天链接最美的不 1.闭包加作用域问题 let test let a = ()=>{ let n=99 test = ()=>{ n...

    senntyou 评论0 收藏0
  • 结束了我短暂的秋招,说点自己的感受

    摘要:总体来说,玄武科技的真的很热情,为他们点个赞,虽然自己最后没能去玄武科技,然后就是技术面非常简单,面和高管面也都还好,不会有压抑的感觉,总体聊得很愉快。 该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb... 秋招历程流水账总结 笔主大四准毕业生,在秋招末流比较幸运地进入了一家...

    KoreyLee 评论0 收藏0

发表评论

0条评论

church

|高级讲师

TA的文章

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