JavaScript中的this

JavaScript中的this

介绍

JavaScript 中的 this 对初学者来说一直是一个棘手的问题,因为它的指向实在太灵活了,如果没有真的理解它,在面试或笔试中被问到 this 指向谁,就只能靠猜。不仅是面试,实际开发中用到 this 的场景也特别多,很多隐蔽的 bug 都有可能是它导致的,用好了 this,也能写出更简洁的代码。本文我们一起来学一学 this 吧~

this是什么

首先,学习一个知识,先要理解它是什么,在大多数面向对象语言中,this 表示当前对象的一个引用,而 JS 中的 this 是完全不同的概念。

我们查阅 MDN 文档可知,this 是当前执行上下文(globalfunctioneval)的一个属性。

也就是说,我们可以把 JS 中的 this 分为三种,分别是:

  • 全局上下文中的 this
  • 函数上下文中的 this
  • eval 上下文中的 this

关于 eval ,在 MDN 或很多书籍和文档中都建议永远不要使用它,我们就不讨论它了,接下来,我们将主要讨论全局上下文和函数上下文的 this

有一点需要注意的是,node 环境中的 thisweb 环境中的 this 是不同的,为了避免大家一开始接受太多概念,我们先只讨论 web 环境的 this

全局上下文的this

全局上下文的 this 指向非常明确,不管有没有启用严格模式,都指向 window 对象

对应代码如下:

<body>
  <script>
    console.log(this === window); // 输出 true
    ("use strict");
    console.log(this === window); // 输出 true
  </script>
</body>

运行上述代码可以看到,的确是不管有没有启用严格模式,全局上下文的 this 都指向 window 对象。

this 添加属性,就相当于给 window 添加属性,给 window 添加属性,就相当于给 this 添加属性,如下代码所示:

this.userName = "zhangsan";
window.age = 18;
console.log(this.age); // 输出 18
console.log(window.userName); // 输出 'zhangsan'

函数上下文的this

全局上下文的 this 很简单,记住无脑指向 window 就完事,复杂就复杂在函数上下文的 this,函数上下文中的 thisarguments 一样,就是函数的隐式参数,可以在任意函数中调用,它的指向不是固定不变的,取决于函数处于什么位置、以什么方式调用,可以总结成如下图:

在这里插入图片描述

全局上下文中的函数

直接调用全局上下文中的函数,this 指向默认情况下为 window

function fn() {
  console.log(this); // 输出 window
}
fn();

function fn() {
  let a = 1;
  console.log(this.a); // 输出 2
}
var a = 2;
fn();

但是在严格模式下,this 指向的就是 undefined

function fn() {
  "use strict";
  console.log(this); // 输出 undefined
}
fn();

对象中的函数

调用对象中的函数,this 指向为这个对象。

const obj = {
  a: 1,
  fn() {
    console.log("this :>> ", this); // 输出 obj
    console.log("this.a :>> ", this.a); // 输出 1
  },
};

obj.fn();

但是如果函数嵌套有函数,此时的 this 指向为 window,就非常令人迷惑。

const obj = {
  a: 1,
  fn() {
    return function () {
      console.log("this :>> ", this); // 输出 window
      console.log("this.a :>> ", this.a); // 输出 100
    };
  },
};

var a = 100;

obj.fn()();

其实可以这么理解:

obj.fn()();

等价于;

const temp = obj.fn(); // 定义一个临时变量来存储 obj.fn 返回的函数
temp(); // 执行这个函数

上面代码示例中的 temp 在运行时是处在 window 环境中的,所以 this 指向 window

箭头函数

遇到对象里有函数嵌套函数的情况,想要 this 指向这个对象,es6 之前,可以用一个临时变量 _this 来暂存 this,代码如下:

const obj = {
  a: 1,
  fn() {
    const _this = this;
    return function () {
      console.log("this :>> ", _this); // 输出 obj
      console.log("this.a :>> ", _this.a); // 输出 1
    };
  },
};

obj.fn()();

ES6 推出了箭头函数的概念,之后我们就可以使用箭头函数解决这个问题,代码如下:

const obj = {
  a: 1,
  fn() {
    return () => {
      console.log("this :>> ", this); // 输出 obj
      console.log("this.a :>> ", this.a); // 输出 1
    };
  },
};

obj.fn()();

对于普通函数来说,内部的 this 指向函数运行时所在的对象。对于箭头函数,它不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。所以这里 fn 中嵌套的匿名箭头函数中的 this,指向它作用域链的上一层的 this,也就是函数 fnthis,也就是 obj

构造函数

构造函数内,如果返回值是一个对象,this 指向这个对象,否则 this 指向新建的实例。

function Person(name) {
  this.name = name;
}
const p = new Person("zhangsan");
console.log(p.name); // 'zhangsan'


function Person(name) {
  this.name = name;
  return {
    name: "xxx",
  };
}
const p = new Person("zhangsan");
console.log(p.name); // 'xxx'


function Person(name) {
  this.name = name;
  return {};
}
const p = new Person("zhangsan");
console.log(p.name); // 'undefined'

如果有返回值,但是不是一个对象,this 还是指向实例。

function Person(name) {
  this.name = name;
  return 123;
}
const p = new Person("zhangsan");
console.log(p.name); // 'zhangsan'

显示改变函数上下文的this

了解了函数中的 this 指向后,我们可以使用 callapplybind 来显式改变函数中的 this 指向。

