从一道面试题引申到N道面试题

简介: 昨天分享了深入浅出 setState 原理篇 ,其中讲到 setState 是同步还是异步的问题?这不,引起了古老的回忆,翻开笔记,想起曾经有一个体验良好的面试,面试官从一道面试题出发,循序渐进,引出了各种知识点,这些知识点能检测出面试者的React知识点、ES6知识点、JS基础等。我在此基础上,加上自己的理解,整理一个个人认为考点较充足的面试分享

昨天分享了深入浅出 setState 原理篇 ,其中讲到 setState 是同步还是异步的问题?这不,引起了古老的回忆,翻开笔记,想起曾经有一个体验良好的面试,面试官从一道面试题出发,循序渐进,引出了各种知识点,这些知识点能检测出面试者的React知识点、ES6知识点、JS基础等。我在此基础上,加上自己的理解,整理一个个人认为考点较充足的面试分享

双方客套,面试正式开始,面试官正手来一个面试题

如下的代码, a 的值是多少


class A extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            a: 0
        }
    }
  componentDidMount() {
    this.setState({ a: 1 });
    setTimeout(() => {
      this.setState({ a: 2 });
    }, 0);
    new Promise((resolve) => {
      resolve(this.setState({ a: 3 }));
    }).then(() => {
      this.setState({ a: 4 });
    });
  }
  render() {
    console.log("state", this.state.a);
    return <div>{this.state.a}</div>;
  }
}


这题考察到 “React 渲染生命周期” 以及 “setState 是同步还是异步” 知识点

答案是:0、3、4、2

首先是 React 渲染生命周期,当挂载时,其生命周期调用顺序为:


  • constructor


  • static getDerivedStateFromProps()


  • render


  • componentDidMount


所以先 render 一次 state.a,值为 0 ,接着进入 componentDidMount 生命周期,this.setState({ a: 1 })this.setState({ a: 3 }) 为同步操作,setTimeout 会将其中的回调函数(即() => { this.setState({ a: 2 })}) 放入宏任务中,then 之后的回调函数(即() =>{this.setState({ a: 4 })})会放入微任务中


因为(legacy 模式下)setState 的同周期内的 setState 会批处理合成为一个 setState,并以后者为主,所以 this.setState({a: 1}) 会被覆盖。因为调用了 setState ,触发了更新,意味着又 render 一次,此时的 state.a 就显示为 3。当此宏任务调用完后去查看微任务队列,发现有未执行的回调函数,执行它 this.setState({ a: 4 }) ,又一次调用 setState,触发更新,state.a 显示 4。微任务队列为空后,查看宏任务队列,发现回调函数 this.setState({ a: 2 }),执行,触发更新,state.a 显示 2


所以其结果为:0、0、4、2


不知道你对否~~


我们接着改造一个这个题,变成


class A extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            a: 0
        }
    }
  componentDidMount() {
    this.setState({ a: 1 });
    console.log("a", this.state.a);
    setTimeout(() => {
      this.setState({ a: 2 });
      console.log("a", this.state.a);
    }, 0);
    new Promise((resolve) => {
      resolve(this.setState({ a: 3 }));
      console.log("a", this.state.a);
    }).then(() => {
      this.setState({ a: 4 });
      console.log("a", this.state.a);
    });
  }
  render() {
    return <div>{this.state.a}</div>;
  }
}


不在 render 展示 state.a ,而是在调用完 setState 后查看 state.a 的值,结果会如何呢?


改编后的题主要考察组件的数据更新和视图的更新是两码事


答案是:0、0、3、2


首先都是在 componentDidMount 中,其次,与上个案例一样,调用依次是


  • this.setState({ a: 1 });


  • this.setState({ a: 3 });


  • this.setState({ a: 4 });


  • this.setState({ a: 2 });


其区别在于调用 this.setState({ a: 1 }) 和 this.setState({ a: 3 }) 后,数据不会马上更新,调用 setState 后,会将调用压入队列中,到最后一并执行(批处理),所以此时查看 state.a 的值,会看到还是 0,因为它还没触发批处理。而 Promise、setTimeout 之类原生事件会同步执行,值就显示为你 setState 什么,我就显示什么


