JavaScript 面向對象編程【二】(this 關鍵字)

JavaScript 是一門集成了函數編程和麪向對象編程的動態語言。它的對象是一個容器,封裝了屬性(property)和方法(method)。JavaScript的面向對象實現不是基於類,而是基於構造函數(constructor)和原型鏈(prototype)實現的。

this 關鍵字

  1. this對象的含義? this關鍵字總是返回一個對象,此對象就是當前property所在的運行對象。對象的屬性值可以是對象或者函數等,其的屬性值爲地址,調用時,當前環境對象針對地址所對應的對象進行調用。 具體見:this的含義
  2. 給this綁定對象的方法? this的動態切換,爲 JavaScript 創造了巨大的靈活性,但也使得編程變得困難和模糊。我們通過callapplybind這三個方法,來切換/固定this的指向。具體見:綁定 this 的方法

一.this的含義

1.this的含義

  • this關鍵字總是返回一個對象,此對象就是當前property所在的運行對象。
  • 由於屬性是可以被賦值的,所以this所對應的對象也是動態的。
  • JavaScript 語言之中,一切皆對象,運行環境也是對象,所以函數都是在某個對象之中運行,this就是函數運行時所在的對象(環境)。
    //一. this指向person對象
    var person = {
      name: 'Joey',
      describe: function () {
    	return 'name:'+ this.name;
      }
    };
    
    person.describe()
    // "name:Joey"
    
    //二. this所在函數被引用,指向不同對象
    function f() {
      return 'name:'+ this.name;
    }
    
    var A = {
      name: 'Joey',
      describe: f
    };
    
    var B = {
      name: 'Chandler',
      describe: f
    };
    
    A.describe() // "name:Joey"
    B.describe() // "name:Chandler"
    
    //三. this被重新賦值,動態轉換了對象,此處對象是頂層對象
    var A = {
      name: 'Joey',
      describe: function () {
    	return 'name:'+ this.name;
      }
    };
    
    var name = 'Chandler';
    var f = A.describe;
    f() // "name:Chandler"
    
    //四. 實際使用範例,判斷輸入框,每輸入一個值,此值是否在指定範圍,this代表文本框對象
    <input type="text" name="age" size=3 onChange="validate(this, 18, 99);">
    
    <script>
    function validate(obj, lowval, hival){
      if ((obj.value < lowval) || (obj.value > hival))
    	console.log('Invalid Value!');
    }
    </script>
    

二.this的使用場合

1.全局環境

  • 全局環境使用this,它指的就是頂層對象window

  • 不管是不是在函數的內部,只要在全局環境下運行,this就是指頂層對象window

    this === window // true
    
    function f() {
      console.log(this === window);
    }
    f() // true
    

2.構造環境

  • 構造函數中的this,指的是實例對象。this賦值了以後,在對象中生成了一個屬性。如下:

    var Obj = function (p) {
      this.p = p;
    };
    var o = new Obj('Hello World!');
    o.p // "Hello World!"
    

3.對象的方法

  • 如果對象的方法裏面包含thisthis的指向就是方法運行時所在的對象。該方法賦值給另一個對象,就會改變this的指向。

    var obj ={
      foo: function () {
    	console.log(this);
      }
    };
    
    obj.foo() // obj
    
    // 情況一
    (obj.foo = obj.foo)() // window
    //等同於
    (obj.foo = function () {
      console.log(this);
    })()
    // 情況二
    (false || obj.foo)() // window
    //等同於
    (false || function () {
      console.log(this);
    })()
    // 情況三
    (1, obj.foo)() // window
    //等同於
    (1, function () {
      console.log(this);
    })()
    

三.使用誤區

1.避免多層 this

  • 由於this的指向是不確定的,所以切勿在函數中包含多層的this。

    //內部this指向了window對象
    var o = {
      f1: function () {
    	console.log(this);
    	var f2 = function () {
    	  console.log(this);
    	}();
      }
    }
    
    o.f1()// Object// Window
    //上面執行如下
    var temp = function () {
      console.log(this);
    };
    
    var o = {
      f1: function () {
    	console.log(this);
    	var f2 = temp();
      }
    }
    
    
  • 解決方案,引入中間變量或者使用嚴格模式

    //一. 引入中間變量
    var o = {
      f1: function() {
    	console.log(this);
    	var that = this;
    	var f2 = function() {
    	  console.log(that);
    	}();
      }
    }
    
    o.f1()
    // Object
    // Object
    //二. 使用嚴格模式,若內部this指向頂層對象,則會拋出異常
    var counter = {
      count: 0
    };
    counter.inc = function () {
      'use strict';
      this.count++
    };
    var f = counter.inc;
    f()
    // TypeError: Cannot read property 'count' of undefined
    

