前端面试整理
创始人
2024-02-21 23:38:03

Js

1. Localstorage、sessionStorage、cookie、session的区别

(1)web storage和cookie的区别:

Cookie(不设置过期时间) === sessionStorage

WebStorage的目的是克服由cookie所带来的一些限制,当数据需要被严格控制在客户端时,不需要持续的将数据发回服务器。

cookie:是服务器送给客户端并保存在本地的一小块数据,下次客户端继续向服务器发送请求的时候会带上这个小数据,他会告诉服务器这两次请求是来自同一个客户端,能保持用户的登录状态。

webstorage(包括:localStorage和sessionStorage)是在HTML5提出来的,纯粹为了保存数据,不会与服务器端通信

WebStorage两个主要目标:(1)提供一种在cookie之外存储会话数据的路径。(2)提供一种存储大量可以跨会话存在的数据的机制。

  • localStorage:localStorage生命周期是永久,除非主动清除localStorage信息,否则这些信息将永远存在。存放数据大小为一般为5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。

  • sessionStorage:存储在 sessionStorage 里面的数据在页面会话结束时会被清除。

    • 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
    • 打开多个相同的URL的Tabs页面,会创建各自的sessionStorage。
    • 关闭对应浏览器窗口(Window)/ tab,会清除对应的sessionStorage。
  • 相同点:cookie,localStorage,sessionStorage都是在客户端保存数据的,存储数据的类型:都是字符串。

  • 不同点:

    • 生命周期

      cookie:可以设置失效时间,没有设置的话,默认是关闭浏览器后失效,
      localStorage:除非被手动清除,否则将永久保存。
      sessionStorage:仅在当前网页会话下有效,关闭页面或者浏览器后会被清除。sessionStorage就是没有设置有效期的cookie。

    • 存放数据大小:

      cookie:4kb
      localStorage 和sessionStorage:可以保存5MB左右的信息

    • 网络流量

      cookie:每次会携带在HTTP头中,使用cookie保存过多信息会带来性能问题
      localStorage 和 sessionStorage 仅在浏览器中保存。

    • 安全性:WebStorage不会随着HTTP header发送到服务器端,所以安全性相对于cookie来说比较高一些,不会担心截获,但是仍然存在伪造问题;

总结:WebStorage不会随着HTTP header发送到服务器端,比cookie更安全更加省流量,操作也更方便。但是webstorage用到了同步机制所以会影响浏览器的渲染进度

(2)cookie和session的区别

  • 什么是 Cookie

服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。

Cookie 主要用于以下三个方面:

  1. 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  2. 个性化设置(如用户自定义设置、主题等)
  3. 浏览器行为跟踪(如跟踪分析用户行为等)
  • 什么是 Session

Session 代表着服务器和客户端一次会话的过程。Session 对象存储会话所需的属性及配置信息。当服务器收到请求需要创建session对象时,首先会检查客户端请求中是否包含sessionid。如果有sessionid,服务器将根据该id返回对应session对象。如果客户端请求中没有sessionid,服务器会创建新的session对象,并把sessionid在本次响应中返回给客户端。通常使用cookie方式存储sessionid到客户端,在交互中浏览器按照规则将sessionid发送给服务器。

  • Cookie 和 Session 有什么不同?

Cookie 和 Session 有什么不同?

  • 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端。
  • 存取方式的不同,Cookie 只能保存字符串类型,Session 可以存任意数据类型
  • 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
  • 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。
  • 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。

2.Array数组方法概览

WechatIMG197 WechatIMG199

3. JavaScript的数据类型

  • 基本数据类型:Number、String、Boolean、null、undefined

    • number:一些特别情况
      • Nan:数字类型(not a number)
      • Infinity: 正无穷 (比如:1/0)
      • -infinity:负无穷
    • undefined:表示变量声明但未初始化时的值
    • null:表示准备用来保存对象,还没有真正保存对象的值。从逻辑角度看,null值表示一个空对象指针。
  • 引用数据类型:Function、Object、Array、date、RegExp

null==undefined//true
null===undefined//false
NaN==NaN//false NaN和谁都不相等包括自己

4. null,undefined的区别

  • null:表示一个对象被定义了,值为“空值”
    • typeof null //“object”
    • null作为对象原型链的终点
  • undefined:表示不存在这个值(定义这个变量了但是没有赋值)
    • typeof undefined //“undefined”
    • 调用函数时,应该提供的参数没有提供,该参数等于undefined
    • 函数没有返回值时,默认返回undefined

false:undefined、null、NAN、“”、0、false、或者在true的前面➕!

Object = typeof(object/array/null)

如果a没有定义:undefinde = Typeof(a)

Number(null) //0
Number(undefined) //NaN
typeof null // object
typeof undefined // undefined
typeof NaN // number

5. 创建object对象有几种方法

(1)字面量对象 // 默认这个对象的原型链指向object

var o1 = {name: '01'};

(2)通过new Object声明一个对象

var o11 = new Object({name: '011'});

(3)object.create()(一般是Object.create(function.prototype))

var P = {name:'o3'};
var o3 = Object.create(P);// o3.__proto__=== p.prototype

(4)使用显式的构造函数创建对象

var M = function(){this.name='o2'};
var o2 = new M();
o2.__proto__ === M.prototype

6.Object.create与new区别

function A() {this.name = 'abc';
}
A.prototype.a = 'a';
A.prototype.showName = function () {return this.name;
}
var a1 = new A();
var a2 = Object.create(A.prototype);
console.log(a1)
console.log(a2)
在这里插入图片描述 在这里插入图片描述

区别:

所以Object.create()与new的区别在于Object.create只继承原型属性和方法(公有),继承不了构造函数的属性和方法(私有)。而通过new操作符创建的实例,既可以继承原型的属性和方法,又可以继承构造函数的属性和方法。

new原理

  1. 创建一个空对象obj;
  2. 将该空对象的原型设置为构造函数的原型,即obj.proto = func.prototype;
  3. 以该对象为上下文执行构造函数,即func.call(obj);继承私有属性
  4. 返回该对象,即return obj。
