JavaScript進階之’this‘

在這裏插入圖片描述

只有掌握了JavaScript中的this操做符你纔算正式邁入JavaScript這門語言的門檻!咱們一直都在用這個框架,那個框架,但每每忽視掉了js最基礎的東西,筆者認爲這些基礎每每纔是走下去,走深,最不可或缺的東西.那咱們就一塊兒來學習一下js中神奇的this吧--------筆者查看了大量有關this的文章,有些是按照本身思路寫的,有些是直接引用其餘做者成熟的思路的文章javascript

1.什麼是this?

學習一個知識首先要理解他的字面含義,經過翻譯咱們知道,this的含義是這,這個(指較近的人或事物)的意思。那麼咱們結合現實,咱們說的「這」在不一樣的環境所指的事物是不同的。那麼在JavaScript中this在不一樣的環境調用所表達的含義也是很是豐富的。若是你以爲JavaScript中的this和其餘面嚮對象語言Java同樣,是指存儲在實例屬性中的值,那你就大錯特錯了。JavaScript中的this有着在這門語言中不可或缺魔力。java

2.運行的宿主(環境)不一樣this含義也是不同

宿主(環境)解釋

JS的運行環境通常由宿主環境和執行期環境共同構成,宿主環境是由外殼程序(如web瀏覽器就是一個外殼程序)生成,執行期環境是由嵌入到外殼程序中的JS引擎(/JS解釋器)生成的,在執行期環境JS能夠生成內置靜態對象、初始化執行環境等。node

對於JavaScript,宿主環境最多見的是web瀏覽器,瀏覽器提供了一個JavaScript運行的環境,這個環境裏面,須要提供一些接口,好讓JavaScript引擎可以和宿主環境對接。JavaScript引擎纔是真正執行JavaScript代碼的地方,常見的引擎有V8(目前最快JavaScript引擎、Google生產)、JavaScript coregit

可是環境不是惟一的,也就是JavaScript不只僅可以在瀏覽器裏面跑,也能在其餘提供了宿主環境的程序裏面跑,最多見的就是nodejs。一樣做爲一個宿主環境,nodejs也有本身的JavaScript引擎--V8。根據官方的定義: Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applicationsgithub

3.全局中的this

1.瀏覽器環境中
  • 在瀏覽器裏,在全局範圍內,this等價於window對象。用var聲明一個變量和給this或者window添加屬性是等價的
<script>
   console.log(this === window) //true
   
   var a = 3;
   console.log(this.a, window.a)//3  3
</script>
複製代碼

說明:在瀏覽器中,window對象同時也是全局對象web

1.node環境中
  • 在node環境裏,若是使用REPL(Read-Eval-Print Loop,簡稱REPL:讀取-求值-輸出,是一個簡單的,交互式的編程環境)來執行程序,this並非最高級的命名空間,最高級的是global.
> this === global
true
複製代碼
  • 但在node環境裏,若是執行一個js腳本,在全局範圍內,this以一個空對象開始做爲最高級的命名空間,這個時候,它和global不是等價的。
index.js 文件在node環境中執行

console.log(this) //Object {}
console.log(this === global); //false
複製代碼
  • 在node環境裏,在全局範圍內,若是你用REPL執行一個腳本文件,用var聲明一個變量並不會和在瀏覽器裏面同樣將這個變量添加給this。
index.js 文件在node環境中執行

var foo = "bar";
console.log(this.foo);//undefined
複製代碼
  • 可是若是你不是用REPL執行腳本文件,而是直接執行代碼,結果和在瀏覽器裏面是同樣的(神坑)
> var foo = "bar";
 > this.foo
 bar
 > global.foo
 bar
複製代碼
  • 在node環境裏,用REPL運行腳本文件的時候,若是在聲明變量的時候沒有使用var或者let,這個變量會自動添加到global對象,可是不會自動添加給this對象。若是是直接執行代碼,則會同時添加給global和this
index.js 文件在node環境中執行

 foo = "bar";
 console.log(this.foo);//undefined
 console.log(global.foo);//bar
複製代碼

上面的幾種種狀況可能你們已經繞暈了,總結起來就是:在瀏覽器裏面this是老大,它等價於window對象,若是你聲明一些全局變量(無論在任何地方),這些變量都會做爲this的屬性。在node裏面,有兩種執行JavaScript代碼的方式,一種是直接執行寫好的JavaScript文件,另一種是直接在裏面執行一行行代碼。對於直接運行一行行JavaScript代碼的方式,global纔是老大,this和它是等價的。在這種狀況下,和瀏覽器比較類似,也就是聲明一些全局變量會自動添加給老大global,順帶也會添加給this。可是在node裏面直接腳本文件就不同了,你聲明的全局變量不會自動添加到this,可是會添加到global對象。因此相同點是,在全局範圍內,全局變量終究是屬於老大的。編程

