(笔记)面试题汇总附加答案(持续更新)

858 阅读14分钟

JS部分

1. 编写一个方法,该方法接收两个参数,分别为 k 和 一个无序的纯数字数组。该方法在执行后,会返回数组中第 k 大的数字。特别注意,如果数组中,有两位数值一样的数字,同数值数字排名并列。如 [3,1,3,2,5,4,5] 中,第 1 大的数字为 5,第2大的数字为 4,第5大的数字为 1?

  • 这道题目考察数组的常用基本操作方法。思路是先做数组内部值的排序,排序完成之后因为需要由大到小所以在反转下数组 。最后在做数组的去重处理,最终返还答案。实现代码如下:
function getNum(k, arr) {            
    let res = arr.sort((a,b)=>b-a);   // 将数组转化成set去重     
    let set = new Set(res);  // 将类数组转回数组        
    let newArr = Array.from(set);        
    if (typeof newArr[k-1] !== "undefined") { // 返回查找到的数据    
        return newArr[k-1];  
    } else {            // 未找到数据排除错误          
        throw Error("未找到对应数据");        
    }   
 }    
let arr = [3132545];    
let res = getNum(1, arr);    
console.log(res);

2.proto 和 prototype 之前有什么关系?

  • 所有对象都有 proto 属性,函数这个特殊对象除了具有 proto 属性,还有特有的原型属性prototype。prototype对象默认有两个属性,constructor属性和__proto__ 属性。prototype属性可以给函数和对象添加可共享(继承)的方法、属性,而__proto__ 是查找某函数或对象的原型链方式。constructor,这个属性包含了一个指针,指回原构造函数。

3.call(), .apply() .bind() 的区别和作用?bind 方法如何来实现?

  • call 、apply 、bind 作用是 改变函数执行时的上下文,简而言之就是改变函数运行时的this指向。区别在于调用方式及参数传递上。具体如下:
function fn(...args) {        
console.log(this,args); 
}    
fn(1,2);  // fn函数默认this指向是 window对象    
let obj = {        
    myname:"张三"  
}    
fn.call(obj,1,2);  // this 会变成 传入的obj ,args 会变成[1,2];    
fn.apply(obj,[1,2]); // this会变成传入的obj ,传入的参数必须是一个数组;    
fn.bind(obj)(1,2); // this 也会变成传入的obj ,bind不是立即执行需要执行一次

上所述call、apply、bind都可以改变this指向,区别在于 参数传递方式不同,call、apply是立即执行bind不是立即执行。

  • bind实现如下
Function.prototype.myBind = function (context) {   
    // 判断调用对象是否为函数   
    if (typeof this !== "function") {     
        throw new TypeError("Error");   
     }   // 获取参数   
    var args = [...arguments].slice(1);    
    fn = this;   
    return function Fn() {     
    // 根据调用方式,传入不同绑定值     
        return fn.apply(this instanceof Fnnew fn(...arguments) : context, args.concat(...arguments));   
    } 
}

4. js中基础数据类型有哪几种?了解包装对象么?

  • 基础数据类型有6种:  boolean null undefined number string symbol  基础数据类型都是值 ,所以没有方法提供调用的 例如:undefined.split("");那为什么比如 ”abc“.split("")类似这种调用可以被允许?原因是js中会存在包装对象,会把字符串先包装成对象然后在调用对象下的一些方法,方法调用完成之后在销毁对象,这样就完成了基础数据类型的函数调用功能。

5.什么是宏任务?什么是微任务?

  • 微任务:一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。  
  • 宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合。   常见微任务:
  1. Promise.then
  2. MutaionObserver
  3. Object.observe(已废弃;Proxy 对象替代)
  4. rocess.nextTick(Node.js) 常见宏任务 :   
  5. script (可以理解为外层同步代码)  
  6. setTimeout/setInterval  
  7. UI rendering/UI事件  
  8. postMessage,MessageChannel  
  9. setImmediate,I/O(Node.js)

image.png

6.Promise.allSettled 了解吗?动手实现一下 Promise.allSettled?

