金九银十,又是一波跑路。趁着有空把前端基础和面试相关的知识点都系统的学习一遍,参考一些权威的书籍和优秀的文章,最后加上自己的一些理解,总结出来这篇文章。适合复习和准备面试的同学,其中的知识点包括:
String、Number、Boolean、Null、Undefined、Symbol、BigInt、Object
两者都是存放数据的地方。
栈(stack)是自动分配的内存空间,它存放基本类型的值和引用类型的内存地址。
堆(heap)是动态分配的内存空间,它存放引用类型的值。
JavaScript 不允许直接操作堆空间的对象,在操作对象时,实际操作是对象的引用,而存放在栈空间中的内存地址就起到指向的作用,通过内存地址找到堆空间中的对应引用类型的值。
JavaScript 作为一个弱类型语言,因使用灵活的原因,在一些场景中会对类型进行自动转换。
常见隐式类型转换场景有3种:运算、取反、比较
运算的隐式类型转换会将运算的成员转换为 number 类型。
基本类型转换:
true + false // 1
null + 10 // 10
false + 20 // 20
undefined + 30 // NaN
1 + '2' // "12"
NaN + '' // "NaN"
undefined + '' // "undefined"
null + '' // "null"
'' - 3 // -3
引用类型转换:
[1] + 10 // "110"
[] + 20 // "20"
[1,2] + 20 // "1,220"
[20] - 10 // 10
[1,2] - 10 // NaN
({}) + 10 // "[object Object]10"
({}) - 10 // NaN
解析引用类型转换过程:
[1,2] + 20
// 过程:
[1,2].toString() // '1,2'
'1,2' + 20 // '1,220'
[20] - 10
// 过程
[20].toString() // '20'
Number('20') // 20
20 - 10 // 10
取反的隐式类型转换会将运算的成员转换为 boolean 类型。
这个隐式类型转换比较简单,就是将值转为布尔值再取反:
![] // false
!{} // false
!false // true
通常为了快速获得一个值的布尔值类型,可以取反两次:
!![] // true
!!0 // false
比较分为 严格比较===和 非严格比较==,由于===会比较类型,不会进行类型转换。这里只讨论==。
比较的隐式类型转换基本会将运算的成员转换为 number 类型。
undefined==null // true
''==0 // true
true==1 // true
'1'==true // true
[1]=='1' // true
[1,2]=='1,2' // true
({})=='[object Object]' // true
预编译发生在 JavaScript 代码执行前,对代码进行语法分析和代码生成,初始化的创建并存储变量,为执行代码做好准备。
预编译过程:
例子:
function foo(x, y) {
console.log(x)
var x=10
console.log(x)
function x(){}
console.log(x)
}
foo(20, 30)
// 1. 创建AO对象
AO {}
// 2. 寻找形参和变量声明赋值为 undefined
AO {
x: undefined
y: undefined
}
// 3. 实参形参相统一
AO {
x: 20
y: 30
}
// 4. 函数声明提升
AO {
x: function x(){}
y: 30
}
编译结束后代码开始执行,第一个 x 从 AO 中取值,输出是函数x;x 被赋值为 10,第二个 x 输出 10;函数x 已被声明提升,此处不会再赋值 x,第三个 x 输出 10。
作用域能保证对有权访问的所有变量和函数的有序访问,是代码在运行期间查找变量的一种规则。
函数在运行时会创建属于自己的作用域,将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。
在ES6之前创建块级作用域,可以使用 with 或 try/catch。而在ES6引入 let 关键字后,让块级作用域声明变得更简单。let 关键字可以将变量绑定到所在的任意作用域中(通常是{...}内部)。
{
let num=10
}
console.log(num) // ReferenceError: num is not defined
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
let x=1;
function f(x, y=x) {
console.log(y);
}
f(2) // 2
参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。
let x=1;
function foo(x, y=function() { x=2; }) {
x=3;
y();
console.log(x);
}
foo() // 2
x // 1
y 的默认是一个匿名函数,匿名函数内的x指向同一个作用域的第一个参数x。函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的。y函数执行对参数x重新赋值,最后输出的就是2,而外层的全局变量x依然不受影响。
闭包的本质就是作用域问题。当函数可以记住并访问所在作用域,且该函数在所处作用域之外被调用时,就会产生闭包。
简单点说,一个函数内引用着所在作用域的变量,并且它被保存到其他作用域执行,引用变量的作用域并没有消失,而是跟着这个函数。当这个函数执行时,就可以通过作用域链查找到变量。
let bar
function foo() {
let a=10
// 函数被保存到了外部
bar=function () {
// 引用着不是当前作用域的变量a
console.log(a)
}
}
foo()
// bar函数不是在本身所处的作用域执行
bar() // 10
优点:私有变量或方法、缓存
缺点:闭包让作用域链得不到释放,会导致内存泄漏
JavaScript 中的对象有一个特殊的内置属性 prototype(原型),它是对于其他对象的引用。当查找一个变量时,会优先在本身的对象上查找,如果找不到就会去该对象的 prototype 上查找,以此类推,最终以 Object.prototype 为终点。多个 prototype 连接在一起被称为原型链。
原型继承的方法有很多种,这里不会全部提及,只记录两种常用的方法。
function inherit(Target, Origin){
function F() {};
F.prototype=Origin.prototype;
Target.prototype=new F();
// 还原 constuctor
Target.prototype.constuctor=Target;
// 记录继承自谁
Target.prototype.uber=Origin.prototype;
}
圣杯模式的好处在于,使用中间对象隔离,子级添加属性时,都会加在这个对象里面,不会对父级产生影响。而查找属性是沿着 __proto__ 查找,可以顺利查找到父级的属性,实现继承。
使用:
function Person() {
this.name='people'
}
Person.prototype.sayName=function () { console.log(this.name) }
function Child() {
this.name='child'
}
inherit(Child, Person)
Child.prototype.age=18
let child=new Child()
class Person {
constructor() {
this.name='people'
}
sayName() {
console.log(this.name)
}
}
class Child extends Person {
constructor() {
super()
this.name='child'
}
}
Child.prototype.age=18
let child=new Child()
Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
let str='hello'
str.split('')
基本类型按道理说是没有属性和方法,但是在实际操作时,我们却能从基本类型调用方法,就像一个字符串能调用 split 方法。
为了方便操作基本类型值,每当读取一个基本类型值的时候,后台会创建一个对应的基本包装类型的对象,从而让我们能够调用方法来操作这些数据。大概过程如下:
let str=new String('hello')
str.split('')
str=null
this是函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。我理解的this是函数的调用者对象,当在函数内使用this,可以访问到调用者对象上的属性和方法。
this绑定的四种情况:
优先级new绑定最高,最后到默认绑定。
注意点:构造函数内出现return,如果返回基本类型,则提前结束构造过程,返回实例对象;如果返回引用类型,则返回该引用类型。
// 返回基本类型
function Foo(){
this.name='Joe'
return 123
this.age=20
}
new Foo() // Foo {name: "Joe"}
// 返回引用类型
function Foo(){
this.name='Joe'
return [123]
this.age=20
}
new Foo() // [123]
三者作用都是改变this指向的。
call 和 apply 改变 this 指向并调用函数,它们两者区别就是传参形式不同,前者的参数是逐个传入,后者传入数组类型的参数列表。
bind 改变 this 并返回一个函数引用,bind 多次调用是无效的,它改变的 this 指向只会以第一次调用为准。
Function.prototype.mycall=function () {
if(typeof this !=='function'){
throw 'caller must be a function'
}
let othis=arguments[0] || window
othis._fn=this
let arg=[...arguments].slice(1)
let res=othis._fn(...arg)
Reflect.deleteProperty(othis, '_fn') //删除_fn属性
return res
}
apply 实现同理,修改传参形式即可
Function.prototype.mybind=function (oThis) {
if(typeof this !='function'){
throw 'caller must be a function'
}
let fThis=this
//Array.prototype.slice.call 将类数组转为数组
let arg=Array.prototype.slice.call(arguments,1)
let NOP=function(){}
let fBound=function(){
let arg_=Array.prototype.slice.call(arguments)
// new 绑定等级高于显式绑定
// 作为构造函数调用时,保留指向不做修改
// 使用 instanceof 判断是否为构造函数调用
return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_))
}
// 维护原型
if(this.prototype){
NOP.prototype=this.prototype
fBound.prototype=new NOP()
}
return fBound
}
常用:let、const、扩展运算符、模板字符串、对象解构、箭头函数、默认参数、Promise
数据结构:Set、Map、Symbol
其他:Proxy、Reflect
Set:
WeakSet:
Map:
WeakMap:
Promise 是ES6中新增的异步编程解决方案,避免回调地狱问题。Promise 对象是通过状态的改变来实现通过同步的流程来表示异步的操作, 只要状态发生改变就会自动触发对应的函数。
Promise对象有三种状态,分别是:
状态一旦改变既不可逆,可以通过函数来监听 Promise 状态的变化,成功执行 then 函数的回调,失败执行 catch 函数的回调
浅拷贝是值的复制,对于对象是内存地址的复制,目标对象的引用和源对象的引用指向的是同一块内存空间。如果其中一个对象改变,就会影响到另一个对象。
常用浅拷贝的方法:
let arr=[{a:1}, {b:2}]
let newArr=arr1.slice()
let newArr=[...arr1]
深拷贝是将一个对象从内存中完整的拷贝一份出来,对象与对象间不会共享内存,而是在堆内存中新开辟一个空间去存储,所以修改新对象不会影响原对象。
常用的深拷贝方法:
JSON.parse(JSON.stringify(obj))
function deepClone(obj, map=new WeakMap()) {
if (obj===null || typeof obj !=="object") return obj;
const type=Object.prototype.toString.call(obj).slice(8, -1)
let strategy={
Date: (obj)=> new Date(obj),
RegExp: (obj)=> new RegExp(obj),
Array: clone,
Object: clone
}
function clone(obj){
// 防止循环引用,导致栈溢出,相同引用的对象直接返回
if (map.get(obj)) return map.get(obj);
let target=new obj.constructor();
map.set(obj, target);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
target[key]=deepClone(obj[key], map);
}
}
return target;
}
return strategy[type] && strategy[type](obj)
}
事件委托也叫做事件代理,是一种dom事件优化的手段。事件委托利用事件冒泡的机制,只指定一个事件处理程序,就可以管理某一类型的所有事件。
假设有个列表,其中每个子元素都会有个点击事件。当子元素变多时,事件绑定占用的内存将会成线性增加,这时候就可以使用事件委托来优化这种场景。代理的事件通常会绑定到父元素上,而不必为每个子元素都添加事件。
<ul @click="clickHandler">
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
</ul>
clickHandler(e) {
// 点击获取的子元素
let target=e.target
// 输出子元素内容
consoel.log(target.textContent)
}
防抖用于减少函数调用次数,对于频繁的调用,只执行这些调用的最后一次。
/**
* @param {function} func - 执行函数
* @param {number} wait - 等待时间
* @param {boolean} immediate - 是否立即执行
* @return {function}
*/
function debounce(func, wait=300, immediate=false){
let timer, ctx;
let later=(arg)=> setTimeout(()=>{
func.apply(ctx, arg)
timer=ctx=null
}, wait)
return function(...arg){
if(!timer){
timer=later(arg)
ctx=this
if(immediate){
func.apply(ctx, arg)
}
}else{
clearTimeout(timer)
timer=later(arg)
}
}
}
节流用于减少函数请求次数,与防抖不同,节流是在一段时间执行一次。
/**
* @param {function} func - 执行函数
* @param {number} delay - 延迟时间
* @return {function}
*/
function throttle(func, delay){
let timer=null
return function(...arg){
if(!timer){
timer=setTimeout(()=>{
func.apply(this, arg)
timer=null
}, delay)
}
}
}
Currying(柯里化)是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
通用柯里化函数:
function currying(fn, arr=[]) {
let len=fn.length
return (...args)=> {
let concatArgs=[...arr, ...args]
if (concatArgs.length < len) {
return currying(fn, concatArgs)
} else {
return fn.call(this, ...concatArgs)
}
}
}
使用:
let sum=(a,b,c,d)=> {
console.log(a,b,c,d)
}
let newSum=currying(sum)
newSum(1)(2)(3)(4)
优点:
堆分为新生代和老生代,分别由副垃圾回收器和主垃圾回收器来负责垃圾回收。
一般刚使用的对象都会放在新生代,它的空间比较小,只有几十MB,新生代里还会划分出两个空间:form空间和to空间。
对象会先被分配到form空间中,等到垃圾回收阶段,将form空间的存活对象复制到to空间中,对未存活对象进行回收,之后调换两个空间,这种算法称之为 “Scanvage”。
新生代的内存回收频率很高、速度也很快,但空间利用率较低,因为让一半的内存空间处于“闲置”状态。
老生代的空间较大,新生代经过多次回收后还存活的对象会被送到老生代。
老生代使用“标记清除”的方式,从根元素开始遍历,将存活对象进行标记。标记完成后,对未标记的对象进行回收。
经过标记清除之后的内存空间会产生很多不连续的碎片空间,导致一些大对象无法存放进来。所以在回收完成后,会对这些不连续的碎片空间进行整理。
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
JavaScript 作为一门无类的语言,传统的单例模式概念在 JavaScript 中并不适用。稍微转换下思想:单例模式确保只有一个对象,并提供全局访问。
常见的应用场景就是弹窗组件,使用单例模式封装全局弹窗组件方法:
import Vue from 'vue'
import Index from './index.vue'
let alertInstance=null
let alertConstructor=Vue.extend(Index)
let init=(options)=>{
alertInstance=new alertConstructor()
Object.assign(alertInstance, options)
alertInstance.$mount()
document.body.appendChild(alertInstance.$el)
}
let caller=(options)=>{
// 单例判断
if(!alertInstance){
init(options)
}
return alertInstance.show(()=>alertInstance=null)
}
export default {
install(vue){
vue.prototype.$alert=caller
}
}
无论调用几次,组件也只实例化一次,最终获取的都是同一个实例。
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式是开发中最常用的设计模式,在一些场景下如果存在大量的 if/else,且每个分支点的功能独立,这时候就可以考虑使用策略模式来优化。
就像就上面手写深拷贝就用到策略模式来实现:
function deepClone(obj, map=new WeakMap()) {
if (obj===null || typeof obj !=="object") return obj;
const type=Object.prototype.toString.call(obj).slice(8, -1)
// 策略对象
let strategy={
Date: (obj)=> new Date(obj),
RegExp: (obj)=> new RegExp(obj),
Array: clone,
Object: clone
}
function clone(obj){
// 防止循环引用,导致栈溢出,相同引用的对象直接返回
if (map.get(obj)) return map.get(obj);
let target=new obj.constructor();
map.set(obj, target);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
target[key]=deepClone(obj[key], map);
}
}
return target;
}
return strategy[type] && strategy[type](obj)
}
这样的代码看起来会更简洁,只需要维护一个策略对象,需要新功能就添加一个策略。由于策略项是单独封装的方法,也更易于复用。
定义:为一个对象提供一个代用品,以便控制对它的访问。
当不方便直接访问一个对象或者不满足需要的时候,提供一个代理对象来控制对这个对象的访问,实际访问的是代理对象,代理对象对请求做出处理后,再转交给本体对象。
使用缓存代理请求数据:
function getList(page) {
return this.$api.getList({
page
}).then(res=> {
this.list=res.data
return res
})
}
// 代理getList
let proxyGetList=(function() {
let cache={}
return async function(page) {
if (cache[page]) {
return cache[page]
}
let res=await getList.call(this, page)
return cache[page]=res.data
}
})()
上面的场景是常见的分页需求,同一页的数据只需要去后台获取一次,并将获取到的数据缓存起来,下次再请求同一页时,便可以直接使用之前的数据。
定义:它定义对象间的一种一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到通知。
发布订阅模式主要优点是解决对象间的解耦,它的应用非常广泛,既可以用在异步编程中,也可以帮助我们完成松耦合的代码编写。像 eventBus 的通信方式就是发布订阅模式。
let event={
events: [],
on(key, fn){
if(!this.events[key]) {
this.events[key]=[]
}
this.events[key].push(fn)
},
emit(key, ...arg){
let fns=this.events[key]
if(!fns || fns.length==0){
return false
}
fns.forEach(fn=> fn.apply(this, arg))
}
}
上面只是发布订阅模式的简单实现,还可以为其添加 off 方法来取消监听事件。在 Vue 中,通常是实例化一个新的 Vue 实例来做发布订阅中心,解决组件通信。而在小程序中可以手动实现发布订阅模式,用于解决页面通信的问题。
定义:动态地为某个对象添加一些额外的职责,而不会影响对象本身。
装饰器模式在开发中也是很常用的设计模式,它能够在不影响源代码的情况下,很方便的扩展属性和方法。比如以下应用场景是提交表单。
methods: {
submit(){
this.$api.submit({
data: this.form
})
},
// 为提交表单添加验证功能
validateForm(){
if(this.form.name==''){
return
}
this.submit()
}
}
想象一下,如果你刚接手一个项目,而 submit 的逻辑很复杂,可能还会牵扯到很多地方。冒然的侵入源代码去扩展功能会有风险,这时候装饰器模式就帮上大忙了。
MVVM 对应 3个组成部分,Model(模型)、View(视图) 和 ViewModel(视图模型)。
View 不能和 Model 直接通信,它们只能通过 ViewModel 通信。Model 和 ViewModel 之间的交互是双向的,ViewModel 通过双向数据绑定把 View 层和 Model 层连接起来,因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
题外话,你可能不知道 Vue 不完全是 MVVM 模式:
严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 在组件提供了 $refs 这个属性,让 Model 可以直接操作 View,违反了这一规定。
流程主要分为三个部分:
当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
JavaScript 中的对象作为引用类型,如果是创建多个实例,直接使用对象会导致实例的共享引用。而这里创建多个实例,指的是组件复用的情况。因为在编写组件时,是通过 export 暴露出去的一个对象,如果组件复用的话,多个实例都是引用这个对象,就会造成共享引用。使用函数返回一个对象,由于是不同引用,自然可以避免这个问题发生。
“计算属性Watcher”会带有一个 dirty 的属性,在初始化取值完成后,会将 dirty 设置为 false。只要依赖属性不更新,dirty 永远为 false,重复取值也不会再去执行求值函数,而是直接返回结果,从而实现缓存。相反,依赖属性更新会将“计算属性 Watcher”的 dirty 设置为 true,在页面渲染对计算属性取值时,再次触发求值函数更新计算属性。
Object.defineProperty(target, key, {
get() {
const watcher=this._computedWatchers && this._computedWatchers[key]
// 计算属性缓存
if (watcher.dirty) {
// 计算属性求值
watcher.evaluate()
}
return watcher.value
}
})
双向绑定是视图变化会反映到数据,数据变化会反映到视图,v-model 就是个很好理解的例子。其实主要考查的还是响应式原理,响应式原理共包括3个主要成员,Observer 负责监听数据变化,Dep 负责依赖收集,Watcher 负责数据或视图更新,我们常说的收集依赖就是收集 Watcher。
响应式原理主要工作流程如下:
Vue 内部重写数组原型链,当数组发生变化时,除了执行原生的数组方法外,还会调用 dep.notify 通知 Watcher 更新。触发数组更新的方法共7种:
keep-alive 是 Vue 的内置组件,同时也是一个抽象组件,它主要用于组件缓存。当组件切换时会将组件的VNode缓存起来,等待下次重新激活时,再将缓存的组件VNode渲染出来,从而实现缓存。
常用的两个属性 include 和 exclude,支持字符串、正则和数组的形式,允许组件有条件的进行缓存。还有 max 属性,用于设置最大缓存数。
两个生命周期 activated 和 deactivated,在组件激活和失活时触发。
keep-alive 的缓存机制运用LRU(Least Recently Used)算法,
在下次 dom 更新结束之后执行延迟回调。nextTick 主要使用了宏任务和微任务。根据执行环境分别尝试采用:
nextTick 主要用于内部 Watcher 的异步更新,对外我们可以使用 Vue.nextTick 和 vm.$nextTick。在 nextTick 中可以获取更新完成的 dom。
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
单向数据流只允许数据由父组件传递给子组件,数据只能由父组件更新。当数据传递到多个子组件,而子组件能够在其内部更新数据时,在主观上很难知道是哪个子组件更新了数据,导致数据流向不明确,从而增加应用调试的难度。
但子组件更新父组件数据的场景确实存在,有3种方法可以使用:
Object.definedProperty 只能检测到属性的获取和设置,对于新增和删除是没办法检测的。在数据初始化时,由于不知道哪些数据会被用到,Vue 是直接递归观测全部数据,这会导致性能多余的消耗。
Proxy 劫持整个对象,对象属性的增加和删除都能检测到。Proxy 并不能监听到内部深层的对象变化,因此 Vue 3.0 的处理方式是在 getter 中去递归响应式,只有真正访问到的内部对象才会变成响应式,而不是无脑递归,在很大程度上提升了性能。
路由懒加载是性能优化的一种手段,在编写代码时可以使用 import() 引入路由组件,使用懒加载的路由会在打包时单独出来成一个 js 文件,可以使用 webpackChunkName 自定义包名。在项目上线后,懒加载的 js 文件不会在第一时间加载,而是在访问到对应的路由时,才会动态创建 script 标签去加载这个 js 文件。
{
path:'users',
name:'users',
component:()=> import(/*webpackChunkName: "users"*/ '@/views/users'),
}
路由进入前调用
const router=new VueRouter({ ... })
router.beforeEach((to, from, next)=> {
// ...
})
在所有组件内守卫和异步组件被解析之后调用
router.beforeResolve((to, from, next)=> {
// ...
})
路由在确认后调用
router.afterEach((to, from)=> {
// ...
})
路由进入前调用,beforeEnter 在 beforeEach 之后执行
const router=new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next)=> {
// ...
}
}
]
})
路由确认前调用,组件实例还没被创建,不能获取组件实例 this
beforeRouteEnter (to, from, next) {
// ...
// 可以通过回调访问实例
next(vm=> {
// vm 为组件实例
})
},
路由改变时调用,可以访问组件实例
beforeRouteUpdate (to, from, next) {
// ...
},
离开该组件的对应路由时调用,可以访问组件实例 this
beforeRouteLeave (to, from, next) {
// ...
}
vue-router原理是更新视图而不重新请求页面。vue-router共有3种模式:hash模式、history模式、abstract模式。
hash模式使用 hashchange 监听地址栏的hash值的变化,加载对应的页面。每次的hash值变化后依然会在浏览器留下历史记录,可以通过浏览器的前进后退按钮回到上一个页面。
history模式基于History Api实现,使用 popstate 监听地址栏的变化。使用 pushState 和 replaceState 修改url,而无需加载页面。但是在刷新页面时还是会向后端发起请求,需要后端配合将资源定向回前端,交由前端路由处理。
不涉及和浏览器地址的相关记录。通过数组维护模拟浏览器的历史记录栈。
跨模块调用是指当前命名空间模块调用全局模块或者另一个命名空间模块。在调用 dispatch 和 commit 时设置第三个参数为 {root:true}。
modules: {
foo: {
namespaced: true,
actions: {
someAction ({ dispatch, commit, getters, rootGetters }) {
// 调用自己的action
dispatch('someOtherAction') // -> 'foo/someOtherAction'
// 调用全局的action
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
// 调用其他模块的action
dispatch('user/someOtherAction', null, { root: true }) // -> 'user/someOtherAction'
},
someOtherAction (ctx, payload) { ... }
}
}
}
vuex存储的状态在页面刷新后会丢失,使用持久化技术能保证页面刷新后状态依然存在。
这里只记录常用的两种模块:CommonJS模块、ES6模块。
Node.js 采用 CommonJS 模块规范,在服务端运行时是同步加载,在客户端使用需要编译后才可以运行。
module.exports 不为空:
// nums.js
exports.a=1
module.exports={
b: 2
}
exports.c=3
let nums=require('./nums.js') // { b: 2 }
module.exports 为空:
// nums.js
exports.a=1
exports.c=3
let nums=require('./nums.js') // { a: 1, c: 3 }
值拷贝的体现:
// nums.js
let obj={
count: 10
}
let count=20
function addCount() {
count++
}
function getCount() {
return count
}
function addObjCount() {
obj.count++
}
module.exports={ count, obj, addCount, getCount, addObjCount }
let { count, obj, addCount, getCount, addObjCount }=require('./nums.js')
// 原始类型不受影响
console.log(count) // 20
addCount()
console.log(count) // 20
// 如果想获取到变化的值,可以使用函数返回
console.log(getCount()) // 21
// 引用类型会被改变
console.log(obj) // { count: 10 }
addObjCount()
console.log(obj) // { count: 11 }
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
// nums.js
export let count=20
export function addCount() {
count++
}
export default {
other: 30
}
// 同时引入 export default 和 export 的变量
import other, { count, addCount } from './async.js'
console.log(other) // { other: 30 }
console.log(count) // 20
addCount()
console.log(count) // 21
数据变化过程:字节 → 字符 → 令牌 → 树 → 页面
在布局完成后,对DOM布局进行修改(比如大小或位置),会引起页面重新计算布局,这个过程称为“回流”。
对DOM进行不影响布局的修改引起的屏幕局部绘制(比如背景颜色、字体颜色),这个过程称为“重绘”。
回流一定会引起重绘,而重绘不一定会引起回流。由于回流需要重新计算节点布局,回流的渲染耗时会高于重绘。
对于回流重绘,浏览器本身也有优化策略,浏览器会维护一个队列,将回流重绘操作放入队列中,等队列到达一定时间,再按顺序去一次性执行队列的操作。
但是也有例外,有时我们需要获取某些样式信息,例如:
offsetTop,offsetLeft,offsetWidth,offsetHeight,scrollTop/Left/Width/Height,clientTop/Left/Width/Height,getComputedStyle(),或者 IE 的 currentStyle。
这时,浏览器为了反馈准确的信息,需要立即回流重绘一次,所以可能导致队列提前执行。
在浏览器的实现上,诸如渲染任务、JavaScript 脚本执行、User Interaction(用户交互)、网络处理都跑在同一个线程上,当执行其中一个类型的任务的时候意味着其他任务的阻塞,为了有序的对各个任务按照优先级进行执行浏览器实现了我们称为 Event Loop 调度流程。
简单来说,Event Loop 就是执行代码、收集和处理事件以及执行队列中子任务的一个过程。
在一次新的事件循环的过程中,遇到宏任务时,宏任务将被加入任务队列,但需要等到下一次事件循环才会执行。
常见宏任务:setTimeout、setInterval、requestAnimationFrame
当前事件循环的任务队列为空时,微任务队列中的任务就会被依次执行。在执行过程中,如果遇到微任务,微任务被加入到当前事件循环的微任务队列中。简单来说,只要有微任务就会继续执行,而不是放到下一个事件循环才执行。
微任务队列属于任务运行环境内的一员,并非处于全局的位置。也就是说,每个任务都会有一个微任务队列。
常见微任务:Promise.then、Promise.catch、MutationObserver
在当前任务运行环境内,微任务总是先于宏任务执行;
requestAnimationFrame 回调在页面渲染之前调用,适合做动画;
requestIdleCallback 在渲染屏幕之后调用,可以使用它来执行一些不太重要的任务。
源是由 URL 中协议、主机名(域名)以及端口共同组成的部分。
同源策略是浏览器的行为,为了保护本地数据不被JavaScript代码获取回来的数据污染,它是存在于浏览器最核心也最基本的安全功能。
所谓同源指的是:协议、域名、端口号必须一致,只要有一个不相同,那么就是“跨源”。
最常见的同源策略是因为域名不同,也就是常说的“跨域”。一般分为请求跨域和页面跨域。
常用方法是CORS和代理转发。
对于CORS请求,浏览器将其分成两个类型:简单请求和非简单请求。
简单请求符合下面 2 个特征:
任意一条要求不符合的即为非简单请求。常见是自定义 header,例如将token 设置到请求头。
在处理非简单请求时,浏览器会先发出“预检请求”,预检请求为OPTIONS方法,以获知服务器是否允许该实际请求,避免跨域请求对服务器产生预期外的影响。如果预检请求返回200允许通过,才会发真实的请求。
预检请求并非每次都需要发送,可以使用 Access-Control-Max-Age 设置缓存时间进行优化,减少请求发送。
增加头部设定,头部内容以键值对的形式设置。请求头部通过 Accept 字段来告诉服务端可以接收的文件类型,响应头部再通过 Content-Type 字段来告诉浏览器返回文件的类型。
HTTP1.0中每次通信都需要经历建立连接、传输数据和断开连接三个阶段,这会增加大量网络开销。
HTTP1.1增加持久化连接,即连接传输完毕后,TCP连接不会马上关闭,而是其他请求可以复用连接。这个连接保持到浏览器或者服务器要求断开连接为止。
HTTP1.1虽然减少连接带来的性能消耗,但是请求最大并发受到限制,同一域下的HTTP连接数根据浏览器不同有所变化,一般是6 ~ 8个。而且一个TCP连接同一时刻只能处理一个请求,当前请求未结束之前,其他请求只能处于阻塞状态。
HTTP2.0中增加“多路复用”的机制,不再受限于浏览器的连接数限制。基于二进制分帧,客户端发送的数据会被分割成带有编号的碎片(二进制帧),然后将这些碎片同时发送给服务端,服务端接收到数据后根据编号再合并成完整的数据。服务端返回数据也同样遵循这个过程。
第一次握手:客户端向服务端发起连接请求报文,报文中带有一个连接标识(SYN);
第二次握手:服务端接收到客户端的报文,发现报文中有连接标识,服务端知道是一个连接请求,于是给客户端回复确认报文(带有SYN标识);
第三次握手:客户端收到服务端回复确认报文,得知服务端允许连接,于是客户端回复确认报文给服务端,服务端收到客户端的回复报文后,正式建立TCP连接;
如果是两次握手,在第二次握手出现确认报文丢失,客户端不知道服务端是否准备好了,这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。
如果是三次握手,在第三次握手出现确认报文丢失,服务端在一段时间没有收到客户端的回复报文就会重新第二次握手,客户端收到重复的报文会再次给服务端发送确认报文。
三次握手主要考虑是丢包重连的问题。
第一次挥手:客户端向服务端发出连接释放报文,报文中带有一个连接释放标识(FIN)。此时客户端不能再发送数据,但是可以正常接收数据;
第二次挥手:服务端接收到客户端的报文,知道是一个连接释放请求。服务端给客户端回复确认报文,但要注意这个回复报文未带有FIN标识。此时服务端处于关闭等待状态,这个状态还要持续一段时间,因为服务端可能还有数据没发完;
第三次挥手:服务端将最后的数据发送完毕后,给客户端回复确认报文(带有FIN标识),这个才是通知客户端可以释放连接的报文;
第四次挥手:客户端收到服务端回复确认报文后,于是客户端回复确认报文给服务端。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。
服务端需要确保数据完整性,只能先回复客户端确认报文告诉客户端我收到了报文,进入关闭等待状态。服务端在数据发送完毕后,才回复FIN报文告知客户端数据发送完了,可以断开了,由此多了一次挥手过程。
HTTPS之所以比HTTP安全,是因为对传输内容加密。HTTPS加密使用对称加密和非对称加密。
对称加密:双方共用一把钥匙,可以对内容双向加解密。但是只要有人和服务器通信就能获得密钥,也可以解密其他通信数据。所以相比非对称加密,安全性较低,但是它的效率比非对称加密高。
非对称加密:非对称加密会生成公钥和私钥,一般是服务端持有私钥,公钥向外公开。非对称加密对内容单向加解密,即公钥加密只能私钥解,私钥加密只能公钥解。非对称加密安全性虽然高,但是它的加解密效率很低。
CA证书:由权威机构颁发,用于验证服务端的合法性,其内容包括颁发机构信息、公钥、公司信息、域名等。
对称加密不安全主要是因为密钥容易泄露,那只要保证密钥的安全,就可以得到两全其美的方案,加解密效率高且安全性好。所以HTTPS在传输过程中,对内容使用对称加密,而密钥使用非对称加密。
HTTP 缓存包括强缓存和协商缓存,强缓存的优先级高于协商缓存。缓存优点在于使用浏览器缓存,对于某些资源服务端不必重复发送,减小服务端的压力,使用缓存的速度也会更快,从而提高用户体验。
强缓存在浏览器加载资源时,先从缓存中查找结果,如果不存在则向服务端发起请求。
HTTP/1.0 中可以使用响应头部字段 Expires 来设置缓存时间。
客户端第一次请求时,服务端会在响应头部添加 Expirss 字段,浏览器在下一次发送请求时,会对比时间和Expirss的时间,没有过期使用缓存,过期则发送请求。
HTTP/1.1 提出了 Cache-Control 响应头部字段。
一般会设置 max-age 的值,表示该资源需要缓存多长时间。Cache-Control 的 max-age 优先级高于 Expires。
协商缓存的更新策略是不再指定缓存的有效时间,而是浏览器直接发送请求到服务端进行确认缓存是否更新,如果请求响应返回的 HTTP 状态为 304,则表示缓存仍然有效。
Last-Modified 和 If-Modified-Since 对比资源最后修改时间来实现缓存。
ETag 和 If-None-Match 对比资源哈希值,哈希值由资源内容计算得出,即依赖资源内容实现缓存。
跨站脚本(Cross Site Scripting,XSS)指攻击者在页面插入恶意代码,当其他用户访问时,浏览会器解析并执行这些代码,达到窃取用户身份、钓鱼、传播恶意代码等行为。一般我们把 XSS 分为反射型、存储型、DOM 型 3 种类型。
反射型 XSS 也叫“非持久型 XSS”,是指攻击者将恶意代码通过请求提交给服务端,服务端返回的内容,也带上了这段 XSS 代码,最后导致浏览器执行了这段恶意代码。
反射型 XSS 攻击方式需要诱导用户点击链接,攻击者会伪装该链接(例如短链接),当用户点击攻击者的链接后,攻击者便可以获取用户的 cookie 身份信息。
案例:
服务端直接输出参数内容:
<? php
$input=$_GET["param"];
echo "<div>".$input."</div>";
恶意代码链接:
http://www.a.com/test.php?param=<srcipt src="xss.js"></script>
存储型 XSS 也叫“持久型XSS”,会把用户输入的数据存储在服务端,这种XSS具有很强的稳定性。
案例:
比如攻击者在一篇博客下留言,留言包含恶意代码,提交到服务端后被存储到数据库。所有访问该博客的用户,在加载出这条留言时,会在他们的浏览器中执行这段恶意的代码。
DOM 型 XSS 是一种特殊的反射型 XSS,它也是非持久型 XSS。相比于反射型 XSS,它不需要经过服务端,而是改变页面 DOM 来达到攻击。同样,这种攻击方式也需要诱导用户点击。
案例:
目标页面:
<html>
<body>hello</body>
</html>
<script>
let search=new URLSearchParams(location.search)
document.write("hello, " + search.get('name') + '!')
</script>
恶意代码链接:
http://www.a.com/test.index?name=<srcipt src="xss.js"></script>
CSRF 攻击就是在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作。CSRF 并不需要直接获取用户信息,只需要“借用”用户的登录信息相关操作即可,隐蔽性更强。
案例:
假设现在有一个博客网站,得知删除博文的 URL 为:
http://blog.com?m=delete&id=123
攻击者构造一个页面,内容为:
<img src="http://blog.com?m=delete&id=123"></img>
攻击者伪装该网站链接并诱导用户进行点击,用户恰好访问过 blog.com,与该网站的 cookie 身份验证信息还未过期。这时进入攻击者的网站,img 发起请求,请求里携带上cookie,成功删除博文。但是对于用户是无感知的,当用户返回到博客时会发现博文不见了,而这个请求是属于合法请求,因为攻击者借用受害者的身份信息进行操作。
攻击者创建一个网页利用 iframe 包含目标网站,然后通过设置透明度等方式隐藏目标网站,使用户无法察觉目标网站的存在,并且把它遮罩在网页上。在网页中诱导用户点击特定的按钮,而这个按钮的位置和目标网站的某个按钮重合,当用户点击网页上的按钮时,实际上是点击目标网站的按钮。
if (top.location !=location) {
top.location=self.location
}
转自 https://www.cnblogs.com/chanwahfung/p/13616849.html
么理解H5与小程序这两种不同的载体?这篇文章里,作者尝试从产品经理角度对二者做了分析解读,一起来看看,或许可以帮你更好地理清这两个概念。
今天与大家聊聊H5和小程序。还记得小吴在刚成为产品经理时,经常会听到技术同学甚至设计同学提到“这是个H5”、“用H5做就行了”之类的话,但那时的我并不理解H5是个什么东西,听起来觉得很高大上,但实际上总觉得H5与优惠、活动、打折挂钩,而且看上去千篇一律,辨识度很高。
H5和小程序是两种不同的产品形态的载体,而且是当下比较流行的两种技术解决方案。以下是小吴用文心一言4.0生成的对H5和小程序的本质与技术原理的解读:
HTML5,通常称为H5,是最新的HTML标准。它的核心优势在于跨平台性。无论是在桌面还是移动设备上,只要有浏览器,H5应用就可以运行。技术上,H5依赖于浏览器作为其运行环境,通过HTML、CSS和JavaScript等语言实现页面布局、样式设计和交互逻辑。
小程序,则是一种不需要下载安装即可使用的应用,它实现了“触手可及”的轻量级体验。小程序通常依托于大型平台(如微信、支付宝)的生态系统,运行在这些平台的专门环境中。它们通过各自的开发框架,允许开发者快速构建应用。
相信大家看得一头雾水,小吴用稍微简单一点的表达方式再和大家解释一下。
简单来说,H5(HTML5)是一种制作网页的标准,它就像是网页的“建筑材料”,包含了文本、图片、视频和各种互动元素的排版和设计,也就是说H5就是一个网页。
正常来说我们使用百度、谷歌等浏览器上网,访问的都是一个个网页,也就是说,只要你的设备上有浏览器,就可以访问H5页面。想象一下,如果每次要看一篇新闻或购物就需要先下载一个程序,是不是很麻烦?而且现在的APP想体验服务都得注册、登录、输入验证码,但H5就解决了这个问题,你直接在浏览器上打开网页,无需下载安装任何额外的应用,就可以享受其服务。
再来想像这样一种场景,如果没有H5,你想告诉你的朋友“年货节开始啦,快去囤货吧,打折力度真的好大”,恐怕你只能通过微信等其他联系方式告知你的朋友,然后你的朋友还得下载京东或淘宝去享受年货节。有了H5以后,京东等电商平台都会将自己的促销活动做成H5页面的形式,这样一来,你就可以将这个H5页面分享给你的朋友,而你的朋友只需要点击进去,就可以参与到这个活动当中。
所以H5页面是可以通过链接分享的,这就像分享一个有趣的故事一样简单,用户可以通过社交媒体、消息类应用轻松分享。
而小程序大家可以想象成一个迷你版的应用,它通常嵌入在像微信或支付宝这样的大型应用中。使用小程序时,你不需要像普通应用那样去应用商店下载,只需在微信或支付宝里搜索或扫码就可以直接使用。比方说支付宝的蚂蚁森林,微信的同城旅行买火车票,都是支付宝、微信生态下的小程序。
接下来我们来聊聊H5和小程序的区别,以及为什么有的业务选择H5开发,而有些业务选择基于小程序开发。
首先从运行环境的差异角度来说,我们经常从微信里点了一个链接分享,然后解析出来是一个商城的优惠活动,这基本上都是H5页面。但我们应该没在微信里面用过蚂蚁森林或者其他支付宝系的小程序吧。这是因为它的核心优势在于跨平台性。无论是在桌面还是移动设备上,只要有浏览器,H5应用就可以运行。
而小程序则运行在特定平台(如微信、支付宝)的专用容器中,这些容器为小程序提供了一套统一的API和界面标准。例如微信小程序的运行环境是微信应用本身,它为小程序提供了一系列微信特有的功能和接口,如微信支付、朋友圈等。这种专用环境确保了小程序在特定平台上有更好的性能和用户体验,但同时也限制了它们在其他平台上的运行。
从开发成本角度来说,H5相对来说较低一些。微信、支付宝这些互联网巨头都是移动互联网时代的幸运儿,在移动互联网时代之前,大多数朋友们都在通过电脑浏览器进行网上冲浪。而H5的开发依赖于传统的Web技术栈,如HTML、CSS和JavaScript,这些技术广泛应用且成熟,因此有大量现成的工具和框架可供使用,这些工具和框架可以显著提升开发效率。
同时,H5项目通常只需要一套代码即可在多个平台上运行,这进一步减少了开发和维护的工作量。
小程序的开发则需要遵循特定平台的开发框架和标准。例如微信小程序需要使用微信提供的开发工具和API,感兴趣的各位可以搜索一下微信开放平台。虽然这些平台提供了一些便利的开发工具,但开发者仍需学习和适应每个平台的特有规范和接口。此外,如果需要在多个平台上推出小程序,开发者可能需要为每个平台单独开发和维护代码,这无疑增加了工作量和成本。
虽然在开发成本方面H5要显得比较有优势,但是小程序的能力还是要更强大一些。如果我们把APP理解为程序,那小程序就是比程序小一点儿的程序而已,这变相地说明小程序是一种接近原生应用的技术。相较于H5,小程序能更深层次地访问系统资源和权限。
例如小程序可以利用手机硬件(如摄像头、GPS)、实现更丰富的功能(如扫一扫、即时支付)。这些深度整合的功能为小程序提供了更强大的能力,比如微信生态下的顺丰快递小程序,一打开就可以自动定位到我们的当前位置,此外我们也可以直接通过微信支付将快递费转给快递小哥,甚至可以在小程序中直接打开我们的手机相机,进行快递货物的拍摄。
这些功能都是得益于小程序可以获取我们手机的系统权限,通常我们在使用时都会收到手机的系统提示,比方说是否同意获取相册中全部照片,是否同意获取当前位置等。虽然现代浏览器允许H5应用访问一些设备功能,如地理位置、摄像头等,但对于一些高级功能,如后台运行、推送通知等,在H5应用中要么无法实现,要么实现起来较为复杂。
其实现在小程序还是很火爆的,无论是从产品开发的技术选型角度,还是从用户体验角度,小程序都完胜H5一筹,甚至比APP还要吃香。任何行业发展到中后期,都是几家独大,而群英荟萃百花齐放的场面通常是在行业初期。互联网行业更是如此。
目前大家都知道的这几家互联网巨头,都在强调所谓的生态概念。广义来说,所谓的生态就是尽可能地覆盖多的业务,尽可能地抢占市场以及用户的时间,终极愿景是用户手机里只需要安装我的APP,就可以高度便捷化衣食住行,不再需要安装其他APP了。
而从技术角度来理解生态,这么多的功能如何搭建在我的APP中呢?大家可以把这种生态模式理解为乐高积木,这些巨头公司会把积木搭建方法提供给所有开发者并欢迎各位开发者来我的地盘上构建五花八门的乐高积木作品。但乐高的积木不能用在其他品牌的积木中。久而久之,乐高的积木越堆越大,这也便是生态的形成。
由于这些互联网巨头掌握着巨大的流量,很多互联网公司已经将小程序业务作为主力业务,毕竟参天大树好乘凉,寄居蟹也有流量吃。不知道在这个移动互联网时代还会不会有新的生态出现,也不知道这种繁荣的生态下腐蚀的是不是互联网从业者的创新能力。
最后希望这篇文章对各位有所帮助,祝各位睡个好觉。
作者:产品小吴,公众号:产品小吴
本文由 @产品小吴 原创发布于人人都是产品经理,未经作者许可,禁止转载。
题图来自Unsplash,基于CC0协议。
该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务。
读:本文将从全国公路客运票务智慧管理系统的开发目的、开发背景、目标用户、系统设计、系统架构、主要功能模块等方面进行分析,软件主要功能包括:班次管理、部门管理、车辆管理、车站管理、订单管理、公告管理、角色管理、积分管理、路线管理、票务管理、评论管理、权限管理、日志管理、数据报表、投诉管理、用户管理、优惠券管理、站内信管理、折扣管理,全文约5404字,需要10分钟左右。感谢阅读,如有建议和意见欢迎评论交流。
软件开发目的与背景
全国公路客运票务智慧管理系统的设计初衷是为了彻底革新我国公路客运行业的服务质量和运营效率。随着科技的飞速发展和数字化转型的需求,传统的票务管理方式已无法满足日益增长的业务复杂性和用户体验的要求。本系统应运而生,旨在构建一个集智能化、集成化于一体的信息化平台。
在过去的交通行业中,班次调度、部门协作、车辆维护等各个环节存在着信息孤岛的问题,数据难以实时共享,不仅降低了工作效率,还可能导致服务失误。我们的目标是通过这款系统,实现全程的信息化管理,消除信息壁垒,提升决策支持的精确度。
系统的关键模块如班次管理、车辆管理、车站管理等,旨在优化线路规划和资源调度,提高运输效率;订单管理和票务管理则致力于简化购票流程,提供便捷的在线支付方式,提升乘客满意度;角色管理和权限管理确保了数据安全和操作合规性。
此外,积分管理、优惠券管理、折扣管理等功能将激励用户重复使用服务,增强用户粘性;投诉管理模块能快速响应并解决用户问题,体现我们对服务质量的高度关注;数据报表和日志管理为管理者提供了有力的数据分析工具,便于他们进行持续改进。
全国公路客运票务智慧管理系统的开发,不仅是技术进步的体现,更是推动我国公路客运行业向现代化、智能化转型的重要一步。我们期待这款系统能助力整个行业步入全新的发展阶段,实现服务升级、运营高效和用户体验的全面提升。
全国公路客运票务智慧管理系统是一款专为公路运输行业的管理部门和运营商设计的高效信息化工具。它的主要应用场景是全国范围内的长途汽车公司、客运站、旅行社等,这些单位需要管理和优化他们的运营服务,提升票务销售效率,同时满足乘客的便捷需求。
该系统的主要目标用户是各级公路客运公司的管理层,如总经理、运营管理部、售票中心、客户服务等部门负责人;售票员、客服人员、车辆驾驶员以及乘客。对于管理层,系统提供班次调度、部门协作、车辆维护、车站运营等全方位的管理平台,帮助他们实时监控运营状态,提高决策效率。售票员和客服人员可以通过订单管理、票务查询等功能,为乘客提供快速、准确的服务,同时也能通过积分管理、优惠券发放吸引和留住客户。
对于乘客,系统则提供了在线购票、查询班车时刻、路线选择、评价反馈等一站式的服务,使得购票过程更为简单快捷,同时还能通过站内信获取最新资讯和优惠政策。投诉管理模块能及时处理并解决乘客的问题,保障服务质量。
总而言之,全国公路客运票务智慧管理系统旨在通过智能化的技术手段,简化业务流程,提升行业效率,优化用户体验,从而推动整个公路客运行业的数字化转型和升级。
全国公路客运票务智慧管理系统是一款专为公路客运行业设计的全面信息化解决方案。该系统基于Java开发语言,采用了高效稳定的SpringMVC架构,数据库则选用成熟可靠的MySQL,旨在提升行业运营效率和管理水平。
系统的核心功能模块丰富多样,涵盖了业务流程的关键环节。班次管理模块允许管理员灵活创建、修改和查询各类班车信息,确保乘客查询的准确性。部门、车辆和车站管理模块实现资源的集中化管理,便于追踪和维护。订单管理模块支持在线预订、支付及实时状态查询,简化购票流程。
公告管理、角色管理和权限管理模块提供了一套完整的权限控制体系,确保信息安全与合规。积分管理鼓励乘客积极参与,增强忠诚度。路线管理和票务管理模块紧密结合,方便用户了解线路信息和票务价格。评论管理模块收集用户反馈,提升服务质量。数据报表模块提供详尽的业务分析,帮助决策者做出明智选择。
投诉管理模块确保问题快速解决,维护用户权益。用户管理和优惠券管理模块关注用户体验,优化营销策略。站内信管理和折扣管理则进一步提升了用户交互和促销效果。日志管理确保系统的稳定运行,而数据安全通过严格的加密措施得以保障。
总之,全国公路客运票务智慧管理系统是一套全面、智能的管理工具,旨在通过数字化手段,提升公路客运行业的运营效率和服务质量,推动行业向现代化、智能化转型。
全国公路客运票务智慧管理系统采用先进的B/S(Browser/Server)架构设计,这是一种基于互联网的分布式应用模型,用户通过浏览器访问,服务器端负责数据处理和业务逻辑。以下是详细的系统架构描述:
1. 前端界面:使用HTML5、CSS3及JavaScript技术构建,提供用户友好的图形化界面,包括各类模块如班次查询、订单操作等,支持响应式设计,适应不同设备的访问。
2. 后端服务:核心部分由Java语言开发,利用Spring MVC框架进行结构设计,实现了模块间的松耦合。它负责处理用户的请求,调用相应的业务逻辑,然后返回处理结果。
3. 数据库管理:数据库采用关系型数据库,如MySQL或Oracle,存储用户信息、订单记录、车辆数据等关键信息,通过ORM工具如MyBatis进行数据交互。
4. 模块化设计:系统分为多个独立模块,如票务管理、用户管理等,每个模块都有自己明确的功能,有利于团队协作和代码维护。
5. 权限与角色管理:通过Spring Security实现用户权限控制,根据不同的角色赋予不同的操作权限,保证数据安全。
6. 日志与审计:系统集成日志管理模块,记录关键操作和异常情况,便于问题追踪和审计。
7. 报表与数据分析:利用大数据处理技术,生成各类报表,帮助决策者分析运营状况和优化策略。
8. 服务调用与接口:采用RESTful API设计,与其他系统和服务进行无缝对接,提高系统的灵活性和扩展性。
9. 安全措施:通过HTTPS协议保证数据传输安全,同时采用加密算法保护敏感信息,防止数据泄露。
总之,全国公路客运票务智慧管理系统通过这种高效的B/S架构设计,实现了高效、稳定、易用的运营管理,为公路客运行业提供了强大而智能的解决方案。
在浏览器中输入系统网址,打开登录界面后输入登录账号、登录密码、验证码即可登录。
工作台包含:班次管理、部门管理、车辆管理、车站管理、订单管理、公告管理、角色管理、积分管理、路线管理、票务管理、评论管理、权限管理、日志管理、数据报表、投诉管理、用户管理、优惠券管理、站内信管理、折扣管理,根据不同角色权限菜单展示会有所区别。
管理功能主要字段信息包含:管理编码、班次名称、车辆型号、开车时间、到达时间、班次状态等。使用表格形式展示数据信息,方便用户查看和编辑。
管理设置新增、编辑、删除、条件搜索、查看详情等操作,可按照页面提示进行操作执行,界面结构设计简单,操作流程简洁明了,可提升用户操作体验。
*请认真填写需求信息,我们会在24小时内与您取得联系。