一道简单的题目,控制台会输出什么?

点击上方 前端瓶子君,关注公众号

回复算法,加入前端编程面试算法每日一题群

仔细看下,内容如图,你觉得会输出什么?

不要再想着直接复制粘贴到浏览器运行了[狗头]

919566f6304c0009c665da43f78163ba.png
image.png

答案是

1、undefined, funtion arg(){}
2、undefined
3、108
4、undefind
5、我是熊大,年龄77
6、我是熊大,年龄188
7、我是熊大, 年龄88
复制代码

why?听我娓娓道来

第一条:demo(18)执行 触发console.log(obj1, arg)

答案undefined, funtion arg(){}

  • obj1 >>> undefined 这里可能大部分人都知道,因为函数的变量提升

  • arg >>> function 是为啥呢?为啥不是 18

这里要引出第一个知识点:预编译

预编译

运行函数demo的前一刻发生了预编译环节

  • 第一步,生创建AO(Activation Object)对象:可以理解为demo这个函数拥有的一个冰箱,在执行内部代码时就通过这个冰箱来取东西(变量,函数...)

  • 第二步,将形参和变量声明当作AO的属性名,值为undefined

  • 第三步,将形参和实参相统一。

  • 第四步,在函数体里找函数声明,将值赋为函数体

实参18 其实是在第三步。而第四步又被函数体所覆盖。所以第一条arg是function...

第二条 执行console.log(length)

答案undefined。这个不用讲。

第三条 执行console.log(length)没有疑问吧

答案108。这里已经执行了赋值length = 108

