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__ === null3. 构造函数调用的四个步骤
- 创建一个新的空对象
- 将空对象的 proto 指向构造函数的 prototype
- 将构造函数的 this 绑定到新对象
- 如果构造函数返回对象,则返回该对象;否则返回新对象
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] = this2. 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) // 244. 支持管道的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 = 1965. 支持缓存的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