记录近期面试题,面试总结

记录近期换工作时遇到的面试题和面试题答案

css 部分

盒模型

问题:说一下 css 的盒模型

盒模型分为标准模型和怪异盒模型(IE 盒模型)

标准盒模型:盒模型的宽高只是内容(content)的宽高

怪异盒模型:盒模型的宽高是内容(content)+填充(padding)+边框(border)的总宽高

问题:css 如何设置两种模型

/* 标准模型 */
box-sizing:content-box;

 /*IE模型*/
box-sizing:border-box;
复制代码

问题:有没有遇到过边距重叠,怎么解决 边距重叠问题如下图所示

 

 

利用 BFC 去解决,下方详细说明 BFC

参考代码:利用 BFC 的特性,把另一个盒子放入另一个独立的 BFC 中,从而使得两个盒子之间互相不受影响

<section class="top">
    <h1>上</h1>
    margin-bottom:30px;
</section>
<div style="overflow: hidden;">
    <section class="bottom">
    <h1>下</h1>
    margin-top:50px;
    </section>
</div>
复制代码

BFC

问题:说一下 BFC

  1. 什么是 BFC

    BFC(Block Formatting Context)格式化上下文,是 Web 页面中盒模型布局的 CSS 渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。

  2. 形成 BFC 的条件

    • 浮动元素,float 除 none 以外的值
    • 定位元素,position(absolute,fixed)
    • display 为以下其中之一的值 inline-block,table-cell,table-caption
    • overflow 除了 visible 以外的值(hidden,auto,scroll)
  3. BFC 的特性

    • 内部的 Box 会在垂直方向上一个接一个的放置。
    • 垂直方向上的距离由 margin 决定
    • bfc 的区域不会与 float 的元素区域重叠。
    • 计算 bfc 的高度时,浮动元素也参与计算
    • bfc 就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。

rem 原理

rem 布局的本质是等比缩放,一般是基于宽度,假设将屏幕宽度分为 100 份,每份宽度是 1rem,1rem 的宽度是屏幕宽度/100,,然后子元素设置 rem 单位的属性

通过改变 html 元素的字体大小,就可以设置子元素的实际大小。

比 rem 更好的方案(缺点兼容不好)

vw(1vw 是视口宽度的 1%,100vw 就是视口宽度),vh(100vh 就是视口高度)

实现三栏布局

(两侧定宽,中间自适应)

这里写出五种实现方式:

  1. flex 方式实现
