90%口试都不会问的题,由于...

把话说完: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!!!!!!?????? 我示意很忧郁

然后我果断去调研了一番,下面也许总结一下~
针对这题实在要邃晓两点:

  1. 关于对象赋值,通报的都是援用,都是援用挪用
  2. 关于赋值语句,老是先对lhs求值,再对rhs求值,然后PutValue。

能够参考一下ECMAScript规范,下面来看一下上面代码的实行。

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

《90%口试都不会问的题,由于...》

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

第一步,起首对 foo.x 举行求值,foo 指向的是对象 { n: 2 }(下面称为:ObjectF ), ObjectF 没有属性 x ,那末为 ObjectF 增加属性x,左值的求值效果就是对适才增加的属性 x 的援用(某个内存地址X)。
《90%口试都不会问的题,由于...》

第二步, 对右值举行求值,右值是 foo = {n : 2}。递归向下,先对左值求值,获得 foo,foo 照样 ObjectF 援用,然后对右值{a : 2}求值,获得 ObjectE ,接着PutValue将转变 foo 的指向到 ObjectE,赋值表达式foo = {n : 2}返回获得 ObjectE援用。
《90%口试都不会问的题,由于...》
这个时刻 foo 和 ObjectF 已解绑,而且从新指向了 ObjectE,ObjectE上没有 x 这个属性,所以 foo.x 这个时刻是undefined。

第三步, PutValue将左值指向 ObjectE,也就是说第一步中的内存地址X存的是ObjectE的援用。
《90%口试都不会问的题,由于...》

到这里全部赋值历程就完成了。

第二个题目:

// 题目:下面两题的效果是?
(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);

这类奇异的代码照样只管不要写呀!

如果有明白毛病的处所,迎接斧正!

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