JS中`THIS`相關問題梳理

this就是js裏的關鍵字,有特殊意義,表明函數執行主體。javascript

1、定義

  • 函數執行的主體(不是上下文):意思是誰把函數執行的,那麼執行主體就是誰

2、使用狀況

  • 一、全局做用域裏的thiswindow,全局做用域下至關因而window.fn()執行只是把window.省略了(嚴格模式下是undefined)。java

    console.log(this === window) // true
    
    window.a  = 13;
    console.log(this.a) // 13
    複製代碼
  • 二、函數裏的this,看執行主體前有沒有,若是有,那前面是誰,函數裏的this就是誰,若是沒有,那函數裏的this就是window,嚴格模式下是undefined數組

    function fn(){
        console.log(this)
    }
    fn();//window
    
    let obj = {
        fn:function(){
            console.log(this) 
        }
    }
    obj.fn();//obj
    var f = obj.fn;
    f();//window
    複製代碼
  • 三、自執行函數裏的thiswindowundefined(嚴格模式下)app

    (function(){
        console.log(this);//==>window
    })();
    ~function(){}();//==>window
    +function(){}();//==>window
    -function(){}();//==>window
    !function(){}();//==>window
    複製代碼
  • 四、回調函數裏的this通常狀況下是window函數

    let ary = [1,2,3];
    ary.forEach(function(item,index){
        console.log(this)
    })
    //================================//
    setTimeout(function(num){
        console.log(num)
        console.log(this)
    },1000,1)
    //================================//
    function fn(m){
        m()
    }
    fn(function(){
        console.log(this)
    })
    複製代碼
  • 五、箭頭函數沒有thisui

    可是要是在箭頭函數裏使用this,他就會往上一級做用域查找,若是上一級做用域也沒有,那就繼續往上找,直到找到全局的window爲止this

    let obj = {
        num: 2,
        fn: function () {
            // this-->obj
            let m = () => {
                // this-->obj
                console.log(this.num) // obj(向上一級做用域查找)
            }
            m()
        }
    }
    obj.fn()
    複製代碼
  • 六、構造函數裏的this是當前實例spa

  • 七、實例原型上的公有方法裏的this通常是當前實例(下面改變this的方法中有體現)prototype

  • 八、給元素綁定事件行爲,那事件裏的this就是當前被綁定的元素自己code

    btn.onclick = function(){
        console.log(this) // btn
    }
    複製代碼

3、面向對象中有關私有/公有方法中的THIS問題

總結下來this在面向對象中,主要仍是看是誰執行的,也就是執行函數點前面是誰

  • 一、方法執行,看前面是否有點,點前面是誰THIS就是誰
  • 二、把方法總的THIS進行替換
  • 三、再基於原型鏈查找的方法肯定結果便可

4、改變this指向:call/apply/bind

每個函數(普通函數/構造函數/內置類)都是Function這個內置類的實例,因此:函數.__proto__===Function.prototype函數能夠直接調取Function原型上的方法

call / apply / bind

  • 原型上提供的三個公有屬性方法
  • 每個函數均可以調用這個方法執行
  • 這些方法都是用來改變函數中的THIS指向的

一、call

語法:

  • 函數.call(context,params1,params2....)

定義:

  • 函數基於原型鏈找到Function.prototype.call這個方法,而且把它執行,在call方法執行的時候改變裏面的this關鍵字

做用:

  • 讓當前函數執行
  • 把函數中的THIS指向改成第一個傳遞給CALL的實參
  • 把傳遞給CALL其他的實參,當作參數信息傳遞給當前函數

