Javascript 里面为什么在一个类里面写的函数在 console 里面发现属于父类了? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
movq
V2EX    Javascript

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

  •  
  •   movq 2021-10-04 20:53:45 +08:00 3955 次点击
    这是一个创建于 1537 天前的主题,其中的信息可能已经有所发展或是发生改变。

    JS 入门小白求助:

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

    8gXI9QmzVTuOdvh

    lmargGCUYN7bLWV

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

    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
        9
    movq  
    OP
       2021-10-04 22:06:27 +08:00
    @h503mc 你为什么赞同一楼的观点?
    1. 我说哪个地方出错了?
    2. 哪个地方我看错了?
    vance123
        10
    vance123  
       2021-10-04 22:10:21 +08:00   2
    当你没看完新手教程就开始玩游戏.jpg
    详细解释请看<https://zh.Javascript.info/class#shi-mo-shi-class>
    aristolochic
        11
    aristolochic  
       2021-10-04 22:14:22 +08:00   2
    不要试图理解[[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
        12
    seakingii  
       2021-10-04 22:19:15 +08:00
    Javascript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
    Biwood
        13
    Biwood  
       2021-10-04 22:30:51 +08:00 via iPhone   1
    @movq
    因为 js 一开始的设计里面就没有类的概念,只有 prototype,用 prototype chain 来实现继承,现在虽然有声明类的语法,但底层运行还是 prototype 的逻辑

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

    (感觉不是能轻易理解,必须回到没有 class 的时代手动实现一遍原型继承,然后结合具体的项目才行)
    seakingii
        15
    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
        16
    h503mc  
       2021-10-04 23:02:03 +08:00
    seakingii
        17
    seakingii  
       2021-10-04 23:09:12 +08:00
    xavierchow
        18
    xavierchow  
       2021-10-04 23:55:29 +08:00   2
    这是个很好的问题,
    我们定义 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 PrmaryStudent
    helloPrimaryStudent: helloPrimaryStudent()
    ```

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

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

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

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

    这个问题的本质是 JS 恶心的原型链设计误导了从 JAVA,C++过来的人....
    autoxbc
        22
    autoxbc  
       2021-10-05 13:57:42 +08:00
    我单纯的认为 Chrome 显示错了,给每一层 [[Prototype]] 标记了错误的名字,Firefox 做的是正确的,可以对比看看
    autoxbc
        23
    autoxbc  
       2021-10-05 14:13:44 +08:00
    或者说 Chrome 的做法是反直觉的,PrimaryStudent 的原型是个 Student 实例这种拗口的显示属于画蛇添足,树形图本身就有完整的继承关系
    zxCoder
        24
    zxCoder  
       2021-10-05 15:54:32 +08:00
    @vance123 草,为什么是 “shi-mo-shi”,哈哈哈
    zxCoder
        25
    zxCoder  
       2021-10-05 15:55:03 +08:00
    回答 lz 的问题,(历史包袱
    legendecas
        26
    legendecas  
       2021-10-06 01:24:10 +08:00
    显示 [[Prototype]] xxx 不正确是 chrome devtools 的 bug -。- https://bugs.chromium.org/p/chromium/issues/detail?id=1255695
    wizardpisces
        27
    wizardpisces  
       2021-10-11 12:21:03 +08:00
    建议先读一下《 Javascript 高级程序设计》继承部分,先理解不用 class 语法糖,只用 funtion 做继承,然后就明白了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2260 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 00:22 PVG 08:22 LAX 16:22 JFK 19:22
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86