1. 程式人生 > >大前端學習筆記整理【六】this關鍵字詳解

大前端學習筆記整理【六】this關鍵字詳解

 這裡有一個微妙但是非常重要的細節,雖然 this 的繫結規則完全取決於呼叫位置,但是隻有 foo() 執行在非 strict mode 下時,預設繫結才能繫結到全域性物件;嚴格模式下與 foo()的呼叫位置無關:

function foo() {
     console.log( this.a );
  }
  var a = 2;
  (function(){
    "use strict";
    foo(); // 2
  })();

b.隱式繫結

 首先,我們來看一段程式碼:

function foo() {
      console.log( this.a );
  }
  var obj = {
      a: 2,
      foo: foo
  };
  obj.foo(); // 2

 我們可以看出,foo()函式的宣告方式和最後的obj中作為引用屬性新增到obj裡,但即使是這樣,嚴格意義上來說,foo()也不屬於obj物件,就算我們把宣告放入obj中,亦是如此。然而,呼叫位置會使用 obj 上下文來引用函式,因此你可以說函式被呼叫時 obj 物件“擁
有”或者“包含”它。

但是無論如何去稱呼這個模式,當foo()被呼叫的時候,指向確實指到了obj物件。當函式引用有上下文物件時,隱式繫結規則會把函式呼叫中的 this 繫結到這個上下文物件。因為呼叫 foo() 時 this 被繫結到 obj ,因此 this.a 和 obj.a 是一樣的。

這裡還會出現一個問題,隱式丟失。

這個怎麼來理解呢?其實就是說在隱式繫結的函式中,可能會出現繫結物件丟失,然後就會應用預設規則,從而把this繫結到全域性物件或者undefined上。這種情況的出現取決於是否使用嚴格模式。

function foo() {
      console.log( this.a );
  }
  var obj = {
      a: 2,
      foo: foo
  };
  var bar = obj.foo; // 函式別名!
  var a = "Kevin"; // a 是全域性物件的屬性
  bar(); // "Kevin"

來看這個例子,雖然bar是obj.foo的一個引用,但是實際上引用的卻是foo這個函式本身,所以此時,bar()是一個沒有任何修飾的函式呼叫,自然就應用了預設規則進行繫結。

還有一種情況,十分常見,也會出現隱式丟失,那就是在回撥函式中。來看下例子:

function foo() {
      console.log( this.a );
  }
  var obj = {
      a: 2,
      foo: foo
  };
  var a = "Kevin"; // a 是全域性物件的屬性
  setTimeout( obj.foo, 100 ); // "Kevin"

引數傳遞其實就是一種隱式賦值,因此我們傳入函式時也會被隱式賦值,所以結果和上一個例子一樣。就像我們看到的那樣,回撥函式丟失 this 繫結是非常常見的。除此之外,還有一種情況 this 的行為會出乎我們意料:呼叫回撥函式的函式可能會修改 this 。無論是哪種情況, this 的改變都是意想不到的,實際上你無法控制回撥函式的執行方式,因此就沒有辦法控制會影響繫結的呼叫位置。

c.顯式繫結

在分析隱式繫結時,我們必須在一個物件內部包含一個指向函式的屬性,並通過這個屬性間接引用函式,從而把 this 間接(隱式)繫結到這個物件上。那麼如果我們不想在物件內部包含函式引用,而想在某個物件上強制呼叫函式,該怎麼做呢?這個時候,我們就需要使用call或者apply兩個方法來呼叫了。嚴格來說,JavaScript 的宿主環境有時會提供一些非常特殊的函式,它們並沒有這兩個方法。但是這樣的函式非常罕見,JavaScript 提供的絕大多數函式以及你自己建立的所有函式都可以使用 call(..) 和 apply(..) 方法。這兩個方法是如何工作的呢?它們的第一個引數是一個物件,它們會把這個物件繫結到this ,接著在呼叫函式時指定這個 this 。因為你可以直接指定 this 的繫結物件,因此我們稱之為顯式繫結。

來看下這個例子:

function foo() {    
      console.log( this.a )  
  }  
  var obj = {    
      a:2  
  };  
  foo.call( obj ); // 2

通過 foo.call(..) ,我們可以在呼叫 foo 時強制把它的 this 繫結到 obj 上。如果你傳入了一個原始值(字串型別、布林型別或者數字型別)來當作 this 的繫結物件,這個原始值會被轉換成它的物件形式(也就是 new String(..) 、 new Boolean(..) 或者new Number(..) )。這通常被稱為“裝箱”。

