Skip to content

Function.prototype

题目描述

手写实现 JavaScript 的核心方法,包括:

  • Function.prototype.call - 改变函数执行上下文
  • Function.prototype.apply - 类似 call,但参数为数组
  • Function.prototype.bind - 返回绑定上下文的新函数
  • instanceof 操作符 - 检查原型链关系
  • new 操作符 - 创建实例对象

本题考查 this 绑定机制原型链原理JavaScript 底层实现 的深度理解。

核心知识点

1. this 绑定机制的四种规则

javascript
// 1. 默认绑定 - 独立函数调用
function foo() {
  console.log(this)
}
foo() // 严格模式: undefined, 非严格模式: window

// 2. 隐式绑定 - 对象方法调用
const obj = {
  foo() {
    console.log(this)
  },
}
obj.foo() // this 指向 obj

// 3. 显式绑定 - call/apply/bind
foo.call(obj) // 强制 this 指向 obj

// 4. new 绑定 - 构造函数调用
function Constructor() {
  this.name = 'test'
}
new Constructor() // this 指向新创建的实例

2. 原型链关系图

javascript
function Person(name) {
  this.name = name
}
Person.prototype.sayHello = function () {
  return `Hello, ${this.name}`
}

const alice = new Person('Alice')

// 原型链关系:
// alice.__proto__ === Person.prototype
// Person.prototype.__proto__ === Object.prototype
// Object.prototype.__proto__ === null

3. 构造函数调用的四个步骤

  1. 创建一个新的空对象
  2. 将空对象的 proto 指向构造函数的 prototype
  3. 将构造函数的 this 绑定到新对象
  4. 如果构造函数返回对象,则返回该对象;否则返回新对象

4. Symbol 的妙用

javascript
// 使用 Symbol 避免属性名冲突
const uniqueKey = Symbol('temp')
obj[uniqueKey] = func
// 执行后删除,不会污染原对象
delete obj[uniqueKey]

代码实现

javascript
// 手写call
Function.prototype.Call = function (context, ...args) {
  // context为undefined或null时,则this默认指向全局window
  if (context === undefined || context === null) {
    context = window
  }
  // 利用Symbol创建一个唯一的key值,防止新增加的属性与obj中的属性名重复
  const fn = Symbol(1)
  // this指向调用call的函数
  context[fn] = this
  // 隐式绑定this,如执行obj.foo(), foo内的this指向obj
  const res = context[fn](...args)
  // 执行完以后,删除新增加的属性
  delete context[fn]
  return res
}

// apply与call相似,只有第二个参数是一个数组,
Function.prototype.Apply = function (context, args = []) {
  if (context === undefined || context === null) {
    context = window
  }
  const fn = Symbol(2)
  context[fn] = this
  const res = context[fn](...args)
  delete context[fn]
  return res
}

// bind要考虑返回的函数,作为构造函数被调用的情况
Function.prototype.Bind = function (context, ...args) {
  if (context === undefined || context === null) {
    context = window
  }
  const fn = this
  const f = Symbol(3)
  const result = function (...args1) {
    if (this instanceof fn) {
      // result如果作为构造函数被调用,this指向的是new出来的对象
      // this instanceof fn,判断new出来的对象是否为fn的实例
      this[f] = fn
      const res = this[f](...args, ...args1)
      delete this[f]
      return res
    }
    else {
      // bind返回的函数作为普通函数被调用时
      context[f] = fn
      const res = context[f](...args, ...args1)
      delete context[f]
      return res
    }
  }
  // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
  // 实现继承的方式: 使用Object.create
  result.prototype = Object.create(fn.prototype)
  result.prototype.constructor = result
  return result
}

export function instanceOf(obj, fn) {
  const proto = obj.__proto__
  if (proto) {
    if (proto === fn.prototype) {
      return true
    }
    else {
      return instanceOf(proto, fn)
    }
  }
  else {
    return false
  }
}

export function selfNew(fn, ...args) {
  // 创建一个instance对象,该对象的原型是fn.prototype
  const instance = Object.create(fn.prototype)
  // 调用构造函数,使用apply,将this指向新生成的对象
  const res = fn.apply(instance, args)
  // 如果fn函数有返回值,并且返回值是一个对象或方法,则返回该对象,否则返回新生成的instance对象
  return typeof res === 'object' || typeof res === 'function' ? res : instance
}

