IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    JavaScript - this、call、apply、bind

    发表于 2022-07-10 00:00:00
    love 0

    this

    与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

    在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。

    this 的值取决于它出现的上下文:函数、类或全局。

    全局作用域
    • 直接调用一个函数,就是默认绑定,this 在非严格模式下指向的为全局对象 window
    function fn() {
      console.log(this) // window
    }
    fn()
    
    const obj = {
      fn: () => {
        console.log(this) // window
      },
      fn1() {
        return function () {
          console.log(this) // window
        }
      },
      fn2() {
        console.log(this) // window
      },
    }
    
    obj.fn()
    obj.fn1()()
    const Fn = obj.fn2
    Fn()
    
    函数作用域
    • 函数 fn 作为一个对象的方法来调用时,this 指向这个对象
    const obj = {
      fn() {
        console.log(this) // obj
      },
    }
    obj.fn()
    
    • 使用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
    function fn() {
      console.log(this) // fn
    }
    
    let obj = new fn()
    
    function fn() {}
    fn.prototype = {
      func: function () {
        console.log(this) // fn
      },
    }
    
    let FN = new fn()
    FN.func()
    
    const obj = {
      name: 'j',
    }
    
    FN.func.call(obj) // obj
    FN.func.apply(obj) // obj
    
    // 在使用 call,apply,bind 改变 this,这个优先级仅次于 new
    
    箭头函数
    • 箭头函数中的 this 是词法作用域,它继承自外部作用域中的 this 。这意味着箭头函数的 this 和定义它的上下文中的 this 是一样的
    const obj = {
      name: 'Alice',
      greet() {
        const arrowFunction = () => {
          console.log(this.name)
        }
        arrowFunction()
      },
    }
    
    obj.greet() // 输出 "Alice"
    

    call

    Function.prototype.call(thisArg, arg1, arg2, ...)

    • call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

    • 该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

    使用

    function method(a, b) {
      console.log(this, a, b)
    }
    
    method.myCall(null, 1, 2)
    

    具体实现

    • 不传入第一个参数,那么默认为 window
    • 改变了 this 指向,让新的对象可以执行该函数。那么思路可以变成给新的对象添加一个函数,然后在执行完以后删除
    Function.prototype.myCall = function (context, ...args) {
      context = context === null || context === undefined ? globalThis : Object(context)
    
      // 使用Symbol确保不会出现命名冲突
      let key = Symbol('_key')
      // 使用defineProperty创建一个属性存放 this
      Object.defineProperty(context, key, { value: this })
    
      // 运行方法
      let result = context[key](...args)
    
      // 删除方法
      delete context[key]
      return result
    }
    

    apply

    Function.prototype.apply(thisArg, [argsArray])

    • apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。

    使用

    function method(a, b) {
      console.log(this, a, b)
    }
    method.apply({}, [2, 3])
    

    具体实现

    • apply 的实现和 call 类似
    Function.prototype.myApply = function (context) {
      context = context === null || context === undefined ? globalThis : Object(context)
    
      // 使用Symbol确保不会出现命名冲突
      let key = Symbol('_key')
      // 使用defineProperty创建一个属性存放 this
      Object.defineProperty(context, key, { value: this })
    
      // 需要判断是否存储第二个参数
      // 如果存在,就将第二个参数展开
      let result = arguments[1] ? context[key](...arguments[1]) : context[key]()
    
      // 删除方法
      delete context[key]
      return result
    }
    

    bind

    Function.prototype.bind(thisArg, arg1, arg2, ...)

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

    使用

    function method(a, b) {
      console.log(this, a, b)
    }
    
    let newMethod = method.bind({}) //this指向改变为obj2
    newMethod(3, 4)
    

    具体实现

    • bind 和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind 实现柯里化。
    Function.prototype.myBind = function (context) {
      if (typeof this !== 'function') {
        throw new TypeError('Error')
      }
      let _this = this
      let args = [...arguments].slice(1)
      // 返回一个函数
      return function F() {
        // 因为返回了一个函数,我们可以 new F(),所以需要判断
        if (this instanceof F) {
          return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
      }
    }
    


沪ICP备19023445号-2号
友情链接