面试题分析--判断输入输出

1. 考察点:宏任务与微任务、Promise

new Promise(() => {
  setTimeout(() => {
    console.log(0)
  })
  console.log(1)
  throw new Error('')
  console.log(2)
}).then(() => {
  console.log(3)
}, () => {
  console.log(4)
}).catch(() => {
  console.log(5)
}).then(() => {
  console.log(6)
})

输出:

1
4
6
0

分析:

(1) 首先执行整个代码块,即一个Promise,它内部的初始化函数会以同步方式执行

(2) 首先执行setTimeout,会将()=>console.log(0)这个回调函数放到宏任务队列中

(3) 执行console.log(1),之后抛出异常,进入后面的链式调用

(4) 在then方法中进入错误处理函数() => console.log(4),返回状态resolve

    链式调用中有个then()和catch()容易看成跳过then方法进入catch分支,其实then方法接收两个处理函数,分别处理成功、失败的情况,我们一般在then中只设置一个函数来处理成功的情况,将失败的情况放到catch中处理,这是因为当只设置一个函数时,即只设置了成功时的处理函数,失败的情况未被处理,状态会继续向后传递。而设置两个处理函数时,失败时的处理函数会被设置,在未抛出错误的情况下,会处理这个错误,并将返回状态为resolve的状态。所以此时可以进入后面的then

(5) 返回状态为resolve,所以会跳过catch进入then,执行() => console.log(6)

(6) Promise的catch和then都是微任务,js的event loop机制会先执行一轮宏任务中的所有同步任务和所有微任务,再执行下一轮宏任务,所以console.log(0)在这里最后执行

2. 考察点:this指向与作用域、var效果、构造函数创建对象的方式

var a = 10;
var obt = {
  a: 20,
  fn: function () {
    var a = 30; // 思考,这里var改成this.会怎样
    console.log(this.a);
  },
};
obt.fn();
obt.fn.call();
new obt.fn();

输出:

浏览器环境下: 20 10 undefined
node环境下: 20 undefined undefined
将var a = 30改为this.a = 30后: 30 30 30

分析:

(1) obt.fn()相当于先取obt.fn,得到 function() { var a = 30; console.log(this.a) },然后再执行,此时上下文环境为obt,即this指向obt,所以this.a为obt.a为20

(2) obt.fn.call()相当于先拿到obt.fn,此时obt.fn为一个函数x,然后执行x.call(),call中没有接收绑定对象,会默认将x绑定到全局对象上,全局对象浏览器下为window,node环境下为undefined,所以this指向在浏览器中为window,在node环境下为undefined,而在浏览器环境下已经通过var a = 10将a挂载到全局对象上,所以在浏览器环境下this.a为10,在node环境下为undefined。不过如果第一行从var改为let或const,就因为let或const的作用域限制不能提升为全局环境下的变量,输出会成为undefined

(3) new obt.fn()相当于用obt.fn函数做构造函数创建了一个实例对象x,创建过程中会通过构造函数中的this为实例添加属性或方法,但obt.fn中没有通过this为新实例添加成员,所以x中没有挂载a,输出为undefined

(4) 将var a改为this.a后,无论是三者中的哪种方式,都会先对调用对象的a设置为30,然后再取a

3. 考察点:this指向问题

var birth = 2000;
const obj = {
  birth: 2001,
  getAge: function () {
    return 2021 - this.birth;
  },
  getAge1: () => 2021 - this.birth,
  getAge2: function () {
    var myFun = function () {
      return 2021 - this.birth;
    };
    return myFun();
  },
  getAge3: function () {
    var myFun = () => 2021 - this.birth;
    return myFun();
  },
};

console.log(obj.getAge()); 
console.log(obj.getAge1()); 
console.log(obj.getAge2()); 
console.log(obj.getAge3());  

输出:20 21/NaN 21/NaN 20

分析:

(1) obj.getAge(),首先从obj中取getAge,得到函数function () { return 2021 - this.birth },再执行这个函数,上下文是obj,所以this指向obj,结果为2021-2001=20

(2) obj.getAge1(),首先obj中取getAge1,得到箭头函数() => 2021 - this.birth,箭头函数的this指向定义位置所在的作用域的this,在这里是全局作用域,,浏览器环境下为window,而第一行通过var birth= 2000将birth挂到了window下,执行为2021 - 2000 = 21,node环境下为undefined,执行为2021 - undefined = NaN