但是即使這樣,也無法完全解決之前出現的繫結丟失的情況

1. 硬繫結
但是顯式繫結的一個變種可以解決這個問題。
我們來看這樣一個例子

function foo() {
    console.log( this.a );
  }
  var obj = {
      a:2
  };
  var bar = function() {
      foo.call( obj );
  };
  bar(); // 2
  setTimeout( bar, 100 ); // 2
  // 硬繫結的 bar 不可能再修改它的 this
  bar.call( window ); // 2

 我們來看看這個變種到底是怎樣工作的。我們建立了函式 bar() ,並在它的內部手動呼叫了 foo.call(obj) ,因此強制把 foo 的 this 繫結到了 obj 。無論之後如何呼叫函式 bar ,它總會手動在 obj 上呼叫 foo 。這種繫結是一種顯式的強制繫結,因此我們稱之為硬繫結。硬繫結的典型應用場景就是建立一個包裹函式,傳入所有的引數並返回接收到的所有值:

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
  }
  var obj = {
  a:2
  };
  var bar = function() {
    return foo.apply( obj, arguments );
  };
  var b = bar( 3 ); // 2 3
  console.log( b ); // 5

 另一種使用方法是建立一個 i 可以重複使用的輔助函式:

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
  }
  // 簡單的輔助繫結函式
  function bind(fn, obj) {
    return function() {
      return fn.apply( obj, arguments );
    };
  }
  var obj = {
    a:2
  };
  var bar = bind( foo, obj );
  var b = bar( 3 ); // 2 3
  console.log( b ); // 5

 由於硬繫結是一種非常常用的模式,所以在 ES5 中提供了內建的方法 Function.prototype.bind ,它的用法如下:

function foo(something) {
      console.log( this.a, something );
      return this.a + something;
  }
  var obj = {
      a:2
  };
    var bar = foo.bind( obj );
    var b = bar( 3 ); // 2 3
    console.log( b ); // 5

bind(..) 會返回一個硬編碼的新函式,它會把引數設定為 this 的上下文並呼叫原始函式。


2. API呼叫的“上下文”
第三方庫的許多函式,以及 JavaScript 語言和宿主環境中許多新的內建函式,都提供了一個可選的引數,通常被稱為“上下文”(context),其作用和 bind(..) 一樣,確保你的回撥函式使用指定的 this 。舉例來說:

function foo(el) {
        console.log( el, this.id );
    }
    var obj = {
        id: "awesome"
    };
    // 呼叫 foo(..) 時把 this 繫結到 obj
    [1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome    

這些函式實際上就是通過 call(..) 或者 apply(..) 實現了顯式繫結,這樣你可以少些一些程式碼。

d.new繫結

這是第四條也是最後一條 this 的繫結規則,在講解它之前我們首先需要澄清一個非常常見的關於 JavaScript 中函式和物件的誤解。在傳統的面向類的語言中,“建構函式”是類中的一些特殊方法,使用 new 初始化類時會呼叫類中的建構函式。通常的形式是這樣的:

something = new MyClass(..);

JavaScript 也有一個 new 操作符,使用方法看起來也和那些面向類的語言一樣,絕大多數開發者都認為 JavaScript 中 new 的機制也和那些語言一樣。然而,JavaScript 中 new 的機制實際上和麵向類的語言完全不同。首先我們重新定義一下 JavaScript 中的“建構函式”。在 JavaScript 中,建構函式只是一些使用 new 操作符時被呼叫的函式。它們並不會屬於某個類,也不會例項化一個類。實際上,它們甚至都不能說是一種特殊的函式型別,它們只是被 new 操作符呼叫的普通函式而已。
舉例來說,思考一下 Number(..) 作為建構函式時的行為,ES5.1 中這樣描述它:

15.7.2 Number 建構函式當 Number 在 new 表示式中被呼叫時,它是一個建構函式:它會初始化新建立的物件。所以,包括內建物件函式(比如 Number(..) ,詳情請檢視第 3 章)在內的所有函式都可以用 new 來呼叫,這種函式呼叫被稱為建構函式呼叫。這裡有一個重要但是非常細微的區別:實際上並不存在所謂的“建構函式”,只有對於函式的“構造呼叫”。使用 new 來呼叫函式,或者說發生建構函式呼叫時,會自動執行下面的操作。

1. 建立(或者說構造)一個全新的物件。
2. 這個新物件會被執行 [[ 原型 ]] 連線。
3. 這個新物件會繫結到函式呼叫的 this 。
4. 如果函式沒有返回其他物件,那麼 new 表示式中的函式呼叫會自動返回這個新物件。
再來看看下面的例子

function foo(a) {
    this.a = a;
  }
  var bar = new foo(2);
  console.log( bar.a ); // 2

使用 new 來呼叫 foo(..) 時,我們會構造一個新物件並把它繫結到 foo(..) 呼叫中的 this上。 new 是最後一種可以影響函式呼叫時 this 繫結行為的方法,我們稱之為 new 繫結。

優先順序

有種情況我們需要進行考慮,就是在函式呼叫過程中,某個位置如果出現應用了多條繫結規則怎麼辦?那麼要解決這種問題,我們就需要知道規則的優先順序。

毫無疑問,預設規則是四條規則中最低的,所以暫時不做考慮。所以首先,我們需要比對一下顯式繫結與隱式繫結的優先順序,看看哪一個更高一些。

來看如下的例子:

function foo() {
      console.log( this.a );
  }
  var obj1 = {
      a: 2,
      foo: foo
  };
  var obj2 = {
      a: 3,
      foo: foo
  };
  obj1.foo(); // 2
  obj2.foo(); // 3
  obj1.foo.call( obj2 ); // 3
  obj2.foo.call( obj1 ); // 2

 可以看到,顯式繫結優先順序更高,也就是說在判斷時應當先考慮是否可以應用顯式繫結。

接下來,我們需要比對下隱式和new的優先順序,看看誰高誰低。來看如下例子:

function foo(something) {
        this.a = something;
    }
    var obj1 = {
        foo: foo
    };
    var obj2 = {};
    obj1.foo( 2 );
    console.log( obj1.a ); // 2
    obj1.foo.call( obj2, 3 );
    console.log( obj2.a ); // 3
    var bar = new obj1.foo( 4 );
    console.log( obj1.a ); // 2
    console.log( bar.a ); // 4

相關推薦

前端學習筆記整理this關鍵字

 這裡有一個微妙但是非常重要的細節,雖然 this 的繫結規則完全取決於呼叫位置,但是隻有 foo() 執行在非 strict mode 下時,預設繫結才能繫結到全域性物件;嚴格模式下與 foo()的呼叫位置無關: function foo() {      console.log( this.a );

前端學習筆記整理rem與px換算的計算方式

前言 這段時間的小專案中算是真正意義上使用了rem來進行移動端的頁面佈局,專案結束了我反思了一下之前的對於rem的使用...原來我以前對rem用法完全是在搞笑啊!!結合這次這個小專案,我覺得我也有必要對rem佈局以及用法進行一次總結。 ps.文筆可能不太好... 1.什麼是rem 來自於鵝廠ISUX團隊的解釋

前端學習筆記整理行內元素與塊級元素的區別以及絕對定位與固定定位的差異

1.簡要說明: 1. 行內元素會再一條直線上,是在同一行的。比如span和strong; 2. 塊級元素各佔一行。是垂直方向的!比如div和p 假如你要將行內元素變成塊級元素,那麼就只需要在該標籤上加上樣式 display:block; 塊級元素可以用樣式控制其高、寬的值,而行內元素不可以。 行內元素和wi

前端學習筆記整理CSS盒模型與基於盒模型的6種元素居中方案

概覽 CSS盒模型,規定了元素框來處理元素的 內容、內邊距、邊框和外邊距的方式 元素部分是指內容部分,也是最實際的內容,包圍內容的稱之為內邊距,內邊距外圍是邊框,邊框外圍就是外邊距;且外邊距是透明的,所以並不會阻擋其後的元素   * {     margin: 0;     padding: 0;   }

前端學習筆記整理HTTP協議以及http與https的區別

前言 還是老樣子,新部落格開始前總是想先囉嗦幾句...HTTP協議其實在當初學習java時老師就有提過...但是...反正就那麼過去了... 這段時間公司的專案正好要求做https的轉換和遷移,然後自己思考了一下,好像自己對於http連一知半解都算不上...更不提http與https的區別...想想作為一個未

前端學習筆記整理關於JavaScript中的關鍵字——this

寫在前面 工作有那麼一段時間了,但是在工作中,發現自己的理論知識還是有所欠缺。特別是在javascript上,很多東西其實自己屬於知道要用這個,但是不知道為什麼要這麼用...這種情況很是尷尬了,所以寫部落格的很重要一個目的就是鍛鍊我自己的總結能力,把學到的東西總結出來,感覺這樣能讓我更快的去理解所學到的東西。

前端學習筆記整理CSS視覺格式化模型

1. 概念 在視覺格式化模型中,文件樹中的每個元素都將會根據盒模型產生零到多個盒子。這些盒子的佈局由如下因素決定: 盒子的尺寸和型別 定位策略(正常文件流,浮動或者絕對定位) 和文件樹中其他元素的關係 額外的資訊(比如視口的大小,圖片的原始尺寸等) 1.1 視口(viewport) 連續媒體(contin

前端學習筆記整理LESS基礎

第一次接觸CSS預編譯,然後對比後發現其實less的上手容易度確實比sass高不少,再加上公司專案也是使用的less。所以想想還是根據網上的各種教程,整理出來了一些比較基礎的、而且比較能讓我們這種初學者快速上手的例子。 菜雞一隻,程式碼要是有錯或者表達不清的地方,請各位批評指正! 那麼,現在我們就開始正文部分

前端學習筆記關於CSS再次整理

如果你在日常工作中使用 CSS,你的主要目標可能會重點圍繞著使事情“看起來正確”。如何實現這一點經常是遠不如最終結果那麼重要。這意味著比起正確的語法和視覺結果來說,我們更少關心 CSS 的工作原理。 CSS 的視覺結果通常是操作隱藏屬性的間接後果,你可能還沒有意識到這一點。某些 CSS 屬性(比如 backg

Python學習筆記5 轉載基本矩陣運算_20170618

ros class 簡單 lba spa 使用 常見 port 模塊 需要 numpy 庫支持 保存鏈接 http://www.cnblogs.com/chamie/p/4870078.html 1.numpy的導入和使用 from numpy import *;

學習筆記案例財政收入影響因素分析及預測模型

6.0 pearson end 4.5 pandas 問題 特征 ase max() 案例來源:《Python數據分析與挖掘實戰》第13章 案例背景與挖掘目標 輸入數據: 《某市統計年鑒》(1995-2014) 挖掘目標: 梳理影響地方財政收入的關鍵特征,分析、識別影響地

學習筆記案例中醫證型關聯規則挖掘

order 卡方檢驗 eat 業務 並行 pen fill lin 區分 案例來源:《Python數據分析與挖掘實戰》第8章 案例背景與挖掘目標 輸入數據: 挖掘目標: 借助三陰乳腺癌患者的病理信息,挖掘患者的癥狀與中醫證型之間的關聯關系 對截斷治療提供依據,挖掘潛性證素

React 入門學習筆記整理)—— 元件通訊

1、父子元件通訊 1)父元件與子元件通訊,使用Props 父元件將name傳遞給子元件 <GreateH name="kitty"/> 子元件通過props接收父元件的值,並顯示 class GreateH extends React.Component{ static defa

Java基礎筆記整理---07面向物件程式設計-類和物件

int 型別預設值為 0 String型別預設值為null 方法(函式):返回值 方法名(引數型別 引數名稱, ...){方法體(程式碼塊)}this指呼叫的方法中(當前)的變數類與物件1.構造方法:1> 構造物件 構造例項的方法,一個特殊方法,方法名需要與類名相同,

資料學習筆記(十)-Hue的安裝部署和使用

sudo yum install ant asciidoc cyrus-sasl-devel cyrus-sasl-gssapi gcc gcc-c++ krb5-devel libtidy libxml2-devel libxslt-devel mak

web前端學習(三)css學習筆記部分(6)-- 選擇器

9、選擇器詳解 9.1  屬性選擇器   <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</

web前端學習(四)JavaScript學習筆記部分(2)-- JavaScript語法

2.1、Javascript語法-運算子(1)   複數運算子       %取餘     ++     --   賦值運算子     +=     -=     *=     /=     %=   字串操作 <!DOCTYPE html> <html lan

ES系列——配置檔案

elasticsearch的config資料夾裡面有兩個配置檔案:elasticsearch.yml和logging.yml,第一個是es的基本配置檔案,第二個是日誌配置檔案,es也是使用log4j來記錄日誌的,所以logging.yml裡的設定按普通log4j配置檔

轉載FusionSphere架構

computing ssis 修改 hyperv 適配器 drive 特殊 架構 avi FusionSphere底層使用Xen架構: 1.單臺物理機上建立hypervisor系統。 2.將所有單個hypervisor系統整合起來管理使用。 安裝C

轉載Spring AOP 、 JDK動態代理、CGLib動態代理

rto 工廠 第一個 lec 僅支持 sel clas sleep gpo 原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspect Oriented Programing的簡稱,面向切面