js高级-浅谈this指向

1. JavaScript 高级-this指向

在这里插入图片描述

1.1 this的绑定规则

function foo() {
  console.log('foo函数:', this)
}
// 1.方式一:直接调用/默认调用
foo()
// 2.方式二:通过对象调起
let obj = {
  age: 18,
}

obj.aaa = foo
obj.aaa()

采用不同的调用方式,this产生了不同的效果?

  1. 函数在调用时,JavaScript会默认给this绑定一个值
  2. this的绑定和定义的位置(编写的位置)没有关系
  3. this的绑定和调用方式以及调用的位置有关系
  4. this是在运行时被绑定的

this的绑定规则:默认绑定;隐式绑定;显示绑定;new绑定

  1. 默认绑定。只要是函数独立的直接调用,this就指向window(形式再复杂,也只看是不是独立函数调用)。严格模式下,独立调用的函数中的this指向的是undefined。

    案例一

    function foo(func) {
      func()
    }
    let obj = {
      name: 'why',
      bar: function () {
        console.log(this)
      },
    }
    // obj.bar作为参数传递到foo函数中,此时foo函数内部调用func(),func()是普通函数直接调用
    foo(obj.bar)  // window
    obj.bar()		// obj对象
    

    案例二

    function test1() {
      console.log(this)
      test2()
    }
    function test2() {
      console.log(this)
      test3()
    }
    function test3() {
      console.log(this)
    }
    test1() // 打印了3个window
    
  2. 隐式绑定。通过某个对象进行调用,是由某个对象发起的函数调用。

    function foo() {
      console.log(this)
    }
    let obj = {
      name: 'jiaqian',
    }
    obj.aaa = foo
    obj.aaa()
    

    案例二

    function foo() {
      console.log(this)
    }
    let obj1 = {
      name: 'jiaqian',
      foo: foo,
    }
    let obj2 = {
      obj1: obj1,
    }
    obj2.obj1.foo()  // 打印obj1,因为foo是通过obj1对象来调用的
    

    案例三

    function foo() {
      console.log(this)
    }
    let obj1 = {
      name: 'jiaqian',
      foo: foo,
    }
    let bar = obj1.foo
    bar() // window
    
  3. 显式绑定。显示出this绑定哪个对象。

    隐式绑定有一个前提条件: 必须在调用的对象内部有一个对函数的引用(比如一个属性);如果没有这样的引用,在进行调用时,会报找不到该函数的错误; 正是通过这个引用,间接的将this绑定到了这个对象上;

    如果此不需要在对象内部包含这个函数的引用,同时在这个对象上进行强制调用

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

    • 第一个参数是相同的,要求传入一个对象,给this准备的,在调用这个函数时,会将this绑定到传入的对象上

    • 后面的参数,apply是数组,call为参数列表

      func.apply(thisArg,[argsArray])

      function.call(thisArg,arg1,arg2,...)

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

      理解:函数不是经常要传参嘛,之前直接直接调用直接传参,现在的apply和call方法进行调用,此时传参的方式不同是apply和call方法的区别所在

      function foo(name, age, height) {
        console.log(this)
        console.log('foo函数打印', name, age, height)
      }
      foo('kobe', 18, 1.99)
      
      let obj = {}
      // apply第一个参数:绑定this
      // 第二个参数:传入额外的实参,以数组的形式
      foo.apply(obj, ['kobe', 18, 1.99])
      
      // call第一个参数:绑定this
      // 第二个参数:参数列表,后续的参数以多参数的形式,会作为实参
      foo.call(obj, 'james', 35, 2.11)
      
  4. new绑定:JavaScript中的函数可以当做一个类的构造函数来使用

    1. 使用new关键字来调用函数首先会创建一个新的对象
    2. 这个新对象会被执行prototype连接
    3. 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
    4. 执行函数的内部代码(函数体代码)
    5. 如果函数没有返回其他对象,表达式会返回这个新对象

1.2 apply/call/bind

