JS设计模式
文章目录
- 1 什么是设计模式?
- 2 发布-订阅模式
- 2.1 DOM事件
- 2.2 基于Broadcast Channel实现跨页面通信
- 2.3 基于localStorage实现跨页面通信
- 2.4 使用 Vue 的 EventBus 进行跨组件通信
- 2.4 使用 React 的 EventEmitter 进行跨组件通信
- 3 装饰器模式
- 3.1 React 高阶组件 HOC
- 3.2 AOP 面向切面编程
- 3.3 axios调用时添加token
- 4 单例模式
- 4.1 惰性单例
- 4.2 通用的惰性单例
- 4.3 Vuex 数据缓存
- 4.4 antd/message
- 4.5 axios取消重复请求
- 5 策略模式
- 5.1 JavaScript版本的策略模式
- 5.2 axios
- 6 迭代器模式
- 6.1 JS中的迭代器
- 6.2 根据不同浏览器选择相应的上传组件
- 7 代理模式
- 7.1 虚拟代理合并HTTP请求
- 7.2 缓存代理
- 7.3 Vue中的代理模式
1 什么是设计模式?
设计模式是针对开发中遇到的问题,提出的公认且有效的解决方案。共23种设计模式。
2 发布-订阅模式
对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知
消息的发布者将消息通过消息通道广播出去,然后订阅者通过订阅获取到想要的消息。
2.1 DOM事件
funtion func() {console.log(2)
}// 添加订阅者
document.body.addEventListener('click', func, false)// 删除订阅者
document.body.removeEventListener('click', func)
这里,我们就订阅document.body
上的click
事件,当body
被点击时,body
节点便会向订阅者发布这个消息,当然我们还可以随意的添加或者删除订阅者
2.2 基于Broadcast Channel实现跨页面通信
// A页面监听广播
// 第一步 创建实例
const bc = new BroadcastChannel('myBroadcastChannel')
// 第二部 通过onmessage设置回调事件
bc.onmessage = e => {console.log(e.data)
}// B页面发送广播
const bc = new BroadcastChannel('myBroadcastChannel')
bc.postMessage('hollow word')// 关闭广播
bc.close()
2.3 基于localStorage实现跨页面通信
// http://localhost:8080/A.html
window.addEventListener('storage', e => {// e.key 改变的key值 - msg// e.oldValue 改变前的值// e.newValue 改变后的值 - 哈哈哈if (e.key === 'msg') { ... }
})// http://localhost:8080/B.html
localStorage.setItem('msg', '哈哈哈')
2.4 使用 Vue 的 EventBus 进行跨组件通信
创建一个EventBus (本质上是Vue的一个实例对象)
import Vue from 'vux'
const EventBus = new Vue()
export default EventBus
接着在组件A和组件B中引入bus.js:import Bus from '@/utils/bus'
,组件A在 mounted
钩子中调用 Bus 的注册订阅方法 $on
传入订阅主题和回调方法,组件B中在点击事件中发布主题,让订阅该主题的组件执行回调方法。
// 组件A
mounted () {// 订阅Bus.$on('SayHollow', text => {console.log(text)})
}
// 组件B
methods: {clickEvent () {// 发布Bus.$emit('SayHollow', '啊俊俊')}
}
2.4 使用 React 的 EventEmitter 进行跨组件通信
import React, { Component } from 'react'
//一: 导入EventEmitter
import { EventEmitter } from 'events';//二: 构建事件实例
const EventBus = EventEmitter()//tips:我们可以在多个组件中去增加同一个事件的订阅,这里仅仅是示例
class Observer extends Component {componentDidMount () {// 三: 增加事件订阅this.event1 = EventBus.addListener("someEvent", (params) => {console.log(params)})}componentWillUnMount () {//四: 移除事件订阅EventBus.removeListener(this.event1)}render () {return (<div>事件监听组件</div>)}
}
class Publisher extends Component {handleClick () {const params = {}//五: 发布事件(当someEvent发布时,订阅该事件的函数就会执行)EventBus.emit('someEvent', params)}render () {return (<div><button onClick={this.handleClick.bind(this)}>发布事件</button></div>)}
}
3 装饰器模式
装饰器模式动态的给某个对象添加一些职责,并且不会影响从这个类派生的其他对象。
在传统的面向对象开发中,给对象添加功能时,我们通常会采用继承的方式,继承的方式目的是为了复用,但是随之而来也带来一些问题:
(1)父类和子类存在强耦合的关系,当父类改变时,子类也需要改变;
(2)子类需要知道父类中的细节,至少需要知道接口名,从而进行复用或复写,这样其实破坏了封装性;
(3)继承的方式,可能会创建出大量子类。比如现在有BBA三种类型的汽车,构造了一个汽车基类,三个三种类型的汽车。现在需要给汽车装上雾灯、前大灯、导航仪、刮雨器,如果采用继承的方式,那么就要构建3*4个类。但是如果把雾灯、前大灯、导航仪、刮雨器动态地添加到汽车上,那么只需要增加4个类。这种采用动态添加职责的方式就是装饰器。
装饰器的目的就是在不改变原来类的基础上,为其在运行期间动态的添加职责。
提高编程的低耦合与高可复用性
3.1 React 高阶组件 HOC
import React from 'react';const yellowHOC = WrapperComponent => {return class extends React.Component {render() {<div style={{ backgroundColor: 'yellow' }}><WrapperComponent {...this.props} /></div>;}};
};export default yellowHOC;
import React from 'react';
import yellowHOC from './yellowHOC';class TargetComponent extends Reac.Compoment {render() {return <div>66666</div>;}
}export default yellowHOC(TargetComponent);
3.2 AOP 面向切面编程
业务和系统基础功能分离,用 Decorator
很合适
log装饰器实现
export function log(target, name, decriptor) {var _origin = decriptor.value;decriptor.value = function () {console.log(`Calling ${name} with `, arguments);return _origin.apply(null, arguments);};return decriptor;
}
调用装饰器
import { log } from "./log";
class Person {@logsay(nick) {return `hi ${nick}`;}
}var person = new Person();
person.say("小明");
3.3 axios调用时添加token
4 单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点
- 避免重复的创建实例,节约不必要的开销。
- 通常会在第三方库的开发中使用到。
- 如果是项目的开发,单例模式也会用在一些数据缓存、全局通用弹窗(如登录弹窗)等一些场景中。
4.1 惰性单例
惰性单例指的是在需要的时候才创建对象实例。通常会用在全局唯一且非必需的一些场景,例如:全局弹窗、购物车列表、全局共同信息等场景。
拿书中登录弹窗的例子可以理解的更加清晰:
var createLoginLayer = (function() {var div;return function() {if(!div) {div = document.createElement('div');div.innerHtml = '登录弹窗';div.style.display = 'none';document.body.appendChild(div);}return div;}
})()document.getElementById('loginBtn').onclick = function() {var loginLayer = createLoginLayer();loginLayer.style.display = 'block';
}
在这个例子中,只有在登录按钮点击时,才会去创建登录弹窗dom节点,而不是在页面加载时就默认创建,并且,只有在第一次执行时创建登录弹窗dom节点,再次执行也不会创建多余的节点,节省了一部分性能。
4.2 通用的惰性单例
上面代码在createLoginLayer方法中,既实现了单例的逻辑,也实现了创建登录弹窗的逻辑。这违反了上节介绍的单一职责原则,如果下次仍然需要创建另一个弹窗或其他的功能,我们仍然需要将创建单例这部分逻辑再次抄一遍。这么一说相信大家也能理解到这段代码需要如何进行拆分了:将不变的部分抽离出来,也就是将要介绍的通用的惰性单例
// 通用的惰性单例
var getSingle = function(fn) {var result;return function() {return result || (resule = fn.apply(this, arguments));}
}// 创建登录弹窗的方法就可以改写成
var createLoginLayer = function() {var div = document.createElement('div');div.innerHtml = '登录弹窗';div.style.display = 'none';document.body.appendChild(div);return div;
}var createSingleLoginLayer = getSingle(createLoginLayer);document.getElementById('loginBtn').onclick = function() {var loginLayer = createSingleLoginLayer();loginLayer.style.display = 'block';
}
如上面代码,我们将创建登录弹窗和单例的逻辑分离成createLoginLayer
和getSingle
,这样之后再有了类似的需求,就可以复用到getSingle
方法实现单例。
4.3 Vuex 数据缓存
// src/store.jslet Vue;export class Store {constructor(options = {}) {if (!Vue && typeof window !== 'undefined' && window.Vue) {install(window.Vue)}}
}export function install (_Vue) {if (Vue && _Vue === Vue) {if (__DEV__) {console.error('[vuex] already installed. Vue.use(Vuex) should be called only once.')}return}Vue = _VueapplyMixin(Vue)
}
在上面代码中,很明显可以找到一个单例模式的应用:在Store
的初始化时,只会执行一次install
方法。在install
方法中,会将Vue
赋值,并将vuex
的相关逻辑绑定到Vue实例
上。
4.4 antd/message
// components/messagelet messageInstance;function getMessageInstance(callback) {if (messageInstance) {callback(messageInstance);return;}Notification.newInstance({prefixCls,transitionName,style: { top: defaultTop }, // 覆盖原来的样式getContainer,maxCount,},instance => {if (messageInstance) {callback(messageInstance);return;}messageInstance = instance;callback(instance);},);
}
在上面代码中,我们可以看到当已经存在messageInstance
时,会直接复用对应的实例:callback(messageInstance)
,否则的话,将会赋值messageInstance = instance
。
先看下面这段代码:
<template><div><a-button @click="handleMessage">message</a-button></div>
</template>export default {methods: {handleMessage() {this.$message.info('handleMessage');},},created() {this.$message.config({top: '100px',duration: 1,});},
};
这是一个vue组件,在created
时,我们配置了message
的参数,当点击按钮时,会执行方法调用message.info
方法弹出组件。在message
源码中,会通过执行notice
方法弹出弹窗,而如果我们是第一次弹出弹窗时:
会创建一个ant-message
节点其中top: 100px就是刚才设置的配置项,当我们再次触发时:
可以看到会复用刚刚创建的dom节点,并在内部创建一个弹窗。那么这样做有什么好处呢?
- 最显然的一个优势就是复用了dom节点
- 还有一个优势,当我们连续多次点击时,可以看到弹窗的效果是按顺序依次显示的,只有一个dom节点可以保证多次弹出的弹窗只有一个父节点,那么弹窗位置只要由父节点控制即可,不需要每次都重新计算,效果如图所示:
4.5 axios取消重复请求
import axios from "axios";
const CancelToken = axios.CancelToken;
let cancelId = 0;
let cancelArray = [];// 添加请求拦截器
axios.interceptors.request.use(function (config) {// 在请求时,可以添加自己的特点标识去筛选出需要重复取消的接口const source = CancelToken.source();cancelId++;const id = cancelId;config.cancelId = id;config.cancelToken = source.token;const cancelIndex = cancelArray.findIndex(e => e.url === config.url);cancelArray.push({id,url: config.url,source})if (cancelIndex > -1) {cancelArray[cancelIndex].source.cancel('取消重复请求');cancelArray.splice(cancelIndex, 1)}return config;
}, function (error) {return Promise.reject(error);
});// 添加响应拦截器
axios.interceptors.response.use(function (response) {const cancelIndex = cancelArray.findIndex(e => e.id === response.cancelId);if (cancelIndex >= -1) {cancelArray.splice(cancelIndex, 1)}// 对响应数据做点什么return response;
}, function (error) {if (axios.isCancel(error)) {// 如果是取消的接口,可以自行返回一个特定标识console.log('isCancel')} else {// 对响应错误做点什么return Promise.reject(error);}
});export default axios
5 策略模式
定义一系列算法,把他们一个个封装起来,并且使他们可以相互替换。
在平时的工作中也存在非常多应用场景,比如业务中经常会存在针对不同场景执行不同逻辑的情况,就可以考虑使用策略模式
5.1 JavaScript版本的策略模式
业务描述:实现一个计算年终奖的功能,绩效为S的人有4倍工资,A为3倍工资,B为2倍工资。
var strategies = {'S': function(salary) {return salary * 4;},'A': function(salary) {return salary * 3;},'B': function() {return salary * 2;}
}var calculateBouns = function(level, salary) {return strategies[level](salary);
}calculateBouns('A', 2000);
5.2 axios
axios
既可用于浏览器中又可用于node
环境中,但通过源码可以得知:在不同的环境,将使用不同的方式发起请求
// lib/defaults.jsfunction getDefaultAdapter() {var adapter;if (typeof XMLHttpRequest !== 'undefined') {// For browsers use XHR adapteradapter = require('./adapters/xhr');} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {// For node use HTTP adapteradapter = require('./adapters/http');}return adapter;
}
通过这段代码,可以看到在axios
中,我们会根据环境赋值不同的adapter
,但XHR
和Http
发送请求的方式并不相同,那么如何保证在不同场景使用方式相同呢?其实,axios
会将不同的逻辑在各自内部处理,最终暴露出相同的调用方式,简单看下以下两部分代码:
// lib/adapters/xhr.jsmodule.exports = function xhrAdapter(config) {return new Promise((resolve, reject) => {/**省略xxxx代码*/var request = new XMLHttpRequest();/**省略xxxx代码*/request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);})request.onreadystatechange = function handleLoad() {/**省略xxxx代码*/// 对response进行校验,满足条件则请求成功 resolve(response)settle(resolve, reject, response);}
}// lib/adapters/http.jsmodule.exports = function httpAdapter(config) {return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {var resolve = function resolve(value) {resolvePromise(value);};var reject = function reject(value) {rejectPromise(value);};/**省略xxxx代码*/var transport;// 源码中有不同逻辑的判断,这里简化为其中一种情况transport = isHttpsProxy ? https : http;/**省略xxxx代码*/var req = transport.request(options, function handleResponse(res) {/**省略xxxx代码*/res.on('end', function handleStreamEnd() {/**省略xxxx代码*/settle(resolve, reject, response);});})})
}
两个都返回Promise
,在不同的方法中,各自处理了响应逻辑。在于使用时,也就不需要再区分不同的环境了。
6 迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序返回其中的每个元素。
6.1 JS中的迭代器
在JavaScript中,相信大家经常听到过迭代、循环之类的名词,把这两个概念区分一下:
- 循环,循环就是在满足一定条件时,重复执行同一段代码,典型的例子:
do...while
- 迭代,迭代是指按顺序逐个访问对象中的每一项,典型的例子:
forEach
那么什么样的对象可以被迭代呢?需要满足什么条件呢?
- 要成为可迭代对象,对象必须要实现必须实现
@@iterator
方法,通常可以访问常量Symbol.iterator
访问该属性。 - 目前的内置可迭代对象有:
String
、Array
、TypedArray
、Map
、Set
,他们的原型对象都实现了@@iterator
方法。
当这个对象是可迭代对象时,我们可以通过调用[Symbol.iterator]
方法来按顺序遍历对象中的每一项:
const arr = [1, 2, 3, 4];
// 迭代器
const iterator = arr[Symbol.iterator]()
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
遍历比如forEach
、for...of
等方法,其实就是封装了一个遍历可迭代对象的方法,属于内部迭代器。而当我们通过调用next
方法自行控制迭代对象遍历时,比如ES6中的生成器函数,这种就属于外部迭代器。
6.2 根据不同浏览器选择相应的上传组件
提供一个可以被迭代的方法,使得getActiveUploadObj
、getFlashUploadObj
、getFormUploadObj
依照优先级被迭代
我们会优先选择控件上传,如果没有安装上传控件则使用Flash上传,如果Flash也没有安装,那就只好使用浏览器原生的表单上传了
// 定义各个上传方法
var getActiveUploadObj = function() {try {return new ActiveXObject('TXFTNActiveX.FTNUpload');} cache(e) {return false}
}var getFlashUploadObj = function() {if (supportFlash()) {var str = '<object type="application/x-shockwave-flash"></object>'return $(str).appendTo($('body'));}return false
}var getFormUploadObj = function() {var str = '<input name="file" type="file" />' // 表单上传return $(str).appendTo($('body'));
}// 按优先级迭代函数
var iteratorUploadObj = function() {for (var i = 0, fn; fn = arguments[i++];) {var uploadObj = fn();if (uploadObj !== false) {return uploadObj}}
}// 获取可上传upload对象
var uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, getFormUploadObj)
各个上传对象的方法互不干扰,可以很好的维护和扩展代码。
7 代理模式
为一个对象提供一个代用品或占位符,以便控制对它的访问。
代理模式的关键是当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问。客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
保护代理和虚拟代理:
- 代理B可以帮助A过滤掉一些请求,比如送花的人中年龄太大的或者没有宝马的,这种请求就可以直接在代理B处被拒绝掉,这种代理叫做保护代理
- 假设现实中花的价格不菲,导致在程序世界里,new Flower也是一个代价昂贵的操作,那么我们可以把new Flower的操作交给代理B去执行,代理B会选择在A心情好时在执行new Flower,这种代理叫做虚拟代理
- 虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。保护代理用于控制不同权限的对象对目标对象的访问。保护代理用于控制不同权限的对象对目标对象的访问,而虚拟代理是最常用的一种代理模式。
7.1 虚拟代理合并HTTP请求
假设我们在做一个文件同步的功能,当我们选中一个checkbox的时候,它对应的文件就会被同步到另外一台服务器上面:
var synchronousFile = function (id) {console.log('开始同步文件', id)
}var proxySynchronousFile = function() {var cache = [],timer;return function(id) {cache.push(id);if (timer) {return;}timer = settimeout(() => {synchronousFile(cache.join(','));clearTimeout(timer);timer = null;cache.length = 0; // 清空id集合}, 2000)}
}()var checkbox = document.getElementsByTagName('input')for (var i = 0, c; c = checkbox[i++]) {c.onclick = function() {if (this.checked === true) {proxySynchronousFile(this.id);}}
}
通过一个代理函数proxySynchronousFile
来收集一段时间之内的请求,最后一次性发给服务器,如果不是实时性要求很高的系统,有一点延迟并不会带来太大的副作用,却能大大减轻服务器的压力
7.2 缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前的一致,则可以直接返回前面的存储的运算结果。
var proxyMult = (function() {var cache = {};return function() {var args = Array.prototype.join.call(arguments, ',');if (args in cache) {return cache[args];}return cache[args] = mult.apply(this, arguments);}
})()proxyMult(1, 2, 3, 4) // 24
proxyMult(1, 2, 3, 4) // 24
当第二次调用proxyMult(1, 2, 3, 4)
时,本体mult
函数并没有被计算,proxyMult
直接返回了之前计算好的结果。通过增加缓存代理的方式,mult
函数可以继续专注于自身的职责——计算乘积,缓存功能是由代理对象实现的。
7.3 Vue中的代理模式
当我们使用组件中的data、props和methods时,只需要调用this.xxx即可,拿initData的部分看下源码中是怎么处理的:
// src/core/instance/state.jsfunction initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` +`Use prop default value instead.`,vm)} else if (!isReserved(key)) {proxy(vm, `_data`, key)}}// observe dataobserve(data, true /* asRootData */)
}
可以看到首先我们设置了vm._data
,后面又执行了 proxy(vm, _data, key)
将vm._data.xxx
代理到vm.xxx
上,最后通过observe(data, true)
监听data的变化,将data变为是响应式的。
所以,要知道为什么可以直接使用this.xxx调用到组件中的data,只需要了解proxy的实现即可:
const sharedPropertyDefinition = {enumerable: true,configurable: true,get: noop,set: noop
}export function proxy (target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key]}sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val}Object.defineProperty(target, key, sharedPropertyDefinition)
}
可以看到,通过修改get
和 set
方法后,当我们获取vm.xxx
时,实际则会取到this[sourceKey][key]
,也就是vm._data.xxx
。
相关文章:

JS设计模式
文章目录1 什么是设计模式?2 发布-订阅模式2.1 DOM事件2.2 基于Broadcast Channel实现跨页面通信2.3 基于localStorage实现跨页面通信2.4 使用 Vue 的 EventBus 进行跨组件通信2.4 使用 React 的 EventEmitter 进行跨组件通信3 装饰器模式3.1 React 高阶组件 HOC3.2…...

四、常用样式讲解二
文章目录一、常用样式讲解二1.1 元素隐藏1.2 二级菜单1.3 相对定位和绝对定位1.4 定位的特殊情况1.5 表格1.6 表格的css属性1.7 表格中新增的标签一、常用样式讲解二 1.1 元素隐藏 如何让一个元素隐藏 1、不定义颜色 占用空间 2、display: none 不占用空间 3、visibility: hi…...

KDHX-8700无线高压核相相序表
一、产品简介 KDHX-8700无线高压核相相序表(以下简称“仪器”)用于测定三相线相序、检测环网或双电源电力网闭环点断路器两侧电源是否同相。在闭环两电源之前一定要进行核相操作,否则可能发生短路。仪器适用于380V~35kV交流输电线…...

【C++提高笔记】泛型编程与STL技术
文章目录模板的概念函数模板函数模板语法函数模板注意事项函数模板案例普通函数与函数模板的调用规则模板的局限性类模板类模板语法类模板与函数模板区别类模板中成员函数创建时机类模板对象做函数参数类模板与继承类模板成员函数类外实现类模板分文件编写类模板与友元类模板案…...

