初识JavaScript中的this

7 阅读5分钟

随记:JavaScript中的this

前言

小编学习前端的随记,个人理解,欢迎大佬纠错

正文

this关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

举个例子:

function baz() {
 // 当前调⽤栈是:baz
 // 因此,当前调⽤位置是全局作⽤域
 
 console.log( "baz" );
 bar(); // <-- bar的调⽤位置
}
function bar() {
 // 当前调⽤栈是:baz --> bar
 // 因此,当前调⽤位置在baz中
 
 console.log( "bar" );
 foo(); // <-- foo的调⽤位置
}
function foo() {
 // 当前调⽤栈是:baz --> bar --> foo
 // 因此,当前调⽤位置在bar中
 
 console.log( "foo" );
}
baz(); // <-- baz的调⽤位置

同时,this在函数执行过程中,this一旦被确定了,就不可以再更改

var a = 10;
var obj = {
 a: 20
}
function fn() {
 this = obj; // 修改this,运⾏后会报错
 console.log(this.a);
}
fn();

this的指向——也可以说是this的绑定,可以分为普通函数以及箭头函数两个板块

普通函数

谁调用,绑定谁

根据不同的使用场合,this有不同的值,主要分为默认绑定隐式绑定new绑定显示绑定

默认绑定
var name = 'Jenny';

function person() {
  return this.name;
}

console.log(person()); //Jenny

默认绑定的意思就是window绑定,为什么是输出Jenny?因为在全局环境下定义的person函数,并且直接调用的话,person()前会省略window,即window.person()

那么此时就很清晰明了了,person函数是window调用的,那么this就指向window。所以代码就变成了:

return window.name

也就是控制台会输出Jenny

注意:严格模式下,不能将全局对象window用于默认绑定,this会绑定到 undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。

隐式绑定
function test() {
  console.log(this.x);
}

var obj = {};
obj.x = 1;
obj.m = test;

obj.m(); // 1

上述函数中,test函数被赋值给了obj对象中的m属性,那么obj.m()这行代码,显然m函数是在被obj对象调用,此时this指向obj,那么代码也就变成了:

console.log(obj.x);

那么因此也就打印obj对象中的x属性,也就是1

var o = {
  a:10,
  b:{
    fn:function(){
      console.log(this.a); //undefined
    }
  }
}
o.b.fn();

this绑定遵循就近原则

上述代码中,尽管fn是被多层对象调用,但是this依旧只指向它的上一级对象。因此fn的上一级对象是bb中不存在a属性,因此返回undefined

这里再举一个特殊情况:

var o = {
  a:10,
  b:{
    a:12,
    fn:function(){
      console.log(this.a); //undefined
      console.log(this); //window
    }
  }
}
var j = o.b.fn;
j();

很奇怪吧,为什么this不指向b,反而跳过了b指向了window呢?

我们着重看这行代码:

var j = o.b.fn;

在这行代码中你看到了什么?

=,没错,这是一个赋值操作,相当于把fn赋值给了j,此时j自己就是一个函数,并且不再于oo.b有任何关系。所以当你执行j()时,相当于window.j(),也就是默认绑定,自然this指向了window

new绑定

通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象

function test() {
  this.x = 1;
}
var obj = new test();
obj.x // 1

上述代码之所以输出1,是因为new关键字会强行改变this的指向,令其指向这个实例对象obj

特殊情况:

new过程遇到return一个对象,此时this指向为返回的对象。

function fn() 
{ 
  this.user = 'xxx'; 
  return {}; 
}
var a = new fn(); 
console.log(a.user); //undefined

如果返回一个简单类型的时候,则this指向实例对象。

function fn() 
{ 
  this.user = 'xxx'; 
  return 1;
}
var a = new fn; 
console.log(a.user); //xxx

注意的是null虽然也是对象,但是此时this仍然指向实例对象。

function fn() 
{ 
  this.user = 'xxx'; 
  return null;
}
var a = new fn; 
console.log(a.user); //xxx
显示绑定

apply()call()bind() 是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时 this 指的就是这第⼀个参数

var x = 0;
function test() {
  console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply(obj) // 1

箭头函数

只看定义位置,this不变

先看一段代码:

const obj = {
  sayThis: () => {
    console.log(this);
  }
};
obj.sayThis(); // window
const globalSay = obj.sayThis;
globalSay(); // window 浏览器中的 global 对象

判断箭头函数的规则:

1.箭头函数没有自己的this,它只能蹭别人的this,然而能产生this的只有普通函数以及window,箭头函数会一直向上找,直到找到一个有this的大哥,随后箭头函数的this,就会指向这个大哥。
注意:这里我是进行了形象的解释,想要深入了解的话,可以去了解一下作用域。

那么我们来看这段代码,sayThis()是箭头函数,所以当它被调用时,会一直向上找,obj是对象,没有this,再向上找就找到了window,所以this指向的时window

let foo = () => console.log(this)
foo.call({ a:1 }); // window,call无效
2.箭头函数一旦定义,它的this就被永久锁定了。即使你用call或bind去修改,也会无效。

那么我再举一个特殊情况:

绑定事件监听

const button = document.getElementById('mngb');

button.addEventListener('click', ()=> {
  console.log(this === window) // true
})

我们想要this指向为点击的button,但是它却指向了window。我明白你们的疑问,箭头函数向外跳,不应该被addEventListener函数接住吗?

那么好,小编来解释一下个人理解:

我们要分清楚,在函数内部以及作为函数参数的情况,函数内部我们都知道,但是如果是作为参数的话,代码可以理解为:

const button = document.getElementById('mngb');

// 1.在全局定义一个箭头函数(此时它的this已锁定为window)
const myCallback = () => {
  console.log(this === window)
}
// 2.把这个已经锁定this的函数传给addEventListener
button.addEventListener('click', myCallback);

原型上添加方法

Cat.prototype.sayName = () => {
  console.log(this === window) //true
  return this.name
}

const cat = new Cat('mm');
cat.sayName()

箭头函数的this不得被修改,new失效;箭头函数在被定义时就已确定,sayName被定义时外面并没有函数包裹,所以它的this再向外跳就只能找到window