晚起的虫儿

晚起的虫儿 查看完整档案

北京编辑西安交通大学  |  软件工程 编辑快手  |  前端 编辑 segmentfault.com/u/sprina1997 编辑
编辑

一起成长

个人动态

晚起的虫儿 回答了问题 · 4月14日

前端开发的时候有必要用eslint吗

有必要,能够使代码更易于阅读,你觉得不好用是因为它总是报很多格式上的问题,修改十分麻烦。可以用ESLint的autofix功能在保存时自动修正格式上的错误,就不会提示你像几个空格或者换行之类的错误了。

关注 8 回答 10

晚起的虫儿 关注了用户 · 4月12日

前端小智 @minnanitkong

我不是什么大牛,我其实想做的就是一个传播者。内容可能过于基础,但对于刚入门的人来说或许是一个窗口,一个解惑之窗。我要先坚持分享20年,大家来一起见证吧。

关注 10226

晚起的虫儿 收藏了文章 · 4月12日

简单说 !![]==true 与 []==true 引发的思考

说明

直接说出问题

!![] == true //结果是true 
[] == true   //结果是false 
![] == []    //结果是true

为什么会出现这种情况

解释

首先说一下,如果你看到这些代码,能想到 相等运算符(== ),两个操作数类型不同时,进行的转换,那么你已经接近答案了。

不要浪费时间,我们需要先知道在JavaScript中的一些比较特别的类型转换,最好能记住哦!!!

[] 转为字符串是 ""       // String([]) 返回""
[] 转为数字是 0            // Number([]) 返回0
[] 转为布尔值是 true        // Boolean([]) 返回true
true 转为数字是 1       // Number(true) 返回1
false 转为数字是 0      // Number(false) 返回0

如果想知道为什么,请点这里
简单说 JavaScript中的tostring( ) 与 valueOf( )方法

我们一句一句的看

!![] == true //结果是true
! (逻辑非),会将操作数的布尔值求反,而!! 就是类型转换,将对应的类型转换为boolean型
所以我们看一看,[ ]一次求反 (![]) 返回的就是false,再求反(!![]) 返回的就是true。
最后的比较就变成 true == true 自然结果是 true

[]==true //结果是false
这里我们重点说说,相等运算符(==) 在遇到两个操作数类型不同的时候,要遵守的规则和类型转换
1、如果-个值是null, 另一个是undefined,则它们相等
null == undefined //返回true
2、如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较。

1 == "1" //1==1  //结果是true
2 == "1" //2==1  //结果是false

3、如果其中一个值是true,则将其转换为1再进行比较。如果其中一个值是false,则将其转换为0再进行比较。

"1" == true     //1==1 结果是true
0 == false      //0==0 结果是true

4、如果一个值是对象,另一个值是数字或字符串,则将对象转换为原始值,然后再进行比较。对象通过toString()方法或者valueOf()方法转换为原始值,JavaScript语言核心的内置类先尝试使用valueOf(),再尝试使用toString(),除了日期类,日期类只能使用toString()转换,那些不是JavaScript语言核心中的对象则通过各自的实现中定义的方法转换为原始值。

原始值:不可变更的值,包括undefined、null、布尔值、数字、和字符串。

所有的对象都有toString() valueOf()这两个方法。
toString()方法的作用是,返回一个反映这个对象的字符串。
valueOf()方法的作用是,一个对象那个如果存在任意原始值,它就默认将对象转换为表示它的原始值。

5、其他不同类型之间的比较均不相等。

好的,我们知道这些规则后,再来看行代码
[]==true //结果是false
true 会转为1
[ ] 会转为 0
最后是比较的是 0 == 1,所以结果是false

理解了上面的内容的话,那么
![] == [] //结果是true
这行代码,也就好理解了
![ ] ,也就是 [] 先转为 布尔值(true),然后求反,就是false,false 转为数字就是0
[ ]转为数字就是0
最后就是 0 == 0 ,所以结果就是true

总结

强调一点,[ ] 转数字 是0,转布尔值,是true,但是这不是说, 0 转为布尔值是true,而是false,是false,false。

这篇文章主要是说一些关于隐式转换的事。
根据上面的三行代码,还能写出一些其他的来,看看下面这些有趣的代码吧。

