Vue 소스 코드 학습 의 양 방향 바 인 딩 을 자세히 설명 합 니 다.
10418 단어 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 소스 코드 학습 의 양 방향 바 인 딩 에 대한 상세 한 통합 입 니 다.여러분 께 도움 이 되 시 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 저 에 게 메 시 지 를 남 겨 주세요.편집장 님 께 서 바로 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Vue Render 함수로 DOM 노드 코드 인스턴스 만들기render에서createElement 함수를 사용하여 DOM 노드를 만드는 것은 직관적이지 않지만 일부 독립 구성 요소의 디자인에서 특수한 수요를 충족시킬 수 있습니다.간단한 렌더링 예는 다음과 같습니다. 또한 v...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.