先记住这个答案
不是所有函数都有 prototype。这个属性只为 new 服务:new Foo() 创建的实例,原型会指向 Foo.prototype。普通函数声明、普通函数表达式和 class 能被 new,所以有 prototype。箭头函数、async 函数、对象和类里的简写方法,设计上就不能 new,所以没有 prototype。箭头函数之所以不能 new,是因为它没有自己的 this,而构造的核心就是把新对象作为 this 去初始化。
prototype的用途是给new出来的实例当原型- 箭头函数、
async函数、简写方法没有prototype,也不能new - 生成器有
prototype,但照样不能new - 判断能否
new不能看有没有prototype
把几类函数放在一起对比
光说结论容易记混,直接打印出来最清楚。
prototype 和 new 的结果JavaScriptfunction Normal() {}
const arrow = () => {}
async function asyncFn() {}
const obj = { method() {} }
function* gen() {}
console.log(typeof Normal.prototype) // 有
console.log(arrow.prototype, asyncFn.prototype, obj.method.prototype) // 都没有
console.log(typeof gen.prototype) // 生成器也有
for (const fn of [arrow, gen]) {
try { new fn() } catch (e) { console.log(e.name) }
}查看输出与解释
object
undefined undefined undefined
object
TypeError
TypeError箭头函数、async 函数、简写方法的 prototype 都是 undefined,new 它们会直接报 TypeError: xxx is not a constructor。
生成器是个例外:gen.prototype 存在,但 new gen() 一样报错。它的 prototype 不是给 new 用的,而是给调用 gen() 返回的生成器对象当原型,Object.getPrototypeOf(gen()) === gen.prototype 为 true。async function* 异步生成器也是一样,有 prototype、不能 new。
为什么这些函数被设计成不能 new
new 做的事情是:创建一个空对象,把它的原型设为 Foo.prototype,再以它为 this 执行函数体。一个函数要能被 new,至少要有自己的 this,还要有意义地「初始化一个实例」。
- 箭头函数
- 箭头函数的
this来自外层作用域,调用时不会创建自己的this,没法把新对象绑定进去,所以不能new,也就不需要prototype。它被设计出来就是做轻量回调的。 async函数和简写方法async函数一定返回 Promise,而new应该返回实例对象,两者冲突。简写方法method() {}是 ES6 明确规定的方法语义,它是用来被对象调用的行为,不是构造器。而method: function () {}这种写法到现在也能被new,这是普通函数带来的能力,简写方法没有继承它。
容易答错的地方
- 用
fn.prototype判断函数能不能new - 生成器有
prototype却不能new,bind返回的函数没有prototype却可能能new,两头都会判断错。真要判断,只能try { Reflect.construct(String, [], fn) }这种方式试一下,或者在设计 API 时直接约定传入的是构造器还是普通函数。 - 以为
class里的方法也有prototype class本身有prototype,但类里定义的方法,比如class A { run() {} }里的A.prototype.run,是简写方法,没有prototype,不能new。
面试官还会怎么问?
Function.prototype 和函数的 prototype 属性是一回事吗?
不是。Function.prototype 是所有函数的原型,call、apply、bind 就定义在它上面,箭头函数也继承自它,所以箭头函数也能调用 call。而 Foo.prototype 是 Foo 自己的属性,给 Foo 的实例用。
bind 之后的构造函数还能 new 吗?
能。new 会忽略 bind 绑定的 this,但保留预置的参数,实例的原型是原函数的 prototype,所以 instanceof 原函数为 true。
箭头函数能用 call 改 this 吗?
能调用 call,但改不了 this。传进去的第一个参数会被忽略,箭头函数里的 this 始终是定义它的外层作用域的 this。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。