只使用 ES5 的语法,不考虑异常情况下,用最简单的方式在15分钟内,使下面代码功能实现(允许改变结构):

    1. <div id="app"></div>
    1. var Vue = (function () {
    2. var Vue = function () {
    3. }
    4. return Vue;
    5. });
    6. new Vue({
    7. el: '#app',
    8. template: `
    9. <h1>{{ count }}</h1>
    10. <button onClick="add">+</button>
    11. <button onClick="minus">-</button>
    12. `,
    13. data: function () {
    14. return {
    15. count: 0
    16. }
    17. },
    18. methods: {
    19. add: function () {
    20. this.count += 1;
    21. },
    22. minus: function () {
    23. this.count -= 1;
    24. }
    25. },
    26. });

    根据题目是要实现一个类似 Vue 的 ViewModel 功能部分,但只能在较短的时间内实现。
    分析

    1. 模板编译是跑不掉的,但时间的关系不能使用分析字符串,那就改为分析 DOM,生成 DOM 树
    2. DOM 中的标签内容为 {{}} 要替换为 data 中对应的值(Data Bindings)
    3. 获取带有 onClick 属性的标签,为标签绑定 methods 对应的 click 事件处理函数(DOM Listeners)
    4. 事件改变数据的同时,还有使视图响应。那么要对数据做代理。针对 set 的同时改变相应 DOM 节点的内容

      var Vue = (function () {
      var reg_var = /{{(.+?)}}/;
      var Vue = function (options) {
       this.el = document.querySelector(options.el);
       this.template = options.template;
       this.data = options.data();
       this.methods = options.methods;
      
       this.dataPool = {};
      
       this.init();
      }
      
      Vue.prototype.init = function () {
       // 编译 DOM 树
       var domTree = compile(this, this.template, this.data, this.methods, this.dataPool);
       dataProxy(this, this.data, this.dataPool);
      
       this.el.appendChild(domTree);
      }
      
      function compile(vm, template, data, methods, dataPool) {
       var domTree = createElement('div', template);
       var allNodes = domTree.getElementsByTagName('*'),
           nodeItem = null;
      
       for (var i = 0; i < allNodes.length; i++) {
         nodeItem = allNodes[i];
      
         // 模版变量替换为 data 中的值
         if (reg_var.test(nodeItem.textContent)) {
           compileData(nodeItem, data, dataPool);
         }
      
         // 节点事件处理函数绑定
         if (nodeItem.getAttribute('onClick')) {
           compileEvent(vm, nodeItem, methods);
         }
       }
      
       return domTree;
      }
      
      function createElement(tagName, template) {
       var oEl = document.createElement('div');
       oEl.innerHTML = template;
       return oEl;
      }
      
      function compileData(node, data, dataPool) {
       var key = node.textContent.match(reg_var)[1].trim();
       // 使用数据池做一个数据变量与节点的映射
       dataPool[key] = [node, node.textContent];
      
       update(key, data[key], dataPool);
      }
      
      function compileEvent(vm, node, methods) {
       var cb = node.getAttribute('onClick');
      
       node.addEventListener('click', methods[cb].bind(vm), false);
       node.removeAttribute('onClick');
      }
      
      function dataProxy(vm, data, dataPool) {
       for (var k in data) {
         (function (k) {
           Object.defineProperty(vm, k, {
             get: function () {
               return data[k];
             },
             set: function (newValue) {
               data[k] = newValue;
               update(k, newValue, dataPool);
             }
           })
      
         })(k);
       }
      }
      
      function update(key, value, dataPool) {
       dataPool[key][0].textContent = dataPool[key][1].replace(reg_var, value)
      }
      
      return Vue;
      })();
      

    使下面代码功能实现

    var Counter = (function () {
      var obj = {
        a: 1,
        b: 2,
      };
    })();
    
    Counter.change('a', 100); // a => 100
    Counter.add(); // => a + b = ?
    Counter.minus(); // => a - b = ?
    
    var Counter = (function () {
      var obj = {
        a: 1,
        b: 2,
      };
    
      function change(key, value) {
          if(!obj.hasOwnProperty(key)) {
          throw new ReferenceError("This key is not exist in obj");
        }
        obj[key] = value;
      }
    
      function add() {
          console.log("a + b = " + (obj.a + obj.b));
      }
    
      function minus() {
          console.log("a - b = " + (obj.a - obj.b));
      }
    
      return {
          change: change,
        add: add,
        minus: minus
      }
    })();
    
    Counter.change('a', 100); // a => 100
    Counter.add(); // => a + b = ?
    Counter.minus(); // => a - b = ?
    

    var value = 1;
    
    var foo = {
      value: 2,
      bar: function () {
        return this.value;
      },
    };
    
    console.log(eval('foo.bar()')); 
    
    console.log((foo.bar = foo.bar)());
    
    console.log((false || foo.bar)());
    
    console.log(
      new Function(foo.bar.toString().match(/return(.*?)\;/)[0]).bind(foo).call(window)
    );
    
    • 2
      eval 下与 foo.bar() 无异
    • 1
      foo.bar = foo.bar 赋值是有返回,返回的是其被赋值的值,所以这里相当于是 function () { return this.value }
      那么在 function () { return this.value } 是在全局执行,所以为 1
    • 1
      与上面同理,false || foo.bar 是返回 function () { return this.value },在全局执行,所以为 1
    • 2
      new Function(foo.bar.toString().match(/return(.*?)\;/)[0]) =>
      new Function("return this.value")
      被 bind() 绑定过的 this 的函数 BoundFunction,会忽略 call() / apply() 的 this 指向来执行。所以还是 2

    var a = 0;
    
    if (true) {
      console.log(a, window.a);
      a = 10;
      console.log(a, window.a);
      function a() {}
      console.log(a, window.a);
      a = 11;
      console.log(a, window.a);
    }
    console.log(a, window.a);
    

    在 if block 中是不进行预编译,所以在执行到 if block 时,function a() {} 会提升至 block 最顶

    • function(){} 0

    a = 10 没什么特别

    • 10 0

    当遇到 function a() { } 会统一 if block 与 全局的 a

    • 10 10

    a = 11 没什么特别

    • 11 10

    全局的 a === window.a

    • 10 10

    var x = 1;
    
    function test() {
      x = 2
    }
    
    function foo(
    x, y = test
    ) {
      x = 1;
      y();
      console.log(x);
    }
    
    foo();
    

    1