this&bind&call&apply&new
总结
-
this永远都指向最后调用他的对象
-
bind,call,apply三个函数的作用都是修改this的指向
-
new的优先级高于其他三兄弟(bind,call,apply)
关于this
this在开发中是经常使用的一个关键字,所以了解他也是非常重要的
我们很容易将this理解为指向函数本身,但这种理解是错误的
思考下下面的代码,我们想要记录函数foo被调用的次数
function foo(num){
console.log("foo: "+num);
this.count++;
}
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); // 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的方法
- 是否在new中调用?如果是,则this绑定的就是新创建的对象
- 是否通过call,apply,bind调用?如果是,则this绑定的是指定的对象
- 是否在某个上下文对象中调用(隐式绑定)?如果是,则this就是那个对象
- 如果全都没有,则是默认绑定,在严格模式下绑定到undefined中,否则绑定到全局对象上