[] == 0      //返回结果是 true
![] == 0     //返回结果是 true
[] == ''     //返回结果是 true
!![] == ''   //返回结果是 false
'' == true   //返回结果是 false

文章不长主要是想说清楚,最开始提到的问题。
最后推荐两篇相关的文章,希望对大家有所帮助。
简单说 JavaScript中的tostring( ) 与 valueOf( )方法
简单说 通过JS的隐式转换,关键时刻救你一命

查看原文

晚起的虫儿 赞了文章 · 4月12日

简单说 !![]==true 与 []==true 引发的思考

说明

直接说出问题

!![] == true //结果是true 
[] == true   //结果是false 
![] == []    //结果是true

为什么会出现这种情况

解释

首先说一下,如果你看到这些代码,能想到 相等运算符(== ),两个操作数类型不同时,进行的转换,那么你已经接近答案了。

不要浪费时间,我们需要先知道在JavaScript中的一些比较特别的类型转换,最好能记住哦!!!

[] 转为字符串是 ""       // String([]) 返回""
[] 转为数字是 0            // Number([]) 返回0
[] 转为布尔值是 true        // Boolean([]) 返回true
true 转为数字是 1       // Number(true) 返回1
false 转为数字是 0      // Number(false) 返回0

如果想知道为什么,请点这里
简单说 JavaScript中的tostring( ) 与 valueOf( )方法

我们一句一句的看

!![] == true //结果是true
! (逻辑非),会将操作数的布尔值求反,而!! 就是类型转换,将对应的类型转换为boolean型
所以我们看一看,[ ]一次求反 (![]) 返回的就是false,再求反(!![]) 返回的就是true。
最后的比较就变成 true == true 自然结果是 true

[]==true //结果是false
这里我们重点说说,相等运算符(==) 在遇到两个操作数类型不同的时候,要遵守的规则和类型转换
1、如果-个值是null, 另一个是undefined,则它们相等
null == undefined //返回true
2、如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较。

1 == "1" //1==1  //结果是true
2 == "1" //2==1  //结果是false

3、如果其中一个值是true,则将其转换为1再进行比较。如果其中一个值是false,则将其转换为0再进行比较。

"1" == true     //1==1 结果是true
0 == false      //0==0 结果是true

4、如果一个值是对象,另一个值是数字或字符串,则将对象转换为原始值,然后再进行比较。对象通过toString()方法或者valueOf()方法转换为原始值,JavaScript语言核心的内置类先尝试使用valueOf(),再尝试使用toString(),除了日期类,日期类只能使用toString()转换,那些不是JavaScript语言核心中的对象则通过各自的实现中定义的方法转换为原始值。

原始值:不可变更的值,包括undefined、null、布尔值、数字、和字符串。

所有的对象都有toString() valueOf()这两个方法。
toString()方法的作用是,返回一个反映这个对象的字符串。
valueOf()方法的作用是,一个对象那个如果存在任意原始值,它就默认将对象转换为表示它的原始值。

5、其他不同类型之间的比较均不相等。

好的,我们知道这些规则后,再来看行代码
[]==true //结果是false
true 会转为1
[ ] 会转为 0
最后是比较的是 0 == 1,所以结果是false

理解了上面的内容的话,那么
![] == [] //结果是true
这行代码,也就好理解了
![ ] ,也就是 [] 先转为 布尔值(true),然后求反,就是false,false 转为数字就是0
[ ]转为数字就是0
最后就是 0 == 0 ,所以结果就是true

总结

强调一点,[ ] 转数字 是0,转布尔值,是true,但是这不是说, 0 转为布尔值是true,而是false,是false,false。

这篇文章主要是说一些关于隐式转换的事。
根据上面的三行代码,还能写出一些其他的来,看看下面这些有趣的代码吧。

[] == 0      //返回结果是 true
![] == 0     //返回结果是 true
[] == ''     //返回结果是 true
!![] == ''   //返回结果是 false
'' == true   //返回结果是 false

文章不长主要是想说清楚,最开始提到的问题。
最后推荐两篇相关的文章,希望对大家有所帮助。
简单说 JavaScript中的tostring( ) 与 valueOf( )方法
简单说 通过JS的隐式转换,关键时刻救你一命

查看原文

赞 6 收藏 14 评论 4

晚起的虫儿 回答了问题 · 4月12日

