JS函数的this指向

211 阅读4分钟

学习了coderwhy的JavaScript高级语法视频课的笔记

如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了

一、为什么需要this

  1. 在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:
  • 常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中;
  • 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象;
  • 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。
  1. 我们来看一下编写一个obj的对象,有this和没有this的区别
<script>
        // 没有使用this
        var obj = {
            val: 'obj的对象',
            f: function () {
                console.log(obj.val + "执行了")
            }
        }
        obj.f()
        
        // 使用了this
        var obj2 = {
            val2: 'obj2的对象',
            f2: function () {
                console.log(this.val2 + "执行了")
            }
        }
        obj2.f2()
</script>

二、this指向

  1. this在全局作用域下指向的就是window。但是,开发中很少直接在全局作用于下去使用this,通常都是在函数中使用
  • 所有的函数在被调用时,都会创建一个执行上下文
  • 这个上下文中记录着函数的调用栈、AO对象等
  • this也是其中的一条记录
  1. 接下来,我们认识一下this指向的四个绑定规则,搞懂了也就基本掌握了this的指向问题:
  • 绑定规则一:默认绑定,也就是独立函数调用
<script>
        // 案例一
        function foo () {
            console.log(this)//window
        }
        foo()

        // 案例二
        function test1 () {
            console.log(this)//window
        }
        function test2 () {
            console.log(this)//window
            test1()
        }
        function test3 () {
            console.log(this)//window
            test2()
        }
        test3()

        //案例三
        function foo3 (func) {
            func()
        }
        var obj = {
            name: 'obj',
            bar: function () {
                console.log(this)//window
            }
        }
        foo3(obj.bar)
    </script>
  • 绑定规则二:隐式绑定,也就是它的调用位置中,是通过某个对象发起的函数调用
<script>
        // 案例一
        function foo () {
            console.log(this)//obj{}
        }
        obj = {
            name: 'obj',
            foo: foo
        }
        obj.foo()

        // 案例二
        function foo2 () {
            console.log(this)//obj2{}
        }
        obj2 = {
            name: 'obj2',
            foo2: foo2
        }
        obj3 = {
            name: 'obj3 ',
            obj2: obj2
        }
        obj3.obj2.foo2()

        //案例三(独立函数调用)
        function foo3 (func) {
            console.log(this)//window
        }
        var obj4 = {
            name: 'obj4',
            foo3: foo3
        }
        var bar = obj4.foo3
        bar()//独立函数调用
</script>
  • 绑定规则三:显示绑定,也就是通过call()、apply()和bind()绑定this对象。 这里就不详细讲这三个方法的具体用法了。
<script>
        // 案例一:call()
        function foo () {
            console.log(this)
            // this的指向:
            // window
            // { name: '一个obj' }
            // String {'abc'}
        }
        foo.call(window)
        foo.call({ name: '一个obj' })
        foo.call('abc')
        
        // 案例二:apply()
        function foo2 () {
            console.log(this)
            // this的指向:
            // window
            // { name: '一个obj' }
            // String {'abc'}
        }
        foo2.apply(window)
        foo2.apply({ name: '一个obj' })
        foo2.apply('abc')
        
        // 案例三:bind()——总是显示绑定到一个对象上
        function foo3 () {
            console.log(this)//{name: 'obj', foo3: ƒ}
        }
        var obj = {
            name: 'obj',
            foo3: foo3
        }
        var bar = foo3.bind(obj)
        bar()
</script>
  • 绑定规则四:new绑定。JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字
    • 使用new关键字来调用函数是,会执行如下的操作:
      • 1.创建一个全新的对象;
      • 2.这个新对象会被执行prototype连接;
      • 3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
      • 4.如果函数没有返回其他对象,表达式会返回这个新对象;
<script>
        function Person (name) {
            console.log(this)//Person {}
            this.name = name
        }
        var p = new Person('aaa')
        console.log(p)//Person {name: 'aaa'}
</script>
  1. 内置函数的绑定 有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。这些内置函数会要求我们传入另外一个函数,我们自己并不会显示的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行,这些函数中的this又是如何绑定的呢?
  • 案例:setTimeout、数组的forEach、div的点击
