K-面试

248 阅读27分钟

Html、CSS

重绘repaint和重排reflow:

  • 重绘:元素外观改变(颜色,背景色),但尺寸、定位不变,不会影响其他元素

  • 重排:重新计算尺寸和布局,可能会影响其他元素位置。

    减少重排方法:

    1.集中修改样式,或者直接切换css class、修改前先设置display:none,脱离文档流、使用BFC特性

    2.频繁触发使用节流和防抖、批量操作DOM

    3.使用C3等动画

px、%、em、rem、vw/vh

  • px:基本单位,绝对单位
  • %:相对于父元素的百分比
  • em:相对于当前元素的font-size
  • rem:相对于根节点的font-size.(响应式文字匹配)
  • vw/vh:屏幕宽/高的1%

BFC(块级格式化上下文):

flex(弹性布局)布局

水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。

  • flex-direction:决定主轴的方向

    • row(默认值):主轴为水平方向,起点在左端。
    • row-reverse:主轴为水平方向,起点在右端。
    • column:主轴为垂直方向,起点在上沿。
    • column-reverse:主轴为垂直方向,起点在下沿
  • flex-wrap:换行

    • row(默认值):主轴为水平方向,起点在左端。
    • row-reverse:主轴为水平方向,起点在右端。
    • column:主轴为垂直方向,起点在上沿。
    • column-reverse:主轴为垂直方向,起点在下沿
  • flex-direction:决定主轴的方向

    • nowrap(默认):不换行。
    • wrap:换行,第一行在上方。
    • wrap-reverse:换行,第一行在下方。
  • justify-content:对齐方式

    • flex-start(默认值):左对齐
    • flex-end:右对齐
    • center: 居中
    • space-between:两端对齐,项目之间的间隔都相等。
    • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
  • align-items:定义项目在交叉轴上如何对齐

    • flex-start:交叉轴的起点对齐。
    • flex-end:交叉轴的终点对齐。
    • center:交叉轴的中点对齐。
    • baseline: 项目的第一行文字的基线对齐。
    • stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
  • align-content:定义了多根轴线的对齐方式。

    • flex-start:与交叉轴的起点对齐。
    • flex-end:与交叉轴的终点对齐。
    • center:与交叉轴的中点对齐。
    • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
    • space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
    • stretch(默认值):轴线占满整个交叉轴。

http

Http和 UDP协议:

  • http协议在应用层
  • TCP协议再传输层、有连接,有断开,稳定传输
  • UDP:## 协议再传输层无连接,无断开,不稳定传输,但效率高

TCP三次握手和四次挥手

网络连接是TCP协议,传输内容是HTTP协议

  • 三次握手(建立连接):

    1:客户端(Client)发包,服务端(Server)接收

    2:服务端发包,客户端接收

    3:客户端发包,服务端接收

  • 四次挥手(断开连接):

    1:客户端发包通知,服务端接收

    2:服务端发包回应,客户端接收(通知断连信号)

    3:客户端发包断开,服务端接收

    4.服务端发包断开,客户端接收(断连)

浏览器输入URL后发生了什么

1、浏览器输入URL;

2、查找缓存:浏览器缓存 - 系统缓存 - 路由缓存有显示页面内容;

3、DNS域名解析:浏览器向DNS服务器发起请求,解析URL中的域名对应IP地址;

4、建立TCP连接:解析出IP后,根据IP地址和端口(80 、 443),和服务器建立TCP连接;

5、发起HTTP请求:浏览器发起读取文件的HTTP请求,请求作为三次握手的第三次数据发送到服务器;

6、服务器响应,并把对应html文件发送到浏览器;

7、关闭TCP连接,通过四次挥手释放TCP连接;

8、浏览器收到数据包之后渲染,构建DOM树 - 生成css规则树 - 构建render树(结合DOM和CSS) - 布局(计算节点位置) - 绘制节点;

9、JS引擎解析过程:调用JS引擎执行JS代码,创建window对象 - 加载文件 - 预编译 - 解释执行。

secipt中defer和async

  • 无:html暂停解析,下载JS,执行JS,再解析

  • defer:html继续解析,并行下载JS,html解析完毕执行JS

  • async:html继续解析。并行下载JS,执行JS,再解析html

HTTPS中间人攻击?如何预防?

  • https加密方式:客户端:公钥+内容生成随机key,服务端:私钥解密随机key
  • 中间人攻击:自己的公私和私钥去解随机key
  • 预防:官方正规第三方证书:浏览器通过证书合法验证,会提示

