this是属性和方法“当前”(运行时)所在的对象。this是函数调用时发生的绑定,它的值只取决于调用位置(箭头函数除外)。

函数调用的时候会产生一个执行上下文,this是对这个执行上下文的记录。

❌误区需要注意:

this不是指向函数本身;this和函数作用域无关;this和声明位置无关系,只和调用位置有关系。

名次解释:

调用栈: 到达当前执行位置调用的所有的函数。

既然this是调用时绑定的对象,我们只需要搞清楚this的绑定规则:

1.默认绑定

当函数调用时,如果函数直接调用,没有任何修饰(函数前没有任何对象),则默认绑定this到window。

如果使用严格模式,默认绑定到undefined。

所以,立即执行函数在任何位置,函数内部的this,永远指向window(严格模式下undefined)。

    <script>
// 在函数作用域中声明函数
function a() {
!function b() { // 函数前面有任何符号都能将函数声明变为表达式
console.log(this);// window
}();
!function c() { // 函数前面有任何符号都能将函数声明变为表达式
'use strict';
console.log(this); //undefined
}();
}
var obj = { a }
obj.a();
</script>

2. 隐式绑定(隐式丢失)

当函数调用时,前面有上下文对象时,隐式绑定规则将this绑定到该对象上。

如果多层嵌套,绑定到最近的上下文对象(obj1.obj2)上。

    <script>
var obj2 = {a: 30, foo: foo};
var obj1 = {a: 20, obj2}; // 注意obj2要先声明,否则报错
function foo() {
console.log(this.a); //
// this === obj2
}
obj1.obj2.foo();
</script>

js监听函数的回调函数,将this绑定到调用的DOM对象上。

    <div id="root">Click ME</div>
<script>
const rootElement = document.querySelector('#root');
rootElement.addEventListener('click', function() { // 此处严禁使用箭头函数
console.log(this); // rootElement
})
</script>

前端开发中this最容易出错的地方,就是隐式绑定丢失导致的this指向window(严格模式undefined)的问题。

隐式绑定丢失常见的几种情况:

1.赋值给变量

    <script>
var objA = {
a: 100,
foo: function() {
console.log(this.a);
}
};
objA.foo(); // 100 this === objA
var func = objA.foo; // 相当于func = function(){}
// 函数调用时,相当于函数直接调用,无任何修饰,使用默认绑定
func(); // undefined this === window
</script>

2.作为自定义函数的回调函数传参

将上下文对象的函数作为参数传入回调函数,相当于赋值给参数变量。

    <script>
var a = 'outer';
function foo() {
console.log(this.a);
}
var obj = { a: 'inner', foo }
function doFoo(fn) { // 调用的时候相当于fn=obj.foo
fn(); // 调用位置,相当于直接调用foo()
}
doFoo(obj.foo); // 'outer' this === window
</script>

这个问题可以解释,为什么React类组件中事件回调函数触发必须绑定this:

首先: React类组件中,构造函数、实例方法、静态方法都是在严格模式下运行的。

import React from 'react';
import ReactDOM from 'react-dom'; class App extends React.Component{
constructor(props) {
super(props);
}
onClick() {
console.log(this); // undefine
}
// onClick后面跟的是回调函数,相当于callback = this.onClick;
// <button onClick={callback}>Click Me</button>
// 调用的时候是直接调用callback(),所以this应该是默认绑定
// 由因为在实例方法中,是严格模式,所以是undefined
// 要想在onClick方法中使用this,需要进行this显式绑定
render() {
return (
<button onClick={this.onClick}>Click Me</button>
)
}
} ReactDOM.render(<App />, document.getElementById('root'));

3. 作为javascript一些内置函数的回调函数传参

常见的有setTimeout,数组的遍历函数如forEach, map, reduce, reduceRight, some, every,filter,flatMap等

    <script>
function foo() {
setTimeout(function() {
console.log(this); // window
}, 1000);
console.log(this);// objA
}
var objA = { foo };
objA.foo();
/*
上面是调用定时器setTimeout方法,并传入两个参数,第一个是回调函数
function setTimeout(callback, delay) {
// 等待delayms;
callback();
}
*/
</script>
    <script>
var arr = [1,2,3,4,5,6];
arr.forEach(function(item) {
console.log(this); // window
});// 相当于直接执行forEach方法
/*
上面相当于调用Array原型链上的forEach方法,传入回调函数的参数
Array.prototype.forEach = function(callback) {
callback();
}
*/
</script>

上面的三种情况都是隐式绑定丢失,导致this使用默认绑定的情况。还有一种回调函数this被修改的情况。

PS: this绑定隐式修改

这种情况一般是,有些js库中将事件处理器将回调函数绑定到DOM元素上。