/* css */
.box {
    display: flex;
    justify-content: center;
    height: 200px;
}
.left {
    width: 200px;
    background-color: red;
    height: 100%;
}
.content {
    background-color: yellow;
    flex: 1;
}
.right {
    width: 200px;
    background-color: green;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
复制代码
  1. 浮动方式,此方式 content 必须放在最下边
/* css */
.box {
      height: 200px;
    }
.left {
    width: 200px;
    background-color: red;
    float: left;
    height: 100%;
}
.content {
    background-color: yellow;
    height: 100%;
}
.right {
    width: 200px;
    background-color: green;
    float: right;
    height: 100%;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="right"></div>
    <div class="content"></div>
</div>
复制代码
  1. 绝对定位方式实现
/* css */
.box {
    position: relative;
    height: 200px;
}
.left {
    width: 200px;
    background-color: red;
    left: 0;
    height: 100%;
    position: absolute;
}
.content {
    background-color: yellow;
    left: 200px;
    right: 200px;
    height: 100%;
    position: absolute;
}
.right {
    width: 200px;
    background-color: green;
    right: 0;
    height: 100%;
    position: absolute;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
复制代码
  1. 表格布局实现方式
/* css */
.box {
    display: table;
    height: 200px;
}
.left {
    width: 200px;
    background-color: red;
    height: 100%;
    display: table-cell;
}
.content {
    background-color: yellow;
    height: 100%;
    display: table-cell;
}
.right {
    width: 200px;
    background-color: green;
    height: 100%;
    display: table-cell;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
复制代码
  1. grid 网格布局
/* css */
.box {
    display: grid;
    grid-template-columns: 200px auto 200px;
    grid-template-rows: 200px;
}
.left {
    background-color: red;
}
.content {
    background-color: yellow;
}
.right {
    background-color: green;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
复制代码

使一个盒子水平垂直居中

这里写出五种实现方式:

  1. 宽度和高度已知的
/* css */
#box{
    width: 400px;
    height: 200px;
    position: relative;
    background: red;
}
#box1{
    width: 200px;
    height: 100px;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -100px;
    margin-top: -50px;
    background: green;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
复制代码
  1. 宽度和高度未知
/* css */
 #box{
    width: 800px;
    height: 400px;
    position: relative;
    background: red;
}
#box1{
    width: 100px;
    height: 50px;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    margin: auto;
    background: green;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
复制代码
  1. flex 布局
/* css */
#box{
    width: 400px;
    height: 200px;
    background: #f99;
    display: flex;
    justify-content: center;//实现水平居中
    align-items: center;//实现垂直居中
}
#box1{
    width: 200px;
    height: 100px;
    background: green;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
复制代码
  1. 平移 定位+transform
/* css */
#box{
    width: 400px;
    height: 200px;
    background: red;
    position: relative;
}
#box1{
    width: 200px;
    height: 100px;
    background: #9ff;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
复制代码
  1. table-cell 布局
/* css */
#box{
    display: table-cell;
    vertical-align: middle
}
#box1{
    margin: 0 auto;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
复制代码

js 部分

什么是闭包,闭包的用途

能够读取其他函数内部变量的函数,或简单理解为定义在一个函数内部的函数,内部函数持有外部函数内变量的引用。

闭包的用途:

  • 读取其他函数内部的变量
  • 让变量的值始终保存在内存中
  • JavaScript中闭包的应用都有关键词return,引用 JavaScript 秘密花园中的一段话就是因为:

闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。

call, apply, bind 区别? 怎么实现 call,apply 方法

相似之处:

  1. 都是用来改变函数的 this 对象的指向的。
  2. 第一个参数都是 this 要指向的对象。
  3. 都可以利用后续参数传参。 区别:
  4. call 接受函数传参方式为:fn.call(this, 1, 2, 3)
  5. apply 接受函数传参方式为:fn.apply(this,[1, 2, 3])
  6. bind 的返回值为一个新的函数,需要再次调用: fn.bind(this)(1, 2, 3)

手动实现 call 方法:

Function.prototype.myCall = function(context = window, ...rest) {
    context.fn = this; //此处this是指调用myCall的function
    let result = context.fn(...rest);
    //将this指向销毁
    delete context.fn;
    return result;
};
复制代码

手动实现 apply 方法:

Function.prototype.myCall = function(context = window, params = []) {
    context.fn = this; //此处this是指调用myCall的function
    let result
    if (params.length) {
        result = context.fn(...params)
    }else {
        result = context.fn()
    }
    //将this指向销毁
    delete context.fn;
    return result;
};
复制代码

手动实现 bind 方法:

Function.prototype.myBind = function(oThis, ...rest) {
    let _this = this;
    let F = function() {}
    // 根据 bind 规定,如果使用 new 运算符构造 bind 的返回函数时,第一个参数绑定的 this 失效
    let resFn = function(...parmas) {
        return _this.apply(this instanceof resFn ? this : oThis, [
            ...rest,
            ...parmas
        ]);
    };
    // 继承原型
    if (this.prototype) {
        F.prototype = this.prototype;
        resFn.prototype = new F;
    }
    return resFn;
};
复制代码

怎么理解原型和原型链

每个函数都有 prototype

每一个对象都有 __proto__

实例的 __proto__ 指向构造函数的 prototype

js 引擎会沿着 __proto__ -> ptototype 的顺序一直往上方查找,找到 Object 为止

js 继承的几种方式

常用继承:组合继承,寄生组合继承

