this指向知多少

”this“在JavaScript中很常见,用起来也很”香“,每当咱们想访问一个值或者设置一个值,常会用到它,原生和框架都是,但它同时又让人困惑,使咱们写的代码达不到预期效果,甚至引发bug,本文咱们就来看看,this的指向遵循哪些规律。编程

this等于本身?

先看几个看似正常却错误的理解。数组

指向自身

好比下面这段代码:app

function add(num){
  //记录add被调用的次数
  this.count++;
  console.log("计数" + num)
}
add.count = 0;

add(1)
add(2)
console.log(add.count) // 0
复制代码

能够看到,结果跟预期的不同,count未发生改变。框架

实际上,这段代码无心间建立了一个全局变量 count,在执行add的时候,并未改动add的count。ide

指向所在做用域

仍是先看代码函数

function one(){
  var a = 2;
  this.two();
}
function two(){
  console.log(this.a)
}
one();  // a is not defined
复制代码

这种状况略特殊,由于并不老是错的,这里one调用two可以成功,但却没法所以利用two里面的this来访问one里面的a,这是作不到的。学习

看了两个错误用法,怎样是对的呢?接着往下看。ui

this是什么

this指代变量或者方法和对象之间的从属关系,但它是在运行时进行绑定,而不是编写时,这取决于函数调用时的多种条件,因此,this的绑定和函数声明的位置无关,取决于函数调用的方式this

上面这句话不难理解,由于在哪调用函数彷佛显而易见,其实否则,在某些编程模式下,调用位置可能被隐藏,这时咱们就要顺着调用的路径找到调用位置,而后判断它符合哪一种绑定规则。编码

绑定规则

默认绑定

function get(){
  console.log(this.a);
}

var a = 2;
get();  // 2
复制代码

这种属于独立的函数调用,应用的是函数的默认绑定,this指向全局对象。

怎么判断默认绑定呢,由于它是使用不带任何修饰的函数引用进行调用的。但也要注意,只有在非严格模式下,this才绑定到全局,不然会提示undefined。

隐式绑定

看调用位置是否被某个对象拥有或者包含。

function get(){
  console.log(this.a);
}

var obj = {
  a:2,
  get
}
obj.get()  // 2
复制代码

这里咱们把get函数放在了obj内,但严格来讲,它仍不属于obj,只是被obj包含和调用,这个时候,this就指向了obj,this.a 就和 obj.a等价了。

只是这种状况须要注意的是,它有个就近的规则,就是它只属于离this最近的一层。像下面这段:

function get(){
  console.log(this.a);
}

var obj2 = {
  a:3,
  get
}

var obj1 = {
  a:2,
  obj2
}

obj1.obj2.get(); // 3
复制代码

这段代码中,this绑定在了obj2上,而不是一直向上追溯。

隐式丢失

一个常见的问题就是被隐式绑定的函数丢失了绑定对象,致使应用默认绑定,this就到了全局或者undefined。

第一种状况:方法传递

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
  get
}

var getNum = obj.get;

var a = "global";

getNum(); // global
复制代码

这里咱们会发现,咱们把obj的get方法给了getNum,却不是想象中的效果,其实咱们上面就说了,这里的get并不真实属于obj,而只是在调用时,this被绑定到了obj,不信你能够像下面这么改一下:

var a = "global";
var getNum = obj.get();
console.log(getNum)  // 3
复制代码

又是3,跟指望的一致,这就是细微差异致使结果的不一样。

第二种状况:回调

function get(){
  console.log(this.a);
}

function doGet(get){
  get();
}

var obj = {
  a:3,
  get
}
var a = "global";
doGet(obj.get);  // global
复制代码

这种状况跟上面的相似,由于参数传递就是一种隐式赋值,这个时候,执行get方法的时候,仍是全局的get,this绑定的就是全局对象了。

回调很经常使用,因此,由于回调函数而丢失this的状况也常见,甚至于,调用回调函数的函数可能会修改this,这就让代码行为更加地难以捕捉。

因此有什么好办法弥补这些不肯定问题的发生么。

显式绑定

经过上面的例子能够看到,要想把一个函数的this绑定到对象上,须要下面两个条件:

  • 函数做为对象的属性
  • 经过属性间接调用函数

若是不想这么作呢?

1、call/apply

