让我们来仔细看看幕后发生了什么。

如上所述,在 JavaScript 中,函数可以拥有属性。所有函数都有一个名为 prototype 的特殊属性。请注意,下面的代码是独立的(出于严谨,假设页面没有其他的 JavaScript 代码)。为获得最佳的学习体验,强烈建议你打开控制台,进入“console”标签页,复制并粘贴以下 JavaScript 代码,然后按回车键运行。(大多数 web 浏览器的开发者工具中都包含控制台。请参阅 Firefox 开发者工具、Chrome 开发者工具和 Edge 开发者工具,以了解详情。)

jsfunction doSomething() {}

console.log(doSomething.prototype);

// 你如何声明函数并不重要;

// JavaScript 中的函数总有一个默认的

// 原型属性——有一个例外:

// 箭头函数没有默认的原型属性:

const doSomethingFromArrowFunction = () => {};

console.log(doSomethingFromArrowFunction.prototype);

如上所示,doSomething() 有一个默认的 prototype 属性(正如控制台所示)。运行这段代码后,控制台应该显示一个类似于下面的对象。

{

constructor: ƒ doSomething(),

[[Prototype]]: {

constructor: ƒ Object(),

hasOwnProperty: ƒ hasOwnProperty(),

isPrototypeOf: ƒ isPrototypeOf(),

propertyIsEnumerable: ƒ propertyIsEnumerable(),

toLocaleString: ƒ toLocaleString(),

toString: ƒ toString(),

valueOf: ƒ valueOf()

}

}

备注:

遵循规范的术语,Chrome 控制台使用 [[Prototype]] 表示对象的原型;而 Firefox 使用 。为了保持一致性,我们将使用 [[Prototype]]。

我们可以像下面这样,向 doSomething() 的原型添加属性。

jsfunction doSomething() {}

doSomething.prototype.foo = "bar";

console.log(doSomething.prototype);

其结果为:

{

foo: "bar",

constructor: ƒ doSomething(),

[[Prototype]]: {

constructor: ƒ Object(),

hasOwnProperty: ƒ hasOwnProperty(),

isPrototypeOf: ƒ isPrototypeOf(),

propertyIsEnumerable: ƒ propertyIsEnumerable(),

toLocaleString: ƒ toLocaleString(),

toString: ƒ toString(),

valueOf: ƒ valueOf()

}

}

我们现在可以使用 new 运算符创建基于该原型的 doSomething() 的实例。要使用 new 运算符,只需正常调用函数,只是要在前面加上 new。使用 new 运算符调用函数会返回该函数的实例对象。然后可以在该对象上添加属性。

尝试以下代码:

jsfunction doSomething() {}

doSomething.prototype.foo = "bar"; // 向原型添加一个属性

const doSomeInstancing = new doSomething();

doSomeInstancing.prop = "some value"; // 向对象添加一个属性

console.log(doSomeInstancing);

这会产生类似于下面的输出:

{

prop: "some value",

[[Prototype]]: {

foo: "bar",

constructor: ƒ doSomething(),

[[Prototype]]: {

constructor: ƒ Object(),

hasOwnProperty: ƒ hasOwnProperty(),

isPrototypeOf: ƒ isPrototypeOf(),

propertyIsEnumerable: ƒ propertyIsEnumerable(),

toLocaleString: ƒ toLocaleString(),

toString: ƒ toString(),

valueOf: ƒ valueOf()

}

}

}

如上所示,doSomeInstancing 的 [[Prototype]] 是 doSomething.prototype。但是,这是做什么的呢?当你访问 doSomeInstancing 的属性时,运行时首先会查找 doSomeInstancing 是否有该属性。

如果 doSomeInstancing 没有该属性,那么运行时会在 doSomeInstancing.[[Prototype]](也就是 doSomething.prototype)中查找该属性。如果 doSomeInstancing.[[Prototype]] 有该属性,那么就会使用 doSomeInstancing.[[Prototype]] 上的该属性。

否则,如果 doSomeInstancing.[[Prototype]] 没有该属性,那么就会在 doSomeInstancing.[[Prototype]].[[Prototype]] 中查找该属性。默认情况下,任何函数的 prototype 属性的 [[Prototype]] 都是 Object.prototype。因此会在 doSomeInstancing.[[Prototype]].[[Prototype]](也就是 doSomething.prototype.[[Prototype]](也就是 Object.prototype))上查找该属性。

如果在 doSomeInstancing.[[Prototype]].[[Prototype]] 中没有找到该属性,那么就会在 doSomeInstancing.[[Prototype]].[[Prototype]].[[Prototype]] 中查找该属性。但是,这里有一个问题:doSomeInstancing.[[Prototype]].[[Prototype]].[[Prototype]] 不存在,因为 Object.prototype.[[Prototype]] 是 null。然后,只有在查找完整个 [[Prototype]] 的原型链之后,运行时才会断言该属性不存在,并得出该属性的值为 undefined。

让我们在控制台中输入更多的代码:

jsfunction doSomething() {}

doSomething.prototype.foo = "bar";

const doSomeInstancing = new doSomething();

doSomeInstancing.prop = "some value";

console.log("doSomeInstancing.prop: ", doSomeInstancing.prop);

console.log("doSomeInstancing.foo: ", doSomeInstancing.foo);

console.log("doSomething.prop: ", doSomething.prop);

console.log("doSomething.foo: ", doSomething.foo);

console.log("doSomething.prototype.prop:", doSomething.prototype.prop);

console.log("doSomething.prototype.foo: ", doSomething.prototype.foo);

其结果如下:

doSomeInstancing.prop: some value

doSomeInstancing.foo: bar

doSomething.prop: undefined

doSomething.foo: undefined

doSomething.prototype.prop: undefined

doSomething.prototype.foo: bar