Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
310 views
in Technique[技术] by (71.8m points)

关于ES6中继承的问题 B继承A B.__proto__ = A?

先附上代码

class A{}
class B extends A{}
B.__proto__ === A //true
B.__proto__ === Function.prototype //false
typeof B //function
B.constructor === Function //true

我们知道,每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype

class是语法糖,本质上还是个函数,因此B实际上是个函数,可以看作通过new Function创建出来的Function对象实例

那么问题来了B.__proto__不应该等于Function.prototype吗?为什么等于A呢?这是特殊规定的吗?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

B.__proto__.__proto__ 确实是 Function.prototype ,但首先它的原型是 A ,其原型的原型才是函数原型。因为定义在 A 上的静态方法 B 也要继承。


更新:


每一个对象都有原型,但是对象的原型并不一定是对象的构造函数的 prototype 属性。

我在这里先声明一下几个术语,前端届对原型的称呼一直比较混乱:

  • 对象:JS 中一切非原始值皆对象,函数也是对象的一种
  • 原型:

    • 每个对象都有一个原型,原型本身也是一个普通的对象。
    • 使用点操作符或者 [ ] 访问属性时,会先检查对象本身的属性,如果不存在则会检查对象的原型,如果还不存在则会继续检查对象原型的原型直到原型的尽头 Object.prototype ,它没有原型,它的原型是 null,这个就是所谓的原型链。
    • 获取对象的原型可以用标准的 Object.getPrototypeOf(obj),或者非标准的 obj.__proto__, 以下我都用 Object.getPrototypeOf(obj).
    • 对象的默认 toString, valueOf 方法都来自 Object.prototype。
  • 函数:函数是一种特殊的对象,所以函数也有原型,通过 function 语句声明的函数的原型是 Function.prototype。函数的 call,apply,bind 方法都来自 Function.prototype 上。
  • 构造函数:除了箭头函数外,普通函数都可以当作构造函数用,用法就是使用 new 操作符。
  • 类:ES6 的类是一种特殊的函数,只能通过 new 操作符来使用,不能当普通函数来直接调用。
  • 函数.prototype: 函数对象还有一个特殊的属性,名字叫 prototype。这个我们还是别叫 函数的原型 了吧,因为函数作为对象看待它本身确实是有原型的,所以我们叫它函数的prototype属性,以区别于函数的原型,即:Object.getPrototypeOf(fn) vs fn.prototype.
  • new 一个构造函数发生了什么:

    • 新建一个对象
    • 把对象的原型指向构造函数的 prototype 属性
    • 把对象当 this 运行一遍构造函数
  • 什么是继承?

    • JS 的继承发生在对象之间,而非类之间,就像现实中的儿子继承爸爸,不需要先有儿子类和爸爸类,让儿子类 extends 爸爸类然后实例化。JS 中的对象直接继承另一个对象。
    • JS 的继承是使用原型来实现的。对象属性查找会检查原型链,这就是继承概念的体现。
    • ES5 加入的 Object.create API 就是把一个对象当原型来创建另一个对象之用。
  • 基于 prototype 的继承模拟基于 class 的继承:

    • 函数的 prototype 属性就是用来模拟基于 class 的继承的,否则只要能从一个对象构造另一个对象那 prototype 继承就有了。
    • 当我们 new 一个 class/函数 时,其实我们是把 class/函数的prototype 属性当原型来构造新对象的。你把方法和属性定义在函数的prototype属性上,用起来和传统的类定义方法和字段一个感觉。
    • 对象的构造器本质就是一个 constructor 属性,所以函数的 prototype.constructor 会自动回指到函数本身,以便 new 出来的对象能正确获取到构造器。

说的有点多,回到你的问题:

每一个对象都有原型,但是对象的原型并不一定是对象的构造函数的 prototype 属性。

且不说 constructor 属性并不是一个锁死的属性,JS 中的有些对象也并不一定存在一个构造器。

var father = {money: 1000,house:'big house'};
// father 对象使用对象字面量定义,本质上和 new Object 一回事
// 所以 father 是用 Object 构造的
father.constructor === Object // 没问题

var son = Object.create(father); 
son.money // 这里我们说 son 继承了 father 的所有属性
Object.getPrototypeOf(son) === father; // 毛得问题吧
son.constructor // 这玩意儿会是什么?
son.constructor === father.constructor; // 嗯,来自于父亲

如上代码展示,其实 JS 中对象的构造器不是在所有场合都合理且有意义。构造函数这个概念在基于 prototype 继承的体系里其实是不需要的,它是 JS 模拟基于类的继承加入的东西。

那么题主的问题到底出现在哪里呢?出现在这里:

class是语法糖,本质上还是个函数,因此B实际上是个函数,可以看作通过new Function创建出来的Function对象实例

class 是语法糖没错,本质是函数也没错,错在 class B 并不能看作是 new Function 构造出来的,class A 可以,但是 B 不行。因为 B extends A,B 是通过 A 构造出来的。你忘了,JS 继承的本质是对象继承。 class B extends A 这里隐含了两个 prototype 继承。 B.prototype 继承了 A.prototype, 同时 B 继承了 A。

class A {
  static p = 1;
}
class B extends A{}

B.p; // 1
Object.getPrototypeOf(B.prototype) === A.prototype; // true
Object.getPrototypeOf(B) === A; // true
//或者说用非标准属性表示 B.__proto__ === A

因为静态属性也是需要继承的,所以 B 并不是直接通过 new Function 构造出来的,B 是通过 A 作为原型构造出来的,即 B = Object.create(A); 这样 B 才能获得 A 上定义的静态属性和方法,才符合基于 class 的继承的表现。

所以,B.constructor 在这里并没有很符合实际的意义,并不存在一个函数它把 B 构造了出来。从原型的角度看待就不存在这种问题,B 用了 A 当原型,A 用了 Function.prototype 当原型,仅此而已。


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...