JS-this&bind&call&apply&new

106 阅读4分钟

this&bind&call&apply&new

总结

  1. this永远都指向最后调用他的对象

  2. bind,call,apply三个函数的作用都是修改this的指向

  3. new的优先级高于其他三兄弟(bind,call,apply)

关于this

this在开发中是经常使用的一个关键字,所以了解他也是非常重要的

我们很容易将this理解为指向函数本身,但这种理解是错误的

思考下下面的代码,我们想要记录函数foo被调用的次数

function foo(num){
  console.log("foo: "+num);
  this.count++;
}

foo.count = 0var i;
for(i = 0;i<10;i++){
  if(i>5){
    foo(i);
  }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

console.log(foo.count); // 0

由打印结果可知,我们一共调用了4次foo,可最终的输出count却对于0?

我们在执行foo.count = 0时,foo确实被添加了一个属性count,但是函数内部的this却不是指向那个函数本身

那么,到底要这么记录函数的执行次数呢?

可以看看下面的代码

function foo(num){
  console.log("foo: "+num);
  foo.count++; // 这里this被修改为foo
}

foo.count = 0;

var i;
for(i = 0;i<10;i++){
  if(i>5){
    foo(i);
  }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

console.log(foo.count); // 4 不再是0了,记录成功

这里我们完全依赖于变量foo的词法作用域,但是回避了this的问题

如果我们能让this的指向是对的,是不是就可以解决问题了?

function foo(num){
  console.log("foo: "+num);
  this.count++; // 我们还是使用了this
}

foo.count = 0;

var i;
for(i = 0;i<10;i++){
  if(i>5){
    // 使用call,确保this指向函数本身
    foo.call(foo, i);
  }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

console.log(foo.count); // 4 不再是0了,记录成功

这次我们接受了this,没有回避它,成功解决了问题

上面的例子中,我们使用了call修改了this的指向,接下来我们来讲讲如何修改this的指向

call&apply&bind&new

先来看call和apply

他们两个的作用都是:绑定this,然后立即调用

他们两个只有一个区别:call接受一个参数列表,而apply接受一个包含一个或多个参数的数组

function.call(thisArg, arg1, arg2, ...)

function.apply(thisArg, [argsArray])

来个例子,加深理解

var obj = {
  name:"laoxu",
  age:22
}

function foo(msg1,msg2){
  console.log(this.name,this.age);
  console.log(msg1,msg2);
}

foo.call(obj,"obj1","hello world");
// laoxu 22
// obj1 hello world 
foo.apply(obj,['obj2',"hello world"]);
// laoxu 22
// obj2 hello world 

可以看到,call和apply都实现了绑定this,不同的是apply传入的是一个数组

再来看看bind

bind与call和apply的差别挺大的

它用于绑定this指向,然后返回一个原函数被绑定this后的新函数

来个例子,加深理解

var obj = {
  name: "laoxu",
  age: 22
};

function foo(msg1, msg2) {
  console.log(this.name, this.age);
  console.log(msg1, msg2);
}

var bar = foo.bind(obj);
bar("obj1", "hello world"); // 绑定后并不会立即调用,需要我们再调用
// laoxu 22
// obj1 hello world

这里我们在bind之后,并不会立即调用,需要我们手动调用函数,并且可以在调用时传入参数

最后来看看new

首先需要说明一点:JS中new的机制实际上与面向类的语言完全不同

在传统的面向类的语言中,“构造函数”是类中一些特殊方法,使用new初始化类时会调用类中的构造函数

something = new MyClass(...)

而在JS中,构造函数只是一些使用new操作符时被调用的函数,并不会属于某些类,也不会实例化一个类,他们只是被new操作符调用的普通函数而已

思考下面的代码

function foo(a){
  this.a = a;
}

var bar = new foo(2);
console.log(bar.a); // 2

可以看到,这里使用new调用foo时,我们会构造一个新的对象并把它绑定到foo(...)调用中的this上

优先级

讲了四个可以修改this指向的方法,那么谁的优先级高?当多种方法复用时,我们又该怎么判定this的指向?

这里我直接给出优先级的结论

new > call,apply,bind > 隐式绑定 > 默认绑定

这里讲一下隐式绑定

var bar = obj1.foo()

上面那个就是隐式绑定!对象点出来的就是了

判断this

知道优先级之后,我们就有这样一种判断this的方法

  1. 是否在new中调用?如果是,则this绑定的就是新创建的对象
  2. 是否通过call,apply,bind调用?如果是,则this绑定的是指定的对象
  3. 是否在某个上下文对象中调用(隐式绑定)?如果是,则this就是那个对象
  4. 如果全都没有,则是默认绑定,在严格模式下绑定到undefined中,否则绑定到全局对象上