一条容易理解JS对象的constructor、prototype、__proto__的途径

constructor构造器、prototype原型、__proto__。有关这3个东西的每一个方面都让人非常困惑。困惑来自于这3个东西的命名和含义、函数是对象、基于原型的继承、构造函数与关联对象采用相同名字,等等。

本文以面向对象语言视角,从class出发,找到了一条容易理解JS对象的constructor、prototype、__proto__的途径。按照这个思路,各种奇奇怪怪的问题居然迎刃而解了。最重要的论述总结于第3章:结论整理。

1 从class出发理解JS的思路

面向对象语言中的class

我们从es6标准的class出发:

class Parent{
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}
 
class Child extends Parent{
  constructor(name, age) {
    super(name);
    this.age = age;
  }
  show() {
    console.log(this.getName());
  }
}
 
let obj = new Child("wang", 5);
obj.show();

从面向对象语言的角度去理解继承、构造什么的,非常容易。Child类继承自Parent类,它们有各自的构造函数;变量obj是通过Child的constructor构造出来的,是Child的实例。

如果把函数也视作对象,那么Parent类和Child类的constructor是谁派生的?我们很自然地认为是空函数。空函数是一切函数的祖宗。这么理解是很清楚的。

JS中的class名称是class的构造函数

但是且慢,这里是JavaScript,不是Java啊C#啊C++什么的。JavaScript的标准即ECMAScript标准说了,尽管用了class,但此class非彼class。JS标准要求我们,要想搞懂es6之前的代码,必须回到以原型为基础的对象继承上来。

JS标准规定,没有类,一切都是对象,Function对象相当于空函数,是一切函数的祖宗(后面我们知道,还有个别东西说不清,例如Function.prototype像函数又不像函数,不是Function的子孙)。

实践看看:

不仅如此,像Number啊,Object啊,等等,instanceof Function都是true。

可见,类名代表的是函数。那么它是哪个函数?我们暂时只能认为是类的构造函数。

结果出乎意料,根本就不是普通的函数,也不是类的构造函数,而竟然是整个类的定义!

一上来就整懵了!

对象的constructor属性

我们先引入对象的constructor属性看看:

这表明(1)Child.constructor不是Child类的constructor!(2)一个对象的constructor属性的值是构造这个对象的类,是Function的子孙,但不是构造这个对象的类的构造函数。
第二点说得不够准确,再补充补充(后面我们还看到prototype.constructor是个特例)。为什么说constructor属性指向整个类,而不是类下面的构造函数呢?正如obj.constructor === Child成立,用console.log(obj.constructor.toString())与console.log(Child.toString())打印的结果是一样的。如果obj.constructor指向Child类的构造函数,那么console.log(obj.constructor.toString())打印出来的结果应该是这单个函数:
constructor(name, age) {
    super(name);
    this.age = age;
  }

事实上我尝试了各种办法都把这个函数打印不出来。例如我用console.log(Child.prototype.show.toString());可以打印Child的show方法,但把show换成constructor打印出来的就是整个类的定义。

