前端面试题整理

893 阅读4分钟

vue

  1. vue双向数据绑定
  • vue2 Object.defineProperties数据劫持 + 发布者-订阅者模式
  • vue3 使用Proxy数据劫持

完整代码

  1. vue-router实现

    路由监听

    1. hash监听

      function changeView(view) {
          document.body.innerHTML = view;
      }
      
      const routes = [{
          path: "/asd",
          view: `<div>asd</div>`
      }];
      window.onload = window.onhashchange = function () {
          const path = location.hash.substr(1);
          const {view=""} = routes.find(({path: rPath}) => rPath === path) || {};
          changeView(view);
      };
      
    2. history监听

      function changeView(view) {
          document.body.innerHTML = view;
      }
      
      const routes = [{
          path: "/asd",
          view: `<div>asd</div>`
      }, {
          path: "/asdd",
          view: `<div>asdd</div>`
      }];
      
      function updateView() {
          const path = location.pathname;
          const {view = ""} = routes.find(({path: rPath}) => rPath === path) || {};
          changeView(view);
      }
      
      window.onpopstate = updateView;
      history.pushState = (() => {
          const fn = history.pushState;
          return (url, title = "", data = {}) => (fn.call(history, data, title, url), updateView());
      })();
      history.replaceState = (() => {
          const fn = history.replaceState;
          return (url, title = "", data = {}) => (fn.call(history, data, title, url), updateView());
      })();
      updateView();
      

    路由组件实例化切换

    import Vue from "vue";
    let lastCmp;
    export function changeView(CmpTemp, el = document.querySelector("#app")) {
        if (lastCmp) {
            lastCmp.$destroy();
            lastCmp.$el.parentNode.removeChild(lastCmp.$el);
        }
        const CmpConstructor = Vue.extend(CmpTemp);
        lastCmp = new CmpConstructor();
        lastCmp.$mount();
        el && el.appendChild(lastCmp.$el);
    }
    
    
  2. vuex概念 和几个api

    概念

    • 集中式的状态管理库
    • 所有属性是响应式的 自动更新使用其属性的组件
    • 只能使用commit改变状态 实现单向数据流

    api

    1. commit 提交一个mutation改变state
    2. dispatch 提交一个action
    3. mapState/Mutations/Actions各种语法糖
    4. namespace开启子模块命名空间 然后可以将子模块注册到父模块的modules中
  3. vue异步加载
    1. 使用()=>import(组件)来加载组件当判断为组件是一个函数的时候会在用到它的时候执行,返回一个promise 当组件加载完成后挂载到页面上

    2. require.ensure已被import代替 效果相似

      resolve => require.ensure([依赖], () => resolve(require('./asd.vue')), 'chunkName')
      
  4. vue父子通讯几种方法
    1. props/$emit
    2. provide/inject
    3. parent/\children
    4. vuex
    5. eventBus $on/$off/$emit
  5. 指令/插件
    //指令+插件
    export default function CustomPlugin(Vue: VueConstructor, option) {
        Vue.mixin({
            directives:{
                custom:{
                    //初始化时调用,尚未加入到dom中
                    bind(el,binding){},
                    //插入父节点时调用
                    inserted(){},
                    //使用了该指令的整个组件更新时
                    update(){}
                }
            }
        });
    }
    //使用
    Vue.use(CustomPlugin);
    
    
  6. 生命周期

    create从外到内 mount/destory从内到外

    create/mount/update/active(keepalive专属)/destory

  7. ssr和spa

    spa:

    单页应用,即整个应用的排版结构都是由js在页面解析完后生成的,页面的切换也是用js进行修改dom结构

    优点:减少服务器压力,因为js等静态文件可以进行缓存,服务器只需要提供首页很少的数据。

    缺点:垃圾搜索算法引擎无法识别内容,因为客户端渲染需要一个js执行环境 并且是异步进行的,搜索引擎无法判断

    ssr:

    服务端渲染,将整个页面构建完成后输出

    优点:利于垃圾搜索引擎识别其内容

    缺点:压力较大,需要在服务端执行js生成页面接口输出

  8. webpack优化 / 首屏优化 vendor拆分
    • 多线程打包 happypack

    • 第三方依赖包缓存 autodll-webpack-plugin

    • gzip压缩代码

    • import() 异步组件 懒加载页面

    • 合理调整 splitChunks 使vendor变小 因为资源放CDN的话 并发请求比单独请求一个大js要快

    • 开启http2 多路复用 规避并发资源问题

    • code split 代码分割 异步加载组件代码

    • 图片压缩/精灵图合并

    • 使用webpack的配置,将代码中用到的模块使用cdn导入,然后配置到externals 中,打包时会自动忽略改模块的引入 而是直接使用对应的全局变量 vendor拆分

      //先将模块引入
      <script src="./lodash.js"></script>
      //在webpack配置中设置映射对应的全局变量
      configureWebpack: {externals: {"lodash": "_"}}
      //在代码中导入,当打包时会自动忽略该引入 而改为lod = window._
      import lod from "lodash";
      console.log(lod)
      
  9. vue中key的作用
    • 更高效的让vdom diff时识别差异,提高节点复用率,不至于浪费性能渲染可复用dom
  • 防止就地复用的导致transition-group动画不生效,给每个标签打上标识后利于动画识别
  1. watch和computed的区别

    computed 会缓存值,只有在下一次获取该值时才会重新计算 应用:一个值依赖另一个值进行计算

    watch 只要监听的值发生改变就会触发 应用:需要根据数据实时发生操作的情况 例如请求或者绘制

  2. vue为什么不能监听数组

    Object.defineProperty不能监听数组的未知下标,所以当数组发生动态改变时无法触发更新

  3. 为什么data是一个函数

    因为模板组件导出的是一个对象,如果进行复用的话那每个生成的vue对象引用的data对象是同一个,当其中一个发生改变,所有的都会进行改变,所以使用函数,保证每个data都是唯一的 互不影响

  4. v-model

    v-model是:value/$emit("input",xxx)的语法糖

  5. vue优化
    1. v-if/v-show 区分场景使用
    2. computed/watch 区分场景使用
    3. 路由/组件懒加载
    4. 懒加载列表
    5. 懒加载图片
    6. 预加载静态资源
  6. mvc与mvvm区别
    1. mvc是model发生变化后controller去修改view
    2. mvvm是model发生后 根据view和model的绑定关系 自动修改view

