解析javascript中的this

我们几乎每天都在和this打交道,先不去管他的概念,看一个小栗子,预热一下

this初探

⚠️约定:
因为使用let和const声明的变量没有挂载到全局变量下,所以在全局下声明的变量我们使用var

var heroName = '黄蓉'
function hero() {
  const heroName = "黄药师"
  console.log(this)//window
  console.log(this.heroName);//=>黄蓉
}

hero()
复制代码

我们执行hero(),通过打印我们发现

this指向的是window全局,所以this.heroName,应当在全局下查找heroName,所以输出=>黄蓉
当全局下没有heroName呢?this指向的是全局,全局没有就是没有,那就是undefined

function hero() {
  const heroName = "黄药师"
  console.log(this)//window
  console.log(this.heroName);//=>undefined
}

hero()
复制代码

我们继续看一个栗子

var heroName = '黄蓉'
function hero() {
  const heroName = "黄药师"
  console.log(this)//oHero
  console.log(this.heroName);//=>欧阳锋
}
const oHero={
  heroName:'欧阳锋',
  hero
}
oHero.hero()
复制代码

提示:在声明oHero中的hero中,使用了ES6标准,当key和value同名是,可以像上面这样简写
上面的栗子中,调用hero()的环境变了 ,根据输出我们发现,this的指向变了
this的指向是oHero对象,所以this.heroName=>欧阳锋,同样的当oHero下没有heroName,毫无悬念,就会输出=>undefined
如果前面两个小栗子搞懂了,我们就继续
一个栗子

var heroName = '黄蓉'

function hero() {
  console.log(this.heroName);
}
const oHero1 = {
  heroName:'郭靖',
  hero
};

hero()//=>黄蓉
oHero1.hero()//=>郭靖
复制代码

现在我们尝试着给this下一个定义:

this指的是函数运行时所在的环境,如果一个函数内部有this,this就会指向一个对象,指向哪个对象呢?取决于这个函数的执行环境。 补充(如果在全局下调用这个函数,则this指向全局,如果在某个对象下调用this,则this指向这个对象)

再看一个栗子

var heroName = '黄蓉'
function hero1() {
  const heroName = "黄药师"
  this.hero2()
}
function hero2() {
  console.log(this.heroName);
}
hero1()
复制代码

先不关心输出 我们在hero2中的console.log前加一个debugger,分别看下两个this的指向

 

 

 

 

 

可以发现。两个this均指向window 在脑中跑一遍这段代码,希望你还清醒 我们一起捋一下: 全局下执行hero1() 在hero1中,this指向的是全局=>window, 全局下存在hero2(), 在hero2中,this指向哪里?取决于是哪里调用的hero2(),是在hero1()中调用的hero2(), 所以hero2中的this指向hero1?输出=>黄药师? 但是我们通过debugger得出,hero2中的this指向的是window,所以输出必然是=>黄蓉 为什么呢? 我们再看一个栗子

function hero(){
  console.log(this.heroName);
}
var HERO1 = {
  heroName : '黄蓉',
  hero
}
var HERO2 = {
  heroName : '郭靖',
  HERO1
}
var HERO3 = {
  heroName : '郭靖',
  HERO2
}
HERO3.HERO2.HERO1.hero()//=>黄蓉
复制代码

我相信不会有人写出这样的代码,除了我! 这依然是个调用链 我们在输出前打一个断点

 

this指向了谁?不是最初的调用方HERO3,而是上一次,或者所最后一次=>HERO1 出现上面这种情况,是由函数调用方式决定的

 

  1. 作为一个函数进行的调用
  2. 作为一个对象的方法进行的调用
  3. 作为构造器进行的调用
  4. 通过apply()、call()、bind()函数进行的调用

作为一个函数调用this指向window,作为一个对象的方法调用,this指向当前调用的对象

作为一个函数进行调用

回顾之前的栗子

var heroName = '黄蓉'

function hero() {
  console.log(this.heroName);
}
const oHero1 = {
  heroName:'郭靖',
  hero
};

hero()//=>黄蓉
oHero1.hero()//=>郭靖
复制代码

hero(),是作为一个函数进行调用的,所以this指向window oHero1.hero(), 是作为一个函数的方法调用的,所以this指向oHero1对象

作为一个对象的方法进行调用

继续看一个栗子