JavaScript中的函数都有一些有用的特性,能够用来解决这个问题,好比:call 和 apply,由于能够直接指定this的绑定对象,因此称之为显式绑定。

可看以下代码:

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
}
get.call(obj); // 3
复制代码

经过get.call(),咱们把this强制绑定到了obj上。

从绑定this的角度看,call和apply的区别只是参数的形式不一样,call能够直接写参数,而apply须要以数组的形式传参。

遗憾的是,这仍没法解决上面提到的绑定丢失问题,但它的一种变通方法能够解决。

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
}

var getNum = function(){
  get.call(obj);
}

getNum()  // 2
复制代码

能够看到,这里建立了一个函数给getNum,而后函数内部进行显式地绑定,这样以来,不论再怎样调用getNum,get的this都不会变了。

2、bind

由于上面提到的绑定方式较为经常使用,ES5直接提供了一个内置的方法——Function.prototype.bind。

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
}

var getNum = get.bind(obj);

getNum()  // 3
复制代码

bind()会返回一个硬编码的新函数,把指定的参数设置为this的上下文并调用原函数。

3、API调用上下文

有一些库,或者ES新版本的内置函数,会提供一个可选参数,它的做用和bind()相似,确保回调函数使用指定的this,好比:

let arr = [1,2,3];

function get(el){
  console.log(el,this.id);
}

var obj = {
  id:"item",
}

arr.forEach(get,obj);
// 1 "item" 2 "item" 3 "item"
复制代码

相似的这些函数实际就是经过call()或者apply()实现了显式绑定,这样能够少写一些代码。

new绑定

咱们很熟悉的一句话:”没有对象就new一个“。

JavaScript 中有一些内置对象函数和自定义函数的构造调用,都会用到new关键字,它会经历如下过程:

  • 建立一个新对象
  • 新对象执行prototype链接
  • 新对象绑定到函数调用的this
  • 若是函数没有返回其余对象,就返回这个对象

就会有以下代码的效果:

function Get(a){
  this.a = a;
}
var getNum = new Get(2);
console.log(getNum.a)  // 2
复制代码

这是你们熟悉的用法,只是中间经历的过程不是表面那么简单,须要理解一下。

new是本文提到的最后一种能够影响this绑定的方法,下面看看这些规则的优先级。

规则优先级

显然的,默认绑定优先级是最低的,它能够轻易被改变,因此,咱们主要关心隐式绑定和显式绑定的优先级。可按照以下顺序进行判断:

  • 是否经过new调用来绑定,是,则绑定新建立的对象
var getNum = new Get(); 
复制代码
  • 是否直接或间接经过call、apply、bind绑定,是,绑定指定对象。
var getNum = get.call(obj);
复制代码
  • 是否在某个上下文对象中调用,是,则绑定那个上下文对象
var  getNum = obj.get();
复制代码
  • 若是都不是,使用默认绑定。

箭头函数

曾有人问我,ES6当中比较喜欢哪一个设计,我没多想,就说箭头函数,而后他问我,它跟以前的函数有什么不一样,this绑定就是它们的不一样之一。

箭头函数不使用this的那几种规则,而是根据外层函数/全局做用域来决定

function get(){
  return (a) =>{
    console.log(this.a);
  }
}
var obj1 = {
  a:3,
}

var obj2 = {
  a:2,
}

var getNum = get.call(obj1);
getNum.call(obj2); // 3,不是2
复制代码

由于get内部建立的箭头函数会捕获调用get时的this,getNum也会跟着一块儿绑定到obj1,且这种绑定没法被修改。

这种就像bind方法同样,确保函数被绑定到指定对象,它取代了传统的this机制。

实际上,在ES6以前,咱们经常使用另一种方法来实现它。

function get(){
  var self = this;  // 就是这里
  console.log(self.a);
}
var obj = {
  a:2
}
get.call(obj); //2
复制代码

总结

this绑定是个看似简单,又有一点复杂的东西,除了默认绑定和构造函数调用能够比较天然地理解,其余都绕了那么一点弯儿,但也不用怕,只要通过反复地思考和实践,掌握它们就是本能反应了。

本文尽可能全面,仍不免疏漏,鉴于篇幅太长会增长学习负担,索性没有说起某些特殊状况,多数场景已经够用,欢迎交流探讨。

下篇见!~

博客连接:this指向知多少

相关文章
相关标签/搜索