JavaScript 是一門集成了函數編程和麪向對象編程的動態語言。它的對象是一個容器,封裝了屬性(property)和方法(method)。JavaScript的面向對象實現不是基於類,而是基於構造函數(constructor)和原型鏈(prototype)實現的。
this 關鍵字
- this對象的含義?
this
關鍵字總是返回一個對象,此對象就是當前property
所在的運行對象。對象的屬性值可以是對象或者函數等,其的屬性值爲地址,調用時,當前環境對象針對地址所對應的對象進行調用。 具體見:this的含義 - 給this綁定對象的方法?
this
的動態切換,爲 JavaScript 創造了巨大的靈活性,但也使得編程變得困難和模糊。我們通過call
、apply
、bind
這三個方法,來切換/固定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.對象的方法
-
如果對象的方法裏面包含
this
,this
的指向就是方法運行時所在的對象。該方法賦值給另一個對象,就會改變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 創造了巨大的靈活性,但也使得編程變得困難和模糊。我們通過call
、apply
、bind
這三個方法,來切換/固定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
方法的參數,應該是一個對象。如果參數爲空、null
和undefined
,則默認傳入全局對象。 -
如果
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()
-
作用概述,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
-
在方法內部賦值的時候,將內部的this綁定到原對象
var counter = { count: 0, inc: function () { this.count++; } }; var func = counter.inc.bind(counter); func(); counter.count // 1
-
將
this
綁定到其他的對象上,也可以var counter = { count: 0, inc: function () { this.count++; } }; var obj = { count: 100 }; var func = counter.inc.bind(obj); func(); obj.count // 101
-
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
-
如果bind方法的第一個參數是null或undefined,等於將this綁定到全局對象,函數運行時this指向頂層對象(瀏覽器爲window)
function add(x, y) { return x + y; } var plus5 = add.bind(null, 5); plus5(10) // 15
-
使用注意點,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);
-
使用注意點,結合回調函數使用,回調函數是 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
-
使用注意點,數組中某些方法會接受一些函數作爲參數,這些函數中使用
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()