组合继承: 利用 call 继承父类上的属性,用子类的原型等于父类实例去继承父类的方法

缺点:调用两次父类,造成性能浪费

function Parent(name) {
    this.name = name;
}

Parent.prototype.say = function() {
    console.log(this.name);
};
function Child(name) {
    Parent.call(this, name)
}
Child.prototype = new Parent;
let c = new Child("YaoChangTuiQueDuan");
c.say()
复制代码

寄生组合继承:利用 call 继承父类上的属性,用一个干净的函数的原型去等于父类原型,再用子类的原型等于干净函数的实例

function Parent(name) {
    this.name = name;
}

Parent.prototype.say = function() {
    console.log(this.name);
};

function ExtendMiddle() {}

function Child(name) {
    Parent.call(this, name)
}

ExtendMiddle.prototype = Parent.prototype;
Child.prototype = new ExtendMiddle

let c = new Child("YaoChangTuiQueDuan");
c.say()
复制代码

eventloop

请参考文章 Eventloop 不可怕,可怕的是遇上 Promise

new 的过程中做了什么? 手动实现一个 new

  1. 新生成了一个对象
  2. 链接到原型
  3. 绑定 this
  4. 返回新对象
function create(...rest) {
    // 创建一个空的对象
    let obj = new Object()
    // 获得构造函数
    let Con = rest.shift()
    // 链接到原型
    obj.__proto__ = Con.prototype
    // 绑定 this,执行构造函数
    let result = Con.apply(obj, arguments)
    // 确保 new 出来的是个对象
    return typeof result === 'object' ? result : obj
}
复制代码

说一些常用的 es6

  • let/const
  • 模板字符串
  • 解构赋值
  • 块级作用域
  • Promise
  • Class
  • 函数默认参数
  • 模块化
  • 箭头函数
  • Set
  • Map
  • Array.map
  • 等等

Promise 的基本使用和原理,以及简易版的 Promise 实现

Promise 的几个特性:

  1. Promise 捕获错误与 try/catch 相同
  2. Promise 拥有状态变化,并且状态变化不可逆
  3. Promise 属于微任务
  4. Promise 中的.then 回调是异步的
  5. Promsie 中.then 每次返回的都是一个新的 Promise
  6. Promise 会存储返回值

Promise 的简单实现:

class MyPromise {
    constructor(fn) {
        this.resolvedCallbacks = [];
        this.rejectedCallbacks = [];
        this.state = "PADDING";
        this.value = "";
        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(value) {
        if (this.state === "PADDING") {
            this.state = "RESOLVED";
            this.value = value;
            this.resolvedCallbacks.forEach(cb => cb());
        }
    }

    reject(value) {
        if (this.state === "PADDING") {
            this.state = "REJECTED";
            this.value = value;
            this.rejectedCallbacks.forEach(cb => cb());
        }
    }

    then(resolve = function() {}, reject = function() {}) {
        if (this.state === "PADDING") {
            this.resolvedCallbacks.push(resolve);
            this.rejectedCallbacks.push(reject);
        }
        if (this.state === "RESOLVED") {
            resolve(this.value);
        }
        if (this.state === "REJECTED") {
            reject(this.value);
        }
    }
}
复制代码

有没有使用过 async/await 说一下和 Promise 的区别、联系

  1. 使用了Promise,并没有和Promise冲突
  2. 在写法上完全是同步,没有任何回调函数
  3. await 必须在 async 函数中使用
  4. await 后面的方法必须是一个 Promise
  5. 写在 await 后面的代码,相当于是执行 Promise.resolve()
  6. 使用 try/catch 来捕获错误

如何实现在Object上使用for...of迭代器

可以在 Object 的 prototype 上挂载一个 Symbol.iterator 方法,该方法返回一个对象,对象中包含 next 方法, next 方法也会返回一个对象,对象中包含 valuedone。 value 表示 每次迭代完成的值,done 表示是否迭代完成,为 false 时会继续迭代,为 true 表示迭代完成,停止迭代

Object.prototype[Symbol.iterator] = function () {
  let values = Object.values(this);
  let keys = Object.keys(this);
  let len = 0;
  return {
    next() {
      return {
        value: {
          value: values[len],
          key: keys[len++]
        },
        done: len > values.length
      }
    }
  }
}
复制代码

CommonJS 和 ES6 中的模块化的两者区别