dom

  1. 前端性能优化
    1. 精灵图 减少请求
    2. 静态资源缓存
    3. 压缩代码、图片
    4. gzip
    5. 异步加载代码
    6. 字体图标使用iconfont
    7. base64小图片
    8. 使用cdn
    9. 图片懒加载、内容骨架图
    10. 减少重绘回流
    11. 优化dom结构删除不必要元素,优化css选择器 浏览器从右向左匹配 避免过深的匹配
  2. 网页从输入网址到渲染完成经历了哪些过程?

    并行创建dom tree、cssom tree -> 合并成render tree -> 计算布局layout -> 绘制paint

  3. 事件流 阻止浏览器默认行为/阻止事件冒泡
    • 事件流

      从外到内捕获->从内到外冒泡 event.target是当前触发事件的元素event.currentTarget是当前绑定事件的元素

    • 阻止默认行为e.preventDefault() 阻止事件冒泡e.stopPropagation()

  4. 解决跨域问题/jsonp实现
    • jsonp

      function jsonp(url, callback, params = {}) {
          const script = document.createElement("script");
          //防止并发重名
          const fnName = `fnName${Math.random().toString(16).substr(2)}`;
          window[fnName] = function () {
              const res = callback.apply(this, arguments);
              window[fnName] = null;
              script.parentElement.removeChild(script);
              return res;
          };
          script.src = `${url}${obj2query({...params, callback:fnName })}`;
          document.body.appendChild(script);
      }
      
    • 后端设置允许跨域响应头

      res.setHeader("Access-Control-Allow-Origin", "*");
      res.setHeader("Access-Control-Allow-Headers", "Content-Type"); 
      res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
      
  5. 优化dom操作
    1. 使用innerHTML或者createDocumentFragment插入大量节点
    2. 在遍历外缓存offsetWidth等需要计算元素布局的值
    3. dom操作是通过浏览器操作c构建的数据结构,而非直接在js环境操作 所以比较耗时。在大量操作时使用vdom代替
  6. 行内元素/块级元素有哪些
    • 行内元素

      span/em/i/img/label/strong/input

    • 块级元素

      div/section/p/main/figure/figcaption/ul/ol

  7. img标签alt和title区别

    alt是图片加载失败时的代替文字,title是鼠标放在图片上的描述详情

  8. h5本地存储几种方式 过期时间
    • cookie 默认浏览器关闭消失,可以设置其过期时间
    • localStorage 不主动删除不会消失
    • sessionStorage 标签页关闭消失
    • indexeddb 类似sqllite 不主动删除不会消失 不常用
  9. 回流和重绘

    回流一定会重绘,重绘不一定会回流

    • 回流触发

      1. 窗口调整
      2. 元素布局或内容变化
      3. dom操作
      4. css伪类激活
      5. 计算元素的布局信息 offsetWidth
    • 重绘触发 当页面渲染发生改变时就会触发

    • 优化

      1. 少用table布局
      2. 使用innerHTML/DocumentFragment进行dom操作
      3. 使用display:none,将元素隐藏后操作,只引发两次回流和重绘;
      4. 使用translate来代替left top
      5. 使用绝对定位将元素单独提出来 这样回流仅影响一小部分
  10. 浏览器缓存机制 内存/硬盘 http

    加载图片速度:直接加载内存中已经准备好的image对象 > 内存读取图片 > 硬盘读取图片 > http

    1. 已经被加载一次的小文件下次访问时会直接从内存中取出

    2. 请求的文件如果带有缓存策略响应头,下次访问则会重定向到本地硬盘读取

    3. http缓存策略

      1. expires

      2. cache-control

        1. no-store 禁止缓存

        2. no-cache 强制确认缓存 每次需要发个请求给服务器 判断文件是否有修改 未修改才会拿本地缓存

        3. max-age = 999999999 缓存时间 直接从本地拿去 无需发请求确认

        当max-age时间到后不会直接清除本地缓存, 而是发请求判断本地缓存的文件是否还是最新鲜的 即未修改 则304继续使用本地的缓存 节省带宽

        expires是http1.0的,cache-control是http1.1的 当两者同时存在时cache-control覆盖expires

      3. 304本地重定向

        1. last-modified/etag 判断文件最后修改时间
    4. ajax实现
      function ajax(method = "GET", url, params = {}) {
          const xhr = new XMLHttpRequest();
          return new Promise((resolve, reject) => {
              xhr.onreadystatechange = function () {
                  if (this.readyState !== 4) return;
                  if (this.status !== 200) return reject("请求出错");
                  resolve(JSON.parse(this.response))
              };
              xhr.open(method, method === "GET" ? `${url}${obj2query(params)}` : url, true);
              if (method === "POST") xhr.setRequestHeader("Content-Type", "application/json");
              xhr.send(JSON.stringify(params));
          })
      }
      