2.避免數組處理方法中的 this

  • 數組的map和foreach方法,允許提供一個函數作爲參數。這個函數內部不應該使用this。

    //一. 出現問題,將函數作爲參數傳遞給forEach,this指向的是頂層對象
    var o = {
      v: 'hello',
      p: [ 'a1', 'a2' ],
      f: function f() {
    	this.p.forEach(function (item) {
    	  console.log(this.v + ' ' + item);
    	});
      }
    }
    
    o.f()
    // undefined a1
    // undefined a2
    
    //二. 解決方案,使用中間變量
    var o = {
      v: 'hello',
      p: [ 'a1', 'a2' ],
      f: function f() {
    	var that = this;
    	this.p.forEach(function (item) {
    	  console.log(that.v+' '+item);
    	});
      }
    }
    
    o.f()
    // hello a1
    // hello a2
    
    //三.forEach中,使用this參數來固定運行對象
    var o = {
      v: 'hello',
      p: [ 'a1', 'a2' ],
      f: function f() {
    	this.p.forEach(function (item) {
    	  console.log(this.v + ' ' + item);
    	}, this);
      }
    }
    
    o.f()
    // hello a1
    // hello a2
    

3.避免回調函數中的 this

  • 避免回調函數中的 this

    //輸出false,因爲this指向點擊按鈕的dom對象中
    var o = new Object();
    o.f = function () {
      console.log(this === o);
    }
    
    // jQuery 的寫法
    $('#button').on('click', o.f);
    

四.綁定 this 的方法

  • this的動態切換,爲 JavaScript 創造了巨大的靈活性,但也使得編程變得困難和模糊。我們通過callapplybind這三個方法,來切換/固定this的指向。

1.Function.prototype.call()

  • 函數實例的call方法,可以指定函數內部this的指向(即函數執行時所在的作用域),然後在所指定的作用域中,調用該函數。

    var obj = {};
    
    var f = function () {
      return this;
    };
    
    f() === window // true
    //call將f函數的執行環境綁定爲obj
    f.call(obj) === obj // true
    
  • call方法的參數,應該是一個對象。如果參數爲空、nullundefined,則默認傳入全局對象。

  • 如果call方法的參數是一個原始值,那麼這個原始值會自動轉成對應的包裝對象,然後傳入call方法。

    //一. 空對象
    var n = 123;
    var obj = { n: 456 };
    
    function a() {
      console.log(this.n);
    }
    
    a.call() // 123
    a.call(null) // 123
    a.call(undefined) // 123
    a.call(window) // 123
    a.call(obj) // 456
    
    //二. 原始值
    var f = function () {
      return this;
    };
    
    f.call(5)
    // Number {[[PrimitiveValue]]: 5}
    
  • call方法的描述func.call(thisValue, arg1, arg2, ...),它接受多個參數,第一個爲需要指向的對象,後面是參數

    function add(a, b) {
      return a + b;
    }
    
    add.call(this, 1, 2) // 3
    
  • call方法的一個應用是調用對象的原生方法

    var obj = {};
    obj.hasOwnProperty('toString') // false
    
    // 覆蓋掉繼承的 hasOwnProperty 方法
    obj.hasOwnProperty = function () {
      return true;
    };
    obj.hasOwnProperty('toString') // true
    //跳過對象方法重寫,調用對象原始方法
    Object.prototype.hasOwnProperty.call(obj, 'toString') // false
    

