this指向总结2
this绑定的几种场景
- 全局环境下:this 始终指向全局对象(window), 无论是否严格模式;
- 函数上下文调用
- this默认绑定(函数直接调用)
- this隐式绑定(对象中的this、原型链中的this)
- this显式绑定(call、apply、bind)
- new绑定
- 箭头函数的this
一. 全局环境下
this 始终指向全局对象(window), 无论是否严格模式;
"use strict";
console.log(this) //window
console.log(this.document === document); // true
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
二. 函数上下文调用
1. this默认绑定(函数直接调用)
this默认绑定我们可以理解为函数调用时无任何调用前缀的情景
- 正常情况(非严格模式):this指向全局对象window
- 严格模式:this指向undefined
//非严格模式:this指向全局对象window
function fn1() {
let fn2 = function () {
console.log(this); //window
fn3();
};
console.log(this); //window
fn2();
};
function fn3() {
console.log(this); //window
};
fn1();
//这个例子中无论函数声明在哪,在哪调用,由于函数调用时前面并未指定任何对象,这种情况下this指向全局对象window
//严格模式:this指向undefined
function fn() {
console.log(this); //window
console.log(this.name); //听风是风
};
function fn1() {
"use strict";
console.log(this); //undefined
console.log(this.name); //TypeError: Cannot read property 'name' of undefined
};
var name = '听风是风'
fn()
fn1()
//函数以及调用都暴露在严格模式中的例子:
"use strict";
var name = '听风是风';
function fn() {
console.log(this); //undefined
console.log(this.name);//TypeError: Cannot read property 'name' of undefined
};
fn();
//在严格模式下调用不在严格模式中的函数,并不会影响this指向
var name = '听风是风';
function fn() {
console.log(this); //window
console.log(this.name); //听风是风
};
(function () {
"use strict";
fn();
}());
2. this隐式绑定(对象中的this、原型链中的this)
如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上
- this指向调用本函数的对象
function fn() {
console.log(this.name); //听风是风
};
let obj = {
name: '听风是风',
func: fn
};
obj.func() //此时this指向obj,所以输出“听风是风”
//如果函数调用前存在多个对象,this指向距离调用自己最近的对象,比如这样:
function fn() {
console.log(this.name); //行星飞行
};
let obj = {
name: '行星飞行',
func: fn,
};
let obj1 = {
name: '听风是风',
o: obj
};
obj1.o.func() //此时this指向obj,所以输出“行星飞行”
//那如果我们将obj对象的name属性注释掉
function fn() {
console.log(this.name); //undefined
};
let obj = {
func: fn,
};
let obj1 = {
name: '听风是风',
o: obj
};
obj1.o.func() //此时this指向obj,由于obj对象没有name属性,所以输出undefined
//以下代码输出什么?
function Fn() {};
Fn.prototype.name = '时间跳跃';
function fn() {
console.log(this.name);
};
let obj = new Fn();
obj.func = fn;
let obj1 = {
name: '听风是风',
o: obj
};
obj1.o.func() //时间跳跃
//此时this指向obj,虽然obj对象并没有name属性,但顺着原型链,找到了产生自己的构造函数Fn,由于Fn原型链存在name属性,所以输出“时间跳跃”
隐式丢失
var name = '行星飞行';
let obj = {
name: '听风是风',
fn: function () {
console.log(this.name); ;//行星飞行
}
};
function fn1(param) {
param();
};
fn1(obj.fn)
//这里我们将 obj.fn 这个函数传递进 fn1 中执行,这里只是单纯传递了一个函数而已,所以this指向了window,相当于函数直接调用。
var name = '行星飞行';
let obj = {
name: '听风是风',
fn: function () {
console.log(this.name); //行星飞行
}
};
let fn1 = obj.fn;
fn1(); //这里也是把obj.fn这个函数赋给了fn1,直接调用这个函数,所以this指向了window
var name = '行星飞行';
let obj = {
name: '听风是风',
fn: function () {
console.log(this.name); //时间跳跃
}
};
let obj1 = {
name: '时间跳跃'
}
obj1.fn = obj.fn;
obj1.fn(); //这里把obj.fn这个函数赋给了obj1.fn,执行后this指向obj1,所以输出“时间跳跃”
3. this显式绑定(call、apply、bind)
显式绑定是指我们通过call、apply以及bind方法改变this的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程
- 正常情况:this指向绑定的对象
- 参数为null或者undefined:this指向window
let obj1 = {
name: '听风是风'
};
let obj2 = {
name: '时间跳跃'
};
let obj3 = {
name: 'echo'
}
var name = '行星飞行';
function fn() {
console.log(this.name);
};
fn(); //行星飞行 this指向window
fn.call(obj1); //听风是风 this指向obj1
fn.apply(obj2); //时间跳跃 this指向obj2
fn.bind(obj3)(); //echo this指向obj3
//如果在使用call之类的方法改变this指向时,指向参数提供的是null或者undefined,那么 this 将指向全局对象
let obj1 = {
name: '听风是风'
};
let obj2 = {
name: '时间跳跃'
};
var name = '行星飞行';
function fn() {
console.log(this.name);
};
fn.call(undefined); //行星飞行 this指向window
fn.apply(null); //行星飞行 this指向window
fn.bind(undefined)(); //行星飞行 this指向window
在js API中部分方法也内置了显式绑定,以forEach为例:
let obj = {
name: '听风是风'
};
[1, 2, 3].forEach(function () {
console.log(this.name);//听风是风*3
}, obj);
4. new绑定
- this指向被创建的新对象
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38}; //当构造器返回的值是一个对象时,实例化化的this指向这个对象
}
var b = new C2();
console.log(b.a); // 38
this绑定优先级
显式绑定 > 隐式绑定 > 默认绑定
new绑定 > 隐式绑定 > 默认绑定
为什么显式绑定不和new绑定比较呢?因为不存在这种绑定同时生效的情景,如果同时写这两种代码会直接抛错
function Fn(){
this.name = '听风是风';
};
let obj = {
name:'行星飞行'
}
let echo = new Fn().call(obj);//报错 call is not a function
显式>隐式
//显式>隐式
let obj = {
name:'行星飞行',
fn:function () {
console.log(this.name);
}
};
obj1 = {
name:'时间跳跃'
};
obj.fn.call(obj1);// 时间跳跃
new绑定>隐式
obj = {
name: '时间跳跃',
fn: function () {
console.log(this.name)
}
};
let echo = new obj.fn(); //undefined
三. 箭头函数的this
箭头函数中的this不适用上面介绍的四种绑定规则
准确来说,箭头函数中没有this,箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁
function fn() {
return () => {
console.log(this.name);
};
}
let obj1 = {
name: '听风是风'
};
let obj2 = {
name: '时间跳跃'
};
let bar = fn.call(obj1);
bar(); //因为外层函数的this指向obj1,所以箭头函数this也指向obj1 输出“听风是风”
bar.call(obj2); //因为外层函数的this指向obj1,所以箭头函数this也指向obj1 输出“听风是风”
为啥我们第一次绑定this并返回箭头函数后,再次改变this指向没生效呢?
前面说了,箭头函数的this取决于外层作用域的this,fn函数执行时this指向了obj1,所以箭头函数的this也指向obj1。除此之外,箭头函数this还有一个特性,那就是一旦箭头函数的this绑定成功,也无法被再次修改,有点硬绑定的意思。
当然,箭头函数的this也不是真的无法修改,我们知道箭头函数的this就像作用域继承一样从上层作用域找,因此我们可以修改外层函数this指向达到间接修改箭头函数this的目的。
function fn() {
return () => {
console.log(this.name);
};
};
let obj1 = {
name: '听风是风'
};
let obj2 = {
name: '时间跳跃'
};
fn.call(obj1)(); // 因为外层函数的this指向obj1,所以箭头函数this也指向obj1 输出“听风是风”
fn.call(obj2)(); // 因为外层函数的this指向obj2,所以箭头函数this也指向obj2 输出“时间跳跃”
this真题演练
真题一
var name = 'window'
var obj1 = {
name: '听风是风',
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: '行星飞行'
};
obj1.fn1();//听风是风
obj1.fn1.call(obj2);//行星飞行
obj1.fn2();//window
obj1.fn2.call(obj2);//window
obj1.fn3()();//window
obj1.fn3().call(obj2);//行星飞行
obj1.fn3.call(obj2)();//window
obj1.fn4()();//听风是风
obj1.fn4().call(obj2);// 听风是风
obj1.fn4.call(obj2)();// 行星飞行
第一个输出听风是风,fn1调用前有一个obj1,this为隐式绑定指向obj1,因此读取到obj1的name属性
第二个输出行星飞行,显式绑定优先级高于隐式绑定,所以此时的this指向obj2,读取了obj2的name属性
第三个输出window,箭头函数并没有自己的this,它的this指向由上层执行上下文中的this决定,那为什么上层执行上下文是window呢?
- JavaScript中的上下文分为全局执行上下文,函数执行上下文,而不管是全局上下文或函数上下文的创建都会确认this指向。也就是说,this属于上下文中的一部分,很明显对象obj1并不是一个函数,它并没有权利创建自己的上下文,所以没有自己的this,那么它的外层是谁呢?当然是全局window啦,所以这里的this指向window
第四个输出window,箭头函数的this由外部环境决定,且一旦绑定无法通过call,apply或者bind再次改变箭头函数的this,所以这里虽然使用了call方法但依旧无法修改,所以this还是指向window。
第五个输出window,obj1.fn3()()其实可以改写成这样:
var fn = obj1.fn3();
fn();
//先执行了fn3方法,返回了一个闭包fn,而fn执行时本质上等同于window.fn(),属于this默认绑定,所以this指向全局对象。
第六个输出行星飞行,同样是先执行fn3返回一个闭包,但闭包执行时使用了call方法修改了this,此时指向obj2,这行代码等同于:
var fn = obj1.fn3();
fn.call(obj2);//显式绑定
第七个输出window,obj1.fn3.call(obj2)()修改一下其实是这样,fn被调用时本质上还是被window调用:
var fn = obj1.fn3.call(obj2);
window.fn();//默认绑定
第八个输出听风是风,fn4同样是返回一个闭包,只是这个闭包是一个箭头函数,所以箭头函数的this参考fn4的this即可,很明显此次调用fn4的this指向obj1。
第九个输出听风是风,改写代码其实是这样,显式绑定依旧无法改变箭头函数this
var fn = obj1.fn4();
fn.call(obj2);//显式绑定依旧无法改变this
第十个输出行星飞行,虽然无法直接改变箭头函数的this,但可以通过修改上层上下文的this达到间接修改箭头函数this的目的:
var fn = obj1.fn4.call(obj2);//fn4的this此时指向obj2
window.fn();//隐式绑定无法改变箭头函数this,this与fn4一样
真题二
/*非严格模式*/
var name = 'window'
function Person(name) {
this.name = name;
this.fn1 = function () {
console.log(this.name);
};
this.fn2 = () => console.log(this.name);
this.fn3 = function () {
return function () {
console.log(this.name)
};
};
this.fn4 = function () {
return () => console.log(this.name);
};
};
var obj1 = new Person('听风是风');
var obj2 = new Person('行星飞行');
obj1.fn1(); //听风是风
obj1.fn1.call(obj2); //行星飞行
obj1.fn2(); //听风是风
obj1.fn2.call(obj2); //听风是风
obj1.fn3()(); //window
obj1.fn3().call(obj2); //行星飞行
obj1.fn3.call(obj2)(); //window
obj1.fn4()(); //听风是风
obj1.fn4().call(obj2); //听风是风
obj1.fn4.call(obj2)(); //行星飞行
第一个输出听风是风,与第一题一样,这里同样是隐式绑定,this指向new出来的对象obj1
第二个输出行星飞行,显式绑定,this指向obj2
第三个你是不是觉得是window,很遗憾,这里的箭头函数指向了obj1,输出听风是风。
第四个输出听风是风,我们改写代码其实是这样
var arrowFn = obj1.fn2;//箭头函数this指向obj1
arrowFn.call(obj2);//箭头函数this无法直接改变
第五个输出window,与题一相同,返回闭包本质上被window调用,this被修改。
第六个输出行星飞行,返回闭包后利用call方法显式绑定指向obj2。
第七个输出window,返回闭包还是被window调用
第八个输出听风是风,返回闭包是箭头函数,this同样会指向obj1,虽然返回后也是window调用,但箭头函数无法被直接修改,还是指向obj1。
第九个输出听风是风,箭头函数无法被直接修改
第十个输出行星飞行,箭头函数可通过修改外层作用域this指向从而达到间接修改的目的