this 的指向与 call, apply, bind 的模拟实现

1. this 的指向问题

关于函数的 this 的指向并不是一个很复杂的问题

我们首先要明确一个定义:
fn = function () {...} 指的是 fn 这个属性存储着一个函数的地址 fn_addr, 而我们通过 fn_addr() 来执行相应地址的函数

接下来我们仍将 fn 称为 fn 函数, 只需知道它只是一个存储函数地址的变量即可

1.1 普通函数的 this 指向

对于普通函数 fn, 若

  • xx.fn(), 其中 fn 为对象 xx 上的一个属性, 则执行 fn 函数时, 其 this 指向的是 xx(这个 xx 可以是单个的对象, 也可以是由成员访问符 . 连接起来的嵌套对象: xx === obj.obj1.obj2.obj3)
    function fn() { console.log(this) }
    
    let obj = { val: 1, fn }
    
    obj.fn() //=> {val: 1, fn: ƒ}
    
  • fn(), 则
    • 在严格模式下, 其 this 指向为 undefined
    • 在非严格模式下, 其 this 指向为 window
    "use strict"
    
    function fn() { console.log(this) }
    
    fn() //=> undefined
    
    function fn() { console.log(this) }
    
    fn() //=> window
    
  • 出现 call, apply, bind, 对于 fn.call/apply(xx)fn.bind(xx)()
    • xxnullundefined
      • 则严格模式下, thiswindow
      • 非严格模式下, this 分别为 nullundefined
      "use strict"
      
      function fn() { console.log(this) }
      
      fn.call(null) //=> null
      fn.call(undefined) //=> undefined
      
      function fn() { console.log(this) }
      
      fn.call(null) //=> window
      fn.call(undefined) //=> window
      
    • xx 为引用类型, 则 this 指向为 xx
    • xx 为基础值类型
      • 严格模式下, thisxx
      • 非严格模式下, thisxx 所对应的引用值类型
      "use strict"
      
      function fn() { console.log(this) }
      
      fn.call(1) //=> 1
      
      function fn() { console.log(this) }
      
      fn.call(1) //=> Number {1}
      

1.2 箭头函数的 this 指向

箭头函数没有 this
当在箭头函数中访问 this 时, 其实还是获取的是其所在上下文中的 this

const obj = {val: 1}

const fn = () => console.log(this) // 定义时所在的上下文 this 为 window
let f

function f0() {
  console.log(this) // 输出 f0 上下文的 this
  fn()
  fn.call(obj)
}

function f1() {
  console.log(this) // 输出 f1 上下文的 this
  const fn = () => {console.log(this)} // 所在上下文 this 等到执行 f1 时才知道
  fn()
  fn.call(window) 
  f = fn
}

f0.call(obj)
//=> obj
//=> window
//=> window (该输出证明了箭头函数中的 this 不会被 call 改变)
f1.call(obj) 
//=> obj
//=> obj
//=> obj (该输出证明了箭头函数中的 this 不会被 call 改变)
f() 
//=> obj (即使将 fn 从 f1 里面提出到全局中, 其 this 仍然不变, 表示箭头函数的 this 只与其定义时所在的上下文有关, 而不是与运行时所在的上下文有关)

1.3 立即执行函数的 this 指向

  • 在严格模式下, 其 this 指向 undefined
  • 在非严格模式下, 其 this 指向 window
(function () {
  console.log(this) //=> window
})()
"use strict"
;(function () {
  console.log(this) //=> undefined
})()

1.4 回调函数的 this 指向

  • 若是绑定在 dom 元素上, 用来监听事件的非箭头函数, 其 this 为对应的 dom 元素
  • 若是当作普通的回调函数使用, 则若该回调函数非箭头函数, 其 this 与立即执行函数的 this 指向相同
function fn() {
  console.log(this)
}

[1, 2, 3].reduce(fn, 0)
//=> window
//=> window
//=> window
"use strict"
function fn() {
  console.log(this)
}

[1, 2, 3].reduce(fn, 0)
//=> undefined
//=> undefined
//=> undefined

2. call/apply 的模拟实现

2.1 call 的模拟实现

Function.prototype.call = function(obj, ...args) {
  obj = obj == null ? window : obj // 如果被改变的 this 指向 null 或者 undefined, 则 this 指向 window
  const objType = typeof obj
  obj = (objType !== "object" && objType !== "function") ? Object(obj) : obj // 如果被改变的 this 指向基础类型, 则将 this 改为指向引用类型

  obj.fn = this
  const res = obj.fn(...args)
  delete obj.fn
  return res
}

2.2 apply 的模拟实现

Function.prototype.apply = function(obj, arr) {
  obj = obj == null ? window : obj
  const objType = typeof obj
  const arrType = typeof arr
  obj = objType !== "function" && objType !== "object" ? Object(obj) : obj
  obj.fn = this

  if (arr != null && arrType !== "function" && arrType !== "object") // 若 arr 是非 null 和 undefined 的基础类型, 则报错
    new TypeError("CreateListFromArrayLike called on non-object")
  arr = Array.isArray(arr) ? arr : (arr == null ? [] : Array.from(arr)) // 若 arr 是 null 或 undefined, 则令 arr 为空数组, 若 arr 为非数组的引用类型, 则通过 Array.from 将 arr 转换为数组
  const res = obj.fn(...arr)
  delete obj.fn
  return res
}

2.3 bind 的模拟实现

Function.prototype.bind = function(obj, ...args1) {
  obj = obj == null ? window : obj
  const self = this
  const objType = typeof obj
  obj = objType !== "object" && objType !== "function" ? Object(obj) : obj

  const retFn = function(...args2) {
    if (this instanceof self)
      return self.apply(this, args1.concat(args2))
    return self.apply(obj, args1.concat(args2))
  }

  // 为 retFn.prototype 赋值, 以保证在对 bind 所返回的方法进行 new 操作时, 相应的实例对象原型仍然是调用 bind 的那个函数.prototype
  // 这里为了防止 retFn.prototype 被更改导致 new 出来的实例对象的原型被更改, 所以使用了 Object.create 方法
  retFn.prototype = Object.create(self.prototype)
  retFn.constructor = retFn

  return retFn
}
posted @ 2022-10-03 22:06  小阁下  阅读(28)  评论(0)    收藏  举报