// bind
function foo() {
  console.log('fooL:', this)
}

var obj = {
  age: 18,
}
//  之前:通过apply方法将foo绑定在obj上调用
//  需求:调用foo时,总是绑定到obj对象身上,而且obj对象上没有函数
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
//  如果只是这样绑定在obj上,每次都用apply代码重复
var bar = foo.bind(obj)
bar()  //  因为此时foo的this已经指向obj,即便写成默认绑定(存在优先级),this指向的对象仍为obj
console.log(typeof bar)

如何让一个函数总是显示的绑定到一个对象上?使用bind方法,bind() 方法创建一个新的绑定函数(bound function,BF),绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语)。在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

内置函数的调用的this绑定:内置函数(第三方库比如Vue),根据一些经验

1.3 this绑定优先级

  1. 默认规则的优先级最低
  2. 显式绑定优先级高于隐式绑定
  3. new绑定优先级高于隐式绑定
  4. new绑定优先级高于bind: new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高;new绑定可以和bind一起使用,new绑定优先级更高;bind优先级比apply/call高
// function foo() {
//   console.log('foo:', this)
// }
// let obj = {
//   foo: foo,
// }
// 比较优先级
// 1.显示绑定的优先级高于隐式绑定
// obj.foo.apply('abc')
// 2.bind绑定高于隐式绑定
// foo.bind(obj)
// obj.foo()
// 3.new绑定优先级高于隐式绑定
// var obj = {
//   name: 'Why',
//   foo: function () {
//     console.log('foo:', this)
//   },
// }
// obj.foo()
// new obj.foo()

// 4.1new不可以和apply/call一起使用
// 4.2new优先级高于bind
// function Foo() {
//   console.log('Foo:', this)
// }
// let Func1 = Foo.bind('abc')
// new Func1()
// 5.bind优先级高于apply/call
function Foo1() {
  console.log('Foo1', this)
}
Foo1.bind('aaa').call('bbb')

1.4 绑定之外的情况

  1. 忽略显式绑定:如果在显式绑定中,传入一个null或者undefined,显式绑定会被忽略,使用默认规则

    function foo() {
      console.log('foo', this)
    }
    foo.apply('abc')
    foo.apply(null) // window
    foo.apply(undefined) // window
    
  2. 间接函数引用:创建一个函数的间接引用,这种情况使用默认绑定规则

    function foo() {
      console.log(this)
    }
    let obj1 = {
      name: 'obj1',
      foo: foo,
    }
    let obj2 = {
      name: 'obj2',
    }
    
    ;(obj2.foo = obj1.foo)()
    

1.5 箭头函数的使用

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

  • 箭头函数不会绑定this、arguments属性
  • 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)
let names = ['kobe', 'jiangping', 'jiangfei']
names.forEach((item, index, arr) => {
  console.log(item, index, arr)
})
setTimeout(() => {
  console.log('setTimeout')
}, 3000)

箭头函数的编写优化

  • 如果只有一个参数()可以省略

  • 如果函数执行体中只有一行代码, 那么可以省略大括号

    names.forEach((item, index, arr) => console.log(item, index, arr))
    

    并且这行代码的返回值会作为整个函数的返回值

    let foo1 = item => item * 2
    console.log(foo1(2))
    
  • 如果函数执行体只有返回一个对象, 那么需要给这个对象加上(),此举是为了对象的{}和箭头函数的{}产生混淆设置的规则

    let foo1 = () => ({ name: 'jiangfei' })
    console.log(foo1())
    
// 箭头函数实现nums所有偶数平方的和
var nums = [20, 30, 11, 15, 111]
let result = nums
	.filter(item => item % 2 === 0)
	.map(item => item * item)
	.reduce((preValue, item) => preValue + item, 0)
console.log(result)

this规则之外 – ES6箭头函数:箭头函数不绑定this,而是根据外层作用域来决定this。箭头函数的{ }算是作用域,但是其自身的作用域里面没有this。箭头函数的this是在定义时绑定的,而不是在调用时绑定的。

