最新面试题汇总

297 阅读11分钟

css部分

  • 居中布局:居中布局就是既要水平方向居中,又要垂直方向居中
    • table + margin实现水平方向居中,table-cell + vertical-align实现垂直方向居中
    <div class = 'parent'>
       <div class = 'child' >z先森</div>
    </div>
    
    .parent{
       display:table-cell;
       vertical-align:middle;
    }
    .child{
       display:table;
       margin: 0 auto;
    }
    
    • 优点:浏览器的兼容性比较好;
    • 缺点:vertical-align属性具有继承性,导致父元素的文本也是居中显示的;
    • absolute + transform/margin 实现水平方向和垂直方向居中
    <div class = 'parent'>
       <div class = 'child' >z先森</div>
    </div>
    
    .parent{
       position:relative;
    }
    .child{
       position:absolute;
       top:50%;
       left:50%;
       transform:translate(-50%,-50%)
    }
    
    .parent{
      position:relative;
    }
    .child{
      position:absolute;
      top:50%;
      left:50%;
      margin-top:-50%盒子高度;
      margin-left:-50%盒子宽度;
    }
    
    • 优点:父级元素是否脱离文档流,不影响子级元素的垂直居中效果。
    • 缺点:transform属性是css3中新增的属性,浏览器支持不太好
    • flex + justify-content + align-items实现水平和垂直方向居中
    <div class = 'parent'>
       <div class = 'child' >z先森</div>
    </div>
    
    .parent{
      display:flex;
      justify-content:center;
      align-items:center;
    }
    
    • 优点:简单灵活,功能强大
    • 缺点:PC兼容不太好,移动端的话需要Android4.0+
  • BFC:Block Formatting Content块格式化上下文,形成一个独立的容器,容器里面的盒子和外面的盒子互不干扰,
    • 触发BFC的条件:
      • ①浮动元素:float除去none以外的值
      • ②定位:position:absolute|fixed
      • ③display:inline|table-cell|flex|inline-flex(决定了盒子的属性)
      • ④overflow除visible以外的值
    • BFC块格式上下文的特性
      • 内部的盒子在垂直方向上会一个接一个的排列
      • 处于同一个BFC的元素会发生margin collapse
      • 从左向右格式化的第一个元素的margin-box的左边与容器块的border-box的左边接触,浮动元素也是一样。
      • 形成的BFC盒子里面的元素和外面的元素互不干扰,没有任何关系。
      • 计算BFC高度的时候,要加上浮动元素的高度。
      • 浮动盒区域不叠加到BFC上
    • 解决问题
      • 父元素触发BFC,浮动的元素撑开父元素的高度
      • 形成独立的容器,解决margin-collapse问题
      • 解决margin-top的传递问题:给子容器加margin-top,不但没有跟父容器产生间距,反而让父容器整体下移。
  • 四个定位
    • **static: **静态定位,盒子的位置是常规流布局的位置
    • **relative: **相对定位,生成相对定位的元素,相对于其正常位置进行定位。通过top、bottom、left、right来控制盒子的位置,即使有偏移也会占有原来的位置,其他常规流不可以占用这个位置。
    • **absolute:**绝对定位,盒子从常规流移除,盒子的定位相对于包含他的最近的一个有position属性的元素,利用top、left即可。
    • **fixed :**固定定位,该元素相对于浏览器窗口进行定位。固定定位的元素不会随浏览器窗口的滚动条滚动而变化,也不会受文档流动影响,而是始终位于浏览器窗口内视图的某个位置。
  • **如何清除浮动 :**浮动元素所导致的问题:会导致父元素的高度塌陷(height collapse),和后面的元素发生重叠
    • 给父元素加适当的高度;比较死板;
    • 给父元素触发BFC,这样会把浮动元素的高度计算进去,如果用了overflow:hidden,这个属性是溢出隐藏,那么在设置阴影的时候不起作用。
    • 伪元素
    <style>
    .clearfix::after{
         content:" ";
         display:block;
         clear:both;
         width:0;
         height:0;
         font-size:0;
         overflow:hidden;
    }
    </style>
    
    • 在浮动元素之后,父容器结束标签之前,加一个任意的块级标签,并且加style='clear:both;'
  • 盒子模型
    • 正常盒子模型:box-sizing:content-box
      • 在浏览器显示的实际宽度:width + padding-left/right + border-left/right 
      • 在浏览器显示的实际高度:height + padding-top/bottom + border-top/bottom 
    • 怪异盒子模型:box-sizing:border-box
      • 在浏览器显示的实际宽度:width(已经包含了左右内填充和左右边框)
      • 在浏览器显示的实际高度:height(已经包含了上下内填充和上下边框)
  • 文字超出以省略号显示

