Vue的数据依赖实现原理简析
首先让我们从最简单的一个实例Vue
入手:
const app = new Vue({
// options 传入一个选项obj.这个obj即对于这个vue实例的初始化
})
通过查阅文档,我们可以知道这个options
可以接受:
选项/数据
data
props
propsData(方便测试使用)
computed
methods
watch
选项 / DOM
选项 / 生命周期钩子
选项 / 资源
选项 / 杂项
具体未展开的内容请自行查阅相关文档,接下来让我们来看看传入的选项/数据
是如何管理数据之间的相互依赖的。
const app = new Vue({
el: '#app',
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
},
data: {
msg1: 'Hello world!',
arr: {
arr1: 1
}
},
watch: {
a (newVal, oldVal) {
console.log(newVal, oldVal)
}
},
methods: {
go () {
console.log('This is simple demo')
}
}
})
我们使用Vue
这个构造函数去实例化了一个vue
实例app
。传入了props
, data
, watch
, methods
等属性。在实例化的过程中,Vue
提供的构造函数就使用我们传入的options
去完成数据的依赖管理,初始化的过程只有一次,但是在你自己的程序当中,数据的依赖管理的次数不止一次。
那Vue
的构造函数到底是怎么实现的呢?Vue
// 构造函数
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// 对Vue这个class进行mixin,即在原型上添加方法
// Vue.prototype.* = function () {}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
当我们调用new Vue
的时候,事实上就调用的Vue
原型上的_init
方法.
// 原型上提供_init方法,新建一个vue实例并传入options参数
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
// 将传入的这些options选项挂载到vm.$options属性上
vm.$options = mergeOptions(
// components/filter/directive
resolveConstructorOptions(vm.constructor),
// this._init()传入的options
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm // 自身的实例
// 接下来所有的操作都是在这个实例上添加方法
initLifecycle(vm) // lifecycle初始化
initEvents(vm) // events初始化 vm._events, 主要是提供vm实例上的$on/$emit/$off/$off等方法
initRender(vm) // 初始化渲染函数,在vm上绑定$createElement方法
callHook(vm, 'beforeCreate') // 钩子函数的执行, beforeCreate
initInjections(vm) // resolve injections before data/props
initState(vm) // Observe data添加对data的监听, 将data转化为getters/setters
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 钩子函数的执行, created
// vm挂载的根元素
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
其中在this._init()
方法中调用initState(vm)
,完成对vm
这个实例的数据的监听,也是本文所要展开说的具体内容。
export function initState (vm: Component) {
// 首先在vm上初始化一个_watchers数组,缓存这个vm上的所有watcher
vm._watchers = []
// 获取options,包括在new Vue传入的,同时还包括了Vue所继承的options
const opts = vm.$options
// 初始化props属性
if (opts.props) initProps(vm, opts.props)
// 初始化methods属性
if (opts.methods) initMethods(vm, opts.methods)
// 初始化data属性
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
// 初始化computed属性
if (opts.computed) initComputed(vm, opts.computed)
// 初始化watch属性
if (opts.watch) initWatch(vm, opts.watch)
}
initProps
我们在实例化app
的时候,在构造函数里面传入的options
中有props
属性:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
}
function initProps (vm: Component, propsOptions: Object) {
// propsData主要是为了方便测试使用
const propsData = vm.$options.propsData || {}
// 新建vm._props对象,可以通过app实例去访问
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
// 缓存的prop key
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
observerState.shouldConvert = isRoot
for (const key in propsOptions) {
// this._init传入的options中的props属性
keys.push(key)
// 注意这个validateProp方法,不仅完成了prop属性类型验证的,同时将prop的值都转化为了getter/setter,并返回一个observer
const value = validateProp(key, propsOptions, propsData, vm)
// 将这个key对应的值转化为getter/setter
defineReactive(props, key, value)
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
// 如果在vm这个实例上没有key属性,那么就通过proxy转化为proxyGetter/proxySetter, 并挂载到vm实例上,可以通过app._props[key]这种形式去访问
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
observerState.shouldConvert = true
}
接下来看下validateProp(key, propsOptions, propsData, vm)
方法内部到底发生了什么。
export function validateProp (
key: string,
propOptions: Object, // $options.props属性
propsData: Object, // $options.propsData属性
vm?: Component
): any {
const prop = propOptions[key]
// 如果在propsData测试props上没有缓存的key
const absent = !hasOwn(propsData, key)
let value = propsData[key]
// 处理boolean类型的数据
// handle boolean props
if (isType(Boolean, prop.type)) {
if (absent && !hasOwn(prop, 'default')) {
value = false
} else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {
value = true
}
}
// check default value
if (value === undefined) {
// default属性值,是基本类型还是function
// getPropsDefaultValue见下面第一段代码
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldConvert = observerState.shouldConvert
observerState.shouldConvert = true
// 将value的所有属性转化为getter/setter形式
// 并添加value的依赖
// observe方法的分析见下面第二段代码
observe(value)
observerState.shouldConvert = prevShouldConvert
}
if (process.env.NODE_ENV !== 'production') {
assertProp(prop, key, value, vm, absent)
}
return value
}
// 获取prop的默认值
function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
// no default, return undefined
// 如果没有default属性的话,那么就返回undefined
if (!hasOwn(prop, 'default')) {
return undefined
}
const def = prop.default
// the raw prop value was also undefined from previous render,
// return previous default value to avoid unnecessary watcher trigger
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined) {
return vm._props[key]
}
// call factory function for non-Function types
// a value is Function if its prototype is function even across different execution context
// 如果是function 则调用def.call(vm)
// 否则就返回default属性对应的值
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
Vue
提供了一个observe
方法,在其内部实例化了一个Observer
类,并返回Observer
的实例。每一个Observer
实例对应记录了props
中这个的default value
的所有依赖(仅限object
类型),这个Observer
实际上就是一个观察者,它维护了一个数组this.subs = []
用以收集相关的subs(订阅者)
(即这个观察者的依赖)。通过将default value
转化为getter/setter
形式,同时添加一个自定义__ob__
属性,这个属性就对应Observer
实例。
说起来有点绕,还是让我们看看我们给的demo
里传入的options
配置:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
}
在往上数的第二段代码里面的方法obervse(value)
,即对{key1: 'a', key2: {a: 'b'}}
进行依赖的管理,同时将这个obj
所有的属性值都转化为getter/setter
形式。此外,Vue
还会将props
属性都代理到vm
实例上,通过vm.key1
,vm.key2
就可以访问到这个属性。
此外,还需要了解下在Vue
中管理依赖的一个非常重要的类: Dep
export default class Dep {
constructor () {
this.id = uid++
this.subs = []
}
addSub () {...} // 添加订阅者(依赖)
removeSub () {...} // 删除订阅者(依赖)
depend () {...} // 检查当前Dep.target是否存在以及判断这个watcher已经被添加到了相应的依赖当中,如果没有则添加订阅者(依赖),如果已经被添加了那么就不做处理
notify () {...} // 通知订阅者(依赖)更新
}
在Vue
的整个生命周期当中,你所定义的响应式的数据上都会绑定一个Dep
实例去管理其依赖。它实际上就是观察者
和订阅者
联系的一个桥梁。
刚才谈到了对于依赖的管理,它的核心之一就是观察者Observer
这个类:
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
// dep记录了和这个value值的相关依赖
this.dep = new Dep()
this.vmCount = 0
// value其实就是vm._data, 即在vm._data上添加__ob__属性
def(value, '__ob__', this)
// 如果是数组
if (Array.isArray(value)) {
// 首先判断是否能使用__proto__属性
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
// 遍历数组,并将obj类型的属性改为getter/setter实现
this.observeArray(value)
} else {
// 遍历obj上的属性,将每个属性改为getter/setter实现
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// 将每个property对应的属性都转化为getter/setters,只能是当这个value的类型为Object时
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
// 监听array中的item
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
walk
方法里面调用defineReactive
方法:通过遍历这个object
的key
,并将对应的value
转化为getter/setter
形式,通过闭包维护一个dep
,在getter
方法当中定义了这个key
是如何进行依赖的收集,在setter
方法中定义了当这个key
对应的值改变后,如何完成相关依赖数据的更新。但是从源码当中,我们却发现当getter
函数被调用的时候并非就一定会完成依赖的收集,其中还有一层判断,就是Dep.target
是否存在。
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
// 每个属性新建一个dep实例,管理这个属性的依赖
const dep = new Dep()
// 或者属性描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
// 如果这个属性是不可配的,即无法更改
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
// 递归去将val转化为getter/setter
// childOb将子属性也转化为Observer
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 定义getter -->> reactiveGetter
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 定义相应的依赖
if (Dep.target) {
// Dep.target.addDep(this)
// 即添加watch函数
// dep.depend()及调用了dep.addSub()只不过中间需要判断是否这个id的dep已经被包含在内了
dep.depend()
// childOb也添加依赖
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
return value
},
// 定义setter -->> reactiveSetter
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 对得到的新值进行observe
childOb = observe(newVal)
// 相应的依赖进行更新
dep.notify()
}
})
}
在上文中提到了Dep
类是链接观察者
和订阅者
的桥梁。同时在Dep
的实现当中还有一个非常重要的属性就是Dep.target
,它事实就上就是一个订阅者,只有当Dep.target
(订阅者)存在的时候,调用属性的getter
函数的时候才能完成依赖的收集工作。
Dep.target = null
const targetStack = []
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
那么Vue
是如何来实现订阅者
的呢?Vue
里面定义了一个类: Watcher
,在Vue
的整个生命周期当中,会有4类地方会实例化Watcher
:
Vue
实例化的过程中有watch
选项Vue
实例化的过程中有computed
计算属性选项Vue
原型上有挂载$watch
方法: Vue.prototype.$watch,可以直接通过实例调用this.$watch
方法Vue
生成了render
函数,更新视图时
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
// 缓存这个实例vm
this.vm = vm
// vm实例中的_watchers中添加这个watcher
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
....
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
}
}
// 通过get方法去获取最新的值
// 如果lazy为true, 初始化的时候为undefined
this.value = this.lazy
? undefined
: this.get()
}
get () {...}
addDep () {...}
update () {...}
run () {...}
evaluate () {...}
run () {...}
Watcher
接收的参数当中expOrFn
定义了用以获取watcher
的getter
函数。expOrFn
可以有2种类型:string
或function
.若为string
类型,首先会通过parsePath
方法去对string
进行分割(仅支持.
号形式的对象访问)。在除了computed
选项外,其他几种实例化watcher
的方式都是在实例化过程中完成求值及依赖的收集工作:this.value = this.lazy ? undefined : this.get()
.在Watcher
的get
方法中:
!!!前方高能
get () {
// pushTarget即设置当前的需要被执行的watcher
pushTarget(this)
let value
const vm = this.vm
if (this.user) {
try {
// $watch(function () {})
// 调用this.getter的时候,触发了属性的getter函数
// 在getter中进行了依赖的管理
value = this.getter.call(vm, vm)
console.log(value)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
// 如果是新建模板函数,则会动态计算模板与data中绑定的变量,这个时候就调用了getter函数,那么就完成了dep的收集
// 调用getter函数,则同时会调用函数内部的getter的函数,进行dep收集工作
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
// 让每个属性都被作为dependencies而tracked, 这样是为了deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
一进入get
方法,首先进行pushTarget(this)
的操作,此时Vue
当中Dep.target = 当前这个watcher
,接下来进行value = this.getter.call(vm, vm)
操作,在这个操作中就完成了依赖的收集工作。还是拿文章一开始的demo
来说,在vue
实例化的时候传入了watch
选项:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
},
watch: {
a (newVal, oldVal) {
console.log(newVal, oldVal)
}
},
在Vue
的initState()
开始执行后,首先会初始化props
的属性为getter/setter
函数,然后在进行initWatch
初始化的时候,这个时候初始化watcher
实例,并调用get()
方法,设置Dep.target = 当前这个watcher实例
,进而到value = this.getter.call(vm, vm)
的操作。在调用this.getter.call(vm, vm)
的方法中,便会访问props
选项中的a
属性即其getter
函数。在a
属性的getter
函数执行过程中,因为Dep.target
已经存在,那么就进入了依赖收集
的过程:
if (Dep.target) {
// Dep.target.addDep(this)
// 即添加watch函数
// dep.depend()及调用了dep.addSub()只不过中间需要判断是否这个id的dep已经被包含在内了
dep.depend()
// childOb也添加依赖
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
dep
是一开始初始化的过程中,这个属性上的dep
属性。调用dep.depend()
函数:
depend () {
if (Dep.target) {
// Dep.target为一个watcher
Dep.target.addDep(this)
}
}
Dep.target
也就刚才的那个watcher
实例,这里也就相当于调用了watcher
实例的addDep
方法: watcher.addDep(this)
,并将dep
观察者传入。在addDep
方法中完成依赖收集:
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
这个时候依赖完成了收集,当你去修改a
属性的值时,会调用a
属性的setter
函数,里面会执行dep.notify()
,它会遍历所有的订阅者,然后调用订阅者上的update
函数。
initData
过程和initProps
类似,具体可参见源码。
initComputed
以上就是在initProps
过程中Vue
是如何进行依赖收集的,initData
的过程和initProps
类似,下来再来看看initComputed
的过程.
在computed
属性初始化的过程当中,会为每个属性实例化一个watcher
:
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
// 新建_computedWatchers属性
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
// 如果computed为funtion,即取这个function为getter函数
// 如果computed为非function.则可以单独为这个属性定义getter/setter属性
let getter = typeof userDef === 'function' ? userDef : userDef.get
// create internal watcher for the computed property.
// lazy属性为true
// 注意这个地方传入的getter参数
// 实例化的过程当中不去完成依赖的收集工作
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
但是这个watcher
在实例化的过程中,由于传入了{lazy: true}
的配置选项,那么一开始是不会进行求值与依赖收集的: this.value = this.lazy ? undefined : this.get()
.在initComputed
的过程中,Vue
会将computed
属性定义到vm
实例上,同时将这个属性定义为getter/setter
。当你访问computed
属性的时候调用getter
函数:
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 是否需要重新计算
if (watcher.dirty) {
watcher.evaluate()
}
// 管理依赖
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
在watcher
存在的情况下,首先判断watcher.dirty
属性,这个属性主要是用于判断这个computed
属性是否需要重新求值,因为在上一轮的依赖收集的过程当中,观察者已经将这个watcher
添加到依赖数组当中了,如果观察者发生了变化,就会dep.notify()
,通知所有的watcher
,而对于computed
的watcher
接收到变化的请求后,会将watcher.dirty = true
即表明观察者发生了变化,当再次调用computed
属性的getter
函数的时候便会重新计算,否则还是使用之前缓存的值。
initWatch
initWatch
的过程中其实就是实例化new Watcher
完成观察者的依赖收集的过程,在内部的实现当中是调用了原型上的Vue.prototype.$watch
方法。这个方法也适用于vm
实例,即在vm
实例内部调用this.$watch
方法去实例化watcher
,完成依赖的收集,同时监听expOrFn
的变化。
总结:
以上就是在Vue
实例初始化的过程中实现依赖管理的分析。大致的总结下就是:
initState
的过程中,将props
,computed
,data
等属性通过Object.defineProperty
来改造其getter/setter
属性,并为每一个响应式属性实例化一个observer
观察者。这个observer
内部dep
记录了这个响应式属性的所有依赖。当响应式属性调用
setter
函数时,通过dep.notify()
方法去遍历所有的依赖,调用watcher.update()
去完成数据的动态响应。
这篇文章主要从初始化的数据层面上分析了Vue
是如何管理依赖来到达数据的动态响应。下一篇文章来分析下Vue
中模板中的指令和响应式数据是如何关联来实现由数据驱动视图,以及数据是如何响应视图变化的。
原文地址:https://segmentfault.com/a/1190000010014281
Vue的数据依赖实现原理简析的更多相关文章
- Java Android 注解(Annotation) 及几个常用开源项目注解原理简析
不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...
- PHP的错误报错级别设置原理简析
原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...
- Java Annotation 及几个常用开源项目注解原理简析
PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...
- [转载] Thrift原理简析(JAVA)
转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...
- Spring系列.@EnableRedisHttpSession原理简析
在集群系统中,经常会需要将Session进行共享.不然会出现这样一个问题:用户在系统A上登陆以后,假如后续的一些操作被负载均衡到系统B上面,系统B发现本机上没有这个用户的Session,会强制让用户重 ...
- SIFT特征原理简析(HELU版)
SIFT(Scale-Invariant Feature Transform)是一种具有尺度不变性和光照不变性的特征描述子,也同时是一套特征提取的理论,首次由D. G. Lowe于2004年以< ...
- 基于IdentityServer4的OIDC实现单点登录(SSO)原理简析
写着前面 IdentityServer4的学习断断续续,兜兜转转,走了不少弯路,也花了不少时间.可能是因为没有阅读源码,也没有特别系统的学习资料,相关文章很多园子里的大佬都有涉及,有系列文章,比如: ...
- ARP攻击原理简析及防御措施
0x1 简介 网络欺骗攻击作为一种非常专业化的攻击手段,给网络安全管理者,带来严峻的考验.网络安全的战场已经从互联网蔓延到用户内部的网络, 特别是局域网.目前利用ARP欺骗的木马病毒在局域网中广泛传 ...
- EM算法原理简析——图解
一. 扯淡 转眼间毕业快一年了,这期间混了两份工作,从游戏开发到算法.感觉自己还是喜欢算法,可能是大学混了几年算法吧!所以不想浪费基础... 我是个懒得写博客的人,混了几年coding,写的博客不超过 ...
随机推荐
- Problem 2
Problem 2 # Problem_2.py """ Each new term in the Fibonacci sequence is generated by ...
- 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclips
警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclips ...
- spring 整合freemarker 实现模板继承
<!--freemarker 配置--> <bean id="freemarkerConfig" class="org.springframework. ...
- Mysql 下DELETE操作表别名问题
在用DELETE删除mysql数据库数据时采取一下两种方式: 方式一:DELETE FROM B_PROSON WHERE ID = 1; 不使用别名 方式二:DELETE BP FROM B_PRO ...
- [Android中级]使用Commons-net-ftp来实现FTP上传、下载的功能
本文属于学习分享,如有雷同纯属巧合 利用业余时间.学习一些实用的东西,假设手又有点贱的话.最好还是自己也跟着敲起来. 在android上能够通过自带的ftp组件来完毕各种功能.这次是由于项目中看到用了 ...
- wpf获取webbroswer的两个方法
//跳转前的地址 private void WebBrowser_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref ...
- Flex AsDoc 完整版
Flex 生成AsDoc用的是SDK自带的asdoc.exe工具 生成AsDoc文档的方式有两种:ant或者FlashBuilder 外部配置工具 方法一:外部配置工具 新增一个外部配置工具.过程例如 ...
- Agile实践日志一 -- Grooming Session
Agile实践日志一 -- Grooming Session GroomingSession 这个Session主要Go through我们下一个Sprint须要做的Story,大家都清楚之后,在每 ...
- USACO 1.4 Arithmetic Progressions
Arithmetic Progressions An arithmetic progression is a sequence of the form a, a+b, a+2b, ..., a+nb ...
- BZOJ 4710 容斥原理+dp
//By SiriusRen #include <cstdio> using namespace std; int n,m,a[1005]; typedef long long ll; l ...