4.函數(function)中的this

說明:在函數內部,this的值取決於函數被調用的方式瀏覽器

  • 不管是在瀏覽器環境仍是node環境,除了在DOM事件處理程序裏或者給出了thisArg(接下來會講到)外,若是不是用new調用,在函數裏面使用this都是指代全局範圍的this。
<script>
testa()
function testa(){
     testb()
    function testb(){
        console.log(this === window)//true
      }
 }
</script>

複製代碼
index.js 文件在node環境中執行

foo = "bar";

function testThis () {
  this.foo = "foo";
}

console.log(global.foo);//bar
testThis();
console.log(global.foo);//foo

複製代碼

說明:由於上面代碼不在嚴格模式下,且this的值不是由該調用設置的,因此this的值默認指向全局對象。閉包

  • 除非你使用嚴格模式,這時候this就會變成undefined。
<script type="text/javascript">
      foo = "bar";
  
     function testThis() {
        "use strict";
        this.foo = "foo";
     }
  
      console.log(this.foo); //logs "bar"
     testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
 </script>
複製代碼

說明:然而,在嚴格模式下,this將保持他進入執行環境時的值,因此下面的this將會默認爲undefined,因此,在嚴格模式下,若是 this 沒有被執行環境(execution context)定義,那它將保持爲 undefined。app

  • 若是你在調用函數的時候在前面使用了new,this就會變成一個新的值,和global的this脫離干係。
<script type="text/javascript">
      foo = "bar";
  
      function testThis() {
        this.foo = "foo";
      }
  
      console.log(this.foo); //logs "bar"
      new testThis();
     console.log(this.foo); //logs "bar"
 
     console.log(new testThis().foo); //logs "foo"
</script>
複製代碼

函數裏面的this其實相對比較好理解,若是咱們在一個函數裏面使用this,須要注意的就是咱們調用函數的方式,若是是正常的方式調用函數,this指代全局的this,若是咱們加一個new,這個函數就變成了一個構造函數,咱們就建立了一個實例,this指代這個實例,這個和其餘面向對象的語言很像。另外,寫JavaScript很常作的一件事就是綁定事件處理程序,也就是諸如button.addEventListener(‘click’, fn, false)之類的,若是在fn裏面須要使用this,this指代事件處理程序對應的對象,也就是button。

  • 若是想把this的值從一個環境傳到另外一個環境,就要用到call或者apply的方法。
<script type="text/javascript">
// 將一個對象做爲call和apply的第一個參數,this會被綁定到這個對象。
var obj = {a: 'Custom'};

// 這個屬性是在global對象定義的。
var a = 'Global';

function whatsThis(arg) {
  return this.a;  // this的值取決於函數的調用方式
}

whatsThis();          // 'Global'
whatsThis.call(obj);  // 'Custom'
whatsThis.apply(obj); // 'Custom'
</script>
複製代碼

說明:使用 call 和 apply 函數的時候要注意,若是傳遞給 this 的值不是一個對象,JavaScript 會嘗試使用內部 ToObject 操做將其轉換爲對象。所以,若是傳遞的值是一個原始值好比 7 或 'foo',那麼就會使用相關構造函數將它轉換爲對象,因此原始值 7 會被轉換爲對象,像 new Number(7) 這樣,而字符串 'foo' 轉化成 new String('foo') 這樣,例如:下面代碼

<script type="text/javascript">
function bar() {
  console.log(Object.prototype.toString.call(this));
}

//原始值 7 被隱式轉換爲對象
bar.call(7); // [object Number]
</script>
複製代碼
  • ECMAScript 5 引入了 Function.prototype.bind。調用f.bind(someObject)會建立一個與f具備相同函數體和做用域的函數,可是在這個新函數中,this將永久地被綁定到了bind的第一個參數,不管這個函數是如何被調用的。
<script type="text/javascript">
function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
</script>
複製代碼
  • ECMAScript 5 引入了 Function.prototype.bind。調用f.bind(someObject)會建立一個與f具備相同函數體和做用域的函數,可是在這個新函數中,this將永久地被綁定到了bind的第一個參數,不管這個函數是如何被調用的。
<script type="text/javascript">
function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
</script>
複製代碼
  • 在箭頭函數中,this與封閉詞法環境的this保持一致。在全局代碼中,它將被設置爲全局對象:
