javascript — this的指向

談起this的指向,是實際應用中比較常見的使用,同時也是面試中最多見的問題;在實際的應用中,咱們經過Vue的this去調用方法,得到屬性,在React的生命週期寫法中,經過將函數先bind內部this,而後經過this.函數名調用函數;javascript

this指向

普通函數

  • this是在調用的時候才被動態建立的
  • this 的指向取決於當前被調用的上下文;
    • 全局函數的內部this指向window;
    • 對象內部函數this指向當前對象所在的this;
    • 若是在調用的過程當中,更改了this的指向,則指向被更改的對象;
var  a = '333'
function g_fun(){
  console.log(this) // Window
  console.log("定義在全局的函數",this.a) ;// '333'
}
g_fun() ;

var obj={
  a:'對象內部的a',
  g_fun:function(){
    console.log(this.a) //'對象內部的a'
  }
}
obj.g_fun() // 
複製代碼
圖1 this的指向問題

注意點

⚠️:若是全局函數是用let進行定義的變量,在函數中是沒有辦法經過this.變量訪問的,let定義在全局的變量,沒有辦法掛載到this中,可是能夠經過變量名的方式進行訪問;java

  • 全局經過var定義的變量掛載到this/window
  • 全局經過let 定義的變量不會掛載到this/window
let b = 'mfy'
var b1 = 'mfy'
function g_funb(){
  console.log(this.b) //underfined
  console.log(b);//mfy
  console.log(this.b1) //mfy
}
console.log(this)
g_funb()
複製代碼
圖2 var、let 聲明的變量

箭頭函數

箭頭函數無this,而內部的繼承父執行上下文裏面的this;面試

let arrowFnn = ()=>{
  console.log(this) // window
}

var objfn = {
  name:'ee',
  arrowFnn:()=>{
    console.log(this) //window
  }
}
複製代碼

箭頭函數找this 的指向,只須要按照層級一層一層向上查找,找到第一個非箭頭函數,若是無則this指向全局;markdown

常見面試題

var name = 2;
let funn = {
  name:'mfy',
  printName:()=>{
    var name = 4;
    console.log(name) // 4 
    console.log(this.name) //2
  },
  printName2:function(){
    let name = 'fff'
    console.log(this) // funn
    console.log(this.name) //mfy
  }
}
funn.printName(); 
funn.printName2();
複製代碼

分別執行printName函數,查看打印的內容;app

  • 首先分析 funn.printName
    • 箭頭函數 外部無其餘的具名函數 this指向window
    • 變量name 在函數內部局部做用域和全局都存在
    • 查找局部做用域,找到name 打印值 中止查找
    • 查找當前this,打印window
  • funn.printName2()
    • 具名函數,this指向當前調用者obj, 就是obj函數

    • 變量this.name 直接獲取當前this下的值oop

圖3 常見面試題

根據此面試題還會衍生出其餘的面試題目ui

  • 將全局的var使用let定義
  • funn.printName 內部的this進行更改

更改內部this指向

在一些場景中,咱們須要更改函數的內部this,去實現咱們的相關需求;更改this方式最多的就是call、bind、apply
函數操做中一般用來更改this指向this

  • 箭頭函數沒有this,箭頭函數this只取決於包裹的第一個非箭頭函數的this,
  • call、apply、bind均可以更改this,或者執行當前函數;
  • call、apply都是改變this的指向,做用相同,只是傳值的參數不一樣;

call

使用

var obj ={
 value:22,
}
function list(name,age){
 console.log(this.value) 
}
list.call(obj,'33',33)
複製代碼

手寫實現

綁定this,並執行當前函數spa

Function.prototype.myCall=function(context){
  //context是當前傳入的對象或者其餘想要綁定的this
  var context = context || window;
  context.fn = this;
  //取出當前的this
  var args =[...arguments].slice(1);
  //調用當前的函數
  var result = context.fn(...args);
  //刪除掛在實例上的方法
  delete context.fn;
  //返回調用的結果值
  return result;
}
複製代碼

apply

使用

var obj ={
  value:22,
}
function list(name,age){
  console.log(this.value) 
}
list.call(obj,'33',33)
list.apply(obj,['33',33])
複製代碼

手寫實現

