前端学习笔记
前端学习笔记:
1.vue-router有几种工作模式
Vue Router 有三种主要的工作模式,分别是 hash
模式、history
模式 和 abstract
模式。每种模式的实现方式和应用场景不同,以下是详细介绍:
1. hash
模式
-
特点:
- 使用 URL 中的
#
符号,类似于http://example.com/#/home
。 #
后面的部分不会被发送到服务器,它仅在客户端生效,用来表示前端路由。
- 使用 URL 中的
-
hash
模式是基于浏览器的 hashchange 事件,当 URL 中的#
部分发生变化时,会触发页面的切换。 -
优点:
- 不依赖于服务器的支持,因此兼容性最好。
- 不需要服务器配置,适用于静态页面。
-
缺点:
- URL 不够美观,因为会带有
#
。
- URL 不够美观,因为会带有
-
适用场景:适合简单的前端应用或不想对服务器进行额外配置的项目。
-
使用方式:
Vue Router 默认就是hash
模式,无需特别配置:const router = new VueRouter({ mode: 'hash', routes: [...] });
2. history
模式
-
特点:
- 使用现代浏览器提供的 HTML5 History API 来实现 URL 路由控制,类似于
http://example.com/home
。 - 在这种模式下,URL 中不会带有
#
,显得更加美观和直观。
- 使用现代浏览器提供的 HTML5 History API 来实现 URL 路由控制,类似于
-
优点:
- URL 美观且符合 RESTful 风格。
- 直接导航到某个 URL 时,仍然可以保持单页应用的效果。
-
缺点:
- 需要后端服务器的支持。在刷新页面或直接访问某个 URL 时,服务器端需要将所有路由重定向到
index.html
,否则会出现 404 错误。
- 需要后端服务器的支持。在刷新页面或直接访问某个 URL 时,服务器端需要将所有路由重定向到
-
适用场景:适合现代的前后端分离项目或希望有干净 URL 的项目。
-
使用方式:
配置 Vue Router 为history
模式:const router = new VueRouter({ mode: 'history', routes: [...] });
- 服务器配置:因为
history
模式依赖服务器的配合,服务器需要配置将所有路由都指向index.html
。
Apache 配置:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] </IfModule>
Nginx 配置:
location / { try_files $uri $uri/ /index.html; }
- 服务器配置:因为
3. abstract
模式
-
特点:
abstract
模式并不依赖浏览器的 API,而是将路径的变更模拟为普通的 JavaScript 对象操作。- 这种模式通常用于非浏览器环境,例如 Node.js 服务端渲染或进行单元测试时。
-
优点:
- 可以在不依赖浏览器的情况下模拟路由操作,适合测试或服务端渲染。
-
缺点:
- 不能在浏览器中使用,只能用于非浏览器环境。
-
适用场景:适用于服务端渲染(SSR)或测试环境。
-
使用方式:
配置 Vue Router 为abstract
模式:const router = new VueRouter({ mode: 'abstract', routes: [...] });
总结:
hash
模式:URL 中带有#
,兼容性好,不需要服务器配置,适合简单项目。history
模式:URL 美观,符合现代应用开发需求,但需要服务器配置支持。abstract
模式:用于非浏览器环境,适合服务端渲染或测试。
通常情况下,在浏览器应用中,hash
模式适合无需服务器配置的项目,而 history
模式则适合希望有更加美观 URL 的现代应用。
2.说说SPA
SPA 是一种通过在客户端动态加载和更新页面内容的 Web 应用架构。它通常只加载一次初始页面,之后的所有交互都在当前页面内通过 JavaScript 实现局部更新,不会重新加载整个页面。SPA 通过 AJAX 或 Fetch 请求服务器上的数据,并通过 JavaScript 将新内容注入页面的某些部分。前端框架如 Vue.js 和 React 等非常适合构建这种类型的应用,能够为用户提供更加流畅的体验。不过,SPA 的缺点在于首屏加载时间相对较长,特别是在应用体积较大的时候。此外,SEO 方面的支持也较为复杂,需要结合服务器端渲染(SSR)或预渲染技术来改善搜索引擎的可见性。简单的来说,SPA就相当与一个杯水,杯子里面可以换成牛奶咖啡等饮料,但是杯子不会变。
3.WebSocket心跳机制
WebSocket 心跳机制用于确保客户端和服务器之间的连接保持活跃,尤其是在长时间没有数据传输时。通常,WebSocket 是一种长连接协议,允许客户端和服务器持续通信。然而,如果长时间没有数据传输,网络环境或者服务器会认为连接已断开,可能会主动关闭连接。这时心跳机制就显得很重要。
心跳机制的实现步骤
心跳机制主要分为两部分:客户端发送心跳和服务器响应心跳。可以通过定时发送“ping”消息来实现。
1. 客户端发送心跳包
客户端定时向服务器发送一个“ping”或自定义的心跳消息,服务器收到后返回一个“pong”或相应的响应。这种方式可以检测连接是否正常,并防止长时间没有消息传递时连接断开。
2. 服务器响应心跳包
服务器接收到客户端的心跳消息后,会返回一个响应消息,通知客户端连接正常。如果服务器长时间没有收到客户端的心跳包,可以认为客户端掉线,关闭连接。
客户端代码示例
下面是一个简单的基于 JavaScript 实现的 WebSocket 心跳机制代码示例:
const ws = new WebSocket('ws://yourserver.com');
let heartCheck = {
timeout: 5000, // 心跳间隔时间
timeoutObj: null,
serverTimeoutObj: null,
// 重置心跳
reset: function() {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
this.start();
},
// 开始心跳
start: function() {
const self = this;
this.timeoutObj = setTimeout(function() {
// 向服务器发送心跳包
ws.send('ping');
// 如果超时没有收到响应,关闭 WebSocket 连接
self.serverTimeoutObj = setTimeout(function() {
ws.close(); // 关闭连接
}, self.timeout);
}, this.timeout);
}
};
// 连接成功时启动心跳检测
ws.onopen = function() {
console.log('WebSocket connected');
heartCheck.start(); // 启动心跳
};
// 收到服务器消息
ws.onmessage = function(event) {
console.log('Received from server: ', event.data);
// 如果收到心跳响应,重置心跳机制
if (event.data === 'pong') {
heartCheck.reset();
}
};
// 连接关闭时
ws.onclose = function() {
console.log('WebSocket closed');
};
// 连接发生错误时
ws.onerror = function() {
console.log('WebSocket error');
};
服务器端处理心跳
服务器端需要检测“ping”消息,并返回“pong”作为响应。以下是一个简单的 Node.js WebSocket 服务器示例:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function(ws) {
ws.on('message', function(message) {
console.log('Received from client: %s', message);
// 如果收到的是心跳包,则返回心跳响应
if (message === 'ping') {
ws.send('pong');
}
});
ws.on('close', function() {
console.log('Client disconnected');
});
});
心跳机制的优势
- 保持连接活跃:定期发送心跳包,避免 WebSocket 长时间没有数据传输导致连接超时断开。
- 检测连接状态:客户端和服务器可以通过心跳检测连接是否还在维持,并在必要时重新建立连接。
- 减少资源浪费:如果检测到连接异常,可以及时关闭连接,避免资源浪费。
注意事项
- 心跳频率:心跳包发送的间隔时间不宜过短,以免增加服务器和网络的负载;也不宜过长,以确保及时检测连接状态。
- 异常处理:在 WebSocket 断开时,应该有重连机制,确保客户端能自动重新建立连接。
4.虚拟DOM
基本概念
基本上所有框架引入了虚拟 DOM 来对真实 DOM 进行抽象,也就是现在大家所熟知的 VNode 和 VDOM。
-
Virtual DOM 就是用 js 对象来描述真实 DOM,是对真实 DOM 的抽象,由于直接操作 DOM 性能低但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过 diff 算法比对差异进行更新 DOM(减少了对真实 DOM 的操作)。
-
虚拟 DOM 不依赖真实平台环境从而可以实现跨平台。
VDOM 是如何生成的?
- 在 Vue 中我们常会为组件编写模板 - template
- 这个模板会被编译器编译为渲染函数 - render
- 在接下来的挂载过程中会调用 render 函数,返回的对象就是虚拟 dom
- 会在后续的 patch 过程中进一步转化为真实 dom。
VDOM 如何做 diff 的?
- 挂载过程结束后,会记录第一次生成的 VDOM - oldVnode
- 当前响应式数据发生变化时,将会引起组件重新 render,此时就会生成新的 VDOM - newVnode
- 使用 oldVnode 与 newVnode 做 diff 操作,将更改的部分应用到真实 DOM 上,从而转换为最小量的 dom 操作,高效更新视图。
5.Vue2/Vue3响应式原理
Vue 2 和 Vue 3 的响应式系统核心分别是基于 Object.defineProperty
和 Proxy
实现的。
Vue 2 响应式实现
实现原理:
- 对每个对象的属性进行数据劫持。
- 使用
Dep
来收集依赖,在数据发生变化时通知依赖更新。
// 模拟依赖收集类 Dep
class Dep {
constructor() {
this.subscribers = [];
}
// 添加订阅者
addSub(sub) {
this.subscribers.push(sub);
}
// 通知所有订阅者更新
notify() {
this.subscribers.forEach(sub => sub.update());
}
}
// 模拟观察者 Watcher
class Watcher {
constructor(obj, key, callback) {
this.obj = obj;
this.key = key;
this.callback = callback;
// 将当前 watcher 实例指向全局 Dep.target,用于依赖收集
Dep.target = this;
// 触发 getter 进行依赖收集
this.value = obj[key];
Dep.target = null;
}
update() {
const newValue = this.obj[this.key];
const oldValue = this.value;
if (newValue !== oldValue) {
this.value = newValue;
this.callback(newValue);
}
}
}
// 模拟 Vue 2 响应式定义函数
function defineReactive(obj, key) {
let value = obj[key];
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
// 收集依赖
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
// 通知依赖更新
dep.notify();
}
}
});
}
// 模拟 Vue 2 的响应式处理
function reactive(obj) {
Object.keys(obj).forEach(key => defineReactive(obj, key));
}
// 测试 Vue 2 响应式实现
const data = { name: 'Vue2' };
reactive(data);
// 创建 Watcher,监听 name 属性
new Watcher(data, 'name', (newValue) => {
console.log('name 变化为:', newValue);
});
// 修改响应式数据
data.name = 'Vue.js';
data.name = 'Vue 2.x';
说明:
defineReactive
是响应式的核心,它使用Object.defineProperty
劫持对象的属性,创建 getter 和 setter。Dep
是依赖管理器,用于存储依赖(如Watcher
)。Watcher
观察某个属性的变化,一旦数据改变,调用update
函数。
Vue 3 响应式实现
实现原理:
- 使用
Proxy
来代理对象,并通过get
和set
捕获对象的读写操作。 - 使用
track
和trigger
来进行依赖收集和通知更新。
// 模拟 Vue 3 中的依赖收集
const targetMap = new WeakMap();
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
let dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
// 模拟 Vue 3 中的 reactive
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
const result = Reflect.get(obj, key);
// 收集依赖
track(obj, key);
return result;
},
set(obj, key, value) {
const result = Reflect.set(obj, key, value);
// 触发依赖
trigger(obj, key);
return result;
}
});
}
// 模拟 Vue 3 中的 effect
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
// 测试 Vue 3 响应式实现
const data = reactive({ name: 'Vue3' });
// 注册 effect,监听 name 变化
effect(() => {
console.log('name 变化为:', data.name);
});
// 修改响应式数据
data.name = 'Vue.js';
data.name = 'Vue 3.x';
说明:
Proxy
代理对象,可以监听属性的所有操作(包括读、写、删除等)。track
用于依赖收集,将依赖保存到targetMap
中。trigger
用于触发依赖,遍历依赖并执行。effect
函数自动执行,并在其中访问响应式数据时进行依赖收集。
总结
- Vue 2 使用
Object.defineProperty
实现响应式,只能劫持已有的属性,不能检测新增或删除的属性。 - Vue 3 使用
Proxy
,能够全面代理对象,监听所有操作(包括属性新增、删除、数组索引操作等)。 - Vue 2 的实现有更多限制,需要手动处理数组和新增属性,而 Vue 3 的实现更加灵活和高效。
6.Vue2是怎么解决响应式的缺陷
在 Vue 2 中,由于 Object.defineProperty
无法直接监听对象属性的新增和删除,因此 Vue 2 采取了一些补救措施来处理这些场景。
1. 使用 $set
方法监听新增属性
Vue 2 提供了一个全局方法 Vue.set
(或者在组件实例中使用 this.$set
)来处理对象属性的新增。$set
方法会确保新增的属性也是响应式的。
示例:
const vm = new Vue({
data() {
return {
person: {
name: 'John',
},
};
},
});
// 直接新增属性,Vue 2 无法检测
vm.person.age = 30; // 不是响应式的,无法触发视图更新
// 使用 $set 方法新增属性,Vue 2 可以检测到
vm.$set(vm.person, 'age', 30); // 响应式,视图会更新
原理:
$set
方法会为新增的属性重新定义 getter 和 setter,从而实现对新增属性的响应式监听。底层实际上是通过调用 defineReactive
方法对新增的属性进行响应式处理。
Vue.set = function (target, key, value) {
if (Array.isArray(target) && typeof key === 'number') {
// 如果是数组,直接使用 splice 方法来修改数组
target.length = Math.max(target.length, key);
target.splice(key, 1, value);
return value;
}
if (key in target && !(key in Object.prototype)) {
target[key] = value;
return value;
}
// 处理对象,使用 defineReactive 让新增属性成为响应式的
defineReactive(target, key, value);
target.__ob__.dep.notify(); // 手动触发依赖更新
return value;
};
2. 使用 $delete
方法删除属性
类似地,Vue 2 提供了 $delete
方法用于删除对象属性,并确保在删除时触发视图的更新。
示例:
const vm = new Vue({
data() {
return {
person: {
name: 'John',
age: 30,
},
};
},
});
// 直接删除属性,Vue 2 无法检测
delete vm.person.age; // 不是响应式的,无法触发视图更新
// 使用 $delete 方法删除属性,Vue 2 可以检测到
vm.$delete(vm.person, 'age'); // 响应式,视图会更新
原理:
$delete
方法不仅删除了对象的属性,还会触发依赖的通知,从而更新视图。
Vue.delete = function (target, key) {
if (Array.isArray(target) && typeof key === 'number') {
// 如果是数组,使用 splice 删除
target.splice(key, 1);
return;
}
if (!target.hasOwnProperty(key)) {
return;
}
// 删除属性
delete target[key];
if (!target.__ob__) {
return;
}
// 手动触发依赖更新
target.__ob__.dep.notify();
};
总结
- 新增属性:Vue 2 通过
Vue.set
或this.$set
来实现响应式,可以手动添加新属性并使其响应式。 - 删除属性:Vue 2 通过
Vue.delete
或this.$delete
来实现删除属性并通知视图更新。 - Vue 2 的依赖收集: 通过
Object.defineProperty
劫持对象属性的getter
和setter
,当属性值被读取时进行依赖收集,当属性值发生变化时通知依赖进行更新。 - Vue 3 的依赖收集: 使用
Proxy
代理对象的读取和修改操作,通过track
函数收集依赖,通过trigger
函数触发依赖更新。
虽然这种方式是 Vue 2 的补救措施,但相比 Vue 3 的 Proxy
来说,它相对较为复杂。Vue 3 通过 Proxy
天然支持对象属性的新增和删除,因此不需要额外的 $set
和 $delete
方法。
7.vue2中如何检测数组变化的
在 Vue 2 中,虽然 Object.defineProperty
可以劫持对象属性来实现响应式,但对于数组,由于 Object.defineProperty
只能劫持对象的属性,而不能直接监听数组下标或其原型链的变化,因此 Vue 2 使用了对数组的原型方法进行重写的方式来监听数组的变化。
Vue 2 中检测数组变化的方式
Vue 2 对数组的常用变更方法进行了重写,以确保数组在发生变更时可以触发依赖更新。
1. 重写数组的变更方法
Vue 2 对数组的原型方法进行拦截,重写了 7 个能够修改数组内容的方法:
当使用这些方法修改数组时,Vue 2 能够检测到数组发生了变化,并通知相关依赖进行更新。
const arrayProto = Array.prototype; // 保存数组的原型
const arrayMethods = Object.create(arrayProto); // 创建数组方法的拦截对象
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function(method) {
const original = arrayProto[method]; // 保存原始的数组方法
arrayMethods[method] = function mutator(...args) {
const result = original.apply(this, args); // 调用原始数组方法
const ob = this.__ob__; // 获取 Observer 实例
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
if (inserted) ob.observeArray(inserted); // 对新插入的元素进行响应式处理
ob.dep.notify(); // 手动触发依赖更新,通知视图更新
return result;
};
});
2. 将重写的方法应用到数组
当 Vue 2 将一个数组变成响应式时,数组会被绑定到重写后的方法集合上,这样 Vue 2 就能拦截数组的变更操作。
function observe(value) {
if (Array.isArray(value)) {
protoAugment(value, arrayMethods); // 将重写后的数组方法绑定到数组实例上
observeArray(value); // 遍历数组元素,将每个元素变成响应式
} else {
// 对象的响应式处理
walk(value);
}
}
protoAugment
是用来改变数组的原型指向,使其使用 Vue 重写的数组方法:
function protoAugment(target, src) {
target.__proto__ = src; // 将 target 数组的原型指向重写后的 arrayMethods
}
通过这种方式,Vue 2 可以拦截数组的修改方法,从而检测到数组的变化并触发视图更新。
3. 数组的索引和长度的变化
-
索引变化:Vue 2 无法通过直接修改数组的索引来触发响应式更新。例如,直接使用
vm.items[1] = newItem
这样的方式无法监听到数组的变化。要触发更新,需要使用Vue.set()
方法:Vue.set(vm.items, 1, newItem); // 使数组索引的变化变为响应式
-
长度变化:同样地,Vue 2 无法直接监听数组长度的变化。如果通过设置
arr.length
的方式截断数组,Vue 2 无法自动检测变化并更新视图。
总结
- Vue 2 重写了数组的 7 个方法(如
push
、splice
等),以确保 Vue 能够检测到数组的变更并更新视图。 - 直接修改数组的索引和长度不会触发视图更新,必须使用
Vue.set
来设置新的数组元素或修改数组长度。 - 这种方式虽然有效,但相对比较有限和繁琐,Vue 3 使用
Proxy
直接代理数组对象,从而更为灵活和强大。
8.如何封装组件
封装组件是 Vue 开发中的重要部分,能够使代码更加模块化、可重用。
1. 基本组件封装
首先创建一个 Vue 组件的基本结构。以下是一个简单的封装步骤:
(1) 创建组件文件
在 src/components
目录下创建一个新的组件文件,比如 MyButton.vue
。
<template>
<button @click="handleClick" class="my-button">
<slot></slot>
</button>
</template>
<script>
export default {
name: 'MyButton',
props: {
// 接受父组件传递的参数,控制按钮是否禁用
disabled: {
type: Boolean,
default: false,
},
},
methods: {
handleClick() {
// 点击事件
if (!this.disabled) {
this.$emit('click'); // 向父组件触发自定义事件
}
},
},
};
</script>
<style scoped>
.my-button {
padding: 10px;
background-color: #42b983;
border: none;
color: white;
cursor: pointer;
}
.my-button:disabled {
background-color: grey;
cursor: not-allowed;
}
</style>
(2) 使用组件
在父组件中使用这个 MyButton
组件。
- 首先,确保你在父组件中导入并注册该组件:
<template>
<div>
<MyButton @click="handleButtonClick" :disabled="isDisabled">
Click Me
</MyButton>
</div>
</template>
<script>
import MyButton from './components/MyButton.vue';
export default {
components: {
MyButton,
},
data() {
return {
isDisabled: false,
};
},
methods: {
handleButtonClick() {
alert('Button Clicked!');
},
},
};
</script>
2. 封装更复杂的组件
封装更复杂的组件时,你可以通过以下方式实现组件的可重用性和灵活性:
(1) 组件的动态内容
使用 <slot>
可以让组件变得更灵活。slot
允许父组件在使用组件时插入自定义的内容。上面的例子中,我们在 <button>
标签中使用了 <slot></slot>
,这样父组件可以在 MyButton
中传递按钮文本。
(2) Props 和事件
- Props: 通过
props
,组件可以接收父组件传递的数据。 - 自定义事件: 组件可以使用
this.$emit
来向父组件发送事件,如点击事件、输入事件等。
(3) 复合组件
如果组件较为复杂,可以将它分解为多个小的组件,然后通过父组件组合使用。例如,一个表单组件可以由多个输入组件和一个按钮组件组成。
<template>
<div>
<MyInput v-model="formData.username" placeholder="Username" />
<MyInput v-model="formData.password" type="password" placeholder="Password" />
<MyButton @click="submitForm">Submit</MyButton>
</div>
</template>
<script>
import MyInput from './MyInput.vue';
import MyButton from './MyButton.vue';
export default {
components: {
MyInput,
MyButton,
},
data() {
return {
formData: {
username: '',
password: '',
},
};
},
methods: {
submitForm() {
// 表单提交逻辑
console.log(this.formData);
},
},
};
</script>
3. 使用 emit
和 v-model
双向绑定
实现 v-model 的封装
假如你想封装一个自定义的输入框组件,并且支持 v-model
:
<template>
<input :value="value" @input="handleInput" />
</template>
<script>
export default {
name: 'MyInput',
props: {
value: String,
},
methods: {
handleInput(event) {
this.$emit('input', event.target.value); // 实现双向绑定
},
},
};
</script>
在父组件中,你可以这样使用:
<template>
<div>
<MyInput v-model="username" />
<p>Username: {{ username }}</p>
</div>
</template>
<script>
import MyInput from './components/MyInput.vue';
export default {
components: {
MyInput,
},
data() {
return {
username: '',
};
},
};
</script>
4. 组件封装的最佳实践
- 单一职责: 尽量保持每个组件的功能单一,专注于完成一项任务。
- 复用性: 使用
props
、emit
和slot
等手段,保证组件能够灵活复用。 - 组合组件: 如果组件较为复杂,可以将组件拆分成多个子组件。
9.Vue2 和 Vue3 的主要区别
双向绑定机制:
- Vue2:使用
Object.defineProperty()
实现响应式,后添加的属性无法被自动劫持,需要手动调用$set
。 - Vue3:使用
Proxy
来代理对象,支持深层次监听,可以自动响应新增的属性,不需要手动调用$set
。
$set 的变动:
- 在 Vue2 中,新增属性需要使用
$set
手动添加以确保响应式生效。 - Vue3 使用
Proxy
,不再需要$set
,可以自动响应属性变化。
API 写法:
- Vue2 采用的是 选项式 API(Options API),组件内代码按功能划分(如
data
、methods
、computed
等)。 - Vue3 引入了 组合式 API(Composition API),通过
setup
函数来组织代码,使逻辑和数据能够按功能模块划分,更加清晰、可维护。
指令优先级:
- 在 Vue2 中,
v-for
的优先级高于v-if
。 - 在 Vue3 中,
v-if
的优先级高于v-for
,这提高了性能。
ref 和 $children 的变化:
- Vue3 中,
$ref
的使用需要通过ref()
声明,而不是自动挂载到this.$refs
。 - $children 在 Vue3 中变化较大,鼓励使用
provide/inject
或者emit
来实现组件通信。
更好的 TypeScript 支持:
- Vue3 通过重写的架构提供了对 TypeScript 的原生支持,使得类型推断和类型检查更加简单和高效。
10.Vue3 中如何使用 setup
组织代码?
在 Vue3 中,setup
函数是组合式 API 的核心。为了让项目维护更容易,通常会使用 Hooks 来细化功能模块。
// example of organizing code in Vue 3 setup
<script setup>
import { ref, onMounted } from 'vue';
// Using hooks to organize logic
const useFetchData = () => {
const data = ref(null);
onMounted(async () => {
data.value = await fetchData();
});
return data;
};
const data = useFetchData();
</script>
使用 Hooks(函数式编写),使代码更加模块化,提高可读性和复用性。
11.Vue3 中如何获取类似 Vue2 中的 this
?
在 Vue3 的 setup
函数中没有直接的 this
,可以通过 getCurrentInstance
获取当前组件的实例来访问类似 this
的属性。
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
console.log(instance.proxy); // proxy相当于this
12.Vue3 常用 API 介绍
createApp
:创建一个应用实例,等同于 Vue2 中的new Vue()
。provide/inject
:用于跨层级的组件间通信,父组件提供数据,后代组件注入。directive
:自定义指令,如权限控制等。app.config.globalProperties
:在全局范围内添加方法或属性,类似 Vue2 中的Vue.prototype
。nextTick
:等待 DOM 更新完成的异步方法。computed
:计算属性,具有缓存功能。reactive
和ref
:定义响应式数据,reactive
用于对象,ref
用于基本类型。watch
:用于监听数据的变化。markRaw
:标记某个对象为静态,Vue 不对它进行响应式代理。defineProps
和defineEmits
:在setup
函数中使用,用来接收父组件传递的值和自定义事件。
13.Vue3 常用的响应式数据类型
ref
:用于定义基本类型的响应式数据。const count = ref(0);
reactive
:用于定义对象或数组等复杂类型的响应式数据。const state = reactive({ name: 'Vue' });
toRef
:将一个对象的某个属性单独解构为一个ref
。const nameRef = toRef(state, 'name');
toRefs
:将整个对象的属性全部解构为ref
。const { name, age } = toRefs(state);
14.Teleport 组件及其使用场景
Teleport
是 Vue3 中新增的一个功能,用于将组件渲染到 DOM 树中的指定位置,而不是默认的父组件内部。这在一些特定场景下非常有用,比如模态框、弹窗等需要在页面顶部或其他全局区域显示的元素。
使用场景:
- 渲染全局的模态框、提示框等。
- 将组件渲染到特定的容器中,而非父组件中。
使用示例:
<template>
<teleport to="body">
<div class="modal">
<h2>Modal Content</h2>
</div>
</teleport>
</template>
在这个示例中,虽然 teleport
被定义在某个组件内,但它会将其内容渲染到 body
标签下。
15.vue-router,history模式下需要后台配置,后台如何配置,如何监听url变化
在使用 vue-router
的 history
模式时,路由会使用 HTML5 的 history.pushState
和 history.replaceState
方法来管理路由,这样 URL 就不会再带上 #
符号。然而,这种模式下需要确保后端能够正确处理所有的路由请求,以便在用户直接访问某个 URL 时,能返回正确的页面。
后台配置
假设你使用的是 Node.js
的 Express
框架,下面是一个简单的配置示例:
const express = require('express');
const path = require('path');
const app = express();
// Serve static files from the Vue app
app.use(express.static(path.join(__dirname, 'dist')));
// Handle all routes
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
解释
- 静态文件服务:使用
express.static
中间件来提供静态文件,这里假设你的 Vue 应用已经构建并放在dist
目录中。 - 路由处理:使用
app.get('*', ...)
捕获所有路由请求,并返回index.html
。这确保了所有的路由请求都会加载 Vue 应用的入口文件。 - 启动服务器:设置服务器监听特定的端口。
URL 变化监听
在 Vue 应用中,可以使用 vue-router
提供的 watch
来监听路由的变化。以下是一个示例:
<template>
<div>
<h1>当前路由:{{ $route.path }}</h1>
</div>
</template>
<script>
export default {
watch: {
'$route'(to, from) {
// 当路由变化时执行的逻辑
console.log('路由变化', from.path, '到', to.path);
},
},
};
</script>
解释
- 在 Vue 组件中,使用
watch
监听$route
对象。当路由变化时,$route
会更新,从而触发对应的回调函数。 to
和from
参数可以用来获取新旧路由的信息。
总结
在 vue-router
的 history
模式下,后端需要配置以正确处理所有的路由请求,通常是在后端返回应用的入口文件。同时,在 Vue 应用中可以通过监听 $route
的变化来响应路由的变化。这样就能实现无缝的 SPA 路由管理。
16.this的指向和绑定规则
在 JavaScript 中,this
关键字是一个重要的概念,它的值会根据不同的上下文而变化。理解 this
的指向和绑定规则对于编写正确和高效的 JavaScript 代码至关重要。下面是一些主要的规则和概念:
1. 全局上下文
在全局上下文中,this
指向全局对象。在浏览器中,这个全局对象是 window
。
console.log(this); // 在浏览器中输出 window 对象
2. 函数调用
在普通函数中,this
的指向取决于函数如何被调用:
- 在非严格模式下,如果直接调用函数,
this
将指向全局对象。
function show() {
console.log(this);
}
show(); // 输出 window 对象(在浏览器中)
- 在严格模式下,
this
的值为undefined
。
"use strict";
function show() {
console.log(this);
}
show(); // 输出 undefined
3. 对象的方法
当函数作为对象的方法调用时,this
指向该对象。
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
obj.greet(); // 输出 "Alice"
4. 构造函数
在构造函数中,this
指向新创建的实例。
function Person(name) {
this.name = name;
}
const person1 = new Person('Bob');
console.log(person1.name); // 输出 "Bob"
5. 箭头函数
箭头函数不绑定 this
,它会从外部上下文中继承 this
的值。这意味着,this
的指向是固定的,不会随调用方式而改变。
const obj = {
name: 'Charlie',
greet: () => {
console.log(this.name); // 这里的 this 指向全局对象,而不是 obj
}
};
obj.greet(); // 输出 undefined (在浏览器中,this.name 是 undefined)
6. call
, apply
, bind
方法
call
和apply
方法用于改变函数内部this
的指向。call
接受参数列表。apply
接受一个数组作为参数。
function show() {
console.log(this.name);
}
const obj = { name: 'David' };
show.call(obj); // 输出 "David"
show.apply(obj); // 输出 "David"
bind
方法返回一个新的函数,并永久绑定this
的值。
const boundShow = show.bind(obj);
boundShow(); // 输出 "David"
7. DOM 事件
在事件处理函数中,this
通常指向触发事件的元素。
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this); // 输出触发事件的按钮元素
});
总结
this
的指向取决于调用上下文。- 全局上下文中的
this
指向全局对象。 - 函数作为对象的方法调用时,
this
指向该对象。 - 构造函数中的
this
指向新创建的实例。 - 箭头函数不绑定
this
,而是继承外部上下文的this
。 call
、apply
和bind
方法可以用于明确绑定this
的值。
17.v-model的原理
v-model
是 Vue 中的双向数据绑定的语法糖,用于简化表单输入与数据的同步。它背后其实是通过事件监听和数据更新机制来实现的。
基本思路
- 数据到视图:初始时,数据会被设置到视图(比如输入框)。
- 视图到数据:当用户输入变化时,监听输入事件并更新数据。
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manual v-model</title>
</head>
<body>
<div id="app">
<input type="text" id="myInput">
<p>你输入的内容是: <span id="output"></span></p>
</div>
<script>
// 模拟 Vue 的 data
const appData = {
text: '初始内容' // 双向绑定的内容
};
// 获取输入框和显示区域
const input = document.getElementById('myInput');
const output = document.getElementById('output');
// 1. 将数据渲染到视图 (数据 => 视图)
input.value = appData.text;
output.innerText = appData.text;
// 2. 监听输入框变化,更新数据 (视图 => 数据)
input.addEventListener('input', function(event) {
appData.text = event.target.value; // 更新数据
output.innerText = appData.text; // 更新显示
});
</script>
</body>
</html>
解释
-
初始数据渲染:我们定义了一个简单的
appData
对象,模拟 Vue 的data
,其中text
属性是我们希望双向绑定的数据。 -
数据到视图:我们在页面加载时,将
appData.text
的值设置到输入框 (input.value = appData.text
) 和显示区域 (output.innerText = appData.text
) 中。 -
视图到数据:当用户在输入框中输入内容时,我们通过
input.addEventListener('input', ...)
监听输入事件。每次用户输入时,我们将输入框的值更新到appData.text
,并且同时更新显示的内容。这就实现了视图和数据的双向绑定。
Vue 中的 v-model
在 Vue 中,v-model
实现的原理类似,底层做了以下两件事:
- 绑定数据到
value
属性:相当于<input :value="text">
。 - 监听输入事件,更新数据:相当于
<input @input="text = $event.target.value">
。
Vue 的 v-model
让这个过程更加简洁,简化了我们手动监听和更新数据的代码。
18. vue 对数组的方法做了重写的操作,如何实现对 vue2 中对数组操作的 push()方法
在 Vue 2 中,Vue 对数组的变更检测采用了数据劫持的方式来监听数组的变化。在 Vue 2 中,数组的部分变异方法(例如 push()
、pop()
、shift()
、unshift()
、splice()
等)被重写,以确保 Vue 可以检测到数组的变更并触发视图更新。
Vue 2 的响应式系统基于 Object.defineProperty
,而对数组的变异方法(如 push
)进行拦截和重写,主要是为了能够监听数组的增删改操作,从而保证在操作数组时能正确地更新视图。
接下来,我们将通过示例来实现类似 Vue 2 对 push()
方法的重写操作。
实现对数组 push()
方法的重写
1. 思路
- 我们需要劫持数组的
push
方法。 - 在执行
push()
操作时,除了进行数据的插入,还要触发响应式系统,使得 Vue 能够感知到数据发生了变化。 - Vue 通过代理数组的原型方法来实现这一功能。
2. 实现代码
// 保存数组原型方法
const arrayProto = Array.prototype;
// 创建一个新的对象,继承自数组的原型
const arrayMethods = Object.create(arrayProto);
// 重写 push 方法
arrayMethods.push = function (...args) {
console.log('数组发生了 push 操作,新增了元素:', args);
// 调用原始的 push 方法,保证数据正常插入
const result = arrayProto.push.apply(this, args);
// 模拟 Vue 的响应式通知,触发视图更新
// 在实际的 Vue 中,这里是调用 Vue 内部的 observer 方法来通知视图更新
console.log('触发视图更新');
return result;
};
// 创建一个观察者函数,用于给数组的每个元素添加响应式处理
function observeArray(arr) {
arr.__proto__ = arrayMethods; // 将数组的原型指向我们重写的 arrayMethods
}
// 使用
const arr = [1, 2, 3];
observeArray(arr); // 劫持数组操作
// 使用 push 添加元素
arr.push(4);
arr.push(5);
/* 输出:
数组发生了 push 操作,新增了元素: [4]
触发视图更新
数组发生了 push 操作,新增了元素: [5]
触发视图更新
*/
3. 代码说明
arrayProto
:保存数组的原型方法,目的是为了调用原始的push
方法,保证数组的操作不被破坏。arrayMethods
:创建一个新的对象,这个对象继承自数组的原型,并重写了push
方法。在该方法中,我们先执行原生的push
操作,再手动触发视图更新(在实际的 Vue 中,是触发依赖的更新)。observeArray
:这是一个模拟 Vue 观察数组的方法,它将数组的原型指向我们重写的arrayMethods
,从而使得数组的操作能够被拦截。
4. Vue 2 中的实现原理
在 Vue 2 中,Vue 通过拦截数组的变异方法(如 push
)来实现对数组的响应式监听。Vue 重写了这些变异方法,并在这些方法内部调用了依赖的更新通知。
在 Vue 2 中,当我们调用数组的 push
等方法时,Vue 会执行以下操作:
- 重写数组变异方法:Vue 重写了
push
、pop
、shift
、unshift
、splice
等方法,并在这些方法中插入了依赖收集和派发更新的逻辑。 - 触发视图更新:当数组发生变更时,Vue 会触发依赖的更新,进而重新渲染视图。
通过这种方式,Vue 2 可以保证在我们对数组进行增删改操作时,视图也能自动更新。
19.原型和原型链
原型与原型链
1. 原型(Prototype)
在 JavaScript 中,每个函数对象都有一个特殊的属性叫做 prototype
,这个属性是一个对象。它的作用是:当我们通过某个构造函数创建一个实例对象时,该实例对象会自动关联到这个 prototype
对象上。通过这个 prototype
对象,实例对象可以共享构造函数的属性和方法。
- 构造函数:是一个用于创建对象的函数。当你使用
new
关键字来调用一个函数时,它就是一个构造函数。 - prototype 属性:当一个函数被定义时,系统会自动为它创建一个
prototype
属性,并指向一个对象,这个对象通常叫做“原型对象”。
示例:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
const person1 = new Person('Alice');
person1.greet(); // Hello, my name is Alice
在上面的代码中,Person
是构造函数,它有一个 prototype
属性(即 Person.prototype
),这个属性指向一个对象,该对象包含了 greet
方法。通过 new Person('Alice')
创建的 person1
,它可以访问 greet
方法,这是因为 person1
的原型指向了 Person.prototype
。
2. 原型链(Prototype Chain)
原型链是由对象的原型属性(__proto__
)连接起来的一系列对象的链条。每个对象都有一个隐藏的属性 __proto__
,指向它的构造函数的 prototype
。当你访问一个对象的属性时,JavaScript 引擎会首先查找这个对象本身是否有该属性。如果没有,它会继续在该对象的原型(即 __proto__
指向的对象)中查找,直到找到该属性或到达原型链的末尾(即 null
)。
原型链是 JavaScript 实现继承的基础。当一个对象访问某个属性或方法时,会沿着原型链逐级查找,直到找到该属性或方法为止。
示例:
function Animal(type) {
this.type = type;
}
Animal.prototype.move = function() {
console.log(`${this.type} is moving`);
};
function Dog(name) {
this.name = name;
}
// Dog继承Animal的原型
Dog.prototype = new Animal('dog');
const myDog = new Dog('Buddy');
myDog.move(); // dog is moving
在这个例子中,myDog
是 Dog
的实例,但是它也继承了 Animal
的方法。这是因为 Dog.prototype
指向了一个 Animal
的实例,而 myDog.__proto__
指向 Dog.prototype
,最终通过原型链找到了 Animal
的 move
方法。
3. 原型链的查找过程
当访问 myDog.move()
时,浏览器引擎的查找顺序如下:
- 首先查找
myDog
自身是否有move
方法。 - 如果没有,则沿着原型链查找
myDog.__proto__
,也就是Dog.prototype
。 - 再沿着
Dog.prototype.__proto__
查找Animal.prototype
,发现move
方法并执行。 - 如果
Animal.prototype
上没有找到,则继续查找Object.prototype
,这是所有对象的原型的顶层。 - 最终,如果原型链的顶层
Object.prototype
也没有找到,返回undefined
。
4. 原型链终点
在原型链中,所有对象的原型最终都会指向 Object.prototype
,而 Object.prototype.__proto__
为 null
,这是原型链的终点。
console.log(Object.prototype.__proto__); // null
5. 继承中的原型链
当你使用构造函数继承时,实例对象可以继承父类构造函数的 prototype
上的属性和方法。原型链在继承机制中发挥了重要作用,构造函数的 prototype
会成为实例对象的原型,从而实现继承。
示例:
function Animal() {}
Animal.prototype.eat = function() {
console.log("Eating");
};
function Dog() {}
Dog.prototype = new Animal();
const dog = new Dog();
dog.eat(); // Eating
这里 Dog.prototype
被设置为 Animal
的实例对象,因此 dog
可以访问 Animal.prototype
上的 eat
方法,这就是通过原型链实现的继承。
6. 总结
- 每个 JavaScript 对象都有一个原型(
__proto__
),指向它的构造函数的prototype
对象。 - 原型链是一系列对象的连接,用于属性和方法的继承查找。
- 当查找对象的属性时,JavaScript 引擎会沿着原型链逐级查找,直到找到该属性或者到达原型链的终点。
- 通过原型链可以实现 JavaScript 的继承机制。
20.简述 Vue 的基本原理
Vue 的基本原理可以分为以下几个核心概念:数据响应式系统、虚拟 DOM、模板编译、组件化。这些概念构成了 Vue 的工作机制,使其能够实现高效的、双向绑定的数据驱动视图更新。
1. 数据响应式系统
Vue 的核心是数据响应式系统,它通过“观察者模式”来实现数据和视图的同步更新。当数据发生变化时,视图会自动更新;同样,当视图改变时,数据也会相应更新。
- Vue 使用
Object.defineProperty()
(Vue 2)或Proxy
(Vue 3)来对数据进行“劫持”,即当对象的属性被访问或修改时,Vue 能捕捉到这些操作并通知依赖这个数据的组件进行重新渲染。
数据响应原理
- 数据劫持:Vue 通过
Object.defineProperty
或Proxy
劫持数据的读写操作,使得数据变化能够被监听。 - 依赖收集:当组件依赖的数据被读取时,Vue 会进行依赖收集,记录哪些组件或 DOM 依赖了这个数据。
- 派发更新:当数据发生变化时,Vue 会触发相应的更新机制,通知依赖该数据的组件或 DOM 进行重新渲染。
示例:
const app = new Vue({
data: {
message: 'Hello Vue'
}
});
// 当 message 改变时,Vue 会自动更新页面中的相关部分
app.message = 'Hello World';
2. 虚拟 DOM
Vue 使用虚拟 DOM来进行高效的 DOM 更新。当数据发生变化时,Vue 不会直接操作真实的 DOM,而是先对比“新旧虚拟 DOM”的差异,然后只对那些变化的部分进行更新。这大大减少了直接操作真实 DOM 带来的性能损耗。
工作原理:
- 模板编译:Vue 将模板编译成虚拟 DOM。
- Diff 算法:当数据变化时,Vue 会通过 Diff 算法比较新旧虚拟 DOM,找出最小的变化范围。
- DOM 更新:只更新差异部分,最大限度减少 DOM 操作,提升性能。
3. 模板编译
Vue 提供了基于模板的语法(例如 {{}}
插值和指令)来描述页面的结构,Vue 会将模板编译成虚拟 DOM 的渲染函数。
- Vue 模板中使用了插值、条件渲染(
v-if
)、列表渲染(v-for
)等特性,编译后生成渲染函数,该渲染函数在数据变化时重新执行,生成新的虚拟 DOM。
示例:
<div id="app">
<p>{{ message }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
在这段代码中,Vue 会将 {{ message }}
转换成渲染函数,当 message
数据改变时,页面会自动更新。
4. 组件化
Vue 提供了组件化开发的支持,使得可以将页面拆分为多个可复用、独立的组件。每个组件封装了自己的模板、数据、逻辑和样式。组件化不仅提高了代码的复用性,还提升了开发和维护的效率。
示例:
Vue.component('my-component', {
template: '<div>A custom component!</div>'
});
new Vue({
el: '#app'
});
通过组件化,可以更好地组织代码结构,维护性和扩展性大大提高。
5. 双向数据绑定
Vue 通过 v-model
实现了双向数据绑定,用户在页面上输入内容时,数据模型会自动更新,反过来,数据模型的变化也会实时反映到页面中。
实现原理:
- Vue 通过
input
事件监听输入框的值变化,当值变化时,自动更新数据模型。 - 数据模型通过数据响应式机制,反映到视图上。
示例:
<input v-model="message" />
<p>{{ message }}</p>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
6. Vue Router 和 Vuex
除了核心部分,Vue 还通过插件系统支持单页面应用(SPA)的路由管理(Vue Router)和状态管理(Vuex),这进一步扩展了 Vue 的应用场景。
- Vue Router:用于管理页面之间的切换和导航。
- Vuex:用于管理应用的全局状态,适合大型应用的复杂状态管理。
7. Vue 生命周期
Vue 提供了完整的生命周期钩子函数,开发者可以在这些函数中插入代码,在组件的不同阶段执行特定逻辑。
- beforeCreate:实例初始化后,数据观测和事件配置还未完成。
- created:实例已经创建完成,数据观测和事件配置都已完成,但尚未挂载。
- beforeMount:模板编译/渲染完成,准备挂载到 DOM。
- mounted:组件挂载到 DOM 中,可以进行 DOM 操作。
- beforeUpdate:数据更新时调用,DOM 未更新。
- updated:数据更新后,DOM 也已更新。
- beforeDestroy:实例销毁之前调用。
- destroyed:实例销毁后调用。
总结
Vue 是一个渐进式的 JavaScript 框架,其核心是基于数据驱动的视图渲染,虚拟 DOM 的高效更新,以及组件化的开发模式。通过响应式系统、模板编译、虚拟 DOM、组件化以及双向数据绑定,Vue 实现了数据和视图的同步更新,提供了高效且灵活的开发体验。
21.vue-router的原理
1.什么是 vue-router
vue-router
是 Vue.js 官方的路由管理器,主要用于单页面应用 (SPA) 中处理页面路径之间的切换,实现组件的切换与渲染。本质上,它建立了 URL 和组件之间的映射关系,用户在浏览器中访问不同的路径时,路由控制器根据路径匹配对应的组件并展示。
2.vue-router 的实现原理
2.1 Hash 模式
- 原理: 使用 URL 的
hash
(即#
后面的部分)来模拟路径切换。当 URL 改变时,不会重新加载页面,只会触发浏览器的hashchange
事件来更新视图。 - 特点:
hash
是锚点标识,不会被发送到服务器,改变hash
不会触发页面刷新,且hash
值变化会被记录在浏览器历史记录中。
2.2 History 模式
- 原理: 利用 HTML5 的
pushState()
和replaceState()
方法来操作浏览器历史记录。这两个方法允许在不重新加载页面的情况下改变 URL,依旧保留浏览器的前进、后退功能。 - 特点: URL 看起来是普通的路径,但不会向服务器发送请求。此模式需要服务端进行配置,否则会出现 404 问题。
3.vue-router 使用方式
-
安装 vue-router:
npm install vue-router -S
-
引入并使用 vue-router:
在main.js
中:import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter);
-
创建路由规则:
const routes = [ { path: '/home', component: Home } ]; const router = new VueRouter({ routes });
-
在 Vue 实例中注册路由:
new Vue({ el: '#app', router, render: h => h(App), });
-
在
App.vue
中使用<router-view>
渲染组件:<template> <div> <router-view></router-view> </div> </template>
4.vue-router 参数传递
4.1 使用 name
传递参数
- 在路由配置中给路由指定
name
属性:{ path: '/user', name: 'user', component: User }
- 通过模板:
<router-link :to="{ name: 'user', params: { userId: 123 }}">Go to User</router-link>
- 在组件中通过
$route.params.userId
获取参数。
4.2 URL 传参
通过 params
形式传递参数:
{ path: '/params/:newsId/:newsTitle', component: Params }
在 URL 中直接使用 /params/198/newsTitle
,在组件中使用 $route.params
获取。
4.3 使用 query
传递参数
<router-link :to="{ name: 'Query', query: { queryId: 123 }}">Query Page</router-link>
通过 $route.query.queryId
获取参数。
5.配置子路由
const routes = [
{
path: '/',
component: ParentComponent,
children: [
{ path: 'child', component: ChildComponent }
]
}
];
在 ParentComponent
中使用 <router-view>
渲染子路由的内容。
6.$route 和 $router 的区别
- $route: 包含当前路由的信息对象(如
path
、params
、query
等)。 - $router: 是路由实例对象,包含跳转方法(如
push
、replace
等)和钩子函数。
7.$router.push 和 $router.replace 的区别
- push: 在 history 记录栈中添加一条新记录,后退按钮可以返回之前的页面。
- replace: 替换当前的历史记录,无法通过后退按钮返回之前的页面。
原文地址:https://blog.csdn.net/weixin_50361560/article/details/142736388
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!