我们在上述两个例子中谈到了 Event Loop,在 React 中会因为性能优化而对 setState 做处理,如果在浏览器环境中,上述的例子会怎么展示呢?


console.log('0')
setTimeout(() => {
    console.log("1");
}, 0);
new Promise((resolve) => {
    resolve()
    console.log("2");
}).then(() => {
    console.log("3");
});
console.log('4')


这题主要考验了浏览器的 Event Loop 机制


答案:0、2、4、3、1


第一个值为 0 没有疑问


遇到 setTimeout,所以 console.log(“1”) 排入 宏任务队列


因为 new Promise 中的执行函数会同步执行,而 then 中的"console.log("3")"会进入微任务,所以第二个值为 2,


接着就是第三个值 4,再因为 Event Loop 机制(宏任务-执行全部微任务-再去找宏任务队列第一个),所以先执行微任务,第四个值为3


最后执行宏任务(setTimeout),第五个值为 5


既然说到了 Promise,不妨考考 Promise,手写一个太麻烦,没必要考课本。那来说说为什么 promise 中能 .then,它的链式调用的原理是什么?


每次 new  Promise() 后能 .then().then().then(),因为它每次调用完 then 后,返回了 Promise 实例,所以才能一直调用下去


这样理解下来,链式调用的核心,就是调用完方法后返回对象本身(return this)


那我们出一道题,题目是


class Operator {...}
var op = new Operator(1)
op.add(3).minus(2).multi(2).divide(1)


写出 Operator 构造函数中的 add、minus、multi、divide


我的答案是:


class Operator {
    constructor(initNum) {
        this.num = initNum;
    }
    add(value) {
       this.num = this.num + value 
       return this
    }
    minus(value) {
        this.num = this.num - value;
        return this
    }
    multi(value) {
        this.num = this.num * value;
        return this;
    }
    divide(value) {
        this.num = this.num / value;
        return this;
    }
}


从这题可以引申出 class、ES6还有有哪些特性和柯里化等等


总结



从 this.setState 的一道面试题延申出各种问题,即考察了面试者对 this.setState 的理解,又考了浏览器的 Event Loop,并引申出 Promise 的链式调用,并用一道题目考察面试者的 JS 基础能力,再之后,还可以问 ES6 的特性和柯里化,知识广度就变大了,也能更好的考察面试者


相关文章
|
6月前
|
算法 Java 调度
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
74 0
|
6月前
|
存储 Java
java面试题大全带答案_面试题库_java面试宝典2018
java面试题大全带答案_面试题库_java面试宝典2018
|
6月前
|
存储 设计模式 Java
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
|
6月前
|
SQL 算法 安全
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
|
6月前
|
算法 安全 网络协议
java高级面试题_java面试题大全带答案_线程面试题_java面试宝典2019
java高级面试题_java面试题大全带答案_线程面试题_java面试宝典2019
|
5月前
|
存储 安全 Java
Java面试题:Java内存管理、多线程与并发框架:一道综合性面试题的深度解析,描述Java内存模型,并解释如何在应用中优化内存使用,阐述Java多线程的创建和管理方式,并讨论线程安全问题
Java面试题:Java内存管理、多线程与并发框架:一道综合性面试题的深度解析,描述Java内存模型,并解释如何在应用中优化内存使用,阐述Java多线程的创建和管理方式,并讨论线程安全问题
46 0
|
5月前
|
存储 并行计算 安全
Java面试题:Java内存管理、多线程与并发框架的面试题解析与知识点梳理,深入Java内存模型与垃圾回收机制,Java多线程机制与线程安全,Java并发工具包与框架的应用
Java面试题:Java内存管理、多线程与并发框架的面试题解析与知识点梳理,深入Java内存模型与垃圾回收机制,Java多线程机制与线程安全,Java并发工具包与框架的应用
82 0
|
6月前
|
XML Java 数据库连接
面试必备!Java核心技术100+面试题
面试必备!Java核心技术100+面试题
|
6月前
|
安全 Java 中间件
《面试专题-----经典高频面试题收集一》解锁 Java 面试的关键:深度解析常见高频经典面试题(第一篇)
《面试专题-----经典高频面试题收集一》解锁 Java 面试的关键:深度解析常见高频经典面试题(第一篇)
51 0
|
6月前
|
安全 Java API
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
43 0