[js基础]this指向和Call、Bind、Apply原理Review

274 阅读4分钟

简要回顾一下,温故而知新

this指向和Call、Bind、Apply

调用方式示例函数中的this指向
通过new调用new method()新对象
直接调用Method()全局对象globalThis
通过对象调用obj.method前面的对象
Call,apply,bindMethod.call(ctx)第一个参数

手写new

使用new关键字创建对象的过程涉及以下步骤:

  1. 创建一个新的空对象。
  2. 将这个空对象的原型(__proto__)指向构造函数(即被new调用的函数)的原型对象。
  3. 将构造函数的作用域(this)绑定到新创建的对象上,以便在构造函数中可以使用this关键字引用新对象。
  4. 执行构造函数内部的代码,对新对象进行初始化操作。
  5. 如果构造函数没有显式返回其他对象,则返回这个新对象;否则返回构造函数内显式返回的对象。

这些步骤使得通过new关键字创建的对象能够继承构造函数的属性和方法,并且可以通过原型链访问到构造函数原型对象上定义的属性和方法。

使用

function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.printName = function () {
  console.log('this: ', this)
  console.log('this.name: ', this.name)
}

const sxc = new Person('sxc', 23)
console.log('sxc: ', sxc)
sxc.printName()

console.log('-------------------------')

// 函数返回对象的情况
function Person1(name, age) {
  this.name = name
  this.age = age

  return {
    name: '固定名字',
    length: '18cm',
    printName: function () {
      console.log('this: ', this)
      console.log('this.name: ', this.name)
    }
  }
}

const sxc01 = new Person1('sxc', 23)
console.log('sxc01: ', sxc01)
sxc01.printName()

// 打印
sxc:  Person { name: 'sxc', age: 23 }
this:  Person { name: 'sxc', age: 23 }
this.name:  sxc
-------------------------
sxc01:  { name: '固定名字', length: '18cm', printName: [Function: printName] }
this:  { name: '固定名字', length: '18cm', printName: [Function: printName] }
this.name:  固定名字

实现myNew

function myNew(constructor, ...allArgs) {
  // 创建一个新的空对象
  const obj = {}

  // 将这个空对象的原型(__proto__)指向构造函数(即被new调用的函数)的原型对象
  Object.setPrototypeOf(obj, constructor.prototype)

  // 将构造函数的作用域(this)绑定到新创建的对象上,以便在构造函数中可以使用this关键字引用新对象
  const res = constructor.apply(obj, allArgs)

  // 如果构造函数没有显式返回其他对象,则返回这个新对象;否则返回构造函数内显式返回的对象
  return Object.prototype.toString.call(res) === '[object Object]' ? res : obj
}


function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.printName = function () {
  console.log('this: ', this)
  console.log('this.name: ', this.name)
}

const sxc = new Person('sxc', 23)
console.log('sxc: ', sxc)
sxc.printName()

console.log('-------------------------')

// 函数返回对象的情况
function Person1(name, age) {
  this.name = name
  this.age = age

  return {
    name: '固定名字',
    length: '18cm',
    printName: function () {
      console.log('this: ', this)
      console.log('this.name: ', this.name)
    }
  }
}

const sxc01 = new Person1('sxc', 23)
console.log('sxc01: ', sxc01)
sxc01.printName()

console.log('-------------------------')

function myNew(constructor, ...allArgs) {
  // 创建一个新的空对象
  const obj = {}

  // 将这个空对象的原型(__proto__)指向构造函数(即被new调用的函数)的原型对象
  Object.setPrototypeOf(obj, constructor.prototype)

  // 将构造函数的作用域(this)绑定到新创建的对象上,以便在构造函数中可以使用this关键字引用新对象
  const res = constructor.apply(obj, allArgs)

  // 如果构造函数没有显式返回其他对象,则返回这个新对象;否则返回构造函数内显式返回的对象
  return Object.prototype.toString.call(res) === '[object Object]' ? res : obj
}

const sxc03 = myNew(Person, 'sxc03', 23)
console.log('sxc03: ', sxc03)
sxc03.printName()

console.log('-------------------------')


const sxc04 = myNew(Person1, 'sxc04', 23)
console.log('sxc04: ', sxc04)
sxc04.printName()

