摘要:本文主要通过结合vue官方文档及源码,对vue响应式原理进行深入分析。

1.定义

作为vue最独特的特性,响应式可以说是vue的灵魂了,表面上看就是数据发生变化后,对应的界面会重新渲染,那么响应式系统的底层细节到底是怎么一回事呢?

Tips:vue的响应式系统在vue2.0和vue3.0版本中的底层实现有所不同,简单了来说就是处理属性的getter/setter部分从Object.defineProperty替换成了Proxy(不过vue3也保留了Object.defineProperty方式用于支持IE浏览器)

1.1.vue2.0实现原理

当一个普通的javascript对象传入vue实例作为data选项时,vue将遍历data的所有属性,并使用Object.defineProperty重写这些属性的getter/setter方法,这些属性的getter/setter对于用户不可见,但是vue可以利用他们来追踪依赖,在属性值被访问和修改时通知变更。每个组件实例都对应一个watcher实例,它会在组件渲染的过程中访问过的属性设置为依赖。之后当属性的setter触发时,会通知watcher对关联的组件进行重新渲染。

1.2.vue3.0实现原理

当一个普通的javascript对象传入vue实例作为data选项时,vue会将其转化为Proxy。首次渲染后,组件将跟踪在渲染过程中被访问的属性,组件就成了这些属性的订阅者。当proxy拦截到set操作时,该属性将通知所有订阅了它的组件进行重新渲染。

2.源码解析

通过上面的定义可能对于响应式的原理还不够清楚,接下来通过对vue源码的分析进行深入理解。

2.1.vue2.0源码实现