react 模态框和内容放在父组件还是子组件

用第二种的多

关注 3 回答 2

晚起的虫儿 收藏了文章 · 4月7日

React性能优化总结

文章同步于Github Pines-Cheng/blog

初学者对React可能满怀期待,觉得React可能完爆其它一切框架,甚至不切实际地认为React可能连原生的渲染都能完爆——对框架的狂热确实会出现这样的不切实际的期待。让我们来看看React的官方是怎么说的。React官方文档在Advanced Performanec这一节,这样写道:

One of the first questions people ask when considering React for a project is whether their application will be as fast and responsive as an equivalent non-React version

显然React自己也其实只是想尽量达到跟非React版本相当的性能。

你所不知道的render

react的组件渲染分为初始化渲染和更新渲染。
在初始化渲染的时候会调用根组件下的所有组件的render方法进行渲染,如下图(绿色表示已渲染,这一层是没有问题的):

图片描述

但是当我们要更新某个子组件的时候,如下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):

图片描述

我们的理想状态是只调用关键路径上组件的render,如下图:

图片描述

但是react的默认做法是调用所有组件的render,再对生成的虚拟DOM进行对比,如不变则不进行更新。这样的render和虚拟DOM的对比明显是在浪费,如下图(黄色表示浪费的render和虚拟DOM对比)

图片描述

Tips:

  • 拆分组件是有利于复用和组件优化的。

  • 生成虚拟DOM并进行比对发生在render()后,而不是render()前。

更新阶段的生命周期

  • componentWillReceiveProps(object nextProps):当挂载的组件接收到新的props时被调用。此方法应该被用于比较this.props 和 nextProps以用于使用this.setState()执行状态转换。(组件内部数据有变化,使用state,但是在更新阶段又要在props改变的时候改变state,则在这个生命周期里面)

  • shouldComponentUpdate(object nextProps, object nextState): -boolean 当组件决定任何改变是否要更新到DOM时被调用。作为一个优化实现比较this.props 和 nextProps 、this.state 和 nextState ,如果React应该跳过更新,返回false。

  • componentWillUpdate(object nextProps, object nextState):在更新发生前被立即调用。你不能在此调用this.setState()

  • componentDidUpdate(object prevProps, object prevState): 在更新发生后被立即调用。(可以在DOM更新完之后,做一些收尾的工作)

Tips:

  • React的优化是基于shouldComponentUpdate的,该生命周期默认返回true,所以一旦prop或state有任何变化,都会引起重新render。

shouldComponentUpdate

react在每个组件生命周期更新的时候都会调用一个shouldComponentUpdate(nextProps, nextState)函数。它的职责就是返回true或false,true表示需要更新,false表示不需要,默认返回为true,即便你没有显示地定义 shouldComponentUpdate 函数。这就不难解释上面发生的资源浪费了。

为了进一步说明问题,我们再引用一张官网的图来解释,如下图( SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)):

图片描述

根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。

  • C1根节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,需要更新。

  • C2节点,红色SCU (false),表示不需要更新,所以C4,C5均不再进行检查

  • C3节点同C1,需要更新

  • C6节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,更新DOM。

  • C7节点同C2

  • C8节点,绿色SCU (true),表示需要更新,然后vDOMEq绿色,表示虚拟DOM一致,不更新DOM。

带坑的写法:

  • {...this.props} (不要滥用,请只传递component需要的props,传得太多,或者层次传得太深,都会加重shouldComponentUpdate里面的数据比较负担,因此,请慎用spread attributes(<Component {...props} />))。

  • ::this.handleChange()。(请将方法的bind一律置于constructor)

  • this.handleChange.bind(this,id)

  • 复杂的页面不要在一个组件里面写完。

  • 请尽量使用const element

  • map里面添加key,并且key不要使用index(可变的)。具体可参考使用Perf工具研究React Key对渲染的影响

  • 尽量少用setTimeOut或不可控的refs、DOM操作。

  • propsstate的数据尽可能简单明了,扁平化。

  • 使用return null而不是CSS的display:none来控制节点的显示隐藏。保证同一时间页面的DOM节点尽可能的少。

性能检测工具

React官方提供的:React.addons.Perf

