前端面试的一些总结

356 阅读30分钟

前端面试总结(自用)

(总结收集来源于网络)

数据类型

  • 基础数据类型
    • number:整数和小数
    • string:文本
    • boolean:表示真伪的两个特殊值
    • null:表示空值
    • undefined:表示未定义或不存在
    • ES6新增:symbol和bigInt
  • 复杂数据类型
    • Object
    • Function
    • Array

输入网址后的过程

  1. 解析域名(DNS域名解析)
    • 浏览器DNS缓存
    • 计算机DNS缓存
    • 路由器DNS缓存
    • 网络运营商DNS缓存
    • 递归查询
  2. 建立TCP连接(TCP三次握手)
    1. 客户端发送服务器,告知准备好了,请确认
    2. 服务器发送客户端,告知也准备好了,请确认
    3. 客户端发送服务器,确认完毕
  3. 发请求到服务器(发送请求报文)
  4. 服务器响应返回(返回响应报文)
  5. 浏览器解析渲染页面
    • 遇到HTML,调用HTML解析器,解析成DOM树
    • 遇到css,调用css解析器,解析成CSSOM树
    • 遇到JS,调用JS解析器,解析JS代码
      • 可能要修改的元素节点,重新调用HTML解析器,解析成新的DOM树
      • 可能要修改样式,重新调用css解析器,解析成新的CSSOM树
  6. 断开TCP连接(TCP四次挥手)
    1. 客户端发送服务器,告知请求发送完毕,可以断开了
    2. 服务器发送客户端,告知请求接收完毕
    3. 服务器发送客户端,告知响应发送完毕,可以断开了
    4. 客户端发送服务器,告知响应接收完毕

检测数据类型

  • typeof
    • 注意:
      • 使用typeof检测一个未声明的值,不会报错,而是返回undefined
      • 检测Array返回Object,因为Array是特殊的对象
      • 检测null返回Object
  • instanceof
  • Object.prototype.tostring

原型

  • 每个函数都有一个显式原型属性:prototype
  • 每个实例都有一个隐式原型属性:.proto
  • 实例的__proto__与对应函数的prototype都指向原型对象
  • 原型对象上有一个constructor属性指向对应的构造函数

如何区分构造函数和普通函数

  • 通过是否调用new来区分一个函数是不是构造函数 ps:
  • 无论是函数名还是变量名,统称为标识
  • 构造函数具有显式原型属性和对应的原型对象。那么,普通函数也有显式原型属性和对应的原型对象

new关键字做了什么?

  1. 创建一个空对象,如果有实参,就将实参传给形参
  2. 该对象的隐式原型属性会指向构造函数的原型对象
this.__proto__ = Person.prototype
  1. 声明了this,这个新对象会绑定到函数调用的this
  2. 会默认返回当前的实例对象this
    • 如果返回值不是对象数据类型,会自动返回this
    • 如果返回值是对象数据类型,会返回该对象,不会返回this

原型链

  • 从对象的_proto_开始,连接的所有对象,就是原型链,也叫隐式原型链
  • 查找对象属性,先找自身,找不到就沿着原型链查找,还找不到就返回undefined

71636872412_.pic_hd.jpg

查找对象属性的基本流程

  • 现在对象自身上找,如果有,直接返回
  • 如果没有,根据__proto__在原型对象上找,找到就返回
  • 还是没有就一直沿着原型链找,直到找到为止
  • 找到了就返回,找到最后__proto__为null时,返回undefined

面试题——表达式a.b的解析流程

  • 先在作用域链查看a
    • 不存在:报错
    • 存在:得到a的值
      • 基本类型
        • null/undefined => 报错
        • number/string/boolean => 创建一个此值的包装对象
      • 地址值 => 解析b => 查找b属性
        • 先找自身,找到返回,如果没找到
        • 原型链查找
          • 找到返回
          • 没找到返回undefined

理解:A是不是B创建的?(A instanceof B 的原理)

实际上就是递归查找B的原型对象到底有没有出现在A的原型链上

A.__proto__ === B.prototype
A.__proto__.__proto__ === B.prototype

终极原型链

  1. 万物皆对象(除基本数据类型)
  2. 所有的对象都是Object的实例对象(直接或间接)
  3. 所有的函数都是Function的实例对象(包括Function自身)
  4. 函数时特殊的对象

注意:

1.到底是函数创建了对象,还是对象创建了函数? (其实JS底层使用C语言写了三个东西)

  1. Object

  2. Function

  3. Function.prototype 2.在JS终极原型链中,有四条是作者自己加上去的

  4. Function的隐式原型属性和Function的显式原型属性指向同一个原型对象

    • 由于Function自己本身就是一个函数,所以它也要能使用所有函数共享的方法
  5. Object的隐式原型属性和Function的显式原型属性指向同一个原型对象

  6. Function的原型对象的隐式原型属性和Object的显式原型属性指向相同

    • 因为在JS设计的时候,Function就是一个特殊的对象,那么说明Object的原型对象要出现在Function的原型链上
  7. Object的原型对象的隐式原型属性为null

    • 因为所有的对象都是Object的实例对象,会导致Object的原型对象的隐式原型指向自己,产生死循环

image.png