在vue2.0中,vue的响应式系统是基于数据拦截+发布订阅的模式,包含了四个模块:

  1. Observer:通过Object.defineProperty拦截data属性的setter/getter方法,从而使每个属性都拥有一个Dep,当触发getter时收集依赖(使用该属性的watcher),当触发setter时通知更新;
  2. Dep:依赖收集器,用于维护依赖data属性的所有Watcher;
  3. Watcher:将视图依赖的属性绑定到Dep中,当数据修改时触发setter,调用Dep的notify方法,通知所有依赖该属性的Watcher进行update更新视图,使属性值与视图绑定起来;
  4. Compile:模板指令解析器,对模板每个元素节点的指令进行扫描解析,根据指令模板替换属性数据,同时注入Watcher更新数据的回调方法。

  • Observer
  1. import Dep from './dep'
  2. import VNode from '../vdom/vnode'
  3. import { arrayMethods } from './array'
  4. import {
  5. def,
  6. warn,
  7. hasOwn,
  8. hasProto,
  9. isObject,
  10. isPlainObject,
  11. isPrimitive,
  12. isUndef,
  13. isValidArrayIndex,
  14. isServerRendering
  15. } from '../util/index'
  16.  
  17. const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
  18.  
  19. /**
  20. * In some cases we may want to disable observation inside a component's
  21. * update computation.
  22. */
  23. export let shouldObserve: boolean = true
  24.  
  25. export function toggleObserving (value: boolean) {
  26. shouldObserve = value
  27. }
  28.  
  29. /**
  30. * Observer class that is attached to each observed
  31. * object. Once attached, the observer converts the target
  32. * object's property keys into getter/setters that
  33. * collect dependencies and dispatch updates.
  34. */
  35. export class Observer {
  36. value: any;
  37. dep: Dep;
  38. vmCount: number; // number of vms that have this object as root $data
  39.  
  40. constructor (value: any) {
  41. this.value = value
  42. this.dep = new Dep()
  43. this.vmCount = 0
  44. def(value, '__ob__', this)
  45. if (Array.isArray(value)) {
  46. if (hasProto) {
  47. protoAugment(value, arrayMethods)
  48. } else {
  49. copyAugment(value, arrayMethods, arrayKeys)
  50. }
  51. this.observeArray(value)
  52. } else {
  53. this.walk(value)
  54. }
  55. }
  56.  
  57. /**
  58. * Walk through all properties and convert them into
  59. * getter/setters. This method should only be called when
  60. * value type is Object.
  61. */
  62. walk (obj: Object) {
  63. const keys = Object.keys(obj)
  64. for (let i = 0; i < keys.length; i++) {
  65. defineReactive(obj, keys[i])
  66. }
  67. }
  68.  
  69. /**
  70. * Observe a list of Array items.
  71. */
  72. observeArray (items: Array) {
  73. for (let i = 0, l = items.length; i < l; i++) {
  74. observe(items[i])
  75. }
  76. }
  77. }
  78.  
  79. // helpers
  80.  
  81. /**
  82. * Augment a target Object or Array by intercepting
  83. * the prototype chain using __proto__
  84. */
  85. function protoAugment (target, src: Object) {
  86. /* eslint-disable no-proto */
  87. target.__proto__ = src
  88. /* eslint-enable no-proto */
  89. }
  90.  
  91. /**
  92. * Augment a target Object or Array by defining
  93. * hidden properties.
  94. */
  95. /* istanbul ignore next */
  96. function copyAugment (target: Object, src: Object, keys: Array) {
  97. for (let i = 0, l = keys.length; i < l; i++) {
  98. const key = keys[i]
  99. def(target, key, src[key])
  100. }
  101. }
  102.  
  103. /**
  104. * Attempt to create an observer instance for a value,
  105. * returns the new observer if successfully observed,
  106. * or the existing observer if the value already has one.
  107. */
  108. export function observe (value: any, asRootData: ?boolean): Observer | void {
  109. if (!isObject(value) || value instanceof VNode) {
  110. return
  111. }
  112. let ob: Observer | void
  113. if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  114. ob = value.__ob__
  115. } else if (
  116. shouldObserve &&
  117. !isServerRendering() &&
  118. (Array.isArray(value) || isPlainObject(value)) &&
  119. Object.isExtensible(value) &&
  120. !value._isVue
  121. ) {
  122. ob = new Observer(value)
  123. }
  124. if (asRootData && ob) {
  125. ob.vmCount++
  126. }
  127. return ob
  128. }
  129.  
  130. /**
  131. * Define a reactive property on an Object.
  132. */
  133. export function defineReactive (
  134. obj: Object,
  135. key: string,
  136. val: any,
  137. customSetter?: ?Function,
  138. shallow?: boolean
  139. ) {
  140. const dep = new Dep()
  141.  
  142. const property = Object.getOwnPropertyDescriptor(obj, key)
  143. if (property && property.configurable === false) {
  144. return
  145. }
  146.  
  147. // cater for pre-defined getter/setters
  148. const getter = property && property.get
  149. const setter = property && property.set
  150. if ((!getter || setter) && arguments.length === 2) {
  151. val = obj[key]
  152. }
  153.  
  154. let childOb = !shallow && observe(val)
  155. Object.defineProperty(obj, key, {
  156. enumerable: true,
  157. configurable: true,
  158. get: function reactiveGetter () {
  159. const value = getter ? getter.call(obj) : val
  160. if (Dep.target) {
  161. dep.depend()
  162. if (childOb) {
  163. childOb.dep.depend()
  164. if (Array.isArray(value)) {
  165. dependArray(value)
  166. }
  167. }
  168. }
  169. return value
  170. },
  171. set: function reactiveSetter (newVal) {
  172. const value = getter ? getter.call(obj) : val
  173. /* eslint-disable no-self-compare */
  174. if (newVal === value || (newVal !== newVal && value !== value)) {
  175. return
  176. }
  177. /* eslint-enable no-self-compare */
  178. if (process.env.NODE_ENV !== 'production' && customSetter) {
  179. customSetter()
  180. }
  181. // #7981: for accessor properties without setter
  182. if (getter && !setter) return
  183. if (setter) {
  184. setter.call(obj, newVal)
  185. } else {
  186. val = newVal
  187. }
  188. childOb = !shallow && observe(newVal)
  189. dep.notify()
  190. }
  191. })
  192. }
  193.  
  194. /**
  195. * Set a property on an object. Adds the new property and
  196. * triggers change notification if the property doesn't
  197. * already exist.
  198. */
  199. export function set (target: Array | Object, key: any, val: any): any {
  200. if (process.env.NODE_ENV !== 'production' &&
  201. (isUndef(target) || isPrimitive(target))
  202. ) {
  203. warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  204. }
  205. if (Array.isArray(target) && isValidArrayIndex(key)) {
  206. target.length = Math.max(target.length, key)
  207. target.splice(key, 1, val)
  208. return val
  209. }
  210. if (key in target && !(key in Object.prototype)) {
  211. target[key] = val
  212. return val
  213. }
  214. const ob = (target: any).__ob__
  215. if (target._isVue || (ob && ob.vmCount)) {
  216. process.env.NODE_ENV !== 'production' && warn(
  217. 'Avoid adding reactive properties to a Vue instance or its root $data ' +
  218. 'at runtime - declare it upfront in the data option.'
  219. )
  220. return val
  221. }
  222. if (!ob) {
  223. target[key] = val
  224. return val
  225. }
  226. defineReactive(ob.value, key, val)
  227. ob.dep.notify()
  228. return val
  229. }
  230.  
  231. /**
  232. * Delete a property and trigger change if necessary.
  233. */
  234. export function del (target: Array | Object, key: any) {
  235. if (process.env.NODE_ENV !== 'production' &&
  236. (isUndef(target) || isPrimitive(target))
  237. ) {
  238. warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  239. }
  240. if (Array.isArray(target) && isValidArrayIndex(key)) {
  241. target.splice(key, 1)
  242. return
  243. }
  244. const ob = (target: any).__ob__
  245. if (target._isVue || (ob && ob.vmCount)) {
  246. process.env.NODE_ENV !== 'production' && warn(
  247. 'Avoid deleting properties on a Vue instance or its root $data ' +
  248. '- just set it to null.'
  249. )
  250. return
  251. }
  252. if (!hasOwn(target, key)) {
  253. return
  254. }
  255. delete target[key]
  256. if (!ob) {
  257. return
  258. }
  259. ob.dep.notify()
  260. }
  261.  
  262. /**
  263. * Collect dependencies on array elements when the array is touched, since
  264. * we cannot intercept array element access like property getters.
  265. */
  266. function dependArray (value: Array) {
  267. for (let e, i = 0, l = value.length; i < l; i++) {
  268. e = value[i]
  269. e && e.__ob__ && e.__ob__.dep.depend()
  270. if (Array.isArray(e)) {
  271. dependArray(e)
  272. }
  273. }
  274. }

