JS代码输出题总结

原型/原型链

  1. 函数 + 变量提升
function Foo() {
    log = function () { // 定义了全局log
        console.log(1);
    };
    return this;
}
Foo.log = function () {
    console.log(2);
}
Foo.prototype.log = function () {
    console.log(3);
}
var log = function () {
    console.log(4);
}
function log() {
    console.log(5);
}

Foo.log(); //2 
log(); //4
Foo().log();//1 
log(); //1
new Foo.log();//2
new Foo().log();//3

在这里插入图片描述
Foo.log(); ,函数也是一种对象,对象上挂载了log属性,所以第一个输出2
log(); 21行输出4,因为16行的函数声明优先提升,之后被13行的表达式覆盖了
Foo().log(); Foo() 由于没有new操作符,返回的this为global,就是window,window.log() 因为Foo函数中log没有使用定义变量操作符var const 或 let 去定义,所以为全局变量,覆盖了13行的log函数,输出1
log() 输出1,因为上一条
new Foo.log() 与Foo.log()同理
new Foo().log();//3 调用了Foo原型上的log函数,输出3

  1. prototype
function Parent() {
    this.a = []
}
 
Parent.prototype.b = []
 
let child1 = new Parent()
child1.a.push(1)
child1.b.push(2)
 
let child2 = new Parent()
console.log(child2.a) // []
console.log(child2.b) // [2]

child1和child2共享b这个数组,因为是挂载在原型上的属性

  1. instanceof
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true

Function为函数的构造函数,在Object原型链上
Object为对象的构造函数

  1. function原型
console.log( (function f(){}).__proto__ === Function.prototype); // true
function foo(){
this.a = 100;
this.b = 200;
}

function goo(){
this.c = 300;
this.d = 400;
}

var seed = new foo();  //1

foo.prototype.fuss2 = function(fn) {
fn.prototype = seed;
return new fn;
}

var a = seed.fuss2(goo);
console.log(a);

if ( "fuss2" in a ) {
var b = a.fuss2(foo);
}

console.log(Object.prototype.hasOwnProperty.call(a, "a"));
console.log("b" in a);
console.log("fuss2" in a);
console.log("c" in b);

在这里插入图片描述

闭包

  1. for循环
var s = 0;
var i = 1;
var funcs = [];
var n = 3;
function x(n) {
 for (i = 0; i < 3; i++) {
   funcs[i] = () => {
     s = s + i * n;
     console.log(s);
   };
 }
}
x(1);
funcs[0](); // 3
funcs[1](); // 6
funcs[2](); // 9

因为是箭头函数,捕获了函数上层的变量,所以函数在执行的时候n = 1;而这个for循环中的i是全局变量,最终执行完后为3,所以在函数执行的时候i都等于3。
综上应该输出3、6、9

this指向

  1. 带参数
var length = 10;
function fn() {
    console.log(this.length);
}
var obj = {
    length: 5,
    method: function () {
    fn();
    arguments[0]();
  }
}
obj.method(fn, 1);

输出:
10
2

  1. 第一个this为window
  2. 第二个this为arguments,arguments[0]为fn函数

事件循环

其他

  1. try-catch-finally
const test = () => {
	try {
		throw Error();
		return 'try'
	} catch (error) {
		return 'error'
	} finally {
		return 'finally'
	}
}
test();//finally

当finally块中有return关键字时,会拦截之前所return的内容

  1. async
console.log('script start') // 1 script 第一个宏任务

async function async1() {
  await async2() // 返回的promise的then为本次宏任务下的第一个微任务,await后的内容相当于then微任务,async2里面的相当于resolve里的同步任务
  console.log('async1 end') // 5 第一个宏任务下的第一个微任务
}
async function async2() {
  console.log('async2 end') // 2
}
async1() // script 第一个宏任务,立即执行

setTimeout(function() { // 下一个时间循环里的宏任务
  console.log('setTimeout') // 8 第二个宏任务
}, 0)