实用机器学习-学习笔记
文章目录9.1模型调参9.1.1思考与总结9.1.2 基线baseline9.1.3SGD ADAM9.1.4 训练代价9.1.5 AUTOML9.1.6 要多次调参管理9.1.7复现实验的困难9.1模型调参 9.1.1思考与总结 1了解了baseline和调参基本原则 2了解了adams和sgd的优劣 3了解了训练树和神经网络的基本代价 4了解了a…...

2023-02-15 学习记录--React-邂逅Redux(二)
React-邂逅Redux(二) “天道酬勤,与君共勉”——承接React-邂逅Redux(一),让我们一起继续探索Redux的奥秘吧~☺️ 一、前言 React-邂逅Redux(一)让我们对Redux有了初步认识ÿ…...

Framework——【MessageQueue】消息队列
定义 队列是 Apache RocketMQ 中消息存储和传输的实际容器,也是 Apache RocketMQ 消息的最小存储单元。 Apache RocketMQ 的所有主题都是由多个队列组成,以此实现队列数量的水平拆分和队列内部的流式存储。 队列的主要作用如下: 存储顺序性…...

SpringBoot依赖原理分析及配置文件
💟💟前言 友友们大家好,我是你们的小王同学😗😗 今天给大家打来的是 SpringBoot依赖原理分析及配置文件 希望能给大家带来有用的知识 觉得小王写的不错的话麻烦动动小手 点赞👍 收藏⭐ 评论📄…...

