학습 노트 - 데이터 양방향 귀속

29339 단어 ***
학습 노트 - 데이터 양방향 귀속
이것은 제가 vue를 배운 노트입니다. 오류가 있으면 지적해 주십시오.
1 MVVM
양방향 데이터 귀속은 대상 속성 변화와 보기의 변화를 서로 귀속시키는 것을 말한다.다시 말하면name 속성을 가진user 대상이 있으면 요소의 내용과 연결되어 user에게 주어집니다.name에 새 값을 부여하면 페이지 요소 노드도 해당하는 데이터를 표시합니다.마찬가지로, 만약 페이지 요소 (보통 input) 의 데이터가 바뀌면, 새로운 값을 입력하면user 대상의name 속성에 변화가 발생할 수 있습니다.
MVVM은 마이크로소프트가 최초로 제기한 것으로 데스크톱 응용 프로그램의 MVC 사상을 참고했다. 앞쪽 페이지에서 모델을순수한JavaScript 대상으로 표시하고View가 디스플레이를 책임지며 둘을 최대한 분리했다.Model과 View를 연결하는 것이 ViewModel입니다.ViewModel은 모드의 데이터를 View에 동기화하고 View의 수정을 모드에 동기화합니다.
한 마디로 하면 데이터와 표현은 분리된다. 어떤 데이터가 바뀌면 페이지에서 이 데이터를 사용하는 모든 요소의 내용이 바뀐다.다음은 가장 간단한 데이터 귀속의 예이다. Vue2.0 원본 읽기 노트 - 양방향 귀속 실현 원리, 이 예는 매우 간단하고 거칠다. 세 가지 일을 했다.
  • obj 대상을 생성하여 데이터 저장에 사용
  • keyup 이벤트를 감청하고 이벤트가 터치될 때 선택한 input 탭의 값을obj 대상의hello 속성에 부여합니다.
  • obj 대상의 Hello 속성을 바꾸는 set 방법은 Hell이 부여되었을 때 이 값을 선택한 두 요소에 동시에 부여합니다.
  • 
     <head>head>
     <body>
      <div id="app">
        <input type="text" id="a">
        <span id="b">span>
      div>
    
      <script type="text/javascript">
       var obj = {};
       Object.defineProperty(obj, 'hello', {
           get: function() {
               console.log('get val:'+ val);
               return val;
           },
           set: function(newVal) {
                val = newVal;
                console.log('set val:'+ val);
                document.getElementById('a').value = val;
                document.getElementById('b').innerHTML = val;
           }
        });
        document.addEventListener('keyup', function(e) {
          obj.hello = e.target.value;
        });
       script>
      body>
    html>

    1.1 데이터의 양방향 연결을 실현하는 방식
    쌍방향 데이터가 밑바닥을 연결하는 사상은 매우 기본적이며 세 단계로 압축될 수 있다.
  • 우리는 어떤 UI 요소가 상응하는 속성에 귀속되었는지 식별하는 방법이 필요하다(위의 예에서 요소를 직접 선택했지만 대외 함수를 제공하지 않았다)
  • 속성 및 UI 요소의 변화를 모니터링해야 합니다
  • .
  • 우리는 모든 변화를 귀속된 대상과 원소에 전파해야 한다
  • 일반적으로 데이터 바인딩을 수행하는 방법은 다음과 같습니다.
  • 발표자-구독자모드
  • 더티(Dirty) 체크
  • 데이터 납치
  • 그 중에서 가장 간단하고 효과적인 방법은 게시자-구독자 모델을 사용하는 것이다.위의 예는 바로 사용되었다.
    게시자 - 구독자 모드의 사상은 매우 간단하다. 사용자 정의 데이터 속성을 사용하여 HTML 코드에서 귀속을 가리킨다.바인딩된 모든 JavaScript 객체와 DOM 요소는 게시자 객체에 가입됩니다.언제든지 귀속된 내용 (예를 들어 자바스크립트 대상이나 HTML 입력 필드) 이 변화를 탐지하면, 게시자 - 구독자 모드로 에이전트를 전송합니다. 이것은 모든 귀속된 대상과 요소에 변화를 방송하고 전파합니다.다음은 자바스크립트의 양방향 데이터 연결을 이야기하는 예입니다. 주석에 제 이해를 추가했습니다.
    function DataBinder(object_id){
        //       PubSub     
        var pubSub = { //   pubSub   ,      callbacks   ,      
            callbacks: {}, //                  ,      ,           
            on: function(msg,callback){ // on        ,     (          ),      
                this.callbacks[msg] = this.callbacks[msg] || []; //   msg     ,    (    ,     )
                this.callbacks[msg].push(callback); //            
            },
            publish: function(msg){ // publish   
                this.callbacks[msg] = this.callbacks[msg] || []; //    msg      ,   this.callbacks      msg        ,    ,        
                for(var i = 0, len = this.callbacks[msg].length; i//            msg       
                    this.callbacks[msg][i].apply(this,arguments); //           ,  this    publish     ,     publish           
                }
            }
        },
        data_attr = "data-bind-" + object_id, //        ,          ,  “data-bind”    ,              ,         
        message = object_id + ":change", //        ,          ,  “:change”    ,              ,           
        changeHandler = function(evt){ //         ,          
            var target = evt.target || evt.srcElemnt, //IE8          
                prop_name = target.getAttribute(data_attr); //       data_attr   
    
                if(prop_name && prop_name !== ""){ //       ,          
                    pubSub.publish(message,prop_name,target.value); //    message   ,        message      ,          ,  this    publish     ,     publish           (publish       apply)
                }
        };
        //          PubSub 
        if(document.addEventListener){ //          ,    changeHandler   
            document.addEventListener("change",changeHandler,false);
        }else{
            //IE8  attachEvent   addEventListener     
            document.attachEvent("onchange",changeHandler);
        }
        //PubSub                
        pubSub.on(message,function(vet,prop_name,new_val){ //    pubSub.on   ,   message        ,   ,message              ,        ,                    
            var elements = document.querySelectorAll("[" + data_attr + "=" + prop_name + "]"), //        ,             
                    tah_name;
            for(var i = 0,len =elements.length; i < len; i++){ //          
                tag_name = elements[i].tagName.toLowerCase();
    
                if(tag_name === "input" || tag_name === "textarea" || tag_name === "select"){ //        ,         
                    elements[i].value = new_val;
                }else{
                    elements[i].innerHTML = new_val;
                }
            }
        });
        return pubSub;
    }
    
    // model        
    function User(uid){
        var binder = new DataBinder(uid), //      pubSub   ,           uid               
        user = {
            attributes: {}, //          
            set: function(attr_name,val){ //    set   ,           publish          
                this.attributes[attr_name] = val;
                //  “publish”    
                binder.publish(uid+ ":change", attr_name, val,this);
            },
            get: function(attr_name){
                return this.attributes[attr_name];
            }
        }
        return user; //            ,      ,           
    }       
    
    var user = new User(123); //      user   ,      attributes         ,              
    user.set("name","Wolfgang"); //       data-bind-123="name"     html        ,         ,    

    그리고 더러운 검사, 더러운 검사는 당신이 어떻게 변화하는지, 언제 변화하는지에 무관심하고 특정한 검사 단계에서 데이터가 바뀌었는지에 대한 데이터 감청 기술이다.간단하게 말하자면, 더러운 검사는 데이터가 바뀌었는지 직접 검사하는 것이다. 만약 감청된 데이터가 바뀌면 이 값을 모든 피감청자에게 전달한다.
    데이터 납치는 속성의 setget 방법을 개조하여 데이터의 변화를 감시하고 구독자에게 메시지를 발표하여 해당하는 감청 리셋을 촉발하는 것이다.
    2 vue 데이터 양방향 바인딩
    vue는 데이터 납치 방식을 통해 데이터 연결을 하는 것으로 알려졌는데 그 중에서 가장 핵심적인 방법은 Object를 통해 연결하는 것이다.defineProperty () 는 속성에 대한 납치를 실현하고 데이터의 변동을 감청하는 목적을 달성합니다.
    mvvm의 양방향 귀속을 실현하려면 주로 다음과 같이 진행되었다.
  • 데이터 감청기 Observer를 실현하여 데이터 대상의 모든 속성을 감청할 수 있다. 변동이 있으면 최신 값을 받아서 구독자에게 알릴 수 있다
  • 하나의 명령 해석기Compile를 실현하고 각 요소 노드의 명령을 스캔하고 해석하며 명령 템플릿에 따라 데이터를 교체하고 해당하는 업데이트 함수를 귀속시킨다
  • Observer와Compile를 연결하는 다리로서 각 속성 변동에 대한 알림을 구독하고 받을 수 있으며 명령을 실행하여 연결된 상응하는 리셋 함수를 실행하여 보기를 업데이트할 수 있음
  • mvvm 입구 함수, 상기 세 가지를 통합
  • 예는 대체로 이 문장에서 나온 것으로, 나는 나의 이해에 근거하여 약간의 수정을 하고, 주석을 첨가하였다.
    이해하기 쉽도록 먼저 하나의 메시지의 저장 중전의 구조 함수를 실현한다.
    var uid = 0; //       uid   Dep        id,       
    
    function Dep() {
        this.id = uid++; //   Dep      id,      uid  1
        this.subs = [];
    }
    Dep.prototype = {
        addSub: function(sub) { //    sub
            this.subs.push(sub);
        },
    
        depend: function() {
            Dep.target.addDep(this); //       Dep   target        (         this)    subs  
        },
    
        removeSub: function(sub) { //    sub
            var index = this.subs.indexOf(sub);
            if (index != -1) {
                this.subs.splice(index, 1);
            }
        },
    
        notify: function() { //      subs      
            this.subs.forEach(function(sub) {
                sub.update();
            });
        }
    };

    객체의 등록 정보를 수정하면 바인딩된 각 등록 정보마다 Dep 인스턴스가 있습니다.모든 Dep 실례에는 알림이 필요한 대상을 저장하기 위해 subs 속성이 있습니다. 대상의 속성이 바뀔 때 set 방법을 통해 이 속성의 Dep 실례의 원형인 notify 방법을 호출하고 subs 그룹에 저장된 내용에 따라 이 속성 값을 귀속시킨 수정된 내용을 알립니다.
    function Observer(data) {
        this.data = data;
        this.walk(data); //        ,    
    }
    
    Observer.prototype = {
        walk: function(data) {
            var me = this;
            Object.keys(data).forEach(function(key) { //    data    ,      get / set
                me.convert(key, data[key]);
            });
        },
        convert: function(key, val) {
            this.defineReactive(this.data, key, val);
        },
    
        defineReactive: function(data, key, val) { //        
            var dep = new Dep();
            var childObj = observe(val);
    
            Object.defineProperty(data, key, {
                enumerable: true, //    
                configurable: false, //    define
                get: function() {
                    if (Dep.target) {
                        dep.depend(); //      Dep.target     dep     subs    
                    }
                    return val;
                },
                set: function(newVal) {
                    if (newVal === val) {
                        return;
                    }
                    val = newVal;
                    //     object  ,    
                    childObj = observe(newVal);
                    //      
                    dep.notify();
                }
            });
        }
    };
    
    function observe(value, vm) {
        if (!value || typeof value !== 'object') {
            return;
        }
    
        return new Observer(value);
    };

    그리고 html 템플릿을 컴파일하여 각 노드와 그 속성에 따라'{{}}}','v-','on'등 특수 문자열이 포함되어 있는지 판단하고 귀속되었는지 판단하며 귀속된 속성 개get set을 처리한다.
    function Compile(el, vm) {
        this.$vm = vm;
        this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    
        if (this.$el) {
            this.$fragment = this.node2Fragment(this.$el);
            this.init();
            this.$el.appendChild(this.$fragment);
        }
    }
    
    Compile.prototype = {
        node2Fragment: function(el) {
            var fragment = document.createDocumentFragment(),
                child;
    
            //         fragment
            while (child = el.firstChild) { //    el     ,       child,   true
                fragment.appendChild(child); //   child   el     fragment  ,el        ,       
            }
    
            return fragment; //    fragment
        },
    
        init: function() {
            this.compileElement(this.$fragment); //   fragment     
        },
    
        compileElement: function(el) {
            var childNodes = el.childNodes,
                me = this;
    
            [].slice.call(childNodes).forEach(function(node) { //       ,    
                var text = node.textContent;
                var reg = /\{\{(.*)\}\}/;
    
                if (me.isElementNode(node)) {
                    me.compile(node); //       
    
                } else if (me.isTextNode(node) && reg.test(text)) { //       
                    me.compileText(node, RegExp.$1);
                }
    
                if (node.childNodes && node.childNodes.length) {
                    me.compileElement(node); //     ,     
                }
            });
        },
    
        compile: function(node) {
            var nodeAttrs = node.attributes, //    dom     html         
                me = this;
    
            [].slice.call(nodeAttrs).forEach(function(attr) { //        ,  
                var attrName = attr.name;
                if (me.isDirective(attrName)) { //              ,     ,       
                    var exp = attr.value;
                    var dir = attrName.substring(2);
                    //        
                    if (me.isEventDirective(dir)) {
                        compileUtil.eventHandler(node, me.$vm, exp, dir);
                        //     
                    } else {
                        compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
                    }
                    node.removeAttribute(attrName); //       
                }
            });
        },
        compileText: function(node, exp) {
            compileUtil.text(node, this.$vm, exp);
        },
    
        isDirective: function(attr) {
            return attr.indexOf('v-') == 0;
        },
    
        isEventDirective: function(dir) {
            return dir.indexOf('on') === 0;
        },
    
        isElementNode: function(node) { //          
            return node.nodeType == 1;
        },
    
        isTextNode: function(node) { //          
            return node.nodeType == 3;
        }
    }

    마지막으로watch를 실현하고 속성의 변화를 감시한다.watch의 모든 실례는 감청하고자 하는 속성의 dep.subs 그룹에 추가됩니다. 감청하는 데이터에 변화가 발생하면 notify 함수를 호출하고 함수 내부에서subs를 호출하기 때문에watch 실례의 updata 방법으로 이 데이터를 감청하는 대상을 알립니다.알림을 받은 후 대상은 값이 바뀌었는지 판단합니다. 바뀌면 리셋 함수를 호출하고 보기를 변경합니다.
    function Watcher(vm, exp, cb) {
        this.cb = cb;
        this.vm = vm;
        this.exp = exp;
        //          getter,   dep    ,  Observer    
        this.value = this.get(); 
    }
    Watcher.prototype = {
        update: function() {
            this.run(); //          
        },
        run: function() {
            var value = this.get(); //      
            var oldVal = this.value;
            if (value !== oldVal) { //        
                this.value = value;
                this.cb.call(this.vm, value, oldVal); //   Compile      ,    
            }
        },
        get: function() {
            Dep.target = this;  //           
            var value = this.vm[exp];   //   getter,           
            Dep.target = null;  //     ,  
            return value;
        }
    };

    마지막으로 MVVM 구조기를 통해 위와 부분을 통합하여 데이터 귀속을 실현한다.
    function MVVM(options) {
        this.$options = options;
        var data = this._data = this.$options.data;
        observe(data, this);
        this.$compile = new Compile(options.el || document.body, this)
    }

    위의 내용은 데이터 귀속을 실현하는 대략적인 사고방식일 뿐이고 다른 내용은 내가 천천히 보완할 것이다.
    3 vue 데이터 양방향 귀속 결함
    3.1 vue 인스턴스가 생성된 후 속성을 추가하여 수신할 수 없음
    Vue 인스턴스를 만들면 모든 DOM 객체가 스트리밍되고 각 데이터 속성에 get과 set이 추가됩니다.get과 set을 사용하면 Vue에서 데이터의 변경 사항을 살펴보고 업데이트를 트리거할 수 있습니다.단, Vue 실례화 후에 속성을 추가하거나 삭제하면, 이 속성은 vue에서 처리되지 않고 get과 set을 변경합니다.
    새 대상을 만들고 싶지 않으면 Vue를 사용할 수 있습니다.set 새 대상 속성을 설정합니다.이 메서드는 속성을 응답 속성으로 만들고 뷰 업데이트를 트리거합니다.
    function addToCart (id) {
        var item = this.cart.findById(id);
        if (item) {
            item.qty++
        } else {
            //           ,   item.qty = 1
            //   Vue.set          
            Vue.set(item, 'qty', 1)
            this.cart.push(item)
        }
    }
    addToCart(myProduct.id);

    3.2 배열
    Object.defineProperty의 결함 중 하나는 그룹의 변화를 감청할 수 없다는 것입니다.
    인덱스(index)를 사용하여 배열 항목을 직접 설정할 때 vue에서 감지되지 않습니다.
    app.myArray[index] = newVal;
    

    그러나 Vue의 문서에서 Vue는 수조의 변화를 감지할 수 있지만 다음과 같은 8가지 방법만 있다vm.items[indexOfItem] = newValue 이런 것은 감지할 수 없다.
    push();
    pop();
    shift();
    unshift();
    splice();
    sort();
    reverse();

    마찬가지로 Vue를 사용할 수 있습니다.set을 사용하여 배열 항목을 설정합니다.
    Vue.set(app.myArray, index, newVal);
    

    3.3 proxy와 defineproperty
    Proxy 객체는 ES2015 사양에 정식으로 게시되어 속성 검색, 값 지정, 열거, 함수 호출 등의 기본 작업에 대한 사용자 정의 동작을 정의하는 데 사용됩니다.
    이것은 목표 대상에 앞서'차단'을 가설하고 외부에서 이 대상에 대한 방문을 반드시 이 층을 통해 차단해야 하기 때문에 외부의 방문을 필터하고 고칠 수 있는 메커니즘을 제공했다.
    Proxy는 Object입니다.defineProperty의 전방위적인 강화판으로 구체적인 문서는 이곳을 볼 수 있습니다.
    Proxy에는 13가지 차단 방법이 있는데 apply、ownKeys、deleteProperty、has 등에 국한되지 않는 Object입니다.defineProperty에 없는Proxy는 새로운 대상을 되돌려줍니다. 우리는 새로운 대상만 조작해서 목적을 달성할 수 있습니다. Object.defineProperty는 객체 속성만 직접 수정할 수 있습니다.Proxy는 새로운 표준으로서 브라우저 업체의 중점적인 지속적인 성능 최적화, 즉 전설의 새로운 표준의 성능 배당금을 받을 것이다.물론 Proxy의 열세는 호환성 문제이고polyfill로 닦을 수 없기 때문에 Vue의 저자는 다음 버전(3.0)을 기다려야 Proxy로 다시 쓸 수 있다고 밝혔다.
    참조 자료:
    MVVM(류설봉의 공식 사이트)은 자바스크립트의 양방향 데이터 귀속 더러움 검사와 그 장점에 대해 이야기한다.0 원본 읽기 노트 - 양방향 귀속 실현 원리 분석 Vue 원리 & 양방향 귀속 MVVM mdn Object 실현.defineProperty()Vue 응답식 및 결함 면접관: 쌍방향 귀속Proxy를 실현하는 것이 defineproperty보다 우열이 어떻습니까?MDN Proxy

    좋은 웹페이지 즐겨찾기