前端常见面试题(持续更新中)

HTML

  • 回流和重绘是什么,如何减少回流、重绘?

    当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。 每个页面至少需要一次回流,就是在页面第一次加载的时候。 在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

    回流一定会导致重绘,重绘不一定导致回流。

    减少回流重绘的方法:减少对render tree的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:

    1. 直接改变className
    2. 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
    3. 使用display:none技术,只引发两次回流和重绘; ( 只是减少重绘和回流的次数,display:none 是会引起重绘并回流,相对来说,visibility: hidden只会引起重绘 )
    4. 不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存
    5. 让元素脱离动画流,减少回流的Render Tree的规模

    其它资料

CSS

  • 如何使用CSS画一个三角形?

    widthheight设置为0, border设置为宽度和transparent,border-left设置为宽度和颜色。

  • 关于CSS盒模型
    • width默认是算不算paddingborder的(box-sizing: content-box),除非设置为box-sizing: border-box
    • IE 5和6的呈现却是不正确的。他们会默认box-sizing: border-box,计算上borderpadding。避免方法是将边距添加到父元素和子元素。IE8 及更早IE版本不支持设置填充的宽度和边框的宽度属性。链接

JavaScript

  • 事件传递中,event.targetevent.currentTarget的区别是什么?

    event.targent是触发事件监听的对象,event.currentTarget是添加事件监听的对象。示例代码:链接

  • 请手撸一个防抖和节流

    防抖:触发的事件在一段时间内没有再触发事件才会执行。触发是时候不知道会不会执行

    function debounce(fn, delay=0){
        if (typeof fn !== 'function'){
            throw new TypeError("Function not valid")
        }
        if (typeof delay!== 'number'){
            throw new TypeError("Delay not valid")
        }
    
        let timeid = null
    
        return function(){
            let context = this
            let args = arguments
    
            if (timeid){
                clearTimeout(timeid)
            }
    
            timeid = setTimeout(function(){
                return fn.apply(context, args)
            }, delay)
        }
    }
    
    // 测试代码
    function log(text){
        console.log(text)
    }
    myFun = debounce(log, 2000)
    myFun(1)
    myFun(1)
    myFun(1)
    myFun(1)

    节流:一个时间段内只会调用一次事件处理函数。触发是时候就能知道会不会执行

    function throttle(fn, delay){
        if (typeof fn !== 'function'){
            throw new TypeError("Function not valid")
        }
        if (typeof delay!== 'number'){
            throw new TypeError("Delay not valid")
        }
    
        let timer = null
    
        return function(){
            let context = this
            let args = arguments
            if (!timer){
                timer =  setTimeout(function(){
                    result = fn.apply(context, args)
                    timer = null
                    return result
                }, delay)
            }
        }
    }
    
    // 测试代码
    function log(text){
        console.log(text)
    }
    myFun = throttle(log, 2000)
    setInterval(()=>{myFun(1), 200})
    • 拓展问题:function.prototype.callfunction.prototype.apply的区别:

      function.prototype.call的参数是Function.call(obj,[param1[,param2[,…[,paramN]]]])

      function.prototype.apply的参数是Function.apply(obj[,argArray])

      另:function.prototype.bind返回一个新函数,但是不执行,需要调用才会执行。其接受的参数类似于function.prototype.call

  • JavaScript函数的this绑定问题

    在标准函数中,this引用的是把函数当成方法调用的上下文对象,箭头函数中,this引用的是定义箭头函数的上下文。

    例题

    var length = 10;
    function fn() {
        console.log(this.length)
    }
    
    var obj = {
        length: 5,
        method: function (fn) {
            fn.bind(this)();  // 3
            fn();  // 4
            arguments[0]();  // 5
        }
    }
    fn()  // 1
    obj.method(fn)  // 2

    执行顺序是:1, 2, 3, 4, 5

    其中1, 3, 4, 5的输出依次是:10, 5, 10, 1(浏览器中),或者undefined, 5, undefined, 1(node中)

    原因:

    1. fn绑定window(var会自动绑定window)或者全局变量,输出10或者undefined
    2. 显式绑定obj,输出5
    3. 同1,此处并没有上下文
    4. 绑定了arguments,arguments的长度是1
  • 请手撸一个深拷贝 ❓

    function isType(obj, type) {
        return Object.prototype.toString.call(obj).indexOf(type) !== -1
    }
    
    function deepCopy(obj) {
        if (typeof obj !== 'object') {
            return obj
        }
        else if (isType(obj, "String") || isType(obj, "Number") || isType(obj, "Boolean") || isType(obj, "Null")) {
            return obj  // 如果需要保持是一个包装类型的基本类型的话,再另作讨论
        }
        else if (isType(obj, "Date")) {
            return new Date(obj.valueOf())
        }
        else if (isType(obj, "RegExp")) {
            return new RegExp(obj.valueOf())
        }
        else if (isType(obj, "Set")) {
            return new Set(obj)
        }
        else if (isType(obj, "Map")) {
            return new Map(obj)
        }
        let newObj = Array.isArray(obj) ? [] : {}
        for (const key in obj) {
            if (typeof obj[key] == 'function') {
                newObj[key] = obj[key].bind(newObj)
            }
            else {
                newObj[key] = deepCopy(obj[key])
            }
        }
    
    }
    • 如何判断object的具体类型 链接
      • typeof
      • Object.prototype.toString.call(obj).indexOf(type) !== -1
      • obj instanceOf Object
  • JS的数组有哪些方法 ❓

    React

  • React的生命周期钩子有哪些?

    componentDidMount, shouldComponentUpdate(nextProps, nextState), componentDidUpdate(prevProps, prevState, snapshot), componentWillUnmount

  • React的hooks有哪些?

    基本:useState, useEffect, useContext

    额外:useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, useDebugValus

    • useEffect的第二个参数是干什么的?

      第二个参数是一个依赖数组,当且仅当第二个依赖数组中有任何一个量改变的时候,useEffect第一个参数中的函数才会执行。

    • useCallbackuseMemo分别是干什么的,有什么异同?

      useCallback用于返回一个回调函数(而useEffect直接执行函数),这个函数会使用依赖的值,当依赖改变的时候函数的效果才会改变。useMemo用于返回一个memorized值,这个值取决于依赖。他们的共同点是都使用了闭包,都有依赖数组(他们调用的值都会是上次依赖数组更新的时候存下来的值,如果调用了其它外部的、不在依赖数组的值,这些值也会被保存下来,直到依赖数组的值改变才会更新)。

    • useContext是干什么的?

      提供一个上下文环境,共享那些对于一个组件树来说是全局的数据。

  • React组件之间的通信方式

    • 父组件传给子组件:props逐级传递、使用ref传递
    • 子组件传给父组件:使用回调函数
    • 父组件向后代所有组件:Context.Provider
    • 一个数据源跨组件通信:指定contextType
    • 多个数据源跨组件通信:使用Context.Consumer
  • 什么时候使用React class components,而不使用React function components
    1. 需要使用ref但是又不能使用ref forwaring的情况
    2. React Hooks不能代替Lifecycle的情况
    3. React的memo都不能代替shouldComponentUpdate的情况
    4. Error boundaries: getDerivedStateFromError, componentDidCatch
    5. 当需要实现一些容器组件的时候,需要改变内部状态来实现组件的改变的时候❓