<script>
        // 案例一
        setTimeout(function () {
            console.log(this)//window
        }, 1000)
        
        // 案列二
        var arr = ['a', 'b', 'c', 'd', 'e']
        var obj = {
            name: 'obj'
        }
        arr.forEach(function () {
            console.log(this)//五次obj对象
        }, obj)
        
        //案例三
        var box = document.querySelector('.box')
        box.onclick = function () {
            console.log(this === box)//this就是指向box
        }
</script>
  1. this绑定规则的优先级总结
  • 默认规则(函数独立调用)的优先级最低
  • 显示绑定优先级高于隐式绑定
  • new绑定优先级高于隐式绑定
  • new绑定优先级高于bind
    • new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
    • new绑定可以和bind一起使用,new绑定优先级更高
  1. this绑定规则之外
  • 忽略显示绑定:如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则(函数独立调用)
  • 间接函数引用:创建一个函数的 间接引用,这种情况使用默认规则(函数独立调用)
<script>
        function foo () {
            console.log(this)
        }

        var obj1 = {
            name: 'obj1',
            foo: foo
        }
        var obj2 = {
            name: 'obj2'
        }

        obj1.foo()//obj1
        ; (obj2.foo = obj1.foo)()//window
</script>
  • ES6箭头函数:箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this

三、面试题

1.

<script>
        var name = "window";
        var person = {
            name: "person",
            sayName: function () {
                console.log(this.name);
            }
        }
        function sayName () {
            var sss = person.sayName;
            sss(); // window
            person.sayName(); // person
            (person.sayName)(); // person
            (b = person.sayName)(); // window
        }
        sayName()
</script>

2.

  • 注意:
    • var person1={...}不是块级作用域
    • 箭头函数没有this,向上父级作用域找(父级作用域看函数定义位置,而不是调用所在位置)
<script>
        var name = 'window'
        var person1 = {
            name: 'person1',
            foo1: function () {
                console.log(this.name)
            },
            
            foo2: () => console.log(this.name),
            
            foo3: function () {
                return function () {
                    console.log(this.name)
                }
            },
            
            foo4: function () {
                return () => {
                    console.log(this.name)
                }
            }
        }

        var person2 = { name: 'person2' }

        person1.foo1(); // person1
        person1.foo1.call(person2); // person2

        person1.foo2() // window
        person1.foo2.call(person2) // window

        person1.foo3()() // window
        person1.foo3.call(person2)() // window
        person1.foo3().call(person2) // person2

        person1.foo4()()// person1
        person1.foo4.call(person2)() // person2
        person1.foo4().call(person2) // person1
</script>

3.

  • 注意:
    • 箭头函数没有this,向上父级作用域找(父级作用域看函数定义位置,而不是调用所在位置)
<script>
        var name = 'window'
        function Person (name) {
            this.name = name
            
            this.foo1 = function () {
                console.log(this.name)
            }
            
            this.foo2 = () => console.log(this.name)
            
            this.foo3 = function () {
                return function () {
                    console.log(this.name)
                }
            }
            
            this.foo4 = function () {
                return () => {
                    console.log(this.name)
                }
            }
        }

        var person1 = new Person('person1')
        var person2 = new Person('person2')

        person1.foo1() // person1
        person1.foo1.call(person2) // person2

        person1.foo2() // person1
        person1.foo2.call(person2) //person1 

        person1.foo3()() // window
        person1.foo3.call(person2)() // window
        person1.foo3().call(person2) // person2

        person1.foo4()() // person1
        person1.foo4.call(person2)() // person2
        person1.foo4().call(person2) // person1
</script>

4.

  • 注意:
    • 箭头函数没有this,向上父级作用域找(父级作用域看函数定义位置,而不是调用所在位置)
<script>
        var name = 'window'
        function Person (name) {
            this.name = name
            
            this.obj = {
                name: 'obj',
                foo1: function () {
                    return function () {
                        console.log(this.name)
                    }
                },
                
                foo2: function () {
                    return () => {
                        console.log(this.name)
                    }
                }
            }
        }

        var person1 = new Person('person1')
        var person2 = new Person('person2')

        person1.obj.foo1()() // window
        person1.obj.foo1.call(person2)() //window 
        person1.obj.foo1().call(person2) // person2

        person1.obj.foo2()() // obj
        person1.obj.foo2.call(person2)()//person2
        person1.obj.foo2().call(person2)//obj
</script>