Function.prototype.myApply=function(context){
  var context = context || window;
  context.fn = this;
  var result = null;
  //判斷是否有參數傳入
  if(arguments[1]){
    // 將參數進行分割開
     result = context.fn(...arguments[1])
  }else{
     result = context.fn()
  }
  delete context.fn;
  return result;
}
複製代碼

bind

使用

bind 和 call、apply 不相同點在於,能夠綁定函數,可是不會當即執行

  • 將當前函數經過傳遞參數的形式,更改this的指向
  • 只會將當前函數掛載到函數中,不會當即執行

首先是進行bind的使用分析

var obj = {
  a:2,
  b:4
}
function demo(a,b){
  console.log(...arguments)
  console.log(a,b)
  return false
} 
let fun = demo.bind(obj,5)   
console.log(fun.name); // 'bound demo' 
console.log(fun.bind.name); // 'bind' 
console.log(fun.bind); // 'bind' 
console.log((function(){}).bind().name); // 'bound '
console.log((function(){}).bind().length); // 0
複製代碼
  • 經過bind進行綁定的this的值返回一個bound的函數
  • 除第一個參數外,其餘的參數都看成形參傳入到demo函數中
  • bind的函數打印出來爲 bound demo ,匿名函數的話爲bound+空格
  • bind後的返回值函數,執行後返回值是原函數(demo)的返回值。

若是返回的fun在進行實例化函數呢?

var obj = {
  a:2,
  b:4
}
function demo(a,b){
  console.log(...arguments)
  console.log(a,b)
  return false
} 
let fun = demo.bind(obj,5)   
var demom = new fun(6);
console.log(demom,'demom') //demo {} 'demom'
複製代碼
圖4 實例化後的bind
  • bind的原有的this指向失效了
  • new fun返回的是demo原生構造器的新對象
  • 包含了new的操做符號的內容

手寫實現

經過上面的實例進行分析bind的功能

  1. 建立了一個全新的對象。
  2. 這個對象會被執行[[Prototype]](也就是__proto__)連接。
  3. 生成的新對象會綁定到函數調用的this。
  4. 經過new建立的每一個對象將最終被[[Prototype]]連接到這個函數的prototype對象上。
  5. 若是函數沒有返回對象類型Object(包含Functoin, Array, Date, RegExg, Error),那麼new表達式中的函數調用會自動返回這個新的對象。

6.返回當前的bound

Function.prototype.myBindDemo = function (context) {
 //1. 獲取調用的函數 
  let fn = this;
  //2. 分離參數bind時候傳入的參數
  let args = [].slice.call(arguments, 1);
  //3.定義一個bound函數
  function bound() {
    //3.1 合併調用的參數和當前傳入的參數
    let currArgs = args.concat([].slice.call(arguments,0)) ;//合併全部的參數
    // 3.2 判斷是不是new的操做
    if(this instanceof bound){
      //3.3 建立一個全新的對象 ->而且執行[[Prototype]]__proto__連接->連接到這個函數的`prototype`對象上。斷開當前的原型鏈
      if(fn.prototype){
        function Empty() {} //建立一個函數
        Empty.prototype = fn.prototype; //該函數指向原來函數的this
        bound.prototype = new Empty(); //脫離當前的原型鏈,將該bound指向其餘
      }
        //3.4 生成的新對象會綁定到函數調用裏面的this
        let result = fn.call(this,...currArgs)
        var isObject = typeof result === 'object' && result !== null;
        var isFunction = typeof result === 'function';
        if(isObject || isFunction){
            return result;
        }
        // 返回當前call的函數
        return this;
    }else{
        // apply修改this指向,把兩個函數的參數合併傳給self函數,並執行self函數,返回執行結果
        return fn.apply(context, currArgs); 
    } 
  }  
  return bound; 
}

複製代碼

bind 總結

  • bind是Function原型鏈中的Function.prototype的一個屬性,它是一個函數,修改this指向,合併參數傳遞給原函數,返回值是一個新的函數。
  • bind返回的函數能夠經過new調用,這時提供的this的參數被忽略,指向了new生成的全新對象。內部模擬實現了new操做符。

面試常見問題總結

  • this指向問題
    • 全局函數this指向
    • 箭頭函數 this指向
    • this指向應用題判斷
  • 更改this指向的方式
  • 三種方式區別
  • 三種方式手寫實現

參考文檔

相關文章
相關標籤/搜索