2022年最新前端面试题(大前端时代来临卷起来吧小伙子们..持续维护走到哪记到哪)_前端面试

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

css经典高频面试题

前端核心手写面试题看你的核心扎实不扎实

js部分面试题


js的数据类型关于数据类型相关的

基本数据类型

ES5的5种NullundefinedBooleanNumberString ES6新增Symbol表示独一无二的值 ES10新增BigInt 表示任意大的整数

一种引用数据类型本质上是由一组无序的键值对组成

引用数据类型: Object。包含Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据也就是说JavaScript中所有值的类型都是上面8中之一。

null 和 undefined 的区别

相同

在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等

首先 Undefined 和 Null 都是基本数据类型这两个基本数据类型分别都只有一个值就是 undefined 和 null。

不同

undefined 代表的含义是未定义

定义了形参没有传实参显示undefined

一般变量声明了但还没有定义的时候会返回 undefined

对象属性名不存在时显示undefined

函数没有写返回值即没有写return拿到的是undefined

null 代表的含义是空对象。也作为对象原型链的终点

null 主要用于赋值给一些可能会返回对象的变量作为初始化。

ES10新增BigInt 表示任意大的整数

BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时以任意精度表示整数的能力尤为重要。使用BigInt整数溢出将不再是问题。

此外可以安全地使用更加准确时间戳大整数ID等而无需使用变通方法。 BigInt目前是第3阶段提案 一旦添加到规范中它就是JS 第二个数字数据类型也将是 JS 第8种基本数据类型

要创建BigInt只需在整数的末尾追加n即可。比较:
console.log(9007199254740995n);    // → 9007199254740995n
console.log(9007199254740995);     // → 9007199254740996
​
或者可以调用BigInt()构造函数
BigInt("9007199254740995");    // → 9007199254740995n
​
// 注意最后一位的数字
9007199254740992 === 9007199254740993;    // → true
console.log(9999999999999999);    // → 10000000000000000

数据类型存储以及堆栈内存是什么

基本数据类型直接存储在栈内存中占据空间小大小固定属于被频繁使用的数据。指的是保存在内存中的简单数据段number string 布尔

引用数据类型同时存储在栈内存与堆内存中占据空间大大小不固定。

引用数据类型将指针存在栈中将值存在堆中。 当我们把对象值赋值给另外一个变量时复制的是对象的指针指向同一块内存地址意思是变量中保存的实际上只是一个指针这个指针指向内存堆中实际的值数组 对象

堆(heap)和栈(stack)有什么区别存储机制

是一种连续储存的数据结构具有先进后出后进先出的性质

通常的操作有入栈压栈出栈和栈顶元素。想要读取栈中的某个元素就是将其之间的所有元素出栈才能完成。

堆  是一种非连续的树形储存数据结构具有队列优先,先进先出 每个节点有一个值整棵树是经过排序的。特点是根结点的值最小或最大且根结点的两个子树也是一个堆。常用来实现优先队列存取随意。

js数据类型判断条件分支

if语句和逻辑运算

所有基本类型中Boolean值是false的只有6个分别是 : 0 NaN ' ' null undefined false 引用类型Boolean值全是true.

if条件是单个值时如果是truly值条件成立 如果是falsely值条件不成立

逻辑运算符以及他们的运算规则?

        && 逻辑与    两边都是true才返回true否则返回false
        || 逻辑或    两边只要有一个是true就返回true否则返回false
         逻辑非   用来取一个布尔值相反的值  

数据类型判断

typeof 对于基本数据类型判断是没有问题的但是遇到引用数据类型如Array是不起作用
console.log(typeof 2);    // number
console.log(typeof null); // object
`instanceof` 只能正确判断引用数据类型  而不能判断基本数据类型其内部运行机制是判断在其原型链中能否找到该类型的原型
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true
constructor 似乎完全可以应对基本数据类型和引用数据类型 但如果声明了一个构造函数并且把他的原型指向了 Array 的原型所以这种情况下constructor 也显得力不从心
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
console.log((2).constructor === Number); // true
Object.prototype.toString.call() 完美的解决方案可以通过toString() 来获取每个对象的类型
​
`Object.prototype.toString.call()` 使用 Object 对象的原型方法 toString 来判断数据类型
​
var a = Object.prototype.toString;
 
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));
补充基本数据类型赋值的时候  赋的是具体的值   引用数据类型传的是地址一个变另一个跟着变

js数据类型转换

在JavaScript中类型转换有三种情况

转换为数字调用Number(),parseInt(),parseFloat()方法

转换为字符串调用.toString()或String()方法

转换为布尔值调用Boolean()方法 还有隐式转换 注意null、undefined没有.toString方法

​
转换为数字
Number()可以把任意值转换成数字如果要转换的字符串中有不是数字的值则会返回NaN
​
Number('1')   // 1
Number(true)  // 1
Number('123s') // NaN
Number({})  //NaN
​
​
parseInt(string,radix)解析一个字符串并返回指定基数的十进制整数radix是2-36之间的整数表示被解析字符串的基数。
parseInt('2') //2
parseInt('2',10) // 2
parseInt('2',2)  // NaN
parseInt('a123')  // NaN  如果第一个字符不是数字或者符号就返回NaN
parseInt('123a')  // 123
​
​
parseFloat(string)解析一个参数并返回一个浮点数
​
parseFloat('123a')
//123
parseFloat('123a.01')
//123
parseFloat('123.01')
//123.01
parseFloat('123.01.1')
//123.01
​
隐式转换
let str = '123'
let res = str - 1 //122
str+1 // '1231'
+str+1 // 124
​
转换为字符串
.toString()  ⚠️注意null,undefined不能调用
​
Number(123).toString()
//'123'
[].toString()
//''
true.toString()
//'true'
​
​
String() 都能转
String(123)
//'123'
String(true)
//'true'
String([])
//''
String(null)
//'null'
String(undefined)
//'undefined'
String({})
//'[object Object]'
​
​
隐式转换当+两边有一个是字符串另一个是其它类型时会先把其它类型转换为字符串再进行字符串拼接返回字符串
​
let a = 1
a+'' // '1'
转换为布尔值
0, ''(空字符串), null, undefined, NaN会转成false其它都是true
Boolean()
Boolean('') //false
Boolean(0) //false
Boolean(1) //true
Boolean(null) //false
Boolean(undefined) //false
Boolean(NaN) //false
Boolean({}) //true
Boolean([]) //true
​
条件语句
​
let a
if(a) {
  //...   //这里a为undefined会转为false所以该条件语句内部不会执行
}
​
隐式转换 !!
​
let str = '111'
console.log(!!str) // true


{}和[]的valueOf和toString的返回结果
valueOf返回指定对象的原始值
​
对象                  返回值 
Array               返回数组对象本身。
Boolean             布尔值。
Date                存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function            函数本身。
Number              数字值。
Object              对象本身。这是默认情况。
String              字符串值。
                    Math 和 Error 对象没有 valueOf 方法。
​
toString返回一个表示对象的字符串。默认情况下toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖
toString() 返回 "[object type]"其中 type 是对象的类型。
​
({}).valueOf()   //{}
({}).toString()  //'[object Object]'
[].valueOf()    //[]
[].toString()   //''
​

数据类型相比较objected .is ==和===

=== 属于严格判断直接判断两者类型是否相同如果两边的类型不一致时不会做强制类型准换不同则返回false如果相同再比较大小不会进行任何隐式转换对于引用类型来说比较的都是引用内存地址所以===这种方式的比较除非两者存储的内存地址相同才相等反之false

== 二等表示值相等。判断操作符两边对象或值是否相等类型可以不同如果两边的类型不一致则会进行强制类型转化后再进行比较使用Number()转换成Number类型在进行判断。例外规则null==undefinednull/undefined进行运算时不进行隐式类型转换。通常把值转为Boolean值进行条件判断。Boolean(null)===Boolean(undefined)>false===false 结果为true

Object.is()在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等但NaN与NaN相等

==操作符的强制类型转换规则
​
字符串和数字之间的相等比较将字符串转换为数字之后再进行比较。
其他类型和布尔类型之间的相等比较先将布尔值转换为数字后再应用其他规则进行比较。
null 和 undefined 之间的相等比较结果为真。其他值和它们进行比较都返回假值。
对象和非对象之间的相等比较对象先调用 ToPrimitive 抽象操作后再进行比较。
如果一个操作值为 NaN 则相等比较返回 false NaN 本身也不等于 NaN 。
如果两个操作值都是对象则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象则相等操作符返回true否则返回 false。
​
    '1' == 1 // true
    '1' === 1 // false
    NaN == NaN //false
    +0 == -0 //true
    +0 === -0 // true
    Object.is(+0,-0) //false
    Object.is(NaN,NaN) //true

typeof null 的结果是什么为什么

typeof null 的结果是Object。

在 JavaScript 第一个版本中所有值都存储在 32 位的单元中每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中共有五种数据类型

000: object   - 当前存储的数据指向一个对象。
  1: int      - 当前存储的数据是一个 31 位的有符号整数。
010: double   - 当前存储的数据指向一个双精度的浮点数。
100: string   - 当前存储的数据指向一个字符串。
110: boolean  - 当前存储的数据是布尔值。

有两种特殊数据类型

  • undefined的值是 (-2)30(一个超出整数范围的数字)

  • null 的值是机器码 NULL 指针(null 指针的值全是 0)

那也就是说null的类型标签也是000和Object的类型标签一样所以会被判定为Object。

事件的故事

什么是事件

事件是文档和浏览器窗口中发生的特定的交互瞬间事件就发生了。

一是直接在标签内直接添加执行语句

二是定义执行函数。

addeventlistener 监听事件

事件类型分两种事件捕获、事件冒泡。

事件捕获就是网景公司提出的事件流叫事件捕获流由外往内从事件发生的顶点开始逐级往下查找一直到目标元素。

事件冒泡IE提出的事件流叫做事件冒泡就是由内往外从具体的目标节点元素触发逐级向上传递直到根节点。

什么是事件流?

事件流就是页面接受事件的先后顺序就形成了事件流。

自定义事件

自定义事件就是自己定义事件类型自己定义事件处理函数。

事件委托

事件委托又名事件代理。事件委托就是利用事件冒泡就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡那么委托也就没法实现了

阻止事件冒泡

event.stopPropagation() .stop修饰符

addEventListener(‘click',函数名,true/false) 默认值为false即 使用事件冒泡true 事件捕获

好处提高性能减少了事件绑定从而减少内存占用

应用场景 在vue中事件委托

我们经常遇到vue中v-for一个列表列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现

封装事件绑定

我们在封装这个函数的时候可以用addEventListener(事件监听)来实现 封装的函数有三个参数第一个是要绑定事件的元素第二个是要绑定的事件类型第三个是事件的执行函数。 调用这个函数 就可以实现给某个元素绑定一个事件了。

Javascript 的作用域和作用域链

作用域 作用域是定义变量的区域它有一套访问变量的规则这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量标识符进行变量查找。简单说函数内部局部作用域函数外面全局作用域。

作用域就是一个变量可以使用的范围主要分为全局作用域和函数作用域

全局作用域就是Js中最外层的作用域在哪里都可以访问

函数作用域是js通过函数创建的一个独立作用域只能在函数内部访问函数可以嵌套所以作用域也可以嵌套

Es6中新增了块级作用域由大括号包裹比如if(){},for(){}等

防抖节流

防抖所谓防抖就是指触发事件后在 n 秒内函数只能执行一次如果在 n 秒内又触发了事件则会重新计算函数执行时间。

节流所谓节流就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现分别是时间戳版和定时器版。

鼠标事件 mouseenter与mouseover区别

mouseenter 鼠标进入被绑定事件监听元素节点时触发一次再次触发是鼠标移出被绑定元素再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出即使鼠标动了也不再触发。

mouseover 鼠标进入被绑定事件监听元素节点时触发一次如果目标元素包含子元素鼠标移出子元素到目标元素上也会触发。

mouseenter 不支持事件冒泡 mouseover 会冒泡

引用数据类型 object

object的方法

Object.is() 是一种判断两个值是否相同的方法。
语法Object.is(value1, value2);
参数value1要比较的第一个值。value2要比较的第二个值。
返回值一个布尔表达式指示两个参数是否具有相同的值。

Object.assign() 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。
语法Object.assign(target, ...sources)
参数target目标对象——应用源属性的对象修改后返回。sources源对象——包含你要应用的属性的对象。
返回值修改后的目标对象。


Object.entries() ES8的Object.entries是把对象转成键值对数组 [key, value] 对的数组。
语法Object.entries(obj)
参数obj要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
Object.fromEntries则相反是把键值对数组转为对象

Object.values() 方法返回给定对象自己的可枚举属性值的数组其顺序与 for...in 循环提供的顺序相同。
语法Object.values(obj)
参数obj要返回其可枚举自身属性值的对象。返回值包含给定对象自己的可枚举属性值的数组。

Object.prototype.hasOwnProperty()
hasOwnProperty() 方法返回一个布尔值指示对象是否具有指定的属性作为它自己的属性。
如果指定的属性是对象的直接属性则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明则返回 false。
语法hasOwnProperty(prop)
参数prop要测试的属性的字符串名称或符号。
返回值如果对象将指定的属性作为自己的属性则返回true否则为false。

Object.keys()
Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组以与普通循环相同的顺序迭代。
语法Object.keys(obj)
参数obj要返回可枚举自身属性的对象。
返回值表示给定对象的所有可枚举属性的字符串数组。

Object.prototype.toString()
toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时将自动调用此方法 id。默认情况下toString() 方法由从 Object 继承的每个对象继承。
语法toString()
返回值表示对象的字符串。

Object.freeze()
Object.freeze() 方法冻结一个对象这意味着它不能再被更改。冻结对象可防止向其添加新属性防止删除现有属性防止更改现有属性的可枚举性、可配置性或可写性并防止更改现有属性的值。它还可以防止其原型被更改。
语法Object.freeze(obj)
参数obj要冻结的对象。返回值传递给函数的对象。

Object.create()方法创建一个新对象使用现有的对象来提供新创建的对象的__proto__。 请打开浏览器控制台以查看运行结果。
语法const me = Object.create(person);
参数
proto新创建对象的原型对象。
propertiesObject
可选。需要传入一个对象该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined该传入对象的自有可枚举属性(即其自身定义的属性而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
返回值
一个新对象带着指定的原型对象和属性。

对象和面向对象

对象属性和方法的集合叫做对象万物皆对象。

面向对象首先就是找对象如果该对象不具备所需要的方法或属性那就给它添加。 面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。

创建对象的方式(4种)new Object、字面量、构造函数、原型。

什么是深拷贝浅拷贝浅拷贝 赋值的区别如何实现

深拷贝和浅拷贝是针对复杂数据类型来说的浅拷贝只拷贝一层而深拷贝是层层拷贝。

1.浅拷贝

  • 将原对象或原数组的引用直接赋给新对象新数组新对象只是对原对象的一个引用而不复制对象本身新旧对象还是共享同一块内存

  • 如果属性是一个基本数据类型拷贝就是基本类型的值如果属性是引用类型拷贝的就是内存地址,

2.深拷贝

  • 创建一个新的对象和数组将原对象的各项属性的“值”数组的所有元素拷贝过来是“值”而不是“引用”

  • 深拷贝就是把一个对象从内存中完整的拷贝出来从堆内存中开辟了新区域用来存新对象并且修改新对象不会影响原对象

3、赋值

当我们把一个对象赋值给一个新的变量时赋的是该对象在栈中的内存地址而不是堆中的数据。也就是两个对象

具体实现看开头的手写系列

浅拷贝的实现方式
   1、object.assign()
   2、lodash 里面的 _.clone 
   3、...扩展运算符
   4、 Array.prototype.concat 
   5、 Array.prototype.slice
​
    深拷贝的实现方式
    1、 JSON.parse(JSON.stringify())
    2、递归操作
    3、cloneDeep
    4、Jquery.extend()   

数组

数组的方法

1、sort( )sort 排序 如果下面参数的正反 控制 升序和降序 返回的是从新排序的原数组
2、splice( )向数组的指定index处插入 返回的是被删除掉的元素的集合会改变原有数组截取类 没有参数返回空数组原数组不变一个参数从该参数表示的索引位开始截取直至数组结束返回截取的 数组原数组改变两个参数第一个参数表示开始截取的索引位第二个参数表示截取的长度返回截取的 数组原数组改变三个或者更多参数第三个及以后的参数表示要从截取位插入的值。会改变原数据
3、pop( )从尾部删除一个元素 返回被删除掉的元素改变原有数组。
4、push( )向数组的末尾追加 返回值是添加数据后数组的新长度改变原有数组。
5、unshift( )向数组的开头添加 返回值是添加数据后数组的新长度改变原有数组。
6、shift( )从头部删除一个元素 返回被删除掉的元素改变原有数组。
7、reverse( ) 原数组倒序  它的返回值是倒序之后的原数组
8、concat( )数组合并。
9、slice( )数组元素的截取返回一个新数组新数组是截取的元素可以为负值。从数组中截取如果不传参会返回原数组。如果只传入一个参数会从头部开始删除直到数组结束原数组不会改变传入两个参数第一个是开始截取的索引第二个是结束截取的索引不包含结束截取的这一项原数组不会改变。最多可以接受两个参数。
10、join( )讲数组进行分割成为字符串  这能分割一层在套一层就分隔不了了
11、toString( )数组转字符串
12、toLocaleString( )将数组转换为本地数组。
13、forEach( )数组进行遍历
14、map( )没有return时对数组的遍历。有return时返回一个新数组该新数组的元素是经过过滤(逻辑处理)过的函数。
15、filter( )对数组中的每一运行给定的函数会返回满足该函数的项组成的数组。
16、every( )当数组中每一个元素在callback上被返回true时就返回true。注every其实类似filter只不过它的功能是判断是不是数组中的所有元素都符合条件并且返回的是布尔值。
17、some( )当数组中有一个元素在callback上被返回true时就返回true。注every其实类似filter只不过它的功能是判断是不是数组中的所有元素都符合条件并且返回的是布尔值。
18、reduce( )回调函数中有4个参数。prev之前计算过的值next之前计算过的下一个的值indexarr。把数组列表计算成一个
19.isArray() 判断是否是数组
20. indexOf  找索如果找到了就会返回当前的一个下标若果没找到就会反回-1
21. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标若果没找到就会反回-1
22. Array.of() 填充单个值
23. Array.from() 来源是类数组    
24.fill填充方法 可以传入3各参数 可以填充数组里的值也就是替换 如果一个值全部都替换掉     第一个参数就是值 第二个参数 从起始第几个 第三个参数就是最后一个
find  查找这一组数 符合条件的第一个数 给他返回出来
findIndex() 查找这一组数 符合条件的第一数的下标 给他返回出来     没有返回 -1  
keys 属性名  values属性值  entries属性和属性值
forEach 循环遍历 有3个参数 无法使用 break continue  参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素

 
### 改变数组本身的api
1. `pop()`  尾部弹出一个元素
2. `push()` 尾部插入一个元素
3. `shift()`  头部弹出一个元素
4. `unshift()`  头部插入一个元素
5. `sort([func])` 对数组进行排序,func有2各参数其返回值小于0那么参数1被排列到参数2之前反之参数2排在参数1之前
6. `reverse()` 原位反转数组中的元素
7. `splice(pos,deleteCount,...item)`  返回修改后的数组从pos开始删除deleteCount个元素并在当前位置插入items
8. `copyWithin(pos[, start[, end]])` 复制从start到end(不包括end)的元素到pos开始的索引返回改变后的数组浅拷贝
9. `arr.fill(value[, start[, end]])` 从start到end默认到数组最后一个位置不包括end填充val返回填充后的数组
其他数组api不改变原数组




map 映射关系的数组  map 主要就是有返回值可以return 数组   判断的会返回boolean 
1、map()方法返回一个新数组新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。
2、map()方法按照原始数组元素顺序依次处理元素。

注意
map()不会对空数组进行检测。
map()不会改变原始数组。
map() 函数的作用是对数组中的每一个元素进行处理返回新的元素。
filter 满足条件的都能返回 是一个数组
some返回boolean 循环数组 只要有一个成员通过了就会返回 true 反而 false
every返回boolean 循环数组 只有全部成员通过了就会返回 true 反而 false 
reduce() 累加器 把上一次计算的值给下一次计算进行相加
set 对象允许你存储任何类型的唯一值无论是原始值或者是对象引用  
delete  [1] delete 可以删除数组中的一向
**Array.isArray()** 用于确定传递的值是否是一个 [`Array`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array)。
flat  扁平化 将嵌套的数组 “拉平”变成一维数组。该方法返回一个新数组对原数据没有影响。// 参数写的就是代表要扁平到第几层



//1、every()
var arr = [1,56,80,5];
var main = arr.every(n => n > 0);
console.log(main)   //输出:true

//2、some()
var arr = [1,-56,80,-5];
var main = arr.some(n => n > 0);
console.log(main)    //输出:true

//3、reducer()
var arr = [10,20,30,40]
let result = arr.reduce(function(prev,next,index,arr){
	return prev + next;
})
console.log(result);  //输出:100

// 4、filter  返回满足要求的数组项组成的新数组
var arr3 = [3,6,7,12,20,64,35]
var result3 = arr3.filter((item,index,arr)=>{
    return item > 3
})
console.log(result3)  //[6,7,12,20,64,35]

// 5、map  返回每次函数调用的结果组成的数组
var arr4 = [1,2]
var result4 = arr4.map((item,index,arr)=>{
    return `<span>${item}</span>`
})
console.log(result4)  
/*[ '<span>1</span>',
  '<span>2</span>', ]*/


ES6数组的常用方法

1、Array.from( )将对象或字符串转成数组注意得有length。
2、Array.of( ) 将一组值转换为数组。
3、copyWithin(targetstart(可选)end(可选))数组内数据的复制替换
	target从该位置开始替换数据
	start从该位置开始读取数据默认为0
	end到该位置停止数据的读取默认为数组的长度
4、find( )用于找出第一个符合条件的数组成员。
5、findIndex( )返回第一个符合条件的数组成员的位置如果所有成员都不符合条件则返回-1。
6、fill(valuestartend)使用给定值填充一个数组。
	value填充的值
	start开始填充的位置
	end填充结束的位置。
7、keys( )对键名的遍历。
8、values( )对键值的遍历。
9、entries( )对键值对的遍历。
10、includes( )数组原型的方法,查找一个数值是否在数组中只能判断一些简单类型的数据对于复杂类型的数据无法判断。该方法接受两个参数分别是查询的数据和初始的查询索引值。
11、flat( )用于数组扁平数组去除未定义。可以去除空项。
12、flatMap( )对原数组的每个成员执行一个函数。
13、Map( )是一组键值对的结构具有极快的查找速度。
14、Set( )Set和Map类似也是一组key的集合但不存储value。由于key不能重复所以在Set中没有重复的key。


//1、Array.from()  --   Array.of()
	var  arrayLink = {
		"0":"a",
		"1":"b",
		"2":"c",
		length:3
	}
	var arr = Array.from(arrayLink)
	console.log(arr)   // 输出: [a,b,c]
	console.log(Array.from("abcdefg"))  //输出:["a", "b", "c", "d", "e", "f", "g"]
	console.log(Array.of(1,2,3,4,5))  //输出: [1, 2, 3, 4, 5]

//2、copyWithin()
	var arr = [1,2,3,4,5];
	var main = arr.copyWithin(0,3);
	console.log(main);   //输出:[4,5,3,4,5]

//3、find()
	var arr = [1,-5,2,9,-6];
	var main = arr.find(n =>  n < 0);
	console.log(main);   //输出:-5

//4、fill()
	var arr = ["a","b","c","d"];
	console.log(arr.fill(7,1,2));//输出:["a",7,"c","d"]  

//5、keys()  values()  entries()
	var arr = ["a","b","c","d"];
	for(let index of arr.keys()){
		console.log(index);
	}
	for(let elem of arr.values()){
		console.log(elem);
	}
	for(let [index,elem] of arr.entries()){
		console.log(index,elem);
	}  

//6、includes()
	let arr = [12,34,223,45,67]
	console.log(arr.includes(45))   //输出:true
	[1, 2, NaN].includes(NaN)     // true
	[1, 2, NaN].indexOf(NaN)      // -1

//7、Map
	var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
	m.get('Michael'); // 95
	//初始化Map需要一个二维数组或者直接初始化一个空Map。Map具有以下方法
	var m = new Map(); // 空Map
	m.set('Adam', 67); // 添加新的key-value
	m.set('Bob', 59);
	m.has('Adam'); // 是否存在key 'Adam': true
	m.get('Adam'); // 67
	m.delete('Adam'); // 删除key 'Adam'
	m.get('Adam'); // undefined
	//由于一个key只能对应一个value所以多次对一个key放入value后面的值会把前面的值冲掉
	var m = new Map();
	m.set('Adam', 67);
	m.set('Adam', 88);
	m.get('Adam'); // 88

//8、Set
	//要创建一个Set需要提供一个Array作为输入或者直接创建一个空Set
	var s1 = new Set(); // 空Set
	var s2 = new Set([1, 2, 3]); // 含1, 2, 3
	//重复元素在Set中自动被过滤
	var s = new Set([1, 2, 3, 3, '3']);
	s; // Set {1, 2, 3, "3"}  注意数字3和字符串'3'是不同的元素
	//通过add(key)方法可以添加元素到Set中可以重复添加但不会有效果
	s.add(4);
	s; // Set {1, 2, 3, 4}
	s.add(4);
	s; // 仍然是 Set {1, 2, 3, 4}
	//通过delete(key)方法可以删除元素
	var s = new Set([1, 2, 3]);
	s; // Set {1, 2, 3}
	s.delete(3);
	s; // Set {1, 2}

字符串

字符串的方法

1、chartAt( )返回在指定位置的字符
2、concat( )返回新的字符串**将一个或多个字符串与原字符串连接合并
3、indexOf( )检索字符串返回第一次出现的索引没有出现则为-1
4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引没有则-1fromIndex的值相对于从尾部开始的索引
5、split( )返回一个以指定分隔符出现位置分隔而成的一个数组数组元素不包含分隔符
6、substr( )从起始索引号提取字符串中指定数目的字符
7、substring( )提取字符串中两个指定的索引号之间的字符
8、toLowerCase( )字符串转小写
9、toUpperCase( )字符串转大写
10、valueOf( )返回某个字符串对象的原始值 
11、trim( )删除字符串两边的空格
12、trimeState 取出开始的空格
13、trimeEnd  去除末尾空格
14、includes(searchString[, position])返回boolean判断一个字符串是否包含在另一个字符串中从postition索引开始搜寻默认0
15、slice( )提取字符串片段并在新的字符串中返回被提取的部分
16、search(regexp)返回首次匹配到的索引没有则-1执行正则表达式和 String 对象之间的一个搜索匹配
17、toString()返回一个表示调用对象的字符串该方法返回指定对象的字符串形式
18、trim()返回去掉两端空白后的新字符串 还有trimend trimstart
19、replace() 把指定的字符串替换成为别的字符

超长字符串存储到栈内存中

字符串属于基础类型所以会觉得字符串是存在栈内存中的但是要知道V8默认栈内存是984Kib那如果一个超长字符串 > 984Kib能装的进栈内存

字符串的内容存于堆内存中指针存于栈内存中且相同的字符串指向同一个堆内存地址

新增或者修改字符串后如果是一个之前不存在的字符串则新开辟内存空间如果是已有的则直接使用已有的内存空间

当我们新建一个字符串时V8会从内存中查找一下是否已经有存在的一样的字符串找到的话直接复用。如果找不到的话则开辟一块新的内存空间来存这个字符串并把地址赋给变量。

javascript函数

  • 声明函数的几种方式

函数声明
function 函数名(参数1参数2...){   //要执行的语句 } 
函数表达式
var func2=function(b){}//函数表达式
var func3=function func4(c){}//命名式函数表达式
var func5=(function(n1,n2){})();//立即执行的函数表达式
return function(){ };//作为返回值的函数表达式
Function构造器
var 变量名 = new Function("参数1","参数2",...,"参数n","函数体");  
立即执行函数
var func5=(function(n1,n2){})();//立即执行的函数表达式 ()()
  • 函数声明与函数表达式的区别

函数声明会将那个函数提升到最前面即使你写代码的时候在代码块最后才写这个函数成为全局函数。

函数声明要指定函数名而函数表达式不用可以用作匿名函数。

  • 函数调用的几种方式

1.直接调用 函数名加上括号

2.函数表达式 变量名

  • 函数的长度

函数的length属性将返回没有指定默认值的参数个数。也就是说指定了默认值后length属性将失真。

function fun1(a) { }
function fun2(a, b) { }
function fun3(a, b, c) { }
function fun4(a, b, c, d) { }
function fun5(...args) { }
function fun6(a = 1, b, c, d) { }
​
console.log(fun1.length) // 1
console.log(fun2.length) // 2
console.log(fun3.length) // 3
console.log(fun4.length) // 4
console.log(fun5.length) // 0
console.log(fun6.length) // 0
  • 立即执行函数iife和使用场景

立即执行函数( function( ){ })( ) 返回值可以为基本数据类型也能返会任何类型的值。

写法原因因为在 javascript 里括号内部不能包含语句当解析器对代码进行解释的时候先碰到了() 然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。

作用立即执行函数会形成一个单独的作用域我们可以封装一些临时变量或者局部变量避免污染全局变量。

使用场景 ①代码在页面加载完成之后不得不执行一些设置工作比如时间处理器创建对象等等。 ②所有的这些工作只需要执行一次比如只需要显示一个时间。

③需要一些临时的变量但是初始化过程结束之后就再也不会被用到我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中 不会让任何变量泄露成全局变量。

arguments 的对象是什么

arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收是一个类似于数组的对象他有length属性可以arguments[ i ]来访问对象中的元素 但是它不能用数组的一些方法。 例如push、pop、slice等。arguments虽然不是一个数组但是它可以转成一个真正的数组。

取之可以用 展开运算符来 数组和类数组类数组 ①拥有length属性其它属性(索引)为非负整数箭头函数里没有arguments ②不具有数组所具有的方法 ③类数组是一个普通对象而真实的数组是Array类型。

常见的类数组argumentsdocument.querySelectorAll得到的列表jQuery对象($("div"))

this指向的问题高频

在全局的环境下this是指向window 的

普通函数调用直接调用中的this 会指向 window 严格模式下this会指向 undefined自执行函数 this 指向 window定时器中的 this 指向 window

在对象里调用的this指向调用函数的那个对象

在构造函数以及类中的this构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象所以构造函数中的 this 指向 当前实例化的对象

方法中的this谁调用就指向谁。

箭头函数没有自己的 this箭头函数的this在定义的时候会继承自外层第一个普通函数的this

函数式编程含义

函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数避免共享状态、可变作用和副作用来构建软件的过程。 目的使用函数来抽象作用在数据之上的控制流和操作从而在系统中消除副作用并减少对状态的改变。

闭包

1、闭包的概念就是只有权利访问另一个函数作用域中的变量一般就是函数包裹着函数。

3、闭包可以重用一个变量且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中不会被垃圾回收机制处理

4、闭包的缺点由于闭包会使得函数中的变量都被保存在内存中内存消耗很大所以不能滥用闭包否则会造成网页的性能问题在IE中可能导致内存泄露。解决方法是在退出函数之前将不使用的局部变量全部删除。

5、为什么要用闭包使用场景 : 防抖、节流、函数套函数避免全局污染

闭包原理
函数执行分成两个阶段(预编译阶段和执行阶段)。
​
    1.在预编译阶段如果发现内部函数使用了外部函数的变量则会在内存中创建一个“闭包”对象并保存对应变量值
      如果已存在“闭包”则只需要增加对应属性值即可。
    2.执行完后函数执行上下文会被销毁函数对“闭包”对象的引用也会被销毁但其内部函数还持用该“闭包”的引用
      所以内部函数可以继续使用“外部函数”中的变量
​
利用了函数作用域链的特性一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中函数执行完毕其执行作用域链销毁
但因内部函数的作用域链仍然在引用这个活动对象所以其活动对象不会被销毁直到内部函数被烧毁后才被销毁。

call、apply、bind封装与区别

都是来改变this指向和函数的调⽤实际上call与apply的功能是相同的只是两者的传参方式不一样

call⽅法跟的是⼀个参数列表

apply跟⼀个 数组作为参数call⽅法和apply使⽤后就直接调⽤

bind 传参后不会立即执行而是返回一个改变了this指向的函数这个函数可以继续传参且执行需要类似于bind()()两个括号才能调⽤。

  • call 的性能要比apply好一点尤其是当函数传递参数超过3个的时候后期开发 call 多多一点

  • call 用扩展运算符就可以吧 apply 来代替了

bind 返回的函数可以作为构造函数吗

可以的 如果是直接调用f1()和f2()的this指针指向会不同.

function f1() {
    console.log(432,this) // 432 f1 {}
}

const f2=f1.bind(obj)
const obj2=new f2()
obj2.a =223
console.log(obj2) // f1 {a: 223}
// ----------------
let obj = {a:1}
function f1() {
    console.log(432,this)
}

const f2=f1.bind(obj)() // 432 {a: 1}

函数柯里化卡瑞化、加里化

概念把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数并且返回接受余下的参数而且返回结果的新函数的技术。 容易理解的概念Currying概念其实很简单只传递给函数一部分参数来调用它让它返回一个函数去处理剩下的参数主要是利用闭包实现的。

特点

①接收单一参数将更多的参数通过回调函数来搞定

②返回一个新函数用于处理所有的想要传入的参数

③需要利用call/apply与arguments对象收集参数

④返回的这个函数正是用来处理收集起来的参数。

作用能进行部分传值而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参然后在另一处确定另一部分实参这个时候柯里化和偏应用就能派上用场。

用途我认为函数柯里化是对闭包的一种应用形式延迟计算、参数复用、动态生成函数(都是闭包的用途)。

柯里化函数例子

柯里化函数把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。

我的理解就是将一个接受多个参数的函数转化为接收一个参数并且不改变输出结果的一种办法。我觉得这就是js的柯里化函数

// 简单的相加函数
var add = function (x,y) {
    return x + y
}
// 调用
add(1,2)
​
// 柯里化以后
var add = function (x) { //柯里化函数(闭包)
    return function (y) {
        return x + y
    }
}
add(1)(2)

这样做有什么好处我得理解是在需要的情况下生成一个中间工具简化代码并且清晰代码。

什么是高阶函数

高阶函数只是将函数作为参数 函数的返回值返回值是函数

function higherOrderFunction(param,callback){
    return callback(param);
}

构造函数

new的原理

new实际上是在堆内存中开辟一个空间。
    ①创建一个空对象构造函数中的this指向这个空对象
    ②这个新对象被执行[ [ 原型 ] ]连接
    ③执行构造函数方法属性和方法被添加到this引用的对象中
    ④如果构造函数中没有返回其它对象那么返回this即创建的这个的新对象否则返回构造函数中返回的对象。
​
function _new(){
    let target = {};   //创建的新对象
    let [constructor,...args] = [...arguments];
       //执行[[原型]]连接,target是constructor的实例
    target.__proto__ = constructor.prototype;
        //执行构造函数,将属性或方法添加到创建的空对象上
    let result = constructor.prototype;
    if(result && (typeof (result) == "object" || typeof (result) == "function")){
           //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
        return result;
    }
       //如果构造函数返回的不是一个对象,返回创建的对象
    return target;
}
​
​
自己理解的new         
    new实际上是在堆内存中开辟一个新的空间。首先创建一个空对象obj然后呢
    把这个空对象的原型(__proto__)和构造函数的原型对象(constructor.prototype)连接(说白了就是等于)
    然后执行函数中的代码就是为这个新对象添加属性和方法。最后进行判断其返回值如果构造函数返回的是一个对象
    那就返回这个对象如果不是那就返回我们创建的对象。
 

封装一个通用的事件绑定函数

需要点击每个a来。弹出他们的内容
<div id="div3">
    <a href="#">a1</a><br>
    <a href="#">a2</a><br>
    <a href="#">a3</a><br>
    <a href="#">a4</a><br>
    <button id='btn1'>加载更多...</button>
</div>
// 封装通用的事件绑定函数
function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}
//获取父元素
const fu = document.getElementById('div3')
bindEvent(fu, 'click', function (event) {
    // console.log(event.target) // 获取触发的元素
    let target=event.target
    event.preventDefault() // 阻止默认行为
    //过滤符合条件的子元素主要是过滤掉 加载更多 
    if(target.nodeName.toLowerCase()==="A"){
        alert(target.innerHTML;
    }
})

作用域js的机制

垃圾回收机制和内存机制

垃圾回收

浏览器的js具有自动垃圾回收机制垃圾回收机制也就是自动内存管理机制垃圾收集器会定期的找出那些不在继续使用的变量然后释放内存。但是这个过程不是实时的因为GC开销比较大并且时停止响应其他操作所以垃圾回收器会按照固定的时间间隔周期性的执行。

内存泄露

如果 那些不再使用的变量它们所占用的内存 不去清除的话就会造成内存泄漏

内存泄露其实就是我们的程序中已经动态分配的堆内存由于某些原因没有得到释放造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果

比如说

1、闭包在闭包中引入闭包外部的变量时当闭包结束时此对象无法被垃圾回收GC。

2、DOM当原有的DOM被移除时子结点引用没有被移除则无法回收

3、Times计时器泄露

作用域

1、作用域

作用域就是一个变量可以使用的范围主要分为全局作用域和函数作用域

全局作用域就是Js中最外层的作用域

函数作用域是js通过函数创建的一个独立作用域函数可以嵌套所以作用域也可以嵌套

Es6中新增了块级作用域由大括号包裹比如if(){},for(){}等

2、自由变量

当前作用域外的变量都是自由变量一个变量在当前作用域没有定义但是被使用了就会向上级作用域一层一层依次查找直至找到为止如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链。

3、变量提升

每个var声明的变量function声明的函数存在变量提升。let const不存在变量提升

在js中声明之前未定义会在js的最上方会形成一个预解析池用来存储声明了但没有先定义的变量名

4、作用域链

作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问通过作用域链我们可以访问到外层环境的变量和 函数 简单来说内部函数访问外部函数的变量这种链式查找的机制被称为作用域链

谈谈JS的运行机制

1. js单线程

JavaScript语言的一大特点就是单线程即同一时间只能做一件事情。

2. js事件循环

js代码执行过程中会有很多任务这些任务总的分成两类

  • 同步任务

  • 异步任务

需要注意的是除了同步任务和异步任务任务还可以更加细分为macrotask(宏任务)和microtask(微任务)js引擎会优先执行微任务

微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
​
宏任务包括了 script 脚本的执行、setTimeout setInterval setImmediate 一类的定时事件还有如 I/O 操作、UI 渲
染等。
  1. 首先js 是单线程运行的在代码执行的时候通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。

  2. 在执行同步代码的时候如果遇到了异步事件js 引擎并不会一直等待其返回结果而是会将这个事件挂起继续执行执行栈中的其他任务

  3. 当同步事件执行完毕后再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。

  4. 任务队列可以分为宏任务对列和微任务对列当当前执行栈中的事件执行完毕后js 引擎首先会判断微任务对列中是否有任务可以执行如果有就将微任务队首的事件压入栈中执行。

  5. 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。

最后可以用下面一道题检测一下收获

setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function(resolve, reject) {
  console.log(2);
  resolve()
}).then(function() {
  console.log(3)
});
process.nextTick(function () {
  console.log(4)
})
console.log(5)

第一轮主线程开始执行遇到setTimeout将setTimeout的回调函数丢到宏任务队列中在往下执行new Promise立即执行输出2then的回调函数丢到微任务队列中再继续执行遇到process.nextTick同样将回调函数扔到为任务队列再继续执行输出5当所有同步任务执行完成后看有没有可以执行的微任务发现有then函数和nextTick两个微任务先执行哪个呢process.nextTick指定的异步任务总是发生在所有异步任务之前因此先执行process.nextTick输出4然后执行then函数输出3第一轮执行结束。

第二轮从宏任务队列开始发现setTimeout回调输出1执行完毕因此结果是25431

JS延迟加载的方式

JavaScript 是单线程js不走完下面不会走是因为同步会阻塞DOM的解析因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。

1.把JS放在页面的最底部

2.script标签的defer属性脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本。

3.是在外部JS加载完成后浏览器空闲时Load事件触发前执行标记为async的脚本并不保证按照指定他们的先后顺序执行 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本。

4.动态创建script标签监听dom加载完毕再引入js文件

宏任务和微任务

js中的一个机制就是遇到宏任务先将宏任务放入eventqueue然后在执行微任务。

宏任务setTimeout,setInterval,Ajax,DOM事件

微任务Promise async/await

想明白这个机制 就要理解js单线程。因为JS是单线程语言只能同时做一件事儿。js任务需要排队顺序执行如果一个任务时间过长后边的任务也会等着。假如我们在请求一个网址时图片加载很慢网页总不能一直卡不出来

这个时候就可以用异步来解决了异步的特点不会阻塞代码的执行 ,解决了单线程等待的这个问题

在执行同步代码的时候如果遇到了异步事件js 引擎并不会一直等待其返回结果而是会将这个事件挂起继续执行执行栈中的其他任务

异步和单线程是相辅相成的js是一门单线程语言所以需要异步来辅助。

宏任务macrotask 可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到
   执行栈中执行)。
   常见的宏任务script, setTimeout, setInterval, setImmediate, I/O, UI rendering。
   
微任务microtask(异步) 可以理解是在当前task执行结束后立即执行的任务。
​
常见的微任务process.nextTick(Nodejs),Promise.then(), MutationObserver。
​
线程进程?
线程是最小的执行单元进程是最小的资源管理单元一个线程只能属于一个进程而一个进程可以有多个线程但至少有一个线程

内存泄露

如果 那些不再使用的变量它们所占用的内存 不去清除的话就会造成内存泄漏

比如说

1、闭包在闭包中引入闭包外部的变量时当闭包结束时此对象无法被垃圾回收GC。

2、DOM当原有的DOM被移除时子结点引用没有被移除则无法回收

3、Times计时器泄露

JS预解析变量提升它导致了什么问题

JS代码在执行前浏览器会对js代码进行扫描默认的把所有带var和function声明的变量进行提前的声明或者定义遵循先解析后使用的原则。 变量提升的表现是在变量或函数声明之前访问变量或调用函数而不会报错。

原因 JavaScript引擎在代码执行前有一个解析的过程预编译创建执行上线文初始化一些代码执行时需要用到的对象。 当访问一个变量时会到当前执行上下文中的作用域链中去查找而作用域链的首端指向的是当前执行上下文的变量对象这个变量对象是执行上下文的一个属性 它包含了函数的形参、所有的函数和变量声明这个对象的是在代码解析的时候创建的。

首先要知道JS在拿到一个变量或者一个函数的时候会有两步操作即解析和执行。

1.在解析阶段 JS会检查语法并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境先把代码中即将执行的变量、函数声明都拿出来 变量先赋值为undefined函数先声明好可使用。在一个函数执行之前也会创建一个函数执行上下文环境跟全局执行上下文类似 不过函数执行上下文会多出this、arguments和函数的参数。

全局上下文变量定义函数声明 函数上下文变量定义函数声明thisarguments

2.在执行阶段就是按照代码的顺序依次执行。

那为什么会进行变量提升呢主要有以下两个原因

1、提高性能
2、容错性更好
​
1提高性能 在JS代码执行之前会进行语法检查和预编译并且这一操作只进行一次。这么做就是为了提高性能如果没有这一步
    那么每次执行代码前都必须重新解析一遍该变量函数而这是没有必要的因为变量函数的代码并不会改变解析一遍就够了。
    
    在解析的过程中还会为函数生成预编译代码。在预编译时会统计声明了哪些变量、创建了哪些函数并对函数的代码进行压缩去除注释、
    不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间不需要再解析一遍去获取代码中声明了哪些变量创建了哪些函数
    并且因为代码压缩的原因代码执行也更快了。
    
2容错性更好 变量提升可以在一定程度上提高JS的容错性看下面的代码
​
    a = 1
    var a
    console.log(a) //1
    如果没有变量提升这段代码就会报错导致的问题
var tmp = new Date();
​
function fn(){
    console.log(tmp);
    if(false){
        var tmp = 'hello nanjiu';
    }
}
fn();  // undefined
        
    在这个函数中原本是要打印出外层的tmp变量但是因为变量提升的问题内层定义的tmp被提到函数内部的最顶部
    相当于覆盖了外层的tmp所以打印结果为undefined。
    var tmp = 'hello nan jiu';
    
    for (var i = 0; i < tmp.length; i++) {
        console.log(tmp[i]);
    }
    console.log(i); // 13
​
由于遍历时定义的i会变量提升成为一个全局变量在函数结束之后不会被销毁所以打印出来13。
总结      
解析和预编译过程中的声明提升可以提高性能让函数可以在执行时预先为变量分配栈空间
声明提升还可以提高JS代码的容错性使一些不规范的代码也可以正常执行
函数是一等公民当函数声明与变量声明冲突时变量提升时函数优先级更高会忽略同名的变量声明

服务端渲染

解释服务端渲染的模式下当用户第一次请求页面时由服务器把需要的组件或页面渲染成 HTML 字符串然后把它返回给客户端。客户端拿到手的是可以直接渲染然后呈现给用户的 HTML 内容不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站可以说是“所见即所得”页面上呈现的内容我们在 html 源文件里也能找到。有了服务端渲染当请求用户页面时返回的body里已经有了首屏的html结构之后结合css显示出来。
    
优点
    ①首屏渲染快(关键性问题)相比于加载单页应用我只需要加载当前页面的内容而不需要像 React 或者 Vue 一样加载全部的 js 文件
    ②SEO(搜索引擎)优化不同爬虫工作原理类似只会爬取源码不会执行网站的任何脚本
    ③可以生成缓存片段、节能

缺点用户体验较差不容易维护、通常前端改了部分html或者css后端也需要改

使用场景vue全家桶或者react全家桶都是推荐通过服务端渲染来实现路由的。

Event Loop Event Queue

在js中我们经常需要同时执行很多件任务例如定时器事件。异步数据而js是单线程的原因不能同时进行很多件事情必须等上一件任务执行完了才会执行下一个需要通过Event Loop 来处理很多任务的执行

因为js是单线程的代码执行的时候将不同的函数执行上下文压入到栈中进行有序的执行
在执行同步代码的时候如果遇到了异步事件js引擎并不会一直等待其返回结果就是将它挂起继续执行栈中其他的任务
当同步任务执行完了再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
任务队列分为的宏任务队列和微任务队列当前的执行栈中执行完js引擎会首先判断微任务队列是否有任务可以执行有的话放到栈中执行。
当微任务队列中的任务执行完了再去判断宏任务中的队列。



为什么会有任务队列呢
还是因为 javascript 单线程的原因单线程就意味着一个任务一个任务的执行
执行完当前任务执行下一个任务这样也会遇到一个问题就比如说要向服务端通信加载大量数据如果是同步执行
js 主线程就得等着这个通信完成然后才能渲染数据为了高效率的利用cpu, 就有了同步任务和异步任务之分。

同步和异步的区别各举一个Js中同步和异步的案例

概念

同步上一件事情没有完成继续处理上一件事情只有上一件事情完成了才会做下一件事情

异步 规划要做一件事情,如果是异步事情不是当前立马去执行这件事情需要等一定的时间这样的话我们不会等着他执行而是继续执行下面的操作

对于写程序同步往往会阻塞没有数据过来我就等着异步则不会阻塞没数据来我干别的事有数据来去处理这些数据。

同步案例for循环语句alert(),console.log()等 js大部分都是同步编程

异步案例所有定时器ajax异步请求所有的事件绑定都是异步;

举例子

同步就是实时处理如打电话比如服务器一接收客户端请求马上响应这样客户端可以在最短的时间内得到结果但是如果多个客户端或者一个客户端发出的请求很频繁服务器无法同步处理就会造成涌塞。

同步如打电话通信双方不能断我们是同时进行同步你一句我一句这样的好处是对方想表达的信息我马上能收到但是我在打着电话我无法做别的事情。

异步就是分时处理如收发短信服务器接收到客户端请求后并不是立即处理而是等待服务器比较空闲的时候加以处理可以避免涌塞。

BOM浏览器对象模型

js操作BOM

浏览器对象模型BOM Browser Object Model是JavaScript的组成之一它提供了独立于内容与浏览器窗口进行交互的对象使用浏览器对象模型可以实现与HTML的交互。它的作用是将相关的元素组织包装起来提供给程序设计人员使用从而降低开发人员的劳动量提高设计Web页面的能力。

window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ;

history : go(参数) , back() , foward() ;

location : herf属性.

1、window.location.href = '你所要跳转到的页面'; 2、window.open('你所要跳转到的页面’); 2、window.history.back(-1):返回上一页 4、window.history.go(-1/1):返回上一页或下一页五、 3、history.go("baidu.com")
4、window.print() 直接掉用打印窗口可以用来拔面试题。

说出5个以上Math对象中的成员。

Math.PI 圆周率

Math.floor() 向下取整

Math.ceil() 向上取整

Math.round() 四舍五入版 就近取整

Math.abs() 绝对值

Math.max()/Math.min() 求最大和最小值

Math.random() 获取范围在[0,1)内的随机值

setTimeout与setInterval区别与机制

setTimeout()和setInterval()经常被用来处理延时和定时任务。

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式

setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式直到clearInterval把它清除。

机制

因为js是单线程的。浏览器遇到setTimeout 和 setInterval会先执行完当前的代码块在此之前会把定时器推入浏览器的
待执行时间队列里面等到浏览器执行完当前代码之后会看下事件队列里有没有任务有的话才执行定时器里的代码

window的onload事件和domcontentloaded

window.onload当一个资源及其依赖资源已完成加载时将触发onload事件。 document.onDOMContentLoaded当初始的HTML文档被完全加载和解析完成之后 DOMContentLoaded事件被触发而无需等待样式表、图像和子框架的完成加载。 区别 ①onload事件是DOM事件onDOMContentLoaded是HTML5事件。 ②onload事件会被样式表、图像和子框架阻塞而onDOMContentLoaded不会。 ③当加载的脚本内容并不包含立即执行DOM操作时使用onDOMContentLoaded事件是个更好的选择会比onload事件执行时间更早。

cookiessessionStorage 和 localStorage 的区别?

cookie一个大小不超过4K的小型文本数据一般由服务器生成可以设置失效时间若没有设置时间关闭浏览器cookie失效若设置了 时间cookie就会存放在硬盘里过期才失效每次http请求header都携带cookie

localStorage5M或者更大永久有效窗口或者浏览器关闭也会一直保存除非手动永久清除或者js代码清除因此用作持久数据不参与和服务器的通信

sessionStorage关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端即浏览器中保存不参与和服务器的通信。

location、之lnavigator和history

location 对象存储了当前文档位置URL相关的信息简单地说就是网页地址字符串。使用 window 对象的 location 属性可以访问。

href会重新定位到一个URLhash会跳到当前页面中的anchor名字的标记(如果有)而且页面不会被重新加载

history

window 对象给我们提供了一个 history 对象与浏览器历史记录进行交互。该对象包含用户在浏览器窗口中 访问过的 URL。

history.back 可以后退一个网页

history.go 可以前进后退 1前进 -1 后退

history.forward  前进

navigator对象

window.navigator`对象包含有关浏览器的信息可以用它来查询一些关于运行当前脚本的应用程序的相关信息

navigator.appCodeName 只读,任何浏览器中总是返回 'Gecko'。该属性仅仅是为了保持兼容性。

navigator.appName 只读,返回浏览器的官方名称。不要指望该属性返回正确的值。

navigator.appVersion 只读,返回一个字符串表示浏览器的版本。不要指望该属性返回正确的值。

navigator.platform 只读,返回一个字符串表示浏览器的所在系统平台。

navigator.product 只读,返回当前浏览器的产品名称如"Gecko"。

navigator.userAgent 只读,返回当前浏览器的用户代理字符串user agent string

DOM文档对象模型

DOM是 document 用来表示文档中对象的标准模型他是由节点和对象组成的结构集合。在浏览器解析HTML标签时会构建一个DOM树结构。

操作说明书

拿到指定节点
var id = document.getElementById("id");  //返回带有指定id的元素
var name = document.getElementByTagName("li"); //返回带有指定标签的元素
var class = document.getElementByClassName("class"); //返回带有包含执行类名的所有元素节点列表。`
 创建DOM节点
var node = document.createElement("div");
var attr = document.createAttribute("class");
var text = document.createTextNode("菜呀菜");`
 插入DOM节点
node.appendChild(text) //插入新的子节点
node.insertBefore(pre,child) //在node元素内child前加入新元素`
 删除DOM节点
node.removeChild(text) //从父元素删除子元素节点
 修改DOM节点
node.setAttribute("class","name") //修改设置属性节点
node.replaceChild(pre,child)  //父节点内新子节点替换旧子节点`
 常用DOM属性
node.innerHtml  //获取/替换元素内容
node.parentNode  //元素节点的父节点
node.parentElement  //元素节点的父元素节点一般与Node节点相同
node.firstChild  //属性的第一个节点
node.lastChild   //属性的最后一个节点
node.nextSibling //节点元素后的兄弟元素包括回车空格换行
node.nextElementSibling //节点元素后的兄弟元素节点
node.previousSibling //获取元素的上一个兄弟节点元素文本注释
node.previousElementSibling //获取元素的上一个兄弟节点只包含元素节点
node.childNodes  //元素节点的子节点空格换行默认为文本节点
node.children    //返回当前元素的所有元素节点
node.nodeValue   //获取节点值
node.nodeName    //获取节点名字
node.attributes  //元素节点的属性节点
node.getAttribute("name")  //元素节点的某个属性节点
node.style.width = "200px"  //设置css样式`

常用的api

offset、client、scroll的用法?

offset系列 经常用于获得元素位置 offsetLeft offsetTop

client经常用于获取元素大小 clientWidth clientHeight 

scroll 经常用于获取滚动距离 scrollTop scrollLeft

js面试题的扩展

什么是函数式编程? 命令式编程声明式编程

声明式编程专注于”做什么”而不是”如何去做”。在更高层面写代码更关心的是目标而不是底层算法实现的过程。 如css, 正则表达式sql 语句html, xml…

命令式编程(过程式编程) : 专注于”如何去做”这样不管”做什么”都会按照你的命令去做。解决某一问题的具体算法实现。

如 for

函数式编程把运算过程尽量写成一系列嵌套的函数调用。

如 forEach

iframe的优缺点有哪些

优点
    ①iframe能够原封不动的把嵌入的网页展现出来
    ②如果有多个网页引用iframe那么你只需要修改iframe的内容就可以实现调用的每一个页面内容的更改方便快捷。
    ③网页如果为了统一风格头部和版本都是一样的就可以写成一个页面用iframe来嵌套可以增加代码的可重用。
    ④如果遇到加载缓慢的第三方内容如图标和广告这些问题可以由iframe来解决。

缺点
    ①会产生很多页面不易管理
    ②iframe框架结构有时会让人感到迷惑如果框架个数多的话可能会出现上下、左右滚动条会分散访问者的注意力用户体验度差。
    ③代码复杂无法被一些搜索引擎索引到这一点很关键现在的搜索引擎爬虫还不能很好的处理iframe中的内容所以使用iframe会不利于搜索引擎优化。
    ④很多的移动设备PDA 手机无法完全显示框架设备兼容性差。
    ⑤iframe框架页面会增加服务器的http请求对于大型网站是不可取的。

如何让(a == 1 && a == 2 && a == 3)的值为true

" == "操作符在左右数据不一致的时候会先进行隐式转换该值意味着不是基本数据类型
因为如果a是null或者undefined、bool类型都不可能返回true可以推测a是复杂数据类型。
​
方法一数组的 toString 接口默认调用数组的 join 方法重新 join 方法
​
let a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3) //true
​
​
方法二利用数据劫持(Proxy/Object.definedProperty)
​
let i = 1;
let a = new Proxy({},{
    i:1,
    get:function(){
        return () => this.i++
    }
});
console.log(a == 1 && a == 2 && a == 3);

为什么0.1+0.2 ! == 0.3如何让其相等

在开发过程中遇到类似这样的问题

let n1 = 0.1, n2 = 0.2
console.log(n1 + n2)  // 0.30000000000000004

这里得到的不是想要的结果要想等于0.3就要把它进行转化

(n1 + n2).toFixed(2) // 注意toFixed为四舍五入
复制代码
toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢

计算机是通过二进制的方式存储数据的所以计算机计算0.1+0.2的时候实际上是计算的两个数的二进制的和

es6部i分面试题

1、 ES6 新增特性

  1. 新增了块级作用域(let,const)

  2. 提供了定义类的语法糖(class)

  3. 新增了一种基本数据类型(Symbol)

  4. 新增了变量的解构赋值

  5. 函数参数允许设置默认值引入了 rest 参数新增了箭头函数

  6. 数组新增了一些 API如 isArray / from / of 方法;数组实例新增了entries()keys() 和 values() 等方法

  7. 对象和数组新增了扩展运算符

  8. ES6 新增了模块化(import/export)

  9. ES6 新增了 Set 和 Map 数据结构

  10. ES6 原生提供 Proxy 构造函数用来生成 Proxy 实例

  11. ES6 新增了生成器(Generator)和遍历器(Iterator)

2、require与import的区别和使用(CommonJS规范和es6规范)

1、import是ES6中的语法标准也是用来加载模块文件的import函数可以读取并执行一个JavaScript文件然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块export可以有多个export default只能有一个。

2、require 定义模块module变量代表当前模块它的exports属性是对外的接口。通过exports可以将模块从模块中导出其他文件加载该模块实际上就是读取module.exports变量他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的只是导出需要定义导出名。

require与import的区别

1require是CommonJS规范的模块化语法import是ECMAScript 6规范的模块化语法

2require是运行时加载import是编译时加载

3require可以写在代码的任意位置import只能写在文件的最顶端且不可在条件语句或函数作用域中使用

4require通过module.exports导出的值就不能再变化import通过export导出的值可以改变

5require通过module.exports导出的是exports对象import通过export导出是指定输出的代码

6require运行时才引入模块的属性所以性能相对较低import编译时引入模块的属性所所以性能稍高。

3、箭头函数

js中我们在调⽤函数的时候经常会遇到this作⽤域的问题这个时候ES6给我们提箭头函数

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

2、 箭头函数不绑定arguments,取而代之用rest参数…解决

3、 this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this

4、 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响.

5、 箭头函数没有prototype(原型)所以箭头函数本身没有this

6、 箭头函数不能当做Generator函数,不能使用yield关键字、

7、 写法不同箭头函数把function省略掉了 => 也可以吧return 省略调 写法更简洁

8、箭头函数不能通过call、apply、bind方法直接修改它的this指向。

4、简述 let const var 的区别 以及使用场景

  • var let 是用来声明变量的而const是声明常量的 var
1.var声明的变量存在变量提升即变量可以在声明之前调用值为undefined ​   
2、一个变量可多次声明后面的声明会覆盖前面的声明 ​   
3、在函数中使用var声明变量的时候该变量是局部的作用域只在函数内部而如果在函数外部使用 var该变量是全局的
  • let
1、不存在变量提升let声明变量前该变量不能使用。就是 let 声明存在暂时性死区 ​      
2、let命令所在的代码块内有效在块级作用域内有效作用域只是在花括号里面 ​      
3、let不允许在相同作用域中重复声明注意是相同作用域不同作用域有重复声明不会报错
  • const
1、const声明一个只读的常量声明后值就不能改变 ​     
2、let和const在同一作用域不允许重复声明变量const声明一个只读的常量。一旦声明常量的值就不能改变但对于对象和数据这种  引用类型内存地址不能修改可以修改里面的值。 ​   
3、let和const不存在变量提升即它们所声明的变量一定要在声明后使用否则报错 

4、能用const的情况下尽量使用const大多数情况使用let避免使用var。 const > let > var const声明的好处一让阅读代码的人知道该变量不可修改二是防止在修改代码的过程中无意中修改了该变量导致报错减少bug的产生

5、map和forEach的区别

相同点

都是循环遍历数组中的每一项 forEach和map方法里每次执行匿名函数都支持3个参数参数分别是item当前每一项、index索引值、arr原数组需要用哪个的时候就写哪个 匿名函数中的this都是指向window 只能遍历数组

注意forEach对于空数组是不会调用回调函数的。

不同点

map方法返回一个新的数组数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。) map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素将元素传给回调函数.(没有return返回值是undefined

6、promise的解释

1、Promise 是异步编程的一种解决方案主要用于异步计算支持链式调用可以解决回调地狱 的问题自己身上有all、reject、resolve、race 等方法原型上有then、catch等方法。

2、可以将异步操作队列化按照期望的顺序执行返回符合预期的结果可以在对象之间传递和操作 promise帮助我们处理队列

3、promise 有三个状态pending[待定]初始状态fulfilled[实现]操作成功rejected[被否决]操作失败

4、Promise 对象状态改变从pending变为fulfilled和从pending变为rejected。只要这两种情况发生状态就凝固了不会再变了

5、如果不设置回调函数Promise内部抛出的错误不会反应到外部但是写了then 和 catch 会被then的第二个参数 或 catch所捕获

  • promise 的 then 为什么可以支持链式调用

promise 的then会返回一个新的 promise 对象能保证 then 方 可以进行链式调用

补充

Promise.all哪怕一个请求失败了也能得到其余正确的请求结果的解决方案

Promise.all默认只要有一个错误就直接返回错误。promise.all中任何一个promise 出现错误的时候都会执行reject导致其它正常返回的数据也无法使用
Promise.all(
  [
    Promise.reject({ code: 500, msg: "服务异常" }),
    Promise.resolve({ code: 200, list: [] }),
    Promise.resolve({ code: 200, list: [] })
  ].map(p => p.catch(e => e))
)
  .then(res => {
    console.log("res=>", res);
  })
  .catch(error => {
    console.log("error=>", error);
  });
res=> [ { code: 500, msg: '服务异常' },
  { code: 200, list: [] },
  { code: 200, list: [] } ]
核心内容是map方法map的每一项都是promisecatch方法返回值会被promise.reslove()包裹这样传进promise.all的数据都是resolved状态的。

// 使用Promise.all 其中id为69的商品,返回失败,会导致整个Promise接受到reject状态.
// 所以进行改造, p catch 得到的err  为返回失败抛出的信息, 进行置空
.map(p => p.catch(err => '')))

6、async、await的原理

Async 和 await 是一种同步的写法但还是异步的操作两个必须配合一起使用

函数前面的async关键字表明该函数内部有异步操作。调用该函数时会立即返回一个Promise对象。

await 是个运算符用于组成表达式await 表达式的运算结果取决于它等的东西如果是promise则会等待promaise 返回结果接普通函数直接进行链式调用.

await 能够获取promise执行的结果 await必须和async一起使用才行async配合await使用是一个阻塞的异步方法

如果await后面不是Promise对象, 就直接返回对应的值只能在async函数中出现, 普通函数直接使用会报错

await语句后的Promise对象变成reject状态时那么整个async函数会中断后面的程序不会继续执行

使用场景

我在项目中 需求执行第一步将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据然后使用第一步返回的数据执行第 二步操作的接口调用达到异步操作。

7、解构赋值

ES6 允许按照一定模式从数组和对象中提取值对变量进行赋值这被称为解构赋值

常见的几种方式有

1.默认值

2.交换变量

3.将剩余数组赋给一个变量

结构数组和对象字符串区别

对象的解构与数组类似但有所不同。数组的元素是按次序排列的变量的取值由它的位置决定

而对象的属性没有次序变量必须与属性同名才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象.

我在项目中就是从目标对象或数组中提取自己想要的变量。最常用的场景是element-ui,vant-ui按需引入请求接口返回数据提取想要数据。

8、 for...in 迭代和 for...of 有什么区别

1、 推荐在循环对象属性的时候使用 for...in,在遍历数组的时候的时候使用for...of。

2、 for in遍历的是数组的索引而for of遍历的是数组元素值

3、for...of 不能循环普通的对象需要通过和 Object.keys()搭配使用

4、for...in 遍历顺序以数字为先 无法遍历 symbol 属性 可以遍历到公有中可枚举的

5、从遍历对象的角度来说for···in会遍历出来的为对象的key但for···of会直接报错。

9、 generator 有了解过吗

  • Generator 生成器 也是 ES6 提供的一种异步编程解决方案语法行为与传统函数完全不同 function *{}

  • Generator 函数是一个状态机封装了多个内部状态除了状态机还是一个遍历器对象生成函数。

  • Generator 是分段执行的, yield 又得可暂停next方法可启动。每次返回的是yield后的表达式结果这使得Generator函数非常适合将异步任务同步化

  • Generator 并不是为异步而设计出来的它还有其他功能对象迭代、控制输出、部署Interator`接口…

  • Generator函数返回Iterator对象因此我们还可以通过for...of进行遍历,原生对象没有遍历接口通过Generator函数为它加上这个接口就能使用for...of进行遍历了

promise、Generator、async/await进行比较

promise和async/await是专门用于处理异步操作的
Generator并不是为异步而设计出来的它还有其他功能对象迭代、控制输出、部署Interator接口…
promise编写代码相比Generator、async更为复杂化且可读性也稍差
Generator、async需要与promise对象搭配处理异步情况
async实质是Generator的语法糖相当于会自动执行Generator函数
async使用上更为简洁将异步代码以同步的形式进行编写是处理异步编程的最终方案

10、js构造函数的静态成员和实例成员

js的构造函数在别的后台语言上叫做类上可以添加一些成员可以在构造函数内部的this上添加可以在构造函数本身上添加通过这两种方式添加的成员就分别称为实例成员和静态成员

实例成员:构造函数中this上添加的成员 静态成员构造函数本身上添加的成员

实例成员只能由实例化的对象来访问 静态成员只能由构造函数本身来访问 实例化对象的proto指向构造函数的prototype属性指向的对象实例化的对象可以访问到它后者身上的成员

构造函数生成实例的执行过程使用面向对象编程时,new关键字做了什么

  1. 新建了一个Object对象

  2. 修改构造函数this的指向是其指向新建的Object对象并且执行构造函数

  3. 为Object对象添加了一个proto属性是其指向构造函数的prototype属性

  4. 将这个Object对象返回出去

11、set和map数据结构有哪些常用的属性和方法

set数据的特点是数据是唯一的

const set1 = new Set()

增加元素 使用 add
set2.add(4)

是否含有某个元素 使用 has
console.log(set2.has(2)) 

查看长度 使用 size
console.log(set2.size) 

删除元素 使用 delete
set2.delete(2)

size: 返回Set实例的成员总数。
add(value)添加某个值返回 Set 结构本身。
delete(value)删除某个值。
clear()清除所有成员没有返回值。

Set的不重复性

传入的数组中有重复项会自动去重
const set2 = new Set([1, 2, '123', 3, 3, '123'])

Set`的不重复性中要注意`引用数据类型和NaN
两个对象都是不用的指针所以没法去重
const set1 = new Set([1, {name: '孙志豪'}, 2, {name: '孙志豪'}])

如果是两个对象是同一指针则能去重
const obj = {name: '我们一样'}
const set2 = new Set([1, obj, 2, obj])

NaN !== NaNNaN是自身不等于自身的但是在Set中他还是会被去重
const set = new Set([1, NaN, 1, NaN])

map数据结构

Map`对比`object`最大的好处就是key不受`类型限制


定义map
const map1 = new Map()

新增键值对 使用 set(key, value)
map1.set(true, 1)

判断map是否含有某个key 使用 has(key)
console.log(map1.has('哈哈')) 

获取map中某个key对应的value
console.log(map1.get(true)) 

删除map中某个键值对 使用 delete(key)
map1.delete('哈哈')


定义map也可传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']])
console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }

12、proxy 的理解

Proxy 对象用于创建一个对象的代理从而实现基本操作的拦截和自定义如属性查找、赋值、枚举、函数调用等。

Proxy 可以理解成在目标对象之前架设一层“拦截”外界对该对象的访问都必须先通过这层拦截因此提供了一种机制可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理用在这里表示由它来“代理”某些操作可以译为“代理器”。

13、Es6中新的数据类型symbol

symbol 是es6 加入的是一个基本数据类型它代表的是一个独一无二的值SYMBOL 值是由 SYMBOL函数生成也就是说现在我们定义对象的属性名字可以是原有的字符串 也可以是 symbol 类型的symbol 可以保证不与其他属性名冲突减少了bug的产生

如果那 symbol 对比的话 就是会返回 false

symbol 他是一个原始类型的值就不可以使用 new 关键字symbol不是对象 没有迭代器的接口 不能去添加属性值他是类似于字符串的一种类型

symbol 不能用来四则运算否则会报错只能用显示的方式转为字符串

symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰作为区分 否则会混淆

14、iterator == iteration 遍历器的概念

遍历器Iterator就是这样一种机制。它是一种接口为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口就可以完成遍历操作

Iterator 的作用有三个一是为各种数据结构提供一个统一的、简便的访问接口二是使得数据结构的成员能够按某种次序排列三是 ES6 创造了一种新的遍历命令for...of循环Iterator 接口主要供for...of消费。

其实iteration == iterator 有三个作用

  1. 为各种数据结构提供一个统一的、简便的访问接口

  2. 使得数据结构的成员能够按某种次序排列

  3. 主要供for...of消费

15、Object.assign

Object.assign可以实现对象的合并。它的语法是这样的 Object.assign(target, ...sources)

Object.assign会将source里面的可枚举属性复制到target。如果和target的已有属性重名则会覆盖。同时后续的source会覆盖前面的source的同名属性。

Object.assign复制的是属性值如果属性值是一个引用类型那么复制的其实是引用地址就会存在引用共享的问题

Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。

那么什么是类数组对象呢所谓类数组对象最基本的要求就是具有length属性的对象。

1、将类数组对象转换为真正数组

let arrayLike = {
    0: 'tom', 
    1: '65',
    2: '男',
    3: ['jane','john','Mary'],
    'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['tom','65','男',['jane','john','Mary']]

那么如果将上面代码中 length 属性去掉呢实践证明答案会是一个长度为0的空数组。

这里将代码再改一下就是具有 length 属性但是对象的属性名不再是数字类型的而是其他字符串型的代码如下

let arrayLike = {
    'name': 'tom', 
    'age': '65',
    'sex': '男',
    'friends': ['jane','john','Mary'],
    length: 4
}
let arr = Array.from(arrayLike)
console.log(arr)  // [ undefined, undefined, undefined, undefined ]

会发现结果是长度为4元素均为 undefined 的数组

由此可见要将一个类数组对象转换为一个真正的数组必须具备以下条件

1、该类数组对象必须具有 length 属性用于指定数组的长度。如果没有 length 属性那么转换后的数组是一个空数组。

2、该类数组对象的属性名必须为数值型或字符串型的数字

16、谈谈你对模块化开发的理解

我对模块的理解是一个模块是实现一个特定功能的一组方法。在最开始的时候js 只实现一些简单的功能所以并没有模块的概念 但随着程序越来越复杂代码的模块化开发变得越来越重要。

由于函数具有独立作用域的特点最原始的写法是使用函数来作为模块几个函数作为一个模块但是这种方式容易造成全局变量的污 染并且模块间没有联系。

后面提出了对象写法通过将函数作为一个对象的方法来实现这样解决了直接使用函数作为模块的一些缺点但是这种办法会暴露所 有的所有的模块成员外部代码可以修改内部属性的值。

现在最常用的是立即执行函数的写法通过利用闭包来实现模块私有作用域的建立同时不会对全局作用域造成污染。

17、js 的几种模块规范

js 中现在比较成熟的有四种模块加载方案

  • 第一种是 CommonJS 方案它通过 require 来引入模块通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案它是以同步的方式来引入模块的因为在服务端文件都存储在本地磁盘所以读取非常快所以以同步的方式加载没有问题。但如果是在浏览器端由于模块的加载是使用网络请求因此使用异步加载的方式更加合适。

  • 第二种是 AMD 方案这种方案采用异步加载的方式来加载模块模块的加载不影响后面语句的执行所有依赖这个模块的语句都定义在一个回调函数里等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。

  • 第三种是 CMD 方案这种方案和 AMD 方案都是为了解决异步模块加载的问题sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。

  • 第四种方案是 ES6 提出的方案使用 import 和 export 的形式来导入导出模块。

加油快通关了通关了你会有收获的。

vue面试题

核心原理部分

mvc mvvm和mvp的区别

MVVM 就是 Model-View-ViewModel 的缩写MVVM 将视图和业务逻辑分开。

View视图层Model 数据模型而 ViewModel 是把两者建立通信的桥梁。

在 MVVM 框架下View 和 Model 之间没有直接的联系而是通过 ViewModel 进行交互。View 和 ViewModel 之间以及 Model 和 ViewModel 之间的交互都是双向的因此 view 数据的变化会同步到 Model 中而 Model 数据的变化也会立即反映到 View 上。可以说它们两者是实时更新的互相影响。 ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来而 View 和 Model 之间的同步工作完全是自动的因此开发者只需要关注业务逻辑不需要手动操作 DOM也不需要关注数据状态的同步问题这些都由 MVVM 统一管理

整体看来MVVM比MVC精简很多不仅简化了业务与界面的依赖还解决了数据频繁更新的问题不用再用选择器操作DOM元素。因为在MVVM中View不知道Model的存在Model和ViewModel也观察不到View这种低耦合模式提高代码的可重用性。

【优点】

数据源和视图实现了双向绑定很好的做到了数据的一致性 相比于mvp各层的耦合度更低一个viewmodel层可以给多个view层共用。

【缺点】

因为使用了dataBinding增加了大量的内存开销增加了程序的编译时间项目越大内存开销越大。 数据绑定使得 Bug 很难被调试。你看到界面异常了有可能是你 View 的代码有 Bug也可能是 Model 的代码有问题

MVC 全名是 Model View Controller是模型(model)-视图(view)-控制器(controller)的缩写一种软件设计典范
- Model模型是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
- View视图是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
- Controller控制器是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据控制用户输入并向模型发送数据,也可以将Model的数据用View显示出来

【优点】

耦合性低方便维护可以利于分工协作 重用性高

【缺点】

使得项目架构变得复杂对开发人员要求高

MVP MVP 是从经典的模式MVC演变而来它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理Model提供数据View负责显示在MVP中View并不直接使用Model它们之间的通信是通过Presenter (MVC中的Controller)来进行的

Vue底层实现原理

vue.js是采用数据劫持结合发布者-订阅者模式的方式通过Object.defineProperty()来劫持各个属性的setter和getter在数据变动时发布消息给订阅者触发相应的监听回调 Vue是一个典型的MVVM框架模型Model只是普通的javascript对象修改它则-视图View会自动更新。这种设计让状态管理变得非常简单而直观

Observer数据监听器 : Observer的核心是通过Object.defineProprtty()来监听数据的变动这个函数内部可以定义setter和getter每当数据发生变化就会触发setter。这时候Observer就要通知订阅者订阅者就是Watcher

Compile指令解析器 : Compile主要做的事情是解析模板指令将模板中变量替换成数据然后初始化渲染页面视图并将每个指令对应的节点绑定更新函数添加鉴定数据的订阅者一旦数据有变动收到通知更新-视图

Watcher订阅者 : Watcher订阅者作为Observer和Compile之间通信的桥梁主要做的事情是

  1. 在自身实例化时往属性订阅器(dep)里面添加自己

  2. 自身必须有一个update()方法

  3. 待属性变动dep.notice()通知时能调用自身的update()方法并触发Compile中绑定的回调

Vue模版编译原理。

vue中的模板template无法被浏览器解析并渲染因为这不属于浏览器的标准不是正确的HTML语法所有需要将template转化成一个JavaScript函数这样浏览器就可以执行这一个函数并渲染出对应的HTML元素就可以让视图跑起来了这一个转化的过程就成为模板编译。

Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步

第一步是将 模板字符串 转换成 element ASTs解析器

第二步是对 AST 进行静态节点标记主要用来做虚拟DOM的渲染优化优化器

第三步是 使用 element ASTs 生成 render 函数代码字符串代码生成器

vue虚拟domdiff算法

虚拟 DOM其实就是用对象的方式取代真实的 DOM 操作把真实的 DOM 操作放在内存当中在内存中的对象里做模拟操作。当页面打开时浏览器会解析 HTML 元素构建一颗 DOM 树将状态全部保存起来在内存当中模拟我们真实的 DOM 操作操作完后又会生成一颗 dom 树两颗 DOM 树进行比较根据 diff 算法比较两颗 DOM 树不同的地方只渲染一次不同的地方。

个人理解虚拟dom他不并不是真实的 dom 是根据模板生成一个js对象使用createElement方法根据这个js对象再去生成真实的dom对复杂的文档DOM结构提供一种方便的工具进行最小化的DOM操作 是可以快速的渲染和高效的更新元素提高浏览器的性能

例如一个 ul 标签下很多个 li 标签其中只有一个 li 有变化这种情况下如果使用新的 ul 去替代旧的 ul,因为这些不必要的 DOM 操作而造成了性能上的浪费但是如果直接使用虚拟节点覆盖旧节点的话减少了很多的不必要的 DOM 操作。

我们在渲染页面的时候 会对新的虚拟dom和旧的虚拟dom进行对比 只渲染不同的地方而不再是像之前只要发生变化全部的真实dom都要重新渲染所以提高了渲染的效率。

缺点首次渲染大量DOM时由于多了一层虚拟DOM的计算会比innerHTML插入慢

diff算法

diff 算法是一种通过同层的树节点进行比较的高效算法,比较方式diff整体策略为深度优先同层比较

diff算法 当data发生改变 会根据新的数据生成一个新的虚拟dom 新的虚拟dom和旧的虚拟dom进行对比这个对比的过程就是diff算法会找到不同地方只去渲染不同的地方总的来说就是减少DOM重绘和回流。

为什么要用虚拟DOM来描述真实的DOM呢

创建真实DOM成本比较高如果用 js对象来描述一个dom节点成本比较低另外我们在频繁操作dom是一种比较大的开销。所以建议用虚拟dom来描述真实dom。

响应式原理

 vue的响应式原理
   什么是响应式“响应式”是指当数据改变后Vue 会通知到使用该数据的代码。例如视图渲染中使用了数据数据改变后视图也会自动更新。
   Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法data 中声明的属性都被添加了访问器属性当读取 data 中的数据时自动调用 get 方法当修改 data 中的数据时自动调用 set 方法检测到数据的变化会通知观察者 Wacher观察者 Wacher自动触发重新render 当前组件子组件不会重新渲染,生成新的虚拟 DOM 树Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别并记录下来最后加载操作将所有记录的不同点局部修改到真实 DOM 树上。
​
   
   
Object.defineProperty怎么用 三个参数有什么作用啊
     Object.defineProperty() 方法会直接在一个对象上定义一个新属性或者修改一个对象的现有属性并返回此对象。
     Object.defineProperty(obj, prop, {})
     obj需要定义属性的对象
     prop需要定义的属性
     {}要定义或修改的属性描述符。
     value: "18",         // 设置默认值得
     enumerable: true,    //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了   默认值false
     writable: true,      // 控制属性可以被修改   默认值false
     configurable: true,  // 控制属性可以被删除   默认值false
      get // 当有人读取 prop 的时候  get函数就会调用,并且返回就是 sss 的值
      set // 当有人修改 prop 的时候  set函数就会调用, 有个参数这个参数就是修改后的值
​
     
Object.defineProperty 能定义symbol类型吗 
       在ES6中由于 Symbol类型的特殊性用Symbol类型的值来做对象的key与常规的定义或修改不同而Object.defineProperty 是定        义key为Symbol的属性的方法之一。
    
   
    
vue2和vue3的响应式原理都有什么区别呢
vue2 用的是 Object.defindProperty 但是vue3用的是Proxy 
Object.defindProperty虽然能够实现双向绑定了但是还是有缺点只能对对象的属性进行数据劫持所以会深度遍历整个对象不管层级有多深只要数组中嵌套有对象就能监听到对象的数据变化无法监听到数组的变化Proxy就没有这个问题可以监听整个对象的数据变化所以用vue3.0会用Proxy代替definedProperty。
​
上面就是一个典型的例子当我们点击按钮想要根据数组 arr 的下标改变其元素的时候你会发现 data 中的数据改变了但是页面中的数据并没有改变。
         我会用  this.$set( target, key, value ) 来解决
       参数
         {Object | Array} target
         {string | number} propertyName/index
         {any} value 
         第一参数时指定要修改的数据 target
         第二个参数就是你要设置数据的下标或者是属性名
         第三个参数就是现在要修改的数据 重新赋的值
改变/添加 对象属性的时候:this.$set(data 实例,"属性名(添加的属性名)","属性值(添加的属性值)")
改变/添加 数组属性的时候:this.\$set(data 实例,数组下标,"改变后的元素(添加的元素)")
​
原因  vue在创建实例的时候把data深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它这样才能让它是响应的。
​
为什么要用  this.$set  呢 this.$set是干什么的?
      当你发现你给对象加了一个属性在控制台能打印出来但是却没有更新到视图上时也许这个时候就需要用到this.$set这个方法了简单来说this.$set的功能就是解决这个问题的啦。官方解释向响应式对象中添加一个属性并确保这个新属性同样是响应式的且触发视图更新。它必须用于向响应式对象上添加新属性因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi')你会发现vue官网是vue.setvue.set的用法
​
​
       
那 Vue.set 和 this.$set 有什么区别 
    Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上this.$set() 是将 set 函数绑定在 Vue原型上。 
 

vue双向数据绑定原理

是采用数据劫持结合发布者-订阅者模式的方式通过Object.defineProperty()来劫持各个属性的settergetter在数据变动时发布消息给订阅者触发相应的监听回调从而达到数据和视图同步。

主要分为四部分

1、 observer 主要是负责对Vue数据进行递归遍历使其数据拥有get和set方法当有数据给某个对象值赋值就触发 setter 就监听到数据的变化了。 如有变动可拿到最新值并通知订阅者

2、compile 指令解析器负责绑定数据和指令解析。 将模板中的变量替换成数据然后初始化渲染页面视图并将每个指令对应的节点绑定更新函数。一旦数据有变动收到通知更新视图

3、 订阅者 watcher Watcher订阅者是Observer和Compile之间通信的桥梁主要做的事情是 负责数据监听当数据发生改变能调用自身的update()方法并触发Compile中绑定的更新函数

4、实现一个订阅器 dep 采用发布者订阅者模式用来收集订阅者的 watcher对监听器 observer 和订阅者 watcher 进行统一管理

vue3的Proxy 相比于 vue2的defineProperty 的优势

在vue3 中
Vue3是通过Object.define.proxy 对对象进行代理从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变唯一的缺点是兼容性的问题因为 Proxy 是 ES6 的语法

Vue3.0 摒弃了 Object.defineProperty改为基于 Proxy 的观察者机制探索。
首先说一下 Object.defineProperty 的缺点
Object.defineProperty 无法监控到数组下标的变化导致直接通过数组的下标给数组设置值不能实施响应。 this.$set()解决
Object.defineProperty 只能劫持对象的属性因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里是通过递归 + 遍历 data 对象来实现对数据的监控的如果属性值也是对象那么需要深度遍历显然如果能劫持一个完整的对象才是更好的选择。


而要取代它的 Proxy 有以下两个优点
可以劫持整个对象并返回一个新对象。有多种劫持操作(13 种)

补充
Proxy 用于创建一个对象的代理从而实现基本操作的拦截和自定义如属性查找、赋值、枚举、函数调用等。 mdn
Proxy 是 ES6 新增的一个属性翻译过来的意思就是代理用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问其功能非常类似于设计模式中的代理模式。



1、vue 中数组中的某个对象的属性发生变化视图不更新如何解决?
 Object.defineProperty 无法监控到数组下标的变化导致直接通过数组的下标给数组设置值不能实施响应。 this.$set()解决

 问题原因因为 vue 的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。解决方案如下
方案一利用  this.set(this.obj,key,val)
例this.set(this.obj,‘k1’,‘v1’)
方案二就利用 Object.assign({}this.obj)创建新对象  
如果是数组就 Object.assign([]this.obj)
如果是对象就 Object.assign({}this.obj)。

vue.js的两个核心是什么

灵活的组件应用高效的数据绑定

渐进式框架的理解,vue数据驱动的理解

渐进式代表的含义是主张最少——它是一个轻量级框架只做了自己该做的事没有做不该做的事

每个框架都不可避免会有自己的一些特点从而会对使用者有一定的要求这些要求就是主张主张有强有弱它的强势程度会影响在业务开发中的使用方式。

这里的vue数据驱动的是视图也就是DOM元素指的是让DOM的内容随着数据的改变而改变框架的理解

Vue的SSR是什么有什么好处

SSR全称Server Side Render

  • 有利于SEO由于是在服务端将数据填充进HTML之后再推送到浏览器所以有利于SEO的爬取

  • 首屏渲染快

SSR的缺点

  • 开发条件会受到限制服务器端渲染只支持beforeCreate和created两个钩子

  • 当需要一些外部扩展库时需要特殊处理服务端渲染应用程序也需要处于Node.js的运行环境

  • 更多的服务端负载。

vue3.0 与 vue2.0 的区别

1.性能提升

更小巧更快速支持摇树优化。支持 Fragments (支持多个根节点)和跨组件渲染支持自定义渲染器。

2.API 变动

Vue2使用 选项类型APIOptions API 对比Vue3 合成型APIComposition API

optionsApi 使用传统api中新增一个需求要在datamethodscomputed中修改

compositionApi 我们可以更加优雅的组织我们的代码函数让我们的代码更加有序的组合在一起

3.重写虚拟 DOM (Virtual DOM Rewrite)

随着虚拟 DOM 重写减少 运行时runtime开销。重写将包括更有效的代码来创建虚拟节点。

vue3 没有了过滤器

双向数据绑定 从 Object.defineProperty() 变成了 proxy通过下标修改数组变化了视图数据没发生变化 this.$set() vue3不需要

  1. 双向数据绑定原理发生了改变使用proxy替换Object.defineProerty,使用Proxy的优势

  • 可直接监听数组类型的数据变

  • 监听的目标为对象本身不需要像Object.defineProperty一样遍历每个属性有一定的性能提升

  • 可直接实现对象属性的新增/删除

setup 函数

3.0新加入了TypeScript以及PWA支持

默认使用懒加载

可以不用加上key

vue3 的watch监听可以进行终止监听

生命周期有了一定的区别 Vue2--------------vue3

beforeCreate  -> setup()    开始创建组件之前创建的是data和method
created    -> setup()
beforeMount  -> onBeforeMount   组件挂载到节点上之前执行的函数。
mounted    -> onMounted 组件挂载完成后执行的函数
beforeUpdate  -> onBeforeUpdate 组件更新之前执行的函数。
updated    -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount    组件挂载到节点上之前执行的函数。
destroyed   -> onUnmounted  组件卸载之前执行的函数。
activated   -> onActivated  组件卸载完成后执行的函数
deactivated  -> onDeactivated

vue 与 react的区别

相同点

  • 都是使用了虚拟dom

  • 组件化开发

  • 父子之间通信单项数据流

  • 都支持服务端渲染

不同点

  • reacct 的jsx vue的是 template

  • 数据变化react 手动 setState vue自动响应式处理 proxy object.DefineProperty

  • react 单向数据流 vue双向数据流

  • react 的 redux mobx vue 的vuex 。pinia

vue生命周期11个进行扩展延伸

声明周期那几个每一个生命周期的特点,可以做什么。

beforeCreate() 创建前这个时候data中的数据还未定义所以不能使用
created()创建后 最早开始使用 data和methods中数据的钩子函数
​
beforeMount()挂载前 指令已经解析完毕内存中已经生成dom树但是尚未挂载到页面中去此时页面还是旧的。
mounted()挂载后 dom已经渲染完毕此时页面和内存中都是最新的数据最早可以操作DOM元素钩子函数
​
 beforeUpdate()更新前 当视图层的数据发生改变会执行这个钩子 内存更新但是DOM节点还未更新数据没有与页面同步
 updated()更新后 数据更新完成以后触发的方法DOM节点已经更新
​
 beforeDestroy()即将销毁 data和methods中的数据此时还是可以使用的可以做一些释放内存的操作
 destroyed()销毁完毕  组件已经全部销毁Vue实例已经被销毁Vue中的任何数据都不可用
 
 其他三个
activated  被 keep-alive 缓存的组件激活时调用。
deactivated 被 keep-alive 缓存的组件停用时调用。
errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用
​
Vue3.0中的生命周期做了一些改动
beforeCreate  -> setup()    开始创建组件之前创建的是data和method
created       -> setup()
beforeMount   -> onBeforeMount  组件挂载到节点上之前执行的函数。
mounted       -> onMounted  组件挂载完成后执行的函数
beforeUpdate  -> onBeforeUpdate 组件更新之前执行的函数。
Update        - > onUpdated组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount    组件挂载到节点上之前执行的函数。
destroyed     -> onUnmounted    组件卸载之前执行的函数。
​
​
- vue的实例加载完成是在哪个声明周期完成呢
beforeCreate
- vue的dom挂载完成是在哪个声命周期里呢
mounted
​
1、created mounted 的区别
created 模板渲染成html前调用即通常初始化某些属性值然后再渲染成视图。
mounted在模板渲染成html后调用通常是初始化页面完成后再对html的dom节点进行一些需要的操作。
​
​
2、怎么在created里面操作dom?
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样不同的是回调的 this 自动绑定到调用它的实例上。
可以根据打印的顺序看到在created()钩子函数执行的时候DOM 其实并未进行任何渲染而此时进行DOM操作并无作用而在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象而通过this.$nextTick()获取到的值为dom更新之后的值
​
 setTimeout(() => {
      console.log(this.$refs.button);
 });
 
 
3、那 setTimeout this.$nextTick 什么区别呢
setTimeout 将同步转换为异步 this.$nextTick 
​
 this.$nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它
​
4、this.$nextTick()是宏任务还是微任务啊 
 优先是Promise.then方法是个微任务这样可以避免多一次队列进而少一次UI渲染节省性能
​
​
5、a页面跳转到b页面周期执行
页面a----beforeCreate undefined
页面a----created 1
页面a----beforeMount 1
页面a----mounted 1
页面b----beforeCreate undefined
页面b----created 1
页面b----beforeMount 1
页面a----beforeDestroy 1
页面a----destroyed 1
页面b----mounted 1
​
​
6、组件 和 页面周期 的执行顺序
- 页面beforeCreate undefined
- 页面created 1
- 页面beforeMount 1
- 组件beforeCreate undefined
- 组件created 5555
- 组件beforeMount 5555
- 组件mounted 5555
- 页面mounted 1
​
7、父子组件生命周期执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
代码更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
代码销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
代码常用钩子简易版
父create->子created->子mounted->父mounted
​
​
8、补充单一组件钩子执行顺序
​
activated, deactivated 是组件keep-alive时独有的钩子
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured
​
​
watch
仅仅是数据发生改变的时候会侦听到
只是会检测到你写在watch里的那些属性,没写的就不会触发。
​
updated
执行到它的时候时候是数据发生变化且界面更新完毕
不能监听到路由数据(例如网址中的参数)
所有的数据发生变化都会调用(消耗性能)
每次触发的代码都是同一个
​
computed
1、监控自己定义的变量不用再data里面声明函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化响应式属性没有变化直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用return返回
6、不要在computed 中对data中的数据进行赋值操作这会形成一个死循环。
​
methods不会被缓存方法每次都会去重新计算结果。methods 方法表示一个具体的操作主要书写业务逻辑
使用 methods 方法编写的逻辑运算在调用时 add() 一定要加“()”methods 里面写的多位方法调用方法一定要有。methods方法页面刚加载时调用一次以后只有被调用的时候才会被调用。我们在长度框和宽度框的值输入完以后点击“+” methods 方法调用一次。这里很明显我们采用 methods 会更节省资源。
​
​
使用场景
​
watch
1、watch 函数是不需要调用的。
2、重点在于监控监控数据发生变化的时候执行回调函数操作。
3、当我们需要在数据变化时执行异步或开销较大的操作时应该使用 watch
4、函数名就是你要监听的数据名字
5、监控一些input框值的特殊处理适合一个数据影响多个数据。
6、数据变化时执行一些异步操作或开销比较大的操作
​
computed
在模板中放入太多的逻辑会让模板过重且难以维护在需要对数据进行复杂处理且可能多次使用的情况下尽量采取计算属性的方式
一个需要的结果受多个数据影响的时候比如购物车结算金额(受到很多处的价格结算)。
操作某个属性执行一些复杂的逻辑并在多处使用这个结果。
内部函数中多处要使用到这个结果的。
1、监控自己定义的变量不用再data里面声明函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化响应式属性没有变化直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用return返回
6、不要在computed 中对data中的数据进行赋值操作这会形成一个死循环。
​

一般在哪个生命周期请求异步数据

可以啊钩子函数中的 createdbeforeMountmounted 中进行调用因为在这三个钩子函数中data 已经创建可以将服务端端返回的数据进行赋值。

在created中最好

能更快获取到服务端数据减少页面加载时间用户体验更好

SSR不支持 beforeMount 、mounted 钩子函数放在 created 中有助于一致性。

mounted 在请求完数据之后需要对 dom 进行操作的时候可以用到

vue中 methods,computed, watch 的区别

computed 是vue中的计算属性具有缓存性当他的依赖于值发生改变的时候才会重新调用

methods 是没有缓存的只要调用就会执行一般结合事件来使用

watch 没有缓存性 监听data中的属性 属性值只要发生变化就会执行 可以利用他的特性做一些异步的操作

created和mounted区别

createddom渲染前调用即通常初始化某些属性值

mounted在dom渲染后调用通常是初始化页面完成后再对html的dom节点进行一些需要的操作

生命周期钩子是如何实现的

Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好内部采用数组的方式存储然后在创建组件实例的过程中会一次执行对应的钩子方法发布

vuex常问的考点

Vuex严格模式

开启严格模式仅需在创建 store 的时候传入 strict: true

const store = new Vuex.Store({
  // ...
  strict: true
})
在严格模式下无论何时发生了状态变更且不是由 mutation 函数引起的将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

开发环境与发布环境

不要在发布环境下启用严格模式严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式以避免性能损失。

类似于插件我们可以让构建工具来处理这种情况

const store = createStore({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})

vuex是什么 state,getters,mutations,actions,modules的用途和用法

vuex是一个状态管理工具所谓状态的是就是数据采用集中式存储管所有组件的状态是为了结局中大型项目一个数据共享的问题。vuex 他可以将数据保存到本地数据是响应式的能够保持数据页面的共享提高开发效率。

好处

能够在 vuex 中集中管理共享的数据易于开发和后期维护 可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中 存储在 vuex 中的数据都是响应式的能够实时保持数据与页面的同步能够高效地实现组件之间的数据共享提高开发 效率

vuex核心

  1. statevuex的基本数据数据源存放地用于定义共享的数据。

  2. getter从基本数据派生的数据相当于state的计算属性

  3. mutation提交更新数据的方法唯一 一个可以操作state 中数据的方法必须是同步的第一个参数是state第二个参数是cmmi传过来的数据

  4. actionaction是用来做异步操作的一般用来发请求在 action 中写入函数然后在页面中用dispatch调用然后在 action 中通过commit 去调用 mutation 通过 mutation 去操作state。

  5. modules模块化vuex可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰方便管理

详述Vuex运行机制

运行机制Vuex提供数据state来驱动视图这里指的是Vue组件视图通过Dispatch派发Action在Action中可以进一步做一些异步的操作例如通过ajax请求后端的接口数据然后通过Commit提交给Mutations由Mutations去最终更改state。那么为什么要经过Mutations呢这是因为我们要在Vue调试工具(Devtools)中记录数据的变化这样可以通过插件去进行进一步的调试。所以说Mutations中只能是纯同步的操作如果是有异步操作那么就需要在Actions中进行处理。如果说没有异步操作那么可以直接由组件进行Commit操作Mutations。

 

高级用法辅助函数语法糖

mapState,mapActions,mapMutations,mapGetters

  1. 辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的

  2. 如何使用

Import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'

computed(){ ...mapState(['数据名字'])}

Vuex 页面刷新数据丢失怎么解决

需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件

推荐使用 vuex-persist 插件它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage 而是直接将状态保存至 cookie 或者 localStorage 中

Vuex 为什么要分模块并且加命名空间

模块:由于使用单一状态树应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时store 对象就有可能变得相当臃肿。为了解决以上问题Vuex 允许我们将 store 分割成模块module。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。方便管理

vue的路由

vue-router(路由原理路由守卫)

由于Vue在开发时对路由支持的不足于是官方补充了vue-router插件。vue的单页面应用是基于路由和组件的路由用于设定访问路径并将路径和组件映射起来。传统的页面应用是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中则是路径之间的切换实际上就是组件的切换。路由就是SPA单页应用的路径管理器。再通俗的说vue-router就是我们WebApp的链接路径管理系统。

原理 一般源码中都会用到 window.history 和 location.hash 原理通过改变浏览器地址URL在不重新请求页面的情况下更新页面视图通过BOM中的location对象其中对象中的location.hash储存的是路由的地址、可以赋值改变其URL的地址。而这会触发hashchange事件而通过window.addEventListener监听hash值然后去匹配对应的路由、从而渲染页面的组件 1.一种是# hash,在地址中加入#以欺骗浏览器地址的改变是由于正在进行页内导航 2.一种是h5的history,使用URL的Hash来模拟一个完整的URL

路由有两种模式 hash和history模式 默认是hash

vue-router的实现原理(核心)更新视图但不重新请求页面。

1、hash ——即地址栏 URL 中的#符号它的特点在 于hash 虽然出现 URL 中但不会被包含在 HTTP 请求中对后端完全没有影 响因此改变 hash 不会重新加载页面。

2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题

3、history 模式下前端的 URL 必须和实际向后端发起请求的 URL 一致否则返回 404 错误。

•一全局的守卫

无论访问哪一个路径都会触发全局的钩子函数位置是调用router的方法 router/index.js

router.beforeEach 全局前置守卫 进入路由之前

router.beforeResolve 全局解析守卫在beforeRouteEnter调用之后调用

同时在所有组件内守卫和异步路由组件被解析之后解析守卫就被正确调用

router.afterEach 全局后置钩子 进入路由之后

你也可以注册全局后置钩子然而和守卫不同的是这些钩子不会接受 next 函数也不会改变导航本身

二组件级路由守卫 放在要守卫的组件里跟data和methods同级

  • beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到zhis

  • beforeRouteUpdate (2.2) 路由复用同一个组件时

  • beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等

//在组件内部进行配置,这里的函数用法也是和beforeEach一毛一样
const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不能获取组件实例 `this`
    // 因为当守卫执行前组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变但是该组件被复用时调用
    // 举例来说对于一个带有动态参数的路径 /foo/:id在 /foo/1 和 /foo/2 之间跳转的时候
    // 由于会渲染同样的 Foo 组件因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

三单个路由规则独享的守卫 写在路由配置中只有访问到这个路径才能触发钩子函数

beforeEnter:(to,from,next)=>{ alert("欢迎来到孙志豪的界面") next() }

参数

  • to: Route: 即将要进入的目标 路由对象

  • from: Route: 当前导航正要离开的路由对象

  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

重定向用哪个属性

redirect:”/路径”

vue路由的跳转方式有几种

1、<router-link to="需要跳转到页面的路径"> 2、this.$router.push()跳转到指定的url并在history中添加记录点击回退返回到上一个页面

3、this.$router.replace()跳转到指定的url但是history中不会添加记录点击回退到上上个页面

4、this.$touter.go(n)向前或者后跳转n个页面n可以是正数也可以是负数

router.push、router.replace、router.go、router.back的区别

router.push跳转并向history栈中加一个记录可以后退到上一个页面

router.replace跳转不会向history栈中加一个记录不可以后退到上一个页面

router.go传正数向前跳转传负数向后跳转

router.back 返回到上一级页面

vue 路由传参数如何实现、query 和 params

主要通过 query 和 params 来实现

(1) query可以使用name和path而params只能使用name

(2) 使用params传参刷新后不会保存而query传参刷新后可以保存

(3) Params在地址栏中不会显示query会显示

(4) Params可以和动态路由一起使用query不可以

5to=”/goods?id=1001”this.然后在接收的页面通过 $route.query.id 来接收

路由对象route和router的区别

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

router 是“路由实例对象”包括了路由的跳转方法(push、go)钩子函数等。

vue-router 路由钩子函数是什么 执行顺序是什么执行顺序

一、打开页面的任意一个页面没有发生导航切换。
全局前置守卫beforeEach (路由器实例内的前置守卫)
路由独享守卫beforeEnter(激活的路由)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
​
二、如果是有导航切换的(从一个组件切换到另外一个组件)
组件内守卫beforeRouteLeave(即将离开的组件)
全局前置守卫beforeEach (路由器实例内的前置守卫)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
​
​
完整的导航解析流程
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数创建好的组件实例会作为回调函数的参数传入。

动态路由

动态路由是指路由器能够自动的建立自己的路由表能够根据实际情况的变化实时地进行调整。用开头后面跟的值是不确定的。这个值是我们要传递的参数 动态路由匹配本质上就是通过url进行传参

    比如在写一个商品详情页面的时候我们的页面结构都一样只是渲染的数据不同而已这时候就可以根据商品的不同id去设置动态路由只需要写一个组件就可以把每个商品的商品详情映射到同一个组件上去。
    { 
        path: '/Jqxq/:id', // 路由配置拼接
        name: 'Jqxq',
        component: Jqxq
    }
    跳转 this.$router.push('/Jqxq/'+ item.id)
    接收   this.$route.params.id

嵌套路由

vue项目中界面通常由多个嵌套的组件构成 必须先清楚这样一件事,一个<router-view/>对应展示的就是一个组件 因此实现嵌套路由有两个要点: 路由对象中定义子路由 用children实现嵌套路由 组件内<router-view/>的使用.

路由配置

export default new Router({
    mode: 'history', //路由模式取值为history与hash
    base: '/', //打包路径默认为/可以修改
    routes: [
    {
        path: string, //路径
        ccomponent: Component; //页面组件
        name: string; // 命名路由-路由名称
        components: { [name: string]: Component }; // 命名视图组件
        redirect: string | Location | Function; // 重定向
        props: boolean | string | Function; // 路由组件传递参数
        alias: string | Array<string>; // 路由别名
        children: Array<RouteConfig>; // 嵌套子路由
        // 路由单独钩子
        beforeEnter?: (to: Route, from: Route, next: Function) => void; 
        meta: any; // 自定义标签属性比如是否需要登录
        icon: any; // 图标
        // 2.6.0+
        caseSensitive: boolean; // 匹配规则是否大小写敏感(默认值false)
        pathToRegexpOptions: Object; // 编译正则的选项
    }
    ]
})

怎么定义 vue-router 的动态路由? 怎么获取传过来的值

在 router 目录下的 index.js 文件中对 path 属性加上 /:id使用 route 对象的 params.id 获取

Vue-router共有几种模式默认是那种

有两种模式 hash和history模式 默认是hash

1、hash ——即地址栏 URL 中的#符号它的特点在 于hash 虽然出现 URL 中但不会被包含在 HTTP 请求中对后端完全没有影 响因此改变 hash 不会重新加载页面。

2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题

history 模式下前端的 URL 必须和实际向后端发起请求的 URL 一致如 地址后加上/items/id。后端如果缺少对 /items/id 的路由处理将返回 404 错误。

路由懒加载

使用原因在单页应用中如果没有应用懒加载运用 webpack 打包后的文件将会异常的大造成进入首页时需要加载的内容过多延时过长不利于用户体验而运用懒加载则可以将页面进行划分需要的时候加载页面可以有效的分担首页所承担的加载压力减少首页加载用时 原理vue 异步组件技术异步加载vue-router 配置路由 , 使用 vue 的异步组件技术 , 实现按需加载。

{ path: '/home', component: () => import('@/views/home/home.vue') } // 懒加载

能说下 vue-router 中常用的路由模式实现原理吗

hash 模式
location.hash 的值实际就是 URL 中#后面的东西 它的特点在于hash 虽然出现 URL 中但不会被包含在 HTTP 请求中对后端完全没有影响因此改变 hash 不会重新加载页面。

可以为 hash 的改变添加监听事件
window.addEventListener("hashchange", funcRef, false);

每一次改变 hashwindow.location.hash都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点就可以来实现前端路由“更新视图但不重新请求页面”的功能了
特点兼容性好但是不美观

history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站在当前已有的 back、forward、go 的基础之上它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点当调用他们修改浏览器历史记录栈后虽然当前 URL 改变了但浏览器不会刷新页面这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。

指令部分进行扩展

vue常用修饰以及常见指令

修饰符
.stop  阻止事件冒泡
.cpture 设置事件捕获
.self  只有当事件作用在元素本身才会触发
.prevent 阻止默认事件比如超链接跳转
.once 事件只能触发一次
.native 触发js原生的事件
.number 把文本框的内容转换为数字
.trim  去除文本框左右空格
  • 常见指令

⑴v-bind给元素绑定属性
⑵v-on给元素绑定事件
⑶v-html给元素绑定数据且该指令可以解析 html 标签
⑷v-text给元素绑定数据不解析标签
⑸v-model数据双向绑定
⑹v-for遍历数组
⑺v-if条件渲染指令动态在 DOM 内添加或删除 DOM 元素
⑻v-else条件渲染指令必须跟 v-if 成对使用
⑼v-else-if判断多层条件必须跟 v-if 成对使用
⑽v-cloak解决插值闪烁问题
⑾v-once只渲染元素或组件一次
⑿v-pre跳过这个元素以及子元素的编译过程以此来加快整个项目的编译速度
⒀v-show条件渲染指令将不符合条件的数据隐藏(display:none)

v-for 与 v-if 的优先级

v-for 比 v-if 优先如果每一次都需要遍历整个数组将会影响速度尤其是当之需要渲染很小一部分的时候。

vue中key 的作用

“key 值:用于管理可复用的元素。因为 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 变得非常快,但是这样也不总是符合实际需求。 2.2.0+ 的版本里,当在组件中使用 v-for 时,key 是必须的。”

key是给每一个vnode的唯一id也是diff的一种优化策略可以根据key更准确 更快的找到对应的vnode节点更高效的对比虚拟DOM中每个节点是否是相同节点相同就复用不相同就删除旧的创建新的

key 是使用 index 还是 id 啊

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时它默认用“就地复用”策略。如果数据项的顺序被改变Vue 将不会移动 DOM 元素来匹配数据项的顺序 而是简单复用此处每个元素并且确保它在特定索引下显示已被渲染过的每个元素。key 的作用主要是为了高效的更新虚拟 DOM。

举例子加入写一个带有复选框的列表

选中第一个节点的复选框点击删除vue中是这样操作的删除后新的数据这时会进行比较第一个节点的标签一样值不一样就会复用原来位置的标签不会做删除和创建在第一个节点中是将复选框选中的当我们看见好像是把第一个删除了但是点击后去看复选框的时候还是选中在第一个如果是直接将第一个节点删除了那么复选框就不会选中。

vue 初始化页面闪动问题。

能够解决插值表达式闪烁问题需要在style中设置样式[v-clock]{display:none}

v-if和v-show的区别及使用场景

v-if 动态的创建或者销毁元素为true的时候为显示为false的时候不显示要使用v-else必须和v-if紧挨着

v-show 是控制元素的显示或者隐藏在我们的标签上会看到有displayblocknone

v-if 有更高的切换消耗而 v-show 有更高的初始化渲染消耗一般推荐频繁切换的时候使用 v-show 更好当我们的判断分支比较多的时候和首次渲染的时候 使用v-if

自定义指令自定义过滤器

出了vue自带的指定以外我们如果需要对dom进行底层操作的时候这里就用到了自定义指令分为一下

全局 vue.directive:{"",{}} 局部directives:{指令名{钩子函数}}

  1. bind只调用一次指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  2. inserted被绑定元素插入父节点时调用 (仅保证父节点存在但不一定已被插入文档中)。

  3. update所在组件的 VNode 更新时调用但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

  4. componentUpdated指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  5. unbind只调用一次指令与元素解绑时调用。

参数:

el指令所绑定的元素

binding一个对象包含一下

name指令名不包括 v- 前缀。

value指令的绑定值

补充  vue3的自定义指令钩子函数?
​
created - 自定义指令所在组件, 创建后
beforeMount - 就是Vue2.x中的 bind, 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只是加入进了DOM, 但是渲染没有完成
mounted - 就是Vue2.x中的 inserted, 自定义指令所在DOM, 插入到父 DOM 后调用, 渲染已完成(最最重要)
beforeUpdate - 自定义指令所在 DOM, 更新之前调用
updated - 就是Vue2.x中的 componentUpdated
beforeUnmount - 销毁前
unmounted - 销毁后

自定义指令原理

1.在生成 ast 语法树时遇到指令会给当前元素添加 directives 属性

2.通过 genDirectives 生成指令代码

3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子

4.当执行指令对应钩子函数时调用对应指令定义的方法

选项对象和常用api

什么是过滤器怎么定义全局和局部过滤器

过滤器是对 即将显示的数据做进一步的筛选处理然后显示过滤器并没有改变原来的数据只是在原数据的基础上产生新的数据

全局

Vue.filter(‘过滤器名’,funciton(val){})

局部过滤器定义在组件内部 filters 属性上.它只能在此组件内部使用.

filters{过滤器名:funciton(参数){//逻辑代码}}

使用 过滤时间过滤金钱

什么是 mixin

Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。 mixin 项目变得复杂的时候多个组件间有重复的逻辑就会用到mixin

如果希望在多个组件之间重用一组组件选项例如生命周期 hook、 方法等则可以将其编写为 mixin并在组件中简单的引用它。

然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook那么它在执行时将优化于组件自已的 hook。

在vue.js中mixin和页面执行顺序问题

mixin中的代码先执行单文件中的后执行。

mixin的beforeCreate > 父beforeCreate > mixin的created > 父created > mixin的beforeMount > 父beforeMount > 子beforeCreate > 子created > 子beforeMount > 子mounted > mixin的mounted >父mounted

nextTick 使用场景和原理

在下次DOM更新循环结束后执行延迟回调。在修改数据之后立即使用这个方法获取更新后的DOM。使用场景是可以在created钩子函数中拿到dom节点

nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法

vue的删除数组和原生删除数组的区别delete

删除数组

  1. delete只是把数组元素的值变成empty/undefined元素的键不变数组长度不变。

  2. Vue.delete直接删除数组改变数组的键值和长度。

删除对象

两者相同都会把键名属性/字段和键值删除。

Vue.extend 作用和原理

官方解释Vue.extend 使用基础 Vue 构造器创建一个“子类”。参数是一个包含组件选项的对象。

其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并 基础用法

<div id="mount-point"></div>
// 创建构造器
/* Vue.extend( options )
  参数{Object} options
  用法使用基础 Vue 构造器创建一个“子类”。参数是一个包含组件选项的对象
       data 选项是特例需要注意 在 Vue.extend() 中它必须是函数*/
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例并挂载到一个元素上。
new Profile().$mount('#mount-point')
​
// 结果如下
<p>Walter White aka Heisenberg</p>
​
/*
可以看到extend 创建的是 Vue 构造器而不是我们平时常写的组件实例所以不可以通过 new Vue({ components: testExtend }) 来直接使用需要通过 new Profile().$mount(’#mount-point’) 来挂载到指定的元素上。
*/
​

组件模块部分插槽单页面通信

vue 组件父子子父兄弟通信

父传递子如何传递

1在父组件的子组件标签上绑定一个属性挂载要传输的变量 ​ 2在子组件中通过props来接受数据props可以是数组也可以是对象接受的数据可以直接使用 props: ["属性名"] props:{属性名:数据类型}

子传递父如何传递

1在父组件的子组件标签上自定义一个事件然后调用需要的方法 ​ 2在子组件的方法中通过 this.$emit("事件")来触发在父组件中定义的事件数据是以参数的形式进行传递的

兄弟组件如何通信

1找到min.js文件给他vue挂载一个 公共的 $bus Vue.prototype.$bus = new Vue() ​ 2传送数据的一方 用this.$bus.$emit('事件名''传送的数据') ​ 3在 接收数据的一方用通过 Bus.$on("事件名",(data)=>{data是接受的数据})

12种组件通信 vue12种通信方式

prop 验证和默认值

props:会接收不同的数据类型常用的数据类型的设置默认值的写法Number, String, Boolean, Array, Function, Object

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定父级prop 的更新流动到子组件中但是反过来则不行。这样防止子组件意外改变父级组件的状态从而导致你的应用的数据流向难以理解。若果在子组件中直接修改prop传递的值Vue会发出警告

方法二通过 vuex 实现 (要了解)

具体实现vuex 是一个状态管理工具主要解决大中型复杂项目的数据共享问题主要包括 state,actions,mutations,getters 和 modules 5 个要素主要流程组件通过 dispatch 到 actionsactions 是异步操作再 actions中通过 commit 到 mutationsmutations 再通过逻辑操作改变 state从而同步到组件更新其数据状态

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

① 项目使用 keep-alive 时可搭配组件的 name 进行缓存过滤。 ② DOM 做递归组件时需要调用自身 name ③ vue-devtools 调试工具里显示的组件名称是由 vue 中组件 name 决定的

自定义组件

在vue 中的 component 中新建组件定义好<template>视图层<script>逻辑层<style>css样式层。然后在页面引入在components 中注册组件 在页面中作为标签来使用。

在vue中开发都是用的组件化的思想开发的组件封装的方式可以使我们的开发效率提高把单页面的每个模块拆分为一个组件件

组件封装的方式解决了我们传统项目开发效率低难以维护复用性低等问题。

使用比如说封装一个 swiper 首先我们要定义一个props要接受传递的数据写入响应的逻辑在通过import引入到页面注册作为标签使用即可。

keep-alive 的作用

keep-alive是Vue提供给我们一个内置组件会缓存不活动的组件实例而不是销毁它们 作为标签使用 包裹在需要缓存的组件外

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

作用 比如列表页面进入详情我们想保存列表滚动的位置我们就可以使用keep-alive保存列表页面的滚动位置。

组件使用keep-alive以后会新增两个生命周期 actived() deactived()

activated组件激活时使用 与 deactivated组价离开时调用

有两个参数 允许组件有条件的进行缓存。

include - 包裹的组件名会被缓存

exclude 包裹的组件名都不会被缓存

keep-alive 缓存beforDestroy 还会执行吗。

首先答案是不会的准确的说是不会直接调用。 默认情况下也就是没有设置keep-alive,当离开当前路由时会直接调用beforeDestroy和destroyed来销毁。 当组件设置keep-alive后不会直接调用这个销毁周期函数而是会在生命周期函数新增两个activated和deactivated; 当退出的时候会执行deactivated 函数

VUE组件中的data为什么是一个函数

Object是引用数据类型如果不用function返回每个组件的data都是内存的同一个地址一个数据改变了其他也改变了这就造成了数据污染。如果data是一个函数每个实例的data都在闭包中就不会各自影响了

组件特性及好处、组件的基本组成

(1) 特性:重用性、可指定性、互操作性、高内聚性、低耦合度

(2) 好处组件可以扩展HTML元素、封装可重用代码

template 结构html代码

script行为

style样式

什么是slot什么是命名slotslot怎么使用

插槽就是父组件往子组件中插入一些内容。

有三种方式默认插槽具名插槽作用域插槽

  1. 默认插槽就是把父组件中的数据显示在子组件中子组件通过一个slot插槽标签显示父组件中的数据

  2. 具名插槽是在父组件中通过slot属性给插槽命名在子组件中通过slot标签根据定义好的名字填充到对应的位置。这样就可以指定多个可区分的slot在使用组件时灵活地进行插值。

  3. 作用域插槽是带数据的插槽子组件提供给父组件的参数父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过v-slot=""要穿过来的数据“来接受数据。

scoped 原理及穿透方法

vue 中的 scoped 通过在 DOM 结构以及 css 样式上加唯一不重复的标记data-v-hash 的方式以保证唯一(通过 PostCSS 转译)达到样式私有模块化的目的。

scoped 的 3 条渲染规则 ① 给 HTML 的 DOM 节点加一个不重复的 data 属性来表示它的唯一性 ② 在每句 css 选择器末尾编译后的生成的 css 语句加一个当前组件的 data 属性选择器来私有化样式 ③ 如果组件内部包含有其他组件只会给其他组件的最外层标签加上 ddan 当前组件的 data 属性。 补充

在做项目中会遇到这么一个问题即引用了第三方组件需要在组件中局部修改第三方组件的样式而又不想去除scoped属性造成组件之间的样式污染。那么有哪些解决办法呢
①不使用scopeds省略不推荐
② 在模板中使用两次style标签。
③scoped穿透/deep/ >>>

函数式组件使用场景和原理

函数式组件与普通组件的区别
​
1.函数式组件需要在声明组件是指定 functional:true
2.不需要实例化所以没有this,this通过render函数的第二个参数context来代替
3.没有生命周期钩子函数不能使用计算属性watch
4.不能通过$emit 对外暴露事件调用事件只能通过context.listeners.click的方式调用外部传入的事件
5.因为函数式组件是没有实例化的所以在外部通过ref去引用组件时实际引用的是HTMLElement
6.函数式组件的props可以不用显示声明所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
​
​
优点 1.由于函数式组件不需要实例化无状态没有生命周期所以渲染性能要好于普通组件 2.函数式组件结构比较简单代码结构更清晰
使用场景
一个简单的展示组件作为容器组件使用 比如 router-view 就是一个函数式组件
“高阶组件”——用于接收一个组件作为参数返回一个被包装过的组件

vue的项目中问题

单页面应用和多页面应用区别以及优缺点

单页面只有一个html页面跳转方式是组件之间的切换

优点跳转流畅、组件化开发、组件可复用、开发便捷

缺点首屏加载过慢

多页面有多个页面跳转方式是页面之间的跳转

优点首屏加载块

缺点跳转速度慢

为什么要使用脚手架

快速开始一个vue项目不用手动配置直接开发

口喷axios封装

首先要安装axios一般我会在项目的src目录中新建一个network文件夹,作为我们的网络请求模块然后在里面新建一个http.js和一个api.js文件和一个reques.js。

http.js文件用来封装我们的axios basUrl Tiemout

api.js用来统一管理我们的接口url,

request.js中添加请求拦截和响应拦截。在请求拦截中会给请求头添加token字段还有loading动画的开启。在响应拦截中可以做一些loading动画的关闭还有可以根据后端返回的状态码做一些检验token是否有效或者过期的操作。

接着就是做一些axios进行的api接口的封装这里我用到了asyncawait封装请求接口函数这样可以将异步操作同步化操作代码更加友好避免回调地域的出现。

vue中如何解决跨域问题

在vue开发中实现跨域在vue项目根目录下找到vue.config.js文件如果没有该文件则自己创建在proxy中设置跨域


devServer: {
    proxy: {  //配置跨域
      '/api': {
        target: 'http://121.121.67.254:8185/',  //这里后台的地址模拟的;应该填写你们真实的后台接口
        changOrigin: true,  //允许跨域
        pathRewrite: {
          /* 重写路径当我们在浏览器中看到请求的地址为http://localhost:8080/api/core/getData/userInfo 时
            实际上访问的地址是http://121.121.67.254:8185/core/getData/userInfo,因为重写了 /api
           */
          '^/api': '' 
        }
      },
    }
  },

assets和static的区别

assets中的文件会经过webpack打包重新编译推荐在assets存放js等需要打包编译的文件。

static中的文件不会打包编译。static中的文件只是复制一遍。static中建议放一些外部第三方文件自己的放assets里别人的放static中。图片推荐放在static里

Vue data 中某一个属性的值发生改变后视图会立即同步执行重新渲染吗

不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化 Vue 将开启一个队列并缓冲在同一事件循环中发生的所有数据变更。

如果同一个watcher被多次触发只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后在下一个的事件循环tick中Vue 刷新队列并执行实际已去重的工作。

多环境变量

首先是通过在根目录下创建.env.*配置文件文件development 本地开发环境配置、staging 测试环境配置、production 正式环境配置生产环境。因为我在创建的文件中并没有定义很多变量只定义了基础的env所以需要在src目录下创建一个config文件夹创建对应的环境变量文件用来管理不同的环境。在config中创建对应的文件是为了后期修改起来方便不需要重启项目符合开发习惯。之后就是根据需要的环境在封装的axios中通过解构赋值的方式导入放在baseURL中就可以使用。

element-ui和vant-ui按需引入

首先安装按需引入的插件在babel.config.js中添加按需引入的配置创建一个plugin文件夹定义一个js文件用来存放按需引入的代码之后在建好的js文件中首先导入vue再导入需要的vant-ui插件通过vue.use()全局注入。修改样式可以用样式穿透 /deep/

Vue 解决了什么问题

① 虚拟 domdom 操作时非常耗性能的不再使用原生的 dom 操作节点极大的解放 dom 操作但具体操作的还是 dom不过是换了一种方式。提供了很多指令当然需要 对 dom 进行底层操作的时候就用到自定义指令

② 视图、数据、结构分离使数据的更改更为简单只需要操作数据就能完成相关操作。

③ 组件化把一个单页应用中的各种模块拆分到一个一个单独的组件中便于开发以及后期的维护

Vue.js 的特点

简洁页面由 HTML 模板+Json 数据+Vue 实例组成 数据驱动自动计算属性和追踪依赖的模板表达式

组件化用可复用、解耦的组件来构造页面 轻量代码量小不依赖其他库 快速精确有效批量 DOM 更新 模板友好可通过 npmbower 等多种方式安装很容易融入 Vue 的核心库只关注视图层并且非常容易学习

请说出 vue.cli 项目中 src 目录每个文件夹和文件的用法

assets 文件夹是放静态资源

components 是放组件

router 是定义路由相关的配置;

view 视图

app.vue 是一个应用主组件

main.js 是入口文件

描述下 vue 从初始化页面–>修改数据–>刷新页面 UI 过程

当 Vue 进入初始化阶段时一方面 Vue 会遍历 data 中的属性并用 Object.defineProperty 将它转化成 getter/setterd 的形式实现数据劫持

另一方面Vue 的指令编译器 Compiler 对元素节点的各个指令进行解析初始化视图并订阅 Watcher 来更新视图此时 Watcher 会将自己添加到消息订阅器 Dep 中此时初始化完毕。
当数据发生变化时触发 Observer 中 setter 方法立即调用 Dep.notify( )Dep 这个数组开始遍历所有的订阅者并调用其 update 方法Vue 内部再通过 diff 算法patch 相应的更新完成对订阅者视图的改变。

Vue 怎么兼容 IE

使用 babel-polyfill 插件,和前缀名 hack

Vue 怎么重置 data

使用 Object.assign()vm.data 可 以 获 取 当 前 状 态 下 的 data

Object.assign(this.$data, this.$options.data())

vue-router 登陆权限的判断

vue-router的登陆权限判断主要是在全局钩子函数中进行的我们在router.js文件中的定义路由里将需要登陆权限的页面加上meta属性值是对象的形式然后在该对象中自定义一个属性属性值就是一个Boolean值这时候在main.js文件的全局钩子函数中进行判断如果需要跳转的页面的自定义属性值为true那么将进行判断其是否登录如果没有登录则告诉用户登录如果有登录那么进行页面跳转。

vue-cli 替我们做了哪些工作

vue-cli 是基于 Vue.js 进行快速开发的完整系统也可以理解成是很多 npm 包的集合。

vue-cli 完成的功能

.vue 文件 --> .js 文件 ES6 语法 --> ES5 语法 Sass,Less,Stylus --> CSS 对 jpg,png,font 等静态资源的处理 热更新 定义环境变量区分 dev 和 production 模式 如果开发者需要补充或修改默认设置需要在 package.json 同级下新建一个 vue.config.js 文件

Vue 如何检测数组变化

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截而是选择对 7 种数组push,shift,pop,splice,unshift,sort,reverse方法进行重写(AOP 切片思想)

所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新

vue 中使用了哪些设计模式

1.工厂模式 - 传入参数即可创建实例 虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode

2.单例模式 - 整个程序有且仅有一个实例 vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉

3.发布-订阅模式 (vue 事件机制)

4.观察者模式 (响应式数据原理)

5.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略

如何解决vue首屏加载过慢

① 把不常改变的库放到 index.html 中通过 cdn 引入

然后找到 build/webpack.base.conf.js 文件在 module.exports = { } 中添加以下代码

externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
}

②vue 路由懒加载图片懒加载使用异步组件按需加载

③ 不生成 map 文件找到 config/index.js 文件修改为 productionSourcceMapfalse

④vue 组件尽量不要全局引入

⑤ 使用更轻量级的工具库

⑥ 开启 gzip 压缩这个优化是两方面的前端将文件打包成.gz 文件然后通过 nginx 的配置让浏览器直接解析.gz 文件。

⑦ 首页单独做服务端渲染如果首页真的有瓶颈可以考虑用 node 单独做服务端渲染而下面的子页面仍用 spa 单页的方式交互。这里不推荐直接用 nuxt.js 服务端渲染方案因为这样一来增加了学习成本二来服务端的维护成本也会上升有时在本机测试没问题在服务端跑就有问题为了省心还是最大限度的使用静态页面较好。

Vue和JQuery的区别在哪?为什么放弃JQuery用Vue?

jQuery是直接操作DOMVue不直接操作DOMVue的数据与视图是分开的Vue只需要操作数据就行它是个框架

jQuery的操作DOM行为是频繁的而Vue利用虚拟DOM的技术大大提高了更新DOM时的性能它是个库

Vue中不倡导直接操作DOM开发者只需要把大部分精力放在数据层面上

Vue集成了一些库大大提高开发效率例如Route、Vuex等等

你都做过哪些Vue的性能优化

尽量减少data中的数据data中的数据都会增加getter和setter会收集对应的watcher v-if和v-for不能连用 v-if 和 v-show 区分使用场景 v-for 遍历必须加 keykey 最好是 id 值且避免同时使用 v-if 如果需要使用v-for给每项元素绑定事件时使用事件代理

SPA 页面采用keep-alive缓存组件 在更多的情况下使用v-if替代v-show 使用路由懒加载、异步组件

防抖、节流 第三方模块按需导入

长列表滚动到可视区域动态加载,不需要响应式的数据不要放到 data 中可以Object.freeze() 冻结数据

图片懒加载

SEO优化 预渲染

服务端渲染SSR 打包优化,

压缩代码 Tree Shaking/Scope Hoisting

使用cdn加载第三方模块 多线程打包happypack splitChunks抽离公共文件 sourceMap优化 骨架屏

PWA 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。防止内部泄漏组件销毁后把全局变量和事件销毁

js的大山

使用面向对象编程时,new关键字做了什么

  1. 新建了一个Object对象

  2. 修改构造函数this的指向是其指向新建的Object对象并且执行构造函数

  3. 为Object对象添加了一个proto属性是其指向构造函数的prototype属性

  4. 将这个Object对象返回出去

es6和es5的继承继承不用搞那么麻烦项目中还是用 class

原型链继承

父类的实例作为子类的原型,易于实现父类的新增实例与属性子类都能访问创建子类实例不能向父类构造函数中传参数。

     原型链继承
     实现
         父类的实例作为子类的原型
         可以在子类中增加实例属性如果要新增加原型属性和方法需要在new 父类构造函数的后面
     优点
         简单易实现 父类的新增实例与属性子类都能访问
     缺点
     	 无法实现多继承  创建子类实例时不能向父类构造函数中传参数

    function Person() {
        this.a = true
    }
    Person.prototype.fn = function () {
        console.log('我是父类')
    }

    function Son() {
        this.b = false
    }
     这里是关键 创建 Person 的实例 然后赋值给 Son 的原型
    Son.prototype = new Person()
    let xiaoming = new Son()
    xiaoming.fn()
    console.log(xiaoming.a)

构造函数继承伪造对象、经典继承

不能继承原型属性/方法,可以实现多继承可以传参无法复用

    构造函数继承
    实现
       在子类内,使用call()调用父类方法,并将父类的this修改为子类
       的this.相当于是把父类的实例属性复制了一份放到子类的函数内.
    优点
	   解决了子类构造函数向父类构造函数中传递参数
       可以实现多继承call或者apply多个父类
    缺点
	   方法都在构造函数中定义无法复用
	   不能继承原型属性/方法只能继承父类的实例属性和方法

    function Father(age, name) {
        this.a = '22'
    }
    function Children() {
        Father.call(this)
    }
    let Class = new Children()
    console.log(Class.a);

组合继承

通过call 对实例属性的继承原型链对原型方法的继承 调用多次

   组合继承
    实现
       组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承
       借用构造函数技术来实现实例属性的继承。
    缺点
	   由于调用了两次父类所以产生了两份实例
    优点
	   函数可以复用
	   不存在引用属性问题
	   可以继承属性和方法并且可以继承原型的属性和方法
    

    function Father(name) { // 这里的name 就是 Son 传过来的
        this.name = name
        this.colors = [1, 2]
    }

    Father.prototype.sayName = function () {
        alert(this.name)
    }

    function Son(name, age) {
        //继承实例属性第一次调用Father()
        Father.call(this, name) // 这里通过 call 传过去 name  继承实例属性
        this.age = age
    }
    Son.prototype = new Father() // 继承父类方法第二次调用 Father
    Son.prototype.ff = function () { // 子类的方法
        console.log('666');
    }
    var aa = new Son('小明', 5)
    aa.colors.push(3)
    console.log(aa); // 打印了 父类的属性 和方法   以及子类的方法
    var bb = new Son('小红', 50)
    aa.colors.push(999999)
    console.log(bb); // 打印了 父类的属性 和方法   以及子类的方法

Es6有class继承:

首先利用class构造一个父类然后利用extends与super实现子类继承

ES6类继承extends
extends关键字主要用于类声明或者类表达式中以创建一个类该类是另一个类的子类。其中constructor表示构造函数一个类中只能有一个构造函数有多个会报出SyntaxError错误,如果没有显式指定构造方法则会添加默认的 constructor方法使用例子如下。
class Rectangle {
    // constructor
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
    
    // Getter
    get area() {
        return this.calcArea()
    }
    
    // Method
    calcArea() {
        return this.height * this.width;
    }
}

const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200

-----------------------------------------------------------------
// 继承
class Square extends Rectangle {

  constructor(length) {
    super(length, length);
    
    // 如果子类中存在构造函数则需要在使用“this”之前首先调用 super()。
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }
}

const square = new Square(10);
console.log(square.area);
// 输出 100

ES5继承和ES6继承的区别

es5继承首先是在子类中创建自己的this指向最后将方法添加到this中

Child.prototype=new Parent() || Parent.apply(this) || Parent.call(this)

es6继承是使用class关键字先创建父类的实例对象this最后在子类class中修改this

javascript原型与原型链

  1. 每个函数都有一个prototype属性被称为显示原型

  2. 每个实例对象都会有_ _proto_ _属性,其被称为隐式原型

  3. 每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype

  4. 每个prototype原型都有一个constructor属性指向它关联的构造函数。

原型链

获取对象属性时如果对象本身没有这个属性那就会去他的原型__proto__上去找如果还查不到就去找原型的原型一直找到最 顶层(Object.prototype)为止。Object.prototype对象也有proto属性值为null。链式查找机制叫原型链。

javascript 创建对象的几种方式

1、我们一般使用字面量的形式直接创建对象

1第一种是工厂模式工厂模式的主要工作原理是用函数来封装创建对象的细节从而通过调用函数来达到复用的目的。

2第二种是构造函数模式。js 中每一个函数都可以作为构造函数只要一个函数是通过 new 来调用的那么我们就可以把它称为构造函数。

3第三种模式是原型模式因为每一个函数都有一个 prototype 属性这个属性是一个对象它包含了通过构造函数创建的所有实例都能共享的属性和方法。

4第四种模式是组合使用构造函数模式和原型模式这是创建自定义类型的最常见方式。

5第五种模式是动态原型模式这一种模式将原型方法赋值的创建过程移动到了构造函数的内部通过对属性是否存在的判断可以实现仅在第一次调用函数时对原型对象赋值一次的效果。

6第六种模式是寄生构造函数模式这一种模式和工厂模式的实现基本相同

什么是设计模式

概念

设计模式是一套被反复使用的代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式让代码变得工程化设计模式是软件工程的基石。

1、js工厂模式,去做同样的事情实现同样的效果,解决多个相似的问题这时候需要使用工厂模式

2、发布订阅模式,通过Object.defineProperty()来劫持各个属性的settergetter在数据变动时发布消息给订阅者触发相应的监听回调。

3、单例模式 单例模式 保证一个类仅有一个实例并提供一个访问它的全局访问点

constructorprotoprototype的三角关系。

构造函数的prototype指向原型对象

实例对象的proto指向构造函数的prototype所指向原型对象

原型对象的constructor指向构造函数

面向过程面向对象面向过程和面向对象的优缺点

一、面向过程面向过程就是分析出实现需求所需要的步骤通过函数一步一步实现这些步骤接着依次调用即可。

二、面向对象将数据与函数绑定到一起进行封装这样能够更快速的开发程序减少了重复代码的重写过程面向过程

优点性能上它是优于面向对象的因为类在调用的时候需要实例化开销过大。

缺点不易维护、复用、扩展

用途单片机、嵌入式开发、Linux/Unix等对性能要求较高的地方

面向对象:

面向对象有三大特性封装继承多态。

优点易维护、易复用、易扩展由于面向对象有封装、继承、多态性的特性可以设计出低耦合的系统使系统更加灵活、更加易于维护 。

缺点性能比面向过程低

扩展面试题

spa

spa 就是我们的单页面应用spa 应用就是只有一个html页面在vue中可以通过vue-router 来进行页面的切换的而非刷新整个页面可以实现无刷新切换页面的技术

SPA的原理?
通过这一点可以用js监听url中hash值的变化 onhashchange 事件在当前 URL 的锚部分(以 '#' 号为开始) 发生改变时触发 。哈希值的变换并不会引发页面的刷新和跳转,当监听到hash变化,就可以动态的切换组件,就可以实现无刷新切换页面技术

spa 的优点
页面切换快
页面每次切换跳转时并不需要做`html`文件的请求这样就节约了很多`http`发送时延我们在切换页面的时候速度很快。
用户体验好
页面片段间的切换快在网络环境差的时候, 因为组件已经预先加载好了, 并不需要发送网络请求, 所以用户体验好
转场动画

spa 的缺点
首屏加载比较慢因为要请求一次html同时还要发送一次js请求两次请求回来了首屏才会显示
不利于SEO 
seo 效果较差 因为搜索引擎只识别html里面的内容并不识别js里的内容因为单页面就是js渲染出来的影响网站的排名

mpa

MPA多页面应用程序 指的就是有多个独立的html页面每个页面必须重复加载html js css 资源多页面跳转需要整个页面资源刷新。

优点
1、首屏加载速度快
当我们访问页面的时候服务器只返回了一个html页面就展示出来了只发了一次http请求所以页面显示非常快.
2、SEO效果好
因为搜索引擎在做网站排名的时候要根据网页的内容给网页排名搜素引擎只可以识别html内容多页面就是将内容放在html中所以排名要好一点。

缺点
因为每跳转一个页面都要发送一次http请求如果网络情况不好的情况下页面之间来回跳转就会发生明显的卡顿有的时候半天还加载不出来影响用户体验。

转场动画也不好实现

1、 Vue与Angular以及React的区别

▍Angular

框架比较成熟完整过于庞大上手难

指令以ng-xxx开头 由谷歌开发和维护

版本1比较适合PC端开发版本2在往移动端靠

不支持低版本浏览器 内置指令和自定义指令

内置过滤器和自定义过滤器 支持双向数据绑定

▍Vue

它是一个轻量级框架其核心库只关注视图层简单小巧、易学易上手

指令以v-xxx开头 个人维护项目 适合于移动端开发 不支持低版本浏览器

内置指令和自定义指令 内置过滤器和自定义过滤器 支持双向数据绑定

使用DOM模板。中心思想一切都是组件组件实例之间可以嵌套 核心库不内置列数AJAXRoute等功能到核心包而是以插件的方式加载 基于依赖追踪的观察系统并且异步队列更新。

▍React

依赖虚拟DOM 采用特殊的JSX语法 中心思想一切都是组件组件实例之间可以嵌套 核心库不内置列数AJAXRoute等功能到核心包而是以插件的方式加载。

2、 请描述一下你对webpack的理解

Webpack Webpack 是一个项目打包工具

可以压缩代码和图片把浏览器识别不了的代码转化为能识别的可以启动一个热加载服务器

配置跨域、路径别名、打包分析、cdn映入、去掉console.log、单独打包第三方模块、ie兼容、eslint规范、图片压缩

3、vue2对比vue3

最大的区别就是 Vue2使用 选项类型APIOptions API 对比Vue3 合成型APIComposition API

  1. 双向数据绑定原理发生了改变使用proxy替换Object.defineProerty,使用Proxy的优势

  • 可直接监听数组类型的数据变

  • 监听的目标为对象本身不需要像Object.defineProperty一样遍历每个属性有一定的性能提升

  • 可直接实现对象属性的新增/删除

  1. 默认使用懒加载

在2.x版本里。不管数据多大都会在一开始就为其创建观察者在数据很大时就会造成性能的问题。在3.x中只会对渲染出来的数据创建观察者而且3.x的观察者更高效。

3.0新加入了TypeScript以及PWA支持

生命周期有了一定的区别

Vue2--------------vue3

beforeCreate -> setup() 开始创建组件之前创建的是data和method

created -> setup()

beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。

mounted -> onMounted 组件挂载完成后执行的函数

beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。

updated -> onUpdated 组件更新完成之后执行的函数。

beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。

destroyed -> onUnmounted 组件卸载之前执行的函数。

activated -> onActivated 组件卸载完成后执行的函数

deactivated -> onDeactivated

4、git命令

1. git init 初始化git仓库 (mac中Command+Shift+. 可以显示隐藏文件)

2. git status 查看文件状态

3. git add 文件列表 追踪文件

4. git commit -m 提交信息 向仓库中提交代码

5. git log 查看提交记录

1.分支明细

1主分支master第一次向 git 仓库中提交更新记录时自动产生的一个分支。

2开发分支develop作为开发的分支基于 master 分支创建。

3功能分支feature作为开发具体功能的分支基于开发分支创建

2.分支命令

1git branch 查看分支

2git branch 分支名称 创建分支

3git checkout 分支名称 切换分支

4git merge 来源分支 合并分支 (备注必须在master分支上才能合并develop分支)

5git branch -d 分支名称 删除分支分支被合并后才允许删除-D 强制删除

3.暂时保存更改

1存储临时改动git stash

2恢复改动git stash pop

更加详细请看git常用命令

git怎么解决多人冲突

是当前修改是左箭头方向传入的是右箭头的方向

中间用等于号分割等号上边是当前修改本地下边是传入的修改线上的代码。

两人同时提交可能会出现冲突解决办法是手动修改冲突

5、前端有哪些页面优化方法?

- 减少 HTTP请求数
- 从设计实现层面简化页面
- 合理设置 HTTP缓存
- 资源合并与压缩
- 合并 CSS图片减少请求数的又一个好办法。
- 将外部脚本置底将脚本内容在页面信息内容加载后再加载
- 多图片网页使用图片懒加载。
- 在js中尽量减少闭包的使用
- 尽量合并css和js文件
- 尽量使用字体图标或者SVG图标来代替传统的PNG等格式的图片
- 减少对DOM的操作
- 在JS中避免“嵌套循环”和 “死循环”
- 尽可能使用事件委托事件代理来处理事件绑定的操作
- 浏览器缓存
- 防抖、节流
- 资源懒加载、预加载
- 开启Nginx gzip压缩
三个方面来说明前端性能优化
一 webapck优化与开启gzip压缩
    1.babel-loader用 include 或 exclude 来帮我们避免不必要的转译不转译node_moudules中的js文件
    其次在缓存当前转译的js文件设置loader: 'babel-loader?cacheDirectory=true'
    2.文件采用按需加载等等
    3.具体的做法非常简单只需要你在你的 request headers 中加上这么一句
    accept-encoding:gzip
    4.图片优化采用svg图片或者字体图标
    5.浏览器缓存机制它又分为强缓存和协商缓存
二本地存储——从 Cookie 到 Web Storage、IndexedDB
    说明一下SessionStorage和localStorage还有cookie的区别和优缺点
三代码优化
    1.事件代理
    2.事件的节流和防抖
    3.页面的回流和重绘
    4.EventLoop事件循环机制
    5.代码优化等等

node网络

1、什么是axios

基于promise的http库可以用在浏览器和node.js支持promiseAPI客户端支持防御xsrf

2、Node是什么别看这么简单有的人一问就懵

Node是一个基于Chrome V8引擎的JavaScript代码运行环境。

浏览器软件能够运行JavaScript代码浏览器就是JavaScript代码的运行环境

Node软件能够运行JavaScript代码Node就是JavaScript代码的运行环境

3、模块化的意义

一句话降低软件的复杂性。使其可控可维护可扩展。

一个功能就是一个模板多个模板可以组成完整应用抽离一个模板不会影响其他功能的运行

4、网站的组成

网站应用程序主要分为两大部分客户端和服务器端。客户端在浏览器中运行的部分就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。服务器端在服务器中运行的部分负责存储数据和处理应用逻辑。

5、为什么要用node

简单强大轻量可扩展。

简单体现在node使用的是javascript,json来进行编码强大体现在非阻塞IO,可以适应分块传输数据较慢的网络环境尤其擅长高并发访问轻量体现在node本身既是代码又是服务器前后端使用统一语言;可扩展体现在可以轻松应对多实例多服务器架构同时有海量的第三方应用组件。

6、node中的异步和同步怎么理解?

node是单线程的异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的IO,这在高并发环境会是一个很大的性能问题所以同步一般只在基础框架的启动时使用用来加载配置文件初始化程序什么的.

7、什么是npmNpm的使用场景

NPM是随同NodeJS一起安装的包管理工具能解决NodeJS代码部署上的很多问题。

使用场景

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

b. 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。

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

8、get与post请求有什么区别

  1. get是从服务器上获取数据post是向服务器传送数据。

  2. POST比GET安全因为数据在地址栏上不可见。

  3. get方式提交的数据最多只能有1024字节而post则没有此限制。

  4. GET使用URL或Cookie传参。而POST将数据放在request BODY中。

  5. GET与POST都有自己的语义不能随便混用。

  6. 据研究在网络环境好的情况下发一次包的时间和发两次包的时间差别基本可以无视。而在网 络环境差的情况下两次包的TCP在验证数据包完整 性上有非常大的优点。post 发送两次get 只发送一次。

  7. 并不是所有浏览器都会在POST中发送两次包Firefox就只发送一次。

ajax

什么是ajaxajax有什么优缺点

ajax不是语言ajax是一种在无需重新加载整个网页的情况下能够更新部分网页的技术

优点

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

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

缺点

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

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

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

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

5、不容易调试

原生Ajax的创建过程

1.创建xhr 核心对象
var xhr=new XMLHttpRequest();
​
2.调用open 准备发送
参数一请求方式
参数二: 请求地址
参数三true异步false 同步
xhr.open('post','http://www.baidu.com/api/search',true)
​
3.如果是post请求必须设置请求头。
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
​
4.调用send 发送请求 如果不需要参数就写null
xhr.send('user=tom&age=10&sex=女')
​
5.监听异步回调 onreadystatechange
判断readyState 为4 表示请求完成
判断status 状态码 为 200 表示接口请求成功
responeseText 为相应数据。字符串类型。
xhr.onreadystatechange=function(){
    if(xhr.readyState==4){ 
        if(xhr.status==200){
            console.log(xhr.responseText);
            var res=JSON.parse(xhr.responseText);
            console.log(res);
            if(res.code==1){
            modal.modal('hide');
           location.reload();
       }
    }
            
            
备注如果是post请求想要传json格式数据。
设置请求头
​
1.xhr.setRequestHeader('Content-Type', 'application/json')
​
open发送数据
2.xhr.open({_id:xxx,user:xxxx,age:xxxx})

web安全及防护

1.XSS攻击原理

XSS(Cross-Site Scripting跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码当被攻击者登陆网站时就会执行这些恶意代码这些脚本可以读取 cookiesession tokens或者其它敏感的网站信息对用户进行钓鱼欺诈甚至发起蠕虫攻击等。

XSS避免方式

  1. url参数使用encodeURIComponent方法转义

  2. 尽量不是有InnerHtml插入HTML内容

  3. 使用特殊符号、标签转义符。

2.CSRF攻击跨站请求伪造

CSRFCross-site request forgery跨站请求伪造攻击者诱导受害者进入第三方网站在第三方网站中向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证绕过后台的用户验证达到冒充用户对被攻击的网站执行某项操作的目的。

CSRF避免方式

  1. 添加验证码

  2. 使用token

    • 服务端给用户生成一个token加密后传递给用户

    • 用户在提交请求时需要携带这个token

    • 服务端验证token是否正确

3.SQL注入攻击

就是通过吧SQL命令插入到Web表单递交或输入域名最终达到欺骗服务器执行恶意的SQL命令。

解决表单输入时通过正则表达式将一些特殊字符进行转换

4、DDoS攻击

DDoS又叫分布式拒绝服务全称 Distributed Denial of Service其原理就是利用大量的请求造成资源过载导致服务不可用。

解决

  1. 限制单IP请求频率。

  2. 防火墙等防护设置禁止ICMP包等

  3. 检查特权端口的开放

使用基于token的登录流程

1. 客户端使用用户名跟密码请求登录

2. 服务端收到请求去验证用户名与密码

3. 验证成功后服务端会签发一个 Token再把这个 Token 发送给客户端

4. 客户端收到 Token 以后可以把它存储起来比如放在 Cookie 里或者 Local Storage 里

5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token

6. 服务端收到请求然后去验证客户端请求里面带着的 Token如果验证成功就向客户端返回请求的数据

状态码

常见http状态码分类

    200响应成功
    301永久重定向
    302临时重定向
    304资源缓存
    403服务器禁止访问
    404服务器资源未找到
    500 502服务器内部错误
    504 服务器繁忙
    1xx Informational信息状态码      接受请求正在处理
    2xx Success成功状态码            请求正常处理完毕
    3xx Redirection重定向状态码      需要附加操作已完成请求
    4xx Client Error客户端错误状态码  服务器无法处理请求
    5xx Server Error服务器错误状态码  服务器处理请求出错

浏览器从输入url到渲染页面发生了什么

这玩意一定要说全了装逼

用户输入阶段
合成 URL浏览区会判断用户输入是合法 URL比如用户输入的是搜索的关键词默认的搜索引擎会合成新的如果符合url规则会根据url协议在这段内容加上协议合成合法的url 
   
查找缓存
网络进程获取到 URL先去本地缓存中查找是否有缓存资源如果有则拦截请求直接将缓存资源返回给浏览器进程否则进入网络请请求阶段    
    
DNS 解析
DNS 查找数据缓存服务中是否缓存过当前域名信息有则直接返回否则会进行 DNS 解析返回域名对应的 IP 和端口号如果没有指定端口号http 默认 80 端口https 默认 443。如果是 https 请求还需要建立 TLS 连接
    
建立 TCP 连接
TCP 三次握手与服务器建立连接然后进行数据的传输三次握手开喷

发送 HTTP 请求
浏览器首先会向服务器发送请求行它包含了请求方法、请求 URI 和 HTTP 协议的版本另外还会发送请求头告诉服务器一些浏览器的相关信息比如浏览器内核请求域名

服务器处理请求
服务器首先返回响应行包括协议版本和状态码比如状态码 200 表示继续处理该请求如果是 301则表示重定向服务器也会向浏览器发送响应头包含了一些信息

页面渲染:

查看响应头的信息做不同的处理比如重定向存储cookie 看看content-type的值根据不同的资源类型来用不同的解析方式


浏览器渲染原理直接开干.....

浏览器将获取的HTML文档解析成DOM树。
处理CSS标记构成层叠样式表模型CSSOM(CSS Object Model)。
将DOM和CSSOM合并为渲染树(rendering tree)代表一系列将被渲染的对象。
渲染树的每个元素包含的内容都是计算过的它被称之为布局layout。浏览器使用一种流式处理的方法只需要一次绘制操作就可以布局所有的元素。
将渲染树的各个节点绘制到屏幕上这一步被称为绘制painting。

断开 TCP 连接

数据传输完成正常情况下 TCP 将四次挥手断开连接。但是如果浏览器或者服务器在HTTP头部加上 Connection: keep-aliveTCP 就会一直保持连接。

网络安全、HTTP协议

TCP UDP 区别

1.`TCP`向上层提供面向连接的可靠服务 `UDP`向上层提供无连接不可靠服务。
2.虽然 `UDP` 并没有 `TCP` 传输来的准确但是也能在很多实时性要求高的地方有所作为
3.对数据准确性要求高速度可以相对较慢的可以选用`TCP`

区别UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输不使用流量控制和拥塞控制可靠传输使用流量控制和拥塞控制
连接对象个数支持一对一一对多多对一和多对多交互通信只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小仅8字节首部最小20字节最大60字节
适用场景适用于实时应用IP电话、视频会议、直播等适用于要求可靠传输的应用例如文件传输

Http和Https区别高频

1.`HTTP` 的URL 以http:// 开头而HTTPS 的URL 以https:// 开头
2.`HTTP` 是不安全的而 HTTPS 是安全的
3.`HTTP` 标准端口是80 而 HTTPS 的标准端口是443
4.`在OSI` 网络模型中HTTP工作于应用层而HTTPS 的安全传输机制工作在传输层
5.`HTTP` 无法加密而HTTPS 对传输的数据进行加密证的网络协议安全性高于HTTP协议。
6.`HTTP`无需证书而HTTPS 需要CA机构wosign的颁发的SSL证书一般免费证书少因而需要一定费用。

GET和POST区别高频

1.GET在浏览器回退不会再次请求POST会再次提交请求
2.GET请求会被浏览器主动缓存POST不会要手动设置
3.GET请求参数会被完整保留在浏览器历史记录里POST中的参数不会
4.GET请求在URL中传送的参数是有长度限制的而POST没有限制
5.GET参数通过URL传递POST放在Request body中
6.GET参数暴露在地址栏不安全POST放在报文内部更安全
7.GET一般用于查询信息POST一般用于提交某种信息进行某些修改操作
8.GET产生一个TCP数据包POST产生两个TCP数据包
Ge和post的选择
1.私密性的信息请求使用post如注册、登陆。
2.查询信息使用get。

三次握手和四次挥手

三次握手

第一次建立连接时客户端发送syn包到服务器等待服务端确认

第二次服务器收到syn包必须确认客户的syn同时也发送一个syn包即syn+ACK包

第三次客户端收到服务器的syn和ack包向服务器发送确认包ack发送完毕客户端和服务端连接成功完成三次握手

四次挥手

第一次浏览器发送完数据后发送fin请求断开连接

第二次服务器发送ack到客户端确认客户端的断开请求

第三次服务器请求断开fin的请求

第四次客户端确认服务器的断开ack

POST的content-type几种方式

POST 方法中对发送数据编码的方式也就是 Content-Type 有四种方式其中默认是 application/x-www-form-urlencoded最方便的是 application/json 。

四种方式包括

  • application/x-www-form-urlencoded URL encoded
  • multipart/form-data 键值对型数据
  • application/json (Json 类型数据)
  • text/xml xml

传统的ajax请求时候Content-Type默认为"文本"类型。

传统的form提交的时候Content-Type默认为"Form"类型。

axios传递字符串的时候Content-Type默认为"Form"类型。

axios传递对象的时候Content-Type默认为"JSON"类型

http1.0、http1.1、http2.0的区别

  1. 1和1.0相比1.1可以一次传输多个文件

  2. http1.x解析基于文本

  3. http2.0采用二进制格式新增特性 多路复用、header压缩、服务端推送(静态html资源)

浏览器缓存的作用

浏览器缓存的作用减少冗余的数据传输节省网络带宽更快加载页面缓存降低了服务器的要求有更快的响应

http如何实现缓存

个人理解

强制缓存浏览器在加载资源的时候会根据本地缓存中的headers中的信息(expires,cache-control)是否要强缓存如果命中的话则会使用缓存中的资源否则继续发送请求。

协商缓存客户端向服务端发送请求服务端检测是否有对应的标识如果没有服务端会返回客户端对应的标识客户端在下次请求把标识带过去服务器会验证标识如果通过了则会响应304告诉浏览器读取缓存如果没有通过则返回请求的资源。

两类缓存规则可以同时存在强制缓存优先级高于对比缓存也就是说当执行强制缓存的规则时如果缓存生效直接使用缓存不再执行对比缓存规则。

基于对比缓存不管是否使用缓存都需要向服务器发送请求那么还用缓存干什么
服务端在进行标识比较后只返回header部分通过状态码通知客户端使用缓存不再需要将报文主体部分返回给客户端。

缓存的资源去哪里了

memory cache 将资源文件缓存到内存中下次请求读取的是内存中的
disk cache 将资源存到硬盘中下次请求从硬盘中读取

http报文

HTTP报文就是浏览器和服务器间通信时发送及响应的数据块。
浏览器向服务器请求数据发送请求(request)报文
服务器向浏览器返回数据返回响应(response)报文。
报文信息主要分为两部分header,数据主体部分(body)

能不能说一说浏览器的本地存储各自优劣如何

浏览器的本地存储主要分为Cookie、WebStorage和IndexDB, 其中WebStorage又可以分为localStorage和sessionStorage

共同点: 都是保存在浏览器端、且同源的

不同点

  1. cookie数据始终在同源的http请求中携带即使不需要即cookie在浏览器和服务器间来回传递。cookie数据还有路径path的概念可以限制cookie只属于某个路径下sessionStoragelocalStorage不会自动把数据发送给服务器仅在本地保存。

  2. 存储大小限制也不同

  • cookie数据不能超过4KsessionStorage和localStorage可以达到5M

  • sessionStorage仅在当前浏览器窗口关闭之前有效

  • localStorage始终有效窗口或浏览器关闭也一直保存本地存储因此用作持久数据

  • cookie只在设置的cookie过期时间之前有效即使窗口关闭或浏览器关闭

  1. 作用域不同

  • sessionStorage不在不同的浏览器窗口中共享即使是同一个页面

  • localstorage在所有同源窗口中都是共享的也就是说只要浏览器不关闭数据仍然存在

  • cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭数据仍然存在

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“2022年最新前端面试题(大前端时代来临卷起来吧小伙子们..持续维护走到哪记到哪)_前端面试” 的相关文章