JS标准是否对类和类的构造函数进行了模糊处理?即只见class而不见class的构造函数(即class的构造函数这个在C#/JAVA等面向对象语言中存在的概念在JS中不存在)?我们说构造器是不是就是指整个类而非类的构造函数?这么理解有点强词夺理。

我们不如把一个对象的constructor属性就看作是构造这个对象的类的构造函数,而把constructor.toString()的结果看作是JS标准模糊处理的结果。这样理解似乎仅仅是对constructor.toString()开了特例,但更符合思维习惯,所以暂时就这么认为。

再来看看Child.constructor是个什么东西?就是说,构造Child类的构造函数的函数,是什么东西?不在JS语境中,或者认为只能是空函数,即Function。Function类似空函数,是一切函数的祖宗,自然也就是一切构造函数的祖宗。我们也可以这么看,Function()作为一个函数可以构造出一个函数,不妨认为一切函数都可以视作用Function()构造出来的,因此一切函数作为对象都是由Function类的构造函数构造出来的,故一切函数的constructor属性必然指向Function类的构造函数。

JS中,所有函数,包括class的构造函数,所有的constructor,都是Function构造出来的。事实的确如此:

function funcA() {};
console.log(funcA.constructor === Function);// 返回true

只有非函数的对象,其constructor属性的值才千差万别。比如上面的obj.constructor===Child。又如:

我们据此梳理一下:

(1)Number是class级别的函数,构造Number这个class的东西是Number.constructor === Function;Number(3)是Number的实例对象,构造它的是Number这个class级别的函数;Number对应class,是函数,Number(3)对应class实例,是对象。这两东西的父母是谁?

(2)Object是class级别的函数,构造Object这个class的东西是Object.constructor === Function;Object()是Number的实例对象,是个空白对象,构造它的是Object这个class级别的函数;Object对应class,是函数,Object()对应class实例,是对象。这两东西的父母是谁?

(3)Function是class级别的函数,构造Function这个class的东西是Function.constructor === Function,自己构造了自己?!Function()是Function的实例对象,是个匿名的空函数,构造它的是Function这个class级别的函数;Function对应class,是函数,Function()对应class实例,还是个函数。这两东西的父母是谁?

对象的prototype属性

为了搞清楚函数或对象的父母,JS中引入了原型链的概念。A若在B的原型链上,则可以说A是B的祖宗。Object在一切对象一切函数的原型链上,所以Object是一切对象一切函数的祖宗。原型链由对象的__proto__属性说明,而__proto__又指向别的对象的prototype属性,所以我们先看prototype属性。

prototype属性是constructor、prototype、__proto__3个概念最难理解的。

只有函数才有prototype属性,普通对象没有。函数与原型对象是伴生的、一对一的关系。无论何时,只要创建一个函数,就会为这个函数创建一个prototype属性,指向原型对象;默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之伴生的函数。即Fn.prototype.constructor===Fn。

这点立即就造成一个重大问题:前面我们说对象的constructor属性指向构造这个对象的class,但是现在与class关联的原型对象的constructor属性却指向的是class。很明显,prototype.constructor是个特例。为什么它是特例还可以这样认为:若按先前的理解,原型的constructor指向构造原型的类的构造函数,很明显它不存在于JS语境内,所以这种理解没有意义。

抛开constructor指向整个class的问题不谈,抛开prototype.constructor的含义问题不谈,如果我们把原型对象理解为class,而把JS函数理解为class的构造函数,是否可以?我们暂且这么认为。

于是Child指的是Child类的构造函数,Child.prototype指的才是Child类。

对象的__proto__属性

一个对象的__proto__指向创建它的函数的prototype属性。

于是我们不妨认为,实例对象的__proto__指向实例化对象的class,class的__proto__指向其父class。看前面的源代码,因为obj 由Child类创建而来,故

Child从Parent派生,Parent从Object派生,然而我们还是被耍了:

其实是我们自己疏忽了!类名在JS中只能代表构造函数,.prototype才代表类,所以正确的姿势是:

果然正确了。

但还有问题没有解决,Child.__proto__又是什么东西?

我先把普通函数给搞清楚了。对于function funcA() {};,则:

这是因为funcA从空函数创建而来,空函数的原型就是Function.prototype。按这个思路又弄清楚了下面两个:

继续:

Parent.__proto__ 都是对的,但是Child不对。考虑到Child类的构造函数继承于Parent的构造函数,最后终于找对了:

所以我们就明白了,一个class,如果派生自Object类,就认为其构造函数(用class的类名表示)是从Function类即Function.prototype派生来的;如果派生自其它类,就认为其构造函数(用该class的名字代表)是从父类的构造函数(用父class的名字代表)派生来的。

2 验证这个思维模式

一个一个地验证。

关于Object

Object在JS中代表Object类的构造函数。Object.constructor代表构造Object的构造函数的函数,一切函数都可视作以Function()的方式被构造出来,故Object.constructor === Function;这也表示Object的构造函数用Function类的构造函数构造出来的,故Object.__proto__指向Function类,即Function.prototype。继续深入的内容转到Function。

Object.prototype代表Object这个类。原型的constructor被标准规定为指回类的构造函数,故Object.prototype.constructor === Object。创建Object类的东西不在JS语境中,且Object类没有父类,故Object.prototype.__proto__ === null。原型的原型是个不存在的概念,故Object.prototype.prototype === undefined。

整理后的实测结果:

关于Function

Function在JS中代表Function类的构造函数。Function.constructor代表构造Function的构造函数的函数,一切函数都可视作以Function()的方式被构造出来,故Function.constructor === Function;这也表示Function的构造函数用Function类的构造函数构造出来的,故Function.__proto__指向Function类,即Function.prototype。

Function.prototype代表Function这个类。原型的constructor被标准规定为指回类的构造函数,故Function.prototype.constructor === Function。创建Function类的东西不在JS语境中,但Function类的父类是Object,故Function.prototype.__proto__ === Object.prototype。原型的原型是个不存在的概念,故Function.prototype.prototype === undefined。

整理后的实测结果:

关于Object和Function,不少是属性是相互指向的,其实去深究它们这件事本身没有什么价值。上述的理解只不过是为了方便记忆结果而已,甚至记这个结果这件事本身也没什么价值。我们这么做是结果,仅仅是为了让我们的大脑不感到难受卡壳走不通,而是感到理顺了,舒服了。

关于Number、Boolean等JS内置对象

就拿Number举例,余此类推。

Number在JS中代表Number类的构造函数。Number.constructor代表构造Number的构造函数的函数,一切函数都可视作以Function()的方式被构造出来,故Number.constructor === Function;这也表示Number的构造函数用Function类的构造函数构造出来的,故Number.__proto__指向Function类,即Function.prototype。。

Number.prototype代表Number这个类。原型的constructor被标准规定为指回类的构造函数,故Number.prototype.constructor === Number。创建Number类的东西不在JS语境中,但Number类的父类是Object,故Number.prototype.__proto__ === Object.prototype。原型的原型是个不存在的概念,故Number.prototype.prototype === undefined。

整理后的实测结果:

关于自定义的没有父类的class

比如:

class myCls{
    constructor() {}
}

myCls在JS中代表myCls类的构造函数。myCls.constructor代表构造myCls的构造函数的函数,一切函数都可视作以Function()的方式被构造出来,故myCls.constructor === Function;这也表示myCls的构造函数用Function类的构造函数构造出来的,故myCls.__proto__指向Function类,即Function.prototype。。

myCls.prototype代表myCls这个类。原型的constructor被标准规定为指回类的构造函数,故myCls.prototype.constructor === myCls。创建myCls类的东西不在JS语境中,但myCls类的父类是Object,故myCls.prototype.__proto__ === Object.prototype。原型的原型是个不存在的概念,故myCls.prototype.prototype === undefined。

整理后的实测结果:

我们看到myCls与Number等内置对象是极其相似的。

关于普通函数

普通函数与Number等内置对象也是极其相似的。这是因为声明一个普通函数,就相当于创建了一个同名的class,然后把这个函数作为同名class的构造函数。

下面是实测结果:

function funcA() {};

如果这个普通函数作为对象构造函数,如var v12=new funcA();,情况仍然不变。

关于有父类的class

这就是本文开头的那个例子了:

class Parent{
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}
 
class Child extends Parent{
  constructor(name, age) {
    super(name);
    this.age = age;
  }
  show() {
    console.log(this.getName());
  }
}

Child在JS中代表Child类的构造函数。Child.constructor代表构造Child的构造函数的函数,一切函数都可视作以Function()的方式被构造出来,故Child.constructor === Function;Child的构造函数虽然是用Function类的构造函数构造出来的,但它更应该视作是从Parent类的构造函数派生而来,故Child.__proto__===Parent。

Child.prototype代表Child这个类。原型的constructor被标准规定为指回类的构造函数,故Child.prototype.constructor === Child。创建Child类的东西不在JS语境中,但Child类的父类是Parent,故Child.prototype.__proto__ === Parent.prototype。原型的原型是个不存在的概念,故Child.prototype.prototype === undefined。

整理后的实测结果:

Child从Parent派生出来,Child.__proto__===Parent,Child.prototype.__proto__ === Parent.prototype;Number从Object派生出来,Number.__proto__ === Function.prototype,Number.prototype.__proto__ === Object.prototype;为什么不是Number.__proto__ === Object?这是因为Child、Parent、Number、Object都是与它们同名的class的构造函数,Child类的构造函数从Parent类的构造函数派生出来,但是Number类的构造函数却不是从Object类的构造函数派生而来,而是从空函数派生而来。

关于变量

在前例的基础上定义一个变量var v = new Child('John', 30);。分析一下v的情况:

v.constructor代表构造v的构造函数,即Child类的构造函数,它在JS中用Child表示,故v.constructor === Child;v是用Child类的构造函数构造出来的,故v.__proto__指向Child类,在JS用原型表示,即Child.prototype。

变量不是函数,故v没有prototype。

整理后的实测结果:

我们换一个变量试试:var v=Number(3);实测结果类似:

再换一个对象定义式试试:var v={a:1, b:2};,实测结果为:

仍然是类似的结论。

至此我们拉通完毕,从class的观念出发被证明是正确的。

3 结论整理

下面的结论中有些是等效认知,它们有助于理解constructor、prototype、__proto__属性,不是JS标准的官方表述。在这个前提下,规则是:

  1. 类也是一种对象。若classA继承自classB,可以把classA类看作是classB的实例代入后续描述,结论成立(这证明了我们的论述是逻辑自洽的)。
  2. 一个类的名字classA在JS中不代表该类,而代表该类的构造函数。因此Object、Function、Number等名称代表的是Object类、Function类、Number类的构造函数,任何classA类的构造函数都由classA代表。
  3. classA.prototype代表该classA类,只有函数才有prototype。因此Object.prototype才真正代表Object类、Function.prototype才真正代表Function类、Number.prototype才真正代表Number类,等等。
  4. 一切函数都是用Function类的构造函数Function()构造出来,要么是直接构造的(是Function类的构造函数的儿子),要么是间接构造的(是Function类的构造函数的孙子或更远后代)。普通函数是直接构造的。对于classA类的构造函数,若classA类派生自Object类,就认为classA类的构造函数是直接构造出来的;否则,classA类派生自非Object类的classB类,就认为classA类的构造函数是classB类的构造函数的儿子,是Function()间接构造出来的。需要注意的是,若classA extends Function显示存在,则classA的构造函数被认为是派生自Function的构造函数,而不是由Function()直接构造出来,因此classA.__proto__===Function;与之对比的是普通函数的__proto__===Function.prototype。
  5. 一个对象的constructor属性指向构造/派生这个对象的类的构造函数。classA类的实例v由于是从classA的构造函数函数构造而来,故v.constructor===classA恒成立。classA.construtor表示的是构造/派生出classA类的构造函数的 类classB的构造函数;根据第4条,若classA extends Object,无论隐式还是显式,视classA类的构造函数是用Function()函数直接构造出来的,故classA.construtor===Function恒成立;否则,classA extends classB(classB类不是Object类),视classA类的构造函数派生自classB类的构造函数,即classA.constructor===classB恒成立。
  6. classA类的constructor属性是个例外,被强制规定为指回classA的构造函数。根据第3条,classA类的constructor属性表示为classA.prototype.constructor,classA的构造函数表示为classA,故classA.prototype.constructor===classA恒成立。若非如此规定,会存在两个问题:(1)根据第5条,classA.prototype.constructor仍然恒等于classB/Function,与classA.constructor重复了;(2)classA类的实例v无法通过v.prototype访问到classA类的构造函数,v不知道其父亲。
  7. 一个对象的__proto__属性指向实例化/派生这个对象的类。classA的实例v是classA类实例化而来,故v.__proto__===classA.prototype恒成立。若classA继承自classB,则classA.__proto__指向派生classA类的构造函数的classB的构造函数,即classA.__proto__===classB恒成立。对于类的构造函数,根据这一条和第4条,若classA extends Object,无论隐式还是显式,视classA类的构造函数是用Function()函数直接构造出来的,故classA.__proto__===Function.prototype恒成立;否则,classA extends classB(classB类不是Object类),视classA类的构造函数派生自classB类的构造函数,即classA.__proto__===classB恒成立。
  8. 一切引用类型,即一切对象的始祖都是Object类。Object类没有父亲,构造/派生出Object类的东西不存在于JS语境中,故Object.prototype.__proto__ === null恒成立。
  9. 原型的原型是个伪概念,不存在这个这个东西,故任何东西.prototype.prototype都是undefined。
  10. es标准文档中每个内置对象的Properties of the classA Constructor,还真就是classA类的构造函数(也是对象)的属性。所以prototype、MAX_VALUE是Number类的构造函数的属性,不是Number类的属性,设var v=Number(3),只存在Number.prototype、Number.MAX_VALUE,而不存在v.prototype、v.MAX_VALUE;其它依此类推。
  11. es标准文档中每个内置对象的Properties of the classA Prototype Object,还真就是classA类的属性,所以classA类的实例v继承了这些属性。所以对于var v=Number(3),可以调用v.toFixed()的原因是Number存有方法toFixed(),即Number.prototype.toFixed()存在;其它依此类推。

我们可以清楚地看到,上述第2条是造成理解困难的最主要原因。没办法,JS标准这么规定了,没法改变现状。

另外,constructor属性不仅与__proto__属性存在功能重复,且存在特例。但深入思考可以发现,造成困境的根本原因还是在第2条。

其它相关的结论:

  1. typeof返回"function"的东西,不一定满足instanceof Function为true!instanceof检查是否在原型链上,即通过原型链确定继承关系。typeof则用另一种算法确定变量的类型。Function.prototype instanceof Function为false,typeof Function.prototype返回"function"。Function instanceof Object是true,Object instanceof Function也是true。
  2. typeof和instanceof都不能判断一个东西A是不是完全等同另一个东西B,只有===操作符才可以。

根据上述规则,整理表格在后。

表1 v为变量时的表达式含义
表达式含义
v

变量v(下面设v从classA类实例化而来,classA类继承自classB类)

v.constructor

classA类的构造函数,表达式为classA

v.prototypeundefined
v.__proto__classA类,表达式为classA.prototype
v.constructor.constructor派生/构造出classA.constructor的 类的构造函数,即为classB类或Function类的构造函数,表达式为classB或Function
v.constructor.prototype

classA类,表达式为classA.prototype。故有:

v.__proto__ === v.constructor.prototype

v.construtor.__proto__classB类的构造函数,表达式为classB
v.__proto__.constructorclassA类的构造函数,表达式为classA
v.__proto__.prototypeundefined
v.__proto__.__proto__classB类,表达式为classB.prototype

我现在对CSDN的编辑功能感到非常讨厌。它反复让我的写作丢失。一发布,似乎回到以前的版本。也可能是它不支持多张表格。我下决心再也不使用CSDN在线编辑文档了。

附:其它混乱不堪的认识

基于es5标准,JS创建对象有7种方式(尽管ECMAScript标准创造了多种对象构造方式,但没有面向对象语言通用的class定义法是不完整的、难学的,所以es6及后增加了class定义法):

JavaScript 创建对象的几种常见模式 - 知乎

JavaScript七种非常经典的创建对象方式

前一篇文章给出了constructor、prototype、__proto__之间的关系图。

根据这个图,梳理下constructor和prototype的关系:

(1)构造器就是构造函数,即构造器constructor是函数。故Fn.prototype.constructor===Fn。

(2)构造器的原型prototype是对象,但不是函数,所以不是构造函数,不是构造器。

(3)构造器通过属性prototype拥有原型对象,原型对象通过属性constructor拥有构造器。

说了说去就只有两个东西:构造器及其原型,构造器是函数,原型是普通对象。题外话:函数也是一种对象。

再根据这个图,梳理下__proto__与它们的关系:

(1)构造器Fn或者说Fn.prototype.constructor创建对象object1。

(2)被创建的object1通过__proto__属性拥有构造它的构造器的原型prototype。即object1.__proto__===Fn.prototype, Fn.prototype!==object1。

因此这个图描述了3个事物(object1对象、Fn原型对象、Fn函数)之间的关系:object1对象和Fn原型对象都是普通对象,不是函数;Fn函数(即构造器)把object1对象和Fn原型对象关联了起来,让它们形成了继承关系,object1对象是儿女,Fn原型对象是父母。

这么看来,弄晕我们的仅仅是属性名字,把__proto__理解为parentObject似乎就理解了所有。于是一个对象的__proto__指向其父对象,沿着继承关系反向追踪,最终达到Object对象,这样就形成了一根继承关系的链条,称为原型链。Object对象是原型链的根,没有父对象,其__proto__是不存在的,是null。

迷思

访问Object.__proto__的结果让人倍感意外,它是一个函数。这是因为只要直接访问Object、Number什么的,都是访问相应的构造函数。正确的访问姿势是Object.prototype.__proto__,用console.log打印的结果是null。

这个特点造成了“Function instanceof Object是true(函数是一种对象),Object instanceof Function也是true(Object是构造函数)”这样的迷思。网上到处都在讨论Function和Object是谁生谁的问题。

我们不妨来看看更容易造成迷思的东西:Function

console.log(typeof Function.constructor, Function.constructor instanceof Function, Function.constructor instanceof Object); 
// 输出function true true
console.log(typeof Function.prototype, Function.prototype instanceof Function, Function.prototype instanceof Object); 
// 输出function false true
console.log(typeof Function.__proto__, Function.__proto__ instanceof Function, Function.__proto__ instanceof Object); 
// 输出function false true
console.log(Function.prototype.constructor===Function);// 输出true
console.log(Function.constructor === Function);// 输出true
console.log(Function.prototype === Function);// 输出false
console.log(Function.__proto__=== Function.prototype);// 输出true

Function的原型和父对象是函数但不是Function。到这里我们总算意识到一个问题:typeof返回"function"的东西,不一定满足instanceof Function为true!instanceof据说是检查是否在原型链上,即通过原型链确定继承关系。typeof则用另一种算法确定变量的类型。那么迷思来了,typeof和instanceof,谁的检测更可靠?谁说了算?Function.prototype到底是不是函数?我在javascript中Function.prototype是对象还是函数? - 知乎 这里又发现了一张图:

从这张图我们看到的是混乱不堪、难以理解的场面。它告诉我们,ECMAScript标准更多的是从语言实现的视角出发制定规则,而无法从程序员(语言的用户)的视角以确定的规则来理解标准,从而难以理解各种对象间的关系。我们且看且走,能理解多少算多少,可能应该是记住实践结果而非理清js的规则。

在这里我想说,造成迷惑的主要原因是一个名字,既作为构造函数,又作为对象。例如Number既是构造函数,又是对象。函数是对象,对象又成了函数,而函数还不等于对象,那么函数和对象的关系就成了 剪不断,理还乱。ECMAScript标准在这个问题上的处理是稀里糊涂的,标准拎不清,程序员就更拎不清了。

规则不清,实践说了算

Function→Function原型对象→Object

这个结果怎么理解?迷惑可能主要还是来自Function既是构造函数又是内置对象这个特点。我们要记住,单独用Function,它就是构造函数。因此,Function构造函数的父母是Function的原型对象,而Function的原型对象的父母则是Object。所以我们应该认为:Function不是Object的儿子,而是Object是孙子。问题似乎唯一成了:Function的原型对象是个什么东西? 目前的答案就是typeof返回的结果是"function",但它不是函数,而是介于函数Function和Object之间的用于过渡的这么一个东西。

两句话概况:Function(构造函数)是Object是孙子,是Function原型对象的儿子。Function原型对象是介于函数Function和Object之间的、用于过渡的一个东西,不是函数,但typeof返回"function"。

还没有说得足够清楚,还有:Function(构造函数)的constructor属性是它自身,Function原型对象的constructor属性还是Function自身。 

Number

console.log(typeof Number.constructor, Number.constructor instanceof Function, Number.constructor instanceof Object); 
// 输出function true true
console.log(typeof Number.prototype, Number.prototype instanceof Function, Number.prototype instanceof Object); 
// 输出object false true
console.log(typeof Number.__proto__, Number.__proto__ instanceof Function, Number.__proto__ instanceof Object); 
// 输出function false true
console.log(Number.prototype.constructor === Number);//输出true
console.log(Number.constructor === Number);//输出false
console.log(Number.constructor === Function);//输出true

Number在这里只是构造函数,所以Number.prototype.constructor === Number的结果是true。

Number的构造函数是Function不是Number。不仅如此,其它如Date、Boolean的构造函数,甚至是Object的构造函数即constructor属性,都是Function。我们不妨这么认为,constructor肯定都是Function或其子孙;没有显示构造函数的,就都默认是从不带参数的Function(即function f(){})构造出来的,这仅仅是一种“视作”。

constructor是真正的函数,typeof和instanceof验证了这一点。

prototype是真正的函数,typeof和instanceof验证了这一点。

让我们惊讶的是Number的__proto__,用typeof检测是函数,用instanceof检测不是函数!这是个什么原理?仔细一想,似乎明白了,Number是函数,它的父对象应该是Function才对。但是为什么instanceof Function又是false呢?所以,Number构造函数是函数,但不是Function的子孙

其实任意写一个构造函数,结果也是如此,构造函数都不是Function的子孙:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
console.log(Person.__proto__ instanceof Function);// 输出false

var v1=new Number(3);
console.log(v1);
console.log(v1.__proto__ === Number.prototype, v1.__proto__ === Number);
var v2={name:"John", age:50};
console.log(v2);
console.log(v2.__proto__ === Object.prototype, v2.__proto__ === Object);

Chrome的输出是:

从这个例子我们知道:v1从Number继承而来,v2从Object继承而来;子对象的__proto__不是父对象,而是父对象的prototype。

后一句的理解存在问题。这意思难道是:子对象的__proto__是父对象构造函数的prototype?父对象与其构造函数是什么关系?

展开Number{3}:

__proto__:Number下面本层的函数/属性对应es标准文档中的prototype的属性。而es标准文档中的constructor的属性则位于更下一层,即点开这个树中的constructor才能看到。我们在访问属性也是这样的。例如toString()是prototype的属性,则可以用v1.toString(),而isNaN是constructor的属性,则必须用Number.isNaN(v1)或者v1.constructor.isNaN(v1) 。这样看来,v1.constructor才是Number对象本身?实测v1.constructor === Number返回true。

越想越让人迷惑不解,Number这个东西是函数这种对象,constructor看来也是函数这种对象,但是Number的父对象居然不是Function而是Object。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
JavaScript 中,每个对象都有一个 __proto__ 属性,指向其构造函数的原型对象。而每个函数都有一个 prototype 属性,指向该函数实例化出来的对象的原型对象。 __proto__ 属性是一个指针,指向该对象的构造函数的原型对象。通过 __proto__ 属性可以访问原型对象中的属性和方法。这个属性在 ES6 中已经被标准化,可以用 Object.getPrototypeOf() 来获取对象的原型。 prototype 属性是函数的一个特殊属性,指向一个对象。当函数用作构造函数创建实例对象时,该对象的原型会指向构造函数的 prototype 属性指向的对象。也就是说,该对象可以访问构造函数原型对象中的属性和方法。 举个例子: ``` function Person(name) { this.name = name; } Person.prototype.sayHello = function() { console.log(`Hello, my name is ${this.name}`); } const p = new Person('Tom'); console.log(p.__proto__ === Person.prototype); // true console.log(Person.prototype.constructor === Person); // true console.log(p.constructor === Person); // true ``` 在这个例子中,我们定义了一个构造函数 `Person`,并给其原型对象添加了一个 `sayHello` 方法。我们通过 `new` 关键字实例化了一个 `Person` 对象 `p`。这个对象的 `__proto__` 属性指向了 `Person.prototype`,因此我们可以通过 `p.__proto__.sayHello()` 或者 `Person.prototype.sayHello.call(p)` 来调用 `sayHello` 方法。同时,我们也可以通过 `Person.prototype` 来访问 `Person` 构造函数原型对象中的属性和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值