关键技术点

1. Symbol 避免属性冲突

javascript
// 错误方式:可能覆盖已有属性
context.fn = this

// 正确方式:使用 Symbol 确保唯一性
const fnSymbol = Symbol('temp')
context[fnSymbol] = this

2. call/apply 的核心原理

javascript
// 核心思想:利用隐式绑定改变 this
// obj.method() -> method 内的 this 指向 obj

function myCall(context, ...args) {
  context[fn] = this // 将函数作为 context 的方法
  context[fn](...args) // 调用方法,this 自动绑定到 context
  delete context[fn] // 清理临时属性
}

3. bind 的 new 调用检测

javascript
function boundFunction(...args) {
  // 检测是否通过 new 调用
  if (new.target) {
    // new 调用:忽略绑定的 this
    return new originalFunc(...outerArgs, ...args)
  }
  else {
    // 普通调用:使用绑定的 this
    return originalFunc.call(context, ...outerArgs, ...args)
  }
}

4. instanceof 的原型链遍历

javascript
function myInstanceof(obj, constructor) {
  let current = Object.getPrototypeOf(obj)
  const target = constructor.prototype

  // 沿着原型链向上查找
  while (current !== null) {
    if (current === target)
      return true
    current = Object.getPrototypeOf(current)
  }

  return false
}

使用示例

javascript
// call 使用示例
function greet(greeting, punctuation) {
  return `${greeting}, ${this.name}${punctuation}`
}

const person = { name: 'Alice' }
console.log(greet.myCall(person, 'Hello', '!')) // "Hello, Alice!"

// apply 使用示例
const numbers = [1, 5, 3, 9, 2]
console.log(Math.max.myApply(null, numbers)) // 9

// bind 使用示例
const boundGreet = greet.myBind(person, 'Hi')
console.log(boundGreet('?')) // "Hi, Alice?"

// bind 的 new 调用
function Person(name, age) {
  this.name = name
  this.age = age
}

const BoundPerson = Person.myBind(null, 'John')
const john = new BoundPerson(25)
console.log(john.name) // "John"
console.log(john.age) // 25

// instanceof 使用示例
console.log(myInstanceof(john, Person)) // true
console.log(myInstanceof(john, Object)) // true
console.log(myInstanceof(john, Array)) // false

// new 使用示例
function Car(brand, model) {
  this.brand = brand
  this.model = model
  this.start = function () {
    return `${this.brand} ${this.model} is starting`
  }
}

const myCar = myNew(Car, 'Toyota', 'Camry')
console.log(myCar.brand) // "Toyota"
console.log(myCar.start()) // "Toyota Camry is starting"

// 构造函数返回对象的情况
function SpecialConstructor() {
  this.name = 'instance'
  return { custom: 'object' } // 返回自定义对象
}

const special = myNew(SpecialConstructor)
console.log(special.custom) // "object"
console.log(special.name) // undefined

// 复杂继承示例
function Animal(name) {
  this.name = name
}

Animal.prototype.speak = function () {
  return `${this.name} makes a sound`
}

function Dog(name, breed) {
  Animal.myCall(this, name)
  this.breed = breed
}

// 设置原型链
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

Dog.prototype.bark = function () {
  return `${this.name} barks!`
}

const dog = myNew(Dog, 'Buddy', 'Golden Retriever')
console.log(dog.speak()) // "Buddy makes a sound"
console.log(dog.bark()) // "Buddy barks!"
console.log(myInstanceof(dog, Dog)) // true
console.log(myInstanceof(dog, Animal)) // true

// 边界情况测试
console.log(greet.myCall(null, 'Hello', '!')) // this指向global
console.log(greet.myCall(undefined, 'Hi', '?')) // this指向global
console.log(greet.myCall(42, 'Hey', '.')) // this指向Number包装对象

// 箭头函数与 bind(箭头函数无法绑定 this)
const arrowFunc = x => x * 2
const boundArrow = arrowFunc.myBind({ value: 10 })
console.log(boundArrow(5)) // 10(this绑定无效)

// 异常处理测试
try {
  myInstanceof(123, 'not a function')
}
catch (e) {
  console.log(e.message) // "Right-hand side of instanceof is not a function"
}

try {
  myNew('not a function')
}
catch (e) {
  console.log(e.message) // "Constructor must be a function"
}