{ width:100px; text-overflow:ellipsis; white-space:nowrap; overflow:hidden; }

- **css3新增属性以及常规用法**
- **border-radius:左上角 右上角 右下角 左下角**
- **box-shadow:①x轴的偏移量②y轴的偏移量③阴影的模糊半径④阴影的扩展半径⑤阴影的颜色⑥内阴影**
- **text-shadow:X轴 Y轴 模糊半径 颜色**
- **background-size:x y (背景尺寸)|cover(裁剪)|contain(留白)**
- **background-clip:text 以文字向外进行裁剪保留文字的轮廓背景|border-box|padding-box|content-box**
- **linear-gradient(方位角度|颜色 颜色):线性渐变**
- **radial-gradient(直接放颜色):径向渐变**
- **css选择器的优先级**
 - !important(尽量不要使用)
 - 内联样式
 - ID选择器
 - 类选择器|属性选择器|伪类选择器
 - 元素选择器
 - 通配符选择器
 - 继承
 - 浏览器默认属性
## JS部分
- 实现数组去重
- **双for循环(冒泡)**  时间复杂度是O(n²)

for(let i = 0;i<arr.length;i++){ let item = arr[i]; for(let j = i + 1;j < arr.length;j++){ if(item === arr[j]){ arr[j] = arr[arr.length-1]; arr.length--; j--; }
} }

- **单for循环** 时间复杂度为O(n) 空间换时间

let obj = {}; for(let i=0;i<arr.length;i++){ let item = arr[i]; if(obj[item] !== undefined){ arr[i] = arr[arr.length - 1]; arr.length--; i--; } obj[item] = item }

- **set方法**

let ary = [1, 2, 2, 3, 4, 2, 1, 3, 1, 3]; ary = Array.from(new Set(ary)); console.log(ary);

- **indexOf**

let ary = []; for(var i = 0;i<arr.length;i++){ if(ary.indexOf(arr[i]) === -1){ ary.push(arr[i]) } } return ary;

- **sort排序方法**

arr = arr.sort(); let ary = [arr[0]]; for(var i = 1;i< arr.length;i++){ if(arr[i]!==arr[i-1]){ ary.push(arr[i]) } } return ary;

- **includes**

let ary = []; for(var i = 0;i<arr.length;i++){ if(!ary.includes(arr[i])){ ary.push(arr[i]) } }

- **reduce + includes**

arr.reduce((prev , cur )=>{ return prev.includes(cur)?prev:[...prev,cur] },[])

- **深克隆**

function cloneDeep(obj){ //基本数据类型 + symbol + function也不用处理 if(typeof obj === null ) return null; if(typeof obj !== 'object' ) return obj;

//对特殊的对象进行深克隆
if(obj instanceof Date) return new Date(obj);
if(obj instanceof RegExp) return new RegExp(obj);

//新开辟一个堆内存 
let cloneObj = new obj.constructor;
for(let key in obj){
   if(!obj.hasOwnProperty(key)) break;
   cloneObj[key] = cloneDeep(obj[key])
}
return  cloneObj

}

