this的指向(附面試題)

ES5中this的指針

按照this指針的優先級,列出下面常會遇到的四種狀況,從上到下依次是優先級從高到低(後面會詳細比較優先級)。javascript

  1. 函數是和new一塊兒被調用的嗎(new綁定)?若是是,this就是新構建的對象。

    var bar = new foo()html

  2. 函數是用callapply被調用(明確綁定),甚至是隱藏在bind 硬綁定 之中嗎?若是是,this就是明確指定的對象。

    var bar = foo.call( obj2 )java

  3. 函數是用環境對象(也稱爲擁有者或容器對象)被調用的嗎(隱含綁定)?若是是,this就是那個環境對象。

    var bar = obj1.foo()react

  4. 不然,使用默認的this默認綁定)。若是在strict mode下,就是undefined,不然是global對象。 var bar = foo()

以上,就是理解對於普通的函數調用來講的this綁定規則所需的所有。是的···幾乎是所有。git

apply、call、bind

由於apply、call存在於Function.prototype中,因此每一個方法都有這兩個屬性。es6

  • call
函數名.call(對象,arg1....argn)
//功能:
    //1.調用函數
    //2.將函數內部的this指向第一個參數的對象
    //3.將第二個及之後全部的參數,做爲實參傳遞給函數
  • apply
    主要用途是直接用數組傳參
函數名.apply(對象, 數組/僞數組);
//功能:
    //1.調用函數
    //2.將函數內部的this指向第一個參數的對象
    //3.將第二個參數中的數組(僞數組)中的元素,拆解開依次的傳遞給函數做爲實參
//案例求數組中最大值
var a=Math.max.apply( null, [ 1, 2, 5, 3, 4 ] );
console.log(a);// 輸出:5
  • bind
  1. 是建立一個新的函數,咱們必需要手動去調用:
var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.bind(a,1,2)()   // 3
  • 注意事項github

    1. call和apply功能幾乎一致,惟一的區別就是傳參的方式!!
      2.call和apply的第一個參數若是爲null或者undefined,那麼this指向window
      3.call和apply的第一個參數若是爲值類型的數據,那麼會將這個值類型的數據,轉換成其對應的引用類型的數據,而後再將this指向這個引用類型的數據
      4.call和apply當即執行這個函數,bind方法可讓對應的函數想何時調就何時調用,而且能夠將參數在執行的時候添加,這是它們的區別,根據本身的實際狀況來選擇使用。
      5.當參數的個數肯定的狀況下可使用call;當參數的個數不肯定的狀況下可使用apply

apply應用

JavaScript