function hero1(){
  this.hero2()
}
function hero2(){
  console.log(this.heroName);//Uncaught TypeError: this.hero2 is not a function
}
var HERO1 = {
  heroName : '黄蓉',
  hero1
}
var HERO2 = {
  heroName : '郭靖',
  HERO1
}
HERO2.HERO1.hero1()
复制代码

报错了

HERO1对象下调用的hero1(),所以hero1()中this指向的是HERO1,但是HERO1中不存在方法hero2(),所以报错 稍作修改

function hero1(){
  HERO3.hero2()
}
function hero2(){
  console.log(this.heroName);//=>郭靖
}
var HERO1 = {
  heroName : '黄蓉',
  hero1
}
var HERO2 = {
  heroName : '郭靖',
  HERO1
}
var HERO3 = {
  heroName : '郭靖',
  hero2
}
HERO2.HERO1.hero1()
复制代码

我们似乎总结出来一点小窍门,我们从最后的输出反推执行顺序 是谁调用的hero2(),是HERO3,所以hero2,中的this指向HERO3,所以输出是=>郭靖

上面说了前两种情况,其实(1)是(2)的一种特殊情况, 即当作为一个函数调用的时候,就是作为window对象的一个方法进行调用

一个小栗子进行简单回顾

var heroName = "郭靖";

function hero1() {
  console.log(this.heroName);
}
var HERO1 = {
  heroName: "黄蓉",
  hero1:hero1
};
HERO1.hero1();//=>黄蓉
hero1();//=>郭靖
复制代码

都是执行hero1()方法,同样都是输出this.heroName,却得到了不同的结果

this的指向是在函数执行的时候定义的,而不是在函数创建时定义的

基于上面的栗子,我们再看一个栗子

var heroObj = {
  heroName: "郭靖",
  heroFoo: {
    heroName: "黄蓉",
    hero: function() {
      console.log(this.heroName); 
    }
  }
};

heroObj.heroFoo.hero();//=>黄蓉

var h = heroObj.heroFoo.hero;
h();//=>undefined
复制代码

为了直观表达我们做一下改动

//部分代码省略
window.heroObj.heroFoo.hero();//=>黄蓉

var h = heroObj.heroFoo.hero;
window.h();//=>undefined
复制代码

输出的结果是相同的,我要表达的内容也很就很直观明显了, 这些对象和方法,最终都会挂载到window对象下,我们看这一句

window.heroObj.heroFoo.hero();//=>黄蓉
复制代码

hero()中的this是指向调用它的对象,那是哪个对象调用的hero()呢?

这是个问题

这是个问题?

这不是个问题

this指向的是最后调用它的对象

上面的栗子

window.heroObj.heroFoo.hero();//=>黄蓉
复制代码

最后调用hero()的是heroFoo,所以hero()中this指向了heroFoo对象

var h = heroObj.heroFoo.hero;
window.h();//=>undefined
复制代码

最后调用h()的是window对象,所以hero()中this指向了window对象

综上

this的指向是在函数执行的时候定义的,而不是在函数创建时定义的,this指向的是最后调用它的对象

下面讨论剩下的两种情况

作为构造器进行调用

之前在介绍面向对象的时候,谈一谈javascript面向对象,讨论过,使用构造函数来创建对象

function Hero(name, nickname, skill) {
  this.name = name;
  this.nickname = nickname;
  this.doSth = function() {
   return skill
  };
}
const hero = new Hero("黄药师", "东邪", "碧海潮生曲");
console.log(hero);
复制代码

使用自定义构造器构造一个对象需要四步

  1. 创建一个新对象
const hero = new Hero("黄药师", "东邪", "碧海潮生曲");
复制代码
  1. 将构造函数的作用域赋给新对象,设置原型链(因此this就指向了这个新对象hero)
hero.__proto__=Hero.prototype;
复制代码
  1. 执行构造函数Hero中的代码(为这个新对象hero添加属性和方法)
  2. 返回新对象hero

说this指向hero,Hero内所有针对this的操作,都会发生在hero上面

console.log(hero.name);//=>黄药师
console.log(hero.nickname);//=>东邪
console.log(hero.doSth());//=>碧海潮生曲
复制代码

构造函数是不需要return的, return在普通函数中也不是必须存在的,我们知道,在普通函数中,如果没有手动return ,会默认return undefined 但是如果在构造函数中使用了return,会存在一些坑 我们一起来填坑 我们把上面的栗子进行简化