let bar = () => {
  console.log('bar', this)
}
// 即使通过apply调用,也是没有this
bar.apply('aaaa') // bar window
//  this的查找规则
let obj = {
  name: 'obj',
  foo: function () {
    let bar = () => {
      console.log('bar', this)
    }
    return bar
  },
}
let foo1 = obj.foo()
foo1.apply('aaa') // bar object

箭头函数的应用

模拟网络请求

// 网络请求的工具函数
function request(url, callbackFn) {
  let results = ['abc', 'kobe', 'james']
  callbackFn(results)
}
// 实际操作的位置(业务)
let obj = {
  name: [],
  network: function () {
    request('/names', function (res) {
      this.names.concat(res)
    })
  },
}
obj.network()

上述案例中:由于在这里使用的函数是普通函数不是箭头函数,普通的函数里面绑定了this,这个this的指向取决于如何调用,工具函数中的回调函数的调用方式为默认调用,所以函数network在回调调用时this指向了window而不是我们希望的obj

早期没有箭头函数时:引入一个变量去改变this的指向

// 网络请求的工具函数
function request(url, callbackFn) {
  var results = ['abc', 'kobe', 'james']
  callbackFn(results)
}
// 实际操作的位置(业务)
var obj = {
  names: [],
  network: function () {
    var _this = this
    request('/names', function (res) {
      _this.names = _this.names.concat(res)
    })
  },
}
obj.network()
console.log(obj.names) // ['abc', 'kobe', 'james']

有了箭头函数后,因为箭头函数不绑定this,而是根据外层作用域找this,这里外层作用域就是network里,network函数中通过对象调用,所以network的this指向对象,所以箭头函数的this指向对象

function request(url, callbackFn) {
  var results = ['abc', 'kobe', 'james']
  callbackFn(results)
}
// 实际操作的位置(业务)
var obj = {
  names: [],
  network: function () {
    request('/names', res => {
      this.names = this.names.concat(res)
    })
  },
}
obj.network()
console.log(obj.names)

1.6 this面试题分析

面试题一

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,同上,括号不会改变this的绑定,它们仅用于控制表达式的优先级
  ;(b = person.sayName)(); // window,一个函数的间接引用
}
sayName()

面试题二

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 箭头函数的this看外层作用域的this,即全局对象
person1.foo2.call(person2) // window 同上,箭头函数自身不绑定this,调用call方法也没办法将this绑定到传入的对象上

person1.foo3()() // window person1.foo3()返回一个函数,最后面的()表示默认调用了该函数
/* window 首先person1.foo3.call(person2)改变了this指向,this指向了person2
       ,foo3方法执行后返回了一个匿名函数,此时直接调用了这个返回的匿名函数 */
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2 foo3方法返回了一个匿名函数,该匿名函数经过call方法this指向了person2

person1.foo4()() // person1 foo4返回一个箭头函数,箭头函数的this绑定到foo4的this,即person1
person1.foo4.call(person2)() // person2 foo4方法的this被call改变为person2,返回的箭头函数的this绑定到foo4的this,即person2
person1.foo4().call(person2) // person1 先调用foo4返回一个箭头函数,箭头函数的this绑定到foo4的this,即person1,call无法改变箭头函数的this。

面试题三

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调用
person1.foo1.call(person2) // person2

person1.foo2() // person1 箭头函数foo2的this在Person构造函数执行时被绑定到了新创建的对象person1
person1.foo2.call(person2) // person1

person1.foo3()() // window
person1.foo3.call(person2)() // window person1.foo3.call(person2)返回的普通函数直接调用
person1.foo3().call(person2) // person2

person1.foo4()() // person1 
person1.foo4.call(person2)() // person2 foo4的this被call方法绑定到person2,返回的箭头函数的this绑定到foo4的this
person1.foo4().call(person2) // person1

面试题四

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
  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉闷昂志、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值