普通函数和箭头函数this 指向的区别?如何改变this指向?

150 篇文章 2 订阅
31 篇文章 0 订阅

目录

一、普通函数的this指向

1、声明式 --- 指向的是Window 

2、匿名函数 / 赋值式 --- 指向的是Window

3、定义在对象中的函数 --- 指向的是对象

4、绑定的事件处理函数 --- 指向的是绑定事件处理函数的标签

5、立即执行的函数表达式 --- 指向的是Window

6、闭包 -- 指向的是Window

7、函数赋值(隐式丢失)-- 指向的是Window

8、参数赋值的情况 -- 指向的是Window

9、当函数是参数的时候,也就是作为父函数的回调函数时,函数中 this 指向是由api接口文档中指明的 -- 由api接口文档指明

10、构造函数中的this -- 指向的是实例化之后的对象

11、普通函数 this 指向的示例 

二、普通函数的this指向总结(四种绑定规则)

三、普通函数this指向优先值问题

四、箭头函数的this指向

1、箭头函数中,this 的指向是父级程序的this指向。 

2、默认绑定规则(独立调用的方式)对箭头函数无效

3、隐式绑定规则(对象调用的方式)对箭头函数无效

4、显示绑定规则(call/apply/bind方式)对箭头函数无效

5、new不能实例箭头函数

6、总结

7、示例 

五、如何改变this指向?


一、普通函数的this指向

1、声明式 --- 指向的是Window 

属于独立调用,所以指向Window

function fn1() {
    console.log(this); // Window{}
}
fn1();

2、匿名函数 / 赋值式 --- 指向的是Window

属于独立调用,所以指向Window

const fn2 = function() {
    console.log(this); // Window{}
}

fn2();

3、定义在对象中的函数 --- 指向的是对象

被obj触发函数调用,所以指向obj对象 

const obj = {
    fn3: function() {
        console.log(this);
    }
}
obj.fn3();

4、绑定的事件处理函数 --- 指向的是绑定事件处理函数的标签

监听的是oDiv的点击事件,所以指向oDiv

const oDiv = document.querySelector('div');
// oDiv.onclick = function(){
//     console.log(this);
// }

oDiv.addEventListener('click' , function(){
    console.log(this);
})

5、立即执行的函数表达式 --- 指向的是Window

在全局作用域内声明的函数,对象内部方法中的匿名函数和内部函数的this具有默认值,该值指向Window对象。

属于独立调用,所以指向Window

   (function (){
     console.log(this);
   })(); // 打印 "window" 对象

// 或者:

   (function (){
     console.log(this);
   }()); // 打印 "window" 对象

6、闭包 -- 指向的是Window

先执行 fn() , 这时等于 function test() {} ,最后实际是独立调用test方法,所以指向Window

        function fn() {
            function test() {
                console.log(this) // Window
            }
            return test
        }
        fn()()

7、函数赋值(隐式丢失)-- 指向的是Window

bar 其实是等于 function foo(){} ,最后实际是独立调用foo方法,所以指向Window

        function foo() {
            console.log(this) // Window
        }
        var obj = {
            foo: foo
        }
        var bar = obj.foo
        bar()

8、参数赋值的情况 -- 指向的是Window

在预编译的过程中,实参被赋值为形参。是个值拷贝的过程,属于浅拷贝。

可以把实参fn看成赋值成为了 function foo() {} , 最后实际是独立调用foo方法,所以指向Window

        function foo() {
            console.log(this) // Window
        }
        function bar(fn) {
            fn()
        }
        var obj = {
            foo: foo
        }
        bar(obj.foo)

9、当函数是参数的时候,也就是作为父函数的回调函数时,函数中 this 指向是由api接口文档中指明的 -- 由api接口文档指明

        var obj = {}
        let arr = [1, 2, 3]
        arr.forEach(function() {
            console.log(this) // Window
        })
        arr.forEach(function() {
            console.log(this) // obj对象
        }, obj)

10、构造函数中的this -- 指向的是实例化之后的对象

        function Person() {
            this.do = function(e) {
                console.log(e)
            }
            console.log(this) // 指向实例化之后的对象
        }
        var teacher = new Person()
        teacher.do('教书')
        var student = new Person()
        student.do('学习')

 当构造函数 return 引用值的时候,会改变this的指向。所以一般不在构造函数中 return

        function Person() {
            this.do = function(e) {
                console.log(e)
            }
            this.b = '111'
            console.log(this) // 指向实例化之后的对象
            return {
                a: 1
            } // return的值为引用值的时候,会改变this的指向
        }
        var teacher = new Person()
        console.log(teacher) // { a: 1 }
        console.log(teacher.b) // undefined

11、普通函数 this 指向的示例 

示例1: 

