13

在JavaScript Ninja的秘密,2013 年,第 125 页一书中,它说:

JavaScript 中的每个对象都有一个名为的隐式属性,该属性constructor 引用用于创建对象的构造函数。而且因为原型是构造函数的一个属性,所以每个对象都有办法找到它的原型。

实际上,这可能是我听说过的关于 JavaScript 的最有缺陷的事情之一,而且它来自一位所谓的 JavaScript 专家。这不是真的吗

  1. 任何 JavaScript 对象“都有办法使用内部[[prototype]]属性找到它的原型”(如ECMA-262 规范,第 32 页)。它可以在 Chrome 和 Firefox 上使用 访问__proto__,在最新版本的 IE 中使用Object.getPrototypeOf

  2. 任何对象都通过在指向constructor的原型对象中获取属性来获取该属性。__proto__constructor属性有时甚至没有正确设置,因为某些 JavaScript 库或框架根本不使用它。 constructor是原型对象的属性,而不是对象本身的属性:

(在 Chrome 的开发者工具中可以看到):

> function Foo() {}
undefined

> var foo = new Foo()
undefined

> foo.hasOwnProperty("constructor")
false

> foo.__proto__.hasOwnProperty("constructor")
true

> foo.__proto__.constructor === Foo
true

上面的(1)和(2)是真的吗?constructor引用文本中 JavaScript 中的“隐式属性命名”是什么?它是否试图表示类似[[prototype]]which 是内部属性的东西?但更重要的是,我想知道上面的(1)和(2)是否正确,而不是引用的文字所说的。

4

3 回答 3

2

引用的文字非常准确,并且非常简单地解释了这种机制。

“JavaScript 中的每个对象都有一个名为 constructor 的隐式属性,它引用用于创建对象的构造函数。”

正如@Mathletics 指出的那样,这是绝对正确的:

foo.constructor === Foo // true

...“而且因为原型是构造函数的属性,所以每个对象都有办法找到它的原型。”

这也可以简单理解为已读。从构造函数中获取原型是实例找到其原型的有效方法。

foo.constructor.prototype // Foo {}

并且

foo.constructor.prototype === foo.__proto__ // true

我认为这本书描述它的方式是最合适的。出于某种原因,“ __proto__ ”属性在每一侧都使用双下划线命名。正如您所指出的,它是一个内部属性,双下划线是一种广泛使用的命名内部属性的约定。hasOwnProperty不是“可见的” 。并不是因为它是一个内部属性,而是因为它不是直接设置在对象本身上的。这可能会更好地解释hasOwnPropery的含义:

foo.a = 4;
foo.a; // 4
foo.hasOwnProperty("a"); // true
foo.constructor.prototype.b = 5;
foo.b; // 5
foo.hasOwnProperty("b"); // false
于 2013-06-25T09:02:33.257 回答
1

约翰·雷西格错了

是的,jQuery 的作者也会犯错。他是这样说的:

JavaScript 中的每个对象都有一个名为的隐式属性,该属性constructor引用用于创建对象的构造函数。而且因为原型是构造函数的一个属性,所以每个对象都有办法找到它的原型。

以下是他的说法是错误的原因:

  1. 不是每个对象都有原型。因此,这些对象也没有任何隐式属性。
  2. 并非每个具有原型的对象都是由构造函数创建的。然而,这不包括对象、数组和正则表达式文字和函数,因为这些对象是由构造函数隐式创建的。
  3. 并非每个具有原型并由构造函数创建的对象都具有名为 的隐式属性constructor
  4. 并非每个具有原型、由构造函数创建并具有名为的隐式属性constructor的对象都指向创建该对象的构造函数。
  5. 并非每个具有原型、由构造函数创建并具有constructor指向创建该对象的构造函数的隐式属性的对象,都具有prototype在其构造函数上调用的属性。
  6. 并非每个具有原型的对象都由构造函数创建,具有名为的隐式属性,该属性constructor指向创建该对象的构造函数,并且具有prototype在其构造函数上调用的属性,并且该属性指向该对象的原型。

让我们通过例子来证明这些陈述,以证明 John Resig 的陈述是错误的。由于所有这些陈述都以断言“不是每个对象”开头,我们只需要找到每个陈述的一个例子来证明 John Resig 的陈述是错误的。

声明 1 的证明

Object.create方法可用于创建新对象并设置其内部[[prototype]]属性。因此它可以用来创建一个没有原型的对象:

var o = Object.create(null); // o has no prototype

上例中的对象没有原型 - 它的内部[[prototype]]属性设置为null. 因此它也没有任何隐式属性

陈述 2 的证明

现在让我们创建另一个p继承自 object 的对象o,如下所示:

var p = Object.create(o); // the prototype of p is o

因此该对象p有一个原型,但它不是由构造函数创建的。

陈述 3 的证明

好吧,让我们p从构造函数创建对象(实际上这正是Object.create函数的实现方式):

function F() {}  // F is a constructor
F.prototype = o; // objects constructed by F inherit from o
var p = new F;   // p is an object which is constructed by F

这里的对象p是由构造函数创建的F。但是它没有任何名为constructor.

陈述 4 的证明

如果变量o被分配了一个对象字面量然后用作prototype构造函数的属性F怎么办?