js

  1. 异步是什么/闭包是什么/递归是什么
    • 异步:浏览器是单线程环境,为了防止操作阻塞UI渲染,会将耗时操作放在同步任务结束后再执行。
    • 闭包:闭包是指有权限访问(引用)另一个函数作用域的变量的函数,用途:创建一个私有变量环境,防止变量污染。
    • 递归:重复调用自身函数,需要一个出口,不然会导致堆栈溢出。
  2. 深拷贝/浅拷贝
    • 浅拷贝 只拷贝基础类型,引用类型只是复制引用
      • Object.assign()
      • 拓展符{...obj}
    • 深拷贝
      • JSON.parse(JSON.stringify(obj)) 不能复制不能被解析的值(symbol,function)和循环引用
      • lodash.deepClone
      • 或者可以自己实现一个,需要注意循环引用,可以用weakMap来缓存对象,将循环引用的属性指向缓存的对象引用
  3. 数组排序/乱序/分组/去重/reduce
    • 排序

      [5,2,1,5,3].sort((p,c)=>p-c)
      //正数降序 负数升序 0排一起
      
      for (let i = arr.length-1; i > 0 ; i--) {
          for (let j = 0; j < i; j++) {
              if (arr[i] < arr[j]) continue;
              [arr[i], arr[j]] = [arr[j], arr[i]]
          }
      }
      
    • 乱序

      for (let i = 0; i < arr.length; i++) {
          const idx = Math.floor(Math.random() * arr.length);
          [arr[i], arr[idx]] = [arr[idx], arr[i]];
      }
      
    • 分组

      arr.reduce((map, item) => (map[item] = (map[item] + 1 || 1), map), {})
      
    • 去重

      Array.from(new Set(arr))
      
      arr.reduce((map, item) => (map.set(item, ""), map), new Map()).keys()
      
      arr.reduce((arr, item) => arr.includes(item)?arr:arr.concat(item), []);
      
    • reduce

      有初始值则从0开始,无则从1角标开始,并把0角标作为初始值

      Array.prototype.cusReduce = function (fn, initialVal) {
          let start = 0, arr = this;
          if (!initialVal) {
              start++;
              initialVal = arr[0]
          }
          while (start < arr.length) {
              initialVal = fn.call(this, initialVal, arr[start++], arr);
          }
          return initialVal;
      };
      
  4. 节流/防抖
    • 节流

      function throttle(fn, timeout) {
          let timer = null;
          return function () {
              if (timer) return;
              fn.apply(this, arguments);
              timer = setTimeout(() => timer = null, timeout)
          }
      }
      
    • 防抖

      function debounce(fn, timeout) {
          let timer = null;
          return function () {
              clearTimeout(timer);
              timer = setTimeout(() => fn.apply(this, arguments), timeout)
          }
      }
      
  5. call/apply/bind实现
    • call

      Function.prototype.cusCall = function () {
          const fn = this;
          const [ctx, ...args] = [].slice.call(arguments);
          Object.defineProperty(ctx, "call", {enumerable: false, value: fn});
          let res = null;
          eval(`res = ctx["call"](` + args.map((arg) => JSON.stringify(arg)) + `)`);
          ctx["call"] = null;
          return res;
      };
      
    • apply

      Function.prototype.cusApply = function () {
          const [ctx, args] = [].slice.call(arguments);
          let res = null;
          eval(`res = this.cusCall(ctx,` + args.map((arg) => JSON.stringify(arg)) + `)`);
          return res;
      }
      
    • bind

      Function.prototype.cusBind = function () {
          const [ctx, ...args] = [].slice.call(arguments);
          const fn = this;
          return function () {
              const args2 = [].slice.call(arguments);
              return fn.cusCall(ctx, [].concat(args, args2));
          }
      };
      
  6. 数据类型/原型
    • 数据类型 Number/Boolean/String/null/undefined/Symbol/Object

    • 原型

      每个对象都有原型(__proto__)指向构造器的prototype,每个对象都可以调用原型链上的所有属性方法