<script type="text/javascript">
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
</script>
複製代碼

5.原型(prototype)中的this

  • 你建立的每個函數都是函數對象,他們會自動獲取一個特殊的屬性prototype,你能夠給這個屬性賦值。當你用new的方式調用一個函數的時候,你就能經過this訪問你給prototype賦的值了。
<script type="text/javascript">
function Thing() {
       console.log(this.foo);
 }
 
 Thing.prototype.foo = "bar";
 
 var thing = new Thing(); //logs "bar"
 console.log(thing.foo);  //logs "bar"
</script>
複製代碼
  • 當你使用new爲你的函數建立多個實例的時候,這些實例會共享你給prototype設定的值。對於下面的例子,當你調用this.foo的時候,都會返回相同的值,除非你在某個實例裏面重寫了本身的this.foo
<script type="text/javascript">
 function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  Thing.prototype.setFoo = function (newFoo) {
      this.foo = newFoo;
  }
 
 var thing1 = new Thing();
 var thing2 = new Thing();
 
 thing1.logFoo(); //logs "bar"
 thing2.logFoo(); //logs "bar"
 
 thing1.setFoo("foo");
 thing1.logFoo(); //logs "foo";
 thing2.logFoo(); //logs "bar";
 
 thing2.foo = "foobar";
 thing1.logFoo(); //logs "foo";
 thing2.logFoo(); //logs "foobar";
</script>
複製代碼
  • 實例裏面的this是一個特殊的對象。你能夠把this想成一種獲取prototype的值的一種方式。當你在一個實例裏面直接給this添加屬性的時候,會隱藏prototype中與之同名的屬性。若是你想訪問prototype中的這個屬性值而不是你本身設定的屬性值,你能夠經過在實例裏面刪除你本身添加的屬性的方式來實現。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  Thing.prototype.setFoo = function (newFoo) {
      this.foo = newFoo;
  }
 Thing.prototype.deleteFoo = function () {
     delete this.foo;
 }
 var thing = new Thing();
 thing.setFoo("foo");
 thing.logFoo(); //logs "foo";
 thing.deleteFoo();
 thing.logFoo(); //logs "bar";
 thing.foo = "foobar";
 thing.logFoo(); //logs "foobar";
 delete thing.foo;
 thing.logFoo(); //logs "bar";
</script>
複製代碼
  • 或者你也能直接經過引用函數對象的prototype 來得到你須要的值。
<script type="text/javascript">
function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      console.log(this.foo, Thing.prototype.foo);
  }
  
  var thing = new Thing();
  thing.foo = "foo";
  thing.logFoo(); //logs "foo bar";
</script>
複製代碼

此時的this是指,構造函數的原型上的方法至於爲何看下面

  • 經過一個函數建立的實例會共享這個函數的prototype屬性的值,若是你給這個函數的prototype賦值一個Array,那麼全部的實例都會共享這個Array,除非你在實例裏面重寫了這個Array,這種狀況下,函數的prototype的Array就會被隱藏掉。
<script type="text/javascript">
 function Thing() {
 }
 Thing.prototype.things = [];
 
 
 var thing1 = new Thing();
 var thing2 = new Thing();
 thing1.things.push("foo");
 console.log(thing2.things); //logs ["foo"]
</script>
複製代碼
  • 實際上你能夠經過把多個函數的prototype連接起來的從而造成一個原型鏈,所以this就會魔法般地沿着這條原型鏈往上查找直到找你你須要引用的值。
<script type="text/javascript">
  function Thing1() {
  }
  Thing1.prototype.foo = "bar";
  
  function Thing2() {
  }
  Thing2.prototype = new Thing1();
  
  
 var thing = new Thing2();
 console.log(thing.foo); //logs "bar"
</script>
複製代碼
  • 一些人利用原型鏈的特性來在JavaScript模仿經典的面向對象的繼承方式。任何給用於構建原型鏈的函數的this的賦值的語句都會隱藏原型鏈上游的相同的屬性。
<script type="text/javascript">
  function Thing1() {
  }
  Thing1.prototype.foo = "bar";
  
  function Thing2() {
      this.foo = "foo";
  }
  Thing2.prototype = new Thing1();
  
 function Thing3() {
 }
 Thing3.prototype = new Thing2();
 
 var thing = new Thing3();
 console.log(thing.foo); //logs "foo"