  1. 前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
  2. 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
  3. 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
  4. 后者会编译成 require/exports 来执行的

module.exports 和 exports 有什么关系

为了方便,Node为每个模块提供一个 exports 变量,指向 module.exports,相当于 exports 是 module.exports 地址的引用

会产生的问题:如果将 exports 新赋值了一个对象,如: exports = {},这个时候,就会打断与 module.exports 的联系,会导致导出不成功

什么是防抖和节流?有什么区别?

  • 控制频繁调用事件的调用频率
  • 防抖和节流的作用都是防止函数多次调用。
  • 区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的情况会每隔一定时间(参数wait)调用函数。

深拷贝用来解决什么问题?如何实现

什么是堆,什么是栈

HTTP 部分

HTTP之请求消息Request

  1. 请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
  2. 请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本.
  3. 请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息
  4. 空行,请求头部后面的空行是必须的
  5. 请求数据也叫主体,可以添加任意的其他数据。

HTTP之响应消息Response

HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

  1. 状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
  2. 消息报头,用来说明客户端要使用的一些附加信息
  3. 第三部分:空行,消息报头后面的空行是必须的
  4. 第四部分:响应正文,服务器返回给客户端的文本信息。

浏览器

在浏览器地址栏键入URL,按下回车之后会经历以下流程:

  1. 解析 url 到 dns 服务器
  2. dns 服务器返回 ip 地址到浏览器
  3. 跟随协议将 ip 发送到网络中
  4. 经过局域网到达服务器
  5. 进入服务器的 MVC 架构 Controller
  6. 经过逻辑处理,请求分发,调用 Model 层
  7. Model 和数据进行交互,读取数据库,将最终结果通过 view 层返回到网络回到浏览器
  8. 浏览器根据请求回来的 html 和关联的 css, js 进行渲染
  9. 在渲染的过程中,浏览器根据 html 生成 dom 树,根据 css 生成 css 树
  10. 将 dom 树和 css 树进行整合,最终知道 dom 节点的样式,在页面上进行样式渲染
  11. 浏览器去执行 js 脚本
  12. 最终展现页面

浏览器渲染的过程,大致可以分为五步:

  1. html代码转化为dom
  2. css代码转化为cssom
  3. 结合dom和cssom,生成一颗渲染树
  4. 生成布局,即将所有的渲染树的节点进行平面合成
  5. 将布局绘制在屏幕上

浏览器缓存

当浏览器再次访问一个已经访问过的资源时,它会这样做:

  1. 看看是否命中强缓存,如果命中,就直接使用缓存了。
  2. 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。
  3. 如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。
  4. 否则,请求网络返回最新的资源。

浏览器缓存的位置:

  1. Service Worker: 它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
  2. Memory Cache: 内存缓存,读取内存中的数据肯定比磁盘快。但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
  3. Disk Cache: Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

缓存的实现: 强缓存和协商缓存都是根据 HTTP Header 来实现的

什么是重绘和回流

vue&react

说一下 MVVM

MVVM是双向数据绑定

  • M: Model 数据层
  • V: View 视图层
  • VM: ViewModel 视图层和数据层中间的桥,视图层和数据层通信的桥梁

view 层通过事件去绑定 Model 层, Model 层通过数据去绑定 View 层

什么是 Virtual DOM 为什么使用 Virtual DOM

在之前,渲染数据时,会直接替换掉 DOM 里的所有元素,换成新的数据,为了渲染无用 DOM 所造成的性能浪费,所以出现了 Virtual DOM, Virtual DOM 是虚拟 DOM,是用 js 对象表示的树结构,把 DOM 中的属性映射到 js 的对象属性中,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等。当数据改变时,重新渲染这个 js 的对象结构,找出真正需要更新的 DOM 节点,再去渲染真实 DOM。Virtual DOM 本质就是在 JS 和 DOM 之间做了一个缓存

为什么操作真实 dom 有性能问题

因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

Vue 的响应式原理

Vue 内部使用了 Object.defineProperty() 来实现数据响应式,通过这个函数可以监听到 setget 的事件。

