前端面试题2021

1,825 阅读10分钟

记录最近前端面试题

  1. vue-router 有几种模式,分别有什么区别,用到了哪些H5的api hash, history

    hash模式:

  • url路径会出现“#”号字符
    
  • hash值不包括在Http请求中,它是交由前端路由处理,所以改变hash值时不会刷新页面,也不会向服务器发送请求
    
  • hash值的改变会触发hashchange事件
    
    history模式:
  • 整个地址重新加载,可以保存历史记录,方便前进后退
    
  • 依赖H5 API和后台配置,没有后台配置的话,页面刷新时会出现404
    
    利用pushState() ,replaceState() 可以对浏览器的历史记录栈进行修改,当浏览器跳转到新的状态的时候,将触发popState 事件
  1. Node与Element的区别

    1. Node是节点,其中包含不同类型的节点,Element只是Node节点的一种。
    2. Element继承与Node,可以调用Node的方法。
    3. 给所有DOM元素添加方法,只需要污染Node或者Element的原型链就行。
  2. 伪数组与数组

    1. 对象没有数组的Array.prototype 的属性值,类型是 Object ,而数组类型是 Array
    2. 数组是索引,对象是键值对
    3. 使用对象创建伪数组,伪数组可以使用部分方法 ,例如:[].push.call(obj, 'd'); 常用的伪数组有:函数内部的arguments,new Set返回的值
  3. new操作符做哪些操作

  • 创建了一个新的空对象o
  • 让这个o对象的 __proto__指向函数的原型prototype
  • this指向o对象
  • 将o对象赋给a对象
    o.__proto__ = Foo.prototype;
    Foo.call(o);
    a = o;
  1. 获取页面所有dom节点,并去重 document.getElementsByTagName('html'),循环获取得节点,判断节点类型是不是为1,则将节点属性为1的标签作为属性值存在对象中,再判断有没有孩子节点,有的话化递归一下

  2. Set, Map, WeakSet, WeakMap的区别

    • Set

      • 成员唯一、无序且不重复
      • [value, value],键值与键名是一致的(或者说只有键值,没有键名)
      • 可以遍历,方法有:add、delete、has
    • WeakSet

      • 成员都是对象
      • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
      • 不能遍历,方法有add、delete、has
    • Map

      • 本质上是键值对的集合,类似集合
      • 可以遍历,方法很多可以跟各种数据格式转换
    • WeakMap

      • 只接受对象作为键名(null除外),不接受其他类型的值作为键名
      • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
      • 不能遍历,方法有get、set、has、delete
  3. webpack中style-loader,css-loader的作用

  • less-loader 是将less语法转为css
  • css-loader只是帮我们解析了css文件里面的css代码转为js。
  • style-loader将css-loader解析后的内容挂载到html页面
  1. vue父子组件传值,兄弟组件传值, 孙子组件传值
  • 父子组件传值: props, emit,emit, parent, children,children, root, attrs,attrs, listener,ref, $refs, provide/inject
  • 兄弟组件传值: eventBus, vue.prototype.eventBus = new Vue(), 子组件通过this.eventBus.emit(name,所传的),兄弟组件通过this.eventBus.emit('name', '所传的值'),兄弟组件通过 this.eventBus.on('name','所传的值')
  1. ...解构赋值, object.assign(), concat是浅拷贝还是深拷贝

  2. 手写防抖函数

function debounce (func, wait) {
        let timer;
        return function() {
          let context = this; // 注意 this 指向
          let args = arguments; // arguments中存着e

          if (timer) clearTimeout(timer);

          timer = setTimeout(() => {
            func.apply(this, args)
          }, wait)
        }
    }
  1. 手写深拷贝函数
function cloneDeep (obj){
    // 1.判断是否为null 或undefined
    if (typeof obj == null ) return obj;
    // 2.判断是否为日期Date
    if (obj instanceof Date) return new Date(obj);
    // 3.判断是否为正则 typeof /\d+/ === 'object'
    if (obj instanceof RegExp) return new RegExp(obj);
    // 4.如果不是数组或对象,返回该值
    if (typeof obj !== 'object') return obj;
    // 接下来,要么是对象,要么是数组 可以用 new obj.constructor得到它类型的空值
    let cloneObj = new obj.constructor;
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 递归深拷贝
            cloneObj[key] = cloneDeep(obj[key]);
        }
    }
    return cloneObj;
}
  1. 闭包的作用,以及实现 闭包就是能够读取其他函数内部变量的函数

  2. promise中的源码实现,比如all方法是怎么实现的

Promise.prototype.all = function(promises) {
  let results = [];
  let promiseCount = 0;
  let promisesLength = promises.length;
  return new Promise(function(resolve, reject) {
    for (let i; i< promisesLength; i++) {
      Promise.resolve(promises[i]).then(function(res) {
        promiseCount++;
        // results.push(res);
        results[i] = res;
        // 当所有函数都正确执行了,resolve输出所有返回结果。
        if (promiseCount === promisesLength) {
          return resolve(results);
        }
      }, function(err) {
        return reject(err);
      });
    }
  });
};
  1. vue模板编译过程 Vue中模版的编译是如下过程:模版--->ast(抽象树)-->render 函数->虚拟 dom->实际 dom。

  2. diff算法

  3. vue双向数据绑定以及监听更新

  4. eventloop的运行机制

  5. vue3的新特性, 静态模板

  6. 怎样让for of 遍历对象不报错,或者说怎么将对象转为可迭代器

  7. canvas的方法 ctx = canvas.getContext("2d") canvas.toDataURL("image/jpg", 0.6) ctx.drawImage(img, 0, 0, newW, newH) ctx.getImageData(x, y, el.width, el.height); ctx.putImageData(oImgData, 0, 0);

  8. 原型与原型链,执行上下文 原型: 函数有prototype,是个对象,指向内存地址中这个构造函数的属性和方法 原型链: 每个对象都有一个__proto__属性,指向构造该对象的构造函数的原型

  9. 设计模式 单例模式,观察者模式,原型模式,工厂模式,装饰者模式

  10. 图片压缩以及比例缩放 利用canvas

  11. webpack如何做编译,实现按需加载 webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 [依赖图(dependency graph)],然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容

