源码分析:vue和react组件事件绑定中的this

栏目: JavaScript · 发布时间: 8年前

内容简介:直接从问题开始吧。第一种情况代码:运行结果:

直接从问题开始吧。

第一种情况代码:

<template>
  <button  @click="sayHello">say hellow</button>
</template>
<script>
  export default {
    methods: {
      sayHello() {
        console.log('hello:',this);
      }
    }
  }
</script>
复制代码

运行结果:

源码分析:vue和react组件事件绑定中的this

第二种情况:

<template>
  <button  @click="sayHello">say hellow</button>
</template>
<script>
  export default {
    methods: {
      sayHello: () => {
        console.log('hello:',this);
      }
    }
  }
</script>
复制代码

运行结果:

源码分析:vue和react组件事件绑定中的this

你能解释为什么会这样么?

vue源码分析——从模板解析到运行时事件绑定

我们先通过源码来分析一下整个流程(vue@2.5.17的dist/vue.common.js)。

v-on的解析

分析事件绑定,先去找v-on的实现代码:

源码分析:vue和react组件事件绑定中的this

通过搜索,我定位了这样一段代码。

processAttrs这个函数是处理模板中的属性的,其中有个分支是处理 v-on指令的

v-on:click.native.stop="sayHello"
复制代码

这里的name就是click,value就是sayHello,而native和stop就是modifiers,el为传进来的当前解析的元素。

addHandler顾名思义就是给当前的xx事件绑定一个handler,我们接着去看addHandler的实现。

源码分析:vue和react组件事件绑定中的this

删掉了一些无关代码后的addHandler方法如图,开始是处理各种modifier,然后是创建一个newHandler,加到事件的handlers数组中去,因为我们这里只绑定了一个handler,所以走的else的分支。

到这,元素的click已经绑定了handler了。

模板编译流程

说起来,通过搜索定位到某段代码并不能吧流程看全,我们从模板编译的入口开始看。

你可以在vue@2.5.17的dist/vue.common.js文件的最后看到:

源码分析:vue和react组件事件绑定中的this

在Vue上挂了compile这个属性,而这个属性指向compileToFunctions,从名字可以看出,这个方法是把模板编译成函数的。

源码分析:vue和react组件事件绑定中的this

通过搜索,发现在这个方法属于ref$1这个对象,而这个对象是通过createCompiler方法创建的。

源码分析:vue和react组件事件绑定中的this

继续搜索,看到他是调用createCommpilerCreator来生成的,而createCommpilerCreator通过注释可以看到他是有针对ssr的特殊处理,这里我们不用管,看图中标出的3个地方,就是模板编译的3个阶段:parse、optimize、generate。parse是从模板编译成ast抽象语法树,ast抽象语法树优化(optimize)之后,通过generate来生成最终代码,可以看到返回的renderer就是我们生成的。这就是模板编译成render函数的过程。

handler代码生成

其实我们之前分析的processAttrs就是parse的部分,现在我们关注的是generate的部分,因为我们要去看handler生成的代码,

源码分析:vue和react组件事件绑定中的this

从根元素开始生成,继续去看genElement

源码分析:vue和react组件事件绑定中的this

可以看到处理了static、once、for、if等指令,处理了template,slot等特殊标签,然后判断了是不是组件,我们这里明显不是,所以走到了genData$2这个函数。

源码分析:vue和react组件事件绑定中的this

这个函数是处理vnode的各种属性,我们这里只关注events的handler,所以继续去看genHandlers

源码分析:vue和react组件事件绑定中的this

这里只是对native和非native的events分别做了处理,加上了前缀on或者nativeOn,继续去看genHandler

源码分析:vue和react组件事件绑定中的this

我们没有modifier所以,是这个分支。

我们知道v-bind的值可以是

sayHello
function() {alert('hello');}   或   () => {alert('hello');}
sayHello($event);
复制代码

这3种方式吧,通过正则表达式判断出了方法路径(methodPath),函数表达式(functionExpression)这两种方式。

源码分析:vue和react组件事件绑定中的this

(其实看到正则表达式我就犯晕,感叹想要写模板解析必须正则表达式得很熟啊)

我们开始的sayHello属于方法路径的方式,所以直接返回sayHello。

至此,我们已经完成了模板到render函数的解析,判断出了最终生成的handler就是sayHello,没做任何处理。

vdom的运行时解析

接下来就是render函数渲染的vdom的解析生成真实dom了,我们只需要看事件绑定的部分,所以搜索addEventListener,然后你会发现这段代码。

源码分析:vue和react组件事件绑定中的this

这貌似是我们要找的代码,往上查找调用add$1的地方,

源码分析:vue和react组件事件绑定中的this

看到updateDOMListeners这个函数名,就可以确定找对了,这里调用了updateListeners函数,

源码分析:vue和react组件事件绑定中的this

这里的on就是handlers,而cur就是具体的handler,也就是说我们sayHello就是在这里绑定到了元素上。

vue组件初始化

但是我们还没有看到对this的处理啊,这是因为我们之分析了模板和render部分,没有分析组件对option中methods的处理。

源码分析:vue和react组件事件绑定中的this

这里的initMixin就是初始化的过程,会处理options

源码分析:vue和react组件事件绑定中的this

