JavaScript中this指向详解

关于this,首先我们要知道this究竟是什么,用官方语言说:

面向对象语言中 this 表示当前对象的一个引用。
但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。

确切地说,this是当前环境执行期上下文对象的一个属性,不同的环境下,不同作用下,表现是不同的,用一句话概括this的指向的话,就是:this的指向,是在调用函数时根据执行上下文动态确定的

默认绑定规则

在全局作用下,this===window,在全局作用域下,window.a ===this.a,此时相当于this的指向为window

在匿名函数里,this也指向window

(注意,在严格模式下,全局作用时this绑定的是undefined)

在函数中,this指向window

`   console.log(window === this)
	//true
	//函数的独立调用
    function test() {
        console.log(this === window)
    }
	//等价于window.test()
    test();
	//true	

隐式绑定规则

对象调用时

谁调用就指向谁
`
每一个函数执行,都会有一个自身的this指向。函数执行,才会产生自身的this指向,this指向才有意义。因此每一个函数的this指向都不相同,但可能相等。每个函数都有独立的this。

`  
  let obj = {
        a: 2,
        foo: function() {
// 指向obj对象
            console.log(this);
//指向window
            function test() {
                console.log(this)
            }
		//window调用,独立调用一定指向window
            test();
		//立即调用,依然是window调用,指向window
          (function() {
                console.log(this)
            })()

          function test() {
                console.log(this)
            }
            // 产生闭包
            return test;
        }
    }
	//obj调用
    obj.foo();
	obj.foo()()//闭包执行
	`

闭包:当函数执行的时候导致函数被定义,并抛出

闭包ruturn一个函数,在window下调用 (obj.foo()==test)

但是,一个按钮绑定的回调函数,如果不闭包this指向window,如果发生了闭包,那么this指向调用它的按钮

在隐式调用时还会有一些嵌套调用的情况,这时this的指向是最后调用它的对象

隐式绑定的例外情况

给出了一个对象,但没有在对象中执行(隐式丢失)

隐式调用的this指向与调用有关,与定义无关

`   
 let a = 1;

        function foo() {
            console.log(this)
        }
        let obj = {
                a: 2,
                foo: foo
            }
            //bar持有foo的引用
        let bar = obj.foo;
        //指向window,它在赋值后再执行,赋值后与原对象无关,是独立调用
        bar();`

用函数作为参数


```javascript
`       function foo() {
        console.log(this);
    }
    // 在编译阶段,实参被赋值为形参(值的浅拷贝)或者说fn获取到foo的引用,所以fn的执行方式就是fn的执行方式
    // 父函数是有能力决定子函数的this指向的
    function bar(fn) {
        new fn();
        fn(obj);
        fn.bind(obj)();//指向this
    }
    let obj = {
            a: 2,
            foo: foo
        }
        //obj.foo是一个持有函数的引用,最终指向一个函数
    bar(obj.foo)`

## 显式绑定
有些高阶函数的this指向是通过api中的参数指明的。比如foreach第二个函数就可以指明函数的this指向;没有则默认指向window;sort,setIntenval默认的指向也是window,可以通过参数更改

可以通过apply,call,bind绑定来更改this指向

```javascript
function foo(a, b, c) {
        console.log(this);
        console.log(a, b, c)
    }
    let obj = {
        a: 2,
        foo: foo
    }
    obj.foo(1, 2, 3);
    let bar = obj.foo;
    // bar(); //指向window
    bar.call(obj, 1, 2, 3); //直接传参
    bar.call(1, 2, 3); //this指向包装类Number
    bar.apply(obj, [1, 2, 3]); //用数组收集参数
    bar.apply(null, [1, 2, 3]); //传null则指向window(绑定失败,采用默认window)
    bar.bind(obj)(1, 2, 3); //bind返回一个函数,在函数中传参

new绑定

使用了new,this就指向了实例化对象,这就是我们在vue中可以直接用this来调用data中值的原因,我们使用的vue就是一个实例化的对象

