This

449 阅读11分钟

this总是指向调用者(this关键字总是指向函数所在的当前对象)

  1. 没有明确调用者时,this指向window
  2. 有明确调用者时,this指向调用者
  3. 使用new调用时,this指向new出来的对象
  4. 箭头函数里面的this在它申明时确定,跟他当前作用域的this一样
  5. DOM事件回调里面,this指向绑定事件的对象
  6. 严格模式下this是undefined

JS中的this并不是一个确定的值,在不同情况下有不同的指向。

this关键字则是用来引用函数执行时的上下文对象(所以在定义这个变量时,其实是不知道它真正的值的,只有运行时才能确定他的值),即调用该函数的那个对象(当前的实例)。

作用域与this的关联:作用域规定了变量的可访问范围,而this则指示了函数执行时的上下文对象,两者共同构建了JavaScript代码的执行环境逻辑。

作用域:作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

我们来看看如下代码:

function func() {
  this.name = "小小飞";
  console.log(this);    // 看一下this是啥
}

这个方法很简单,只是给this添加了一个name属性,我们把这个方法复制到Chrome调试工具看下结果:

image-20200226153116734

上图中我们直接调用了func(),发现this指向的是window,name属性添加到了window上。

分类

使用new调用时,this指向new出来的对象

这个规则其实是JS面向对象的一部分,JS使用了一种很曲折的方式来支持面向对象。当你用new来执行一个函数时,这个函数就变成了一个类,new关键字会返回一个类的实例给你,这个函数会充当构造函数的角色。作为面向对象的构造函数,必须要有能够给实例初始化属性的能力,所以构造函数里面必须要有某种机制来操作生成的实例,这种机制就是this。让this指向生成的实例就可以通过this来操作实例了。关于JS的面向对象更详细的解释可以看这篇文章。

this的这种特性还有一些妙用。一个函数可以直接调用,也可以用new调用,那假如我只想使用者通过new调用有没有办法呢?下图截取自Vue源码:

image-20200226160322071

Vue巧妙利用了this的特性,通过检查this是不是Vue的一个实例来检测使用者是通过new调用的还是直接调用的。

没有明确调用者时,this指向window

这个其实在最开始的例子就讲过了,那里没有明确调用者,this指向的是window。我们这里讲另外一个例子,函数里面的函数,this指向谁?

function func() {
  function func2() {
    console.log('this:', this);   // 这里的this指向谁?
  }
  func2();
}

直接执行:

image-20200226162443098

使用new执行:

image-20200226162626673

无论是直接执行,还是使用new执行,this的值都指向的window。直接执行时很好理解,因为没有明确调用者,那this自然就是window。需要注意的是使用new时,只有被new的func才是构造函数,他的this指向new出来的对象,他里面的函数的this还是指向window

有明确调用者时,this指向调用者

看这个例子:

var obj = {
  myName: "小小飞",
  func: function() {
    console.log(this.myName);
  }
}

obj.func();    // 小小飞

上述例子很好理解,因为调用者是obj,所以func里面的this就指向obj,this.myName就是obj.myName。其实这一条和上一条可以合在一起,没有明确调用者时其实隐含的调用者就是window,所以经常有人说this总是指向调用者

下面我们将这个例子稍微改一下:

var myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: function() {
    console.log(this.myName);
  }
}

var anotherFunc = obj.func;

anotherFunc();   // 输出是啥?

这里的输出应该是“大飞哥”,因为虽然anotherFunc的函数体跟obj.func一样,但是他的执行环境不一样,他其实没有明确的调用者,或者说调用者是window。这里的this.myName其实是window.myName,也就是“大飞哥”。

我们将这个例子再改一下:

let myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: function() {
    console.log(this.myName);
  }
}

var anotherFunc = obj.func;

anotherFunc();   // 输出是啥?

这次我们只是将第一个var改成了let,但是我们的输出却变成了undefined。这是因为let,const定义变量,即使在最外层也不会变成window的属性,只有var定义的变量才会成为window的属性。

箭头函数里面的this在它申明时确定,跟他当前作用域的this一样。

这句话的意思是箭头函数本身并不具有this,箭头函数在被申明时确定this,这时候他会直接将当前作用域的this作为自己的this。还是之前的例子我们将函数改为箭头函数:

var myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: () => {
    console.log(this.myName);
  }
}

var anotherFunc = obj.func;

