JavaScript學習之Object(下)this

this

爲何用this

this可以動態的取值,實現了隱式傳遞一個對象的引用
var obj = {
            a : {

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

上面的例子中想在c中取b的值用this就能簡單搞定,必定程度上實現了動態取值;obj.a.c(),這個this就是綁定了obj.a,這個就是上下文對象也就是調用棧javascript

this是什麼

this始終是一個對象,函數在被調用時發生綁定,具體綁定了什麼看調用的位置,有必定的綁定規則java

綁定規則

  • 默認綁定
    當函數不帶任何修飾被調用時,運用默認綁定,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,哪一個對象調用就綁定誰app

    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)
    例以下面例子也是隱式丟失this

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

    要注意這種別名:prototype

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

    實際上就是:code

    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對象
相關文章
相關標籤/搜索