1.简单说一下let,var,const的区别?
<1>.let,const是es6规范提出来的,专门用于定义变量的,而var是在es6之前一直沿用的专门用于定义变量的
<2>.let和const之间唯一的区别是 let是用来定义变量(可以随意改变其引用),const是用来定义常量的(不允许修改其引用)
<3>.var只支持块级作用域以及函数作用域,而const,let 在此基础基础之上,还支持了块级作用域。
<4>.var存在变量提升,而let,const不存在 。
var比如
console.log(num);//undefined
var num=100;
console.log(num);//报错 出现暂时性死区
let/const num=100;
<5>.var 允许重复定义变量的,而且后面的会覆盖前面的
let,const 不允许重复定义变量,只要你定义了就会报错。
注意:引用
const
对于基本类型而言 变量引用代表的是值本身(不允许修改其值的)
const num=100;
num=100;//错误(不允许二次赋值)
对于对象类型而言 变量引用代表的是值的地址(不允许改变对象的地址)
const user={name:"jack"}
user.name="rouse";//可以,因为并没有修改user对象的地址
user={};//不允许的 改变对象地址不允许的
块级作用域的目的是为了隔离变量?
for(let i=0;i<100;i++){
}
for(let i=0;i<100;i++){
}
console.log(i);//报错
for(var i=0;i<100;i++){
}
for(var i=0;i<100;i++){
}
console.log(i);//报错
在块级作用域没有出来之前,我们可以通过哪些手段模拟出块级作用域?
立即执行,也可以达到隔离变量的目的。
(async function show(){
})();
衍生问题:
<1>.es6没有出来之前,我们可以通过哪些手段模拟出块级作用域?
使用立即执行到达隔离变量的目的,其目的与块级作用域相同。
2.你知道暂时性死区吗?你能跟我简单说一下吗?
知道。
我们知道变量在编译阶段分为3个阶段,分别是创建变量 初始化变量(undefied) 使用变量,由于let,const不存在变量提升,说白了初始化阶段不会被提升,因此我们把创建变量这个阶段到初始化阶段之前的空间称之为暂时性死区(TDZ)
console.log(num);
console.log(num);
console.log(num);
console.log(num);
let num=100;
console.log();
编译一下:
let num;//创建变量阶段(变量里面没有值)
console.log(num);
console.log(num);
console.log(num);
console.log(num);
num=undefined;
num=100;
3.你能跟我说一下原型,还有原型链吗?
原型:
<1>.每一个函数都有一个属性prototype,这个属性保存的属性值 我们称之为 显示原型对象
<2>.每一个对象都有一个属性__proto__, 这个属性保存的属性值 我们称之为 隐士原型对象
原型链:
当我们在访问某个属性/函数的时候,首先会看当前对象中有没有对应的属性,没有就会到当前对象的原型对象中寻找,如果还找不到,就会到对象的原型对象对应的原型对象(===父类的原型对象)中去找,如果还没有,就会一层一层继续往上寻找,知道找到了Object的原型对象(最终父类的原型对象)为止,因为Object是最终父类,本身没有父类了,所以Object原型对象的原型对象是null,我们把这整个寻值的过程称之为原型链。
注意:
我们为什么要用原型对象这种设计?(原型对象这种设计有何意义?)
<1>.可以利用原型对象建立共享的函数(不仅给当前类创建的对象共享,还可以给当前类得到后代类共享),这样可以用来节约创建对象的内存。
<2>.原型对象设计的最初目的是为了方便继承,子类原型对象的原型对象保存的是父类的原型对象
根据原型链的我们知道,对象是可以访问原型链上面的内容(属性,函数),子类一旦继承了父类的内容,是可以直接使用的,不仅如此,子类还可以改写继承自父类的内容,根据原型链我们还知道,对象会优先到当前对象去寻找,调用的时候如果子类改写了父类的内容,优先调用改写之后的属性或者函数。
4.你能列举一下ES6的新特性吗?
1.let,const
2.数组操作对应函数也进行的扩展 (filter,forEach,map,some,every,reduce)等等
3.es6模块化
4.Map(key,value),Set 类去重
5.引入class的方式定义构造函数
6.static (静态关键字)
7.箭头函数
8.速写函数以及速写属性
目的:
<1>.列举出来的内容自己一定要提前熟悉好
5.操作数组的函数,哪些函数会改变原数组,哪些函数不会改变原数组?
会改变原数组的有哪些?
push()
pop()
unshift()
shift()
splice();//2个参数删除 3个参数插入
sort();//排序
不会改变原数组的函数
forEach()
map()
filter()
some()
every()
find()
reduce()
6. undefined和null的区别
<1>.undefined和null都属于基本数据类型,分别属于Undefined类型 以及 Null类型
<2>.使用typeof检测类型的时候 undefined对应的类型是 Undefined类型 null是Object类型
(typeof 检测null类型不准确)
<3>.null在内存中是以0的方式区存储的,因此null与数值做运算都是以0去做运算
null+22=22;
undefined 来自于window对象的undefined属性,属于非数值类型因此与数值做运算的时候返回的是非数(NaN)
<4>.null代表的是空对象的含义,可以用做初始化一个对象,或者清空对象(回收堆内存,模仿后端的一种设计),
但是js除了对象类型还有基本类型,于是有设计了一个undefined,代表一个没有赋值的任意类型的变量,代表无值的含义。
数值类型:
存在window对象中
NaN:非数
Infinity:无穷大
都属于number类型
为什么typeof检测 null的时候返回的是Object类型 ?
平时从论坛上面有看到过相应的解释,null在内存中是以0的方式存储的,其2进制全是0,在内存中对象类型和基本类型想要做区分吗,对象类型2进制前面3位是0,而null全是0 因为检测过程中会将null当做object
7. 你能说一下深拷贝和浅拷贝?
1.不管是深拷贝还是浅拷贝针对的都是对象类型
深拷贝:值拷贝,2个对象指向不同的内存地址,将一个对象的值拷贝到另一个对象中
放式1:
let user1={name:jack,pass:"1234"};
let user2={...user1};
方式2:遍历的方式一个一个拷贝进行
for in Object.keys()
方式3:对象合并,也会产生新对象
let user3=Object.assign({},user1);
方式4:JSON对象转换
let user4=JSON.parse(JSON.stringify(user1));
浅拷贝:地址拷贝,2个对象指向同一个地址
方式1:
let user1={name:jack,pass:"1234"};
let user2=user1;
方式2:
let user3=Object.assign(user1);
存在问题:
<1>.上述的深拷贝 只针对对象中的属性都是基本类型,如果属性是对象类型深拷贝会失效
<2>.对象类型的属性其实还是浅拷贝(对象这个标识内存中存的都是地址)
let user1={name:"jack",pass:"1234",obj:{sex:"男",address:"上海"}};
let user2={...user1};
user2.name="rouse";//深拷贝
user2.obj.sex="女";//对于obj来说属于浅拷贝(obj是对象类型)
console.log(user1);
怎么解决:可以采用遍历对象,然后如果属性是对象类型,采用递归的方式进行拷贝。
//深拷贝对象和数组
function copyObj(obj) {
let res={};
if(obj instanceof Array){
res=[];
}
for(let fieldName in obj){
if(obj[fieldName] instanceof Object){
res[fieldName]=copyObj(obj[fieldName]);
}else{
res[fieldName]=obj[fieldName];
}
}
return res;
}
let user1={name:"jack",pass:"1234",obj:{sex:"男",address:"上海"}};
let user2=copyObj(user1);
user2.obj.sex="女";
console.log(user1);//男
8. 你知道宏任务和微任务吗?
JS本身是一门单线程的语言,同步的思想容易出现同步阻塞,影响程序执行的效率,为了解决同步阻塞的问题,引出了异步的思想,而异步任务就分为宏任务和微任务。
常见的宏任务:
定时器:
回调函数:
ajax: 定时器:
常见的微任务:
Promise
async await
宏任务和微任务有什么区别?
并列的情况下 优先执行微任务,如果微任务列表的任务全部空了,才会去执行宏任务,利用事件循环不断的判断
也就是说,当执行宏任务的时候,判断微任务列表空了没,空了才会执行宏任务,不断判断的过程我们称之为事件循环
如果宏任务中嵌套微任务(先执行宏任务,再执行微任务)
9. 你知道事件循环吗,能简单说一下吗?
当js代码执行的时候,会将同步代码放入到同步队列,异步代码进入到异步队列,程序会优先执行同步队列中的代码,当同步队列中的代码全部执行完毕,就会判断异步任务中的等待队列中的有否有任务,有的话就会执行,每一次执行异步任务中等待队列的任务,都会判断一下同步队列是否有任务,每一次都会判断,我们把这个不断判断的过程称之为事件循环
10.你能说一下Promise的原理吗?
Promise有3种状态,分别是pending 初始化的状态,fulfilled 成功的状态,rejected失败状态,当实例化Promise的时候,内部就会将状态设置为pending(通过this.status这个属性记录),当内部调用resolve函数时,会将状态设置为fulfilled,并且将resolve传递进来的实参存入promise对象的value属性中,当内部调用rejected函数时,会将状态设置为rejected,并且将rejected传递进来的实参存入promise对象的value属性中, 当使用promise对象调用.then函数的时候,内部进行判断,如果状态是fulfilled那么就会调用then的第一个参数对应的回调函数,并且将Promise的value属性传入回调函数,如果状态是rejected,那么就会调用then的第二个参数对应回调函数,并且将Promise的value属性传入回调函数。 ===> catch函数中调用了 then(null,error);
11.简述你对闭包的理解?
闭包定义: 在一个函数内部嵌套另一个函数,并且这个内部函数需要访问到外部函数的变量,还需要将内部函数返回(外部能够调用到这个内部函数),由于内部函数在外面还在使用,导致外部函数调用完毕,内部函数中正在使用的变量,无法销毁,我们把这种现象称之为闭包。 闭包特点: 通过闭包实现将函数中的局部变量当做全局变量来用,延长了变量的生命周期,如果滥用闭包,会导致内存泄漏。 function outer(){ let num=100; function inner(){ //内部函数必须调用外部函数的变量 } 返回内部函数 }
12.this在不同环境下代表的含义?
1.全局或者纯函数中的this代表window对象 2.json对象中的普通函数内部的this代表当前json对象 3.构造函数中的this代表通过这个构造函数实例化(new)的对象 4.箭头函数中的this代表的是外层的对象 5.定时器的回调函数中的this代表的是window对象 6.事件中的this,代表的是对应标签的js dom对象
13.如何去改变this的指向,以及有什么区别?
apply(),bind(),call() <1>.三个函数中的第一个参数,是一个对象,是哪个对象那么this就代表(指向)哪个对象 <2>.applay()第二个参数是一个数组,用来给调用的函数传递参数列表 <3>.bind() 第二个参数是一个可变参数,用来给调用的函数传递参数列表的,调用bind函数之后返回的是一个函数,需要调用一下,函数才会执行 <4>.call()第二个参数是一个可变参数,用来给调用的函数传递参数列表的 let obj1={ name:"obj1", show(name,age){ console.log(this,name,age); } } let obj2={name:"obj2"} //无法改变this指向 //obj1.show(); obj1.show.call(obj2,"admin",18); obj1.show.apply(obj2,["charles",19]); obj1.show.bind(obj2,"admin",18)();
14.检测类型的方式有哪些,分别有什么缺点?
1.typeof: 检测基本类型除了null其他的都是比较准确的,检测函数返回的是function,检测任何对象返回的都是Object 2.instanceof 专门用于检测引用类型,但是无法检测基本数据类型 3.constructor 对象.constructor === Student.prototype.constructor 改变了构造函数的指向,就无法比较了 4.Object.prototype.toString.call(检测的数据) [object,Array/Number/...]
15.new 一个对象发生了什么?
<1>.创建一个空的对象 <2>.将构造函数中的this指向空的对象 <3>.将构造函数的原型对象浅拷贝给空对象的 __proto__属性 <4>.返回对象的地址
16.防抖和节流?
1.概念:
防抖:一个函数连续多次触发,我们只执行最后一次。 节流:一个函数连续多次触发,我们按照一定的时间间隔多次执行。
2.为什么需要使用防抖和节流?
考虑这样两个场景--- 1.一个按钮被点击时,会发送网络请求。为了防止用户无意多次点击,或有人恶意连续发送请求,我们不希望按钮连续被点击时,每次都发送网络请求。而是过一定时间没有再点击时,我们才发送请求---即只执行最后一次---便是防抖。 2.滚动事件中会触发网络请求,但是我们不希望在滚动时一直发送网络请求,而是隔一定时间发起一次---便是节流。
3.那如何实现防抖和节流?
1.防抖的实现?
<button type="button" onclick="test01()">发送</button> <script type="text/javascript"> let timer=null; function test01() { if(timer){ clearTimeout(timer); } timer=setTimeout(function () { console.log("发送成功"); },500); } </script>
2.节流的实现?
<button type="button" onclick="test01()">发送</button> <script type="text/javascript"> let timer=null; function test01() { if(timer==null){ timer=setTimeout(function () { console.log("发送成功"); timer=null; },2000); } } </script>
4.防抖和节流的应用场景分析?
1.防抖多用于: 用户输入用户名、手机号时,最后一个字母输入后再验证; 一个按钮发送请求时,最后一次按才发送; 窗口resize,当窗口确定大小时才渲染,防止重复渲染; 输入框内容联想; 2.节流多用于: 滚动加载,mousemove ,mousehover;
17. Object.defineProperty 和 Proxy区别?
1.Proxy代理的是整个对象,Object.defineProperty只代理对象上的某个属性,如果要求代理整个对象则需要多层嵌套的数据需要循环递归绑定; 2.Proxy性能优于Object.defineProperty 3.对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到,需要借助$set方法; 4.数组的某些方法(push、unshift和splice)Object.defineProperty监听不到,Proxy可以监听到; 5.当然Proxy和Object.defineProperty都无法监听到对象中嵌套对象的属性改变;
1.Object.defineProperty代码实现
let user={obj:{address:"上海"},name:"jack",pass:"1234",sex:"男",age:18}; //1.Object.defineProperty() 只能监听对象中的属性 不能监听整个对象(不能监听对象) //2.监听对象怎么办? 对象遍历+递归 //data(){return {obj:{obj:{}},name:"} //let fieldName="obj"; function lisenObj(obj) { Object.keys(obj).forEach(fieldName=>{ let oldValue=obj[fieldName];//原来的值 if(oldValue instanceof Object){ lisenObj(oldValue);//递归对象添加监听 }else{ Object.defineProperty(obj,fieldName,{ get(){ console.log(fieldName+":get属性被使用了"); return oldValue; }, set(newValue){ oldValue=newValue; console.log(fieldName+":set属性值被修改了"); } }); } }); } lisenObj(user); user.name; user.name="xxxx"; user.age; user.age=88; user.pass; user.pass="1234"; user.sex; user.sex="女"; user.obj.address; user.obj.address="北京";
2.Proxy代码实现
let stu={_id:1,stuName:"张三",stuPass:"1234",obj:{teach:"uuu"}}; let stuProxy=new Proxy(stu,{ get(target,prop){ console.log(target,prop,"getter被调用了"); return Reflect.get(target,prop); }, set(target,prop,value){ console.log(target,prop,"setter被调用了"); return Reflect.set(target,prop,value); } }); console.log(stuProxy); stuProxy._id=100; console.log(stuProxy._id); console.log(stuProxy.obj); stuProxy.obj.teach="iii";
18.作用域,作用域链?
作用域: 全局作用域:window中的属性以及最外层的定义函数或者变量,全局作用于中的内容可以给任何其他作用域去访问,全局变量的滥用,容易造成变量的污染,名字容易出现冲突等等 函数作用域:在函数内部定义的变量,仅仅作用域函数内部,函数调用完毕,在函数中定义的变量对应的内存就会销毁(闭包除外)。 块级作用域:只有let/const定义的变量才支持块级作用域,块级作用域以{}作为块级作用域的访问范围,出了{}块级作用域内部定义的变量就会销毁 作用域链: 当我们去访问某个变量的时候,如果当前作用域找不到,就回到上一层作用域寻找,一层一层往上找,直到找到全局作用域,还找不到,继续到window对象中寻找,我们把这个寻值的过程称之为作用域链
19.数组与对象之间相互转换关系
数组转对象 <1>.遍历 <2>.扩展运算符 <3>.对象合并Object.assign({},arr); <4>.二维数组 [[name,value],[..],[..]] Object.fromEntries(arr) 对象转数组: <1>.遍历 <2>.扩展运算符 <3>.Object.entries(obj);//[[],[]] <4>.Object.keys(obj).map(item=>{return [item,obj[item]]});
20.sessionStorage ,localStorage,cookie 区别?
1、概念区别:
localStorage是客户端存储数据的两个对象,不会自动把数据发给服务器,仅在本地保存,用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除 sessionStorage是客户端存储数据的两个对象,不会自动把数据发给服务器,仅在本地保存,用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据 cookie是一般由后端服务器生成,然后响应至客户端进行缓存,当下一次再去访问服务器的时候会自动携带cookie至后端服务器. cookie的扩展问题,cookie只能由后端服务器生成吗,前端可不可以生成cookie(答案是当然可以) 可以利用jquery-cookie依赖包中提供的 $.cooke() 函数创建。
2、存储时间区别
localStorage除非被手动清除,否则可以永久存储 sessStorage仅在当前会话有效,关闭页面或者浏览器后会被清除。 cookie服务器端可以设置失效时间,如果没有设置存活,默认是客户端关闭浏览器后失效。
3、存储数据大小区别
localStorage的存储空间是5M(一个域) sessStorage的存储空间是5M (一个域) cookie只有4k左右,存储过多数据会带来性能问题
4、作用域区别
sessionStorage不在不同的浏览器窗口共享,即使是同一个页面 localStorage在所有同源窗口中都是共享的 cookie也是在所有同源窗口中都是共享的
5.扩展问题:分别怎么去存储,或者清空缓存
localStorage和sessionStorage的使用 不管是 localStorage,还是 sessionStorage,可使用的API都相同,常用的有如下几个(以localStorage为例): 保存数据:localStorage.setItem(key,value); 读取数据:localStorage.getItem(key); 删除单个数据:localStorage.removeItem(key); 删除所有数据:localStorage.clear(); cookie的使用: 创建cookie一般是由后端创建,客户端缓存,每一次访问后端接口时都会默认携带cookie 删除cookie, 如果是在后端删除,可以设置cookie的存活时间为0 setMaxAge(0) 如果在前端直接删除cookie ,可以使用 jquery-cookie这个依赖包提供的 $.removeCookie(key)删除cookie
你有了解过 强缓存:一般由服务端创建(后端),客户端缓存,一般是可以设置到期时间的,不仅如此,当下一次访问服务端的时候,会自动将本地缓存的数据携带到后端。 弱缓存: 一般是由客户端去创建缓存的,没有固定的到期时间的设置,访问服务端的时候,不会自动将该缓存携带到后端。 协议缓存: 基于http协议的缓存,该缓存成功或者失败的标志如下 200:代表协议缓存失效(从新请求) 304:代表协议缓存成功(从缓存中获取) 当重复查询后端接口 第一次查询 第二次查询 url 参数 全部相同,这种情况下 默认缓存第一次查询出来的结果,第二查询就会返回缓存的数据
21.单页应用与多页应用的区别
概念:
SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。 MPA多页面应用 (MultiPage Application),指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。
区别:
22.SPA(单页应用)首屏加载速度慢怎么解决?
1.SPA 首屏加载时间慢的原因? 网络延时问题 资源文件体积是否过大 资源是否重复发送请求去加载了 加载脚本的时候,渲染内容堵塞了 2.常见的几种SPA首屏优化方式 1.减小入口文件积 :常用的手段是路由懒加载,只有在解析路由时才会加载组件 2.静态资源本地缓存:前端合理利用:localStorage 3.UI框架按需加载 :例如element-UI, antd, 我们要按需引入 4.图片资源的压缩: 图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素,对于所有的图片资源,我们可以进行适当的压缩,对于页面上使用的icon,可以使用在线字体图标,或者雪碧图,将众多的小图标合并到一张图中,用以减轻http请求的压力。 5.避免组件重复打包: 假如A.js 文件是一个常用的库,现在有多个路由使用A.js文件,这样就造成重复下载 解决方案: 在webpack的config文件中,修改CommonsChunkPlugin的配置minChunks:2,minChunks为2会把使用2次及以上的包抽离出来,放进公共依赖文件中避免了重复加载组件。 6.开启Gzip压缩: 拆完包后,我们再用gzip做一下压缩,安装compression-webpack-plugin,webpack中配置安装 7.使用SSR: SSR解释:(SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染)
23.javascript延迟加载的方式有哪些?
延迟加载就是等页面加载完成之后再加载 JavaScript 文件。 js 延迟加载有助于提高页面加载速度。
一般有以下几种方式:
defer 属性:给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
async 属性:给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
动态创建 DOM 方式:动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
使用 setTimeout 延迟方法:设置一个定时器来延迟加载js脚本文件
让 JS 最后加载:将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
24.为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
`arguments`是一个对象,它的属性是从 0 开始依次递增的数字,还有`callee`和`length`等属性,由于类数组属性名字是由数字组成,因此调用属性的过程与数组根据下标访问元素的方式相似,因而称之为类数组,但是类数组本质上是对象而非数组,因此类数组没有数组常见的方法属性,如`forEach`, `reduce`等。
要遍历类数组,有三个方法:
(1)将数组的方法应用到类数组上,这时候就可以使用call
和apply
方法,如:
function test01(){ Array.prototype.forEach.call(arguments, a => console.log(a)) }
(2)使用Array.from方法将类数组转化成数组:
function test02(){ const arrArgs = Array.from(arguments) arrArgs.forEach(a => console.log(a)) }
(3)使用展开运算符将类数组转化成数组
function test03(){ const arrArgs = [...arguments] arrArgs.forEach(a => console.log(a)) }
25. ES6模块与CommonJS模块有什么异同?
ES6 Module和CommonJS模块的区别:
CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,也就是指针指向不能变,类似const;
import的接⼝是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。
ES6 Module和CommonJS模块的共同点:
CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。
CommonJS 通过 module.exports 或者 exports 暴露变量(对象,函数,以及其他任何数据),并且通过require方式加载模块。
ES6模块化 通过 export 或者 export default 的方式 暴露变量,并且通过 import方式引入
CommonJS class Student{ } module.exports= 对象 exports module.exports 指向同一个对象地址 let Student=require("...."); ES6 模块化 export default { } import 引入 只能用,不能修改 对象可以改变属性 不能改变对象地址
26.ajax执行流程/原理
1.1 ajax常见函数和属性介绍
xmlhttprequest:js提供的一个对象 作用就是专门用于实现 客户端与服务端进行异步交互的一个对象,实现局部数据 交互: 请求 :客户端向服务端发送数据 响应 : 服务端给客户端发送数据 open():设置请求的地址以及请求的方式 send():发送请求 status:表示返回的响应的状态。即状态码。请求发送以及接受完毕为200 readyState:表示请求的当前状态 - 0:表示xmlhttprequest对象被创建 - 1:open()已被调用 - 2:send方法已被调用,已经获取响应头信息 - 3:正在接受响应数据 - 4:响应数据接收完毕(通信成功时,状态值为4) onreadyStateChange:当readyState属性发生变化时会触发。 responseText:保存了响应数据
1.2 ajax执行流程
1. 新建一个xmlhttprequest对象 2. 调用open()方法 3. 指定onreadyStateChange事件,判断当readyState ==4 && status==200 时,说明数据接收完毕。响应数据会作为实际参数传递到该事件中. 4. 调用send方法,来发送请求 5. 会使用xmlhttprequest对象的responseText属性来获取响应数据
1.3 ajax 实现的实例
function Ajax(type,url,success){ var xhr = new XMLHttpRequest();// 新建一个xmlhttprequest对象 xhr.open(type,url);// 设置请求地址以及请求方式 xhr.onreadystatechange = function(){ if(xhr.status==200 && xhr.readyState==4){ success(xhr.responseText); } } xhr.send(null); } Ajax('get','./data.json',success); function success(data){ console.log(data); console.log(JSON.parse(data)) }
27. axios原理
2.1 axios原理文字描述
axios是一个基于 Promise 来管理 http 请求的简洁、易用且高效的代码封装库。通俗一点来讲,它是一个前端替代Ajax的一个东西,可以使用它发起http请求接口功能,它是基于Promise的,相比于Ajax的回调函数能够更好的管理异步操作,不仅如此,新建一个Axios对象时,会有两个拦截器,request拦截器,response拦截器。
2.2 axios 请求拦截器
请求拦截器的作用是在请求发送前进行一些操作,例如在每个请求头header里加上token,统一做了处理如果以后要改也非常容易。 统一处理token实例代码: axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么,例如加入token if (localStorage.getItem('token')) { config.headers.token = localStorage.getItem('token'); } return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); });
2.3 axios 响应拦截器
响应拦截器的作用是在接收到响应后进行一些操作,例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页。 统一处理实例代码: axios.interceptors.response.use(function (res) { // 在接收响应做些什么,例如跳转到登录页 ...... return res;//后端响应回来的数据 }, function (err) { // 对响应错误做点什么 if (err && err.response && err.response.status) { if (err.response.status == 401) {//权限不够 alert("登录超时"); router.push('/login');//跳转到登录页面 } else { return Promise.reject(err); } } else { return Promise.reject(err); } });
28. axios 与ajax区别?(掌握)
Ajax是对原生XHR的封装,为了达到我们跨域的目的,增添了对JSONP的支持。ajax不是一门新技术,而是多种技术的组合,用于快速的创建动态页面,能够实现无刷新更新数据从而提高用户体验。 axios由客户端请求ajax引擎,再由ajax引擎请求服务器,服务器作出一系列响应之后返回给ajax引擎,由ajax引擎决定将这个结果写入到客户端的什么位置。实现页面无刷新更新数据。
29. 箭头函数与普通函数的区别
(1)箭头函数比普通函数更加简洁
(2)箭头函数没有自己的this
(3)call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global'; let fun1 = () => { console.log(this.id) }; fun1(); // 'Global' fun1.call({id: 'Obj'}); // 'Global' fun1.apply({id: 'Obj'}); // 'Global' fun1.bind({id: 'Obj'})(); // 'Global'
(4)箭头函数不能作为构造函数使用(也不能new)
构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
(5)箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
(6)箭头函数没有prototype
30. async/await对比Promise的优势
代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅
错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余
调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。
let res=0; .then(data=>{//100 res=data; });//微任务 setTimeout(()=>{//宏任务 console.log(res); },1000); let res=awaiit...; console.log(res); .then(res=>{ }).catch(error){ .... } try { await ... }catch(error){ console.error(error); }
31.事件委托或者事件代理的原理是什么?
事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。 <ul id="father"> <li>11111</li> <li>22222</li> <li>33333</li> <li>44444</li> </ul> <script type="text/javascript"> let ulDom=document.querySelector("#father"); ulDom.addEventListener("click",function (e) { //拿到目标对象(点那个标签就是代表哪个标签对象) let target=e.target; alert(target.innerHTML); },false); </script>
32.事件冒泡和事件捕获的区别是什么?
事件冒泡:从下至上(是指子元素向父元素传递的过程), bool=false冒泡(默认) 事件捕获:从上至下(是指父元素向子元素传递的过程), bool=true 捕获 一般不会用到
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <style type="text/css"> #grandfather{ width: 600px; height: 600px; background: hotpink; margin: 0px auto; } #father{ width: 400px; height: 400px; background: green; } #son{ width: 200px; height: 200px; background: yellow; } </style> </head> <body> <div id="grandfather"> <div id="father"> <div id="son"></div> </div> </div> <script type="text/javascript"> let grandfatherDom=document.querySelector("#grandfather"); let fatherDom=document.querySelector("#father"); let sonDom=document.querySelector("#son"); grandfatherDom.addEventListener("click",function () { console.log("grandfather"); },false); fatherDom.addEventListener("click",function () { console.log("father"); },false); sonDom.addEventListener("click",function () { console.log("son"); },false); </script> </body> </html>
33. rem,em,px的区别?
px、em、rem都是计量单位,都能表示尺寸,但是有有所不同,而且其各有各的优缺点。 Px表示“绝对尺寸”(并非真正的绝对),实际上就是css中定义的像素(此像素与设备的物理像素有一定的区别,后续详细说明见文末说明1),利用px设置字体大小及元素宽高等比较稳定和精确。Px的缺点是其不能适应浏览器缩放时产生的变化,因此一般不用于响应式网站。 em表示相对尺寸,其相对于当前对象内文本的font-size(如果当前对象内文本的font-size计量单位也是em,则当前对象内文本的font-size的参考对象为父元素文本font-size)。使用em可以较好的相应设备屏幕尺寸的变化,但是在进行元素设置时都需要知道父元素文本的font-size及当前对象内文本的font-size,如有遗漏可能会导致错误。 rem也表示相对尺寸,其参考对象为根元素<html>的font-size,因此只需要确定这一个font-size。 html 15px div: 2rem 等价于 30px 动态设置html大小,整个网页动态变化(屏幕适配的用的比较多)
34.如何用原生js给一个按钮的点击事件绑定两个事件处理函数?
使用事件监听绑定多个事件 <button type="button" id="btn_id">点击</button> let btnDom=document.getElementById("btn_id"); btnDom.addEventListener("click",function () { alert("123"); }); btnDom.addEventListener("click",function () { alert("456"); });
35. 回流与重绘的概念
(1)回流
当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。
下面这些操作会导致回流:
页面的首次渲染
浏览器的窗口大小发生变化
元素的内容发生变化
元素的尺寸或者位置发生变化
元素的字体大小发生变化
激活CSS伪类
查询某些属性或者调用某些方法
添加或者删除可见的DOM元素
(2)重绘
当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。
下面这些操作会导致回流:
color、background 相关属性:background-color、background-image 等
outline 相关属性:outline-color、outline-width 、text-decoration
border-radius、visibility、box-shadow
注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。
36. v-if和v-show的区别?
1.标签v-show与v-if的作用 在 vue 中 v-show 与 v-if 的作用效果是相同的,都能控制元素在页面是否显示。但是v-show不管返回的值是true还是false都会渲染 ,其底层使用css中的display来实现显示或者隐藏的,而v-if 如果返回的是true会渲染标签,但是如果返回false不会渲染标签 2.v-show与v-if的使用场景: <1>.v-if 与 v-show 都能控制dom元素在页面的显示 <2>.v-if 相比 v-show 开销更大的(直接操作dom节点增加与删除) <3>.如果需要非常频繁地切换,则使用 v-show 较好 <4>.如果在运行时条件很少改变,则使用 v-if 较好 ------------------------------------------------- 细读 2.v-show与v-if的区别:控制手段不同 编译过程不同 编译条件不同 <1>.控制手段: v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在。 v-if显示隐藏是将dom元素整个添加或删除 <2>.编译过程: v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件 v-show只是简单的基于css切换 <3>.编译条件: v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为false时,并不做操作,直到为true才渲染 v-show 由false变为true的时候不会触发组件的生命周期 v-if由false变为true的时候,触发组件的beforeCreate、create、beforeMount、mounted钩子,由true变为false的时候触发组件的beforeDestory、destoryed方法 <4>.性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗
37. vue中父组组件通信的方式有哪几种?
父子组件之间 <1>.父组件传递数据给子组件 使用自定义属性 v-bind <2>.子组件传递数据给父组件 使用自定义事件 v-on 兄弟组件之间 <1>.子1传父 父再传子2 $emit() <2>.事件总线 vue $on <3>.vuex state getters 其他扩展 <1>.ref / $refs 这种方式也是实现父子组件之间的通信 ref:这个属性用在子组件上,它的用用就指向了子组件的实例,可以通过实例来访问组件的数据和方法 <2>.依赖注入(provide / inject) 这种方式就是vue中依赖注入,该方法用于 父子组件之间 的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方式来进行传值。就不用一层一层的传递数据了。 provide和inject是vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。 provide 钩子用来发送数据或方法。 inject钩子用来接收数据或方法
38. Vue生命周期?
38.1 单个组件的生命周期
Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。
beforeCreate(创建前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
created**(创建后)** :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到
$el
属性。beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。
beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,
this
仍能获取到实例。destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive
独有的生命周期,分别为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 activated
钩子函数。
38.2. Vue 子组件和父组件执行顺序
加载渲染过程:
1.父组件 beforeCreate
2.父组件 created
3.父组件 beforeMount
4.子组件 beforeCreate
5.子组件 created
6.子组件 beforeMount
7.子组件 mounted
8.父组件 mounted
更新过程:
1.父组件 beforeUpdate
2.子组件 beforeUpdate
3.子组件 updated
4.父组件 updated
销毁过程:
1.父组件 beforeDestroy
2.子组件 beforeDestroy
3.子组件 destroyed
4.父组件 destoryed
39. data为什么是一个函数而不是一个对象?
new Vue({ data(){ return { } } }) 因为组件中的data如果是一个对象的话, 对象A的值发生改变,对象B的值也会跟着改变 (产生这样的原因这是两者共用了同一个内存地址,即data是存在Vue函数的原型对象中,多个实例共用同一个原型对象) 但是,组件中的data是一个函数的话,不会产生这样的问题(因为函数返回的对象内存地址并不相同) 这样的好处是,不会受到其他实例对象数据的污染. 组件中data必须为函数,目的是为了防止多个组件对象之间共用一个data,产生数据污染。 //假设是对象代码实现: function Component() { } Component.prototype.data={_id:100}; let c1=new Component(); c1.data._id=100; let c2=new Component(); c2.data._id=200; console.log(c1.data);//200 console.log(c2.data);//200 //假设是函数 正解不会互相影响 function Component() { this.data=this.data(); } Component.prototype.data=function(){ return {_id:100} }; let c1=new Component(); c1.data._id=100; let c2=new Component(); c2.data._id=200; console.log(c1.data);//100 console.log(c2.data);//200
40. 浅谈一下Vuex?
Vuex 是 Vue 官方配套的数据管理仓库,该仓库可以用来管理 Vue 项目中所有的公共数据,以及操作公共数据的方法。 vuex 共有5大核心,分别为: 1. state:state 用来保存项目中的公共数据,等同于组件中的 data; 2. getters:getters 用来保存从 state 数据身上得到的新数据,等同于组件中的 computed;(*) 3. mutations:mutations 用来保存所有同步的修改 state的方法(唯一修改 state 的途径); 4. actions:actions 用来保存所有异步的方法; 5: modules 这个主要是为了模块化处理拆分,较大的项目如果全都写在index.js文件里面会显得臃肿凌乱 它可讲多给状态进行抽离到对应的Js文件里面 使用modules最后进行合并,方便后期的维护。
41.浏览器刷新后vuex中的状态消失如何解决?
41.1 为什么说刷新页面vuex的数据会丢失
刷新页面vuex的数据会丢失属于正常现象,因为JS的数据都是保存在浏览器的堆栈内存里面的,刷新浏览器页面,以前堆栈申请的内存被释放,这就是浏览器的运行机制,那么堆栈里的数据自然就清空了。
41.2 第一种方法用sessionStorage
将接口返回的数据保存在vuex的store里,也将这些信息也保存在sessionStorage里。注意的是vuex中的变量是响应式的,而sessionStorage不是,当你改变vuex中的状态,组件会检测到改变,而sessionStorage就不会了,页面要重新刷新才可以看到改变,所以应让vuex中的状态从sessionStorage中得到,这样组件就可以响应式的变化。
在store文件夹里面的js文件 示例如下
const state = { authInfo: JSON.parse(sessionStorage.getItem("COMPANY_AUTH_INFO")) || {} } const getters = { authInfo: state => state.authInfo, } const mutations = { SET_COMPANY_AUTH_INFO(state, data) { state.authInfo = data sessionStorage.setItem("COMPANY_AUTH_INFO", JSON.stringify(data)) } } //actions 模块里无需使用 sessionStorage export default { namespaced: true, state, getters, mutations, //actions, }
其实这里还可以用 localStorage,但是它没有期限;所以常用的还是sessionStorage,当浏览器关闭时会话结束。
41.3第二种方法是 vuex-along
示例如下
(1)安装 vuex-along
npm install vuex-along --save
(2)在store文件夹里的index.js中引入vuex-along并配置相关代码
import Vue from 'vue' import Vuex from 'vuex' import indexOne from "./modules/indexOne" import VueXAlong from 'vuex-along' Vue.use(Vuex) const store=new Vuex.Store({ strict: false, modules:{ indexOne }, plugins: [VueXAlong({ name: 'along', //存放在localStroage或者sessionStroage 中的名字 local: false, //是否存放在local中 false 不存放 如果存放按照下面session的配置配 session: { list: [], isFilter: true } //如果值不为false 那么可以传递对象 其中 当isFilter设置为true时, list 数组中的值就会被过滤调,这些值不会存放在seesion或者local中 })] }) export default store;
41.4第三种方法是 vuex-persistedstate
示例如下
(1)安装 vuex-persistedstate
npm install --save vuex-persistedstate
(2)在store文件夹里的index.js中引入vuex-persistedstate并配置相关代码
import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import createPersistedState from "vuex-persistedstate" Vue.use(Vuex) const store = new Vuex.Store({ modules: { user }, plugins: [createPersistedState({ storage: window.sessionStorage, reducer(val) { return { // 只储存state中的user user: val.base } } })] }) export default store;
41.5第四种方法是 vuex-persist
示例如下
(1)安装 vuex-persist
npm install --save vuex-persist or yarn add vuex-persist
(2)在store文件夹里的index.js中引入vuex-persist并配置相关代码
import Vue from 'vue' import Vuex from 'vuex' import indexOne from "./modules/indexOne" import VuexPersistence from 'vuex-persist' Vue.use(Vuex) const vuexLocal = new VuexPersistence({ storage: window.localStorage }) const store = new Vuex.Store({ strict: false, modules:{ indexOne, }, plugins: [vuexLocal.plugin] }) export default store;
其实解决此问题的方法有很多,基本上都是要借助于localStorage或者sessionStroage来存放。
42.vue中如何缓存组件
使用kepp-alive缓存组件: `<keep-alive>`组件中还提供了以下属性: - include - 字符串或正则表达式。只有名称匹配的组件会被缓存。 - exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。 注意1: 被包含在`<keep-alive>`中的组件,会比普通组件多出两个生命周期函数:`activated`与`deactivated`。 - `activated`:在`<keep-alive>`中组件被激活时调用,即进入组件时触发; - `deactivated`:在`<keep-alive>`中组件停用时调用,即离开组件时触发。 注意2: 使用`<keep-alive>`包裹组件,会将组件的数据保留在内存中。如果希望每次进入页面时都获取最新的数据,需要在`activated`阶段获取数据,承担原来`created`钩子函数中获取数据的任务。 1.你在开发中遇到过什么问题,又是怎么去解决的? 2.你有经历过组件缓存失效的问题吗? <1>. 后面有了解了一下keep-alive缓存的原理,后面就解决了,实际上keep-alive缓存组件前,底层会有一个判断,先获取包裹的第一个节点,然后判断该节点是否属于组件节点(是不是一个组件),如果是就会进行缓存,如果不是就不会进行缓存。 <keep-alive> <div></div> <router-view/> </keep-alive> <keep-alive> <div> <router-view/> </div> </keep-alive> <keep-alive> <Child1/> <Child2/> </keep-alive>
43.为什么Vuex要通过mutations修改state,而不能直接修改
在vuex中可以直接修改state数据吗?可以,但是官方不推荐 因为state是实时更新的,mutations无法进行异步操作,而如果直接修改state的话是能够异步操作的,当你异步对state进行操作时,还没执行完,这时候如果state已经在其他地方被修改了,这样就会导致程序存在问题了。所以state要同步操作,通过mutations的方式限制了不允许异步。 ------------------------------------------------------------------------------------- 更加详细的解读: vuex 不但是一种全局修改数据的工具,更重要的意义是在于把跨组件的交互,拆分为基于状态管理的处理。 使用如 vuex 本身就是希望基于这样一个数据结构的约定,使得项目代码更加直观和简单。每一个 state 树对应整个项目的一个数据,每一个 mutation 执行完成后都可以更新到一个新的状态。这样 devtools 就可以打个 snapshot 存下来。可以尝试开着 devtool 调用一个异步的 action,可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看 mutation 对应的状态。 所以,通过commit 提交 mutation 的方式来修改 state 时,vue的调试工具能够记录每一次 state 的变化,这样方便调试。但是如果是直接修改state,则没有这个记录,那样做会使状态不受我们管控。如果是多个模块需要引用一个state的,然后每个人可能由不同的人开发,如果直接修改值,可能会造成数据的混乱,Mutation 也记录不到,到时候调试会有麻烦。但是通过 mutation 修改 state 这样设计,就有个统一的地方进行逻辑运算去修改。如果逻辑有变动,修改一个地方就可以了。
44. Computed 和 Watch 的区别
对于Computed:
它支持缓存,只有依赖的数据发生了变化,才会重新计算
不支持异步,当Computed中有异步操作时,无法监听数据的变化
computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。
computed:{ getTotal(){ return this.num*this.price } } ===> computed:{ getTotal:{ get(){ return this.num*this.price } set(val){ this.num=val; } } } Object.defineProperty(user,fieldName,{ set(val){==123 } get(){ } }) user[field] user[field]="123";
对于Watch:
它不支持缓存,数据变化时,它就会触发相应的操作
支持异步监听
监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
当一个属性发生变化时,就需要执行相应的操作
监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
immediate:组件加载立即触发回调函数
deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch。
num watch:{ getNum(){ //监听 非深度监听 } getStudent:{ handler(newValue,oldValue){ }, deep:true, immediate:true }, }
总结:
computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
运用场景:
当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API )。
45. Computed 和 Methods 的区别
可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
methods:{
getTotal(){
return this.num*this.price;
}
}
不同点:
computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值;
method 调用总会执行该函数。
46.v-model 是如何实现的,语法糖实际是什么?
动态绑定了 input 的 value 指向了 message 变量,并且在触发 input 事件的时候去动态把 message 设置为目标值
<input v-model="sth" /> // 等同于 <input v-bind:value="message" v-on:input="message=$event.target.value" > //$event 指代当前触发的事件对象; //$event.target 指代当前触发的事件对象的dom; //$event.target.value 就是当前dom的value值; //在@input方法中,value => sth; //在:value中,sth => value;
47.$nextTick原理
$nextTick:根据官方文档的解释,它可以在 DOM 更新完毕之后执行一个回调函数,并返回一个 Promise // 修改数据 vm.msg = "Hello"; // DOM 还没有更新 Vue.nextTick(function() { // DOM 更新了 }); 这样设计你的目的是为了保证在下一次事件循环开始时,才开始更新 DOM,保证当数据变化完成之后才开始进行渲染,避免出现重复渲染的情况。 1. 比如:vue中改变data中的数据的时候 ,立即获取对应标签的dom对象,是无法获取对应的新值的,如果想要获取到渲染之后得到新值,可以通过手动调用 nexTick,在回调函数中即可获得。
48. 过滤器的作用,如何实现一个过滤器
过滤器是用来过滤数据的,在Vue中使用`filters`来过滤数据,`filters`不会修改数据,而是过滤数据,改变用户看到的输出 例如,在显示金额,给商品价格添加单位: 实现过滤器代码如下: <li>商品价格:{{item.price | filterPrice}}</li> filters: { filterPrice (price) { return price ? ('¥' + price) : '--' } }
49 .webpack是什么?
webpack是一个模块化打包工具,将不同的资源和文件,进行打包,合并在一个文件里。 作用: <1>.进行重新加载编译。实际就是将浏览器不认识的语法编译成浏览器认识的语法。比如less 编译成css,TS 编译成ES6 语法 转成 ES5等等。 <2>.减少io请求。通常我们在请求后,会返回一个html到浏览器。这时,我们如果打开控制台,就会发现在html页面通过script,link等标签引用的静态资源,浏览器会再次发出请求去获取这些资源。但是webpack的打包,将所有的静态资源都合并好了,减少了io请求。
50. 路由的hash和history模式的区别
Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。
50.1 hash模式
简介: hash模式是开发中默认的模式,它的URL带着一个#,例如:http://www.abc.com/#/vue,它的hash值就是#/vue
。
特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
50.2 history模式
简介: history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
特点: 当使用history模式时,URL就像这样:http://abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
51.vue中如何进行dom操作
1.在事件里面默认参数event.target就是对应绑定vue事件标签的dom对象 <button @click="add">点击</button> add(event){ event.target 就是button的dom对象 } 2.或者在自定义指定中,凡是使用自定义的指令对应的标签,在自定义指令对应的回调函数中的参数就是对应标签的dom对象 <button v-test>点击</button> import Vue from 'vue'; Vue.directive('test', { // 当被绑定的元素插入到 DOM 中时 inserted: function (el) { // ... 指令的功能 el就是dom对象 } }) 3.vue框架里面是可以写js代码,所以也可以 使用document.getElementById("bid") 的方式获取标签对应的dom对象 <button id="bid">点击</button>
52.vue2和vue3的区别
1. vue2和vue3双向数据绑定原理发生了改变 vue2的双向数据绑定是利用了es5 的一个API Object.definepropert() 对数据进行劫持 结合发布订阅模式来实现的。vue3中使用了es6的proxyAPI对数据进行处理。 相比与vue2,使用proxy API 优势有:defineProperty只能监听某个属性,不能对全对象进行监听;可以省去for in 、闭包等内容来提升效率(直接绑定整个对象即可);可以监听数组,不用再去单独的对数组做特异性操作,vue3可以检测到数组内部数据的变化。 2.生命周期不同 vue2 --------------- vue3 beforeCreate Created beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroyed -> onBeforeUnmount destroyed -> onUnmounted activated -> onActivated deactivated -> onDeactivated vue3没有beforeCreate 以及created函数 3.vue2 面向对象的思维,内部使用this去调用vue中的数据以及函数比较固定(比如methods里面只能定义函数,data里面是用来定义数据的),而vue3采用组合式api的写法,比较自由,定义数据和函数位置没有要求。 4.vue3属于按需加载,而vue2不是
53.url渲染页面的过程(了解)
1、首先浏览器应用程序会解析出url中的域名 2、根据域名获取对应的ip地址,会先从缓存中找,如果没有则从本机域名解析 host,还没有的话DNS就会层层解析。 3、拿到ip地址后,浏览器向服务器发起三次握手 4、握手建立后,就开始组装http请求报文,发送报文 5、服务器收到请求后,开始解析,并返回数据 6、浏览器收到服务器的响应,通过解析把页面呈现给用户
54. MVVM
MVVM 分为 Model、View、ViewModel: - Model代表数据模型,数据和业务逻辑都在Model层中定义; - View代表UI视图,负责数据的展示; - ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作; Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
MVVM(Model-View-ViewModel)是一种软件架构设计模式,它是一种简化用户界面的事件驱动编程方式。
在MVVM架构中,是不允许数据和视图直接通信的,只能通过ViewModel来通信,而ViewModel就是定义了一个Observer观察者。ViewModel是连接View和Model的中间件。
ViewModel能够观察到数据的变化,并对视图对应的内容进行更新。(vue2通过Object.defineProperty实现)
ViewModel能够监听到视图的变化,并能够通知数据发生变化。(内部封装了一些dom监听 通过指令实现)
到此,我们就明白了,Vue.js就是一个MVVM的实现者,它的核心就是实现了DOM监听与数据绑定。
55. Vue-Router 的懒加载如何实现
const router = new VueRouter({ routes: [ { path: '/list', component: () => import('@/components/list.vue') } ] }); 有没有其他的方式? 有的,也可以通过 require引入 const router = new Router({ routes: [ { path: '/list', component: resolve => require(['@/components/list'], resolve) } ] })
56. Vue-Router导航守卫
导航,指的是路由正在发生改变,即一个路由想要跳转到另一个路由,在这个过程中,可以设置“导航守卫”来拦截这个跳转,拦截下来后,可以做一些处理,例如用户登陆状态的判断,处理完成后,导航守卫可以来决定是否继续这次跳转。 导航守卫的分类: 1. 全局守卫:作用于项目中的所有路由; 2. 路由独享守卫:只作用于当前路由; 3. 组件内的守卫:作用于与该组件相关的路由;
56.1、全局前置守卫
在全局守卫的分类中,router
路由实例对象提供了一个“全局前置守卫”。
因此,我们可以在 /src/router/index.js
文件中,通过 router
对象来设置一个全局前置守卫:
// 创建路由实例对象 const router = new VueRouter({ // ... }) // 全局前置守卫(在 router 创建之后设置) router.beforeEach((to, from, next) => { to.path//客户端访问的路由路径 next() //放行 1.不需要token认证的路由(/login ,addUser ,...) // 放行2. 需要token认证 但是 token认证通过 next("/login")//1.一般未登录,认证失败 跳到登录界面 });
参数说明:
to:对象,想要去到的路由对象;
from:对象,即将离开的路由对象;
next:函数,控制路由跳转的方法;
56.2、前置路由独享守卫
路由独享守卫中也有一个前置守卫:beforeEnter
。
[ { path: '/home', name: 'Home', // 路由名称 component: Home, beforeEnter: (to, from, next) => { } } ]
上例代码就表示,当用户进入 /home
路由时会被 beforeEnter
拦截下来。首页的用户身份判断也可以用这个守卫来实现。
56.3、离开组件时的守卫
组件内的守卫有离开组件时触发的守卫:
export default { beforeRouteLeave(to, from, next) { } }
我们可以在用户准备离开组件前做一些处理,最后通过 next
来控制用户的离开。
57.谈谈对路由的理解
前端路由,用来管理浏览器的 URL 和页面组件之间的关系。我们可以通过对前端路由的配置,来决定哪一个 URL 显示哪一个组件。
58.Vue-router跳转和location.href有什么区别
使用 window.location.href= 来跳转,简单方便,但是刷新了页面; 而vue-router跳转只是组件之间的切换,属于局部刷新 。
59. params和query的区别
你能跟我说一下vue路由是怎么传参的吗?
//<1>.配置动态路由: const routes = [ { path: '/home', // ... 其他配置 children: [ { // 动态路由:下面的 :id 用于匹配浏览器路径中动态的内容,其中 id 可以任意命名 path: 'studentsUpdate/:id', name: 'StudentsUpdate', component: StudentsUpdate, } ] } ] //<2>.在标签中使用动态路由传递数据id <router-link to="/home/studentsUpdate/001"></router-link> //<3>.组件里面获取动态路由传递过来的数据 this.$route.params.id 获取动态路由传递的数据 //<4>.什么是动态路由? 动态路由,指的是路由的路径中,有一部分内容是动态不固定的。但是不管路由中动态部分如何变化,他们指向的都是同一个路由。 比如 /home/studentsUpdate/001 /home/studentsUpdate/002 使用动态路由是的上述2个url指向同一个路由 总结:params是用来获取动态路由传递过来的参数 <router-link to="/home/studentsUpdate?id=1">修改</router-link> <router-link :to="{ path: '/home/studentsUpdate', query: { id: 1 } }">修改</router-link> created() { //1.使用query获取路由跳转传递过来的参数 console.log(this.$route.query); }
60. $router,$route区别?
$ router是用来操作路由的,比如进行路由的跳转 $ route是用来获取路由信息的,比如获取动态路由传递过来的参数,或者一个路由跳转到另一个路由传递过来的参数.
开发环境,测试环境,正式环境
61.为什么vue中的v-if和v-for不建议一起用?
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。这意味着 v-if 将分别重复运行于 每个 v-for 循环中,即先运行 v-for 的循环,然后在每一个 v-for 的循环中,再进行 v-if 的条件对比,会造成性能问题,影响速度。这一点是 Vue 官方的风格指南中明确指出的一点。
62.混入mixins
混入对象,就是用来提取多个组件中相同的 JS 逻辑代码。混入对象本身,拥有和组件对象一样的属性,包括 data、computed、watch 以及生命周期函数等。当我们的项目中,多个组件之间有一些 data、methods 等代码相同,就可以用混入对象提取出来。 例如,组件 A 和 组件 B 有相同的 data 数据和 methods 方法,我们就可以将相同的这一部分属性从组件中提取出来,设置在混入对象中:
63.开发中,如何做防止订单重复提交
1.使用防抖解决用户如果多次点击按钮,只会触发最后一次,解决重复提交的问题(防止恶意提交) 2.点击提交后,按钮设置一个加载动画,当响应完成之后,结束加载动画(因为按钮加载的过程中,无法触发点击事件,从而不会重复提交) (响应过慢,用户无意多次发出请求) 等等
64. Vue的性能优化有哪些
(1)编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
(2)SEO优化
预渲染
服务端渲染SSR
(3)打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化
(4)用户体验
骨架屏
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
65.对虚拟DOM的理解?
可以看作是一个使用javascript模拟DOM结构的树形结构。对于我们开发者而言呢,操作DOM结构是非常昂贵的,它改动后,整个容器中的内容中的内容都要重新渲染一遍,就相当于“推倒重来”,如果项目相对来说比较复杂的话,是非常影响性能的。vdom就可以很好地解决这个问题。 虚拟DOM其实就是用一个原生的JS对象去描述一个DOM节点,最终可以通过一系列操作使这棵树映射到真实环境上。 相当于在js与DOM之间做了一个缓存,利用diff算法对比新旧虚拟DOM记录到一个对象中按需更新, 将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。 总结: 虚拟dom实质上就是使用js原生对象去模拟/描述实际的dom节点,因为直接操作dom节点,当数据多次变化的时候,就会出现多次渲染,严重影响效率,而使用虚拟dom,会将多次dom修改的结果一次性更新到页面(因为对象去模拟的),减少渲染次数,不仅如此,虚拟dom会利用diff算法对比新老节点,没有发生变化的节点不会重复渲染。
66.less或者sass相比css有什么优势?
<1>.less或者sass支持嵌套 <2>.less或者sass支持变量定义 <3>.less或者sass支持“模板函数”(自己定义的名称。。。),比如有些CSS需要做兼容前缀的话,你可以这样使用一个模板函数定义一下,调用的时候传入正常值就可以了。会自动生成前缀的CSS等
67.如何做响应式布局或者如何适配?
<1>.利用对属性设置百分比来适配不同屏幕,注意这里的百分比是相对于父元素; 能够设置的属性有 width、height、padding、margin,其他属性比如 border、font-size 不能用百分比设置的。 <2>.使用媒体查询 (CSS3 @media 查询)利用媒体查询设置不同分辨率下的css 样式,来适配不同屏幕. <3>.rem 响应式布局,当前页面中元素的rem 单位的样式值都是针对于html 元素的font-size 的值进行动态计算的 <4>.vw 响应式布局,根据 PSD 文件宽度或高度作为标准,元素单位 px 转换为 vw 或 vh. <5>.flex 弹性布局,利用 flex 属性来适配不同屏幕 vw 解释: 在css中,vw是一个长度单位,一个视口单位,是指相对于视口的宽度;视口会被均分为100单位的vw,则1vw等于 视口宽度的1%,比如浏览器的宽度为1920px,则“1vw=1920px/100=19.2px”。
68.如何让一个盒子在水平方向和垂直方向都居中?
html代码如下:
固定样式:
方法一:利用定位(常用方法,推荐)
.parent{
position:relative;
}
.child{
position:absolute;
top:50%;
left:50%;
margin-top:-50px;
margin-left:-50px;
}
方法一的原理就是定位中心点是盒子的左上顶点,所以定位之后我们需要回退盒子一半的距离。
方法二:利用margin:auto;
.parent{
position:relative;
}
.child{
position:absolute;
margin:auto;
top:0;
left:0;
right:0;
bottom:0;
}
方法三:利用display:table-cell;
.parent{
display:table-cell;
vertical-align:middle;
text-align:center;
}
.child{
display:inline-block;
}
方法四:利用display:flex;设置垂直水平都居中;
.parent{
display:flex;
justify-content:center;
align-items:center;
}
方法五:计算父盒子与子盒子的空间距离(这跟方法一是一个道理);
计算方法:父盒子高度或者宽度的一半减去子盒子高度或者宽的的一半。
.child{
margin-top:200px;
margin-left:200px;
}
方法六:利用transform
.parent{
position:relative;
}
.child{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}
方法七:利用calc计算
.parent{
position:relative;
}
.child{
position:absolute;
top:calc(200px);//(父元素高-子元素高)÷ 2=200px
let:calc(200px);//(父元素宽-子元素宽)÷ 2=200px
}
69.css Sprites(雪碧图或者精灵图)有什么优缺点?
优点: <1>.CSS Sprites能很好地减少网页的http请求,从而大大的提高页面的性能,这是CSS Sprites最大的优点,也是其被广泛传播和应用的主要原因; <2>.CSS Sprites能减少图片的字节; <3>.CSS Sprites解决了网页设计师在图片命名上的困扰,只需对一张集合的图片命名,不需要对每一个小图片进行命名,从而提高了网页制作效率。 <4>.CSS Sprites只需要修改一张或少张图片的颜色或样式来改变整个网页的风格。 缺点: <1>.图片合并麻烦:图片合并时,需要把多张图片有序的合理的合并成一张图片,并留好足够的空间防止版块出现不必要的背景。 <2>.图片适应性差:在高分辨的屏幕下自适应页面,若图片不够宽会出现背景断裂。 <3>.图片定位繁琐:开发时需要通过工具测量计算每个背景单元的精确位置。 <4>.可维护性差:页面背景需要少许改动,可能要修改部分或整张已合并的图片,进而要改动css。在避免改动图片的前提下,又只能(最好)往下追加图片,但这样增加了图片字节
70.你知道哪些css3新特性和h5新特性?
css3: <1>.新增了很多选择器 比如过滤选择器nth-of-type... 表单选择器:checked... <2>.新增了很多新属性 比如背景颜色渐变,linear-gradint(...) 盒子阴影:box-shadow 文字阴影:text-shadow 边框圆角:border-radius 动画transform(平移 translate,旋转scale ,缩放rotate ,倾斜skew),自定义动画 animation等等 h5: <1>.提供语义化标签:header(头部),aside(侧边栏),article(文章),footer(底部),nav(导航)等 <2>.提供表单的新属性,比如 required 防止提交空数据 placeholder 给输入框提供相应的输入提示,pattern设置输入表单的正则匹配规则,不满足规则则无法提交。 <3>.提供一些新表单元素 比如 type="number" 数字输入框 type="email" 邮箱表单 type="range"数字滑块以及时间的表单 type="date" type="time" type="datetime-local" 等等
71 扩展问题 :如何画一个倒三角
<div></div> .h{ width: 0px; height: 0px; border: 50px solid transparent; border-top-color: red; }
72. 响应式原理?
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
72.1 第一步:Observe(观察者):
Observer把对象的每个属性通过Object.defineProperty转换为带有getter和setter的属性,把一个普通的对象转换为响应式的对象。当访问或者设置属性时,vue就可以做些其他的操作。而且它会递归遍历对象的所有属性,以完成深度的属性转换。在组件生命周期中,这件事发生在beforeCreate之后,created之前。
注意:由于遍历时只能遍历到对象的当前属性,因此无法监测到将来动态增加或删除的属性。
因此vue提供了$set和$delete两个实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性。
对于数组,vue会更改它的隐式原型,之所以这样做,是因为vue需要监听那些可能改变数组内容的方法.
结论:总之,Observer的目标,就是要让一个对象属性的读取、赋值,内部数组的变化都要能够被vue感知到。
72.2 第二步:Dep( 发布者)
vue会为响应式对象中的每个属性、对象本身、数组本身创建一个Dep
实例,当读取响应式对象的某个属性时,它会进行依赖收集。当改变某个属性时,它会派发更新。
72.3 第三步:Watcher(订阅者)
而这里又出现了一个问题,当某个函数执行的过程中,用到了响应式数据,但响应式数据是无法知道是哪个函数在使用自己的。因此,vue没有直接执行函数,而是把函数交给一个叫做watcher的对象,每个函数执行时都应该创建一个watcher,通过watcher去执行。watcher会设置一个全局变量,让全局变量记录当前负责执行的watcher等于自己,然后再去执行函数。在函数的执行过程中,如果发生了依赖记录,那么Dep就会把这个全局变量记录下来,表示有一个watcher用到了我这个属性。
每一个vue组件实例,都至少对应一个watcher,该watcher中记录了该组件的render函数。
watcher首先会把render函数运行一次以收集依赖,于是那些在render中用到的响应式数据就会记录这个watcher。当数据变化时,Dep就会通知该watcher,而watcher将重新运行render函数,从而让界面重新渲染同时重新记录当前的依赖。
72.4 第四步:Scheduler(调度器)
现在还剩下最后一个问题,就是Dep通知watcher之后,如果watcher执行中运行对应的函数,就有可能导致函数频繁运行,从而导致效率低下。因此,watcher收到派发更新的通知后,实际上不是立即执行对应函数,而是把自己交给一个叫调度器的东西。调度器维护一个执行队列,该队列同一个watcher仅会存在一次,队列中的watcher不是立即执行,它会通过一个叫做nextTick的工具方法,把这些需要执行的watcher放入到事件循环的微队列中。nextTick的具体做法是通过Promise完成的。也就是说,当响应式数据变化时,render函数的执行是异步的,并且在微队列中。
代码层面上理解:
vue响应式原理,采用数据劫持结合发布者-订阅者这种设计模式实现双向绑定的,其中数据劫持vue2是利用Object.defineProperty()实现getter,setter监听,而vue3采用代理实现监听。 Vue底层 通过Observe这个对象实现对data中的每一个属性利用遍历对象递归的方式实现监听(Object.defineProperty()),不仅如此,还提供发布者对象(Dep),每一个属性都会对应一个dep对象,对应的属性被调用的时候,会给dep发布者对象添加订阅(Watcher),属性多个标签中使用,会生成多个watcher对象(depend()),当数据发生变化的时候 setter函数触发,发布者对象会调用dep.notify() 通知所有的Watcher 更新数据。
73. 虚拟DOM的解析过程
虚拟DOM的解析过程:
首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。
74. 为什么要用虚拟DOM
(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能
下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶
真实DOM∶ 生成HTML字符串+重建所有的DOM元素
虚拟DOM∶ 生成vNode+ DOMDiff+必要的dom更新(新老节点对比有差异更新,修改变化的内容,不会替换整个dom)
Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,依然可以给你提供过得去的性能。
(2)跨平台
Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。
75. 虚拟DOM真的比真实DOM性能好吗
首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
正如它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。
76. DIFF算法的原理
diff算法可以看作是一种对比算法,对比的对象是新旧虚拟Dom。顾名思义,diff算法可以找到新旧虚拟Dom之间的差异,但diff算法中其实并不是只有对比虚拟Dom,还有根据对比后的结果更新真实Dom。 ---- 先根据真实DOM生成一颗 virtual DOM ,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode ,然后 Vnode 和 oldVnode 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 oldVnode 的值为 Vnode 。 diff的过程就是调用名为 patch 的函数,记录新老节点的差异。 当数据发生改变时,set方法会让调用 Dep.notify 通知所有订阅者Watcher,订阅者就会调用 patch 给真实的DOM打补丁,更新相应的视图。
77.v-for 中为什么要使用key
由于更新状态的时候,如果没有key,新节点与老节点 之间进行一一对比,找到对应节点的差异 ,通过patch记录差异,如果使用唯一key,对比当前新节点与 拥有相同的key的节点进行对比,将差异记录下来使用key能够提高程序的效率。
78. 为什么不建议将 数组的index(0,1,2,3)作为key
因为数组中的index都是以0开头,如果往数组中插入元素,或者删除元素,都会导致数组的index重新排列,还是从0,1,。。。,所以使用index作为key很保证key的唯一性,不建议使用index作为key
79.关于小程序中涉及的生命周期(小程序)
79.1、小程序生命周期
小程序的生命周期函数是在 app.js
里面调用的,App(Object)
函数用来注册一个小程序。接受一个 Object
参数,指定其小程序的生命周期回调。
生命周期 参数 描述 onLaunch 无 在小程序初始化完成时执行(全局只触发一次) onShow 无 在小程序启动,或从后台进入前台时执行 onHide 无 在小程序隐藏,即从前台进入后台时执行 onError 无 在小程序发生脚本错误,或者 api 调用失败时触发,会带上错误信息 onPageNotFound 无 在组件实例被从页面节点树移除时执行
// app.js App({ onLaunch() { //获取用户信息 } });
79.2、页面的生命周期
页面的生命周期方法可以直接定义在 Page
构造器的第一级参数中。
生命周期 参数 描述 onLoad options 在页面加载时执行 onReady 无 在页面初次渲染完成时执行 onShow 无 在页面显示时执行 onHide 无 在页面隐藏时执行 onUnload 无 在页面卸载时执行
Page({ onLoad() { } })
79.3、组件的生命周期
组件的生命周期方法可以直接定义在 Component
构造器的第一级参数中。
自小程序基础库版本 2.2.3 起,组件的的生命周期也可以在 lifetimes
字段内进行声明(这是推荐的方式,其优先级最高)。
组件的生命周期如下表所示:
生命周期 参数 描述 最低版本 created 无 在组件实例刚刚被创建时执行 1.6.3 attached 无 在组件实例进入页面节点树时执行 1.6.3 ready 无 在组件在视图层布局完成后执行 1.6.3 moved 无 在组件实例被移动到节点树另一个位置时执行 1.6.3 detached 无 在组件实例被从页面节点树移除时执行 1.6.3 error Object Error
每当组件方法抛出错误时执行 2.4.1
Component({ // 2.2.3 之前 created() { }, // 2.2.3 以后 lifetimes: { created() { }, attached() { } } })
80. 小程序页面跳转
80.1 代码示例:
标签方式: <navigator url="/pages/list/list?id=100" open-type=""></navigator> 代码方式: wx.navigateTo({url: '/pages/list/list'});
open-type:跳转方式
方法说明:
方法 说明 wx.navigateTo()
保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。(可以传参) wx.switchTab()
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 ,无法传参。 wx.reLaunch()
关闭所有页面,打开到应用内的某个页面 (既可以传参,也可以跳转tabBar) wx.redirectTo()
关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。 wx.navigateBack()
关闭当前页面,返回上一页面或多级页面。
80.2 页面之间传值
页面之间进行跳转的时候,可以通过 URL 路径来传递参数:
<navigator url="/pages/list/list?id=1&name=海信"></navigator>
在页面的 onLoad 生命周期中接收其他页面传递的参数:
Page({ onLoad(options) { console.log(options)//{id:1,name:"海信"} } })
81.关于自定义组件(小程序uni-app+原生+PC)
1.消息弹框 2.swiper实现轮播图 3.头部导航或者底部公共部分 4.侧边栏scroll-view 5.下拉列表 (有些下拉列表里面的内容都是重复的) 等等 6.还有分页组件的封装 7.时间,日历组件的封装
82. 小程序中的常见事件
82.1 事件介绍
重点记住: tap:点击事件 longpress:长按事件
82.2、绑定事件
小程序中提供了两种方式来绑定事件:
bind
:普通事件绑定;catch
:阻止事件冒泡;
示例:
<button bindtap="事件处理函数名"></button> <button catchtap="事件处理函数名"></button>
事件处理函数,指的就是在页面对应的 .js
文件中设置的方法,例如:
Page({ handleClick() { console.log('事件触发时执行的函数') } })
82.3、事件传值
小程序中要实现事件方法的传值,有两种方式:
自定义属性
data-
:mark
(2.7.1 新增):
82.3.1 data-
我们可以在绑定事件的元素身上,通过 data-
的方式添加自定义属性,来设置要传递的参数。
<viewc data-id="{{item.cat_id}}"></view>
在事件处理函数中可以通过事件对象的 currentTarget
属性来接收:
Page({ handleClick(event) { console.log(event.currentTarget.dataset.id); } })
82.3.2 mark
我们也可以在绑定事件的元素身上,通过 mark
属性设置要传递的参数。
<view mark:id="{{item.cat_id}}"></view>
在事件处理函数中可以通过事件对象的 mark
属性来接收:
Page({ handleClick(event) { console.log(event.mark.id); } })
83. 你做的项目小程序大小有多大,有没有大小限制
1.小程序单个包,限制不超过2M 2.如果超过2M,需要做分包处理 3.分包,包的数量是没有限制的,但是,每个包的大小不超过2M,分包方式开发,总的大小不超过20M 扩展提问为什么微信小程序要对大小做限制? 因为小程序的设计初衷就是用完即走,轻量化应用。不建议将它当成一个app来设计,所以考虑到启动速度等方面所以才对大小做了限制。
84.如果超时了,如何解决?
1.使用第三方库尽量按需加载, 2.使用官方推荐的WeUI组件库,或者其他的一些轻量级的框架,uView开发等等 3.图片压缩或上传服务器 4.分包加载
85.讲一下分包
1.首先在app.json里面全局配置里面配置分包:(subpackages 属性和pages同级)
我在根路径创建了两个分包,分别是home
和pay
"pages": [ "pages/index/index", "pages/user/index", ], "subpackages":[ { "root":"template/home", //分包的根路径,彼此之间不可以重复 "name":"home", //分包的标识名字,用于预加载分包时用 "pages":[ //分包的路径(不预加载的时候,用户进入分包路径才会加载分包资源) "shop_house/shop_house" ], "independent":false //是否独立分包(可以不加载主包就独立加载的包,独立分包不能当做全局资源) }, { "root":"template/pay", "name":"pay", "pages":[ "pay_house/pay_house" ], "independent":false } ],
2.创建文件
template
是存放分包的文件夹。home
和pay
这两个文件夹分别是template
的子文件夹,里面可以放图片等其他资源和页面。 注意:静态资源哪个模块的就放哪个包下,不要共享,共享就会变成主包里的资源。这也就是为什么不直接把images
文件放在template
根路径的原因
86.跨域
1.jsonp:利用script标签天生就支持的跨域的特点来实现请求跨域,默认不支持POST,只能支持GET 2.ajax里面设置: withCredentials:true,可以实现跨域 get/post方式都可以处理 3.配置代理服务器: 详解: 如果采用webpack来搭建我们项目。webpack默认开启一个服务器8000,浏览器来访问8000服务器得到前端的静态资源,里面会有ajax异步请求。在请求3000这台服务器,出现跨域。 将所有的请求都让8000来处理,如果发现你的请求不是获取静态资源,访问后端接口。webpack中配置一台proxy代理服务器,代理服务器转发请求。
87.项目开发上线流程
项目: 1.从0开始? 在线点餐的软件 2.中途接手 3.项目后期维护 第一步 项目的确定 经过产品/UI (1个) 竞品分析==>参考美团,饿了吗类似软件 测试 1个测试 前端: 2-3 后端(都非常懂运维)/运维 (4-6个) ---------------------------------------- 对项目反复讨论 讨论各个功能实现、及注意事项等 确定最终的开发版本 产品会把最终版本整理成文档(书面说明),并确定大概上线日期 第二步 项目的分工 UI → 按原图设计 测试 → 测试用例 前后端商议如何开发,最终生成一个接口文档(效果以文档内容为准) 接口文档包括 功能如何实现、 接口地址、 请求方式 请求参数 返回数据的格式 后端 → 做自己的开发 前端 (业务,需求说明,安排任务的 产品经理) 会根据能力进行划分 功能分配 页面分配 技术分配 第三步 项目整合 多人协同开发 需要使用版本控制进行项目整合 通过版本控制完成开发 第四步 项目测试 Bug系统提交项目 使用版本控制修改测试出来的Bug 修改完成后进行下一步 那个功能 等级 状态 描述 用户模块的修改 严重 未解决 .... 添加 一般 ..... 第五步 构建项目 npm run build 如less解析 合并文件 压缩等 第六步 上线 测试再次进行测试后 进行产品上线 运维负责上线任务
88.项目打包指令
vue的项目打包指令: npm run build 打包项目 yarn run build 打包项目 package.json scripts:{ }
89.轮播图
element中使用 el-carousel组件实现轮播图 ant-design框架中使用 Carouse组件实现轮播图的 小程序中使用swiper组件实现轮播图的 自己有没有写过轮播图(利用定时器) 原生 自定义组件
90.启动项目的常见指令
npm run serve 启动vue2项目的指令 npm run dev 启动vue3项目指令 npm start/yarn start启动 react项目指令 package.json scripts:{ }
PC端来说 绝大部分使用ui框架开发 小程序(限制打包大小): 纯原生开发, ui框架 , 混合式开发(ui框架+原生) 占据多数 原生开发:(用户体验更好) 真正意义上的按需加载 框架开发:(开发体验更好)
91 React组件通信方法
父子组件:父组件通过props传递数据给子组件,props只读无法修改,满足单向数据流。
父组组件:子组件要传递数据给父组件,通过父组件传递事件函数的方式,子组件调用。
跨组件通信:事件总线,yarn add events包。import {EventEmmiter} from "events"
redux来完成组件的通信。
其他方式(了解)
父组件要得到子组件的数据:ref也可以实现效果。在子组件身上ref,父组件通过ref得到节点。能够子组件state或者函数。父组件直接调用。文件上传的时候。
父组件 export default () => { const parentRef = useRef(); function focusHander() { console.log(parentRef); parentRef.current.focus(); parentRef.current.value = '哈哈'; } return ( <> <ForwardChild ref={parentRef} /> <button onClick={focusHander}>获取焦点</button> </> ) } 子组件 function Child(props, parentRef) { console.log(props); return ( <> <input type="text" ref={parentRef} /> </> ) } /** * 使用forwardRef将ref直接传递进去 */ let ForwardChild = forwardRef(Child);
跨组件通信:Context,在React中有一个Context的内容,是一个容器,可以把要通信的内容放在这个容器里面,其他组件可以任意的从这个组件中获取数据。
const ThemeContext = React.createContext('light'); // 创建 class App extends React.Component { render() { // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。 // 无论多深,任何组件都能读取这个值。 return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // 中间的组件再也不必指明往下传递 theme 了。 function Toolbar(props) { return ( <div> <ThemedText /> </div> ); } class ThemedText extends React.Component { // 指定 contextType 读取当前的 theme context。 // React 会往上找到最近的 theme Provider,然后使用它的值。 static contextType = ThemeContext; render() { return <div>{this.context}</div>; } }
92.React生命周期函数有哪些?
一共有三个阶段:
装载阶段:组件会在DOM树上面进行渲染
更新阶段:(运行阶段),组件发生变化,页面需要重新渲染
卸载阶段:组件从DOM移除的时候
挂载阶段
执行组件的构造函数。实例化组件
render函数会执行,能够完成页面的渲染
DOM会执行更新,VNode对比,
componentDidMount,页面已经挂载完毕,你可以发送异步请求,也可以获取DOM
更新阶段
shouldComponentUPdate这个钩子函数会在数据更新之前执行,可以接受两个参数,nextProps,一个nextState。你可以根据自己的数据解构来判断是否返回true,一旦true执行render函数完成更新,false,停止执行页面更新
componentDidUpdate:页面更新完毕过后,执行的钩子函数。
卸载阶段
会执行componentWillonMount,证明组件正在进行销毁,你可以在里面进行资源回收。定时器销毁,对象事件解除绑定,路由绑定事件都可以在这个地方完成。
面包屑导航,我们需要监听路由产生变化,会给history绑定事件,一旦离开这个组件将事件卸载。
你需要执行倒计时动画离开这个组件的时候,关闭定时器。
PureComponent默认已经实现了对引用类型浅对比,只对比对象的引用。
93.在React中网络请求应该在那个钩子函数中执行?
对于网络请求最好componentDidMount中去操作。
为啥在componentDidMount中调用。因为在componentWillMount里发送异步请求能够更在的拿到结果,这种说法是错误的。在网络请求的过程中,componentWillMount执行和componentDidMount进行对比,他们两个执行效率了差别跟网络请求事件相比,不值一提。可能1-2毫秒。
在react中获取到数据过后,需要调用setState,我们最好是保证组件已经挂载完毕后,在来执行setState数据
94. React 事件机制
<div onClick={this.handleClick.bind(this)}>点我</div>
React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。
1.event.preventDefault() preventDefault() 方法阻止元素发生默认的行为(例如,当点击提交按钮时阻止对表单的提交) 2.event.stopPropagation() event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。尤其是点击事件。
实现合成事件的目的如下:
合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
95.高阶组件
高阶组件时React里面提出的一个概念,本质上是一个高阶函数
95.1.高阶函数
高阶函数:英文叫Higher-order function。JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
function show(fun){ } show(function(){ })
回调函数:也是高阶函数,将一个函数传递给另外一个函数,并且在内部基于事件、其他方式来调用了这个函数。
ajax({ url:"", success(){ } })
95.2 高级组件
高阶组件的英文是 Higher-Order Components,简称为 HOC;
官方的定义:
高阶组件是参数为组件,返回值为新组件的函数
;
import {useState} from "react"; //1.函数组件一定是一个函数 //2.但是函数不一定就是函数组件 //3.函数组件 内部必须return (jsx语法) //4.return 的新组件与 函数的参数组件 父子组件关系 export function wrapperComponent(MyComponent) { return ()=>{ const [page,setPage]=useState({current:1,pageSize:10}); const handlerPage=(current,pageSize)=>{ page.current=current; page.pageSize=pageSize; setPage({...page}); } return ( <div> <h1>蜗牛学苑</h1> <MyComponent page={page} handlerPage={handlerPage}/> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> </div> ) } } import {wrapperComponent} from "../utils"; function User(props) { const changePage=()=>{ props.handlerPage(200,300); } return ( <div> <p>{JSON.stringify(props.page)}</p> <button onClick={changePage}>点击</button> User </div> ); } export default wrapperComponent(User);//新组建 是当前User的父组件
95.3 高阶组件应用分析
需要代码重用时, react如果有多个组件都用到了同一段逻辑, 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复.
96.小程序登录授权
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。
96.1 说明
调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
96.2 注意事项
会话密钥
session_key
是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。临时登录凭证 code 只能使用一次
97.React的事件和普通的HTML事件有什么不同?
区别:
对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
对于事件函数处理语法,原生事件为字符串,react 事件为函数;
react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用
preventDefault()
来阻止默认行为。
合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:
兼容所有浏览器,更好的跨平台;
将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。
方便 react 统一管理和事务机制。
事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。
98.React如何判断什么时候重新渲染组件?
组件状态的改变可以因为props
的改变,或者直接通过setState
方法改变。组件获得新的状态,然后React决定是否应该重新渲染组件。只要组件的state发生变化,React就会对组件进行重新渲染。这是因为React中的shouldComponentUpdate
方法默认返回true
,这就是导致每次更新都重新渲染的原因。
当React将要渲染组件时会执行shouldComponentUpdate
方法来看它是否返回true
(组件应该更新,也就是重新渲染)。所以需要重写shouldComponentUpdate
方法让它根据情况返回true
或者false
来告诉React什么时候重新渲染什么时候跳过重新渲染。
99.React受控组件和非受控组件
不管是受控还是非受控我们都是为了获取到页面上值(表单的内容)
1、受控组件 受控组件依赖于状态,受控组件的修改会实时映射到状态值上,受控组件必须要在表单上使用onChange事件来绑定对应的事件 2、非受控组件(操作原生节点,获取到节点的内容,手动赋值给指定state对象) React中使用的虚拟dom来进行数据更新操作,原则上我们不需要操作dom。但是有时候你需要使用原生dom来实现代码效果。react并没有将这条路堵死非受控组件,操作原生节点,获取到节点的内容,手动赋值给指定state对象
100.为什么 useState 要使用数组而不是对象
useState 的用法:
const [count, setCount] = useState(0)
可以看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢?
这里用到了解构赋值,所以先来看一下ES6 的解构赋值:
数组的解构赋值
const foo = [1, 2, 3]; const [one, two, three] = foo; console.log(one); // 1 console.log(two); // 2 console.log(three); // 3
对象的解构赋值
const user = { id: 888, name: "xiaoxin" }; const { id, name } = user; console.log(id); // 888 console.log(name); // "xiaoxin"
看完这两个例子,答案应该就出来了:
如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净
如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值
下面来看看如果 useState 返回对象的情况:
// 第一次使用 const { state, setState } = useState(false); // 第二次使用 const { state: counter, setState: setCounter } = useState(0)
这里可以看到,返回对象的使用方式还是挺麻烦的,更何况实际项目中会使用的更频繁。
总结:useState 返回的是 array 而不是 object 的原因就是为了降低使用的复杂度,返回数组的话可以直接根据顺序解构,而返回对象的话要想使用多次就需要定义别名了。
101.同时引用这三个库react.js、react-dom.js和babel.js它们都有什么作用?
- react:包含react所必须的核心代码 - react-dom:react渲染在不同平台所需要的核心代码 - babel:将jsx转换成React代码的工具
102. 对 React Hooks 新特性
useState: useEffect:模拟react中的生命周期 //1.模拟componentDidMount useEffect(()=>{ console.log("componentDidMount"); },[]); //2.模拟componentDidUpdate useEffect(()=>{ console.log("componentDidUpdate",count); },[count]) //3.模拟componentWillunMount useEffect(()=>{ // 清除函数,只有在组件销毁的时候才会执行 return ()=>{ console.log("componentWillunMount"); } },[]) React-useMemo/useCallback:react中的计算属性 useRef:非受控表单 useNavigate(v6)/useHistory(v6一下):获取到history对象执行路由跳转 (通过js代码实现路由跳转) useLocation:可以获取url地址信息search传参(通过state传递过来的数据) useParams:接受路由传递的参数(动态路由)
103.对 React 和 Vue 的理解,它们的异同
相似之处:
都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库
都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板。
都使用了Virtual DOM(虚拟DOM)提高重绘性能
都有props的概念,允许组件间的数据传递
都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性
不同之处:
1)数据流
Vue默认支持数据双向绑定,而React一直提倡单向数据流
2)虚拟DOM
Vue2.x开始引入"Virtual DOM",消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。
Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。
3)组件化
React与Vue最大的不同是模板的编写。
Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。
React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。
具体来讲:React中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 完组件之后,还需要在 components 中再声明下.
4)监听数据变化的实现原理不同
Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。
5)高阶组件
react可以通过高阶组件(Higher Order Components-- HOC)来扩展,而vue需要通过mixins来扩展。
原因高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不采用HOC来实现。
6)构建工具
两者都有自己的构建工具
React ==> Create React APP
Vue ==> vue-cli
7)跨平台
React ==> React Native
Vue ==> Weex
104.Redux运行原理
1. 获取到仓库的数据 直接将react componet组件和store仓库链接。直接获取仓库的数据 2. 修改仓库的数据 在组件中需要派发aciton通知对象,给reducer,reducer会根据通知对象来决定如何对数据进行修改。store仓库里面的数据发生变化,页面就自动更新
105. Redux 和 Vuex 有什么区别,它们的共同思想
(1)Redux 和 Vuex区别
vuex: 1.5个属性实现数据共享 ,本身可恶意用来定义异步任务的(actions中) 2.在组件中通过mutations中定义的同步函数直接修改state数据(通过commit()去调用mutations中的同步函数) redux: 1.3个重要的属性 ,本身不能定义异步任务 必须使用第三方的插件(redux-saga) 2.在组件中不能直接修改仓库中的数据,需要根据通知,找到对应的reducer,然后修改数据 3.组件中都可以直接使用数据仓库中的数据
Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutations函数里改变state值即可
Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutations函数->store改变(vue检测到数据变化自动渲染)
通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;
(2)共同思想
单—的数据源
变化可以预测
本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。
106.Redux如何进行异步请求
redux本身是无法执行异步请求,但是redux开放了中间件加载的功能。你可以用第三方的中间件来配合redux完成异步请求。
比如:redux-saga这个中间件使用generator编程+迭代器来完成异步请求。
redux-saga这个中间件封装generator编程。介绍到常用的一些api
put、call、takeEvery
call:发送异步请求的函数,通过call来调用底层会默认执行异步函数,但是执行完成得到成功结果,next(res)返回。yeild后面接收到成功结果
put:通过调用put函数来派发请求到reducer,put底层默认可以获取disptach。
takeEvery:用于创建一个监听,一旦监听到页面上dispatch type名字是generator里面定义的名字,进入saga发送异步请求
redux-thunk也可以发送异步请求。采用await和asycn的方式,开发更加简单,功能更加简单
107.React-Router的实现原理
客户端路由实现的思想:
基于 hash 的路由:通过监听
hashchange
事件,感知 hash 的变化改变 hash 可以直接通过 location.hash=xxx
基于 H5 history 路由:
改变 url 可以通过 history.pushState 和 resplaceState 等,会将URL压入堆栈,同时能够应用
history.go()
等 API监听 url 的变化可以通过自定义事件触发实现
react-router 实现的思想:
基于
history
库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render
108.ReactRouter里面提供Link和a标签有什么区别?
最终在DOM中渲染过后的结果就算一个a标签。
Link这个标签虽然最终渲染是a标签,但是你在使用过程中React会默认接管a标签的默认行为。只会触发跟Route相关的路径进行匹配。使用a的跳转方式,可以跳转到任何地方。
109. React-Router如何获取URL的参数和历史对象?
(1)获取URL的参数
get传值
路由配置还是普通的配置,如:'admin'
,传参方式如:'admin?id='1111''
。通过this.props.location.search
获取url获取到一个字符串'?id='1111'
可以用url,qs,querystring,浏览器提供的api URLSearchParams对象或者自己封装的方法去解析出id的值。
动态路由传值
路由需要配置成动态路由:如path='/admin/:id'
,传参方式,如'admin/111'
。通过this.props.match.params.id
取得url中的动态路由id部分的值,除此之外还可以通过useParams(Hooks)
来获取
通过query或state传值
传参方式如:在Link组件的to属性中可以传递对象{pathname:'/admin',query:'111',state:'111'};
。通过this.props.location.state
或this.props.location.query
来获取即可,传递的参数可以是对象、数组等,但是存在缺点就是只要刷新页面,参数就会丢失。
(2)获取历史对象
如果React >= 16.8 时可以使用 React Router中提供的Hooks
import { useHistory } from "react-router-dom"; let history = useHistory();
2.使用this.props.history获取历史对象
let history = this.props.history;