Vue 소스 코드 학습 의 양 방향 바 인 딩 을 자세히 설명 합 니 다.

의 원리
일반적인 JavaScript 대상 을 Vue 인 스 턴 스 의 data 옵션 에 전달 하면 Vue 는 이 대상 의 모든 속성 을 옮 겨 다 니 며 Object.defineProperty 를 사용 하여 이 속성 을 모두 getter/setter 로 변환 합 니 다.Object.defineProperty 는 ES5 에서 shim 할 수 없 는 특성 입 니 다.이것 은 바로 Vue 가 IE8 과 더 낮은 버 전 브 라 우 저 를 지원 하지 않 는 이유 입 니 다.
위의 말 은 Vue 공식 문서 에서 캡 처 한 것 으로 Object.defineProperty 를 사용 하여 데이터 변경 에 대한 감청 을 실현 하 는 것 을 볼 수 있 습 니 다.Vue 는 주로 관찰자 모드 를 사용 하여 데이터 와 보기 의 양 방향 연결 을 실현 합 니 다.

function initData(vm) { // data      _data           
 vm._data = vm.$options.data;
 const keys = Object.keys(vm._data); 
 let i = keys.length;
 while(i--) { 
  const key = keys[i];
  proxy(vm, `_data`, key);
 }
 observe(data, true /* asRootData */) // data    
}

첫 번 째 데이터 초기 화 에서 new Vue()작업 을 수행 한 후 initData()를 실행 하여 사용자 가 들 어 오 는 data 를 초기 화 합 니 다.마지막 작업 은 data 에 응답 식 을 추가 하 는 것 입 니 다.
이루어지다
Vue 내부 에 세 개의 대상 이 존재 합 니 다:Observer,Dep,Watcher.이것 도 응답 식 을 실현 하 는 핵심 입 니 다.
Observer
Observer 대상 은 data 의 모든 속성 을 getter/setter 형식 으로 바 꿉 니 다.다음은 간략화 코드 입 니 다.자세 한 코드 는 보 세 요여기 요..

export function observe (value) {
 //         
 if (!isObject(value) || value instanceof VNode) {
  return
 }
 ...
 ob = new Observer(value)
}
export class Observer {
 constructor (value) {
  ... //          
  this.walk(value)
 }

 walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i]) //       setter/getter
  }
 }
 ...
}

//  set/get
export function defineReactive (
 obj: Object,
 key: string,
 val: any
) {
 //             watcher  , setter         
 const dep = new Dep()
 ...
 //            observer
 let childOb = observe(val)
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
   if (Dep.target) {
    dep.depend() //   dep    watcher    watcher.addDep watcher     dep.subs 
    if (childOb) { //              
     childOb.dep.depend()
     ...
    }
   }
   return value
  },
  set: function reactiveSetter (newVal) {
   ...
   childOb = observe(newVal) //          ,     observe
   dep.notify() //      watcher    
  }
 })
}

Observer 대상 을 만 들 때 data 의 모든 속성 에 define Reactive 방법 을 한 번 실 행 했 습 니 다.현재 속성 이 대상 이 라면 재 귀적 으로 깊이 있 게 옮 겨 다 닙 니 다.이 방법 은 dep 인 스 턴 스 를 만 들 었 습 니 다.모든 속성 에 해당 하 는 dep 가 있 고 모든 의존 도 를 저장 합 니 다.그리고 속성 을 위해 setter/getter 를 설정 합 니 다.getter 에서 의존 도 를 수집 하고 setter 에서 업 데 이 트 를 보 냅 니 다.여기 서 수집 의존 도 는 addSub 를 직접 사용 하지 않 는 것 은 Watcher 가 만 들 때 자동 으로 dep.subs 에 자신 을 추가 할 수 있 도록 하 는 것 입 니 다.그러면 데이터 가 방문 할 때 만 의존 수집 을 할 수 있 고 불필요 한 의존 수집 을 피 할 수 있 습 니 다.
Dep
Dep 는 게시 자 입 니 다.데이터 업데이트 가 구독 자(watcher)에 게 알 리 는 것 일 때 수집 의존 을 책임 집 니 다.(watcher)4567915)

export default class Dep {
 static target: ?Watcher; //    watcher
 constructor () {
  this.subs = []
 }
 //  watcher
 addSub (sub: Watcher) {
  this.subs.push(sub)
 }
 //  watcher
 removeSub (sub: Watcher) {
  remove(this.subs, sub)
 }
 //  watcher      dep 
 depend () {
  if (Dep.target) {
   Dep.target.addDep(this)
  }
 }
 //      
 notify () {
  ...
  for (let i = 0, l = subs.length; i < l; i++) {
   subs[i].update()
  }
 }
}

Watcher
원본 주소

//     (a.b),      
export function parsePath (path: string): any {
 if (bailRE.test(path)) {
  return
 }
 const segments = path.split('.')
 return function (obj) {
  for (let i = 0; i < segments.length; i++) {
   if (!obj) return
   obj = obj[segments[i]]  //             
  }
  return obj
 }
}
export default class Watcher {
 constructor (
  vm: Component,
  expOrFn: string | Function,
  cb: Function,
  options?: ?Object,
  isRenderWatcher?: boolean
 ) {
  this.vm = vm
  if (isRenderWatcher) {
   vm._watcher = this
  } 
  //    watcher    ,destroy    watcher    
  vm._watchers.push(this)
  // options
  if (options) {
   ...
   this.before = options.before
  }
  ...
  //          Dep     id
  this.deps = []
  this.depIds = new Set()
  //        Dep     id
  this.newDeps = []
  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)
   ...
  }
  ...
  this.value = this.get()
 }

 /** * Evaluate the getter, and re-collect dependencies. */
 get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
   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)) {
    dep.addSub(this)
   }
  }
 }

 //                       
 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
 },
 update () {
  ...
  //         ,    this.get
  this.get();
 }
 // ...
}
수집 에 의존 하 는 트리거 는 render 를 실행 하기 전에 렌 더 링 Watcher 를 만 듭 니 다.