` 
     function Person() {
            this.a = 1;
            // return 1 //输出实例化对象
            // return的值为引用值,就会改变当前的this指向
            return {}   //输出空
        }
        //这时this是实例化后的对象
        var person = new Person();
        console.log(person)`
new绑定的优先级大于显式绑定,显式绑定的优先级大于隐式绑定

        // function foo(b) {
        //     this.a = b;
        // }
        // var obj1 = {}
        // var bar = foo.bind(obj1)
        // bar(2);
        // console.log(obj1.a) //2
        // var baz = new bar(3)
        //     // 此时bar是bind返回的一个新的函数
        // console.log(obj1.a) //2,证明之前的obj1没有改变,new的优先级高,此时bind不执行
        // console.log(baz.a) //3 证明new时this指向了构造函数的实例baz,
        //     // 只要有new,this一定指向实例对象,本质上实例对象就是构造函数返回的this

这里还有一些关于new的补充,在我们用new调用构造函数时,具体做了以下操作

1.创建一个新的对象

2、将构造函数的this指向这个新对象

3.为这个对象添加属性或方法

4、返回这个新对象

关于构造函数的this,我们还要注意构造函数返回的东西,如果返回一个对象,那么this指向这个返回的对象;如果返回的不是对象,那么this指向实例


```javascript
`    function Foo() {
        this.user = 'Mike'
        const o = {
            user: 'SAM'
        }
        return o;
    }
    const instance = new Foo()
    console.log(instance.user)  //SAM`

## 箭头函数

**箭头函数的指向取决于父环境中的this指向**

当我们需要在内层函数中调用外层函数的this,我们可以将this存起来,或者显式改变指向


```javascript
   function foo() {
        var that = this;
        console.log(this)

        function test() {
            console.log(this)
            console.log(that)
        }
        test.call(obj) //三个this全部指向obj
        test.call(foo) //第二个this会指向foo()
    }

    var obj = {
        a: 1,
        foo: foo
    }
    obj.foo()

但我们这时也可以用箭头函数来改变this指向,让他们指向外层,箭头函数内部没有this指向,箭头函数的this取它父级的this

`     function foo() {
        console.log(this)

        let test = () => {
            console.log(this)
        }
        te`

这时两个this都指向对象obj

箭头函数本质上没有this,完全使用父级this

   function foo() {
        console.log(this)

        let test = () => {
            console.log(this)
        }
        return test
    }

    var obj = {
        a: 1,
        foo: foo
    }
    obj.foo()()
        //此时第一个this由于隐式调用所以指向obj,第二个this的this与第一个相同,所以也指向obj`

在箭头函数中,显示绑定和隐式绑定都无法改变this的指向

` 
      function foo() {
            console.log(this)

            let test = () => {
                console.log(this)
            }
            return test
        }

        var obj = {
            a: 1,
            foo: foo
        }
        var obj2 = {
                a: 2,
                foo: foo
            }
            //当使用箭头函数时,显式绑定规则无效,this仍然跟父元素一致
        var bar = foo().call(obj2) //这时两次打印都是window`
在对象中使用箭头函数时,由于对象没有this,会继续往上找,最终this指向window

    `    var obj = {
            a: 1,
            foo: () => {
                console.log(this)
            }
        }
     
        obj.foo()  //打印出window

`

箭头函数不允许作为构造函数使用。

最后看一波题

`   
 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(); //window    在父作用域(window)找this
    obj1.fn2.call(obj2); //window   箭头函数的调用规则不适用

    obj1.fn3()(); //window      自调用指向window
    obj1.fn3().call(obj2); //2      子调用,call生效
    obj1.fn3.call(obj2)(); //window     父作用域的this指向obj2,子函数仍是自调用

    obj1.fn4()(); //1    箭头函数不存在this,找到父作用域fn4,fn4对象调用,指向obj1  
    obj1.fn4().call(obj2); //1      箭头函数不适用于call,仍然指向obj1
    obj1.fn4.call(obj2)(); //2      fn4先绑定到obj2,因此指向obj2`
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值