理解了JS 中的function調用的小祕密, this, apply(),call(),bind()原來很簡單啊

前言:

我但願在開始讀這篇文章以前,你瞭解過函數調用、this指向、call()apply()bind(),固然,只要瞭解過就好,由於本文就是爲了讓你更好的理解它們。(轉載請註明出處---掘金果醬淋)javascript

開篇:

我將說明幾個你在閱讀下文時可能會以爲困惑的概念,當你以爲疑惑時,能夠回到這裏來看看。html

  1. 「指向」、「指針」怎麼理解?你可能會讀到相似「這只是對象的指針,雖然刪除了指向對象的指針,但對象依舊佔用着內存」的語句。

打個比方:咱們上學時(小學),老師會給學生們安排一個固定的座位號,目的是爲了方便老師讓學生回答問題時不用記住學生姓名,直接喊號,提升效率。那麼,每一個學生對應着一個座位號,如1,2,3分別表明小明,小紅,小三,這裏須要知道,座位號1,2,3和小明,小紅,小三並非徹底相同的事物,前者是一個具備表明性的數字(賓語是數字),後者是實實在在的人(對象),可是二者又存在一一對應的關係,這時候,咱們能夠這樣說,數字1指向小明,數字2指向小紅,數字3指向小三(「指向」是動詞),那麼用圖解的方式畫出來就是1->小明,2->小紅,3->小三。看看,數字與對象之間的箭頭,就是指針(名詞)。也就是說,「指針」就是數字與對象之間的關係的一種名詞性的說法。前端

  1. 「執行環境」怎麼理解?你可能會讀到相似「函數在被調用的時候,會被推入到它的執行環境中,函數的執行環境中存在哪些變量」的語句

打個比方:ECMAScript是一個導演,對象是演員(就那麼幾個如ObjectArrayMath等),變量是道具。運行JavaScript代碼至關於「導演讓演員按照劇本藉助必定的道具在舞臺上演繹出一部話劇」,咱們分析這句話,ECMAScript、對象、變量都有對應的喻體了,那劇本和舞臺又是什麼呢?劇本就是ECMAScript中制定的規則,舞臺就是咱們說的「執行環境」!一場話劇,在不一樣的階段,須要上場的演員和須要使用的道具是不一樣的,所謂「你方唱罷我登場」,「執行環境」在不一樣階段也是不一樣對象的表演舞臺,存放的變量道具也不一樣。java

  1. call(), apply()方法幾乎同樣,只是傳入參數的方式不同,後者第二個參數是一個數組,那爲何要存在着兩個幾乎同樣的東西?這不是重複造輪子麼?

這須要介紹它們使用場景來告訴你緣由:node

Math對象有個max() 方法: 能夠返回傳入數字中的最大者:數組

var max = Math.max(1, 8, 3, 15, 4, 5); // 調用Math的max()取得傳入的最大參數並賦值給max
 console.log(max); // 打印出15
 
複製代碼

上面的例子能夠寫成這樣,效果與上面的徹底同樣:瀏覽器

var max = Math.max.call(Math, 1, 8, 3, 15, 4, 5); // 調用Math的max()取得傳入的最大參數並賦值給max(函數調用的小動做)
 console.log(max); // 打印出15
 
複製代碼

那麼接下來我換個需求,我想要讓你結合max()方法,找出一個數字數組中最大的數字。你可能會說,簡單啊,而後給出了這些方案!閉包

var arr = [1, 8, 3, 15, 4, 5]; //聲明數組表達式
var max = Math.max.call(Math, ...arr); // ES6解構語法======(這裏有疑問看正文後再回來消化下)
console.log(max); // 打印15

複製代碼

能夠的,很機智地實現了需求。可是你回想一下apply() 的第二個參數是什麼?數組!看代碼app

var arr = [1, 8, 3, 15, 4, 5]; //聲明數組表達式
var max = Math.max.apply(Math, arr); // apply方法
console.log(max); // 打印15

複製代碼

有沒有那種 「我正好須要,你正好專業」 的感受~!函數


正文:function調用的「小祕密」

