使用this可以减少传入上下文对象,可以隐式传递一个对象引用。使API简洁而复用,可以自动引用合适的上下文对象。
【要注意的几个点】
1. this不一定指向自身;
2. this不一定指向函数作用域(因为作用域无法通过js代码访问,它存在js引擎内部);
3. this是在运行时(函数被调用时)绑定的,不是编写代码时绑定。this的绑定取决于函数的调用位置,不是函数声明的位置;
4. 不能通过this来引用一个词法作用域内部的东西
【找this的指向】
分析调用栈(为了到达当前执行位置所调用的所有函数),找到当前正在执行的函数的前一个调用,即调用栈的第二个元素。因为函数被调用时,会创建一个活动记录(执行上下文)。该活动记录包括(函数的调用栈、传入的参数、函数的调用方法……)
[方法]:在代码前插入一条debugger语句,在开发者工具中查看call stack中的第二个元素,就是this的调用位置.
function first(){
//当前调用栈first
//当前的调用位置是全局作用域
console.log("first");
second();//second的调用位置
}
function second(){
//当前调用栈first->second
//当前的调用位置是first
console.log("second");
third();//third的调用位置
}
function third(){
//当前调用栈first->second->third
//当前的调用位置是second
console.log("third");
}
//first的调用位置
first();//firstsecondthird
【四条this规则】:
1.默认绑定(绑定到全局对象),但严格模式下this会绑定到undefin
ed:
【例子】:不带任何修饰的函数调用
function example(){
console.log(this.a);
}
var a="karine";
example(); //karine
2.隐式绑定
(在一个对象内部包含一个指向函数的属性,通过该属性间接地引用函数,从而把this间接地绑定到该对象上。)
function example(){
console.log(this.a);
}
var obj={
a:"karine",
example:example
};
obj.example(); //karine
在对象属性引用链中,只有最顶层(最后一层)会影响调用位置。
function example(){
console.log(this.a);
}
var obj1={
a:"karine",
example:example
};
var obj2={
a:"wu",
obj1:obj1
};
obj2.obj1.example(); //karine
当函数引用有上下文对象时,函数里的this绑定在这个上下文对象上。
但是这种情况可能会出现“隐式丢失”,即丢失绑定的对象,应用默认绑定(全局对象/undefinded):
1. 将函数引用赋值给一个新变量,然后执行。
var obj3 = obj1.example;
obj3(); //全局or undefined
实际上obj3引用的是example函数本身,因此this还是指向全局/undefined
2. 或者是调用回调函数的函数修改了this
function test(fn){
fn();
}
test(obj1.example); //全局or undefined
实际上,参数传递也是一种赋值,只不过是隐式赋值。所以道理同上。也会指向全局。
3.显式绑定
(在对象上强制调用函数,可以使用call(对象)、apply(对象)方法,会把形参里的对象绑定到this上,接着在调用函数时指定这个this。如果传入的参数为原始值(string、number、boolean),该原始值会自动转为其对象形式(new String()、new Number()、new Boolean()),这通常被称为“装箱”)
function example(){
console.log(this.a);
}
var obj = {
a:"karine"
};
example.call(obj); //karine
example.apply(obj);//karine
var o = example.bind(obj);
o(); //karine
“硬绑定”bind 的this不会被显示or隐式绑定修改。
4.new绑定
在js中,构造函数只是使用new操作符是被调用的普通函数而已,不属于某个类,也不会实例化一个类。使用new调用函数时,会自动执行以下操作:
1.创建一个全新的对象
2.该对象会被执行[[prototype]]连接
3.该对象会绑定到函数调用的this
4.若函数没有返回其他对象,new表达式中的函数调用会自动返回该对象。
function example(a){
this.a=a;
}
var obj4 = new example(“karine”);
console.log(obj4.a);//“karine”
【四种this绑定方法的优先级】
new绑定 > 显式绑定 >隐式绑定 > 默认绑定
【例外】:
1.null、undefined作为this的绑定对象传入call、apply、bind时,会被忽略,实际应用的是默认绑定(全局对象)
function example(){
console.log("a:"+this.a);
}
var a = "karine";
example.call(null); //a:karine
传入null的情况:
(1)使用apply来展开一个数组
(2)使用bind进行柯里化
function example(a,b){
console.log("a:"+a+“,b:”+b);
}
//使用apply来展开一个数组
example.apply(null,[2,3]); //a:2,b:3
//使用bind进行柯里化(预先设置一些参数)
var o = example.bind(null,2);
o(3);//a:2,b:3
若想避免不必要的this绑定,可以使用ES6的…操作符来代替apply()
example.apply(null,[2,3]) === example(…[2,3])
为了安全,若想忽略this绑定,可以给this传入一个空的非委托对象Φ (“Object.create(null)”),不会产生任何副作用,因为this会限制在这个空对象中,不会对全局对象产生任何影响。
Object.create(null)和{}很像,但是不会创建object.prototype,所以比{}“更空”
var Φ = Object.create(null);
example.apply(Φ,[2,3]); //a:2,b:3
var o = example.bind(Φ,2);
o(3); //a:2,b:3
2.间接引用(经常在赋值时发生),会应用默认绑定。
3.ES6中 “=>”定义的箭头函数不使用this的四种标准规则。而是根据外层作用域来决定this。箭头函数的绑定无法修改。
function example(){
return(a)=>{
console.log(this.a);
}
}
var obj1={a:2};
var obj2={a:3};
var test = example.call(obj1);
test.call(obj2); //2
由于example()的this绑定到obj1,所以test中引用箭头函数的this也会绑定到obj1。
【总结】:
1.找到this的调用位置
2.根据四条规则判定this的绑定对象:
(1)new:绑定到新创建的对象
(2)call、apply、bind:绑定到指定的对象
(3)上下文对象:绑定到该上下文对象
(4)默认:全局或者undefined(严格模式下)
3.箭头函数根据当前的词法作用域来决定this
4.有些调用可能无意中使用默认规则,可以使用一个空对象Object.create(null)忽略this绑定,来保护全局对象