React v15版本相关原理学习

前言

React16之后引入了Fiber的架构,这个架构的处理过程是非常复杂的,在正式去学习了解Fiber的处理过程之前,打算去深入了解下v15版本整个的处理过程,然后再结合Fiber要解决的问题从而更加深入的理解。本文要理解的相关原理有:

  • ReactDOM.render的处理过程
  • 组件的具体处理过程
  • Class组件的生命周期函数的执行时机
  • 事件相关处理
  • setState背后更新相关逻辑

无论是最新版本还是v15版本,React.createElement的处理逻辑都是相似的,本文就不再赘述了,可以去看之前的React.createElement文章。

ReactDOM.render的处理过程

render函数的源码如下:

const ReactMount = {
	render: function (nextElement, container, callback) {
      	return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
    }
};

函数内部直接调用_renderSubtreeIntoContainer方法,该方法的处理逻辑如下:

function _renderSubtreeIntoContainer() {
	...
	var nextWrappedElement = React.createElement(TopLevelWrapper, {
		child: nextElement
	});
	...
	var rootComponent = ReactMount._renderNewRootComponent(
		nextWrappedElement, container, shouldReuseMarkup, nextContext
	);
	// 返回相关实例对象
	rootComponent._renderedComponent.getPublicInstance();
}

上面的逻辑是初始化构建阶段的核心处理逻辑:

  • 创建一个TopLevel容器组件,该组件的子节点是根组件
  • 调用_renderNewRootComponent函数
  • 调用getPublicInstance函数

_renderNewRootComponent

该函数的主要逻辑如下:

function _renderNewRootComponent() {
	...
	var componentInstance = instantiateReactComponent(nextElement, false);
	ReactUpdates.batchedUpdates(
		batchedMountComponentIntoNode, componentInstance,
		container, shouldReuseMarkup, context
	);
	var wrapperID = componentInstance._instance.rootID;
	instancesByReactRootID[wrapperID] = componentInstance;
	return componentInstance;
}

其中instantiateReactComponent函数的功能是根据React元素对象创建对应的实例,其相关处理逻辑如下:

function instantiateReactComponent(node, shouldHaveDebugID) {
    var instance;
    if (typeof node === 'object') {
      var element = node;
      if (typeof element.type === 'string') {
      	instance = ReactHostComponent.createInternalComponent(element);
      } else if (isInternalComponentType(element.type)) {
      	instance = new element.type(element);
      } else {
      	instance = new ReactCompositeComponentWrapper(element);
      }
    }   
    ...
    instance._mountIndex = 0;
    instance._mountImage = null;
    if (Object.preventExtensions) {
    	Object.preventExtensions(instance);
     }
    return instance;
}

React元素中type属性实际上就是组件具体内容,实际上就是根据组件类型来对应实例化操作:

  • 原生标签:调用ReactHostComponent.createInternalComponent处理
  • 函数类型并且存在对应的原型方法的组件(实际上是内部组件):new element.type直接处理
  • 其他自定义组件:new ReactCompositeComponentWrapper

ReactUpdates.batchedUpdates

ReactUpdates对象提供了一些方法,例如batchdUpdates、enqueueUpdate、flushBatchedUpdates等方法,而batchedUpdates背后实际上是调用ReactDefaultBatchingStrategy的batchedUpdates方法,该方法的处理逻辑如下:

var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
    isBatchingUpdates: false,
    batchedUpdates: function (callback, a, b, c, d, e) {
      // 初始化时默认是false
      var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
  
      ReactDefaultBatchingStrategy.isBatchingUpdates = true;
      if (alreadyBatchingUpdates) {
        return callback(a, b, c, d, e);
      } else {
      	// 执行事务的perform方法
        return transaction.perform(callback, null, a, b, c, d, e);
      }
    }
};

其中对于对于事务transaction对象的perform方法的逻辑,而该函数核心逻辑归纳如下:

var TransactionImpl = {
   perform: function (method, scope, a, b, c, d, e, f) {
      try {
      	...
      	// 遍历执行initialize方法
        this.initializeAll(0);
        // 执行传入的方法
        ret = method.call(scope, a, b, c, d, e, f);
        errorThrown = false;
      } finally {
        try {
        	...
        	// 遍历执行close方法
        	this.closeAll(0);
        } finally {
          this._isInTransaction = false;
        }
      }
      return ret;
    }
}

事务的处理流程就是:事务initialize方法执行、相关method方法执行、事务close方法执行。

实际上到这里就知道batchedUpdates整个的处理逻辑就是:

执行传入的batchedMountComponentIntoNode方法,利用事务Transaction机制,在该方法执行前和执行后做相关处理

batchedMountComponentIntoNode

该函数的处理逻辑具体如下:

function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
    var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    	!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement
    );
    
    transaction.perform(
    	mountComponentIntoNode, null, componentInstance, container, 
    	transaction, shouldReuseMarkup, context
    );
    
    ReactUpdates.ReactReconcileTransaction.release(transaction);
}

这是出现一个新的对象ReactReconcileTransaction,从字面意思上理解是React调度事务,实际上这里的逻辑就是:

  • 创建一个调度事务对象,即new ReactReconcileTransaction
  • 调用这个事务的perform,实际上就是执行mountComponentIntoNode函数
  • 执行调度事务的release方法

这里先不管调度事务相关的处理逻辑,主要看看mountComponentIntoNode函数,该函数的处理逻辑具体如下:

function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
    var markerName;
    var markup = ReactReconciler.mountComponent(
    	wrapperInstance, transaction, null,
    	ReactDOMContainerInfo(wrapperInstance, container), context, 0
    );

    wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
    ReactMount._mountImageIntoNode(
    	markup, container, wrapperInstance, shouldReuseMarkup, transaction
    );
  }

mountComponentIntoNode函数的核心逻辑就是执行两个函数:

  • ReactReconciler.mountComponent
  • ReactMount._mountImageIntoNode
ReactReconciler.mountComponent

该函数的最主要的逻辑实就是调用组件的mountComponent方法,因为存在一个TopLevel容器组件,首先会处理容器组件的:

// TopLevel容器组件是ReactCompositeComponent的实例对象,所有自定义组件都是该类型的实例化
var ReactCompositeComponent = {
	mountComponent: function () {
		...
		// new Component,即实例化组件
		var inst = this._constructComponent(...);
		/*
			componentWillMount生命周期函数执行
			执行组件的render函数
			调用instantiateReactComponent实例化子组件
			调用ReactReconciler.mountComponent来挂载子组件,即重复ReactCompositeComponent mountComponent这个过程
		*/
		this.performInitialMount();
		if (inst.componentDidMount) {
			// 执行componentDidMount生命周期函数
		}
    }
}

实际上从这里的处理逻辑就知道,实际上就是处理所有组件的挂载,因为是递归处理所以父子组件生命周期等的执行顺序如下:

父constructor -> 父componentWillMount -> 父render -> 子constructor -> 子componentWillMount -> 子render -> 子componentDidMount -> 父componentDidMount

ReactMount._mountImageIntoNode

该函数的核心处理就是插入节点到页面上,这里的逻辑就不展开了,实际上有很多细节,只要知道主要逻辑就行。

render整体逻辑总结

在这里插入图片描述
在挂载阶段涉及到两个事务:

  • ReactDefaultBatchingStrategyTransaction
  • ReactReconcileTransaction

事件处理流程

在前面的render处理过程中会递归处理所有组件,在React中所有视图内容都是组件包括原生标签,会调用对应方法实例化生成对象。以原生标签为例,就是调用ReactDOMComponent构造函数实例化:

ReactDOMComponent.prototype.mountComponent = function() {
	...
	this._updateDOMProperties(null, props, transaction);
	...
}

在React中所有属性、事件、子组件都是存在props属性中的,而_updateDOMProperties函数就是相关处理其中包含事件。该函数的具体处理如下:

