JS 深入浅出This指向(精简)


从原理出发

首先我们围绕耳熟能详的“this始终指向它的调用者”开始。这句结论虽然没有什么问题,但是说得过于笼统,还是得深入到背后的执行原理才能举一反三解决问题。

举个简单的例子:

var obj = {
  num: 2;
  foo: function () { console.log(this.num) }
};

var foo = obj.foo;
var num = 3;

obj.foo();//2

foo();//3

obj.foo();是对象obj调用自身的属性(方法)foothis指向调用者obj
②由var foo = obj.foo;获取函数,并用foo();在全局作用域中调用函数,所以this指向浏览器全局对象Window

内存数据结构的概述

JS之所以设计this,是因为其内存的数据结构的特征。

var obj = { foo:  5 };

①上面的对象赋值给变量,其实质是JS引擎现在内存中生成{foo: 5},然后再把该对象的内存地址赋值给变量obj。也就是说onj.foo实质上是先从obj获取对象的内存地址然后再从地址读取原始对象,最后返回对象属性foo的值。
②原始对象以词典结构保存,一个属性名对应一个属性描述对象。如下图,foo属性的描述对象就包含4个描述属性,而最重要的值保存在描述对象[[value]]中。
image
foo属性值为函数时,JS引擎同样会先将原始函数保存在内存中,然后把该函数的内存地址存放于foo属性的描述对象中的[[value]]里面。
image

从原理出发章节参考于JavaScript 的 this 原理——阮一峰的网络日志

为何造就this

我们需要JS在函数体内部可以引用当前环境的其他内部变量,就像这样:

var f = function () {- 
  console.log(this.x)//内部引用当前调用者变量x
};

var x = 1;//实质给全局对象Window添加属性 x 并赋值 1
var obj = {
  x: 2,//调用者内部变量
  f: f
}

f();//结果为1,执行于全局环境,所以 this.x 指向 Window.x
boj.f();//结果为2,执行于 obj 内部环境,所以 this.x 指向 obj.x

这就说明了造就this的目的就是为函数内部的语句指定当前运行环境,而当前运行环境不一定就是该函数内部环境哈。

this的扩展延伸

构造函数与this

function name () {
  this.fne = "yulin"
};

var a = new name();
console.log(a.fne);//yulin

关于这个构造函数这里先笼统提一些,new关键字会创建一个空对象name { },并且会将函数name()的保留在[[Prototype]]原型中而且会执行该函数并返回执行结果(这里有几种情况下文会提到),最后整个对象name { }赋值给变量aa即是对象实例。所以this.fne指向a.fne

thisreturn的问题

上文说到构造函数执行的返回结果的有几种情况:

  1. 返回对象时,this会指向返回的对象,null除外,否则指向起始调用者。
function name () {
  this.fne = "yulin";
  return {}
};
var a = new name();
console.log(a.fne);//undefined,指向 {}

function name () {
  this.fne = "yulin";
  return function () {}
};
var a = new name();
console.log(a.fne);//undefined,指向 function () {}

function name () {
  this.fne = "yulin";
  return null
};
var a = new name();
console.log(a.fne);//yulin,指向 null,null是特殊的对象不会更改 this

function name () {
  this.fne = "yulin";
  return undefined
};
var a = new name();
console.log(a.fne);//yulin,指向 name {}

更灵活的this指向方法

  1. 当我们需要在a对象中调用b对象的方法时,我们就需要用到call()方法指定目的运行环境。
const a = {
  name: 'yulin',
  fn: function (e, q) {
  console.log(this.name);
  console.log(e + q);
  }
};

const b = {
  name: 'yhh'
}

var x = a.fn
x.call(b, 2, 3);//yhh 5
  1. apply()方法与call()类型,但传入apply的第二个参数必须是数组,就像[1, 2, 3], [a , b, c]以及数组变量。
const a = {
  name: 'yulin',
  fn: function (e, q) {
  console.log(this.name);
  console.log(e + q);
  }
};

const b = {
  name: 'yhh'
}

var x = a.fn
x.apply(b, [2, 3]);//yhh 5

/*
以下代码效果一样
let arr = [2, 3];
x.apply(b, arr);
*/

请注意:a.call(null)a.apply(null)this指向都是Window
3. bind()call(), apply()完全不同,bind()改变this指向的同时还返回被调用的属性,方法。

const a = {
  name: 'yulin',
  fn: function (e, q) {
  console.log(this.name);
  console.log(e + q);
  }
};

const b = {
  name: 'yhh'
}

var x = a.fn

x.bind(b);
/*? (e, q) {
  console.log(this.name);
  console.log(e + q);
}*/

let y = x.bind(b);
y(2, 3);//yhh 5