首先,请记住 JavaScript 主要是 prototypal language,而不是 class-based language1。 Foo 不是一个类,它是一个函数,它是一个对象。您可以使用new 关键字从该函数实例化一个对象,这将允许您创建类似于标准 OOP 语言中的类的东西。
我建议大多数时候忽略__proto__,因为它对跨浏览器的支持很差,而是专注于了解prototype 的工作原理。
如果你有一个从函数2创建的对象的实例,并且你以任何方式访问它的成员之一(方法、属性、属性、常量等),访问将顺着原型层次结构,直到它 (a) 找到成员,或者 (b) 没有找到另一个原型。
层次结构从被调用的对象开始,然后搜索其原型对象。如果原型对象有原型,则重复,如果不存在原型,则返回undefined。
例如:
foo = {bar: 'baz'};
console.log(foo.bar); // logs "baz"
foo = {};
console.log(foo.bar); // logs undefined
function Foo(){}
Foo.prototype = {bar: 'baz'};
f = new Foo();
console.log(f.bar);
// logs "baz" because the object f doesn't have an attribute "bar"
// so it checks the prototype
f.bar = 'buzz';
console.log( f.bar ); // logs "buzz" because f has an attribute "bar" set
在我看来,您至少已经对这些“基本”部分有所了解,但我需要明确说明。
在 JavaScript 中,一切都是对象3。
一切都是一个对象。
function Foo(){} 不仅定义了一个新函数,它还定义了一个可以使用Foo 访问的新函数对象。
这就是为什么您可以使用Foo.prototype 访问Foo 的原型。
您还可以在Foo 上设置更多功能:
Foo.talk = function () {
alert('hello world!');
};
这个新功能可以使用:
Foo.talk();
我希望您现在已经注意到函数对象上的函数和静态方法之间的相似之处。
将f = new Foo(); 视为创建一个类实例,将Foo.prototype.bar = function(){...} 视为为该类定义一个共享方法,而将Foo.baz = function(){...} 视为为该类定义一个公共静态方法。
ECMAScript 2015 为此类声明引入了多种语法糖,以使它们更易于实现,同时也更易于阅读。因此前面的例子可以写成:
class Foo {
bar() {...}
static baz() {...}
}
允许bar 被称为:
const f = new Foo()
f.bar()
和baz 被称为:
Foo.baz()
1:class was a "Future Reserved Word" in the ECMAScript 5 specification,但 ES6 引入了使用 class 关键字定义类的能力。
2:本质上是由构造函数创建的类实例,但有很多细微差别我不想误导你
3:primitive values——包括undefined、null、布尔值、数字和字符串——在技术上不是对象,因为它们是低级语言实现。布尔值、数字和字符串仍然与原型链交互,就好像它们是对象一样,因此出于本答案的目的,即使它们不完全是,也更容易将它们视为“对象”。