vue 간단 한 MVVM 프레임 워 크 실현

14023 단어 vueMVVM
자신 도 모 르 게 전단 지 를 접 한 지 반년 이 지 났 습 니 다.지식 에 대한 학습 이 쓸 줄 아 는 차원 에 만 머 물 러 서 는 안 된다 는 것 을 알 게 되 었 습 니 다.이것 은 제 가 jQuery 를 배 운 지 얼마 되 지 않 아 이런 경험 을 하 게 되 었 습 니 다.
jQuery 는 JS 의 코드 라 이브 러 리 이지 만 JS 의 기본 동작 을 하루 이틀 만 배우 면 jQuery 의 기본 문법 을 빨리 파악 하고 능숙 하 게 사용 할 수 있 습 니 다.그러나 jQUery 라 이브 러 리 뒤의 실현 원 리 를 모 르 면 jQuery 를 사용 하지 않 으 면 jQuery 를 깨끗이 잊 어 버 릴 것 이 라 고 믿 습 니 다.이것 은 그 이 유 를 모 르 는 결과 일 수도 있 습 니 다.
최근 vue 를 배 울 때 다시 한 번 이런 곤 혹 스 러 움 을 겪 었 습 니 다.vue 의 기본 적 인 사용 을 능숙 하 게 파악 할 수 있 지만 MV*모델,데이터 납치,양 방향 데이터 연결,데이터 대리 에 대해 서도 두 마디 할 수 있 습 니 다.근 데 조금 만 더 깊이 들 어가 면 힘 들 어.그래서 요 며칠 동안 많은 기술 문장 을 연구 하기 로 결심 했다.어 쩔 수 없 이 구 덩이 를 버 리 고 기술 블 로 그 를 볼 수 밖 에 없다)vue 에 대해 서도 표범 을 엿 보 는 인식 을 가지 게 된 셈 이다.마지막 으로 자신 이 배 운 지식 을 실천 해 보 세 요.데이터 에이전트,데이터 납치,템 플 릿 분석,양 방향 연결 을 바탕 으로 작은 vue 프레임 워 크 를 실현 합 니 다.
따뜻 한 알림:글 은 각 모듈 의 의존 관 계 를 실현 하여 분석 하지만 읽 을 때 vue 의 집행 순서에 따라 분석 하면 초보 자 에 게 더욱 우호 적 입 니 다.추천 하 는 읽 기 순 서 는 VMVM,데이터 에이전트 실현,Observe 실현,Complie 실현,Watcher 실현 이다.
원본 코드:https://github.com/yuliangbin/MVVM
기능 설명 은 다음 과 같다.

데이터 에이전트
아래 템 플 릿 을 예 로 들 면 바 꿀 루트 요소 인'\#mvvm-app'에는 텍스트 노드 가 하나 밖 에 없습니다.\#text,\#text 의 내용 은{{{name}}입 니 다.다음 템 플 릿 으로 VUE 프레임 워 크 의 대체 적 인 실현 절 차 를 자세히 알 아 보 겠 습 니 다.

<body>
 <div id="mvvm-app">
  {{name}}
 </div>
 <script src="./js/observer.js"></script>
 <script src="./js/watcher.js"></script>
 <script src="./js/compile.js"></script>
 <script src="./js/mvvm.js"></script>
 <script>
  let vm = new MVVM({
   el: "#mvvm-app",
   data: {
    name: "hello world"
   },  
  })

 </script>