总结:Observer通过重写data上各个属性的setter/getter方法,对每个属性都维护一个Dep,用于收集依赖该属性的所有Watcher,当该属性触发setter时,派发更新的通知。

  • Dep
  1. import type Watcher from './watcher'
  2. import { remove } from '../util/index'
  3. import config from '../config'
  4.  
  5. let uid = 0
  6.  
  7. /**
  8. * A dep is an observable that can have multiple
  9. * directives subscribing to it.
  10. */
  11. export default class Dep {
  12. static target: ?Watcher;
  13. id: number;
  14. subs: Array;
  15.  
  16. constructor () {
  17. this.id = uid++
  18. this.subs = []
  19. }
  20.  
  21. addSub (sub: Watcher) {
  22. this.subs.push(sub)
  23. }
  24.  
  25. removeSub (sub: Watcher) {
  26. remove(this.subs, sub)
  27. }
  28.  
  29. depend () {
  30. if (Dep.target) {
  31. Dep.target.addDep(this)
  32. }
  33. }
  34.  
  35. notify () {
  36. // stabilize the subscriber list first
  37. const subs = this.subs.slice()
  38. if (process.env.NODE_ENV !== 'production' && !config.async) {
  39. // subs aren't sorted in scheduler if not running async
  40. // we need to sort them now to make sure they fire in correct
  41. // order
  42. subs.sort((a, b) => a.id - b.id)
  43. }
  44. for (let i = 0, l = subs.length; i < l; i++) {
  45. subs[i].update()
  46. }
  47. }
  48. }
  49.  
  50. // The current target watcher being evaluated.
  51. // This is globally unique because only one watcher
  52. // can be evaluated at a time.
  53. Dep.target = null
  54. const targetStack = []
  55.  
  56. export function pushTarget (target: ?Watcher) {
  57. targetStack.push(target)
  58. Dep.target = target
  59. }
  60.  
  61. export function popTarget () {
  62. targetStack.pop()
  63. Dep.target = targetStack[targetStack.length - 1]
  64. }