new Promise(resolve => { 
  console.log('Promise') // 3 script 第一个宏任务下的
  resolve()
})
  .then(function() { // 6 第一个宏任务下的第二个微任务
    console.log('promise1') 
  })
  .then(function() { // 7 第一个宏任务下的第二个微任务
    console.log('promise2')
  })

console.log('script end') // 4 script 第一个宏任务
// script start
// async2 end
// Promise 
// script end
// async1 end
// promise1
// promise2
// setTimeout

首先先来解释下上述代码的 async 和 await 的执行顺序。当我们调用 async1 函数时,会马上输出 async2 end,并且async2函数返回一个 Promise接下来在遇到 await的时候会就让出线程开始执行 async1 外的代码,所以我们完全可以把 await 看成是让出线程的标志

await后的内容相当于then微任务,async2里面的相当于resolve里的同步任务

然后当同步代码全部执行完毕以后,就会去执行所有的异步代码,那么又会回到 await 的位置执行返回的 Promise 的 resolve 函数,这又会把 resolve 丢到微任务队列中,接下来去执行 then 中的回调,当两个 then 中的回调全部执行完毕以后,又会回到 await 的位置处理返回值,这时候你可以看成是 Promise.resolve(返回值).then(),然后 await 后的代码全部被包裹进了 then 的回调中,所以 console.log(‘async1 end’) 会优先执行于 setTimeout。

如果你觉得上面这段解释还是有点绕,那么我把 async 的这两个函数改造成你一定能理解的代码

new Promise((resolve, reject) => {
  console.log('async2 end')
  // Promise.resolve() 将代码插入微任务队列尾部
  // resolve 再次插入微任务队列尾部
  resolve(Promise.resolve())
}).then(() => {
  console.log('async1 end')
})

也就是说,如果 await 后面跟着 Promise 的话,async1 end 需要等待三个 tick 才能执行到。那么其实这个性能相对来说还是略慢的,所以 V8 团队借鉴了 Node 8 中的一个 Bug,在引擎底层将三次 tick 减少到了二次 tick。但是这种做法其实是违法了规范的,当然规范也是可以更改的,这是 V8 团队的一个 PR,目前已被同意这种做法。

所以 Event Loop 执行顺序如下所示:

  • 首先执行同步代码,这属于宏任务
  • 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
  • 执行所有微任务
  • 当执行完所有微任务后,如有必要会渲染页面
  • 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数
  1. 类型转换
console.log([] == ![]) // true
console.log({} == !{}) // false

[] == ! [] -> [] == false -> [] == 0 -> ‘’ == 0 -> 0 == 0 -> true
说明:

  1. ![] = false(!可将变量转换成boolean类型,null、undefined、NaN以及空字符串(‘’)取反都为true,其余都为false)
  2. 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1,则需要把 false -> 0
  3. 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较,如果对象没有valueOf()方法,则调用 toString(),即**[].toString() -> ''** (返回的是空字符串)
  4. 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值,即**Number('') -> 0**

{} == ! {} -> {} == false -> {} == 0 -> NaN == 0 -> false
同理:
第三步中, {}.toString() -> NaN(返回的是NaN)

  1. 变量提升
    函数提升会优先于变量提升,而且不会被同名的变量覆盖,但是,如果这个同名变量已经赋值了,那函数变量就会被覆盖。当二者同时存在时,会先指向函数声明。
alert(a) // 函数字符串
a() // 10字符串
var a=3
function a(){
	alert(10)
}
alert(a) // 3字符串
a=6
a() // 报错:a不是函数

等价于:

function a(){
	alert(10)
}
var a;
alert(a) // 函数a字符串
a() // 10字符串
a=3 // 函数a被数字3覆盖
alert(a) // 3字符串
a=6
a() // 报错:a不是函数
  1. 引用类型/基本类型
var a = {},
b = {key: 'b'},
c = {key: 'c'};
a[b] = 123;
a[c] = 456;
console.log(a[b]);
// 456

b和c都是引用类型,在a中的key都为"[object object]"字符串,a[c]将a[b]覆盖了,所以输出了456

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值