JavaScript 里面为什么在一个类里面写的函数在 console 里面发现属于父类了?

2021-10-04 20:53:45 +08:00
 movq

JS 入门小白求助:

为什么我在 Student 里面写的 helloStudent 函数显示在 Object 类里面,而在 PrimaryStudent 里面写的 helloPrimaryStudent 函数显示是在 Student 类里面?

3263 次点击
所在节点    JavaScript
27 条回复
seakingii
2021-10-04 21:17:52 +08:00
没有错,是你看错了
demonzoo
2021-10-04 21:27:30 +08:00
console 里面那个 prototype 看到的是父级的属性啊,所以越往深越是父级,你没看到最深的一层是 Object 吗?
rodrick
2021-10-04 21:28:37 +08:00
class 里的函数 就相当于是 Student.prototype.helloStudent = function()xx
class 只是语法糖 建议先看看 js 的原型链 理解一下关系
Biwood
2021-10-04 21:35:14 +08:00
原型和构造函数需要区分一下,你看的其实是原形链,这没有问题,ES6 引入的 class 本质上只是原型式继承的语法糖,当你理解了原型链的工作机制就会明白为什么是这样了
movq
2021-10-04 21:46:06 +08:00
@seakingii @demonzoo 我没说是语言错了啊,我是问这是怎么理解。因为在 Java 或者 C++里面是在一个类里面写函数,这个函数就属于这个类,而不是属于父类吧。
movq
2021-10-04 21:47:05 +08:00
@rodrick 我好奇的是,为什么在一个类里面写函数,会把这个函数给这个类的 prototype
renmu123
2021-10-04 21:55:36 +08:00
@movq 因为 js 里压根没有类,只是用原型模拟出来的语法糖。
h503mc
2021-10-04 21:57:22 +08:00
这是我的理解

jack(PrimaryStudent 的实例)的原型是一个 Student 实例(因为 PrimaryStudent 类扩展了 Student 类)
所以 helloPrimaryStudent 方法在 jack.[[Prototype]]实例上并且 jack.[[Prototype]]的名字是 Student

同理,jack 原型的原型是一个 Object 实例(因为 Student 类默认扩展 Object 类)
所以 helloStudent 方法在 jack.[[Prototype]].[[Prototype]]实例上并且 jack.[[Prototype]].[[Prototype]]的名字是 Object

不要被 object 的构造函数的类名骗了

@seakingii #1 +1
movq
2021-10-04 22:06:27 +08:00
@h503mc 你为什么赞同一楼的观点?

1. 我说哪个地方出错了?
2. 哪个地方我看错了?
vance123
2021-10-04 22:10:21 +08:00
当你没看完新手教程就开始玩游戏.jpg
详细解释请看<https://zh.javascript.info/class#shi-mo-shi-class>
aristolochic
2021-10-04 22:14:22 +08:00
不要试图理解[[Prototype]]属性,这个不是学习 JavaScript 本身需要知道的,你看它写着是个原型,但很容易断章取义贻笑大方:

下面摘抄 [MDN 原文]( https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto) :

> __proto__的读取器(getter)暴露了一个对象的内部 [[Prototype]] 。对于使用对象字面量创建的对象,这个值是 Object.prototype (en-US)。对于使用数组字面量创建的对象,这个值是 Array.prototype 。对于 functions,这个值是 Function.prototype (en-US)。对于使用 new fun 创建的对象,其中 fun 是由 js 提供的内建构造器函数之一(Array, Boolean, Date, Number, Object, String 等等),这个值总是 fun.prototype 。对于用 js 定义的其他 js 构造器函数创建的对象,这个值就是该构造器函数的 prototype 属性。

> __proto__ 的设置器(setter)允许对象的 [[Prototype]] 被变更。前提是这个对象必须通过 Object.isExtensible() 判断为是可扩展的,如果不可扩展,则会抛出一个 TypeError 错误。要变更的值必须是一个 object 或 null,提供其它值将不起任何作用。

那么很明显,无论你的 Student 类还是 PrimaryStudent 类,很显然都不是 JavaScript 的内建构造器函数,new 出来的 [[Prototype]]只会是其对应的函数的 prototype 。PrimaryStudent 的 prototype 是 Student,Student 的 prototype 是 Object,显示十分正确。
seakingii
2021-10-04 22:19:15 +08:00
Javascript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
Biwood
2021-10-04 22:30:51 +08:00
@movq
因为 js 一开始的设计里面就没有类的概念,只有 prototype,用 prototype chain 来实现继承,现在虽然有声明类的语法,但底层运行还是 prototype 的逻辑

在没有 class 的时代,只要你声明了一个函数(即类),它就默认带有 prototype,值为 {},当你 new 一个实例的时候,本质上就是产生一个 object,这个 object 默认的属性和方法都直接继承自函数的 prototype,而且这个 object 本身又可以作为另外一个函数的 prototype,这就形成了“链”,也就是继承

