深入理解js中的this
学过javascript的同学想必都听过对this指向的描述,谁调用就指向谁,但究竟是怎么回事,欢迎往下看。
先来看看以下例子:
1 | var obj = { |
解释:
obj.foo()摆明了是obj调用其属性对象foo,所以里面的this指向的就是obj这个对象。
对于foo()来说,等价于window.foo()。我们习惯性将全局变量window的属性直接以属性名展现。
JavaScript语言之所以有this的设计,跟内存里面的数据结果有关。
内存的数据结构
1 | var obj = {foo : 5}; |
上面的代码将一个对象赋值给变量obj
。js引擎会先在内存里面生成一个对象{foo:5},然后把这个对象的内存地址赋值给变量obj
。
也就是说,变量obj
是一个地址(reference)。后面如果要读取obj.foo
,引擎先从obj
拿到内存地址,然后再从该地址读出原始的对象,返回它的foo
属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo
属性,实际上是以下面的形式保存的。
1 | foo: { |
注意:foo
属性的值保存在属性描述对象value
属性里。
若属性的值是一个函数呢
1 | var obj = { foo: function () {} }; |
这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo
属性的value
属性。
由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。
1 | var f = function () {console.log(this);}; |
js 允许在函数体内部,引用当前环境的其他变量。
而this
的设计目的就是在函数体内部,指代函数当前的运行环境。
this的实例讲解
1 | var o = { |
o.b.fn()因为函数fn()是被b调用,所以this指向b,this.a的值自然是b.a
由此我们可以总结出几种情况:
情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。
情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
1 | var o = { |
尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。
还有一种比较特殊的情况
1 | var o = { |
这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。
this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window。
构造函数版this
1 | function Fn(){ |
这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,为什么我说a是对象,因为用了new关键字就是创建一个对象实例,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。
当this遇上return时
先对比一下几个例子:
返回值是一个对象
1 | function fn() |
返回一个函数
1 | function fn() |
返回普通值
1 | function fn() |
由此可知:
如果返回值是一个对象/函数,那么this指向的就是那个返回的对象/函数,如果返回值不是一个对象/函数那么this还是指向函数的实例。
不过有一点需要注意就是:
虽然null也是对象,但this还是指向那个函数的实例
1 | function fn() |
知识点补充:
1.在严格版中的默认的this不再是window,而是undefined。
2.new操作符会改变函数this的指向问题,虽然我们上面讲解过了,但是并没有深入的讨论这个问题,网上也很少说,所以在这里有必要说一下。
1 | var num = 1; |
为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
我们来看看new时发生的4个阶段。
1 | var Func = function(){}; |
创建一个空对象
1
var obj = new Func();
设置原型链
1
obj._proto_ = Func.prototype;
让Func中的this指向obj,并执行Func的函数体。
1
var res = Func.call(obj);
判断Func的返回值类型
如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。
1
2
3
4
5
6if (typeof(result) == "object"){
func=result;
}
else{
func=obj;;
}
vue中的this及其作用域
这是vue文档里的原话:
All lifecycle hooks are called with their ‘this’ context pointing to the Vue instance invoking it.
意思是:在Vue所有的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例,即(new Vue)。
箭头函数中的this
箭头函数没有自己的this和作用域,它的this是继承来的;默认指向在定义时他所处的对象(宿主环境),而不是执行时的对象,定义时,它的对象环境可能是window;箭头函数可以方便让我们在setTimeout,setInterval中方便的使用this。
1 | var app = new Vue({ |
第一个输出“hello”,第二个输出“你好”。说明showMessage1()中的this指向的是window,showMessage()指向vue实例组件。
绑定vue实例到this的方法
为防止this指向出现歧义,有两种方法绑定this。
使用bind
1 | showMessage:function(){ |
对setTimeout()里的匿名函数使用bind()绑定到vue实例的this。这样在匿名函数中的this也是vue实例了。
把vue实例的this赋值给另一个变量再使用。
1 | showMessage:function(){ |
总结:
对于普通函数(包括匿名函数),this指向的是直接调用者,在非严格状态下,如果没有直接调用者,this指向window。在严格状态下,如果没有直接调用者,this为undefined。
箭头函数没有自己的作用域,在他内部使用的this是有它的宿主环境决定。
使用call,apply,bind绑定的,this指向绑定的对象。
this绑定的优先级
this绑定的优先级:
new > bind > call(apply) > obj.func() > 默认绑定。
1 | var obj = {}; |
obj.log = console.log
为obj对象创建了一个函数(即console.log)的引用log,因为这是个函数的引用,所以想要执行有两种方法,一种是直接加上(),另一种如上转变this指向。而obj.log.call(console,this)
中的第二个参数是从外部传进去的,和函数内的this对象无任何关系。