点进去以后,你会发现

源码分析:vue和react组件事件绑定中的this

这说明vue对state的定义就是包含data、props、computed、methods和watch的,这和react的state定义差别挺大。

我们看initMethods部分,这部分是我们所关心的。

源码分析:vue和react组件事件绑定中的this

看到这里已经找到我们想要的东西了:组件在init的时候会把所有methods都给绑定到vm上。

箭头函数的解析

还记得我们该开始的问题是什么吗?

刚开始的问题是为什么this打印的是undefined,这里已经绑定到this了啊。

这时候我们打开babel官网,输入这段代码:

源码分析:vue和react组件事件绑定中的this

你发现箭头函数的this是绑定到当前上下文,也就是父级函数运行时的this的,而我们的组件定义根本没父级函数。

<script>
    export default {
       methods : {
           sayHello: () => {
                   console.log('hello:', this);
           }
      }
   }
</script>
复制代码

他的this指向全局对象,在严格模式下,全局对象就是undfined。

源码分析:vue和react组件事件绑定中的this

用babel repl验证一下也是这样。

分析过程总结

分析到这里,我们已经定位到问题是因为箭头函数的this绑定到了全局对象,而全局独享在严格模式下为undefined导致。

虽然对于模板编译的流程和组件初始化过程的分析没多大必要,但是通过分析,我们知道了3种handler定义方式(方法路径、函数表达式、函数体)最终生成的函数代码的区别,以及vue组件初始化的时候会自动把methods的this绑定到组件实例。

源码分析:vue和react组件事件绑定中的this

简化的运行流程如图所示,我们先是分析了模板编译的流程,主要是parse阶段(把模板解析成ast)和generate阶段(根据ast生成vdom),然后分析了vdom运行时绑定dom handler的过程,之后又分析了组件初始化时对methods的处理。分析的流程不代表运行的流程,运行时还是从组件初始化开始的。

react组件的使用箭头函数定义

class Hello extends React.Component {
  sayHello = () => {
  	console.log('hello', this);
  }
  render() {
  	return <button onClick={this.sayHello}>say hello</button>;
  }
}

ReactDOM.render(
  <Hello/>,
  document.getElementById('container')
);
复制代码

你觉得上面的写法有问题么

源码分析:vue和react组件事件绑定中的this

是没有问题的,那为什么vue中有问题呢,就算vue使用render函数还是有问题,不信你可以试下下面的代码。

<script>
  export default {
    methods:{
      sayHello: () => {
        console.log('hello:', this);
      }
    },
    render:function (createElement) {
      return createElement('button', {
        on: {
          click: this.sayHello
        }
      },'say Hello')
    }
  }
</script>
复制代码

打印的this依然是undefined。

为什么同样的逻辑在vue和react里表现不一样呢?

其实,是因为写法的不一样,react的组件定义只是类的声明,创建实例后才会运行,而创建组件实例时,会初始化this,这时候this自然指向组件对象。而vue的组件定义是对象式的写法,在定义的过程中箭头函数就已经绑定到了当前上下文,而这时候组件还没创建,这时候this就是undefined。

所以,react组件的定义时方法可以使用箭头函数,而vue的组件定义时methods不可以使用箭头函数。

java和js中this绑定的区别

java是纯面向对象的语言,通过new + 类的构造器的方式创建出对象以后,对象的方法里this永远指向该对象,也就是对象在创建好的那一刻,this就永远固定了。

源码分析:vue和react组件事件绑定中的this

js既有面向对象的成分,也支持面向过程的写法,在js里函数作为一种对象类型而存在。这就导致了函数时可以被多个对象引用的,并且也可以作为一种变量而存在。

源码分析:vue和react组件事件绑定中的this

java从机制上保证了方法是只属于一个类的对象的,没法被别的类或变量共享,this自然永远不变。而js因为把函数当作一种对象类型,自然也就可以被多个对象或变量共享,那么this就只能在运行时动态确定了。

java就像封建社会,方法是一辈子只能嫁给一个类,this永远不变,而js就像现代社会,函数是可以随时改变所属对象的,需要运行时才能确定。

也正因为这样的语言特性,使得this成为了js开发无处不在的一个问题。

总结

通过vue源码的模板编译和组件初始化时methods的处理,以及babel对箭头函数的转译等方面进行分析,确定了vue组件中methods使用箭头函数写法,this为undefind的原因:对象式的定义方式下methods绑定到了全局对象,所以就算使用render函数替代模板也不能解决问题。

而react中使用箭头函数定义方法是没问题的,因为类式的声明写法,之后在创建对象时才会去解析执行,render时this已经指向组件对象了。

之后通过 java 中方法和js中方法的区别,通过内存结构图说明了为什么this是js中很常见的一个问题。

总之,因为js中函数是一种对象类型,在堆中分配空间,所以函数的指向是可以修改的,this指向只有在运行时才能确定。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

How to Build a Billion Dollar App

How to Build a Billion Dollar App

George Berkowski / Little, Brown Book Group / 2015-4-1 / USD 24.95

Apps have changed the way we communicate, shop, play, interact and travel and their phenomenal popularity has presented possibly the biggest business opportunity in history. In How to Build a Billi......一起来看看 《How to Build a Billion Dollar App》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具