this指向总结2

2020-04-20  本文已影响0人  简单tao的简单

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
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就会隐式绑定到这个对象上

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 指向变化过程

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绑定

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呢?

第四个输出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指向从而达到间接修改的目的

上一篇下一篇

猜你喜欢

热点阅读