ref
的使用ref
接受一个原始值,返回一个具有响应式的对象,对象有一个value属性,其值就是所传递的原始值。
ref是做的一个拷贝关系,修改对象msg的值,不会影响对象obj,视图会发生变化
。
import { ref } from "vue";
let obj = { name: "你好", age: 16 };
let msg = ref(obj.name);
console.log(msg.value); // 你好
let arr = ref([]) // ref 可以是任何类型 也可以是对象
function change1() {
msg.value = "世界";
//修改msg1数据的时候必须要加 .value 渲染数据的时候就不用加了
console.log(obj, msg.value);
// {name: "你好", age: 16} '世界'
// 数据此时没改变 但是页面的数据改变了
}
change1();
return {
obj,
msg,
}
如果给dom上加
ref
就是当前的dom元素
import {ref} from 'vue'
export default {
name: "HomeNew",
setup(p, { emit }) {
const target = ref(null);
// 懒加载
console.log(target);
return {
target,
};
},
};
toRef
的使用toRef
用来给抽离响应式对象中的某一个属性,并把该属性包裹成ref对象,使其和原对象产生链接
toRef
是做的一种引用关系,修改msg2的值,会影响对象msg,但视图不会发生变化
setup(){
let msg = { name: 'zs', age: 16 }
let msg2 = toRef(msg, 'name')
console.log(msg2.value) // zs
function change2() {
msg2.value = 'ww'
console.log(msg, msg2.value) // {name: "ww", age: 16} ww
//此时 msg.ww 数据变了 但是视图上的是不会变的
}
change2()
return { msg2,change2 }
}
toRefs
的使用toRefs用来把响应式对象转换成普通对象,把对象中的每一个属性,包裹成ref对象
toRefs就是toRef的升级版,只是toRefs是把响应式对象进行转换,其余的特性和toRef无二
setup(){
let msg = { name: 'zs', age: 16 }
let msg3 = toRefs(msg)
console.log(msg) // { name:ref, age:ref } ref代指ref对象
function change3() {
msg3.name.value = 'zl'
msg3.age.value = 20
console.log(msg, msg3) // {name: "zl", age: 20} { name:ref, age:ref }
}
change3()
return { msg3, change3 }
}
这样写模板中直接写入 {{ list }} 就可以了 , 不用 {{ obj.list }},修改数据的时候还是 obj.list = 'aaa'
import { reactive, toRefs } from "vue";
setup() {
let obj = reactive({
list: [],
newS: [],
moods: [],
});
return { ...toRefs(obj) };
}
ref、toRef、toRefs 都可以将某个对象中的属性变成响应式数据
ref
的本质是拷贝,修改响应式数据,不会影响到原始数据,视图会更新
toRef、toRefs
的本质是引用,修改响应式数据,会影响到原始数据,视图不会更新
toRef
一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
toRefs
接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef
执行
以下是 支付宝支付
业务场景介绍:
H5移动端支持微信支付 [ 微信支付分为微信内支付(JSAPI支付官方API)和微信外支付(H5支付官方API)] && 支付宝支付 [手机网站支付转 APP 支付 官方API ]
订单生成逻辑:前端请求后端提交订单,后端去和微信或者支付宝对接生成订单(后续支付都是这个逻辑进行的对接)
一、移动端微信支付,vue中如何玩?
在移动端微信支付分为微信内支付和微信外支付。
1.在订单组件中选择支付方式之后在支付页面先去判断是否是在微信内:
//判断是否微信
is_weixn(){
var ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger'){
return true;
} else {
return false;
}
},
2.触发立即支付方法,根据微信内外的不同请求后端不同的接口,如果是微信外支付非常简单了~
3.【微信外支付】下面先看微信外支付,官方文档也写的很清楚,后端返回一个url地址,前端的工作就是拿到这个url地址进行跳转就可以了,看一下2-3步代码:
handelPay() {
if(this.wechatpayType == 'wxpay'){
// console.log("微信内支付")
let data={
amount:this.number,
}
this.$http.insideWeChatPay(data).then( res => {
if(res.data.code === 200){
this.weChatParameter=res.data.data
// console.log(this.weChatParameter,"微信内支付需要参数")
this.weixinPay()
}else{
Toast({
message: res.data.msg,
position: 'middle',
duration: 1000
});
}
});
} else if(this.wechatpayType == 'wxpay_php'){
// console.log("微信外支付")
let data={
amount:this.number,
}
this.$http.outsideWeChatPay(data).then( res => {
if(res.data.code === 200){
let url=res.data.data
window.location.replace(url) //这里是后端返回的URL直接进行跳转即可完成微信外支付
}else{
Toast({
message: res.data.msg,
position: 'middle',
duration: 1000
});
}
});
}
},
4.在调起支付的页面监听从其他页面返回的事件,进行一些刷新业务逻辑的实现即可,至此微信外支付已经完成。
document.addEventListener("visibilitychange", function() {
//需要的操作
});
5.【微信内支付】微信内支付比起微信外支付稍微复杂一点,但是也不难,(3步骤代码里面已经请求支付方式接口拿到了微信内支付所需要的参数)根据官方API
微信内置js对象 WeixinJSBridge,进行开发,至此微信浏览器内支付已经完成
//解决微信内置对象报错
weixinPay(data){
var vm= this;
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', vm.onBridgeReady(data), false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', vm.onBridgeReady(data));
document.attachEvent('onWeixinJSBridgeReady',vm.onBridgeReady(data));
}
}else{
vm.onBridgeReady();
}
},
//微信内置浏览器类,weChatParameter对象中的参数是3.步骤代码中从后端获取的数据
onBridgeReady(){
var vm = this;
var timestamp=Math.round(vm.weChatParameter.timeStamp).toString();
WeixinJSBridge.invoke(
'getBrandWCPayRequest',{
debug:true,
"appId":vm.weChatParameter.appId, //公众号名称,由商户传入
"timeStamp":timestamp, //时间戳,自1970年以来的秒数
"nonceStr":vm.weChatParameter.nonceStr, //随机串
"package":vm.weChatParameter.package,
"signType":vm.weChatParameter.signType, //微信签名方式:
"paySign":vm.weChatParameter.paySign, //微信签名
jsApiList: [
'chooseWXPay'
]
},
function(res){
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
if(res.err_msg == "get_brand_wcpay_request:ok" ){
Toast({
message: '支付成功',
position: 'middle',
duration: 3000
});
vm.number=null
vm.$router.go(-1)
//window.location.href = vm.BASE_URL + 'index.html#/depositResult'
}else{
Toast({
message: '支付失败',
position: 'middle',
duration: 3000
});
}
}
);
},
6.微信内部浏览器支付也可以封装一下,在全局都可以直接调用:
//微信浏览器支付
function wxpay(params,callback){
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady(params,callback), false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady(params,callback));
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady(params,callback));
}
}else{
onBridgeReady(params,callback);
}
}
function onBridgeReady(params,callback){
var that = this
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":params.appId,
"timeStamp":params.timeStamp,
"nonceStr":params.nonceStr,
"package":params.package,
"signType":params.signType,
"paySign":params.paySign
},
function(res){
callback(res)
}
);
}
7.组件中调用微信支付:
this.commonUtils.wxpay(res.data.data,function(payResult){
if(payResult.err_msg == "get_brand_wcpay_request:ok" ){
//执行
}
})
二、移动端支付宝支付,vue中如何玩?
其实支付宝支付也有H5支付和支付宝浏览器支付,这里只做H5支付,因为已经满足了业务需求。
1.支付宝中的H5支付和PC端的一样,主要是后端的工作量,后端完成订单的生成之后返给前端的是form表单,前端只需要负责做页面的跳转即可:
//立即支付按钮
onSubmit() {
if (this.payWay == 1) {
//支付宝支付
this.$router.push({path: '/aliPay', query: {orderId: this.orderId}});
} else if (this.payWay == 2) {
//微信支付,这里跳转到本文的微信支付模块的3.步骤handelPay方法
}
},
2.选择支付宝方式之后进入支付宝承载页面:
当然不想写承载页的还有其他方法调起支付,具体逻辑具体分析,根据不同的业务类型去变通比如:
const div = document.createElement('div');
div.innerHTML = (res.data); //res.data是返回的表单
document.body.appendChild(div);
document.forms.alipaysubmit.submit();
3.进入到支付宝支付页(至此但有一个问题,调起支付后,用户中途取消支付或者点返回键会整个网页一起关闭退出,或者一直在进入支付页面,不知道有没有更好的SEO方案)
vue面试题
核心原理部分
mvc mvvm和mvp的区别?
MVVM 就是 Model-View-ViewModel 的缩写,MVVM 将视图和业务逻辑分开。
View:视图层,Model 数据模型,而 ViewModel 是把两者建立通信的桥梁。
在 MVVM 框架下,View 和 Model 之间没有直接的联系,而是通过 ViewModel 进行交互。View 和 ViewModel 之间以及 Model 和 ViewModel 之间的交互都是双向的,因此 view 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反映到 View 上。可以说它们两者是实时更新的,互相影响。 ViewModel 通过双向数据绑定把 View 层和 Model >层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,因此开发者只需要关注业务逻辑,不需要手动操作 DOM,也不需要关注数据状态的同步问题,这些都由 MVVM 统一管理,
整体看来,MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器>操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也观察不到View,这种低耦合模式提高代码的可重用性。
【优点】
数据源和视图实现了双向绑定,很好的做到了数据的一致性 相比于mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用。
【缺点】
因为使用了dataBinding,增加了大量的内存开销,增加了程序的编译时间,项目越大内存开销越大。 数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范
- Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
- View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
- Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据,也可以将Model的数据用View显示出来
【优点】
耦合性低,方便维护,可以利于分工协作 重用性高
【缺点】
使得项目架构变得复杂,对开发人员要求高
MVP MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的
Vue底层实现原理
vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则-视图(View)会自动更新。这种设计让状态管理变得非常简单而直观
Observer(数据监听器) :
Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher
Compile(指令解析器) :
Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新-视图
Watcher(订阅者) : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
在自身实例化时往属性订阅器(dep)里面添加自己
自身必须有一个update()方法
待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调
vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)
第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
虚拟 DOM,其实就是用对象的方式取代真实的 DOM 操作,把真实的 DOM
操作放在内存当中,在内存中的对象里做模拟操作。当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM
树,将状态全部保存起来,在内存当中模拟我们真实的 DOM 操作,操作完后又会生成一颗 dom 树,两颗 DOM 树进行比较,根据 diff
算法比较两颗 DOM 树不同的地方,只渲染一次不同的地方。
(个人理解)虚拟dom他不并不是真实的 dom
,是根据模板生成一个js对象(使用createElement,方法),根据这个js对象再去生成真实的dom,对复杂的文档DOM结构,提供一种方便的工具,进行最小化的DOM操作
,是可以快速的渲染和高效的更新元素,提高浏览器的性能,
例如
,一个 ul 标签下很多个 li 标签,其中只有一个 li 有变化,这种情况下如果使用新的 ul 去替代旧的 ul,因为这些不必要的
DOM 操作而造成了性能上的浪费,但是如果直接使用虚拟节点覆盖旧节点的话,减少了很多的不必要的 DOM 操作。
我们在渲染页面的时候 会对新的虚拟dom和旧的虚拟dom进行对比
只渲染不同的地方,而不再是像之前只要发生变化,全部的真实dom都要重新渲染,所以提高了渲染的效率。
缺点:首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
diff 算法是一种通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较
diff算法 当data发生改变 会根据新的数据生成一个新的虚拟dom
,新的虚拟dom和旧的虚拟dom进行对比,这个对比的过程就是diff算法,会找到不同地方,只去渲染不同的地方,总的来说就是减少DOM,重绘和回流。
为什么要用虚拟DOM来描述真实的DOM呢?
创建真实DOM成本比较高,如果用
js对象来描述一个dom节点,成本比较低,另外我们在频繁操作dom是一种比较大的开销。所以建议用虚拟dom来描述真实dom。
vue的响应式原理?
什么是响应式,“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
Object.defineProperty怎么用, 三个参数?,有什么作用啊?
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, {})
obj:需要定义属性的对象
prop:需要定义的属性
{}:要定义或修改的属性描述符。
value: "18", // 设置默认值得
enumerable: true, //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了 默认值false
writable: true, // 控制属性可以被修改 默认值false
configurable: true, // 控制属性可以被删除 默认值false
get // 当有人读取 prop 的时候 get函数就会调用,并且返回就是 sss 的值
set // 当有人修改 prop 的时候 set函数就会调用, 有个参数这个参数就是修改后的值
Object.defineProperty 能定义symbol类型吗?
在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定 义key为Symbol的属性的方法之一。
vue2和vue3的响应式原理都有什么区别呢?
vue2 用的是 Object.defindProperty 但是vue3用的是Proxy
Object.defindProperty虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属性进行数据劫持,所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,就能监听到对象的数据变化无法监听到数组的变化,Proxy就没有这个问题,可以监听整个对象的数据变化,所以用vue3.0会用Proxy代替definedProperty。
上面就是一个典型的例子,当我们点击按钮想要根据数组 arr 的下标改变其元素的时候,你会发现 data 中的数据改变了,但是页面中的数据并没有改变。
我会用 this.$set( target, key, value ) 来解决
参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
第一参数时指定要修改的数据 (target)
第二个参数就是你要设置数据的下标或者是属性名
第三个参数就是现在要修改的数据 (重新赋的值)
改变/添加 对象属性的时候:this.$set(data 实例,"属性名(添加的属性名)","属性值(添加的属性值)")
改变/添加 数组属性的时候:this.\$set(data 实例,数组下标,"改变后的元素(添加的元素)")
原因 : vue在创建实例的时候把data深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
为什么要用 this.$set 呢? this.$set是干什么的?
当你发现你给对象加了一个属性,在控制台能打印出来,但是却没有更新到视图上时,也许这个时候就需要用到this.$set()这个方法了,简单来说this.$set的功能就是解决这个问题的啦。官方解释:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi'),你会发现vue官网是vue.set,vue.set的用法
那 Vue.set 和 this.$set 有什么区别 ?
Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上。
是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调从而达到数据和视图同步。
1、 observer 主要是负责对Vue数据进行递归遍历,使其数据拥有get和set方法,当有数据给某个对象值赋值,就触发 setter
就监听到数据的变化了。( 如有变动可拿到最新值并通知订阅者 )
2、compile 指令解析器负责绑定数据和指令解析。
将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数。一旦数据有变动,收到通知,更新视图
3、 订阅者 watcher : Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是
负责数据监听,当数据发生改变,能调用自身的update()方法,并触发Compile中绑定的更新函数
4、实现一个订阅器 dep: 采用发布者订阅者模式,用来收集订阅者的 watcher,对监听器 observer 和订阅者 watcher
进行统一管理
在vue3 中 Vue3是通过Object.define.proxy 对对象进行代理,从而实现数据劫持。使用Proxy的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法 Vue3.0 摒弃了
Object.defineProperty,改为基于 Proxy 的观察者机制探索。 首先说一下 Object.defineProperty 的缺点:
① Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this. s e t ( ) 解决② O b j e c t . d e f i n e P r o p e r t y 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。 V u e 2. X 里,是通过递归 + 遍历 d a t a 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。 ‘ ‘ ‘ ‘ 而要取代它的 P r o x y 有以下两个优点可以劫持整个对象,并返回一个新对象。有多种劫持操作 ( 13 种 ) 补充: P r o x y 用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 m d n P r o x y 是 E S 6 新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。 P r o x y 让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。 ‘ ‘ ‘ ‘ 1 、 v u e 中数组中的某个对象的属性发生变化,视图不更新如何解决 ? O b j e c t . d e f i n e P r o p e r t y 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 t h i s . set()解决 ② Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。 ` ` ` ` 而要取代它的 Proxy 有以下两个优点 可以劫持整个对象,并返回一个新对象。有多种劫持操作(13 种) 补充: Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 mdn Proxy 是 ES6 新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。 ` ` ` ` 1、vue 中数组中的某个对象的属性发生变化,视图不更新如何解决? Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this. set()解决②Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.X里,是通过递归+遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。‘‘‘‘而要取代它的Proxy有以下两个优点可以劫持整个对象,并返回一个新对象。有多种劫持操作(13种)补充:Proxy用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。mdnProxy是ES6新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。Proxy让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。‘‘‘‘1、vue中数组中的某个对象的属性发生变化,视图不更新如何解决?Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。this.set()解决 问题原因:因为 vue
的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。解决方案如下 方案一:利用
this.set(this.obj,key,val) 例:this.set(this.obj,‘k1’,‘v1’) 方案二:就利用
Object.assign({},this.obj)创建新对象 如果是数组就 Object.assign([],this.obj)
如果是对象就 Object.assign({},this.obj)。
灵活的组件应用,高效的数据绑定
渐进式代表的含义是:主张最少——它是一个轻量级框架,只做了自己该做的事,没有做不该做的事
每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。
这里的vue数据驱动的是视图,也就是DOM元素,指的是让DOM的内容随着数据的改变而改变框架的理解
SSR全称Server Side Render
有利于SEO:由于是在服务端,将数据填充进HTML之后再推送到浏览器,所以有利于SEO的爬取
首屏渲染快
SSR的缺点:
开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
更多的服务端负载。
1.性能提升
更小巧,更快速;支持摇树优化。支持 Fragments (支持多个根节点)和跨组件渲染;支持自定义渲染器。
2.API 变动
Vue2使用 选项类型API(Options API) 对比Vue3 合成型API(Composition API)
optionsApi 使用传统api中,新增一个需求,要在data,methods,computed中修改
compositionApi 我们可以更加优雅的组织我们的代码,函数,让我们的代码更加有序的组合在一起
3.重写虚拟 DOM (Virtual DOM Rewrite)
随着虚拟 DOM 重写,减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。
vue3 没有了过滤器
双向数据绑定 从 Object.defineProperty() 变成了 proxy,通过下标修改数组变化了视图数据没发生变化
this.$set() vue3不需要
双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:
可直接监听数组类型的数据变
监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
可直接实现对象属性的新增/删除
setup 函数
3.0新加入了TypeScript以及PWA支持
默认使用懒加载
可以不用加上key
vue3 的watch监听可以进行终止监听
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
相同点 :
都是使用了虚拟dom
组件化开发
父子之间通信单项数据流
都支持服务端渲染
不同点:
reacct 的jsx vue的是 template
数据变化,react 手动 setState vue自动响应式处理 proxy object.DefineProperty
react 单向数据流 ,vue双向数据流
react 的 redux mobx vue 的vuex 。pinia
beforeCreate() 创建前,这个时候data中的数据,还未定义,所以不能使用
created()创建后 最早开始使用 data和methods中数据的钩子函数
beforeMount()挂载前 指令已经解析完毕内存中已经生成dom树,但是尚未挂载到页面中去,此时页面还是旧的。
mounted()挂载后 dom已经渲染完毕,此时页面和内存中都是最新的数据,最早可以操作DOM元素钩子函数
beforeUpdate()更新前 当视图层的数据发生改变会执行这个钩子 内存更新,但是DOM节点还未更新,数据没有与页面同步
updated()更新后 数据更新完成以后触发的方法,DOM节点已经更新
beforeDestroy()即将销毁 data和methods中的数据此时还是可以使用的,可以做一些释放内存的操作
destroyed()销毁完毕 组件已经全部销毁,Vue实例已经被销毁,Vue中的任何数据都不可用
其他三个:
activated 被 keep-alive 缓存的组件激活时调用。
deactivated 被 keep-alive 缓存的组件停用时调用。
errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用
Vue3.0中的生命周期做了一些改动:
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
Update - > onUpdated组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
- vue的实例加载完成是在哪个声明周期完成呢
beforeCreate
- vue的dom挂载完成是在哪个声命周期里呢
mounted
1、created mounted 的区别?
created 模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
2、怎么在created里面操作dom?
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象,而通过this.$nextTick()获取到的值为dom更新之后的值
setTimeout(() => {
console.log(this.$refs.button);
});
3、那 setTimeout this.$nextTick 什么区别呢?
setTimeout 将同步转换为异步 this.$nextTick
this.$nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,
4、this.$nextTick()是宏任务还是微任务啊?
优先是Promise.then方法,是个微任务,这样可以避免多一次队列,进而少一次UI渲染,节省性能
5、a页面跳转到b页面周期执行
页面a----beforeCreate undefined
页面a----created 1
页面a----beforeMount 1
页面a----mounted 1
页面b----beforeCreate undefined
页面b----created 1
页面b----beforeMount 1
页面a----beforeDestroy 1
页面a----destroyed 1
页面b----mounted 1
6、组件 和 页面周期 的执行顺序
- 页面beforeCreate undefined
- 页面created 1
- 页面beforeMount 1
- 组件beforeCreate undefined
- 组件created 5555
- 组件beforeMount 5555
- 组件mounted 5555
- 页面mounted 1
7、父子组件生命周期执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
代码更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
代码销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
代码常用钩子简易版
父create->子created->子mounted->父mounted
8、补充单一组件钩子执行顺序
activated, deactivated 是组件keep-alive时独有的钩子
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured
watch
仅仅是数据发生改变的时候会侦听到;
只是会检测到你写在watch里的那些属性,没写的就不会触发。
updated
执行到它的时候时候是数据发生变化且界面更新完毕;
不能监听到路由数据(例如网址中的参数);
所有的数据发生变化都会调用(消耗性能);
每次触发的代码都是同一个
computed
1、监控自己定义的变量,不用再data里面声明,函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用return返回
6、不要在computed 中对data中的数据进行赋值操作,这会形成一个死循环。
methods不会被缓存:方法每次都会去重新计算结果。methods 方法表示一个具体的操作,主要书写业务逻辑;
使用 methods 方法编写的逻辑运算,在调用时 add() 一定要加“()”,methods 里面写的多位方法,调用方法一定要有()。methods方法页面刚加载时调用一次,以后只有被调用的时候才会被调用。我们在长度框和宽度框的值输入完以后,点击“+” methods 方法调用一次。这里很明显我们采用 methods 会更节省资源。
使用场景?
watch:
1、watch 函数是不需要调用的。
2、重点在于监控,监控数据发生变化的时候,执行回调函数操作。
3、当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch
4、函数名就是你要监听的数据名字
5、监控一些input框值的特殊处理,适合一个数据影响多个数据。
6、数据变化时,执行一些异步操作,或开销比较大的操作
computed:
在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式
一个需要的结果受多个数据影响的时候,比如购物车结算金额(受到很多处的价格结算)。
操作某个属性,执行一些复杂的逻辑,并在多处使用这个结果。
内部函数中多处要使用到这个结果的。
1、监控自己定义的变量,不用再data里面声明,函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用return返回
6、不要在computed 中对data中的数据进行赋值操作,这会形成一个死循环。
可以啊钩子函数中的 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data
已经创建,可以将服务端端返回的数据进行赋值。
在created中最好
能更快获取到服务端数据,减少页面加载时间,用户体验更好;
SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。
mounted 在请求完数据之后需要对 dom 进行操作的时候可以用到
computed 是vue中的计算属性,具有缓存性,当他的依赖于值,发生改变的时候才会重新调用
methods 是没有缓存的,只要调用,就会执行,一般结合事件来使用
watch 没有缓存性 监听data中的属性 属性值只要发生变化就会执行 可以利用他的特性做一些异步的操作
created:dom渲染前调用,即通常初始化某些属性值
mounted:在dom渲染后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作
Vue的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
开启严格模式,仅需在创建 store 的时候传入 strict: true:
const store = new Vuex.Store({
// ...
strict: true
})
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
const store = createStore({
// ...
strict: process.env.NODE_ENV !== 'production'
})
vuex是一个状态管理工具,所谓状态的是就是数据,采用集中式存储管所有组件的状态,是为了结局中大型项目一个数据共享的问题。vuex 他可以将数据保存到本地,数据是响应式的,能够保持数据页面的共享,提高开发效率。
好处:
能够在 vuex 中集中管理共享的数据,易于开发和后期维护 可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发 效率
vuex核心:
state:vuex的基本数据,数据源存放地,用于定义共享的数据。
getter:从基本数据派生的数据,相当于state的计算属性
mutation:提交更新数据的方法,唯一 一个可以操作state 中数据的方法,必须是同步的,第一个参数是state,第二个参数是cmmi传过来的数据
action:action是用来做异步操作的,一般用来发请求,在 action 中写入函数,然后在页面中用dispatch调用,然后在 action 中通过commit 去调用 mutation 通过 mutation 去操作state。
modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理
运行机制:Vuex提供数据(state),来驱动视图(这里指的是Vue组件),视图通过Dispatch派发Action,在Action中可以进一步做一些异步的操作(例如通过ajax请求后端的接口数据),然后通过Commit提交给Mutations,由Mutations去最终更改state。那么为什么要经过Mutations呢?这是因为我们要在Vue调试工具(Devtools)中记录数据的变化,这样可以通过插件去进行进一步的调试。所以说Mutations中只能是纯同步的操作,如果是有异步操作,那么就需要在Actions中进行处理。如果说没有异步操作,那么可以直接由组件进行Commit操作Mutations。
mapState,mapActions,mapMutations,mapGetters
辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的
如何使用:
Import { mapActions, mapGetters, mapMutations, mapState } from ‘vuex’
computed(){ …mapState([‘数据名字’])}
需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件
推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中
模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。方便管理
ES5 的 5 种:Null,undefined,Boolean,Number,String, ES6 新增:Symbol 表示独一无二的值 ES10 新增:BigInt 表示任意大的整数
一种引用数据类型:(本质上是由一组无序的键值对组成)
引用数据类型: Object。包含 Object、Array、 function、Date、RegExp。 JavaScript 不支持创建任何自定义类型的数据,也就是说 JavaScript 中所有值的类型都是上面 8 中之一。
null 和 undefined 的区别?
相同:
在 if 语句中 null 和 undefined 都会转为 false 两者用相等运算符比较也是相等
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
不同:
undefined 代表的含义是未定义,
定义了形参,没有传实参,显示 undefined
一般变量声明了但还没有定义的时候会返回 undefined
对象属性名不存在时,显示 undefined
函数没有写返回值,即没有写 return,拿到的是 undefined
null 代表的含义是空对象。也作为对象原型链的终点
null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
ES10 新增:BigInt 表示任意大的整数
BigInt 数据类型的目的是比 Number 数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用 BigInt,整数溢出将不再是问题。
此外,可以安全地使用更加准确时间戳,大整数 ID 等,而无需使用变通方法。 BigInt 目前是第 3 阶段提案, 一旦添加到规范中,它就是 JS 第二个数字数据类型,也将是 JS 第 8 种基本数据类型:
要创建 BigInt,只需在整数的末尾追加 n 即可。比较:
console.log(9007199254740995n); // → 9007199254740995n
console.log(9007199254740995); // → 9007199254740996
或者,可以调用 BigInt()构造函数
BigInt(“9007199254740995”); // → 9007199254740995n
// 注意最后一位的数字
9007199254740992 === 9007199254740993; // → true
console.log(9999999999999999); // → 10000000000000000
数据类型存储以及堆栈内存是什么
基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。指的是保存在栈内存中的简单数据段;number string 布尔
引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。
引用数据:类型将指针存在栈中,将值存在堆中。 当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,数组 对象
堆(heap)和栈(stack)有什么区别存储机制
栈: 是一种连续储存的数据结构,具有先进后出后进先出的性质。
通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。
堆: 是一种非连续的树形储存数据结构,具有队列优先,先进先出; 每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。
js 数据类型判断,条件分支
if 语句和逻辑运算
所有基本类型中 Boolean 值是 false 的只有 6 个,分别是 : 0 NaN ’ ’ null undefined false 引用类型 Boolean 值全是 true.
if 条件是单个值时,如果是 truly 值,条件成立, 如果是 falsely 值,条件不成立
逻辑运算符以及他们的运算规则?
&& 逻辑与 两边都是 true,才返回 true,否则返回 false
|| 逻辑或 两边只要有一个是 true,就返回 true,否则返回 false
! 逻辑非 用来取一个布尔值相反的值
typeof 对于基本数据类型判断是没有问题的,但是遇到引用数据类型(如:Array)是不起作用
console.log(typeof 2); // number
console.log(typeof null); // object
instanceof
只能正确判断引用数据类型 而不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
constructor 似乎完全可以应对基本数据类型和引用数据类型 但如果声明了一个构造函数,并且把他的原型指向了 Array 的原型,所以这种情况下,constructor 也显得力不从心
console.log((true).constructor === Boolean); // true
console.log((‘str’).constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
console.log((2).constructor === Number); // true
Object.prototype.toString.call() 完美的解决方案,可以通过 toString() 来获取每个对象的类型,
Object.prototype.toString.call()
使用 Object 对象的原型方法 toString 来判断数据类型:
var a = Object.prototype.toString;
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call(‘str’));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));
补充:基本数据类型赋值的时候 赋的是具体的值 引用数据类型传的是地址,一个变另一个跟着变
在 JavaScript 中类型转换有三种情况:
转换为数字(调用 Number(),parseInt(),parseFloat()方法)
转换为字符串(调用.toString()或 String()方法)
转换为布尔值(调用 Boolean()方法) 还有隐式转换 注意:null、undefined 没有.toString 方法
转换为数字
Number():可以把任意值转换成数字,如果要转换的字符串中有不是数字的值,则会返回 NaN
Number(‘1’) // 1
Number(true) // 1
Number(‘123s’) // NaN
Number({}) //NaN
parseInt(string,radix):解析一个字符串并返回指定基数的十进制整数,radix 是 2-36 之间的整数,表示被解析字符串的基数。
parseInt(‘2’) //2
parseInt(‘2’,10) // 2
parseInt(‘2’,2) // NaN
parseInt(‘a123’) // NaN 如果第一个字符不是数字或者符号就返回 NaN
parseInt(‘123a’) // 123
parseFloat(string):解析一个参数并返回一个浮点数
parseFloat(‘123a’)
//123
parseFloat(‘123a.01’)
//123
parseFloat(‘123.01’)
//123.01
parseFloat(‘123.01.1’)
//123.01
let str = ‘123’
let res = str - 1 //122
str+1 // ‘1231’
+str+1 // 124
.toString() ⚠️ 注意:null,undefined 不能调用
Number(123).toString()
//‘123’
[].toString()
//‘’
true.toString()
//‘true’
String() 都能转
String(123)
//‘123’
String(true)
//‘true’
String([])
//‘’
String(null)
//‘null’
String(undefined)
//‘undefined’
String({})
//‘[object Object]’
隐式转换:当+两边有一个是字符串,另一个是其它类型时,会先把其它类型转换为字符串再进行字符串拼接,返回字符串
let a = 1
a+‘’ // ‘1’
0, ‘’(空字符串), null, undefined, NaN 会转成 false,其它都是 true
Boolean()
Boolean(‘’) //false
Boolean(0) //false
Boolean(1) //true
Boolean(null) //false
Boolean(undefined) //false
Boolean(NaN) //false
Boolean({}) //true
Boolean([]) //true
条件语句
let a
if(a) {
//… //这里 a 为 undefined,会转为 false,所以该条件语句内部不会执行
}
隐式转换 !!
let str = ‘111’
console.log(!!str) // true
{}和[]的 valueOf 和 toString 的返回结果?
valueOf:返回指定对象的原始值
对象 返回值
Array 返回数组对象本身。
Boolean 布尔值。
Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function 函数本身。
Number 数字值。
Object 对象本身。这是默认情况。
String 字符串值。
Math 和 Error 对象没有 valueOf 方法。
toString:返回一个表示对象的字符串。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,
toString() 返回 “[object type]”,其中 type 是对象的类型。
({}).valueOf() //{}
({}).toString() //‘[object Object]’
[].valueOf() //[]
[].toString() //‘’
数据类型相比较 objected .is 和=
=== 属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回 false 如果相同再比较大小,不会进行任何隐式转换对于引用类型来说,比较的都是引用内存地址,所以===这种方式的比较,除非两者存储的内存地址相同才相等,反之 false
== 二等表示值相等。判断操作符两边对象或值是否相等类型可以不同,如果两边的类型不一致,则会进行强制类型转化后再进行比较,使用 Number()转换成 Number 类型在进行判断。例外规则,null==undefined,null/undefined 进行运算时不进行隐式类型转换。通常把值转为 Boolean 值,进行条件判断。Boolean(null)=Boolean(undefined)>false=false 结果为 true
Object.is()在===基础上特别处理了 NaN,-0,+0,保证-0 与+0 不相等,但 NaN 与 NaN 相等
==操作符的强制类型转换规则
字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。
‘1’ == 1 // true
‘1’ === 1 // false
NaN == NaN //false
+0 == -0 //true
+0 === -0 // true
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
typeof null 的结果是什么,为什么?
typeof null 的结果是 Object。
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
000: object - 当前存储的数据指向一个对象。
1: int - 当前存储的数据是一个 31 位的有符号整数。
010: double - 当前存储的数据指向一个双精度的浮点数。
100: string - 当前存储的数据指向一个字符串。
110: boolean - 当前存储的数据是布尔值。
有两种特殊数据类型:
undefined 的值是 (-2)30(一个超出整数范围的数字);
null 的值是机器码 NULL 指针(null 指针的值全是 0)
那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样,所以会被判定为 Object。
事件是文档和浏览器窗口中发生的特定的交互瞬间,事件就发生了。
一是直接在标签内直接添加执行语句,
二是定义执行函数。
addeventlistener 监听事件
事件类型分两种:事件捕获、事件冒泡。
事件捕获就是:网景公司提出的事件流叫事件捕获流,由外往内,从事件发生的顶点开始,逐级往下查找,一直到目标元素。
事件冒泡:IE 提出的事件流叫做事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点。
事件流就是,页面接受事件的先后顺序就形成了事件流。
自定义事件,就是自己定义事件类型,自己定义事件处理函数。
事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了
event.stopPropagation() .stop 修饰符
addEventListener(‘click’,函数名,true/false) 默认值为 false(即 使用事件冒泡)true 事件捕获
好处:提高性能,减少了事件绑定,从而减少内存占用
应用场景 在 vue 中事件委托:
我们经常遇到 vue 中 v-for 一个列表,列表的每一项都绑定了@click 处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个 item 的 click 事件委托给父元素的形式来实现
我们在封装这个函数的时候可以用 addEventListener(事件监听)来实现 ,封装的函数有三个参数,第一个是要绑定事件的元素,第二个是要绑定的事件类型,第三个是事件的执行函数。 调用这个函数 就可以实现给某个元素绑定一个事件了。
作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。简单说:函数内部局部作用域,函数外面全局作用域。
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数## 作用域
全局作用域就是 Js 中最外层的作用域,在哪里都可以访问
函数作用域是 js 通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套
Es6 中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。
mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。
mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。
mouseenter 不支持事件冒泡 mouseover 会冒泡
object 的方法
Object.is() 是一种判断两个值是否相同的方法。
语法:Object.is(value1, value2);
参数:value1:要比较的第一个值。value2:要比较的第二个值。
返回值:一个布尔表达式,指示两个参数是否具有相同的值。
将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存
如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,
2.深拷贝:
创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象
3、赋值:
当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象
具体实现看开头的手写系列
1、object.assign()
2、lodash 里面的 _.clone
3、…扩展运算符
4、 Array.prototype.concat
5、 Array.prototype.slice
1、 JSON.parse(JSON.stringify())
2、递归操作
3、cloneDeep
4、Jquery.extend()
数组
数组的方法
1、sort( ):sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组
2、splice( ):向数组的指定 index 处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据
3、pop( ):从尾部删除一个元素 返回被删除掉的元素,改变原有数组。
4、push( ):向数组的末尾追加 返回值是添加数据后数组的新长度,改变原有数组。
5、unshift( ):向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数组。
6、shift( ):从头部删除一个元素 返回被删除掉的元素,改变原有数组。
7、reverse( ): 原数组倒序 它的返回值是倒序之后的原数组
8、concat( ):数组合并。
9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。从数组中截取,如果不传参,会返回原数组。如果只传入一个参数,会从头部开始删除,直到数组结束,原数组不会改变;传入两个参数,第一个是开始截取的索引,第二个是结束截取的索引,不包含结束截取的这一项,原数组不会改变。最多可以接受两个参数。
10、join( ):讲数组进行分割成为字符串 这能分割一层在套一层就分隔不了了
11、toString( ):数组转字符串;
12、toLocaleString( ):将数组转换为本地数组。
13、forEach( ):数组进行遍历;
14、map( ):没有 return 时,对数组的遍历。有 return 时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。
16、every( ):当数组中每一个元素在 callback 上被返回 true 时就返回 true。(注:every 其实类似 filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
17、some( ):当数组中有一个元素在 callback 上被返回 true 时就返回 true。(注:every 其实类似 filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
18、reduce( ):回调函数中有 4 个参数。prev(之前计算过的值),next(之前计算过的下一个的值),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 , 参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素
pop()
尾部弹出一个元素push()
尾部插入一个元素shift()
头部弹出一个元素unshift()
头部插入一个元素sort([func])
对数组进行排序,func 有 2 各参数,其返回值小于 0,那么参数 1 被排列到参数 2 之前,反之参数 2 排在参数 1 之前reverse()
原位反转数组中的元素splice(pos,deleteCount,...item)
返回修改后的数组,从 pos 开始删除 deleteCount 个元素,并在当前位置插入 itemscopyWithin(pos[, start[, end]])
复制从 start 到 end(不包括 end)的元素,到 pos 开始的索引,返回改变后的数组,浅拷贝arr.fill(value[, start[, end]])
从 start 到 end 默认到数组最后一个位置,不包括 end,填充 val,返回填充后的数组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
。
flat 扁平化 将嵌套的数组 “拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。// 参数写的就是代表要扁平到第几层
//1、every()
var arr = [1,56,80,5];
var main = arr.every(n => n > 0);
console.log(main) //输出:true
//2、some()
var arr = [1,-56,80,-5];
var main = arr.some(n => n > 0);
console.log(main) //输出:true
//3、reducer()
var arr = [10,20,30,40]
let result = arr.reduce(function(prev,next,index,arr){
return prev + next;
})
console.log(result); //输出:100
// 4、filter 返回满足要求的数组项组成的新数组
var arr3 = [3,6,7,12,20,64,35]
var result3 = arr3.filter((item,index,arr)=>{
return item > 3
})
console.log(result3) //[6,7,12,20,64,35]
// 5、map 返回每次函数调用的结果组成的数组
var arr4 = [1,2]
var result4 = arr4.map((item,index,arr)=>{
return ${item}
})
console.log(result4)
/[ ‘1’,
‘2’, ]/
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。
//1、Array.from() – Array.of()
var arrayLink = {
“0”:“a”,
“1”:“b”,
“2”:“c”,
length:3
}
var arr = Array.from(arrayLink)
console.log(arr) // 输出: [a,b,c]
console.log(Array.from(“abcdefg”)) //输出:[“a”, “b”, “c”, “d”, “e”, “f”, “g”]
console.log(Array.of(1,2,3,4,5)) //输出: [1, 2, 3, 4, 5]
//2、copyWithin()
var arr = [1,2,3,4,5];
var main = arr.copyWithin(0,3);
console.log(main); //输出:[4,5,3,4,5]
//3、find()
var arr = [1,-5,2,9,-6];
var main = arr.find(n => n < 0);
console.log(main); //输出:-5
//4、fill()
var arr = [“a”,“b”,“c”,“d”];
console.log(arr.fill(7,1,2));//输出:[“a”,7,“c”,“d”]
//5、keys() values() entries()
var arr = [“a”,“b”,“c”,“d”];
for(let index of arr.keys()){
console.log(index);
}
for(let elem of arr.values()){
console.log(elem);
}
for(let [index,elem] of arr.entries()){
console.log(index,elem);
}
//6、includes()
let arr = [12,34,223,45,67]
console.log(arr.includes(45)) //输出:true
[1, 2, NaN].includes(NaN) // true
[1, 2, NaN].indexOf(NaN) // -1
//7、Map
var m = new Map([[‘Michael’, 95], [‘Bob’, 75], [‘Tracy’, 85]]);
m.get(‘Michael’); // 95
//初始化 Map 需要一个二维数组,或者直接初始化一个空 Map。Map 具有以下方法:
var m = new Map(); // 空 Map
m.set(‘Adam’, 67); // 添加新的 key-value
m.set(‘Bob’, 59);
m.has(‘Adam’); // 是否存在 key ‘Adam’: true
m.get(‘Adam’); // 67
m.delete(‘Adam’); // 删除 key ‘Adam’
m.get(‘Adam’); // undefined
//由于一个 key 只能对应一个 value,所以,多次对一个 key 放入 value,后面的值会把前面的值冲掉:
var m = new Map();
m.set(‘Adam’, 67);
m.set(‘Adam’, 88);
m.get(‘Adam’); // 88
//8、Set
//要创建一个 Set,需要提供一个 Array 作为输入,或者直接创建一个空 Set:
var s1 = new Set(); // 空 Set
var s2 = new Set([1, 2, 3]); // 含 1, 2, 3
//重复元素在 Set 中自动被过滤:
var s = new Set([1, 2, 3, 3, ‘3’]);
s; // Set {1, 2, 3, “3”} 注意:数字 3 和字符串’3’是不同的元素
//通过 add(key)方法可以添加元素到 Set 中,可以重复添加,但不会有效果:
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
//通过 delete(key)方法可以删除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
字符串
字符串的方法
1、chartAt( ):返回在指定位置的字符;
2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex 的值相对于从尾部开始的索引
5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
6、substr( ):从起始索引号提取字符串中指定数目的字符;
7、substring( ):提取字符串中两个指定的索引号之间的字符;
8、toLowerCase( ):字符串转小写;
9、toUpperCase( ):字符串转大写;
10、valueOf( ):返回某个字符串对象的原始值;
11、trim( ):删除字符串两边的空格;
12、trimeState 取出开始的空格
13、trimeEnd 去除末尾空格
14、includes(searchString[, position])返回 boolean,判断一个字符串是否包含在另一个字符串中,从 postition 索引开始搜寻,默认 0
15、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
16、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配
17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
18、trim()返回去掉两端空白后的新字符串 还有 trimend trimstart
19、replace() 把指定的字符串替换成为别的字符
超长字符串存储到栈内存中
字符串属于基础类型,所以会觉得字符串是存在栈内存中的,但是要知道,V8 默认栈内存是 984Kib,那如果一个超长字符串 > 984Kib 能装的进栈内存吗?
字符串的内容存于堆内存中,指针存于栈内存中,且相同的字符串指向同一个堆内存地址
新增或者修改字符串后,如果是一个之前不存在的字符串,则新开辟内存空间,如果是已有的,则直接使用已有的内存空间
当我们新建一个字符串时,V8 会从内存中查找一下是否已经有存在的一样的字符串,找到的话直接复用。如果找不到的话,则开辟一块新的内存空间来存这个字符串,并把地址赋给变量。
声明函数的几种方式
function 函数名(参数 1,参数 2,…){ //要执行的语句 }
var func2=function(b){}//函数表达式
var func3=function func4©{}//命名式函数表达式
var func5=(function(n1,n2){})();//立即执行的函数表达式
return function(){ };//作为返回值的函数表达式
Function 构造器
var 变量名 = new Function(“参数 1”,“参数 2”,…,“参数 n”,“函数体”);
立即执行函数
var func5=(function(n1,n2){})();//立即执行的函数表达式 ()()
函数声明与函数表达式的区别
函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。
函数声明要指定函数名,而函数表达式不用,可以用作匿名函数。
函数调用的几种方式
1.直接调用 函数名加上括号 ()
2.函数表达式 变量名()
函数的 length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length 属性将失真。
function fun1(a) { }
function fun2(a, b) { }
function fun3(a, b, c) { }
function fun4(a, b, c, d) { }
function fun5(…args) { }
function fun6(a = 1, b, c, d) { }
console.log(fun1.length) // 1
console.log(fun2.length) // 2
console.log(fun3.length) // 3
console.log(fun4.length) // 4
console.log(fun5.length) // 0
console.log(fun6.length) // 0
立即执行函数(iife)和使用场景
立即执行函数:( function( ){ })( ) 返回值可以为基本数据类型,也能返会任何类型的值。
写法原因:因为在 javascript 里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(), 然后碰到 function 关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。
作用:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。
使用场景: ① 代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。 ② 所有的这些工作只需要执行一次,比如只需要显示一个时间。
③ 需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中, 不会让任何变量泄露成全局变量。
arguments 的对象是什么?
arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收,是一个类似于数组的对象,他有 length 属性,可以 arguments[ i ]来访问对象中的元素, 但是它不能用数组的一些方法。 例如 push、pop、slice 等。arguments 虽然不是一个数组,但是它可以转成一个真正的数组。
取之可以用 展开运算符来 数组和类数组类数组: ① 拥有 length 属性,其它属性(索引)为非负整数;箭头函数里没有 arguments ② 不具有数组所具有的方法; ③ 类数组是一个普通对象,而真实的数组是 Array 类型。
常见的类数组:arguments,document.querySelectorAll 得到的列表,jQuery 对象($(“div”));
在全局的环境下 this 是指向 window 的
普通函数调用直接调用中的 this 会指向 window, 严格模式下 this 会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window
在对象里调用的 this,指向调用函数的那个对象,
在构造函数以及类中的 this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象
方法中的 this 谁调用就指向谁。
箭头函数没有自己的 this,箭头函数的 this 在定义的时候,会继承自外层第一个普通函数的 this
函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。 目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。
1、闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。
3、闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理
4、闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
5、为什么要用闭包:使用场景 : 防抖、节流、函数套函数避免全局污染
闭包原理
函数执行分成两个阶段(预编译阶段和执行阶段)。
1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,
如果已存在“闭包”,则只需要增加对应属性值即可。 2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,
所以内部函数可以继续使用“外部函数”中的变量
利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,
但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。
call、apply、bind 封装与区别
都是来改变 this 指向和函数的调⽤,实际上 call 与 apply 的功能是相同的,只是两者的传参方式不一样,
call ⽅法跟的是⼀个参数列表,
apply 跟⼀个 数组作为参数,call ⽅法和 apply 使⽤后就直接调⽤
bind 传参后不会立即执行,而是返回一个改变了 this 指向的函数,这个函数可以继续传参,且执行,需要类似于 bind()()两个括号才能调⽤。
call 的性能要比 apply 好一点(尤其是当函数传递参数超过 3 个的时候)后期开发 call 多多一点
call 用扩展运算符就可以吧 apply 来代替了
bind 返回的函数可以作为构造函数吗?
不可以,会报错的哦, ERROR > Uncaught TypeError: s is not a constructor
概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 容易理解的概念:Currying 概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。
特点:
① 接收单一参数,将更多的参数通过回调函数来搞定;
② 返回一个新函数,用于处理所有的想要传入的参数;
③ 需要利用 call/apply 与 arguments 对象收集参数;
④ 返回的这个函数正是用来处理收集起来的参数。
作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。
用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。
柯里化函数例子
柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。
我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。我觉得这就是 js 的柯里化函数
// 简单的相加函数
var add = function (x,y) {
return x + y
}
// 调用:
add(1,2)
// 柯里化以后
var add = function (x) { //柯里化函数(闭包)
return function (y) {
return x + y
}
}
add(1)(2)
这样做有什么好处,我得理解是在需要的情况下生成一个中间工具,简化代码,并且清晰代码。
高阶函数只是,将函数作为参数 , 函数的返回值返回值是函数
function higherOrderFunction(param,callback){
return callback(param);
}
new 实际上是在堆内存中开辟一个空间。
① 创建一个空对象,构造函数中的 this 指向这个空对象;
② 这个新对象被执行[ [ 原型 ] ]连接;
③ 执行构造函数方法,属性和方法被添加到 this 引用的对象中;
④ 如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
function _new(){
let target = {}; //创建的新对象
let [constructor,…args] = […arguments];
//执行[[原型]]连接,target 是 constructor 的实例
target.proto = constructor.prototype;
//执行构造函数,将属性或方法添加到创建的空对象上
let result = constructor.prototype;
if(result && (typeof (result) == “object” || typeof (result) == “function”)){
//如果构造函数执行的结构返回的是一个对象,那么返回这个对象
return result;
}
//如果构造函数返回的不是一个对象,返回创建的对象
return target;
}
new 实际上是在堆内存中开辟一个新的空间。首先创建一个空对象 obj,然后呢,
把这个空对象的原型(proto)和构造函数的原型对象(constructor.prototype)连接(说白了就是等于);
然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象,
那就返回这个对象,如果不是,那就返回我们创建的对象。
需要点击每个 a,来。弹出他们的内容
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
const fu = document.getElementById(‘div3’)
bindEvent(fu, ‘click’, function (event) {
// console.log(event.target) // 获取触发的元素
let target=event.target
event.preventDefault() // 阻止默认行为
//过滤符合条件的子元素,主要是过滤掉 加载更多
if(target.nodeName.toLowerCase()===“A”){
alert(target.innerHTML;
}
})
浏览器的 js 具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存。但是这个过程不是实时的,因为 GC 开销比较大并且时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏
内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
比如说:
2、DOM:当原有的 DOM 被移除时,子结点引用没有被移除则无法回收
3、Times 计时器泄露
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域
全局作用域就是 Js 中最外层的作用域
函数作用域是 js 通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套
Es6 中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链。
每个 var 声明的变量,function 声明的函数存在变量提升。let const 不存在变量提升
在 js 中声明之前未定义,会在 js 的最上方会形成一个预解析池,用来存储声明了但没有先定义的变量名
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数 , 简单来说:内部函数访问外部函数的变量这种链式查找的机制被称为作用域链
谈谈 JS 的运行机制
JavaScript 语言的一大特点就是单线程,即同一时间只能做一件事情。
js 代码执行过程中会有很多任务,这些任务总的分成两类:
需要注意的是除了同步任务和异步任务,任务还可以更加细分为 macrotask(宏任务)和 microtask(微任务),js 引擎会优先执行微任务
微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲
染等。
首先 js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
最后可以用下面一道题检测一下收获:
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2);
resolve()
}).then(function() {
console.log(3)
});
process.nextTick(function () {
console.log(4)
})
console.log(5)
第一轮:主线程开始执行,遇到 setTimeout,将 setTimeout 的回调函数丢到宏任务队列中,在往下执行 new Promise 立即执行,输出 2,then 的回调函数丢到微任务队列中,再继续执行,遇到 process.nextTick,同样将回调函数扔到为任务队列,再继续执行,输出 5,当所有同步任务执行完成后看有没有可以执行的微任务,发现有 then 函数和 nextTick 两个微任务,先执行哪个呢?process.nextTick 指定的异步任务总是发生在所有异步任务之前,因此先执行 process.nextTick 输出 4 然后执行 then 函数输出 3,第一轮执行结束。
第二轮:从宏任务队列开始,发现 setTimeout 回调,输出 1 执行完毕,因此结果是 25431
JavaScript 是单线程(js 不走完下面不会走是因为同步)会阻塞 DOM 的解析,因此也就会阻塞 DOM 的加载。所以有时候我们希望延迟 JS 的加载来提高页面的加载速度。
1.把 JS 放在页面的最底部
2.script 标签的 defer 属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
3.是在外部 JS 加载完成后,浏览器空闲时,Load 事件触发前执行,标记为 async 的脚本并不保证按照指定他们的先后顺序执行, 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
4.动态创建 script 标签,监听 dom 加载完毕再引入 js 文件
宏任务和微任务
js 中的一个机制,就是遇到宏任务,先将宏任务放入 eventqueue,然后在执行微任务。
宏任务:setTimeout,setInterval,Ajax,DOM 事件
微任务:Promise async/await
想明白这个机制 就要理解 js 单线程。因为 JS 是单线程语言,只能同时做一件事儿。js 任务需要排队顺序执行,如果一个任务时间过长,后边的任务也会等着。假如,我们在请求一个网址时,图片加载很慢,网页总不能一直卡不出来,
这个时候就可以用异步来解决了,异步的特点不会阻塞代码的执行 ,解决了单线程等待的这个问题
在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
异步和单线程是相辅相成的,js 是一门单线程语言,所以需要异步来辅助。
宏任务 macrotask: 可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到
执行栈中执行)。
常见的宏任务:script, setTimeout, setInterval, setImmediate, I/O, UI rendering。
微任务 microtask(异步): 可以理解是在当前 task 执行结束后立即执行的任务。
线程是最小的执行单元,进程是最小的资源管理单元一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程
内存泄露
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏
比如说:
1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。
2、DOM:当原有的 DOM 被移除时,子结点引用没有被移除则无法回收
3、Times 计时器泄露
JS 预解析(变量提升),它导致了什么问题?
JS 代码在执行前,浏览器会对 js 代码进行扫描,默认的把所有带 var 和 function 声明的变量进行提前的声明或者定义,遵循先解析后使用的原则。 变量提升的表现是,在变量或函数声明之前访问变量或调用函数而不会报错。
原因 JavaScript 引擎在代码执行前有一个解析的过程(预编译),创建执行上线文,初始化一些代码执行时需要用到的对象。 当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性, 它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。
首先要知道,JS 在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。
1.在解析阶段 JS 会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来, 变量先赋值为 undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似, 不过函数执行上下文会多出 this、arguments 和函数的参数。
全局上下文:变量定义,函数声明 函数上下文:变量定义,函数声明,this,arguments
2.在执行阶段,就是按照代码的顺序依次执行。
那为什么会进行变量提升呢?主要有以下两个原因:
1、提高性能
2、容错性更好
(1)提高性能 在 JS 代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,
那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、
不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),
并且因为代码压缩的原因,代码执行也更快了。
(2)容错性更好 变量提升可以在一定程度上提高 JS 的容错性,看下面的代码:
a = 1
var a
console.log(a) //1
如果没有变量提升,这段代码就会报错导致的问题
var tmp = new Date();
function fn(){
console.log(tmp);
if(false){
var tmp = ‘hello nanjiu’;
}
}
fn(); // undefined
在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,
相当于覆盖了外层的tmp,所以打印结果为undefined。
var tmp = 'hello nan jiu';
for (var i = 0; i < tmp.length; i++) {
console.log(tmp[i]);
}
console.log(i); // 13
由于遍历时定义的 i 会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来 13。
总结
解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
声明提升还可以提高 JS 代码的容错性,使一些不规范的代码也可以正常执行
函数是一等公民,当函数声明与变量声明冲突时,变量提升时函数优先级更高,会忽略同名的变量声明
服务端渲染
解释:服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。有了服务端渲染,当请求用户页面时,返回的 body 里已经有了首屏的 html 结构,之后结合 css 显示出来。
优点:
① 首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件;
②SEO(搜索引擎)优化:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本
③ 可以生成缓存片段、节能;
缺点:用户体验较差,不容易维护、通常前端改了部分 html 或者 css,后端也需要改;
使用场景:vue 全家桶或者 react 全家桶,都是推荐通过服务端渲染来实现路由的。
Event Loop Event Queue
在 js 中我们经常需要同时执行很多件任务,例如,定时器,事件。异步数据,而 js 是单线程的原因不能同时进行很多件事情,必须等上一件任务执行完了才会执行下一个,需要通过 Event Loop 来处理很多任务的执行
因为 js 是单线程的,代码执行的时候,将不同的函数执行上下文压入到栈中进行有序的执行,
在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,就是将它挂起,继续执行栈中其他的任务
当同步任务执行完了,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
任务队列分为的宏任务队列和微任务队列,当前的执行栈中执行完,js 引擎会首先判断微任务队列是否有任务可以执行有的话,放到栈中执行。
当微任务队列中的任务执行完了再去判断宏任务中的队列。
为什么会有任务队列呢?
还是因为 javascript 单线程的原因,单线程,就意味着一个任务一个任务的执行,
执行完当前任务,执行下一个任务,这样也会遇到一个问题,就比如说,要向服务端通信,加载大量数据,如果是同步执行,
js 主线程就得等着这个通信完成,然后才能渲染数据,为了高效率的利用 cpu, 就有了同步任务和异步任务之分。
同步和异步的区别?各举一个 Js 中同步和异步的案例?
同步:上一件事情没有完成,继续处理上一件事情,只有上一件事情完成了,才会做下一件事情
异步: 规划要做一件事情,如果是异步事情,不是当前立马去执行这件事情,需要等一定的时间,这样的话,我们不会等着他执行,而是继续执行下面的操作
对于写程序,同步往往会阻塞,没有数据过来,我就等着,异步则不会阻塞,没数据来我干别的事,有数据来去处理这些数据。
同步案例:for 循环语句,alert(),console.log()等 js 大部分都是同步编程
异步案例:所有定时器,ajax 异步请求,所有的事件绑定都是异步;
举例子
同步,就是实时处理(如打电话),比如服务器一接收客户端请求,马上响应,这样客户端可以在最短的时间内得到结果,但是如果多个客户端,或者一个客户端发出的请求很频繁,服务器无法同步处理,就会造成涌塞。
同步如打电话,通信双方不能断(我们是同时进行,同步),你一句我一句,这样的好处是,对方想表达的信息我马上能收到,但是,我在打着电话,我无法做别的事情。
异步,就是分时处理(如收发短信),服务器接收到客户端请求后并不是立即处理,而是等待服务器比较空闲的时候加以处理,可以避免涌塞。
浏览器对象模型(BOM :Browser Object Model)是 JavaScript 的组成之一,它提供了独立于内容与浏览器窗口进行交互的对象,使用浏览器对象模型可以实现与 HTML 的交互。它的作用是将相关的元素组织包装起来,提供给程序设计人员使用,从而降低开发人员的劳动量,提高设计 Web 页面的能力。
window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ;
history : go(参数) , back() , foward() ;
location : herf 属性.
1、window.location.href = ‘你所要跳转到的页面’; 2、window.open('你所要跳转到的页面’); 3、window.history.back(-1):返回上一页 4、window.history.go(-1/1):返回上一页或下一页五、 5、history.go(“baidu.com”);
说出 5 个以上 Math 对象中的成员。
Math.PI 圆周率
Math.floor() 向下取整
Math.ceil() 向上取整
Math.round() 四舍五入版 就近取整
Math.abs() 绝对值
Math.max()/Math.min() 求最大和最小值
Math.random() 获取范围在[0,1)内的随机值
setTimeout 与 setInterval 区别与机制
setTimeout()和 setInterval()经常被用来处理延时和定时任务。
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式
setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,直到 clearInterval 把它清除。
机制:
因为 js 是单线程的。浏览器遇到 setTimeout 和 setInterval 会先执行完当前的代码块,在此之前会把定时器推入浏览器的
待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
window 的 onload 事件和 domcontentloaded
window.onload:当一个资源及其依赖资源已完成加载时,将触发 onload 事件。 document.onDOMContentLoaded:当初始的 HTML 文档被完全加载和解析完成之后, DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。 区别: ①onload 事件是 DOM 事件,onDOMContentLoaded 是 HTML5 事件。 ②onload 事件会被样式表、图像和子框架阻塞,而 onDOMContentLoaded 不会。 ③ 当加载的脚本内容并不包含立即执行 DOM 操作时,使用 onDOMContentLoaded 事件是个更好的选择,会比 onload 事件执行时间更早。
cookies,sessionStorage 和 localStorage 的区别?
cookie:一个大小不超过 4K 的小型文本数据,一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器 cookie 失效,若设置了 时间,cookie 就会存放在硬盘里,过期才失效,每次 http 请求,header 都携带 cookie
localStorage:5M 或者更大,永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除或者 js 代码清除,因此用作持久数据,不参与和服务器的通信
sessionStorage 关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。
location、之 lnavigator 和 history
location 对象存储了当前文档位置(URL)相关的信息,简单地说就是网页地址字符串。使用 window 对象的 location 属性可以访问。
href 会重新定位到一个 URL,hash 会跳到当前页面中的 anchor 名字的标记(如果有),而且页面不会被重新加载
window 对象给我们提供了一个 history 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中) 访问过的 URL。
history.back 可以后退一个网页
history.go 可以前进后退 1 前进 -1 后退
history.forward 前进
window.navigator`对象包含有关浏览器的信息,可以用它来查询一些关于运行当前脚本的应用程序的相关信息
navigator.appCodeName 只读,任何浏览器中,总是返回 ‘Gecko’。该属性仅仅是为了保持兼容性。
navigator.appName 只读,返回浏览器的官方名称。不要指望该属性返回正确的值。
navigator.appVersion 只读,返回一个字符串,表示浏览器的版本。不要指望该属性返回正确的值。
navigator.platform 只读,返回一个字符串,表示浏览器的所在系统平台。
navigator.product 只读,返回当前浏览器的产品名称(如,“Gecko”)。
navigator.userAgent 只读,返回当前浏览器的用户代理字符串(user agent string)
DOM 文档对象模型
DOM 是 document 用来表示文档中对象的标准模型,他是由节点和对象组成的结构集合。在浏览器解析 HTML 标签时,会构建一个 DOM 树结构。
操作说明书
拿到指定节点
var id = document.getElementById(“id”); //返回带有指定 id 的元素
var name = document.getElementByTagName(“li”); //返回带有指## 定标签的元素
var class = document.getElementByClassName(“class”); //返回带有包含执行类名的所有元素节点列表。 创建DOM节点 var node = document.createElement("div"); var attr = document.createAttribute("class"); var text = document.createTextNode("菜呀菜");
node.appendChild(text) //插入新的子节点
node.insertBefore(pre,child) //在 node 元素内 child 前加入新元素 删除DOM节点 node.removeChild(text) //从父元素删除子元素节点 修改DOM节点 node.setAttribute("class","name") //修改设置属性节点 node.replaceChild(pre,child) //父节点内新子节点替换旧子节点
node.innerHtml //获取/替换元素内容
node.parentNode //元素节点的父节点
node.parentElement //元素节点的父元素节点(一般与 Node 节点相同)
node.firstChild //属性的第一个节点
node.lastChild //属性的最后一个节点
node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行)
node.nextElementSibling //节点元素后的兄弟元素节点
node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释)
node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点)
node.childNodes //元素节点的子节点(空格,换行默认为文本节点)
node.children //返回当前元素的所有元素节点
node.nodeValue //获取节点值
node.nodeName //获取节点名字
node.attributes //元素节点的属性节点
node.getAttribute(“name”) //元素节点的某个属性节点
node.style.width = “200px” //设置 css 样式`
常用的 api
offset、client、scroll 的用法?
offset 系列 经常用于获得元素位置 offsetLeft offsetTop
client 经常用于获取元素大小 clientWidth clientHeight
scroll 经常用于获取滚动距离 scrollTop scrollLeft
什么是函数式编程? 命令式编程?声明式编程?
声明式编程:专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。 如:css, 正则表达式,sql 语句,html, xml…
命令式编程(过程式编程) : 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。
如: for()
函数式编程:把运算过程尽量写成一系列嵌套的函数调用。
如 : forEach()
优点:
①iframe 能够原封不动的把嵌入的网页展现出来;
② 如果有多个网页引用 iframe,那么你只需要修改 iframe 的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
③ 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用 iframe 来嵌套,可以增加代码的可重用。
④ 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由 iframe 来解决。
缺点:
① 会产生很多页面不易管理;
②iframe 框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
③ 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理 iframe 中的内容,所以使用 iframe 会不利于搜索引擎优化。
④ 很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
⑤iframe 框架页面会增加服务器的 http 请求,对于大型网站是不可取的。
如何让(a == 1 && a == 2 && a == 3)的值为 true?
" == "操作符在左右数据不一致的时候,会先进行隐式转换,该值意味着不是基本数据类型,
因为如果 a 是 null 或者 undefined、bool 类型都不可能返回 true;可以推测 a 是复杂数据类型。
方法一:数组的 toString 接口默认调用数组的 join 方法,重新 join 方法
let a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3) //true
方法二:利用数据劫持(Proxy/Object.definedProperty)
let i = 1;
let a = new Proxy({},{
i:1,
get:function(){
return () => this.i++
}
});
console.log(a == 1 && a == 2 && a == 3);
为什么 0.1+0.2 ! == 0.3,如何让其相等
在开发过程中遇到类似这样的问题:
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2) // 0.30000000000000004
这里得到的不是想要的结果,要想等于 0.3,就要把它进行转化:
(n1 + n2).toFixed(2) // 注意,toFixed 为四舍五入
复制代码
toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?
计算机是通过二进制的方式存储数据的,所以计算机计算 0.1+0.2 的时候,实际上是计算的两个数的二进制的和
es6 部 i 分面试题
新增了块级作用域(let,const)
提供了定义类的语法糖(class)
新增了一种基本数据类型(Symbol)
新增了变量的解构赋值
函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数
数组新增了一些 API,如 isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法
对象和数组新增了扩展运算符
ES6 新增了 Set 和 Map 数据结构
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
ES6 新增了生成器(Generator)和遍历器(Iterator)
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 编译时引入模块的属性所所以性能稍高。
js 中我们在调⽤函数的时候经常会遇到 this 作⽤域的问题,这个时候 ES6 给我们提箭头函数。
1、 箭头函数是匿名函数不能作为构造函数,不能使用 new
2、 箭头函数不绑定 arguments,取而代之用 rest 参数…解决,
3、 this 指向不同,箭头函数的 this 在定义的时候继承自外层第一个普通函数的 this
4、 箭头函数通过 call()或 apply()调用一个函数,只传入了一个参数,对 this 并没有影响.
5、 箭头函数没有 prototype(原型),所以箭头函数本身没有 this
6、 箭头函数不能当做 Generator 函数,不能使用 yield 关键字、
7、 写法不同,箭头函数把 function 省略掉了 ()=> 也可以吧 return 省略调 写法更简洁
8、箭头函数不能通过 call()、apply()、bind()方法直接修改它的 this 指向。
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 的产生
相同点
都是循环遍历数组中的每一项 forEach 和 map 方法里每次执行匿名函数都支持 3 个参数,参数分别是 item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个 匿名函数中的 this 都是指向 window 只能遍历数组
注意:forEach 对于空数组是不会调用回调函数的。
不同点
map 方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。) map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有 return,返回值是 undefined)
1、Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱 的问题,自己身上有 all、reject、resolve、race 等方法,原型上有 then、catch 等方法。
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列
3、promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败
4、Promise 对象状态改变:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了
5、如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部,但是写了 then 和 catch ,会被 then 的第二个参数 或 catch 所捕获
promise 的 then 为什么可以支持链式调用
promise 的 then 会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用
补充:
Promise.all 哪怕一个请求失败了也能得到其余正确的请求结果的解决方案
Promise.all 默认只要有一个错误就直接返回错误。promise.all 中任何一个 promise 出现错误的时候都会执行 reject,导致其它正常返回的数据也无法使用
Promise.all(
[
Promise.reject({ code: 500, msg: “服务异常” }),
Promise.resolve({ code: 200, list: [] }),
Promise.resolve({ code: 200, list: [] })
].map(p => p.catch(e => e))
)
.then(res => {
console.log(“res=>”, res);
})
.catch(error => {
console.log(“error=>”, error);
});
res=> [ { code: 500, msg: ‘服务异常’ },
{ code: 200, list: [] },
{ code: 200, list: [] } ]
核心内容是 map 方法,map 的每一项都是 promise,catch 方法返回值会被 promise.reslove()包裹,这样传进 promise.all 的数据都是 resolved 状态的。
// 使用 Promise.all 其中 id 为 69 的商品,返回失败,会导致整个 Promise 接受到 reject 状态.
// 所以进行改造, p catch 得到的 err 为返回失败抛出的信息, 进行置空
.map(p => p.catch(err => ‘’)))
6、async、await 的原理
Async 和 await 是一种同步的写法,但还是异步的操作,两个必须配合一起使用
函数前面的 async 关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个 Promise 对象。
await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是 promise 则会等待 promaise 返回结果,接普通函数直接进行链式调用.
await 能够获取 promise 执行的结果 await 必须和 async 一起使用才行,async 配合 await 使用是一个阻塞的异步方法
如果 await 后面不是 Promise 对象, 就直接返回对应的值,只能在 async 函数中出现, 普通函数直接使用会报错
await 语句后的 Promise 对象变成 reject 状态时,那么整个 async 函数会中断,后面的程序不会继续执行
使用场景:
我在项目中: 需求:执行第一步,将执行第一步的结果返回给第二步使用。在 ajax 中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第 二步操作的接口调用,达到异步操作。
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值
常见的几种方式有
1.默认值
2.交换变量
3.将剩余数组赋给一个变量
结构数组和对象字符串区别
对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象.
我在项目中:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui 按需引入,请求接口返回数据,提取想要数据。
1、 推荐在循环对象属性的时候,使用 for…in,在遍历数组的时候的时候使用 for…of。
2、 for in 遍历的是数组的索引,而 for of 遍历的是数组元素值
3、for…of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
4、for…in 遍历顺序以数字为先 无法遍历 symbol 属性 可以遍历到公有中可枚举的
5、从遍历对象的角度来说,for···in 会遍历出来的为对象的 key,但 for···of 会直接报错。
Generator 生成器 也是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同 function *(){}
Generator 函数是一个状态机,封装了多个内部状态,除了状态机,还是一个遍历器对象生成函数。
Generator 是分段执行的, yield (又得)可暂停,next 方法可启动。每次返回的是 yield 后的表达式结果,这使得 Generator 函数非常适合将异步任务同步化
Generator 并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署 Interator`接口…)
Generator 函数返回 Iterator 对象,因此我们还可以通过 for…of 进行遍历,原生对象没有遍历接口,通过 Generator 函数为它加上这个接口,就能使用 for…of 进行遍历了
promise、Generator、async/await 进行比较:
promise 和 async/await 是专门用于处理异步操作的
Generator 并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署 Interator 接口…)
promise 编写代码相比 Generator、async 更为复杂化,且可读性也稍差
Generator、async 需要与 promise 对象搭配处理异步情况
async 实质是 Generator 的语法糖,相当于会自动执行 Generator 函数
async 使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
js 的构造函数(在别的后台语言上叫做类)上可以添加一些成员,可以在构造函数内部的 this 上添加,可以在构造函数本身上添加,通过这两种方式添加的成员,就分别称为实例成员和静态成员
实例成员:构造函数中 this 上添加的成员 静态成员:构造函数本身上添加的成员
实例成员,只能由实例化的对象来访问 静态成员,只能由构造函数本身来访问 实例化对象的 proto 指向构造函数的 prototype 属性指向的对象,实例化的对象可以访问到它后者身上的成员
构造函数生成实例的执行过程:使用面向对象编程时,new 关键字做了什么?
新建了一个 Object 对象
修改构造函数 this 的指向,是其指向新建的 Object 对象,并且执行构造函数
为 Object 对象添加了一个 proto 属性,是其指向构造函数的 prototype 属性
将这个 Object 对象返回出去
set 数据的特点是数据是唯一的
const set1 = new Set()
增加元素 使用 add
set2.add(4)
是否含有某个元素 使用 has
console.log(set2.has(2))
查看长度 使用 size
console.log(set2.size)
删除元素 使用 delete
set2.delete(2)
size: 返回 Set 实例的成员总数。
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值。
clear():清除所有成员,没有返回值。
Set 的不重复性
传入的数组中有重复项,会自动去重
const set2 = new Set([1, 2, ‘123’, 3, 3, ‘123’])
Set的不重复性中,要注意
引用数据类型和 NaN
两个对象都是不用的指针,所以没法去重
const set1 = new Set([1, {name: ‘孙志豪’}, 2, {name: ‘孙志豪’}])
如果是两个对象是同一指针,则能去重
const obj = {name: ‘我们一样’}
const set2 = new Set([1, obj, 2, obj])
NaN !== NaN,NaN 是自身不等于自身的,但是在 Set 中他还是会被去重
const set = new Set([1, NaN, 1, NaN])
map 数据结构
Map对比
object最大的好处就是,key不受
类型限制
定义 map
const map1 = new Map()
新增键值对 使用 set(key, value)
map1.set(true, 1)
判断 map 是否含有某个 key 使用 has(key)
console.log(map1.has(‘哈哈’))
获取 map 中某个 key 对应的 value
console.log(map1.get(true))
删除 map 中某个键值对 使用 delete(key)
map1.delete(‘哈哈’)
定义 map,也可传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], [‘哈哈’, ‘嘻嘻嘻’]])
console.log(map2) // Map(3) { true => 1, 1 => 2, ‘哈哈’ => ‘嘻嘻嘻’ }
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
symbol 是 es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL 值是由 SYMBOL 函数生成,也就是说现在我们定义对象的属性名字可以是原有的字符串 也可以是 symbol 类型的,symbol 可以保证不与其他属性名冲突,减少了 bug 的产生,
如果那 symbol 对比的话 就是会返回 false
symbol 他是一个原始类型的值就,不可以使用 new 关键字,symbol 不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型
symbol 不能用来四则运算,否则会报错,只能用显示的方式转为字符串
symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。
其实 iteration == iterator 有三个作用:
为各种数据结构,提供一个统一的、简便的访问接口;
使得数据结构的成员能够按某种次序排列;
主要供 for…of 消费
Object.assign 可以实现对象的合并。它的语法是这样的: Object.assign(target, …sources)
Object.assign 会将 source 里面的可枚举属性复制到 target。如果和 target 的已有属性重名,则会覆盖。同时后续的 source 会覆盖前面的 source 的同名属性。
Object.assign 复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题
Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。
那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有 length 属性的对象。
1、将类数组对象转换为真正数组:
let arrayLike = {
0: ‘tom’,
1: ‘65’,
2: ‘男’,
3: [‘jane’,‘john’,‘Mary’],
‘length’: 4
}
let arr = Array.from(arrayLike)
console.log(arr) // [‘tom’,‘65’,‘男’,[‘jane’,‘john’,‘Mary’]]
那么,如果将上面代码中 length 属性去掉呢?实践证明,答案会是一个长度为 0 的空数组。
这里将代码再改一下,就是具有 length 属性,但是对象的属性名不再是数字类型的,而是其他字符串型的,代码如下:
let arrayLike = {
‘name’: ‘tom’,
‘age’: ‘65’,
‘sex’: ‘男’,
‘friends’: [‘jane’,‘john’,‘Mary’],
length: 4
}
let arr = Array.from(arrayLike)
console.log(arr) // [ undefined, undefined, undefined, undefined ]
会发现结果是长度为 4,元素均为 undefined 的数组
由此可见,要将一个类数组对象转换为一个真正的数组,必须具备以下条件:
1、该类数组对象必须具有 length 属性,用于指定数组的长度。如果没有 length 属性,那么转换后的数组是一个空数组。
2、该类数组对象的属性名必须为数值型或字符串型的数字
我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。
由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系。
后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。
现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
js 中现在比较成熟的有四种模块加载方案:
第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和 require.js 的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。
加油快通关了,通关了你会有收获的。
vue 面试题
核心原理部分
mvc mvvm 和 mvp 的区别?
MVVM 就是 Model-View-ViewModel 的缩写,MVVM 将视图和业务逻辑分开。
View:视图层,Model 数据模型,而 ViewModel 是把两者建立通信的桥梁。
在 MVVM 框架下,View 和 Model 之间没有直接的联系,而是通过 ViewModel 进行交互。View 和 ViewModel 之间以及 Model 和 ViewModel 之间的交互都是双向的,因此 view 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反映到 View 上。可以说它们两者是实时更新的,互相影响。 ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,因此开发者只需要关注业务逻辑,不需要手动操作 DOM,也不需要关注数据状态的同步问题,这些都由 MVVM 统一管理,
整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作 DOM 元素。因为在 MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也观察不到 View,这种低耦合模式提高代码的可重用性。
【优点】
数据源和视图实现了双向绑定,很好的做到了数据的一致性 相比于 mvp 各层的耦合度更低,一个 viewmodel 层可以给多个 view 层共用。
【缺点】
因为使用了 dataBinding,增加了大量的内存开销,增加了程序的编译时间,项目越大内存开销越大。 数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范
【优点】
耦合性低,方便维护,可以利于分工协作 重用性高
【缺点】
使得项目架构变得复杂,对开发人员要求高
MVP MVP 是从经典的模式 MVC 演变而来,它们的基本思想有相通的地方 Controller/Presenter 负责逻辑的处理,Model 提供数据,View 负责显示,在 MVP 中 View 并不直接使用 Model,它们之间的通信是通过 Presenter (MVC 中的 Controller)来进行的
Vue 底层实现原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter 和 getter,在数据变动时发布消息给订阅者,触发相应的监听回调 Vue 是一个典型的 MVVM 框架,模型(Model)只是普通的 javascript 对象,修改它则-视图(View)会自动更新。这种设计让状态管理变得非常简单而直观
Observer(数据监听器) : Observer 的核心是通过 Object.defineProprtty()来监听数据的变动,这个函数内部可以定义 setter 和 getter,每当数据发生变化,就会触发 setter。这时候 Observer 就要通知订阅者,订阅者就是 Watcher
Compile(指令解析器) : Compile 主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新-视图
Watcher(订阅者) : Watcher 订阅者作为 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
在自身实例化时往属性订阅器(dep)里面添加自己
自身必须有一个 update()方法
待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调
Vue 模版编译原理。
vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)
第二步是对 AST 进行静态节点标记,主要用来做虚拟 DOM 的渲染优化(优化器)
第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
vue 虚拟 dom,diff 算法
虚拟 DOM,其实就是用对象的方式取代真实的 DOM 操作,把真实的 DOM 操作放在内存当中,在内存中的对象里做模拟操作。当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM 树,将状态全部保存起来,在内存当中模拟我们真实的 DOM 操作,操作完后又会生成一颗 dom 树,两颗 DOM 树进行比较,根据 diff 算法比较两颗 DOM 树不同的地方,只渲染一次不同的地方。
(个人理解)虚拟 dom 他不并不是真实的 dom ,是根据模板生成一个 js 对象(使用 createElement,方法),根据这个 js 对象再去生成真实的 dom,对复杂的文档 DOM 结构,提供一种方便的工具,进行最小化的 DOM 操作 ,是可以快速的渲染和高效的更新元素,提高浏览器的性能,
例如,一个 ul 标签下很多个 li 标签,其中只有一个 li 有变化,这种情况下如果使用新的 ul 去替代旧的 ul,因为这些不必要的 DOM 操作而造成了性能上的浪费,但是如果直接使用虚拟节点覆盖旧节点的话,减少了很多的不必要的 DOM 操作。
我们在渲染页面的时候 会对新的虚拟 dom 和旧的虚拟 dom 进行对比 只渲染不同的地方,而不再是像之前只要发生变化,全部的真实 dom 都要重新渲染,所以提高了渲染的效率。
缺点:首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢
diff 算法
diff 算法是一种通过同层的树节点进行比较的高效算法,比较方式:diff 整体策略为:深度优先,同层比较
diff 算法 当 data 发生改变 会根据新的数据生成一个新的虚拟 dom ,新的虚拟 dom 和旧的虚拟 dom 进行对比,这个对比的过程就是 diff 算法,会找到不同地方,只去渲染不同的地方,总的来说就是减少 DOM,重绘和回流。
为什么要用虚拟 DOM 来描述真实的 DOM 呢?
创建真实 DOM 成本比较高,如果用 js 对象来描述一个 dom 节点,成本比较低,另外我们在频繁操作 dom 是一种比较大的开销。所以建议用虚拟 dom 来描述真实 dom。
什么是响应式,“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher 自动触发重新 render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
Object.defineProperty 怎么用, 三个参数?,有什么作用啊?
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, {})
obj:需要定义属性的对象
prop:需要定义的属性
{}:要定义或修改的属性描述符。
value: “18”, // 设置默认值得
enumerable: true, //这一句控制属性可以枚举 enumerable 改为 true 就可以参与遍历了 默认值 false
writable: true, // 控制属性可以被修改 默认值 false
configurable: true, // 控制属性可以被删除 默认值 false
get // 当有人读取 prop 的时候 get 函数就会调用,并且返回就是 sss 的值
set // 当有人修改 prop 的时候 set 函数就会调用, 有个参数这个参数就是修改后的值
Object.defineProperty 能定义 symbol 类型吗?
在 ES6 中,由于 Symbol 类型的特殊性,用 Symbol 类型的值来做对象的 key 与常规的定义或修改不同,而 Object.defineProperty 是定 义 key 为 Symbol 的属性的方法之一。
vue2 和 vue3 的响应式原理都有什么区别呢?
vue2 用的是 Object.defindProperty 但是 vue3 用的是 Proxy
Object.defindProperty 虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属性进行数据劫持,所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,就能监听到对象的数据变化无法监听到数组的变化,Proxy 就没有这个问题,可以监听整个对象的数据变化,所以用 vue3.0 会用 Proxy 代替 definedProperty。
上面就是一个典型的例子,当我们点击按钮想要根据数组 arr 的下标改变其元素的时候,你会发现 data 中的数据改变了,但是页面中的数据并没有改变。
我会用 this. s e t ( t a r g e t , k e y , v a l u e ) 来解决参数: O b j e c t ∣ A r r a y t a r g e t s t r i n g ∣ n u m b e r p r o p e r t y N a m e / i n d e x a n y v a l u e 第一参数时指定要修改的数据( t a r g e t )第二个参数就是你要设置数据的下标或者是属性名第三个参数就是现在要修改的数据(重新赋的值)改变 / 添加对象属性的时候 : t h i s . set( target, key, value ) 来解决 参数: {Object | Array} target {string | number} propertyName/index {any} value 第一参数时指定要修改的数据 (target) 第二个参数就是你要设置数据的下标或者是属性名 第三个参数就是现在要修改的数据 (重新赋的值) 改变/添加 对象属性的时候:this. set(target,key,value)来解决参数:Object∣Arraytargetstring∣numberpropertyName/indexanyvalue第一参数时指定要修改的数据(target)第二个参数就是你要设置数据的下标或者是属性名第三个参数就是现在要修改的数据(重新赋的值)改变/添加对象属性的时候:this.set(data 实例,“属性名(添加的属性名)”,“属性值(添加的属性值)”)
改变/添加 数组属性的时候:this.$set(data 实例,数组下标,“改变后的元素(添加的元素)”)
原因 : vue 在创建实例的时候把 data 深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
为什么要用 this. s e t 呢? t h i s . set 呢? this. set呢?this.set 是干什么的?
当你发现你给对象加了一个属性,在控制台能打印出来,但是却没有更新到视图上时,也许这个时候就需要用到 this. s e t ()这个方法了,简单来说 t h i s . set()这个方法了,简单来说this. set()这个方法了,简单来说this.set 的功能就是解决这个问题的啦。官方解释:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = ‘hi’),你会发现 vue 官网是 vue.set,vue.set 的用法
Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue 原型上。
是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调从而达到数据和视图同步。
1、 observer 主要是负责对 Vue 数据进行递归遍历,使其数据拥有 get 和 set 方法,当有数据给某个对象值赋值,就触发 setter 就监听到数据的变化了。( 如有变动可拿到最新值并通知订阅者 )
2、compile 指令解析器负责绑定数据和指令解析。 将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数。一旦数据有变动,收到通知,更新视图
3、 订阅者 watcher : Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是 负责数据监听,当数据发生改变,能调用自身的 update()方法,并触发 Compile 中绑定的更新函数
4、实现一个订阅器 dep: 采用发布者订阅者模式,用来收集订阅者的 watcher,对监听器 observer 和订阅者 watcher 进行统一管理
vue3 的 Proxy 相比于 vue2 的 defineProperty 的优势
在 vue3 中
Vue3 是通过 Object.define.proxy 对对象进行代理,从而实现数据劫持。使用 Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法
Vue3.0 摒弃了 Object.defineProperty,改为基于 Proxy 的观察者机制探索。
首先说一下 Object.defineProperty 的缺点:
① Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
② Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。
而要取代它的 Proxy 有以下两个优点
可以劫持整个对象,并返回一个新对象。有多种劫持操作(13 种)
补充:
Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 mdn
Proxy 是 ES6 新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。
1、vue 中数组中的某个对象的属性发生变化,视图不更新如何解决?
Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
问题原因:因为 vue 的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。解决方案如下
方案一:利用 this.set(this.obj,key,val)
例:this.set(this.obj,‘k1’,‘v1’)
方案二:就利用 Object.assign({},this.obj)创建新对象
如果是数组就 Object.assign([],this.obj)
如果是对象就 Object.assign({},this.obj)。
灵活的组件应用,高效的数据绑定
渐进式代表的含义是:主张最少——它是一个轻量级框架,只做了自己该做的事,没有做不该做的事
每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。
这里的 vue 数据驱动的是视图,也就是 DOM 元素,指的是让 DOM 的内容随着数据的改变而改变框架的理解
SSR 全称 Server Side Render
有利于 SEO:由于是在服务端,将数据填充进 HTML 之后再推送到浏览器,所以有利于 SEO 的爬取
首屏渲染快
SSR 的缺点:
开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子;
当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境;
更多的服务端负载。
vue3.0 与 vue2.0 的区别 1.性能提升
更小巧,更快速;支持摇树优化。支持 Fragments (支持多个根节点)和跨组件渲染;支持自定义渲染器。
2.API 变动
Vue2 使用 选项类型 API(Options API) 对比 Vue3 合成型 API(Composition API)
optionsApi 使用传统 api 中,新增一个需求,要在 data,methods,computed 中修改
compositionApi 我们可以更加优雅的组织我们的代码,函数,让我们的代码更加有序的组合在一起
3.重写虚拟 DOM (Virtual DOM Rewrite)
随着虚拟 DOM 重写,减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。
vue3 没有了过滤器
双向数据绑定 从 Object.defineProperty() 变成了 proxy,通过下标修改数组变化了视图数据没发生变化 this.$set() vue3 不需要
双向数据绑定原理发生了改变,使用 proxy 替换 Object.defineProerty,使用 Proxy 的优势:
可直接监听数组类型的数据变
监听的目标为对象本身,不需要像 Object.defineProperty 一样遍历每个属性,有一定的性能提升
可直接实现对象属性的新增/删除
setup 函数
3.0 新加入了 TypeScript 以及 PWA 支持
默认使用懒加载
可以不用加上 key
vue3 的 watch 监听可以进行终止监听
生命周期有了一定的区别 Vue2--------------vue3
beforeCreate -> setup() 开始创建组件之前,创建的是 data 和 method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
vue 与 react 的区别
相同点 :
都是使用了虚拟 dom
组件化开发
父子之间通信单项数据流
都支持服务端渲染
不同点:
reacct 的 jsx vue 的是 template
数据变化,react 手动 setState vue 自动响应式处理 proxy object.DefineProperty
react 单向数据流 ,vue 双向数据流
react 的 redux mobx vue 的 vuex 。pinia
vue 生命周期(11 个进行扩展延伸)
声明周期那几个?每一个生命周期的特点,可以做什么。
beforeCreate() 创建前,这个时候 data 中的数据,还未定义,所以不能使用
created()创建后 最早开始使用 data 和 methods 中数据的钩子函数
beforeMount()挂载前 指令已经解析完毕内存中已经生成 dom 树,但是尚未挂载到页面中去,此时页面还是旧的。
mounted()挂载后 dom 已经渲染完毕,此时页面和内存中都是最新的数据,最早可以操作 DOM 元素钩子函数
beforeUpdate()更新前 当视图层的数据发生改变会执行这个钩子 内存更新,但是 DOM 节点还未更新,数据没有与页面同步
updated()更新后 数据更新完成以后触发的方法,DOM 节点已经更新
beforeDestroy()即将销毁 data 和 methods 中的数据此时还是可以使用的,可以做一些释放内存的操作
destroyed()销毁完毕 组件已经全部销毁,Vue 实例已经被销毁,Vue 中的任何数据都不可用
其他三个:
activated 被 keep-alive 缓存的组件激活时调用。
deactivated 被 keep-alive 缓存的组件停用时调用。
errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用
Vue3.0 中的生命周期做了一些改动:
beforeCreate -> setup() 开始创建组件之前,创建的是 data 和 method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
Update - > onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
vue 的实例加载完成是在哪个声明周期完成呢
beforeCreate
vue 的 dom 挂载完成是在哪个声命周期里呢
mounted
created 模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。
this. n e x t T i c k ( ) 将回调延迟到下次 D O M 更新循环之后执行。在修改数据之后立即使用它,然后等待 D O M 更新。它跟全局方法 V u e . n e x t T i c k 一样,不同的是回调的 t h i s 自动绑定到调用它的实例上。可以根据打印的顺序看到,在 c r e a t e d ( ) 钩子函数执行的时候 D O M 其实并未进行任何渲染,而此时进行 D O M 操作并无作用,而在 c r e a t e d ( ) 里使用 t h i s . nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。 可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this. nextTick()将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待DOM更新。它跟全局方法Vue.nextTick一样,不同的是回调的this自动绑定到调用它的实例上。可以根据打印的顺序看到,在created()钩子函数执行的时候DOM其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.nextTick()可以等待 dom 生成以后再来获取 dom 对象,而通过 this.$nextTick()获取到的值为dom更新之后的值
setTimeout(() => {
console.log(this.$refs.button);
});
setTimeout 将同步转换为异步 this.$nextTick
this.$nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,
优先是 Promise.then 方法,是个微任务,这样可以避免多一次队列,进而少一次 UI 渲染,节省性能
页面 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
页面 beforeCreate undefined
页面 created 1
页面 beforeMount 1
组件 beforeCreate undefined
组件 created 5555
组件 beforeMount 5555
组件 mounted 5555
页面 mounted 1
7、父子组件生命周期执行顺序
加载渲染过程
父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted
代码更新过程
父 beforeUpdate->子 beforeUpdate->子 updated->父 updated
代码销毁过程
父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed
代码常用钩子简易版
父 create->子 created->子 mounted->父 mounted
8、补充单一组件钩子执行顺序
activated, deactivated 是组件 keep-alive 时独有的钩子
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured
watch
仅仅是数据发生改变的时候会侦听到;
只是会检测到你写在 watch 里的那些属性,没写的就不会触发。
updated
执行到它的时候时候是数据发生变化且界面更新完毕;
不能监听到路由数据(例如网址中的参数);
所有的数据发生变化都会调用(消耗性能);
每次触发的代码都是同一个
computed
1、监控自己定义的变量,不用再 data 里面声明,函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用 return 返回
6、不要在 computed 中对 data 中的数据进行赋值操作,这会形成一个死循环。
methods 不会被缓存:方法每次都会去重新计算结果。methods 方法表示一个具体的操作,主要书写业务逻辑;
使用 methods 方法编写的逻辑运算,在调用时 add() 一定要加“()”,methods 里面写的多位方法,调用方法一定要有()。methods 方法页面刚加载时调用一次,以后只有被调用的时候才会被调用。我们在长度框和宽度框的值输入完以后,点击“+” methods 方法调用一次。这里很明显我们采用 methods 会更节省资源。
使用场景?
watch:
1、watch 函数是不需要调用的。
2、重点在于监控,监控数据发生变化的时候,执行回调函数操作。
3、当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch
4、函数名就是你要监听的数据名字
5、监控一些 input 框值的特殊处理,适合一个数据影响多个数据。
6、数据变化时,执行一些异步操作,或开销比较大的操作
computed:
在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式
一个需要的结果受多个数据影响的时候,比如购物车结算金额(受到很多处的价格结算)。
操作某个属性,执行一些复杂的逻辑,并在多处使用这个结果。
内部函数中多处要使用到这个结果的。
1、监控自己定义的变量,不用再 data 里面声明,函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用 return 返回
6、不要在 computed 中对 data 中的数据进行赋值操作,这会形成一个死循环。
一般在哪个生命周期请求异步数据
可以啊钩子函数中的 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
在 created 中最好
能更快获取到服务端数据,减少页面加载时间,用户体验更好;
SSR 不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。
mounted 在请求完数据之后需要对 dom 进行操作的时候可以用到
vue 中 methods,computed, watch 的区别
computed 是 vue 中的计算属性,具有缓存性,当他的依赖于值,发生改变的时候才会重新调用
methods 是没有缓存的,只要调用,就会执行,一般结合事件来使用
watch 没有缓存性 监听 data 中的属性 属性值只要发生变化就会执行 可以利用他的特性做一些异步的操作
created 和 mounted 区别?
created:dom 渲染前调用,即通常初始化某些属性值
mounted:在 dom 渲染后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作
生命周期钩子是如何实现的
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
vuex 常问的考点
Vuex 严格模式
开启严格模式,仅需在创建 store 的时候传入 strict: true:
const store = new Vuex.Store({
// …
strict: true
})
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
开发环境与发布环境
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
const store = createStore({
// …
strict: process.env.NODE_ENV !== ‘production’
})
vuex 是什么, state,getters,mutations,actions,modules 的用途和用法
vuex 是一个状态管理工具,所谓状态的是就是数据,采用集中式存储管所有组件的状态,是为了结局中大型项目一个数据共享的问题。vuex 他可以将数据保存到本地,数据是响应式的,能够保持数据页面的共享,提高开发效率。
好处:
能够在 vuex 中集中管理共享的数据,易于开发和后期维护 可以做状态管理、采用 localstorage 保存信息、数据一直存储在用户的客户端中 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发 效率
vuex 核心:
state:vuex 的基本数据,数据源存放地,用于定义共享的数据。
getter:从基本数据派生的数据,相当于 state 的计算属性
mutation:提交更新数据的方法,唯一 一个可以操作 state 中数据的方法,必须是同步的,第一个参数是 state,第二个参数是 cmmi 传过来的数据
action:action 是用来做异步操作的,一般用来发请求,在 action 中写入函数,然后在页面中用 dispatch 调用,然后在 action 中通过 commit 去调用 mutation 通过 mutation 去操作 state。
modules:模块化 vuex,可以让每一个模块拥有自己的 state、mutation、action、getters,使得结构非常清晰,方便管理
详述 Vuex 运行机制
运行机制:Vuex 提供数据(state),来驱动视图(这里指的是 Vue 组件),视图通过 Dispatch 派发 Action,在 Action 中可以进一步做一些异步的操作(例如通过 ajax 请求后端的接口数据),然后通过 Commit 提交给 Mutations,由 Mutations 去最终更改 state。那么为什么要经过 Mutations 呢?这是因为我们要在 Vue 调试工具(Devtools)中记录数据的变化,这样可以通过插件去进行进一步的调试。所以说 Mutations 中只能是纯同步的操作,如果是有异步操作,那么就需要在 Actions 中进行处理。如果说没有异步操作,那么可以直接由组件进行 Commit 操作 Mutations。
高级用法辅助函数(语法糖)
mapState,mapActions,mapMutations,mapGetters
辅助函数可以把 vuex 中的数据和方法映射到 vue 组件中。达到简化操作的目的
如何使用:
Import { mapActions, mapGetters, mapMutations, mapState } from ‘vuex’
computed(){ …mapState([‘数据名字’])}
Vuex 页面刷新数据丢失怎么解决
需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件
推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中
Vuex 为什么要分模块并且加命名空间
模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。方便管理
vue-router(路由原理?路由守卫?)
由于 Vue 在开发时对路由支持的不足,于是官方补充了 vue-router 插件。vue 的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在 vue-router 单页面应用中,则是路径之间的切换,实际上就是组件的切换。路由就是 SPA(单页应用)的路径管理器。再通俗的说,vue-router 就是我们 WebApp 的链接路径管理系统。
原理 一般源码中,都会用到 window.history 和 location.hash 原理:通过改变浏览器地址 URL,在不重新请求页面的情况下,更新页面视图,通过 BOM 中的 location 对象,其中对象中的 location.hash 储存的是路由的地址、可以赋值改变其 URL 的地址。而这会触发 hashchange 事件,而通过 window.addEventListener 监听 hash 值然后去匹配对应的路由、从而渲染页面的组件 1.一种是# hash,在地址中加入#以欺骗浏览器,地址的改变是由于正在进行页内导航 2.一种是 h5 的 history,使用 URL 的 Hash 来模拟一个完整的 URL
路由有两种模式 hash 和 history 模式 默认是 hash
vue-router 的实现原理(核心):更新视图但不重新请求页面。
1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题
3、history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误。
•一:全局的守卫
无论访问哪一个路径,都会触发全局的钩子函数,位置是调用 router 的方法 router/index.js
router.beforeEach 全局前置守卫 进入路由之前
router.beforeResolve 全局解析守卫,在 beforeRouteEnter 调用之后调用
同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用
router.afterEach 全局后置钩子 进入路由之后
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
二:组件级路由守卫 放在要守卫的组件里,跟 data 和 methods 同级
beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到 zhis
beforeRouteUpdate (2.2) 路由复用同一个组件时
beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
//在组件内部进行配置,这里的函数用法也是和 beforeEach 一毛一样
const Foo = {
template: ...
,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 this
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 this
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 this
}
}
三:单个路由规则独享的守卫 写在路由配置中,只有访问到这个路径,才能触发钩子函数
beforeEnter:(to,from,next)=>{ alert(“欢迎来到孙志豪的界面”) next() }
参数
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由对象
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
重定向用哪个属性?
redirect:”/路径”
vue 路由的跳转方式有几种
1、 2、this.$router.push()跳转到指定的 url,并在 history 中添加记录,点击回退返回到上一个页面
3、this.$router.replace()跳转到指定的 url,但是 history 中不会添加记录,点击回退到上上个页面
4、this.$touter.go(n)向前或者后跳转 n 个页面,n 可以是正数也可以是负数
router.push、router.replace、router.go、router.back 的区别?
router.push:跳转,并向 history 栈中加一个记录,可以后退到上一个页面
router.replace:跳转,不会向 history 栈中加一个记录,不可以后退到上一个页面
router.go:传正数向前跳转,传负数向后跳转
router.back 返回到上一级页面
vue 路由传参数如何实现、query 和 params
主要通过 query 和 params 来实现
(1) query 可以使用 name 和 path 而 params 只能使用 name
(2) 使用 params 传参刷新后不会保存,而 query 传参刷新后可以保存
(3) Params 在地址栏中不会显示,query 会显示
(4) Params 可以和动态路由一起使用,query 不可以
(5)to=”/goods?id=1001”this.然后在接收的页面通过 $route.query.id 来接收
路由对象 route 和 router 的区别
route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
router 是“路由实例对象”,包括了路由的跳转方法(push、go),钩子函数等。
vue-router 路由钩子函数是什么 执行顺序是什么执行顺序
一、打开页面的任意一个页面,没有发生导航切换。
全局前置守卫 beforeEach (路由器实例内的前置守卫)
路由独享守卫 beforeEnter(激活的路由)
组件内守卫 beforeRouteEnter(渲染的组件)
全局解析守卫 beforeResolve(路由器实例内的解析守卫)
全局后置钩子 afterEach(路由器实例内的后置钩子)
二、如果是有导航切换的(从一个组件切换到另外一个组件)
组件内守卫 beforeRouteLeave(即将离开的组件)
全局前置守卫 beforeEach (路由器实例内的前置守卫)
组件内守卫 beforeRouteEnter(渲染的组件)
全局解析守卫 beforeResolve(路由器实例内的解析守卫)
全局后置钩子 afterEach(路由器实例内的后置钩子)
完整的导航解析流程
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
动态路由:
动态路由是指路由器能够自动的建立自己的路由表,能够根据实际情况的变化实时地进行调整。用开头,后面跟的值是不确定的。这个值是我们要传递的参数 动态路由匹配本质上就是通过 url 进行传参
比如在写一个商品详情页面的时候,我们的页面结构都一样,只是渲染的数据不同而已,这时候就可以根据商品的不同id去设置动态路由,只需要写一个组件,就可以把每个商品的商品详情映射到同一个组件上去。
{
path: '/Jqxq/:id', // 路由配置拼接
name: 'Jqxq',
component: Jqxq
}
跳转 this.$router.push('/Jqxq/'+ item.id)
接收 : this.$route.params.id
嵌套路由:
vue 项目中,界面通常由多个嵌套的组件构成, 必须先清楚这样一件事,一个对应展示的就是一个组件 因此实现嵌套路由有两个要点: 路由对象中定义子路由 用 children 实现嵌套路由 组件内的使用.
路由配置:
export default new Router({
mode: ‘history’, //路由模式,取值为 history 与 hash
base: ‘/’, //打包路径,默认为/,可以修改
routes: [
{
path: string, //路径
ccomponent: Component; //页面组件
name: string; // 命名路由-路由名称
components: { [name: string]: Component }; // 命名视图组件
redirect: string | Location | Function; // 重定向
props: boolean | string | Function; // 路由组件传递参数
alias: string | Array; // 路由别名
children: Array; // 嵌套子路由
// 路由单独钩子
beforeEnter?: (to: Route, from: Route, next: Function) => void;
meta: any; // 自定义标签属性,比如:是否需要登录
icon: any; // 图标
// 2.6.0+
caseSensitive: boolean; // 匹配规则是否大小写敏感?(默认值:false)
pathToRegexpOptions: Object; // 编译正则的选项
}
]
})
怎么定义 vue-router 的动态路由? 怎么获取传过来的值
在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 route 对象的 params.id 获取
Vue-router 共有几种模式?默认是那种?
有两种模式 hash 和 history 模式 默认是 hash
1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 地址后加上/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。
路由懒加载
使用原因:在单页应用中,如果没有应用懒加载,运用 webpack 打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时 原理:vue 异步组件技术:异步加载,vue-router 配置路由 , 使用 vue 的异步组件技术 , 实现按需加载。
{ path: ‘/home’, component: () => import(‘@/views/home/home.vue’) } // 懒加载
能说下 vue-router 中常用的路由模式实现原理吗
hash 模式
location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
可以为 hash 的改变添加监听事件
window.addEventListener(“hashchange”, funcRef, false);
每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
特点:兼容性好但是不美观
history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
指令部分进行扩展
vue 常用修饰以及常见指令
修饰符
.stop 阻止事件冒泡
.cpture 设置事件捕获
.self 只有当事件作用在元素本身才会触发
.prevent 阻止默认事件,比如超链接跳转
.once 事件只能触发一次
.native 触发 js 原生的事件
.number 把文本框的内容转换为数字
.trim 去除文本框左右空格
常见指令
⑴v-bind:给元素绑定属性
⑵v-on:给元素绑定事件
⑶v-html:给元素绑定数据,且该指令可以解析 html 标签
⑷v-text:给元素绑定数据,不解析标签
⑸v-model:数据双向绑定
⑹v-for:遍历数组
⑺v-if:条件渲染指令,动态在 DOM 内添加或删除 DOM 元素
⑻v-else:条件渲染指令,必须跟 v-if 成对使用
⑼v-else-if:判断多层条件,必须跟 v-if 成对使用
⑽v-cloak:解决插值闪烁问题
⑾v-once:只渲染元素或组件一次
⑿v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度
⒀v-show:条件渲染指令,将不符合条件的数据隐藏(display:none)
v-for 与 v-if 的优先级
v-for 比 v-if 优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
vue 中 key 的作用
“key 值:用于管理可复用的元素。因为 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 变得非常快,但是这样也不总是符合实际需求。 2.2.0+ 的版本里,当在组件中使用 v-for 时,key 是必须的。”
key 是给每一个 vnode 的唯一 id,也是 diff 的一种优化策略,可以根据 key,更准确, 更快的找到对应的 vnode 节点,更高效的对比虚拟 DOM 中每个节点是否是相同节点,相同就复用,不相同就删除旧的创建新的
key 是使用 index 还是 id 啊
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key 的作用主要是为了高效的更新虚拟 DOM。
举例子:加入写一个带有复选框的列表
选中第一个节点的复选框,点击删除,vue 中是这样操作的,删除后新的数据这时会进行比较,第一个节点的标签一样,值不一样,就会复用原来位置的标签,不会做删除和创建,在第一个节点中是将复选框选中的,当我们看见好像是把第一个删除了,但是点击后去看复选框的时候还是选中在第一个,如果是直接将第一个节点删除了那么复选框就不会选中。
vue 初始化页面闪动问题。
能够解决插值表达式闪烁问题,需要在 style 中设置样式[v-clock]{display:none}
v-if 和 v-show 的区别及使用场景?
v-if 动态的创建或者销毁元素,为 true 的时候为显示,为 false 的时候不显示,要使用 v-else 必须和 v-if 紧挨着
v-show 是控制元素的显示或者隐藏,在我们的标签上会看到有 display:block,none
v-if 有更高的切换消耗,而 v-show 有更高的初始化渲染消耗,一般推荐频繁切换的时候使用 v-show 更好,当我们的判断分支比较多的时候,和首次渲染的时候 使用 v-if
自定义指令,自定义过滤器
出了 vue 自带的指定以外,我们如果需要对 dom 进行底层操作的时候这里就用到了自定义指令,分为一下
全局: vue.directive:{“”,{}} 局部:directives:{指令名:{钩子函数}}
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
参数:
el:指令所绑定的元素
binding:一个对象包含一下,
name:指令名,不包括 v- 前缀。
value:指令的绑定值
补充 : vue3 的自定义指令钩子函数?
created - 自定义指令所在组件, 创建后
beforeMount - 就是 Vue2.x 中的 bind, 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只是加入进了 DOM, 但是渲染没有完成
mounted - 就是 Vue2.x 中的 inserted, 自定义指令所在 DOM, 插入到父 DOM 后调用, 渲染已完成(最最重要)
beforeUpdate - 自定义指令所在 DOM, 更新之前调用
updated - 就是 Vue2.x 中的 componentUpdated
beforeUnmount - 销毁前
unmounted - 销毁后
自定义指令原理
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
选项对象和常用 api
什么是过滤器?怎么定义全局和局部过滤器
过滤器是对 即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据
全局:
Vue.filter(‘过滤器名’,funciton(val){})
局部过滤器,定义在组件内部 filters 属性上.它只能在此组件内部使用.
filters:{过滤器名:funciton(参数){//逻辑代码}}
使用: 过滤时间,过滤金钱
什么是 mixin ?
Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。 mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到 mixin
如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
在 vue.js 中 mixin 和页面执行顺序问题
mixin 中的代码先执行,单文件中的后执行。
mixin 的 beforeCreate > 父 beforeCreate > mixin 的 created > 父 created > mixin 的 beforeMount > 父 beforeMount > 子 beforeCreate > 子 created > 子 beforeMount > 子 mounted > mixin 的 mounted >父 mounted
nextTick 使用场景和原理
在下次 DOM 更新循环结束后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。使用场景是:可以在 created 钩子函数中拿到 dom 节点
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
vue 的删除数组和原生删除数组的区别 delete
删除数组
delete 只是把数组元素的值变成 empty/undefined,元素的键不变,数组长度不变。
Vue.delete 直接删除数组,改变数组的键值和长度。
删除对象
两者相同,都会把键名(属性/字段)和键值删除。
Vue.extend 作用和原理
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并 基础用法
// 创建构造器
/* Vue.extend( options )
参数:{Object} options
用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象;
data 选项是特例,需要注意: 在 Vue.extend() 中它必须是函数;*/
var Profile = Vue.extend({
template: ‘
{{firstName}} {{lastName}} aka {{alias}}
’,// 结果如下:
Walter White aka Heisenberg
/*
可以看到,extend 创建的是 Vue 构造器,而不是我们平时常写的组件实例,所以不可以通过 new Vue({ components: testExtend }) 来直接使用,需要通过 new Profile().$mount(’#mount-point’) 来挂载到指定的元素上。
*/
组件模块部分(插槽,单页面,通信)
vue 组件父子,子父,兄弟通信
父传递子如何传递
(1)在父组件的子组件标签上绑定一个属性,挂载要传输的变量 (2)在子组件中通过 props 来接受数据,props 可以是数组也可以是对象,接受的数据可以直接使用 props: [“属性名”] props:{属性名:数据类型}
子传递父如何传递
(1)在父组件的子组件标签上自定义一个事件,然后调用需要的方法 (2)在子组件的方法中通过 this.$emit(“事件”)来触发在父组件中定义的事件,数据是以参数的形式进行传递的
兄弟组件如何通信
(1)找到 min.js 文件,给他 vue 挂载一个 公共的 b u s V u e . p r o t o t y p e . bus Vue.prototype. busVue.prototype.bus = new Vue() (2)传送数据的一方 用 this. b u s . bus. bus.emit(‘事件名’,‘传送的数据’) (3)在 接收数据的一方用通过 Bus.$on(“事件名”,(data)=>{data 是接受的数据})
12 种组件通信 vue12 种通信方式
prop 验证,和默认值
props:会接收不同的数据类型,常用的数据类型的设置默认值的写法,Number, String, Boolean, Array, Function, Object
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新流动到子组件中,但是反过来则不行。这样防止子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。若果在子组件中直接修改 prop 传递的值,Vue 会发出警告,
方法二:通过 vuex 实现 (要了解)
具体实现:vuex 是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括 state,actions,mutations,getters 和 modules 5 个要素,主要流程:组件通过 dispatch 到 actions,actions 是异步操作,再 actions 中通过 commit 到 mutations,mutations 再通过逻辑操作改变 state,从而同步到组件,更新其数据状态
组件中写 name 选项有什么作用
① 项目使用 keep-alive 时,可搭配组件的 name 进行缓存过滤。 ② DOM 做递归组件时需要调用自身 name ③ vue-devtools 调试工具里显示的组件名称是由 vue 中组件 name 决定的
自定义组件
在 vue 中的 component 中新建组件,定义好视图层,
在 vue 中开发,都是用的组件化的思想开发的,组件封装的方式可以使我们的开发效率提高,把单页面的每个模块拆分为一个组件件,
组件封装的方式解决了我们传统项目,开发效率低,难以维护,复用性低等问题。
使用:比如说封装一个 swiper 首先我们要定义一个 props 要接受传递的数据,写入响应的逻辑,在通过 import 引入到页面注册作为标签使用即可。
keep-alive 的作用
keep-alive 是 Vue 提供给我们一个内置组件,会缓存不活动的组件实例,而不是销毁它们, 作为标签使用 包裹在需要缓存的组件外
在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染 DOM,减少加载时间及性能消耗,提高用户体验性
作用: 比如列表页面进入详情,我们想保存列表滚动的位置,我们就可以使用 keep-alive 保存列表页面的滚动位置。
组件使用 keep-alive 以后会新增两个生命周期 actived() deactived()
activated(组件激活时使用) 与 deactivated(组价离开时调用)
有两个参数: 允许组件有条件的进行缓存。
include - 包裹的组件名会被缓存
exclude 包裹的组件名都不会被缓存
keep-alive 缓存 beforDestroy 还会执行吗。
首先,答案是不会的,准确的说是不会直接调用。 默认情况下,也就是没有设置 keep-alive,当离开当前路由时,会直接调用 beforeDestroy 和 destroyed 来销毁。 当组件设置 keep-alive 后,不会直接调用这个销毁周期函数,而是会在生命周期函数新增两个,activated 和 deactivated; 当退出的时候会执行 deactivated 函数
VUE 组件中的 data 为什么是一个函数
Object 是引用数据类型,如果不用 function 返回,每个组件的 data 都是内存的同一个地址,一个数据改变了其他也改变了,这就造成了数据污染。如果 data 是一个函数,每个实例的 data 都在闭包中,就不会各自影响了
组件特性及好处、组件的基本组成
(1) 特性:重用性、可指定性、互操作性、高内聚性、低耦合度
(2) 好处:组件可以扩展 HTML 元素、封装可重用代码
template 结构(html 代码)
script 行为
style 样式
什么是 slot?什么是命名 slot?slot 怎么使用?
插槽就是父组件往子组件中插入一些内容。
有三种方式,默认插槽,具名插槽,作用域插槽
默认插槽就是把父组件中的数据,显示在子组件中,子组件通过一个 slot 插槽标签显示父组件中的数据
具名插槽是在父组件中通过 slot 属性,给插槽命名,在子组件中通过 slot 标签,根据定义好的名字填充到对应的位置。这样就可以指定多个可区分的 slot,在使用组件时灵活地进行插值。
作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过 v-slot=""要穿过来的数据“来接受数据。
scoped 原理及穿透方法
vue 中的 scoped 通过在 DOM 结构以及 css 样式上加唯一不重复的标记:data-v-hash 的方式,以保证唯一(通过 PostCSS 转译),达到样式私有模块化的目的。
scoped 的 3 条渲染规则: ① 给 HTML 的 DOM 节点加一个不重复的 data 属性,来表示它的唯一性; ② 在每句 css 选择器末尾(编译后的生成的 css 语句)加一个当前组件的 data 属性选择器来私有化样式; ③ 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上 ddan 当前组件的 data 属性。 补充:
在做项目中,会遇到这么一个问题,即:引用了第三方组件,需要在组件中局部修改第三方组件的样式,而又不想去除 scoped 属性造成组件之间的样式污染。那么有哪些解决办法呢?
① 不使用 scopeds 省略(不推荐);
② 在模板中使用两次 style 标签。
③scoped 穿透:/deep/ >>>
函数式组件使用场景和原理
函数式组件与普通组件的区别
1.函数式组件需要在声明组件是指定 functional:true 2.不需要实例化,所以没有 this,this 通过 render 函数的第二个参数 context 来代替 3.没有生命周期钩子函数,不能使用计算属性,watch 4.不能通过 e m i t 对外暴露事件,调用事件只能通过 c o n t e x t . l i s t e n e r s . c l i c k 的方式调用外部传入的事件 5. 因为函数式组件是没有实例化的,所以在外部通过 r e f 去引用组件时,实际引用的是 H T M L E l e m e n t 6. 函数式组件的 p r o p s 可以不用显示声明,所以没有在 p r o p s 里面声明的属性都会被自动隐式解析为 p r o p , 而普通组件所有未声明的属性都解析到 emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件 5.因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement 6.函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到 emit对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件5.因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement6.函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到attrs 里面,并自动挂载到组件根元素上面(可以通过 inheritAttrs 属性禁止)
优点 1.由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件 2.函数式组件结构比较简单,代码结构更清晰
使用场景:
一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
“高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件
vue 的项目中问题
单页面应用和多页面应用区别以及优缺点,
单页面:只有一个 html 页面,跳转方式是组件之间的切换
优点:跳转流畅、组件化开发、组件可复用、开发便捷
缺点:首屏加载过慢
多页面:有多个页面,跳转方式是页面之间的跳转
优点:首屏加载块
缺点:跳转速度慢
为什么要使用脚手架
快速开始一个 vue 项目,不用手动配置,直接开发
口喷 axios 封装
首先要安装 axios,一般我会在项目的 src 目录中,新建一个 network 文件夹,作为我们的网络请求模块,然后在里面新建一个 http.js 和一个 api.js 文件和一个 reques.js。
http.js 文件用来封装我们的 axios basUrl Tiemout,
api.js 用来统一管理我们的接口 url,
request.js 中添加请求拦截和响应拦截。在请求拦截中,会给请求头添加 token 字段,还有 loading 动画的开启。在响应拦截中,可以做一些 loading 动画的关闭,还有可以根据后端返回的状态码,做一些检验 token 是否有效或者过期的操作。
接着就是做一些 axios 进行的 api 接口的封装,这里我用到了 async,await 封装请求接口函数,这样可以将异步操作同步化操作,代码更加友好,避免回调地域的出现。
vue 中如何解决跨域问题?
在 vue 开发中实现跨域:在 vue 项目根目录下找到 vue.config.js 文件(如果没有该文件则自己创建),在 proxy 中设置跨域
devServer: {
proxy: { //配置跨域
‘/api’: {
target: ‘http://121.121.67.254:8185/’, //这里后台的地址模拟的;应该填写你们真实的后台接口
changOrigin: true, //允许跨域
pathRewrite: {
/_ 重写路径,当我们在浏览器中看到请求的地址为:http://localhost:8080/api/core/getData/userInfo 时
实际上访问的地址是:http://121.121.67.254:8185/core/getData/userInfo,因为重写了 /api
_/
‘^/api’: ‘’
}
},
}
},
assets 和 static 的区别?
assets 中的文件会经过 webpack 打包,重新编译,推荐在 assets 存放 js 等需要打包编译的文件。
static 中的文件,不会打包编译。static 中的文件只是复制一遍。static 中建议放一些外部第三方文件,自己的放 assets 里,别人的放 static 中。(图片推荐放在 static 里)
Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环 tick 中,Vue 刷新队列并执行实际(已去重的)工作。
多环境变量
首先是通过在根目录下创建.env.*(配置文件)文件,development 本地开发环境配置、staging 测试环境配置、production 正式环境配置(生产环境)。因为我在创建的文件中并没有定义很多变量,只定义了基础的 env,所以需要在 src 目录下创建一个 config 文件夹,创建对应的环境变量文件,用来管理不同的环境。在 config 中创建对应的文件是为了后期修改起来方便,不需要重启项目,符合开发习惯。之后就是根据需要的环境,在封装的 axios 中通过解构赋值的方式导入,放在 baseURL 中就可以使用。
element-ui 和 vant-ui 按需引入
首先安装按需引入的插件,在 babel.config.js 中添加按需引入的配置,创建一个 plugin 文件夹,定义一个 js 文件用来存放按需引入的代码,之后在建好的 js 文件中首先导入 vue,再导入需要的 vant-ui 插件,通过 vue.use()全局注入。修改样式可以用样式穿透 /deep/
Vue 解决了什么问题
① 虚拟 dom:dom 操作时非常耗性能的,不再使用原生的 dom 操作节点,极大的解放 dom 操作,但具体操作的还是 dom,不过是换了一种方式。提供了很多指令当然需要 对 dom 进行底层操作的时候就用到自定义指令
② 视图、数据、结构分离:使数据的更改更为简单,只需要操作数据就能完成相关操作。
③ 组件化:把一个单页应用中的各种模块拆分到一个一个单独的组件中,便于开发,以及后期的维护
Vue.js 的特点
简洁:页面由 HTML 模板+Json 数据+Vue 实例组成 数据驱动:自动计算属性和追踪依赖的模板表达式
组件化:用可复用、解耦的组件来构造页面 轻量:代码量小,不依赖其他库 快速:精确有效批量 DOM 更新 模板友好:可通过 npm,bower 等多种方式安装,很容易融入 Vue 的核心库只关注视图层,并且非常容易学习
请说出 vue.cli 项目中 src 目录每个文件夹和文件的用法
assets 文件夹是放静态资源;
components 是放组件;
router 是定义路由相关的配置;
view 视图;
app.vue 是一个应用主组件;
main.js 是入口文件
描述下 vue 从初始化页面–>修改数据–>刷新页面 UI 过程?
当 Vue 进入初始化阶段时,一方面 Vue 会遍历 data 中的属性,并用 Object.defineProperty 将它转化成 getter/setterd 的形式,实现数据劫持;
另一方面,Vue 的指令编译器 Compiler 对元素节点的各个指令进行解析,初始化视图,并订阅 Watcher 来更新视图,此时 Watcher 会将自己添加到消息订阅器 Dep 中,此时初始化完毕。
当数据发生变化时,触发 Observer 中 setter 方法,立即调用 Dep.notify( ),Dep 这个数组开始遍历所有的订阅者,并调用其 update 方法,Vue 内部再通过 diff 算法,patch 相应的更新完成对订阅者视图的改变。
Vue 怎么兼容 IE
使用 babel-polyfill 插件,和前缀名 hack
Vue 怎么重置 data
使用 Object.assign(),vm.data 可 以 获 取 当 前 状 态 下 的 data ,
Object.assign(this. d a t a , t h i s . data, this. data,this.options.data())
vue-router 登陆权限的判断
vue-router 的登陆权限判断主要是在全局钩子函数中进行的,我们在 router.js 文件中的定义路由里,将需要登陆权限的页面加上 meta 属性,值是对象的形式,然后在该对象中自定义一个属性,属性值就是一个 Boolean 值,这时候在 main.js 文件的全局钩子函数中进行判断,如果需要跳转的页面的自定义属性值为 true,那么将进行判断其是否登录,如果没有登录,则告诉用户登录,如果有登录,那么进行页面跳转。
vue-cli 替我们做了哪些工作
vue-cli 是基于 Vue.js 进行快速开发的完整系统,也可以理解成是很多 npm 包的集合。
vue-cli 完成的功能:
.vue 文件 --> .js 文件 ES6 语法 --> ES5 语法 Sass,Less,Stylus --> CSS 对 jpg,png,font 等静态资源的处理 热更新 定义环境变量,区分 dev 和 production 模式 如果开发者需要补充或修改默认设置,需要在 package.json 同级下新建一个 vue.config.js 文件
Vue 如何检测数组变化
数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想)
所以在 Vue 中修改,数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
vue 中使用了哪些设计模式 1.工厂模式 - 传入参数即可创建实例 虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例 vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
如何解决 vue 首屏加载过慢?
① 把不常改变的库放到 index.html 中,通过 cdn 引入
然后找到 build/webpack.base.conf.js 文件,在 module.exports = { } 中添加以下代码:
externals: {
‘vue’: ‘Vue’,
‘vue-router’: ‘VueRouter’,
‘element-ui’: ‘ELEMENT’,
}
②vue 路由懒加载,图片懒加载,使用异步组件,按需加载
③ 不生成 map 文件,找到 config/index.js 文件,修改为 productionSourcceMap:false
④vue 组件尽量不要全局引入
⑤ 使用更轻量级的工具库
⑥ 开启 gzip 压缩:这个优化是两方面的,前端将文件打包成.gz 文件,然后通过 nginx 的配置,让浏览器直接解析.gz 文件。
⑦ 首页单独做服务端渲染:如果首页真的有瓶颈,可以考虑用 node 单独做服务端渲染,而下面的子页面仍用 spa 单页的方式交互。这里不推荐直接用 nuxt.js 服务端渲染方案,因为这样一来增加了学习成本,二来服务端的维护成本也会上升,有时在本机测试没问题,在服务端跑就有问题,为了省心,还是最大限度的使用静态页面较好。
Vue 和 JQuery 的区别在哪?为什么放弃 JQuery 用 Vue?
jQuery 是直接操作 DOM,Vue 不直接操作 DOM,Vue 的数据与视图是分开的,Vue 只需要操作数据就行它是个框架
jQuery 的操作 DOM 行为是频繁的,而 Vue 利用虚拟 DOM 的技术,大大提高了更新 DOM 时的性能它是个库
Vue 中不倡导直接操作 DOM,开发者只需要把大部分精力放在数据层面上
Vue 集成了一些库,大大提高开发效率,例如 Route、Vuex 等等
你都做过哪些 Vue 的性能优化?
尽量减少 data 中的数据,data 中的数据都会增加 getter 和 setter,会收集对应的 watcher v-if 和 v-for 不能连用 v-if 和 v-show 区分使用场景 v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if 如果需要使用 v-for 给每项元素绑定事件时使用事件代理
SPA 页面采用 keep-alive 缓存组件 在更多的情况下,使用 v-if 替代 v-show 使用路由懒加载、异步组件
防抖、节流 第三方模块按需导入
长列表滚动到可视区域动态加载,不需要响应式的数据不要放到 data 中(可以 Object.freeze() 冻结数据)
图片懒加载
SEO 优化 预渲染
服务端渲染 SSR 打包优化,
压缩代码 Tree Shaking/Scope Hoisting
使用 cdn 加载第三方模块 多线程打包 happypack splitChunks 抽离公共文件 sourceMap 优化 骨架屏
PWA 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启 gzip 压缩等。防止内部泄漏,组件销毁后把全局变量和事件销毁
js 的大山
使用面向对象编程时,new 关键字做了什么?
新建了一个 Object 对象
修改构造函数 this 的指向,是其指向新建的 Object 对象,并且执行构造函数
为 Object 对象添加了一个 proto 属性,是其指向构造函数的 prototype 属性
将这个 Object 对象返回出去
es6 和 es5 的继承(继承不用搞那么麻烦,项目中还是用 class)
原型链继承
父类的实例作为子类的原型,易于实现,父类的新增实例与属性子类都能访问,创建子类实例,不能向父类构造函数中传参数。
原型链继承
实现:
父类的实例作为子类的原型
可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
优点:
简单,易实现 父类的新增实例与属性子类都能访问
缺点:
无法实现多继承 创建子类实例时,不能向父类构造函数中传参数
function Person() {
this.a = true
}
Person.prototype.fn = function () {
console.log('我是父类')
}
function Son() {
this.b = false
}
这里是关键 创建 Person 的实例 然后赋值给 Son 的原型
Son.prototype = new Person()
let xiaoming = new Son()
xiaoming.fn()
console.log(xiaoming.a)
构造函数继承(伪造对象、经典继承)
不能继承原型属性/方法,可以实现多继承,可以传参,无法复用,
构造函数继承
实现:
在子类内,使用call()调用父类方法,并将父类的this修改为子类
的this.相当于是把父类的实例属性复制了一份放到子类的函数内.
优点:
解决了子类构造函数向父类构造函数中传递参数
可以实现多继承(call或者apply多个父类)
缺点:
方法都在构造函数中定义,无法复用
不能继承原型属性/方法,只能继承父类的实例属性和方法
function Father(age, name) {
this.a = '22'
}
function Children() {
Father.call(this)
}
let Class = new Children()
console.log(Class.a);
组合继承
通过 call 对实例属性的继承,原型链对原型方法的继承, 调用多次
组合继承
实现:
组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,
借用构造函数技术来实现实例属性的继承。
缺点:
由于调用了两次父类,所以产生了两份实例
优点:
函数可以复用
不存在引用属性问题
可以继承属性和方法,并且可以继承原型的属性和方法
function Father(name) { // 这里的name 就是 Son 传过来的
this.name = name
this.colors = [1, 2]
}
Father.prototype.sayName = function () {
alert(this.name)
}
function Son(name, age) {
//继承实例属性,第一次调用Father()
Father.call(this, name) // 这里通过 call 传过去 name 继承实例属性
this.age = age
}
Son.prototype = new Father() // 继承父类方法,第二次调用 Father
Son.prototype.ff = function () { // 子类的方法
console.log('666');
}
var aa = new Son('小明', 5)
aa.colors.push(3)
console.log(aa); // 打印了 父类的属性 和方法 以及子类的方法
var bb = new Son('小红', 50)
aa.colors.push(999999)
console.log(bb); // 打印了 父类的属性 和方法 以及子类的方法
Es6 有 class 继承:
首先利用 class 构造一个父类,然后利用 extends 与 super 实现子类继承
ES6 类继承 extends
extends 关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中 constructor 表示构造函数,一个类中只能有一个构造函数,有多个会报出 SyntaxError 错误,如果没有显式指定构造方法,则会添加默认的 constructor 方法,使用例子如下。
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 输出 100
ES5 继承和 ES6 继承的区别:
es5 继承首先是在子类中创建自己的 this 指向,最后将方法添加到 this 中
Child.prototype=new Parent() || Parent.apply(this) || Parent.call(this)
es6 继承是使用 class 关键字先创建父类的实例对象 this,最后在子类 class 中修改 this
javascript 原型与原型链
每个函数都有一个 prototype 属性,被称为显示原型
每个实例对象都会有_ proto _属性,其被称为隐式原型
每一个实例对象的隐式原型_ proto _属性指向自身构造函数的显式原型 prototype
每个 prototype 原型都有一个 constructor 属性,指向它关联的构造函数。
原型链
获取对象属性时,如果对象本身没有这个属性,那就会去他的原型proto上去找,如果还查不到,就去找原型的原型,一直找到最 顶层(Object.prototype)为止。Object.prototype 对象也有 proto 属性值为 null。链式查找机制叫原型链。
javascript 创建对象的几种方式
1、我们一般使用字面量的形式直接创建对象
(1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。
(2)第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。
(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。
(4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。
(5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。
(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,
什么是设计模式?
概念:
设计模式是一套被反复使用的代码,设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式让代码变得工程化,设计模式是软件工程的基石。
1、js 工厂模式,去做同样的事情,实现同样的效果,解决多个相似的问题这时候需要使用工厂模式
2、发布订阅模式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
3、单例模式 单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点
constructor,proto,prototype 的三角关系。
构造函数的 prototype 指向原型对象
实例对象的 proto 指向构造函数的 prototype 所指向原型对象
原型对象的 constructor 指向构造函数
面向过程,面向对象,面向过程和面向对象的优缺点
一、面向过程:面向过程就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可。
二、面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程面向过程:
优点:性能上它是优于面向对象的,因为类在调用的时候需要实例化,开销过大。
缺点:不易维护、复用、扩展
用途:单片机、嵌入式开发、Linux/Unix 等对性能要求较高的地方
面向对象:
面向对象有三大特性:封装,继承,多态。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 。
缺点:性能比面向过程低
扩展面试题
spa
spa 就是我们的单页面应用,spa 应用就是只有一个 html 页面,在 vue 中可以通过 vue-router 来进行页面的切换的,而非刷新整个页面,可以实现无刷新切换页面的技术
SPA 的原理?
通过这一点可以用 js 监听 url 中 hash 值的变化 onhashchange 事件在当前 URL 的锚部分(以 ‘#’ 号为开始) 发生改变时触发 。哈希值的变换并不会引发页面的刷新和跳转,当监听到 hash 变化,就可以动态的切换组件,就可以实现无刷新切换页面技术
spa 的优点?
页面切换快:
页面每次切换跳转时,并不需要做html
文件的请求,这样就节约了很多http
发送时延,我们在切换页面的时候速度很快。
用户体验好:
页面片段间的切换快,在网络环境差的时候, 因为组件已经预先加载好了, 并不需要发送网络请求, 所以用户体验好
转场动画
spa 的缺点?
首屏加载比较慢因为要请求一次 html 同时还要发送一次 js 请求,两次请求回来了首屏才会显示
不利于 SEO
seo 效果较差 因为搜索引擎只识别 html 里面的内容,并不识别 js 里的内容,因为单页面就是 js 渲染出来的,影响网站的排名
mpa
MPA 多页面应用程序 指的就是有多个独立的 html 页面,每个页面必须重复加载 html js css 资源,多页面跳转需要整个页面资源刷新。
优点
1、首屏加载速度快
当我们访问页面的时候,服务器只返回了一个 html,页面就展示出来了,只发了一次 http 请求,所以页面显示非常快.
2、SEO 效果好
因为搜索引擎在做网站排名的时候,要根据网页的内容给网页排名,搜素引擎只可以识别 html 内容,多页面就是将内容放在 html 中,所以排名要好一点。
缺点
因为每跳转一个页面都要发送一次 http 请求,如果网络情况不好的情况下,页面之间来回跳转就会发生明显的卡顿,有的时候半天还加载不出来,影响用户体验。
转场动画也不好实现
1、 Vue 与 Angular 以及 React 的区别
▍Angular
框架比较成熟完整,过于庞大,上手难;
指令以 ng-xxx 开头; 由谷歌开发和维护;
版本 1 比较适合 PC 端开发,版本 2 在往移动端靠;
不支持低版本浏览器; 内置指令和自定义指令;
内置过滤器和自定义过滤器; 支持双向数据绑定;
▍Vue
它是一个轻量级框架,其核心库只关注视图层,简单小巧、易学易上手;
指令以 v-xxx 开头; 个人维护项目; 适合于移动端开发; 不支持低版本浏览器;
内置指令和自定义指令; 内置过滤器和自定义过滤器; 支持双向数据绑定;
使用 DOM 模板。中心思想:一切都是组件,组件实例之间可以嵌套; 核心库不内置列数 AJAX,Route 等功能到核心包,而是以插件的方式加载; 基于依赖追踪的观察系统,并且异步队列更新。
▍React
依赖虚拟 DOM; 采用特殊的 JSX 语法; 中心思想:一切都是组件,组件实例之间可以嵌套; 核心库不内置列数 AJAX,Route 等功能到核心包,而是以插件的方式加载。
2、 请描述一下你对 webpack 的理解?
Webpack Webpack 是一个项目打包工具
可以压缩代码和图片,把浏览器识别不了的代码转化为能识别的,可以启动一个热加载服务器
配置跨域、路径别名、打包分析、cdn 映入、去掉 console.log、单独打包第三方模块、ie 兼容、eslint 规范、图片压缩
3、vue2 对比 vue3
最大的区别就是: Vue2 使用 选项类型 API(Options API) 对比 Vue3 合成型 API(Composition API)
双向数据绑定原理发生了改变,使用 proxy 替换 Object.defineProerty,使用 Proxy 的优势:
可直接监听数组类型的数据变
监听的目标为对象本身,不需要像 Object.defineProperty 一样遍历每个属性,有一定的性能提升
可直接实现对象属性的新增/删除
默认使用懒加载
在 2.x 版本里。不管数据多大,都会在一开始就为其创建观察者,在数据很大时,就会造成性能的问题。在 3.x 中,只会对渲染出来的数据创建观察者,而且 3.x 的观察者更高效。
3.0 新加入了 TypeScript 以及 PWA 支持
生命周期有了一定的区别
Vue2--------------vue3
beforeCreate -> setup() 开始创建组件之前,创建的是 data 和 method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
4、git 命令
git init 初始化 git 仓库 (mac 中 Command+Shift+. 可以显示隐藏文件)
git status 查看文件状态
git add 文件列表 追踪文件
git commit -m 提交信息 向仓库中提交代码
git log 查看提交记录
1.分支明细
(1)主分支(master):第一次向 git 仓库中提交更新记录时自动产生的一个分支。
(2)开发分支(develop):作为开发的分支,基于 master 分支创建。
(3)功能分支(feature):作为开发具体功能的分支,基于开发分支创建
2.分支命令
(1)git branch 查看分支
(2)git branch 分支名称 创建分支
(3)git checkout 分支名称 切换分支
(4)git merge 来源分支 合并分支 (备注:必须在 master 分支上才能合并 develop 分支)
(5)git branch -d 分支名称 删除分支(分支被合并后才允许删除)(-D 强制删除)
3.暂时保存更改
(1)存储临时改动:git stash
(2)恢复改动:git stash pop
更加详细请看 git 常用命令
git 怎么解决多人冲突?:
是当前修改是左箭头方向,传入的是右箭头的方向,
中间用等于号分割,等号上边是当前修改(本地),下边是传入的修改(线上的代码)。
两人同时提交可能会出现冲突,解决办法是手动修改冲突
5、前端有哪些页面优化方法?
基于 promise 的 http 库,可以用在浏览器和 node.js,支持 promiseAPI,客户端支持防御 xsrf
Node 是一个基于 Chrome V8 引擎的 JavaScript 代码运行环境。
浏览器(软件)能够运行 JavaScript 代码,浏览器就是 JavaScript 代码的运行环境
Node(软件)能够运行 JavaScript 代码,Node 就是 JavaScript 代码的运行环境
一句话:降低软件的复杂性。使其可控,可维护,可扩展。
一个功能就是一个模板,多个模板可以组成完整应用,抽离一个模板不会影响其他功能的运行
网站应用程序主要分为两大部分:客户端和服务器端。客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用 HTML、CSS、JavaScript 构建。服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。
简单强大,轻量可扩展。
简单体现在 node 使用的是 javascript,json 来进行编码,强大体现在非阻塞 IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问,轻量体现在 node 本身既是代码,又是服务器,前后端使用统一语言;可扩展体现在可以轻松应对多实例,多服务器架构,同时有海量的第三方应用组件。
node 是单线程的,异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的 IO,这在高并发环境会是一个很大的性能问题,所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.
NPM 是随同 NodeJS 一起安装的包管理工具,能解决 NodeJS 代码部署上的很多问题。
使用场景:
a. 允许用户从 NPM 服务器下载别人编写的第三方包到本地使用。
b. 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用。
c. 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别人使用。
get 是从服务器上获取数据,post 是向服务器传送数据。
POST 比 GET 安全,因为数据在地址栏上不可见。
get 方式提交的数据最多只能有 1024 字节,而 post 则没有此限制。
GET 使用 URL 或 Cookie 传参。而 POST 将数据放在 request BODY 中。
GET 与 POST 都有自己的语义,不能随便混用。
据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基,本可以无视。而在网 络环境差的情况下,两次包的 TCP 在验证数据包完整 性上,有非常大的优点。post 发送两次,get 只发送一次。
并不是所有浏览器都会在 POST 中发送两次包,Firefox 就只发送一次。
什么是 ajax?ajax 有什么优缺点?
ajax 不是语言,ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术
优点
1、最大的一点是页面无刷新,用户的体验非常好。
2、使用异步方式与服务器通信,具有更加迅速的响应能力。
缺点
1、ajax 不支持浏览器 back 按钮。
2、安全问题 AJAX 暴露了与服务器交互的细节。
3、对搜索引擎的支持比较弱。
4、破坏了程序的异常机制。
5、不容易调试
原生 Ajax 的创建过程 1.创建 xhr 核心对象
var xhr=new XMLHttpRequest();
2.调用 open 准备发送
参数一:请求方式
参数二: 请求地址
参数三:true 异步,false 同步
xhr.open(‘post’,‘http://www.baidu.com/api/search’,true)
3.如果是 post 请求,必须设置请求头。
xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’)
4.调用 send 发送请求 (如果不需要参数,就写 null)
xhr.send(‘user=tom&age=10&sex=女’)
5.监听异步回调 onreadystatechange
判断 readyState 为 4 表示请求完成
判断 status 状态码 为 200 表示接口请求成功
responeseText 为相应数据。字符串类型。
xhr.onreadystatechange=function(){
if(xhr.readyState4){
if(xhr.status200){
console.log(xhr.responseText);
var res=JSON.parse(xhr.responseText);
console.log(res);
if(res.code==1){
modal.modal(‘hide’);
location.reload();
}
}
备注:如果是 post 请求,想要传 json 格式数据。
设置请求头
1.xhr.setRequestHeader(‘Content-Type’, ‘application/json’)
open 发送数据
2.xhr.open({_id:xxx,user:xxxx,age:xxxx})
web 安全及防护
XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登陆网站时就会执行这些恶意代码,这些脚本可以读取 cookie,session tokens,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。
XSS 避免方式:
url 参数使用 encodeURIComponent 方法转义
尽量不是有 InnerHtml 插入 HTML 内容
使用特殊符号、标签转义符。
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF 避免方式:
添加验证码
使用 token
服务端给用户生成一个 token,加密后传递给用户
用户在提交请求时,需要携带这个 token
服务端验证 token 是否正确
就是通过吧 SQL 命令插入到 Web 表单递交或输入域名,最终达到欺骗服务器执行恶意的 SQL 命令。
解决:表单输入时通过正则表达式将一些特殊字符进行转换
DDoS 又叫分布式拒绝服务,全称 Distributed Denial of Service,其原理就是利用大量的请求造成资源过载,导致服务不可用。
解决:
限制单 IP 请求频率。
防火墙等防护设置禁止 ICMP 包等
检查特权端口的开放
使用基于 token 的登录流程
客户端使用用户名跟密码请求登录
服务端收到请求,去验证用户名与密码
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
常见 http 状态码分类:
200响应成功
301永久重定向
302临时重定向
304资源缓存
403服务器禁止访问
404服务器资源未找到
500 502服务器内部错误
504 服务器繁忙
1xx Informational(信息状态码) 接受请求正在处理
2xx Success(成功状态码) 请求正常处理完毕
3xx Redirection(重定向状态码) 需要附加操作已完成请求
4xx Client Error(客户端错误状态码) 服务器无法处理请求
5xx Server Error(服务器错误状态码) 服务器处理请求出错
这玩意一定要说全了装逼
合成 URL:浏览区会判断用户输入是合法 URL,比如用户输入的是搜索的关键词,默认的搜索引擎会合成新的,如果符合 url 规则会根据 url 协议,在这段内容加上协议合成合法的 url
网络进程获取到 URL,先去本地缓存中查找是否有缓存资源,如果有则拦截请求,直接将缓存资源返回给浏览器进程;否则,进入网络请请求阶段;
DNS 查找数据缓存服务中是否缓存过当前域名信息,有则直接返回;否则,会进行 DNS 解析返回域名对应的 IP 和端口号,如果没有指定端口号,http 默认 80 端口,https 默认 443。如果是 https 请求,还需要建立 TLS 连接;
TCP 三次握手与服务器建立连接,然后进行数据的传输;(三次握手开喷)
发送 HTTP 请求:
浏览器首先会向服务器发送请求行,它包含了请求方法、请求 URI 和 HTTP 协议的版本;另外还会发送请求头,告诉服务器一些浏览器的相关信息,比如浏览器内核,请求域名;
服务器处理请求:
服务器首先返回响应行,包括协议版本和状态码,比如状态码 200 表示继续处理该请求;如果是 301,则表示重定向,服务器也会向浏览器发送响应头,包含了一些信息;
查看响应头的信息,做不同的处理,比如重定向,存储 cookie 看看 content-type 的值,根据不同的资源类型来用不同的解析方式
浏览器渲染原理直接开干…
浏览器将获取的 HTML 文档解析成 DOM 树。
处理 CSS 标记,构成层叠样式表模型 CSSOM(CSS Object Model)。
将 DOM 和 CSSOM 合并为渲染树(rendering tree),代表一系列将被渲染的对象。
渲染树的每个元素包含的内容都是计算过的,它被称之为布局 layout。浏览器使用一种流式处理的方法,只需要一次绘制操作就可以布局所有的元素。
将渲染树的各个节点绘制到屏幕上,这一步被称为绘制 painting。
断开 TCP 连接:
数据传输完成,正常情况下 TCP 将四次挥手断开连接。但是如果浏览器或者服务器在 HTTP 头部加上 Connection: keep-alive,TCP 就会一直保持连接。
网络安全、HTTP 协议
TCP UDP 区别 1.TCP
向上层提供面向连接的可靠服务 ,UDP
向上层提供无连接不可靠服务。 2.虽然 UDP
并没有 TCP
传输来的准确,但是也能在很多实时性要求高的地方有所作为 3.对数据准确性要求高,速度可以相对较慢的,可以选用TCP
区别 UDP TCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
连接对象个数 支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅 8 字节 首部最小 20 字节,最大 60 字节
适用场景 适用于实时应用(IP 电话、视频会议、直播等) 适用于要求可靠传输的应用,例如文件传输
Http 和 Https 区别(高频) 1.HTTP
的 URL 以 http:// 开头,而 HTTPS 的 URL 以 https:// 开头 2.HTTP
是不安全的,而 HTTPS 是安全的 3.HTTP
标准端口是 80 ,而 HTTPS 的标准端口是 443 4.在OSI
网络模型中,HTTP 工作于应用层,而 HTTPS 的安全传输机制工作在传输层 5.HTTP
无法加密,而 HTTPS 对传输的数据进行加密,证的网络协议,安全性高于 HTTP 协议。 6.HTTP
无需证书,而 HTTPS 需要 CA 机构 wosign 的颁发的 SSL 证书,一般免费证书少,因而需要一定费用。
GET 和 POST 区别(高频)
1.GET 在浏览器回退不会再次请求,POST 会再次提交请求
2.GET 请求会被浏览器主动缓存,POST 不会,要手动设置
3.GET 请求参数会被完整保留在浏览器历史记录里,POST 中的参数不会
4.GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有限制
5.GET 参数通过 URL 传递,POST 放在 Request body 中
6.GET 参数暴露在地址栏不安全,POST 放在报文内部更安全
7.GET 一般用于查询信息,POST 一般用于提交某种信息进行某些修改操作
8.GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包
Ge 和 post 的选择: 1.私密性的信息请求使用 post(如注册、登陆)。 2.查询信息使用 get。
三次握手和四次挥手
三次握手:
第一次:建立连接时,客户端发送 syn 包到服务器,等待服务端确认
第二次:服务器收到 syn 包,必须确认客户的 syn,同时也发送一个 syn 包,即 syn+ACK 包
第三次:客户端收到服务器的 syn 和 ack 包,向服务器发送确认包 ack,发送完毕,客户端和服务端连接成功,完成三次握手
第一次:浏览器发送完数据后,发送 fin 请求断开连接
第二次:服务器发送 ack 到客户端,确认客户端的断开请求
第三次:服务器请求断开 fin 的请求
第四次:客户端确认服务器的断开 ack
POST 的 content-type 几种方式
POST 方法中对发送数据编码的方式,也就是 Content-Type 有四种方式,其中默认是 application/x-www-form-urlencoded,最方便的是 application/json 。
四种方式包括:
application/x-www-form-urlencoded (URL encoded)
multipart/form-data (键值对型数据)
application/json (Json 类型数据)
text/xml (xml)
传统的 ajax 请求时候,Content-Type 默认为"文本"类型。
传统的 form 提交的时候,Content-Type 默认为"Form"类型。
axios 传递字符串的时候,Content-Type 默认为"Form"类型。
axios 传递对象的时候,Content-Type 默认为"JSON"类型
http1.0、http1.1、http2.0 的区别
1 和 1.0 相比,1.1 可以一次传输多个文件
http1.x 解析基于文本,
http2.0 采用二进制格式,新增特性 多路复用、header 压缩、服务端推送(静态 html 资源)
浏览器缓存的作用
浏览器缓存的作用:减少冗余的数据传输,节省网络带宽,更快加载页面,缓存降低了服务器的要求,有更快的响应
个人理解:
强制缓存:浏览器在加载资源的时候,会根据本地缓存中的 headers 中的信息(expires,cache-control)是否要强缓存,如果命中的话,则会使用缓存中的资源,否则继续发送请求。
协商缓存:客户端向服务端发送请求,服务端检测是否有对应的标识,如果没有服务端会返回客户端对应的标识,客户端在下次请求把标识带过去服务器会验证标识,如果通过了,则会响应 304,告诉浏览器读取缓存,如果没有通过则返回请求的资源。
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则。
基于对比缓存,不管是否使用缓存都需要向服务器发送请求,那么还用缓存干什么?
服务端在进行标识比较后,只返回 header 部分,通过状态码通知客户端使用缓存,不再需要将报文主体部分返回给客户端。
缓存的资源去哪里了
memory cache 将资源文件缓存到内存中,下次请求读取的是内存中的
disk cache 将资源存到硬盘中,下次请求从硬盘中读取
HTTP 报文就是浏览器和服务器间通信时发送及响应的数据块。
浏览器向服务器请求数据,发送请求(request)报文;
服务器向浏览器返回数据,返回响应(response)报文。
报文信息主要分为两部分:header,数据主体部分(body)
能不能说一说浏览器的本地存储?各自优劣如何?
浏览器的本地存储主要分为 Cookie、WebStorage 和 IndexDB, 其中 WebStorage 又可以分为 localStorage 和 sessionStorage。
共同点: 都是保存在浏览器端、且同源的
不同点:
cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器和服务器间来回传递。cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下 sessionStorage 和 localStorage 不会自动把数据发送给服务器,仅在本地保存。
存储大小限制也不同,
cookie 数据不能超过 4K,sessionStorage 和 localStorage 可以达到 5M
sessionStorage:仅在当前浏览器窗口关闭之前有效;
localStorage:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;
cookie:只在设置的 cookie 过期时间之前有效,即使窗口关闭或浏览器关闭
作用域不同
sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;
localstorage:在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在
cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在~~~~~~~~~~~~