console.log('-------------------------')

// 打印
sxc:  Person { name: 'sxc', age: 23 }
this:  Person { name: 'sxc', age: 23 }
this.name:  sxc
-------------------------
sxc01:  { name: '固定名字', length: '18cm', printName: [Function: printName] }
this:  { name: '固定名字', length: '18cm', printName: [Function: printName] }
this.name:  固定名字
-------------------------
sxc03:  Person { name: 'sxc03', age: 23 }
this:  Person { name: 'sxc03', age: 23 }
this.name:  sxc03
-------------------------
sxc04:  { name: '固定名字', length: '18cm', printName: [Function: printName] }
this:  { name: '固定名字', length: '18cm', printName: [Function: printName] }
this.name:  固定名字
-------------------------

手写Bind

Function.prototype.myBind = function (ctx, ...boundArgs) {

  const fn = this

  // 特殊处理
  const resFunc = function () {
    const restArgs = [...arguments]
    const totalArgs = boundArgs.concat(restArgs)

    if(Object.getPrototypeOf(this) === resFunc.prototype){
      // 当前是使用new的方式调用的这个函数
      return new fn(...totalArgs)

    } else {
      // 其他情况
      return fn.apply(ctx, totalArgs)
    }
  }

  return resFunc
}

常规使用

function func(a, b, c, d) {
  console.log(this)
  console.log(a, b, c, d)
}

const boundFunc1 = func.bind({ a: 1 }, 1, 2)(3, 4)
const boundFunc2 = new (func.bind({ b: 1 }, 11, 12))(13, 14)
const boundFunc3 = func.myBind({ c: 1 }, 21, 22)(23, 24)
const boundFunc4 = new (func.bind({ d: 1 }, 31, 32))(33, 34)

// 打印结果
{ a: 1 }
1 2 3 4
func {}
11 12 13 14
{ c: 1 }
21 22 23 24
func {}
31 32 33 34

手写Call

Function.prototype.myCall = function (ctx,...args) {
  ctx = ctx === undefined || ctx === null ? globalThis : ctx

  // 待执行的函数
  const fn = this 

  const fnKey = Symbol('fn')
  
  Object.defineProperty(ctx, fnKey, {
    enumerable: false,
    value: fn,
  })

  const fnRes = ctx[fnKey](...args)
  delete ctx[fnKey]
  return fnRes
}


效果

const a = () => {
  return 1
}
console.log(Object.prototype.toString.call(a) === '[object Function]')

function sayHello(info) {
  console.log('Hello, ' + this.name + '!' + (info || ''))
}

const person1 = { name: 'Alice' }
const person2 = { name: 'Bob' }

// 使用 call 方法调用 sayHello,并指定不同的 this 值
sayHello.call(person1) // 输出: Hello, Alice
sayHello.call(person2) // 输出: Hello, Bob


console.log('-----------------------')

const person3 = { name: 'JJ' }
sayHello.myCall(person3, 123123)
console.log('person3: ', person3);
console.log(Object.prototype.toString.myCall(a) === '[object Function]')

手写Apply

Function.prototype.myApply = function (ctx, args) {
  ctx = ctx === undefined || ctx === null ? globalThis : ctx

  // 待执行的函数
  const fn = this

  const fnKey = Symbol('fn')

  Object.defineProperty(ctx, fnKey, {
    enumerable: false,
    value: fn
  })

  const fnRes = ctx[fnKey](...(args || []))
  delete ctx[fnKey]
  return fnRes
}

效果

const a = () => {
  return 1
}
console.log(Object.prototype.toString.apply(a) === '[object Function]')

function sayHello(info) {
  console.log('Hello, ' + this.name + '!' + (info || ''))
}

const person1 = { name: 'Alice' }
const person2 = { name: 'Bob' }

// 使用 call 方法调用 sayHello,并指定不同的 this 值
sayHello.apply(person1)
sayHello.apply(person2)

console.log('-----------------------')

const person3 = { name: 'JJ' }
sayHello.myApply(person3, [123123])
console.log('person3: ', person3);
console.log(Object.prototype.toString.myApply(a) === '[object Function]')