总结:Dep一方面用数组收集与属性相关的Watcher,另一方面遍历数组通知每个Watcher进行update。

  • Watcher
  1. import {
  2. warn,
  3. remove,
  4. isObject,
  5. parsePath,
  6. _Set as Set,
  7. handleError,
  8. noop
  9. } from '../util/index'
  10.  
  11. import { traverse } from './traverse'
  12. import { queueWatcher } from './scheduler'
  13. import Dep, { pushTarget, popTarget } from './dep'
  14.  
  15. import type { SimpleSet } from '../util/index'
  16.  
  17. let uid = 0
  18.  
  19. /**
  20. * A watcher parses an expression, collects dependencies,
  21. * and fires callback when the expression value changes.
  22. * This is used for both the $watch() api and directives.
  23. */
  24. export default class Watcher {
  25. vm: Component;
  26. expression: string;
  27. cb: Function;
  28. id: number;
  29. deep: boolean;
  30. user: boolean;
  31. lazy: boolean;
  32. sync: boolean;
  33. dirty: boolean;
  34. active: boolean;
  35. deps: Array;
  36. newDeps: Array;
  37. depIds: SimpleSet;
  38. newDepIds: SimpleSet;
  39. before: ?Function;
  40. getter: Function;
  41. value: any;
  42.  
  43. constructor (
  44. vm: Component,
  45. expOrFn: string | Function,
  46. cb: Function,
  47. options?: ?Object,
  48. isRenderWatcher?: boolean
  49. ) {
  50. this.vm = vm
  51. if (isRenderWatcher) {
  52. vm._watcher = this
  53. }
  54. vm._watchers.push(this)
  55. // options
  56. if (options) {
  57. this.deep = !!options.deep
  58. this.user = !!options.user
  59. this.lazy = !!options.lazy
  60. this.sync = !!options.sync
  61. this.before = options.before
  62. } else {
  63. this.deep = this.user = this.lazy = this.sync = false
  64. }
  65. this.cb = cb
  66. this.id = ++uid // uid for batching
  67. this.active = true
  68. this.dirty = this.lazy // for lazy watchers
  69. this.deps = []
  70. this.newDeps = []
  71. this.depIds = new Set()
  72. this.newDepIds = new Set()
  73. this.expression = process.env.NODE_ENV !== 'production'
  74. ? expOrFn.toString()
  75. : ''
  76. // parse expression for getter
  77. if (typeof expOrFn === 'function') {
  78. this.getter = expOrFn
  79. } else {
  80. this.getter = parsePath(expOrFn)
  81. if (!this.getter) {
  82. this.getter = noop
  83. process.env.NODE_ENV !== 'production' && warn(
  84. `Failed watching path: "${expOrFn}" ` +
  85. 'Watcher only accepts simple dot-delimited paths. ' +
  86. 'For full control, use a function instead.',
  87. vm
  88. )
  89. }
  90. }
  91. this.value = this.lazy
  92. ? undefined
  93. : this.get()
  94. }
  95.  
  96. /**
  97. * Evaluate the getter, and re-collect dependencies.
  98. */
  99. get () {
  100. pushTarget(this)
  101. let value
  102. const vm = this.vm
  103. try {
  104. value = this.getter.call(vm, vm)
  105. } catch (e) {
  106. if (this.user) {
  107. handleError(e, vm, `getter for watcher "${this.expression}"`)
  108. } else {
  109. throw e
  110. }
  111. } finally {
  112. // "touch" every property so they are all tracked as
  113. // dependencies for deep watching
  114. if (this.deep) {
  115. traverse(value)
  116. }
  117. popTarget()
  118. this.cleanupDeps()
  119. }
  120. return value
  121. }
  122.  
  123. /**
  124. * Add a dependency to this directive.
  125. */
  126. addDep (dep: Dep) {
  127. const id = dep.id
  128. if (!this.newDepIds.has(id)) {
  129. this.newDepIds.add(id)
  130. this.newDeps.push(dep)
  131. if (!this.depIds.has(id)) {
  132. dep.addSub(this)
  133. }
  134. }
  135. }
  136.  
  137. /**
  138. * Clean up for dependency collection.
  139. */
  140. cleanupDeps () {
  141. let i = this.deps.length
  142. while (i--) {
  143. const dep = this.deps[i]
  144. if (!this.newDepIds.has(dep.id)) {
  145. dep.removeSub(this)
  146. }
  147. }
  148. let tmp = this.depIds
  149. this.depIds = this.newDepIds
  150. this.newDepIds = tmp
  151. this.newDepIds.clear()
  152. tmp = this.deps
  153. this.deps = this.newDeps
  154. this.newDeps = tmp
  155. this.newDeps.length = 0
  156. }
  157.  
  158. /**
  159. * Subscriber interface.
  160. * Will be called when a dependency changes.
  161. */
  162. update () {
  163. /* istanbul ignore else */
  164. if (this.lazy) {
  165. this.dirty = true
  166. } else if (this.sync) {
  167. this.run()
  168. } else {
  169. queueWatcher(this)
  170. }
  171. }
  172.  
  173. /**
  174. * Scheduler job interface.
  175. * Will be called by the scheduler.
  176. */
  177. run () {
  178. if (this.active) {
  179. const value = this.get()
  180. if (
  181. value !== this.value ||
  182. // Deep watchers and watchers on Object/Arrays should fire even
  183. // when the value is the same, because the value may
  184. // have mutated.
  185. isObject(value) ||
  186. this.deep
  187. ) {
  188. // set new value
  189. const oldValue = this.value
  190. this.value = value
  191. if (this.user) {
  192. try {
  193. this.cb.call(this.vm, value, oldValue)
  194. } catch (e) {
  195. handleError(e, this.vm, `callback for watcher "${this.expression}"`)
  196. }
  197. } else {
  198. this.cb.call(this.vm, value, oldValue)
  199. }
  200. }
  201. }
  202. }
  203.  
  204. /**
  205. * Evaluate the value of the watcher.
  206. * This only gets called for lazy watchers.
  207. */
  208. evaluate () {
  209. this.value = this.get()
  210. this.dirty = false
  211. }
  212.  
  213. /**
  214. * Depend on all deps collected by this watcher.
  215. */
  216. depend () {
  217. let i = this.deps.length
  218. while (i--) {
  219. this.deps[i].depend()
  220. }
  221. }
  222.  
  223. /**
  224. * Remove self from all dependencies' subscriber list.
  225. */
  226. teardown () {
  227. if (this.active) {
  228. // remove self from vm's watcher list
  229. // this is a somewhat expensive operation so we skip it
  230. // if the vm is being destroyed.
  231. if (!this.vm._isBeingDestroyed) {
  232. remove(this.vm._watchers, this)
  233. }
  234. let i = this.deps.length
  235. while (i--) {
  236. this.deps[i].removeSub(this)
  237. }
  238. this.active = false
  239. }
  240. }
  241. }

