this到底是什么?

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); // 30
            // this === obj2
        }
        obj1.obj2.foo();
    </script>

js监听函数的回调函数,将回调函数绑定到调用的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等

    <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); // 5
        //还可以通过bind
        // var bar = foo.bind(obj, 3);
    </script>

2. 解决回调函数this丢失

1)手动绑定

最典型的是React类组件中的事件处理器回调函数,需要将函数内部this绑定绑定到当前组件上。

a. React类组件中的事件处理器回调函数的this绑定形式有三种:

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

箭头函数内部没有this,所以箭头函数的this绑定代码块外部的this。即定义时所在对象。

这点和普通的this是执行时所在对象不同。

// 箭头函数固定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>
        )
    }
 }
  • 使用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>
        )
    }
 }
  • 在事件属性的回调函数中使用箭头函数
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>
        )
    }
 }

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

  • this赋值给变量传参;适用于setTimeout,forEach等
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>
        )
    }
 }
  • 利用函数本身提供的参数:只适用于forEach等数组方法,不适用于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);// 传入参数
    }
    render() {
        return (
            <div onClick={this.add}>Click Me</div>
        )
    }
 }

4. new 绑定

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

1)创建一个空对象

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

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

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

5.优先级

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

       

 

猜你喜欢

转载自www.cnblogs.com/lyraLee/p/11609709.html
今日推荐