在JS中,一切事物皆對象毛俏,運行環(huán)境也是對象,就連方法也是對象,在最底層有windows對象淌哟⊥讲郑可以說所有的全局變量掉弛,方法都是掛載在window對象上的芋肠。
當然window也有其他的一些屬性alert吭净、confirm等。
this
this.property // window
如上,返回window對象实愚,this指的就是property屬性所運行的環(huán)境對象。
const person = {
name:'ccg',
dothing(){
console.log(this.name);
}
}
person.dothing();//ccg
如上沸柔,this.name在dothing中進行執(zhí)行循衰,所以this便指向了dothing的運行環(huán)境person。
然而this是可變的褐澎,可以進行更改指向的
const person = {
name:'ccg',
dothing(){
console.log(this.name);
}
}
const person2 = {name:'ccg2'};
person2.dothing = person.dothing;
person2.dothing();//ccg2
如上会钝,我們將person2的dothing事件指向了person.dothing。運行環(huán)境便是person2工三,所以執(zhí)行結(jié)果是ccg2迁酸。
下面是上面例子的拆解,便于理解
const dothing = function () {
console.log(this.name);
}
const person = {
name: 'ccg',
dothing
}
const person2 = { name: 'ccg2' };
person2.dothing = person.dothing;
person2.dothing();//ccg2
注意:函數(shù)只要進行變量賦值俭正,那么函數(shù)內(nèi)的this便會更改奸鬓。
const person = {
dothing:function(){
console.log(this);
}
}
const func = person.dothing;
func();//window
如上,我們將person.dothing事件賦值給了func掸读,因為func的調(diào)用方是window串远,也就是func的運行環(huán)境是window宏多,所以它內(nèi)部的this就是window對象。
<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>
如上抑淫,我們在輸入框進行輸入時onchange事件會執(zhí)行绷落,但是所傳入的this指向的卻是當前的文本對象也就是input框。通過this.value獲取輸入框的值始苇。
this 的實質(zhì)
JS之所以有this這個概念砌烁,跟內(nèi)存存儲機制有關。
const obj = {name:'ccg'};
如上催式,一個簡單的obj對象函喉。按照語法理解,似乎我們是先聲明一個變量obj荣月,然后給obj了一個對象值管呵,其實不然。在JS中是先在內(nèi)存中創(chuàng)建一個{name:'ccg'}哺窄,然后將內(nèi)存地址賦值給obj捐下,那么obj這個時候就指向了{name:'ccg'}。也就是說萌业,obj其實就是一個內(nèi)存地址坷襟。
原始的對象保存是以字典結(jié)構(gòu)保存,每一個屬性都有自己的描述對象生年。如下:
{
name: {
[[value]]: 'ccg'
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
對象的沒個屬性都可以進行描述屬性修改婴程,這就是為什么我們使用Object.create()創(chuàng)建對象是第二個參數(shù)會以描述對象的形式書寫。
關于Object對象http://www.reibang.com/p/0846f6642769
這樣的數(shù)據(jù)結(jié)構(gòu)似乎看起來更加清晰抱婉,但問題是档叔,對象屬性有可能是一個方法。
{
name: {
[[value]]: 函數(shù)地址
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
如上蒸绩,JS需要在內(nèi)存中創(chuàng)建一個方法衙四,然后將方法地址綁定給name.value
函數(shù)是一個單獨的值,JS是允許更改函數(shù)的上下文環(huán)境的患亿。
function f(){}
const obj = {f}
f();
obj.f()
如上传蹈,f是一個方法,我們允許f單獨執(zhí)行窍育,則它的運行環(huán)境是window。在obj.f()中它的運行環(huán)境便是obj宴胧。
那么我們需要一個變量來指明我所運行的上下文漱抓,便于我獲取我需要的方法或變量,這個時候就有了this關鍵字恕齐,它的目的就是指明當前環(huán)境的上下文乞娄。
this的運用環(huán)境
全局環(huán)境
console.log(this === window); //true
function func(){
console.log(this === window);
}
func(); //true
以上代碼說明不管實在函數(shù)內(nèi)部還是在外部,只要實在全局變量下,所以的this都指向頂層對象window仪或。
構(gòu)造函數(shù)
在構(gòu)造函數(shù)中this指的是當前實例對象确镊。
function Person(name){this.name = name};
Person('ccg');
const p = new Person('ccg2');
console.log(name); //ccg
conso.log(p.name); //ccg2
如上,Person是一個簡單的構(gòu)造函數(shù)范删。我們將Person當方法調(diào)用時蕾域,this指向window,便會聲明一個全局變量name到旦。我們將Person當構(gòu)造函數(shù)時旨巷,this便指向當前實例p,p便有了自己的屬性name添忘。
對象的方法
我們都知道采呐,在對象的方法內(nèi),this默認是指向當前對象的搁骑。但是下列幾種比較特殊:
// 情況一
(obj.foo = obj.foo)() // window
// 情況二
(false || obj.foo)() // window
// 情況三
(1, obj.foo)() // window
上面代碼中斧吐,obj.foo就是一個值。這個值真正調(diào)用的時候仲器,運行環(huán)境已經(jīng)不是obj了煤率,而是全局環(huán)境,所以this不再指向obj娄周。
可以這樣理解涕侈,JavaScript 引擎內(nèi)部,obj和obj.foo儲存在兩個內(nèi)存地址煤辨,稱為地址一和地址二裳涛。obj.foo()這樣調(diào)用時,是從地址一調(diào)用地址二众辨,因此地址二的運行環(huán)境是地址一端三,this指向obj。但是鹃彻,上面三種情況郊闯,都是直接取出地址二進行調(diào)用,這樣的話蛛株,運行環(huán)境就是全局環(huán)境团赁,因此this指向全局環(huán)境。上面三種情況等同于下面的代碼谨履。
const a = {
aName:'a',
b:{
f:function(){
conso.log(this.aName);'
}
}
}
a.b.f(); //undefined
如上欢摄,this指向的a.b并不能指向a
我們要么在a.b中添加變量,要么使用變量賦值進行更改this的上下文笋粟。
this注意點
由于this是不確定的怀挠,盡量不要再方法中使用多重this
const obj = {
name:'',
func:function(){
(function test(){
console.log(this);
})()
}
}
obj.func(); //window
如上析蝴,在obj.func中添加一個自執(zhí)行函數(shù),在自執(zhí)行函數(shù)中this便指向了window绿淋,因為自執(zhí)行函數(shù)的調(diào)用方式window闷畸。
一個解決辦法就是在obj.func聲明that = this;然后早自執(zhí)行函數(shù)值使用that進行調(diào)用obj吞滞。但是當我們嵌套多層時需要創(chuàng)建很多個變量來指明上下文佑菩,是一件很麻煩的事情。
或者我們可以使用JS的嚴格模式防止this指向window冯吓,在嚴格模式中禁止this指向window倘待,會報錯。
避免再數(shù)組處理方法中使用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
上面代碼中凸舵,foreach方法的回調(diào)函數(shù)中的this,其實是指向window對象失尖,因此取不到o.v的值啊奄。原因跟上一段的多層this是一樣的,就是內(nèi)層的this不指向外部掀潮,而指向頂層對象菇夸。
解決這個問題的一種方法,就是前面提到的仪吧,使用中間變量固定this庄新。
另一種方法是將this當作foreach方法的第二個參數(shù),固定它的運行環(huán)境薯鼠。如下
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
避免回調(diào)中使用this
var o = new Object();
o.f = function () {
console.log(this === o);
}
// jQuery 的寫法
$('#button').on('click', o.f);
上面代碼中择诈,點擊按鈕以后,控制臺會顯示false出皇。原因是此時this不再指向o對象羞芍,而是指向按鈕的 DOM 對象,因為f方法是在按鈕對象的環(huán)境中被調(diào)用的郊艘。這種細微的差別荷科,很容易在編程中忽視,導致難以察覺的錯誤纱注。
為了解決這個問題畏浆,可以采用下面的一些方法對this進行綁定,也就是使得this固定指向某個對象狞贱,減少不確定性
JS中this的動態(tài)綁定刻获,雖然為JS帶來了一定的靈活性,但也為初級開發(fā)者帶來了一定的難度斥滤,初學者可能無法快速的定位到this的上下文将鸵。有時候我們需要將this固定下來,JS提供了call佑颇、apply顶掉、bind三個方法專門用于this指定
Function.prototype.call();
函數(shù)實例的call方法可以為函數(shù)指定運行的作用于,即函數(shù)內(nèi)部this的指向挑胸。
function func(){
console.log(this);
}
const obj = {};
func(); //window
func.call(obj);// obj
如上痒筒,func是一個全局方法,obj是一個全局變量茬贵,我們通過call方法將obj作為參數(shù)傳遞簿透,在func中this便指向了obj。
注意:call方法會在我們設置時自執(zhí)行解藻。
call方法的參數(shù)老充,是應該是一個對象,當我們沒傳或者傳遞null螟左、undefined時啡浊,參數(shù)默認是window也就是全局對象。如下:
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
如果call中參數(shù)是一個原始值胶背,數(shù)字或者字符串或者Boolean等巷嚣,默認會包裝成對象傳給call
call的第一個參數(shù)是需要綁定的對象,從第二個參數(shù)開始便是運行函數(shù)所需要的參數(shù)
function func(a, b) {
this.name = 'test';
console.log(a + b)
};
const obj = { func };
func.call(obj, 1, 2); //3
console.log(obj); //test
如上钳吟,我們可以將func定義為一個公共方法來處理我們需要處理的數(shù)據(jù)對象廷粒。
call方法的一個應用是調(diào)用對象的原生方法。
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆蓋掉繼承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
如上红且,hasOwnProperty是obj對象繼承的方法坝茎,如果這個方法一旦被覆蓋,就不會得到正確結(jié)果直焙。call方法可以解決這個問題景东,它將hasOwnProperty方法的原始定義放到obj對象上執(zhí)行,這樣無論obj上有沒有同名方法奔誓,都不會影響結(jié)果斤吐。
Function.prototype.apply();
apply的使用方法基本和call一致,也是用來改變函數(shù)運行上下文的方法厨喂,區(qū)別是apply只有兩個參數(shù)和措,第一個參數(shù)是需要綁定的Objec,第二個參數(shù)是一個數(shù)組蜕煌,數(shù)組由函數(shù)需要的參數(shù)構(gòu)成派阱。
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
因為apply第二個參數(shù)需要穿數(shù)組,所以我們可以使用apply做一些特殊的事情斜纪。
1.找出數(shù)組最大元素
const a = [1,2,45,2,3,5,2314,12,4];
Math.max.apply(null,a); //2314
2.將數(shù)組的空元素轉(zhuǎn)換為undefined
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]
空元素與undefined的區(qū)別是我們在forEach時空元素會被跳過贫母,undefined則會被讀為undefined文兑。
3.轉(zhuǎn)換疑似數(shù)組的對象
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]
利用數(shù)組的slice屬性可以裝類似于數(shù)組的對象轉(zhuǎn)換成數(shù)組(例如arguments)
從上面的代碼可以看出,這個方法運行的前提是對象必須有l(wèi)ength屬性腺劣。
4.綁定回調(diào)函數(shù)的對象绿贞。
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);
Function.prototype.bind()
apply和call方法可以用來改變this的指向,但是它們是立即執(zhí)行函數(shù)橘原,也就是我們放什么時候綁定籍铁,什么時候就會自執(zhí)行一次。
更簡潔的辦法就是使用bind綁定趾断,bind的傳參與call一致拒名,但是它不會立即執(zhí)行
const obj = {
name:'ccg',
func:function(){
'use strict'
console.log(this.name);
}
}
const print = obj.func;
print();//Uncaught TypeError: Cannot read property 'name' of undefined
如上,我們在將obj.func作為變量進行賦值給print芋酌,再執(zhí)行print會報錯增显,因為在print中this指向的是window,沒有name屬性脐帝。
const print = obj.func.bind(obj);
print(); //ccg
當我們使用bind進行綁定之后便可以使用甸怕。且不會立即執(zhí)行。
bind綁定需要注意:
1.每次綁定都會返回一個方法腮恩,會產(chǎn)生一些問題梢杭。例如事件綁定時
element.addEventListener('click', o.m.bind(o));
element.removeEventListener('click', o.m.bind(o)); //無效
如上,我們在事件綁定時使用bind秸滴,會生成一個匿名函數(shù)武契,并且我們沒辦法取消綁定。
但是我們可以將事件寫成一個變量荡含,如下:
var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);
2.結(jié)合回調(diào)函數(shù)使用
var counter = {
count: 0,
inc: function () {
'use strict';
this.count++;
}
};
function callIt(callback) {
callback();
}
callIt(counter.inc.bind(counter));
counter.count // 1