updateComponent = () => {
 vm._update(vm._render(), hydrating) //  render  VNode   dom
}
new Watcher(vm, updateComponent, noop, {
 before () {
  if (vm._isMounted) {
   callHook(vm, 'beforeUpdate')
  }
 }
}, true /* isRenderWatcher */)

렌 더 링 Watcher 생 성 시 Dep.target 을 자신 에 게 가리 키 고 updateComponent 를 실행 합 니 다render VNode 생 성 및 실행update 는 VNode 를 실제 DOM 으로 렌 더 링 합 니 다.render 과정 에서 템 플 릿 을 컴 파일 합 니 다.이때 data 에 접근 하여 getter 를 촉발 합 니 다.이때 Dep.target 은 렌 더 링 Watcher 를 가리 키 고 있 기 때 문 입 니 다.이 어 렌 더 링 Watcher 는 자신의 addDep 를 실행 합 니 다.단정 한 후에 dep.addSub(this)를 실행 하여 자신의 push 를 속성 에 대응 하 는 dep.subs 에 실행 합 니 다.같은 속성 은 현재 Watcher 에서 데이터 가 인 용 됨 을 나타 내 는 한 번 만 추 가 됩 니 다.
당render 가 끝나 면 popTarget()을 실행 하고 현재 Dep.target 을 이전 라운드 의 손가락 으로 되 돌려 줍 니 다.마지막 으로 null 로 돌 아 왔 습 니 다.즉,모든 수집 이 완료 되 었 습 니 다.이후 cleanupDeps()를 실행 하면 이전 라운드 에 필요 하지 않 은 의존 도 를 제거 합 니 다.데이터 변 화 는 setter 를 터치 하여 Watcher 에 대응 하 는 update 속성 을 실행 하고 get 방법 을 실행 하면 Dep.target 을 현재 실행 중인 Watcher 에 게 이 Watcher 의 업 데 이 트 를 다시 실행 합 니 다.
여기 서 deps,new Deps 두 개의 의존 표,즉 지난 라운드 의 의존 과 최신 의존 을 볼 수 있 습 니 다.이 두 개의 의존 표 는 주로 의존 제거 에 사 용 됩 니 다.하지만 addDep 에서 if(!this.newDepIds.has(id)는 수 집 된 의존 도 를 유일 하 게 판단 하고 중복 되 는 데 이 터 를 수집 하지 않 습 니 다.왜 클 린 업 데 프 스에 서 다시 판단 을 해 야 합 니까?

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
cleanupdps 에서 이전 라운드 의 의존 도 를 제거 합 니 다.새로운 라운드 에서 다시 수집 하지 않 은 것 입 니 다.즉,데이터 가 새로 고침 된 후에 일부 데 이 터 는 더 이상 렌 더 링 되 지 않 습 니 다.예 를 들 어:

<body>
 <div id="app">
  <div v-if='flag'> </div>   
  <div v-else> </div> 
  <button @click="msg1 += '1'">change</button>   
  <button @click="flag = !flag">toggle</button>  
 </div> 
  <script type="text/javascript">
  var vm = new Vue({
   el: '#app',
   data: {
    flag: true,
    msg1: 'msg1',
    msg2: 'msg2'
   }
  })
  </script> 
</body>

change 를 클릭 할 때마다 msg 1 은 1 을 연결 합 니 다.이 때 다시 렌 더 링 을 촉발 합 니 다.우리 가 toggle 을 클릭 할 때 flag 가 바 뀌 어서 msg 1 은 더 이상 렌 더 링 되 지 않 습 니 다.그러나 change 를 클릭 할 때 msg 1 은 변화 가 생 겼 지만 다시 렌 더 링 을 촉발 하지 않 았 습 니 다.이것 이 바로 cleanupDeps 의 역할 입 니 다.cleanupdps 라 는 단 계 를 제거 하면 같은 의존 도 를 추가 하 는 것 을 방지 할 수 있 지만 데 이 터 는 업데이트 할 때마다 다시 렌 더 링 을 하고 의존 도 를 다시 수집 합 니 다.이 예 에서 toggle 이후 재 수집 의존 에는 msg 1 이 없습니다.표시 되 지 않 아 도 되 지만 setter 가 설정 되 어 있 기 때문에 msg 1 을 바 꾸 면 setter 가 실 행 됩 니 다.cleanupDeps 를 실행 하지 않 으 면 msg 1 의 의존 표 에 의존 하고 다시 렌 더 링 을 할 수 있 습 니 다.이것 은 불합리 합 니 다.그래서 수집 이 끝 날 때마다 불필요 한 의존 을 없 애 야 합 니 다.
총결산
수집 에 의존 하 는 것 은 모든 데 이 터 를 수집 하여 어떤 Watcher(과장 Watcher,coptute dWatcher 등)에 의 해 인용 되 는 지 하 는 것 이다.이 데이터 가 업 데 이 트 될 때 그 에 의존 하 는 Watcher 에 게 업 데 이 트 를 알 리 는 것 이다.
위 에서 말씀 드 린 것 은 편집장 님 께 서 소개 해 주신 Vue 소스 코드 학습 의 양 방향 바 인 딩 에 대한 상세 한 통합 입 니 다.여러분 께 도움 이 되 시 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 저 에 게 메 시 지 를 남 겨 주세요.편집장 님 께 서 바로 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!

좋은 웹페이지 즐겨찾기