总结:Watcher是一个依赖于数据的订阅者,当数据发生变化时,Dep调用notify方法,触发这些Watcher的update方法。

  • Compile
  1. class Complie {
  2. constructor(el, vm) {
  3. this.el = this.isElementNode(el) ? el : document.querySelector(el)
  4. this.vm = vm
  5.  
  6. if (this.el) {
  7. // 1.获取文档碎片对象,放入内存中,会减少页面的回流和重绘
  8. let fragment = this.nodeFragment(this.el)
  9.  
  10. // 2.编译模板
  11. this.complie(fragment)
  12.  
  13. // 3.追加子元素到根元素上
  14. this.el.appendChild(fragment)
  15. }
  16. }
  17.  
  18. isElementNode(node) {
  19. return node.nodeType === 1
  20. }
  21. nodeFragment(el) {
  22. el.firstChild
  23. // 创建一个内存碎片对象
  24. const fragment = document.createDocumentFragment()
  25. let firstChild
  26. while ((firstChild = el.firstChild)) {
  27. fragment.appendChild(firstChild)
  28. }
  29. return fragment
  30. }
  31.  
  32. // 遍历获取并区分元素节点还是文本节点,然后进行相应的处理
  33. complie(fragment) {
  34. // 1.获取到每个子节点
  35. const childNodes = fragment.childNodes
  36. childNodes.forEach(child => {
  37. if (this.isElementNode(child)) {
  38. // 是元素节点
  39. // 编译元素节点
  40. this.complieElement(child)
  41. } else {
  42. // 文本节点
  43. // 编译文本节点
  44. this.complieText(child)
  45. }
  46.  
  47. if (child.childNodes && child.childNodes.length) {
  48. this.complie(child)
  49. }
  50. })
  51. }
  52. // 编译元素
  53. complieElement(node) {
  54. const attributes = node.attributes
  55. Array.from(attributes).forEach(attr => {
  56. const { name, value } = attr
  57. if (this.isDirective(name)) {
  58. // 表明是一个指令
  59. const [, dirctive] = name.split('-')
  60. const [dirName, eventName] = dirctive.split(':') // text html model on
  61.  
  62. /*
  63. node(整个节点)
  64. value(msg)
  65. this.vm(相当于整个 MVue实例对象)
  66. eventName(v-on:click='btnClick') 中的事件名btnClick
  67. */
  68. // 更新数据 数据驱动视图
  69. compileUtil[dirName](node, value, this.vm, eventName)
  70.  
  71. // 删除有指令的标签上的属性
  72. node.removeAttribute('v-' + dirctive)
  73. } else if (this.isEventName(name)) {
  74. // @click='handleClick'
  75. let [, eventName] = name.split('@')
  76. compileUtil['on'](node, value, this.vm, eventName)
  77. }
  78. })
  79. }
  80. isEventName(eventName) {
  81. return eventName.startsWith('@')
  82. }
  83.  
  84. // 检测字符串是否以 v- 开头
  85. isDirective(attrName) {
  86. return attrName.startsWith('v-')
  87. }
  88.  
  89. // 编译文本
  90. complieText(node) {
  91. const content = node.textContent
  92. if (/\{\{(.+?)\}\}/.test(content)) {
  93. compileUtil['text'](node, content, this.vm)
  94. }
  95. }
  96. }

