人生三大问题,我是谁?我从哪儿来?我到哪里去?哲学上,没有人能真正回答这三个问题,同样,在js中,因为情况太多,要想真正理解各种情况下js中this代表什么也是一比较难的事情,更加尴尬的是,this这个关键字,在js中不但复杂而且使用的很多,以至于不弄懂this到底代表什么,就不敢去写js,更不必谈去看别人的代码,更加的吃力。 理解this指向的一个原则,this的指向一定是一个对象,永远指向函数运行时所在的对象,而不是函数被创建时所在的对象,而且这个对象是最后调用函数的那个对象,
但是箭头函数和bind绑定过的函数除外。this的四大绑定规则:默认绑定、显式绑定、隐式绑定、new绑定,前面已经说到this不但复杂而且很重要,但是也不要怕,只要理解了显示绑定,基本就能掌握this,当然是建立在大量练习的前提下,因为隐式绑定和默认绑定就是从显式绑定派生来的,下面结合各种情况分析this的指向。
call 、apply与this(显式绑定)
call和apply的作用是给函数重新指定调用者,指定this的指向,在js中函数也是对象,既然是对象,就有方法,call、apply就是函数的方法,可以显式指定函数的this指向的对象,即call、apply第一个参数。
function foo() {
console.log( this.name);
}
var obj = {
name: '小明'
};
foo.call(obj); // => 小明
foo.apply(obj); // => 小明
var tmp = foo.bind(obj)
tmp() // => 小
bind与this(显式绑定,硬绑定)
bind和call,apply有点类似,只不过后者是立即执行,而bind是返回一个永远绑定到某对象的函数,bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被调用的。
function foo() {
return this.a;
}
var bar = foo.bind({
a: 'hello'
});
console.log(bar()) // hello
var o = {
a: 'world',
foo: foo,
bar: bar
};
console.log(o.foo(), o.bar()) // world hell
全局的this(默认绑定)
function foo() {
console.log( this);
}
foo();//windo
用显式绑定的方式去理解,以上代码可以理解为foo.call(window)。
new与this
function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
console.log(this);
}
//作为构造函数使用
var o = new Person("Lily", 18, "F"); //this为当前对象 Person {name: "Lily", age: 18, gender: "F"
构造函数与普通函数区别是调用方式而不是定义方式,使用new关键字就是构造函数,否则就是普通函数。new关键字改变了函数内this的指向,使其指向刚创建的对象。 不能返回一个空对象。
function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
return {};//返回空对象
}
//作为构造函数使用
var o = new Person("Lily", 18, "F");
console.log(o.name) //undefine
但是可以返回基本数据问题。
function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
return 1; //返回标量
}
//作为构造函数使用
var o = new Person("Lily", 18, "F");
console.log(o.name) //Lil
作为对象方法的this(隐式绑定)
let name = '全局';
var obj = {
name: '小明',
getName: function () {
return this.name;
}
};
//作为对象方法调用
console.log(obj.getName()); //小
用显式绑定的方式去理解,以上代码可以理解为obj.getName.call(obj)。 this指向最后一个调用函数的对象,而不是开始的那个对象
var parent = {
age:30,
son:{
age:5,
getAge:function(){
console.log(this.age); //5 ,会打印出来son的年龄,而不是parent的年龄
}
}
}
parent.son.getAge()
对象方法作为函数调用的this(默认绑定)
window.age = 100;
var obj = {
age: 10,
getAge: function () {
return this.age;
}
};
var getAge = obj.getAge;
//作为普通函数调用
console.log(getAge()); //10
尽管getAge方法是obj对象的, 但是最终调用这个方法的对象是window,可以理解为getAge.call(window)
对象原型链上的this
function Foo(){
this.x = 10;
}
Foo.prototype.getX = function () {
console.log(this); //Foo {x: 10, getX: function}
console.log(this.x); //10
}
var foo = new Foo();
foo.getX()
在 Foo.prototype.getX 函数中,this 指向的 foo 对象。不仅仅如此,即便是在整个原型链中,this 代表的也是当前对象的值。
Es6 箭头函数中的 this
箭头函数不仅仅是匿名函数的简写,更大的好处是解决了匿名函数的this指向问题,有利于封装回调函数。 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。 如何去理解定义函数所在作用域指向的对象,看了以下代码就会明白:
var name = 'window';
var A = {
name: 'A',
sayHello: () => {
console.log(this.name)
}
}
A.sayHello();// 这里不是输出A,而是输出windo
作用域是指函数内部,这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。 如果要想this指向对象A,那么就要在箭头函数外面再包一层函数
var name = 'window';
var A = {
name: 'A',
sayHello: function(){
var s = () => console.log(this.name)
return s//返回箭头函数s
}
}
var sayHello = A.sayHello();
sayHello();// 输出A
具体逻辑是因为sayHello是一个函数所以箭头函数s作用域是sayHello,而sayHello指向的对象是A, 所以箭头函数的this指向对象A。 由于JavaScript函数对this绑定的错误处理,下面的代码无法得到预期结果,fn函数中的this会指向指向window或undefined,需要使用var that = this的hack方法修正。
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
}
但是箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 2
因为this在箭头函数中已经按照词法作用域绑定了,箭头函数中的this可不改变,仅仅与其定义的位置有关,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略。
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);//call方法的第一个参数会被忽略
}
};
obj.getAge(2015); // 2
DOM事件绑定与this
函数被用作事件处理函数时,它的this指向触发事件的元素 click me! btn1 click me! btn2 内联事件的this指向window
<button id='btn1' onclick="clickHandler()">click me! btn1</button>
<button id='btn2' onclick="clickHandler()">click me! btn2</button>
<script>
function clickHandler() {
console.log(this, this.id)
}
</script
如果要想在内联事件处理函数中的 this 也指向该元素,那么我们可以用 bind 函数指定其 this 值。
<button id='btn1' onclick="clickHandler.bind(this)()">click me! btn1</button>
<button id='btn1' onclick="clickHandler.call(this)">click me! btn1</button>
<button id='btn1' onclick="clickHandler.apply(this)">click me! btn1</button
window定时器与this
由于setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致这些代码中包含的 this 关键字会指向 window (或全局)对象。
var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(function(){
console.log(this.num);
}, 1000)
}
}
var obj = new Obj;
obj.getNum();//1 打印的为obj.num,值为1
obj.getNumLater()//0 期望打印出来obj的num,然后却打印出来window的num,this指向了windo
如果要想定时器里面的this指向Obj,
var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(function(){
console.log(this.num);
}.bind(this), 1000)//利用bind()将this绑定到这个函数上
}
}
var obj = new Obj;
obj.getNum();//1 打印的为obj.num,值为1
obj.getNumLater()//1 打印的为obj.num,值为
或者使用箭头函数纠正this的指向
var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(() => {console.log(this.num);}, 1000)
}
}
var obj = new Obj;
obj.getNum();//1 打印的为obj.num,值为1
obj.getNumLater()//0 打印的为window.num,值为
with和this
with 语句 为一个或一组语句指定默认对象,使用 with 语句时,代码变得更短且更易读,如果在 with 语句块中使用 this,它就代表 with 所指定的对象。,但是因为with会降低代码的效率,所以在实际开发中不推荐使用,所以不详细讨论。
严格模式下的this
严格模式并没有让this变复杂, 严格模式影响的只是context的默认值,仅此而已。
[]与this,迷惑的[]
function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]()
这段代码很容易把arr[0]() 理解成fn(),从而让人误解执行的过程是fn.call(window),从而让人理解this指向window,然后实际上的执行过程是arr[0]() => arr.fn() => this指向arr。 总结,this的指向一定是一个对象,永远指向函数运行时所在的对象,而不是函数被创建时所在的对象,而且这个对象是最后调用函数的那个对象,但是箭头函数和bind绑定过的函数除外: 1 . 函数是否在 new 中调用 (new 绑定),如果是,那么 this 绑定的是新创建的对象。 2 . 函数是否通过 call,apply 调用,或者使用了 bind(即硬绑定),如果是,那么 this 绑定的就是指定的对象。 3 . 函数是否在某个上下文对象中调用 (隐式绑定),如果是的话,this 绑定的是那个上下文对象。一般是 obj.foo()。 4 . 如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到 undefined,否则绑定到全局对象。 5 . 如果把 Null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。 6 . 如果是箭头函数,箭头函数的 this 继承的是外层代码块的 this。
参考文章
箭头函数