注意:

  • 若是執行CALL一個實參都沒有傳遞,非嚴格模式下是讓函數中的THIS指向WINDOW,嚴格模式下指向的是UNDEFINED
  • fn.call(null);
    • //=>this:window
    • 嚴格下是null(第一個參數傳遞的是null/undefined/不傳,非嚴格模式下this指向window,嚴格模式下傳遞的是誰this就是誰,不傳thisundefined

使用方法:

function fn(){}
fn.call(); //=>fn函數基於原型鏈找到Function.prototype上的call方法,而且讓其執行(執行的是call方法:方法中的this是fn)
fn.call.call(); //=>fn.call就是Function.prototype上的call方法,也是一個函數,只要是函數就能用原型上的方法,因此能夠繼續調用call來執行


Function.prototype.call = function $1(){
//...
}
fn.call => $1
fn.call() => $1()  this:fn
fn.call.call() => $1.call() => 繼續讓call執行,this:$1

實例.方法():都是找到原型上的內置方法,讓內置方法先執行(只不過執行的時候作了一些事情會對實例產生改變,而這也是這些內置方法的做用),內置方法中的THIS通常都是當前操做的實例

複製代碼

call簡單實現原理

```javascript
//=>咱們的需求是想讓FN執行的時候,方法中的THIS指向OBJ
obj.fn(); //=>Uncaught TypeError: obj.fn is not a function  
//由於此時obj中並無fn這個屬性

-------解決辦法---------

obj.fn = fn;
obj.fn(); //=>this:obj  //=>'OBJ'
delete obj.fn;//=>對象中本來沒有,因此使用後要刪掉
```
複製代碼

本身基於原生JS簡單的實現內置的call方法

  • 詳細梳理:

    ~ function () {
            /* * myCall:改變函數中的THIS指向 * @params * context 能夠不傳遞,傳遞必須是引用類型值(由於後面要給它加$fn的屬性) */
            function myCall(context) {
    	        //this:sum 也就是當前要操做的這個函數實例
    	        context = context || window;
    	        let args = [], //=>除第一個參數外剩餘傳遞的信息值
    		        result;
    	        for (let i = 1; i < arguments.length; i++) {
    		        args.push(arguments[i]);
    	        }
    	        context.$fn = this;
    	        result = context.$fn(...args); //=>args=[10,20] ...是ES6中的展開運算符,把數組中的每一項分別的展開傳遞給函數 //=>context.$fn(10,20)
    	        delete context.$fn;
    	        return result;
            }
            /* 擴展到內置類的原型上 */
            Function.prototype.myCall = myCall;
        }();
    複製代碼
  • 可簡寫爲

    function myCall(context,...arg){
            //context-->obj this-->fn
            context = context || window; // 若是你不傳參、或者傳null、傳undefined,那context的值都是window
            let res = null; // 建立一個變量,準備接收函數執行結果
            context.fn = this; // 把fn增長到obj裏
            res =  context.fn(...arg); // 讓fn指向
            delete context.fn; // 把fn從obj裏刪除
            return res; // 把this的返回值return出去
         }
        Function.prototype.myCall = myCall;
        console.log(fn.myCall(obj,1,2)) // 'ss'
    複製代碼
  • 以題爲例分析

最終咱們能夠得出:

  • 一個CALL是讓左邊函數執行(this是傳遞的參數)
  • 多個CALL是讓最後傳參的函數執行(thiswindow/undefined

二、apply

call方法同樣,都是把函數執行,而且改變裏面的this關鍵字的,惟一的區別就是傳遞給函數參數的方式不一樣

  • call是一個個傳參
  • apply是按照數組傳參
let obj={name:'OBJ'};
let fn=function(n,m){
    console.log(this.name);
}
//=>讓fn方法執行,讓方法中的this變爲obj,而且傳遞10/20
fn.call(obj,10,20);
fn.apply(obj,[10,20]);
複製代碼

三、bind

call/apply同樣,也是用來改變函數中的this關鍵字的,只不過基於bind改變this,當前方法並無被執行,相似於預先改變this

語法:

  • 函數.bind(context,params1,....)

區別:

  • bind是預處理this,他並不會讓函數執行
  • bind方法的返回值是一個改變this以後的新函數

做用:

  • 把函數中的THIS指向經過預處理的方式改成第一個傳遞給BIND的實參
  • 通常使用在綁定點擊事件,不讓函數當即執行時
    let obj={name:'OBJ'};
    function fn(){
        console.log(this.name);
    }
    document.body.onclick=fn; //=>當事件觸發,fn中的this:BODY
    
    //=>點擊BODY,讓FN中的THIS指向OBJ
    //document.body.onclick=fn.call(obj); //=>基於call/apply這樣處理,不是把fn綁定給事件,而是把fn執行後的結果綁定給事件
    document.body.onclick=function(){
        //this:BODY
        fn.call(obj);
    }
    document.body.onclick=fn.bind(obj); 
    複製代碼

注意:

  • IE6~8中不支持bind方法
  • 預先作啥事情的思想被稱爲「柯理化函數」

優勢:

  • bind的好處是:經過bind方法只是預先把fn中的this修改成obj,此時fn並無執行呢,當點擊事件觸發纔會執行fncall/apply都是改變this的同時當即把方法執行)

相關文章
相關標籤/搜索