作用域(就是变量的作用范围,某块范围之内,所有具有的变量)

  • 全局作用域(程序的最外层作用域,一直存在)
  • 函数作用域(函数定义时创建)
  • 块级作用域(ES6新增):const/let ({}内的就是)

预解析

  1. 变量提升 -> 只提升声明部分,赋值语句还会保留在原地(在变量定义语句之前,就可以访问到这个变量)
  2. 函数提升 -> 整体提升(在函数定义语句之前,就执行该函数)
  3. 函数表达式 -> 在JS引擎眼里,他就是变量提升(只提升声明部分) 注意:提升至当前作用域的最前面

作用域确认时间:函数声明的时候

扩展:

  • 在编程界,具有动态作用域和词法作用域(静态作用域),但是JS中只有静态作用域
  • 函数表达式,本质是变量声明,所以预解析环节,函数表达式只会把变量提升

作用域链

  • 多个嵌套的作用域形成的由内向外的结构,用于查找变量

闭包

  • 什么是闭包?
    • 闭包是一个对象(js容器),内部存放要使用到的变量,闭包就是意外存活的变量对象
  • 如何产生闭包?
    • 函数嵌套函数,内部函数使用到外部函数变量的时候,会产生闭包
  • 闭包的原理
    • 闭包的实现原理,其实是利用了作用域链的特性,我们都知道作用域链就是在当前执行环境下访问某个变量时,如果不存在就一直向外层寻找,最终寻找到最外层也就是全局作用域,这样就形成了一个链条。
  • 闭包的形式
    1. 闭包是一个函数,而且存在于另一个函数当中
    2. 闭包可以访问到父级函数的变量,且该变量不会销毁
  • 闭包是什么时候产生的?
    1. 外部函数被调用的时候
    2. 内部函数声明的时候
  • 闭包的好处
    • 延长局部变量的生命周期
    • 模块化(减少全局的污染)
  • 闭包的坏处
    • 由于缓存下来的变量数据还会占用内存,所以会导致内存泄露,如果内存泄露过多,导致内存溢出,程序会宕机
  • 如何解决闭包的副作用
    • 由于垃圾回收机制是根据是否还有被引用来决定是否回收内存的,而闭包不会销毁的原因,是因为有函数正在依赖它,而函数不会被销毁的原因,是因为有变量正在指向它。所以,我们只需将指向该函数的所有标识(函数名和变量名)全部移除,没人在使用它,那么函数就会被销毁,闭包也会被销毁
  • 闭包的应用
    • 需求:实现a的自增

      1. 通过全局变量,可以实现,但会污染其他程序
      var a = 10;
      function Add(){
          a++;
          console.log(a);
          }
          Add();
          Add();
          Add();
      
      1. 定义一个局部变量,不污染全局,但实现不了递增
      var a = 10;
      function Add2(){
          var a = 10;
          a++;
          console.log(a);
          }
          Add2();
          Add2();
          Add2();
          console.log(a)
      
      1. 通过闭包,可以实现函数内部局部变量递增,不会影响全部变量
      var a = 10;
      function Add3(){
          var a = 10;
          return function(){
              a++;
              return a;
          };
      };
      var cc = Add3();
      console.log(cc());
      console.log(cc());
      console.log(cc());
      console.log(a);
      

执行上下文

  1. 每次函数调用的时候,会在内存中开辟的一块空间
  2. 组成部分
    1. 运行当前函数所需要的内存
    2. 变量对象(用于收集当前函数中声明的所有变量)
  3. 创建时间:函数调用
  4. 销毁时间:函数执行结束

this

this 就是一个指针,指向调用函数的对象

常见的this指向

  1. 普通调用 A() -> this指向window
  2. 构造调用 new A() -> this指向当前函数创建的实例对象
  3. 隐式调用 obj.A() -> this指向前面的实例对象
  4. 显式调用 A.call(obj) -> this指向call中的第一个实参
  5. 箭头函数 -> this指向外层作用域的this
  6. 事件回调函数 -> this指向事件源
  7. 定时器的回调函数 -> this指向window
  8. vue组件中method函数 -> this指向vue组件实例对象

call和apply的区别

  • 相同点
    1. 改变当前借调函数的this指向(仅当前这次)
    2. 都会同步指向借调函数
    3. 第一个参数都是用于修改当前本次执行的this,如果传入null或undefined,就会指向window
  • 不同点 传递参数不同
    • A.call(obj,a,b,c) -> 传入实参以逗号隔开,数量从0到无限
    • A.apply(obj,[a,b,c]) -> 传入实参以数组存储,数量为0到2

手写call()

  • 不使用ES6语法
Function.prototype.myCall = function(obj){
    //判断,当传入参数为null或undefined时,this指向window
    if(obj === null||obj === undefined ){
        obj = window;
    }
    var args = []; //获取传入的参数
    for(var i = 1;i<arguments.length;i++){
        args.push(arguments[i]);
    }
    obj._b = this; //改变this指向
    var res = eval("obj._b("+args.toString()+")");
    delete obj._b; //改完指向就删除
    return res;
}
  • 使用ES6语法