function MyallSettled(list){
    let resArr = new Array(list.length);
    let num = 0;
    return new Promise(resolve => {            
        list.forEach((item,key) => {               
            let obj = {};                
            item.then(res =>{                    
                obj['status'] = "fulfilled";                    
                obj.value = res;                    
                resArr[key]= obj;         
                num++                   
                if(num===list.length){                        
                resolve(resArr);              
            }},err=>{                  
                obj['status'] = "rejected";                    
                obj.reson = err;                    
                resArr[key] = obj;                    
                num++                    
                if(num===list.length){                        
                    resolve(resArr);                  
                }                
            })         
         })       
      });    
}

6.了解js中设计模式吗?动手实现一下单例模式?

let CreateSinge = (function() {
    let instance;
    return function(name) {
        if(instance) {
            return instance;
        }
        return instance = new Single();
    }
})();

let Single = function(name) {
    this.name = name;
}

Single.prototype.getName = function(){
    conslole.log(this.name);
}
let getName  = new CreateSinge('getName');
let getDom = new CreateSinge('getDom');
getDom.getName();

7.虚拟 DOM 有什么作用,如何构建虚拟DOM?

 class Vdom{
     constructor(option) {
         this.tagName = option.tagName;
         this.props = option.props || {};
         this.children = opton.children || {};
     }
     render() {
         let el = document.createElement(this.tagName);
         for(let propsKay in this.props) {
             el.setAttribure(propsKay,this.props[propsKay]);
         }
         if (Array.isArray(this.children)) {
             this.chidren.forEach((item)=> {
                 el.appendChild(item.render());
             });
         } else {
                 el.innerText = this.children;
         }
         return el;
     }
 }

8.meta viewport相关

<!DOCTYPE html>  <!--H5标准声明,使用 HTML5 doctype,不区分大小写-->
<head lang=”en”> <!--标准的 lang 属性写法-->
<meta charset=’utf-8′>    <!--声明文档使用的字符编码-->
<meta http-equiv=”X-UA-Compatible” content=”IE=edge,chrome=1″/>   <!--优先使用 IE 最新版本和 Chrome-->
<meta name=”description” content=”不超过150个字符”/>       <!--页面描述-->
<meta name=”keywords” content=””/>     <!-- 页面关键词-->
<meta name=”author” content=”name, email@gmail.com”/>    <!--网页作者-->
<meta name=”robots” content=”index,follow”/>      <!--搜索引擎抓取-->
<meta name=”viewport” content=”initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no”> <!--为移动设备添加 viewport-->
<meta name=”apple-mobile-web-app-title” content=”标题”> <!--iOS 设备 begin-->
<meta name=”apple-mobile-web-app-capable” content=”yes”/>  <!--添加到主屏后的标题(iOS 6 新增)
是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏-->
<meta name=”apple-itunes-app” content=”app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL”>
<!--添加智能 App 广告条 Smart App Banner(iOS 6+ Safari)-->
<meta name=”apple-mobile-web-app-status-bar-style” content=”black”/>
<meta name=”format-detection” content=”telphone=no, email=no”/>  <!--设置苹果工具栏颜色-->
<meta name=”renderer” content=”webkit”> <!-- 启用360浏览器的极速模式(webkit)-->
<meta http-equiv=”X-UA-Compatible” content=”IE=edge”>     <!--避免IE使用兼容模式-->
<meta http-equiv=”Cache-Control” content=”no-siteapp” />    <!--不让百度转码-->
<meta name=”HandheldFriendly” content=”true”>     <!--针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓-->
<meta name=”MobileOptimized” content=”320″>   <!--微软的老式浏览器-->
<meta name=”screen-orientation” content=”portrait”>   <!--uc强制竖屏-->
<meta name=”x5-orientation” content=”portrait”>    <!--QQ强制竖屏-->
<meta name=”full-screen” content=”yes”>              <!--UC强制全屏-->
<meta name=”x5-fullscreen” content=”true”>       <!--QQ强制全屏-->
<meta name=”browsermode” content=”application”>   <!--UC应用模式-->
<meta name=”x5-page-mode” content=”app”>   <!-- QQ应用模式-->
<meta name=”msapplication-tap-highlight” content=”no”>    <!--windows phone 点击无高亮
设置页面不缓存-->
<meta http-equiv=”pragma” content=”no-cache”>
<meta http-equiv=”cache-control” content=”no-cache”>
<meta http-equiv=”expires” content=”0″>