</body>
데이터 에이전트
1.데이터 에이전트 가 무엇 입 니까?
vue 에서 데 이 터 를 data 대상 에 씁 니 다.그러나 데이터 에 접근 할 때 vm.data.name 을 통 해 접근 할 수도 있 고 vm.name 을 통 해 접근 할 수도 있 습 니 다.이것 이 바로 데이터 에이전트 입 니 다.한 대상 에서 다른 대상 의 속성 을 동적 으로 방문 하고 설정 할 수 있 습 니 다.
2.실현 원리
정적 바 인 딩(예 를 들 어 vm.name=vm.data.name)은 결 과 를 변수 에 한꺼번에 부여 할 수 있 고 Object.defineProperty()방법 으로 바 인 딩 하면 set 와 get 함수 로 할당 하 는 중간 과정 을 통 해 데이터 의 동적 바 인 딩 을 실현 할 수 있다 는 것 을 알 고 있 습 니 다.구체 적 인 실현 은 다음 과 같다.

let obj = {};
let obj1 = {
 name: 'xiaoyu',
 age: 18,
}
//  origin    target  
function proxyData(origin,target){
 Object.keys(target).forEach(function(key){
  Object.defineProperty(origin,key,{//  origin   key  
   enumerable: false,
   configurable: true,
   get: function getter(){
    return target[key];//origin[key] = target[key];
   },
   set: function setter(newValue){
    target[key] = newValue;
   }
  })
 })
}
vue 의 데이터 에이전트 도 이런 방식 으로 이 루어 진다.

function MVVM(options) {
 this.$options = options || {};
 var data = this._data = this.$options.data;
 var _this = this;//    vm

 //     
 //    vm._data.xxx -> vm.xxx 
 Object.keys(data).forEach(function(key) {
  _this._proxyData(key);
 });
 observe(data, this);
 this.$compile = new Compile(options.el || document.body, this);

}

MVVM.prototype = {
_proxyData: function(key) {
 var _this = this;
 if (typeof key == 'object' && !(key instanceof Array)){//            ,       
  this._proxyData(key);
 }
 Object.defineProperty(_this, key, {
  configurable: false,
  enumerable: true,
  get: function proxyGetter() {
   return _this._data[key];
  },
  set: function proxySetter(newVal) {
   _this._data[key] = newVal;
  }
 });
},
};
Observe 실현
1.양 방향 데이터 바 인 딩
데이터 변동--->보기 업데이트
보기 업데이트--->데이터 변동
데이터 가 변동 할 때 보기 업 데 이 트 를 실현 하려 면 먼저 데이터 변동 을 어떻게 알 아야 하 는 지,Object.defineProperty()함수 로 data 대상 의 데 이 터 를 감청 하고 데이터 가 변동 하면 set()방법 을 촉발 할 수 있 습 니 다.따라서 우 리 는 데이터 감청 기 Observe 를 실현 하여 데이터 대상 의 모든 속성 을 감청 해 야 한다.특정한 속성 데이터 가 변화 할 때 최신 데이터 알림 을 받 아 이 속성의 구독 기 를 연결 시 키 고 구독 기 는 해당 하 는 데이터 업데이트 메 시 지 를 실행 하여 보기 의 갱신 을 실현 해 야 한다.
this.name='hello vue'를 설정 하면 set 함 수 를 실행 하고 구독 자 에 게 해당 하 는 리 셋 함 수 를 실행 하 라 고 알 리 며 데이터 변동 을 실현 하고 보기 업데이트 에 대응 합 니 다.

function observe(data){
 if (typeof data != 'object') {
  return ;
 }
 return new Observe(data);
}

function Observe(data){
 this.data = data;
 this.walk(data);
}