Function.prototype.myCall = function(Obj,...args){
    //判断,当传入参数为null或undefined时,this指向window
    if(Obj === null||Obj === undefined ){
        Obj = window;
    }
    Obj._b = this; //改变this指向
    var res = Obj._b(...args);
    delete Obj.-b;
    return res;

手写bind()

Function.prototype.myBind = function (obj,...args){
    //判断,当传入参数为null或undefined时,this指向window
     if(obj === null||obj === undefined ){
        obj = window;
     }
     return (...args) => {
         this.apply(obj,[...args,...args1])
         }
     }

Promise

  • ES6推出的新的更好的异步编程解决方案
    • 可以在异步操作启动后或完成后,再指定回调函数得到异步结果数据
    • 解决嵌套回调的回调地狱问题——promise的链式调用
  • promise对象的三种状态
    • pending -> 初始状态
    • resolved/fulfilled -> 成功的状态
    • rejected -> 失败的状态
  • promise状态的两种变化(变化是不可逆的)
    • pending -> resolved
    • pending -> rejected

注意:

  • Promise是一个构造函数,用于创建promise实例对象(初始化状态时pending)
  • Promise构造函数必须接受一个实参,也就是执行器函数,该执行器函数会被同步执行
    • 执行器函数接收2个实参
      1. resolve -> 可以将当前的promise状态改为成功
      2. reject -> 可以将当前的promise状态改为失败

promise.then

  • 该函数最多接受2个实参,数据类型:函数
    • 如果前面的promise的状态变为成功,触发第一个回调
    • 如果前面的promise的状态变为失败,触发第二个回调
  • 总是返回一个新的promise
  • 新的promise的结果有then指定的回调函数指向的结果决定
    • 抛出错误
    • 返回成功的promise
    • 返回失败的promise 扩展:
  • 如何让.than的回调函数返回成功
    1. .than的回调函数中返回值不是一个promise对象
    2. .than返回成功的promise
  • 如何让.than的回调函数返回失败
    1. .than的回调函数返回一个失败的promise对象
    2. 报错

promise.all([promise1,promise2,promise3])

  • 批量/一次性发生多个异步请求
  • 全部成功,返回的promise才为成功
  • 只要有一个失败,返回的promse就失败

promise.race([promise1,promise2,promise3])

  • 一次性发送多个异步请求,谁先回来用谁

async/await

  • async/await和promise的关系
    • async/await是消灭异步回调的终极武器
    • 作用:简化promise对象的使用,不用在使用then/catch来指定回调函数
    • 和promise并不排斥,反而相辅相成
    • 执行async函数,返回promise对象
    • await相当于promise的then
    • try...catch可捕获异常,相当于promise的catch
  • await之后的代码都会被放在promise的then的成功回调中,一定会返回一个全新的promise对象
  • 如何控制该promise的状态变化
    • 变为成功:当前函数中所有代码都执行结束,如果当前函数返回非promise对象的数据,会作为async函数返回promise的结果
    • 变为失败:当前函数执行过程中报错 注意:如果async函数中出现错误,后续代码将不会继续执行

缓存

cookie

  • 服务器发送到浏览器的一小块数据,浏览器会进行存储,并与下一个请求一起发送到服务器
  • 可以设置有效时长,没设置的话,会话关闭时失效
  • 存储大小4kb左右
  • 自动携带在请求头中,过多使用cookie来保存数据会导致性能问题
  • 主要用途
    1. 会话管理:登陆,购物车,游戏得分或者服务器应该记住的其他内容
    2. 个性化:用户偏好,主题或其他设置
    3. 追踪:记录和分析用户行为
  • 使用方式
    • 保存cookie的值
        var dataCookie = '110'
        document.cookie = 'token'+'='+dataCookie
    
    • 获取指定的cookie
        function getCookie(name) { 
            let result = document.cookie.match("(^|[^;]+)\\s*" + name + "\\s*=\\s*([^;]+)") 
            return result ? result.pop() : "" 
        }
    

session

  • 客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是session对象
  • 服务器可以利用session存储客户端在同一个会话期间的一些操作记录
  • 仅保存当前会话,会话关闭则失效

localStorage和sessionStorage

  • 区别
    • localStorage:本地缓存,没有时间限制,将一直缓存在本地
    • sessionStorage:会话缓存,即浏览器关闭时清楚缓存数据
  • 容量:存储大小5mb左右
  • 使用方法
    1. 设置缓存数据:localStorage.setItem(key,value)
    2. 获取缓存数据:localStorage.getItem(key)
    3. 获取全部:localStorage.valueOf()
    4. 获取指定下标的key键值:localStorage.key(n)
    5. 删除缓存数据:localStorage.removeItem(key)
    6. 清空全部:localStorage.clear()

同步和异步

  1. 同步:前一个任务结束后执行后一个任务
  2. 异步:做一个任务的同时还能做其他任务
  • 同步任务都在主线程上执行,形成一个执行栈
  • 异步任务是通过回调函数实现的

数组的方法

  • push() :向数组的末尾添加一项或多项,返回更新后数组的长度
  • pop() :删除数组的最后一项,返回被删除的项
  • unshift() :向数组开头添加一项或多项,返回更新后数组的长度
  • shift() :删除数组第一项,返回被删除的项
  • splice() : 删除,插入,替换
    1. 参数1:索引值
    2. 参数2:删除数量
    3. 参数3:替换值
  • reverse() :翻转数组
  • sort() :数组排序
  • concat() :合并数组
  • slice() :复制创建新数组
    1. 参数1:开始的索引值
    2. 参数2:结束的索引值
  • indexof() :查找元素在数组的索引值,找到返回索引值,否则返回-1
  • lastindexof() :倒着查找元素在数组的索引值,找到返回索引值,否则返回-1

H5的新特性

  1. 语义化标签
    • header
    • footer
    • section
    • nav
    • aside
    • article
  2. input新属性
    • color:颜色选择器
    • number:数字
    • Email:邮件格式
    • Date:日期
  3. 音视频
    • video
    • audio
  4. canvas画布
  5. getCurrentPostion():获取用户地理位置
  6. 选择器
    1. first-Child
    2. last-Child
    3. nth-Child
  7. 伪类
    • a:link
    • a:visited
    • a:hover
    • a:active
  8. border-radius圆角
  9. box-shadow阴影
  10. websocket
  11. ::before ::after
  12. flex
  13. transition动画

CSS3的新特性

  • 选择器
    1. E:first-of-type: 选择属于其父元素的首个E元素的每个E元素
    2. E:last-of-type: 选择属于其父元素的最后E元素的每个E元素
    3. E:only-of-type: 选择属于其父元素唯一的E元素的每个E元素
    4. E:only-child: 选择属于其父元素的唯一子元素的每个E元素
    5. E:nth-child(n): 选择属于其父元素的第n个子元素的每个E元素
    6. E:nth-last-child(n): 选择属于其父元素的倒数第n个子元素的每个E元素
    7. E:nth-of-type(n): 选择属于其父元素第n个E元素的每个E元素
    8. E:nth-last-of-type(n): 选择属于其父元素倒数第n个E元素的每个E元素
    9. E:last-child: 选择属于其父元素最后一个子元素每个E元素
  • 动画
    • transition
    • Animation
    • transform
  • 边框
    • border-radius
    • border-shadow
    • border-image
  • 背景
    • background-clip
    • background-origin
    • background-size
    • background-break
  • 文字效果
    • word-wrap :允许文本强制文本进行换行
    • text-overflow :设置或检索当当前行超过指定容器的边界时如何显示
      • clip
      • ellipsis
    • text-shadow:可向文本应用阴影
  • 用户界面
    • box-sizing
      • content-box:
        • padding和border不被包含在定义的width和height之内
      • border-box:
        • padding和border被包含在定义的width和height之内。对象的实际宽度就等于设置的width值

文本溢出隐藏

  • 单行文本溢出隐藏
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
  • 多行文本溢出隐藏
    overflow:hidden;
    text-overflow:ellipsis;
    display:-webkit-box;
    -webkit-line-clamp:2;
    -webkit-box-orient:vertical;

ES6的新特性

const/let

- const定义常量
- let定义变量
- 与var的区别(var有变量提升,有初始化提升,值可变)
    - 有块作用域
    - 没有变量提升
    - 不会添加到window上
    - 不能重复声明

ps:暂时性死区问题说明:其实const/let是有变量提升的,但没有初始化提升

默认参数

  • 原来写法
```js
    function fn(name,age){
        var name = name || 'lihua'
    }
```
  • ES6
```js
    function fn(name='lihua',age = 18){}
```

扩展运算符...

  • 原来拼接数组 arr1.concat(arr2)
  • ES7 [...arr1,...arr2]

剩余参数

    function fn(name,...params){
        console.log(name)
        console.log(params)
        }
    fn('lihua',1,2,3,4) // lihua [1,2,3,4]

模版字符串

  • 原来写法
```js
    console.log(name+'今年'+age+'岁啦')
```
  • ES6
```js
    console.log(`${name}今年${age}岁啦`)
```

Object.keys

  • 可以用来获取对象的key的集合,进而可以获取对应key的value
    const Obj = {
        name:'lihua',
        age:22,
        gender:'男'
        }
     console.log(Object.keys(Obj)) //['name','age','gender']

箭头函数

  • 与普通函数的区别
    1. 箭头函数不可作为构造函数,不能使用new
    2. 箭头函数没有自己的this
    3. 箭头函数没有arguments对象
    4. 箭头函数没有原型对象

解构赋值

  • 解构对象
    const {name,age,gender} = obj
    
  • 解构数组
    const [a,b,c] = arr
    
  • 解构重名
    const {name:newname} = obj
    
  • 嵌套解构
    const {doing:{evening}} = obj
    
  • 默认赋值
    const {name,age,gender} = obj
    
  • 乱序解构
    const [a,b,c=1] = arr
    
  • 形参解构
    add({id,title}){}
    
  • 引入模块解构
    import {getList} from '@/api'
    

ES6一些数组的方法

  • Array.reduce
    • 对数组元素从左到右依次执行reducer函数,返回一个累计的值
    • 第一个参数是callback函数:pre为上次return的值,next为本次数组遍历的项
    • 第二个参数为初始值,也就是第一个pre
     //计算1+2+3+4+5的值
     const arr = [1,2,3,4,5];
     const sum = arr.reduce((pre,next)=>{
         return pre+next;
         },0) // 15
     //统计元素出现的次数
     const arr = ['我','我','你','我','他','你'];
     const obj = arr.reduce((pre,next)=>{
         if(pre[next]){
             pre[next]++
             }else{
             pre[next] = 1;
             }
         return pre
         },{})
  • Array.forEach
    • forEach方法按升序为数组中含有效值的每一项执行一次callback函数
    • 总是返回undefined值,并且不可链式调用
    • forEach的参数
    arr.forEach((self,index,arr) =>{},this)
    
    **self:**  数组当前遍历的元素,默认从左往右依次获取数组元素\
    **index:**  数组当前元素的索引,第一个元素索引为0,依次类推\
    **arr:**  当前遍历的数组\
    **this:**  回调函数中this指向
    
     const arr = [1,2,3,4,5]
     arr.forEach((item,index,arr)=>{
         console.log(item,index,arr)
         })
  • Array.map

    • 常用于返回一个处理过后的新数组
    • 按照原始数组元素顺序依次处理元素
    • 不会对空数组进行检测
    • 不会改变原始数组
         const arr = [1,2,3,4,5];
         console.log(arr.map((num,index,arr)=>2*num)) //[2,4,6,8,10]
    
  • Array.filter - 用来过滤数组

        const arr = [1,2,3,4,5];
        const arr2 = arr.filter((num,index,arr)=> num >3) //[4,5]
    
  • Array.some

    • 一真即真
     const arr = [1,2,3,4,5];
     console.log(arr.some(val=>val = 5)) // true

  • Array.every - 一假即假
      const arr = [1,2,3,4,5];
      console.log(arr.every(val=>val = 5)) //false

对象属性同名简写

  • 原来写法
     const name = 'lihua';
     const age = 18;
     const obj = {
         name:name,
         age:age
         }
  • ES6
     const name = 'lihua';
     const age = 18;
     const obj = {
         name,
         age
         }

find和findIndex

  • find:找到返回被找元素,找不到返回undefined
  • findIndex:找到返回被找元素索引,找不到返回-1

for of和for in

  • for in:遍历方法,可遍历对象和数组
     const obj = {name:'lihua',age:18,sex:'man'};
     const arr = [1,2,3,4,5];
     //for in 遍历对象
     for(let key in obj){
         console.log(key) // name age sex
         }
     //for in 遍历数组
     for(let index in arr){
         console.log(index) //0,1,2,3,4
         }
  • for of:遍历方法,可遍历数组,不能遍历对象
     const arr = [1,2,3,4,5];
     for(let item of arr){
         console.log(item) //1,2,3,4,5
         }

Promise

async函数

await表达式

类语法class

  • 本质上也是function,class是function的语法糖
     class Person{
         constructor(name){
             this.name = name;
             }
         sayName(){
             console.log(this.name)
             }
         }
     const lihua = new Person('李华')
     lihua.sayName() // 李华
  • static定义的属性和方法只能class自己使用,实例无法使用
  • extend继承
     class Animal{
         constructor(name,age){
             this.name = name;
             this.age = age;
             }
         }
     class Cat extends Animal{
         say(){
             console.log(this.name,this.age);
             }
         }
     const cat = new Cat('大橘',5)
     cat.say() // 大橘 5

新容器语法

Set

  • Set对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值

  • Set本身是一个构造函数,用来生成Set数据结构。Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

  • Set实例对象的属性

    • size:返回Set实例的成员总数
        var q = new Set([1,2,3,4,5])
        console.log(q.size) // 5
    
  • Set实例对象的方法

    1. add:添加某个值,返回 Set 结构本身(可以链式调用)
        //不传数组
        const set1 = new Set();
        set1.add(1);
        set1.add(2);
        console.log(set1) // Set(2) 展开 {1,2}
        //传数组
        const set2 = new Set([1,2,3]);
        set2.add(4);
        set2.add('qwe');
        console.log(set2) //Set(5) 展开 {1,2,3,4,'qwe'}
    
    1. delete:删除某个值,删除成功返回true,否则返回false
        set2.delete(2);
        console.log(set2)//Set(4) 展开 {1,3,4,'qwe'}
    
    1. has:返回一个布尔值,表示该值是否为Set的成员
        console.log(set2.has(3) //true
    
    1. clear:清除所有成员,没有返回值
        set2.clear()
        console.log(set2) //Set(0)
    
  • 利用Set的不重复性,实现去重

        const arr1 = [1,1,2,2,3,3];
        const arr2 = [...new Set(arr1)] //[1,2,3]
    

Map

  • 对比Object最大的好处就是key不受类型限制

ES7

  • includes:传入元素,如果数组中能找到,则返回true,否则返回false
    const arr = [1,2,NaN];
    console.log(arr.indexOf(NaN) // -1 找不到NaN
    console.log(arr.includes(NaN) //true

ES8

  • Object.values:获取对象的value集合
    const obj = {name:'lihua',age:22}
    const value = Object.values(obj) // ['lihua',22]
  • Object.entries:获取对象的键值对集合
    const values2 = Object.entries(obj) // [['name','lihua'],['age',22]]

AJAX

  • 是一种用于创建快速动态网页的技术
  • 可以使网页实现异步更新,无需加载整个网页,对某部分进行更新
  • ajax请求与一般HTTP请求的区别
    • ajax请求是一种特别的http请求
    • 对服务端来说,没有区别,区别在浏览器端
    • 只有xhr和fetch发出的才是ajax请求
    • 浏览器接收请求
      • 一般请求:浏览器一般会直接显示响应体数据,也就是刷新/跳转页面
      • ajax请求:浏览器不会对界面进行任何更新操作,只是调用监视的回调函数并传入响应相关数据
  • 优点
    • 页面无需刷新
    • 异步通信,更快的响应能力
  • 缺点
    • 不能后退
    • 不能用url访问
    • 存在安全问题
    • 破坏程序的异常机制
  • 封装一个简易的ajax异步请求函数
    function ajax(url){
        return new Promise((resolve,reject)=>{
            const xhr = new XMLHttpRequest()//创建一个xhr对象
            xhr.open('GET',url,true) //初始化异步请求
            xhr.send(null)//发送请求
            //处理响应
            xhr.onReadystatechange = function(){
                if(xhr.readystate !==4){ //响应值不为4,直接结束
                    return
                    )
                 if(xhr.status>=200&&xhr.status<300){
                     resolve(JSON.parse(xhr.responseText))
                     }else{
                     reject(new Error('request error status'+request.status))
                     }
                  }
               })
            }

AJAX面试题

  1. 什么是ajax?作用是什么?
    • AJAX = 异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器 进行少量数据交换,AJAX可以使网页实现异步更新
  2. 为什么要用ajax?
    • AJAX应用程序的优势在于
      1. 通过异步模式,提升了用户体验
      2. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用
      3. AJAX引擎在客户端运行,承担一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载
  3. ajax的特点
    • AJAX可以实现动态不刷新(局部刷新)
      • 就是在不更新整个页面的前提下维护数据。这使得web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变过的信息
  4. ajax技术体系的组成部分有哪些?
    • HTML
    • CSS
    • DOM
    • XML
    • XMPHttpRequest
    • JavaScript
  5. 请介绍一下XMLHttpRequest对象 AJAX的核心是JavaScript对象XMLHttpRequest。它是一种支持异步请求的技术。简而言之,XMLHttpRequest使您可以使用JavaScript向服务器提出请求并处理响应,而不阻塞用户。通过XMLHttpRequest对象,开发人员可以在页面加载以后进行页面的局部刷新
  6. ajax有几种请求方式?优缺点?
  • 常用的有
    • post
    • get
    • delete
    • put
  • 代码上的区别
    1. get通过url传递参数
    2. post设置请求头规定请求数据类型
  • 使用上的区别
    1. post比get安全,因为post参数在请求体中,get参数在url上面
    2. get传输速度比post快,根据传参决定,因为post通过请求体传参,后台通过数据流接收,速度稍微慢一点。而get通过url传参可以直接获取
    3. post传输文件理论上没有限制,get传输文件大概7-8k,ie是4k左右
    4. get获取数据,post上传数据,上传的数据比较多,而且上传数据都是重要数据。所以不论安全性还是数据量级post是最好的选择
  1. 什么情况造成跨域?
    • 同源策略限制,不同源会造成跨域
    • 协议,域名和端口号,只要有一个不相同就是非同源
  2. 跨域解决方案
    1. JsonP
      • 原理:动态创建一个script标签。利用script标签的src属性不受同源策略限制。因为src和href属性都不受同源策略限制
    2. CORS跨域资源共享
      • 原理:服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器将会允许跨域请求
    3. nginx反向代理(项目上线时)
    4. webpack配置代理服务器
      • 在vue.config.js中配置devServer
  3. http创建的状态码有哪些?
    • 2开头(表示成功处理请求的状态码)
      • 200 服务器以成功处理了请求
    • 3开头(表示要完成请求,需要进一步操作,通常用来重定向)
      • 304 (未修改)自从上次请求后,请求的网页未修改过,服务器返回此响应时不会返回网页内容
    • 4开头(表示请求可能出错,妨碍了服务器的处理)
      • 400 (错误请求)服务器不理解请求的语法
      • 403 (禁止)服务器拒绝请求
      • 404 (未找到)服务器找不到请求的网页
    • 5开头(表示服务器错误)

VUE组件间通信

  1. props

    • 父组件中,给子组件的标签上添加标签属性,子组件声明props进行接收
    // 父组件
    <template>
      <div class="section">
        <person :persons="personList"></person>
      </div>
    </template>
    
    <script>
    import person from './test/person.vue'
    export default {
      name: 'HelloWorld',
      components: { person },
      data() {
        return {
          personList: ['小明', '小华', '小红']
        }
      }
    }
    </script>
    
    
    
    // 子组件 
    <template> 
        <div> 
            <span v-for="(item, index) in persons" :key="index">{{item}}</span>
        </div> 
    </template> 
    <script> export default { props: ['persons'] } </script>
    
  2. $emit

    • $emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接收参数
    • 参数1:自定义事件
    • 参数2:传递到参数
  3. provide/inject

    • 祖孙组件之间通信
    • 祖先组件在配置对象中,创建属性provide,给他提供一个对象,该对象内的属性可以被后代组建获取到
    • 后代组件在配置对象中,创建属性inject,写法与props数组版本相同,只需声明自己想获取到属性名即可
  4. $children/$parent

    • $children -> 用于获取子组件组成的数组
    • $parent -> 用于获取父组件实例对象
  5. $refs

    • 给标签添加,找到的是真实DOM
    • 给组件添加,找到组件实例对象
  6. $attrs/$listeners

    • $attr:用于接受当前组件没有接收的标签属性
    • $listeners:用于接收当前组件上所有的自定义事件
  7. 事件总线(eventBus)

    • 通过注册一个新的vue实例,通过调用这个实例的emitemit和on来监听和触发这个实例的事件,通过传入参数实现全局通信
    • 它是一个不具备DOM的组件,有的仅仅只是它的实例方法而已,因此非常轻便
  8. Vuex

    • vue的一个扩展包,用于集中管理多组件之间的共享数据,进行多组件间的通信
    • 核心概念
      • store:用于管理state,mutation,action,getter等数据,并向外提供一些方法的对象
      • state:用于存储共享数据
      • mutation:用于直接修改store中state数据,一般将同步代码写入mutation
      • action:用于间接修改state数据,一般将异步代码写入action
      • getter:相当于vue的computed
      • dispatch:用于分发action
      • modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

VUE中computed与methods和watch的区别

  • computed
    1. 支持缓存,多次读取,只执行一次计算,只有依赖的数据发生改变,才会重新计算
    2. 不支持异步,computed中有异步操作时无效,无法监听数据的变化
    3. computed默认走缓冲,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
    4. 如果一个属性是其他计算而来的,这个属性依赖其他属性,一般用computed
    5. 如果computed属性属性值是函数,默认走get方法,函数的返回值就是属性的属性值
  • methods
    • 没有缓存,多次读取,多次调用
  • watch
    1. 支持异步
    2. 监听的函数接收2个参数,一个数新值,一个是旧值
    3. 监听数据必须是响应式数据

VUE的优点

  1. 声明式,响应式的数据绑定
  2. 组件化的开发,写组件的目的在于可复用或可组合
  3. 虚拟DOM

MVVM和MVC

  • MVVM
    • M:model数据层:保存数据
    • V:view视图层:展示页面
    • VM:ViewModel
      • 可以通过vm将model层数据展示在view层
      • 通过vm可以将view层用户输入的数据,来更新model层数据
  • MVC
    • M:model数据层:保存数据
    • V:view视图层:展示页面
    • C:controller层:负责将model数据流向view层

VUE的生命周期

  • 初始化阶段
    • beforCreate:
      • 创建之前初始化生命周期或自定义事件,注入数据代理和数据劫持。这个阶段vue实例刚刚在内存中创建,此时data和methods都还没初始化好
    • created:
      • 这个阶段vue实例在内存中已经创建好了,data和methods也能获取到了,但是模版还没编译。此时可以发送请求,发送时机更早,数据回来更快。注意不要做复杂耗时到操作,否则由于js引擎解析的特点,会导致页面的解析和渲染延迟
    • beforeMount:
      • 这个阶段完成了模版的编译,但还没挂载到页面上,挂载之前准备挂载该组件,并且将template字符串转换成render函数,通过render函数生成虚拟DOM,再通过虚拟DOM生成真实DOM
    • mounted:
      • 这个阶段,模版编译好了,也挂载到页面中了,页面显示。可以发请求,首屏展示更快,先展示初始页面,再发请求。可以操作真实DOM,比如创建swiper实例,实现轮播图。可以绑定事件总线,绑定自定义事件
  • 更新阶段
    • beforeUpdate:
      • 更新之前执行此函数,此时data中数据的状态值是最新的,但页面上显示的数据还是原始的,还没有重新开始渲染DOM树
    • updated:
      • 更新之后执行此函数,此时data中数据的状态值是最新的,而且页面上显示的数据也是最新的,DOM节点已经被重新渲染了
  • 卸载阶段
    • beforeDestroy
      • vue实例被销毁之前,这个阶段vue实例还能用。需解绑事件监听:通过vue绑定的不用管,自己通过原生DOM绑定的事件回调函数需解绑
    • destroyed
      • vue实例销毁后调用,此时所有实例指示的所有东西都会解绑,事件监听器也移除,子实例也被销毁
  • 配合keep-alive进行组件缓存的生命周期
    • activated激活
    • deactivated失活
  • 捕获后代组件错误
    • errorCaptured
  • 处理ssr的函数
    • serverPrefetch

VUE响应式原理

  1. 数据代理
    • 将data数据代理到实例对象上,遍历所有data数据,通过OBject.defineProperty()方法将data中的数据定义在实例对象上
    • 内部通过get定义属性读取的方法,实际读取的是原数据data
    • 通过set定义属性设置的方法,实际设置的是原数据data
  2. 数据劫持
    • 遍历所有data数据,进行重新定义,将其定义成响应式
    • 通过Object.defineProperty()方法,重新定义get和set
    • 此时会通过闭包保存一个dep对象
    • get将来通过dep就能建立dep和watcher的联系
    • set将来通过dep就能通知所有watcher去更新用户界面
  3. 模版解析
    • 会创建watch实例,出发get方法收集所有dep和watcher之间的关系
  4. 将元素节点转换成文档碎片节点
  5. 编译模版

双向数据绑定原理

  • 通过v-model实现
  • v-model会给元素绑定value属性和绑定input事件,当用户输入数据,会触发input事件,在input事件中更新data数据

VUE的核心点

  1. 数据驱动:即viewModel,指视图是由数据驱动生成的,我们对视图的修改,不会直接操作DOM,而是通过修改数据,保证了数据和视图的一致性
  2. 组件系统:就是为了解决页面布局等问题,而vue中组件分为两种,全局和局部,提供了强大的页面布局功能

vue的修饰符(未施工)

diff算法

  • 页面初次渲染不会进行diff算法,diff算法只存在于数据更新后,为了优化性能
  • 在数据更新后会生成新的虚拟DOM,于旧的虚拟DOM比较
  • 只会同级比较
  • 比较标签名
    • 新前vs旧前
    • 新后vs旧前
    • 新后vs旧后
    • 新前vs旧后
    • 最后遍历整个列表查找key值
  • 补充:
    • v-for遍历的列表数据更新会根据key值决定是否重新渲染

nextTick

  • 是为了开发者在操作数据后,立即需要操作DOM,提供的API
  • nextTick在数据更新的时候会开启一个队列,缓冲同一事件中对所有数据的变更,我们定义的回调函数也会推入这个队列
  • 在确保DOM操作完成后执行回调

后台权限管理

  • 如何知道账号的权限
    • 用户登录之后通过token获取用户的详细信息
  • 如何控制路由跳转
    1. 项目启动时,只注册常量路由
    2. 获取用户信息之后,对应的权限信息更新到vuex中
    3. 生成当前账号专用的路由数组
    4. 将最新的路由数组动态注册到项目的VueRouter中

深浅拷贝

  • 浅拷贝:仅复制了引用,彼此之间的操作会互相影响
  • 深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响
  • 实现深拷贝
    1. JSON.stringify()和JSON.parse()配合使用
    2. 利用递归,对属性中所有引用类型的值进行遍历,直到是基本类型值为止

小程序的生命周期

  1. 进入页面
    1. onload:页面加载,相当于vue的created
    2. onshow:页面显示,相当于vue的activated
    3. onReady:渲染结束
  2. 离开页面
    1. onHide:页面隐藏,相当于vue的deactivated
    2. onUnload:页面卸载,相当于vue的beforeDestroy 注意:
  • 离开页面时,如果使用wx.navigateTo跳转
    1. 保留当前页,从而触发onHide,不会出发onUnload
    2. 点击回退,可以重新展示上一个
  • 如果使用wx.redirectTo跳转
    1. 关闭当前页,触发onUnload,不会触发onHide
    2. 这种方法离开没有回退按钮,所以只能重新进入该页面,所有生命周期都重新触发

小程序的事件绑定

  1. 冒泡事件
    bind+'事件名' = '回调函数'
  1. 非冒泡事件
    catch+'事件名' = '回调函数'
  1. 捕获事件
    capture-bind+':'+'事件名' = '回调函数'
  1. 非捕获事件
    capture-catch+':'+'事件名' = '回调函数'

小程序的路由跳转

  1. 声明式导航:通过navigate组件实现声明式导航
  2. 编程式导航:
    1. wx.navigateTo({}) (类似vue中的push)
      • 保留当前页
      • url必传
      • 会保留上一页
      • 注意:小程序页面栈最多10层,旧版本5层,超出上限无法跳转
    2. wx.redirectTp({}) (类似vue中的replace)
      • 关闭当前页(销毁当前页面的实例对象)
      • url必传
      • 销毁上一页,如果想要展示需要重新挂载
      • 注意:由于会销毁上一页,所以无需考虑页面栈的问题
    3. wx.switchTab({})
      • 该API专门用于跳转tabBar页面

路由

  • 前端路由
    • 优点:访问新页面近变换一下路径,没有网络延迟,用户体验好
    • 缺点:使用浏览器的前进,后退会重新发送请求,没有合理利用缓存,不利于SEO
    • 2种实现方案
      1. hash
        • 优点:兼容性好,后端不需要做任何的配置
        • 缺点:地址栏有#,不能使用锚点功能
        • 原理:
          1. 控制地址栏变化
            • 通过window.location.hash="/about"操作浏览器的地址栏,并且页面不会重新请求
          2. 监听地址栏变化
            • 通过window身上的hashchange事件监听变化
      2. history
        • 优点:不会影响锚点功能
        • 缺点:兼容性较差,项目上线时,后端需要进行专门的配置
        • 原理:
          1. 通过window.history.pushState({},"","/about"),可以操作浏览器的地址栏,并且保证页面不会重新请求
          2. 监视地址栏变化
          3. 通过window身上的popstate事件,可以监视地址栏变化,但是只能监视浏览器的前进后退

webpack

  • 只认识JS和JSON
  • 核心概念
    1. 入口entry:入口文件地址
    2. output:一个对象,包含编译之后的文件名和保存地址
    3. loader:帮助webpack编译他不认识的文件或代码(模块转换器)
    4. plugin:扩展插件
    5. mode:告知webpack使用相应模式的内展优化
      • development 默认
      • production 开启树摇
  • 树摇:不打包没有用到的代码
    • 开启Terserplugin插件:将mode设置为production
  • 多进程打包(threda-loader)
    • rules中哪个rule对象需要多进程打包,就在他loader最前面加上thread-loader
  • 代码分割
Optimization:{
    splitChunks:{
        chunks:"all",//分割类型
        minSize:1 //分割体积
        }
     }