</script>
複製代碼
  • 我喜歡把被賦值給prototype的函數叫作方法。在上面的例子中,我已經使用過方法了,如logFoo。這些方法有着相同的prototype,即建立這些實力的原始函數。我一般把這些原始函數叫作構造函數。在prototype裏面定義的方法裏面使用this會影響到當前實例的原型鏈的上游的this。這意味着你直接給this賦值的時候,隱藏了原型鏈上游的相同的屬性值。這個實例的任何方法都會使用這個最新的值而不是原型裏面定義的這個相同的值。
<script type="text/javascript">
  function Thing1() {
  }
  Thing1.prototype.foo = "bar";
  Thing1.prototype.logFoo = function () {
      console.log(this.foo);
  }
  
  function Thing2() {
      this.foo = "foo";
 }
 Thing2.prototype = new Thing1();
 
 
 var thing = new Thing2();
 thing.logFoo(); //logs "foo";
</script>
複製代碼
  • 在JavaScript裏面你能夠嵌套函數,也就是你能夠在函數裏面定義函數。嵌套函數能夠經過閉包捕獲父函數的變量,可是這個函數沒有繼承this
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      var info = "attempting to log this.foo:";
      function doIt() {
          console.log(info, this.foo);
      }
      doIt();
 }
  
 var thing = new Thing();
 thing.logFoo();  //logs "attempting to log this.foo: undefined"
</script>
複製代碼

在doIt裏面的this是global對象或者在嚴格模式下面是undefined。這是形成不少不熟悉JavaScript的人深陷 this陷阱的根源。在這種狀況下事情變得很是糟糕,就像你把一個實例的方法看成一個值,把這個值看成函數參數傳遞給另一個函數可是卻不把這個實例傳遞給這個函數同樣。在這種狀況下,一個方法裏面的環境變成了全局範圍,或者在嚴格模式下面的undefined。

<script type="text/javascript">
function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {  
      console.log(this.foo);   
  }
  
  function doIt(method) {
      method();
 }
 
 
 var thing = new Thing();
 thing.logFoo(); //logs "bar"
 doIt(thing.logFoo); //logs undefined
</script>
複製代碼
  • 一些人喜歡先把this捕獲到一個變量裏面,一般這個變量叫作self,來避免上面這種狀況的發生
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () {
      var self = this;
      var info = "attempting to log this.foo:";
      function doIt() {
          console.log(info, self.foo);
      }
     doIt();
 }
 
 
 var thing = new Thing();
 thing.logFoo();  //logs "attempting to log this.foo: bar"
</script>
複製代碼
  • 可是當你須要把一個方法做爲一個值傳遞給一個函數的時候並無論用。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () { 
      var self = this;
      function doIt() {
          console.log(self.foo);
      }
      doIt();
 }
 
 function doItIndirectly(method) {
     method();
 }
  
 var thing = new Thing();
 thing.logFoo(); //logs "bar"
 doItIndirectly(thing.logFoo); //logs undefined
</script>
複製代碼
  • 你能夠經過bind將實例和方法一切傳遞給函數來解決這個問題,bind是一個函數定義在全部函數和方法的函數對象上面
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () { 
      console.log(this.foo);
  }
  
  function doIt(method) {
      method();
 }
  
 var thing = new Thing();
 doIt(thing.logFoo.bind(thing)); //logs bar
</script>
複製代碼
  • 你一樣可使用apply和call來在新的上下文中調用方法或函數。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
  Thing.prototype.logFoo = function () { 
      function doIt() {
          console.log(this.foo);
      }
     doIt.apply(this);
  }
 
 function doItIndirectly(method) {
     method();
 }
 
 
 var thing = new Thing();
 doItIndirectly(thing.logFoo.bind(thing)); //logs bar
</script>
複製代碼
  • 你能夠用bind來代替任何一個函數或者方法的this,即使它沒有賦值給實例的初始prototype。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
   
  function logFoo(aStr) {
      console.log(aStr, this.foo);
  }
   
 var thing = new Thing();
 logFoo.bind(thing)("using bind"); //logs "using bind bar"
 logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
 logFoo.call(thing, "using call"); //logs "using call bar"
 logFoo("using nothing"); //logs "using nothing undefined"
</script>
複製代碼
  • 你應該避免在構造函數裏面返回任何東西,由於這可能代替原本應該返回的實例。
<script type="text/javascript">
  function Thing() {
      return {};
  }
  Thing.prototype.foo = "bar";
  
  
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  
 var thing = new Thing();
 thing.logFoo(); //Uncaught TypeError: undefined is not a function
</script>
複製代碼