obj.func();      // 大飞哥
anotherFunc();   // 大飞哥

上述代码里面的obj.func()输出也是“大飞哥”,是因为obj在创建时申明了箭头函数,这时候箭头函数会去寻找当前作用域,因为obj是一个对象,并不是作用域,所以这里的作用域是window,this也就是window了。

再来看一个例子:

var myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: function () {
    return {
      getName: () => {
        console.log(this.myName);
      }
    }
  }
}

var anotherFunc = obj.func().getName;

obj.func().getName();      // 小小飞
anotherFunc();   // 小小飞

两个输出都是“小小飞”,obj.func().getName()输出“小小飞”很好理解,这里箭头函数是在obj.func()的返回值里申明的,这时他的this其实就是func()的this,因为他是被obj调用的,所以this指向obj。

那为什么anotherFunc()输出也是“小小飞”呢?这是因为anotherFunc()输出的this,其实在anotherFunc赋值时就确定了:

  1. var anotherFunc = obj.func().getName;其实是先执行了obj.func()
  2. 执行obj.func()的时候getName箭头函数被申明。这时候箭头函数的this应该是当前作用域的this,也就是func()里面的this。 func()因为是被obj调用,所以this指向obj
  3. 调用anotherFunc时,其实this早就确定了,也就是obj,最终输出的是obj.myName

再来看一个构造函数里面的箭头函数,前面我们说了构造函数里面的函数,直接调用时,他的this指向window,但是如果这个函数时箭头函数呢:

var myName = "大飞哥";

function func() {
  this.myName = "小小飞";

  const getName = () => {
    console.log(this.myName);
  }

  getName();
}

new func(); // 输出啥?

这里输出的是“小小飞”,原理还是一样的,箭头函数在申明时this确定为当前作用域的this,在这里就是func的作用域,跟func的this一样指向new出来的实例。如果不用new,而是直接调用,这里的this就指向window。

DOM事件回调里面,this指向绑定事件的对象

function func(e) {
  console.log(this === e.currentTarget);   // 总是true
  console.log(this === e.target);          // 如果target等于currentTarget,这个就为true
}

const ele = document.getElementById('test');

ele.addEventListener('click', func);

currentTarget指的是绑定事件的DOM对象,target指的是触发事件的对象。如果回调是箭头函数,this是箭头函数申明时作用域的this。

严格模式下this是undefined

function func() {
  "use strict"
  console.log(this);
}

func();   // 输出是undefined

注意这里说的严格模式下this是undefined是指在函数体内部,如果本身就在全局作用域,this还是指向window。

<html>
  ...
  <script>
    "use strict"
    console.log(this);     // window
  </script>
  ...
</html>

改变函数中的this指向 call/apply/bind

call/apply/bind

共同点:

  1. 用来改变函数中this的指向
  2. 第一个参数都是this要指向的对象,也就是想指定的上下文(函数的每次调用都会拥有一个特殊值——本次调用的上下文(context)——这就是this关键字的值。)取值有以下4种情况:
    (1) 不传,或者传null,undefined, 函数中的this指向window对象
    (2) 传递另一个函数的函数名,函数中的this指向这个函数的引用
    (3) 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如 String、Number、Boolean
    (4) 传递一个对象,函数中的this指向这个对象
  3. 都可以利用后续参数传参

区别:

  1. call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里
  2. bind和call,apply最大的区别就是call,apply会立即执行方法,而bind并不会立即执行,而是会返回一个新方法供后面使用。
const obj = {
  myName: "大飞哥",
  func: function(age, gender) {
    console.log(`我的名字是${this.myName}, 我的年龄是${age},我是一个${gender}`);
  }
}

const obj2 = {
  myName: "小小飞"
}

obj.func.call(obj2, 18, "帅哥");  // 我的名字是小小飞, 我的年龄是18,我是一个帅哥

obj.func.apply(obj2, [18, "帅哥"]);   // 我的名字是小小飞, 我的年龄是18,我是一个帅哥

const func2 = obj.func.bind(obj2);   // 返回一个this改为obj2的新方法
func2(18, "帅哥");    // 我的名字是小小飞, 我的年龄是18,我是一个帅哥

bind函数也可以接收多个参数,第二个及以后的参数会作为新函数的参数传递进去,比如前面的bind也可以这样写:

