结论:this
的指向,是在调用函数时根据执行上下文所动态确定的。
执行上下文:
每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。
- 全局环境:JavaScript代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval(不建议使用,可忽略)
1 默认绑定规则
全局下默认指向window
console.log(this === window);//true
//console.log({} === {});//false,因为比较的是地址
//函数的独立调用,函数定义在window下
function test(){
console.log(this === window);//true
}
test();//等价于window.test();
2 隐式绑定规则
谁调用就指向谁
var a = 0;
var obj = {
a:2.
foo:function test(){
console.log(this);//obj.foo();中this指向obj
function test(){
console.log(this);//每个函数执行会产生自身对应的this,两个函数的this是不同的,但指向可能会相等
}
test();//函数的独立调用,所以这里this指向window
}
}
函数立即执行
function test(){ console.log(this); } test();//window //等价于 (function(){ console.log(this);//window })(); //或者等价于 (function(){ console.log(this);//window }());
闭包
var a = 0; var obj = { a:2. foo:function (){ function test(){ console.log(this); } return test; //闭包易于理解的定义:当函数执行时,导致函数被定义,并抛出(return) } } obj.foo()();//obj.foo()等价于test,所以这个语句等价于test(),结果参考函数立即执行,为window
2.1 例外
2.1.1 变量赋值(隐式丢失)
var a = 0;
function foo(){
console.log(this);
}
var obj = {
a:2,
foo:foo
}
var bar = obj.foo;//等价于foo
//方法被重写/赋值时,会存在隐式丢失
bar();//在这里函数才开始执行,本质上是函数独立调用,指向window
2.1.2 参数赋值
var a = 0;
function foo(){
console.log(this);
}
function bar(fn){
fn();
}
var obj = {
a:2,
foo:foo
}
//预编译的过程中,实参被赋值为形参;(值的拷贝的过程,浅拷贝)
bar(obj.foo);//window
2.2 改变this指向
var a = 0;
function foo(){
console.log(this);
}
var arr=[1,2,3];
//api 接口中指明的;
//回调函数是这里的子函数
arr.forEach(function(item,idx,arr){
console.log(this);
},obj)//这里的obj指明了this是什么,如果没有则指向window
arr.sort(function(a,b){
console.log(this);
return a-b;
})
//父函数是有能力决定子函数的this指向的
var obj = {
a:2,
foo:foo
}
3 显式绑定:call apply bind
var a = 0;
function foo(a,b,c){
console.log(a,b,c);
console.log(this);
}
var obj = {
a:2,
foo:foo
}
var bar = obj.foo;//等价于foo
//方法被重写/赋值时,会存在隐式丢失
//bar();//window
obj.foo(1,2,3);
bar.call(obj,1,2,3);//call(绑定对象,参数1,参数2,参数3,...)
bar.apply(obj,[1,2,3]);//apply(绑定对象,数组)
bar.bind(obj)(1,2,3);//bind不会直接执行,返回一个函数,要执行再加一个();
其他各种情况:
var a = 0;
function foo(a,b,c){
console.log(this);
}
var obj = {
a:2,
foo:foo
}
var bar = obj.foo;//等价于foo
//方法被重写/赋值时,会存在隐式丢失
//bar();//window
obj.foo(1,2,3);
bar.call(1,2,3);//this为Number{1}这个对象
bar.call(false,2,3);//this为Boolean{false}这个对象
bar.call(undefined,2,3);//不是对象,绑定失败,默认绑定window
bar.apply(null,[1,2,3]);//不是对象,绑定失败,默认绑定window
bar.bind(obj)(1,2,3);//bind不会直接执行,返回一个函数,要执行再加一个();
4 new 绑定
function Person(){
//var this = {};
//this.a = 1;
//return this;
return 1;
}
//如果构造函数中return了引用值,则this指向这个引用值,但ES6规定构造函数一般不主动设定返回值
var person = new Person();//this指向构造函数实例化后的对象
console.log(person);
var obj = {
a:1
};
//立即执行函数+call
(function(){
console.log(this);
}).call(obj)//obj
//无意义
5 优先级
new>显式>隐式>默认
6 例子
var name = '222';
var a = {
name:'111',
say:function(){
console.log(this.name);
}
}
var fun = a.say;
fun();//window//222
a.say();///a//111
7 箭头函数的this
箭头函数的this指向是由外层作用域(父环境)决定的,它自身不存在this
所有绑定规则不适用于箭头函数
- 默认绑定规则(独立调用)对箭头函数无效
- 显式绑定规则对箭头函数无效
- 隐式绑定规则对箭头函数无效
- 箭头函数不能作为构造函数,所以new规则也不能应用于箭头函数
8 复杂的例子
var name = 'window'
var obj1 = {
name:'1',
fn1:function(){
console.log(this.name)
},
fn2:()=> console.log(this.name),
fn3:function(){
return function(){
console.log(this.name)
}
},
fn4:function(){
return ()=>console.log(this.name)
}
}
var obj2 = {
name:'2'
};
obj1.fn1();//1
obj1.fn1.call(obj2);//2
obj1.fn2();//1->error window->correct
obj1.fn2.call(obj2);//1->error window->correct
obj1.fn3()();//window
obj1.fn3().call(obj2);//2
obj1.fn3.call(obj2)();//window
obj1.fn4()();//window->error 1->correct
obj1.fn4().call(obj2);//window->error 1->correct
obj1.fn4.call(obj2)();2
function Foo(){
getName = function(){alert(1);};
return this;
}
Foo.getName = function (){alert(2);};
Foo.prototype.getName = function(){alert(3);};
var getName = function(){alert(4);};
function getName(){alert(5);}
//请写出以下输出结果
Foo.getName();//2
getName();//4
Foo().getName();4->error 1->correct
//因为Foo函数里的getName方法没有使用var,所以是全局方法,所以window下的getName被改变了
getName();//4->error 1->correct
new Foo.getName();//1 2
new Foo().getName();//1 2
new new Foo().getName();//1 2