  1. 首先利用 Object.defineProperty() 给 data 中的属性去设置 set, get 事件
  2. 递归的去把 data 中的每一个属性注册为被观察者
  3. 解析模板时,在属性的 get 事件中去收集观察者依赖
  4. 当属性的值发生改变时,在 set 事件中去通知每一个观察者,做到全部更新

Vue 的模板如何被渲染成 HTML? 以及渲染过程

  1. vue 模板的本质是字符串,利用各种正则,把模板中的属性去变成 js 中的变量,vif,vshow,vfor 等指令变成 js 中的逻辑
  2. 模板最终会被转换为 render 函数
  3. render 函数执行返回 vNode
  4. 使用 vNode 的 path 方法把 vNode 渲染成真实 DOM

Vue 的整个实现流程

  1. 先把模板解析成 render 函数,把模板中的属性去变成 js 中的变量,vif,vshow,vfor 等指令变成 js 中的逻辑
  2. 执行 render 函数,在初次渲染执行 render 函数的过程中 绑定属性监听,收集依赖,最终得到 vNode,利用 vNode 的 Path 方法,把 vNode 渲染成真实的 DOM
  3. 在属性更新后,重新执行 render 函数,不过这个时候就不需要绑定属性和收集依赖了,最终生成新的 vNode
  4. 把新的 vNode 和 旧的 vNode 去做对比,找出真正需要更新的 DOM,渲染

什么是 diff 算法,或者是 diff 算法用来做什么

  • diff 是linux中的基础命令,可以用来做文件,内容之间的对比
  • vNode 中使用 diff 算法是为了找出需要更新的节点,避免造成不必要的更新

Vuex是什么

vuex 就像一个全局的仓库,公共的状态或者复杂组件交互的状态我们会抽离出来放进里面。

vuex的核心主要包括以下几个部分:

  • state:state里面就是存放的我们需要使用的状态,他是单向数据流,在 vue 中不允许直接对他进行修改,而是使用 mutations 去进行修改
  • mutations: mutations 就是存放如何更改状态的一些方法
  • actions: actions 是来做异步修改的,他可以等待异步结束后,再去使用 commit mutations 去修改状态
  • getters: 相当于是 state 的计算属性

使用:

  • 获取状态在组件内部 computed 中使用 this.$store.state 得到想要的状态
  • 修改的话可在组件中使用 this.$store.commit 方法去修改状态
  • 如果在一个组件中,方法,状态使用太多。 可以使用 mapState,mapMutations 辅助函数

Vue 中组件之间的通信

如何解决页面刷新后 Vuex 数据丢失的问题

Vue中插槽的用法

Vue 的生命钩子函数,调用每个钩子的时候做了什么

前端路由的两种实现原理

什么是ssr, ssr和之前的后台模板有什么区别

算法

排序算法(快排,冒泡)

笔试算法题

已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

方法一:
function handleArr(arr) {
  let flatten = (arr) => arr.push ? arr.reduce((pre, current) => [...pre, ...flatten(current)], []) : [arr];
  return [...new Set(flatten(arr))].sort((a, b) => a - b);
}

方法二:
[...new Set(arr.toString().split(",").map(Number))].sort((a,b)=> a - b)

方法三:
[...new Set(arr.flat(Infinity))].sort((a,b)=>{ return a - b})


作者:腰长腿却短
链接:https://juejin.im/post/5ca1ac256fb9a05e6938d2d1
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/sinat_17775997/article/details/89007225