9.一个页面上有大量的图片(大型电商网站),加载很慢,你有哪些方法优化这些图片的加载,给用户更好的体验。

  • 图片懒加载,在页面上的未可视区域可以添加一个滚动事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。
  • 如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。
  • 如果图片为css图片,可以使用CSSsprite,SVGsprite,Iconfont、Base64等技术。
  • 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。
  • 如果图片展示区域小于图片的真实大小,则因在服务器端根据业务需要先行进行图片压缩,图片压缩后大小与展示一致。

vue部分

1、vue 中组件间有哪些通信方式?

  • 1.prop与 $emit适用于父子组件通信这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
  • 2.ref 与 parent 与parent 与 children适用于父子组件通信ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例parent/parent / children:访问父 / 子实例–
  • 3.EventBus (emit/emit / on)适用于父子、隔代、兄弟组件通信这种方法通过一个空的 Vue 实例/作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
  • 4.attrs/attrs/listeners适用于隔代组件通信attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind=attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind=“attrs” 传入内部组件。通常配合 inheritAttrs 选项一起使用。$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=“$listeners” 传入内部组件
  • 5.provide / inject适用于隔代组件通信祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
  • 6.Vuex适用于父子、隔代、兄弟组件通信Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

2.vue 中 v-show 和 v-if 的区别是什么?

  • v-show 只是在 display: none 和 display: block 之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。  
  • v-if 的话就得说到 Vue 底层的编译了。当属性初始为 false 时,组件就不会被渲染,直到条件为 true,并且切换条件时会触发销毁/挂载组件,  并且基于 v-if 的这种惰性渲染机制,可以在必要的时候才去渲染组件,减少整个页面的初始渲染开销。

3.keep-alive有什么作用

  •  如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
  •  对于 keep-alive 组件来说,它拥有两个独有的生命周期钩子函数,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

4.说下vue生命周期钩子函数?

  • beforeCreate :这个时期,this变量还不能使用,在data下的数据,和methods下的方法,watcher中的事件都不能获得到。
  • created这个时候可以操作vue实例中的数据和各种方法,但是还不能对"dom"节点进行操作。
  • beforeMounte:在挂载开始之前被调用:相关的 render 函数首次被调用
  • mounted:挂载完毕,这时dom节点被渲染到文档内,一些需要dom的操作在此时才能正常进行
  • beforeUpdate:data中数据已经更新完毕,页面视图还未响应更改– updated:数据和视图都更新完毕
  • beforeDestroy:销毁之前,实例上事件、指令等都可以使用,这里组件没有真正的销毁。
  • destroyed:数据、指令、等完全销毁

5.Vue中computed和watch区别?

  • computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。
  • watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。

6.mixin 和 mixins 区别

  • mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的
Vue.mixin({
    beforeCreate() {
        // ...逻辑
        // 这种方式会影响到每个组件的 beforeCreate 钩子函数
    }
})