智慧机场,或将成为航空领域数字孪生技术得完美应用
在《智慧民航建设路线图》文件中,民航局明确指出,智慧机场是实现智慧民航的四个核心抓手之一。这一战略性举措旨在推进数字化技术与航空产业的深度融合,为旅客提供更加智能化、便捷化、安全化的出行服务,进一步提升我国民航发展的…...

SQL64 对顾客ID和日期排序
描述有Orders表cust_idorder_numorder_dateandyaaaa2021-01-01 00:00:00andybbbb2021-01-01 12:00:00bobcccc2021-01-10 12:00:00dickdddd2021-01-11 00:00:00【问题】编写 SQL 语句,从 Orders 表中检索顾客 ID(cust_id)和订单号(…...

MybatisPlus使用聚合函数
前言 今天遇到了一个求总数返回的情况,我一想这不是用sum就完事了吗。 但是仔细想想,MybatisPlus好像没有直接使用sum的api。 虽然没有直接提供,但是办法还是有的,下面就分享下如何实现的: 首先如果使用sql是这么写…...

工程管理系统源码企业工程管理系统简介
一、立项管理 1、招标立项申请 功能点:招标类项目立项申请入口,用户可以保存为草稿,提交。 2、非招标立项申请 功能点:非招标立项申请入口、用户可以保存为草稿、提交。 3、采购立项列表 功能点:对草稿进行编辑&#x…...

《计算机视觉和图像处理简介 - 中英双语版》:使用 OpenCV对图像进行空间滤波
文章大纲 Linear Filtering 线性滤波器Filtering Noise 过滤噪声Gaussian Blur 高斯滤波Image Sharpening 图像锐化Edges 边缘滤波Median 中值滤波Threshold Function Parameters 阈值函数参数References本文大概需要40分钟 Spatial Operations in Image Processing 图像处理中…...

FreeRTOS软件定时器 | FreeRTOS十三
目录 说明: 一、定时器简介 1.1、定时器 1.2、软件定时器 1.3、硬件定时器 1.4、FreeRTOS软件定时器 1.5、软件定时器服务任务作用 1.6、软件定时器的命令队列 1.7、软件定时器相关配置 1.8、单次定时器和周期定时器 1.9、软件定时器结构体 二、软件定时…...

电脑文件被误删?360文件恢复工具,免费的文件恢复软件
电脑里面保存着各种文件,因为误操作我们把还需要用的文件给删除了。很多人都想要使用不收费的文件恢复软件来进行恢复操作,但是又不清楚有哪些文件可以帮到我们。接下来就给大家介绍,一款真正免费的数据 恢复app,一起来看看&#…...

pg_cron优化案例--terminate pg_cron launcher可自动拉起
场景 在PostgreSQL中我们可以使用pg_cron来实现数据库定时任务 我有一个select 1的定时任务,每分钟触发一次 testdb# select * from cron.job ;jobid | schedule | command | nodename | nodeport | database | username | active | jobname -------…...

Python 之 NumPy 随机函数和常用函数
文章目录一、随机函数1. numpy.random.rand(d0,d1,…,dn)2. numpy.random.randn(d0,d1,…,dn)3. numpy.random.normal()4. numpy.random.randint()5. numpy.random.sample6. 随机种子np.random.seed()7. 正态分布 numpy.random.normal二、数组的其他函数1. numpy.resize()2. nu…...

【目标检测】K-means和K-means++计算anchors结果比较(附完整代码,全网最详细的手把手教程)
写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大努力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 一、介绍 YOLO系列目标检测算法中基于anchor的模型还是比较多的,例如YOLOv3、YOLOv4、YOLOv5等,我们可以随机初始化a…...

Java高手速成 | 图说重定向与转发
我们先回顾一下Servlet的工作原理,Servlet的工作原理跟小猪同学食堂就餐的过程很类似。小猪同学点了烤鸡腿(要奥尔良风味的),食堂窗口的服务员记下了菜单,想了想后厨的所有厨师,然后将菜单和餐盘交给专门制…...

Git:不小心在主分支master上进行修改,怎么才能将修改的数据保存到正确的分支中
1.如果还没有push commit 代码第一步:将所修改的代码提交到暂存区git stash第二步:切换到正确的分支git checkout 分支名第三步:从暂存区中取出保存到正确的分支中git stash pop第四步:重新提交git push origin 分支名2.如果已经p…...

都2023年了,如果不会Stream流、函数式编程?你确定能看懂公司代码?
👳我亲爱的各位大佬们好😘😘😘 ♨️本篇文章记录的为 Stream流、函数式编程 相关内容,适合在学Java的小白,帮助新手快速上手,也适合复习中,面试中的大佬🙉🙉🙉。 ♨️如果…...

亚马逊云科技汽车行业解决方案
当今,随着万物智联、云计算等领域的高速发展,创新智能网联汽车和车路协同技术正在成为车企加速发展的关键途径,推动着汽车产品从出行代步工具向着“超级智能移动终端”快速转变。 挑战无处不在,如何抢先预判? 随着近…...

为什么学了模数电还是看不懂较复杂的电路图
看懂电路并不难。 (1) 首先要摆正心态,不要看到错综复杂的电路图就一脸懵逼,不知所错。你要明白,再复杂的电路也是由一个个的基本电路拼装出来的。 (2) 基础知识当然是少不了的,常用的基本电路结构搞搞清楚。 (3) 分析电路之前先要…...

帮公司面试了一个30岁培训班出来的程序员,没啥工作经验...
首先,我说一句:培训出来的,优秀学员大有人在,我不希望因为带着培训的标签而无法达到用人单位和候选人的双向匹配,是非常遗憾的事情。 最近,在网上看到这样一个留言,引发了程序员这个圈子不少的…...

勒索软件、网络钓鱼、零信任和网络安全的新常态
当疫情来袭时,网络罪犯看到了他们的机会。随着公司办公、政府机构、学校和大学从以往的工作模式转向远程线上办公模式,甚至许多医疗保健设施都转向线上,这种快速的过渡性质导致了不可避免的网络安全漏洞。消费者宽带和个人设备破坏了企业安全…...

python3 字符串拼接与抽取
我们经常会有对字符串进行拼接和抽取的需求,下面有几个例子可以作为参考。 需求1:取出ip地址的网络地址与网络掩码进行拼接,分别使用shell脚本和python3实现 # echo "192.168.0.1"|awk -F. {print $1"."$2"."…...

Linux就该这么学:存储结构与管理硬盘
Linux系统中常见的目录名称以及相应内容 目录名称应放置文件的内容/boot开机所需文件—内核、开机菜单以及所需配置文件等/dev以文件形式存放任何设备与接口/etc配置文件/home用户主目录/bin存放单用户模式下还可以操作的命令/lib开机时用到的函数库,以及/bin与/sbin下面的命令…...

JSP四大作用域,九大内置对象
面试题:JSP和Servlet的区别?JSP的本质就是servleJSP更加侧重于视图的展示,servlet更注重逻辑的处理。面试题:include指令和jsp:include标签的区别?从效果上来说,没区别。include指令是把两个页面合成一个js…...

机器学习笔记之生成模型综述(五)重参数化技巧(随机反向传播)
机器学习笔记之生成模型综述——重参数化技巧[随机反向传播]引言回顾神经网络的执行过程变分推断——重参数化技巧重参数化技巧(随机反向传播)介绍示例描述——联合概率分布示例描述——条件概率分布总结引言 本节将系统介绍重参数化技巧。 回顾 神经网络的执行过程 上一节…...

1、创建第一个Android项目
1.1、创建Android工程项目:双击打开Android Studio。在菜单栏File中new-->new project3、在界面中选择Empty Activity,然后选择next4、在下面界面中修改工程名称,工程保存路径选择java语言,然后点击finishAndroid studio自动为…...