前端面试题总结

393 阅读5分钟

HTML相关

有哪些是块状元素和内联元素?

HTML可以将元素分类(display)方式分为行内元素(display:inline)、块状元素(display:block)和行内块状元素(display:inline-block)三种

块状元素特点:如div、p、nav、aside、header、footer、section、article、ul-li、address等等

  • 能够识别宽高
  • margin和padding的上下左右均对其有效
  • 可以自动换行
  • 多个块状元素标签写在一起,默认排列方式为从上至下

内联元素的含义:如span、b、i、img、a等等

  • 设置宽高无效
  • 对margin仅设置左右方向有效,上下无效;padding设置上下左右都有效,即会撑大空间
  • 不会自动进行换行

CSS相关

重绘(Repaint)和回流(Reflow)

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

  • 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
  • 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
  • 重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。
  • 重绘是当节点需要更改外观而不会影响布局的,比如改变 color就叫称为重绘
  • 回流是布局或者几何属性需要改变就称为回流。
  • 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。

所以以下几个动作可能会导致性能问题

  • 改变 window 大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型

很多人不知道的是,重绘和回流其实和 Event loop 有关

  • 当 Event loop 执行完 Microtasks后,会判断 document 是否需要更新。- 因为浏览器是 60Hz 的刷新率,每 16ms才会更新一次。
  • 然后判断是否有resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
  • 判断是否触发了 media query
  • 更新动画并且发送事件
  • 判断是否有全屏操作事件
  • 执行 requestAnimationFrame回调
  • 执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
  • 更新界面
  • 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。

减少重绘和回流

使用 translate 替代 top

<div class="test"></div>
<style>
	.test {
		position: absolute;
		top: 10px;
		width: 100px;
		height: 100px;
		background: red;
	}
</style>
<script>
	setTimeout(() => {
        // 引起回流
		document.querySelector('.test').style.top = '100px'
	}, 1000)
</script>
  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
  • 把 DOM 离线后修改,比如:先把 DOM 给 display:none(有一次 Reflow),然后你修改100次,然后再把它显示出来
  • 不要把 DOM结点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 1000; i++) {
    // 获取 offsetTop 会导致回流,因为需要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop)
}
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
  • CSS选择符从右往左匹配查找,避免 DOM 深度过深
  • 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video 标签,浏览器会自动将该节点变为图层。

盒子模型

JavaScript相关

值类型

包含:undefined、boolean、number、string、symbol

引用类型

包含:[] 、 {} 、 function 、 null

值类型和引用类型的区别

值类型:值类型的值放在栈内存中(另一种说法:指针直接指向值)

引用类型:引用类型的值放在堆内存中(另一种书法:指针指向值的地址)

// 值类型
var a = 100
var b = a
b = 200
console.log(a)    // 100

// 引用类型
var obj1 = {a:100}
var obj2 = obj1
obj2.a = 200
console.log(obj1.a)  // 200

判断类型

typeof可判断哪些类型?

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分)

深拷贝

/**
 * 深拷贝
 */

const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    },
    arr: ['a', 'b', 'c']
}

const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }

    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }

    // 返回结果
    return result
}

原型和原型链

ES5-ES6原型,原型链

所有的引用类型(数组、对象、函数)都具有对象特性,即可自由扩展属性(除了“null”)

const obj = {}
obj.a = 100

const arr = []
arr.a = 200

function fn() {}
fn.a = 300

所有的引用类型(数组、对象、函数)都有一个 proto 属性(隐式原型属性),属性值是一个普通的对象

所有的函数和class,都有一个 prototype(显式原型)属性,属性值也是一个普通的对象

所有的引用类型(数组、对象、函数), proto 属性值(隐式原型属性)指向它的构造函数的“prototype”属性值

原型链:当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_(即它的构造函数的 prototype(显式原型))中寻找

执行上下文(简称:上下文)

概念:变量或函数的上下文决定了它们可以访问到哪些数据,以及它们的行为。

特点:每个上下文都有一个关联的变量对象,这个对象里包含了这个上下文中定义的所有变量和函数

全局上下文是最外层的上下文,就是window对象

本人通俗的理解:上下文就是作用域的问题。内部可以使用外部的属性或者方法,但是外部没办法使用内部的属性或者方法

this指向的问题

所有场景中this所取的值是在函数执行的时候决定的,而不是定义的时候

箭头函数中,this指向的是定义箭头函数

function fn1(){
    console.log(this)
}
fn1   // 打印结果:window