react官方提供一个插件React.addons.Perf可以帮助我们分析组件的性能,以确定是否需要优化。
打开console面板,先输入Perf.start()执行一些组件操作,引起数据变动,组件更新,然后输入Perf.stop()。(建议一次只执行一个操作,好进行分析)
再输入Perf.printInclusive查看所有涉及到的组件render,如下图(官方图片):
Flfo-tdhVWQNu3Qou1bPgIlHFLln

或者输入Perf.printWasted()查看下不需要的的浪费组件render,如下图(官方图片):
Fpcch1iZkcJU9U-mlUxjnX9lcO9S

优化前:
FuX9A-2VfmgFMDycQYvtnR1ovBEb
优化后:
Fi4w1W_Fq4A3eUdsv_0U67Z5WZ8N

其他的检测工具

react-perf-tool为React应用提供了一种可视化的性能检测方案,该工程同样是基于React.addons,但是使用图表来显示结果,更加方便。
图片描述

React官方的解决方案

PureRenderMixin(es5)

var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <div className={this.props.className}>foo</div>;
  }
});

Shallow Compare (es6)

var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

es7装饰器的写法:

import pureRender from "pure-render-decorator"
...

@pureRender
class Person  extends Component {
  render() {
    console.log("我re-render了");
    const {name,age} = this.props;

      return (
        <div>
          <span>姓名:</span>
          <span>{name}</span>
          <span> age:</span>
          <span>{age}</span>
        </div>
      )
  }
}

pureRender很简单,就是把传进来的component的shouldComponentUpdate给重写掉了,原来的shouldComponentUpdate,无论怎样都是return ture,现在不了,我要用shallowCompare比一比,shallowCompare代码及其简单,如下:

function shallowCompare(instance, nextProps, nextState) {
  return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}

缺点

shallowEqual其实只比较props的第一层子属性是不是相同,如果props是如下

{
  detail: {
    name: "123",
    age: "123"
  }
}

他只会比较props.detail ===nextProps.detail,导致在传入复杂的数据的情况下,优化失效。

补充(4.25)

React在15.3.0里面加入了了React.PureComponent - 一个可继承的新的基础类, 用来替换react-addons-pure-render-mixin。用法:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

在ES6里面写起来简直爽歪歪,可惜一样只支持浅比较。

immutable.js

我们也可以在 shouldComponentUpdate() 中使用使用 deepCopy 和 deepCompare 来避免无必要的 render(),但 deepCopy 和 deepCompare 一般都是非常耗性能的。

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。请看下面动画:

FpPDekdncL-A9N69NnI3-O8CgGQ8

Immutable 则提供了简洁高效的判断数据是否变化的方法,只需 === 和 is 比较就能知道是否需要执行 render(),而这个操作几乎 0 成本,所以可以极大提高性能。修改后的 shouldComponentUpdate 是这样的:

import { is } from 'immutable';

shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
  const thisProps = this.props || {}, thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
      Object.keys(thisState).length !== Object.keys(nextState).length) {
    return true;
  }

  for (const key in nextProps) {
    if (!is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
      return true;
    }
  }
  return false;
}

react-immutable-render-mixin

这是一个facebook/immutable-js的react pure render mixin 的库,可以简化很多写法。
使用react-immutable-render-mixin可以实现装饰器的写法。

import React from 'react';
import { immutableRenderDecorator } from 'react-immutable-render-mixin';

@immutableRenderDecorator
class Test extends React.Component {
  render() {
    return <div></div>;
  }
}

这里可参考我的另一篇blog:使用immutable优化React

无状态组件

为了避免一定程度的浪费,react官方还在0.14版本中加入了无状态组件
这种组件没有状态,没有生命周期,只是简单的接受 props 渲染生成 DOM 结构。无状态组件非常简单,开销很低,如果可能的话尽量使用无状态组件。比如使用箭头函数定义:

// es6
const HelloMessage = (props) => <div> Hello {props.name}</div>;
render(<HelloMessage name="John" />, mountNode);

因为无状态组件只是函数,所以它没有实例返回,这点在想用 refs 获取无状态组件的时候要注意,参见DOM 操作。

高阶组件(接下来的方向)

大部分使用mixin和class extends的地方,高阶组件都是更好的方案——毕竟组合优于继承

参考文章

使用ES6编写React应用(4):使用高阶组件替代Mixins
Mixin 已死,Composition 万岁