Observe.prototype = {
 walk: function(data){
  let _this = this;
  for (key in data) {
   if (data.hasOwnProperty(key)){
    let value = data[key];
    if (typeof value == 'object'){
     observe(value);
    }
    _this.defineReactive(data,key,data[key]);
   }
  }
 },
 defineReactive: function(data,key,value){
  Object.defineProperty(data,key,{
   enumerable: true,//   
   configurable: false,//   define
   get: function(){
    console.log('    ' + key);return value;
   },
   set: function(newValue){
    console.log('    ' + key);
    if (newValue == value) return;
    value = newValue;
    observe(newValue);//       
   }
  })
 }
}
2.구독 기 구현
구독 자 에 게 알 리 려 면 먼저 구독 자(모든 구독 자 를 통일 적 으로 관리)가 있어 야 합 니 다.관리 하기 편리 하도록 모든 data 대상 의 속성 에 구독 기(new Dep)를 추가 합 니 다.
구독 기 에 저 장 된 것 은 구독 자 Watcher(나중에 설명 할 것)입 니 다.구독 자가 여러 개 있 을 수 있 기 때문에 저 희 는 하나의 배열 을 만들어 서 유지 해 야 합 니 다.데이터 가 바 뀌 면 구독 기의 notify()방법 을 터치 하고 구독 자 는 자신의 update 방법 으로 보기 업 데 이 트 를 실현 합 니 다.

function Dep(){
 this.subs = [];
}
Dep.prototype = {
 addSub: function(sub){this.subs.push(sub);
 },
 notify: function(){
  this.subs.forEach(function(sub) {
   sub.update();
  })
 }
}
속성 에 응답 하 는 set()함수 가 호출 될 때마다 구독 기 를 터치 하기 때문에 코드 가 완전 하 게 보 충 됩 니 다.

Observe.prototype = {
 //         
 defineReactive: function(data,key,value){
  let dep = new Dep();//       ,     key   get/set   ,               dep  
  Object.defineProperty(data,key,{
   enumerable: true,//   
   configurable: false,//   define
   get: function(){
    console.log('    ' + key);
    return value;
   },
   set: function(newValue){
    console.log('    ' + key);
    if (newValue == value) return;
    value = newValue;
    observe(newValue);//       
    dep.notify();//        
   }
  })
 }
}
Complie 구현
copile 은 템 플 릿 명령 을 분석 하고 템 플 릿 의 data 속성 을 data 속성 에 대응 하 는 값(예 를 들 어{{name}}을 data.name 값 으로 바 꾸 는 것)으로 바 꾼 다음 렌 더 링 페이지 보 기 를 초기 화하 고 데이터 속성 마다 감청 데 이 터 를 추가 하 는 구독 자(new Watcher)를 추가 합 니 다.데이터 가 바 뀌 면 알림 을 받 고 보 기 를 업데이트 합 니 다.
교체 해 야 할 루트 요소 엘 의 HTML 탭 을 옮 겨 다 니 면 여러 번 의 DOM 노드 작업 과 관련 될 수 있 기 때문에 페이지 의 정렬 이나 재 그리 기 를 피 할 수 없습니다.성능 과 효율 을 향상 시 키 기 위해 우 리 는 루트 요소 엘 의 모든 노드 를 문서 조각 fragment 로 변환 하여 해석 컴 파일 작업 을 하고 해석 이 완 료 된 다음 에 fragment 를 원래 의 실제 dom 노드 에 추가 합 니 다.
주:문서 조각 자체 도 하나의 노드 이지 만 이 노드 append 를 페이지 에 넣 을 때 이 노드 라벨 은 루트 노드 로 html 문서 에 표시 되 지 않 고 그 안의 하위 노드 는 완전히 표시 할 수 있 습 니 다.
Compile 은 템 플 릿 을 분석 하고 템 플 릿 에 있 는 하위 요 소 를 문서 조각 노드 fragment 에 추가 합 니 다.

function Compile(el,vm){
 this.$vm = vm;//vm     
 this.$el = document.querySelector(el);//          
 if (this.$el){
  this.$fragment = this.nodeToFragment(this.$el);
  this.init();
  this.$el.appendChild(this.$fragment);
 } 
}
Compile.prototype = {
 nodeToFragment: function(el){
  let fragment = document.createDocumentFragment();
  let child;
  while (child = el.firstChild){
   fragment.appendChild(child);//append        
  }
  return fragment;
  
 },
};
copileElement 방법 은 모든 노드 와 하위 노드 를 옮 겨 다 니 며 스 캔 분석 컴 파일 을 하고 해당 하 는 명령 렌 더 링 함 수 를 호출 하여 데이터 렌 더 링 을 하 며 해당 하 는 명령 업데이트 함 수 를 호출 하여 바 인 딩 을 합 니 다.코드 와 주석 설명 을 자세히 보 세 요.
우리 템 플 릿 은 하나의 텍스트 노드 만 포함 되 어 있 기 때문에 copile Element 방법 이 실 행 된 후 들 어 갑 니 다.this.compileText(node,reg.exec(node.textContent)[1]);//#text,'name'