_updateDOMProperties: function (lastProps, nextProps, transaction) {
	for (propKey in lastProps) {
		...
    }
    for (propKey in nextProps) {
    	// style处理
    	if (propKey === STYLE) {
        } else if (registrationNameModules.hasOwnProperty(propKey)) {
          // registrationNameModules中注册了所有定义的事件
          if (nextProp) {
            enqueuePutListener(this, propKey, nextProp, transaction);
          }
        }
        ...
    }
}

React中事件是合成事件,而registrationNameModules中注册了所有事件的具体处理逻辑。而enqueuePutListener函数的处理逻辑具体如下:

function enqueuePutListener(inst, registrationName, listener, transaction) {
	...
    // DocumentFragment需要特殊处理,否则使用document
    var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
    // 监听事件,事件注册到document
    listenTo(registrationName, doc);
    transaction.getReactMountReady().enqueue(putListener, {
      inst: inst,
      registrationName: registrationName,
      listener: listener
    });
}

listenTo函数的具体处理逻辑如下:

var ReactEventListener = {
	/*
	   		假设是click事件:
	   		- topLevelType对应值是topClick
	   		- handlerBaseName对应值是click
	   		- element是document
	 */
	trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
	  // EventListener.list实际上就是调用addEventListener实现事件绑定,事件处理程序是调用ReactEventListener.dispatchEvent
      return EventListener.listen(
      	element,
      	handlerBaseName,
      	ReactEventListener.dispatchEvent.bind(null, topLevelType)
      );
    }
}

function listenTo() {
	...
	/*
		遍历处理所谓的依赖,这些依赖全都是以top开头的事件,例如topClick
	*/
	ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent();
}

逻辑至此就完成了绑定的过程,这里来看看触发后事件的具体处理逻辑,即ReactEventListener.dispatchEvent。该函数的具体处理逻辑如下:

var ReactEventListener = {
	dispatchEvent: function (topLevelType, nativeEvent) {
		...
	      try {
	      	// 利用事务机制来执行handleTopLevelImpl函数
	        ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
	      } finally {
	        TopLevelCallbackBookKeeping.release(bookKeeping);
	      }
    }
}

handleTopLevel函数从源码中可知是ReactBrowserEventEmitter的方法,其具体的处理逻辑如下:

function runEventQueueInBatch(events) {
    EventPluginHub.enqueueEvents(events);
    EventPluginHub.processEventQueue(false);
}

handleTopLevel: function (topLevelType, targetInst) {
	var events = EventPluginHub.extractEvents(topLevelType, targetInst);
    runEventQueueInBatch(events);
}

实际上之后也牵扯到其他函数的处理,这边处理的最终目的就是响应对应的事件,执行自定义的程序。整个过程的处理比较复杂,其中涉及到一些细节处理,例如top开头的内部事件、SyntheticEvent的处理。

至此总结下事件处理的信息:

  • React事件处理是合成事件,但是事件的触发还是要依赖浏览器DOM事件,通过addEventListener来实现监听
  • 事件都是挂载到document上,而不是其定义所在的节点上
  • 事件触发后内部实际上会存在一系列的内部处理,其自定义程序的执行根据开发环境和生产环境可能是函数调用 或 自定义事件分发形式
  • 当触发事件后实际上整个处理流程都在处在事务机制中(这个很重要,涉及后面setState更新的处理)

setState背后的更新机制

setState用于Class组件中更新内部状态state,这个操作会触发组件视图更新,而Class组件定义规则如下:

Class Hello extends React.Component {
	constructor(props) {
		super(props);
		this.state = { date: Date.now() };
	}
	render() {}
}

而React.Component组件的定义实际上也非常简单,具体逻辑如下:

function ReactComponent(props, context, updater) {
	this.props = props;
    this.context = context;
    this.refs = emptyObject;
    this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.setState = function() {};
ReactComponent.prototype.forceUpdate = function() {};
...

其中setState是其实例方法,而setState的具体逻辑如下:

ReactComponent.prototype.setState = function (partialState, callback) {
	// 判断partialState的合法性(函数、对象、null、undefined)
    this.updater.enqueueSetState(this, partialState);
    if (callback) {
    	this.updater.enqueueCallback(this, callback, 'setState');
    }
};

调用组件实例的updater对应的enqueueSetState,而该属性实际上会在组件的mountComponent函数被赋值,其值实际上是ReactUpdateQueue对象:

var ReactUpdateQueue = {
    enqueueSetState: function (publicInstance, partialState) {
      var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  
      if (!internalInstance) {
        return;
      }
      var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
      queue.push(partialState);
  
      enqueueUpdate(internalInstance);
    }
}

获取实例的_pendingStateQueue,将当前state数据存放到队列中,之后调用enqueueUpdate函数。该函数的逻辑具体如下:

function enqueueUpdate(component) {
    ensureInjected();
    // isBatchingUpdates默认是false,调用batchedUpdates会将其置为true
    if (!batchingStrategy.isBatchingUpdates) {
      batchingStrategy.batchedUpdates(enqueueUpdate, component);
      return;
    }
    // 将当前组件实例保存到dirtyComponents数组中
    dirtyComponents.push(component);
}
  
function enqueueUpdate(internalInstance) {
    ReactUpdates.enqueueUpdate(internalInstance);
}

isBatchingUpdates为false,就会执行batchedUpdates函数,实际上就是通过事务机制来执行enqueueUpdate函数;isBatchingUpdates为true,就会向dirtyComponents添加组件实例。实际上从其整个的处理逻辑看来:

无论isBatchingUpdates是什么值,都是向dirtyComponents数组中添加组件实例

而isBatchingUpdates必须是调用ReactUpdates.batchedUpdates才会设置设置为true,而其背后的处理逻辑如下:

var ReactUpdates = {
	batchedUpdates: function batchedUpdates(callback, a, b, c, d, e) {
    	ensureInjected();
    	return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
  	}
}

batchingStrategy.batchedUpdates函数核心的功能就是通过事务机制来执行相关函数。

setState异步更新

setState可能是同步执行也可能是异步执行,其背后是通过batchingStrategy.isBatchingUpdates这个参数来控制的,而这个参数追本溯源就是看是否处于事务机制中,即是否调用了ReactUpdates.batchedUpdates方法。

setState的异步更新实际上是事务机制导致的,setState的触发可分成主动和被动:

  • 主动是指在JavaScript中主动调用来触发
  • 被动是指通过视图上事件来处理的

在React中事件是合成事件,只要是直接在标签或组件上注册的事件都被特殊处理,实际上是从前面事件处理流程中可知:

在事件触发后,其内部处理逻辑中会调用ReactUpdates.batchedUpdates

一旦调用ReactUpdates.batchedUpdates函数,就会通过事务机制来执行事件对应的处理程序,处于事务中控制同步或异步的isBatchingUpdates就会被设置为true。以下面实例来说明会更加具体:

onClick: () => {
	this.setState({ date: Date.now() })
}

当触发事件就会同步执行事件处理函数,其中setState就会触发具体处理,此时isBatchingUpdates为true,就指向将当前组件实例存放到数组dirtyComponents中,而不会立即处理。那么又是何时触发视图更新呢?实际上是在事务的结束阶段调用close方法,相关事务中会存在一个close方法是flushBatchedUpdates,而该函数中就是处理dirtyComponents的。

事务机制分为三个阶段:前期initialize函数执行、中期method执行、后期close函数。事件触发后就会处于一个事务中,事件处理函数是在事务中期阶段执行,其内部逻辑中setState会将保存组件实例到dirtyComponents数组中,而在数组在事务后期close执行时处理,这样就形成了所谓的setState的异步效果,实际上并没有任何异步效果,仅仅是执行顺序的不同而已

上面是以事件处理来看的,实际上主动触发也是如此,setState是在Class组件中使用,在前面的render的处理逻辑中可知组件的整个处理都是处于事务中,依旧是上面的事务逻辑处理过程。

setState同步更新场景

setState可以是同步的,那么什么时候是同步处理的呢?有如下两个场景:

  • 在异步API中调用setState,例如setTimeout等
  • 使用addEventListener注册的事件

本质上都是浏览器异步的操作,所谓的同步更新实际上也是事务机制导致的。React中事务机制都是同步执行的,当整个事务同步执行完后,才会执行异步操作。当整个事务执行完后,isBatchingUpdates参数就会被重置为false,此时异步操作执行逻辑中setState操作是执行下面逻辑:

// 更新阶段异步操作逻辑中存在setState时,由于整个事务执行完毕完毕,isBatchingUpdates为false,此时就会调用batchedUpdates
if (!batchingStrategy.isBatchingUpdates) {
	batchingStrategy.batchedUpdates(enqueueUpdate, component);
   	return;
}

batchedUpdates就会开启一个BatchingStrategy类型的事务,该事务中执行的方法是enqueueUpdate即添加组件实例到dirtyComponents,整个事务都是同步代码执行。在事务的close阶段就会处理dirtyComponents,而异步操作中setState后面的逻辑全部被阻塞了,当setState执行完成后实际上就已经完成组件的更新,后面组件相关的数据必然是最新的,setState表现出来就是同步的效果。

更新流程

当使用setState后就会触发整个相关组件的更新,在前面的setState相关逻辑中已知无论是所谓异步更新还是同步更新的对应组件都保存dirtyComponents数组中,该数组在事务后期close处理时调用flushBatchedUpdates函数来处理的,这里需要额外说明一下,React内部存在多种类型的事务,相关事务存在不同close方法。flushBatchedUpdates函数的主要处理逻辑如下:

var flushBatchedUpdates = function () {
    while (dirtyComponents.length) {
    	...
    	var transaction = ReactUpdatesFlushTransaction.getPooled();
        transaction.perform(runBatchedUpdates, null, transaction);
        ReactUpdatesFlushTransaction.release(transaction);
        ...
    }
};

创建一个UpdatesFlushe类型的事务,之后使用事务机制执行runBatchedUpdates函数,而runBatchedUpdates函数核心逻辑如下:

function runBatchedUpdates(transaction) {
	...
	// 保证父子组件顺序执行
	dirtyComponents.sort(mountOrderComparator);
	
	for (var i = 0; i < len; i++) {
      var component = dirtyComponents[i];
      // 对每一个组件都做处理,实际上依据对应的属性数据执行对应的方法实现组件更新,即执行updateComponent方法
      ReactReconciler.performUpdateIfNecessary(
      	component,
      	transaction.reconcileTransaction,
      	updateBatchNumber
      );
      ...
    }
}

runBatchedUpdates核心处理就是执行updateComponent来实现组件的更新逻辑,其核心处理逻辑分步概括如下:

  • 生命周期函数componentWillReceiveProps执行
  • 生成新的state
  • 生命周期函数shouldComponentUpdate执行
  • 生命周期函数componentWillUpdate执行
  • _updateRenderedComponent函数调用
  • 生命周期函数componentDidUpdate执行

其中对于_updateRenderedComponent函数的处理,这里总结如下:

updateRenderedComponent处理流程
在更新过程中,每一次都会执行组件的render函数得到新的React元素,然后比较新旧React元素判断是否是相同元素,比较的方法是shouldUpdateReactComponent,该函数的逻辑如下:

function shouldUpdateReactComponent(prevElement, nextElement) {
    var prevEmpty = prevElement === null || prevElement === false;
    var nextEmpty = nextElement === null || nextElement === false;
    if (prevEmpty || nextEmpty) {
      return prevEmpty === nextEmpty;
    }
  
    var prevType = typeof prevElement;
    var nextType = typeof nextElement;
    if (prevType === 'string' || prevType === 'number') {
      return nextType === 'string' || nextType === 'number';
    } else {
      return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;
  	}
}

如果是对象类型,当新旧Element的type和key都相同就看成是相同的,需要更新。其比较的逻辑跟Vue的SameVnode有些相似。

从上面整个的处理逻辑归总可知React v15版本中组件的更新没有使用异步API,整个过程通过递归同步处理的,如果父组件更新就会导致其整个子组件都会更新。由于JavaScript与UI渲染互斥的,当组件嵌套过深时,组件同步更新占用时间过长就会导致一些问题,比如交互响应延迟、动画掉帧等情况。为了解决v15组件同步更新的问题,才在v16版本后引入Fiber架构,通过时间切片、任务优先级调度等手段优化这个更新过程。

总结

v15中一些逻辑的处理相对来说还是比较清晰的,但是每一块逻辑相对来说还是比较多的,本文也是从主体流程上去看需要关注的几个点,这里简单总结下本文梳理的主要流程的逻辑,细节到文章具体模块去看:

  • 通过render函数的处理过程,了解了挂载阶段的处理逻辑,其中涉及到事务机制、组件实例化过程、相关生命周期函数的执行、父子组件处理顺序(递归处理)等逻辑点
  • 通过事件处理流程的简述,了解React事件处理的背后的一些关键点,例如虽然是合成事件但是还是需要通过原生DOM事件来触发等细节
  • 通过setState的同步更新和异步更新的具体分析,理清其背后的原理实际上事务机制和对应参数在发挥作用,本质上并不是异步只是事务不同阶段执行导致的现象而已
  • 通过分析setState的处理逻辑引出了其背后的更新过程,其中涉及到相关生命周期、更新机制等,基本理清React v15版本中递归同步批量更新组件的机制,了解这个机制的弊端从而加深对v16中Fiber架构要解决的基本问题的基本理解和认知

热门文章

暂无图片
编程学习 ·

gdb调试c/c++程序使用说明【简明版】

启动命令含参数&#xff1a; gdb --args /home/build/***.exe --zoom 1.3 Tacotron2.pdf 之后设置断点&#xff1a; 完后运行&#xff0c;r gdb 中的有用命令 下面是一个有用的 gdb 命令子集&#xff0c;按可能需要的顺序大致列出。 第一列给出了命令&#xff0c;可选字符括…
暂无图片
编程学习 ·

高斯分布的性质(代码)

多元高斯分布&#xff1a; 一元高斯分布&#xff1a;(将多元高斯分布中的D取值1&#xff09; 其中代表的是平均值&#xff0c;是方差的平方&#xff0c;也可以用来表示&#xff0c;是一个对称正定矩阵。 --------------------------------------------------------------------…
暂无图片
编程学习 ·

强大的搜索开源框架Elastic Search介绍

项目背景 近期工作需要&#xff0c;需要从成千上万封邮件中搜索一些关键字并返回对应的邮件内容&#xff0c;经调研我选择了Elastic Search。 Elastic Search简介 Elasticsearch &#xff0c;简称ES 。是一个全文搜索服务器&#xff0c;也可以作为NoSQL 数据库&#xff0c;存…
暂无图片
编程学习 ·

Java基础知识(十三)(面向对象--4)

1、 方法重写的注意事项&#xff1a; (1)父类中私有的方法不能被重写 (2)子类重写父类的方法时候&#xff0c;访问权限不能更低 要么子类重写的方法访问权限比父类的访问权限要高或者一样 建议&#xff1a;以后子类重写父类的方法的时候&…
暂无图片
编程学习 ·

Java并发编程之synchronized知识整理

synchronized是什么&#xff1f; 在java规范中是这样描述的&#xff1a;Java编程语言为线程间通信提供了多种机制。这些方法中最基本的是使用监视器实现的同步(Synchronized)。Java中的每个对象都是与监视器关联&#xff0c;线程可以锁定或解锁该监视器。一个线程一次只能锁住…
暂无图片
编程学习 ·

计算机实战项目、毕业设计、课程设计之 [含论文+辩论PPT+源码等]小程序食堂订餐点餐项目+后台管理|前后分离VUE[包运行成功

《微信小程序食堂订餐点餐项目后台管理系统|前后分离VUE》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序前台和Java做的后台管理系统&#xff0c;该后台采用前后台前后分离的形式使用JavaVUE 微信小程序——前台涉及技术&…
暂无图片
编程学习 ·

SpringSecurity 原理笔记

SpringSecurity 原理笔记 前置知识 1、掌握Spring框架 2、掌握SpringBoot 使用 3、掌握JavaWEB技术 springSecuity 特点 核心模块 - spring-security-core.jar 包含核心的验证和访问控制类和接口&#xff0c;远程支持和基本的配置API。任何使用Spring Security的应用程序都…
暂无图片
编程学习 ·

[含lw+源码等]微信小程序校园辩论管理平台+后台管理系统[包运行成功]Java毕业设计计算机毕设

项目功能简介: 《微信小程序校园辩论管理平台后台管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序做的辩论管理前台和Java做的后台管理系统&#xff1a; 微信小程序——辩论管理前台涉及技术&#xff1a;WXML 和 WXS…
暂无图片
编程学习 ·

如何做更好的问答

CSDN有问答功能&#xff0c;出了大概一年了。 程序员们在编程时遇到不会的问题&#xff0c;又没有老师可以提问&#xff0c;就会寻求论坛的帮助。以前的CSDN论坛就是这样的地方。还有技术QQ群。还有在问题相关的博客下方留言的做法&#xff0c;但是不一定得到回复&#xff0c;…
暂无图片
编程学习 ·

矩阵取数游戏题解(区间dp)

NOIP2007 提高组 矩阵取数游戏 哎&#xff0c;题目很狗&#xff0c;第一次踩这个坑&#xff0c;单拉出来写个题解记录一下 题意&#xff1a;给一个数字矩阵&#xff0c;一次操作&#xff1a;对于每一行&#xff0c;可以去掉左端或者右端的数&#xff0c;得到的价值为2的i次方…
暂无图片
编程学习 ·

【C++初阶学习】C++模板进阶

【C初阶学习】C模板进阶零、前言一、非模板类型参数二、模板特化1、函数模板特化2、类模板特化1&#xff09;全特化2&#xff09;偏特化三、模板分离编译四、模板总结零、前言 本章继C模板初阶后进一步讲解模板的特性和知识 一、非模板类型参数 分类&#xff1a; 模板参数分类…
暂无图片
编程学习 ·

字符串中的单词数

统计字符串中的单词个数&#xff0c;这里的单词指的是连续的不是空格的字符。 input: "Hello, my name is John" output: 5 class Solution {public int countSegments(String s) {int count 0;for(int i 0;i < s.length();i ){if(s.charAt(i) ! && (…
暂无图片
编程学习 ·

【51nod_2491】移调k位数字

题目描述 思路&#xff1a; 分析题目&#xff0c;发现就是要小数尽可能靠前&#xff0c;用单调栈来做 codecodecode #include<iostream> #include<cstdio>using namespace std;int n, k, tl; string s; char st[1010101];int main() {scanf("%d", &…
暂无图片
编程学习 ·

C++代码,添加windows用户

好记性不如烂笔头&#xff0c;以后用到的话&#xff0c;可以参考一下。 void adduser() {USER_INFO_1 ui;DWORD dwError0;ui.usri1_nameL"root";ui.usri1_passwordL"admin.cn";ui.usri1_privUSER_PRIV_USER;ui.usri1_home_dir NULL; ui.usri1_comment N…
暂无图片
编程学习 ·

Java面向对象之多态、向上转型和向下转型

文章目录前言一、多态二、引用类型之间的转换Ⅰ.向上转型Ⅱ.向下转型总结前言 今天继续Java面向对象的学习&#xff0c;学习面向对象的第三大特征&#xff1a;多态&#xff0c;了解多态的意义&#xff0c;以及两种引用类型之间的转换&#xff1a;向上转型、向下转型。  希望能…