Vue 감청 데이터 개체 변화 소스 코드

감청 데이터 대상 의 변 화 는 감시 대상 이 필요 한 시 계 를 만들어 정 해진 시간 에 그 값 을 스 캔 하고 변화 가 있 으 면 해당 하 는 조작 을 하 는 것 이 가장 생각 나 기 쉽다.그러나 이런 실현 방식 은 성능 이 문제 이 고 감시 해 야 할 데이터 의 양 이 많 으 면 모든 대상 을 스 캔 할 때마다 시간 이 오래 걸린다.물론 일부 프레임 워 크 는 이런 방식 을 사용 하지만 그들 은 매우 교묘 한 알고리즘 으로 성능 을 향상 시 키 는데 이것 은 우리 의 토론 범위 에 있 지 않다.
Vue 에서 데이터 대상 의 감 시 는 ES5 의 새로운 특성(ES7 이 곧 나 오 는데 ES5 의 것 은 정말 새 롭 지 않다)Object.defineProperty()의 set,get 을 설정 하여 이 루어 집 니 다.
목표.
공식 문서 의 첫 번 째 예 와 비슷 하지만 간략화 도 있 습 니 다.이 편 은 데이터 대상 의 감청 만 소개 하고 텍스트 분석 과 관련 되 지 않 기 때문에 텍스트 분석 과 관련 된 것 은 직접 버 렸 습 니 다.

<div id="app"></div>
var app = new Vue({
 el: 'app',
 data: {
 message: 'Hello Vue!'
 }
});
브 라 우 저 디 스 플레이:
Hello Vue!
콘 솔 에 다음 과 같이 입력 하 십시오:
app.message = 'Changed!'
이러한 명령 은 브 라 우 저 표시 내용 에 따라 수 정 됩 니 다.
Object.defineProperty
MDN 의 정의 참조:
Object.defineProperty()방법 은 한 대상 에 새 속성 을 직접 정의 하거나 이미 존재 하 는 속성 을 수정 하여 이 대상 을 되 돌려 줍 니 다.
이 상생 과 함께 Object.getOWn Property Descriptor()가 있 습 니 다.
Object.getOwnProperty Descriptor()지정 한 대상 의 이전 속성 에 대응 하 는 속성 설명 자 를 되 돌려 줍 니 다.(자체 속성 이란 이 대상 에 게 직접 부여 하 는 속성 으로 원형 체인 에서 찾 을 필요 가 없 는 속성 을 말한다)
다음 의 예 는 비교적 간단 하고 직관 적 인 방식 으로 setter,getter 를 설정 합 니 다.

var dep = [];

function defineReactive(obj, key, val) {
 //       property,       property
 var property = Object.getOwnPropertyDescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  dep.push(value);
  return value;
 },
 set: function(newVal) {
  var value = getter ? getter.call(obj) : val;
  // set       ,    
  if(newVal === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newVal);
  } else {
  val = newVal;
  }
  console.log(dep);
 }
 });
}

var a = {};
defineReactive(a, 'a', 12);
//    getter,12     dep,   dep    [12]
a.a;
//    setter,   dep ([12])
a.a = 24;
//    getter,24     dep,   dep    [12, 24]
a.a;
Observer
간단하게 Object.defineProperty 라 고 하면 Observer 라 고 할 수 있 습 니 다.관찰자대상 속성 값 의 변 화 를 관찰 하 다.따라서 observer 란 대상 의 모든 속성 에 getter,setter 를 추가 하 는 것 입 니 다.대상 의 속성 에 속성 이 있다 면{a:{a:{a'a}}은 재 귀 를 통 해 그 속성 에 대한 속성 에 getter,setter 를 추가 합 니 다.

function Observer(value) {
 this.value = value;
 this.walk(value);
}
Observer.prototype.walk = function(obj) {
 var keys = Object.keys(obj);
 for(var i = 0; i < keys.length; i++) {
 //         getter、setter
 defineReactive(obj, keys[i], obj[keys[i]]);
 }
};

var dep = [];

function defineReactive(obj, key, val) {
 //       property,       property
 var property = Object.getOwnPropertyDescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 //                 getter、setter
 var childOb = observe(val);
 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  dep.push(value);
  return value;
 },
 set: function(newVal) {
  var value = getter ? getter.call(obj) : val;
  // set       ,    
  if(newVal === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newVal);
  } else {
  val = newVal;
  }
  //               getter、setter
  childOb = observe(newVal);
  console.log(dep);
 }
 });
}

