call / apply / bind / new
先理解原理和示例,再练习完整口述与追问。
回答要点
调用与绑定、参数合并、构造调用、返回值、实现边界。
原理拆解
call/apply 的核心是以指定接收者调用函数并传参;bind 创建保存接收者与前置参数的新函数,构造调用还有特殊规则;new 则分配对象、连接原型、执行构造过程,并在构造器显式返回对象时采用该对象。用临时属性模拟调用时还需考虑覆盖、删除与对象可扩展性。
示例与推演
测试普通调用接收者、null/原始值与严格模式,bind 后再次 call 是否仍保持绑定,new 绑定函数的实例关系,以及构造器返回原始值/对象的区别。下方实现用 Reflect 等能力表达核心行为,并说明没有完整复制原生所有内部语义。
易错点
箭头函数无构造能力,不能通过补 prototype 就变成构造器。简化 bind 与 Reflect.construct 的支持范围要讲清。
参考回答可直接练习口述
call 和 apply 立即调用,区别主要是参数形式;bind 返回函数,合并预置参数,普通调用使用绑定 this,构造调用则忽略绑定 this 并创建新实例。使用临时属性挂到 this 上的口诀实现会污染对象,也不能准确模拟严格模式和冻结对象;下面借助 Reflect 表达底层调用,且不修改 Function.prototype。bind 是教学封装,不完全复制原生元数据和构造器内部标记。
function callWith(fn, thisArg, ...args) {
return Reflect.apply(fn, thisArg, args)
}
function applyWith(fn, thisArg, args) {
if (args != null && typeof args !== 'object' && typeof args !== 'function') {
throw new TypeError('args 必须是类数组对象或空值')
}
return Reflect.apply(fn, thisArg, args == null ? [] : args)
}
function bindWith(fn, thisArg, ...preset) {
if (typeof fn !== 'function') throw new TypeError('fn 必须是函数')
function bound(...later) {
const args = [...preset, ...later]
if (new.target) {
return Reflect.construct(fn, args, new.target === bound ? fn : new.target)
}
return Reflect.apply(fn, thisArg, args)
}
Object.defineProperty(bound, Symbol.hasInstance, {
value(instance) { return instance instanceof fn }
})
return bound
}
function newOrdinary(Ctor, ...args) {
// 只检查可构造性,不执行用户构造函数;主体演示普通构造函数的 new 步骤。
Reflect.construct(function () {}, [], Ctor)
const prototype = Ctor.prototype
const objectLike = prototype !== null &&
(typeof prototype === 'object' || typeof prototype === 'function')
const instance = Object.create(objectLike ? prototype : Object.prototype)
const result = Reflect.apply(Ctor, instance, args)
return result !== null && (typeof result === 'object' || typeof result === 'function')
? result : instance
}
function newWithReflect(Ctor, ...args) {
return Reflect.construct(Ctor, args)
}
示例:callWith(function(x){return this.n+x},{n:2},3) 为 5;applyWith(fn,obj,[1,2]) 展开类数组。普通 function Person(name){this.name=name} 可用 newOrdinary(Person,'张三')。手工 new 主体仅支持普通函数构造器,不支持 class、内建构造器内部槽、bound 构造器和 new.target 语义;这些用 newWithReflect。bindWith 不模拟原生 bound 的 length/name、无 prototype 属性以及 extends 等细节,也不等价于完整 polyfill;构造非构造器仍会抛错。时间 O(参数个数)。bind 语义、构造语义