一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 全局执行上下文中的 this
四 函数执行上下文中的 this
 4.1 通过 call/bind/apply 扭转 this
 4.2 通过对象调用办法设置
 4.3 通过构造函数中设置
五 this 设计缺点和应答计划
 5.1 嵌套函数中的 this 不会从外层函数中继承
 5.2 一般函数中 this 指向全局对象 window
六 React 中 this 指向
 6.1 解决方案一:提前 bind 绑定 this
 6.2 解决方案二:调用时 bind 绑定 this
 6.3 解决方案三:返回一个箭头函数
 6.4 解决方案四:将调用办法变成箭头函数(失败)
 6.5 React 的 this 指向理论内容
 6.6 React 的 this 指向解决方案
七 小结
八 题目
 8.1 this 题目解析 5 步曲
 8.2 let/const 的 this
 8.3 箭头函数的 this
 8.4 求输入后果
 8.5 隐式绑定失落问题
  8.5.1 求输入后果
  8.5.2 求输入后果
 8.6 显示绑定问题
  8.6.1 求输入后果
  8.6.2 求输入后果
 8.7 求输入后果
 8.8 论述题
九 参考文献

二 前言

返回目录

先记住 1 句话:

  • this 永远指向最初调用它的那个对象

再牢记 2 句话:

  • 一般函数中 this 的指向,是 this 执行时的上下文
  • 箭头函数中 this 的指向,是 this 定义时的上下文

作用域链和 this 是两套不同的零碎,它们之间根本没太多分割。

this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this

执行上下文分为 3 种:

  • 全局执行上下文
  • 函数执行上下文
  • eval 执行上下文
留神这里是浏览器中的 this,和 Node 中的 this 是不一样的。

三 全局执行上下文中的 this

返回目录

在 Chrome 控制台中输出:this,你会看到答案:

Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

全局执行上下文中的 this 是指向 Window 的。

function foo() {  console.log(this);}foo();

这段代码也是输入 Window,为什么?

记住 this 就是谁调用它就指向谁。

咱们在全局对象中调用 foo,实际上就相当于 window.foo() 的一个调用,那么就是指向 Window

在执行下面代码之后,其实小伙伴能够在 Chrome 的控制台输出 window,会看到外面存在 foo() 办法。

留神这里是非严格模式。严格模式下的全局对象是 undefined,那就会报谬误 Uncaught TypeError: Cannot read property 'name' of undefined

四 函数执行上下文中的 this

返回目录

在下面咱们晓得,个别的调用办法,是调用 window 上的办法。

那怎么获取以后函数的 this 呢?

4.1 通过 call/bind/apply 扭转 this

返回目录
this.myName = 'jsliang';let foo = function() {  this.myName = 'zhazhaliang';}foo();console.log(window.myName); // 输入啥?console.log(foo.myName); // 输入啥?

这时候的 this 指向 window,所以输入后果为;

  • zhazhaliang
  • undefined

通过 call 绑定后:

this.myName = 'jsliang';let foo = function() {  this.myName = 'zhazhaliang';}foo.call(foo);console.log(window.myName); // 输入啥?console.log(foo.myName); // 输入啥?

输入后果为:

  • jsliang
  • zhazhaliang

当然你也能够换成 applybind,这里不做累述。

4.2 通过对象调用办法设置

返回目录

应用对象来调用其外部的一个办法,该办法的 this 是指向对象自身的。

  • 案例 1
let myObj = {  name: 'jsliang',  showThis: function() {    console.log(this.name);  },};myObj.showThis(); // 输入啥?

答案:输入 jsliang

咱们要时刻牢记:谁调用的指向谁。这里是通过 myObj 进行的一个调用,所以此刻的 this 指向 myObj。而 myObj 外面存有 name: jsliang,所以输入 jsliang

当然,咱们要有自知之明:

  • 案例 2
let myObj = {  myName: 'jsliang',  showThis: function() {    console.log(this.myName);  },};let foo = myObj.showThis;foo(); // 输入啥?