如:jquery库的事件回调函数

<body>
<div id="root">Click Me 1</div>
<script>
$("#root").click(function() { // click方法的回调函数
console.log(this); // $("#root")
})
</script>
</body>

3. 显式绑定

js提供了三种显示绑定的方法apply,call,bind。另外

三种方法调用有区别,具体的可以参考三种方法的详细介绍

通过这三种方法,可以将函数this绑定到一个不相关的对象, 还可以解决隐式绑定中隐式丢失的问题。

1. 指定对象

    <script>
function foo(sth) {// sth === 3
return this.a + sth; // this.a === 2
}
var obj = {a:2};
var bar = function() {
// apply和call绑定后立即执行,所以要用外面的函数套一层
return foo.apply(obj, arguments);
// 如果是call
// return foo.call(obj, ...arguments)
};
var b = bar(3);
console.log(b); //
//还可以通过bind
// var bar = foo.bind(obj, 3);
</script>

2. React中事件处理绑定this-不传参

React类组件中,不绑定,默认this严格模式下是undefined,需要将函数内部this绑定绑定到当前组件上。

1. 使用箭头函数固定this的指向

箭头函数内部没有this(箭头函数不能使用new命令),所以箭头函数的this绑定代码块外部作用域(函数作用域或者全局作用域)的this。即定义时所在对象。

这点和普通的this是执行时所在对象不同。而且箭头函数绑定作用域后不能修改(不能通过bind等方法修改)。

// 箭头函数固定this的方法适用于setTimeout,类实例方法
class App extends React.Component{
constructor(props) {
super(props);
}
add = () => {
console.log(this); // 当前组件
     setTimeout(() => {
       console.log(this); // 当前组件
     },1000)
}
render() {
return (
<div onClick={this.add}>Click Me</div>
)
}
}

有一点需要注意: 箭头函数绑定的是代码块外部的函数或者全局作用域。对象没有作用域。

    const shape = {
q() {
console.log(this);
},
p: () => {
console.log(this);
}
}
console.log(shape.p()); // window/严格模式下undefined
console.log(shape.q()); // shape

2. 使用bind方法在构造函数中进行绑定

class App extends React.Component{
constructor(props) {
super(props);
this.add = this.add.bind(this);
}
add(e) {
console.log(this); // 当前组件
setTimeout(function(){
console.log(this); // 当前组件
}.bind(this), 1000)
}
render() {
return (
<div onClick={this.add}>Click Me</div>
)
}
}

3. React中事件处理绑定this-传参

只有传参的时候才使用这些绑定方式;

因为它每次渲染生成新的回调函数,如果作为props传参,会进行额外的重新渲染。

1. 在事件属性的回调函数中使用箭头函数

这种方式,必须显式的传递事件对象e

class App extends React.Component{
constructor(props) {
super(props);
}
add(e) {
console.log(this); // 当前组件
}
render() {
return (
<div onClick={(e) => this.add(e)}>Click Me</div>
)
}
}

2. 在事件属性的回调函数中使用bind方法

这种方式会隐式的传递事件对象e, 在其他参数之后

class App extends React.Component{
constructor(props) {
super(props);
}
add(id, e) { //第二个参数
console.log(this); // 当前组件
}
render() {
return (
<div onClick={this.add.bind(this, id}>Click Me</div>
)
}
}

3. 使用data-*属性传递参数

该方法不会在render后每次生成新函数;

  add = (e) => {
console.log(e.target.dataset.number)
}
render() {
return (
<button data-number={5} onClick={this.add}>App </button>
)
}

4. this赋值给变量传参

针对setTimeout等回调函数隐式绑定丢失的情况,除了上面的箭头箭头函数和bind方法,还有

class App extends React.Component{
constructor(props) {
super(props);
this.add = this.add.bind(this);
}
add(e) {
console.log(this); // 当前组件
const that = this; //赋值给变量,传递
setTimeout(function(){
console.log(that); // 当前组件
}, 1000)
}
render() {
return (
<div onClick={this.add}>Click Me</div>
)
}
}

5. 使用一些原生函数的自身参数

数组的很多处理方法,以回调函数作为参数,这些回调函数中this会默认是window/undefined(严格模式);

他们提供最后一个参数用于绑定内部的this。不适用于setTimeout.

class App extends React.Component{
constructor(props) {
super(props);
this.add = this.add.bind(this);
}
add(e) {
console.log(this); // 当前组件
const that = this;
([1]).forEach(function(item) {
console.log(this); // 当前组件
},that);// 第二个参数是被绑定的this对象
}
render() {
return (
<div onClick={this.add}>Click Me</div>
)
}
}

4. new 绑定

使用new命令实例化一个构造函数的时候,逻辑如下:

1)创建一个空对象