(3) obj.getAge2(),首先obj中取getAge2,得到函数x,再执行x,即执行function () { return 2021 - this.birth },此时执行上下文为全局对象,浏览器环境下为window,而第一行通过var birth= 2000将birth挂到了window下,执行为2021 - 2000 = 21,node环境下为undefined,执行为2021 - undefined = NaN

// 函数x
function () {
  var myFun = function () {
    return 2021 - this.birth;
  };
  return myFun();
},

(4) obj.getAge3(),调用时执行箭头函数() => 2021 - this.birth,this指向定义位置(getAge3)所在的作用域的this,即obj,所以输出为2021 - 2001 = 20

4. 考察点:js的event loop

setTimeout(() => {
  Promise.resolve().then(() => {
	console.log(88)
  })
  console.log(1)
}, 0)

Promise.resolve(() => {
  console.log(3)
  return 5
}).then((val) => {
  console.log(4, val)
  return 6
}).catch(() => {
  console.log(5)
}).finally((val) => {
  console.log(6, val)
})

console.log(7)

输出结果

7
4 () => {
	console.log(3)
    return 5
}
6 undefined
1
88

分析:

(1) js会首先将整段代码放到宏任务队列中

(2) 从宏任务队列中取争端代码放入执行栈中执行,此时为线性执行,首先遇到setTimeout,将回调函数x放入宏任务队列中,然后遇到Promise.resolve(),为微任务,放入微任务队列中,再执行console.log(7)

(3) 此时执行栈中任务执行完毕,微任务队列中还有任务,会将一次宏任务中的所有微任务执行完毕后再去执行下一个宏任务,通过Promise.resolve创建的是resolve状态的Promise,携带信息是箭头函数y,() => { console.log(3); return 5 },所以被调用链的then的第一个处理函数捕获,此时输出4以及函数y

(4) 继续执行调用链,由于是resolve状态,跳过catch,并进入finally,而finally对应的回调函数没有输入,所以拿到的val为undefined,输出为6 undefined

(5) 微任务队列清空,从宏任务队列中取出setTimeout并开始执行,先执行同步任务console.log(1),再执行微任务console.log(88)

5. 考察点:React生命周期与hooks、setState更新

function App() {
  const [txt, updateTxt] = useState('xhs');
  console.log(txt);

  useEffect(() => {
    console.log('xxx', txt);
    updateTxt('xhs-btn');
  })

  return (
    <>
      { console.log(2333) }
      
    
  );
}

输出

xhs
2333
xxx xhs
xxx xhs-btn
2333
xxx xhs-btn
xhs-btn
2333

分析:

React函数组件,首先通过useState创建被观察的成员对象txt以及修改他的方法updateTxt,函数组件会在每次父组件更新或state变化时发生更新,更新时重新执行一次该函数

(1) React函数组件声明,先通过useState创建被观察的成员对象txt以及修改他的方法updateTxt,然后通过console.log(txt)打印xhs

(2) 进入组件的return,返回待渲染的组件,此时执行console.log(2333)

(3) 执行useEffect中的回调函数,没有依赖数组所以每次更新都会重新执行此回调函数,没有返回所以不执行清理操作。此时先执行console.log('xxx', txt),此时txt是xhs

(4) 执行updateTxt,更新组件状态,此时函数组件重新执行一次,但useState只在第一次声明是调用不会重复声明,执行console.log(txt),此时txt为xhs-btn

(5) 进入组件的return,返回待渲染的组件,此时执行console.log(2333)

(6) 组件渲染后useEffect再次执行,执行console.log('xxx', txt),此时txt是xhs-btn

(7) state更新后再次执行函数组件,执行console.log(txt),此时txt为xhs-btn

(8) 进入组件的return,返回待渲染的组件,此时执行console.log(2333)

(9) 此时组件未发生重新渲染,useEffect不执行

【TODO:还是解释的不太清楚,需要结合源码分析React具体是怎么优化的,因为如果将updateTxt更新后的值改为每次不一样的,会陷入循环导致卡死】

你可能感兴趣的:(javascript)