javascript

仿vue - safe

2019-01-21  本文已影响0人  Viker_bar

本文主要对学习Vuejs源码做总结,理解双向绑定,以及学习框架设计思想。
源码参考版本:V2.6.10 总共11945行,去除复杂逻辑以及异常处理后为945行

精简版:


(function(g,f){
    g.Vue = f();
}(this,function(){


    /********************************************************************************
    *
    * @Author zpw
    * @Description 静态变量(类变量)
    * 
    ********************************************************************************/

    var queue = [],
        timerFunc,
        activeInstance = null,
        identity = function(_){ return _; },
        config = ({
            parsePlatformTagName: identity,
            async: true,
        }),
        isUsingMicroTask = false,
        callbacks = [];


    /********************************************************************************
    *
    * @Author zpw
    * @Description 工具方法
    * 
    ********************************************************************************/

    function toString(val){
      return String(val);
    }

    function isNative (Ctor) {
      return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
    }

    function isDef (v) {
      return v !== undefined && v !== null
    }

    function isUndef (v) {
      return v === undefined || v === null
    }

    function query(el) {
      if (typeof el === 'string') {
        var selected = document.querySelector(el);
        if(!selected){
          return document.createElement('div')
        }
        return selected
      }else{
        return el
      }
    }

    function createFunction(code){
      try{
        return new Function(code);
      }catch(err) {
        console.log(err);
      }
    }

    //浅拷贝
    function extend (to, _from) {
      for(var key in _from) {
        to[key] = _from[key];
      }
      return to
    }

    function createTextVNode (val) {
      //VNode是JavaScript对象
      return new VNode(undefined, undefined, undefined, String(val))
    }

    function getOuterHTML(el){
      if(el.outerHTML){
        return el.outerHTML
      }else{
        var container = document.createElement('div');
        container.appendChild(el.cloneNode(true));
        return container.innerHTML
      }
    }

    function def (obj, key, val, enumerable) {
      Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
      });
    }

    //非IO的异步回调兼容处理(https://zhuanlan.zhihu.com/p/33090541)
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      var p = Promise.resolve();
      timerFunc = function () {
        p.then(flushCallbacks); //触发这里的flushCallbacks函数
      };
      isUsingMicroTask = true;
    }else if(!isIE && typeof MutationObserver !== 'undefined' && (
      isNative(MutationObserver) ||
      MutationObserver.toString() === '[object MutationObserverConstructor]'
    )){
      var counter = 1;
      var observer = new MutationObserver(flushCallbacks);
      var textNode = document.createTextNode(String(counter));
      observer.observe(textNode, {
        characterData: true
      });
      timerFunc = function () {
        counter = (counter + 1) % 2;
        textNode.data = String(counter);
      };
      isUsingMicroTask = true;
    }else if(typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      timerFunc = function () {
        setImmediate(flushCallbacks);
      };
    }else{
      timerFunc = function(){
        setTimeout(flushCallbacks, 0);
      };
    }


    /********************************************************************************
    *
    * @Author zpw
    * @Description 实例方法
    * 
    ********************************************************************************/

    function createElement$1 (tagName, vnode) {
      var elm = document.createElement(tagName);
      if (tagName !== 'select') {
          return elm
      }
    }

    function createElementNS (namespace, tagName) {
      return document.createElementNS(namespaceMap[namespace], tagName)
    }

    function createTextNode (text) {
      return document.createTextNode(text)
    }

    function createComment (text) {
      return document.createComment(text)
    }

    function insertBefore (parentNode, newNode, referenceNode) {
      parentNode.insertBefore(newNode, referenceNode);
    }

    function removeChild (node, child) {
      node.removeChild(child);
    }

    function appendChild (node, child) {
      node.appendChild(child);
    }

    function parentNode (node) {
      return node.parentNode
    }

    function nextSibling (node) {
      return node.nextSibling
    }

    function tagName (node) {
      return node.tagName
    }

    function setTextContent (node, text) {
      node.textContent = text;
    }

    function setStyleScope (node, scopeId) {
      node.setAttribute(scopeId, '');
    }

    var nodeOps = _nos =  Object.freeze({
      createElement: createElement$1,
      createElementNS: createElementNS,
      createTextNode: createTextNode,
      createComment: createComment,
      insertBefore: insertBefore,
      removeChild: removeChild,
      appendChild: appendChild,
      parentNode: parentNode,
      nextSibling: nextSibling,
      tagName: tagName,
      setTextContent: setTextContent,
      setStyleScope: setStyleScope
    });


    function VNode(
      tag,
      data,
      children,
      text,
      elm,
      context,
      componentOptions
    ){
      this.tag = tag;
      this.data = data;
      this.children = children;
      this.text = text;
      this.elm = elm;
      this.context = context;
      this.key = data && data.key;
      this.componentOptions = componentOptions;
    };

    function installRenderHelpers (target) {
      target._s = toString;
      target._v = createTextVNode;
    }


    function noop(a, b, c){};

    function setActiveInstance(vm) {
      var prevActiveInstance = activeInstance;
      activeInstance = vm;
      return function () {
        activeInstance = prevActiveInstance;
      }
    }

    function mergeOptions(options){
      return options
    }

    var sharedPropertyDefinition = {
            enumerable: true,
            configurable: true,
            get: noop,
            set: noop
        };
    function proxy (target, sourceKey, key) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      };
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val;
      };
      Object.defineProperty(target, key, sharedPropertyDefinition);
    }


    function flushCallbacks () {
      var copies = callbacks.slice(0);
      callbacks.length = 0;
      for (var i = 0; i < copies.length; i++){
        copies[i]();
      }
    }


    function nextTick (cb, ctx) {
      callbacks.push(function () {
        cb.call(ctx);
      });
      timerFunc();
    }


    function flushSchedulerQueue () {
      for (index = 0; index < queue.length; index++) {
        watcher = queue[index];
        watcher.run();
      }
    }


    function queueWatcher (watcher) {
      queue.push(watcher);
      nextTick(flushSchedulerQueue);
    }


    function renderMixin(Vue){

        installRenderHelpers(Vue.prototype);

        Vue.prototype._render = function(){
          var vm = this;
          var ref = vm.$options;
          var render = ref.render;

          vm.$vnode = undefined;

          var vnode;
          try{
            // 解析: vnode = render.call(vm._renderProxy) 
            //1:用javascript对象模拟虚拟Dom;即:with(this){return _c("div",{attrs:{"id":"app"}},[_v(""+_s(message)+"")])}
            //2:_c函数由虚拟Dom树转化为真实Dom
            //2.1: 把_c函数分解运行,如下:
            // var v1 = vm._v,
            //     s1 = vm._s;

            // var o1 = {
            //       attrs:{"id":"app"}
            //     },
            //     a2 = [v1(s1("self message 'Examples'"))];


            //3:挂载虚拟Dom到Vm上下文
            // vnode = vm._c("div",o1,a2);

            //3:挂载虚拟Dom到Vm上下文
            vnode = render.call(vm._renderProxy);

          }catch(err){
             console.log(err);
          }
          return vnode
        };
      }


      function lifecycleMixin(Vue){
            Vue.prototype._update = function (vnode, hydrating) {
              var vm = this;
              var prevEl = vm.$el;
              var prevVnode = vm._vnode;
              var restoreActiveInstance = setActiveInstance(vm);

              vnode.elm = vm.$el;
              vm._vnode = vnode;

              var oldVnode = prevEl,
                  vnode = vnode;

              if (!prevVnode) {
                // initial render
                createPatchFunction(nodeOps,oldVnode,vnode);
                // vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false);
              } else {
                // updates render
                createPatchFunction(prevVnode,vnode);
                // vm.$el = vm.__patch__(prevVnode, vnode);
              }
              restoreActiveInstance();
            };
      }


      function mountComponent(vm,el){
        vm.$el = el;

        // beforeMount hook

        var updateComponent = function () {
            vm._update(vm._render());
          }

        new Watcher(vm, updateComponent, noop, {}, true);

        hydrating = false;

        if(vm.$vnode == null) {
          vm._isMounted = true;
        }
        return vm
      }


      Vue.prototype.$mount = function(el){
        el = el && query(el);
        return mountComponent(this, el)
      };

      function mountComponent(vm,el){
        vm.$el = el;
        var updateComponent = function () {
            vm._update(vm._render());
          }

        new Watcher(vm, updateComponent, noop, {}, true);

        hydrating = false;

        if(vm.$vnode == null){
          vm._isMounted = true;
        }
        return vm
      }


      Vue.prototype.$mount = function(el){
        el = el && query(el);
        return mountComponent(this, el)
      };


      function baseCompile(){

          function parse(template){
             var ast = '抽象语法树(Abstract Syntax Tree)';
             return ast
          }

          function genText(ast){

            /*
            *
            *
            * 这里的变量决定初始化的时候 dep订阅器中有几个订阅者(Watcher)
            * eg :
            * _s(testMessage,testMsg)   dep 里面就是2个订阅者
            * _s(testMessage)           dep 里面就是1个订阅者
            *
            */
            var txt = '_s(testMessage)' //写死的变量
            return ("_v(" + txt+ ")")
          }

          function genNode(ast) {
            return genText(ast)
          }

          function genChildren(ast){
              var txt = genNode(ast);
              return ("[" + txt + "]")
          }


          function genElement(ast){
              var code,
                  tag = 'div',
                  data = '{attrs:{"id":"app"}}',
                  children = genChildren(ast);

                code = "_c('" + tag + "'" + "," + data + "," + children + ")";

              return code
          }


          function generate (ast,options){
            var code = ast ? genElement(ast) : '_c("div")';
            return {
              render: ("with(this){return " + code + "}") //"with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n          "+_s(message)+"\n      ")])}" 
            }
          }

          

        function init(template){

           var ast = parse(template),
               code = generate(ast);

           return {
              ast: ast,
              render: code.render
            }
        }

        return init(template);

      }

      function compileToFunctions(template,options,vm){
          var compiled = baseCompile(template);
          var res = {};

          res.render = createFunction(compiled.render);
          return res;
      }

      function createElement (
          context,
          tag,
          data,
          children,
          normalizationType,
          alwaysNormalize
        ){
          return _createElement(context, tag, data, children, normalizationType)
        }


        function _createElement (
          context,
          tag,
          data,
          children,
          normalizationType
        ) {
          var vnode;
          if(typeof tag === 'string') {
            vnode = new VNode(
              config.parsePlatformTagName(tag), data, children,
              undefined, undefined, context
            );
          }
          return vnode
        }


        function createPatchFunction(nodeOps,oldVnode,vnode) {

          function emptyNodeAt(elm){
            return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
          }

          function insert (parent, elm, ref$$1) {
            nodeOps.insertBefore(parent, elm, ref$$1);
          }

          function createChildren (vnode, children, insertedVnodeQueue) {
              for (var i = 0; i < children.length; ++i) {
                createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
              }
          }

          function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
            removeAndInvokeRemoveHook(vnodes[0]);
          }

          function removeAndInvokeRemoveHook (vnode, rm) {
            removeNode(vnode.elm); //childElm为#app元素
          }

          function removeNode(el) {
              // parent :body 元素
              // el: #app元素
              var parent = nodeOps.parentNode(el);
              nodeOps.removeChild(parent, el);
          }


          function patchVnode (
            oldVnode,
            vnode,
            insertedVnodeQueue,
            ownerArray,
            index,
            removeOnly
          ) {

            var elm = vnode.elm = oldVnode.elm; //#app

            var i;
            var data = vnode.data;

            var oldCh = oldVnode.children;
            var ch = vnode.children;
              
            if(isUndef(vnode.text)) {
              if(oldCh !== ch){ 
                updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); 
              }
            }else if(oldVnode.text !== vnode.text) {
              _nos.setTextContent(elm, vnode.text);
            }
            
          }


          function createElm(vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index){

            var data = vnode.data;
            var children = vnode.children;
            var tag = vnode.tag;

            if(isDef(tag)){
                vnode.elm = nodeOps.createElement(tag, vnode);
                createChildren(vnode, children, insertedVnodeQueue);
                insert(parentElm, vnode.elm, refElm);
            }else{
                vnode.elm = nodeOps.createTextNode(vnode.text);
                insert(parentElm, vnode.elm, refElm);
            }

          }



          function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {

            var oldStartVnode = oldCh[0],
                newStartVnode = newCh[0],
                newStartIdx = 0;

            patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
            
          }



         function patch(){

            var isInitialPatch = false;
            var insertedVnodeQueue = [];
            var isRealElement = isDef(oldVnode.nodeType);
            var removeOnly = undefined;

            if(!isRealElement){
                patchVnode(nodeOps, oldVnode, insertedVnodeQueue, null, null, removeOnly); //导致无法正常使用
            }else{
              oldVnode = emptyNodeAt(oldVnode);
            }

            var oldElm = oldVnode.elm;
            // var parentElm = document.querySelector('body');
            var parentElm = nodeOps.parentNode(oldElm);

            vnode.elm = undefined;

            createElm(
              vnode,
              insertedVnodeQueue,
              parentElm,
              nodeOps.nextSibling(oldElm)
            );
            
            removeVnodes(parentElm, [oldVnode], 0, 0);

            // invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
            return vnode.elm
          }

          return  patch();
        }


        var mount = Vue.prototype.$mount;
        Vue.prototype.$mount = function(el){
          el = el && query(el);
          if(el === document.body || el === document.documentElement) return this;

          var options = this.$options;
          template = getOuterHTML(el);

            var ref = compileToFunctions(template, {}, this);
            var render = ref.render;
            options.render = render;

            //mount.call(this, el)
            return mount.call(this,el)
        };

        var Dep = function Dep () {
          this.subs = [];
        };

        Dep.prototype.addSub = function(sub) {
          this.subs.push(sub);
        };

        Dep.prototype.removeSub = function(sub) {
          remove(this.subs, sub);
        };

        Dep.prototype.depend = function(){
            Dep.target.addDep(this); //Dep.target 就是Watcher对象
        };

        Dep.prototype.notify = function() {
          var subs = this.subs.slice();
          for (var i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
          }
        };

        Dep.target = null;
        var targetStack = [];

        function pushTarget (target) {
          targetStack.push(target);
          Dep.target = target;
        }

        function popTarget () {
          targetStack.pop();
          Dep.target = targetStack[targetStack.length - 1];
        }


        var Observer = function(value) {
          this.value = value;
          this.dep = new Dep();
          this.vmCount = 0;
          def(value, '__ob__', this);
          if(Array.isArray(value)) {
            this.observeArray(value);
          }else{
            this.walk(value);
          }
        };

        Observer.prototype.walk = function(obj) {
          var keys = Object.keys(obj);
          for (var i = 0; i < keys.length;i++) {
            defineReactive$$1(obj, keys[i]);
          }
        };

        Observer.prototype.observeArray = function(items) {
          for (var i = 0, l = items.length; i < l; i++) {
            observe(items[i]);
          }
        };


        function observe(value) {
          if(typeof value !== 'object') return;
          var ob = new Observer(value);
          ob.vmCount++;
          return ob
        }


        function defineReactive$$1(
          obj,
          key,
          val
        ){
          // var o = { bar: 42 },  
          // d = Object.getOwnPropertyDescriptor(o, "bar");

          /**
          *
          * Input:
          *
          * configurable: true  //属性是否可以被修改和删除
          * enumerable: true    //属性是否可以被枚举
          * value: 42           //属性值(仅针对数据属性描述符有效)
          * writable: true      //属性的值是否可以被改变(仅针对数据属性描述有效)
          *
          */   
          // console.dir(d);
          //instanceof  某个对象是不是另一个对象的实例(原型链上有也算对象的一个实例)

          var dep = new Dep(),
              property = Object.getOwnPropertyDescriptor(obj, key),
              val ;

          if(property && property.configurable === false) return;

          var getter = property && property.get,  //存储getter构造器
              setter = property && property.set;  //存储setter构造器

          val = obj[key];

          Object.defineProperty(obj, key, {
              enumerable: true,
              configurable: true,
              get: function() {
                var value = getter ? getter.call(obj) : val;  //如果有getter构造器,this指向obj,否则直接取值
                if(Dep.target){
                  console.log("触发获取了--");
                  dep.depend(); //添加订阅者
                }
                return value
              },
              set: function(newVal) {
                var value = getter ? getter.call(obj) : val;
                if(newVal === value || (newVal !== newVal && value !== value)) return;
                if(setter){
                  console.log("触发设置数据了-----");
                  setter.call(obj, newVal);
                }else{
                  console.log("触发设置数据了+++++");
                  val = newVal;
                }
                dep.notify();
              }
          });
        }



        function createWatcher (
          vm,
          expOrFn,
          handler,
          options
        ) {
          return vm.$watch(expOrFn, handler, options)
        }


        function Watcher(
          vm,
          expOrFn,
          cb,
          options,
          isRenderWatcher
        ){
          this.vm = vm;
          if(isRenderWatcher) {
            vm._watcher = this;
          }
          vm._watchers.push(this);
         
          this.cb = cb;
          this.active = true;
          this.deps = [];
          this.newDeps = [];
          this.expression = expOrFn.toString();

          if(typeof expOrFn === 'function') {
            this.getter = expOrFn;
          }

          this.value = this.get(); //将自己添加到dep订阅器

        };



        Watcher.prototype.get = function(){
          pushTarget(this);
          var value;
          var vm = this.vm;
          try {
            value = this.getter.call(vm, vm); //触发dep.depend(),且告诉dep订阅器 把自己收集到dep订阅器里面,为之后的数据变化做准备
          }catch (e){
           
          }finally {
            popTarget();
          }
          return value
        };


        Watcher.prototype.addDep = function(dep) {
          dep.addSub(this);
        };

         Watcher.prototype.update = function update () {
              queueWatcher(this);
          };


        Watcher.prototype.run = function() {
          var value = this.get();
        };

        Watcher.prototype.depend = function(){
          var i = this.deps.length;
          while (i--) {
            this.deps[i].depend();
          }
        };


        function initProxy(vm){
            vm._renderProxy = vm;
        };

        function initRender(vm){
          //将createElement fn绑定到此实例,得到合适的渲染上下文
          vm._c = function (a, b, c, d) {
            return createElement(vm, a, b, c, d, false); 
          };
        }

        function initMixin (Vue) {
          Vue.prototype._init = function (options) {

            var vm = this;
            //合并对象
            vm.$options = mergeOptions(options);

            initProxy(vm);
            initRender(vm);
            //beforeCreate Hook
            initState(vm); //为所有data属性添加getter,setter方法
            //created Hook

            if(vm.$options.el){
              vm.$mount(vm.$options.el);
            }

          };
        }

        function initState(vm){
          vm._watchers = [];
          initData(vm);
        }

        function initData(vm){
          var data = vm.$options.data,
              keys = Object.keys(data),
              i = keys.length;

            data = vm._data = data || {};
            while (i--) {
              var key = keys[i];
                proxy(vm, "_data", key);
            }
          observe(data);
        }

        function Vue(options){
          this._init(options);
        }

        initMixin(Vue);
        lifecycleMixin(Vue);
        renderMixin(Vue);

        return Vue;
        
}))

分析图:

[源码分析图]

<html>
  <head>
      <meta charset="utf-8">
      <title>safe</title>
      <script src="./js/Mvue.js?v=1.0.0"></script>
  </head>
  <body>
      <div id="app">
          {{ testMessage }}
      </div>
      <script>
          
          var app = new Vue({
            el: '#app',
            data: {
              testMessage : 'Hello Vue!'
            }
          })

          app._data.testMessage  = "Vue Hello!"

      </script>
  </body>
</html>
上一篇 下一篇

猜你喜欢

热点阅读