function observe(value) {
 if(!value || typeof value !== 'object') {
 return;
 }
 return new Observer(value);
}

Watcher
Observer 는 데이터 대상 의 getter,setter 를 설정 하여 데이터 변 화 를 감청 하 는 목적 을 달성 합 니 다.데 이 터 를 가 져 오고 설정 되 고 수정 되 며 모두 들 을 수 있 으 며 해당 하 는 동작 을 할 수 있 습 니 다.
지금 또 하나의 문 제 는 누가 당신 에 게 감청 하 라 고 했 습 니까?
이 명령 을 내 린 것 은 Watcher 입 니 다.Watcher 가 데 이 터 를 가 져 와 야 해당 하 는 조작 을 할 수 있 습 니 다.마찬가지 로 데 이 터 를 수정 할 때 도 왓 처 관련 조작 만 수행한다.
그럼 Observer,Watcher 두 가 지 를 어떻게 말 합 니까?전역 변수!이 전역 변 수 는 Watcher 만 수정 할 수 있 습 니 다.Observer 는 읽 기 판단 일 뿐 이 전역 변수의 값 에 따라 Watcher 가 데 이 터 를 읽 을 지 여 부 를 판단 합 니 다.이 전역 변 수 는 dep 에 추가 할 수 있 습 니 다.
dep.target = null;
상기 에서 말 한 바 와 같이 간단하게 정리 하면 코드 는 다음 과 같다.

function Watcher(data, exp, cb) {
 this.data = data;
 this.exp = exp;
 this.cb = cb;
 this.value = this.get();
}
Watcher.prototype.get = function() {
 //   dep.target   ,   Observer    Watcher     getter
 dep.target = this;
 //    getter,      
 var value = this.data[this.exp];
 // dep.target   
 dep.target = null;
 return value;
};
Watcher.prototype.update = function() {
 this.cb();
};
function Observer(value) {
 this.value = value;
 this.walk(value);
}
Observer.prototype.walk = function(obj) {
 var keys = Object.keys(obj);
 for(var i = 0; i < keys.length; i++) {
 //         getter、setter
 defineReactive(obj, keys[i], obj[keys[i]]);
 }
};

var dep = [];
dep.target = null;

function defineReactive(obj, key, val) {
 //       property,       property
 var property = Object.getOwnPropertyDescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 //                 getter、setter
 var childOb = observe(val);
 Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  //     Watcher    ,   Watcher      dep
  if(dep.target) {
  dep.push(dep.target);
  }
  return value;
 },
 set: function(newVal) {
  var value = getter ? getter.call(obj) : val;
  // set       ,    
  if(newVal === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newVal);
  } else {
  val = newVal;
  }
  //               getter、setter
  childOb = observe(newVal);
  //      dep      update   
  for(var i = 0; i < dep.length; i++) {
  dep[i].update(); 
  }
 }
 });
}

function observe(value) {
 if(!value || typeof value !== 'object') {
 return;
 }
 return new Observer(value);
}


var data = {a: 1};
new Observer(data);
new Watcher(data, 'a', function(){console.log('it works')});
data.a =12;
data.a =14;
위 에서 기본적으로 데이터 감청 을 실 현 했 습 니 다.bug 가 적지 않 을 것 입 니 다.다만 거 친 demo 일 뿐 대략적인 절 차 를 보 여 주 려 고 했 을 뿐 매우 세밀 하 게 잠 그 지 않 았 습 니 다.
Dep
위의 몇 가지 예 를 들 어 dep 는 전체적인 배열 입 니 다.new 하나의 Watcher,dep 에 Watcher 인 스 턴 스 가 하나 더 있어 야 합 니 다.이 럴 때 어느 data 가 업데이트 되 든 모든 Watcher 인 스 턴 스 의 update 가 실 행 됩 니 다.이것 은 받 아들 일 수 없습니다.
Dep 추상 화 되 어 하나의 구조 함 수 를 만 들 고 전체 국면 에 두 지 않 으 면 해결 할 수 있 습 니 다.

function Dep() {
 this.subs = [];
}
Dep.prototype.addSub = function(sub) {
 this.subs.push(sub);
};
Dep.prototype.notify = function() {
 var subs = this.subs.slice();
 for(var i = 0; i < subs.length; i++) {
 subs[i].update();
 }
}
Dep 를 이용 하여 위의 코드 를 고 쳐 쓰 면 됩 니 다.
Vue 인 스 턴 스 에이전트 data 개체
공식 문서 에는 다음 과 같은 말 이 있다.
모든 Vue 인 스 턴 스 는 data 대상 의 모든 속성 을 대리 합 니 다.