Compile.prototype = {
 nodeToFragment: function(el){
  let fragment = document.createDocumentFragment();
  let child;
  while (child = el.firstChild){
   fragment.appendChild(child);//append        
  }
  return fragment;
  
 },
 
 init: function(){
  this.compileElement(this.$fragment);
 },
 
 compileElement: function(node){
  let childNodes = node.childNodes;
  const _this = this;
  let reg = /\{\{(.*)\}\}/g;
  [].slice.call(childNodes).forEach(function(node){
   
   if (_this.isElementNode(node)){//       ,       
    _this.compile(node);
   } else if (_this.isTextNode(node) && reg.test(node.textContent)){
    //       ,    data  ( {{name}}),       
    _this.compileText(node,reg.exec(node.textContent)[1]);//#text,'name'
   }
   
   if (node.childNodes && node.childNodes.length){
    //          ,         
    _this.compileElement(node);
    
   }
  })
 },
 compileText: function(node,exp){//#text,'name'
   compileUtil.text(node,this.$vm,exp);//#text,vm,'name'
 },};
CompileText()함 수 는 렌 더 링 페이지 보 기 를 초기 화 합 니 다(data.name 의 값 을\#text.textContent=data.name 을 통 해 페이지 에 표시 합 니 다).그리고 각 DOM 노드 에 감청 데 이 터 를 추가 하 는 구독 자(여 기 는\#text 노드 에 Wather 를 추가 합 니 다).

let updater = {
 textUpdater: function(node,value){ 
  node.textContent = typeof value == 'undefined' ? '' : value;
 },
}
 
let compileUtil = {
 text: function(node,vm,exp){//#text,vm,'name'
  this.bind(node,vm,exp,'text');
 },
 
 bind: function(node,vm,exp,dir){//#text,vm,'name','text'
  let updaterFn = updater[dir + 'Updater'];
  updaterFn && updaterFn(node,this._getVMVal(vm,exp));
  new Watcher(vm,exp,function(value){
   updaterFn && updaterFn(node,value)
  });
  console.log('    ');
 }
};
현재 우 리 는 텍스트 노드 해석 을 실현 할 수 있 는 Compile()함 수 를 완 성 했 고 그 다음 에 우 리 는 Watcher()함 수 를 실현 했다.
Watcher 구현
앞에서 말 했 듯 이 Observe()함 수 는 data 대상 의 속성 납 치 를 실현 하고 속성 값 이 바 뀔 때 구독 기의 notify()를 터치 하여 구독 자 Watcher 에 게 알 리 면 구독 자 는 자신의 update 방법 으로 보기 업 데 이 트 를 실현 합 니 다.
Compile()함 수 는 템 플 릿 을 분석 하고 페이지 를 초기 화 하 며,데이터 속성 마다 데 이 터 를 감청 하 는 구독 자(new Watcher)를 추가 합 니 다.
왓 처 구독 자 는 오 브 서버 와 컴 파일 간 통신 의 가교 역할 을 하기 때문에 왓 처 의 역할 이 무엇 인지 대충 알 수 있다.
주로 하 는 일 은:
자신의 예화 시 구독 기(dep)에 자신 을 추가 합 니 다.
자신 에 게 update()방법 이 있어 야 합 니 다.
속성 변동 dep.notice()알림 이 있 을 때 자신의 update()방법 을 호출 하고 Compile 에 연 결 된 리 셋 을 실행 할 수 있 습 니 다.
먼저 모든 코드 를 제시 한 다음 에 구체 적 인 기능 을 분석한다.

//Watcher
function Watcher(vm, exp, cb) {
 this.vm = vm;
 this.cb = cb;
 this.exp = exp;
 this.value = this.get();//             
};

Watcher.prototype = {
 update: function(){
  this.run();
 },
 run: function(){
  const value = this.vm[this.exp];
  //console.log('me:'+value);
  if (value != this.value){
   this.value = value;
   this.cb.call(this.vm,value);
  }
 },
 get: function() { 
  Dep.target = this; //     
  var value = this.vm[this.exp] //     ,  defineProperty  get     
  Dep.target = null; //     
  return value;
 }
}

//    Observe Dep,    
Observe.prototype = {
 defineReactive: function(data,key,value){
  let dep = new Dep();
  Object.defineProperty(data,key,{
   enumerable: true,//   
   configurable: false,//   define
   get: function(){
    console.log('    ' + key);
    //       Watcher    ,       
    if (Dep.target){
     //console.log('   Dep.target');
     dep.addSub(Dep.target);
    }
    return value;
   },
  })
 }
}

Dep.prototype = {
 addSub: function(sub){this.subs.push(sub);
 },
}
Observe()함수 가 실 행 될 때 모든 속성 에 구독 기 dep 를 추가 한 것 을 알 고 있 습 니 다.이 dep 는 속성의 get/set 함수 에 닫 혀 있 습 니 다.따라서,우 리 는 Watcher 를 예화 할 때 this.get()함 수 를 호출 하여 data.name 속성 에 접근 할 수 있 습 니 다.이것 은 defineProperty()함수 내의 get 함 수 를 촉발 합 니 다.get 방법 이 실 행 될 때 속성의 구독 기 dep 에 현재 watcher 인 스 턴 스 를 추가 하여 속성 값 이 변화 할 때 watcher 인 스 턴 스 는 업데이트 알림 을 받 을 수 있 습 니 다.
그러면 Watcher()함수 중의 get()함수 내 Dep.taeger=this 는 또 어떤 특별한 의미 가 있 습 니까?Google 이 원 하 는 것 은 Watcher 를 예화 할 때 해당 Watcher 인 스 턴 스 를 dep 구독 기 에 한 번 추가 하면 됩 니 다.앞으로 data.name 속성 에 접근 할 때마다 dep 구독 기 를 한 번 추가 하 는 것 을 원 하지 않 습 니 다.그래서 저 희 는 this.get()함 수 를 예화 할 때 Dep.target=this 로 현재 Watcher 인 스 턴 스 를 표시 합 니 다.dep 구독 기 에 추가 한 후에 Dep.target=null 을 설정 합 니 다.
VMVM 구현
MVVM 은 데이터 바 인 딩 의 입구 로 서 Observer,Compile,Watcher 세 가 지 를 통합 시 키 고 Observer 를 통 해 자신의 model 데이터 변 화 를 감청 하 며 Compile 을 통 해 컴 파일 템 플 릿 명령 을 분석 하고 최종 적 으로 Watcher 를 이용 하여 Observer 와 Compile 간 의 통신 다 리 를 구축 하여 데이터 변화->보기 업데이트 에 도달 합 니 다.보기 인 터 랙 션 변화(input)->데이터 model 이 변경 한 양 방향 바 인 딩 효과.

function MVVM(options) {
 this.$options = options || {};
 var data = this._data = this.$options.data;
 var _this = this;
 //     
 //    vm._data.xxx -> vm.xxx 
 Object.keys(data).forEach(function(key) {
  _this._proxyData(key);
 });
 observe(data, this);
 this.$compile = new Compile(options.el || document.body, this);
}

좋은 웹페이지 즐겨찾기