对this的简单理解
this的绑定时间
var length=10; function f(){ var length=20; console.log(this.length); } f(); //10
我们以这个简单的代码块为例子,可以看到this在方法f里面,那么this在什么时候绑定呢?顺着代码来,可以看到,我们这里是一个声明的函数方法(函数定义的三种方法之一)。先声明,然后分配地址空间(初始化),再定义函数体,那么this是在定义函数体后就绑定了吗,看结果,显然不是,那么this只会是在被调用的时候才绑定的。
我们接着深度剖析一下,为什么是在调用时也就是运行时绑定的呢?
我们知道JS是一个脚本语言,很多东西都是简化的,JS在声明函数方面,把构造函数/方法/lambda/全局函数等隐式地混合在一起了。
我们这里要知道——当声明方法时需要找到对应的构造函数,让这个方法内部的 this 指向构造函数生成的实例;当声明构造函数的时候,this 则指向构造函数生成的这个实例;当声明 lambda 表达式的时候,不存在 this 指向,这时候 this 应按照闭包的规程逐次向上层作用域找,没找到则抛出异常。
在函数声明的时候,JS是不能通过静态分析去确定这到底是构造函数还是lambda等等,所以只能在动态调用方法时去绑定this。
额外提一点,在lambda方面,JS在ES6也是引入了箭头函数,有兴趣的可以了解一下。
this的绑定方式
一共有四种绑定方式(优先级从低到高),我们一一介绍。
1、默认绑定
我们知道了this是在调用时绑定的,那么绑定谁呢也就是说指向谁呢?具体的在下一部分讲。
很容易理解,默认绑定就是说this会有一个默认的绑定对象。
2、隐式绑定
这里也容易理解,this隐式地改变了原来的默认绑定。简单来说,就是偷偷地改变了绑定对象,不再是默认的。
3、显式绑定
既然是显式的,那么就不再是偷偷的了,而是明目张胆地用一些方法去改变this绑定的对象。那么是哪些方法呢,这里简单介绍一下:call,apply,bind。
var a={ name:"shan", age:21, sex:"male" } var b={ name:"sun", getName:function (){ console.log("name:"+this.name+ " age:"+this.age); } } b.getName.call(a); //name:shan age:21
可以看到call把函数里this的绑定对象从b转到了a,并且执行了该函数。call和apply的区别就是,call后面的参数是一个参数队列,apply后面则是合在一起的一个数组;至于bind,它和call的区别就是不会执行函数,而是返回一个新函数对象。
4、new绑定
new关键字是用来实例化的,this自然是指向实例化后的实例对象。
class gn{ name="shan"; constructor() { console.log(this.name); } } var b=new gn(); //shan console.log(b.name); //shan
其实new关键字的实现原理也是涉及到了显式绑定方法,有兴趣的可以去了解一下。
this的指向
1、函数声明
function f(){ var length=100; console.log(this.length); } f(); //undefined
无对象调用时,this是一个默认绑定,指向的是默认的全局对象也就是window,还有下面这种情况:
var length=10; function f(){ var length=100; console.log(this.length); } f(); //10
这里涉及了一个数据声明的问题,有兴趣的可以去学习一下。
2、有对象调用
var a={ name:"shan", getName:function(){ console.log(this.name); } } a.getName(); //shan
有对象调用时,this隐式绑定了该对象。所以指向该对象,如上,返回的是shan。如下,可以对比一下:
var name="dan"; var a={ getName:function(){ console.log(this.name); } } a.getName(); //undefined
可见,this绑定的不再是默认的window对象。
3、函数表达式
var name="shan"; var a=function(){ console.log(this.name) } a();
这个也是无对象调用,所以是默认绑定,指向window。
4、实例化调用
这里参考上方的new绑定,this指向的就是实例化后的对象
var a=function(){ this.name="shan"; console.log(this.name); } var b=new a(); //shan console.log(b.name); //shan b.name="dan"; console.log(b.name); //dan
5、call、apply调用
这里参考上方的显式绑定,call、apply其实是一种上下文绑定
var a={ name:"shan", age:21, sex:"male" } var b={ name:"sun", getName:function (){ console.log("name:"+this.name+ " age:"+this.age); } } b.getNmae.apply(a); //name:shan age:21
6、箭头函数
var a={ name:"dan", getName:function (){ setTimeout(()=>console.log(this.name),100) } } a.getName();
箭头函数本身是没有this关键字的,但是它会继承父环境,在这里体现出来的就是它继承了getName函数的this,指向了a对象。