2.Function.prototype.apply()

  • apply方法的作用與call方法類似,也是改變this指向,然後再調用該函數。唯一的區別就是,它接收一個數組作爲函數執行時的參數:func.apply(thisValue, [arg1, arg2, ...])

    //apply方法的第一個參數也是this所要指向的那個對象,如果設爲null或undefined,則等同於指定全局對象。
    function f(x, y){
      console.log(x + y);
    }
    
    f.call(null, 1, 1) // 2
    f.apply(null, [1, 1]) // 2
    
  • Function.prototype.apply()的應用

    . 找出數組最大元素
    var a = [10, 2, 4, 15, 9];
    Math.max.apply(null, a) // 15. 將數組的空元素變爲undefined,空元素與undefined的差別在於,數組的forEach方法會跳過空元素,但是不會跳過undefined
    Array.apply(null, ['a', ,'b'])
    // [ 'a', undefined, 'b' ]. 轉換類似數組的對象,利用數組對象的slice方法
    Array.prototype.slice.apply({0: 1, length: 1}) // [1]
    Array.prototype.slice.apply({0: 1}) // []
    Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
    Array.prototype.slice.apply({length: 1}) // [undefined]. 綁定回調函數的對象。下面代碼,點擊按鈕以後,控制檯將會顯示true。由於apply方法(或者call方法)不僅綁定函數執行時所在的對象,還會立即執行函數,因此不得不把綁定語句寫在一個函數體內。
    var o = new Object();
    
    o.f = function () {
      console.log(this === o);
    }
    
    var f = function (){
      o.f.apply(o);
      // 或者 o.f.call(o);
    };
    
    // jQuery 的寫法
    $('#button').on('click', f);
    

3.Function.prototype.bind()

  1. 作用概述,bind方法用於將函數體內的this綁定到某個對象,然後返回一個新函數

    var d = new Date();
    d.getTime() // 1481869925657
    
    var print = d.getTime;
    print() // Uncaught TypeError: this is not a Date object.
    //上述範例調用print會報錯,是因爲,getTime內部的this已經不指Dat對象的實例了
    //使用bind方法解決此問題,將將getTime方法內部的this綁定到d對象
    var print = d.getTime.bind(d);
    print() // 1481869925657
    
  2. 在方法內部賦值的時候,將內部的this綁定到原對象

    var counter = {
      count: 0,
      inc: function () {
    	this.count++;
      }
    };
    
    var func = counter.inc.bind(counter);
    func();
    counter.count // 1
    
  3. this綁定到其他的對象上,也可以

    var counter = {
      count: 0,
      inc: function () {
    	this.count++;
      }
    };
    
    var obj = {
      count: 100
    };
    var func = counter.inc.bind(obj);
    func();
    obj.count // 101
    
  4. bind還可以接受更多的參數,將這些參數綁定原函數的參數

    //將add函數綁定到obj,並指定第一個參數
    var add = function (x, y) {
      return x * this.m + y * this.n;
    }
    
    var obj = {
      m: 2,
      n: 2
    };
    
    var newAdd = add.bind(obj, 5);
    newAdd(5) // 20
    
  5. 如果bind方法的第一個參數是null或undefined,等於將this綁定到全局對象,函數運行時this指向頂層對象(瀏覽器爲window)

    function add(x, y) {
      return x + y;
    }
    
    var plus5 = add.bind(null, 5);
    plus5(10) // 15
    
  6. 使用注意點,bind方法每次都返回一個新函數

    //一.若寫爲如下方式,click事件綁定bind方法生成的一個匿名函數,綁定後將無法解除綁定
    element.addEventListener('click', o.m.bind(o));
    element.removeEventListener('click', o.m.bind(o));
    
    //二.正確使用方式
    var listener = o.m.bind(o);
    element.addEventListener('click', listener);
    element.removeEventListener('click', listener);
    
  7. 使用注意點,結合回調函數使用,回調函數是 JavaScript 最常用的模式之一,但是一個常見的錯誤是,將包含this的方法直接當作回調函數。

    //若不使用bind函數,直接將counter作爲函數的參數,則this將指向頂層對象
    var counter = {
      count: 0,
      inc: function () {
    	'use strict';
    	this.count++;
      }
    };
    
    function callIt(callback) {
      callback();
    }
    
    callIt(counter.inc.bind(counter));
    counter.count // 1
    
  8. 使用注意點,數組中某些方法會接受一些函數作爲參數,這些函數中使用this需要注意,如下:

    var obj = {
      name: 'Joey',
      times: [1, 2, 3],
      print: function () {
    	this.times.forEach(function (n) {
    	  console.log(this.name);
    	});
      }
    };
    obj.print()
    // 沒有任何輸出。this.times指向的是times數組,而this.name在調用時指向的是頂層對象,它的調用和下面方法一致
    obj.print = function () {
      this.times.forEach(function (n) {
    	console.log(this === window);
      });
    };
    obj.print()// true // true // true
    
    //解決方案
    obj.print = function () {
      this.times.forEach(function (n) {
    	console.log(this.name);
      }.bind(this));
    };
    
    obj.print()
    
發佈了181 篇原創文章 · 獲贊 65 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章