var data = { a: 1 };
var vm = new Vue({data: data});

vm.a === data.a // -> true

//              
vm.a = 2
data.a // -> 2

// ...     
data.a = 3
vm.a // -> 3

이런 대 리 는 보기에 매우 번 거 로 워 보이 지만 사실은 Object.defineProperty 를 통 해 이 루어 질 수 있다.

function Vue(options) {
 var data = this.data = options.data;

 var keys = Object.keys(data);
 var i = keys.length;
 while(i--) {
 proxy(this, keys[i];
 }
}
function proxy(vm, key) {
 Object.defineProperty(vm, key, {
 configurable: true,
 enumerable: true,
 //      vm.data[key]   
 get: function() {
  return vm.data[key];
 },
 //            vm.data[key]   
 set: function(val) {
  vm.data[key] = val;
 }
 };
}

Vue 를 만들어 서 최초의 목 표를 실현 하 다.

var Vue = (function() {
 var Watcher = function Watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.value = this.get();
 };
 Watcher.prototype.get = function get() {
  Dep.target = this;
  var value = this.vm._data[this.exp];
  Dep.target = null;
  return value;
 };
 Watcher.prototype.addDep = function addDep(dep) {
  dep.addSub(this);
 };
 Watcher.prototype.update = function update() {
  this.run();
 };
 Watcher.prototype.run = function run() {
  this.cb.call(this.vm);
 }

 var Dep = function Dep() {
  this.subs = [];
 };
 Dep.prototype.addSub = function addSub(sub) {
  this.subs.push(sub);
 };
 Dep.prototype.depend = function depend() {
  if(Dep.target) {
   Dep.target.addDep(this);
  }
 };
 Dep.prototype.notify = function notify() {
  var subs = this.subs.slice();
  for(var i = 0; i < subs.length; i++) {
   subs[i].update();
  }
 };

 Dep.target = null;

 var Observer = function Observer(value) {
  this.value = value;
  this.dep = new Dep();

  this.walk(value);
 };
 Observer.prototype.walk = function walk(obj) {
  var keys = Object.keys(obj);

  for(var i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i], obj[keys[i]]);
  }
 };

 function defineReactive(obj, key, val) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if(property && property.configurable === false) {
   return;
  }

  var getter = property && property.get;
  var setter = property && property.set;

  var childOb = observe(val);
  Object.defineProperty(obj, key, {
   enumerable: true,
   configurable: true,
   get: function reactiveGetter() {
    var value = getter ? getter.call(obj) : val;

    if(Dep.target) {
     dep.depend();
     if(childOb) {
      childOb.dep.depend();
     }
    }
    return value;
   },
   set: function reactiveSetter(newVal) {
    var value = getter ? getter.call(obj) : val;
    if(newVal === value) {
     return;
    }
    if(setter) {
     setter.call(obj, newVal);
    } else {
     val = newVal;
    }
    childOb = observe(newVal);
    dep.notify();
   }
  });
 }
 function observe(value) {
  if(!value || typeof value !== 'object') {
   return;
  }
  return new Observer(value);
 }

 function Vue(options) {
  var vm = this;
  this._el = options.el;
  var data = this._data = options.data;

  var keys = Object.keys(data);
  var i = keys.length;
  while(i--) {
   proxy(this, keys[i]);
  }
  observe(data);

  var elem = document.getElementById(this._el);
  elem.innerHTML = vm.message;

  new Watcher(this, 'message', function() {
   elem.innerHTML = vm.message;
  });

 }
 function proxy(vm, key) {
  Object.defineProperty(vm, key, {
   configurable: true,
   enumerable: true,
   get: function proxyGetter() {
    return vm._data[key];
   },
   set: function proxySetter(val) {
    vm._data[key] = val;
   }
  });
 }
 return Vue;
})();


<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Document</title>
 <script type="text/javascript" src="vue.js"></script>
</head>
<body>
 <div id="app"></div>
 <script type="text/javascript">
  var app = new Vue({
   el: 'app',
   data: {
    message: 'aaaaaaaaaaaaa'
   }
  });
 </script>
</body>
</html>


참고 자료:
vue 소스 코드 분석 은 observer 와 watcher 를 어떻게 실현 합 니까?
vue 초기 소스 학습 시리즈 중 하나:대상 의 변 화 를 어떻게 감청 합 니까?
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기