起因

博主在学习mvvm框架源代码时发现,网络上很多源代码都不像vue一样支持在{{}}中写js语法,所以博主很纳闷,于是自己研究了半天,下面看思路

方法1

思路是使用eval加上es6中的解构赋值+eval来实现执行js,看下面代码:

function Run(vm,exp){
    let keys=Object.keys(vm);
    try{
        eval(`let {${keys.join(',')}}=vm;`);
        return new Function(`return ${ev}`)();
    }catch (e) {
        return "";
    }
}

结果运行报错,仔细一看是在解构的时候出错的,研究了半天发现只要vm里面的变量有getter和setter就会解构赋值失败。
经过博主再次更新代码如下:

function Run(vm,exp){
    let keys=Object.keys(vm);
    eval(`let ${keys.join(',')};`);
    keys.forEach((key)=>{
        eval(key+"=vm['"+key+"']");
    });
    try{
        return new Function(`return ${exp}`)();
    }catch (e) {
        return "";
    }
}

终于成功运行了,但是很奇怪我直接在网上搜索改变js作用域的文章都没有找到。

方法2(Vue使用此方法)

在上面虽然实现了但是我很好奇vue是怎么做的,不可能向我们一样lou吧。于是博主抱着偷代码的心思去下载了源码来看。博主喜欢运行在html页面上调试他看他运行路线。所以博主就直接在setter下断点

 set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        /* eslint-enable no-self-compare */
        if (customSetter) {
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) { return }
        if (setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }

废话多说直接进入dep.notify();刷新方法

  Dep.prototype.notify = function notify () {
    // stabilize the subscriber list first
    var subs = this.subs.slice();
    if (!config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort(function (a, b) { return a.id - b.id; });
    }
    for (var i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  };

可以看到这里update方法就是刷新了在进去

  /**
   * Subscriber interface.用户接口
   * Will be called when a dependency changes.将在依赖项更改时调用
   */
  Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };

这里可以看到官方大大还是很用心的注释了,虽然咱英文不咋地但是还有翻译呀,咱们继续往下溜

 /**
   * Push a watcher into the watcher queue.将观察者推入观察者队列
   * Jobs with duplicate IDs will be skipped unless it's具有重复ID的作业将被跳过,除非
   * pushed when the queue is being flushed.刷新队列时推送
   */
  function queueWatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
      has[id] = true;
      if (!flushing) {
        queue.push(watcher);
      } else {
        // if already flushing, splice the watcher based on its id如果已经刷新,则根据观察程序的ID将其拼接
        // if already past its id, it will be run next immediately.如果已经超过了它的ID,它将立即运行
        var i = queue.length - 1;
        while (i > index && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(i + 1, 0, watcher);
      }
      // queue the flush
      if (!waiting) {
        waiting = true;

        if (!config.async) {
          flushSchedulerQueue();
          return
        }
        nextTick(flushSchedulerQueue);
      }
    }
  }

这里来到queueWatcher方法,我也不一部一部跟踪了直接来到run方法

 /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  Watcher.prototype.run = function run () {
    if (this.active) {
      var value = this.get();
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        var oldValue = this.value;
        this.value = value;
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue);
          } catch (e) {
            handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
          }
        } else {
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  };

此时注意this.get方法了,进入get之后看下面,没错下面就快到vue官方大大的run方法
1.png

咱们在进去发现还有一层看图说话,这里已经是vm.update了说明已经是刷新方法了,进去瞅一瞅
2.png

看到下面就是最终执行了
3.png

我把执行代码放下面,发现主要就是with这个东东,百度看一看,发现with(obj)就能在代码块中直接使用obj中的值val了而不需要obj.val。至此也明白了vue官方大大是怎么做的了,学到一课不错不错,下面是vue官方大大的执行代码

(function anonymous() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_c('div', [_c('div', {
            attrs: {
                "x-show": "pd"
            }
        }, [_v("显示")]), _v(" "), _c('div', {
            attrs: {
                "x-show": "!pd"
            }
        }, [_v("隐藏")]), _v(" "), _c('div', [_v(_s(a.b))]), _v(" "), _c('div', [_v(_s(list[1]))]), _v(" "), _c('div', [_v(_s(list[2]))]), _v(" "), _c('div', [_v(_s(list))]), _v(" "), _c('input', {
            directives: [{
                name: "model",
                rawName: "v-model",
                value: (inputVal),
                expression: "inputVal"
            }],
            attrs: {
                "type": "text"
            },
            domProps: {
                "value": (inputVal)
            },
            on: {
                "input": function($event) {
                    if ($event.target.composing)
                        return;
                    inputVal = $event.target.value
                }
            }
        }), _v(" "), _c('div', [_v(_s(inputVal))])])])
    }
}
)

标签: 如何将对象作为JS的作用域默认变量, with的使用, 改变js作用域

添加新评论