const func3 = obj.func.bind(obj2, 18);   // 注意我们这里已经传了一个年龄参数
func3("帅哥");    
//注意这里只传了性别参数,年龄参数已经在func3里面了,
//输出还是:我的名字是小小飞, 我的年龄是18,我是一个帅哥

实现一个call

Function.prototype.call2 = function (object, ...args) {
  if (typeof this !== "function") {
    throw new Error("Must call with a function");
  }

  var object = object || window;
  object.fn = this;

  var result = object.fn(...args);
  delete object.fn;
  return result;
};

实现一个apply

Function.prototype.apply2 = function (object, arr) {
  if (typeof this !== "function") {
    throw new Error("Must call with a function");
  }
  
  var object = object || window;
  object.fn = this;

  var result;
  if (!arr) {
    result = object.fn();
  } else {
    result = object.fn(...arr);
  }
  delete object.fn;
  return result;
};

实现一个bind

Function.prototype.bind2 = function (object) {
  if (typeof this !== "function") {
    throw new Error("Must call with a function");
  }

  return (...args) => {
    return this.apply(object, args);
  };
};

总结

  1. 函数外面的this,即全局作用域的this指向window。
  2. 函数里面的this总是指向直接调用者。如果没有直接调用者,隐含的调用者是window。(对于作为一个对象的属性(或方法)的函数且作为对象的一个属性被调用时,this 关键字指向的是这个对象本身而非 global 对象。如果函数被赋值到了另一个变量中,并没有作为obj的一个属性被调用,那么this的值就是window)
  3. 使用new调用一个函数,这个函数即为构造函数。构造函数里面的this是和实例对象沟通的桥梁,他指向实例对象。
  4. 箭头函数里面的this在它申明时确定,跟他当前作用域的this一样。
  5. DOM事件回调里面,this指向绑定事件的对象(currentTarget),而不是触发事件的对象(target)。当然这两个可以是一样的。如果回调是箭头函数,请参考上一条,this是它申明时作用域的this。
  6. 严格模式下,函数里面的this指向undefined,函数外面(全局作用域)的this还是指向window。
  7. call和apply可以改变this,这两个方法会立即执行原方法,他们的区别是参数形式不一样。
  8. bind也可以修改this,但是他不会立即执行,而是返回一个修改了this的函数。

setTimeout中的this指向:函数中的this指向的是运行时的上下文,因此是window;而箭头函数会帮你把this绑定到声明时的上下文(等于当前作用域的this)(所以防抖、节流函数直接用箭头函数返回,就不用自己绑定this了)blog.csdn.net/weixin_4572…

引申例子

function a(){   
  console.log(this);   //输出函数a中的this对象
}       

function b(){}       

var c={name:"call"};    //定义对象c  

a.call();   //window
a.call(null);   //window
a.call(undefined);   //window
a.call(1);   //Number
a.call('');   //String
a.call(true);   //Boolean
a.call(b);   //function b(){}
a.call(c);   //Object
function class1(){   
  this.name=function(){   
    console.log("我是class1内的方法");   
  }   
}   
function class2(){ 
  class1.call(this);  //此行代码执行后,当前的this指向了class1(也可以说class2继承了class1)   
}   

var f=new class2();   
f.name();   //调用的是class1内的方法,将class1的name方法交给class2使用
function eat(x,y){   
  console.log(x+y);   
}   
function drink(x,y){   
  console.log(x-y);   
}   
eat.call(drink,3,2);

输出:5

这个例子中的意思就是用 eat 来替换 drink,eat.call(drink,3,2) == eat(3,2) , 所以运行结果为:console.log(5);
注意:js 中的函数其实是对象,函数名是对 Function 对象的引用。

function Animal(){   
  this.name="animal";   
  this.showName=function(){   
    console.log(this.name);   
  }   
}   
function Dog(){   
  this.name="dog";   
}   
var animal=new Animal();   
var dog=new Dog();       

animal.showName.call(dog);

输出:dog

在上面的代码中,我们可以看到Dog里并没有showName方法,那为什么(this.name)的值是dog呢?

关键就在于最后一段代码(animal.showName.call(dog)),意思是把animal的方法放到dog上执行,也可以说,把animal 的showName()方法放到 dog上来执行,所以this.name 应该是 dog。

function Animal(name){   
  this.name=name;   
  this.showName=function(){   
    console.log(this.name);   
  }   
}   
function Dog(name){   
  Animal.call(this,name);   
}   
var dog=new Dog("Crazy dog");   
dog.showName();

输出:Crazy dog