this call apply bind

在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
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咒唆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子释液,更是在濱河造成了極大的恐慌全释,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件误债,死亡現(xiàn)場離奇詭異浸船,居然都是意外死亡,警方通過查閱死者的電腦和手機寝蹈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門李命,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人箫老,你說我怎么就攤上這事封字。” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵阔籽,是天一觀的道長流妻。 經(jīng)常有香客問我,道長笆制,這世上最難降的妖魔是什么合冀? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮项贺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峭判。我一直安慰自己开缎,他們只是感情好,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布林螃。 她就那樣靜靜地躺著奕删,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疗认。 梳的紋絲不亂的頭發(fā)上完残,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音横漏,去河邊找鬼谨设。 笑死,一個胖子當著我的面吹牛缎浇,可吹牛的內(nèi)容都是我干的扎拣。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼素跺,長吁一口氣:“原來是場噩夢啊……” “哼二蓝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起指厌,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤刊愚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后踩验,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸥诽,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年箕憾,在試婚紗的時候發(fā)現(xiàn)自己被綠了衙传。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡厕九,死狀恐怖蓖捶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扁远,我是刑警寧澤俊鱼,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布刻像,位于F島的核電站,受9級特大地震影響并闲,放射性物質(zhì)發(fā)生泄漏细睡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一帝火、第九天 我趴在偏房一處隱蔽的房頂上張望溜徙。 院中可真熱鬧,春花似錦犀填、人聲如沸蠢壹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽图贸。三九已至,卻和暖如春冕广,著一層夾襖步出監(jiān)牢的瞬間疏日,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工撒汉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沟优,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓睬辐,卻偏偏與公主長得像净神,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子溉委,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350