你怕不是「博客技术」大佬

为什么那么多人喜欢往这位大佬的位置里面套呢?

  1. 我们招的不是应届生,是「高级前端工程师」,不要拿你们的应届生笔试题的思路往里面套行吗?
  2. 我问了这位大佬觉他他比较厉害的技术是啥时,他自己说的。不然我面试的时候凭空出题?当然是让他告诉我哪里牛逼,我就从哪里出题啊。
  3. 这个问题实际上是一道「工程题」,我们招的「高级前端工程师」,不是低端搬砖码农。遇到一个未知的问题你要用你的「工程思维」去解决它,可能不是最优解,甚至不存在最优解,但是不存在无解,总能想办法接近目标一步吧?不然难道还要手把手带吗?
  4. 我也不是只问这些,以这个事件做一个引子而已。


之前面了一个 4 年经验的所谓「高级前端」,一上来就牛逼哄哄的跟我讲自动懂什么「浏览器渲染原理」,把我忽悠地一愣一愣的,反正我没有听懂,我也不会。

于是我就出了一道题,假设我们要实现一个简单 demo ,如何用 cavans 来渲染一些简单的 div 和 css。

规则如下:

  1. 只有 div 标签,一个 div 只能有一个 class。
  2. css 只有四个属性,direction 属性表示横向还是纵向布局,height / width 属性表示宽高是需要支持 px,color 属性表示 div 的颜色。

比如渲染下面这个这串 css 和 html:

       .container {
        direction: horizontal;
      }
      .left {
        height: 200px;
        width: 100px;
        color: black;
      }
      .right {
        height: 200px;
        width: 200px;
        color: green;
      }
      .red-block {
        height: 50px;
        width: 50px;
        color: red;
      }
      .grey-block {
        height: 100px;
        width: 100px;
        color: grey;
      }


        <div class="container">
         <div class="left"></div>
         <div class="right">
           <div class="red-block">
           </div>
           <div class="grey-block">
           </div>
         </div>
        </div>

我想,既然是懂的浏览器渲染的大佬,这应该不是什么问题吧,这道题就算拿来问应届生都不算什么困难的问题吧?

这位大佬盯着这个题目看了一分钟的题目,然后干净利落地给了我两个字:不会!

Excuuuuuuuuuuse me?

我不甘心,继续追问是哪里不懂呢?一点头绪都没有吗,说说思路也行啊。于是他又把他那个「浏览器渲染原理」背了一遍。

我又问了,你从哪里学的这个所谓的「浏览器渲染原理」?他告诉我说是别人的博客……原来是看博客学技术啊。我觉得可以发明一个新词——博客技术,专门指那些靠看别人的二手博客学来的技术。

事后我反思了一下是不是我要求太高了,于是我就花了一个小时把我自己在面试上面出的题目拿代码实现了一遍:

https://bramblex.github.io/render-demo/bramblex.github.io

好像,并没有难度啊。那我拿到这个问题时候是如何思考的呢?

  1. 怎么解析 css 和 html ?—— 寻找现有的库,并且将输出的结果简化成我们需要的最简单模式。
  2. 怎么把样式和节点对应上?—— 将样式合并到节点树上。
  3. 怎么画一个节点?—— 需要宽度和高度,那么没有设定宽度和高度的节点来计算。
  4. 怎么画多个节点的布局?—— 要根据方向确定需要记录 OffsetX 还是 OffsetY 来记录下一次绘制的起点
  5. 怎么画子节点?—— 子节点需要父节点的 OffsetX 和 OffsetY 来确定原点进行绘制。
  6. 最后怎么计算一个没有宽高的节点的真实宽高? —— 先确定方向,主轴方向上子节点长度相加,交叉轴方向上取最大长度。

这就是一个思考过程,如何将一个大问题分解成一个一个小问题,然后逐一攻破的这种思维能力。这是「博客」不会取教,也没办法教的。别人的博客是别人思考的结果,但是很多人却企图用别人思考的结果代替自己的能力,最终只会越来越菜。越不去思考,就越不会思考。

共勉。


不想撕逼,但居然有人觉得值 50k 才能答得上我的题,行吧楼下放上值 50k 的代码。以及欢迎有公司花 50k 来挖我,我的邮箱是 qjnight@gmail.com。

https://github.com/bramblex/render-demo/blob/master/render.jsgithub.com
// render.js
const render = (function () {

    const ctx = document.getElementById('output-canvas').getContext('2d')

    function simplifyDom(doms) {
        return doms.filter(({ type }) => type === 'tag').map(dom => ({
            className: dom.attributes['class'],
            children: dom.children ? simplifyDom(dom.children) : undefined,
            scrollTop: 0
        }))
    }

    function simplifySheet(sheet) {
        const style = {}
        for (const rule of sheet.cssRules) {
            const className = rule.mSelectorText.replace('.', '')
            style[className] = {}
            for (const { property, values: [{ value }] } of rule.declarations) {
                if (property === 'width' || property === 'height') {
                    style[className][property] = parseFloat(value)
                } else {
                    style[className][property] = value
                }
            }
        }
        return style
    }

    function parseHtml(html) {
        const handler = new Tautologistics.NodeHtmlParser.HtmlBuilder(function (error) {
            if (error) {
                console.error(error)
            }
        })
        const parser = new Tautologistics.NodeHtmlParser.Parser(handler)
        parser.parseComplete(html)
        return simplifyDom(handler.dom)
    }

    function parseStyle(style) {
        var parser = new CSSParser();
        var sheet = parser.parse(style, false, true);
        return simplifySheet(sheet)
    }

    function renderDoms(doms, direction = 'vertical', _offsetX = 0, _offsetY = 0) {
        if (!doms) {
            return
        }

        let offsetX = _offsetX
        let offsetY = _offsetY

        for (const dom of doms) {
            ctx.fillStyle = dom.style.color;
            ctx.fillRect(offsetX, offsetY, dom.style.width, dom.style.height)
            renderDoms(dom.children, dom.style.direction, offsetX, offsetY, dom.scrollTop)

            if (direction === 'horizontal') {
                offsetX += dom.style.width
            } else {
                offsetY += dom.style.height
            }
        }
    }

    function mergeStyle(doms, style) {
        if (!doms) return
        for (const dom of doms) {
            dom.style = {
                direction: 'vertical',
                color: 'transparent',
                ...style[dom.className]
            }

            const children = mergeStyle(dom.children, style)

            if (!children) {
                continue
            }

            if (typeof dom.style.height === 'undefined') {
                const childrenHasHeight = children.filter(child => typeof child.style.height === 'number')
                if (childrenHasHeight.length > 0) {
                    dom.style.height
                        = dom.style.direction === 'horizontal'
                            ? Math.max(...childrenHasHeight.map(child => (child.style.height)))
                            : childrenHasHeight.reduce((sum, child) => sum + child.style.height, 0)
                }
            }

            if (typeof dom.style.width === 'undefined') {
                const childrenHasWidth = children.filter(child => typeof child.style.width === 'number')
                if (childrenHasWidth.length > 0) {
                    dom.style.width
                        = dom.style.direction === 'horizontal'
                            ? childrenHasWidth.reduce((sum, child) => sum + child.style.width, 0)
                            : Math.max(...childrenHasWidth.map(child => child.style.width))
                }
            }
        }
        return doms
    }

    return function render(html, css) {
        const doms = mergeStyle(parseHtml(html), parseStyle(css))
        renderDoms(doms)
    }

})()

编辑于 2019-09-21

文章被以下专栏收录