------------------------------------------------------
fn1.call({ x: 100 }) // 打印结果:{ x: 100 }
------------------------------------------------------
const fn2 = fn1.bind({ x: 200 })
fn2()   // 打印结果: { x: 200 }
------------------------------------------------------
const zhangsan = {
    name: '张三',
    sayHi(){
        console.log(this)
        // this 即当前对象
    }
    wait(){
        setTimeout(function() {
            console.log(this)
            // this指向window
        })
    }
}
--------------------------------------------------------
const zhangsan = {
    name: '张三',
    sayHi(){
        console.log(this)
        // this 即当前对象
    }
    wait(){
        setTimeout(() => {
            console.log(this)
            // this指向当前对象
        })
    }
}
----------------------------------------------------------
class People {
    constructor(name){
        this.name = name
        this.age = 20
    }
    sayHi(){
        console.log(this)
    }
}
const zhangsan = new People('张三')
zhangsan.sayHi() // 打印结果: zhangsan对象

手写bind

      Function.prototype.newBind = function () {
        // 将参数拆解为数组
        console.log('arguments',arguments)
        const newArr = Array.prototype.slice.call(arguments);
        console.log("newArr", newArr);
        
        // 获取this(数组第一项)
        const newThis = newArr.shift();

        // xxx.newBind(....)中的xxx
        const self = this;

        return function () {
          return self.apply(newThis, newArr);
        };
      };

      function fn1(a, b) {
        console.log("this", this);
        console.log(a, b);
        return "Hello";
      }

      const fn2 = fn1.newBind({ x: 100 }, 10, 20);
      fn2();

闭包

概念:闭包指的是那些使用了另外一个函数作用域内的变量的函数,通常是在嵌套函数中实现的。

闭包:自由变量的查找,是在函数定义的地方,向上级作用作用域查找,不是在执行的地方

function create(){
    const a = 100
    return function () {
        console.log(a)
    }
}

const a = 200 
fn = create()
fn()    // 输出结果:100

--------------------分割线-------------------

function print(fn) {
    const a = 100
    fn()
}
const a = 200
function fn(){
    console.log(a)
}
print(fn) // 输出结果:200

实际开发中闭包的应用

  • 隐藏数据
  • 做一个简单的cache工具
      function createCache() {
        const data = {};
        return {
          set: function (key, value) {
            data[key] = value;
          },
          get: function (key) {
            return data[key];
          },
        };
      }

      const newData = createCache();
      newData.set("a", 100);
      console.log(newData.get("a"));

异步和单线程

js是单线程语言,只能同时做一件事

异步和同步的区别

  • 基于js是单线程语言
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行

异步应用场景

  • 网络请求,如ajax图片加载
  • 定时任务,如setTimeout
      /* Promise图片加载实例 */
      function loadImg(src) {
        return new Promise((resolve, reject) => {
          const img = document.createElement("img");
          img.onload = () => {
            resolve(img);
          };
          img.onerror = () => {
            reject(new Error("图片加载失败"));
          };
          img.src = src;
        });
      }

      const url1 =
        "https://gimg2.baidu.com/image_search/src=http%3A%2F%2F1812.img.pp.sohu.com.cn%2Fimages%2Fblog%2F2009%2F11%2F18%2F18%2F8%2F125b6560a6ag214.jpg&refer=http%3A%2F%2F1812.img.pp.sohu.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1625236813&t=b636a3bec755a4639ad07f59b28fe2cc";
      const url2 =
        "https://gimg2.baidu.com/image_search/src=http%3A%2F%2F2c.zol-img.com.cn%2Fproduct%2F124_500x2000%2F748%2FceZOdKgDAFsq2.jpg&refer=http%3A%2F%2F2c.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1625236813&t=0ec6f945dc883b312daef0050bbdc290";

      loadImg(url1)
        .then((img) => {
          console.log(img.width);
          return img; // 普通对象
        })
        .then((img) => {
          console.log(img.height);
          return loadImg(url2); // Promise对象
        })
        .then((img) => {
          console.log(img.width);
          return img;
        })
        .then((img) => {
          console.log(img.height);
        })
        .catch((ex) => console.log(ex));

event loop(事件循环/事件轮询)

微任务和宏任务

微任务:setTimeout setInterval Ajax Dom事件

宏任务:Promise async/await

微任务和宏任务区别

  • 微任务执行时机比宏任务要早
  • 微任务在DOM渲染前触发,宏任务在DOM渲染后触发。
  • 微任务是ES6规定的,宏任务是浏览器规定的。

React框架

异步组件

  • import()
  • React.lazy
  • React.Suspense

性能优化

React中默认:父组件有更新,子组件则无条件也更新!!!

  • shouldComponentUpdate(简称SCU)
shouldComponentUpdate(nextProps, nextState){
    if(nextProps.a !== this.props.a) { 
        return true
    }
    return false  
}
  • PureComponent
  • memo
  • immutable.js

Redux使用

React原理

函数式编程

  • 一种编程范式
  • 纯函数
  • 不可变值

更新中