var o = {};      // object literals inherit from Object.prototype
function F() {}  // F is a constructor
F.prototype = o; // objects constructed by F inherit from o
var p = new F;   // p is an object which is constructed by F

现在该对象p有一个名为的隐式属性constructor,但它指向Object而不是F. 因此p.constructor.prototype指向Object.prototype而不是o

陈述 5 的证明

也许您认为问题在于继承?好吧,让我们完全取消继承。从头开始:

var p = new F;      // p is constructed by F, it inherits from F.prototype
delete F.prototype; // devious isn't it? I love being naughty
function F() {}     // declarations are hoisted

好吧,现在对象p继承自F.prototype,并且它有一个名为的隐式属性constructor,它指向F它自己。但是,由于我们删除了该prototype属性,F我们无法访问pvia的原型p.constructor.prototype(它现在将返回undefined)。

陈述 6 的证明

让我们稍微修改一下最后一个例子。我们不会删除F.prototype,而是将其设置为其他内容。例如:

var o = {};      // object literals inherit from Object.prototype
var p = new F;   // p is constructed by F, it inherits from F.prototype
F.prototype = o; // oops, what will happen now?
function F() {}  // declarations are hoisted

现在该对象p继承自F.prototype,并且它有一个名为的隐式属性constructor,该属性指向F它自己。但是,由于我们设置F.prototype为何o时访问p.constructor.prototype,我们将获得o而不是原始的F.prototype.

结论

如您所见,John Resig 的说法是完全错误的。我们不需要 6 个例子来证明这一点。任何一个例子都足够了。但是,我想表明他的说法是多么错误。因此,我写了我能想到的所有可能的例子来反驳他的说法。

JavaScript 是一种原型面向对象的编程语言,这意味着对象继承自其他对象。构造函数不是严格要求创建对象的。然而,它们被赋予了过度的重要性,因为不幸的是,这就是原型继承在 JavaScript 中的工作方式。

原型继承的构造函数模式经常令人困惑和误导。此外,它隐藏了不使用构造函数的原型继承的真实方式(原型继承的原型模式)。要了解更多信息,请阅读以下答案:https ://stackoverflow.com/a/17008403/783743

于 2013-06-25T12:14:36.507 回答
0

我是如何学习它的以及可以观察到什么

每个对象都有一个__proto__属性,每个函数都有一个附加prototype属性。那prototype是一个对象本身,并且有一个名为constructor指向原始函数的属性:

function C(){}
console.log(C.hasOwnProperty("prototype")); //true
console.log(C.prototype.hasOwnProperty("constructor")); //true
console.log(C.prototype.constructor === C); //true

当一个对象被创建时,__proto__它的属性被设置为指向prototype它的构造函数的属性:

var c = new C();
console.log(c.__proto__ === C.prototype) //true

关于什么c是什么的所有信息,它是否从它的__proto__属性中知道。这可以__proto__通过手动更改属性来观察:

function D(){}
c.__proto__ = D.prototype;
console.log(c.constructor === D) //true
console.log(c instanceof D) //true

所以考虑到所有这些,真的很难相信有一个构造函数属性可以派生出一切。但是在内部构造函数属性(即)中仍然可能存在__constr__我们无法访问但在请求原型时可以访问的属性。至少我猜是有可能的。

一件事也让我有点困惑,是这样的:

console.log(c.hasOwnProperty("__proto__")) //false

我的猜测是__proto__该方法不会抓住它hasOwnProperty,但也许其他人可以对此有所了解。

小提琴

ECMA 怎么说

这里还有一些ECMA 规范的摘录:

所有对象都有一个称为 [[Prototype]] 的内部属性。此属性的值为 null 或对象,用于实现继承。本机对象是否可以将宿主对象作为其 [[Prototype]] 取决于实现。每个 [[Prototype]] 链必须具有有限长度(即,从任何对象开始,递归访问 [[Prototype]] 内部属性最终必须导致空值)。[[Prototype]] 对象的命名数据属性被继承(作为子对象的属性可见)用于获取访问,但不用于放置访问。命名访问器属性被继承用于获取访问和放置访问

和:

8.12.2 [[获取属性]] (P)

当使用属性名称 P 调用 O 的 [[GetProperty]] 内部方法时,将执行以下步骤:

  1. 令 prop 为调用 O 的 [[GetOwnProperty]] 内部方法的结果,属性名为 P。

  2. 如果 prop 不是未定义的,则返回 prop。

  3. 令 proto 为 O 的 [[Prototype]] 内部属性的值。

  4. 如果 proto 为 null,则返回 undefined。

  5. 返回使用参数 P 调用 proto 的 [[GetProperty]] 内部方法的结果。

我的结论

ECMA 明确指出存在一个内部原型属性,如果 JavaScript 仍然会首先访问一些内部构造函数属性来访问已经存在的东西,那么这将没有任何意义。此外,可以测试的所有内容都表明构造函数是原型的属性,而不是相反。

所以我会说可以肯定地说,这就是它的工作原理。

我还重读了 Jon Resig 的引述。我认为他的意思the prototype is a property of the constructor是我们可以直接访问并且每个函数都具有的原型属性。他在这里也不是特别具体。我猜他只是想要一个简单的解释,不想马上让人们感到困惑。

于 2013-06-21T14:22:43.823 回答