不要被開篇的東西嚇到,本文的正文很簡單的。就是告訴你function調用時你不知道的「小動做」(跟this相關的)。

先要知道: 函數中,thisarguments這兩個函數的屬性,只要在函數執行的時候纔會知道它們分別是指向誰(好好琢磨一下這話)。

1. 通常函數的調用(全局環境中函數的調用)

首先,通常地,咱們在全局環境中聲明函數和執行函數的過程以下:

function hello(someone) {
    console.log(this + "你好啊 " + someone);
} // 函數聲明

hello("掘金果醬淋"); // 函數調用,打印出 //[object Window]你好啊 掘金果醬淋

複製代碼

其實,函數在內部執行的時候,還作了個小動做,也就是咱們要說的小祕密

function hello(someone) {
    console.log(this + "你好啊 " + someone);
} // 函數聲明

hello.call(window, "掘金果醬淋"); // 函數調用,打印出 //[object Window]你好啊 掘金果醬淋

複製代碼

對比一下,發現重點了麼?函數在執行的時候,自動將this指向了window這個全局對象(注意node環境下全局對象是global),與咱們手動讓this指向window打印出來的結果同樣!

那你可能還會反駁,書上不是說,在嚴格模式下(「use strict」)時,this時指向了undefinded,那是由於在嚴格模式下,函數調用的小動做是這樣的:

function hello(someone) {
 'use strict';
    console.log(this + "你好啊 " + someone);
} // 函數聲明

hello("掘金果醬淋"); // 函數調用,打印出 //undefined你好啊 掘金果醬淋
hello.call(undefined, "掘金果醬淋"); // 打印出 //undefined你好啊 掘金果醬淋

複製代碼

怎麼樣,有沒有豁然開朗的感受?

2. 對象方法的調用(對象裏面的函數的調用)

首先,存在這樣一個對象,日常的調用這樣的:

var person = {
    name: "掘金果醬淋",
    hello: function(someone) {
      console.log(this + " 你好啊 " + someone);
    }
  };
// 正常調用
 person.hello("world");// [object Object] 你好啊 世界

複製代碼

有了以前的解密,相信你能理解函數在調用時的小動做是這樣的:

var person = {
    name: "掘金果醬淋",
    hello: function(someone) {
      console.log(this + " 你好啊 " + someone);
    }
  };
// 小動做
 person.hello.call(person, "world");// [object Object] 你好啊 世界

複製代碼

call(), apply(), bind()的原理很簡單啊!

通過前面解析函數調用的小祕密,咱們知道它們都「偷偷地」調用了call()

咱們再看一下它們在MDN中的定義:

call()定義

fun.call(thisArg, arg1, arg2, ...)

參數

thisArg 在 fun 函數運行時指定的 this 值。if(thisArg == undefined|null) this = window,if(thisArg == number|boolean|string) this == new Number()|new Boolean()| new String()

arg1, arg2, ... 指定的參數列表。


apply()定義

func.apply(thisArg, [argsArray])

參數

thisArg 可選的。在 func 函數運行時使用的 this 值。請注意,this可能不是該方法看到的實際值:若是這個函數處於非嚴格模式下,則指定爲 null 或 undefined 時會自動替換爲指向全局對象,原始值會被包裝

argsArray 可選的。一個數組或者類數組對象,其中的數組元素將做爲單獨的參數傳給 func 函數。若是該參數的值爲 null 或 undefined,則表示不須要傳入任何參數。從ECMAScript 5 開始可使用類數組對象。 瀏覽器兼容性 請參閱本文底部內容。。


bind()定義

function.bind(thisArg[, arg1[, arg2[, ...]]])

參數

thisArg 調用綁定函數時做爲this參數傳遞給目標函數的值。 若是使用new運算符構造綁定函數,則忽略該值。當使用bind在setTimeout中建立一個函數(做爲回調提供)時,做爲thisArg傳遞的任何原始值都將轉換爲object。若是bind函數的參數列表爲空,執行做用域的this將被視爲新函數的thisArg。

arg1, arg2, ... 當目標函數被調用時,預先添加到綁定函數的參數列表中的參數