2)将对象的原型对象指向构造函数的prototype属性

3)将这个空对象绑定到this

4) 没有其他返回对象的情况下,返回this

5.优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

PS: 特殊的this取值

在class中,this除了指代类的实例对象外,在静态方法中的this有别的指向:

静态方法中,this指向当前类。

this绑定问题的更多相关文章

  1. ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  2. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  3. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  4. 冒泡,setinterval,背景图的div绑定事件,匿名函数问题

    1.会冒泡到兄弟元素么? $(function(){ $("#a").click(function(){alert("a")}) $("#b" ...

  5. Xamarin+Prism开发详解二:Xaml文件如何简单绑定Resources资源文件内容

    我们知道在UWP里面有Resources文件xxx.resx,在Android里面有String.Xml文件等.那跨平台如何统一这些类别不一的资源文件以及Xaml设计文件如何绑定这些资源?应用支持多国 ...

  6. 数据的双向绑定 Angular JS

    接触AngularJS许了,时常问自己一些问题,如果是我实现它,会在哪些方面选择跟它相同的道路,哪些方面不同.为此,记录了一些思考,给自己回顾,也供他人参考. 初步大致有以下几个方面: 数据双向绑定 ...

  7. Html.DropDownLis绑定数据库

    效果: 方法一: View: <div class="col-md-md-4"> <div class="input-group"> & ...

  8. ASP.NET MVC——模型绑定

    这篇文章我们来讲讲模型绑定(Model Binding),其实在初步了解ASP.NET MVC之后,大家可能都会产生一个疑问,为什么URL片段最后会转换为例如int型或者其他类型的参数呢?这里就不得不 ...

  9. Spring MVC初始化参数绑定

    初始化参数绑定与类型转换很类似,初始化绑定时,主要是参数类型 ---单日期 在处理器类中配置绑定方法  使用@InitBinder注解 在这里首先注册一个用户编辑器 参数一为目标类型   proper ...

  10. SpringMVC初始化参数绑定--日期格式

    一.初始化参数绑定[一种日期格式] 配置步骤: ①:在applicationcontext.xml中只需要配置一个包扫描器即可 <!-- 包扫描器 --> <context:comp ...

随机推荐

  1. Kubernetes对Pod调度指定Node以及Node的Taint 和 Toleration

    由于博客园不支持markdown,推荐以下url阅读: 原创url:https://blog.csdn.net/weixin_42495873/article/details/103364868 ## ...

  2. python中的 __inti__ 和 __new__ 方法的区别

    这个要从Python的面向对象实例化的过程说起 类名() 之后,开辟一块内存空间,然后调用__init__把空间的内存地址作为self的参数传递到函数的内部,所有和self有关的参数,属性都会和sel ...

  3. 关联安装 mysql ,zabbix , nginx, php

    /usr/local/zabbix-3.2.6 /usr/local/php-5.6.3 /usr/local/mysql-5.7.26 安装mysql mv /etc/yum.repos.d/Cen ...

  4. java. util. concurrent. atomic

    一.原子更新基本类型 AtomicInteger AtomicBoolean AtomicLong 二.原子更新数组 AtomicIntegerArray AtomicLongArray Atomic ...

  5. Scratch与物理·天文:模拟中国嫦娥探月工程,探索月球的背面!

    北京时间2019年5月16日凌晨,国际顶级学术期刊<自然>(Nature)在线发表了一篇来自中国科学家的成果:中国的嫦娥四号月球探测器2019年1月3日在月球背面的冯卡门陨石坑(Von K ...

  6. (二)手动配置第一个HelloWorld程序

    上例的HelloWorld是由Android sutudio 自动生成的,现在我们手动来配置. 1. 重新创建工程 2. 创建空的Activity 生成的MainActivity.java 文件: p ...

  7. jQuery_jQuery的两把利器

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. 听课笔记--DP--Authentication Failed

    Authentication Failed https://www.codechef.com/problems/AUTHEN/ 从一个长为N+K的由小写字母组成的字符串中删去K个字符, 可以得到多少种 ...

  9. [转载]Linux 命令详解:./configure、make、make install 命令

    [转载]Linux 命令详解:./configure.make.make install 命令 来源:https://www.cnblogs.com/tinywan/p/7230039.html 这些 ...

  10. C++ DLL debug版本在其他PC上缺少依赖的处理方式

    1.正常情况提供给其他人的都是Release版本DLL 2.在需要提供Debug版本时,目标机器上可能会缺少环境,或者和生成DLL的环境不匹配导致DLL无法加载,提示DLL无法找到. 3.使用DLL依 ...