日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

javascript:this 关键字

發布時間:2023/12/31 javascript 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 javascript:this 关键字 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

看過[阮一峰]()的關于 this 的教程,講了很多比較好的例子,但沒有對其本質的東西解釋清楚,而且部分例證存在問題;于是,打算重寫本章節,從this的本質入手;

本文為作者的原創作品,轉載需注明出處;

this 是什么?

this可以理解為一個指針,指向調用對象;

判斷 this 是什么的四個法則

官網定義

先來看第一段官方的解釋,

In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked. The value of this is determined using a simple series of steps:

  • If the function is invoked using Function.call or Function.apply, this will be set to the first argument passed to call/apply. If the first argument passed to call/apply is null or undefined, this will refer to the global object (which is the window object in Web browsers).
  • If the function being invoked was created using Function.bind, this will be the first argument that was passed to bind at the time the function was created.
  • If the function is being invoked as a method of an object, this will refer to that object.
  • Otherwise, the function is being invoked as a standalone function not attached to any object, and this will refer to the global object.
  • 大致翻譯如下,
    this是這么一個特殊的關鍵字,它是用來指向一個當前正在被調用( a being invoked )方法的調用對象的;( 等等,這句話其實隱藏了一個非常關鍵的信息,那就是this是在運行期 生效的,怎么生效的?在運行期,this被賦值,將某個對象賦值給this,與聲明期無關,也就是說,this是運行期相關的 );this的賦值場景,歸納起來,分為如下四種情況,

  • 如果方法是被Function.call或者Function.apply調用執行.... bla..bla..
    參考 function prototype call 小節
  • 如果是被Function.bind... bla...bla
    參考 function prototype bind 小節
  • 如果某個方法在運行期是被一個對象( an object )調用( 備注:這里為了便于理解,我針對這種情況,自己給起了個名稱叫關聯調用 ),在運行期,會將該 object 的引用賦值給該方法的this。
    備注:被一個對象調用?何解?其實就是指語句obj.func(),這個時候,func()方法內部的this將會被賦值為obj對象的引用,也就是指向obj;
  • 如果該方法在運行期被當做一個沒有依附在任何 object 上的一個獨立方法被調用(is being invoked as a standalone function not attached to any object ),那么該方法內部的this將會被賦值為全局對象(在瀏覽器端就是 windows )
    獨立方法 ( standalone function )?在運行期,如果func方法被obj關聯調用的,既是通過obj.func()的方式,那么它就不是standalone的;如果是直接被調用,沒有任何對象關聯,既是通過func()調用,那么這就是standalone的。
  • this 運行期相關

    官網定義 2

    再來看另外一句非常精煉的描述,來加深理解

    The this keyword is relative to the execution context, not the declaration context.

    this關鍵字與運行環境有關而與聲明環境無關;(補充,而作用域鏈和閉包是在函數的聲明期創建的,參考創建時機)

    補充,是如何與函數的運行期相關的,參考this 指針運行時賦值

    我的補充

    法則 #3 和 #4,大多數情況都非常容易理解,有幾種情況需要特別注意,

  • 函數嵌套
    需要注意的是object對象中的函數內部再次嵌套函數的情況,

    var name = "windows"; var obj = { name:"object", f1:function(){ console.log("this: "+this.name)function f2(){ console.log("this: " + this.name)}f2(); } };

    執行

    > obj.f1();this: objectthis: windows

    可以看到,在運行期,被調用函數 f1() 中的this指向 obj_,而被調用函數 _f2() 中的this指向的是 windows ( global object );因為 f1 函數在當前的運行時中是通過 obj.f1() 進行的關聯調用,所以,根據定義 #3,在當前的運行期間,_f1()_ 內部的 this 是指向 obj 對象的( 通過將 obj 的引用直接賦值給 this ),而, f2 函數在運行期是沒有與其它 object 進行關聯調用,所以,在當前的運行時期,_f2_ 是一個 standalone 的函數,所以,根據定義 #4,在當前的運行期間,_f2()_ 的內部this是指向 windows 的。(注意,這里我反復強調當前運行期間,是因為this是在運行時被賦值的,所以,要特別注意的是,即使某個函數的定義不變,但在不同的執行環境(運行環境)中,this是會發生變化;)

  • 回調函數
    參看函數回調場景-1和函數回調場景-2
  • 函數賦值
    參看將函數賦值-standalone以及相關變種章節
  • 可見,要判斷this在運行期到底指的是什么,并沒有那么容易,但是,只要牢牢的把握好兩點,就可以迎刃而解,

    • this是運行期相關的
      更確切的說,this是在運行期被賦值的,所以,它的值是在運行期動態確定的。
    • this是否與其它對象關聯調用
      這里的關聯調用指的是 javascript 的一種語法,既是調用語句顯式的寫為obj.func(),另外需要注意的是,_javascript_ 方法的調用不會隱式的隱含 this。只要沒有顯式的關聯調用,那么就是standalone的調用,就符合法則 #4,所以,this指向 _Global Object_。

    this 的 Object

    注意,this定義中所指的Object指的是 javascript 的 Object 類型,既是通過

    var o1 = {}; var o2 = new Object(); var o3 = Object.create(Object.prototype);

    這樣的方式構建出來的對象;

    備注,最開始,自己有個思維的誤區,認為既然 javascript 一切皆為對象,那么this指針是指向對象的,那么是不是也可以指向Function,Number等對象?答案是否定的。

    起初,我是按照上面的邏輯來理解的,直到當我總結到bind 是如何實現的小節后,發現Function對象在調用方法屬性bind的時候,bind方法內部的this指向的是Function,這才恍然大悟,this的Object實際上是可以指向任何 javascript Object的,包括 Object_、_Function 等。

    this 是變化的

    我們來看這樣一個例子,

    var C = "王麻子";var A = {name: '張三',describe: function () {return '姓名:'+ this.name;} };var B = {name: '李四' };// 執行, > A.describe();'張三'> B.describe = A.describe; > B.describe()'李四'> var describe = A.describe; > describe();'王麻子'

    可以看到,雖然 A.describe 方法的定義不變,但是其運行時環境發生了變化,_this_ 的指向也就發生了變化。

    > B.describe = A.describe; > B.describe()'李四'

    在運行時,相當于運行的是 B 的 describe 方法

    > var describe = A.describe; > describe();'王麻子'

    在運行時,相當于運行的是 windows 的 describe 方法

    方法調用沒有隱含 this

    經常寫 Java 代碼的原因,經常會習慣性的認為只要在對象方法里面調用某個方法或者屬性,隱含了 this,比如

    public class Person{String name;public String getName(){return name;}public String getName2(){return this.name;}}

    而 Javascript 實際上并沒有這種隱含的表達方式;詳細驗證過程參考將函數賦值-standalone

    關聯調用 - 容易混淆的場景

    從this 是什么章節中,為了方便對 #3 進行描述,我起了個名字叫做 關聯調用 ;那么有些情況看似是 _關聯調用_,實則不然;

    我們有一個標準的對象,定義如下,

    var name = "windows"; var obj = {name: "obj",foo: function () {console.log("this: "+ this.name);} };

    通過標準的 關聯調用 的方式,我們進行如下的調用,

    > obj.foo() 'this: obj'

    根據法則 #3 既 關聯調用 的定義,得到 this -> obj_;如果事事都如此的簡單,如此的標準,那可就好了,總會有些讓人費解的情況,現在來看看如下的一些特殊的例子,加深對 _關聯調用 的理解。

    將函數賦值 - standalone

    > var fooo = obj.foo > fooo();'this: windows'

    輸出的 windows_,既是 _this -> global object_,而不是我們期望的 _obj_;為什么?原因是,_obj.foo 其實是 foo 函數的函數地址,通過 var fooo = obj.foo 將該函數的地址賦給了變量 _fooo_,那么當執行

    > fooo();

    的時候,fooo() 執行的是是一個standalone的方法,根據法則 #4,所以該方法內部的this指向的是 Global Object_;注意,_obj.foo 表示函數 foo 的入口地址,所以,變量 fooo 等價與 foo 函數。

    備注:由于受到寫 Java 代碼習慣的原因,很容易將這里解釋為默認執行的是this.fooo(),_fooo()_ 的調用隱含了this,因此就會想到,由于this指向的 Global Object_,所以這里當然返回的就是this: windows;但是,這樣解釋,是不對的,因為 _Javascript 壓根沒有這種隱含this的概念,參看用例,

    var name = "windows";var o = {name : "o",f2 : function(){console.log( "o -> f2");console.log( "this: "this.name );},f : function(){console.log("f.this -> " + this.name);var f2 = function(){console.log( "f -> f2");console.log( this.name );}f2(); // f -> f2this.f2(); // o -> f2}}

    可以看到,在 o.f() 函數中,如果 f2() 的調用隱含了this,那么 f2() 和 this.f2() 兩者調用應該是等價的;但是,在實際執行過程中,_f2()_ 和 this.f2() 執行的是兩個截然不同的方法,因此 f2() ≠ this.f2()_,所以 _f2() 并沒有隱示的表示為 _this.f2()_;

    將函數賦值變種 - 匿名 standalone 函數立即執行

    > (obj.foo = obj.foo)() 'this: windows'

    首先,立即執行 foo 函數,然后將 foo 函數賦值給對象 obj 對象的 foo 屬性;等價于執行如下的代碼,

    var name = "windows"; var obj = { name : "obj" }; (obj.foo = function () {console.log("this: " + this.name); })();

    輸出,

    'this: windows'

    可以看到,_this_ -> _global object_,這里為什么指向的是 _global object_?其實這里的立即執行過程,就是執行的如下代碼,

    (function () {console.log("this: " + this.name); }());

    由此可以看出,實際上進行一個匿名函數的立即執行;也就是說執行過程中并沒有使用 關聯調用_,而是一次 _standalone 函數的自身調用,所以根據法則 #4,_this_ -> _global object_。執行完以后,將該匿名函數賦值給 _obj.foo_。

    再次執行,

    > obj.foo();'this: obj'

    這次執行的過程是一次標準的 關聯調用 過程,所以根據法則 #3,_this_ -> _obj_。

    作為判斷條件 - 匿名函數立即執行

    > (false || obj.foo)() 'windows'

    等價于執行,

    (false || function () {console.log("this: " + this.name); })()

    原理和函數賦值變種-匿名 standalone 函數立即執行 一致,等價于立即執行如下的匿名函數

    (function () {console.log("this: " + this.name); })()

    其實,把這個例子再做一個細微的更改,其中邏輯就看得更清楚了,為 foo 函數添加一個返回值 return true

    var name = "windows"; var obj ={name: "obj",foo: function () {console.log("this: "+ this.name);return true;} };

    再次執行,

    > (false || obj.foo)() 'windows'true

    可見,_obj.foo_ 函數執行以后,返回 _true_。上述代碼其實等價于執行如下的代碼,

    (false || function () {console.log("this: " + this.name);return true; })()

    函數回調場景 0 - 基本原理

    var counter = {count: 0,inc: function () {'use strict';this.count++;} };function callIt(callback) {callback(); }> callIt(counter.inc)TypeError: Cannot read property 'count' of undefined

    可以看到,把一個定義有this關鍵字的函數作為其它函數的回調函數,是危險的,因為this在運行期會被重新賦值,上述例子很直觀的描述了這一點,之所以報錯,是因為this指向了 _Global Object_。要解決這樣的問題,可以使用bind,調用的時候改為

    > callIt(counter.inc.bind(counter))1

    函數回調場景 1 - setTimeout

    var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ console.log(this.name); }, waitShowName : function(){ setTimeout(this.showName, 1000); } }; // 執行,> nameObj.waitShowName();'Tom'undefined

    setTimeout(this.showName, 1000);將 nameObj.showName 函數作為回調函數參數傳遞給 setTimeout_;那么為什么當 _setTimeout 執行回調的時候,_nameObj.showName_ 方法返回的是 undefined 呢?為什么不是返回全局對象對應的 name Bob_?原因只有一個,那就是 _setTimeout 有自己的 this 對象,而它沒有 name 屬性,而在回調 showName 函數的時候,_showName_ 函數中的 this 正是 setTimeout 上下文中的 this_,而該 _this 并沒有定義 name 屬性,所以這里返回 _undefined_。

    函數回調場景 2 - DOM 對象

    var o = new Object();o.f = function () {console.log(this === o); }o.f() // true,得到期望的結果 this -> o

    但是,如果將f方法指定給某個click事件,this的指向發生了改變,

    $('#button').on('click', o.f);

    點擊按鈕以后,返回的是false,是因為在執行過程中,this不再指向對象o了而改為指向了按鈕的DOM對象了;Sounds Good,但問題是,怎么被改動的?看了一下 jQuery 的源碼,_event.js_,摘錄重要的片段如下,

    function on( elem, types, selector, data, fn, one ) {.......if ( one === 1 ) {origFn = fn;fn = function( event ) {// Can use an empty set, since event contains the infojQuery().off( event );return origFn.apply( this, arguments );};// Use same guid so caller can remove using origFnfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );}....... }

    o.f 函數的地址賦值給 fn 參數,_fn_ -> origFn_,最后是通過origFn.apply( this, arguments );來調用 _o.f 函數的,而這里的 this 就是當前的 DOM 對象,既是這個按鈕 button_;通過這樣的方式,在執行過程中,通過回調函數 _$("button").on(...) 成功的將新的 this 對象 button 注入了 o.f 函數。那么如何解決呢?參看function.prototype.apply())
    的小節#3,動態綁定回調函數。

    函數回調場景 3 - 數組對象方法的回調

    var obj = {name: '張三',times: [1, 2, 3],print: function () {this.times.forEach(function (n) {console.log(this.name);});} };> obj.print();'undefined''undefined''undefined'

    這里我們期望的是,依次根據數組 times 的長度,輸出 obj.name 三次,但是實際運行結果是,數組雖然循環了三次,但是每次輸出都是 _undefined_,那是因為匿名函數

    function(n){console.log(this.name); }

    作為數組 times 的方法 forEach 的回調函數執行,在 forEach 方法內部該匿名函數必然是作為 standalone 方法執行的,所以,this指向了 _Global Object_;

    進一步,為什么“在 forEach 方法內部該匿名函數必然是作為 standalone 方法執行的”?為什么必然是作為 standalone 方法執行?是因為不能在 forEach 函數中使用 this.fn() 的方式來調用該匿名回調函數( fn 作為參數引用該匿名回調函數 ),因為如果這樣做,在運行時期會報錯,因為在 forEach 函數的 this 對象中找不到 fn 這樣的屬性,而該 this 對象指向的是 obj.times 數組對象。因此,得到結論“在 forEach 方法內部該匿名函數必然是作為 standalone 方法執行的”

    解決辦法,使用 bind

    obj.print = function () {this.times.forEach(function (n) {console.log(this.name);}.bind(this)); };> obj.print()'張三''張三''張三'

    將 obj 對象作為 this 綁定到該匿名函數上,然后再作為回調函數參數傳遞給 forEach 函數,這樣,在 forEach 函數中,用 standalone 的方式調用 fn 的時候,_fn_ 中的 this 指向的就是數組對象 obj 對象,這樣,我們就能順利的輸出 obj.name 了。

    綁定 this

    有上述描述可知,this的值在運行時根據不同上下文環境有不同的值,因此我們說this的值是變化的,這就給我們的編程帶來了麻煩,有時候,我們期望,得到一個固定的this。Javascript 提供了call、apply以及bind這三個方法,來固定this的指向;這三個方法存儲在 function.prototype 域中,

    function.prototype.call()

    總結起來,就是解決函數在調用的時候,如何解決this動態變化的問題。

    調用格式,

    func.call(thisValue, arg1, arg2, ...)

    第一個參數是在運行時用來賦值給 func 函數內部的 this 的。

    通過f.call(obj)的方式調用函數,在運行時,將 obj 賦值給 _this_;

    var obj = {};var f = function () {return this; };f() === this // true f.call(obj) === obj // true

    call方法的參數是一個對象,如果參數為 空_、_null 或者 _undefined_,則使用默認的全局對象;

    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方法的參數是一個原始值,那么這個原始值會自動轉成對應的包裝對象,然后賦值給 this

    var f = function () {return this; };> f.call(5)[Number: 5]

    call方法可以接受多個參數,第一個參數就是賦值給 this 的對象,

    var obj = {name : 'obj' }function add(a, b) {console.log(this.name);return a + b; }> add.call(obj, 1, 2) obj3

    call方法可以調用對象的原生方法;

    var obj = {}; obj.hasOwnProperty('toString') // false// “覆蓋”掉繼承的 hasOwnProperty 方法 obj.hasOwnProperty = function () {return true; }; obj.hasOwnProperty('toString') // trueObject.prototype.hasOwnProperty.call(obj, 'toString') // false

    方法 hasOwnProperty 是對象 obj 從 Object.prototype 中繼承的方法,如果一旦被覆蓋,就不會得到正確的結果,那么,我們可以使用call的方式調用原生方法,將 obj 作為 this 在運行時調用,這樣,變通的,我們就可以調用 obj 對象所繼承的原生方法了。

    function.prototype.apply()

    總結起來,和call一樣,就是解決函數在調用的時候,如何解決this動態變化的問題。

    apply方法的作用與call方法類似,也是改變this指向,然后再調用該函數。唯一的區別就是,它接收一個數組作為函數執行時的參數,使用格式如下。 func.apply(thisValue, [arg1, arg2, ...]) apply方法的第一個參數也是this所要指向的那個對象,如果設為null或undefined,則等同于指定全局對象。第二個參數則是一個數組,該數組的所有成員依次作為參數,傳入原函數。

    原函數的參數,在call方法中必須一個個添加,但是在apply方法中,必須以數組形式添加

    function f(x,y){console.log(x+y); }f.call(null,1,1) // 2 f.apply(null,[1,1]) // 2
  • 找出數組最大的元素

    var a = [10, 2, 4, 15, 9]; Math.max.apply(null, a) // 15
  • 將數組的空元素變為 undefined

    Array.apply(null, ["a",,"b"]) // [ 'a', undefined, 'b' ]

    空元素與undefined的差別在于,數組的forEach方法會跳過空元素,但是不會跳過undefined。因此,遍歷內部元素的時候,會得到不同的結果。

    var a = ['a', , 'b'];function print(i) {console.log(i);}a.forEach(print)// a// bArray.apply(null, a).forEach(print)// a// undefined// b
  • 綁定回調函數的對象
    函數回調場景-2我們看到this被動態的更改為了 DOM 對象 _button_,這往往不是我們所期望的,所以,我們可以再次綁定回調函數來固定this,如下,

    var o = new Object();o.f = function () {console.log(this === o);}var f = function (){o.f.apply(o);// 或者 o.f.call(o);};$('#button').on('click', f);

    這樣,我們用 f 函數封裝原來的回調函數 o.f_,并使用apply方法固定住this,使其永遠指向 _object o,這樣,就達到了this不被動態修改的目的。

  • function.prototype.bind()

    總結起來,其實就是在把函數作為參數傳遞的時候,如何解決this動態變化的問題。

    解決的問題

    在認識關聯調用 - 容易混淆的場景中,我們濃墨重彩的描述了將函數賦值以后,導致this在運行期發生變化的種種場景,而且在編程過程當中,也是非常容易導致問題的場景;那么有沒有這么一種機制,即便是在函數賦值后,在運行期依然能夠保護并固定住我的this?答案是有的,那就是bind。下面,我們來看一個例子,

    var d = new Date(); d.getTime() // 1481869925657

    我們使用語句 d.getTime() 通過對象 d 關聯調用函數 getTime()_,根據法則 #3,函數 _getTime() 內部的 this 指向的是對象 d_,然后從 _d 對象中成功獲取到了時間。但是,我們稍加改動,將對象 d 中的函數 getTime 賦值給另外一個變量,在執行呢?

    var print = d.getTime; print() // Uncaught TypeError: this is not a Date object.

    Wow~, 畫風突變,得不到時間了,而且還拋出了一個程序異常,好玩,你的程序因此崩潰.. 這就是this在執行期動態變化所導致的,當我們將函數 d.getTime 賦值給 print_,然后語句 _print() 表示將函數 getTime 作為 standalone 的函數在運行期調用,所以,內部的this發生變化,指向了 _Global Object_,也因此,我們得不到時間了,但我們得到一個意想不到的異常..

    Ok, 別怕,孩子,bind登場了,

    var print = d.getTime.bind(d); print() // 148186992565

    在 賦值過程中_,將函數通過bind語法綁定this對象 _d 以后,再賦值給一個新的變量;這樣,即便 print() 再次作為 standalone 的函數在運行期調用,this的指向也不再發生變化,而是固定的指向了對象 _d_。

    bind 是如何實現的

    if(!('bind' in Function.prototype)){Function.prototype.bind = function(){var fn = this; // 當前調用 bind 的當前對象 fn ( fn.bind(..) )var context = arguments[0]; // 用來綁定 this 對象的參數var args = Array.prototype.slice.call(arguments, 1);var fnbound = function(){return fn.apply(context, args);}return fnbound;} }

    給Function對象的prototype原型中新增一個屬性bind,該bind是一個 function 函數;這里要特別特別注意,每次bind調用以后,返回的是一個新的function,

    var fnbound = function(){return fn.apply(context, args);}return fnbound;

    通過 fnbound 函數套一層原函數 fn 作為閉包,然后返回這個新的 function _fnbound_;大部分教程就是這樣介紹即止了;其實,我想問的是,為什么bind要這么設計,直接返回fn.apply(context, args);不是挺好嗎?為什么還要在外面套一層新函數 _fnbound_?Ok,這里我就來試圖解釋下原因吧;

    采用反證法,如果,我們不套這么一層新函數 _fubound_,看看,會怎樣?于是,我們得到如下的實現,

    if(!('bind' in Function.prototype)){Function.prototype.bind = function(){var fn = this; // 當前調用 bind 的當前對象 fn ( fn.bind(..) )var context = arguments[0]; // 用來綁定 this 對象的參數var args = Array.prototype.slice.call(arguments, 1);return fn.apply(context, args);} }

    直接返回fn.apply(context, args),oh,頓時,我明白了,fn.apply(...)這是一條執行命令啊,它會立即執行 fn_,將 _fn 執行的結果返回.. 而我們這里的bind的初衷只是擴充 fn 函數的行為(既綁定this對象),然后返回一個函數的引用,而正式因為我們無法在綁定以后,直接返回原有函數的引用,所以,這里,我們才需要創建一個新的函數并返回這個新的函數的引用,已達到bind的設計目的。Ok,這下總算是清楚了。

    特性

    綁定匿名函數
    obj.print = function () {this.times.forEach(function (n) {console.log(this.name);}.bind(this)); };

    可見,我們可以直接改匿名函數執行bind,然后在將其賦值給某個對象;更詳細的用例參考函數回調場景 3 - 數組對象方法的回調

    作為函數直接調用
    var altwrite = document.write; altwrite("hello");

    在瀏覽器運行這個例子,得到錯誤Uncaught ReferenceError: alwrite is not defined,這個錯誤并沒有真正保留底層的原因,真正的原因是,_document_ 對象的 write 函數再執行的時候,內部this指向了 Global Object

    為了解決上述問題,我們可以bind document 對象,

    altwrite.bind(document)("hello")

    注意這里的寫法,altwrite.bind(document)返回的是一個Function,所以可以直接跟參數調用。

    綁定函數參數

    除了綁定this對象意外,還可以綁定函數中的參數,看如下的例子,

    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

    add.bind(obj, 5);除了綁定 add 函數的this對象為 obj 以外,將其固定為 obj 以外,還綁定了 add 函數的第一個參數 x_,并將其固定為 _5_;這樣,得到的 _newAdd 函數只能接收一個參數,那就是 y 了,因為 x 已經被bind綁定且固定了,所以可以看到,隨后執行的語句newAdd(5)傳遞的實際上是 y 參數。

    若綁定 null 或者 undefined

    如果bind方法的第一個參數是 null 或 _undefined_,等于將this綁定到全局對象,函數運行時this指向 _Global Object_。

    var name = 'windows';function add(x, y) {console.log(this.name);return x + y; }var plus = add.bind(null, 5); // 綁定了 x 參數> plus(10) // 賦值的是 y 參數,于是執行的是 5 + 10'windows'15
    改寫原生方法的使用方式

    首先,

    > [1, 2, 3].push(4)4 // 輸出新增后數組的長度

    等價于

    Array.prototype.push.call([1, 2, 3], 4)

    第一個參數 [1, 2, 3] 綁定 push 函數的this關鍵字,第二個參數 _4_,是需要被添加的值。

    補充一下

    為什么說這里是等價的?我們來解讀一下

    > [1, 2, 3].push(4)4 // 輸出新增后數組的長度

    的執行過程,_[1, 2, 3]_ 作為數組對象,調用其原型中的 Array.prototype.push 方法,很明顯,采用的是關聯調用,因此 push 函數內部的 this 指向的是數組對象 _[1, 2, 3]_;而這里,我們通過

    Array.prototype.push.call([1, 2, 3], 4)

    這樣的調用方式,只是換湯不換藥,同樣是執行的數組中的原型方法 _push_,只是this的傳遞方式不同而已,這里是通過bind直接將this賦值為數組對象 _[1, 2, 3]_,而不是通過之前的關聯調用;所以,兩種調用方式是等價的。

    補充完畢

    再次,

    call 方法調用的是 Function 對象的原型方法既 Function.prototype.call(...)_,那么我們再來將它 _bind 一下,看看會有什么結果

    > var push = Function.prototype.call.bind(Array.prototype.push);> push([1, 2, 3], 4);4 // 返回數組長度// 或者寫為> var a = [1, 2, 3]; > push(a, 4);4 > a[1, 2, 3, 4]

    我們得到了一個具備數組 push 操作的一個新的函數 push(...) ( 注: bind 每次回返回一個新的函數 );

    那是為什么呢?

    可以看到,背后的核心是,

    push([1, 2, 3], 4);

    等價于執行

    Array.prototype.push.call([1, 2, 3], 4)

    所以,我們得證明Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)與Array.prototype.push.call([1, 2, 3], 4)兩個函數的執行過程是等價的( 注意,為什么比較的是執行過程等價,因為call函數是立即執行的,而bind返回的是一個函數引用,所以必須比較兩者的執行過程 );其實,要證明這個問題,最直接方法就是去查看函數Function.prototype.call的源碼,可惜,我在官網 MDN Function.prototype.call() 上面也沒有看到源碼;那么這里,其實可以做一些推理,

    Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)

    通過bind,這里返回一個新的 call 函數,該函數綁定了 Array.prototype.push Function 對象做為其this對象;那么Function.prototype.call函數內部會怎么執行呢?我猜想應該就是執行this.apply(context, params)之類的,this表示的是 Array.prototype.push_,context表示的既是這里的數組對象 _[1, 2, 3]_, params表示的既是這里的參數 _4

    Array.prototype.push.call([1, 2, 3], 4)
    同理,由上述Function.prototype.call函數內部的執行過程是執行this.apply(context, params)的推斷來看,this依然是指向的 Array.prototype.push_,context表示的既是這里的數組對象 _[1, 2, 3]_, params表示的既是這里的參數 _4_;所以,這里的調用方式與 _Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) 的方式等價;所以,我們得出如下結論,
    Array.prototype.push.call([1, 2, 3], 4) <=> Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) <=> push([1, 2, 3], 4)

    使用 bind 的一些注意事項

    每次返回一個新函數

    bind方法每運行一次,就返回一個新函數,這會產生一些問題。比如,監聽事件的時候,不能寫成下面這樣。

    element.addEventListener('click', o.m.bind(o));

    上面代碼中,click 事件綁定bind方法新生成的一個匿名函數。這樣會導致無法取消綁定,所以,下面的代碼是無效的。

    element.removeEventListener('click', o.m.bind(o));

    正確的方法是寫成下面這樣,使得 add 和 remove 使用的是同一個函數的引用。

    var listener = o.m.bind(o); element.addEventListener('click', listener); // ... element.removeEventListener('click', listener);

    use strict

    使用嚴格模式,該部分可以參考阮一峰的教程嚴格模式,說得非常詳細;不過應用到面向對象編程里面,主要就是為了避免this在運行期動態指向 _Global Object_,如果發生這類的情況,報錯;例如

    function f() {'use strict';this.a = 1; };f();// 報錯,this未定義

    當執行過程中,發現函數 f 中的this指向了 _Global Object_,則報錯。

    構造函數中的 this

    this -> Object.prototype instance

    構造函數比較特別,_javascript_ 解析過程不同于其它普通函數;

    假如我們有如下的構造函數,

    var Person = function(name, age){this.name = name;this.age = age; }

    當 javascript 語法解析器解析到如下語句以后,

    var p = new Person('張三', 35);

    實際上執行的是,

    function new( /* 構造函數 */ constructor, /* 構造函數參數 */ param1 ) {// 將 arguments 對象轉為數組var args = [].slice.call(arguments);// 取出構造函數var constructor = args.shift();// 創建一個空對象,繼承構造函數的 prototype 屬性var context = Object.create(constructor.prototype);// 執行構造函數var result = constructor.apply(context, args);// 如果返回結果是對象,就直接返回,則返回 context 對象return (typeof result === 'object' && result != null) ? result : context; }

    備注:_arguments_ 可表示一個函數中所有的參數,也就是一個函數所有參數的結合。

    下面,我們一步一步的來分析該構造函數的實現,弄清楚this指的是什么,

    constructor

    就是 Person 構造函數,

    context

    var context = Object.create(constructor.prototype);通過 constructor.prototype 創建了一個新的對象,也就是 Person.prototype 的一個實例 _Person.prototype isntance_;

    constructor.apply(context, args);

    注意,這步非常關鍵,_context_ 作為 constructor 構造函數的this,所以

    var Person = function(name, age){this.name = name;this.age = age; }

    中的this在執行過程中指向的實際上就是該 context 對象。

    result

    是constructor.apply(context, args);方法調用的返回值,我們當前用例中,_Person_ 構造函數并沒有返回任何東西,所以,這里是 _null_。

    return (typeof result === 'object' && result != null) ? result : context;

    new方法的最后返回值,如果 result 不為 null_,則返回 _result 否則返回的是 context_;我們這個用例,當初始化構造函數完成以后,返回的是 _context 既 _Person.prototype instance_,也就是構造函數中的this指針;這也是大多數構造函數應用的場景。

    Object.prototype instance -> Object.prototype

    var Obj = function (p) {this.p = p; };Obj.prototype.m = function() {return this.p; };

    執行,

    > var o = new Obj('Hello World!');> o.p 'Hello World!'> o.m() 'Hello World!'

    說實話,當我第一次看到這個例子的時候,_o.p_ 還好理解,_o_ 就是表示構造函數 Obj 內部的this對象,是一個通過 Object.create(Obj.prototype) 得到的一份 Obj.prototype 的實例對象;但是,當我看到 o.m 的時候,還是有點懵逼,_Obj.prototype_ 并不是代表的this呀,_Object.create(Obj.prototype)_ 才是( 既 Obj.prototype instance ),所以在 Obj.prototype 上定義的 m 方法,怎么可以通過 o.m() 既通過 Obj.prototype instance 來調用呢?( 注意,關系 o -> Object.create(Obj.prototype) -> Obj.prototype instance -> this != Obj.prototype ) 當理解到 prototype 的涵義有,才知道,_Obj.prototype instance_ 會繼承 Obj.prototype 中的公共屬性的,所以,這里通過 Obj.prototype 對象定義的 m 函數可以通過 Object.prototype instance 進行調用。

    References

    本文轉載自筆者的私人博客,傷神的博客,http://www.shangyang.me/2017/...
    [Javascript中this關鍵字詳解](
    http://www.cnblogs.com/justan...
    jQuery Fundamentals Chapter - The this keyword

    總結

    以上是生活随笔為你收集整理的javascript:this 关键字的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。