blockquote { border-left: 3px solid rgba(128, 128, 128, 1) }
p { line-height: 1.8 }
h3 { margin-left: 0 !important; padding-left: 0 !important }
pre { color: rgba(255, 250, 250, 1); font-family: "Courier New"; background-color: rgba(45, 45, 45, 1); border: 1px solid rgba(128, 128, 128, 1) }

深入解析vue响应式原理的更多相关文章

  1. 深度解析 Vue 响应式原理

    深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...

  2. vue响应式原理解析

    # Vue响应式原理解析 首先定义了四个核心的js文件 - 1. observer.js 观察者函数,用来设置data的get和set函数,并且把watcher存放在dep中 - 2. watcher ...

  3. Vue源码--解读vue响应式原理

    原文链接:https://geniuspeng.github.io/2018/01/05/vue-reactivity/ Vue的官方说明里有深入响应式原理这一节.在此官方也提到过: 当你把一个普通的 ...

  4. 详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  5. vue响应式原理,去掉优化,只看核心

    Vue响应式原理 作为写业务的码农,几乎不必知道原理.但是当你去找工作的时候,可是需要造原子弹的,什么都得知道一些才行.所以找工作之前可以先复习下,只要是关于vue的,必定会问响应式原理. 核心: / ...

  6. 深入Vue响应式原理

    深入Vue.js响应式原理 一.创建一个Vue应用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h =& ...

  7. 浅析Vue响应式原理(三)

    Vue响应式原理之defineReactive defineReactive 不论如何,最终响应式数据都要通过defineReactive来实现,实际要借助ES5新增的Object.definePro ...

  8. 浅谈vue响应式原理及发布订阅模式和观察者模式

    一.Vue响应式原理 首先要了解几个概念: 数据响应式:数据模型仅仅是普通的Javascript对象,而我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率. 双向绑定:数据改变,视图 ...

  9. 手摸手带你理解Vue响应式原理

    前言 响应式原理作为 Vue 的核心,使用数据劫持实现数据驱动视图.在面试中是经常考查的知识点,也是面试加分项. 本文将会循序渐进的解析响应式原理的工作流程,主要以下面结构进行: 分析主要成员,了解它 ...

