Vue click事件的问题?

这个问题我看github上面有人也提了类似的 issue 但是好像没有说明具体原因出来?https://github.com/vuejs/vue/...

具体场景
我在一个头文件里面定义了一个方法 例如

var ms = {

open:function(url){
    //伪代码
}

}

在具体页面我把这个文件引入在头部

这是业务代码

<div id='app'>

<button @click='ms.open('./home/)'>按钮1</button>
<button onclick='ms.open('./home/)'>按钮2</button>

</div>
<script>

var app = new Vue({
    el:#app,
    data:{
    }
})

</script>

问题来了,不管我点的是按钮1还是按钮2,都显示ms未定义?请问各位大佬,这是什么原因?针对这样的场景有没有好的解决办法?

阅读 5k
3 个回答

总结:vue.min.js 不知出于何种原因,没有设置 _renderProxyhasProxy,导致 with 语句会正确执行原本的语义。

打了断点找了一通,找到原因了:

本质上就是 with 的问题。
先来看 vue.js 中设置渲染代理的地方:

    initProxy = function initProxy (vm) {
      if (hasProxy) {
        // determine which proxy handler to use
        var options = vm.$options;
        var handlers = options.render && options.render._withStripped
          ? getHandler
          : hasHandler;
        vm._renderProxy = new Proxy(vm, handlers);
      } else {
        vm._renderProxy = vm;
      }
    };

可以看到,当浏览器中 Proxy 存在且为原生对象时会设置 vm._renderProxy 为某个代理,在我们的例子中将设置 hasHandler 代理,后面会看到。

而 vue 渲染一个节点元素使用的代码如下:

    Vue.prototype._render = function () {
        ...
        var render = ref.render;
        ...
        try {
            vnode = render.call(vm._renderProxy, vm.$createElement);
       } catch (e) {
         ...
       }

其中 ref.render 即模板被编译后的渲染函数:

function anonymous() {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('button',{on:{"click":() => {debugger; ms.open()}}},[_v("\n     测试 压缩版本\n  ")])])}
}

可以看到,此时渲染函数中的 this 将会被指向 vm._renderProxy代理,而代理的响应函数为:

    var hasHandler = {
      has: function has (target, key) {
        var has = key in target;
        var isAllowed = allowedGlobals(key) ||
          (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data));
        if (!has && !isAllowed) {
          if (key in target.$data) { warnReservedPrefix(target, key); }
          else { warnNonPresent(target, key); }
        }
        return has || !isAllowed
      }
    };

注意到代理挂载的是 has 拦截,因此当 with(vm._renderProxy) {... ms ...} 时,会调用

has(vm._renderProxy, 'ms')

此时不满足 has 函数中所设置的一系列条件,所以会报错。

而在 min 版本下,我不清楚为何设置 vm._renderProxy 的代码没了,只有在 _init 函数中简单的 n._renderProxy = n,即在 min 下不会上面说的代理。

既然不走代理,那么

with(vm._renderProxy) {
    ...
    ms
    ...
}

此时由于 _renderProxy 没有设置为 hasProxy,那么 with 会简单得尝试在 _renderProxy 上寻找 ms,找不到的时候会向上寻找一直到 window 作用域,此时即找到。


只有注册在 vue 内的方法才能被 vue 调用,因为模板会被重新编译为 vue 作用域(具体来说应该是某个组件)内的渲染函数。

你的 ms 既不是 vue (组件)的 data,也不是 computed,更不是 methods,vue 上哪儿找 ms 去。

import ms from '...'

var app = new Vue({
  el: #app,
  data () {
    return {ms} // 放在 data
  },
  computed: {
    ms () {
      return ms // 放在 computed
    }
  },
  methods: {
    // 使用此法,则点击事件需要改写成 <button @click="ms_open('./home/')"></button>
    ...Object.keys(ms).reduce((p, c) => [p[`ms_${c}`] = ms[c], p][1], {}) 
  }
})

template 中的方法,默认会去当前的 vue 实例上找,你这个是全局的方法,vm 上没有,可以把这个方法挂载到你的实例上试一下:

var app = new Vue({
    el:#app,
    data:{
    },
    methods: {
        ms: window.ms
    }
})

vue 的 template.会自动在前面加上this,指向vm实例。

比如: @="fn". 那肯定就是这个组件的fn属性。
然后,你这个写在外边的是监听不到的,所以才会报这种问题。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题