应用:

  1. 继承,给构造器的原型指向其他构造器生成的对象 从而继承该函数上的属性方法
  1. 事件循环event loop

    浏览器的事件循环:宏任务->微任务->宏任务....

    宏任务:一个script标签内的任务属于一个宏任务,settimeout

    微任务:promise

  2. 模块化的几种方式
    1. commonjs同步 用于nodejs

      1. 定义模块 根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性

      2. 模块输出: 模块只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入该对象

      3. 加载模块: 加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象

      4. exports是module.exports的引用 所以当exports = xxx时将丢失引用,正常用法应该为exports.xxx = xxx

    2. AMD异步 用于浏览器 主要有require.js库支持

      1. 定义模块 define(id?, dependencies?, factory);
        1. id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
        2. dependencies:是一个当前模块依赖的模块名称数组
        3. factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值
      2. 引用模块 require([dependencies], function(){});
        1. 第一个参数是一个数组,表示所依赖的模块
        2. 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
    3. CMD 异步 用于浏览器 主要有SeaJs库支持

         // 定义模块  myModule.js
         define(function(require, exports, module) {
           var $ = require('jquery.js')
          $('div').addClass('active');
          });
      // 加载模块
      seajs.use(['myModule.js'], function(my){});
      
    4. es6模块化 统一 由webpack支持

      1. 是将所有文件打包到同一文件中,可使用代码分割插件进行分割。互相依赖
      2. 是用webpack内建函数进行require和define 其原理与cmd amd差不多
  3. async/await实现
    1. 生成器维护了一个promise,专门用于返回函数最终值的
    2. 生成器另外维护了一串promise,专门用于执行每个yield返回的promise或者普通值,将其强行转换成promise并.then串连继续执行,直到gen最终返回done=true
    3. 整个过程就是为了让generatorpromise.then的进行下自动执行next函数
    function asyncGeneratorStep(gen, resolve, reject, _next, _throw, value) {
        let info;
        try {
            info = gen.next(value);
        } catch (e) {
            return reject(e);
        }
        if (info.done) return resolve(info.value);
        Promise.resolve(info.value).then(_next, _throw);
    
    }
    
    function asyncToGen(fn) {
        return function (...args) {
            return new Promise((resolve, reject) => {
                const gen = fn.apply(this, args);
    
                function _next(value) {
                    asyncGeneratorStep(gen, resolve, reject, _next, _throw, value)
                }
    
                function _throw(value) {
                    asyncGeneratorStep(gen, resolve, reject, _next, _throw, value)
                }
    
                _next();
            })
        }
    }
    
  4. instanceOf
    function cusInstanceOf(left, right) {
        while (left = Object.getPrototypeOf(left)) {
            if (left === right.prototype) return true;
        }
        return false;
    }
    
  5. curry
    function curry(fn) {
        return function closure(...args) {
            if (args.length === fn.length) return fn.apply(this, args);
            return (...others) => closure.apply(this,args.concat(others));
        }
    }
    
  6. 单例化
    function Singleton(Constructor) {
        let instance = null;
        return new Proxy(Constructor, {
            construct(target, argArray, newTarget) {
                instance || (instance = Reflect.construct(target, argArray, newTarget));
                return instance;
            }
        })
    }
    
  7. 深入剖析:Vue核心之虚拟DOM

    点击前往

