前端基础整理

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,arry
    	return 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);
        }
        //打印1
        
        var 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 = 2
  foo()
}
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; // 声明变量a
        console.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 合并成一个渲染树。
    • 根据渲染树来布局,以计算每个节点的几何信息。
    • 将各个节点绘制到屏幕上。

当浏览器遇到<script>标记的时候,浏览器会执行之间的JavaScript代码。嵌入的js代码是顺序执行的,每个脚本定义的全局变量和函数,都可以被后面执行的脚本所调用。变量的调用,必须是前面已经声明,否则获取的变量值是undefined。

23. 解释一下什么是回调函数,并提供一个简单的例子

就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

24. 说说你对原型(prototype)理解

(1)原型

在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了所有实例共享的属性和方法。当使用构造函数新建一个实列后,这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值

原型的主要作用就是为了实现继承与扩展对象

(2)原型链

当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。

25. 简单说说js中的继承

继承就是子类继承父类的属性和方法,目的可以让子类的实例能够使用父类的属性和方法

(1)原型链继承(父亲的私有和prototype都有变成了子的公有(prototype))

  • 原型链继承的核心

    • 子类构造函数.prototype=父类的实例

    • 子类构造函数.prototype.constructor=子类构造函数

  • 特点:

    • js继承是把父类的原型放到子类的原型链上,实例想要调用这些方法,其实是基于_proto_原型链的机制查找完成的。

    • 子类可以重写父类上的属性和方法(这个其实就是复制了一份在子类身上,所以子类重写不影响父亲自己的想法)

    • 父类中私有的或者公有的属性和方法,最后都会变成子类公有的属性和方法

(2)call继承又称(借用构造函数继承)(只有父亲的私有没有prototype最后也是私有)

在子类构造函数中把父类构造函数当作普通函数执行,并且通过call方法把父类构造函数中的this替换成子类的实例(this),这样相当于给子类实例上设置了私有的属性和方法。

  • 特点:

  • 只能继承父类的私有的属性和方法(因为只是把父类构造当作普通函数执行了一次,跟父类的原型上的方法和属性没有任何关系)

  • 父类的私有的属性和方法都会变成子类私有的属性和方法

function B(y,x){
  this.y=y
  A.call(this,x)
}

(3)组合继承

结合原型链继承和借用构造函数继承组合起来实现的继承

  • 子类的私有属性存了父类的私有属性
  • 子类的prototype存了父类的私有属性+共有属性
function B(y,x){
  this.y=y
  A.call(this,x)
}
 
B.prototype=new A()
B.prototype.constructor=B
B.prototype.getY=function(){
  console.log(this.y)
}

(4)寄生组合继承 (最完美的js继承解决方案)

结合原型链继承和call继承的方法,同时自己创建一个对象,并且让这个对象的原型指向父类构造函数的prototype.实现寄生组合继承

  • 特点:

    • 父类私有的属性和方法,成为子类实例私有的属性和方法

    • 父类公有的属性和方法,成为子类实例公有的属性和方法

function B(y,x){
  this.y=y
  A.call(this,x)
}
Son.prototype = Father.prototype //这个想法很好,但是子有自己方法怎么办?❌
// 通俗讲就是子.prototype指向父.prototype(注意是这是指针不是覆盖)
// 所以修改son.prototype的时候其实修改的是Father.prototype
B.prototype = Object.create(A.prototype)
B.prototype.constructor=B

(5)Class类继承

class B extends A{ //通过extends 实现原型链继承
  consrtuctor(y){
    super(100) // call继承(借用构造函数继承)
    this.y=y
	}
  getY=function(){
  	console.log(this.y)
  }
}
let b =new B(200)
  • 父类私有的属性和方法会变成子类私有的属性和方法

  • 父类公有属性和方法会变成子类公有的属性和方法

26. this

普通函数:指向window

对象方法:指向方法所在的对象

构造函数:指向实例对象(原型上的方法也指向调用者:实例对象)

绑定的事件函数:指向调用者,比如获取的按钮元素对象

定时器函数:指向window,默认使用window调用setInterval\setTimeout(可用箭头函数更改)

立即执行函数:指向window

原型中的this指向调用他的对象不指向本身

  • 隐式绑定:一般来说谁调用了方法,该方法的 this 就指向谁,例如:

  • 隐式丢失:应用默认绑定,从而把 this 绑定到全局对象中:

    • bar=obj.foo引用的是函数本身,所以bar()执行的时候发现this指向的是window

    • function foo() {
        console.log(this.a)
      }
                                          
      var obj = {
        a: 2,
        foo
      }
                                          
      var a = 3
      var bar = obj.foo
      bar() // 3
      
  • 显式绑定:call,apply,bind 和 new

  • 箭头函数:箭头函数的绑定无法被修改。

this 的优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

27. 数组中的forEach和map的区别

forEach 和map的相同点:

  • 都是循环遍历数组中的每一项
  • forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项), index(索引值),arr(原数组)
  • 都对原来数组没有影响。

不同点:

  • map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值
  • Foreach是没有返回值的
  • map方法不会对空数组进行检测,若arr为空数组,则map方法返回的也是一个空数组。
  • forEach对于空数组是不会调用回调函数的。 无论arr是不是空数组,forEach返回的都是 undefined
28. for in和for of的区别
  • for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
  • for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
  • 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

总结: for…in 循环主要是为了遍历对象而生,不适用于遍历数组;for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。for…of不能循环普通的对象,需要通过和Object.keys()搭配使用

29. Call和apply,bind的区别
  • 相同点:都可以改变函数内部this指向

  • 不同点:

    • call和apply都会调用函数,并改变函数内部this指向
    • call和apply传递参数不同,call传递参数arg1,arg2…形式,apply必须数组形式[arg]
    • bind不会调用函数,可以改变this指向
  • 应用场景:

    • call经常做继承

    • apply经常跟数组有关系,比如借助数学对象实现数组最大最小

      • let arr = [1,55,2,78]
        let max = Math.max.apply(Math,arr)
        
    • bind用于不想调用函数还想改变this指向,比如说改变定时器内部this指向

30. New操作符具体干了什么呢?
  1. 创建一个空对象obj;
  2. 将该空对象的原型设置为构造函数的原型,即obj.proto = func.prototype;
  3. 以该空对象为上下文执行构造函数(父亲函数),即func.call(obj);继承私有属性
  4. 返回该对象,即return obj。

new 实现了什么功能:

  1. 访问到了构造函数里的私有属性
  2. 访问到了构造函数 prototype 里的属性
31. Split()和join()的区别?

Split()是把一串字符(根据某个分隔符)分成若干个元素存放在一个数组里即切割成数组的形式;

join() 是把数组中的字符串连成一个长串,可以大体上认为是 Split()的逆操作

32. js数组去重,能用几种方法实现

(1)for循环 + indexOf()方法 + push()方法

let arr = ['aa','bb','cc','aa'];
let newArr = [];
for(var i = 0; i < arr.length; i++) {
	// indexOf()方法返回在数组中找到给定元素的第一个索引,若不存在,返回 -1
    // 判断是否在newArr数组中存在  1是存在,-1是不存在
	if(newArr.indexOf(arr[i]) == -1) {
	  // push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。
      // 如果不存在,就追加到newArr里
		newArr.push(arr[i]);
	}
}
console.log(newArr)   // ['aa', 'bb', 'cc']

(2)forEach循环 + indexOf() + push()

let arr = ['aa','bb','cc','aa'];
let newArr = [];
arr.forEach(item => {
	// item 代表arr数组里的每一项
	if(newArr.indexOf(item) == -1)
	newArr.push(item);
})
cosnole.log(newArr);  // ['aa', 'bb', 'cc']

(3)forEach循环 + includes()方法+ push()方法

let arr = ['aa','bb','cc','aa'];
let newArr = [];
arr.forEach(item => {
	// includes()方法用于判断字符串是否包含指定的子字符串,有则返回true,无则返回false;includes() 方法区分大小写。
	if(!newArr.includes(item))
	newArr.push(item);
})
console.log(newArr);  // ['aa', 'bb', 'cc']

(4)ES6的set()方法

let arr = ['aa','bb','cc','aa'];
// set 用于数组去重,成员是唯一的,不可以重复
let  newArr = [...new Set(arr)];
console.log(newArr);   // ['aa', 'bb', 'cc']

(5)Array.from()方法 + set()方法

let arr = ['aa','bb','cc','aa'];
let newArr = Array.from(new Set(arr));
console.log(newArr); // ['aa', 'bb', 'cc']
33. Class和普通构造函数有何区别?

class是构造函数的语法糖

Class 在语法上更加贴合面向对象的写法

Class 实现继承更加易读、易理解

更易于写 java 等后端语言的使用

本质还是语法糖,使用 prototype

34. 什么是js事件循环 event loop

JavaScript是单线程的编程语言,事件循环是保证js单线程不堵塞的执行机制

Event Loop主要有三部分:主线程、宏任务(macrotask)、微任务(microtask)

js的任务队列分为同步任务和异步任务,所有的同步任务都是在主线程里执行的,异步任务可能会在macrotask或者microtask里面

执行顺序是先执行主线程,遇到宏任务放入宏任务队列,遇到微任务放入微任务队列,主线程执行完毕先清空微任务队列(先进先出),再执行下一个宏任务,依次循环

同步和异步:

同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务

异步任务是指不进入主线程,而进入任务队列的任务,只有主线程通知任务队列,某个异步任务可以执行了,该任务才会进入主线程

浏览器的Event loop是在HTML5中定义的规范,而node中则由libuv库实现。

(1)浏览器中的Event loop

所有同步任务都在主线程上执行,形成一个执行栈

主线程之外,还存在一个任务队列。

  • 任务队列分为macro-task(宏任务)和micro-task(微任务)。
  • macro-task(宏任务):setTimeout, setInterval, setImmediate, I/O等
  • micro-task(微任务):process.nextTick,promise.then/.finally

(2)node中的Event loop

外部输入数据–>轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O事件回调阶段(I/O callbacks)–>闲置阶段(idle, prepare)

只需了解timers(会执行 setTimeout 和 setInterval 回调)、poll(读取文件)、check(会执行setImmediate()的回调)

  • node中的event loop分为6个阶段,不同于浏览器的是,这里每一个阶段都对应一个事件队列,node会在当前阶段中的全部任务执行完,清空NextTick Queue,清空Microtask Queue,再执行下一阶段。

    • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
    • check 阶段:执行setImmediate()的回调
    • close callbacks 阶段:执行close事件的callback,例如socket.on(“close”,func)
    • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
    • I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
    • idle, prepare 阶段:仅node内部使用process.nextTick()
    // 加入两个nextTick()的回调函数
    process.nextTick(function () {
      console.log('nextTick延迟执行1');
    });
    process.nextTick(function () {
      console.log('nextTick延迟执行2');
    });
    // 加入两个setImmediate()的回调函数
    setImmediate(function () {
      console.log('setImmediate延迟执行1');
      // 进入下次循环
      process.nextTick(function () {
        console.log('最后执行');
      });
    });
    setTimeout(() => {
      console.log('timer1')
      Promise.resolve().then(function() {
        console.log('promise1')
      })
    }, 0)
    setImmediate(function () {
      console.log('setImmediate延迟执行2');
    });
    console.log('正常执行');
    正常执行
    nextTick延迟执行1
    nextTick延迟执行2
    timer1
    promise1
    setImmediate延迟执行1
    最后执行
    setImmediate延迟执行2
    
    process.nextTick(()=>{
      console.log(1);
    })
    process.nextTick(()=>{
      console.log(2);
    })
    setImmediate(()=>{
      console.log(3);
      process.nextTick(()=>{
        console.log(4);
      })
    })
    setImmediate(()=>{
      console.log(5);
    })
    setTimeout(() => {
      console.log(8);
    }, 0);
    process.nextTick(()=>{
      console.log(7);
    })
    console.log(6);
    6 1 2 7 8 3 4  5
    

(3)总结

  • 同一个上下文下,MicroTask微任务会比MacroTask宏任务先运行。
  • 浏览器是先取出一个MacroTask宏任务执行,再执行MicroTask微任务中的所有任务。Node是按照六个阶段执行,每个阶段切换时,再执行MicroTask微任务队列

(4)

setImmediate()是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate指定的回调函数

process.nextTick()方法可以在当前"执行栈"的尾部待下一次Event Loop(主线程读取"任务队列")之前触发process指定的回调函数。也就是说,它指定的任务总是发生在所有异步任务之前,当前主线程的末尾。

35. Eval是做什么的

把字符串参数解析成JS代码并运行,并返回执行的结果;

应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)在IE8及IE8一下的版本就不支持了

由JSON字符串转换为JSON对象的时候可以用eval,例如:

var json="{name:'Mr.CAO',age:30}"
var jsonObj=eval("("+json+")")
console.log(jsonObj);
36. Let,const,var

(1)let

  • let声明的变量具有块级作用域,使用var声明的变量不具有块级作用域

    防止循环变量变成全局变量

  • 声明的关键字不存在变量提升,原来可以先使用在声明,现在必须先声明在使用

  • 具有暂时性死区,不受外部影响

(2)const

作用:声明常量,常量就是值(内存地址)不能变化的量

  • 具有块级作用域
  • 声明常量的时候必须赋初始值
  • 常量赋值后,值不能被更改,数组元素可以被修改

let,const,var区别

varLetConst
函数级作用域块级作用域块级作用域
变量提升不存在变量提升不存在变量提升
值可以更改值可以更改值不可以更改
37. JavaScript中的定时器有哪些?他们的区别及用法是什么?

(1)setTimeout(延时器):在指定的毫秒数后调用函数或计算表达式。

setTimeout有两个参数,第一个是function:表示将要执行的函数或者代码,第二个参数delay是推迟执行的毫秒数。如果省略就是马上执行。

(2)setInterval (定时器):按照指定的周期(以毫秒计)来调用函数或计算表达式。

aHR0cHM6Ly9ib3gua2FuY2xvdWQuY24vOTQ0MTNmNTlhMTU3YzcyNTAwMzY5OTk1NjkxYjdhMTBfNDM0eDE0MS5wbmc
38. Object.create与new区别

区别:

所以Object.create()与new的区别在于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);
在这里插入图片描述
39. 共享传递和按值传递

共享传递是指: 在传递对象的时候,传递对象的引用的副本。

注意: 按引用传递是传递对象的引用,而按共享传递是传递对象的引用的副本!其实就是你能通过指针改里面的内容,但是你不能更改指针

链接中第一个是没有改变指针,第二个是直接改变了拥有的指针,所以修改不会有影响

所以修改 o.value,可以通过引用找到原值,但是直接修改 o,并不会修改原值。所以第二个和第三个例子其实都是按共享传递。

最后你可以这么理解:
在JS中,参数如果是基本类型是按值传递,如果是引用类型按共享传递。

40. js精度问题:0.1+0.2!=0.3
截屏2022-07-25 17.23.13

因为:0.1和0.2的二进制都是无限循环

解决:

//四舍五入
a = (0.1+0.2).tofixed(5) //”0.30000“
parseFloat(a)//0.3
41. 静态作用域与动态作用域

JS 采用的是静态作用域

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

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

function foo() {
    console.log(a);
}
function bar() {
    var a = 3;
    foo();
}
var a = 2;
bar(); // 2;
// 如果foo(a)有参数a那就是3

bar 调用,bar里面foo被调用,foo函数需要查找变量a,由于JavaScript是词法作用域(即静态作用域),foo被解析时在全局作用域.
所以只能在全局作用域中找a,输出结果为2,而非bar作用域中的a。如果js采用的时动态作用域,那么foo在bar中调用,就会先在bar中查询a,输出为3。

42. 多维数组转一维数组
  • reduce方法
const oldArr = [[1,2,3],[2,4,6]];
const newArr = oldArr.reduce((prev, curr) => (prev.concat(curr)), []);
  • concat方法
const oldArr = [[1,2,3],[2,4,6]];
const newArr = [].concat(...oldArr);
const newArr = Array.prototype.concat.apply([], oldArr);
  • flat() 二维降一维
const newArr = oldArr.flat();
  • 递归
const ergodic = (arr) => {
  arr.forEach((item) => {
    if (Array.isArray(item)) {
      ergodic(item);
    } else {
      newArr.push(item);
    }
  })
}
43.高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值返回

sort、filter

44. this指向

普通函数:指向window

对象方法:指向方法所在的对象

构造函数:指向实例对象(原型上的方法也指向调用者:实例对象)

绑定的事件函数:指向调用者,比如获取的按钮元素对象

定时器函数:指向window,默认使用window调用setInterval\setTimeout(可用箭头函数更改)

立即执行函数:指向window

原型中的this指向调用他的对象不指向本身

45. 默认参数,动态参数,剩余参数
// y就是默认参数,默认参数值的位置一定为尾参数,即参数定义的尾部
// 函数的length属性会返回函数的参数数量,其不包括默认参数
function(x,y=1){
  console.log("qsa")
}

// arguments 是一个类数组对象,是传给一个函数的参数列表
function fun1() {
    let len = arguments.length;//3
  }
fun1("a", 0, { name: "cao" });

// 剩余参数表示法...rest
// 箭头函数没有arguments
let func = (...rest) => {
    console.log(rest)
  }
func(1, 2, 3) 
输出:[1,2,3]
46. 前端路由的实现原理

本质上是监测浏览器地址栏url的变化,截取url地址,然后根据相应的规则做不同的处理

在HTML5新特性出来之前,前端路由基本上都是基于 hash 来实现的

https://web-example.com/#/page1

因为 # 后面的值的变化,并不会向服务器发起请求,所以就不会刷新页面;并且每次 hash 的变化,都会触发window上的 hashchange 事件,开发者可以监听这个事件,从而处理页面的切换、展示。

hash router

Hash的基本概念:Hash 方法是在路由中带有一个 #,主要原理是通过监听 # 后的 URL 路径标识符的更改而触发的浏览器 hashchange 事件,然后通过获取 location.hash 得到当前的路径标识符,再进行一些路由跳转的操作

代码实现

47. promise

Promise 是一个构造函数,接收一个函数function(resolve, reject)作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。

  • then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。

  • catch方法,该方法相当于then方法的第二个参数,指向reject的回调函数。不过catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。

  • **all方法,**可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected

Promise.all([promise1,promise2,promise3]).then(res=>{
    console.log(res);
    //结果为:[1,2,3] 
})
  • race方法all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected

​ 那么race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
  • **finally()**不管promise最后的状态,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数。

    服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

    server.listen(port)
      .then(function () {
        // ...
      })
      .finally(server.stop);
    

注意: 在构造 Promise 的时候,构造函数内部的代码是立即执行的

48. async/await

async/await 使得异步代码看起来像同步代码。async函数返回一个Promise对象,可以使用then 方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体后面的语句。

49. 创建obj和继承对比

WechatIMG298

class B extends A{ //通过extends 实现原型链继承
  consrtuctor(y){
  super(100) // call继承(借用构造函数继承)
  this.y=y
}

CSS

0.display详细
  • display:none

将元素与其子元素从普通文档流中移除。这时文档的渲染就像元素从来没有存在过一样,也就是说它所占据的空间消失了。元素的内容也会消失。

  • display:block

    • block元素会独占一行,多个block元素会各自新起一行。默认情况下,block元素宽度自动填满其父元素宽度;

    • block元素可以设置margin和padding属性;

    • block元素可以设置width、height属性。

    • block有width,浏览器自动帮我们设置margin-right来实现填充。没有宽度,我们就可以自己设置margin-right.

  • display:inline

    • inline元素不会独占一行,多个相邻的行内元素会排列在同一行里,直到一行排列不下,才会新换一行,其宽度随元素的内容而变化;

    • inline元素设置width、height属性无效;

    • inline元素的margin和padding属性,水平方向的padding-left、padding-right、margin-left、margin-right都产生边距效果;但竖直方向的padding-top、padding-bottom、margin-top、margin-bottom不会产生边距效果。

  • display:inline-block

    • 将对象呈现为inline对象,但是对象的内容作为block对象呈现,之后的内联对象会被排列在同一行内。就是集合了block和inline的全部优点。width、height、margin、padding设置都会生效。
  • display: flex

    • flex:1=1 1 0%实际代表的是三个属性的简写:弹性元素如何伸长或缩短以适应flex容器中的可用空间

      • flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

        • 如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。
        • 如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
      • flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

        • 如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。
        • 如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
      • flex-basis是用来设置盒子的基准宽度,并且basis和width同时存在basis会把width干掉

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

      语法等值
      flex: initial0 1 auto如果子项内容很多,由于flex-shrink:1,因此,会缩小,表现效果就是文字换行
      flex: 0flex: 0 1 0%flex-0时候会表现为最小内容宽度,自动换行
      flex: noneflex: 0 0 autoflex-none时候会表现为最大内容宽度,字数过多时候会超过容器宽度。不会换行
      flex: 1flex: 1 1 0%元素充分利用剩余空间,同时不会侵占其他元素应有的宽度的时候,适合使用flex:1
      flex: autoflex: 1 1 auto元素充分利用剩余空间,但是各自的尺寸按照各自内容进行分配的时候
0.Transform

transform不能用在行内元素上(span等),但是行内元素素可以随着块级父元素一起变形,但是不能直接旋转。除非把它们的显示方式改为display:block,display:inline-block等。

3D效果与transform-styleperspective`属性配和才能达到的效果

  • 移动(translate)

    • transform: translateX(-5em) translateY(50px) translateZ(200px);
    • translate(-5em, 50px)更方便。第一个值代表x轴,第二个值代表y轴
      • translate(50px) === translate(50px , 0)
    • translateZ()沿z轴平移元素,即在第三个维度中移动元素。translateZ()只接受长度值。其实任何有关z轴的值都不可以使用百分数。
      • translate3d()这个简写属性能同时指定x轴,y轴,z轴的平移量。translate3d(1em,-50px,0)
  • 缩放(scale)

    • translate()一样,scale3d()的三个数都必须是有效的。
    • scale(2)将把元素的宽度和高度都放大两倍。
  • 旋转(rotate)

    • rotate()==rotateZ()

    • rotate(45deg) === rotate3d(0,0,1,45deg)

      • rotate3d(<number>,<number>,<number>,<angle>)  
        
        • rotate(45deg)用3D旋转表示是 rotate3d(0,0,1,45deg)。这个向量在x轴和y轴上的大小是0,在z轴上的大小是1。也就是说,旋转中心是z轴。元素将绕指定的向量旋转45度。
  • 倾斜(skew)

img
  • perspective 属性与perspective()函数的区别

    • perspective()函数只为目标元素定义视域,比如声明perspective(800px) rotateY(45deg)。那么只有应用这个规则的元素才能使用设定的视域。

    • perspective 属性定义的视域深度应用到目标元素的所有子元素上。

这就是两者的重要区别。perspective 属性创建的3D空间为所有子元素共有,而perspective()函数只对目标元素有效果,而且要放在变形函数列表的开头或前面,多数时候,应该使用perspective属性。

0. Transition

transition-duration 用于指定这个过渡的持续时间

transition-property 用于指定应用过渡属性的名称
transition-delay 用于指定延迟过渡的时间
transition-timing-function 用于指定过渡的类型

  • 简写:过渡时间 过渡样式 过渡形式 延迟时间

    • transition: 1s width, 2s height;
  • 过渡的坑

    • transition 在元素首次渲染还没有完成的情况下,是不会触发过渡的。
      过渡如果写在js 中,则必须 写在 onload 函数中,否则在页面中的元素还没有渲染完的情况下不会触发过渡!

    • <style>
          * {
              margin: 0;
              padding: 0;
          }
                  
          .box {
              width: 200px;
              height: 200px;
              background-color: deepskyblue;
              margin: 100px auto;
              transition: 1s;
          }
      </style>
      <script>
      	/* 在元素还没有完全加载的时候是不会触发过渡的 */
          var box = document.querySelector(".box");
          box.style.width = "300px";
      </script>
      </head>
                  
      <body>
          <div class="box"></div>
      </body>
                  
      
    • <script>
          window.onload = function () {
              var box = document.querySelector(".box");
              box.style.width = "300px";
          };
      </script>
      
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个中文
<style>
  div {
    font-size: 46px;
    font-family: monospace;
    /* 1ch 代表0的宽度  */
    width: 0ch;
    white-space: nowrap;
    /* animation: move 4s linear  forwards; */
    /* steps 就是分几步来完成动画,有了 steps 不要写ease linear了 */
    animation: move 4s steps(9) forwards;
    overflow: hidden;
  }

  @keyframes move {
    0% {}
    100% {
      width: 18ch;
    }
  }
</style>

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

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

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

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

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

  1. 给父元素定义高度
    • 优点:操作简单
    • 缺点:高度定死
  2. 添加一个空元素 <div class="clear"></div> (.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

  • 需要知道子元素的宽高
<style>
  .outer {
    position: relative;
    width: 300px;
    height: 300px;
    background: red;
  }
  
  .inner {
    position: absolute;
    width: 200px;
    height: 200px;
    background: yellow;
    left: 50%;//相对于父盒子的宽度
    top: 50%;//相对于父盒子的高度
    // 因为此时子盒子的左上角在父盒子的中心
    margin-left: -100px;
    margin-top: -100px;
  }
</style>
<div class="outer">
  <div class="inner">
    12345
  </div>
</div>

(2)position + magin:auto

  • 需要知道子元素的宽高left,right,top,bottom一个都不能少
<style>
		.outer {
			position: relative;
			width: 300px;
			height: 300px;
			background: red;
		}

		.inner {
			position: absolute;
			width: 200px;
			height: 200px;
			background: yellow;
			left: 0;
			right: 0;
			top: 0;
			bottom: 0;
			margin: auto;
    }

(3)position + calc

  • 兼容性依赖于 calc,只支持 IE9及以上
  • 需要知道子元素宽高
.outer {
  position: relative;
  width: 300px;
  height: 300px;
  background: red;
}

.inner {
  position: absolute;
  width: 200px;
  height: 200px;
  background: yellow;
  left: calc(50% - 100px);
  right: calc(50% - 100px);
}

(4)position + transform

.outer {
  position: relative;
  width: 300px;
  height: 300px;
  background: red;
}

.inner {
  position: absolute;
  width: 200px;
  height: 200px;
  background: yellow;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
}

(5)display:flex

.parent {
  width: 500px;
  height: 500px;
  border: 1px solid #000;
  display: flex;
  justify-content: center;
  align-items: center;
}
.child {
	....
}
6 如何水平居中一个 img?
img {
  display: block;
  margin-left: auto;
  margin-right: auto;
  width: 40%;
}
7. position
  • static:static 是 position 的默认属性值,当 position 为 static 的时候,left | right | top | bottom 属性无效,也就是当没有设置 position 属性的时候,会默认为是:position: static
  • relative:当 position 为 relative 的时候,如果设置了 left 、right、top、bottom 属性,它们会生效,发生偏移时的参照为 position 属性取 static 时盒子的位置
  • absolute:当 position 为 absolute 的时候,元素会被移出正常文档流,并不为元素预留空间,元素相对于最近的非 static 定位祖先元素发生偏移
    • 当元素没有非 static 定位祖先时,会相对 html 发生偏移
  • fixed:当 position 为 fixed 的时候,元素会被移出正常文档流,并不为元素预留空间,元素相对于屏幕视口(viewport)的 位置来发生偏移, 元素的位置在屏幕滚动时不会改变,这一点与 absolute 不同,absolute 在屏幕滚动时会跟着一起滚动
  • sticky:sticky 定位,或者叫粘性定位,可以被认为是 relative 和 fixed 的混合,元素在跨越特定阈值前为相对定位,之后为固定定位。
8.1. 两栏布局
  • 浮动布局:float+margin
<div id="box">
  <div id="left"></div>
  <div id="right"></div>
</div>

<style>
  #box {
    height: 200px;
  }
  
  #box > div {
    height: 100%;
  }
  
  #left {
    float: left;
    width: 200px;
    background: pink;
  }
  
  #right {
    margin-left: 200px;
    background: yellow;
  }
</style>
  • 绝对定位:position
<div id="box">
  <div id="left"></div>
  <div id="right"></div>
</div>

<style>
	#box {
    height: 200px;
    position: relative;
  }
  
  #box > div {
    height: 100%;
    position: absolute;
    top: 0;
  }
  
  #left {
    left: 0;
    width: 200px;
    background: pink;
  }
  
  #right {
    right: 0;
    background: yellow;
    left: 200px;
  }
</style>
  • Flex 布局:flex:1
<div id="box">
  <div id="left"></div>
  <div id="right"></div>
</div>

<style>
	#box {
    height: 200px;
    display: flex;
  }
  
  #left {
    width: 200px;
    background: pink;
  }
  
  #right {
    flex: 1;
    background: yellow;
  }
</style>
  • **表格布局:**display:table;父:width:100%;子:display:table-cell;
<div id="box">
  <div id="left"></div>
  <div id="right"></div>
</div>

<style>
	#box {
    height: 200px;
    display: table;
    width: 100%;
  }
  
  #box > div {
    height: 100%;
  }
  
  #left {
    display: table-cell;
    width: 300px;
    background: pink;
  }
  
  #right {
    display: table-cell;
    background: yellow;
  }
</style>
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
<header>      <!--:页眉通常包括网站标志、主导航、全站链接以及搜索框。-->
<nav>           <!--:标记导航,仅对文档中重要的链接群使用。-->
<main>         <!--:页面主要内容,一个页面只能使用一次。-->
<article>       <!--:定义外部的内容,其中的内容独立于文档的其余部分。-->
<section>     <!--:定义文档中的节。比如章节、页眉、页脚或文档中的其他部分。-->
<aside>        <!--:定义其所处内容之外的内容。如侧栏、文章链接、广告、相关产品列表等。-->
<footer>        <!--:页脚,只有当父级是body时,才是整个页面的页脚。--> 
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-debounce
  function 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<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/超文本引用)**指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加<link href="common.css"rel=“stylesheet”/>那么浏览器会识别该文档为 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 实例的 e l 和 d a t a 都已初始化,挂载虚拟 d o m 节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中因此 ‘ el 和 data 都已初始化,挂载虚拟 dom节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中 因此 ` eldata都已初始化,挂载虚拟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
      <template>
      	<level1 :test="1" :temp="'abc'"></level1>
      </template>
      
    • 在子组件使用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>
    	<!-- 绑定自定义事件方法 -->
    	<level1 @testCallback="receiveCall"></level1>
    </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中增加一个过滤,如下图所示:

<div id="app"> 
  <keep-alive exclude="Detail">
    <router-view/>
    </keep-alive>
</div>
  • 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来替代普通连接

  • 不带参数
<router-link :to="{path:'/home'}"> //name,path都行, 建议用name
  • 带参数

    • params传参数 (类似post)
    <router-link :to="{name:'home', params: {id:1}}">
    // 路由配置 path: "/home/:id" 或者 path: "/home:id"
    // 不配置path ,第一次可请求,刷新页面id会消失
    // 配置path,刷新页面id会保留
                        
    // html 取参 $route.params.id
    // script 取参 this.$route.params.id
    
    • query传参数 (类似get,url后面会显示参数)
    <router-link :to="{name:'home', query: {id:1}}">
    // 路由可不配置
                      
    // 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
}

 <router-link :to='{name: "item", params:{itemId: item.id}}'>{{item.name}}</router-link>
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修饰符。

    <!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
    <!-- 而不会等待 `onScroll` 完成  -->
    <!-- 这其中包含 `event.preventDefault()` 的情况 -->
    <div v-on:scroll.passive="onScroll">...</div>
    
  • native:如果在自定义组件标签上绑定原生事件,则需要加上.native。

(2)v-bind修饰符

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

与v-model的区别:

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

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

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

//父组件
<comp :myMessage.sync="bar"></comp> 
//子组件
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}} 
<div v-bind:data="13.44 | formatePrice"></div>
  • 局部过滤器
<script>
	export default {
		data() {
			return {}
		},
		filters: { 
	    formateTime(val) {
	      let date = new Date(val)
	      let year = date.getFullYear()
	      let month = date.getMonth() + 1
	      let day = date.getDate()
	      return `${year}/${month}/${day}`
	    }
	  }
	}
</script>

在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.mutations
    this.mutations = {};
    Object.keys(options.mutations).forEach(mutationsName=>{
      this.mutations[mutationsName] = (payload)=>{
        options.mutations[mutationsName](this.state, payload)
      }
    });
    // 4.解析options.actions
    this.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.state
    return this.vm.state
  }
  // 3.用箭头函数的目的是让this一直指向Store——>$store.commit
  commit = (mutationName, payload) => {this.mutations[mutationsName](payload)}// 4.$store.dispatch
  dispatch = (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

  • 官网解释

  • <transition></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注释呢?
<template comments>
  ...
</template>
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的延时,原因是移动端屏幕双击会缩放。

解决:

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

    <meta name="viewport" content="user-scalable=no">
    
  • 利用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就是click
  console.log(e.currentTarget) //ul 返回绑定的元素box
  console.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就是click
  console.log(e.bubbles)//true 说明是事件冒泡
}
// 如果事件是focus,onblur,scroll那么e.bubbles就是false

// stopPropagation()阻止事件的捕获和冒泡 
box.onclick = function(ev){
  var e = ev || event //e就是MouseEvent,e.type就是click
  e.stopPropagation() //如果是是冒泡:目标事件处理了,目标事件父级对象不会处理(不会向上)
                      //如果是是捕获:目标事件处理了,目标事件子级对象不会处理(不会向下)
}

// stopImmediatePropagation:不仅阻止事件的捕获和冒泡也能阻止同一个事件的其他监听函数被调用(一个按钮有两个回调)
box.onclick = function(ev){
  var e = ev || event //e就是MouseEvent,e.type就是click
  e.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树和渲染树的区别

​ 浏览器将 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 延迟加载的方式有哪些?(<script>放在头部了)
  1. defer 属性:页面解析完了,脚本顺次执行
  2. async 属性:谁先加载完就先执行谁
  3. 动态创建DOM方式
  4. 使用jQuery的getScript方法
  5. 使用setTimeout延迟方法
  6. 让JS最后加载
截屏2022-08-05 16.21.53

Ajax/计算机网络相关

HTTP协议

截屏2022-08-05 17.03.08
1. XMLHttpRequest

XMLHttpRequest已经得到广泛接受,后来W3C对它进行了标准化,提出了XMLHttpRequest标准。XMLHttpRequest标准又分为Level 1Level 2

  • XMLHttpRequest Level 1主要存在以下缺点:

    • 不能发送二进制文件(如图片、视频、音频等),只能发送纯文本数据。

    • 在发送和获取数据的过程中,无法实时获取进度信息,只能判断是否完成。

    • 受同源策略的限制,不能发送跨域请求。

  • Level 2Level 1进行了改进,XMLHttpRequest Level 2中新增了以下功能:

    • 在服务端允许的情况下,可以发送跨域请求。

    • 支持发送和接收二进制数据。

    • 新增formData对象,支持发送表单数据。

    • 发送和获取数据时,可以获取进度信息。

    • 可以设置请求的超时时间。

  • XMLHttpRequest对象发送请求相关API

    • Accept:客户端可以处理的内容类型。比如:Accept: */*

    • Accept-Charset:客户端可以处理的字符集类型。比如:Accept-Charset: utf8

    • Accept-Encoding:客户端可以处理的压缩编码。比如:Accept-Encoding: gzip, deflate, br

    • Accept-Language:客户端当前设置的语言。比如:Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

    • Connection:客服端与服务器之间连接的类型。比如:Connection: keep-alive

    • Cookie:当前页面设置的任何Cookie

    • Host:发出请求页面所在的域。

    • Referer:表示当前请求页面的来源页面的地址,即当前页面是通过此来源页面里的链接进入的。

    • User-Agent:客户端的用户代理字符串。一般包含浏览器、浏览器内核和操作系统的版本型号信息。

    • Content-Type:客户端告诉服务器实际发送的数据类型。比如:Content-Type: application/x-www-form-urlencoded

  • open()方法:并不会真正发送请求,而只是启动一个请求以备发送。

    • 第一个参数 method:要发送的请求的类型。比如GETPOSTPUTDELETE等。

    • 第二个参数 url:请求的URL

    • 第三个参数 async:是否异步发送请求的布尔值。true为异步发送请求。

    • const xhr = new XMLHttpRequest()
      xhr.open('get', '/userInfo', true)
      
  • **send()方法:**第一个参数data:作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null。该参数可以接收字符串、FormDataBlobArrayBuffer等类型。

    • const xhr = new XMLHttpRequest()
      xhr.send(null)
      
  • **setRequestHeader()方法:**可以设置自定义的请求头部信息

    • 第一个参数 header:头部字段的名称。

    • 第二个参数 value:头部字段的值。

    • const xhr = new XMLHttpRequest()
      xhr.open('get', '/server', true)
      xhr.setRequestHeader('MyHeader', 'MyValue')
      xmlhttp.send()
      
  • readyState属性和onreadystatechange事件readyState属性表示请求/响应过程的当前活动阶段。这个属性的值如下:

    • 0(UNSENT)未初始化。尚未调用open()方法。
    • 1(OPENED)启动。已经调用open()方法,但没有调用send()方法。
    • 2(HEADERS_RECEIVED)发送。已经调用send()方法,但尚未接收到响应。
    • 3(LOADING)接收。已经接收到部分响应数据。
    • 4(DONE)完成。已经接收到全部响应数据。

    只要readyState属性的值发生变化,都会触发一次onreadystatechange事件

    const xhr = new XMLHttpRequest()
    xhr.open('get', '/server', true)
    xhr.onreadystatechange = function () {
      if(xhr.readyState !== 4) {
        return  
      }
      if(xhr.status >= 200 && xhr.status < 300) {
        console.log(xhr.responseText)
      }
    }
    xhr.send(null)
    
  • timeout属性和ontimeout事件

    timeout属性表示请求在等待响应多少毫秒之后就终止。如果在规定的时间内浏览器还没有接收到响应,就会触发ontimeout事件处理程序。

    const xhr = new XMLHttpRequest()
    xhr.open('get', '/server', true)
    //将超时设置为3秒钟
    xhr.timeout = 3000 
    // 请求超时后请求自动终止,会调用 ontimeout 事件处理程序
    xhr.ontimeout = function(){
        console.log('请求超时了')
    }
    xhr.send(null)
    
  • overrideMimeType()方法

overrideMimeType()方法能够重写服务器返回的MIME类型,从而让浏览器进行不一样的处理。

假如服务器返回的数据类型是text/xml,由于种种原因浏览器解析不成功报错,这时就拿不到数据。为了拿到原始数据,我们可以把MIME类型改成text/plain,这样浏览器就不会去自动解析,从而我们就可以拿到原始文本了。

const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.overrideMimeType('text/plain')
xhr.send(null)
  • GET请求

将查询字符串参数追加到URL的末尾,将信息发送给服务器。

GET参数的编码方式是无法人为干涉的,这导致了不同浏览器有不同的编码方式,因此最稳妥的方案是人工预编码,人工解码,从而禁止浏览器编码的干涉。

const xhr = new XMLHttpRequest()
// 使用encodeURIComponent()进行编码
const tempParam = encodeURIComponent('age')
const tempValue = encodeURIComponent('20')
xhr.open('get', '/server?tempParam=tempValue&money=100', true)
  • POST请求:POST请求把数据作为请求的主体(请求的body)提交。下面是四种常见的POST请求提交数据方式。
    • application/x-www-form-urlencoded:浏览器的原生<form>表单,如果不设置enctype属性,那么最终就会以application/x-www-form-urlencoded方式提交数据。
    • multipart/form-data:表单上传文件时,必须让<form>表单的enctype等于multipart/form-data
    • application/json:当发送Ajax请求时,把application/json作为请求头,用来告诉服务端消息主体是序列化后的JSON字符串。
    • text/xml:使用HTTP作为传输协议,XML作为编码方式的远程调用规范。
  • 使用XMLHttpRequest模拟表单提交

Content-Type头部信息设置为application/x-www-form-urlencoded。可以使用XMLHttpRequest对象来模仿表单提交。

const xhr = new XMLHttpRequest()
xhr.open('post', '/server', true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
const form = document.getElementById('myForm') 
// serialize()为表单序列化方法
xhr.send(serialize(form))

也可以使用XMLHttpRequest level 2FormData来序列化表单数据。

const xhr = new XMLHttpRequest()
xhr.open('post', '/server', true)
const form = document.getElementById('myForm')
const formData = new FormData(form)
formData.append("id", "123456")
xhr.send(formData)

使用FormData不必明确地在XMLHttpRequest对象上设置请求头部。XMLHttpRequest对象能够识别传入的数据类型是FormData的实例,并配置适当的头部信息。

2. 什么是Ajax,Ajax都有哪些优点和缺点?
  • 什么是Ajax

Ajax是Asynchronous JavaScript and XML的缩写。异步JavaScript和Xml。他是指一种创建交互式网页应用的网页开发技术。

  • Ajax的原理

通过XmlHTTPRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。

简单的说,也就是javascript可以及时向服务器提出请求和处理响应,而不阻塞用户。达到无刷新的效果。

  • 优点

    1、最大的一点是页面无刷新,用户的体验非常好。

    2、使用异步方式与服务器通信,具有更加迅速的响应能力。

    3、可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求和响应对服务器造成的负担。

    4、基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。

  • 缺点

    1、ajax不支持浏览器back按钮。

    2、安全问题 AJAX暴露了与服务器交互的细节。

    3、对搜索引擎的支持比较弱。

    4、破坏了程序的异常机制。

    5、不容易调试。

3. Ajax请求总共有多少种Callback(进度api)

onloadstart、onprogress、onerror、onabort、onload、onloaded

4. Ajax应用和传统web应用有什么不同
  • 在传统的JavaScript编程中,用户需要点击“submit”按钮来发送或者接收数据信息,然后 等待服务器响应请求,需要重新加载整个页面。

  • 使用Ajax技术,就可以使javascript通过XMLHTTPRequest对象直接与服务器进行交互。

  • 通过HTTP Request,一个web页面可以发送一个请求到web服务器并且接收web服务器 返回的信息(不用重新加载页面),展示给用户的还是同一个页面,用户感觉页面刷新,也看不到JavaScript后台进行的发送请求和接收响应。

5. 请解释一下 JavaScript 的同源策略
  • 概念:

同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。它最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。
这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。
指一段脚本只能读取来自同一来源的窗口和文档的属性。

  • 为什么要有同源限制?我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。
6. 阐述一下异步加载JS

异步加载又叫非阻塞加载,浏览器在下载执行js的同时,还会继续进行后续页面的处理

  • 动态插入 script 标签
  • script 标签上添加 defer 或者 async 属性
    • 没有async属性,script将立即获取(下载)并执行,期间阻塞了浏览器的后续处理。如果有async属性,那么script将被异步下载并执行,同时浏览器继续后续的处理。
    • 所有的defer脚本必须保证按顺序执行的。
  • 通过 Ajax 去获取 js 代码,然后通过 eval 执行
7. 延迟加载

有些JS代码在某些情况在需要使用,并不是页面初始化的时候就要用到。延迟加载就是为了解决这个问题。将JS切分成许多模块,页面初始化时只加载需要立即执行的JS,然后其它JS的加载延迟到第一次需要用到的时候再加载。类似图片的延迟加载。

JS的加载分为两个部分:下载和执行。异步加载只是解决了下载的问题,但是代码在下载完成后就会立即执行,在执行过程中浏览器处于阻塞状态,响应不了任何需求。

解决思路:为了解决JS延迟加载的问题,可以利用异步加载缓存起来,但不立即执行,需要的时候在执行。如何进行缓存呢?将JS内容作为Image或者Object对象加载缓存起来,所以不会立即执行,然后在第一次需要的时候在执行。

模拟较长的JS代码执行时间
    var start = Number(new Date());
    while(start + 5000 > Number(new Date())){//执行JS}
    这段代码将使JS执行5秒才完成!

JS延迟加载机制(LazyLoad):简单来说,就是在浏览器滚动到某个位置在触发相关的函数,实现页面元素的加载或者某些动作的执行。如何实现浏览器滚动位置的检测呢?可以通过一个定时器来实现,通过比较某一时刻页面目标节点位置和浏览器滚动条高度来判断是否需要执行函数。

8. Get和Post的区别?什么情况下用到

区别

1、GET使用URL或Cookie传参。而POST将数据放在BODY中

2、GET的URL会有长度上的限制,则POST的数据则可以非常大

3、POST比GET安全,因为数据在地址栏上不可见

最本质的区别

Get是用来从服务器上获得数据(检索),而post是用来向服务器上传递数据(创新)

Get/Post使用场景

若符合下列任一情况,则post方法:

1、请求的结果有持续性的作用,例如:数据库内添加新的数据行

2、若使用get方法,则表单上收集的数据可能让URL过长

3、要传送的数据不是采用ASCII编码

若符合下列任一情况,则用Get方法:

1、请求是为了查找资源,html表单数据仅用来搜索

2、请求结果无持续性的副作用(幂等性)

3、收集的数据及html表单内的输入字段名称的总长不超过1024个字符

9.HTTP与HTTPS的区别
  • Http

超文本传输协议,用于在浏览器和服务器之间传递信息的,是以明文的形式发送内容的,不适合传输敏感信息,比如:身份证、账号、密码……

  • https

是在http的基础上增加了ssl协议,ssl是依靠证书来验证服务器的身份的。它对浏览器和服务器之间的通信进行加密。

Https协议是由ssl+http构成的,是可加密的是进行身份认证的网络协议,因此它比http更安全。

  • 区别:

    • https是需要申请证书的,证书需要费用。
  • Http是明文传输,https是加密传输

    • 连接方式不同,端口不同 http采用80 https 443
  • http是无状态连接,https由ssl+http构成的 是可加密的,身份认证的网络协议。

10. 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么

http协议基于TCP/IP协议
过程:浏览器输入域名->浏览器查询dns是否有缓存->DNS查询到域名->TCP/IP链接(三次握手)->建立连接->浏览器发出请求->服务器响应(1.2.3.4)->浏览器会先获得响应头然后在获得相应体(因为响应体有时候会很大).

1、浏览器地址栏输入url

2、浏览器会先查看浏览器缓存--系统缓存--路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步

3、域名解析(DNS)获取相应的ip

4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手

5、握手成功,浏览器向服务器发送http请求,请求数据包

6、服务器请求数据,将数据返回到浏览器

7、浏览器接收响应,读取页面内容,解析html源码,生成DOm树

8、解析css样式、浏览器渲染,js交互绑定多个域名,数量不限; 
11.描述一下HTTP的请求过程与原理(高薪常问)
  • HTTP请求的过程

域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起HTTP请求 -->服务器响应HTTP请求,浏览器得到html代码 -->浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户

  • 请求原理

HTTP协议是应用层的一种协议,是一种C/S架构服务,基于TCP/IP协议来通信,监听在TCP的80端口上,HTTP协议实现的是客户端可以向服务端获得web资源

12.HTTPS有几次握手和挥手?HTTPS的原理什么是

HTTPS是3次握手和4次挥手,和HTTP是一样的。

原理:

HTTPS在传输数据前需要客户端(浏览器)与服务器(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息.TLS/SSL协议是一套加密传输协议,使用了非对称加密,对称加密,以及HASH算法。

HTTPS为什么安全:

HTTPS之所以比HTTP安全,是因为他利用ssl/tls 协议传输。它包含证书,卸载,流量转发,负载均衡,页面适配,浏览器适配等。保障了传输过程的安全性

13.什么是TCP连接的三次握手
  • 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态, 等待服务器确认;

  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个 SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此 包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

  • TCP协议优点

TCP发送的包有序号,对方收到包后要给一个反馈,如果超过一定时间还没收到反馈就 自动执行超时重发,因此TCP最大的优点是可靠。

  • TCP协议缺点

很简单,就是麻烦,如果数据量比较小的话建立连接的过程反而占了大头,不断地重发也会造成网络延迟,因此比如视频聊天通常就使用UDP,因为丢失一些包也没关系,速度流畅才是重要的。

14.为什么TCP连接需要三次握手四次挥手
  • 为什么是三次握手

为了防止已失效的连接请求报文段突然有送到了服务器,因而产生错误,假设两次握手时,客户发出的第一个请求连接报文段在某一网络节点长时间滞留,以致延误到连接释放后才到达服务器。服务器收到失效的连接请求报文段后,认为是客户又发出一次新的连接请求。 于是向客户发送确认报文段,同意建立连接,此时在假定两次握手的前提下,连接建立成功。这样会导致服务器的资源白白浪费

  • 为什么是四次挥手

TCP协议是全双工通信,这意味着客户端和服务器端都可以向彼此发送数据,所以关闭连接 是双方都需要确认的共同行为,假设是三次挥手时,首先释放了客户到服务器方向的连接, 此时TCP连接处于半关闭状态,这时客户不能向服务器发送数据,而服务器还是可以向客户发送数据。如果此时客户收到了服务器的确认报文段后,就立即发送一个确认报文段,这会导致服务器向客户还在发送数据时连接就被关闭。这样会导致客户没有完整收到服务器所发的报文段

15.TCP与UDP的区别有哪些

TCP 是面向连接的传输控制协议,而UDP提供了无链接的数据报服务//类似电话与邮件

TCP 面向连接,提供可靠的数据服务

TCP首部开销20字节,UDP首部开销8字节

TCP逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

UDP没有拥塞机制,因此网络出现拥堵不会使源主机的发送效率降低(有利于实时会议视频 等)

TCP的连接只能是点到点的,UDP支持一对一,多对一,多对多的交互通信

16.HTTP2 / HTTP1 之间的区别是什么

由于 https 在安全方面已经做的非常好了,http 改进的关注点放在了性能方面。对于 http2.0 而言,它对于性能的提升主要在于两点:

  • 头部压缩

    • 在 http1.1 之前,请求体一般会有响应的压缩编码过程,通过Content-Encoding头部字段来指定,但是请求头并没有被压缩
    • 当请求字段非常复杂的时候,尤其对于 GET 请求,请求报文几乎全是请求头,这个时候还是存在非常大的优化空间的。 http2.0 针对头部字段,也采用了对应的压缩算法——HPACK,对请求头进行压缩
  • 多路复用

    • http2.0 可以复用TCP连接,在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”
  • 二进制分帧

    • http2.0 采用二进制格式传输数据,而非 http1 时候的文本格式,可以将请求和响应数据分割为更小的帧,并且它们采用二进制格式(01串),方便了机器的解析
  • 服务器推送

    • HTTP2让服务器可以将响应主动”推送”到客户端缓存中,HTTP2中服务器会主动将资源推 送给客户端,例如把js和css文件主动推送给客户端而不用客户端解析HTML后请求再响应
17.介绍一下websocket

http协议的通信只能由客户端发起,这就会引起如果服务端有连续的状态变化客户端要获取很麻烦。

websocket 连接允许客户端和服务器之间进行全双工通信, 以便任一方都可以通过建立的连接将数据推送到另一端。websocket 只需要建立一次连接, 就可以一直保持连接状态

截屏2022-07-19 17.51.00
18.拆解一下URL的各个部分,分别是什么意思

例如:scheme://host:port/path?query#fragment

  • .scheme:通信协议,常用的HTTP,ftp,maito等

  • .host:主机,服务器(计算机)域名系统 (DNS) 主机名或 IP 地址

  • .port:端口号,整数,可选,省略时使用方案的默认端口,如HTTP的默认端口为80

  • .path:路径,由零或多个"/"符号隔开的字符串,一般用来表示主机上的一个目录或文件地址

  • .query:查询,可选,用于给动态网页传递参数,可有多个参数,用"&“符号隔开,每个参数的名和值用”="符号隔开

  • .fragment:信息片断,字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。(也称为锚点)

19. JSON和XML之间的区别:

1、JSON是JavaScript Object Notation;XML是可扩展标记语言。
2、JSON是基于JavaScript语言;XML源自SGML。
3、JSON是一种表示对象的方式;XML是一种标记语言,使用标记结构来表示数据项。
4、JSON不提供对命名空间的任何支持;XML支持名称空间。
5、JSON支持数组;XML不支持数组。
6、XML的文件相对难以阅读和解释;与XML相比,JSON的文件非常易于阅读。
7、JSON不使用结束标记;XML有开始和结束标签。
8、JSON的安全性较低;XML比JSON更安全。
9、JSON不支持注释;XML支持注释。
10、JSON仅支持UTF-8编码;XML支持各种编码。

Node.js

1. node的优点是什么?缺点是什么

Node.js 就是一个服务器端的非阻塞式I/O的事件驱动的 JavaScript运行环境。

  • Node.js 有三个特性 :

① 服务器端:字面意思,Node.js 运行在服务器端,为 Javascript提供运行环境的环境服务。
② 非阻塞异步: Node.js 采用了非阻塞型 I/O 机制,在做 I/O 操作的时候不会造成任何的阻塞,当完成之后,以时间的形式通知执行操作。
例如在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。
③ 事件驱动: 事件驱动就是当进来一个新的请求的时,请求将会被压入一个事件队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数
比如读取一个文件,文件读取完毕后,就会触发对应的状态,然后通过对应的回调函数来进行处理

  • 优点

  • 处理高并发场景性能更佳

  • 适合I/O密集型应用,值的是应用在运行极限时,CPU占用率仍然比较低,大部分时间是在做 I/O硬盘内存读写操作

  • 缺点:因为 Node.js 是单线程,带来的缺点有:

    • 不适合CPU密集型应用

    • 只支持单核CPU,不能充分利用CPU

    • 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃。

  • Node.js 的 模块系统

Node.js 中没有全局作用域的概念;
Node.js 中,只能通过 require 方法来加载执行多个 JavaScript 脚本文件;
require 加载只能是执行其中的代码,文件与文件之间由于是模块作用域,所以不会有污染的问题;

模块作用域虽然可以带来了一些好处,可以加载执行多个文件,可以完全避免变量命名冲突污染的问题。

  • Node.js 应用场景

    • 其应用场景分类如下:

      • 善于I/O,不善于计算。因为Nodejs是一个单线程,如果计算(同步)太多,则会阻塞这个线程;
    • 大量并发的I/O,应用程序内部并不需要进行非常复杂的处理;

    • websocket 配合,开发长连接的实时交互应用程序,即:实时通讯 ;

    • 具体场景可以表现为如下:

      ① 用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程序;

      ② 基于web、canvas等多人联网游戏;

      ③ 基于 web 的多人实时聊天客户端、聊天室、图文直播;

      ④ 单页面浏览器应用程序;

      ⑤操作数据库、为前端和移动端提供基于json的API;

2. npm作用是什么

允许用户从NPM服务器下载别人编写的第三方包到本地使用

允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用

通过NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号,可以通过package.json文件来管理项目信息,配置脚本

3. module.exports和exports的区别

本质上是无区别的

最终暴露给外部的都是module.exports,exports只是module.exports的辅助工具,他们是相等的,所以既用exports.xxx也用module.exports,则之前的exports.xxx会被覆盖掉

4. CommonJS 中的 require/exports 和 ES6 中的 import/export 区别

CommonJS 中的 require/exports 和比ES6 中的 import/export出现得早,node.js是他的实现;CommonJS 中的 require/exports 是老的标准,ES6 中的 import/export要比它更加权威。es6通过babel转化为es5执行,所以写的import/export是通过babel转换为require/exports执行的。二者其实都可以看做是输出一个对象和引入一个对象并使用其中的属性或方法。

CommonJS 中的 require/exports是运行时加载,就是说它没办法进行编译处理和优化。而ES6中的 import/export是编译时加载,即在静态编译时就已经包含了需要导入导出的模块

5. 什么是前后端分离的项目?什么是JS渲染的项目,前端渲染和后端渲染的区别(高薪常问)
  • 前后端分离的项目

前端HTML页面通过Ajax调用后端的RESTFUL API接口并使用JSON数据进行交互

  • JS渲染的项目

通过Ajax请求数据以后, 通过JS代码动态创建html的标签和数据等(一般右键查看网页源代码 是看不到渲染后的HTML标签的)

  • 前端渲染

    • 指的是后端返回JSON数据,前端利用预先写的html模板,循环读取JSON数据,拼接字符串(ES6的模板字符串特性大大减少了拼接字符串的的成本),并插入页面。
    • 好处:网络传输数据量小。不占用服务端运算资源(解析模板),模板在前端(很有可能仅部分在前端),改结构变交互都前端自己来了,改完自己调就行。
    • 坏处:前端耗时较多,对前端工作人员水平要求相对较高。前端代码较多,因为部分以前在后台处理的交互逻辑交给了前端处理。占用少部分客户端运算资源用于解析模板。
  • 后端渲染:

    • 前端请求,后端用后台模板引擎直接生成html,前端接收到数据之后,直接插入页面。

    • 好处:前端耗时少,即减少了首屏时间,模板统一在后端。前端(相对)省事,不占用客户端运算资源(解析模板)

    • 坏处:占用服务器资源。

  • 前端渲染与后端渲染对比

    • 后端渲染

      • 页面呈现速度:快,受限于用户的带宽
      • 流量消耗:少一点点(可以省去前端框架部分的代码)
      • 可维护性:差(前后端东西放一起,掐架多年,早就在闹分手啦)
      • seo 友好度:好
      • 编码效率:低(这个跟不同的团队不同,可能不对)
    • 前端渲染

      • 页面呈现速度:主要受限于带宽和客户端机器的好坏,优化的好,可以逐步动态展开内容, 感觉上会更快一点

      • 流量消耗:多一点点(一个前端框架大概50KB)当然,有的用后端渲染的项目前端部分也有在用框架

      • 可维护性:好,前后端分离,各施其职,代码一目明了

      • SEO友好度:差,大量使用Ajax,多数浏览器不能抓取Ajax数据

      • 编码效率:高,前后端各自只做自己擅长的东西,后端最后只输出接口,不用管页面呈现, 只要前后端人员能力不错,效率不会低

6. 事件的订阅和发布的设计模式是什么

其实就是收集事件名, 对应的方法体, 当触发对应事件名时, 把事件名对应的所有方法体调用执行一遍

7. express中Router的作

它拥有和app类似的方法,例如 get、post、all、use 等等router它解决了直接把 app 暴露给其它模块使得 app 有被滥用的风险, 优化路由管理

8. express优点、缺点
  • express 的优点

线性逻辑:路由和中间件完美融合,通过中间件形式把业务逻辑细分,简化,一个请求进来经过一系列中间件处理后再响应给用户,再复杂的业务也是线性了,清晰明了

  • express 缺点

express 是基于 callback 来组合业务逻辑。Callback 有两大硬伤,一是不可组合,二是异常不可捕获

9. 什么是中间件

中间件的本质就是一个函数,在收到请求和返回相应的过程中做一些我们想做的事情。Express文档中对它的作用是这么描述的:

执行任何代码。 修改请求和响应对象。 终结请求-响应循环。 调用堆栈中的下一个中间件。

Express文档中把他们分为了五类,但是他们的原理相同,只是用法不同:

应用级中间件 路由级中间件 错误处理中间件 内置中间件 第三方中间件

express是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个express应用就是在调用各种中间件函数。封装了一些或许复杂但肯定是通用的功能。

10. 请介绍一下require的模块加载机制

1、先计算模块路径
2、如果模块在缓存里面,取出缓存
3、加载模块
4、输出模块的exports属性即可

11.express中如何获取路由的参数

/users/:name使用req.params.name来获取;

req.body.username则是获得表单传入参数username

express路由支持常用通配符 ?, +, *, and ()

12. express response有哪些常用方法

res.download() 弹出文件下载

res.end() 结束response

res.json() 返回json 在这里插入代码片

res.jsonp() 返回jsonp

res.redirect() 重定向请求

res.render() 渲染模板

res.send() 返回多种形式数据

res.sendFile 返回文件

res.sendStatus() 返回状态

webpack

1. 什么是webpack

webpack是前端工程化的具体解决方案,一个静态模块打包器。

在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

主要功能:提供友好的前端模块化开发支持,以及代码压缩、处理浏览器端js的兼容性、性能优化等强大的功能

2. 前端工程化
  • 模块化(js的模块话、css的模块话、资源的模块话)模块化是对文件、对代码和资源拆分,
  • 组件化(复用现有的UI结构、样式、行为)对 UI 层面的拆分
  • 规范化(目录结构的划分、编码规范化、接口规范化、文档规范化、git分支管理)
  • 自动化(自动化创建,自动化部署,自动化测试)
    • 从最早先的 grunt、gulp 等,再到目前的 webpack、parcel。这些自动化工具在自动化合并、构建、打包都能为我们节省很多工作。而这些只是前端自动化其中的一部分,前端自动化还包含了持续集成、自动化测试等方方面面。
3. webpack的优点是什么
  • 轻松使用es6。因为webpack可以在浏览器不支持es6的情况下让你使用es6语法,最新的webpack版本已经不需要配置也能过变异es6语法,之前的版本都需要加入babel加载器才可以打包es6.

  • 可以打包文件,代码体积小加载快

  • 代码改变后,可是在浏览器上没有显示出改变后的效果。

  • 研发流程层面:

    • 统一高效的开发环境
    • 统一的构建流程和产出标准
4. webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全
  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数

  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译

    • compiler:webpack的运行入口,实例化时定义webpack构建主要流程,同时创建时使用的核心对象compilation;
    • compilation:由compiler实例化,存储构建过程中使用的数据,用户监控这些数据的变化,每次构建创建一个compilation实例;
  • 确定入口:根据配置中的 entry 找出所有的入口文件

  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理

  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系

  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会

    • chunk:一般一个入口对应一个chunk;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统,在以上过程中,webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 webpack 提供的 API 改变 webpack的运行结果

5. 说一下 webpack 的热更新原理(HMR)

HMRHot Module Replacement是指当你对代码修改并保存后,webpack将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面而非整体刷新页面。

总结:

  • 加载页面时保存应用程序状态
  • 只更新改变的内容,节省调试时间
  • 修改样式更快,几乎等同于在浏览器中更改样式
$ npm install webpack webpack-dev-server --save-dev
// package.json
"dependencies": {
    "webpack": "^4.41.2",
    "webpack-dev-server": "^3.10.1"
},
// webpack:
devServer: {
    contentBase: path.resolve(__dirname, 'dist')//表示告诉服务器从哪里提供内容。(也就是服务器启动的根目录,默认为当前执行目                                                   录,一般不需要设置)
    hot: true,
    historyApiFallback: true,
    compress: true
},

核心流程

  • 使用 webpack-dev-server (后面简称 WDS)托管静态资源,同时以 Runtime 方式注入 HMR 客户端代码;
  • 浏览器加载页面后,与 WDS 建立 WebSocket 连接;
  • Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件;
  • 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围;
  • 浏览器加载发生变更的增量模块;
  • Webpack 运行时触发变更模块的 module.hot.accept 回调,执行代码变更逻辑;
  • done;
6. webpack 与grunt、gulp的不同

三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等

grunt和gulp是基于任务和流(Task、Stream)的,类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据,整条链式操作构成一个任务,多个任务就构成了整个web的构建流程。

webpack是基于入口,会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin扩展webpack功能。

总结:

  • 从构建思路上来说

gulp和grunt需要开发者将前端构建过程拆分多个Task,并合理控制所有Task的调用关系,webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader 做何种解析和加工。

  • 从知识背景来说

gulp更像后端开发者的思路,需要对于整个流程了如指掌,webpack更倾向于前端开发者的思路

7.哪些常见的Loader(加载器)?有哪些常见的Plugin?他们是解决什么问题的?

loader

file-loader:把文件输出到另一个文件中,在代码中通过相对URL去引用输出文件

url-loader:和file-loader类似,但是能在文件很小的情况下以base64的方式把文件内容注入到代码中去

source-map-loader:加载额外的source Map 文件,以方便断电调试

image-loader:加载并压缩图片文件

babel-loader: 把ES6转成ES5

css-loader:加载CSS,支持模块化、压缩、文件导入等特性

style-loader:把CSS代码注入到JavaScript中,通过DOM操作去加载CSS

eslint-loader:通过ESLint检查JavaScript代码

plugin

define-plugin:定义环境变量

Commons-chunk-plugin:提取公共代码

uglify-webpack-plugin:通过UglifyES压缩ES6代码

8. Loader和Plugin 的不同?
  • loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中

  • plugin 赋予了 webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事。基于事件机制工作,监听打包过程的某些点从而执行广泛的任务。

  • 从整个运行时机上来看,如下图所示:

    • loader 运行在打包文件之前
    • plugins 在整个编译周期都起作用
图片
9.分别介绍一下bundle,chunk,module的作用是什么

1、module:开发中的每一个文件都可以看作是module,模块不局限于js,也包含css图片等

2、chunk:表示代码块,一个chunk可以由多个模块组成

3、bundle:最终打包完成的文件,一般就是和chunk一一对应的关系,bundle就是对chunk进行编译压缩打包等处理后的产出

10. 如何利用webpack来优化前端性能

webpack中的optimization主要用来自定义一些优化打包策略

webpack优化前端的手段有:

  • JS代码压缩
  • CSS代码压缩
  • Html文件代码压缩
  • 文件大小压缩
  • 图片压缩
  • Tree Shaking
  • 代码分离
  • 内联 chunk
11. 使用webpack开发时,你用过哪些可以提高效率的插件?

1、webpack-dashboard:可以更友好的展示相关打包信息。

2、webpack-merge:提取公共配置,减少重复配置代码

3、speed-measure-webpack-plugin:简称 SMP,分析出 webpack 打包过程中 Loader

和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈

4、size-plugin:监控资源体积变化,尽早发现问题

5、HotModuleReplacementPlugin:模块热替换

12. source map是什么?生产环境怎么用?

Source Map 就是一个信息文件,里面存储了代码打包转换后的位置信息,实质是一个 json 描述文件,维护了打包前后的代码映射关系。

实际上就是一个 JSON 键值对

devtool: "source-map"
13. 请详细说明一下Babel编译的原理是什么?

AST:即用一个树型结构的对象来描述js代码

大多数编译器的工作过程可以分为三部分:

  • 1、Parse(解析):将源代码转换成抽象语法树AST;

    一般来说,Parse 阶段可以细分为两个阶段:词法分析(Lexical Analysis, LA)和语法分析(Syntactic Analysis, SA)。

    词法分析:就是把一句完整的代码拆成一块一块的,形成了一个数组,数组的第一项都是一个对象,是对每一小块语法的描述;
    语法分析:通过语法分析把每个对象转化为 AST

  • 2、Transform(转换):对AST做一些特殊处理,让它符合编译器的期望

    Transform,即操作 AST, 也就是操作其中的节点,可以增删改这些节点,从而转换成实际需要的 AST

  • 3、Generate(代码生成):将第二步经过转换过的AST(抽象语法树)生成新的代码

14、什么是长缓存?在webpack中如何做到长缓存优化?

浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或者更新,都需要浏览器去下载新的代码,最方便和最简单的更新方式就是引入新的文件名称。

在webpack中,可以在output给出输出的文件制定chunkhash,并且分离经常更新的代码和框架代码,通过NameModulesPlugin或者HashedModulesPlugin使再次打包文件名不变。

手撕题

1. bind函数

创建一个新的函数

Function.prototype.myBind = function (thisArg, ...args) {
    if (thisArg == null) {
        thisArg = window || global
    }
    const fn = this
    if (typeof fn !== 'function') {
        throw 'wrong called'
    }
    return function (...otherArgs) {
        return fn.apply(thisArg, args.concat(otherArgs))
    }
}
2. call函数

改变内部this指向,立即执行

把当前函数放在一个对象里面,对象的函数方法this指针就是该对象

Function.prototype.myCall = function (thisArg, ...args) {
    const fn = this//表示当前函数,因为当前函数调用了myCall这个方法谁调用了这个方法this就是谁
    if (typeof fn !== 'function') {
        throw 'wrong called'
    }
    if (thisArg == null) {
        thisArg = window || global
    }
    thisArg[fn] = fn//把当前函数放入对象当中
    let res = thisArg[fn](...args)
    delete thisArg[fn]
    return res
}
3. new创建对象
function myNew(con, ...args) {
    let newObj = Object.create(con.prototype) // 空对象的原型设置为构造函数的原型,即obj.proto = func.prototype;
    let res = con.apply(newObj, args) //继承对像属性和方法
    return typeof res === 'object' && res != null ? res : newObj
}
4. instanceof实现

用于检测构造函数的 prototype属性是否出现在某个实例对象的原型链上

例如数组实例 arr的构造函数 Array的原型对象就在该实例对象的原型链上。

function myInstanceof(left, right) {
    if (typeof left !== 'object' || right == null) {
        return false
    }

    left = left.__proto__
    right = right.prototype
    while (true) {
        if (left == null) return false
        if (left === right) return true
        left = left.__proto__
    }
}
5. 防抖函数

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。(函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。)(用户频繁触发事件)

思路:外界事件触发定时器,定时器存在就清除重新定一个定时器,最后执行完将定时器初始化

function debounce(fn, delay = 500) {
    let timer = null
    return function (...args) {
        // 如果定时器已经开启,则清除定时器
        if (timer) clearTimeout(timer)
        // 重新定时
        timer = setTimeout(() => {
            fn.apply(this, args)
            timer = null
        }, delay)
    }
}
6. 节流函数

函数节流是间隔时间执行。

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。(函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹。)

function throttle(fn, delay = 200) {
    let timer = null
    return function (...args) {
        // 如果定时器存在,说明还未到时,直接返回
        if (timer) return
        // 设置定时任务,到时后执行fn,并重置定时器为null
        timer = setTimeout(() => {
            fn.apply(this, args)
            timer = null
        }, delay)
    }
}
7. 函数柯里化

部分求值,给函数分步传递参数

每次传递参数进行处理,并返回一个更具体的函数接受剩下的参数

const sum = (...args) => {
  return args.reduce((a,b) => {
    return a+b;
  })
}

var currying = function(func){
  const args = [] //func函数的参数
  return function result(...rest){//接受剩下的参数,需要递归调用
    if(rest.length === 0 ){//当长度等于0的时候就开始计算sum
      return func(...args)
    }else{//参数有长度
      args.push(...rest);
      return result;//链式调用
    }
  }
}
currying(sum)(1)(2,3)(5)()
8. 数组扁平化
截屏2022-07-25 11.40.27
Array.prototype.flat = function(){
  const result = this.map(item=>{ //对数组每项进行map处理,返回新的数组
    if(Array.isArray(item)){//元素还是个数组,继续进行扁平化
      return item.flat()
    }
    return [item]//[[1],[2],[3]]
  })
  return [].concat(...result)
} 
//去重set
Array.prototype.unique = function(){
  return [...new Set(this)]
}
//排序
const sotrFn = (a,b) => a-b;
arr.flat().unique()..sort(sortFn)
9. 数组转树形结构
var menu_list = [
    {
        id: '1',
        menu_name: '设置',
        menu_url: 'setting',
        parent_id: 0,
    },
    {
        id: '1-1',
        menu_name: '权限设置',
        menu_url: 'setting.permission',
        parent_id: '1',
    },
    {
        id: '1-2',
        menu_name: '菜单设置',
        menu_url: 'setting.menu',
        parent_id: '1',
    },
    {
        id: '2',
        menu_name: '订单',
        menu_url: 'order',
        parent_id: 0,
    },
    {
        id: '2-1',
        menu_name: '报单审核',
        menu_url: 'order.orderreview',
        parent_id: '2',
    },
    {
        id: '2-2',
        menu_name: '退款管理',
        menu_url: 'order.refundmanagement',
        parent_id: '2',
    },
]
function toTree{
  var res = []
  var map = {}
  data.forEach(item=>{//根据id弄个字典
    map[item.id] = item;
  })
  data.foreach(item=>{
    let parent = map[item.pid];
    if(parent){
      (parent.child || (parent.child = [])).push(item)
    }else{//pid找不到导致parent不存在
      res.push(item)
    }
  })
  return res
}
10. 对象深拷贝
截屏2022-07-25 15.09.25

**浅拷贝方法:**Object.assign({},list)、[…list]、{…list}、map、filter、reduce

深拷贝方法:

  • Json

    • JSON.parse(JSON.stringify(list))

    • json能解决数组的深拷贝也能解决Object的深拷贝

function deepCopy(newObj, oldObj){
  for(let k in oldObj){
    // 判断我们的属于那种数据类型
    // 1. 获取属性值
    let item = oldObj[k]
    // 2. 判断值是否是数组
    if(item instanceof Array){
      newObj[k] = [];
      deepCopy(newObj[k], item)
    }else if(item instanceof Object){// 3. 判断是是否是对象
      newObj[k] = {}
      deepCopy(newObj[k], item)
    }else{// 4. 属于简单数据类型
      newObj[k] = item
    }
  }
}
11.洋葱模型
app.use(async next => {
  console.log(1)
  await next()
  console.log(2)
})
app.use(async next => {
  console.log(3)
  await next()
  console.log(4)
})
app.use(async next => {
  console.log(5)
  await next()
  console.log(6)
})
app.compose() //135642
12.手写发布订阅模式就是事件总线

创建一个 EventEmitter

在该类上创建一个事件中心(Map)

on 方法用来把函数 fn 都加到事件中心中(订阅者注册事件到调度中心)

emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码)

off 方法可以根据 event 值取消订阅(取消订阅)

once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)

注册一个 newListener 用于监听新的事件订阅

<body>
  <div id="app"></div>
  <script>
    class Vue {
      constructor() {
        // 用来存储事件
        // 存储的 例子 this.subs = { 'myclick': [fn1, fn2, fn3] ,'inputchange': [fn1, fn2] }
        this.subs = {}
      }
      // 实现 $on 方法 type是任务队列的类型 ,fn是方法
      $on(type, fn) {
        // 判断在 subs是否有当前类型的 方法队列存在
        if (!this.subs[type]) {
          // 没有就新增一个 默认为空数组
          this.subs[type] = []
        }
        // 把方法加到该类型中
        this.subs[type].push(fn)
      }
      // 实现 $emit 方法
      $emit(type) {
        // 首先得判断该方法是否存在
        if (this.subs[type]) {
          // 获取到参数
          const args = Array.prototype.slice.call(arguments, 1)
          // 循环队列调用 fn
          this.subs[type].forEach((fn) => fn(...args))
        }
      }
    }

    // 使用
    const eventHub = new Vue()
    // 使用 $on 添加一个 sum 类型的 方法到 subs['sum']中
    eventHub.$on('sum', function () {
      let count = [...arguments].reduce((x, y) => x + y)
      console.log(count)
    })
    // 触发 sum 方法
    eventHub.$emit('sum', 1, 2, 4, 5, 6, 7, 8, 9, 10)
  </script>
</body>
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] = value
                    resolvedCount++;   
                    // 如果全部p都为resolved状态,return的promise状态为resolved
                    if(resolvedCount === promises.length){
                        resolve(values)
                    }
                },
                reason =>{ //只要有一个失败 return的promise状态reject
                    reject(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的状态就为resolved
                    resolve(value)

                },
                reason => { //只要有一个失败,return的promise状态就为reject
                    reject(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';//默认的状态 pending
      this.value = undefined//成功的值默认undefined
      this.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两个函数传给执行器executer
        executer(resolve,reject);
      }catch(e){
        reject(e);//失败的话进catch
      }
    }
    then(onFufilled,onReject){
      //如果状态成功调用onFufilled
      if(this.status = 'resolve'){
        onFufilled(this.value);
      }
      //如果状态失败调用onReject
      if(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.mutations
    this.mutations = {};
    Object.keys(options.mutations).forEach(mutationsName=>{
      this.mutations[mutationsName] = (payload)=>{
        options.mutations[mutationsName](this.state, payload)
      }
    });
    // 4.解析options.actions
    this.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.state
    return this.vm.state
  }
  // 3.用箭头函数的目的是让this一直指向Store——>$store.commit
  commit = (mutationName, payload) => {this.mutations[mutationsName](payload)}// 4.$store.dispatch
  dispatch = (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
<!DOCTYPE html>
<html>
<head>
  <script src="vue-2.5.22.js"></script>
  <link rel="stylesheet" type="text/css" href="animate-3.7.0.css"></link>
</head>
<style>
  /* 把所有元素内外边距和列表样式去掉 */
  *{
    margin: 0;
    padding: 0;
    text-decoration: none;
    list-style: none;
  }
  #banner{
    width: 600px;
    height: 300px;
    border: 1px solid #999999;
    overflow: hidden;
    position: relative;
  }
  #imgList{
    /*5张图片每个图片宽600px,因为要补上最后的空白*/
    width: 3000px;
    height: 300px;
  }
  #imgList li{
    float: left;
  }
  #imgList img{
    width: 600px;
    height: 300px;
  }
  .prev{
    background: #666;
    width: 30px;
    height: 40px;
    color: #fff;
    /* 设置对齐方式 */
    text-align: center;
    line-height: 40px;
    position: absolute;
    left: 5px;
    top: 45%;
    cursor: pointer;
  }
  .next{
    background: #666;
    width: 30px;
    height: 40px;
    color: #fff;
    /* 设置对齐方式 */
    text-align: center;
    line-height: 40px;
    position: absolute;
    right: 5px;
    top: 45%;
    cursor: pointer;
  }
  #iconList{
    position: absolute;
    right: 10px;
    bottom: 10px;
  }
  #iconList li{
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: #666;
    text-align: center;
    line-height: 30px;
    color: #ffffff;
    float: left;
    margin-left: 5px;
    cursor: pointer;
  }
</style>
<body>
  <div id="banner">
    <ul id="imgList">
      <li><img src="./img/pic1.jpg" alt=""></li>
      <li><img src="./img/pic2.jpg" alt=""></li>
      <li><img src="./img/pic3.jpg" alt=""></li>
      <li><img src="./img/pic4.jpg" alt=""></li>
      <li><img src="./img/pic1.jpg" alt=""></li>
    </ul>
    <ul id="iconList">
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
    </ul>
    <div class="prev"> < </div>
    <div class="next"> > </div>
  </div>
  <script>
    var left = 0;
    var timer;
    var imgList = document.getElementById('imgList')
    var ePrev = document.querySelector('.prev')
    var enext = document.querySelector('.next')
    // icon数组
    var iconArray = document.getElementById('iconList').getElementsByTagName('li')
    // 获取圆点列表元素
    var iconList = document.querySelector('#iconList')
    run();
    function run(){
      if(left <= -2400){
        // 说明已经全部轮动完了,开始新的一轮
        left = 0;
      }
      var m = Math.floor(-left/600)
      imgList.style.marginLeft = left + 'px'
      var n = (left % 600 == 0) ? n = 1200 : n =10; // 注意先判断left 再left操作
      left -= 10
      timer = setTimeout(run,n) // 其实有点像递归了
      colorChange(m)
    }
    // 直接改变imgList的marginLeft
    function imgChange(n){
      let lt = -(n * 600);
      imgList.style.marginLeft = lt + 'px'
      left = lt;
    }
    ePrev.onclick = function (){
      let prevGo = Math.floor(-left / 600) - 1;
      if(prevGo === -1){
        prevGo = 3;
      }
      imgChange(prevGo)
    }
    enext.onclick = function (){
      let nextGo = Math.floor(-left / 600) + 1;
      if(nextGo === 4){
        nextGo = 0;
      }
      imgChange(nextGo)
    }
    function colorChange(m){
      // 通过for循环所有li的元素背景色情况
      for(let i = 0; i < iconArray.length; i++){
        //为什么??
        iconArray[i].style.backgroundColor = ''
      }
      if(m < iconArray.length){
        iconArray[m].style.backgroundColor = 'red'
      }
    }
    // 使用事件委托,获取li的父元素ul
    iconList.onclick = function () {
      var tg = event.target;//当前点击的li
      let x = tg.innerHTML - 1;
      colorChange(x);
      imgChange(x);
    }
    // 鼠标停留就不会滚动
    imgList.onmouseover = function(){
      clearTimeout(timer);
    }
    imgList.onmouseout = function (){
      run()
    }
  </script>
