3

this

为什么用this

this能够动态的取值,实现了隐式传递一个对象的引用
        var obj = {
            a : {

                b : 2,
                c : function () {
                        console.log(this.b);
                    }
            }
        }

上面的例子中想在c中取b的值用this就能简单搞定,一定程度上实现了动态取值;obj.a.c(),这个this就是绑定了obj.a,这个就是上下文对象也就是调用栈

this是什么

this始终是一个对象,函数在被调用时发生绑定,具体绑定了什么看调用的位置,有一定的绑定规则

绑定规则

  • 默认绑定
    当函数不带任何修饰被调用时,运用默认绑定,this就是window全局对象;但要注意的是在严格模式下,则不行,this会绑定成undefined

    function f() {
        //"use strict"
        console.log(this.a);
    }
    var a = 123;
    f();        //123
    function f() {
        "use strict"
        console.log(this.a);
    }
    var a = 123;
    f();        //报错,undefined不能够添加属性
  • 隐式绑定
    当上下文对象(调用栈)调用函数时,会隐式绑定this,哪个对象调用就绑定谁

        function f() {
            console.log(this.a);
        }
        var obj = {
            a : 123,
            b : f,
        }
        obj.b();
      

    上面例子实际上就是this = obj,而不是this = obj.a
    这是最简单的,但是这种绑定常常会发生隐式丢失,而采用默认绑定

        function f() {
            console.log(this.a);
        }
        var obj = {
            a : 123,
            b : f,
        }
        var a = 345
        var f1 = obj.b;   //采取函数别名而发生了隐式丢失
        f1();             //345,这等同于直接执行了f函数

    我的理解是函数取别名是最好赋值上下文对象,也就是调用栈(var f1 = b)
    例如下面例子也是隐式丢失

        function f() {
            console.log(this.a);
        }
        var obj = {
            a : 123,
            b : f,
        }
        function fn(f) {        //等同于setTimeout(obj.b, 100)
            f();
        }
        fn(obj.b);

    要注意这种别名:

    function f() {
        console.log(this.a);
    }
    var obj = {a : 1};
    obj.fn = f;
    obj.fn()

    实际上就是:

    function f() {
        console.log(this.a);
    }
    var obj = {
        a : 1,
        fn : f,
    };
    obj.fn()
  • 显示绑定
    javascript提供了3种call,apply,bind;作用都是改变this的指向
  • call
    可以填入多个参数

    func.call(thisValue, arg1, arg2, arg3,...)

    第一个参数是this要绑定的对象,若参数为空,undefined,null,则默认绑定window;若参数为原始值,则this绑定对应的包装类对象
    后面参数则是传入func的参数

        function f(n, m) {
            console.log(n, m, this.a);
        }
        var obj = {a : 1};
        f.call(obj, 3, 4);     //3 4 1
        function f() {
            console.log(this);
        }
        f.call("123");       //String {"123"}
  • apply
    apply与call类似只是参数不同,就是将传入原函数的参数变成数组形式

    func.call(thisValue, [arg1, arg2, arg3,...])

    apply与call可以这样理解:就是thisValue借用func函数功能来实现效果
    类似于thisValue.fn(arg1, arg2, arg3...)
    利用这一特点可以实现有趣的效果

  • 找数组中最大的数

    var arr = [11, 2, 3, 4, 5];
    var nmax = Math.max.apply(null, arr);   //11

    null的作用是因为用不到this,用null代替,仅用后面的参数,有柯里化的感觉
    建议我们一般定义一个比null还空的空对象:var ø = Object.create(null)

  • 将数组中的空元素变成undefined,这个样就可遍历了,其属性描述的enumerable:true

    var arr = [11, , 3, 4, 5];
    var arr1 = Array.apply(null, arr);  //[11, undefined, 3, 4, 5]
  • 转换类数组对象,利用Array的实例方法slice,前提是必须有length属性

    var arr = Array.prototype.slice.apply({0 :1, 1 : 2, length : 3});  //[1, 2, empty]
    var arr = Array.prototype.slice.apply({0 :1});  //[]
    var arr = Array.prototype.slice.apply({length : 1});  //[empty]
  • bind
    bind函数参数形式和call类似

    func.bind(thisValue, arg1, arg2, arg3,...)

    我对他的理解是对原函数进行改造,生成自己的新函数,主要改造就是this绑定成thisValue,并且可以固定部分参数,当然后面arg选填
    类似于thisValue.fn
    我们可以自定义一个简单bind函数:

    function myBind(fn, obj) {
        return function () {
            fn.apply(obj, arguments);
        }    
    }
    function f(n) {
        console.log(n + this.a)
    }
    var obj = {a : 1};
    var bind = myBind(f, obj);
    bind(4);               //5
        

    原生态bind用法也类似

    function f(n,m) {
        console.log(this.a + n + m);
    }
    var obj = {a : 1};
    var bar = f.bind(obj, 2);
    bar(3);    //6

    注意点bind每次返回一个新函数,在监听事件时要注意,不然romove不掉

    element.addEventListener('click', o.m.bind(o));
    element.removeEventListener('click', o.m.bind(o));

    因为o.m.bind(o)返回的时新函数,所以remove的也不是开启时的函数了
    正确做法:添加一个值,记录开启时的函数

    var listener = o.m.bind(o)
    element.addEventListener('click', listener);
    element.removeEventListener('click', listener);

    有趣的地方:
    利用call与bind实现原始方法

    var slice = Function.prototype.call.bind(Array.prototype.slice);
    slice([1, 2, 3], 1);   //[2, 3]

    可以拆分bind看

    Array.prototype.slice.Function.prototype.call([1, 2, 3], 1)

    其中Function.prototype.call 就是call方法

    Array.prototype.slice.call([1, 2, 3], 1)

    拆分call

    [1, 2, 3].Array.prototype.slice(1)

    而Array.prototype.slice就是slice
    所以

    [1, 2, 3].slice(1)

    个人理解可以看成,Array.prototype.slice实现了slice功能,Function.prototype.call实现了arguments中this的绑定以及参数的带入。所以函数最总调用时显示:slice([1, 2, 3], 1);

    同理
    var push = Function.prototype.call.bind(Array.prototype.push);
    var pop = Function.prototype.call.bind(Array.prototype.pop);

    同时bind也能被改写

    function f() {
      console.log(this.a);
    }
    
    var obj = { a : 123 };
    var bind = Function.prototype.call.bind(Function.prototype.bind);
    bind(f, obj)() // 123
  • new的this绑定
    当用new来指向函数F时,函数变成构造函数F,this也会发生变化
    this = Object.create(F.prototype)
    具体new的功能可看我的new篇