var array1 = [12 , "foo" , {name "Joe"} , -2458]; 
var array2 = ["Doe" , 555 , 100]; 
Array.prototype.push.apply(array1, array2); 
/* array1 值爲  [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

call應用(將僞數組轉爲數組)

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
Array.prototype.join.call(arrayLike, '&'); // name&age&sex
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// slice能夠作到類數組轉數組
Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]

call應用(判斷複雜數據類型)

console.log(
    Object.prototype.toString.call(num),
    Object.prototype.toString.call(str),
    Object.prototype.toString.call(bool),
    Object.prototype.toString.call(arr),
    Object.prototype.toString.call(json),
    Object.prototype.toString.call(func),
    Object.prototype.toString.call(und),
    Object.prototype.toString.call(nul),
    Object.prototype.toString.call(date),
    Object.prototype.toString.call(reg),
    Object.prototype.toString.call(error)
);
// '[object Number]' '[object String]' '[object Boolean]' '[object Array]' '[object Object]'
// '[object Function]' '[object Undefined]' '[object Null]' '[object Date]' '[object RegExp]' '[object Error]'

bind在react中應用

下面的例子是來自react官網的案例面試

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(this);
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.json

ES6中this的指向

箭頭函數this指向注意事項

箭頭函數體內的this對象,若是包裹在函數中就是函數調用時所在的對象,若是放在全局中就是指全局對象window。而且固定不會更改。換句話說內部的this就是外層代碼塊的this數組

下面是對比分析普通函數和箭頭函數中this區別

// 普通函數
function foo() {
  setTimeout(function() {
    console.log('id:', this.id);
  });
}

var id = 21;
foo.call({ id: 42 }); //21
// 箭頭函數
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;
foo.call({ id: 42 }); //42
// 上面的匿名函數定義時所在的執行環境就是foo函數,因此匿名函數內部的this執向始終會和foo函數的this執向保持一致,不會更改,如同下面的這個案例
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;
foo(); //21(沒有用call)
// ES5普通函數模擬上面es6函數的執行過程
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

call的做用就是將foo函數的執行環境從window改爲對象{id: 42}
定時器的做用就是延遲執行當前函數的外部執行環境,不管有沒有設置延遲時間

普通函數解釋:定義時this指向函數foo做用域,可是在定時器100毫秒以後執行函數時,此時this指向window對象

箭頭函數解釋:this始終指向定義時所在對象,也就是始終指向foo做用域

進一步分析this

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};
handler.init()// Handlingclickfor123456

箭頭函數的this始終指向handler,若是是普通函數,this指向document

this指向的固定化,並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this致使內部的this就是外層代碼塊的this。正是由於它沒有this,因此也就不能用做構造函數。

注意事項(綁定事件)

在IE678裏面不支持addEventListener和removeEventListener,而是支持attchEvent和detachEvent兩個方法。

語法:target.attachEvent(「on」+type, listener);

attachEvent和addEventListener區別:

  1. 在attchEvent裏面的this指向的不是事件的調用者,而是window(奇葩),而addEventListener指向的事件調用者。
  2. attachEvent的type必定要加上on,否則沒效果

面試題

下面的面試題1、2、四都設計到引用問題,若是不是很好理解還能夠理解成在 es5 中,永遠是this 永遠指向最後調用它的那個對象。摘錄連接

下面涉及指針應用還有綁定之類的概念來自You Dont Konw JS連接

面試題一

this.x = 9;    // this refers to global "window" object here in the browser
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();   
// returns 9 - The function gets invoked at the global scope

// Create a new function with 'this' bound to module
// New programmers might confuse the
// global var x with module's property x
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

retrieveX只是getX函數的引用,也就是隻是getX的一個指針(getX的另外一個指針是module.getX),因此retrieveX仍是指向getX函數自己的

和上面相似的案例,下面的func只是函數引用,因此即便在函數內部,仍是執行的函數自己,不受詞法做用域限制(箭頭函數則受限制)

document.getElementById( 'div1' ).onclick = function(){
    console.log( this.id );// 輸出: div1
    var func = function(){ 
        console.log ( this.id );// 輸出: undefined
    } 
    func();
}; 
//修正後
document.getElementById( 'div1' ).onclick = function(){
    var func = function(){ 
        console.log ( this.id );// 輸出: div1
    } 
    func.call(this);
};
function foo() {
    console.log( this.a );
}

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // 2

面試題二

var A = function( name ){ 
    this.name = name;
};
var B = function(){ 
    A.apply(this,arguments);
};
B.prototype.getName = function(){ 
    return this.name;
};
var b=new B('sven');
console.log( b.getName() ); // 輸出:  'sven'

面試題三

確實,許多包中的函數,和許多在JavaScript語言以及宿主環境中的內建函數,都提供一個可選參數,一般稱爲「環境(context)」,這種設計做爲一種替代方案來確保你的回調函數使用特定的this而沒必要非得使用bind(..)。

舉例來講:

function foo(el) {
    console.log( el, this.id );
}

var obj = {
    id: "awesome"
};

// 使用`obj`做爲`this`來調用`foo(..)`
[1, 2, 3].forEach( foo, obj ); // 1 awesome  2 awesome  3 awesome

面試題四

明確綁定 的優先權要高於 隱含綁定

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綁定的優先級高於隱含綁定(new和call/apply不能同時使用,因此new foo.call(obj1)是不容許的,也就是不能直接對比測試 new綁定 和 明確綁定)

相關文章
相關標籤/搜索