一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 全局执行上下文中的 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
当然你也能够换成 apply
和 bind
,这里不做累述。
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();
答案是:
- jsliang
- 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);
下面代码输入啥?
答案:undefined
、undefined
、undefined
解析:如果把 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
,否则,this
为 undefined
。
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
还没被执行,所以在 doFoo
中 fn()
就相当于 window.fn()
,所以指向到 window
啦!
留神此时调用的时候,查找的fn
是window
上的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
解析:
- 此刻的
fn()
调用,查找到的地位还是window.foo()
,所以调用的时候会指向window
。 - 这里的
fn()
是通过传参进来的,而不是doFoo
外面存在的,所以执行的时候this
找到的是foo
定义的地位,实际上还是window.fn()
- 如何改过这个问题?
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)
:将foo
的this
指向了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/ 处取得。