如今咱們在回想一下前面的函數調用,那就很好理解了,咱們在聲明函數時,thisarguments沒法知道是誰(前面說過),那就是undefinded或者null,因此根據MDN的定義,在調用函數是,函數的小動做偷偷調用call()方法,爲函數設置了this對象。apply()同理!只不過你要注意它接收參數的方式不一樣。

bind()呢,也很好理解了,咱們不想在函數執行時才被函數的小動做指定this對象,而是要固定this對象,那麼bind()方法就是在內部調用了call()或者apply()方法主動指定this對象,同時爲了函數能夠複用,借用了閉包來保存這個this對象(閉包這裏很少說),如下是模擬bind()方法的示例:

// 定義一個對象
var person = {
    name: "掘金果醬淋",
    hello: function(thing) {
      console.log(this.name + " 你好啊 " + thing);
    }
};
// 模擬bind方法的操做,接收一個函數和一個this對象(執行環境)
var bind = function(func, thisValue) {
    return function() {
        return func.apply(thisValue, arguments); // 注意apply()和arguments的妙用
    };
};
  
var boundHello = bind(person.hello, person);
boundHello("世界"); // 打印出// 掘金果醬淋 你好啊 世界

複製代碼

怎麼樣,挺簡單的吧!


後話:this對象究竟是什麼?

this對象就是函數的執行環境(以爲不理解看一下開篇部分),我說過,執行環境是舞臺,函數就是演員,函數能夠調用的變量是表演須要的道具。那麼,改變函數的執行環境有什麼意義呢?咱們看例子:

var nullArr = []; // 空數組
var arrType = Object.prototype.toString.call(nullArr); // 調用Object對象原型中的方法,同時將執行環境(this)指向 nullArr 

console.log(arrType); // 打印 // [object Array]
console.log(nullArr.toString()); // 空字符串

複製代碼

首先,咱們須要知道,

  1. Object.prototype是全部對象的終端原型對象,其中包括的屬性方法是全部對象共享的,其餘對象也能夠 重寫 那些在終端原型對象中的方法

  2. 幾乎全部的引用對象都有本身的toString()且是重寫了的;

上面例子中,Object.prototype.toString()是終端原型對象的方法,而nullArr做爲一個數組的實例,只能調用本身Array.prototype原型中的toString();從例子中咱們能夠知道,一個空數組調用本身Array.prototype原型的toString()只能獲得一個空字符串

在特殊狀況下,那咱們想要數組實例nullArr可以使用Object.prototype.toString()方法,簡單的方法就是給數組重寫一個這樣的方法,可是若是每個須要該方法的數組都從新寫一次,這就很不符合複用的原則了。

那咱們調用call()方法將Object.prototype.toString()方法的執行環境(this)主動變成了nullArr,那麼這個方法就能夠調用這個執行環境中的變量了(舞臺道具),從nullArr的角度看,等於它擁有了Object.prototype.toString()方法,其實應該說擁有了Object.prototype.toString()方法的指針(看開篇),注意指針只是一種關係,而不是重寫了對象,(固然,這種關係只在call()執行時有,執行結束後就沒有了—退下舞臺)。

結語:很簡單是假的

可以看完和理解上面的分析過程,你可能會得出了一個結論:這不簡單啊,挺繞的!不得不認可,我說謊了,其實並不簡單。然而我是爲了讓你有信心看下去,事情作過以後就會簡單了。

我以爲人對未知的東西都會有必定的恐懼感,但若是有人一直強調很簡單,那麼便不會連開始嘗試的勇氣都沒有!


參考文章:

  1. 追夢者-JavaScript中call,apply,bind方法的總結
  2. 追夢者-完全理解js中this的指向,沒必要硬背
  3. Yehuda Katz-Understanding JavaScript Function Invocation and "this"(理解JavaScript函數調用和「this」)(這篇纔是頓悟的來源)

QQ:1448373124(歡迎交流前端技術,對於文章疏漏處歡迎指正)

轉載請註明出處---掘金果醬淋
相關文章
相關標籤/搜索