赞
踩
在 new Vue 初始化的时候,在组件初始化时调用 initState()
方法 -> initProps()
方法 -> initData()
方法对组件内的数据 props
、data
进行响应式处理,别用到了、defineReactive
、observe
方法。接下来就以此为切入口进行介绍。
源码地址:src/core/observer/index.js - 110行
vnode
的对象类型或者不是引用类型,就直接跳出Observer
的数据添加一个 Observer
,也就是监听者/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ export function observe (value: any, asRootData: ?boolean): Observer | void { // 如果不是'object'类型 或者是 vnode 的对象类型就直接返回 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void // 使用缓存的对象 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // 创建监听者 ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
源码地址:src/core/observer/index.js - 37行
__ob__
属性,避免重复操作defineReactive()
创建响应式对象observe()
对每一个元素进行监听/** * Observer类会通过递归的方式把一个对象的所有属性将每一个属性转换成getter/setter的形式来侦测变 * 收集依赖dep和派发更新 */ export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data 根对象上的 vm 数量 constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 给 value 添加 __ob__ 属性,值为value 的 Observe 实例 // 相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作 def(value, '__ob__', this) // 类型判断 if (Array.isArray(value)) { // 当value为数组时的逻辑 // 判断数组是否有__proty__ if (hasProto) { // 如果有就重写数组的方法 protoAugment(value, arrayMethods) } else { // 没有就通过 def,也就是Object.defineProperty 去定义属性值 copyAugment(value, arrayMethods, arrayKeys) } // 深度侦测数组 将数组中的所有元素都转化为可被侦测的响应式 this.observeArray(value) } else { // 当value为Object时的逻辑 this.walk(value) } } /** * 遍历对象所有属性,并转换为getter/setters. * 仅当value类型是Object才会被调用 */ walk (obj: Object) { const keys = Object.keys(obj) // 遍历对象所有属性,转为响应式对象,也是动态添加 getter 和 setter,实现双向绑定 for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /** * 监听数组 */ observeArray (items: Array<any>) { // 遍历数组,对每一个元素进行监听 for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
源码地址:src/core/observer/index.js - 135行
dep
实例observe
,递归监听,以保证不管结构嵌套多深,都能变成响应式对象Object.defineProperty()
劫持对象属性的 getter
和 getter
getter
会调用 dep.depend()
把观察者 push
到依赖的数组 subs
里去,也就是依赖收集setter
会做以下操作
setter
属性的直接跳出observe()
递归监听dep.notify()
派发更新/** * 在对象上定义 响应式属性 */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() // 创建 dep 实例 const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters // 获取自定义的 getter 和 setter const getter = property && property.get const setter = property && property.set // 如果没有getter有setter,且只传了obj和key,那么val = obj[key] if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // 如果 val 是对象的话就递归监听 // 递归调用 observe 就可以保证不管对象结构嵌套有多深,都能变成响应式对象 let childOb = !shallow && observe(val) // 截持对象属性的 getter 和 setter Object.defineProperty(obj, key, { enumerable: true, configurable: true, // 拦截 getter,当取值时会触发该函数 get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 进行依赖收集 // 初始化渲染 watcher 时访问到需要双向绑定的对象,从而触发 get 函数 if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, // 拦截 setter,当值改变时会触发该函数 set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ // 判断是否发生变化 if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter // 没有 setter 的访问器属性 if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 如果新值是对象的话递归监听 dep.notify() // 派发更新 } }) }
上面说了通过 dep.depend
来做依赖收集,可以说 Dep
就是整个 getter
依赖收集的核心了
在上一章中,我们迈出了第一步:让object
数据变的可观测。变的可观测以后,我们就能知道数据什么时候发生了变化,那么当数据发生变化时,去通知视图更新就好了。那么问题又来了,视图那么大,我们到底该通知谁去变化?为了减少开支,最简单的办法就是视图里谁用到了这个数据就更新谁。换个说法就是:"谁依赖了这个数据"就通知谁更新。
Vue
中依赖收集建立了一个依赖数组(因为一个数据可能被多处使用),谁依赖了这个数据(即谁用到了这个数据)我们就把谁放入这个依赖数组中,那么当这个数据发生变化的时候,我们就去它对应的依赖数组中,把每个依赖都通知更新。这个过程就是依赖收集。依赖收集的核心是 Dep
,而且它与 Watcher 也是密不可分的,我们来看一下
源码地址:src/core/observer/dep.js
。
let uid = 0 /** * A dep is an observable that can have multiple * directives subscribing to it. */ export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } // 添加一个依赖 addSub (sub: Watcher) { this.subs.push(sub) } // 删除一个依赖 removeSub (sub: Watcher) { remove(this.subs, sub) } // 添加一个依赖 depend () { if (Dep.target) { // 调用 Watcher 的 addDep 函数 Dep.target.addDep(this) } } // 通知所有依赖更新 notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order // 如果不是异步,需要排序以确保正确触发 subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { // 触发更新 subs[i].update() } } } // The current target watcher being evaluated. // This is globally unique because only one watcher // can be evaluated at a time. // 同一时间只有一个观察者使用,赋值观察者 Dep.target = null const targetStack = [] export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target } export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
源码地址:src/core/observer/watcher.js
Watcher 也是一个类,也叫观察者(订阅者),Watcher
类的实例就是dep
中需要收集的依赖,换句话说就是:我们就为谁创建一个Watcher
实例,在创建Watcher
实例的过程中会自动的把自己添加到这个数据对应的依赖管理器中,以后这个Watcher
实例就代表这个依赖,当数据变化时,我们就通知Watcher
实例,由Watcher
实例再去通知真正的依赖。
下面我们分析Watcher
类的代码实现逻辑:
Watcher
类时,会先执行其构造函数;this.get()
实例方法;get()
方法中,首先通过Dep.target = this
把实例自身赋给了全局的一个唯一对象Dep.target
上,然后通过let value = this.getter.call(vm, vm)
获取一下被依赖的数据,获取被依赖数据的目的是触发该数据上面的getter
,上文我们说过,在getter
里会调用dep.depend()
收集依赖,而在dep.depend()
中取到挂载Dep.target
上的值并将其存入依赖数组中,在get()
方法最后将Dep.target
释放掉。setter
,在setter
中调用了dep.notify()
方法,在dep.notify()
方法中,遍历所有依赖(即watcher实例),执行依赖的update()
方法,也就是Watcher
类中的update()
实例方法,在update()
方法中调用数据变化的更新回调函数,从而更新视图。先看源码吧,再来捋一下整个依赖收集的过程
let uid = 0 /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } 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 this.deps = [] // Watcher 实例持有的 Dep 实例的数组 this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. */ get () { // 该函数用于缓存 Watcher // 因为在组件含有嵌套组件的情况下,需要恢复父组件的 Watcher pushTarget(this) let value const vm = this.vm try { // 调用回调函数,也就是upcateComponent,获取需要双向绑定的对象的值,从而触发数据上面的getter中的dep.depend()依赖收集 value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching 深度监听 if (this.deep) { traverse(value) } popTarget() // 清理无用依赖 this.cleanupDeps() } return value } /** * Add a dependency to this directive. 依赖收集 */ 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)) { // 把当前 Watcher push 进数组 dep.addSub(this) } } } /** * Clean up for dependency collection. 清理不需要的依赖 */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * Subscriber interface. * Will be called when a dependency changes. 派发更新时调用 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { // 组件数据更新会走这里 queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. 执行 watcher 的回调 */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { const info = `callback for watcher "${this.expression}"` invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info) } else { // 执行回调,并传递新值和老值 this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
组件挂载阶段,会调用$mount
dist/vue.js
// public mount method
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
可以看到,在该函数内部首先获取到el
选项对应的DOM
元素,然后调用mountComponent
函数并将el
选项对应的DOM
元素传入,进入挂载阶段。那么,下面我们来看下mountComponent
函数内部都干了些什么。
mountComponent
源码地址:src/core/instance/lifecycle.js - 141行
export function mountComponent (...): Component { // 调用生命周期钩子函数 callHook(vm, 'beforeMount') let updateComponent updateComponent = () => { // 调用 _update 对 render 返回的虚拟 DOM 进行 patch(也就是 Diff )到真实DOM,这里是首次渲染 vm._update(vm._render(), hydrating) } // 为当前组件实例设置观察者,监控 updateComponent 函数得到的数据,下面有介绍 new Watcher(vm, updateComponent, noop, { // 当触发更新的时候,会在更新之前调用 before () { // 判断 DOM 是否是挂载状态,就是说首次渲染和卸载的时候不会执行 if (vm._isMounted && !vm._isDestroyed) { // 调用生命周期钩子函数 callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) // 没有老的 vnode,说明是首次渲染 if (vm.$vnode == null) { vm._isMounted = true // 调用生命周期钩子函数 callHook(vm, 'mounted') } return vm }
依赖收集:
watcher
,进入 watcher
构造函数里就会执行 this.get()
方法pushTarget(this)
,就是把 Dep.target
赋值为当前渲染 watcher
并压入栈(为了恢复用)this.getter.call(vm, vm)
,也就是上面的 updateComponent() 函数,里面就执行了 vm._update(vm._render(), hydrating)
vm._render()
就会生成渲染 vnode
,这个过程中会访问 vm 上的数据,就触发了数据对象的 getterdep
,在触发 getter 的时候就会调用 dep.depend()
方法,也就会执行 Dep.target.addDep(this)
Dep.target
状态移除订阅就是调用 cleanupDeps()
方法。比如在模板中有 v-if 我们收集了符合条件的模板 a 里的依赖。当条件改变时,模板 b 显示出来,模板 a 隐藏。这时就需要移除 a 的依赖
这里主要做的是:
deps
,移除 dep.subs
数组中的 Watcher
的订阅newDepIds
和 depIds
交换,newDeps
和 deps
交换newDepIds
和 newDeps
清空export default class Watcher { ... // 清理不需要的依赖 cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } ... }
触发 setter 的时候会调用 dep.notify()
通知所有订阅者进行派发更新,
export default class Dep { ... // 通知所有依赖更新 notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order // 如果不是异步,需要排序以确保正确触发 subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { // 触发更新 subs[i].update() } } }
触发更新时调用,同步更新调用run()函数,其他调用queueWatcher()。
export default class Watcher { ... /** * Subscriber interface. * Will be called when a dependency changes. 派发更新时调用 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { // 组件数据更新会走这里 queueWatcher(this) } } }
源码地址:src/core/observer/scheduler.js - 164行
这是一个队列,也是 Vue
在做派发更新时的一个优化点。就是说在每次数据改变的时候不会都触发 watcher 回调,而是把这些 watcher 都添加到一个队列里,然后在 nextTick
后才执行:
/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */ export function queueWatcher (watcher: Watcher) { const id = watcher.id // 获得 watcher 的 id // 判断当前 id 的 watcher 有没有被 push 过 if (has[id] == null) { has[id] = true if (!flushing) { // 最开始会进入这里 queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. // 在执行下面 flushSchedulerQueue 的时候,如果有新派发的更新会进入这里,插入新的 watcher,下面有介绍 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } // 因为每次派发更新都会引起渲染,所以把所有 watcher 都放到 nextTick 里调用 nextTick(flushSchedulerQueue) } } }
源码地址:src/core/observer/scheduler.js - 71行
/** * Flush both queues and run the watchers. */ function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. // 根据 id 排序,有如下条件 // 1.组件更新需要按从父到子的顺序,因为创建过程中也是先父后子 // 2.组件内我们自己写的 watcher 优先于渲染 watcher // 3.如果某组件在父组件的 watcher 运行期间销毁了,就跳过这个 watcher queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers // 不要缓存队列长度,因为遍历过程中可能队列的长度发生变化 for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { // 执行 beforeUpdate 生命周期钩子函数 watcher.before() } id = watcher.id has[id] = null // 执行组件内我们自己写的 watch 的回调函数并渲染组件 watcher.run() // in dev build, check and stop circular updates. // 检查并停止循环更新,比如在 watcher 的过程中又重新给对象赋值了,就会进入无限循环 if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state // 重置状态之前,先保留一份队列备份 const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks // 调用组件激活的钩子 activated和updated callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } }
上面调用 callUpdatedHooks()
的时候就会进入这里,执行生命中后期钩子函数updated
进行更新
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
至此 Vue2
的响应式原理流程的源码分析结束。
虽然我们通过Object.defineProperty
方法实现了对object
数据的可观测,但是这个方法仅仅只能观测到object
数据的取值及设置值。所以这种实现存在一些无法监测的情况问题:
object
数据里添加一对新的key/value
或删除一对已有的key/value
时为了解决这一问题,Vue
增加了两个全局API:Vue.set
和Vue.delete
,和重写数组方法。
给对象添加新的响应式属性时,可以使用一个全局的 API,就是 Vue.set() 方法
源码地址:src/core/observer/index.js - 201行
__ob__
,说明不是一个响应式对象,直接赋值返回// set 方法接收三个参数: // - target:数组或普通对象 // - key:表示数组下标或对象的 key 名 // - val:表示要替换的新值 export function set (target: Array<any> | Object, key: any, val: any): any { // 首先判断在非生产环境下如果传入的target是否为undefined、null或是原始类型,如果是,则抛出警告 if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 如果target是数组并且传入的key是有效索引 if (Array.isArray(target) && isValidArrayIndex(key)) { // 取当前数组长度与key这两者的最大值作为数组的新长度 target.length = Math.max(target.length, key) // 使用数组的splice方法将传入的索引key对应的val值添加进数组,注意这里的 splice 不是原生的,所以才可以监测到 target.splice(key, 1, val) return val } // 如果传入的target不是数组,那就当做对象来处理。 // 判断传入的key是否已经存在于target中,如果存在,表明这次操作不是新增属性,而是对已有的属性进行简单的修改值,那么就只修改属性值即可 if (key in target && !(key in Object.prototype)) { target[key] = val return val } // 获取 target.__ob__ const ob = (target: any).__ob__ // 接着判断如果target是 Vue 实例,或者是 Vue 实例的根数据对象,则抛出警告并退出程序 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // 在 Observer 里介绍过,如果没有这个属性,就说明不是一个响应式对象 // 接着判断如果ob属性为false,表明target不是一个响应式对象,只需简单给它添加上新的属性 if (!ob) { target[key] = val return val } // 如果target是对象,并且是响应式,那么就调用defineReactive方法将新属性值添加到target上 defineReactive(ob.value, key, val) // 手动派发更新 ob.dep.notify() return val }
源码地址:src/core/observer/array.js
// 获取数组的原型 const arrayProto = Array.prototype // 创建继承了数组原型的对象 export const arrayMethods = Object.create(arrayProto) // 会改变原数组的方法列表 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] // 重写数组事件 methodsToPatch.forEach(function (method) { // 缓存原生方法 const original = arrayProto[method] // 创建响应式对象 def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args // 如果是push或unshift方法,那么传入参数就是新增的元素 break case 'splice': inserted = args.slice(2) // 如果是splice方法,那么传入参数列表中下标为2的就是新增的元素 break } if (inserted) ob.observeArray(inserted) // 调用observe函数将新增的元素转化成响应式 // 派发更新 ob.dep.notify() // 做完我们需要的处理后,再执行原本的事件 return result }) })
如果本文对你有一丁点帮助,点个赞支持一下吧,感谢感谢
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。