JavaScript高级 this指向问题

73 阅读7分钟

1. this 在不同情况下的绑定指向

公共情况下定义一个函数, 来测试它不同情况下的 this 指向问题

	// 定义函数
    function foo(name) {
      console.log("foo函数:", this)
    }

1. 普通情况下被调用时

    // 1.方式一: 直接调用
    foo() // foo函数: Window

2. 以对象的方法调用时

	// 定义函数
    function foo(name) {
      console.log("foo函数:", this)
    }

    var obj = { name: "why" }
    obj.aaa = foo
    
    obj.aaa() // foo函数: {name: 'why', aaa: ƒ}

3. 通过 call 调用时

    // 定义函数
    function foo(name) {
      console.log("foo函数:", this)
    }

    foo.call("x") // foo函数: String

4. 获得的启示

  1. this的绑定和定义的位置(编写的位置)没有关系, 但是this的绑定和调用方式以及调用的位置有关系

  2. this是在运行时被绑定的,函数在调用时,JavaScript会默认给this绑定一个值

2. 绑定规则

1. 默认绑定

独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用

    // 定义函数
    // 1.普通的函数被独立的调用
    function foo() {
      console.log("foo:", this)
    }
    foo() // window


    // 2.函数定义在对象中, 但是独立调用
    var obj = {
      name: "why",
      bar: function() {
        console.log("bar:", this)
      }
    }

    var baz = obj.bar
    baz() // window


    // 3.高阶函数
    function test(fn) {
      fn()
    }

    test(obj.bar) // window

    // 4.严格模式下, 独立调用的函数中的this指向的是undefined

2. 隐式绑定

常见的调用方式是通过某个对象进行调用的,也就是它的调用位置中,是通过某个对象发起的函数调用

隐式绑定有一个前提条件:必须在调用的对象内部有一个对函数的引用,正是通过这个引用,间接的将this绑定到了这个对象上

	// 隐式绑定
    function foo() {
      console.log("foo函数:", this)
    }

    var obj = {
      bar: foo
    }

    obj.bar() // foo函数: Object

3. new 绑定

	/*
      1.创建新的空对象
      2.将this指向这个空对象
      3.执行函数体中的代码
      4.没有显示返回非空对象时, 默认返回这个对象
    */
    function foo() {
      this.name = "why"
      console.log("foo函数:", this)
    }

    new foo() // foo函数: foo

4. 显示绑定

我们明确的绑定了this指向的对象,所以称之为显式绑定

JavaScript所有的函数都可以使用call和apply方法

    // 显式绑定
    var obj = {
      name: "why"
    }

    function foo() {
      console.log("foo函数:", this)
    }

    // 执行函数, 并且函数中的this指向obj对象
    // obj.foo = foo
    // obj.foo()

    // 执行函数, 并且强制this就是obj对象
    foo.call(obj)
    
    foo.call(123)
    foo.call("abc")

5. 内置函数

主要是考经验

    // 内置函数(第三方库): 根据一些经验
    // 1.定时器
    setTimeout(function() {
      console.log("定时器函数:", this) // 定时器函数: Window
    }, 1000)

    // 2.按钮的点击监听
    var btnEl = document.querySelector("button")
    btnEl.onclick = function() {
      console.log("btn的点击:", this) // btn的点击: <button>​按钮​</button>​
    }

    btnEl.addEventListener("click", function() {
      console.log("btn的点击:", this) // // btn的点击: <button>​按钮​</button>​
    })

    // // 3.forEach
    var names = ["abc", "cba", "nba"]

    names.forEach(function(item) {
      console.log("forEach:", this) // forEach: Window 
    })

    names.forEach(function(item) {
      console.log("forEach:", this) // forEach: String
    }, "aaaa")

3. apply / call / bind

通过call或者apply绑定this对,显示绑定后,this就会明确的指向绑定的对象

1. apply

    function foo(name, age, height) {
      console.log("foo函数被调用:", this)
      console.log("打印参数:", name, age, height)
    }

    // ()调用
    foo("why", 18, 1.88)

    // apply
    // 第一个参数: 绑定this
    // 第二个参数: 传入额外的实参, 以数组的形式
    foo.apply("apply", ["kobe", 30, 1.98])

2. call

    function foo(name, age, height) {
      console.log("foo函数被调用:", this)
      console.log("打印参数:", name, age, height)
    }

    // ()调用
    foo("why", 18, 1.88)

	// call
    // 第一个参数: 绑定this
    // 参数列表: 后续的参数以多参数的形式传递, 会作为实参
    foo.call("call", "james", 25, 2.05)

3. bind

bind() 方法创建一个新的绑定函数,在bind() 被调用时,这个新函数的this 被指定为bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

    function foo(name, age, height, address) {
      console.log("foo:", this)
      console.log("参数:", name, age, height, address)
    }

    var obj = { name: "why" }

    // 需求: 调用foo时, 总是绑定到obj对象身上(不希望obj对象身上有函数)
    // 1.bind函数的基本使用
    var bar = foo.bind(obj)
    bar() // this -> obj

    // 2.bind函数的其他参数(了解)
    var bar = foo.bind(obj, "kobe", 18, 1.88)
    bar("address")