http跨域:浏览器同源策略

解决跨域:

  • JOSNP:script不受同源策略影响

  • CORS:服务端配置允许跨域

    为什么发送options请求:

    1.options请求是跨域请求之前的预检查

    2.浏览器自行发起的,无需干预

    3.不影响实际功能

前端攻击:

  • XSS:Cross Site Script跨站脚本攻击 将JS代码插入到网页内容中,渲染时实行JS代码,(特殊字符替换)

  • CSRF:Cross Site Request Forgery跨站请求伪造 诱导用户去访问另一个网站接口,伪造请求;

    用户登录了A网站,有了cookie,诱导用户到B网站,并发去A网站的请求,A网站API发现有cookie,认为是用户自己操作;

    解决:严格的跨域限制 + 验证码机制

  • Click Jacking:点击劫持 诱导界面上蒙一个透明的iframe,诱导用户点击

  • SQL注入

    提交内容时写入SQL语句,破坏数据库 处理输入的内容,替换特殊字符(处理掉SQL语句的单引号等)

token和cookie

  • token:需自定义传递、需自己存储、没有跨域限制 前端发起登录,返回token,前端存储token,以后每次访问接口时,都会带着token作为用户信息

  • cookie:配合session使用 http是无状态,每次请求都要带cookie,以便识别身份、服务端也可向客户端set-cookie,大小限制4kb、cookie不跨域共享

JS:一切皆关联数组

JS变量:

1.函数内var出来的

2.函数的形参变量

注:既没有var,也没有形参,就不是局部变量

var a=10;
function fun(){
    var a=100;
    a++;
    console.log(a);   //101
}
fun(); 
console.log(a);  //10
------------------------------------------
var a=100;
function fun(){
    a=100;
    a++;
    console.log(a);   //101
}
fun(); 
console.log(a);  //101
------------------------------------------
var a=10;
function fun(a){
    a++;
    console.log(a);   //11
}
fun(a); 
console.log(a);  //10

forEach、map、filter

  • forEach:循环数组中每一个元素并采取操作, 没有返回值, 可以不用知道数组长度

    1.没有返回值
    2.无法中断执行
    3.跳过空位
    4.对原数组进行修改

  • map:遍历数组每个元素,并回调操作,需要返回值,返回值组成新的数组,原数组不变

    1.有返回值
    2.无法中断执行
    3.跳过空位
    4.对原数组进行修改

  • filter:过滤通过条件的元素组成一个新数组, 原数组不变

    1.不会改变原数组,它返回过滤后的新数组。

  • some函数,遍历数组中是否有符合条件的元素,返回Boolean值

  • every函数, 遍历数组中是否每个元素都符合条件, 返回Boolean值

  • for...in/of | | for...in | for...of| | --- | --- |--- | |取值 | 得到index| 得到value | | 遍历对象 | 可以 | 不可以 | | 遍历Map/Set | 不可以 | 可以 | | 遍历generator | 不可以 | 可以 | | | 对象、数组、字符串 | 数组、字符串、Map、Set |

事件冒泡和事件捕获

  • 事件冒泡

    从子元素向上触发:li -> ul -> div -> body -> html -> document

    阻止冒泡:

    1.给子级加 event.stopPropagation( );

    2.在事件处理函数中返回 false;

    3.event.target==event.currentTarget,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;

  • 事件捕获: 事件会从最外层开始发生,直到最具体的元素:**document -> html -> body -> div -> ul -> li **

注:阻止默认事件

 1.event.preventDefault( )

 2.return false

深拷贝和浅拷贝

  • 浅拷贝:一般指的是把对象的第一层拷贝到一个新对象上去

        var a = { count: 1, deep: { count: 2 } } 
        var b = Object.assign({}, a) 
        // 或者 
        var b = {...a}
    
  • 深拷贝:一般需要借助递归实现,如果对象的值还是个对象,要进一步的深入拷贝,完全替换掉每一个复杂类型的引用。

     var deepCopy = (obj) => {
        var ret = {} 
        for (var key in obj) {
            var value = obj[key] ret[key] = typeof value === 'object' ? deepCopy(value) : value 
        } 
        return ret 
     }
    

原型和原型链

  • 原型:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是 prototype 对象。
  • 原型链:由相互关联的原型组成的链状结构就是原型链。

作用域和作用域链:

作用域和作用域链都是对象!!!

  • 全局作用域:即window对象

  • 函数作用域(AO):其实是js引擎在调用函数时才临时创建的一个作用域对象。其中保存函数的局部变量。而函数调用完,函数作用域对象就释放了。

  • 从当前作用域开始一层层往上找某个变量,如果找到全局作用域还没找到,就放弃寻找 。这种层级关系就是作用域链。

函数调用过程:

1.创建函数作用域:function() == new Function();

2.执行函数

3.释放函数、作用域对象、局部变量

闭包:

含义: 既重用变量又保护变量不被污染的一种编程方法,只要希望给一个函数,保存一个即可反复使用,又不会被外界污染的专属局部变量时,就用闭包

概括: 外层函数调用后,外层函数的作用域对象,被返回的内层函数的作用域链引用着,无法释放,就形成了闭包

如何使用:

1.用外层函数包裹要保护的变量和使用变量的内层函数;

2.在外层函数内部返回内层函数对象;

3.调用外层函数,用变量接住返回的内层函数对象

//第1步: 用外层函数包裹要保护的变量和内层函数
function mother(){
    var total = 1000;
    //第2步: 返回内层函数对象
    return function pay(money){
        total-=money
        console.log(`花了${money}还剩${total}元`)
    }
}
//第3步: 调用外层函数,用变量接住内层函数对象
var pay=mother();
//pay接住的就是mother()返回出来的内层函数对象
pay(100);//应该剩900
total=0;//别人程序执行了代码
pay(100);//应该剩800

强调: 因为内层函数只是定义,未加()调用。所以内层函数代码不执行

缺点: 内存泄漏:及时释放不用的闭包:将保存的内层函数对象的变量赋值为null

注:外层函数返回内层函数的方法:

1.return;

2.强行赋值为全局变量;

3.将函数包裹在对象或者数组中;

例:

function fun(){
    var i=999;
    //给从未声明过的变量赋值,自动在全局创建该变量
    nAdd=function(){i++};//1
    return function(){//2
        console.log(i)
    }
}
var getN=fun();
getN();//999
nAdd();//1000
getN();//999
------------------------------------------
function mother(){
    var i=0;
    return function(){//2
        i++;
        console.log(i)
    }
}
var get1=mother();//老大
get1();//老大 == 1
var get2=mother();//妈妈又生了老二
get2();////老二 == 1
get1();//老大的红包+1 == 2
get2();//老二的红包+1 == 2
------------------------------------------
function fun(){
    //arr是给未声明的变量强行赋值,自动在全局创建该变量
    arr=[];
    for(var i=0;i<3;i++){
        arr[i]=function(){//反复创建了3个孩子
            console.log(i);//因为只是创建函数,所以这里暂时不执行。
        }
    }
    //for结束后,i=3 
}
   //妈妈一次刨妇产生了3个孩子,包了一个共用的红包,红包里是3块钱
arr[0]();//3
arr[1]();//3
arr[2]();//3
------------------------------------------
function mother(){
    var sum = 0;
    function f1(){
        sum++;
        return f1;
    }
    //valueOf:转数字
    f1.valueOf = function(){
        return sum;
    }
    f1.toString = function(){
        return sum + '';
    }
    return f1
}
// + : 隐式转换数字:走valueOf
console.log(+mother()) ------ 0
console.log(+mother()()) ------ 1
console.log(+mother()()()) ------ 2

new关键字

1、创建新对象

2、自动让新创建的子对象继承构造函数的原型对象,自动设置子对象的__proto__属性,指向构造函数的原型对象。

3、调用构造函数,将构造函数中的this指向new刚创建的新对象,在构造函数内通过强行赋值方式,为新对象添加规定的属性和方法

4、返回新对象

this常见两种情况

  1. this总是指向函数的直接调用着;

  2. new Fun()中Fun中的this指向new创建的新对象;

  3. this指当前对象

替换this指向

call

  1. 立刻调用一次点前的函数

  2. 自动将当前的函数中的this替换为指定的新对象

  3. 还能向要调用的函数中传实参值

apply

  1. 调用当前的函数

  2. 替换当前的函数中的this为指定对象

  3. 先拆散数组为多个元素值,再分别传给函数的形参变量

bind

  1. 创建一模一样的新函数副本

  2. 永久替换this为指定对象

  3. 永久替换部分形参变量为固定的实参值!

注:被bind()永久绑定的this,即使用call,也无法再替换为其它对象了。——箭头函数的底层原理