call

Function.prototype.call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

function fn() {
  console.log(this.name);
}

const obj = {
  name: "zhangsan",
};
fn.call(obj); // 指定 this 为 obj,输出 'zhangsan'

apply

Function.prototype.apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

applycall 的功能完全一样,只是传参形式不一样,call 是传多个参数,apply 是只传参数集合。

function add(x, y, z) {
  return this.x + this.y + this.z;
}

const obj = {
  x: 1,
  y: 2,
  z: 3,
};

console.log(add.call(obj, 1, 2, 3)); // 输出 6
console.log(add.apply(obj, [1, 2, 3])); // 输出 6,只是传参形式不同而已

bind

Function.prototype.bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

bindcallapply 的区别是,函数调用 callapply 会直接调用,而调用 bind 是创建一个新的函数,必须手动去再调用一次,才会生效。

function add(x, y, z) {
  return this.x + this.y + this.z;
}

const obj = {
  x: 1,
  y: 2,
  z: 3,
};

const add1 = add.bind(obj, 1, 2, 3); // bind 会返回一个新的函数
console.log(add1()); // 执行新的函数,输出 6

练习

第 1 题

function fn() {
  console.log(this); // ?
}

fn();

第 2 题

function fn() {
  "use strict";
  console.log(this); // ?
}

fn();

第 3 题

const obj = {
  userName: "zhangsan",
  fn() {
    console.log(this); // ?
    console.log(this.userName); // ?
  },
};

obj.fn();

第 4 题

const obj = {
  userName: "zhangsan",
  fn() {
    console.log(this); // ?
    console.log(this.userName); // ?
  },
};

const fn = obj.fn;

fn();

第 5 题

const person = {
  userName: "zhangsan",
  wife: {
    userName: "xxx",
    fn() {
      console.log(this); // ?
      console.log(this.userName); // ?
    },
  },
};

person.wife.fn();

第 6 题

const o1 = {
  text: "o1",
  fn() {
    return this.text;
  },
};

const o2 = {
  text: "o2",
  fn() {
    return o1.fn();
  },
};

const o3 = {
  text: "o3",
  fn() {
    var fn = o1.fn;
    return fn();
  },
};

console.log(o1.fn()); // ?
console.log(o2.fn()); // ?
console.log(o3.fn()); // ?

第 7 题

const o1 = {
  text: "o1",
  fn() {
    return this.text;
  },
};

const o2 = {
  text: "o2",
  fn: o1.fn,
};

console.log(o1.fn()); // ?
console.log(o2.fn()); // ?

第 8 题

const foo = {
  name: "zhangsan",
  sayName() {
    console.log(this.name);
  },
};

const bar = {
  name: "xxx",
};

foo.sayName.call(bar); // ?
foo.sayName.apply(bar); // ?
foo.sayName.bind(bar)(); // ?

第 9 题

function Person1(name) {
  this.name = name;
}
function Person2(name) {
  this.name = name;
  return {};
}
function Person3(name) {
  this.name = name;
  return {
    name: "xxx",
  };
}
function Person4(name) {
  this.name = name;
  return 1;
}

const p1 = new Person1("zhangsan");
const p2 = new Person2("zhangsan");
const p3 = new Person3("zhangsan");
const p4 = new Person4("zhangsan");

console.log(p1.name); // ?
console.log(p2.name); // ?
console.log(p3.name); // ?
console.log(p4.name); // ?

第 10 题

var userName = "xxx";
const obj1 = {
  userName: "zhangsan",
  fn() {
    setTimeout(function () {
      console.log(this.userName); // ?
    });
  },
};

const obj2 = {
  userName: "zhangsan",
  fn() {
    setTimeout(() => {
      console.log(this.userName); // ?
    });
  },
};

obj1.fn();
obj2.fn();

第 11 题

var userName = "xxx";
const obj1 = {
  userName: "lin",
  fn() {
    return () => {
      console.log(this.userName); // ?
    };
  },
};

const obj2 = {
  userName: "lin",
  fn() {
    return () => {
      return () => {
        console.log(this.userName); // ?
      };
    };
  },
};

obj1.fn()();
obj2.fn()()();

第 12 题

function foo() {
  return () => {
    console.log(this.a);
  };
}

const obj1 = {
  a: 2,
};

const obj2 = {
  a: 3,
};

const bar = foo.call(obj1); // ?
bar.call(obj2); // ?

第 13 题

var a = 100;
const foo = () => () => {
  console.log(this.a);
};

const obj1 = {
  a: 2,
};

const obj2 = {
  a: 3,
};

const bar = foo.call(obj1); // ?
bar.call(obj2); // ?

如果你对上面的几道练习题都可以完成,则基本对该知识点掌握了。具体的题目答案再评论区。有啥不懂不理解的也欢迎在评论区进行提问和讨论。

总结

最后,我们再回顾一下 this 的具体指向,如果是全局上下文的 this,就指向 window,如果是函数上下文的 this,指向不是固定不变的,取决于函数处于什么位置、以什么方式调用,牢记下面这张图,我们本文举的所有例子都可以通过这张图找到答案。

在这里插入图片描述

当然,还有一个优先级的概念,优先级是 new 调用 > callapplybind 调用 > 对象上的函数调用 > 普通函数调用,一般面试不会考得这么细,同学们可以去自行敲代码实践一下,记住上面那张图,足够了。文章内容来源

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值