随机推荐

  1. selenium IDE使用-1

    selenium 硒 Mercury汞,外国人喜欢取这化学的名字 一.selenium概述 1.selenium是开源免费的,针对web应用程序功能自动化测试的工作. 2.做功能自动化的原因:回归测试 ...

  2. js下 Day07、DOM案例

    一.折叠菜单 效果图: 功能思路分析: 功能一:数据渲染 \1. 模拟数据,一级数据为数组套对象的形式,二级数据为数组: \2. 先渲染一级数据,然后再嵌套渲染二级数据(map().join('')) ...

  3. Ecshop V2.7代码执行漏洞分析

    0x01 此漏洞形成是由于未对Referer的值进行过滤,首先导致SQL注入,其次导致任意代码执行. 0x02 payload: 554fcae493e564ee0dc75bdf2ebf94caads ...

  4. PHPCMS V9.6.0 SQL注入漏洞分析

    0x01 此SQL注入漏洞与metinfo v6.2.0版本以下SQL盲注漏洞个人认为较为相似.且较为有趣,故在此分析并附上exp. 0x02 首先复现漏洞,环境为: PHP:5.4.45 + Apa ...

  5. Windows安装VsCode 和Nodejs Vue

    一.安装VSCode 1.在官网下载并安装VSCode https://code.visualstudio.com/Download 注意:解压到非系统盘(节约系统盘空间,也方便后面使用) 文件夹最好 ...

  6. js 点击input焦点不弹出键盘 PDA扫描枪

    直接贴代码 1.利用input readonly属性 当input有readonly属性的时候,即使获取焦点,也不会吊起小键盘 扫码枪输入的间隔大概在15-60毫秒,然后手动输入的100-200毫秒之 ...

  7. C#访问Access数据库提示未安装ISAM

    解决办法 1.在前面加上Jet OLEDB:,如: Jet OLEDB:Database Password='zt' <add name="ConStrOleDb" conn ...

  8. 关于MVC中 服务器无法在发送 HTTP 标头之后修改 cookie此类问题的解决

    处理方法 使用过滤器控制权限时,若无权则跳转到无权页面,但是每次跳转都会出现 ERROR - System.Web.HttpException (0x80004005): 服务器无法在已发送 HTTP ...

  9. 3.mysql小表驱动大表的4种表连接算法

    小表驱动大表 1.概念 驱动表的概念是指多表关联查询时,第一个被处理的表,使用此表的记录去关联其他表.驱动表的确定很关键,会直接影响多表连接的关联顺序,也决定了后续关联时的查询性能. 2.原则 驱动表 ...

  10. JPA 复杂查询 - Querydsl

     添加依赖 <!--query dsl --> <dependency> <groupId>com.querydsl</groupId> <art ...