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绑定 和 明确绑定)

相关文章
相关标签/搜索