React同构直出(接下来方向)

同构基于服务端渲染,却不止是服务端渲染。

React在减少重复渲染方面确实是有一套独特的处理办法,那就是virtual DOM,但显示在首次渲染的时候React绝无可能超越原生的速度。因此,我们在做优化的时候,接下来可以做的事情就是:

  • 首屏时间可能会比较原生的慢一些,但可以尝试用React Server Render (又称Isomorphic)去提高效率

参考文章

React同构直出优化总结
腾讯新闻React同构直出优化实践

参考文章

react组件性能优化探索实践
React移动web极致优化
React vs Angular 2:冰与火之歌

时间仓促,难免有遗漏,如果觉得对你有帮助,请点推荐

查看原文

晚起的虫儿 发布了文章 · 4月2日

记一道前端二叉树算法面试题

背景

写这篇文章主要是记录一下自己面试被问到的这个问题: 求二叉树每层最大值 到 构造一颗二叉树去验证 到进一步优化循环层数 的解答过程。(面试常见套路,手写->优化/其他解法/时间空间复杂度分析)

时间一久就会忘记怎么写,下次又得想半天,而面试肯定不会让现场想半天的,所以需要熟记思路。

这个题目源于我写的字节跳动实习的面经 https://segmentfault.com/a/1190000038543869,将解答过程写进面经里面会比较长,所以单独抽出来总结一下。


1. 求二叉树每层的最大节点

这个方法比较常见,记得数据结构书中二叉树层序遍历的代码示例就是这个方法,leetcode的题解也多是两层循环。

ps:如果一开始就写出一层循环那当然不会有后续让优化的问题了。但推荐写这种,后续大多会被问到怎么用一层实现(我小米和字节都被问到了)。这时再装作想一想然后写出来岂不是更显得自己应变能力强,有算法基础而不是背出来的?

var largestValues = function(root) {
     if(!root) return []
     const res = []    // 最终输出的结果,存放每层的最大值
     let queue = [root]     // 根节点入队
     while(queue.length) {
         let len = queue.length
         let tmp = []   // 存放当前层的所有节点的值
         while(len) {   // 此处的len记录的是当前层的节点数,为0代表遍历完当前层的节点,开始遍历下一层
             let curr = queue.shift()
             if(curr.left) queue.push(curr.left)
             if(curr.right) queue.push(curr.right)
             tmp.push(curr.val)
             len--
         }
         res.push(Math.max(...tmp))
     }
     return res
}

2. 构造一颗二叉树,用于验证我们写的求每层最大值的函数

// 二叉树的节点的构造方法
 function TreeNode(val, left, right) {
         this.val = val
         this.left = null
         this.right = null
  }
  
  // 接下来我们构建一颗第一层为1,第二层为2,3,第三层为4,5,6的二叉树
  let p1 = new TreeNode(1)
  let p2 = new TreeNode(2)
  let p3 = new TreeNode(3)
  let p4 = new TreeNode(4)
  let p5 = new TreeNode(5)
  let p6 = new TreeNode(6)
  
  // 将这些节点相互连接起来
  p1.left = p2
  p1.right = p3
  p2.left = p4
  p2.right = p5
  p3.left = p6

  // 验证
  console.log(largestValues(p1));   // 输出 [1, 3, 6]

3. 循环层数优化:将刚刚写的求二叉树每层最大值的方法中的两层循环改为一层

思路:在前面的方法中,我们内层循环的作用是记录当前层的节点数,让我们知道什么时候本层节点遍历完。既然少了一层循环,那我们就只能用额外空间去记录下一层节点了

 var largestValuesImprove = function(root) {
        if(!root) return []
        const res = []    // 最终输出的结果,存放每层的最大值
        let queue = [root]     // 存放当前层的节点
        let nextLevel = []   // 存放下一层的节点
        let tmp = []
        while(queue.length>0 || nextLevel.length>0) {
            if(queue.length===0) {   // 当前层遍历完
                queue = nextLevel   // 取下一层
                nextLevel = []
                res.push(Math.max(...tmp))
                tmp = []
            }
            let curr = queue.shift()
            tmp.push(curr.val)
            if(curr.left) nextLevel.push(curr.left)
            if(curr.right) nextLevel.push(curr.right)
        }
        // 这里一定要注意最后一次跳出循环时没有走if语句,需要再把最后一层的值放入res
        res.push(Math.max(...tmp))
        return res
 }
 
 // 验证
 console.log(largestValuesImprove(p1));

