js this

125 阅读5分钟

zhuanlan.zhihu.com/p/82504422

默认:指向window;隐式:指向调用方法的对象;显式:call, apply, bind;new一个对象的过程;箭头函数

  • this绑定

    1. 默认绑定与隐式绑定

      • 默认:this指向window

      • 隐式:this指向调用方法的对象

      • 例子

        function foo() { 
            console.log(this.bar); 
        } 
        var bar = "bar1"; 
        var o2 = {bar: "bar2", foo: foo}; 
        var o3 = {bar: "bar3", foo: foo}; 
        foo();            // "bar1" – 默认绑定
        o2.foo();          // "bar2" – 隐式绑定
        o3.foo();          // "bar3" – 隐式绑定
        

        foo()这种调用方法,就是默认绑定。如果在非严格模式下,this就是全局对象,浏览器当中就是window。而如果在严格模式(use strict)下,this就会是undefined。

        之所以这是默认绑定,因为foo的调用不属于任何人,前面没有任何限定条件。这是最简单的绑定。

        o2.foo()和o3.foo()这两种调用方法,都是隐式绑定。Foo是作为o2和o3的方法而调用的,那么谁调用foo,this就指向谁。在上面的例子中,o2.foo()中的this指向o2,因此this.bar就是o2当中的bar: “bar2”;同理,o3.foo()打印出来的就是o3中的”bar3”。

      • 例子

        function a() {
        	function b() {
        		console.log(this)
        		function c() {
        			"use strict";
        			console.log(this)
        		}
        		c()
        	}
        	b()
        }
        a()
        // window
        // undefined
        // 默认绑定
        
      • 例子

        var name = 1
        function special() {
        	console.log(this.name)
        }
        var girl = {
        	name: 2,
        	detial: function() {
        		console.log(this.name)
        	},
        	woman: {
        		name: 3,
        		detail: function() {
        			console.log(this.name)
        		}
        	},
        	special
        }
        girl.detail()
        girl.woman.detail()
        girl.special()
        // 2  // 隐式
        // 3  // 隐式
        // 2  // 隐式,只是赋值给special
        
      • 例子

        var name = 1
        function a() {
        	var name = 2
        	console.log(this.name)
        }
        function d(i) {
        	return i()
        }
        var b = {
        	name: 3,
        	detail: function(){
        		console.log(this.name)
        	},
        	bibi: function() { // 闭包
        		return function() {
        			console.log(this.name)
        		}
        	}
        }
        var c = b.detail
        b.a = a
        var e = b.bibi()
        a() // 1     默认绑定,所以this指向window
        c() // 1     默认绑定
        b.a() // 3   隐式绑定,指向b
        d(b.detail) // 1  
        e() // 1  
        
    2. 显式绑定(硬绑定)

      如果foo是通过call、apply或者bind调用的,那么这种调用就是显式绑定。这种绑定中,this的指向就是这三个函数中传递的第一个参数。

      • call的用法:A.call(B,x,y)

        1. 改变函数A的this指向,使之指向B
        2. 把A函数放到B中运行(指向B),x和y是A函数的参数
      • apply:传参数是以数组的形式

      • bind:和call一样,但是返回的是函数,所以要()

      • 例子

        function foo() { 
        	console.log(this.bar); 
        } 
        var bar = "bar1"; 
        var obj = {bar: "bar2"}; 
        
        foo();          // "bar1"   默认绑定
        foo.call(obj);     // "bar2"  显式绑定,使用obj作为"this"
        
    3. 关键字new绑定(构造函数绑定)

      如果把new这个关键字放在一个函数调用的前面,JS编译器会做这四件事情:

      1. 创建一个新的空的对象

      2. this指向这个新对象,继承该函数的原型

      3. 属性和方法加到this对象中

      4. 如果这个函数不返回任何东西,那么就会默认return this

      • 例子

        function foo() { 
            this.baz = "baz"; 
            console.log(this.bar + " " + baz); 
        } 
        var bar = "bar"; 
        var baz = new foo(); // undefined undefined  baz不会自动往下找
        // 这里baz是undefined是因为,变量的提升和赋值不是同时进行的
        // 先执行new foo,才执行了var baz = 赋值
        
    4. 箭头函数

      • 箭头函数会无视以上所有的规则,this的值就是函数创建时候所在的lexical scope中的this

      • 例子

        function Person(){
          this.age = 0;
          setTimeout(function () {
            console.log(this.age);     // 输出undefined
          }, 1000);
        }
        var p = new Person();
        function Person(){
          this.age = 10;
          setTimeout(()=> {
            console.log(this.age);     // 输出10
          }, 1000);
        }
        var p = new Person();
        
      • 箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定

      • 箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正式因为它没有this,所以也就不能作构造函数,不能写一个箭头函数,然后new这个函数

      • 箭头函数中的this是在定义函数的时候绑定

        • 例子

          var x = 11
          var obj = {
          	x: 22,
          	say: () => {
          		console.log(this.x) // 11
          	}
          }
          obj.say()
          
          • 所谓的定义时候绑定,就是this是继承自父执行上下文中的this,比如这里的箭头函数中的this.x,箭头函数本身与say平级义key: value的形式,也就是箭头函数本身所在的对象为obj,而obj的父执行上下文就是window,因此这里的this.x实际上表示的是window.x,因此输出是11

          • 关于为什么这个箭头函数的平级是obj

            var name = 'xi'
                  var age = 17
                  var obj = {
                    name: 'xx',
                    objAge: this.age,
                    myFun: function () {
                      console.log(this.name + this.age)
                    }
                  }
                  console.log(obj.objAge)   // 输出是17,因为平级的this指向obj 
                  obj.myFun() // 输出是xx undefined
            
        • 例子

          var obj = {
          	birth: 1990,
          	age: function() {
          		var b = this.birth
          		var fn = () => new Date.getFullYear() - this.birth // this指向obj
          		return fn()
          	}
          }
          obj.getAge() // 31
          
          • 例子中箭头函数本身是在getAge方法中定义的,因此,getAge方法的父执行上下文是obj,因此这里的this指向则为obj对象
        • 例子

          var a = 1
                var obj = {
                  a: 2,
                  fun: () => {
                    var obj = {
                      a: 3,
                      fun: () => {
                        console.log(this.a)
                      }
                    }
                    obj.fun()
                  }
                }
                obj.fun() // 1
          
  • 用call方法考虑调用对象

    1. 在函数中直接使用

      • 例子

        function get(content){
        	console.log(content)
        }
        get('你好')
        // 是下面的简写
        get.call(window, '你好')
        
    2. 函数作为对象的方法被调用(谁调用我,我就指向谁)

      • 例子

        var person = {
        	name: '张三',
        	run: function(time) {
        		console.log(`${this.name}在跑步 最多${time}min就不行了`)
        	}
        }
        person.run(30)
        person.run.call(person, 30)
        
    • 例子

      // 问题
      var name = 222
            var a = {
              name: 111,
              say: function () {
                console.log(this.name)
              }
            }
            var fun = a.say
            fun()
            a.say()
            var b = {
              name: 333,
              say: function (fun) {
                fun()
              }
            }
            b.say(a.say)
            b.say = a.say
            b.say()
      
      // 答案
      var name = 222
            var a = {
              name: 111,
              say: function () {
                console.log(this.name)
              }
            }
            var fun = a.say
            fun() // 函数的直接使用,是fun.call(window)的语法糖,那么this指向window,222
            a.say() // a.say.call(a),指向a,111
            var b = {
              name: 333,
              say: function (fun) {
                fun() // fun.call(window),指向的是window,222
              }
            }
            b.say(a.say) // 不管,直接看fun
            b.say = a.say
            b.say() // 指向的是b,333
      
      var name = 222
            var a = {
              name: 111,
              say: function () {
                console.log(this.name)
              }
            }
            var b = {
              name: 333,
              say: function (fun) {
                fun()  // 因为fun.call(window)  // 另一种说法:函数里的函数指向window
              }
            }
            b.say(a.say) // 222
      
      var c = {
      	name: 444,
      	say: function() {
      		function test() {
      			console.log(this.name)
      		}
      		test() // 因为test.call(window)
      	}
      }
      c.say() // 222