webpack中使用require.ensure实现按需加载 image.png

  1. requestAnimationFrame 是怎么实现的

优势:A. 浏览器可以优化并行的动画动作, 更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果 B. 一旦页面不处于浏览器的当前标签,就会自动停止刷新, 这就节省了cpu,Gpu和电力

  1. 纯函数 只依赖参数的函数,同样的参数得到同样的返回结果

  2. vue3中setup与vue2中的created有何不同, 为什么要加一个setup函数

  3. vue指令有几个勾子

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
  • unbind:只调用一次,指令与元素解绑时调用。

勾子有四个参数el, binding, vnode,oldVnode

  1. HTML代码中<% %>、<%=%>、<%:%>各是什么意思 <%%> 中间可以写服务端代码

<%=%> 与<%:%> 表示取后台变量

  1. htmlwebpackplugin 插件是干嘛的 生成新的index.html文件,并自动将webpack打包的bundle.js自动引入

  2. 获取页面中所有标签,去重

 let allDom = document.querySelectorAll('*')
        let obj = {}
        for(let i in allDom){
            let name = allDom[i].localName
            obj[name] = allDom[i]
        }
        let allLabel = Object.keys(obj)
        console.log('aa', allLabel)
  1. 浏览器重绘与重排

重排(也称回流):当DOM的变化影响了元素的几何属性(宽或高),浏览器需要重新计算元素的几何属性,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树,然后将渲染树绘制到屏幕上, 叫做重排, 重排必重绘

重绘: 将渲染树将受影响的部分渲染到屏幕上叫重绘

重排何时发生

很显然,每次重排,必然会导致重绘,那么,重排会在哪些情况下发生?

  • 添加或者删除可见的DOM元素
  • 元素位置改变
  • 元素尺寸改变
  • 元素内容改变(例如:一个文本被另一个不同尺寸的图片替代)
  • 页面渲染初始化(这个无法避免)
  • 浏览器窗口尺寸改

获取布局信息的操作会导致队列刷新,比如:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
  • scrollTop, scrollLeft, scrollWidth, scrollHeight
  • clientTop, clientLeft, clientWidth, clientHeight
  • getComputedStyle() (currentStyle in IE)

总结

重排和重绘是DOM编程中耗能的主要原因之一,平时涉及DOM编程时可以参考以下几点:

  • 尽量不要在布局信息改变时做查询(会导致渲染队列强制刷新)
  • 同一个DOM的多个属性改变可以写在一起(减少DOM访问,同时把强制渲染队列刷新的风险降为0)
  • 如果要批量添加DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排(fragment元素的应用)
  • 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
  1. 柯里化函数 柯里化是一种将使用多个参数的函数转换成使用单参数的函数链,也就是将一个n参函数转成n个一元函数。 使用场景,比如bind。 例: f(a,b,c) = f(a)(b)(c)

  2. 偏函数 偏函数则是固定一个函数的一个或者多个参数,也就是将一个n元函数转换成n-x元函数。 例: f(a,b,c) = f(a,b)(c)

  3. compose函数, 利用reduceRight() 加载方向是从右往左

  4. pipe函数 加载方向是从左往右

  5. 强缓存与协商缓存 www.jianshu.com/p/9c95db596…

在浏览器中输入URL并回车后都发生了什么?

  1. 解析URL

  2. DNS解析

  3. 浏览器与网站建立Tcp链接

  4. 请求和传输数据

  5. 浏览器渲染页面

  6. 自己实现一个eventBus,或者说vue的eventBus实现原理

  7. webpack打包做了什么 它会在内部从一个或多个入口点构建一个依赖图,然后将项目中所需的每一个模块组合成一个或多个bundles,它们均为静态资源,用于项目展示内容

  8. loader是什么 loader让webpack能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被加载到依赖视图中

  9. plugin是什么 插件是扩展webpack的功能,可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量

  10. webpack配置

  11. CommonJS规范,AMD规范 CommonJS规范是同步模块加载, AMD规范是异步模块加载

CommonJS 规范是为了解决 JavaScript 的作用域问题而定义的模块形式,可以使每个模块它自身的命名空间中执行。该规范的主要内容是,模块必须通过 module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。

AMD规范中模块通过 define 函数定义在闭包中,格式如下:

id 是模块的名字,它是可选的参数。 dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数一次传入 factory 中。如果没有指定 dependencies ,那么它的默认值是 ["require", "exports", "module"] 。 factory 是最后一个参数,它包裹了模块的具体实现,它是一个函数或者对象。如果是函数,那么它的返回值就是模块的输出接口或值。