const obj = {
    fn1() {
        console.log(this)
        function getName() {
            console.log(this)
        }
        getName()
    },
    name: '小草莓',
    fn2(callback) {
        console.log(this)
        callback()
    }
}

obj.fn1() // 打印obj对象  打印 Window对象
obj.fn2(function() {
    console.log(this) //  打印obj对象 打印 Window对象
})

 示例2: 

var a = 11
function fn() {
    console.log(this)
    this.a = 22;
    let b = function() {
        console.log(this)
        console.log(this.a);
    };
    b();
}
var x = new fn();

结果: 第一个this打印:

第二个this打印Window对象;this.a 打印 11;

解析: 因为普通函数中的this指向的是调用它的对象,如果没有直接调用对象,会指向undefined或者Window,一般都会指向Window。在严格模式下才会指向undefined。

上述的例子中,并没有明确的调用对象,而普通函数中的this并不会向上继续找对象,所以直接返回Window。

二、普通函数的this指向总结(四种绑定规则)

① 默认绑定:

在全局中声明的变量和函数(默认指向Window);

函数独立调用时(声明式函数、匿名函数 / 赋值式的方式、立即执行函数、闭包)(都是指向Window);

② 隐式绑定:

对象调用(也就是谁调用就是指向谁,所以就是指向调用这个函数的对象)

(存在隐式丢失的问题:函数赋值和参数赋值的情况); 

绑定的事件处理函数(指向的是绑定事件处理函数的标签);

③ 显式绑定:

call / apply / bind (指向第一个参数)

文章: 如何改变this指向?_小草莓蹦蹦跳的博客-CSDN博客

④ new绑定:

构造函数中的this指向实例化出来的对象

三、普通函数this指向优先值问题

毋庸置疑,默认绑定的优先级肯定是最低的其次,比较下【显式绑定】和【隐式绑定】哪个优先级更高?

        function foo(b) {
            console.log(this.a)
        }
        var obj1 = {
            a: 'obj1',
            foo: foo
        }
        var obj2 = {
            a: 'obj2',
            foo: foo
        }
        obj1.foo() // obj1
        obj2.foo() // obj2

        obj1.foo.call(obj2) // obj2
        obj2.foo.call(obj1) // obj1

由上面程序输入可以看出, 显式绑定的优先级高于隐式绑定

接着,就是比较一下【new绑定】和【显示绑定】哪个优先级更高?

        function foo(b) {
            this.a = b
        }
        var obj = {}
        var bar = foo.bind(obj)
        bar(2)

        // 通过显式绑定更改了foo的this指向,使其指向了obj
        // 而在foo函数中,this.a赋值为2,所以obj.a就等于2
        console.log(obj.a) // 2 

        var barZ = new bar(3)

        // obj.a已经赋值了,后续没有被改,所以依旧是2
        console.log(obj.a) // 2

        // 通过new绑定后,this指向了实例化后的对象,也就是barZ
        // 而在foo函数中,this.a赋值为3,所以barZ.a就等于3
        console.log(barZ.a) // 3

根据上面的程序输入可以看出,new绑定的优先级高于显式绑定

在实际开发中,几乎不会用到这种写法。

所以,new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

四、箭头函数的this指向

1、箭头函数中,this 的指向是父级程序的this指向。 

箭头函数内部是没有this指向的,箭头函数的this指向父级作用域的this;如果没有,则this指向的就是Window

        function foo() {
            console.log(this) // obj对象
            // 情况一
            // function test() {
            //     console.log(this) // Window
            // }
            // test()

            // 情况二
            // function test() {
            //     console.log(this) // obj对象
            // }
            // test.call(this)

            // 情况三
            var test = () => {
                console.log(this) // obj对象
            }
            test()
        }
        var obj = {
            a: 1,
            foo: foo
        }
        obj.foo()
    const obj = {
      sayThis: () => {
        console.log(this);
      }
    };
    // 因为JavaScript没有块作用域,所以在定义sayThis的时候,里面的this就绑到window上去了
    obj.sayThis(); // window 
    const globalSay = obj.sayThis;
    globalSay(); // window 浏览器中的global对象
const liEle = document.querySelectorAll('li');
liEle.forEach((item, key) => {
    // 箭头函数的this指向的是父级程序
    // forEach()的this指向window
    console.log('打印1', this) // Window

    item.addEventListener('click', () => {
        console.log('打印2', this) // Window
    })
})

虽然箭头函数的this能够在编译的时候就确定了this的指向,但也需要注意一些潜在的坑!!

绑定事件监听:

可以看到,我们其实是想要this为点击的 button,但此时this指向了Window~

const button = document.getElementById('btn');
button.addEventListener('click', () => {
    console.log(this) // Window
})

 在原型上添加方法:

此时this指向Window~

function Cat(title) {
  this.title = title
}
Cat.prototype.sayName = () => {
  console.log(this) // Window
  return this.title
}
const cat = new Cat('我是标题啊');
console.log(cat.sayName()) // undefined

2、默认绑定规则(独立调用的方式)对箭头函数无效

        function foo() {
            console.log(this) // obj对象
            var test = () => {
                console.log(this) // obj对象
            }
            return test
        }
        var obj = {
            a: 1,
            foo: foo
        }
        obj.foo()()

3、隐式绑定规则(对象调用的方式)对箭头函数无效

        function foo1() {
            console.log(this) // obj对象
        }
        var foo2 = () => {
            console.log(this) // Window
        }
        var obj = {
            a: 1,
            foo1: foo1,
            foo2: foo2
        }
        obj.foo1()
        obj.foo2()

4、显示绑定规则(call/apply/bind方式)对箭头函数无效

        function foo1() {
            console.log(this) // obj2对象
        }
        var foo2 = () => {
            console.log(this) // Window
        }
        var obj = {
            a: 1,
            foo1: foo1,
            foo2: foo2
        }
        var obj2 = {
            a: 2
        }
        obj.foo1.call(obj2)
        obj.foo2.call(obj2)
        function foo() {
            console.log(this) // Window
            var test = () => {
                console.log(this) // Window
            }
            return test
        }
        var obj = {
            a: 1,
            foo: foo
        }
        var obj2 = {
            a: 2,
            foo: foo
        }
        foo().call(obj)

5、使用箭头函数应注意什么?

① 使用了箭头函数,this就不是指向window,而是父级(指向是可变的)

不能够使用arguments对象

不能用作构造函数这就是说不能够使用new命令,否则会抛出一个错误

      var Foo = () => {
        console.log(this);
      };
      var a = new Foo(); // 报错Foo is not a constructor

④ 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数 

6、总结

所有绑定规则,在箭头函数中都不适用。

箭头函数本身不存在this指向,this取决于父环境中的this指向

7、示例 

示例1:

const obj = {
    // 普通函数:this指向调用它的对象
    fn1: function() {
        console.log(this) // {fn1: ƒ, fn2: ƒ, fn3: ƒ}
    },

    // 箭头函数:this指向是父级程序的this指向
    // 父级程序是obj对象,但只有函数有this,obj对象没有this
    // 父级程序没有this,指向的是window
    fn2: () => {
        console.log(this) // Window对象
    }, 

    // fn3是一个普通函数:this指向的是obj对象
    fn3: function() {
        // fn4是一个箭头函数:this指向的是父级程序的this指向
        // 父级程序是fn3,fn3的this指向的是obj对象,所以fn4箭头函数的this也是指向obj对象
        const fn4 = () => {
            console.log(this) // {fn1: ƒ, fn2: ƒ, fn3: ƒ}
        }
        fn4()
    }
}
obj.fn1()
obj.fn2()
obj.fn3()

 示例2:

var x = 11;
var obj = {
    x: 22,
    y: this,
    say: () => {
        console.log(this.x);
    }
}
obj.say();
console.log(obj.y);

 结果: 先输出11,后输出window对象

解析:obj 对象中的 this 指向的就是 window,也就是全局环境。所以obj.y打印的是window对象;因为箭头函数中的 this 指向父级程序的指向。从以上例子可以看出来,父级obj指向了window,所以this.x打印的是11

示例3: 

var a = 11;
function fn() {
  this.a = 22;
  let b = () => {
    console.log(this.a)
  }
  b();
}
var x = new fn();

解析:箭头函数中会往上寻找this,直到找到所代表的this为止。例子中,构造函数被实例化成为一个对象x,那x中的this指代的就是对象x本身,所以箭头函数this就代表x对象,x对象内部的a值为22,所以输出22。

结果: 输出22 

示例4:

  

结果:

1                    2 

Window         Window

Window         2                 Window

1                   1                  2

示例5:

      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();
      getName();
      Foo().getName();
      getName();

      new Foo.getName();
      new Foo().getName();
      new new Foo().getName();

解析:

预编译中声明提升:function getName(){} 属于函数声明式,提升到最前面。

所以,全局的getName被替换成为function(){ alert(4) }

执行完Foo()后,函数返回了this,this指向window。所以Foo().getName()相当于window.getName(),而函数中的getName是全局的,执行函数的时候,替换掉了之前的输出为4的getName,当前就是输出1

结果:

 2        4        1        1

 2        3        3

五、如何改变this指向?

如何改变this指向?_小草莓蹦蹦跳的博客-CSDN博客

六、总结 

如何改变this指向?_小草莓蹦蹦跳的博客-CSDN博客

  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值