7.Vue2.x和Vue3.x渲染器的diff算法

  • 同级比较,再比较子节点

  • 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)

  • 比较都有子节点的情况(核心diff)

  • 递归比较子节点

  • 正常Diff两个树的时间复杂度是O(n^3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。

  • Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅

  • 在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升

  • 虽然文档不建议我们在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。

  • mixins 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。

  • 另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并

react部分

1.React 的组件间通信都有哪些形式?

  1. 父传子:在 React 中,父组件调用子组件时可以将要传递给子组件的数据添加在子组件的属性中,在子组件中通过 props 属性进行接收。这个就是父组件向子组件通信。
  2. 子传父:React 是单向数据流,数据永远只能自上向下进行传递。当子组件中有些数据需要向父级进行通信时,需要在父级中定义好回调,将回调传递给子组件,子组件调用父级传递过来的回调方法进行通信。
  3. 跨组件通信 - context。使用 context API,可以在组件中向其子孙级组件进行信息传递。

2.React 中如何实现路由懒加载?

  • 在 React 16 中,新增了 lazy 方法,通过 lazy 方法可以轻松实现组件懒加载,当然要实现路由懒加载的话,其实也只需要把路由组件结合 lazy 使用即可
 import {Route} from "react-router-dom";  
 import React,{Suspense} from 'react';       
 const HomeView = React.lazy(()=>import("./home"));     
 function App(){          
     return <div>              
     <h1>路由懒加载</h1>              
     <Route path="/" exact render={()=>{                    
         return <Suspense fallback={<div>组件Loading进来之前的占位内容</div>}>                    
         <HomeView />              
     </Suspense>             
     }} />           
     </div>       
}        
 export default App;
  •  在上述代码中,我们使用 lazy 引入了一个动态组件,然后将该组件放入了根路由中。这样的话只有用户访问网站首页时,才会动态加载这个组件。  这里要注意,在 React 规范中,lazy 和 Suspense 必须配合使用,lazy 引入的动态组件必须要放入 Suspense 中,Suspense 的 fallback 属性是 lazy 的组件没有加载进来之前的占位内容。

3. React 的生命周期函数都有哪些,分别有什么作用?

  1. constructor: 初始化组件,初始化组件的 state 等。
  2. static getDerivedStateFromProps():该函数用于将 props 中的信息映射到 state 中。
  3. render: 生成虚拟DOM
  4. componentDidMount:组件挂载完成,通过在该函数中去处理副作用 更新阶段:
  5. static getDerivedStateFromProps()
  6. shouldComponentUpdate():该生命周期函数用于判断是否要进行组件更新。
  7. getSnapshotBeforeUpdate():组件已经完成 diff,即将更新真实 DOM,用户获取上一次的DOM快照。该函数必须搭配 componentDidUpdate 一块使用,返回值会变成 componentDidUpdate 第三个参数。
  8. componentDidUpdate(): 组件更新完成,通常在该函数中进行副作用处理。 即将卸载: componentWillUnmount:组件即将卸载,用于删除组件添加到全局的数据或事件。

5.react性能优化是哪个周期函数

  • shouldComponentUpdate 这个方法用来判断是否需要调用render方法重新描绘dom。因为dom的描绘非常消耗性能,如果我们能在shouldComponentUpdate方法中能够写出更优化的dom diff算法,可以极大的提高性能

6.我现在有一个button,要用react在上面绑定点击事件,要怎么做?

class Demo {

  onClick = (e) => {
    alert('我点击了按钮')
  }

  render() {
    return <button onClick={this.onClick}>
      按钮
    </button>
  }

7.react 的渲染过程中,兄弟节点之间是怎么处理的?也就是key值不一样的时候

  • 通常我们输出节点的时候都是map一个数组然后返回一个ReactNode,为了方便react内部进行优化,我们必须给每一个reactNode添加key,这个key prop在设计值处不是给开发者用的,而是给react用的,大概的作用就是给每一个reactNode添加一个身份标识,方便react进行识别,在重渲染过程中,如果key一样,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新,如果key不一样,则react先销毁该组件,然后重新创建该组件

8.React实现的移动应用中,如果出现卡顿,有哪些可以考虑的优化方案

  1. 增加shouldComponentUpdate钩子对新旧props进行比较,如果值相同则阻止更新,避免不必要的渲染,或者使用PureReactComponent替代Component,其内部已经封装了shouldComponentUpdate的浅比较逻辑
  2. 对于列表或其他结构相同的节点,为其中的每一项增加唯一key属性,以方便React的diff算法中对该节点的复用,减少节点的创建和删除操作
  3. render函数中减少类似onClick={() => {doSomething()}}的写法,每次调用render函数时均会创建一个新的函数,即使内容没有发生任何变化,也会导致节点没必要的重渲染,建议将函数保存在组件的成员对象中,这样只会创建一次
  4. 组件的props如果需要经过一系列运算后才能拿到最终结果,则可以考虑使用reselect库对结果进行缓存,如果props值未发生变化,则结果直接从缓存中拿,避免高昂的运算代价
  5. webpack-bundle-analyzer分析当前页面的依赖包,是否存在不合理性,如果存在,找到优化点并进行优化