zoukankan      html  css  js  c++  java
  • this 指向问题ES5

    ES5中this的指针

    按照this指针的优先级,列出下面常会遇到的四种情况,从上到下依次是优先级从高到低(后面会详细比较优先级)。

    1. 函数是和new一起被调用的吗(new绑定)?如果是,this就是新构建的对象。

      var bar = new foo()

    2. 函数是用callapply被调用(明确绑定),甚至是隐藏在bind 硬绑定 之中吗?如果是,this就是明确指定的对象。

      var bar = foo.call( obj2 )

    3. 函数是用环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,this就是那个环境对象。

      var bar = obj1.foo()

    4. 否则,使用默认的this默认绑定)。如果在strict mode下,就是undefined,否则是global对象。 var bar = foo()

    以上,就是理解对于普通的函数调用来说的this绑定规则所需的全部。是的···几乎是全部。

    apply、call、bind

    因为apply、call存在于Function.prototype中,所以每个方法都有这两个属性。

    • call
    函数名.call(对象,arg1....argn)
    //功能:
        //1.调用函数
        //2.将函数内部的this指向第一个参数的对象
        //3.将第二个及以后所有的参数,作为实参传递给函数
    • apply
      主要用途是直接用数组传参
    函数名.apply(对象, 数组/伪数组);
    //功能:
        //1.调用函数
        //2.将函数内部的this指向第一个参数的对象
        //3.将第二个参数中的数组(伪数组)中的元素,拆解开依次的传递给函数作为实参
    //案例求数组中最大值
    var a=Math.max.apply( null, [ 1, 2, 5, 3, 4 ] );
    console.log(a);// 输出:5
    • bind
    1. 是创建一个新的函数,我们必须要手动去调用:
    var a ={
      name : "Cherry",
      fn : function (a,b) {
       console.log( a + b)
      }
     }
     var b = a.fn;
     b.bind(a,1,2)()   // 3
    • 注意事项

      1. call和apply功能几乎一致,唯一的区别就是传参的方式!!
        2.call和apply的第一个参数如果为null或者undefined,那么this指向window
        3.call和apply的第一个参数如果为值类型的数据,那么会将这个值类型的数据,转换成其对应的引用类型的数据,然后再将this指向这个引用类型的数据
        4.call和apply立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。
        5.当参数的个数确定的情况下可以使用call;当参数的个数不确定的情况下可以使用apply

    apply应用

    JavaScript
    
    var array1 = [12 , "foo" , {name "Joe"} , -2458]; 
    var array2 = ["Doe" , 555 , 100]; 
    Array.prototype.push.apply(array1, array2); 
    /* array1 值为  [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

    call应用(将伪数组转为数组)

    var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
    Array.prototype.join.call(arrayLike, '&'); // name&age&sex
    Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
    // slice可以做到类数组转数组
    Array.prototype.map.call(arrayLike, function(item){
        return item.toUpperCase();
    }); 
    // ["NAME", "AGE", "SEX"]

    call应用(判断复杂数据类型)

    console.log(
        Object.prototype.toString.call(num),
        Object.prototype.toString.call(str),
        Object.prototype.toString.call(bool),
        Object.prototype.toString.call(arr),
        Object.prototype.toString.call(json),
        Object.prototype.toString.call(func),
        Object.prototype.toString.call(und),
        Object.prototype.toString.call(nul),
        Object.prototype.toString.call(date),
        Object.prototype.toString.call(reg),
        Object.prototype.toString.call(error)
    );
    // '[object Number]' '[object String]' '[object Boolean]' '[object Array]' '[object Object]'
    // '[object Function]' '[object Undefined]' '[object Null]' '[object Date]' '[object RegExp]' '[object Error]'

    bind在react中应用

    下面的例子是来自react官网的案例

    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
    
        // This binding is necessary to make `this` work in the callback
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        console.log(this);
        this.setState(prevState => ({
          isToggleOn: !prevState.isToggleOn
        }));
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        );
      }
    }
    
    ReactDOM.render(
      <Toggle />,
      document.getElementById('root')
    );

    If you forget to bind this.handleClick and pass it to onClickthis will be undefined when the function is actually called.

    ES6中this的指向

    箭头函数this指向注意事项

    箭头函数体内的this对象,如果包裹在函数中就是函数调用时所在的对象,如果放在全局中就是指全局对象window。并且固定不会更改。换句话说内部的this就是外层代码块的this

    下面是对比分析普通函数和箭头函数中this区别

    // 普通函数
    function foo() {
      setTimeout(function() {
        console.log('id:', this.id);
      });
    }
    
    var id = 21;
    foo.call({ id: 42 }); //21
    // 箭头函数
    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    
    var id = 21;
    foo.call({ id: 42 }); //42
    // 上面的匿名函数定义时所在的执行环境就是foo函数,所以匿名函数内部的this执向始终会和foo函数的this执向保持一致,不会更改,如同下面的这个案例
    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    
    var id = 21;
    foo(); //21(没有用call)
    // ES5普通函数模拟上面es6函数的执行过程
    function foo() {
      var _this = this;
    
      setTimeout(function () {
        console.log('id:', _this.id);
      }, 100);
    }

    call的作用就是将foo函数的执行环境从window改成对象{id: 42}
    定时器的作用就是延迟执行当前函数的外部执行环境,无论有没有设置延迟时间

    普通函数解释:定义时this指向函数foo作用域,但是在定时器100毫秒之后执行函数时,此时this指向window对象

    箭头函数解释:this始终指向定义时所在对象,也就是始终指向foo作用域

    进一步分析this

    var handler = {
      id: '123456',
    
      init: function() {
        document.addEventListener('click',
          event => this.doSomething(event.type), false);
      },
    
      doSomething: function(type) {
        console.log('Handling ' + type  + ' for ' + this.id);
      }
    };
    handler.init()// Handlingclickfor123456

    箭头函数的this始终指向handler,如果是普通函数,this指向document

    this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

    注意事项(绑定事件)

    在IE678里面不支持addEventListener和removeEventListener,而是支持attchEvent和detachEvent两个方法。

    语法:target.attachEvent(“on”+type, listener);

    attachEvent和addEventListener区别:

    1. 在attchEvent里面的this指向的不是事件的调用者,而是window(奇葩),而addEventListener指向的事件调用者。
    2. attachEvent的type一定要加上on,不然没效果

    面试题

    下面的面试题一、二、四都设计到引用问题,如果不是很好理解还可以理解成在 es5 中,永远是this 永远指向最后调用它的那个对象。摘录链接

    下面涉及指针应用还有绑定之类的概念来自You Dont Konw JS链接

    面试题一

    this.x = 9;    // this refers to global "window" object here in the browser
    var module = {
      x: 81,
      getX: function() { return this.x; }
    };
    
    module.getX(); // 81
    
    var retrieveX = module.getX;
    retrieveX();   
    // returns 9 - The function gets invoked at the global scope
    
    // Create a new function with 'this' bound to module
    // New programmers might confuse the
    // global var x with module's property x
    var boundGetX = retrieveX.bind(module);
    boundGetX(); // 81

    retrieveX只是getX函数的引用,也就是只是getX的一个指针(getX的另一个指针是module.getX),所以retrieveX还是指向getX函数本身的

    和上面类似的案例,下面的func只是函数引用,所以即使在函数内部,还是执行的函数本身,不受词法作用域限制(箭头函数则受限制)

    document.getElementById( 'div1' ).onclick = function(){
        console.log( this.id );// 输出: div1
        var func = function(){ 
            console.log ( this.id );// 输出: undefined
        } 
        func();
    }; 
    //修正后
    document.getElementById( 'div1' ).onclick = function(){
        var func = function(){ 
            console.log ( this.id );// 输出: div1
        } 
        func.call(this);
    }; 
    function foo() {
        console.log( this.a );
    }
    
    var a = 2;
    var o = { a: 3, foo: foo };
    var p = { a: 4 };
    
    o.foo(); // 3
    (p.foo = o.foo)(); // 2

    面试题二

    var A = function( name ){ 
        this.name = name;
    };
    var B = function(){ 
        A.apply(this,arguments);
    };
    B.prototype.getName = function(){ 
        return this.name;
    };
    var b=new B('sven');
    console.log( b.getName() ); // 输出:  'sven'

    面试题三

    确实,许多包中的函数,和许多在JavaScript语言以及宿主环境中的内建函数,都提供一个可选参数,通常称为“环境(context)”,这种设计作为一种替代方案来确保你的回调函数使用特定的this而不必非得使用bind(..)。

    举例来说:

    function foo(el) {
        console.log( el, this.id );
    }
    
    var obj = {
        id: "awesome"
    };
    
    // 使用`obj`作为`this`来调用`foo(..)`
    [1, 2, 3].forEach( foo, obj ); // 1 awesome  2 awesome  3 awesome

    面试题四

    明确绑定 的优先权要高于 隐含绑定

    function foo() {
        console.log( this.a );
    }
    
    var obj1 = {
        a: 2,
        foo: foo
    };
    
    var obj2 = {
        a: 3,
        foo: foo
    };
    
    obj1.foo(); // 2
    obj2.foo(); // 3
    
    obj1.foo.call( obj2 ); // 3
    obj2.foo.call( obj1 ); // 2

    new绑定的优先级高于隐含绑定(new和call/apply不能同时使用,所以new foo.call(obj1)是不允许的,也就是不能直接对比测试 new绑定 和 明确绑定)

  • 相关阅读:
    C#中的语言记忆功能
    C#中 文件的打开及保存
    无边框窗体设置
    Windows获取浏览器中存储的明文密码
    (CVE-2020-17530)Struts2 S2-061漏洞复现
    (CVE-2020-14882​&14883)Weblogic RCE复现
    内网渗透学习-信息收集篇
    Spring Boot Actuator H2 RCE复现
    Linux解压文件
    Windows本地提权
  • 原文地址:https://www.cnblogs.com/l8l8/p/8974073.html
Copyright © 2011-2022 走看看