(感觉不是能轻易理解,必须回到没有 class 的时代手动实现一遍原型继承,然后结合具体的项目才行)
rabbbit
2021-10-04 22:37:59 +08:00
seakingii
2021-10-04 22:44:50 +08:00
```
//第一步: 使用早期语法定义了一个类"Student",现在的 Class 本质上差不多,可以算是语法糖
function Student() {
//类有个方法
this.helloFromStudent = function () {
console.log("====== call helloFromStudent");
};
}

//实例化类
const inst1 = new Student();
//调用方法
inst1.helloFromStudent();

//打印,这时会看到 "Student"有个下属方法叫"helloFromStudent",同时它的原型是"Prototype",和 Class 语法不同的是,这时打印看到"helloFromStudent"方法没有挂成原型上,实际调用效果是一样的
console.dir(inst1);




//第二步:直接在原型上添加了一个新的方法"method2"
//这也是脚本语言特性,可以动态更改类...
Student.prototype.method2 = function () {
this.helloFromStudent();
console.log("=====call method2");
};

//又构造了一个
const inst2 = new Student();
inst2.method2();

//这时可以看到,直接在 Student 上有个方法 helloFromStudent,在原型上有另一个方法"method2",实际上都能调用...
console.dir(inst2);

console.log(Student.prototype.helloFromStudent); //undefined , helloFromStudent 方法直接定义在 Student,没有定义在原型上
console.log(Student.prototype.method2);

//和我的例子相比,现在的 class 语法把方法都放在 prototype 上了.




//第三步:在原型上定义一个同名方法....
Student.prototype.helloFromStudent = function () {
console.log("====== call helloFromStudent,我是放在原型上的方法");
};

//又构造了一个
const inst3 = new Student();
inst3.method2();
//打印结构,这时会看到有意思的情况:helloFromStudent 方法同时有两个,一个是 Student 的直接下级,一个是在原型上,实际调用时,原型上的不起作用,说明直接下级的优先级高
console.dir(inst3);




//第四步:定义一个方法,删除直接下属的 helloFromMethod
Student.prototype.deleteHelloFromStudent = function () {
//删除直接方法"helloFromStudent"
delete this["helloFromStudent"];
};

//又构造了一个
const inst4 = new Student();
inst4.deleteHelloFromStudent();
inst4.method2();
//打印结构,这时会看到有意思的情况:helloFromStudent 方法只有一个,是在原型上了...然后原型上的 helloFromStudent 又起作用了
console.dir(inst4);
```
h503mc
2021-10-04 23:02:03 +08:00
seakingii
2021-10-04 23:09:12 +08:00
xavierchow
2021-10-04 23:55:29 +08:00
这是个很好的问题,
我们定义 new 出来的子类实例 primaryStudent 为 p,
则 p.__proto__ === PrimaryStudent(class).prototype,
PrimaryStudent.prototype.__proto__ = Student(class).prototype.
这个是我们关于原型链的基本理解,不会有问题,题主的困惑在于为什么在 web console 中,p.__proto__ 会显示成 Student?

我在 node.js/chrome/safari 中分别尝试了一下,
1. 在 node.js 和 safari 中 p.__proto__ 为 "PrimaryStudent"
```
Welcome to Node.js v12.18.2.
Type ".help" for more information.
> class Student {
... constructor(name) {
..... this.name = name;
..... }
...
... helloStudent() {
... console.log('student');
... }
... }
undefined
>
> class PrimaryStudent extends Student {
... constructor(name, grade) {
..... super(name);
..... this.grade = grade;
..... }
...
... helloPrimaryStudent() {
... console.log('primarystudent');
... }
... }
undefined
> var p = new PrimaryStudent('john', 5)
undefined
> p.__proto__
PrimaryStudent {}
> PrimaryStudent.prototype
PrimaryStudent {}
>
```

2. 在 chrome 中, 如贴主所截图,p.__proto__为 "Student"
```
var p = new PrimaryStudent('john', 5)
p.__proto__
Student {constructor: ƒ, helloPrimaryStudent: ƒ}
constructor: class PrimaryStudent
helloPrimaryStudent: ƒ helloPrimaryStudent()
```

个人认为,其实在不同环境中,PrimaryStudent.prototype(即 p.__proto)还是同样的 object:{constructor: PrimaryStudent,prototype: Student.prototype}, 只不过在 node.js 和 safari 中,解释器用 constructor 来称呼这个 object,
在 chrome 中,它用 prototype 来称呼这个 object,不知道这个有没有回答到贴主的问题,
当然如有错误请各位指正。
Rocketer
2021-10-05 00:19:20 +08:00
楼主的根本问题在于角度不统一,一会儿 class 一会儿原型链,晕就对了。

很多人都说过了,class 是个语法糖,再说直白点就是——js 根本没有 class 。

你写的 class 会被“编译”(不准确,但可以这么理解)成原始的 js,也就是原型链。原型链是 js 这门语言的新知识点,想要彻底理解需要专门学一下,不能用 java 的思想来直接理解它。

不过个人认为原型链也是 js 的糟粕。既然 es6 开始推 class,那新学的人就坚守 class 吧。原型链不看不学不关心,反正实战中也用不到了。
NightCatS
2021-10-05 00:35:03 +08:00
@movq 楼上似乎都没 get 到楼主想要问的点:“为什么 helloPrimaryStudent 函数显示在 Student 类里?”
答:然而 helloPrimaryStudent 函数的确没有被挂载到 Student 原型上,楼主是被 Chrome 的输出误导了。
Chrome 输出中“[[Prototype]]: Student”的含义是:对象 jack 的原型是 Student,下面展开的属性并非是 Student 的原型属性,而是 jack.__proto__ 或者说 PrimaryStudent.protoype 。而 helloPrimaryStudent 函数自然是在 PrimaryStudent.protoype 上的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/805883

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX