stackoverflow-国外的问答网站
JS
1.null 和 undefined 的区别?
相同: 在 if 语句中 null 和 undefined 都会转为false,两者用相等运算符比较也是相等。Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
不同: undefined 代表的含义是未定义,定义了形参,没有传实参,显示为undefined。一般变量声明了但还没有定义的时候会返回 undefined。对象属性名不存在时,显示undefined。函数没有写返回值,即没有写return,拿到的是undefined。null 代表的含义是空对象。也作为对象原型链的终点。null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
2.数据类型存储以及堆栈内存是什么
基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。包含number string 布尔 null undefined,Symbol、BigInt。
引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。类型将指针存在栈中,将值存在堆中。 当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,包含数组和对象。
3.什么是原型/原型链?-原型对象
原型的本质就是一个对象。
当我们在创建一个构造函数之后,这个函数会默认带上一个prototype属性,而这个属性的值就指向这个函数的原型对象。这个原型对象是用来为通过该构造函数创建的实例对象提供共享属性,用来实现基于原型的继承和属性的共享。
我们通过构造函数创建的实例对象都会从这个函数的原型对象上继承上面具有的属性。当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止(最顶层就是Object.prototype的原型,值为null)。通过原型一层层相互关联的链状结构就称为原型链。
4.什么是闭包?
闭包是指有权访问另外一个函数作用域中的变量的函数,一般是在嵌套函数中实现的。形成的原因是,内部的函数存在外部作用域的引用就会导致闭包。闭包中的变量存储的位置是堆内存。闭包的作用,1.保护函数的私有变量不受外部的干扰。形成不销毁的栈内存。2.把一些函数内的值保存下来。闭包可以实现方法和属性的私有化。使用闭包需要注意容易导致内存泄漏。闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。
5.this的指向
函数的调用方式决定了 this 的值(运行时绑定)
1、全局的this非严格模式指向window对象,严格模式指向 undefined。
2、对象的属性方法中的this 指向对象本身。
3、apply、call、bind 可以变更 this 指向为第一个传入的参数。
4、箭头函数中的this指向它的父级作用域,它自身不存在 this。
6.new的原理-new关键字
new实际上是在堆内存中开辟一个空间。
- 首先创建一个空对象,构造函数中的this指向这个空对象;
- 这个新对象被执行原型连接;
- 执行构造函数方法,属性和方法被添加到this引用的对象中;
- 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
7.call、apply、bind封装与区别
call()、apply()和bind()方法 三者作用都是改变this指向
- Function.prototype.call()函数实例的
call方法,可以指定该函数内部this的指向,然后在所指定的作用域中,调用该函数。并且会立即执行该函数。call()方法可以传递两个参数。第一个参数是指定函数内部中this的指向,第二个参数是函数调用时需要传递的参数。 - Function.prototype.apply()
apply方法的作用与call方法类似,也是改变this指向,然后在指定的作用域中,调用该函数。同时也会立即执行该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数。apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined或者this,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,在调用时传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。 - 4.Function.prototype.bind()
bind方法用于指定函数内部的this指向,然后返回一个新函数。bind方法并非立即执行一个函数。
8.浏览器的事件循环EventLoop
js 代码执行过程中,会创建对应的执行上下文并压入执行上下文栈中。
如果遇到异步任务就会将任务挂起,交给其他线程去处理异步任务,当异步任务处理完后,会将回调结果加入事件队列中。
当执行栈中所有任务执行完毕后,就是主线程处于闲置状态时,才会从事件队列中取出排在首位的事件回调结果,并把这个回调加入执行栈中然后执行其中的代码,如此反复,这个过程就被称为事件循环。
事件队列分为了宏任务队列和微任务队列,在当前执行栈为空时,主线程会先查看微任务队列是否有事件存在,存在则依次执行微任务队列中的事件回调,直到微任务队列为空;不存在再去宏任务队列中处理。
常见的宏任务有setTimeout()、setInterval()、用户交互操作,UI渲染。
常见的微任务有promise.then()、promise.catch()、process.nextTick()。
宏任务和微任务的本质区别
- 宏任务有明确的异步任务需要执行和回调,需要其他异步线程支持
- 微任务没有明确的异步任务需要执行,只有回调,不需要其他异步线程支持。
9.宏任务和微任务
js中的一个机制,就是遇到宏任务,先将宏任务放入eventqueue,然后在执行微任务。
宏任务:setTimeout,setInterval,Ajax,DOM事件
微任务:Promise async/await
因为JS是单线程语言,只能同时做一件事儿。js任务需要排队顺序执行,如果一个任务时间过长,后边的任务也会等着。比如,我们在请求一个网址时,图片加载很慢,网页总不能一直卡着不出来,这个时候就可以用异步来解决了,异步的特点不会阻塞代码的执行,解决了单线程等待的这个问题,在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。
10.讲讲v8垃圾回收机制
垃圾回收浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不再继续使用的变量,然后释放内存。这个过程不是实时的,因为GC开销比较大并且需要停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
内存泄露那些不再使用的变量,它们所占用的内存不去清除的话就会造成内存泄漏,内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
比如说:
1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收。
2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收。
3、Times计时器泄露。
11.函数调用的方法
1、普通function直接使用()调用并传参,如:function test(x, y) { return x + y},test(3, 4)。
2、作为对象的一个属性方法调用,如:const obj = { test: function (val) { return val } }, obj.test(2)。
3、使用call或apply调用,更改函数 this 指向,也就是更改函数的执行上下文。
4、new可以间接调用构造函数生成对象实例。
12.defer和async的区别
一般情况下,当执行到 script 标签时会进行下载 + 执行两步操作,这两步会阻塞 HTML 的解析;
async 和 defer 能将script的下载阶段变成异步执行(和 html解析同步进行);
async下载完成后会立即执行js,此时会阻塞HTML解析;
defer会等全部HTML解析完成后执行。
13.浏览器事件机制-事件冒泡、捕获(委托)
DOM 事件流的三个阶段:
捕获阶段:事件最开始由不太具体的节点最早接受事件, 而最具体的节点(触发节点)最后接受事件。为了让事件到达最终目标之前拦截事件。
目标阶段
当事件到达目标节点,事件就进入了目标阶段。事件在目标节点上被触发(执行事件对应的函数),然后会逆向回流,直到传播至最外层的文档节点。
冒泡阶段
事件在目标元素上触发后,会继续随着 DOM 树一层层往上冒泡,直到到达最外层的根节点。
所有事件都要经历捕获阶段和目标阶段,但有些事件会跳过冒泡阶段,比如元素获得焦点 focus 和失去焦点 blur 不会冒泡。
14.事件委托
事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了。
阻止事件冒泡event.stopPropagation() .stop修饰符
addEventListener(‘click',函数名,true/false) 默认值为false(即 使用事件冒泡)true 事件捕获
好处:提高性能,减少了事件绑定,从而减少内存占用
应用场景,在vue中事件委托:我们经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现。
15.防抖节流
pointer-events:none能处理JS中的禁止鼠标点击事件
防抖:就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
// 监听拿到input输入的值
input.addEventListener('input', function (e) {
val(e.target.value)
})
// 防抖的核心代码
function fn(time, fun) {
let flag // 定义状态
return function (value) {
clearTimeout(flag)// 在执行之前 清除 定时器的 flag 不让他执行
flag = setTimeout(() => {
fun(value)
}, time)
}
}
let val = fn(1000, function (val) {
console.log(val)
})
节流:就是指连续触发事件但是在 n 秒中只执行一次函数。
function throttle(fun, time) {
let flag = 0
return function () {
let now = +new Date().valueOf()
// 当前的值 减去上一次的值 >= 传过来的事件 执行
if (now - flag >= time) {
fun.apply(this, arguments)
flag = now
}
}
}
button.onclick = throttle((e) => {
console.log(e)
}, 1000)
16.作用域和作用域链
1、作用域:就是一个变量可以使用的范围,主要分为全局作用域和函数作用域。
全局作用域就是Js中最外层的作用域。
函数作用域是js通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套.
Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
2、自由变量
当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链。
3、作用域链:
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数,简单来说:内部函数访问外部函数的变量这种链式查找的机制被称为作用域链
17.es6和es5的继承-原型链继承
- 第一种是以
原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数 - 第二种方式是使用
借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。 - 第三种方式是
组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。 - 第四种方式是
原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
18.数据类型判断-typeof-instanceof
1.typeof 对于基本数据类型判断是没有问题的,但是遇到引用数据类型(如:Array)是不起作用
console.log(typeof 2); // number
console.log(typeof null); // object
2.`instanceof` 只能正确判断引用数据类型 而不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
3.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
4.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));
补充:基本数据类型赋值的时候 赋的是具体的值 引用数据类型传的是地址,一个变另一个跟着变
19.引用数据类型 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,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
返回值
一个新对象,带着指定的原型对象和属性
20.什么是深拷贝,浅拷贝,浅拷贝赋值的区别,如何实现
深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。
浅拷贝:将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存。如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址。
深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”。深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象。
赋值:当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象
浅拷贝的实现方式:
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()
21.数组方法
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(之前计算过的下一个的值),index,arr。把数组列表计算成一个
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 扁平化 将嵌套的数组 “拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。// 参数写的就是代表要扁平到第几层
ES6数组的常用方法:
1、Array.from( ):将对象或字符串转成数组,注意得有length。
2、Array.of( ): 将一组值转换为数组。
3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换
target:从该位置开始替换数据;
start:从该位置开始读取数据,默认为0;
end:到该位置停止数据的读取,默认为数组的长度
4、find( ):用于找出第一个符合条件的数组成员。
5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
6、fill(value,start,end):使用给定值,填充一个数组。
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。
22.判断数组的方式有哪些
- 通过Object.prototype.toString.call()做判断
- 通过原型链做判断
- 通过ES6的Array.isArray()做判断
- 通过instanceof做判断
- 通过Array.prototype.isPrototypeOf
VUE
1.vue和react的区别
- React 推崇函数式编程,数据不可变以及单向数据流,只能通过
setState来实现视图更新 - Vue 基于数据可变,设计了响应式数据,通过监听数据的变化自动更新视图
- React 推荐使用 jsx + inline style的形式,就是 all in js。
- Vue 是单文件组件形式,在一个组件内分模块(tmplate/script/style),当然vue也支持jsx形式,可以在开发vue的ui组件库时使用
- Vue2采用双端比较,Vue3采用快速比较
react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。,需要使用shouldComponentUpdate()来手动优化react的渲染。
2.vue2对比vue3-vue3的Proxy 相比于 vue2的defineProperty 的优势
最大的区别就是: Vue2使用选项类型API(Options API) 对比Vue3 合成型API(Composition API)
双向数据绑定原理发生了改变,使用proxy替换Object.defineProperty
- Object.defineProperty 的缺点:是无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决。
- Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历。
- 使用Proxy的优势:可直接监听数组类型的数据变化,监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升,可直接实现对象属性的新增/删除,默认使用懒加载。在2.x版本里。不管数据多大,都会在一开始就为其创建观察者,在数据很大时,就会造成性能的问题。在3.x中,只会对渲染出来的数据创建观察者,而且3.x的观察者更高效。唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。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
3.vue生命周期(computed 和watch)
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 缓存的组件停用时调用。
- 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
执行到它的时候时候是数据发生变化且界面更新完毕;
不能监听到路由数据(例如网址中的参数);
所有的数据发生变化都会调用(消耗性能);
每次触发的代码都是同一个
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中的数据进行赋值操作,这会形成一个死循环。
4.vue组件通信方式
- props / $emit
props传递数据和v-model和.sync传递给子组件,子组件通过props接受,
子组件通过$emit修改父组件的值
- ref / $refs
ref 如果在普通的DOM元素上,引用指向的就是该DOM元素。如果在子组件上,引用的指向就是子组件实例,
然后父组件就可以通过 ref 主动获取子组件的属性或者调用子组件的方法。
const child = this.$refs.child;
- parent的使用
`$children`:获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,
可以直接拿到子组件中所有数据和方法等
`$parent`:获取到一个父节点的 VueComponent 对象,同样包含父节点中所有数据和方法等
- attrs / listeners
多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,
比如父组件向孙子组件传递数据时
- eventBus 全局事件总线
EventBus 是中央事件总线,不管是父子组件,兄弟组件,跨层级组件等都可以完成通信
// 找到main.js 加入一下代码 公共的$bus
Vue.prototype.$bus = new Vue()
- vuex 可以定义共享的数据源,在哪里都可以访问 安装配置 使用即可
- provide / inject 祖孙之间通信传值
`provide`:可以让我们指定想要提供给后代组件的数据或方法
`inject`:在任何后代组件中接收想要添加在这个组件上的数据或方法,不管组件嵌套多深都可以直接
拿来用
5.一般在哪个生命周期请求异步数据
可以在钩子函数中的 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端返回的数据进行赋值。在created中最好,能更快获取到服务端数据,减少页面加载时间,用户体验更好;服务端渲染不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。mounted 在请求完数据之后需要对 dom 进行操作的时候可以用到
6.v-for中key的作用?vue 渲染列表为什么要加key?
提升vue渲染性能
- key 的作用主要是为了更高效的更新虚拟 DOM,因为它可以非常精确的找到相同节点,因此 patch 过程会非常高效
- Vue 在 patch 过程中会判断两个节点是不是相同节点时,key 是一个必要条件。比如渲染列表时,如果不写 key,Vue 在比较的时候,就可能会导致频繁更新元素,使整个 patch 过程比较低效,影响性能
- 应该避免使用数组下标作为 key,因为 key 值不是唯一的话可能会导致上面图中表示的 bug,使 Vue 无法区分它他,还有比如在使用相同标签元素过渡切换的时候,就会导致只替换其内部属性而不会触发过渡效果
- Vue 判断两个节点是否相同时主要判断两者的元素类型和 key 等,如果不设置 key,就可能永远认为这两个是相同节点,只能去做更新操作,就造成大量不必要的 DOM 更新操作,明显是不可取的
实际开发过程中更推荐统一加上 key,能够实现更广泛场景的同时,避免了可能发生的状态更新错误,我们一般可以使用 ESlint 配置 key 为 v-for 的必需元素。
7.vue双向数据绑定原理-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之间通信的桥梁,主要做的事情是:
在自身实例化时往属性订阅器(dep)里面添加自己,自身必须有一个update()方法,属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调
实现一个订阅器 dep: 采用发布者订阅者模式,用来收集订阅者的 watcher,对监听器 observer 和订阅者 watcher 进行统一管理
8.Vue的template模版编译原理
vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所以需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就称为模板编译。
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)
第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
9.vue虚拟dom,diff算法
虚拟 DOM,其实就是用对象的方式取代真实的 DOM 操作,把真实的 DOM 操作放在内存当中,在内存中的对象里做模拟操作。虚拟dom他并不是真实的 dom ,是根据模板生成一个js对象(使用createElement方法),根据这个js对象再去生成真实的dom,对复杂的文档DOM结构,提供一种方便的工具,进行最小化的DOM操作 ,是可以快速的渲染和高效的更新元素,提高浏览器的性能,我们在渲染页面的时候 会对新的虚拟dom和旧的虚拟dom进行对比 只渲染不同的地方,而不再是像之前只要发生变化,全部的真实dom都要重新渲染,所以提高了渲染的效率。
缺点:首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
10.diff算法
diff 算法是一种通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较
当data发生改变,会根据新的数据生成一个新的虚拟dom ,新的虚拟dom和旧的虚拟dom进行对比,这个对比的过程就是diff算法,会找到不同地方,只去渲染不同的地方,总的来说就是减少DOM,重绘和重排。
11.v-model 原理剖析
V-model 是用来监听用户事件然后更新数据的语法糖。其本质还是单向数据流,内部是通过绑定元素的 value 值向下传递数据,然后通过绑定 input 事件,向上接收并处理更新数据。单向数据流:父组件传递给子组件的值子组件不能修改,只能通过emit事件让父组件自个改。
// 比如
<input v-model="sth" />
// 等价于
<input :value="sth" @input="sth = $event.target.value" />
复制代码
给组件添加 v-model 属性时,默认会把value 作为组件的属性,把 input作为给组件绑定事件时的事件名
12.$nextTick 原理?
vue有个机制,更新 DOM 是异步执行的,当数据变化会产生一个异步更新队列,要等异步队列结束后才会统一进行更新视图,所以改了数据之后立即去拿 dom 还没有更新就会拿不到最新数据。所以提供了一个 nextTick 函数,它的回调函数会在DOM 更新后立即执行。
nextTick 本质上是个异步任务,由于事件循环机制,异步任务的回调总是在同步任务执行完成后才得到执行。项目中不使用$nextTick的话也可以直接使用Promise.then或者SetTimeout实现相同的效果
13.Vuex 流程 & 原理
Vuex 利用 vue 的mixin 机制,在beforeCreate 钩子前混入了 vuexinit 方法,这个方法实现了将 store 注入 vue 实例当中,并注册了 store 的引用属性 store。
- vuex是一个状态管理工具,采用集中式存储管理所有组件的状态,是为了解决中大型项目一个数据共享的问题。vuex 他可以将数据保存到本地,数据是响应式的,能够保持数据页面的共享,提高开发效率。
- 好处能够在 vuex 中集中管理共享的数据,易于开发和后期维护可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发效率
- vuex核心
1.state:vuex的基本数据,数据源存放地,用于定义共享的数据。
2.getter:从基本数据派生的数据,相当于state的计算属性。
3.mutation:提交更新数据的方法,唯一 一个可以操作state 中数据的方法,必须是同步的,第一个参数是state,第二个参数是commit传过来的数据。
4.action:action是用来做异步操作的,一般用来发请求,在 action 中写入函数,然后在页面中用dispatch调用,然后在 action 中通过commit 去调用 mutation 通过 mutation 去操作state。
5.modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。 - mapState,mapActions,mapMutations,mapGetters辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的 如何使用:Import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'。computed(){ ...mapState(['数据名字'])}
- (1)Redux 和 Vuex区别 1.Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可。2.Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可。3.Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)
14.Vuex 页面刷新数据丢失怎么解决
需要做 vuex 数据持久化,一般使用本地存储的方案来保存数据,可以自己设计存储方案 也可以使用第三方插件,推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中。
15.Vue.use函数里面具体做了哪些事
概念可以通过全局方法Vue.use()注册插件,并能阻止多次注册相同插件,它需要在new Vue之前使用。该方法第一个参数必须是Object或Function类型的参数。如果是Object那么该Object需要定义一个install方法;如果是Function那么这个函数就被当做install方法。Vue.use()执行就是执行install方法,其他传参会作为install方法的参数执行。所以Vue.use()就是执行需要注入插件的install方法。
16.怎么编写一个vue插件
要暴露一个install方法,第一个参数是Vue构造器,第二个参数是一个可选的配置项对象
Myplugin.install = function(Vue, options = {}) {
// 1、添加全局方法或属性
Vue.myGlobalMethod = function() {}
// 2、添加全局服务
Vue.directive('my-directive', {
bind(el, binding, vnode, pldVnode) {}
})
// 3、注入组件选项
Vue.mixin({
created: function() {}
})
// 4、添加实例方法
Vue.prototype.$myMethod = function(methodOptions) {}
}
17.vue中的data为什么是一个函数?
因为当 data 是函数时,组件实例化的时候这个函数将会被调用,返回一个对象,然后给这个对象分配一个内存地址,实例化几次就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
简单来说,就是为了保证组件的独立性和可复用性,如果 data 是个函数的话,每复用一次组件就会返回新的 data,类似于给每个组件实例创建一个私有的数据空间,保护各自的数据互不影响。
18.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)来进行的
19.vue的路由-Vue-Router
vue-router(路由原理?路由守卫?)
vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,实际上就是组件的切换。
路由有两种模式 hash和history模式 默认是hash
vue-router的实现原理(核心):更新视图但不重新请求页面。
1、hash ——即地址栏 URL 中的#符号,它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
2、history ——利用了 HTML5 History api 在浏览器中没有#,有浏览器兼容问题。
3、history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误。
一:全局的守卫
无论访问哪一个路径,都会触发全局的钩子函数,位置是调用router的方法
router.beforeEach 全局前置守卫 进入路由之前
router.beforeResolve 全局解析守卫,在beforeRouteEnter调用之后调用
同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用
router.afterEach 全局后置钩子 进入路由之后
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身
二:组件级路由守卫 放在要守卫的组件里,跟data和methods同级
beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到
beforeRouteUpdate (2.2) 路由复用同一个组件时
beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
三:单个路由规则独享的守卫写在路由配置中,只有访问到这个路径,才能触发钩子函数
beforeEnter:(to,from,next)=>{ alert("欢迎来到孙志豪的界面") next() }
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由对象
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
重定向用哪个属性?
redirect:”/路径”
20.vue路由的跳转方式有几种
<router-link to="需要跳转到页面的路径">this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面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不可以
(5) to=”/goods?id=1001”this.然后在接收的页面通过 $route.query.id 来接收
21.路由对象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进行传参
嵌套路由:
vue项目中,界面通常由多个嵌套的组件构成, 必须先清楚这样一件事,一个对应展示的就是一个组件,因此实现嵌套路由有两个要点: 路由对象中定义子路由,用children实现嵌套路由,组件内<router-view/>的使用。
路由懒加载
使用原因:在单页应用中,如果没有应用懒加载,运用 webpack 打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时 原理:vue 异步组件技术:异步加载,vue-router 配置路由 , 使用 vue 的异步组件技术 , 实现按需加载。
{ path: '/home', component: () => import('@/views/home/home.vue') } // 懒加载
22.Vue的常用指令及作用
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)
23.v-if和v-show的区别及使用场景?
v-if 动态的创建或者销毁元素,为true的时候为显示,为false的时候不显示,要使用v-else必须和v-if紧挨着,v-show 是控制元素的显示或者隐藏,在我们的标签上会看到有display:block,none。
v-if 有更高的切换消耗,而 v-show 有更高的初始化渲染消耗,一般推荐频繁切换的时候使用 v-show 更好,当我们的判断分支比较多的时候,和首次渲染的时候 使用v-if。
24.自定义指令,自定义过滤器
除了vue自带的指定以外,我们如果需要对dom进行底层操作的时候这里就用到了自定义指令,分为:全局: vue.directive:{"",{}} 局部:directives:{指令名:{钩子函数}}
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了
改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新.
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
参数:
el:指令所绑定的元素
binding:一个对象包含一下,
name:指令名,不包括 v- 前缀。
value:指令的绑定值
在Vue中使用filters来过滤数据,filters不会修改数据,而是过滤数据,改变用户看到的输出。
25.keep-alive 的作用
keep-alive是Vue提供给我们一个内置组件,会缓存不活动的组件实例,而不是销毁它们, 作为标签使用,包裹在需要缓存的组件外,在组件切换过程中,把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。作用: 比如列表页面进入详情,我们想保存列表滚动的位置,我们就可以使用keep-alive保存列表页面的滚动位置。 组件使用keep-alive以后会新增两个生命周期 actived() deactived()。activated(组件激活时使用) 与 deactivated(组价离开时调用)有两个参数: 允许组件有条件的进行缓存。include - 包裹的组件名会被缓存。exclude 包裹的组件名都不会被缓存。
26.keep-alive 缓存beforDestroy 还会执行吗。
不会直接调用。 默认情况下,也就是没有设置keep-alive,当离开当前路由时,会直接调用beforeDestroy和destroyed来销毁。 当组件设置keep-alive后,不会直接调用这个销毁周期函数,而是会在生命周期函数新增两个,activated和deactivated; 当退出的时候会执行deactivated 函数
27.什么是slot?什么是命名slot?slot怎么使用?
插槽就是父组件往子组件中插入一些内容。 有三种方式,默认插槽,具名插槽,作用域插槽
- 默认插槽就是把父组件中的数据,显示在子组件中,子组件通过一个slot插槽标签显示父组件中的数据
- 具名插槽是在父组件中通过slot属性,给插槽命名,在子组件中通过slot标签,根据定义好的名字填充到对应的位置。这样就可以指定多个可区分的slot,在使用组件时灵活地进行插值。
- 作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过v-slot=""要穿过来的数据“来接受数据。
28.scoped 原理及穿透方法
vue 中的 scoped 通过在 DOM 结构以及 css 样式上加唯一不重复的标记:data-v-hash 的方式,以保证唯一(通过 PostCSS 转译),达到样式私有模块化的目的。 在做项目中,会遇到这么一个问题,即:引用了第三方组件,需要在组件中局部修改第三方组件的样式,而又不想去除scoped属性造成组件之间的样式污染。那么有哪些解决办法呢? ①不使用scopeds省略(不推荐); ② 在模板中使用两次style标签。 ③scoped穿透:/deep/ >>>
29.函数式组件使用场景和原理
函数式组件与普通组件的区别
- 函数式组件需要在声明组件时指定 functional:true
- 不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
- 没有生命周期钩子函数,不能使用计算属性,watch
- 不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
- 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
- 函数式组件的props可以不用显示声明,所有没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
优点 1.由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件 2.函数式组件结构比较简单,代码结构更清晰 使用场景: 一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件 “高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件
30.单页面应用和多页面应用区别以及优缺点
单页面:只有一个html页面,跳转方式是组件之间的切换 优点:跳转流畅、组件化开发、组件可复用、开发便捷 缺点:首屏加载过慢 多页面:有多个页面,跳转方式是页面之间的跳转 优点:首屏加载块 缺点:跳转速度慢
31.口喷axios封装
首先要安装axios,一般我会在项目的src目录中,新建一个network文件夹,作为我们的网络请求模块,然后在里面新建一个http.js和一个api.js文件和一个reques.js。http.js文件用来封装我们的axios baseUrl Tiemout,api.js用来统一管理我们的接口url,request.js中添加请求拦截和响应拦截。在请求拦截中,会给请求头添加token字段,还有loading动画的开启。在响应拦截中,可以做一些loading动画的关闭,还有可以根据后端返回的状态码,做一些检验token是否有效或者过期的操作。接着就是做一些axios进行的api接口的封装,这里我用到了async,await封装请求接口函数,这样可以将异步操作同步化操作,代码更加友好,避免回调地域的出现。
原生ajax过程
- 创建
XMLHttpRequest对象; - 调用
open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false); - 监听
onreadystatechange事件,当readystate等于4时返回responseText; - 调用send方法传递参数。
32.vue中如何解决跨域问题?
在vue开发中实现跨域:在vue项目根目录下找到vue.config.js文件(如果没有该文件则自己创建),在proxy中设置跨域
33.assets和static的区别?
- 相同点:
assets和static两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点 - 不相同点:
assets中存放的静态资源文件在项目打包时,也就是运行npm run build时会将assets中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在static文件中跟着index.html一同上传至服务器。static中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是static中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于assets中打包后的文件提交较大点。在服务器中就会占据更大的空间。 - 建议: 将项目中
template需要的样式文件js文件等都可以放置在assets中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css等文件可以放置在static中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。
34.Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。 如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中,Vue 刷新队列并执行实际(已去重的)工作。
35.多环境变量.env.
首先是通过在根目录下创建.env.*(配置文件)文件,development 本地开发环境配置、staging 测试环境配置、production 正式环境配置(生产环境)。因为我在创建的文件中并没有定义很多变量,只定义了基础的env,所以需要在src目录下创建一个config文件夹,创建对应的环境变量文件,用来管理不同的环境。在config中创建对应的文件是为了后期修改起来方便,不需要重启项目,符合开发习惯。之后就是根据需要的环境,在封装的axios中通过解构赋值的方式导入,放在baseURL中就可以使用。
36.element-ui和vant-ui按需引入
首先安装按需引入的插件,在babel.config.js中添加按需引入的配置,创建一个plugin文件夹,定义一个js文件用来存放按需引入的代码,之后在建好的js文件中首先导入vue,再导入需要的vant-ui插件,通过vue.use()全局注入。修改样式可以用样式穿透 /deep/
37.Vue 解决了什么问题
① 虚拟 dom:dom 操作时非常耗性能的,不再使用原生的 dom 操作节点,极大的解放 dom 操作,但具体操作的还是 dom,不过是换了一种方式。提供了很多指令当然需要 对 dom 进行底层操作的时候就用到自定义指令 ② 视图、数据、结构分离:使数据的更改更为简单,只需要操作数据就能完成相关操作。 ③ 组件化:把一个单页应用中的各种模块拆分到一个一个单独的组件中,便于开发,以及后期的维护
38.描述下 vue 从初始化页面–>修改数据–>刷新页面 UI 过程?
当 Vue 进入初始化阶段时,一方面 Vue 会遍历 data 中的属性,并用 Object.defineProperty 将它转化成 getter/setter 的形式,实现数据劫持; 另一方面,Vue 的指令编译器 Compiler 对元素节点的各个指令进行解析,初始化视图,并订阅 Watcher 来更新视图,此时 Watcher 会将自己添加到消息订阅器 Dep 中,此时初始化完毕。 当数据发生变化时,触发 Observer 中 setter 方法,立即调用 Dep.notify( ),Dep 这个数组开始遍历所有的订阅者,并调用其 update 方法,Vue 内部再通过 diff 算法,patch 相应的更新完成对订阅者视图的改变。
39.Vue 怎么重置 data
使用 Object.assign(),vm.data 可 以 获 取 当 前 状 态 下 的 data , Object.assign(this.options.data())
40.vue-router 登陆权限的判断
vue-router的登陆权限判断主要是在全局钩子函数中进行的,我们在router.js文件中的定义路由里,将需要登陆权限的页面加上meta属性,值是对象的形式,然后在该对象中自定义一个属性,属性值就是一个Boolean值,这时候在main.js文件的全局钩子函数中进行判断,如果需要跳转的页面的自定义属性值为true,那么将进行判断其是否登录,如果没有登录,则告诉用户登录,如果有登录,那么进行页面跳转。
41.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 文件
42.Vue 如何检测数组变化
数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想) 所以在 Vue 中修改,数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
43.vue 中使用了哪些设计模式
1.工厂模式 - 传入参数即可创建实例 虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例 vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
44.如何解决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 文件,修改为 productionSourcceMap:false
④vue 组件尽量不要全局引入
⑤使用更轻量级的工具库
⑥开启 gzip 压缩:这个优化是两方面的,前端将文件打包成.gz 文件,然后通过 nginx 的配置,让浏览器直接解析.gz 文件。
⑦首页单独做服务端渲染:如果首页真的有瓶颈,可以考虑用 node 单独做服务端渲染,
而下面的子页面仍用 spa 单页的方式交互。
45.你都做过哪些Vue的性能优化?
1.尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
2.v-if和v-for不能连用 v-if 和 v-show 区分使用场景 v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if 如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件 在更多的情况下,使用v-if替代v-show 使用路由懒加载、异步组件
3.防抖、节流 第三方模块按需导入
4.长列表滚动到可视区域动态加载,不需要响应式的数据不要放到 data 中
5.图片懒加载
6.SEO优化 预渲染
7.服务端渲染SSR 打包优化,
8.压缩代码
9.使用cdn加载第三方模块 多线程打包happypack splitChunks抽离公共文件 sourceMap优化 骨架屏
10.PWA 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。防止内部泄漏,组件销毁后把全局变量和事件销毁
46.对SSR的理解
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端SSR的优势:1.更好的SEO2.首屏加载速度更快。SSR的缺点:1.开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;2.当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;3.更多的服务端负载。
React
1.什么是JSX和它的特性
JSX是JavaScript XML的缩写,基于ECMAScript的一种新特性。 特性:1. 自定义组件名首字母大写。2. 嵌套;在render函数中return返回的只能包含一个顶层标签,否则也会报错。3. 求值表达式;JSX基本语法规则,遇到HTML标签(以<开头),就用HTML规则解析;遇到代码块(以{开头),就用JS规则解析 4. 驼峰命名。5. class属性需要写成className。
2.类组件和函数组件之间有什么区别
类组件: 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props。
所有 React 组件都必须是纯函数,并禁止修改其自身 props。React是单项数据流,父组件改变了属性,那么子组件视图会更新。属性 props是外界传递过来的,状态 state是组件本身的,状态可以在组件中任意修改。组件的属性和状态改变都会更新视图。
函数组件: 函数组件接收一个单一的 props 对象并返回了一个React元素,函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。
3.什么是高阶组件
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
4.详细解释 React 组件的生命周期方法
- componentWillMount() – 在渲染之前执行,在客户端和服务器端都会执行。
- componentDidMount() – 仅在第一次渲染后在客户端执行。
- componentWillReceiveProps() – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
- shouldComponentUpdate() – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true ,不想更新组件则返回 false就会阻止render渲染。默认情况下,它返回 true。
- componentWillUpdate() – 在 DOM 中进行渲染之前调用。
- componentDidUpdate() – 在渲染发生后立即调用。
- componentWillUnmount() – 从 DOM 卸载组件后调用。用于清理内存空间。
5.React组件之间通信方式
- 父子:父传子:props; 子传父:子调用父组件中的函数并传参;
- 兄弟:利用redux实现和利用父组件
- 所有关系都通用的方法:利用PubSub.js订阅
ES6面试题
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.Set、Map、WeakSet、WeakMap
Set、Map、WeakSet、WeakMap是ES2015中新增的几个对象:
set类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。
Set和WeakSet与数组类似,这两者的区别就是Set可以存储任何数据类型,而WeakSet只能存储对象的引用,而且是弱引用;
Set对象在实际开发中最常见的就是实现数据去重,示例代码如下:
const arr = [1, 2, 2, 3, 4, 3, 5]
const set = new Set(arr)
// set对象可以使用 ... 展开 所有项
console.log([...set]) // [ 1, 2, 3, 4, 5 ]
复制代码
map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
Map和WeakMap与对象类似,存储方式是键值对形式的,这两者的区别Map的键值对都是可以是任意的而WeakMap键必须是对象的引用而值可以是任意类型的。
3.promise的解释
- Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱 的问题,自己身上有all、reject、resolve、race 等方法,原型上有then、catch等方法。
- 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列
- promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败
- Promise 对象状态改变:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了
- 如果不设置回调函数,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的每一项都是promise,catch方法返回值会被promise.reslove()包裹,
这样传进promise.all的数据都是resolved状态的。
// 使用Promise.all 其中id为69的商品,返回失败,会导致整个Promise接受到reject状态.
// 所以进行改造, p catch 得到的err 为返回失败抛出的信息, 进行置空
.map(p => p.catch(err => '')))
4.async 和 await 的区别
Async 和 await 是一种同步的写法,但还是异步的操作,两个必须配合一起使用
函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。
await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是promise则会等待promaise 返回结果,接普通函数直接进行链式调用.
await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法
如果await后面不是Promise对象, 就直接返回对应的值,只能在async函数中出现, 普通函数直接使用会报错
await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行
5.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的区别
1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;
2,require是运行时加载,import是编译时加载;
3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;
4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;
5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;
6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所以性能稍高。
6.箭头函数
js中我们在调⽤函数的时候经常会遇到this作⽤域的问题,这个时候ES6给我们提箭头函数。
1、 箭头函数是匿名函数不能作为构造函数,不能使用new。
2、 箭头函数不绑定arguments,取而代之用rest参数…解决。
3、 this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this。
4、 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响。
5、 箭头函数没有prototype(原型),所以箭头函数本身没有this。
6、 写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁。
7、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。
7.简述 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的产生
8.解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值
常见的几种方式有
1.默认值
2.交换变量
3.将剩余数组赋给一个变量
结构数组和对象字符串区别
对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象。
我在项目中:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。
9.for...in 迭代和 for...of 有什么区别
1、推荐在循环对象属性的时候,使用 for...in,在遍历数组的时候的时候使用for...of。
2、for in遍历的是数组的索引,而for of遍历的是数组元素值
3、for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
4、for···in会遍历出来的为对象的key,但for···of会直接报错。
10.proxy 的理解
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
11.谈谈你对模块化开发的理解?
我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。
由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污染,并且模块间没有联系。
后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所有的模块成员,外部代码可以修改内部属性的值。
现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
12.js 的几种模块规范?
js 中现在比较成熟的有四种模块加载方案:
- 第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
- 第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
- 第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
- 第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。