完整代码

     // 1. 求二叉树每层的最大节点
     var largestValues = function(root) {
        if(!root) return []
        const res = []    // 最终输出的结果,存放每层的最大值
        let queue = [root]     // 根节点入队
        while(queue.length) {
            let len = queue.length
            let tmp = []   // 存放当前层的所有节点的值
            while(len) {   // 此处的len记录的是当前层的节点数,为0代表遍历完当前层的节点,开始遍历下一层
                let curr = queue.shift()
                if(curr.left) queue.push(curr.left)
                if(curr.right) queue.push(curr.right)
                tmp.push(curr.val)
                len--
            }
            res.push(Math.max(...tmp))
        }
        return res
     }

     // 2. 构造一颗二叉树,用于验证我们写的求每层最大值的函数
     function TreeNode(val, left, right) {    // 节点的构造方法
         this.val = val
         this.left = null
         this.right = null
     }
    // 接下来我们构建一颗第一层为1,第二层为2,3,第三层为4,5,6的二叉树
    let p1 = new TreeNode(1)
    let p2 = new TreeNode(2)
    let p3 = new TreeNode(3)
    let p4 = new TreeNode(4)
    let p5 = new TreeNode(5)
    let p6 = new TreeNode(6)
    // 将这些节点相互连接起来
    p1.left = p2
    p1.right = p3
    p2.left = p4
    p2.right = p5
    p3.left = p6

    // 3. 验证
    console.log(largestValues(p1));   // 输出 [1, 3, 6]


    // 4. 循环层数优化:将我们刚刚写求二叉树每层的最大值中两层循环改为一层
    // 思路:在前面的方法中,我们内层循环的作用是记录当前层的节点数,让我们知道什么时候本层节点遍历完。
    // 既然少了一层循环,那我们就只能用额外空间去记录下一层节点了
    var largestValuesImprove = function(root) {
        if(!root) return []
        const res = []    // 最终输出的结果,存放每层的最大值
        let queue = [root]     // 存放当前层的节点
        let nextLevel = []   // 存放下一层的节点
        let tmp = []
        while(queue.length>0 || nextLevel.length>0) {
            if(queue.length===0) {   // 当前层遍历完
                queue = nextLevel   // 取下一层
                nextLevel = []
                res.push(Math.max(...tmp))
                tmp = []
            }
            let curr = queue.shift()
            tmp.push(curr.val)
            if(curr.left) nextLevel.push(curr.left)
            if(curr.right) nextLevel.push(curr.right)
        }
        // 这里一定要注意最后一次跳出循环时没有走if语句,需要再把最后一层的值放入res
        res.push(Math.max(...tmp))
        return res
     }
     console.log(largestValuesImprove(p1));
查看原文

赞 0 收藏 0 评论 0

晚起的虫儿 回答了问题 · 4月1日

计算机图形学包括哪些技术

个人理解,感觉它只是一门偏理论的学科,对数学能力有一定要求。不能说是包括哪些技术,得看要把它往哪个领域去应用,不同领域用到的具体技术不同。比如你要做U3D,得会C#吧; 你要做vr,就得学vr相关的技术……

关注 4 回答 2

晚起的虫儿 回答了问题 · 3月31日

解决前序遍历:AEFDCB 后序遍历:DCEBFA的二叉树是怎样的结构?

不是 前序+中序 或者 中序+后序 才能确定二叉树的结构吗?
只有前序,后序是没法唯一确定一颗二叉树的结构的。。。

关注 3 回答 3

晚起的虫儿 提出了问题 · 3月30日

ts怎么限定数组的元素类型

我想接收一个这样的数组,数组元素只能从[0,1,2,3,4,5]中取,且不能重复
如[1,2,3] 或 [0,3,4]
用ts能校验满足这种数组的格式吗?

我目前只能这样写,但这样元素可以重复,且无法扩展到数字多了的情况,如0-100

type numberType = 0|1|2|3|4|5;
myArray: numberType[]

关注 1 回答 1

认证与成就

  • 获得 103 次点赞
  • 获得 8 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 8 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-06-27
个人主页被 3.6k 人浏览