zoukankan      html  css  js  c++  java
  • 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监听函数的回调函数,将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); // 5
            //还可以通过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指向当前类。

           

  • 相关阅读:
    EL表达式 (详解)
    宜信面试整理
    Java 合并两个排序数组
    动态规划初识(爬楼梯问题)
    二叉树的最小深度
    ElasticSearch 单字符串多字段查询评分问题
    ES 分词
    汽车之家 面试总结
    浪潮之巅读书笔记
    闲徕互娱 面试总结
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11609709.html
Copyright © 2011-2022 走看看