</body>
</html>

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 判断某个属性或方法是否原生支持,如果不支持或通过第三方实现支持都会返回 false


export 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 = true
        timerFunc()
    }
    
    // 如果没有传入回调,并且当前环境支持 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)) {  // 支持 promise
    const 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) % 2
        textNode.data = String(counter)  // 数据更新
    }
    isUsingMicroTask = true    // 标记当前 nextTick 使用的微任务
    
    
    // 判断当前环境是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    timerFunc = () => { setImmediate(flushCallbacks)  }
} else {

    // 以上三种都不支持就选择 setTimeout
    timerFunc = () => { setTimeout(flushCallbacks, 0) }
}


// 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中
// 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
function flushCallbacks() {
    pending = false    
    const copies = callbacks.slice(0)    // 拷贝一份 callbacks
    callbacks.length = 0    // 清空 callbacks
    for (let i = 0; i < copies.length; i++) {    // 遍历执行传入的回调
        copies[i]()
    }
}

// 为什么要拷贝一份 callbacks

// 用 callbacks.slice(0) 将 callbacks 拷贝出来一份,
// 是因为考虑到在 nextTick 回调中可能还会调用 nextTick 的情况,
// 如果在 nextTick 回调中又调用了一次 nextTick,则又会向 callbacks 中添加回调,
// 而 nextTick 回调中的 nextTick 应该放在下一轮执行,
// 否则就可能出现一直循环的情况,
// 所以需要将 callbacks 复制一份出来然后清空,再遍历备份列表执行回调
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值