- **for in|for of|for循环**
- **for循环**
  - ```
    for(let i = 0,len = arr.length; i < len ;i++){...}
    ```
  - 取值和判断合并,并通过不断的枚举每一项来循环,直到枚举到空值。
    **存在问题:**如果当前数组里面存在非Truthy值时,会导致直接结束
     ```
    for(let i = 0,item ; item = arr[i] ;i++){...}
    ```
   - **倒序**
   ```
   for(let i = arr.length;i--){...}
   ```
- **for in:**专门为循环可枚举对象而设计的。
   - 在使用for in的时候,先检测确认该对象的值不是nullundefined。
   - for in 会遍历出在原型上新增的属性和方法。
   - **Object.hasOwnProperty(prop)** 用来判断某个对象是否含有指定的属性的,返回值为Boolean ,该方法会忽略掉那些从原型链上继承到的属性。
 - **for of:**ES6新增的一个方法,①用于解决**for-in用来遍历数组出现的顺序可能随机的问题**。②**forEach不能breakreturn**③**还支持类数组**④**也支持MapSet**⑤**for-of不遍历普通对象**
 - **for循环和while循环和for of循环 可以通过break等关键字控制循环**
- **实现一个正六边形**
六边形 body { height: 100%; width: 100%; } .w-comb { width: 174px; height: 100px; background-color: #e4e4e4; display: inline-block; position: absolute; top: 50%; left: 50%; /* transform: translate(-50%, -50%); */ margin-top: -50px; margin-left: -87px; } .w-comb1, .w-comb2 { background-color: #e4e4e4; position: absolute; width: inherit; height: inherit; } .w-comb1 { transform: rotate(-60deg); } .w-comb2 { transform: rotate(60deg); } </style>
``` - **解析URL** ``` const url = 'http://zsg.com/index.html?a=1&b=2&c=3&d'; ( proto => { function queryURLParams() { let link = document.createElement('a'); link.href = this; let askText = link.search.substr(1); if (askText) { var arr = askText.split(/(?:&|=)/g); } let obj = {}; this.replace(/([^#&?=])=([^#&?=])/g, (_, name, value) => { obj[name] = value }); arr.length % 2 !== 0 ? obj[arr[arr.length - 1]] = true : null; return obj; } return proto.queryURLParams = queryURLParams; } )(String.prototype); console.log(url.queryURLParams()); ``` - **防抖:**在触发事件后,规定一定的时间只执行一次,在这个范围内再次重新触发同一个事件再次重新计算。依次类推。 - @params - func:触发的函数体 - wait:在最后一次触发设置的等待时间 - immediate:是否在最开始执行一次,传值则为true - @return - 利用闭包返回可调用函数
  let debounce = function (func,wait,immediate){
      //result 用来存储函数体执行的返回结果
      //timeout 记录定时器
      let result = null, 
          timeout = null;
      return function (...args){
             //now 控制函数体是否在触发事件首次执行还是等间隔一定的时间
             let content = this,
                 now = immediate&&!timeout;
             //清除上一次定时器
             clearTimeout(timeout);
             timeout = setTimeout(()=>{
                       timeout = null;
                       if(!immediate) result = func.apply(content,args)
             }.wait);
             if(now) result = func.apply(content,args);
             return result;
      }
  }
  • **节流:**设定一个频率,在这个频率内只执行一次。不管你触发了多少次,在这个时间频率内只执行一次。
    • @params
      • func:需要执行的函数体
      • wait:设定的频率
    • @return
      • 返回可被调用的函数体
    let throttle = function (func , wait){
        //previous记录上一次执行的时间
        let timeout = null,
            result = null,
            previous = 0;
        return function (...args){
               let now = new Date,
                   content = this;
               //remainting记录时间的间隔
               let remaining = wait - (now - previous);
               if(remaining <=0){
                 cleraTimeout(timeout);
                 previous = now;
                 timeout = null;
                 result = func.apply(content , args);
               }else if(!timeout){
                       timeout = setTimeout(()=>{
                                 previous = new Date;
                                 timeout = null;
                                 result = func.apply(content, args)
                       },remainting)
               }
               return result;
        } 
    }
    
  • JQ中数据类型检测
     function toType(obj){
       let typeObj = {};
       ["Boolean","String","Number","Function","Array","Date","Error","Symbol","Object"].forEach(item=>{
           typeObj["[object" + item + "]"] = item.toLowerCase();    
       });
       if(obj == null) return obj + "";
       return typeof obj === "object" || typeof obj === "function" ?
       typeObj[typeObj.toString.call(obj)] || "object" : typeof obj
     }
    
  • 继承
    • **原型继承:**让子类的原型等于父类的实例
    function Parent(){
      this.x = 100;
    }
    function Child(){
      this.y = 200;
    }
    Child.prototype = new Parent;
    Child.prototype.getY = function getY(){
        return this.y
    }
    
    • **call继承:**在子类的构造函数中让父类的方法执行,只把父类本身的属性和方法给到子类实例。
    function Parent(){
      this.x = 100;
    }
    Parent.prototype.getX = function getX() {
      return this.x;
    };
    function Child(){
      Parent.call(this);
      this.y = 300;
    }
    Child.prototype.getY = function getY() {
      return this.y;
    };
    let c1 = new Child;
    console.log(c1);
    
    • 寄生组合式继承
    function Parent(){
      this.x = 100;
    }
    function Child() {
      Parent.call(this);
      this.y = 200;
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    
  • 数组排序
    • 冒泡排序
    // 交换位置函数
    

function swap(arr, i, j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; return arr; } Array.prototype.bubble = function bubble() { // 外层循环I控制比较的轮数 for (let i = 0; i < this.length - 1; i++) { // 里层循环控制每一轮比较的次数J for (let j = 0; j < this.length - 1 - i; j++) { if (this[j] > this[j + 1]) { // 当前项大于后一项,交换位置 swap(this,j,j+1); } } } return this; } let ary = [12, 8, 24, 16, 1]; ary.bubble(); console.log(ary);

- **选择排序**

Array.prototype.select = function select() { for (let j = 0; j < this.length - 1; j++) { let min = j, temp = null; // 找到比当前项还小的这一项索引 for (let i = min + 1; i < this.length; i++) { if (this[i] < this[min]) { min = i; } } // 让最小的项和当前首位交换位置 swap(this,min,j); } return this; }; let ary = [12, 8, 24, 16, 1]; ary.select(); console.log(ary);

- **插入排序**

Array.prototype.insert = function insert() { // 1.准备一个新数组,用来存储抓到手里的牌,开始先抓一张牌进来 let handle = []; handle.push(this[0]);

// 2.从第二项开始依次抓牌,一直到把台面上的牌抓光
for (let i = 1; i < this.length; i++) {
    // A是新抓的牌
    let A = this[i];
    // 和HANDDLE手里的牌依次比较(从后向前比)
    for (let j = handle.length - 1; j >= 0; j--) {
        // 每一次要比较的手里的牌
        let B = handle[j];
        // 如果当前新牌A比要比较的牌B大了,把A放到B的后面
        if (A > B) {
            handle.splice(j + 1, 0, A);
            break;
        }
        // 已经比到第一项,我们把新牌放到手中最前面即可
        if (j === 0) {
            handle.unshift(A);
        }
    }
}
return handle;

} let ary = [12, 8, 24, 16, 1]; ary.insert(); console.log(ary);

- **快速排序**

Array.prototype.quick = function quick() { // 4.结束递归(当数组中小于等于一项,则不用处理) if (this.length <= 1) { return this; } // 1.找到数组的中间项,在原有的数组中把它移除 let middleIndex = Math.floor(this.length / 2); let middleValue = this.splice(middleIndex, 1)[0]; // 2.准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,反之放到右边数组中 let aryLeft = [], aryRight = []; for (let i = 0; i < this.length; i++) { let item = this[i]; item < middleValue ? aryLeft.push(item) : aryRight.push(item); } // 3.递归方式让左右两边的数组持续这样处理,一直到左右两边都排好序为止(最后让左边+中间+右边拼接成为最后的结果) return quick(aryLeft).concat(middleValue, quick(aryRight)); } let ary = [12, 8, 15, 16, 1, 24]; ary.quick(); console.log(ary);

- **希尔排序**

Array.prototype.shell = function shell() { let gap = Math.floor(this.length / 2); while (gap >= 1) { for (let i = gap; i < this.length; i++) { while (i - gap >= 0 && this[i] < this[i - gap]) { swap(this, i, i - gap); i = i - gap; } } gap = Math.floor(gap / 2); } }; let arr = [58, 23, 67, 36, 40, 46, 35, 28, 20, 10]; arr.shell(); console.log(arr);

## vue
- **生命周期**
- **beforecreate:**在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用
- **created:** 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:**数据观测 (data observer),property 和方法的运算,watch/event 事件回调。**然而,挂载阶段还没开始,`$el` `property` 目前尚不可用。
- **beforeMount:**在挂载开始之前被调用:相关的 render 函数首次被调用。
- **mounted:**实例被挂载后调用,这时** el 被新创建的`vm.$el`替换了**。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 `vm.$el`也在文档内。
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/edee85f1c12741cea14721a4c62fd53b~tplv-k3u1fbpfcp-zoom-1.image)
- **beforeUpdate:**数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前**访问现有的 DOM**,比如手动移除已添加的事件监听器。
- **updated:**由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e06e8a50b18b47eeb7cf60b3282e4179~tplv-k3u1fbpfcp-zoom-1.image)
- **activated:**被 keep-alive 缓存的组件激活时调用。
- **deactivated:**被 keep-alive 缓存的组件停用时调用。
- **beforeDestroy:**实例销毁之前调用。在这一步,实例仍然完全可用。
- **destroyed:**实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
- **errorCaptured:**当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:**错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。**此钩子可以返回 false 以阻止该错误继续向上传播。
- **vuex: **数据管控仓库;适用于中大型单页项目
- **State:**唯一数据源,并且状态存储是响应式的。
   - 通过store.state.xxx来获取相应的状态,
   - 如果模块比较多的话,通过在根组件注入到每一个子组件中`Vue.use(Vuex)`,子组件通过`this.$store`来访问状态。
   - mapState辅助函数
- **Getter:** 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
- **Mutation:**更改状态的唯一方法,且必须是同步函数。mutation 都是同步事务
- **Action:**
  - Action 提交的是 mutation,而不是直接变更状态。
  - Action 可以包含任意异步操作。
- **Module:**将store分割成多个模块,便于管理和操作
- **computed 和 watch 的区别和运用的场景**
- **computed: **是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
- **watch: **更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
- ①当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;②当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
- **vue如何实现数据的双向绑定**
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f07ba5694a95491aa07f260479d5e765~tplv-k3u1fbpfcp-zoom-1.image)
- **实现一个监听器 Observer:** 对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
- **实现一个解析器 Compile: **解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
- **实现一个订阅者 Watcher:** Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
- **实现一个订阅器 Dep: **订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
- **v-show与v-if区别**
 - v-show是css的切换,适用于频繁的切换
 - v-if是完整的销毁和渲染,则适用于较少的改变。
- **导航守卫** 
- **全局守卫**

 1.**router.beforeEach:**全局前置守卫 进入路由之前
 
 2.**router.beforeResolve:**全局解析守卫 在调用beforeRouteEenter调用之后调用
 
 3.**router.afterEach:**全局后置钩子 进入路由之后调用
 
- **路由独享的守卫:** 在路由配置表中直接配置beforeEnter
- **组件内的守卫 可以在路由组件内直接定义以下路由导航守卫**

const Foo = { template: ..., beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 this // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 this }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用,我们用它来禁止用户离开 // 可以访问组件实例 this // 比如还未保存草稿,或者在用户离开前, 将setInterval销毁,防止离开之后,定时器还在调用。 } }

- **在实际项目开发过程中遇到过哪些问题,是怎么解决的?**