1:为什么要使用this?
this提供了一种更优雅的方法来隐式'传递'一个对象的引用,因此可以将API设计得更加简洁并且易于复用。
需要注意大家一般会有的两个误解:
(1)this指向函数自身
(2)this指向函数的作用域
作用域无法通过JavaScript代码访问,它存在于JavaScript引擎内部。每当把this和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的!
this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用位置(也就是函数的调用方式)!
2:this的四种绑定规则
1)默认绑定:
当在全局作用域中独立调用函数的时候,如 fun(); ,此时this指代的是全局对象,即window。
function foo() {
console.log(this.a); // this指向全局对象
}
var a = 2;
foo(); // 2
function foo() {
"use strict"; // 在严格模式下,全局对象将无法使用默认绑定,因此this会绑定到undefined。
console.log(this.a);
}
foo(); // TypeError:a undefined
(对于默认绑定来说,决定this绑定对象的是函数体是否处于严格模式,严格指向undefined,非严格指向全局对象)
2)隐式绑定:
在函数调用时,其存在上下文对象,也就是被某个对象拥有,如:var obj = { fun: fun(); }; ,此时this绑定到这个上下文对象上。
function foo() {
console.log(this.a);
}
var a = "1";
let obj1 = {
a: 2,
foo: foo
};
let obj2 = {
a: 22,
obj2: obj2
};
obj1.foo(); // 2 this指向调用函数的对象
obj2.obj1.foo(); // 2 this指向最后一层调用函数的对象
隐式丢失
常见的this绑定问题就是“隐式绑定”的函数会丢失绑定对象,也就是“默认绑定”,从而把this绑定到全局对象(严格模式下为undefined)。
var a = "foo";
function foo(){
console.log(this.a);
}
var obj = {
a : 2,
foo : foo
}
var bar = obj.foo;
bar(); //"foo"
很好的一个例子:
var a = "foo";
function foo(){
console.log(this.a);
}
function test(fn){
fn();
// 相当于 let = obj.foo 绑定丢失 this指向window
}
var obj = {
a : 2,
foo : foo
}
test(obj.foo); //"foo"
3)显式绑定:
调用call(...)或apply(...)进行显式绑定,将函数中的this绑定到指定的对象上,如 fun.call(obj); 或 fun.apply(obj); 。
function foo() {
console.log(this.a);
}
let obj = {
a: 2
};
foo.call(obj); // 2
当传入的不是对象:
如果你传入了一个原始值(字符串,布尔类型,数字类型),来当做this的绑定对象,这个原始值转换成它的对象形式。
如果你把null
或者undefined
作为this的绑定对象传入call
/apply
/bind
,这些值会在调用时被忽略,实际应用的是默认绑定规则
4)new绑定:
new调用函数会自动执行下面操作:
(1)创建(或者说构造)一个全新的对象;
(2)这个新对象会被执行[[原型]]连接;
(3)这个新对象会绑定到函数调用的this;
(4)如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
调用函数是在函数前使用new关键字,会将函数绑定到此时赋值的对象上,如 var obj = new fun(); 。
在上述几种规则中,优先级顺序如下:
显式绑定 > new绑定 > 隐式绑定 > 默认绑定
3:绑定this注意点
1) 忽略this
把null或undefined作为this的绑定对象传入call、apply、bind,调用时会被忽略,实际应用的是默认绑定规则!
function foo(){
console.log(this.a);
}
var a = 1;
foo.call(null, 2); //1
foo.apply(undefined, [3]); //1
2) 间接引用
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 pfoo = o.foo;
pfoo(); //2 隐式丢失
注意:同上述“隐式丢失”结果一样,但是过程不太一样,区分!!
3) ES6箭头函数
箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
箭头函数的绑定无法被修改。常用于回调函数中,如事件处理器或定时器。和ES6之前代码中的this = self机制一样。
function foo(){
setTimeout(()=>{
console.log(this.a);
},100);
}
var obj = { a : 2};
foo.call(obj);
等价于:
function foo(){
var self = this;
setTimeout(function(){
console.log(self.a);
},100);
}
var obj = { a : 2};
foo.call(obj);
4:详解如何确定this
1)(隐式绑定)如果某个对象中某个成员是个function,当从这个对象上调用这个方法时this指向当前对象。
var FenFei = {
firstname:"li",
lastname:"gang",
timeTravel:function(year){
console.log(this.firstname + " " + this.lastname + " is time traveling to " + year);
}
}
FenFei.timeTravel(2014); //li gang is time traveling to 2014(父/拥有者对象:FenFei)
2)(隐私绑定)可以通过创建一个新的对象,来引用FenFei对象上的timeTravel方法。
var Camile = {
firstname:"li",
lastname:"yunxia"
}
Camile.timeTravel = FenFei.timeTravel;
Camile.timeTravel(2014); //li yunxia is time traveling to 2014(父/拥有者对象:Camile)
注意:此示例同上述“隐式丢失”、“间接引用”区分!!!
3)(隐式丢失)使用变量保存FenFei.timeTravel方法的引用
var getTimeTravel = FenFei.timeTravel;
getTimeTravel(2014); //undefined undefined is time traveling to 2014(父/拥有者对象:Window;window对象里并没有firstName和lastName属性)
PS:谨记方法中的this将指向调用它的那个父/拥有者对象!无论何时,当一个函数被调用,我们必须看方括号或者是圆括号左边紧邻的位置,如果我们看到一个引用(reference),那么传到function里面的this值就是指向这个方法所属于的那个对象,如若不然,那它就是指向全局对象的。
4)异步调用的方法内部的this
<button id="async">点击我</button>
var btnDom = document.getElementById("async");
btnDom.addEventListener('click',FenFei.timeTravel);//undefined undefined is time traveling to [object MouseEvent](父/拥有者对象:button)
btnDom.addEventListener('click',function(e){
FenFei.timeTravel(2014);//li gang is time traveling to 2014(父/拥有者对象:FenFei)
});
5)(new绑定)构造函数里的this
当使用构造函数创建一个对象的实例时,构造函数里的this就是新建的实例。
var TimeTravel = function(fName, lName){
this.firstname = fName;
this.lastname = lName;
}
var FenFei = new TimeTravel("li", "gang");
console.log(FenFei.firstname + " " + FenFei.lastname); //li gang
6)(显示绑定)call和apply方法设定this值
var Camile = {
firstname:"li",
lastname:"yunxia"
}
FenFei.timeTravel.call(Camile,2014); //li yunxia is time traveling to 2014(指定this对象为Camile)
FenFei.timeTravel.apply(Camile,[2014]); //li yunxia is time traveling to 2014(指定this对象为Camile)
PS:注意和上述“2”处做对比
7)(显示绑定)bind将函数绑定至某个对象(ES5)
function f(y){
return this.x + y;
}
var o = {x:1};
/* f.bind(o)返回一个新函数,调用g(2)会把原始的函数f()当作o的方法来调用 */
var g = f.bind(o);
g(2); // 3
补充:ES5之前方法模拟bind()方法
function myBind(f, o){
if(f.bind){
return f.bind(o);
}else{
return function(){
return f.apply(o, arguments);
}
}
}
bind的作用和apply,call类似都是改变函数的execute context,也就是runtime时this关键字的指向。但是使用方法略有不同。一个函数进行bind后可稍后执行。