4. this 绑定优先级

  1. 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this

  2. 显示绑定优先级高于隐式绑定

  3. new绑定优先级高于隐式绑定

  4. new绑定可以和bind一起使用,new绑定优先级更高

  5. new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高

    function foo() {
      console.log("foo:", this)
    }

    // 比较优先级:

    // 1.显式绑定绑定的优先级高于隐式绑定
    // 1.1.测试一:apply call bind高于默认绑定
    var obj = { foo: foo }
    obj.foo.apply("abc")
    obj.foo.call("abc")

    // 1.2.测试二:bind高于默认绑定
    var bar = foo.bind("aaa")
    var obj = {
      name: "why",
      baz: bar
    }
    obj.baz()


    // 2.new绑定优先级高于隐式绑定
    var obj = {
      name: "why",
      foo: function() {
        console.log("foo:", this) // obj{}
        console.log("foo:", this === obj) // false
      }
    }
    new obj.foo()


    // 3.new/显式
    // 3.1. new不可以和apply/call一起使用

    // 3.2. new优先级高于bind
    function foo() {
      console.log("foo:", this)
    }
    var bindFn = foo.bind("aaa")
    new bindFn()


    // 4.bind/apply优先级
    // bind优先级高于apply/call
    function foo() {
      console.log("foo:", this)
    }
    var bindFn = foo.bind("aaa")
    bindFn.call("bbb")

bind > new > call/apply > 隐式绑定 > 显式绑定

5. 绑定之外的情况

    // 1.情况一: 显式绑定null/undefined, 那么使用的规则是默认绑定
    function foo() {
      console.log("foo:", this)
    }

    foo.apply("abc") // foo: String
    foo.apply(null) // foo: Window
    foo.apply(undefined) // foo: Window


    // 2.情况二: 间接函数引用
    var obj1 = {
      name: "obj1",
      foo: function() {
        console.log("foo:", this) 
      }
    }
    var obj2 = {
      name: "obj2"
    };
    // ;是必须加的

    (obj2.foo = obj1.foo)() // foo: Window
    // (obj2.foo = obj1.foo) 返回的是 obj2.foo
    // 垃圾代码 恶心人面试题 别写

6. 箭头函数 this 的使用

箭头函数是ES6之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁

箭头函数不会绑定this、arguments属性,因为没有原型,箭头函数也不能作为构造函数来使用,

箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this

    // 1.普通函数中是有this的标识符
    function foo() {
      console.log("foo", this)
    }

    foo()
    foo.apply("aaa") // this => aaa

    // 2.箭头函数中, 压根没有this
    var bar = () => {
      console.log("bar:", this)  
    }
    bar() // bar: window

    // 通过apply调用时, 也是没有this
    bar.apply("aaaa") // 全局this: window

    console.log("全局this:", this) // 全局this: window

    // 3.this的查找规则
    var obj = {
      name: "obj",
      foo: function() {
        var bar = () => {
          console.log("bar:", this)
        }
        return bar
      }
    }
    var fn = obj.foo()
    fn.apply("bbb") // bar: {name: 'obj', foo: ƒ}

箭头函数 this 的使用 案例

    // 网络请求的工具函数
    function request(url, callbackFn) {
      var results = ["abc", "cba", "nba"]
      callbackFn(results)
    }

    // 实际操作的位置(业务)
    var obj = {
      names: [],
      network: function() {
        // 1.早期的时候
        // var _this = this
        // request("/names", function(res) {
        //   _this.names = [].concat(res)
        // })

        // 2.箭头函数写法
        request("/names", (res) => {
          this.names = [].concat(res)
        })
      }
    }

    obj.network()
    console.log(obj)

7. this 面试题

1. 面试题 1

	var name = "window";
	
	var person = {
	  name: "person",
	  sayName: function () {
	    console.log(this.name);
	  }
	};
	
	function sayName() {
	  var sss = person.sayName;
	
	  sss(); // 绑定: 默认绑定, window -> window
	
	  person.sayName(); // 绑定: 隐式绑定, person -> person
	
	  (person.sayName)(); // 绑定: 隐式绑定, person -> person
	
	  (b = person.sayName)(); // 术语: 间接函数引用, window -> window
	}
	
	sayName();

1. 面试题 2

    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 () {
        // console.log(this) // 第一个表达式this -> person1
        // console.log(this) // 第二个表达式this -> person2
        // console.log(this) // 第三个表达式this -> person1
        
        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

1. 面试题 3

    var name = 'window'

    /*
      1.创建一个空的对象
      2.将这个空的对象赋值给this
      3.执行函数体中代码
      4.将这个新的对象默认返回
    */
    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)
        }
      }
    }

    // person1/person都是对象(实例instance)
    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(隐式绑定)

1. 面试题 4

    var name = 'window'

    /*
      1.创建一个空的对象
      2.将这个空的对象赋值给this
      3.执行函数体中代码
      4.将这个新的对象默认返回
    */
    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(隐式绑定)