这时候它又变成 window 指向了,此刻 let foo = myObj.showThis 只是一个定义,真正执行是在 foo()。那么此刻 foo() 是咋搞的呢?window.foo() 啊!毋庸置疑输入 undefined

  • 案例 3
let myObj = {  name: 'jsliang',  showThis: function() {    console.log(this.name);  },};let foo = myObj.showThis;foo(); // 输入啥?

一般来说,这段代码输入应该是 undefined

然而,这里须要留神的是,window.name 是以后 window 的名称,它是 window.open() 关上新网页这个办法的第二个参数的值。

所以这里输入的 window.name 是个空值 '',或者以后存在的 window 的名称。

jsliang 通过一个例子带小伙伴们看看这个是怎么来的:

index.html
<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <meta http-equiv="X-UA-Compatible" content="ie=edge">  <title>jsliang</title></head><body>  <button class="btn">关上新网页</button>  <script>    (function() {      const btn = document.querySelector('.btn');      btn.onclick = function() {        window.open('index.html', 'jsliang 的网页');      }    })()  </script></body></html>

在新关上的网页中的控制台,输出 window.name,获取 jsliang 的网页

论断:

  • 在全局环境中调用一个函数,函数外部的 this 指向的是全局变量 window
  • 通过一个对象来调用其外部的一个办法,该办法的执行上下文中的 this 指向对象自身。

4.3 通过构造函数中设置

返回目录
this.name = 'jsliang';let Foo = function() {  this.name = 'zhazhaliang';}let foo = new Foo();console.log(foo.name); // 输入啥?console.log(window.name); // 输入啥?

答案是:

  • zhazhaliang
  • jsliang

在将这个答案的缘故之前,咱们看下 new Foo() 中,JavaScript 引擎做了什么事:

  • 首先创立一个空对象 tempObj = {}
  • 接着调用 Foo.apply 办法,将 tempObj 作为 apply 办法的参数,这样当 Foo 的执行上下文创立时,它的 this 就指向 tempObj 对象。
  • 而后执行 Foo 函数,此时的 Foo 函数执行上下文中的 this 指向了 tempObj 对象。
  • 最初返回 tempObj 对象。
function myNew(func, ...args) {  const tempObj = {};  func.apply(tempObj, args);  return tempObj;}this.name = 'jsliang';let Foo = function(name, age) {  this.name = name;  this.age = age;}let foo = myNew(Foo, 'zhazhaliang', 25);console.log(foo.name); // 输入啥?console.log(foo.age); // 输入啥?console.log(window.name); // 输入啥?

如上,咱们能够看到此时 this 是属于 tempObj 的,绑定到 foo 下来了,从而获取到:

  • zhazhaliang
  • 25
  • jsliang

当然,理解到这里,咱们还是欠缺下 new 这个手写办法,省得误导小伙伴们感觉 new 就做了那么点事:

function myNew(func, ...args) {  // 1. 判断办法体  if (typeof func !== 'function') {    throw '第一个参数必须是办法体';  }  // 2. 创立新对象  const obj = {};  // 3. 这个对象的 __proto__ 指向 func 这个类的原型对象  // 即实例能够拜访构造函数原型(constructor.prototype)所在原型链上的属性  obj.__proto__ = Object.create(func.prototype);  // 为了兼容 IE 能够让步骤 2 和 步骤 3 合并  // const obj = Object.create(func.prototype);  // 4. 通过 apply 绑定 this 执行并且获取运行后的后果  let result = func.apply(obj, args);    // 5. 如果构造函数返回的后果是援用数据类型,则返回运行后的后果  // 否则返回新创建的 obj  const isObject = typeof result === 'object' && typeof result !== null;  const isFunction = typeof result === 'function';  return isObject || isFunction ? result : obj;}// 测试function Person(name) {  this.name = name;  return function() { // 用来测试第 5 点    console.log('返回援用数据类型');  };}// 用来测试第 2 点和第 3 点Person.prototype.sayName = function() {  console.log(`My name is ${this.name}`);}const me = myNew(Person, 'jsliang'); // 用来测试第 4 点me.sayName(); // My name is jsliangconsole.log(me); // Person {name: 'jsliang'}// 用来测试第 1 点// const you = myNew({ name: 'jsliang' }, 'jsliang'); // 报错:第一个参数必须是办法体

这样,咱们就晓得构造函数中的 new 是怎么一回事了。

五 this 设计缺点和应答计划

返回目录

5.1 嵌套函数中的 this 不会从外层函数中继承

返回目录
var myObj = {  myName: "jsliang",   showThis: function(){    console.log(this.myName); // 输入啥?    function bar(){      console.log(this.myName); // 输入啥?    }    bar();  },};myObj.showThis();

答案是:

  1. jsliang
  2. undefined

解决办法一:通过 that 管制 this 指向

var myObj = {  myName: "jsliang",   showThis: function(){    console.log(this.myName); // 输入啥?    let that = this;    function bar(){      console.log(that.myName); // 输入啥?    }    bar();  },};myObj.showThis();

这样都输入 jsliang 了。

解决办法二:通过 ES6 的箭头函数解决问题

var myObj = {  myName: "jsliang",   showThis: function(){    console.log(this.myName); // 输入啥?    const bar = () => {      console.log(this.myName); // 输入啥?    }    bar();  },};myObj.showThis();

这是因为 ES6 中的箭头函数并不会创立其本身的执行上下文,所以箭头函数中的 this 取决于它的内部函数,即谁调用它 this 就继承自谁。

5.2 一般函数中 this 指向全局对象 window

返回目录

在理论工作中,咱们并不心愿函数执行上下文中的 this 默认指向全局对象,因为这样会突破数据的边界,造成一些误操作。

如果要让函数执行上下文中的 this 指向某个对象,最好的形式是通过 call 办法来显示调用。

这个问题能够通过设置 JavaScript 的 严格模式 来解决。在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined,这就解决下面的问题了。

六 React 中 this 指向

返回目录

来源于:this.handleClik = this.handleClick.bind(this);

为什么要这么操作呢?

咱们先看一份代码比对:

代码 1:对象调用字段中 this
const test = {  myName: 'jsliang',  getName: function() {    console.log(this.myName); // 输入 jsliang  }};test.getName();
代码 2:寄存到全局变量中
const test = {  myName: 'jsliang',  getName: function() {    console.log(this.myName); // 输入 undefined  }};const func = test.getName;func();

所以,React 中对应的办法,如果没有进行绑定,那么 this 就会凌乱指向全局对象 window

那么如何修改这个问题呢?

注:网友指出:上面这 4 种办法的比对其实是有误的,一般对象没法跟类做比照

当然,jsliang 在这里还是想列举进去

具体论断,置信 202X 年咱们再次温习的时候,有机会就去逐渐解答心田纳闷。

所以,为了防止矛盾抵触,小伙伴们能够跳过本章

6.1 解决方案一:提前 bind 绑定 this

返回目录
const test = {  myName: 'jsliang',  getName: function() {    console.log(this.myName); // 输入 jsliang  }};test.getName = test.getName.bind(test);const func = test.getName;func();

对应 React 中的:

constructor (props) {  this.handleClick = this.handleClick.bind(this);}<button onClick={this.handleClick}>btn 1</button>

6.2 解决方案二:调用时 bind 绑定 this

返回目录
const test = {  myName: 'jsliang',  getName: function() {    console.log(this.myName); // 输入 jsliang  }};const func = test.getName.bind(test);func();

对应 React 中的:

<button onClick={this.handleClick.bind(this)}>btn 2</button>

6.3 解决方案三:返回一个箭头函数

返回目录
const test = {  myName: 'jsliang',  getName: function() {    console.log(this.myName); // 输入 jsliang  }};const func = () => test.getName();func();

对应 React 中的:

<button onClick={() => this.handleClick()}>btn 3</button>

6.4 解决方案四:将调用办法变成箭头函数(失败)

返回目录
const test = {  myName: 'jsliang',  getName: () => {    console.log(this.myName);  }};const func = test.getName;func();

对应 React 中的:

handleClick2 = () => {  console.log('jsliang 2021');}<button onClick={this.handleClick2}>btn 4</button>

然而,这种办法失败了,返回 undefined,是什么缘故呢?

网友指出:

  • 这 4 种办法的比对其实是有误的,一般对象没法跟类做比照

当然,jsliang 还是想列举进去,具体论断,置信前面有机会会逐渐解答心田纳闷。

6.5 React 的 this 指向理论内容

返回目录
class Toggle extends React.Component {  constructor(props) {    super(props);    this.state = {isToggleOn: true};    // 为了在回调中应用 `this`,这个绑定是必不可少的    // this.handleClick = this.handleClick.bind(this);  }  handleClick() {    console.log(this);  }  render() {    return (      <button onClick={this.handleClick}>        {this.state.isToggleOn ? 'ON' : 'OFF'}      </button>    );  }}ReactDOM.render(  <Toggle />,  document.getElementById('root'));

已知下面办法,编译后变成:

"use strict";// ...代码省略var Toggle = /*#__PURE__*/function (_React$Component) {  _inherits(Toggle, _React$Component);  var _super = _createSuper(Toggle);  function Toggle(props) {    var _this;    _classCallCheck(this, Toggle);    _this = _super.call(this, props);    _this.state = {      isToggleOn: true    }; // 为了在回调中应用 `this`,这个绑定是必不可少的    // this.handleClick = this.handleClick.bind(this);    return _this;  }  _createClass(Toggle, [{    key: "handleClick",    value: function handleClick() {      console.log(this); // 输入是 undefined    }  }, {    key: "render",    value: function render() {      return /*#__PURE__*/React.createElement("button", {        onClick: this.handleClick      }, this.state.isToggleOn ? 'ON' : 'OFF');    }  }]);  return Toggle;}(React.Component);

次要看 _createClass 办法,第一个参数是被创立的类,第二个参数是一个数组,数组外面是这个被创立类中的办法。

很显然,代码中 handleClick 输入的 this,必定是 undefined

render 办法返回的是 React.createElement,在这个办法中,this 被指向了 _createClass 办法的第一个参数,也就是 Toggle

这时候,如果这个办法变成箭头函数:

handleClick = () => {  console.log(this);}

此时箭头函数是 this 定义时的上下文。

当咱们点击按钮的时候,会调用 handleClick 办法来处理事件,而 handleClick 是在 Toggle 办法中定义的,所以 this 指代 Toggle 这个类。

6.6 React 的 this 指向解决方案

返回目录
import React, { Component } from 'react'class App extends Component {  constructor (props) {    super(props);    this.handleClick = this.handleClick.bind(this);  }  handleClick () {    console.log('jsliang 2020');  }  handleClick2 = () => {    console.log('jsliang 2021');  }  render () {    // 四种绑定办法    return (      <div className='App'>        {/* 办法一:通过 constructor 中进行 bind 绑定 */}        <button onClick={this.handleClick}>btn 1</button>        {/* 办法二:在里边绑定 this */}        <button onClick={this.handleClick.bind(this)}>btn 2</button>        {/* 办法三:通过箭头函数返回事件 */}        <button onClick={() => this.handleClick()}>btn 3</button>                {/* 办法四:让办法变成箭头函数 */}        <button onClick={this.handleClick2}>btn 4</button>                {/* 额定:间接调用不须要绑定 this */}        {this.handleClick()}      </div>    )  }}export default App;

七 小结

返回目录

先记住 1 句话:

  • this 永远指向最初调用它的那个对象

再牢记 2 句话:

  • 一般函数中 this 的指向,是 this 执行时的上下文
  • 箭头函数中 this 的指向,是 this 定义时的上下文

八 题目

返回目录

8.1 this 题目解析 5 步曲

返回目录
  • 第一题
var name = 'window name';function a() {  var name = 'jsliang';  console.log(this.name); // 输入啥?  console.log('inner:' + this); // 输入啥?}a();console.log('outer:' + this); // 输入啥?

下面代码输入啥?


答案:

window nameinner:window nameouter:window name

解析:这里的 a() 能够看成 window.a(),所以是都指向 window 外面的。

  • 第二题
var name = 'window name';var a = {  name: 'jsliang',  fn: function () {    console.log(this.name); // 输入啥?  }}a.fn();

下面代码输入啥?


答案:jsliang

解析:当初是 a.fn(),所以这个指向 a,因而输入 jsliang

  • 第三题
var name = 'window name';var a = {  // name: 'jsliang',  fn: function () {    console.log(this.name); // 输入啥?  }}a.fn();

下面代码输入啥?


答案:undefined

解析:很显著,a 外面并没有 name 办法了,所以 a.fn() 找不到 a 对象外面有 name,因而输入 undefined

  • 第四题
var name = 'window name';var a = {  name: 'jsliang',  fn: function () {    console.log(this.name); // 输入啥?  }}var f = a.fn;f();

下面代码输入啥?


答案:window name

解析:代码 var f = a.fn 并没有调用 a.fn,而是做了个定义。在 f() 的时候才调用了,此时的 fn()window.fn(),所以指向了 window,因而输入 window name

  • 第五题
var name = 'window name';function fn() {  var name = 'jsliang';  function innerFn() {    console.log(this.name);  };  innerFn();}fn();

答案:window name

解析:小伙伴了解了解看看

8.2 let/const 的 this

返回目录
let a = 10;const b = 20;function foo() {  console.log(this.a);  console.log(this.b);}foo();console.log(window.a);

下面代码输入啥?


答案:undefinedundefinedundefined

解析:如果把 var 改成了 let 或者 const,变量是不会被绑定到 window 上的,所以此时会打印出三个 undefined

8.3 箭头函数的 this

返回目录
var name = 'window name';var a = {  name: 'jsliang',  func1: function () {    console.log(this.name);  },  func2: function () {    setTimeout(() => {      this.func1();    }, 100);  },};a.func1(); // 输入啥?a.func2(); // 输入啥?

下面代码输入啥?


答案:

jsliangjsliang

解析:箭头函数的 this 指向函数定义时的 this,而非执行时。箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数蕴含,则 this 绑定的是最近一层非箭头函数的 this,否则,thisundefined

8.4 求输入后果

返回目录
function foo () {  console.log(this.a)};var obj = { a: 1, foo };var a = 2;var foo2 = obj.foo;var obj2 = { a: 3, foo2: obj.foo }obj.foo(); // 输入啥?foo2(); // 输入啥?obj2.foo2(); // 输入啥?

下面代码输入啥?


答:

123

解析:

  • obj.foo()obj 调用 foo(),所以指向 obj,输入 1
  • foo2():实际上是 window.foo2(),指向 window,输入 2
  • obj2.foo2()obj2 调用 foo2(),指向 obj2,输入 3

8.5 隐式绑定失落问题

返回目录

8.5.1 求输入后果

返回目录
function foo() {  console.log(this.a);}function doFoo(fn) {  console.log(this);  fn();}var obj = { a: 1, foo };var a = 2;doFoo(obj.foo); // 输入啥?

下面代码输入啥?


答案:Window {...}2

解析:隐式绑定失落问题deFoo 传参 obj.foo 的时候,此刻 foo 还没被执行,所以在 doFoofn() 就相当于 window.fn(),所以指向到 window 啦!

留神此时调用的时候,查找的 fnwindow 上的 fn,而不是 doFoo 里的,doFoo 并没有设置 fn 这个办法。

8.5.2 求输入后果

返回目录
function foo() {  console.log(this.a);}function doFoo(fn) {  console.log(this);  fn();}var obj = { a: 1, foo };var a = 2;var obj2 = { a: 3, doFoo };obj2.doFoo(obj.foo); // 输入啥?

下面代码输入啥?


答案:{ a: 3, doFoo: f }2

解析:

  1. 此刻的 fn() 调用,查找到的地位还是 window.foo(),所以调用的时候会指向 window
  2. 这里的 fn() 是通过传参进来的,而不是 doFoo 外面存在的,所以执行的时候 this 找到的是 foo 定义的地位,实际上还是 window.fn()
  3. 如何改过这个问题?
function foo() {  console.log(this.a);}function doFoo(fn) {  console.log(this);  fn.call(this);}var obj = { a: 1, foo };var a = 2;var obj2 = { a: 3, doFoo };obj2.doFoo(obj.foo);

8.6 显示绑定问题

返回目录

8.6.1 求输入后果

返回目录
function foo() {  console.log(this.a);}var obj = { a: 1 };var a = 2;foo(); // 输入啥?foo.call(obj); // 输入啥?foo().call(obj); // 输入啥?

下面代码输入啥?


答案:

212Uncaught TypeError: Cannot read property 'call' of undefined

解析:

  • foo():指向 window
  • foo.call(obj):将 foothis 指向了 obj
  • foo().call(obj):先执行 foo(),输入 2,而后它是无返回的,相当于 undefined.call(obj),间接报错

8.6.2 求输入后果

返回目录
function foo() {  console.log(this.a);  return function () {    console.log(this.a);  };}var obj = { a: 1 };var a = 2;foo(); // 输入啥?foo.call(obj); // 输入啥?foo().call(obj); // 输入啥?

下面代码输入啥?


答案:

2121

解析:后面 3 个不用说,和下面一题一样。

最初一个 return function { this.a },所以变成这个办法来 call(obj),因而输入 obj 中的 a,也就是 1

8.7 求输入后果

返回目录
function Foo() {  'use strict'  console.log(this.location);}Foo();

请抉择:

  • A:以后窗口的 Location 对象
  • B:undefined
  • C:null
  • D:TypeError

答案:D

解析:如果没有 use strict,那么选 A;如果是严格模式,那就是 D,严格模式下禁止 this 关键字指向全局对象。

8.8 论述题

返回目录
let userInfo = {  name: 'jsliang',  age: 25,  sex: 'male',  updateInfo: function(){    // 模仿 XMLHttpRequest 申请延时    setTimeout(function(){      this.name = "zhazhaliang"      this.age = 30;      this.sex = 'female';    }, 1000);  },};userInfo.updateInfo();

解决这里的 this 指向问题,求得最终后果:

{  name: "zhazhaliang",  age: 30,  sex: "female",  updateInfo: function(),}

答案:setTimeout(() => {}) 即可。

九 参考文献

返回目录
  • [x] 再来40道this面试题酸爽持续【浏览倡议:1h】
  • [x] this,this,再次探讨javascript中的this,超全面【浏览倡议:10min】
  • [x] JavaScript中的this【浏览倡议:10min】
  • [x] JavaScript深刻之从ECMAScript标准解读this【浏览倡议:20min】
  • [x] 前端根底进阶(七):全方位解读this【浏览倡议:20min】
  • [x] JavaScript根底心法——this【浏览倡议:20min】
  • [x] 11 | this:从JavaScript执行上下文的视角讲清楚this【浏览倡议:2hour】
  • [x] 浅谈react 中的 this 指向【浏览倡议:10min】
  • [x] react的性能优化【浏览倡议:5min】
  • [x] React事件处理函数必须应用bind(this)的起因【浏览倡议:10min】
  • [x] 由React构造函数中bind引起的this指向了解(React组件的办法为什么要用bind绑定this)【浏览倡议:20min】
  • [x] React中this.handleClick = this.handleClick.bind(this)中的this指向问题【浏览倡议:10min】

jsliang 的文档库由 梁峻荣 采纳 常识共享 署名-非商业性应用-雷同形式共享 4.0 国内 许可协定 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/>本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。