点击上方 前端瓶子君,关注公众号
回复算法,加入前端编程面试算法每日一题群
仔细看下,内容如图,你觉得会输出什么?
不要再想着直接复制粘贴到浏览器运行了[狗头]
答案是
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
最后
欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿
回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!
回复「交流」,吹吹水、聊聊技术、吐吐槽!
回复「阅读」,每日刷刷高质量好文!
如果这篇文章对你有帮助,「在看」是最大的支持
》》面试官也在看的算法资料《《
“在看和转发”就是最大的支持