function Hero(name) {
  this.heroName = name;
  return 123
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>郭靖
复制代码
function Hero(name) {
  this.heroName = name;
  return null
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>郭靖
复制代码
function Hero(name) {
  this.heroName = name;
  return undefined
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>郭靖
复制代码

不再进行一一列举,上面return的都是基本数据类型 继续看 数组类型

function Hero(name) {
  this.heroName = name;
  return []
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>undefined
复制代码

object类型

function Hero(name) {
  this.heroName = name;
   return {
          heroName: "黄蓉"
        };
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>黄蓉
复制代码

funciton类型

function Hero(name) {
  this.heroName = name;
   return  function(){}
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>undefined
复制代码

funciton升级

function Hero(name) {
  this.heroName = name;
  return (function() {
    return  {heroName:'黄蓉'}
  })()
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>黄蓉
复制代码

总结一下 构造函数会改版this的指向,指向通过new实例化出来的对象 构造函数中不需要return,当存在return时,以下几点需要注意

  1. 当return的是基本数据类型时,返回不变
  2. 当return的没有返回时,默认返回undefined,返回不变
  3. 当return的是引用类型时,返回的是这引用类型

通过apply()、call()、bind()进行调用

之前在讲面向对象的时候,对call进行过讨论,javascript 面向对象之一篇文章搞定call()方法 它们两个也是用来改变this的指向的,

为什么要改变this的指向呢?

call

看一个栗子

const hero1 = {
    name: "欧阳锋",
    doSth: function (skill) {
        console.log(`${this.name}学习${skill}`);
    }
};

const hero2 = {
    name: "洪七公"
};
hero1.doSth('九阴真经')//=>欧阳锋学习九阴真经
复制代码

执行了hero1对象下面的doSth方法,并传入参数“九阴真经”,最后输出=>欧阳锋学习九阴真经 结合上面的介绍讲解,我们知道,doSth中的this是指向的hero1,这个没问题吧

好了,现在hero2下面有一个“洪七公”,“洪七公”也想调用hero1下面的doSth方法,怎么办呢?

const hero1 = {
    name: "欧阳锋",
    doSth: function (skill) {
        console.log(`${this.name}学习${skill}`);
    }
};

const hero2 = {
    name: "洪七公"
};
hero1.doSth.call(hero2, "降龙十八掌");//=>洪七公学习降龙十八掌
复制代码

doSth是由hero1调用的,默认情况下doSth中的this指向的是hero1,但是使用了call,所以,this的指向变了,指向了call方法中的第一个参数hero2,

apply

apply呢?apply和call很是类似

const hero1 = {
  name: "欧阳锋",
  doSth: function(skill, favourite) {
    console.log(`${this.name}学习${skill}`);
    console.log(`${this.name}喜欢${favourite}`);
  }
};

const hero2 = {
  name: "洪七公"
};
hero1.doSth.apply(hero2, ["降龙十八掌", "吃鸡"]);
hero1.doSth.call(hero2, "降龙十八掌", "吃鸡");
复制代码

这两种写法等效,只是传入的参数格式略有不同

hero1.doSth.apply(hero2, ["降龙十八掌", "吃鸡"]);
hero1.doSth.call(hero2, "降龙十八掌", "吃鸡");
复制代码

bind

bind也可以改变this的指向,在用法上和call和apply略有不同,bind的使用更加灵活

const hero1 = {
	name: "欧阳锋",
	doSth: function(skill, favourite) {
		console.log(`${this.name}学习${skill}`);
		console.log(`${this.name}喜欢${favourite}`);
	}
};

const hero2 = {
	name: "洪七公"
};
const foo = hero1.doSth.bind(hero2, "降龙十八掌", "吃鸡");
foo();
复制代码

之前说的call和apply都是立即执行,而bind不会立即执行,需要手动执行,所以bind的使用更加灵活 不止于此 上面的foo方法在调用的时候可以额外的传入参数

const hero1 = {
  name: "欧阳锋",
  doSth: function(skill, favourite, q, n, b) {
    console.log(`${this.name}学习${skill}`);//=>洪七公学习降龙十八掌
    console.log(`${this.name}喜欢${favourite}`);//=>洪七公喜欢吃鸡
    console.log(q, n, b);//=>1 2 3
  }
};

const hero2 = {
  name: "洪七公"
};
const foo = hero1.doSth.bind(hero2, "降龙十八掌", "吃鸡");
foo(1, 2, 3);


作者:陌上寒
链接:https://juejin.im/post/5c47d922e51d4551e653b8c5
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值