区别

  1. 只在一次调用函数时,临时替换一次this: call

  2. 既要替换一次this,又要拆散数组再传参: apply

  3. 创建新函数副本,并永久绑定this: bind

防抖和节流:

  • 节流:限制执行频率,有节奏的执行(拖拽触发事件)

      function debounce(fn , delay = 200) {
          let timer = 0;
          return function(){
              if(timer) return
              timer = setTimeout(() => {
                  fn.apply(this , arguments)
                  timer = 0
              },delay)
      }
    
  • 防抖:限制执行次数,多次密集的触发只执行一次(input输入,时间间隔)

      function debounce(fn , delay = 200) {
          let timer = 0;
          return function(){
              if(timer) clearTimeout(timer)
              timer = setTimeout(() => {
                  fn.apply(this , arguments)
                  timer = 0
              },delay)
      }
    

Promise:异步微任务

  • 参数:resolve、reject

  • 状态:padding、resolved、rejected

  • 方法:then、catch、finally

  • promise.all():接受一组异步任务,并且在所有异步操作完成执行回调

  • promise.race():接收一组异步任务,只保留第一个执行完成的异步操作的结果,其他方法仍在执行,但结果会被抛弃

  • async中,如果await后跟一个异常或者错误,终止进程。若想不影响async后函数,使用try catch

JS创建对象

  1. new Object() 缺点: 步骤多

  2. 字面量: var 对象名={} 缺点: 如果反复创建多个对象,代码会很冗余

  3. 工厂函数方式:本质还是Object(),将来无法根据对象的原型对象准确判断对象的类型

     function createPerson(name,age) {
         var o = new Object();
         o.name = name;
         o.age = age;
         o.say = function(){ alert(this.name); }
         return o;
     }
     var p1 = createPerson(“lilei",11);
    
  4. 构造函数方式:重复创建,浪费内存

     function Student(sname, sage){
         this.sname=sname;
         this.sage=sage;
         this.intr=function(){ … }
     }
     var lilei=new Student(“Li Lei”,11)
     
    
  5. 原型对象方式:先创建完全相同的对象,再给子对象添加个性化属性。(步骤繁琐)

     function Person() {
    
     }
     Person.prototype.name = “主人很懒";
     Person.prototype.age =11;
     Person.prototype.say = function(){
         console.log(this.name);
     };
     var p1 = new Person(); //创建一个实例p1
     p1.name=“Li Lei” //禁止修改共有属性,而是自动太你家自由属性
     var p2 = new Person(); //创建实例p2
     p2.name = “Han Meimei”; //同上
     console.log(lilei);
     console.log(hmm);
    

6.混合模式:先创建完全相同的对象,再给子对象添加个性化属性。

    function Person(name, age) {
        this.name=name;
        this.age=age;
    }
    Person.prototype.say = function(){
        console.log(this.name);
    };
    var p1 = new Person(“Li Lei”,11);
    var p2 = new Person(“Han Meimei”,12);
    console.log(lilei);
    console.log(hmm);

7.动态混合:先创建完全相同的对象,再给子对象添加个性化属性

    function Person(name, age) {
        this.name=name;
        this.age=age;
        if(Person.prototype.say===“undefined”){
            Person.prototype.say = function(){
                console.log(this.name);
            };
        }
    }
    var p1 = new Person(“Li Lei”,11);
    var p2 = new Person(“Han Meimei”,12);
    console.log(lilei);
    console.log(hmm)

8.寄生构造函数:构造函数里调用其他的构造函数。可读性差

    function Person(name, age) {
        this.name=name;
        this.age=age;
        if(Person.prototype.say===“undefined”){
            Person.prototype.say = function(){
                console.log(this.name);
            };
        }
    }

9.class 1.和使用旧的构造函数完全一样:

2.var 对象名=new class名(属性值,...);

10.闭包

   function Person(name, age) {
        var p={};
        p.getName=function(){ return name };
        p.setName=function(value){ name=value };
        p.getAge=function(){ return age }
        return p;
    }
    var p1 = Student(“Li Lei”,11);
    var p2 = Student(“Han Meimei”,12);
    console.log(p1.getName(), p1.getAge());
    console.log(lilei);
    console.log(hmm);

JS继承

  1. 原型链式继承: 将父类的实例作为子类的原型

    // 定义一个父类型
     function Animal (name) {
         this.name = name;
         this.say = function(){ console.log(I’m this.name); }
     }
     // 原型对象方法
     Animal.prototype.eat = function(food) {
         console.log(this.name + '正在吃:' + food);
     };
     function Cat(){ }
     Cat.prototype = new Animal();
     Cat.prototype.name = 'cat’;
     var cat = new Cat();
     // 缺点: 创建子类实例时,无法向父类构造函数传参
    
  2. 构造函数继承:

    // 定义一个父类型
     function Animal (name) {
         this.name = name;
         this.say = function(){ console.log(I’m this.name); }
     }
     // 原型对象方法
     Animal.prototype.eat = function(food) {
         console.log(this.name + '吃:' + food);
     };
     function Cat(name,age){
         Animal.call(this,name);
         this.age = age;
     }
     var cat = new Cat()
    
  3. 实例继承

    // 定义一个父类型
     function Animal (name) {
         this.name = name;
         this.say = function(){ console.log(I’m this.name); }
     }
     // 原型对象方法
     Animal.prototype.eat = function(food) {
         console.log(this.name + '吃:' + food);
     };
     function Cat(name,age){
         var o = new Animal(name); //先创建子类型实例
         o.age = age;
         return o;
     }
     var cat = new Cat();
    
  4. 拷贝继承

    // 定义一个父类型
     function Animal (name) {
         this.name = name;
         this.say = function(){ console.log(I’m this.name); }
     }
     // 原型对象方法
     Animal.prototype.eat = function(food) {
         console.log(this.name + '吃:' + food);
     };
     function Cat(name,age){
         var animal = new Animal(name);
         for(var p in animal){
             Cat.prototype[p] = animal[p];
         }
         this.age = age
     }
     var cat = new Cat();
    
  5. 组合继承

    // 定义一个父类型
     function Animal (name) {
         this.name = name;
         this.say = function(){ console.log(I’m this.name); }
     }
     // 原型对象方法
     Animal.prototype.eat = function(food) {
         console.log(this.name + '吃:' + food);
     };
     function Cat(name,age){
         Animal.call(this,name);
         this.age = age
     }
     Cat.prototype = new Animal();
     Cat.prototype.constructor = Cat;
     var cat = new Cat();
    
  6. 寄生组合继承

    // 定义一个父类型
     function Animal (name) {
         this.name = name;
         this.say = function(){ console.log(I’m this.name); }
     }
     // 原型对象方法
     Animal.prototype.eat = function(food) {
         console.log(this.name + '吃:' + food);
     };
     function Cat(name,age){ Animal.call(this,name); this.age = age }
         (function(){ // 创建一个没有实例方法的类
         var Super = function(){};
         Super.prototype = Animal.prototype; //将实例作为子类的原型
         Cat.prototype = new Super();
     })();
     var cat = new Cat()
    
  7. ES6 class extends继承

      Class 父类型{
         constructor(){
         
         }
         …
     }
     Class 子类型 extends 父类型{
         constructor(){
             super();
         }
     }
    

JS深拷贝

  1. JSON.stringify()以及JSON.parse(),无法深克隆undefined值和内嵌函数

     var obj1 = { x: 1, y: 2, z: 3 }
     var str = JSON.stringify(obj1);
     var obj2 = JSON.parse(str);
    
  2. Object.assign(target, source)

     var obj1 = {
         x: 1,
         y: 2,
         z: 3
     }
     var obj2 = Object.assign({}, obj1);
    
  3. 自定义递归克隆函数:

     function deepClone(target) {
         let newObj; // 定义一个变量,准备接新副本对象
         // 如果当前需要深拷贝的是一个引用类型对象
         if (typeof target === 'object') {
             if (Array.isArray(target)) {// 如果是一个数组
                 newObj = []; // 将newObj赋值为一个数组,并遍历
                 for (let i in target) { // 递归克隆数组中的每一项
                     newObj.push(deepClone(target[i]))
                 }
             // 判断如果当前的值是null;直接赋值为null
             } else if(target===null) {
                 newObj = null;
              // 判断如果当前的值是一个正则表达式对象,直接赋值
             } else if(target.constructor===RegExp){
                 newObj = target;
                 // 否则是普通对象,直接for in循环递归遍历复制对象中每个属性值
             } else {
                 newObj = {};
                 for (let i in target) {
                     newObj[i] = deepClone(target[i]);
                 }
             }
         // 如果不是对象而是原始数据类型,那么直接赋值
         } else { newObj = target; }
              return newObj;// 返回最终结果
         }
     }
    

ES6

模板字符串

     string = str1 + '合并' + str2;
     //替换为
     string = ` ${str1} 合并 ${str2}`

var let count

标题变量提升重复定义是否修改支持块级作用域属于全局作用域
var
let
count

判断字符串是否包含

  • indexOf():返回字符串首次出现位置,没有返回-1
  • includes():返回布尔
  • startsWith/endsWith():判断是否以什么为开头/结尾,返回布尔

箭头函数

  • 优点:代码简洁,省去function
  • 缺点:没有原型prototype,所以没有this。this指向外层函数或者window

is判断两个值是否相同

     Object.is(val1 , val2)

Object.assign()复制对象

     let obj = {};
     Object.assign( obj , { name : 'Ysl'} );
     console.log(obj)

解构运算符

     //数组
     let arr = ['💜','💚','🧡'] 
     let [a, b, c] = arr; 
     console.log( a, b, c ); // '💜','💚','🧡'

    //对象
    let obj = { a: '🍎', b: '🍏', c: '🍊' } 
    let { a: a, b: b, c: c } = obj; 
    console.log( a, b, c ); // '🍎', '🍏', '🍊'

展开运算符

    let arr = ['☠️', '👿', '👻']; 
    console.log(...arr) // ☠️ 👿 👻 
    let obj1 = { name:'熊猫' , job:'前端'} 
    let obj2 = { hobby:'掘金', ...obj1 } 
    console.log( ...obj2 ) // { hobby:'掘金', name:'熊猫' , job:'前端'}

Vue

组件通信

1.父传子:props接收属性,emits接收方法

2.子传父:$emit()事件传递

3.不相关组件:event()公共:

event.on(‘XXX’ , this.ysl) //监听
event.emit(‘XXX’ , 'Hello Ysl'//接收

4. $attrs:是props和emits中未定义的属性或方法的合集。(主要用于父子)

注:一层组件向三层组件传属性或方法,在二组件中添加:v-bind="attrs",三层即可通过this.attrs",三层即可通过this.atters获取一层属性或者方法

  1. parent:获取父组件的属性方法。parent:获取父组件的属性方法。 refs获取子组件的属性方法(必须再mounted使用)

v-if和v-for(不能同时使用)

Vue2.0:v-for优先级高于v-if(会渲染整个列表):编译后v-if会变成三元表达式

Vue3.0:v-if高于v-for(直接报错)

Vue声明周期

beforeCreate:通常用于插件开发中执行一些初始化任务

created:组件初始化完毕,可以访问各种数据,获取接口数据等

mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。

beforeUpdate:此时视图层还未更新,可用于获取更新前各种状态

updated:完成视图层的更新,更新后,所有状态已是最新

beforeunmounted:实例被销毁前调用,可用于一些定时器或订阅的取消

unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器

:Vue3.0中Destroy改为Unmounted。添加renderTrackedrenderTriggeredserverPrefetch 用于调试。setup最先执行

computed、watch、methods

  • computed:用于计算产生新的数据,有缓存(数据不变,不计算)
  • watch:监听现有数据
  • methods:无缓存(每次获取重新计算)

v-modal

v-model,是model和callback

  • 生成value和input
  • 生成change和radio
  • 生成change和checked

data是函数

如果data是个对象,对象属于引用类型,当修改其中一个属性时,会影响到Vue实例的数据。

set

  • vue给对象和数组本身都增加了dep属性
  • 当给对象新增不存在的属性的时候,就会触发对象依赖的watcher去更新
  • 当修改数组索引的时候,就调用数组本身的splice方法去更新数组

Vue双向绑定

router

作用:通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。

  • 全局守卫:

    • router.beforeEach:全局前置守卫 进入路由之前
    • router.beforeResolve在beforeRouteEnter调用之后调用
    • router.afterEach:全局后置钩子 进入路由之后
  • 路由独享守卫(beforeEnter): 如果不想全局配置守卫的话,你可以为某些路由单独配置守卫:

        const router = new VueRouter({ 
          routes: [ 
              { 
                  path: '/foo', 
                  component: Foo, 
                  beforeEnter: (to, from, next) => {
                     // ... 
                  } 
              } 
          ] 
        })
    
  • 组件守卫:

    • beforeRouteEnter:进入路由前(不能获取组件实例 this)
    • beforeRouteUpdate:路由复用同一个组件时
    • beforeRouteLeave:离开当前路由时
  • to、form、next

    to、form:将要进入和将要离开的路由对象,路由对象指的是平时通过this.$route获取到的路由对象。 next:Function: 这个参数是个函数,且必须调用,否则不能进入路由(页面空白)。

    • next() 进入该路由。
    • next(false): 取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)。
    • next 跳转新路由,当前的导航被中断,重新开始一个新的导航。
  • 完整导航解析:

    1. 导航被触发。

    2. 在失活的组件里调用 beforeRouteLeave 守卫。

    3. 调用全局的 beforeEach 守卫。

    4. 在重用的组件里调用 beforeRouteUpdate 守卫。

    5. 在路由配置里调用 beforeEnter

    6. 解析异步路由组件。

    7. 在被激活的组件里调用 beforeRouteEnter

    8. 调用全局的 beforeResolve 守卫 (2.5+)。

    9. 导航被确认。

    10. 调用全局的 afterEach 钩子。

    11. 触发 DOM 更新。

    12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

Vue响应式

Vue2.0:数组和对象类型的值变化的时候,通过defineReactive方法,借助defineProperty,将所有的属性添加了gettersetter。用户在取值和设置的时候,可以进行一些操作。

缺点:只能监控最外层的属性,如果是多层的,就要进行全量递归。。

Vue3.0:利用ES6的Proxy机制代理要响应化的数据,它有很多好处,编程体验是一致的,不需要使用特殊api,初始化性能和内存消耗都得到了大幅改善。

Vue权限管理(到按钮级别)

1.页面权限和按钮权限

2.前端实现和后端实现:前端繁杂,后端简介且实时更新

前端:前端方案会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后根据角色过滤出路由表。比如我会配置一个asyncRoutes数组,需要认证的页面在其路由的meta中添加一个roles字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该用户能访问的页面,最后通过router.addRoutes(accessRoutes)方式动态添加路由即可。

后端:后端方案会把所有页面路由信息存在数据库中,用户登录的时候根据其角色查询得到其能访问的所有页面路由信息返回给前端,前端再通过addRoutes动态添加路由信息

按钮权限:将按钮要求角色通过值传给v-permission指令,在指令的moutned钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除按钮。

    // 前端组件名和组件映射表
    const map = {
      //xx: require('@/views/xx.vue').default // 同步的⽅式
      xx: () => import('@/views/xx.vue') // 异步的⽅式
    }
    // 服务端返回的asyncRoutes
    const asyncRoutes = [
      { path: '/xx', component: 'xx',... }
    ]
    // 遍历asyncRoutes,将component替换为map[component]
    function mapComponent(asyncRoutes) {
      asyncRoutes.forEach(route => {
        route.component = map[route.component];
        if(route.children) {
          route.children.map(child => mapComponent(child))
        }
      })
    }
    mapComponent(asyncRoutes)

Vuex

Vuex是专门为vue提供的全局状态管理系统,用于多个组件中的数据共享、数据缓存。

  • state:保存公共数据;

  • mutations :修改公共数据;

  • getters: 类似于computed(计算属性,对现有的状态进行计算得到新的数据);

  • actions: 发起异步请求;

  • modules: 拆分模板,把复杂的场景按模块来拆开

nextTick

作用:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

1.nextTick是Vue提供的一个全局API,由于vue的异步更新策略导致我们对数据的修改不会立刻体现在dom变化上

2.Vue 在更新 DOM 时是异步执行的,数据变化时,Vue会开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,如果同一个 watcher 被多次触发,只会被推入到队列中一次。nextTick方法会在队列中加入一个Promise回调函数,确保该函数在前面的dom操作完成后才调用。

:key

作用:主要是为了更高效的更新虚拟DOM

vue判断两个节点是否相同时主要判断两者的key和元素类型等,如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的dom更新操作,影响性能。

注:应该避免使用数组索引作为key,这可能导致一些隐蔽的bug;vue中在使用相同标签元素过渡切换时,也会使用key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

虚拟DOM

它本身就是一个 JavaScript 对象,只不过它是通过不同的属性去描述一个视图结构。 优点:

1.将真实元素节点抽象成 VNode,有效减少直接操作 dom 次数,从而提高程序性能

2.方便实现跨平台

3.在vue中我们常常会为组件编写模板template, 这个模板会被编译器compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。

4.挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。

  • 注:vDOM并不快,数据变化后,重新渲染template,遍历查找不同的数据并且改变(数据驱动视图)

diff

作用:虚拟DOM要想转化为真实DOM就需要通过patch方法转换。

vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数获得最新的虚拟DOM,然后执行patch函数,并传入新旧两次虚拟DOM,通过比对两者找到变化的地方,最后将其转化为对应的DOM操作。

patch过程是一个递归过程,遵循深度优先、同层比较的策略. 过程:

  • 首先判断两个节点是否为相同同类节点,不同则删除重新创建

  • 如果双方都是文本则更新文本内容

  • 如果双方都是元素节点则递归更新子元素,同时更新元素属性

  • 更新子节点时又分了几种情况:

    • 新的子节点是文本,老的子节点是数组则清空,并设置文本;
    • 新的子节点是文本,老的子节点是文本则直接更新文本;
    • 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素;
    • 新的子节点是数组,老的子节点也是数组,那么比较两组子节点,更新细节blabla

v-once

作用:是仅渲染指定组件或元素一次,并跳过未来对其更新。

如果我们有一些元素或者组件在初始化渲染之后不再需要变化,这种情况下适合使用v-once,这样哪怕这些数据变化,vue也会跳过更新,是一种代码优化手段。 有缓存,避免计算。

keep-alive

作用:keep-alive是vue内置组件,keep-alive包裹动态组件component时,会缓存不活动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染DOM。 缓存更新:

  • beforeRouteEnter:在有vue-router的项目,每次进入路由的时候,都会执行beforeRouteEnter

     beforeRouteEnter(to, from, next){
       next(vm=>{
         console.log(vm)
         // 每次进入路由执行
         vm.getData()  // 获取数据
       })
     },
    
  • actived:在keep-alive缓存的组件被激活的时候,都会执行actived钩子

      activated(){
            this.getData() // 获取数据
      },
    

组件递归

组件通过组件名称引用它自己,这种情况就是递归组件。例如Tree/Menu

SPA、SSR

  • SPA(Single Page Application)即单页面应用。一般也称为 客户端渲染

特点:SPA应用只会首次请求html文件,后续只需要请求JSON数据即可,因此用户体验更好,节约流量,服务端压力也较小。但是首屏加载的时间会变长,而且SEO不友好。

  • SSR(Server Side Render)即 服务端渲染。一般也称为 多页面应用

特点:解决SPA首页加载时间长的问题,就有了SSR方案,由于HTML内容在服务器一次性生成出来,首屏加载快,搜索引擎也可以很方便的抓取页面信息。但同时SSR方案也会有性能,开发受限等问题。

Vue优化方案:

  • 路由懒加载:有效拆分App尺寸,访问时才异步加载

    const router = createRouter({
      routes: [
        // 借助webpack的import()实现异步组件
        { path: '/foo', component: () => import('./Foo.vue') }
      ]
    })
    
  • keep-alive:缓存页面:避免重复创建组件实例,且能保留缓存组件状态

    <router-view v-slot="{ Component }">
      <keep-alive>
         <component :is="Component"></component>
      </keep-alive>
    </router-view>
    
  • 使用v-show复用DOM:避免重复创建组件

  • 避免v-for和v-if同时使用

  • 对于不会变化的数据使用v-once

  • 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容

  • 事件的销毁:Vue 组件销毁时,会自动解绑它的全部指令及事件监听器

  • 图片懒加载v-lazy

  • 第三方插件按需导入

  • 服务端渲染/静态网站生成:SSR/SSG。如果SPA应用有首屏渲染慢的问题,可以考虑SSR、SSG方案优化。

Vue3.0新特性:

  • 添加setup:组件内使用composition API得入口(beforeCreate之前)。接受两个参数:props(传入得属性)、context
  • teleport(传送):子组件多层嵌套,会出现样式层级等麻烦。即:即希望继续在组件内部使用,又希望渲染得DOM结构不在嵌套得DOM中
  • 片段Fragment添加多个根节点
  • 更好的 Tree-Shaking:2.0获取全局得API需要ESModule引用,例如nextTick等,现在则直接使用

webpack

作用:

  • 模块打包:可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。
  • 编译兼容:通过webpackLoader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less, .vue, .jsx这类在浏览器无法识别的格式文件
  • 能力扩展:通过webpackPlugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能

打包流程:

1.读取webpack的配置参数;
2.启动webpack,创建Compiler对象并开始解析项目;
3.从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;
4.对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为Javascript文件;
5.整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。