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 · 3262 次点击
    这是一个创建于 1148 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 PrimaryStudent
    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   ·   实用小工具   ·   1006 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 21:45 · PVG 05:45 · LAX 13:45 · JFK 16:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.