第四条 执行if(console.log("undefined") || ....

答案undefined, 没有想到吧?if判断条件内的也会被浏览器执行。不光是可以执行,而且还没有返回值。所以这个时候回走进后面的或 (!!"" + "1" && typeof typeof null && !!length) 然后进行一系列操作:

  • !!"" + "1" >>> 字符串false1(强制类型转换)

  • typeof typeof null >>> typeof "object" >>> 字符串string

第五条 执行obj2.say(光头强,77)没有疑问吧

答案熊大,77。 setTimeout 你先等等!!为啥不是光头强呢?因为say的this是obj1呀。

第六条 执行promise.then : obj2.say(光头强,age)

第七条 执行obj1.say(熊三,88)

第六第七条,就是setTimeout 和promise的较量,也就是当然是微任务promise.then先执行了。

总结

最后,讲解更加详细:

var name = "null";
var age = 38;
var length = 10;
var say = "说话";


// 2. 运行demo的前一刻发生了预编译环节,预编译发生在函数运行的前一刻。
//    - 第一步,生创建AO(Activation Object)对象:可以理解为demo这个函数拥有的一个冰箱,在执行内部代码时就通过这个冰箱来取东西(变量,函数...)。
//    AO: {
//      
//    }
//    - 第二步,将形参和变量声明当作AO的属性名,值为undefined。
//    AO: {
//      arg: undefined,
//      obj1: undefined,
//      length: undefined,
//      obj2: undefined,
//    }
//    - 第三步,将形参和实参相统一。
//    AO: {
//      arg: 18,
//      obj1: undefined,
//      length: undefined,
//      obj2: undefined,
//    }    
//    - 第四步,在函数体里找函数声明,将值赋为函数体。
//    AO: {
//      arg: function arg() {},
//      obj1: undefined,
//      length: undefined,
//      obj2: undefined,
//    }   

// 预编译后,开始解释执行demo
function demo(arg) {

  // 3. 上面说到AO就是这个函数的冰箱,在访问变量时首先去AO里面去找,如果没有再沿着上一层的AO找,一直递归下去,所以此时冰箱(AO)里面的obj1为undefined,arg在预编译时已经变成了函数体。
  //     所以打印:undefined, function arg() {}。
  console.log(obj1, arg);

  // 4. 这句代码还看不看?当然不看,因为在预编译第四部的时候已经提升了。
  function arg() {}

  // 5. 将obj1的值赋值为代码中的对象。
  var obj1 = {
    name: "熊大",
    age: 88,
    say: function () {
      return (name, age) => {
        console.log(`我是${this.name}, 年龄${age}`);
      };
    },
  };

  // 6. 同理,预编译的第二部已经提升了,直接进入下一行。
  var length;
  // 7. 冰箱(AO)里的length是什么?打印:undefined。
  console.log(length);
  // 8. 将AO中的length赋值为108。
  length = 108;
  // 9. 此时AO中的length的值为108,打印108。
  console.log(length);

  // 10. 将obj2赋值为代码中的对象。
  var obj2 = {
    name: "熊二",
    age: 58,
    // 14. 仔细看,赋值的say方法是obj1调用后的say,所以方法内的this指向为obj1,并非是obj2。
    say: obj1.say(),
  };

  // 11. 开始进入到if,首先判断console.log的返回值,要知道一个函数的返回值是不是要运行它?所以控制台输出字符串类型的undefined,返回值为原始类型undefined。
  //     || 运算符不成立进入到(!!"" + "1" && typeof typeof null && !!length)。
  //     !!"" + "1" === 字符串false1(强制类型转换)。
  //     typeof typeof null === typeof "object" === 字符串string
  //     判断成立进入到if语句中
  if (
    console.log("undefined") ||
    (!!"" + "1" && typeof typeof null && !!length)
  ) {
    // 12. setTimeout?里面的代码需要看吗?不需要!啥时候执行再看,直接扔到计时器线程去计时吧,时间到了在事件队列里呆着吧,等我啥时候JS引擎空闲了再去执行事件队列中的回调函数。
    //     什么?!你还不知道JS中的事件循环?别急,三天内我会出一篇精解版来教你事件循环,不会包赔!
    setTimeout(() => {
      // 21. 来吧,可算到我了,在宏队列里都等十年了,开始执行,与15同理,打印熊大,年龄88。至此,队列中的所有回调全部执行完毕。
      obj1.say()("熊三", 88);
    });
    // 13. 同步代码,进入到Obj2的say方法。⬆️
    // 15. 在了解了obj2的say方法中的this指向后,所以打印什么?
    //     (name, age) => {
    //        console.log(`我是${this.name}, 年龄${age}`);
    //     };
    //     打印this.name,this为obj1,所以打印熊大,年龄为传入的age,77。
    obj2.say("光头强", 77);
  } else {
    setTimeout(() => {
      obj2.say("肥波", 199);
    });
  }

  // 16. 进入到Promise。
  new Promise((resolve, reject) => {
    // 17. Promise内的代码都是同步的,如果我在这里加个console.log会立刻执行。resolve传入参数188。
    //     resolve后Promise的状态变成已决状态。将then放入到微队列中,等待执行。
    resolve(188);
  }).then((age) => {
    // 20. 先看微队列,与第15步同理,打印熊大,年龄为age,188。微队列中无可执行回调,去看宏队列。⬆️
    obj2.say("光头强", age);
  });

  // 18. 发现demo执行完毕,demo的AO出栈。
}

// 1. GO预编译完成,执行到demo()后开始执行demo。
demo(18);

// 19. GO(Global Object)发现无执行代码,出栈,此时JS执行线程空闲。此时结束了吗?
//     并没有,还记得我们上面放入的setTimeout和Promise.then的回调函数吗?他们都在事件列队中的宏/微任务等待执行呢。

本文作者:破茧计划_张超

本文链接:https://juejin.cn/post/6980962149115887623

f2180b9f63d715dad5d1a6dbfd94320b.png

最后

欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿

回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!

回复「交流」,吹吹水、聊聊技术、吐吐槽!

回复「阅读」,每日刷刷高质量好文!

如果这篇文章对你有帮助,「在看」是最大的支持

 》》面试官也在看的算法资料《《

“在看和转发”就是最大的支持

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值