怪的是,若是你在構造函數裏面返回了一個原始值,上面所述的狀況並不會發生而且返回語句被忽略了。最好不要在你將經過new調用的構造函數裏面返回任何類型的數據,即使你知道本身正在作什麼。若是你想建立一個工廠模式,經過一個函數來建立一個實例,這個時候不要使用new來調用函數。固然這個建議是可選的。

  • 你能夠經過使用Object.create來避免使用new,這樣一樣可以建立一個實例。
<script type="text/javascript">
  function Thing() {
  }
  Thing.prototype.foo = "bar";
   
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
   
 var thing =  Object.create(Thing.prototype);
 thing.logFoo(); //logs "bar"
</script>
複製代碼
  • 在這種狀況下並不會調用構造函數
<script type="text/javascript">
  function Thing() {
      this.foo = "foo";
  }
  Thing.prototype.foo = "bar";
  
  Thing.prototype.logFoo = function () {
      console.log(this.foo);
  }
  
 var thing =  Object.create(Thing.prototype);
 thing.logFoo(); //logs "bar"
</script>
複製代碼
  • 由於Object.create不會調用構造函數的特性在你繼承模式下你想經過原型鏈重寫構造函數的時候很是有用。
<script type="text/javascript">
  function Thing1() {
      this.foo = "foo";
  }
  Thing1.prototype.foo = "bar";
  
  function Thing2() {
      this.logFoo(); //logs "bar"
      Thing1.apply(this);
      this.logFoo(); //logs "foo"
 }
 Thing2.prototype = Object.create(Thing1.prototype);
 Thing2.prototype.logFoo = function () {
     console.log(this.foo);
 }
 
 var thing = new Thing2();
</script>
複製代碼

6.對象(object )中的this

  • 在一個對象的一個函數裏,你能夠經過this來引用這個對象的其餘屬性。這個用new來新建一個實例是不同的。
<script type="text/javascript">
 var obj = {
     foo: "bar",
     logFoo: function () {
         console.log(this.foo);
     }
 }; 
 obj.logFoo(); //logs "bar"
</script>
複製代碼
  • 注意,沒有使用new,沒有使用Object.create,也沒有使用函數調用建立一個對象。你也能夠將對象看成一個實例將函數綁定到上面。
<script type="text/javascript">
 var obj = {
     foo: "bar"
 };
 
 function logFoo() {
     console.log(this.foo);
 }
 
 logFoo.apply(obj); //logs "bar"
</script>
複製代碼
  • 當你用這種方式使用this的時候,並不會越出當前的對象。只有有相同直接父元素的屬性才能經過this共享變量
<script type="text/javascript">
  var obj = {
      foo: "bar",
      deeper: {
          logFoo: function () {
              console.log(this.foo);
          }
      }
  };
  
 obj.deeper.logFoo(); //logs undefined
</script>
複製代碼
  • 你能夠直接經過對象引用你須要的屬性
<script type="text/javascript">
	var obj = {
	    foo: "bar",
	    deeper: {
	        logFoo: function () {
	            console.log(obj.foo);
	        }
	    }
	};
	
	obj.deeper.logFoo(); //logs "bar"
</script>
複製代碼

7.DOM(event)中的this

  • 在一個HTML DOM事件處理程序裏面,this始終指向這個處理程序被所綁定到的HTML DOM節點
<script type="text/javascript">
  function Listener() {
      document.getElementById("foo").addEventListener("click",
         this.handleClick);
  }
  Listener.prototype.handleClick = function (event) {
      console.log(this); //logs "<div id="foo"></div>"
  }
  
  var listener = new Listener();
 document.getElementById("foo").click();
</script>
複製代碼
  • 除非你本身經過bind切換了上下文
<script type="text/javascript">
  function Listener() {
      document.getElementById("foo").addEventListener("click", 
          this.handleClick.bind(this));
  }
  Listener.prototype.handleClick = function (event) {
      console.log(this); //logs Listener {handleClick: function}
  }
  
  var listener = new Listener();
 document.getElementById("foo").click();
</script>
複製代碼

8.HTML 中的this

  • 在HTML節點的屬性裏面,你能夠放置JavaScript代碼,this指向了這個元素
<div id="foo" onclick="console.log(this);"></div>
 <script type="text/javascript">
 document.getElementById("foo").click(); //logs <div id="foo"...
 </script>
複製代碼

到此結束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

做者

做者: weshmily科技站長

官網: 百度搜索(weshmily科技)

CSDN博客:blog.csdn.net/qq_27118895

GitHub: github.com/weshmily

公衆號:搜索"weshmilyqd"

相關文章
相關標籤/搜索