var newFunc = function ( func ){var obj = Object.creat(func.prototype);var ret = func.call(obj);if(typeof ret === 'object') { //object,null,arryreturn ret;}else { return obj;}
}

从两者的具体实现可以看出:Object.create没有执行步骤三,所以继承不了构造函数的属性和方法。

7. 简述创建函数的几种方式

(1)函数声明

function sum1(num1,num2){return num1+num2;
}

(2)函数表达式

var sum2 = function(num1,num2){return num1+num2;
}

(3)函数对象方式

var sum3 = new Function("num1","num2","return num1+num2");

8.讲讲JavaScript 中的作用域、预解析与变量声明提升

  • 作用域:就是变量的有效范围。 在一定的空间里可以对数据进行读写操作,这个空间就是数据的作用域。

    • 全局作用域:最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的;

    • 局部作用域:一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部

  • 执行 JavaScript 代码的时候分两个过程:预解析过程和代码执行过程。

  • 预解析过程

​ 1.把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值

​ 2.把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用

  • 变量提升:

    • 变量提升:定义变量的时候,变量的声明会被提升到作用域的最上面,变量的赋值不会提升

    • 函数提升:JavaScript解析器首先会把当前作用域的函数声明提前到整个作用域的最前面

      • foo();//1
        var foo;
        function foo() {console.log(1);  
        }foo = function() {console.log(2);
        }
        //打印1var foo;
        function foo() {console.log(1);
        }foo = function() {console.log(2);
        }
        foo()
        // 打印2
        

9.静态作用域与动态作用域

JS 采用的是静态作用域(词法作用域)

静态作用域:函数的作用域在函数定义的时候就已经确定了,函数作用域基于函数创建的位置。

var value = 1
function foo() {console.log(value)
}
function bar() {var value = 2foo()
}
bar()
// 结果是1

动态作用域:函数的作用域是在函数调用的时候才决定的。

10. 预编译

  • 函数内部预编译

    • A0:activation object 活跃对象 函数上下文

    • AO = {第一步:寻找行参和变量声明参数a:undefined 变量声明:var a已经写了就不写了 var b==> a: undefined  b:undefined第二步:将实参参数值复制给行参数==> a:2第三步:寻找函数体声明并赋值 ==> a:function a(); d:function d()第四步:执行test函数
      }function test(a){// 这个a是参数console.log(a)var a = 3; // 声明变量aconsole.log(a)function a(){}console.log(a)var b = function(){}console.log(b)function d(){}
      }
      test(2)
      /* return
      * [Function: a]
      * 3
      * 3
      * [Function: b]
      */
      
  • 全局预编译

    • GO gobal object

    • GO = {第一步:寻找变量声明 a:undefined第二步:寻找函数体并赋值 a:function a(){}第三步:执行 a:1
      }
      var a = 1
      function a(){console.log(2)
      }
      console.log(a)
      // 1
      

11. 作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文中查找,一直找到全局对象中。这样有多个执行上下文的变量对象构成的链表就叫做作用域链。(他的父级上下文是谁,在这个函数被定义的时候就已决定了)

a函数被定义的时候,系统会产生[[scope]]属性,[[scope]]保存该函数的作用域链,该作用域链0号位置保存的是当前环境下的全局上下文GO(存储了)全局上下文中的所有对象。

a函数被执行的时候,作用域链0号位置存储的是a函数的上下文AO,1号位置存储的是GO。查找变量就是从a的AO找到GO

12. 闭包

闭包就是能够读取其他函数内部变量的函数

从作用域链讲:外部函数(test1)没有内部函数(test2)的AO环境,但是内部环境(test2)执行的时候引用了外部函数(test1)的上下文同时也多了个AO,自己的AO先找,找不到就找外部函数的AO在找GO。

注意:当内部函数被返回到外部并保存的时,一定会产生闭包,闭包会使本该被销毁的作用域链一直存着。

过度的闭包会导致内存泄漏,或加载过慢

function test1(){function test2(){var b = 2;console.log(a)}var a = 1;return test2();
}
var c = 3;
var test3 = test1();
test3;
// 1

WechatIMG229

13. 如何延长作用域链?

1、try - catch 语句的catch块;会创建一个新的变量对象,包含的是被抛出的错误对象的声明

2、with 语句。with 语句会将指定的对象添加到作用域链中with(location)

14. 内存泄漏

**内存泄露:**函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。

不再用到的内存却没有释放

  • 意外的全局变量(函数内没有声明变量就直接赋值)

    • function a (){b = 20
      }
      a()
      
    • 解决:严格模式

  • 函数里有this.b=20,外部能访问

    • function a (){this.b = 20//this指向window
      }
      a()
      console.log(b)
      
    • 解决:严格模式

  • 定时器一直在执行,需要执行一次后及时清除定时器(两个内存泄漏 定时器+Dom元素)

    • 这里面还有个泄漏就是你已经清除了test这个dom元素,但是你打印test发现还能打印出来——>tese = null
截屏2022-07-27 17.51.15 截屏2022-07-27 17.55.59
  • 闭包
  • 事件监听
    • 不监听的时候取消事件监听

15. 如何区分数组和对象?

  • 通过ES6中的Array.isArray来识别
Array.isArray([]) //true
Array.isArray({}) //false
  • 通过instanceof来识别
[] instanceof Array //true
{} instanceof Array //false
  • 通过调用constructor来识别
{}.constructor //返回object
[].constructor //返回Array
  • 通过Object.prototype.toString.call方法来识别(最精确的识别)
Object.prototype.toString.call([]) //'[object Array]'
Object.prototype.toString.call({}) //''[object Object]'var toString = Object.prototype.toString
toString.call(1) // '[object Number]'
toString.call('1') // '[object String]'
toString.call(false) // '[object Boolean]'
toString.call(() => {}) // '[object Function]'
toString.call([]) // '[object Array]'

16. 数据类型转换

IMG_7461

显式类型转换

  • Number
Number('1') //1
Number('1a') //NaN
Number('AA') //NaN
Number(null) //0
Number(undefined) //NaN
  • parseInt:变成整数
parseInt('123a') //123
parseInt('aa123') //NaN
parseInt('3.12') //3
parseInt('3.99') //3
  • parseFloat
parseFloat('3.14141').toFixed(2) //3.14
parseFloat('3.14941').toFixed(2) //3.15
  • toString
123.toString() //'123'
undefined.toString() //报错
null.toString() //报错

隐式类型转换

var a = '123'
a++
console.log(a) // 124
var a = '3' * 2 //* % - 都是数学运算
var a = 'a' * 2 // NaN
var a = 'a' + 2 // a2

17. 什么是类数组(伪数组),如何将其转化为真实的数组?

  • **伪数组:**1、具有length属性 2、按索引方式存储数据 3、不具有数组的push.pop等方法

  • 伪数组–>真实数组

(1)声明一个空数组,遍历伪数组把它们添加到所声明的空数组中

(2)使用 Array.prototype.slice.call()Array.prototype.slice.apply()

Array.prototype.slice.call(arrayLike); slice(start,end)从start开始截取到end但是不包括end

Array.prototype.splice.call(arrayLike, 0); splice(start,deleteCount,item1,item2……);

var arr = Array.prototype.slice.call({  0:"hello world",  1: 10,  2: true,  length:3  });  
// ["hello world", 10, true]

(3)使用 ES6 中的扩展运算符

var obj = {0:"hello world",  1: 10,  2: true,  length:3  
}
var arr = [...obj]

(4)使用 ES6 中数组的新方法 from()

Array.from({  0:"hello world",  1: 10,  2: true,  length:3  
});  

18. 如何遍历对象的属性?

(1)Object.keys()

包括对象自身的(不含继承的)所有可以枚举属性(不含 Symbol 属性)。

var arr = ['a','b','c'];
console.log(Object.keys(arr)); // ['0','1','2']
var obj = {foo:'bar',baz:42};
console.log(Object.keys(obj)); // ["foo","baz"]
var anObj ={100:'a',2:'b',7:'c'};
console.log(Object.keys); // 类数组 ['2','7','100']顺便帮你排序了

(2)Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身(不含继承的)的所有属性(不含 Symbol 属性,但是包括不可枚举属性)

截屏2022-07-07 15.45.57

(3)Object.getOwnPropertySymbols(obj)

包含自身的所有属性,包括Symbol

19.如何使用原生JavaScript给一个按钮绑定两个onclick事件?

Var btn=document.getElementById(‘btn’);
//事件监听 绑定多个事件
var btn4 = document.getElementById("btn4");
btn4.addEventListener("click",hello1);
btn4.addEventListener("click",hello2);
function hello1(){alert("hello 1");
}
function hello2(){alert("hello 2");
}

20. 如何实现数组的随机排序?

数组方法sort实现随机排序

var arr = [1, 2, 3, 4, 5]
arr.sort(function () {return Math.random() - 0.5
})
console.log(arr);

21. Function foo() {}和var foo = function() {}之间foo的用法上的区别?

  • var foo = function () {}:这种方式是声明了个变量,而这个变量是个方法,变量(foo)在JavaScript中是可以改变的。

  • function foo() {}:这种方式是声明了个方法,foo这个名字无法改变

function b(){} 为函数声明,程序运行前就已存在

var a = function(){} 为函数表达式,是变量的声明,属于按顺序执行

22. 根据你的理解,请简述JavaScript脚本的执行原理

  • JavaScript是一种动态、弱类型、基于原型的脚本语言,并且它还是一种由浏览器内的解释器执行的程序语言

    • 弱类型:数据类型可以被忽略的语言。一个变量可以赋不同数据类型的值。
  • 当客户端向服务器端请求一个页面时,服务器端会将整个页面所包含 JavaScript 的脚本代码发送到客户端,再由浏览器自上而下逐行读取并且解析其中的 HTML 或脚本代码

    关键渲染路径:(Critical Rendering Path)是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列

    • 处理 HTML 标记并构建 DOM 树。
    • 处理 CSS 标记并构建 CSSOM 树。
    • 将 DOM 与 CSSOM 合并成一个渲染树。
    • 根据渲染树来布局,以计算每个节点的几何信息。
    • 将各个节点绘制到屏幕上。

当浏览器遇到

  • 
    
  • 0. animation

    案例:绕圈:值得注意的是transform效果是不会叠加

    @keyframes move {0% {transform: translateX(0px);}25% {transform: translateX(1000px);}50% {transform: translate(1000px, 500px);}75% {transform: translate(0px, 500px);}100% {transform: translate(0px, 0px);}
    }
    div {width: 100px;height: 100px;background-color: teal;animation-name: move;animation-duration: 3s;
    }
    
    属性描述
    @keyframes规定动画
    animation所有动画属性的简写属性,除了 animation-play-state属性
    animation-name制定需要使用的动画( 必须的 )
    animation-duration规定动画完成一个周期所花费的秒或毫秒( 必须的 )
    animation-timing-function规定动画的速度曲线,默认是"ease"”。
    aniamtion-delay规定动画何时开始,默认是0.
    animation-iteration-count规定动画被播放的次数,默认是1,还有 infinite
    animation-direction规定动画是否在下一周期逆向播放,默认是" normal" alternate逆播放
    animation-play-state规定动画是否正在运行或暂停。默认是" running" 还有’ paused 暂停
    animation-fill-mode规定动画结束后状态,保持 forwards 回到起始 backwards
    • 暂停动画: animation-play-state: pulsed;经常和鼠标经过等其他配合使用

    • 想要动画走回来,而不是直接跳回来: animation- direction: alternate

    • 盒子动画结束后,停在结東位置: animation- fill-mode: forwards

    • 动画简写属性

      animation:动画名称 持续时间 运动曲线 何时开始 播放次数 是否反方向 动画起始或者结束的状态
      animation:move 1s ease 2s 3 alertnate fowwards

    打字机效果

    • 1ch = 1个英文 = 1个数字
    • 2ch = 1个中文
    我不知道你在想什么

    1. 清除浮动 —float布局问题

    • 什么是浮动:元素脱离文档流,不占据空间。浮动元素碰到包含它的边框或者浮动元素的边框停留
    • 浮动产生的原因:
      • 子元素比父元素高,子元素是浮动的,子元素将溢出这个父元素
      • 父元素没有高度,子元素都是浮动的
    • 浮动带来的问题:
      • 父元素的高度无法被撑开,影响与父元素同级的元素
      • 如果当前元素浮动会影响后面元素,那么后面元素也得浮动(影响同级非浮动元素、父元素)(或者需要清除浮动)

    发现:设置float后,宽度高度可随便改变

    • 浮动元素会从普通文档流中脱离,但浮动元素影响的不仅是自己,它会影响周围的元素对其进行环绕;
    • 不管一个元素是行内元素还是块级元素,只要被设置了浮动,那浮动元素就会形成一个块级框,可以设置它的宽度和高度,因此浮动元素常常用于制作横向配列的菜单,可以设置大小并且横向排列。
      • 浮动元素在浮动的时候,其 margin 不会超过包含块的 padding
      • 重叠问题
        • 行内元素与浮动元素发生重叠,其边框、背景和内容都会显示在浮动元素之上
        • 块级元素与浮动元素发生重叠时,边框和背景会显示在浮动元素之下,内容会显示在浮动元素之下

    (1)父元素高度塌陷问题

    一个块级元素如果没有设置高度,其高度是由子元素撑开的。如果对子元素设置了浮动,那么子元素就会脱离文档流,也就是说父元素没有内容可以撑开其高度,这样父级元素的高度就会被忽略,这就是所谓的高度塌陷。

    1. 给父元素定义高度
      • 优点:操作简单
      • 缺点:高度定死
    2. 添加一个空元素
      (.clear { clear: both })
      • 优点:浏览器支持好
      • 缺点:凭空多出很多无用空节点
    3. 让父元素也一起浮动
      • 缺点:无法解决实际问题
    4. 父元素设置为 display: table
      • 缺点:会产生新的问题
    5. 父元素设置 overflow: hidden auto
      • 缺点:无法显示溢出的元素
    6. 父元素伪元素设置清除浮动
    .father {...
    }.father:: after {content: ' ';display: block;height: 0;clear: both;visibility: hidden;
    }
    

    2. Margin Collapse 外边距重叠问题

    外边距重叠指的是,当两个垂直外边距相遇时,它们将形成一个外边距

    (1)相邻marign重叠的问题

    • 全部都为正值,取最大者;
    • 不全是正值,则都取绝对值,然后用正值的最大值减去绝对值的最大值;
    • 没有正值,则都取绝对值,然后用0减去最大值。

    解决:

    • 底部元素设置为浮动 float:left;
    • 底部元素的position的值为absolute/fixed

    (2)元素和父元素margin值问题

    • 外层元素添加padding
    • 外层元素 overflow:hidden;
    • 外层元素透明边框 border:1px solid transparent;
    • 外层元素display:table/ inline-block
    • 外层元素添加position:fixed/absolute
    • 内层元素 加float:left;不改变父宽度
    • 内层元素display:inline-block;
    • 内层元素添加position:fixed/absolute;

    3. 什么是BFC

    • **定义:**块级格式上下文:block formatting context,BFC就是页面上一个隔离的独立容器,容器里面的子元素不会影响到外面的元素

    • BFC 布局规则

      • 内部的 Box 会在垂直方向一个接一个地放置

      • Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠

      • BFC 的区域不会与 float box 重叠

      • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响外面的元素。反之也是如此

      • 计算 BFC 的高度时,浮动元素也参与计算

    • 如何触发BFC:

    float的值为非none
    overflow的值非visible
    display的值为:inline-block, table-cell, table-caption, flex, inline-flex
    position的值为:absolute,fixed
    
    • BFC 的作用

    • 利用 BFC 避免 margin 重叠

    • 自适应两栏布局

    • 清除浮动

    4. 简述transform,transition,animation的作用?

    transform

    描述了元素的静态样式,本身不会呈现动画效果,可以对元素进行旋转rotate、扭曲skew、缩放scale和移动translate以及矩阵变形matrix。

    transition

    样式过渡,从一种效果逐渐改变为另一种效果

    transition: width 2s linear 1s;

    animation

    动画 由@keyframes来描述每一帧的样式

    @keyframes example {from {top: 0px; background-color: yellow;}to {top: 200px; background-color: blue;}
    }
    div {animation-name: example; //使用上面的效果animation-duration: 5s; //实现消耗时间animation-timing-function: linear; //正常速度animation-delay: 2s; //延迟几秒再实现animation-iteration-count: infinite; //动画运行多少次animation-direction: alternate; //指定是向前播放、向后播放还是交替播放动画。
    }
    

    区别

    • transform仅描述元素的静态样式,常常配合transition和animation使用
    • transition通常和hover等事件配合使用,animation是自发的,立即播放
    • animation可设置循环次数
    • animation可设置每一帧的样式和时间,transition只能设置头尾

    5. 如何使一个盒子水平垂直居中?

    (1)position + 负margin

    • 需要知道子元素的宽高
    
    
    12345

    (2)position + magin:auto

    • 需要知道子元素的宽高left,right,top,bottom一个都不能少
    
    
    • 绝对定位:position
    • Flex 布局:flex:1
    • **表格布局:**display:table;父:width:100%;子:display:table-cell;

    8. 三栏布局(双飞翼圣杯)

    • 直接利用float(注意dom中先写left,right再写center!!!)左右只要设置float,中要设置margin:0 auto;
    • 使用position。都设置absolute,左右各设置left:0,right:0;中设置left:200px,right:200px。
    • 使用flex(注意顺序是左中右!)

    利用的是浮动元素 margin 负值的应用

    中间先加载并且自适应,左右固定

    截屏2022-07-06 17.13.37
    • 圣杯布局:一目了然,使用相对行定位
    • 优点:主体内容可以优先加载
    • 缺点:CSS理解起来难一点
    .clearfix::after{//三个模块浮动影响了底部content:"";display:block;clear:both
    }
    .left,.center,.cright{float:left;
    }
    
    截屏2022-07-06 17.38.37
    • 双飞翼布局:不太直观,不需要定位
    • 优点:主体内容可以优先加载
    • 缺点:HTML 代码结构稍微复杂点。
    截屏2022-07-06 17.33.13

    9. CSS的盒子模型?

    • W3C标准的盒子模型(标准盒模型)
    width = content.width
    height = content.height
    盒子的大小 = content + border + padding + marginwidth = content.width
    height = content.height
    盒子的大小 = content + border + padding + margin
    
    • IE标准的盒子模型(怪异盒模型)
    width = content + border + padding
    height = content + border + padding
    盒子的大小 = width(content + border + padding) + margin
    
    • box-sizing来设置盒子模型的解析模式

      • content-box: 默认值,border和padding不算到width范围内,可以理解为是W3c的标准模型(default)。总宽=width+padding+border+margin

      • border-box:border和padding划归到width范围内,可以理解为是IE的怪异盒 模型,总宽=width+margin

    10. 什么是渐进增强和优雅降级?它们有什么不同?

    由于低级浏览器不支持CSS3,但 CSS3 的效果又太优秀不忍放弃,所以在高级浏览中使用 CSS3 而低级浏览器只保证最基本的功能。

    • 渐进增强:向上兼容。针对低版本浏览器构建页面,完成最基本的功能,然后再针对高级浏览器增加功能
    • 优雅降级:向下兼容。先构建一个完整的功能,然后根据浏览器进行测试和修复

    11. 哪些是块级元素那些是行内元素,各有什么特点 ?

    • 块级元素:div、ul、li、dl、dt、dd、p、h1-h6、blockquote、form
      • 块级元素会独占一行,其宽度自动填满其父元素宽度(可以自己设置宽度)
      • 块级元素可以设置margin 和 padding.
    • 行内元素: a(控制跳转)、span、b(等同于strong,粗体)、img、strong、input、select、lable、em(等同于i标签,斜体)、button、textarea 、selecting
      • 行内元素不会独占一行,其宽度随元素的内容而变化(不可以设置宽度)
      • 行内元素的水平方向的padding-left,padding-right,margin-left,margin-right都产生边距效果,但是竖直方向的padding-top,padding-bottom,margin-top,margin-bottom都不会产生边 距效果。(水平方向有效,竖直方向无效)

    12. CSS选择器有哪些?哪些属性可以继承?

    !Important>行内样式>ID选择器>类选择器>标签>通配符>继承>浏览器默认属性

    选择器权重
    ! important无穷
    行间样式1000
    id选择器( # myid)100
    类选择器(.myclassname)、伪类(a:hover、结构伪类nth-child)、属性选择器(a[rel = “external”])10
    标签(div, h1, p)、伪元素选择器(div ::first-line)1
    通配符( * ),子选择器(ul > li),相邻选择器(h1 + p)、后代选择器(li a)0

    可继承的样式: font-size font-family color字体系列属性+文本系列属性(水平对齐、行高)+元素可见性:visibility+表格布局属性(caption-side、border-collapse、border-spacing、empty-cells、table-layout)

    不可继承的样式:盒子模型的属性(border padding margin width height )+背景属性(background、background-color)+定位属性(float、clear、position)

    13. 说说你对语义化的理解?列举5个语义化的标签?

    语义化就是我们通过标签本身就知道标签所在代码的内容具有什么意义,即用使用特定的功能属性的标签做特定的事。比如使用 h1 标签我们就知道这是一个标题,使用 header 就知道这是页眉,使用 footer 就知道这是页脚。

    语义化标签有两个好处:

    • 一是对机器友好。语义化功能让搜索引擎爬虫更有效地捕获页面结构,有利于SEO;利于页面结构分析,如识别文章标题,生成目录等等。
    • 二是对开发者友好。语义化让文档结构更清晰,便于整理和优化页面结构。

    常见的语义化标签:

    img

    14. 列举5个以上的H5事件?

    onblur:当失去焦点时运行脚本

    onclick:当单击鼠标时运行脚本

    onfocus:当获得焦点时运行脚本

    onload:当加载时运行脚本

    onmousedown: 当按下鼠标按钮时运行脚本

    onmousemove:当鼠标指针移动时运行脚本

    15. 使用纯CSS绘制三角形(宽度和高度必须是0)

    .triangle{border-left:50px solid transparent;border-right:50px solid transparent;border-bottom:50px solid #333;width:0;height:0;
    }
    

    16. =什么区别

    • 两者都是判断等式两边是否相等,最大的区别就是会进行类型的转换之后再判断两者是否相等,而=不会进行数据类型的转换,先判断两边的数据类型是否相等,如果数据类型相等的话才会进行接下来的判断,再进行等式两边值得判断,可以理解为只有等式两边是全等(数据类型相同,值相同)的时候结果才会是true,否则全为false。

    17. CSS单位中px、em和rem的区别?

    • px 像素:绝对单位。像素 px 是相对于显示器屏幕分辨率而言的,是一个虚拟长度单位。

    • em :是相对长度单位,相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。它会继承父级元素的字体大小, 因此并不是一个固定的值。

    • rem:是 CSS3 新增的一个相对单位,相对于html根元素的字体尺寸。1rem的大小是通过html下的font-size这个css属性告诉浏览器的

      • html{font-size:10px;如果1rem=10px
        }
        
    • 区别:IE 无法调整那些使用 px 作为单位的字体大小,而 em 和 rem 可以缩放,rem相对的只是 HTML 根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。目前,除了 IE8 及更早版本外,所有浏览器均已支持 rem

    18. CSS中link和@import的区别?

    **important的作用:**提高指定样式规则的优先级

    • 优先级 link > @import

    • 适用范围不同: @import可以在网页页面中使用,也可以在CSS文件中使用,用来将多个 CSS文件引入到一个CSS文件中;而link只能将CSS文件引入到网页页面中

    • 功能范围不同: link属于XHTML标签,而@import是CSS提供的一种语法规则,link标签除了可以加载CSS外,定义rel连接属性( rel=“stylesheet”)等,@import就只能加载CSS

    • 加载顺序不同: 当一个页面被加载的时候,link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再被加载。所以有时候浏览@import加载CSS的页面时开始会没有样式(就是闪烁),网速慢的时候还挺明显

    • 兼容性: 由于@import是css2.1提出的,所以老的浏览器不支持,@import只有在IE5以上的才能识别,而link标签无此问题

    19. Display:none与visibility:hidden的区别?

    • dispaly:none: 设置该属性后,该元素下的元素都会隐藏,占据的空间消失
    • **visibility:hidden:**设置该元素后,元素虽然不可见了,但是依然占据空间的位置
    • 区别:
      • visibility具有继承性,其子元素也会继承此属性,若设置visibility:visible,则子元素会显示
      • visibility不会影响计数器的计算,虽然隐藏掉了,但是计数器依然继续运行着。
      • 在CSS3的transition中支持visibility属性,但是不支持display,因为transition 可以延迟执行,因此配合visibility使用纯CSS实现hover延时显示效果可以提高用户体验
      • display:none会引起重排和重绘 visibility:hidden会引起重绘

    20. 雪碧图 ( 精灵图 )?

    将一个页面涉及到的所有图片都包含到一张大图中去,从而减少你的网站的HTTP请求数量,然后利用CSS的 background-image,background-repeat,background-position属性的组合进行背景定位。

    • 优点:

      • 减少网页的http请求,从而加快了网页加载速度,提高用户体验
      • 减少图片的体积,因为每个图片都有一个头部信息,把多个图片放到一个图片里,就会共用同一个头信息,从而减少了字节数
      • 解决了网页设计师在图片命名上的困扰,只需对一张集合的图片上命名就可以了,不需要对每一个小元素进行命名
    • 缺点:

      • 在宽屏,高分辨率的屏幕下,你的图片如果不够宽,很容易出现背景断裂
      • CSS Sprites在开发的时候,要通过photoshop或其他工具测量计算每一个背景单元的精确位置
      • 精灵图不能随意改变大小和颜色。改变大小会失真模糊,降低用户体验,CSS3新属性可以改变精灵图颜色,但是比较麻烦,并且新属性有兼容问题,现在一般用字体图标代替精灵图

    21. img的alt与title的异同,还有实现图片懒加载的原理?

    • 异同:

      • alt 是图片加载失败时,显示在网页上的替代文字;
      • title 是鼠标放上面时显示的文字,title是对图片的描述与进一步说明;
      • 这些都是表面上的区别,alt是img必要的属性,而title不是。对于网站seo优化来说,title与alt还有最重要的一点: 搜索引擎对图片意思的判断,主要靠alt属性。
    • 图片懒加载的原理:

    将图片的地址放在data-set属性中,由于图片并没有在src中,并不会发送http请求。比较图片到整个页面距离(Y)和 页面滑动的距离 (X) + 浏览器可视区域的大小(Z) ,Y小于两者之和,则图片就显示出来,将图片的data-set属性换为src属性

    截屏2022-07-07 11.39.20

    懒加载原理就是将一次性下载全部通篇改为根据判断可视区域下载。也就是说看到哪下载到哪。

    • 图片的src不设置真实的路径
    • 图片的真实路径设置在其他属性中比如: data-src
    • 通过js判断图片是否进入可视区域。
    • 如果图片进入可视区域将图片src换成真实路径
    • 两种方法:
      • 直接调用api求img相对浏览器视窗的位置getBoundingClientRect()
      • 求img距离顶部距离Y,求可视区域高度Zwindow.innerHeight,求滚动区域高度Xdocument.documentElement.scrollTop
    // 必须等到网页DOM加载完后 再实现
    window.onload = function(){// 获取到浏览器顶部的距离function getTop(e){return e.offsetTop;}// 简易防抖函数,想要深究可以直接引用npm i -S throttle-debouncefunction debounce(wait,fn) {    var timeout = null;    return function() {        if(timeout !== null)   clearTimeout(timeout);        timeout = setTimeout(fn, wait);    } }// 懒加载实现function lazyload(){// 获取图片列表const imgs = document.querySelectorAll('img');// 可视区域高度let height = window.innerHeight;//滚动区域高度let top = document.documentElement.scrollTop || document.body.scrollTop;for(let i=0,length=imgs.length;i//图片距离顶部的距离大于可视区域和滚动区域之和时懒加载if ((height+top)>getTop(imgs[i])) {// 真实情况是页面开始有2秒空白,所以使用setTimeout定时2s(function(i){setTimeout(function(){// 不加立即执行函数i会等于9// 隐形加载图片或其他资源,//创建一个临时图片,这个图片在内存中不会到页面上去。实现隐形加载let temp = new Image();temp.src = imgs[i].getAttribute('data-src');//只会请求一次// onload判断图片加载完毕,真是图片加载完毕,再赋值给dom节点temp.onload = function(){// 获取自定义属性data-src,用真图片替换假图片imgs[i].src = imgs[i].getAttribute('data-src')}},2000)})(i)}}}// 页面加载,先显示当前可以显示的图片lazyload();// 滚屏函数 添加防抖函数window.addEventListener('scroll', debounce(100, lazyload))
    }
    

    22. src和href的区别是?

    • **src(source)**指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置。

      • 如 JavaScript脚本,img 图片和iframe 等元素
      • 当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行
    • **href(hypertext reference/超文本引用)**指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加那么浏览器会识别该文档为 CSS 文件,就会并行下载资源并且不会停止对当前文档的处理

    23. flex=1是什么?

    flex:1实际代表的是三个属性的简写:弹性元素如何伸长或缩短以适应flex容器中的可用空间
    flex-grow是用来增大盒子的,比如,当父盒子的宽度大于子盒子的宽度,父盒子的剩余空间可以利用flex-grow来设置子盒子增大的占比

    flex-shrink用来设置子盒子超过父盒子的宽度后,超出部分进行缩小的取值比例

    flex-basis是用来设置盒子的基准宽度,并且basis和width同时存在basis会把width干掉

    所以flex:1;的逻辑就是用flex-basis把width干掉,然后再用flex-grow和flex-shrink增大的增大缩小的缩小,达成最终的效果。

    24. css实现一条0.5px直线

    .outer {height: 1px;width: 200px;background-color: red;transform: scaleY(0.5);// y轴缩减0.5倍transform-origin: 50% 100%;// 允许你改变被转换元素的位置}

    25. 如何获取页面中出现的所有dom元素的标签类型

    const all = Array.from(document.querySelectorAll("*"));
    const hash = {};
    const res = [];
    all.forEach(it=>{if(!hash[it.tagName]){res.push(it.tagName);hash[it.tagName] = true;}
    })
    console.log(res)
    

    26. CSS常用布局方式

    • 静态布局

      • 最传统的布局方式,网页中所有尺寸都是由px作为单位,设置了min-width,如果宽度小于就会出现滚动条,如果大于这个宽度则内容居中外加背景。
    • 自适应布局

      • 多个静态布局组成的,屏幕分辨率变化时,页面里面元素的位置会变化而大小不会变化。
    • 流式布局(又别名 百分比布局 %)

      • 使用%百分比定义宽度,高度大都是用px来固定住
      • 双飞翼,圣杯布局
    • 弹性布局:flex

      • 可以使元素具有伸缩性,根据父容器的大小,来决定收缩还是扩展。设为flex布局以后,子元素的float、clear和vertical-align属性将失效。
    • 响应式布局

      • 媒体查询可以检测到屏幕的尺寸(主要检测宽度),并设置不同的CSS样式,就可以实现响应式的布局

    27. 实现表格的隔行变色

    通过CSS3 的 :nth-child(an+b)选择器我们可以实现隔行变色的效果。:nth-child(an+b)选择器首先找到所有当前元素的兄弟元素,然后按照位置先后顺序从1开始排序,选择的结果为表达式an+b匹配到的元素集合。(n=0,1,2,3…)

    • tr:nth-child(2n+1):匹配表格中的奇数行
    • tr:nth-child(2n):匹配表格中的偶数行
    • tr:nth-child(1):匹配表示一组兄弟元素中的第一个,且为tr的元素
    /*设置表格奇数行的背景颜色*/
    tr:nth-child(2n+1) {background-color: rgba(166, 221, 53, 0.70);
    }/*设置表格偶数行的背景颜色*/
    tr:nth-child(2n) {background-color: rgba(237, 237, 237, 0.86);
    }/*设置表格th行的背景颜色*/
    thead tr:first-child {background-color: rgba(39, 153, 144, 0.91);
    }
    

    VUE

    vue响应式原理

    1. 数据劫持:当数据变化时,我们可以做一些特定的事情
    2. 依赖收集:我们要知道那些视图层的内容(DOM)依赖了哪些数据(state)
    3. 派发更新:数据变化后,如何通知依赖这些数据的DOM 接下来,我们将一步步地实现一个自己的玩具响应式系统
    • 数据劫持:Object.defineProperty

    • 依赖收集过程:渲染页面时碰到插值表达式,v-bind等需要数据等地方,会实例化一个watcher,实例化watcher就会对依赖的数据求值,从而触发getter,数据的getter函数就会添加依赖自己的watcher,从而完成依赖收集。我们可以理解为watcher在收集依赖,而代码的实现方式是在数据中存储依赖自己的watcher(这个操作就是数据劫持中getter方法的dep.push(watcher))

      • vue2.x的做法是每个组件对应一个watcher,实例化watcher时传入的也不再是一个expression,而是渲染函数,渲染函数由组件的模板转化而来,这样一个组件的watcher就能收集到自己的所有依赖,以组件为单位进行更新
      • Watcher(有get方法和update方法触发回掉函数)
    • 派发更新:触发watcher的回调函数

    数据劫持:Object.defineProperty()方法设置set和get函数来实现数据的劫持

    依赖收集:渲染页面时碰到插值表达式,v-bind等需要数据等地方,会实例化一个watcher,实例化watcher就会对依赖的数据求值,从而触发getter,数据的getter函数就会添加依赖自己的watcher就是watcher在收集依赖但是代码实现方式是依赖存储自己的watcher

    派发更新:数据改变set函数触发watcher的回调函数

    总的来说,data传入Observer转成get/set形式;当Watcher实例读取数据时,会触发getter,被收集到Dep仓库中;当数据更新时,触发setter,通知Dep仓库中的所有Watcher实例更新,Watcher实例负责通知外界

    Vue的双向绑定

    在响应式原理的基础上,上面是普通文本节点响应式,这个是input

    在comple函数的判断文本节点类型上判断nodeType是不是等于1,nodeName是不是‘input’

    然后获取它的属性arr = node.attributes

    遍历arr的元素,如果item.nodename='v-model'创建watcher。

    最重要的一步:

    在有v-model的节点上增加监听事件,确保要给哪个属性名赋值

    截屏2022-09-15 23.04.49

    1. Vue的最大的优势是什么?

    1、Vue.js 可以进行组件化开发,使代码编写量大大减少,读者更加易于理解。

    2、Vue.js 最突出的优势在于可以对数据进行双向绑定。

    3、使用 Vue.js 编写出来的界面效果本身就是响应式的,这使网页在各种设备上都能显示出非常好看的效果。

    4、相比传统的页面通过超链接实现页面的切换和跳转,Vue 使用路由不会刷新页面。

    5、vue是单页面应用,使页面局部刷新,不用每次跳转页面都要请求所有数据和dom,这样大大加快了访问速度和提升用户体验。

    6、而且他的第三方UI组件库使用起来节省很多开发时间,从而提升开发效率。

    2. MVVM和MVC区别是什么?哪些场景适合?

    双向绑定 = 数据驱动 + 自动取值

    • MVVM

    MVVM即Model-View-ViewModel的简写,即模型-视图-视图模型,模型(Model)指的是后端传递的数据,视图(View)指的是所看到的页面,视图模型(ViewModel)是mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:

    一是将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面:数据绑定(响应式原理,数据驱动视图,数据变化就对页面重新渲染)

    二是将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据。:DOM监听

    Vue中VM的实现原理:

    响应式:Object.defineProperty()来做数据劫持和响应式
    模板解析:Vue中的render函数,来将模版转换成虚拟DOM
    将虚拟DOM渲染成html:通过updateComponent方法实现

    • MVC基本定义

    MVC是Model-View- Controller的简写。即模型-视图-控制器。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑,使用MVC的目的就是将M和V的代码分离。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下

    • 使用场景

    场景:数据操作比较多的场景,需要大量操作DOM元素时,采用MVVM的开发方式,会更加便捷,让开发者更多的精力放在数据的变化上,解放繁琐的操作DOM元素

    • 两者之间的区别

    MVC和MVVM其实区别并不大,都是一种设计思想, MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念,vm存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现,也就是说MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性

    • v-model:

    一方面model层通过defineProperty来劫持每个属性(监听每一个属性的get和set方法),一旦检测到变化,通过相应的页面元素更新

    另一方面通过编译模板文件,为控件的v-model绑定input事件,从而页面输入能实时的更新相关data属性值

    3. Vue数据响应式原理和双向绑定的原理是什么?(v-model)

    • 数据劫持:Object.defineProperty()方法设置set和get函数来实现数据的劫持
    • 依赖收集:渲染页面时碰到插值表达式,v-bind等需要数据等地方,会实例化一个watcher,watcher在读取数据的时候,从而触发getter,数据的getter函数就会添加依赖自己的watcherwatcher在收集依赖但是代码实现方式是依赖存储自己的watcher
    • 派发更新:数据改变set函数触发watcher的回调函数
    • 总的来说,data传入Observer转成get/set形式;当Watcher实例读取数据时,会触发getter,被收集到Dep仓库中;当数据更新时,触发setter,通知Dep仓库中的所有Watcher实例更新,Watcher实例负责通知外界
    请添加图片描述

    vue数据的双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。其核心就是通过Object.defineProperty()方法设置set和get函数来实现数据的劫持,在数据变化时发布消息给调度中心,调度中心给订阅者(watcher),触发相应的监听回调($on的fn函数)。

    • 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
    • 实现一个订阅者Watcher,每个Watcher都绑定一个更新函数,Watcher可以收到属性的变化通知并执行相应的函数,从而更新视图
    • 实现一个消息订阅器 Dep ,主要收集订阅者,订阅者Watcher有多个,所以需要一个消息订阅器 Dep 来专门收集这些订阅者,在监听器Observe和订阅者Watcher之间进行统一管理。当 Observe监听到发生变化,就通知Dep 再去通知Watcher去触发更新。
    • 通过编译模板文件,在此中创建watcher实列来更新视图上的值,为控件的v-model绑定input事件,从而页面输入能实时的更新相关data属性值

    4.Object.defineProperty和Proxy的区别

    1、Proxy 可以直接监听对象而非属性;

    2、Proxy 可以直接监听数组的变化;

    3、Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的

    4、Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty 只能遍历对象属性直接修改

    5、Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利

    6、Object.defineProperty 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写

    5. Vue生命周期总共分为几个阶段?

    created:只是在内存里创建好了—>mounted:渲染到浏览器上—>updated:更新—>destroyed:销毁

    截屏2022-09-19 17.59.02
    • beforeCreate(创建前)

      实例还为创建好,不能访问到data、computed、watch、methods上的方法和数据。

    • created(创建后)

      实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。

    • beforeMount(挂载前)

      vue 实例的 el和data都已初始化,挂载虚拟dom节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中因此‘el 和 data 都已初始化,挂载虚拟 dom节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中 因此 `el和data都已初始化,挂载虚拟dom节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中因此‘el` 在这个阶段仍然不可用。

    • mounted(挂载后)

      组件已安装并显示在页面上, $el 可被正常使用,在此阶段可以从 Vue 访问和操作 DOM。

    • beforeUpdate(更新前)

      响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。

    • updated(更新后)

      当 data 变化时,会触发 updated 方法。页面和 data 数据已经保持同步了。在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

    • beforeDestory(销毁前)

      实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。

    • destoryed(销毁后)

      组件销毁之后调用,对 data 的改变不会再触发周期函数,vue 实例已解除事件监听和 dom绑定,但 dom 结构依然存在。

    6. 第一次加载页面会触发哪几个钩子函数?

    当页面第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子函数

    7. 请说下封装 Vue 组件的过程

    vue组件的功能

    1)能够把页面抽象成多个相对独立的模块

    2)实现代码重用,提高开发效率和代码质量,使得代码易于维护

    Vue组件封装过程

    ● 首先,使用Vue.extend()创建一个组件

    ● 然后,使用Vue.component()方法注册组件

    ● 接着,如果子组件需要数据,可以在props中接受定义

    ● 最后,子组件修改好数据之后,想把数据传递给父组件,可以使用emit()方法

    8.Vue组件如何进行传值的?

    • 父传子

      • 在父组件可以通过"v-bind:参数名"或者":参数名"的形式传递参数,参数值可以为任何数据类型,并且在子组件中不能更改父组件传递过来的数据。

        // 父组件部分,向组件level1传递两个参数,test和temp
        
        
      • 在子组件使用props接收父组件传递过来的参数

        props: {test: Number,temp: String},
        props: ['test', 'temp'],
        
    • 子传父(使用$emit和v-on的组合)

      // 子组件执行this.$emit
      mounted() {this.testClick()
      },
      methods: {testClick() {// 触发当前实例上的事件。附加参数都会传给监听器回调。this.$emit('testCallback', [1, 2, 3, 4, 5])}
      }
      
      // 父组件接收this.$emit传来的自定义事件和值
      template>
      methods: {receiveCall(e) {console.log('接收到值了', e)}
      }
      
    • 兄弟组件之间数据共享的方案是EventBus:总线

    IMG_5413

    // main文件中记得创建vue对象
    Vue.prototype.$bus = new Vue()

    9. 虚拟DOM、differ算法、key的作用

    • 什么是虚拟Dom

    虚拟dom就是一个普通的js对象,是一个用来描述真实dom结构的js对象

    虚拟dom的结构,他是一个对象,下面有6个属性,sel表示当前节点标签名,data内是节点的属性,elm表示当前虚拟节点对应的真实节点(这里暂时没有),text表示当前节点下的文本,children表示当前节点下的其他标签

    • 虚拟dom作用

    虚拟dom可以很好的跟踪当前dom状态,因为他会根据当前数据生成一个描述当前dom结构的虚拟节点树(vnodes),然后数据发生变化时,又会生成一个新的虚拟节点树,而这两个虚拟dom恰恰保存了变化前后的状态。然后通过diff算法,计算出两个前后两个虚拟节点树之间的差异,得出一个更新的最优方法(哪些发生改变,就更新哪些)。可以很明显的提升渲染效率以及用户体验

    • 过程

    h函数就是在render函数内运行的,vue在created–>beforeMount之间的时候通过将模板编译成render函数,其实就是将模板编译成某种格式放在render函数内,然后当render函数运行得时候,就会生成虚拟节点(vnode)。

    differ算法

    • 规则:diff 比较两个虚拟dom只会在同层级之间进行比较,不会跨层级进行比较。而用来判断是否是同层级的标准就是
      • 是否在同一层
      • 是否有相同的父级
      • 在这里插入图片描述
    • diff是采用先序深度优先遍历得方式进行节点比较的,即,当比较某个节点时,如果该节点存在子节点,那么会优先比较他的子节点,直到所有子节点全部比较完成,才会开始去比较该节点的同层级节点。
      • 比较分三部分
        • 第一部分patch函数:比较根节点(key和tag(标签名)必须都相同),执行第二步
        • 第二部分patchVnode:这两个节点值得比较,比较两个节点的属性是否相同,节点是否存在文本,文本是否相同。是否存在子节点,子节点是否相同。
          • 1、如果存在文本时,更新文本
          • 2、如果存在属性时,更新属性
          • 3、如果存在子节点时,更新子节点:只有当都存在子节点时,并且oldVnode === newVnode 为false时。会执行updateChildren函数
        • 第三部分updateChildren:比较某个节点下的子节点差异
          • Vnode的子节点VcholdVnode的子节点oldCh提取出来
          • oldChvCh各有两个头尾的变量StartIdxEndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较。一旦StartIdx>EndIdx表明oldChvCh至少有一个已经遍历完了,就会结束比较。
            • 四种比较方式都没有一对成功,就会使用key值比较,会根据oldChild的key生成一张hash表,用newchild的S的key与hash表做匹配,匹配成功就判断S和匹配节点是否为sameNode
              • 如果是,就在真实dom中将成功的节点移到最前面
              • 否则,将S生成对应的节点插入到dom中对应的oldS位置
            • 就算指针中间有可复用的节点都不能被复用了

    Key值的作用

    1、key的作用主要是为了高效的更新虚拟dom,其原理是在patch比较过程中通过key可以精准判断两个节点是否是同一个,使得整个patch过程更加高效,减少dom操作量,提高性能。

    • v-for中:

      • 提升v-for渲染的效率

      • 提高渲染性能:就是避免产生不必要的dom

      • 避免数据混乱的情况出现

    10.keep-alive原理

    • 区别:

    Vue的渲染是从render阶段开始的,但keep-alive的渲染是在patch阶段。

    当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。

    • 作用:

      在组件切换过程中 把切换出去的组件保留在内存中, 防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性

    • 结论:

    组件一旦被 缓存,那么再次渲染的时候就不会执行 created、mounted 等钩子函数。使用keepalive组件后,被缓存的组件生命周期会多activated和deactivated 两个钩子函数,它们的执行时机分别是 包裹的组件激活时调用和停用时调用。

    11. 组件中写 name 选项有什么作用?

    • 当项目使用keep-alive时,可搭配组件name进行缓存过滤

    我们有个组件命名为detail,其中dom加载完毕后我们在父组件的mounted中进行数据加载

    因为我们在App.vue中使用了keep-alive导致我们第二次进入的时候页面不会重新请求,即不会触发mounted函数。

    有两个解决方案:

    一个增加activated()函数,每次进入新页面的时候再获取一次数据。

    还有个方案就是在keep-alive中增加一个过滤,如下图所示:

    • DOM做递归组件时(组建自己调用自己)

    • 当你用vue-tools时

    vue-devtools调试工具里显示的组见名称是由vue中组件name决定的

    12. Vue 组件 data 为什么必须是函数

    因为当data是函数时,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,实例化几次就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。

    简单来说,就是为了保证组件的独立性和可复用性,如果data是个函数的话,每复用一次组件就会返回新的data,类似于给每个组件实例创建一个私有的数据空间,保护各自的数据互不影响。

    13. Vue-router是干什么的,原理是什么?

    (1)vue-router是什么

    SPA(单页应用)的路径管理器,路由用于设定访问路径,并将路径和组件映射起来

    路由模块的本质 就是建立起url和页面之间的映射关系

    (2)vue-router实现原理

    vue中的vue-router是通过hashhistory两种模式实现前端跳转路由,"更新视图但不重新请求页面”是前端路由原理的核心之一,实现主要有两种方式

    1. hash ---- 利用URL中的hash(“#”)

      hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。

      Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据

      hash 模式的原理是 onhashchange 事件(监测hash值变化),可以在 window 对象上监听这个事件,然后解析当前的url根据不同规则展示页面

    2. 利用**History** interface在 HTML5中新增的方法

      pushState() 和 replaceState() 方法。它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求。

    14. 路由之间是怎么跳转的?有哪些方式?

    (1)router-link声明式导航 router-link来替代普通连接

    • 不带参数
     //name,path都行, 建议用name
    
    • 带参数

      • params传参数 (类似post)
      
      // 路由配置 path: "/home/:id" 或者 path: "/home:id"
      // 不配置path ,第一次可请求,刷新页面id会消失
      // 配置path,刷新页面id会保留// html 取参 $route.params.id
      // script 取参 this.$route.params.id
      
      • query传参数 (类似get,url后面会显示参数)
      
      // 路由可不配置// html 取参 $route.query.id
      // script 取参 this.$route.query.id
      

    (2)this.$router.push() 编程式导航

    • 不带参数

      this.$router.push('/home')
      this.$router.push({name:'home'})
      this.$router.push({path:'/home'})
      
    • 带参数

      • query传参(get)
      this.$router.push({name:'home',query: {id:'1'}})
      this.$router.push({path:'/home',query: {id:'1'}})
      
      • params传参(post)
      this.$router.push({name:'home',params: {id:'1'}}) // 只能用 name
      // 路由配置 path: "/home/:id" 或者 path: "/home:id" ,
      // 不配置path ,第一次可请求,刷新页面id会消失
      // 配置path,刷新页面id会保留
      

    (3)其他

    this.$router.replace() (用法同上,push)
    this.$router.go(n) ()
    this.$router.go(n)
    向前或者向后跳转n个页面,n可为正整数或负整数区别:
    this.$router.push
    跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面this.$router.replace
    跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)this.$router.go(n)
    向前或者向后跳转n个页面,n可为正整数或负整数
    

    15. 怎么在组件中监听路由参数的变化?

    路由参数发生了变化,并不会引起触发Vue的生命周期,所以就不能通过生命周期的钩子来实现请求后端数据。

    方式一:watch监听 $route

      watch:{'$route' (to, from) {// 想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象console.log(to, from)}
    },
    

    方式二:通过组件内的导航守卫,beforeRouteUpdate , (和created(){}生命周期函数同级别);

    beforeRouteUpdate (to, from, next) {console.log('to:',to)console.log('from:', from)next();// 记得next()
    },
    

    16. $route 和 $router 的区别是什么?

    • $route 是 “路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。

    • $router 为 VueRouter 的实例,相当于一个全局的路由器对象,里面包含有很多属性和子对象,例如 history 对象,经常使用的跳转链接就可以用 this.router.push 会往 history 栈中添加一个新的记录,返回上一个 history 也是使用 $router.go 方法。

    17. 怎么定义Vue-router的动态路由?怎么获取传过来的动态参数?

    某个模式匹配到的所有路由,全都映射到同一个组件,例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染,那么我们可以在vue-router的路由路径中使用 “动态路由参数” 来达到这个效果

    动态路径参数,使用 “冒号” 开头,一个路径参数,使用冒号标记,当匹配到一个路由时,参数会被设置到 this.$route.params 中,并且可以在每个组件中使用

    this.$router.push({name:"路由地址",params:{itemId:,}
    });{path: '/item/:itemId',name: 'item',component: Item
    }{{item.name}}
    

    18. Vue-router有哪几种路由守卫?

    (1)全局守卫(进入任何一个路由都会执行)

    • beforeEach:进入路由前执行

      • 回调函数中的参数,to:进入到哪个路由去,from:从哪个路由离开,next:函数,决定是否展示你要看到的路由页面。
      • 在main.js中,有一个路由实例化对象router。在main.js中设置守卫已是全局守卫。如下,判断to.path当前将要进入的路径是否为登录或注册,如果是就执行next(),展示当前界面。如果不是,就弹出alert,然后移至登录界面。这样就可实现,用户在未登录状态下,展示的一直是登录界面。
      router.beforeEach((to,from,next)=>{if(to.path == '/login' || to.path == '/register'){next();}else{alert('您还没有登录,请先登录');next('/login');}
      })
      
    • afterEach(这个的回调参数没有next):导航完成时执行

      • main.js文件中
      router.afterEach((to,from)=>{if(to.path === "/news"){alert("进来news了哦");}
      })
      
    • beforeResolve:路由解析之前 这个几乎不用

    (2)路由守卫(进入某个路由才会执行):路由守卫就是路由跳转的时候会触发的钩子函数我们把他称为路由守卫

    • beforeEnter:进入该路由之前

      [{path:'/home',component: Home,beforeEnter:(to,from,next)=>{...}}
      ]
      

    (3)组件内置守卫

    • beforeRouteEnter:进入组件时
    • beforeRouteUpdate:路由不变,传递的参数改变
    • beforeRouteLeave: 离开组件前
    {data(){return {}},beforeRouteEnter(to,from,next){...next()}
    }
    

    19. watch、methods 和 computed 的区别

    (1)methods、watch、computed的区别

    • computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算,主要当作属性来使用

      • computed默认使用的是getter属性
        • computed 的属性可以被视为是 data 一样,可以 getter(读取)和 setter(设值),默认情况下 computed 预设只有 getter , 只能读取,不能改变设值。
      • 当一个数据受多个数据影响时,可以使用computed
        • 本组件计算
        • 计算props的值
        • 计算vuex的state或者getters值的变化
    • methods 方法表示一个具体的操作,主要书写业务逻辑;

    • watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是 computed 和 methods 的结合体;

    (2)Computed与methods对比

    同样都可以达到计算属性的效果
    1 . computed是属性调用(computed是个属性结果),而methods是函数调用
    2 . computed带有缓存功能,而methods不会被缓存

    (3)watch

    1.watch是观察某一个属性的变化,重新计算属性的值。

    2.computed是通过所依赖的属性的变化计算属性值。

    3.大部分下watch和computed是没有区别的,但是如果在数据变化的同时进行异步操作的情况下,watch则是最好的选择

    (4)加载顺序不同

    1. computed 是在 HTML,DOM 加载后马上执行的,如赋值;(属性将被混入到Vue 实例)
    2. methods 则必须要有一定的触发条件才能执行,如点击事件
    3. watch 呢?它用于观察 Vue 实例上的数据变动。
    4. 所以不执行 methods的默认加载顺序是: 先computed再watch
    5. 触发某一事件后: 先computed再methods,最后到watch

    20. 怎么在watch监听开始之后立即被调用?

    watch里面有个immediate属性,它的值是一个Boolean类型的数据,将它设置为 true就可以在 watch 监听开始之后就立即被调用

    let vm=new Vue({el:"#first",data:{msg:'liuneng'},watch:{msg:{handler (newMsg,oldMsg){console.log(newMsg);},immediate:true}}})
    

    21. watch怎么深度监听对象变化?

    1、有个原则监听谁,写谁的名字,然后是对应的执行函数, 第一个参数为最新的改变值,第二个值为上一次改变的值, 注意: 除了监听 data,也可以监听计算属性 或者一 函数的计算结果

    2、启用深度监听对象

    watch:{a:{handler:function(val,oldval){}deep:true}
    }
    

    22. computed中的属性名和data中的属性名可以相同吗?

    不能同名,因为不管是computed属性名还是data数据名还是props数据名都会被挂载在vm实例上,因此这三个都不能同名

    23. 什么是Vue的计算属性

    vue中的计算属性 computed 一种对数据进行复杂处理的方式,使得数据处理结构清晰

    有下面一些好处:

    1、 依赖于数据,数据更新,处理结果自动更新;

    2、 计算属性内部 this 指向 vm 实例;

    3、 在 template 调用时,直接写计算属性名即可;

    4、 常用的是 getter 方法,获取数据,也可以使用 set 方法改变数据;

    5、 相较于 methods,不管依赖的数据变不变,methods 都会重新计算,但是依赖数据不变的时候 computed 从缓存中获取,不会重新计算。

    6、 computed属性的结果会被缓存

    24. 怎么捕获Vue组件的错误信息?

    一、使用errorCaptured

    是组件内部钩子,当捕获一个来自子孙组件的错误时被调用,接收error、vm、info三个参数,return false后可以阻止错误继续向上抛出

    (错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串)

    二、使用errorHandler 为全局钩子

    使用 Vue.config.errorHandler 配置,接收参数与 errorCaptured 一 致,2.6 后可捕捉 v-on 与 promise链的错误,可用于统一错误处理与错误兜底

    25. Vue组件里的定时器要怎么销毁?

    • 第一种:beforeDestroy()生命周期内清除定时器:

      • 首先我在data函数里面进行定义定时器名称:

         data() {                   return {                                          timer: null  // 定时器名称                 }         },
        
      • 然后这样使用定时器:

        this.timer = (() => {     // 某些操作 }, 1000)
        
      • 最后在beforeDestroy()生命周期内清除定时器:

        beforeDestroy() {         clearInterval(this.timer);                 this.timer = null;
        }
        
      • 缺点:

        • 需要在这个组件实例中保存这个 timer,如果可以的话最好只有生命周期钩子可以访问到它。
        • 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化的清理我们建立的所有东西。
    • 第二种:通过$once这个事件侦听器器在定义完定时器之后的位置来清除定时器。

    const timer = setInterval(() =>{ // 某些定时器操作   }, 500);            
    // 通过$once来监听定时器,在beforeDestroy钩子可以被清除。 
    this.$once('hook:beforeDestroy', () => {                     clearInterval(timer);                                     
    })
    

    26. Vue中solt的使用方式,以及solt作用域插槽的用法

    • 什么是插槽:

    插槽就是子组件中的提供给父组件使用的一个占位符,在子组件中用< slot>< /slot > 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的< slot>< /slot >标签。

    插槽包括默认插槽、具名插槽和作用域插槽

    • 具名插槽总结

    在定义好slot插槽后,在需要展示的标签中加上 slot=“name”,即可在指定的位置上显示需要显示的内容

    • 作用域插槽的使用
    IMG_7465

    作用域插槽其实就是带数据的插槽,父组件接收来自子组件的slot标签上通过v-bind绑定进而传递过来的数据,父组件通过scope来进行接受子组件传递过来的数据

    27. Vue常用的修饰符都有哪些

    (1)事件修饰符

    对事件捕获以及目标进行处理:

    • stop:阻止事件的冒泡,相当于调用了event.preventPropagation方法

    • prevent:阻止了事件的默认行为。有了.prevent,点击提交后会执行@click绑定的事件后再提交

    • once:绑定了事件以后只能触发一次,第二次就不会触发

    • capture:事件捕获,从顶层往下触发

    • passive:用于提升移动端scroll事件的性能。在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符。

      
      
      
      
      ...
    • native:如果在自定义组件标签上绑定原生事件,则需要加上.native。

    (2)v-bind修饰符

    sync:实现子组件props的双向绑定

    与v-model的区别:

    一个组件可以多个属性用.sync修饰符,可以同时"双向绑定多个“prop”,而并不像v-model那样,一个组件只能有一个。

    v-model针对更多的是最终操作结果,是双向绑定的结果,是value,是一种change操作。

    v-bind.sync针对更多的是各种各样的状态,是状态的互相传递,是status,是一种update操作。

    //父组件
     
    //子组件
    this.$emit('update:myMessage',params);
    

    (3)表单修饰符

    用在input标签上的v-model指令后,如v-model.lazy=“value”

    • lazy(当光标离开标签时,才会将值赋值给value)
    • trim(过滤掉两边的空格)
    • number(自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值)

    28. v-show和v-if指令的共同点和不同点?

    1. 共同点
    用来控制DOM元素的显示或隐藏

    2. 区别

    • v-show指令:元素始终被渲染到HTML,它只是简单的设置css的style属性来决定实现显示还是隐藏,当不满足条件的元素被设置style=“display:none”的。
    • v-if指令:v-if是动态的向DOM树内添加或删除DOM元素;满足条件是会渲染到html中,不满足条件时是不会渲染到html中的,是通过操纵dom元素来进行切换显示。(创建删除)

    3. 应用场景:

    • 如果要频繁切换某节点,使⽤v-show(切换开销⽐较⼩,初始开销较⼤);
    • 如果不需要频繁切换某节点使⽤v-if(初始渲染开销较⼩,切换开销⽐较⼤)。

    29. 为什么避免 v-if 和 v-for 用在一起

    因为v-for的优先级比v-if的优先级高,所以每次渲染时都会先循环再进行条件判断,而又因为v-if会根据渲染条件为ture或false来决定渲染与否的,所以如果将v-if 和 v-for 用在一起会特别消耗性能

    解决方法:

    将v-if放在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环

    30. Vue-loader是什么?使用它的用途有哪些?

    它就是webpack的一个加载器,主要作用就是解析和转换.vue文件,提取vue中的逻辑代码,style样式和html模板,再分别交给对应的loader去处理
    简单来说,就是把你写的vue代码转换成浏览器能识别的,然后还包含了一些打包和编译的功能

    31. Vue 中怎么自定义过滤

    过滤器是对即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据
    vue中的过滤器分为两种:全局过滤器局部过滤器

    • 全局过滤器
    Vue.filter('formatePrice', (val) => {return val.toFixed(2)
    })
    // 使用
    {{13.44 | formatePrice}} 
    
    • 局部过滤器
    
    

    在vue3中移除了filter过滤器, 功能需要可以使用计算属性代替

    32.Vuex是什么?为什么要用它?

    • 是什么?

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理库。它采用集中式存储 管理 所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

    • 作用:
    1. 我们在使用Vue开发项目中,经常会有通过路由传递参数时,由于参数过多会出现会导致400 Bad Request,vuex可以解决这个问题,

    2. 解决多个组件共享状态时,单向数据流的简洁性很容易被破坏的问题

    3. 解决多个视图依赖同一状态的问题

    4. 解决了在中大型项目中,数据太多时,只需要在vuex更改处理数据,不用去每个页面更改

    • 数据传递流程:
    1. 通过new Vuex.Store()创建一个仓库 state是公共的状态,state—>components渲染页面

    2. 在组件内部通过this.$store.state.属性 来调用公共状态中的state,进行页面的渲染。

    3. 当组件需要修改数据的时候,必须遵循单向数据流。通过this.$store.dispatch来触发actions中的方法

    4. actions中的每个方法都会接受一个对象 这个对象里面有一个commit方法,用来触发mutations里面的方法

    5. mutations里面的方法用来修改state中的数据 mutations里面的方法都会接收到2个参数 一个是store中的state另外一个是需要传递到参数

    6. 当mutations中的方法执行完毕后state会发生改变,因为vuex的数据是响应式的 所以组件的状态也会发生改变

    • 原理实现
    let Vue;class Store{constructor(options){// options就是new Vuex.Store传入的参数// 1.使用vue实列来实现响应式变化,保证状态更新会刷新视图this.vm = new Vue({//1.data会被使用Object.defineProperty重新定义date: {state: options.state}});// 2.解析options.getters,它是一个对象,里面都是函数this.getters={};Object.keys(options.getters).forEach(getterName=>{Object.definePropery(this.getters, getterame,{get:()=>{return options.getters[getterName](this.state)}})});//3.解析options.mutationsthis.mutations = {};Object.keys(options.mutations).forEach(mutationsName=>{this.mutations[mutationsName] = (payload)=>{options.mutations[mutationsName](this.state, payload)}});// 4.解析options.actionsthis.actions = {};Object.keys(options.actions).forEach(actionsName=>{this.actions[actionsName] = (payload)=>{options.actions[actionsName](this, payload)}});}get state(){ //1.获取实例上的state——>store.state// 1.相当于this.state = this.vm.statereturn this.vm.state}// 3.用箭头函数的目的是让this一直指向Store——>$store.commitcommit = (mutationName, payload) => {this.mutations[mutationsName](payload)};// 4.$store.dispatchdispatch = (actionName, payload) =>{this.actions[actionsName](payload)}
    }// 每一个插件都有一个install函数,主要就是只有当前的实例才能使用这个插件
    const install = (_Vue) => { //_Vue是vue的构造函数// 使得当前插件不再依赖Vue而是通过用户把Vue传过来,import vue打包的时候体量太大?Vue = _Vue; //传过来的vue的构造函数// 下载不是下载到vue的原型上,我们只想给当前实列下的组件使用// 使用mixin,抽离组件的公共逻辑,每个组件(vue)调用beforeCreate都会执行里面的方法// 值得注意的是,main.js(父)有个vue,app.vue也有个vue(子)Vue.mixin({beforeCreate(){// console.log(this.name)//root-->app-->app的子...// 为了让所有子组件使用store,就把root的store属性放在每个组件的实列上if(this.$options.store){//是root,这个root是有store的this.$store = this.$options.store}else{// 是子组件this.$store = this.$parent && this.$parent.$store}}}) 
    }export default{Store,install
    }
    

    33.Vuex的Mutation和Action之间的区别是什么

    1、流程顺序

    “相应视图—>修改State”拆分成两部分,视图触发Action,Action再触发Mutation

    2、角色定位

    基于流程顺序,二者扮演不同的角色

    1)Mutation:专注于修改State,理论上是修改State的唯一途径

    2)Action:业务代码、异步请求

    3、限制

    1)角色不同,二者有不同的限制

    2)Mutation:必须同步执行

    3)Action:可以异步,但不能直接操作State

    34. 编程式导航使用的方法以及常用的方法

    1、路由跳转:this.$router.push()

    2、路由替换: this.$router.replace()

    3、后退: this.$router.back()

    4、前进 :this.$router.forward()

    35. Vue怎么实现跨域

    1. 什么是跨域

    ​ 跨域指浏览器不允许当前页面的所在的源去请求另一个源的数据。源指协议、端口、域名。只要这三个中有一个不同就是跨域。

    由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制,防止他人恶意攻击网站

    浏览器采用同源策略,在没有明确授权的情况下,禁止页面加载或执行与自身不同源的任何脚本。

    2. 解决问题

    • Websocket

      Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。 同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

    • 使用 vue-cli 脚手架搭建项目时 proxyTable 解决跨域问题

      • vue.config.js文件中
      proxyTable: {'/api': {                            //使用"/api"来代替"http://f.apiplus.c"target:'http://f.apiplus.cn',      //源地址changeOrigin: true,                //改变源pathRewrite: {'^/api': 'http://f.apiplus.cn'     //路径重写}}
      }
      
    • 使用 CORS(跨域资源共享)

      • 前端设置
        • vue 设置 axios 允许跨域携带 cookie(默认是不带 cookie) axios.defaults.withCredentials = true;
      • 后端设置:
        • 跨域请求后的响应头中需要设置
        • Access-Control-Allow-Origin 为发起请求的主机地址
        • Access-Control-Allow-Credentials,当它被设置为 true 时,允许跨域 带 cookie,但此时 Access-Control- Allow-Origin 不能为通配符*
        • Access-Control-Allow-Headers,设置跨域请求允许的请求头
        • Access-Control-Allow-Methods,设置跨域请求允许的请求方
    • Jsonp

      • 原理:img、script等标签的src属性,不会收到同源策略的限制,jsonp请求数据时,服务端一般返回的是一段可执行的javascript代码,想要使用数据就要在后面加一个callback函数

      • 局限:只能用来进行get请求,需要服务端做一些处理、配合

      • 步骤:

        • 创建一个script标签
        • script的src属性设置接口地址
        • 接口参数,必须要带一个自定义函数名 要不然后台无法返回数据。
        • 通过定义函数名去接收后台返回数据
        //去创建一个script标签
        var script = document.createElement("script");
        //script的src属性设置接口地址 并带一个callback回调函数名称
        script.src = "HTTP://127.0.0.1:8888/index.php?callback=jsonpCallback";
        //插入到页面
        document.head.appendChild(script);
        //通过定义函数名去接收后台返回数据function jsonpCallback(data){
        //  注意 jsonp返回的数据是json对象可以直接使用
        //  Ajax 取得数据是json字符串需要转换成json对象才可以使用。
        //}
        

    36. Vue中动画如何实现

    (1)使用vue内置组件transition

    • 官网解释

    • 包裹要实现动画的组件

    (2)用animate.css插件

    • 首先需要使用npm install animate.css命令下载插件,在node_moudles里面找到animate.css样式。

    • 动画插件可能会在整个项目中使用,所以我在main.js文件中引入,接着在需要使用插件的元素外面添加transition标签,在标签内写enter-active-class和leave-active-class两个属性,其中这里的slideInRight和bounceOutRight代表动画名称,animated表示动画过渡时间

    37. 你对Vue.js的template编译的理解?

    简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点)

    详情步骤:
    首先,把template解析成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式)。然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)然后放入render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)

    38. runtime-compiler和runtime-only的区别

    runtime-compiler的步骤:
    template -> ast -> render -> virtual dom -> 真实dom

    runtime-only的步骤
    render -> virtual dom -> 真实dom

    39.vue渲染模板时怎么保留模板中的HTML注释呢?

    
    

    40. Vue2.0兼容IE哪个版本以上吗

    不支持ie8及以下,部分兼容ie9 ,完全兼容10以上,因为vue的响应式原理是基于es5的Object.defineProperty(),而这个方法不支持ie8及以下

    41. 说一下你在Vue中踩过的坑

    (1)是给对象添加属性的时候,直接通过给data里面的对象添加属性然后赋值,新添加的属性不是响应式的

     data() {return {student: {name: 'al',age: {realAge: 27,virtualAge: 25}}}},
    

    接将 sex 属性添加到 data 中,发现sex属性没有立刻响应到页面(因为没有get 和 set 函数)

    vm.data.student.sex = '男'
    

    vm.$set() 这个方法 和 Vue.set() 方法的作用完全一样,只不过一个是挂载在 Vue上的全局api,一个是挂载在 vm实例上

    Vue.set(vm.student,'sex','男')
    vm.$set(vm.student,'sex','男') 
    

    局限性:不能直接在data里面加,因为data是一个函数

    使用 set() 方法添加属性时,不能直接添加到 vm实例对象上,也不能直接添加到根数据data中,需要添加到 data 中的某一个对象上才能生效

    (2)在created操作dom的时候,是报错的,获取不到dom,这个时候实例vue实例没有挂载

    解决:Vue.nextTick(回调函数进行获取)

    **例子:**在method中数据一更新就立刻操作dom,dom不显示。这时候数据触发的事beforupdate()还没有渲染到DOM

    42. Vue项目优化的解决方案都有哪些

    • 第一个代码层面的优化:

      • 对于图片过多的页面,为了加快页面加载速度,在项目中使用 Vue 的 vue-lazyload 插件

      • 对于路由过多,加载的资源过多的情况下,就把不同路由对应的组件分割成不同的代码块,使用路由懒加载

      • 还可以利用keep-alive将不活动的组件缓存起来。在组件切换过程中将状态保留在内存中,防止重复渲染dom,减少加载时间及性能消耗

    • 第二个从webpack 层面的优化:

    • 在请求较大的图片资源的时候,加载会很慢,这时候我们可以用 image-webpack-loader 来压缩图片

    • 在打包时,可以直接把组件中的模板转换为render函数,这叫做模板预编译。这样一来,运行时就不再需要编译模板了,提高了运行效率。

    • 第三个Web 技术层面的优化:

      • 为了提高用户加载页面的速度,会对静态资源进行缓存

    43. 使用Vue的时候一下加载造成页面卡顿,该如何解决?

    1、像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时, 需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了loading也是不利于用户体验,

    2、而运用懒加载 则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时。

    3、配置路由的时候使用(路由懒加载)

    4、使用进度条、骨架屏

    component:() => import('@/page/dashboard/index'),
    

    44. 你知道style上加scoped属性的原理吗?

    1.什么是scoped

    在Vue组件中,为了使样式私有化(模块化),不对全局造成污染,可以在style标签上添加scoped属性以表示它的只属于当下的模块,局部有效。如果一个项目中的所有vue组件style标签全部加上了scoped,相当于实现了样式的私有化。如果引用了第三方组件,需要在当前组件中局部修改第三方组件的样式,而又不想去除scoped属性造成组件之间的样式污染。此时只能通过穿透scoped的方式来解决,选择器。

    2.原理

    Vue 中的 scoped 属性的效果主要通过 PostCSS 转译实现, PostCSS 给当前组件下的所有 dom 添加了一个唯一不重复的动态属性[ data-v-xxxx],而其他组件就不会添加[data-v-xxxx]属性

    如果引用了第三方组件,需要在当前组件中局部修改第三方组件的样 式,而又不想去除 scoped 属性造成组件之间的样式污染。使用 sass 或 less 的样式穿透 /deep/ 。

    45.说说你对SPA单页面的理解,它的优缺点分别是什么?

    SPA( single page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;而页面的变化是利用路由机制实现 HTML 内容的变换,避免页面的重新加载。

    第一次给服务器发送请求,服务器会给你返回一个页面,之后发送请求不会给你发送页面而是给你发送数据,页面根据数据自动更新

    SPA 单页面的优点

    • 1). 用户体验好,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
    • 2). 减少了不必要的跳转和重复渲染,这样相对减轻了服务器的压力
    • 3). 前后端职责分离,架构清晰,显示逻辑存储在客户端,服务端只提供数据

    SPA 单页面的缺点

    • 1). 初次加载耗时多
    • 2). 不能使用浏览器的前进后退功能,由于单页应用在一个页面中显示所有的内容,所以,无法前进后退
    • 3). 不利于搜索引擎检索:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。多亏了浏览器的histroy机制,我们用hash的变化从而可以实现推动界面变化,从而模拟元素客户端的单页面切换效果:

    46. 怎样理解 Vue 的单向数据流

    1、数据从父级组件传递给子组件,只能单向绑定

    2、子组件内部不能直接修改从父级传递过来的数据

    3、所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行,这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

    4、每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值,这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告

    5、子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改

    47. Vue中数组/对象更新后视图不触发更新原因

    直接修改数组元素是无法触发视图更新(修改对象属性可以),改length也没用

    Object.defineProperty 不能监听原生数组的变化。如需监听数组,要将数组转成对象。

    直接给对象添加元素也不会触发视图更新(数组可以pop,push可以)

    template 在编译的过程中,读取什么数据,才会触发相应变量的get,然后添加watcher。

    数组和对象都无法直接使用delete

    触发视图更新的方法还有:

    • Vue.set:可以设置对象或数组的值,通过key或数组索引,可以触发视图更新(解决数组修改元素和对象增加元素)

      • Vue.set(array, indexOfItem, newValue)
        Vue.set(obj, keyOfItem, newValue)
        
    • Vue.delete:删除对象或数组中元素,通过key或数组索引,可以触发视图更新

    • Vue提供了如下的数组的编译方法,可以触发视图更新(解决数组)

      • 是会改变当前的数组,它们分别是push()、pop()、shift()、unshift()、splice()、sort()、reverse()

        • this.array.splice(indexOfItem, 1, newElement)
          
      • 是不会改变当前的数组,返回一个新数组,它们是filter()、concat()、 slice()、map()。

        • this.array = this.array.filter(...)
          this.array = this.array.concat(...)
          this.array = this.array.map(...)
          
    • Object.assign

      	// 关于对象this.obj = Object.assign( this.obj , {a: 1, b: 2}) // 不会触发更新this.obj = Object.assign( {} , this.obj , {a:1, b:2} ) // 触发更新
      

    48. Vue中如何重置data(初始化 data 中的数据)

    Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    用法: Object.assign(target, …sources)

    第一个参数是目标对象,第二个参数是源对象,就是将源对象属性复制到目标对象,返回目标对象

    其中就是将一个对象的属性copy到另一个对象

     // this.$data 获取的是当前状态下的data对象 // this.$options.data() 获取该组件初始状态下的data// Object.assign 则可以将一个对象赋值给另一个对象// 所以,下面就可以将初始状态的data复制到当前状态的data,实现重置效果:
    Object.assign(this.$data, this.$options.data.call(this))
    //data()中若使用了this来访问props或methods,在重置$data时,注意this.$options.data()的this指向,最好使用this.$options.data.call(this)。
    

    注意: 这里调用options.data 时请用call调用并传入当前this,不这么做的话默认的this可能会指向全局vue对象,这就会导致它报错

    49. Vue 的 nextTick 的原理是什么?

    是什么:主要是获取更新后的Dom(onload完成后获取),将回调延迟到下次DOM更新之后执行。微任务

    nextTick的作用

    • 在created中获取dom。
    • 数据更新后,等dom渲染完成后获取最新的dom

    原理:

    当数据发生变化的时候,vue不回立刻更新dom而是开启一个队列把更新函数放入队列中,然后刷新队列,这里是使用了promise就是当同步任务完成后才刷新队列然后执行里面的更新函数。nextTick就是获取刚才的promise,然后用then方法执行nextTick的回调函数。就是在dom更新完成后才会调用nextTick的回调函数。

    50. scss是什么?在Vue-cli中的安装使用步骤是?有哪几大特性?(高薪常问)

    1、基本定义

    SCSS即是SASS的新语法,是Sassy CSS的简写,是CSS3预处理器,SASS是CSS3的一个扩展,增加了规则嵌套、变量、混合、选择器继承等等,通过使用命令行的工具或WEB框架插件把它转换成标准的、格式良好的CSS代码

    2、使用步骤:

    先装css-loader、node-loader、sass-loader等加载器模块

    在build目录找到webpack.base.config.js,在那个extends属性中加一个拓展.scss

    在同一个文件,配置一个module属性

    然后在组件的style标签加上lang属性 ,例如:lang=”scss”

    3、特性:

    可以用变量,例如($变量名称=值)

    可以用混合器,例如()

    可以嵌套

    51观察者模式和发布订阅模式的区别

    从表面上看:

    • 观察者模式里,只有两个角色 —— 观察者 + 被观察者
    • 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 依赖收集器

    往更深层次讲:

    • 观察者和被观察者,是松耦合的关系
    • 发布者和订阅者,则完全不存在耦合

    在观察者模式中,目标与观察者是直接通信的

    而在订阅与发布者模式中,订阅者与发布者之间是通过统一的调度中心来实现数据的通信

    52. 移动端适配问题

    截屏2022-08-24 16.56.50
    • 为什么需要适配?

      • 因为每个移动端的屏幕尺寸和分辨率大小不一致。

      • 让拥有不同屏幕大小的终端设备拥有一致的UI界面。

    • 不同屏幕带来的问题

      • 1px问题
      • 屏幕尺寸不变,如果手机分辨率翻倍,图片小一倍(使用设备独立像素:1个css像素=一个设备像素)
        • 导致图片模糊(1.使用image-set,2.使用img标签的srcset属性,3.使用svg)
    截屏2022-08-24 17.13.24
    • 适配方案:

      • 响应式设计

        • 截屏2022-08-24 17.17.39
      • 自适应设计

        • 截屏2022-08-24 17.20.42
          • 截屏2022-08-24 17.16.04
          • 截屏2022-08-24 17.16.49
            • flexible方案

              • 截屏2022-08-24 17.28.46
              • 引起1px变粗

                截屏2022-08-24 17.34.12
              • 自己弄配置项

            • 使用视口单位

    53. 移动端click延时问题

    移动端会有300ms的延时,原因是移动端屏幕双击会缩放。

    解决:

    • 禁用缩放。禁用浏览器默认的双击缩放行为

      
      
    • 利用touch事件自己封装这个事件解决300ms延迟

      • 当手指触摸屏幕,记录当前触摸时间
      • 当手指离开屏幕,用离开的时间减去触摸的时间
      • 如果时间小于150ms,并且没有滑动过屏幕,那么就定义为点击
    • 使用插件。fastclick插件解决300ms延迟

    54. 服务器端渲染(SSR)解决了什么问题

    • 解决seo(搜索引擎优化)的问题(利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。)
    • 解决一些需要内容快速展现的问题,浏览器渲染是先mounted创建完空节点再请求接口,拿到数据,在进行数据的渲染,用户可能会看到一个“闪烁”的过程,而服务器端渲染(SSR)则是将数据与节点整合一起渲染(asyncData),则不会有这个“闪烁”的过程(使首屏加载快)

    55.http url get方式传递数组参数

    • ?id=1&id=2&id=3
      • Get 方式有字数限制
      • 后台获取时,只需要reqeust.getParameterValues(“id”) 获取String数组。
    • ?id[]=1&id[]=2&id[]=3
      • 首先 request.getParameter(“id”),然后通过 String的 split 方法拆成数组

    ES6

    1. ES6的新增方法

    (1)新增声明命令let和const

    • let

      • let声明的变量具有块级作用域,使用var声明的变量不具有块级作用域,防止循环变量变成全局变量
      • 声明的关键字不存在变量提升,原来可以先使用在声明,现在必须先声明在使用
      • 具有暂时性死区,不受外部影响
    • const

      • 作用:声明常量,常量就是值(内存地址)不能变化的量
      • 具有块级作用域
      • 声明常量的时候必须赋初始值
      • 常量赋值后,值不能被更改,数组可以
    • let,const,var区别

      varLetConst
      函数级作用域块级作用域块级作用域
      变量提升不存在变量提升不存在变量提升
      值可以更改值可以更改值不可以更改

    (2)解构赋值

    解构赋值就是从目标对象或数组中提取自己想要的变量

    • 默认值
    let arr=[1,2,3,4,5]
    let [a,b,c,d,e,f=10]=arr //其中的10就是默认值
    console.log(a,b,c,d,e,f);
    //在浏览器中打印出来的是[1,2,3,4,5,10]
    
    • 交换变量
    let arr=[1,2,3,4,5,6]
    let [a,b,c,d,e,f=10] =arr
    console.log(a,b,c,d,e,f) //那么f就被重新赋值 
    //在浏览器中打印出来是[1,2,3,4,5,6]
    

    (3)promise方法

    promise是异步编程的一种方案,解决了地狱回调的问题

    promise 是一个对象,从它可以获取异步操作的的最终状态(成功/失败)

    promise有三种状态:pending 初始状态 fulfilled 成功状态 rejected 失败状态

    Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled或者由pending变成rejected

    (4)新的数据类型symbol

    凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突

    Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象

    (5)箭头函数

    • 箭头函数是匿名函数,不能作为构造函数,不能使用new

      //箭头函数
      let fun=()=>{console.log('我是箭头函数');
      };
      let fc=new fun();
      
    • 箭头函数内没有arguments,可以用展开运算符(剩余参数)...解决

      • arguments:是一个方法调用的集合,是一个伪数组,不是真数组,不具有数组的操作的方法,可以用展开运算符...解决
      function A(a){console.log(arguments); // [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 8 }
      };
      A(1,2,3,4,5,8);
      // let B=(b)=>{
      //   console.log(arguments); //报错
      // };
      // B(2,92,32,32);
      let C=(...c)=>{console.log(c);// [ 3, 82, 32, 11323 ]
      };
      C(3,82,32,11323);
      
    • 箭头函数的this,始终指向父级的上下文(箭头函数的this取决于定义位置父级的上下文,跟使用位置没关系,普通函数this指向调用的那个对象)

      var a=200;
      let obj={a:100,fn:function(){console.log(this.a)},foo:()=>{console.log(this.a)}
      }
      obj.fn();//100
      obj.foo();//200
      
    • 箭头函数不能通过call()、apply()、bind()方法直接修改他的this指向。(call、apply、bind会默认忽略第一个参数,但是可以正常传参)

    (6)set

    Set数据结构,类似数组。所有的数据都是唯一的,没有重复的值。它本身是一个构造函数。

    • Set属性和方法
      • Size() 数据的长度
      • Add() 添加某个值,返回 Set 结构本身。
      • Delete() 删除某个值,返回一个布尔值,表示删除是否成功。
      • Has() 查找某条数据,返回一个布尔值。
      • Clear()清除所有成员,没有返回值。

    (7)for…of 循环

    (8)class

    class类的继承ES6中不再像ES5一样使用原型链实现继承,而是引入Class这个概念,class是个语法糖

    ES6所写的类相比于ES5的优点:

    区别于函数,更加专业化(类似于JAVA中的类)

    写法更加简便,更加容易实现类的继承

    (9)async、await

    *使用场景*:async主要来处理异步的操作,
    需求:执行第一步,将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第二步操作的接口调用,达到异步操作。

    使用 async/await, 搭配Promise,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成

    (10)Proxy

    Proxy代理的是整个对象,而不是对象的某个特定属性,不需要我们通过遍历来逐个进行数据绑定。

    • 使用Object.defineProperty的时候,我们遇到的问题有:
      • 一次只能对一个属性进行监听,需要遍历来对所有属性监听。这个我们在上面已经解决了。
      • 在遇到一个对象的属性还是一个对象的情况下,需要递归监听。
      • 对于对象的新增属性,需要手动监听
      • 对于数组通过push、unshift方法增加的元素,也无法监听

    2. Class和ES5构造函数的不同点

    • 类的内部定义的所有方法,都是不可枚举的。
    • ES6的class类必须用new命令操作,而ES5的构造函数不用new也可以执行。
    • ES6的class类不存在变量提升,必须先定义class之后才能实例化,不像ES5中可以将构造函数写在实例化之后。
    • ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。 ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
      • 先有子的指针,然后把父的方法放在这个this内
    • 先有把父继承过来,然后再修改this的指向。

    3. ES6对String字符串类型做的常用升级优化

    ES6在String原型上新增了includes()方法,用于取代传统的只能用indexOf()查找包含字 符的方法(indexOf()返回-1表示没查到,不如includes()方法返回false更明确,语义更 清晰),还新增了startsWith(), endsWith(), padStart(),padEnd(),repeat()等方法,可方便的用于查找,补全字符串。

    4. Async/Await原理

    1. async/await 函数其实就是一种语法糖
    2. async/await 是基于promise实现的,async 函数其实就是把 promise 做了一个包装
    3. 当调用一个 async 函数时,会返回一个 Promise 对象 (关键)
    4. async 函数中可能会有 await 表达式,await表达式 会使 async 函数暂停执行,直到表达式中的Promise解析完成后继续执行 async 中 await 后面的代码并返回解决结果。
    5. 既然返回的是Promise 对象,所以在最外层不能直接获取其返回值,那么肯定可以用原来的方式:then() 链来处理这个 Promise 对象

    4.setTimeout、Promise、Async/Await 的区别

    setTimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行。

    Promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行。

    async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。

    • 执行栈在执行完同步任务后会检查执行栈是否为空,若为空,就会去检查微任务队列是否为空,如果为空就执行宏任务,否则先执行所有微任务。
    • Promise本身是同步的立即执行函数,它的.then()和catch()方法是异步的(微任务)。
    • 注意:加了await就等于同步要立刻执行,他让后面的式子变成微任务
      async function async1(){console.log('async1 start');await async2();console.log('async1 end')}async function async2(){console.log('async2')}console.log('script start');setTimeout(function(){console.log('setTimeout')},0);async1();new Promise(function(resolve){console.log('promise1');resolve();}).then(function(){console.log('promise2')});console.log('script end')
    // script start—>async1 start->async2->promise1->script end->async1 end->promise2->setTimeout
    
      new Promise(function(resolve){console.log('promise1');resolve();}).then(function(){console.log('promise2')});async function async1(){console.log('async1 start');await async2();console.log('async1 end')}async function async2(){console.log('async2')}console.log('script start');setTimeout(function(){console.log('setTimeout')},0);async1();console.log('script end')
    // promise1->script start->async1 start->async2->script end->promise2->async1 end->setTimeout
    

    5. Promise有几种状态,什么时候会进入catch?

    • Promise有几种状态

    三个状态:pending、fulfilled、reject

    两个过程:padding -> fulfilled、padding -> rejected

    • Promise什么时候会进入catch

    当pending为rejectd时,会进入catch

    6.Promise 中 reject 和 catch 处理上有什么区别

    reject 是用来抛出异常,catch 是用来处理异常

    reject 是 Promise 的方法,而 catch 是 Promise 实例的方法

    reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入 catch

    网络异常(比如断网),会直接进入catch而不会进入then的第二个回调

    //第一种
    promise.then((res) => {console.log('then:', res);
    }).catch((err) => {console.log('catch:', err);
    })
    //第二种
    promise.then((res) => {console.log('then:', res);
    }, (err) => {console.log('catch:', err);
    })
    

    7. axios

    Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

    axios可以用在浏览器和 node.js 中是因为,它会自动判断当前环境是什么,如果是浏览器,就会基于XMLHttpRequests实现axios。如果是node.js环境,就会基于node内置核心模块http实现axios

    axios二次封装了哪些???

    • axios有什么特性?(不得不说面试被问到几次)
      • 从浏览器中创建 XMLHttpRequests

      • 从 node.js 创建 http 请求

      • 支持 Promise API

      • 拦截请求和响应

      • 转换请求数据和响应数据

      • 取消请求

      • 自动转换 JSON 数据

      • 客户端支持防御 XSRF:就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。

    WEB API——DOM

    1. 什么是dom?

    document object model文档对象模型,它允许脚本(js)控制Web页面、窗口和文档

    1、DOM 是 W3C(万维网联盟)的标准

    2、DOM 定义了访问 HTML 和 XML 文档的标准

    • 什么是W3C

    中文意思是W3C理事会或万维网联盟。W3C组织是对网络标准制定的一个非赢利组织,像HTML、XHTML、CSS、XML的标准就是由W3C来定制。

    • W3C包括那些标准?

    结构化标准语言(HTML、XML)
    表现标准语言(CSS)
    行为标准(DOM、ECMAScript)

    2. dom操作的常用api有哪些

    1、获取dom节点

    1.1)document.getElementById(‘div1’)

    1.2)document.getElementsByTagName(‘div’)

    1.3)document.getElementsByClassName(‘container’)

    1.4)document.querySelector(‘p’)

    1.5)document.querySelectorAll(‘p’)

    2、获取对象property(js对象的property)

    var p = document.getElementsByTagName(‘p’)[0]

    console.log(p.className);

    3、attribute

    3.1)p.getAttribute(‘data-name’);

    3.2)p.setAttribute(‘data-name’, ‘imooc’);

    query选择符选出来的元素及元素数组是静态的,而getElement这种方法选出的元素是动态的(即:当页面增加了标签,这个集合中也就增加了元素 )。静态的就是说选出的所有元素的数组,不会随着文档操作而改变.
    在使用的时候getElement这种方法性能比较好,query选择符则比较方便

    3. dom节点的Attribute和Property有何区别?

    "我爸是李刚”。attribute仅仅是描述了“李刚”这个名字,而property则是直接代表“李刚”这个人

    property是保存在记忆(比如计算机的内存memory)中的,虽然一开始很准确,但是无法长期保存,经常会把需要长期保存的东西用文字描述下来,这时就需要用到attribute

    attribute是很常用的,比如idclass,所以DOM把它们映射到property上以方便使用。这样我们就会遇到一个对象同时具有id这个attributeproperty(由于class是保留字,所以它被映射到property上时变成className)。

    • 什么是property?

    javascript获取到的DOM节点对象,比如a你可以将他看作为一个基本的js对象,这个节点对象包括很多属性((property),比如“value”,“className”),和其他js object一样,自定义的property也会出现在object的for…in遍历中。

    • Attribute

    是HTML标签上的某个属性(特性),如‘type’,‘id’,‘value’,'class’以及自定义属性,它的值只能是字符串。利用setAttribute和getAttribute来设置和获取属性

    • 总结:
      • property能够从attribute中得到同步;
      • attribute不会同步自定义的property上的值;
      • 非自定义的属性(id/src/href/name/value 等),通过setAttribute 修改其特性值可以同步作用到 property 上,而通过.property 修改属性值有的(value)时候不会同步到 attribute 上(打破了同步机制)
      • property的值可以是任何数据类型,大小写敏感,原则上property应该仅供js操作,attribute的值只能是字符串且大小写不敏感,最后作用于html中,可以影响innerHTML获取的值

    4. dom结构操作怎样添加、移除、移动、复制、创建和查找节点?(必会)

    1、创建新节点

    createDocumentFragment() //创建一个DOM片段

    createElement() //创建一个具体的元素

    createTextnode() //创建一个文本节点

    2、添加、移除、替换、插入

    appendChild()

    removeChild()

    replaceChild()

    insertBefore() //并没有insertAfter()

    3、获取dom节点

    document.getElementById(‘div1’)

    document.getElementsByTagName(‘div’)

    document.getElementsByClassName(‘container’)

    document.querySelector(‘p’)

    document.querySelectorAll(‘p’)

    5. dom事件的级别?(必会)

    DOM级别一共可以分为四个级别:DOM0级、DOM1级、DOM2级和DOM3级。而DOM事件分为3个级别:DOM 0级事件处理,DOM 2级事件处理和DOM 3级事件处理。由于DOM 1(映射文档结构)级中没有事件的相关内容,所以没有DOM 1级事件。

    • DOM 0级事件(给元素的事件行为绑定方法)

      • el.οnclick=function(){}
      • 当希望为同一个元素/标签绑定多个同类型事件的时候(如给上面的这个btn元素绑定3个点击事件),是不被允许的(后面覆盖前面)。DOM0事件绑定,给元素的事件行为绑定方法,发生在事件流的目标和冒泡阶段
    • DOM 2级事件(定义了处理事件的接口,感觉就是可以选择冒泡还是捕获)

      • el.addEventListener(event-name, callback, false(默认:冒泡))
      • 可以给按钮绑定两个点击事件
    • DOM 3级事件在DOM 2级事件的基础上添加了更多的事件类型

    • UI事件,当用户与页面上的元素交互时触发,如:load、scroll

    • 焦点事件,当元素获得或失去焦点时触发,如:blur、focu s

    • 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup

    • 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel

    • 文本事件,当在文档中输入文本时触发,如:textInput

    • 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress

    • 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart

    • 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified

    • 同时DOM3级事件也允许使用者自定义一些事件。

    6. dom事件流?

    一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

    (1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;

    (2)目标阶段:真正的目标节点正在处理事件的阶段;

    (3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。

    捕获—>目标—>冒泡

    7. 事件三要素(必会)

    1、事件源、就是你点的那个div,触发的对象

    2、事件、表示动作,比如点击、滑过等

    3、事件处理函数、表示结果,比如点开关跳转到另一个页面

    8.事件对象和事件委托

    事件对象:绑定事件处理函数的参数

    box.onclick = function(ev){var e = ev || event //e就是MouseEvent,e.type就是clickconsole.log(e.currentTarget) //ul 返回绑定的元素boxconsole.log(e.target) //点击哪个就是哪个
    }
    

    事件委托:当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的上级元素而将事件委托给上级元素来触发处理函数。

    例子:

    截屏2022-09-18 18.00.17
    • 优点:

    1、减少事件注册,节省内存。

    2、在table上代理所有tr(行)的click事件。

    3、在ul上代理所有li的click事件。

    4、不用在新添加的li上绑定click事件。

    5、当删除某个li时,不用移解绑上面的click事件。

    • 缺点:

    1、事件委托基于冒泡,对于不冒泡的事件不支持(load、blur、focus)

    2、层级过多,冒泡过程中,可能会被某层阻止掉

    3、理论上委托会导致浏览器频繁调用处理函数

    9.事件冒泡以及阻止冒泡

    在一个对象上触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,一直到根元素停止。

    bubbles && stopPropagation() && stopImmediatePropagation

    box.onclick = function(ev){var e = ev || event //e就是MouseEvent,e.type就是clickconsole.log(e.bubbles)//true 说明是事件冒泡
    }
    // 如果事件是focus,onblur,scroll那么e.bubbles就是false// stopPropagation()阻止事件的捕获和冒泡 
    box.onclick = function(ev){var e = ev || event //e就是MouseEvent,e.type就是clicke.stopPropagation() //如果是是冒泡:目标事件处理了,目标事件父级对象不会处理(不会向上)//如果是是捕获:目标事件处理了,目标事件子级对象不会处理(不会向下)
    }// stopImmediatePropagation:不仅阻止事件的捕获和冒泡也能阻止同一个事件的其他监听函数被调用(一个按钮有两个回调)
    box.onclick = function(ev){var e = ev || event //e就是MouseEvent,e.type就是clicke.stopImmediatePropagation();
    }
    

    如何来阻止?

    • event.stopPropagation():事件处理过程中,阻止了事件冒泡和捕获,但不会阻击默认行为(它就执行了超链接的跳转)
    • return false:事件处理过程中,阻止了事件冒泡,也阻止了默认行为(比如刚才它就没有执行超链接的跳转)
    • e.stopImmediatePropagation:不仅阻止事件的捕获和冒泡也能阻止同一个事件的其他监听函数被调用(一个按钮有两个回调)
    • event.preventDefault():不阻击事件冒泡,但阻击默认行为(它只执行所有弹框,却没有执行超链接跳转

    10. 自定义事件/ 模拟事件?

    var eve = new Event(‘custome’);
    element.addEventListener(‘custome’,function(){console.log(‘custome’);
    });
    // 触发事件对象(真正开发中可以满足某个条件后才触发事件)
    element.dispatchEvent(eve);
    

    11. 绑定事件和解除事件

    (1)事件绑定

    定义:一个事件可以加多次,且不会覆盖

    • 绑定方法
      • attachEvent (‘on+事件名’,函数名) 这个只兼容ie 6-8
      • addEventListener (事件名,函数名,false) 支持ie9+ chrom firfox
    div.addEventListener('click',fn);
    function fn(){ console.log("春雨绵绵"); }div.attachEvent('onclick',fn);
    // 注意点:attachEvent方法绑定的事件是带on的,addEventListener绑定的事件是不带on的
    
    • 下面我写了一个兼容了IE和火狐谷歌的方法
    var div=document.getElementsByTagName("div")[0];
    addEvent('click',div,fn)
    function addEvent(str,ele,fn){ele.attachEvent?ele.attachEvent('on'+str,fn):ele.addEventListener(str,fn);
    }
    function fn(){console.log("春雨绵绵");
    }
    

    (2)解除绑定事件

    detachEvent方法 只支持IE678,不兼容其他浏览器

    removeEventListener方法 兼容火狐谷歌,不兼容IE8及以下

    • //detachEvent方法写法:
      ele.detachEvent("onclick",fn);// removeEventListener的写法:
      ele.removeEventListener("click",fn);
      
    • 兼容了IE和火狐谷歌的方法

      function remove(str,ele,fn){ele.detachEvent?ele.detachEvent("on"+str,fn):ele.removeEventListener(str,fn);}
      

    12. 比较attachEvent和addEventListener?

    • addEventListener()是符合W3C规范的标准方法; attachEvent()是IE低版本的非标准方法
    • addEventListener()支持事件冒泡和事件捕获(第三个参数false表示不用时间捕获); 而attachEvent()只支持事件冒泡
    • addEventListener()的第一个参数中,事件类型不需要添加on; attachEvent()需要添加’on’
    • 如果为同一个元素绑定多个事件, addEventListener()会按照事件绑定的顺序依次执行, attachEvent()会按照事件绑定的顺序倒序执行

    13. JavaScript动画和CSS3动画有什么区别?

    (1)CSS

    css只需要确定第一帧和最后一帧的关键位置即可,两个关键帧之间帧的内容由Composite线程自动生成,不需要人为处理。当然也可以多次添加关键帧的位置

    • 优点:

    • 浏览器可以对动画进行优化,会比js使用更少的占用cpu资源,但是效果足够流畅

    • 代码相对简单,性能调优方向固定

    • 对于帧速表现不好的低版本浏览器,CSS3可以做到自然降级,而JS则需要撰写额外代码

    • 缺点:

    • 运行过程控制较弱,无法附加事件绑定回调函数。CSS动画只能暂停,不能在动画中寻找一个特定的时间点,不能在半路反转动画,不能变换时间尺度,不能在特定的位置添加回调函数或是绑定回放事件,无进度报告

    • 代码冗长。如果想用 CSS 实现稍微复杂一点动画,最后CSS代码都会变得非常笨重。

    (2)JS动画(逐帧动画)

    首先,在js动画是逐帧动画,是在时间帧上逐帧绘制帧内容,由于是一帧一帧的话,所以其可操作性很高,几乎可以完成任何想要的动画形式。但是由于逐帧动画的帧序列内容不一样,会增加制作负担,且资源占有比较大。

    • 优点:

    • JavaScript动画控制能力很强, 可以在动画播放过程中对动画进行控制:开始、暂停、回放、终止、取消都是可以做到的。

    • 动画效果比css3动画丰富,有些动画效果,比如曲线运动,冲击闪烁,视差滚动效果,只有JavaScript动画才能完成

    • CSS3有兼容性问题,而JS大多时候没有兼容性问题

    • 缺点:

    • js是单线程的脚本语言,当js在浏览器主线程运行时,主线程还有其他需要运行的js脚本、样式、计算、布局、交互等一系列任务,对其干扰线程可能出现阻塞,造成丢帧的情况。

    • 其次,js在做动画的时候,其复杂度是高于css3的,需要考虑一些计算,操作等方便问题。

    14. dom和bom的区别(必会)

    1、bom

    1.1) BOM是Browser Object Model的缩写,即浏览器对象模型。

    1.2) BOM没有相关标准。

    1.3) BOM的最根本对象是window

    2、dom

    2.1) DOM是Document Object Model的缩写,即文档对象模型。

    2.2) DOM是W3C的标准。

    2.3) DOM最根本对象是document(实际上是window.document)

    15. document.write和innerHTML的区别

    • 区别:

      • document.write是直接将内容写入页面的内容流,会导致页面全部重绘,innerHTML将内容写入某个DOM节点,不会导致页面全部重绘
      • document.write()是document对象的方法,而innerHTML是元素的属性
      • document.write()是添加script标签,而innerHTML是指定元素内
      • doc.write()可以进行拼接方法,而innerHTML可以进行+=运算
    • script标签:将 JavaScript 插入 HTML 的主要方法就是使用 script 元素

    • async 异步下载脚本,下载完后立刻执行,只对外部文件有效

    • defer 页面加载完成后再执行脚本,只对外部文件有效

    • integrity 对接脚本资源和制定的加密签名验证

    • src 执行代码外部文件

    • type 值为"module"时,代码会被当成ES6模块

    16. 什么是window对象?什么是document对象?

    一、指代不同

    1、document对象:代表给定浏览器窗口中的HTML文档,document是window的一个对象属性。
    2、window对象:表示浏览器中打开的窗口。

    二、作用不同

    1、document对象:使用document对象可以对HTML文档进行检查、修改或添加内容,并处理该文档内部的事件。

    2、window对象:浏览器会为HTML文档创建一个window对象,并为每个框架创建一个额外的window对象。

    三、使用方式不同:
    1、document对象:在Web页面上,document对象可通过window对象的document属性引用,或者直接引用。
    2、window对象:没有应用于window对象的公开标准,不过所有浏览器都支持该对象。

    17. Js拖动的原理?

    • js的拖拽效果主要用到以下三个事件:

      • mousedown 鼠标按下事件

      • mousemove 鼠标移动事件

      • mouseup 鼠标抬起事件

    • 当点击dom的时候(mousedown),记录当前鼠标的坐标值,也就是x、y值,以及被拖拽的dom的top、left值,而且还要在鼠标按下的回调函数里添加鼠标移动的事件:

      • document.addEventListener(“mousemove”, moving, false)和添加鼠标抬起的事件
    • 此外,还要监听鼠标松开事件,如果鼠标松开就会立刻解除mousemove事件

      • document.addEventListener(“mouseup”,function(){ document.removeEventListener(“mousemove”, moving, false);}, false);
    • 当鼠标按下鼠标移动的时候,记录移动中的x、y值,那么这个被拖拽的dom的top和left值就是:

      • top=鼠标按下时记录的dom的top值+(移动中的y值 - 鼠标按下时的y值)

      • left=鼠标按下时记录的dom的left值+(移动中的x值 - 鼠标按下时的x值);

    image-20220714151936414

    18. 浏览器渲染过程,DOM树和渲染树的区别

    • 关键渲染路径(Critical Rendering Path)是什么?

    ​ 浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。

    • 关键渲染路径共分五个步骤

      构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。

      • 解析 HTML 构建 DOM(DOM 树),并行请求 css/image/js
      • CSS 文件下载完成,开始构建 CSSOM(CSS 树)
      • CSSOM 构建结束后,和 DOM 一起生成 Render Tree(渲染树)
      • 布局(Layout):计算出每个节点在屏幕中的位置
      • 显示(Painting):通过显卡把页面画到屏幕上
    • 为什么要将js放在html文件最后

    不完整的CSSOM是无法使用的,但JavaScript中想访问CSSOM并更改它,CSSOM会阻塞渲染,所以先下载浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM

    通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个script标签时,DOM构建将暂停,直至脚本完成执行。但由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS。

    在这里插入图片描述

    这就是为什么我们需要把 js 放在页面底部的原因,尽量保证 DOM 树生成完毕再去加载 JS,从而出现这样的效果。

    • 性能优化

      • 基于以上的分析,简单的说几条性能优化的方式,自己可以去分析一下为什么这些方式可以做性能优化。

      • 减少 DOM 树渲染时间(譬如降低 HTML 层级、标签尽量语义化等等)

      • 减少 CSSOM 树渲染时间(降低选择器层级等等)

      • 减少 HTTP 请求次数及请求大小

      • 将 css 放在页面开始位置

      • 将 js 放在页面底部位置,并尽可能用 defer 或者 async 避免阻塞的 js 加载,确保 DOM 树生成完才会去加载 JS

      • 用 link 替代@import

      • 如果页面 css 较少,尽量使用内嵌式

      • 为了减少白屏时间,页面加载时先快速生成一个 DOM 树

    DOM树和渲染树的区别

    • 渲染树的重要特性是它仅捕获可见内容,不包括 head 和隐藏元素(display:none),大段文本的每一个行都是独立节点,每一个节点都有对应的 css 属性

    • DOM 树与 HTML 标签一一对应,包括 head 和隐藏元素

    在这里插入图片描述

    19. 重绘和重排(回流)

    • 重排(Reflow):元素的 位置发生变动 时发生重排,也叫回流。此时在 Layout 阶段,计算每一个元素在设备视口内的确切位置和大小。当一个元素位置发生变化时,其父元素及其后边的元素位置都可能发生变化,代价极高。

      • 比如改变变字体的大小,增删 DOM 元素,input 输入内容改变
    • 重绘(Repaint):元素的 样式发生变动 ,但是位置没有改变。此时在关键渲染路径中的 Paint 阶段,将渲染树中的每个节点转换成屏幕上的实际像素,这一步通常称为绘制或栅格化。

      • 改了一下字体颜色、背景颜色

    完美答案:
    重排和重绘是浏览器关键渲染路径上的两个步骤, 浏览器的关键渲染路径就是 DOM 和 CSSOM 生成渲染树,然后根据渲染树通过一个布局(也叫 layout)步骤来确定页面上所有内容的大小和位置,确定布局后,将像素 绘制 (也叫 Paint)到屏幕上。

    其中重排就是当元素的位置发生变动的时候,浏览器重新执行布局这个步骤,来重新确定页面上内容的大小和位置,确定完之后就会进行重新绘制到屏幕上,所以重排一定会导致重绘。

    如果元素位置没有发生变动,仅仅只是样式发生变动,这个时候浏览器重新渲染的时候会跳过布局步骤,直接进入绘制步骤,这就是重绘,所以重绘不一定会导致重排

    20. 如何避免重绘(repaint)和回流(reflow)

    • 使用visibility替换display:none(前者只会引起重绘,后者则会引发回流)
    • 避免使用table布局,因为可能一个小小的改动会造成整个页面布局的重新改动
    • 将动画效果应用到position 属性为absolute或fixed的元素上,避免影响其他元素的布局
    • 避免使用CSS的JavaScript表达式,可能会引发回流
    • 不要一个个改变元素的样式属性,直接改变className,若是动态改变样式,则用cssText
      例:box.style.cssText=“width:200px;height:200px”
    • 尽可能在DOM树的末端改变class ,可以限制回流的范围,使其影响尽可能少的节点。

    21. js 延迟加载的方式有哪些?(

    13. 手写Promise系列(高薪常问)

    //实现promise.all()
    /*Promise函数对象的all方法返回一个promise对象,只有当所有promise都成功时返回的promise状态才成功
    */
    Promise.myAll = function(promises){const values = new Array(promises.length)var resolvedCount = 0; //计状态为resolved的promise的数量return new Promise((resolve,reject)=>{// 遍历promises,获取每个promise的结果promises.forEach((p,index)=>){Promise.resolve(p).then(value =>{// p状态为resolved,将值保存起来values[index] = valueresolvedCount++;   // 如果全部p都为resolved状态,return的promise状态为resolvedif(resolvedCount === promises.length){resolve(values)}},reason =>{ //只要有一个失败 return的promise状态rejectreject(reason);})                         })})
    }//实现Promise.race()
    /*Promise函数对象的race方法返回一个promise对象,状态由第一个完成的promise决定
    */
    Promise.myRace = function(promises){return new Promise((resolve,reject)=>{// 遍历promises,获取每个promise的结果promises.forEach((p,index)=>{Promise.resolve(p).then(value => {// 只要有一个成功,返回的promise的状态就为resolvedresolve(value)},reason => { //只要有一个失败,return的promise状态就为rejectreject(reason)})})})
    }//手写promise
    class myPromise{constructor(fn){//将成功的函数集成在successList数组里面this.successList = [];//将所有的失败函数集成在failList里面this.failList = [];//pending(进行中) fulfilled(已成功) rejcet(以失败)this.state = "pending"//传入的函数对象(异步操作函数内容)fn(this.resolveFn.bind(this),this.rejectFn.bind(this));}// 状态转变为 resolve 方法resolveFn(res){this.state = "fulfilled";this.successList.forEach(function(item){//将成功的事件循环调用item(res)})}// 状态转变为 rejected 方法rejectFn(res){//将注册到的失败所有事件进行调用this.state = "rejected";this.failList.forEach(function(item){item(res)})// throw Error(res);}//then方法then(successFn,failFn){if(typeof successFn == "function"){this.successList.push(successFn);}if(typeof failFn == "function"){this.failList.push(failFn)}}//catch方法catch(failFn){if(typeof failFn == "function"){this.failList.push(failFn)}}
    }var fn = function(resolve,reject){setTimeout(function(){if(false){resolve("自定义成功")}else{reject("自定义失败")}},2000)
    }

    14.使用class 手写一个promise

    /创建一个Promise的类class Promise{constructor(executer){//构造函数constructor里面是个执行器this.status = 'pending';//默认的状态 pendingthis.value = undefined//成功的值默认undefinedthis.reason = undefined//失败的值默认undefined//状态只有在pending时候才能改变let resolveFn = value =>{//判断只有等待时才能resolve成功if(this.status == pending){this.status = 'resolve';this.value = value;}}//判断只有等待时才能reject失败let rejectFn = reason =>{if(this.status == pending){this.status = 'reject';this.reason = reason;}}    try{//把resolve和reject两个函数传给执行器executerexecuter(resolve,reject);}catch(e){reject(e);//失败的话进catch}}then(onFufilled,onReject){//如果状态成功调用onFufilledif(this.status = 'resolve'){onFufilled(this.value);}//如果状态失败调用onRejectif(this.status = 'reject'){onReject(this.reason);}}}

    15. 手写ajax的get和post封装

    截屏2022-07-19 10.22.00

    16. 手写Vuex

    let Vue;class Store{constructor(options){// options就是new Vuex.Store传入的参数// 1.使用vue实列来实现响应式变化,保证状态更新会刷新视图this.vm = new Vue({//1.data会被使用Object.defineProperty重新定义date: {state: options.state}});// 2.解析options.getters,它是一个对象,里面都是函数this.getters={};Object.keys(options.getters).forEach(getterName=>{Object.definePropery(this.getters, getterame,{get:()=>{return options.getters[getterName](this.state)}})});//3.解析options.mutationsthis.mutations = {};Object.keys(options.mutations).forEach(mutationsName=>{this.mutations[mutationsName] = (payload)=>{options.mutations[mutationsName](this.state, payload)}});// 4.解析options.actionsthis.actions = {};Object.keys(options.actions).forEach(actionsName=>{this.actions[actionsName] = (payload)=>{options.actions[actionsName](this, payload)}});}get state(){ //1.获取实例上的state——>store.state// 1.相当于this.state = this.vm.statereturn this.vm.state}// 3.用箭头函数的目的是让this一直指向Store——>$store.commitcommit = (mutationName, payload) => {this.mutations[mutationsName](payload)};// 4.$store.dispatchdispatch = (actionName, payload) =>{this.actions[actionsName](payload)}
    }// 每一个插件都有一个install函数,主要就是只有当前的实例才能使用这个插件
    const install = (_Vue) => { //_Vue是vue的构造函数// 使得当前插件不再依赖Vue而是通过用户把Vue传过来,import vue打包的时候体量太大?Vue = _Vue; //传过来的vue的构造函数// 下载不是下载到vue的原型上,我们只想给当前实列下的组件使用// 使用mixin,抽离组件的公共逻辑,每个组件(vue)调用beforeCreate都会执行里面的方法// 值得注意的是,main.js(父)有个vue,app.vue也有个vue(子)Vue.mixin({beforeCreate(){// console.log(this.name)//root-->app-->app的子...// 为了让所有子组件使用store,就把root的store属性放在每个组件的实列上if(this.$options.store){//是root,这个root是有store的this.$store = this.$options.store}else{// 是子组件this.$store = this.$parent && this.$parent.$store}}}) 
    }export default{Store,install
    }
    

    17. js实现set

    //定义集合类
    function Set() {this.dataStore = [];
    }
    //添加方法(不能允许元素重复)
    Set.prototype.add = function (element) {if (this.dataStore.indexOf(element) < 0) {this.dataStore.push(element);} else {console.log('元素已存在');}
    }
    //删除方法(先判断元素是否存在)
    Set.prototype.remove = function (element) {let pos = this.dataStore.indexOf(element);if (pos > -1) {this.dataStore.splice(pos, 1);} else {console.log('元素不存在');}
    }
    //遍历打印方法
    Set.prototype.display = function () {for (var i in this.dataStore) {console.log(this.dataStore[i]);}
    }
    //判断一个元素是否在一个集合内
    Set.prototype.contain = function(element){if(this.dataStore.indexOf(element) > -1){return true;}else{return false;}
    }
    //求两个集合的并集
    Set.prototype.union = function(set){var newSet = new Set();for(var i in set.dataStore){let element = set.dataStore[i];if(!this.contain(element)){newSet.dataStore.push(element);}}for(var i in this.dataStore){newSet.dataStore.push(this.dataStore[i]);}return newSet;
    }
    //求两个集合的交集
    Set.prototype.intersect = function(set){var newSet = new Set();for(var i in set.dataStore){if(this.contain(set.dataStore[i])){newSet.dataStore.push(set.dataStore[i]);}}return newSet;
    }
    

    18. 观察者模式

     // 创建一个Dep类(目标类)
    class Dep {constructor(name) {this.name = name;this.watcherList = []; //用来登记哪些人要买衣服}add(watcher) {this.watcherList.push(watcher);}notify() {this.watcherList.forEach((item) => {item.update();});}
    }// 创建一个Watcher类(观察者类)class Watcher {constructor(name) {this.name = name;}update() {console.log(this.name + "被通知到了");}}//创建一个被观察者的实例
    const dep = new Dep("宝宝");//创建二个观察者
    const father = new Watcher("爸爸");
    const mother = new Watcher("妈妈");
    //把这二个观察者添加到被观察者的列表中
    dep.add(father);
    dep.add(mother);
    //当宝宝被观察者有需求的时候,就去通知所有的观察者
    dep.notify();
    

    19. 轮播图的实现原理

    WechatIMG256
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    <
    >

    20. 手写Js拖动的原理?

    • js的拖拽效果主要用到以下三个事件:

      • mousedown 鼠标按下事件

      • mousemove 鼠标移动事件

      • mouseup 鼠标抬起事件

    • 当点击dom的时候(mousedown),记录当前鼠标的坐标值,也就是x、y值,以及被拖拽的dom的top、left值,而且还要在鼠标按下的回调函数里添加鼠标移动的事件:

      • document.addEventListener(“mousemove”, moving, false)和添加鼠标抬起的事件
    • 此外,还要监听鼠标松开事件,如果鼠标松开就会立刻解除mousemove事件

      • document.addEventListener(“mouseup”,function(){ document.removeEventListener(“mousemove”, moving, false);}, false);
    • 当鼠标按下鼠标移动的时候,记录移动中的x、y值,那么这个被拖拽的dom的top和left值就是:

      • top=鼠标按下时记录的dom的top值+(移动中的y值 - 鼠标按下时的y值)

      • left=鼠标按下时记录的dom的left值+(移动中的x值 - 鼠标按下时的x值);

    image-20220714151936414

    21. nextTick实现原理

    nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;
    使用场景:想要操作 基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中;

    因为 vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,
    而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更;
    这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数,

    • nextTick 实现原理

    将传入的回调函数包装成异步任务,异步任务又分微任务和宏任务,为了尽快执行所以优先选择微任务;

    nextTick 提供了四种异步方法 Promise.then、MutationObserver、setImmediate、setTimeout(fn,0)

    import { noop } from 'shared/util'
    import { handleError } from './error'
    import { isIE, isIOS, isNative } from './env'//  上面三行与核心代码关系不大,了解即可
    //  noop 表示一个无操作空函数,用作函数默认值,防止传入 undefined 导致报错
    //  handleError 错误处理函数
    //  isIE, isIOS, isNative 环境判断函数,
    //  isNative 判断某个属性或方法是否原生支持,如果不支持或通过第三方实现支持都会返回 falseexport let isUsingMicroTask = false     // 标记 nextTick 最终是否以微任务执行const callbacks = []     // 存放调用 nextTick 时传入的回调函数
    let pending = false     // 标记是否已经向任务队列中添加了一个任务,如果已经添加了就不能再添加了// 当向任务队列中添加了任务时,将 pending 置为 true,当任务被执行时将 pending 置为 false// // 声明 nextTick 函数,接收一个回调函数和一个执行上下文作为参数
    // 回调的 this 自动绑定到调用它的实例上
    export function nextTick(cb?: Function, ctx?: Object) {let _resolve// 将传入的回调函数存放到数组中,后面会遍历执行其中的回调callbacks.push(() => {if (cb) {   // 对传入的回调进行 try catch 错误捕获try {cb.call(ctx)} catch (e) {    // 进行统一的错误处理handleError(e, ctx, 'nextTick')}} else if (_resolve) {_resolve(ctx)}})// 如果当前没有在 pending 的回调,// 就执行 timeFunc 函数选择当前环境优先支持的异步方法if (!pending) {pending = truetimerFunc()}// 如果没有传入回调,并且当前环境支持 promise,就返回一个 promise// 在返回的这个 promise.then 中 DOM 已经更新好了,if (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}
    }// 判断当前环境优先支持的异步方法,优先选择微任务
    // 优先级:Promise---> MutationObserver---> setImmediate---> setTimeout
    // setTimeout 可能产生一个 4ms 的延迟,而 setImmediate 会在主线程执行完后立刻执行
    // setImmediate 在 IE10 和 node 中支持// 当在同一轮事件循环中多次调用 nextTick 时 ,timerFunc 只会执行一次let timerFunc   
    // 判断当前环境是否原生支持 promise
    if (typeof Promise !== 'undefined' && isNative(Promise)) {  // 支持 promiseconst p = Promise.resolve()timerFunc = () => {// 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务p.then(flushCallbacks)if (isIOS) setTimeout(noop)}// 标记当前 nextTick 使用的微任务isUsingMicroTask = true// 如果不支持 promise,就判断是否支持 MutationObserver// 不是IE环境,并且原生支持 MutationObserver,那也是一个微任务
    } else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||MutationObserver.toString() === '[object MutationObserverConstructor]'
    )) {let counter = 1// new 一个 MutationObserver 类const observer = new MutationObserver(flushCallbacks) // 创建一个文本节点const textNode = document.createTextNode(String(counter))   // 监听这个文本节点,当数据发生变化就执行 flushCallbacks observer.observe(textNode, { characterData: true })timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter)  // 数据更新}isUsingMicroTask = true    // 标记当前 nextTick 使用的微任务// 判断当前环境是否原生支持 setImmediate
    } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {timerFunc = () => { setImmediate(flushCallbacks)  }
    } else {// 以上三种都不支持就选择 setTimeouttimerFunc = () => { setTimeout(flushCallbacks, 0) }
    }// 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中
    // 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
    function flushCallbacks() {pending = false    const copies = callbacks.slice(0)    // 拷贝一份 callbackscallbacks.length = 0    // 清空 callbacksfor (let i = 0; i < copies.length; i++) {    // 遍历执行传入的回调copies[i]()}
    }// 为什么要拷贝一份 callbacks// 用 callbacks.slice(0) 将 callbacks 拷贝出来一份,
    // 是因为考虑到在 nextTick 回调中可能还会调用 nextTick 的情况,
    // 如果在 nextTick 回调中又调用了一次 nextTick,则又会向 callbacks 中添加回调,
    // 而 nextTick 回调中的 nextTick 应该放在下一轮执行,
    // 否则就可能出现一直循环的情况,
    // 所以需要将 callbacks 复制一份出来然后清空,再遍历备份列表执行回调
    

    相关内容

    热门资讯

    埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
    应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
    长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
    脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
    世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
    demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
    猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
    埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
    应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...