// 性能对比测试
function performanceTest() {
  const iterations = 1000000
  const obj = { name: 'Test' }

  function testFunc() {
    return this.name
  }

  // 原生 call
  console.time('Native call')
  for (let i = 0; i < iterations; i++) {
    testFunc.call(obj)
  }
  console.timeEnd('Native call')

  // 自实现 call
  console.time('Custom call')
  for (let i = 0; i < iterations; i++) {
    testFunc.myCall(obj)
  }
  console.timeEnd('Custom call')
}

// performanceTest()

扩展思考

1. 支持异步函数的bind

javascript
Function.prototype.asyncBind = function (context, ...outerArgs) {
  const originalFunc = this

  return async function (...innerArgs) {
    if (new.target) {
      throw new TypeError('Async function cannot be called with new')
    }

    try {
      const result = await originalFunc.myCall(
        context,
        ...outerArgs,
        ...innerArgs,
      )
      return result
    }
    catch (error) {
      throw error
    }
  }
}

// 使用示例
async function fetchData(url) {
  const response = await fetch(url)
  return `Data from ${this.name}: ${response.status}`
}

const api = { name: 'UserAPI' }
const boundFetch = fetchData.asyncBind(api)
boundFetch('/users').then(console.log)

2. 支持装饰器的call

javascript
Function.prototype.decoratedCall = function (
  context,
  decorators = [],
  ...args
) {
  let func = this

  // 应用装饰器
  for (const decorator of decorators.reverse()) {
    func = decorator(func)
  }

  return func.myCall(context, ...args)
}

// 装饰器示例
function logDecorator(func) {
  return function (...args) {
    console.log(`Calling ${func.name} with args:`, args)
    const result = func.apply(this, args)
    console.log(`Result:`, result)
    return result
  }
}

function timingDecorator(func) {
  return function (...args) {
    const start = performance.now()
    const result = func.apply(this, args)
    const end = performance.now()
    console.log(`Execution time: ${end - start}ms`)
    return result
  }
}

// 使用示例
function calculate(a, b) {
  return a + b
}

calculate.decoratedCall({}, [logDecorator, timingDecorator], 5, 3)

3. 支持柯里化的bind

javascript
Function.prototype.curry = function (...outerArgs) {
  const originalFunc = this
  const totalArgs = originalFunc.length

  function curried(...currentArgs) {
    const allArgs = [...outerArgs, ...currentArgs]

    if (allArgs.length >= totalArgs) {
      return originalFunc.apply(this, allArgs)
    }
    else {
      return curried.bind(this, ...currentArgs)
    }
  }

  return curried
}

// 使用示例
function multiply(a, b, c) {
  return a * b * c
}

const curriedMultiply = multiply.curry()
const step1 = curriedMultiply(2)
const step2 = step1(3)
const result = step2(4) // 24

4. 支持管道的apply

javascript
Function.prototype.pipeline = function (context, ...funcs) {
  return (...args) => {
    let result = this.myApply(context, args)

    for (const func of funcs) {
      result = func.call(context, result)
    }

    return result
  }
}

// 使用示例
function add(a, b) {
  return a + b
}

function double(x) {
  return x * 2
}

function square(x) {
  return x * x
}

const pipeline = add.pipeline(null, double, square)
console.log(pipeline(3, 4)) // ((3+4)*2)^2 = 196

5. 支持缓存的new

javascript
function memoizedNew(constructor, cacheKey) {
  const cache = new Map()

  return function (...args) {
    const key = cacheKey ? cacheKey(...args) : JSON.stringify(args)

    if (cache.has(key)) {
      return cache.get(key)
    }

    const instance = myNew(constructor, ...args)
    cache.set(key, instance)

    return instance
  }
}

// 使用示例
function ExpensiveObject(id, data) {
  this.id = id
  this.data = data
  this.processed = this.heavyProcessing(data)
}

ExpensiveObject.prototype.heavyProcessing = function (data) {
  // 模拟昂贵操作
  return data.toUpperCase()
}

const createCachedObject = memoizedNew(
  ExpensiveObject,
  id => id, // 缓存键为 id
)

const obj1 = createCachedObject(1, 'test')
const obj2 = createCachedObject(1, 'different') // 返回缓存的 obj1
console.log(obj1 === obj2) // true

内容基于 MIT 许可 | 保持节奏 · 持续积累