this的绑定规则
函数中的this指向
普通函数中的this是谁?—window
对象.方法中的this是谁?-------该方法所属对象
定时器中的this是谁?—window
构造函数中的this是谁?—当前的实例对象
原型对象方法中的this是谁?—实例对象
1.默认绑定
function foo(){
var a = 1;
console.log(this.a) //10
var a = 10;
foo();
像这种直接使用而不带任何修饰的函数调用,默认且只能应用默认绑定
默认绑定到window上,严格模式下是undefined
函数独立调用,函数内部的this也是指向window
function fn (){
console.log(this); //window
}
fn();
var obj = {
a = 2;
foo:function(){
console.log(this) //这个指向当前的对象 obj
//函数当作对象的方法来调用时,this指向该函数的对象
function test (){
console.log(this) //window
} //这个test函数实在函数内独立调用,所以跟外面没关系,
test(); //他就指向window
}
}
obj.foo();
2.隐式绑定
//当函数当作方法来调用,this指向了直接对象,就是谁调用它就this就指向谁
function foo(){
console.log(this.a)
}
var obj = {
a:10,
foo:foo,
obj2:{
a:2,
foo:foo
}
}
//调用的是全局变量相当于打印window.a
//但是全局里没有a变量 所以就打印undefined
foo(); //undefined
//obj调用的foo()函数,所以this就直接指向obj
obj.foo(); //10
//obj2调用的foo()函数,所以this就直接指向obj2
obj.obj2.foo(); //2
2.1隐式丢失
隐式丢失就是指被绑定的函数丢失了绑定对象,从而默认绑定window
var a = 0
function foo(){
console.log(this.a)
}
var obj = {
a : 1;
foo: foo
}
var bar = obj.foo(); //帮foo()方法赋值到 bar变量上
bar(); //打印:0 相当于是window.a()
内置函数setTimeout()hh额setInterval()第一个参数的回调函数中的this指向的是window
setTimeout(function(){
console.log(this) //window
},1000)
var a = 10
function foo(){
console.log(this.a)
}
var obj = {
a:1,
foo:foo
}
setTimeout(obj.foo,1000) //10
3.显示绑定
//call(),apply(),bind()把对象绑定上this上,叫做显示绑定
var a = 0;
function foo(){
console.log(this.a)
}
var obj = {
a:2
}
foo() //0
foo.call(obj) //2
foo.apply(obj) //2
var fn = foo.bind(obj)
fn() //2 bind()有个返回值,需要定义一个变脸返回才行
4.new绑定
function foo(){
this.a = 10;
console.log(this);
}
foo(); // window对象
console.log(window.a); // 10 默认绑定
var obj = new foo(); // foo{ a : 10 } 创建的新对象的默认名为函数名
// 然后等价于 foo { a : 10 }; var obj = foo;
console.log(obj.a); // 10 new绑定
this绑定优先级
new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
call,apply,bind的区别
call从第二个参数开始所有的参数都是原函数的参数
apply只接受两个参数,且第二个参数必须是数组,这个数组代表原函数的参数列表
bind只有一个参数,且不会立即执行,只是将一个值绑定到函数的this上,并将绑定好的函数返回
总结:
1.如果函数被new 修饰
this绑定的是新创建的对象,例:var bar = new foo(); 函数 foo 中的 this 就是一个叫foo的新创建的对象 , 然后将这个对象赋给bar , 这样的绑定方式叫 new绑定 .
2.如果函数是使用call,apply,bind来调用的
this绑定的是 call,apply,bind 的第一个参数.例: foo.call(obj); , foo 中的 this 就是 obj , 这样的绑定方式叫 显性绑定 .
3.如果函数是在某个 上下文对象 下被调用
this绑定的是那个上下文对象,例 : var obj = { foo : foo }; obj.foo(); foo 中的 this 就是 obj . 这样的绑定方式叫 隐性绑定 .
4如果都不是,即使用默认绑定
例:function foo(){…} foo() ,foo 中的 this 就是 window.(严格模式下默认绑定到undefined).
这样的绑定方式叫 默认绑定 .
面试题:
var x = 10;
var obj = {
x: 20,
f: function(){
console.log(this.x); // 20
// 典型的隐性绑定,这里 f 的this指向上下文 obj ,即输出 20
function foo(){
console.log(this.x);
}
foo(); // 10
//闭包内的this指向的也是window
}
};
obj.f();
var x = 10;
var obj = {
x: 20,
f: function(){ console.log(this.x); }
};
var bar = obj.f;
var obj2 = {
x: 30,
f: obj.f
}
obj.f(); // 20
//有上下文,this为obj,隐性绑定
bar(); // 10
// 默认绑定 ( obj.f 只是普通的赋值操作 )
obj2.f(); //30
//不管 f 函数怎么折腾,this只和 执行位置和方式有关
function foo() {
getName = function () { console.log (1); };
//这里的getName 将创建到全局window上
return this;
}
foo.getName = function () { console.log(2);};
//这个getName和上面的不同,是直接添加到foo上的
foo.prototype.getName = function () { console.log(3);};
// 这个getName直接添加到foo的原型上,在用new创建新对象时将直接添加到新对象上
var getName = function () { console.log(4);};
// 和foo函数里的getName一样, 将创建到全局window上
function getName () { console.log(5);}
// 同上,但是这个函数不会被使用,因为函数声明的提升优先级最高,所以上面的函数表达式将永远替换
// 这个同名函数,除非在函数表达式赋值前去调用getName(),但是在本题中,函数调用都在函数表达式
// 之后,所以这个函数可以忽略了
// 通过上面对 getName的分析基本上答案已经出来了
foo.getName (); // 2
// 下面为了方便,我就使用输出值来简称每个getName函数
// 这里有小伙伴疑惑是在 2 和 3 之间,觉得应该是3 , 但其实直接设置
// foo.prototype上的属性,对当前这个对象的属性是没有影响的,如果要使
// 用的话,可以foo.prototype.getName() 这样调用 ,这里需要知道的是
// 3 并不会覆盖 2,两者不冲突 ( 当你使用new 创建对象时,这里的
// Prototype 将自动绑定到新对象上,即用new 构造调用的第二个作用)
getName (); // 4
// 这里涉及到函数提升的问题,不知道的小伙伴只需要知道 5 会被 4 覆盖,
// 虽然 5 在 4 的下面,其实 js 并不是完全的自上而下,想要深入了解的
// 小伙伴可以看文章最后的链接
foo().getName (); // 1
// 这里的foo函数执行完成了两件事, 1. 将window.getName设置为1,
// 2. 返回window , 故等价于 window.getName(); 输出 1
getName (); // 1
// 刚刚上面的函数刚把window.getName设置为1,故同上 输出 1
new foo.getName (); // 2
// new 对一个函数进行构造调用 , 即 foo.getName ,构造调用也是调用啊
// 该执行还是执行,然后返回一个新对象,输出 2 (虽然这里没有接收新
// 创建的对象但是我们可以猜到,是一个函数名为 foo.getName 的对象
// 且__proto__属性里有一个getName函数,是上面设置的 3 函数)
new foo().getName (); // 3
// 这里特别的地方就来了,new 是对一个函数进行构造调用,它直接找到了离它
// 最近的函数,foo(),并返回了应该新对象,等价于 var obj = new foo();
// obj.getName(); 这样就很清晰了,输出的是之前绑定到prototype上的
// 那个getName 3 ,因为使用new后会将函数的prototype继承给 新对象
new new foo().getName (); // 3
// 哈哈,这个看上去很吓人,让我们来分解一下:
// var obj = new foo();
// var obj1 = new obj.getName();
// 好了,仔细看看, 这不就是上两题的合体吗,obj 有getName 3, 即输出3
// obj 是一个函数名为 foo的对象,obj1是一个函数名为obj.getName的对象