绑定优先级

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


  • 隐式绑定与默认绑定比较

    function f() {
            console.log(this.a);
        }
        var obj = {
        a : 123,
            f : f,
        }
    var a = 456;
        obj.f();  // 123

    obj.f()覆盖了f(),因此隐式大于默认

  • 隐式绑定与显示绑定比较

    function f() {
        console.log(this.a);
    }
    var obj = {
        a : 123,
        f : f,
    }
    var obj1 = {
        a : 1123
    }
    obj.f.call(obj1);   //1123

    由输出结果可以看出:call绑定的obj1覆盖了obj,所以显示大于隐式

  • 显示绑定与new绑定

    
    function f(n) {
        this.a = n;
        console.log(this);
    }
    var obj = { b : 2};
    var bar = f.bind(obj);
    console.log(bar(2));
    console.log(new bar(2));
    //{b:2,a:2}
    //undfined
    //{a: 2}
    //{a: 2}

    由输出结果可以看出:new bar(2),{a:2}说明是新生成的空对象,添加了a的属性,其次输出两个说明函数返回了一个this对象也就是{a:2},而不是undefined
    所以new大于显示

使用注意点

  • 注意多层this*

    var obj = {
      f: function () {
        console.log(this);
        var f1 = function () {
          //console.log(this);
        }();
      }
    }
    
    obj.f();
    //{f:func}
    //winodw

    因为传统的this没有继承机制,所以这个匿名函数的this没有任何修饰,采取默认绑定
    有两种方法解决

    var obj = {
      f: function () {
        var self= this
        console.log(self);
        var f1 = function () {
          //console.log(self);
        }();
      }
    }
    
    obj.f();
    //{f:func}
    //{f:func}
    var obj = {
          f: function () {
            console.log(this);
            return () => {
                  console.log(this);
            };
          }
    }
    
    obj.f()();
    //{f:func}
    //{f:func}
  • 注意处理数组时使用this

    var obj = {
        a : 123,
        arr : [1, 2, 3],
        f : function () {
            this.arr.forEach(function (elem) {
                console.log(this.a, elem);
            });
            
        }
    }
    var a = "this是window";
    obj.f();
    //this是window 1
    //this是window 2
    //this是window 3

    forEach中的this指向的是window
    解决办法:

       var obj = {
       a : 123,
       arr : [1, 2, 3],
       f : function () {
           this.arr.forEach(function (elem) {
               console.log(this.a, elem);
           },this);
           
       }
       }
       var a = "this是window";
       obj.f();
       //123 1
       //123 2
       //123 3

    还有一种就是赋值给一个变量self暂时保存,给遍历时用,如同上面的self

  • 注意回调函数中的this

    var obj = new Object();
    obj.f = function () {
      console.log(this === obj);
    }
    
    
    $('#button').on('click', obj.f);   //false

    this指向的是DOM对象而不是obj,这个有点难以察觉,需要注意
    解决办法:硬绑定obj对象

      var obj = new Object();
      obj.f = function () {
        console.log(this === obj);
      }
      function fn(){
        obj.f.apply(obj);
      }
      
      $('#button').on('click', fn);   //true
      //$('#button').on('click', o.f.bind(obj));   //true

    上面的硬绑定相当于固定了this的对象,不会变了。
    我们可以做个软绑定

    if(!Function.prototype.sorfBind) {
        Function.prototype.sorfBind = function (obj) {
            //这个this:执行sorfBind时的调用位置绑定的this
            var fn = this;                
            var arg1 = [].slice.call(arguments, 1);
            var bound = function() {
                //这个this:执行bound时绑定的this
                return fn.apply(
                    //arg1.concat.call(arg1, arguments 作用是整合两次执行传入的参数
                    //(!this || this === (window || global)) 猜测是为了在严格模式下也试用
                    (!this || this === (window || global)) ? obj : this, arg1.concat.call(arg1, arguments)   //arguments是bound函数执行时参数的参数,
                    );
            };
            bound.prototype = Object.create(fn.prototype);
            return bound;
        }
    }
    function f() {
        console.log("name: " + this.name);
    }
    var obj = {name : "obj"},
        obj1 = {name : "obj1"},
        obj2 = {name : "obj2"};
    var fObj = f.sorfBind(obj);
    fObj();                      //name : obj
    obj1.foo = f.sorfBind(obj);
    obj1.foo();                  //name : obj1
    fObj.call(obj2);             //name : obj2
    setTimeout(obj1.foo, 10);    //name : obj

简单谈谈箭头函数对this的影响

  • 初次绑定this后,箭头函数内部的this就固定了

    function f() {
        return (b) => {
            console.log(this.a);
        }
    }
    var obj = {a : 1};
    var obj1 = {a : 2};
    var fn = f.call(obj);
    
    fn();                //1
    fn.call(obj1);       //1
  • 与第一点类似,初次绑定后内部this固定了也就有了继承

    function f() {
        setTimeout(() => {
            console.log(this.a);
        },10);
    }
    var obj = {a : 1};
    f.call(obj);    //1

还有一点,不建议使用的属性也会改变this的指向

  • arguments.callee 也有改变this指向

    function f() {
        console.log(this);
        if(this ==window) {
            arguments.callee();
        }
    }
    f(); 
    //window对象
    //arguments对象

Infinity
293 声望9 粉丝

学前端中,只想激情优雅的写代码