css

  1. 瀑布流
    1. flex

      .c-waterfall {
        //横放
          display: flex;
          &__item {
            //竖放
              flex-direction: column;
              display: flex;
              li {
                  width: 50px;
                  margin: 3px;
                  background: antiquewhite;
              }
              @for $j from 1 to 4 {
                  &:nth-child(#{$j}) {
                      @for $i from 1 to 11 {
                          li:nth-child(#{$i}) {
                              height: get-random();
                          }
                      }
                  }
              }
          }
      }
      
  2. 1px
    1. 媒体查询

      .border { border: 1px solid #999 }
      @media screen and (-webkit-min-device-pixel-ratio: 2) {
          .border { border: 0.5px solid #999 }
      }
      @media screen and (-webkit-min-device-pixel-ratio: 3) {
          .border { border: 0.333333px solid #999 }
      }
      
    2. transofrm:scale

       @mixin one-border($ratio) {
              $size: 100% * $ratio;
              width: $size;
              height: $size;
              position: absolute;
              left: 50%;
              top: 50%;
              border: 1px solid #306eff;
              transform: translate(-50%, -50%) scale(1 / $ratio);
      }
      .c-box2 {
        position: relative;
        @include rect(100px);
        &:after {
          content: "";
          @include one-border(3);
        }
      }
      
    3. box-shadow

      box-shadow: 0 0 0 .333px #306eff;
      
    4. border-image 让UI切一个一半透明 一半实线

    5. background 背景图片

  3. 水平垂直居中

    flex+margin:auto

     #app {
        height: 100px;
        background: #306eff;
        display: flex;
        .box{
          background: black;
          width: 30px;
          height: 30px;
          margin: auto;
        }
      }
    

    flex

     #app {
        height: 100px;
        line-height: 100px;
        background: #306eff;
        display: flex;
        justify-content: center;
        align-items: center;
        .box {
          background: black;
          width: 30px;
          height: 30px;
        }
      }
    

    absolute + transform

     #app {
        height: 100px;
        background: #306eff;
        position: relative;
        .box{
          background: black;
          width: 30px;
          height: 30px;
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translate(-50%,-50%);
        }
      }
    

    absolute + margin:auto

     #app {
        height: 100px;
        background: #306eff;
        position: relative;
        .box{
          background: black;
          width: 30px;
          height: 30px;
          position: absolute;
          left: 0;
          top: 0;
          bottom: 0;
          right: 0;
          margin: auto;
        }
      }
    

    table-cell

      #app {
        height: 100px;
        background: #306eff;
        display: table;
        width: 100%;
        .container {
          display: table-cell;
          vertical-align: middle;
          text-align: center;
        }
        .box {
          background: black;
          margin: 0 auto;
          width: 30px;
          height: 30px;
        }
      }
    

    line-height+inline-block+vertical-align

     #app {
        height: 100px;
        line-height: 100px;
        background: #306eff;
        text-align: center;
        .box {
          display: inline-block;
          background: black;
          width: 30px;
          height: 30px;
          vertical-align: middle;
        }
      }
    
  4. 优化动画
    1. 使用translate代替left top
    2. 使用scale+transform-origin代替width height动画
    3. 使用translate3d或will-change开启移动端设备动画加速
    4. 防止布局回流 将动画层绝对定位提出来
    5. 使用webgl或canvas
  5. 选择器有哪些 优先级排行

    **选择器:**兄弟 相邻 伪类/伪元素 后代 子元素 属性 通配符 id class 标签

    优先级:!important > 内联 > id > class/属性/伪类 > 标签/伪元素 > 通配符

    权重相同 按顺序覆盖

  6. rem自适应方案/响应式方案

    首先移动端需要设置元属性,禁用双击放大,将宽度设置为设备宽度(如果不设置的话当宽度发生变化 内容会相对应的比例进行缩放)

    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    
    • rem自适应 flexible

      根据设计稿和当前页面的宽度计算出比例,设置为根元素的字体,当页面的尺寸使用rem时,就是根据根元素的字体进行计算的。只需要计算出根元素的字体 就可以在不同宽度设备上进行等比例缩放了

    • 响应式方案 bootstrap 各类UI框架的栅格布局

      使用媒体查询 在不同宽度下给指定类名设置宽度比例 使得元素可以在不同设备上显示不同布局和尺寸

  7. BFC

    布局规则:内部的Box会在垂直方向,一个接一个地放置,Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠,每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。

    • 创建
      • html根元素
      • float不为none
      • dispaly不为block
      • overflow:不为visible
      • position不为relative和static
    • 应用
      • 防止margin重叠
      • 防止float父元素高度塌陷
      • 防止被float元素重叠