<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          5種JavaScript的綁定,幫你徹底弄懂this,默認(rèn)綁定、隱式綁定、顯式綁定、new綁定、箭頭函數(shù)綁定

          共 7300字,需瀏覽 15分鐘

           ·

          2021-05-29 23:01

          來源 | https://www.cnblogs.com/echoyya/p/14506742.html


          可以說this與閉包、原型鏈一樣,屬于JavaScript開發(fā)中老生常談的問題了,百度一搜,this相關(guān)的文章鋪天蓋地。可開發(fā)好幾年,被幾道this題安排明明白白的人應(yīng)該不在少數(shù)(我就是其一)。
          我覺得this概念抽象,變化多端總是讓人暈頭轉(zhuǎn)向,但平心它并不是有多難,今天我們就從this綁定的五種場景(默認(rèn)綁定、隱式綁定、顯式綁定、new綁定、箭頭函數(shù)綁定)出發(fā),靜下心來好好聊聊這個 this,本文開始。

          01、this默認(rèn)綁定

          this默認(rèn)綁定我們可以理解為函數(shù)調(diào)用時無任何調(diào)用前綴的情景,它無法應(yīng)對我們后面要介紹的另外四種情況,所以稱之為默認(rèn)綁定,默認(rèn)綁定時this指向全局對象(非嚴(yán)格模式):
          function fn1() {    let fn2 = function () {        console.log(this); //window        fn3();    };    console.log(this); //window    fn2();};
          function fn3() { console.log(this); //window};
          fn1();

          這個例子中無論函數(shù)聲明在哪,在哪調(diào)用,由于函數(shù)調(diào)用時前面并未指定任何對象,這種情況下this指向全局對象window。

          但需要注意的是,在嚴(yán)格模式環(huán)境中,默認(rèn)綁定的this指向undefined,來看個對比例子:

          function fn() {    console.log(this); //window    console.log(this.name);};
          function fn1() { "use strict"; console.log(this); //undefined console.log(this.name);};
          var name = '聽風(fēng)是風(fēng)';
          fn(); fn1() //TypeError: Cannot read property 'a' of undefined

          再例如函數(shù)以及調(diào)用都暴露在嚴(yán)格模式中的例子:

          "use strict";var name = '聽風(fēng)是風(fēng)';function fn() {    console.log(this); //undefined    console.log(this.name);//報錯};fn();

          最后一點,如果在嚴(yán)格模式下調(diào)用不在嚴(yán)格模式中的函數(shù),并不會影響this指向,來看最后一個例子:

          var name = '聽風(fēng)是風(fēng)';function fn() {    console.log(this); //window    console.log(this.name); //聽風(fēng)是風(fēng)};
          (function () { "use strict"; fn();}());
          02、this隱式綁定

          1)、隱式綁定

          什么是隱式綁定呢,如果函數(shù)調(diào)用時,前面存在調(diào)用它的對象,那么this就會隱式綁定到這個對象上,看個例子:
          function fn() {    console.log(this.name);};let obj = {    name: '聽風(fēng)是風(fēng)',    func: fn};obj.func() //聽風(fēng)是風(fēng)

          如果函數(shù)調(diào)用前存在多個對象,this指向距離調(diào)用自己最近的對象,比如這樣:

          function fn() {    console.log(this.name);};let obj = {    name: '行星飛行',    func: fn,};let obj1 = {    name: '聽風(fēng)是風(fēng)',    o: obj};obj1.o.func() //行星飛行

          那如果我們將obj對象的name屬性注釋掉,現(xiàn)在輸出什么呢?

          function fn() {    console.log(this.name);};let obj = {    func: fn,};let obj1 = {    name: '聽風(fēng)是風(fēng)',    o: obj};obj1.o.func() //??

          這里輸出undefined,大家千萬不要將作用域鏈和原型鏈弄混淆了,obj對象雖然obj1的屬性,但它兩原型鏈并不相同,并不是父子關(guān)系,由于obj未提供name屬性,所以是undefined。

          既然說到原型鏈,那我們再來點花哨的,我們再改寫例子,看看下面輸出多少:

          function Fn() {};Fn.prototype.name = '時間跳躍';
          function fn() { console.log(this.name);};
          let obj = new Fn();obj.func = fn;
          let obj1 = { name: '聽風(fēng)是風(fēng)', o: obj};obj1.o.func() //?

          這里輸出時間跳躍,雖然obj對象并沒有name屬性,但順著原型鏈,找到了產(chǎn)生自己的構(gòu)造函數(shù)Fn,由于Fn原型鏈存在name屬性,所以輸出時間跳躍了。

          作用域鏈與原型鏈的區(qū)別:

          當(dāng)訪問一個變量時,解釋器會先在當(dāng)前作用域查找標(biāo)識符,如果沒有找到就去父作用域找,作用域鏈頂端是全局對象window,如果window都沒有這個變量則報錯。

          當(dāng)在對象上訪問某屬性時,首選會查找當(dāng)前對象,如果沒有就順著原型鏈往上找,原型鏈頂端是null,如果全程都沒找到則返一個undefined,而不是報錯。

          2)、隱式丟失

          在特定情況下會存在隱式綁定丟失的問題,最常見的就是作為參數(shù)傳遞以及變量賦值,先看參數(shù)傳遞:

          var name = '行星飛行';let obj = {    name: '聽風(fēng)是風(fēng)',    fn: function () {        console.log(this.name);    }};
          function fn1(param) { param();};fn1(obj.fn);//行星飛行

          這個例子中我們將 obj.fn 也就是一個函數(shù)傳遞進(jìn) fn1 中執(zhí)行,這里只是單純傳遞了一個函數(shù)而已,this并沒有跟函數(shù)綁在一起,所以this丟失這里指向了window。

          第二個引起丟失的問題是變量賦值,其實本質(zhì)上與傳參相同,看這個例子:

          var name = '行星飛行';let obj = {    name: '聽風(fēng)是風(fēng)',    fn: function () {        console.log(this.name);    }};let fn1 = obj.fn;fn1(); //行星飛行

          注意,隱式綁定丟失并不是都會指向全局對象,比如下面的例子:

          var name = '行星飛行';let obj = {    name: '聽風(fēng)是風(fēng)',    fn: function () {        console.log(this.name);    }};let obj1 = {    name: '時間跳躍'}obj1.fn = obj.fn;obj1.fn(); //時間跳躍

          雖然丟失了 obj 的隱式綁定,但是在賦值的過程中,又建立了新的隱式綁定,這里this就指向了對象 obj1。

          03、this顯式綁定

          顯式綁定是指我們通過call、apply以及bind方法改變this的行為,相比隱式綁定,我們能清楚的感知 this 指向變化過程。來看個例子:

          let obj1 = {    name: '聽風(fēng)是風(fēng)'};let obj2 = {    name: '時間跳躍'};let obj3 = {    name: 'echo'}var name = '行星飛行';
          function fn() { console.log(this.name);};fn(); //行星飛行fn.call(obj1); //聽風(fēng)是風(fēng)fn.apply(obj2); //時間跳躍fn.bind(obj3)(); //echo

          比如在上述代碼中,我們分別通過call、apply、bind改變了函數(shù)fn的this指向。

          在js中,當(dāng)我們調(diào)用一個函數(shù)時,我們習(xí)慣稱之為函數(shù)調(diào)用,函數(shù)處于一個被動的狀態(tài);而call與apply讓函數(shù)從被動變主動,函數(shù)能主動選擇自己的上下文,所以這種寫法我們又稱之為函數(shù)應(yīng)用。

          注意,如果在使用call之類的方法改變this指向時,指向參數(shù)提供的是null或者undefined,那么 this 將指向全局對象。

          let obj1 = {    name: '聽風(fēng)是風(fēng)'};let obj2 = {    name: '時間跳躍'};var name = '行星飛行';
          function fn() { console.log(this.name);};fn.call(undefined); //行星飛行fn.apply(null); //行星飛行fn.bind(undefined)(); //行星飛行

          另外,在js API中部分方法也內(nèi)置了顯式綁定,以forEach為例:

          let obj = {    name: '聽風(fēng)是風(fēng)'};
          [1, 2, 3].forEach(function () { console.log(this.name);//聽風(fēng)是風(fēng)*3}, obj);

          call、apply與bind有什么區(qū)別?

          1.call、apply與bind都用于改變this綁定,但call、apply在改變this指向的同時還會執(zhí)行函數(shù),而bind在改變this后是返回一個全新的boundFunction綁定函數(shù),這也是為什么上方例子中bind后還加了一對括號 ()的原因。

          2.bind屬于硬綁定,返回的 boundFunction 的 this 指向無法再次通過bind、apply或 call 修改;call與apply的綁定只適用當(dāng)前調(diào)用,調(diào)用完就沒了,下次要用還得再次綁。

          3.call與apply功能完全相同,唯一不同的是call方法傳遞函數(shù)調(diào)用形參是以散列形式,而apply方法的形參是一個數(shù)組。在傳參的情況下,call的性能要高于apply,因為apply在執(zhí)行時還要多一步解析數(shù)組。

          描述一請參照上面已有例子。

          描述二請參照下方例子,我們嘗試修改 boundFunction 的 this 指向:

          let obj1 = {    name: '聽風(fēng)是風(fēng)'};let obj2 = {    name: '時間跳躍'};var name = '行星飛行';
          function fn() { console.log(this.name);};fn.call(obj1); //聽風(fēng)是風(fēng)fn(); //行星飛行fn.apply(obj2); //時間跳躍fn(); //行星飛行let boundFn = fn.bind(obj1);//聽風(fēng)是風(fēng)boundFn.call(obj2);//聽風(fēng)是風(fēng)boundFn.apply(obj2);//聽風(fēng)是風(fēng)boundFn.bind(obj2)();//聽風(fēng)是風(fēng)

          描述三請參考以下例子:

          let obj = {    name: '聽風(fēng)是風(fēng)'};
          function fn(age,describe) { console.log(`我是${this.name},我的年齡是${age},我非常${describe}!`);};fn.call(obj,'26','帥');//我是聽風(fēng)是風(fēng),我的年齡是26,我非常帥fn.apply(obj,['26','帥']);//我是聽風(fēng)是風(fēng),我的年齡是26,我非常帥

          更多關(guān)于call apply bind可以閱讀博主這篇文章 js中call、apply、bind到底有什么區(qū)別?bind返回的方法還能修改this指向嗎?

          04、new綁定

          準(zhǔn)確來說,js中的構(gòu)造函數(shù)只是使用new 調(diào)用的普通函數(shù),它并不是一個類,最終返回的對象也不是一個實例,只是為了便于理解習(xí)慣這么說罷了。

          那么new一個函數(shù)究竟發(fā)生了什么呢,大致分為三步:

          1.以構(gòu)造器的prototype屬性為原型,創(chuàng)建新對象;

          2.將this(可以理解為上句創(chuàng)建的新對象)和調(diào)用參數(shù)傳給構(gòu)造器,執(zhí)行;

          3.如果構(gòu)造器沒有手動返回對象,則返回第一步創(chuàng)建的對象

          這個過程我們稱之為構(gòu)造調(diào)用,我們來看個例子:

          function Fn(){    this.name = '聽風(fēng)是風(fēng)';};let echo = new Fn();echo.name//聽風(fēng)是風(fēng)

          在上方代碼中,構(gòu)造調(diào)用創(chuàng)建了一個新對象echo,而在函數(shù)體內(nèi),this將指向新對象echo上(可以抽象理解為新對象就是this)。

          若對于new具體過程有疑惑,或者不知道怎么手動實現(xiàn)一個new 方法,可以閱讀博主這篇文章 js new一個對象的過程,實現(xiàn)一個簡單的new方法

          05、 this綁定優(yōu)先級

          我們先介紹前四種this綁定規(guī)則,那么問題來了,如果一個函數(shù)調(diào)用存在多種綁定方法,this最終指向誰呢?這里我們直接先上答案,this綁定優(yōu)先級為:

          顯式綁定 > 隱式綁定 > 默認(rèn)綁定

          new綁定 > 隱式綁定 > 默認(rèn)綁定

          為什么顯式綁定不和new綁定比較呢?因為不存在這種綁定同時生效的情景,如果同時寫這兩種代碼會直接拋錯,所以大家只用記住上面的規(guī)律即可。

          function Fn(){    this.name = '聽風(fēng)是風(fēng)';};let obj = {    name:'行星飛行'}let echo = new Fn().call(obj);//報錯 call is not a function

          那么我們結(jié)合幾個例子來驗證下上面的規(guī)律,首先是顯式大于隱式:

          //顯式>隱式let obj = {    name:'行星飛行',    fn:function () {        console.log(this.name);    }};obj1 = {    name:'時間跳躍'};obj.fn.call(obj1);// 時間跳躍

          其次是new綁定大于隱式:

          //new>隱式obj = {    name: '時間跳躍',    fn: function () {        this.name = '聽風(fēng)是風(fēng)';    }};let echo = new obj.fn();echo.name;//聽風(fēng)是風(fēng)

          06、箭頭函數(shù)的this

          ES6的箭頭函數(shù)是另類的存在,為什么要單獨說呢,這是因為箭頭函數(shù)中的this不適用上面介紹的四種綁定規(guī)則。

          準(zhǔn)確來說,箭頭函數(shù)中沒有this,箭頭函數(shù)的this指向取決于外層作用域中的this,外層作用域或函數(shù)的this指向誰,箭頭函數(shù)中的this便指向誰。

          有點吃軟飯的嫌疑,一點都不硬朗,我們來看個例子:

          function fn() {    return () => {        console.log(this.name);    };}let obj1 = {    name: '聽風(fēng)是風(fēng)'};let obj2 = {    name: '時間跳躍'};let bar = fn.call(obj1); // fn this指向obj1bar.call(obj2); //聽風(fēng)是風(fēng)

          為啥我們第一次綁定this并返回箭頭函數(shù)后,再次改變this指向沒生效呢?

          前面說了,箭頭函數(shù)的this取決于外層作用域的this,fn函數(shù)執(zhí)行時this指向了obj1,所以箭頭函數(shù)的this也指向obj1。

          除此之外,箭頭函數(shù)this還有一個特性,那就是一旦箭頭函數(shù)的this綁定成功,也無法被再次修改,有點硬綁定的意思。

          當(dāng)然,箭頭函數(shù)的this也不是真的無法修改,我們知道箭頭函數(shù)的this就像作用域繼承一樣從上層作用域找,因此我們可以修改外層函數(shù)this指向達(dá)到間接修改箭頭函數(shù)this的目的。

          function fn() {    return () => {        console.log(this.name);    };};let obj1 = {    name: '聽風(fēng)是風(fēng)'};let obj2 = {    name: '時間跳躍'};fn.call(obj1)(); // fn this指向obj1,箭頭函數(shù)this也指向obj1fn.call(obj2)(); //fn this 指向obj2,箭頭函數(shù)this也指向obj2

          總結(jié)

          那么到這里,對于this的五種綁定場景就全部介紹完畢了,如果你有結(jié)合例子練習(xí)下來,我相信你現(xiàn)在對于this的理解一定更上一層樓了。

          那么通過本文,我們知道默認(rèn)綁定在嚴(yán)格模式與非嚴(yán)格模式下this指向會有所不同。

          我們知道了隱式綁定與隱式丟失的幾種情況,并簡單復(fù)習(xí)了作用域鏈與原型鏈的區(qū)別。

          相對隱式綁定改變的不可見,我們還介紹了顯式綁定以及硬綁定,簡單科普了call、apply與bind的區(qū)別,并提到當(dāng)綁定指向為null或undefined時this會指向全局(非嚴(yán)格模式)。

          我們介紹了new綁定以及new一個函數(shù)會發(fā)生什么。

          最后我們了解了不太合群的箭頭函數(shù)中的this綁定,了解到箭頭函數(shù)的this由外層函數(shù)this指向決定,并有一旦綁定成功也無法再修改的特性。

          希望在面試題中遇到this的你不再有所畏懼,到這里,本文結(jié)束。


          學(xué)習(xí)更多技能

          請點擊下方公眾號

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美打炮网 | 亚洲瑟瑟 | 大香蕉伊人在线视频免费看 | 国产无码精品黄色电影 | 亚洲无码国产一区 |