JavaScript 核心原理解析
周爱民
《JavaScript 语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
32699 人已学习
新⼈⾸单¥59
登录后,你可以任选3讲全文学习
课程目录
已完结/共 28 讲
开篇词 (1讲)
JavaScript 核心原理解析
15
15
1.0x
00:00/00:00
登录|注册

13 | new X:从构造器到类,为你揭密对象构造的全程

操作原型/原型链的方法
对象的创建方法
用户返回new的结果
类的构造过程
类的构造过程
类与函数的区别
使用class声明类
类抄写与原型继承的混合使用
原型继承的出现
类抄写的方式创建对象
类和对象的关系
JavaScript 1.0的面向对象系统
知识回顾
创建this的顺序问题
ECMAScript 6之后的类
类与构造器
JavaScript构造器
JavaScript对象构造的全程

该思维导图由 AI 生成,仅供参考

你好,我是周爱民。
今天我只跟你聊一件事,就是 JavaScript 构造器。标题中的这行代码中规中矩,是我这个专栏题目列表中难得的正经代码。
NOTE:需要稍加说明的是:这行代码在 JavaScript 1.x 的某些版本或具体实现中是不能使用的。即使 ECMAScript ed1 开始就将它作为标准语法之一,当时也还是有许多语言并不支持它。
构造器这个东西,是 JavaScript 中面向对象系统的核心概念之一。跟“属性”相比,如果属性是静态的结构,那么“构造器”就是动态的逻辑。
没有构造器的 JavaScript,就是一个充填了无数数据的、静态的对象空间。这些对象之间既没有关联,也不能衍生,更不可能发生交互。然而,这却真的就是 JavaScript 1.0 那个时代的所谓“面向对象系统”的基本面貌。

基于对象的 JavaScript

为什么呢?因为 JavaScript1.0 的时代,也就是最早最早的 JavaScript 其实是没有继承的。
你可能会说,既然是没有继承的,那么 JavaScript 为什么一开始就能声称自己是“面向对象”的、“类似 Java”的一门语言呢?其实这个讲法是前半句对,后半句不对。JavaScript 和 Java 名字相似,但语言特性却大是不同,这就跟北京的“海淀五路居”和“五路居”一样,差了得有 20 公里。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JavaScript对象构造的全程是一篇深入探讨JavaScript构造器和类的技术文章。从JavaScript 1.0时代开始,介绍了构造器的重要性和作用,以及JavaScript面向对象系统的基本面貌。文章详细讨论了构造器、类和继承的概念,深入探讨了JavaScript对象构造的全过程,以及不同阶段的语言设计和发展。特别是在ECMAScript 6中,类的构造过程和继承机制发生了重大变化,要求在构造器方法中显式地使用`super()`来调用父类的构造过程,并且在此之前不能使用`this`引用。此外,文章还涉及了用户代码对`new`运算结果的干涉,以及在构造器中返回值对`new`运算结果的影响。总的来说,本文深入探讨了JavaScript对象构造的全程,对于想要深入了解JavaScript构造器和类的读者来说,是一篇极具价值的文章。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《JavaScript 核心原理解析》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(18)

  • 最新
  • 精选
  • 行问
    谈谈今天的理解: 在 instanceof 运算中,x instanceof AClass 表达式的右侧是一个类名(对于 instanceof 的理解之前是有误解,今天才领悟到) ECMAScript 6 的类是由父类或祖先类创建 this 实例的(也就是 this 是继承而来的,也能够契合前面说的”在调用结束之前,是不能使用 this 引用。不知道这个理解能否正确)

    作者回复: 这个this实例的确是由父类或祖先类创建的。但它不是“继承”来的,因为“继承”这个说法严格来说在JavaScript中就是原型继承,而这个this不是靠原型继承来“传递到”子类的。 在super()调用之前,当前函数——例如子类的构造器——无法访问this,是它的作用域里面没有this这个名字(因为还没有被创建出来嘛)。而super()调用之后,JavaScript引擎会把this“动态地添加到”作用域中,于是this就能访问了。 这个“动态的添加”其实很简单,因为super是子类向父类调用的,所以显然父类调用结束并退出时的当前作用域(或环境)就是子类的,因此ECMAScript约定在退出super()的时候就把已经创建好的this直接“抄写”给当前环境就可以了。这里大概只有一两行代码,很简单的。^^.

    2019-12-13
    11
  • 青史成灰
    首先谢谢老师每次都认真的回答每个问题,但是下面的这个问题,我一下子没想明白。。。 ``` function Device() { this.id = 0; // or increment } function Car() { this.name = "Car"; this.color = "Red"; } Car.prototype = new Device(); var x = new Car(); console.log(x.id); // ``` 这个例子中, 为什么`x.constructor === Device`,按我的理解应该是`x.constructor === Car`才对。但是如果我把`Car.prototype = new Device()`这行代码注释掉,那么就符合我的理解了。。。

    作者回复: x.constructor === Car.prototype.constructor === (new Device()).constructor === Device.prototype.constructor 这个正是因为(new Device())导致的原型继承效果。而且,这种继承方法是ES6之前的标准写法,因为那个时代有且仅有`new`运算会在Car/Device之间维护内部原型继承关系,以确保instanceof运算有效。——instanceof运算是检查Car.[[prototype]]这个内部原型的,而不是检查Car.prototype这个属性。 所以,标准的、完整的在ES6之前实现原型继承的代码“总是(且必然是)”两行,而不是示例中的一行: ``` Car.prototype = new Device(); Car.prototype.constructor = Car; ``` 另外,也有一种写法,用于在Car()这个函数内部来维护这个constructor属性。例如: ``` function Car() { this.constructor = Car; // Or, arguments.callee ... } Car.prototype = new Device; ```

    2020-01-12
    7
  • 油菜
    “函数的“.prototype”的属性描述符中的设置比较特殊,它不能删除,但可以修改(‘writable’ is true)。当这个值被修改成 null 值时,它的子类对象是以 null 值为原型的;当它被修改成非对象值时,它的子类对象是以 Object.prototype 为原型的;否则,当它是一个对象类型的值时,它的子类才会使用该对象作为原型来创建实例。” --------------------- 老师,我的测试结果和这个结论不大一样。 function F(){ this.name1 = 'father'} function S1(){ this.name1 = 'son1'} F.prototype = null; S1.prototype =F; var s1 = new S1(); s1.constructor.prototype; //原型对象是[Function] 而不是null s1 instanceof S1; //true; s1 instanceof F; //TypeError: Function has non-object prototype 'null' in instanceof check ------------- class S2 extends F {} var s2 = new S2(); s2.constructor.prototype; // /原型对象是S2 {},不是null s2 instanceof S2; // true; s2 instanceof F; //Function has non-object prototype 'null' in instanceof check

    作者回复: 这个地方是讲错了,这里混淆了一些内容,一部分是在讨论“它的实例”,另一部分则是在讨论“它的子类对象”。有关的内容应该改成这样(用【】标出修订之处): ``` # 函数的“.prototype”的属性描述符中的设置比较特殊,它不能删除,但可以修改(‘writable’ is true)。 > function f() {} > Object.getOwnPropertyDescriptor(f, 'prototype') { value: f {}, writable: true, enumerable: false, configurable: false } # 当这个值被修改成 null 值时,它的子类对象是以 null 值为【祖先类】原型的; > f.prototype = null > class MyObject extends f {} > Object.getPrototypeOf(Object.getPrototypeOf(new MyObject)) === null true # 当它被修改成非对象值时,【它将不能用于派生子类;但作为构造器,它的实例对象】是以 Object.prototype 为原型的; > f.prototype = 1 > class MyObject2 extends f {} // 异常,不能派生子类 > Object.getPrototypeOf(new f) === Object.prototype true # 否则,当它是一个对象类型的值时,【该函数】才会使用该对象作为原型来创建实例。 > f.prototype = new Object > Object.getPrototypeOf(new f) === f.prototype ```

    2020-11-16
    3
  • westfall
    请问老师,constructor 这个属性是不是可有可无的?

    作者回复: 从原理上来说,在ES6之后,使用类继承的时候这个属性其实意义就不大了。像生成器(以及异步生成器)等函数的prototype上也就根本没有这个属性,表明这它们是不可构造的。 但是有些操作是需要引用这个属性的,例如Symbol.species这个属性会检测对象的构造器,从而创建出一个“相同类型的”实例的,这主要会用到数组和Promise对象上。

    2021-02-04
    1
  • 二二
    老师您好,关于:有一些“类 / 构造器”在 ECMAScript 6 之前是不能派生子类的,例如 Function,又例如 Date。 但是我看babel将es6转成es5是可以实现对于Function的继承,并调用的,请问babel是怎么达到这个效果的呢?

    作者回复: babel转码到es5的时候是加了一堆生成代码的,这些代码模拟了es6类创建的过程(例如为了模拟super关键字,它就维护了一个类继承树)。 所以babel中,如果你用es6的class来写,那么转换到es5的时候,这堆代码也仍然是按es6的过程来创建的。 你可以尝试一下在这里写一段es6的class代码,然后转换成es5的,就知道babel干了些什么了: https://es6console.com/

    2020-10-14
    1
  • 人间动物园
    思考题 1 , 直接执行一个函数也可以创建新的对象: function Person(name){ this.name = name; return this; } person1 = Person('xiaoming');

    作者回复: 不能。 这个this是global,并不是创建的新对象。 ``` ... person1 = Person('xiaoming'); console.log(person1 === global); // true ```

    2020-05-26
    3
    1
  • CoolSummer
    1.创建新的对象 字面量方法创建、Object.create()、工厂模式、构造函数模式 2.操作原型 / 原型链 Object.defineProperty( )/Object.getProperty( ) ES6 的 proxy 和 Reflect

    作者回复: 答案1不够完整,例如事实上类声明也会导致对象的创建(例如class MyClass ...会导致MyClass.prototype创建)。 答案2就离题了。例如defineProperty并不操作原型也不会操作原型链。举两个正确的例子,比如说Object.setPrototypeOf()就是操作原型链的,又例如说class MyClass extends ...会导致MyClass和MyClass.prototype的原型都被操作。

    2020-02-23
    1
  • Smallfly
    老师文中多次提到类继承,您这里指的是从类抄写属性到对象么?我的理解是这个过程属于对象的实例化。JS 只有原型继承一种方式。 还是说因为实例化的过程,包含向 this 对象写入原型,所以称它为类继承,并且包含原型继承?

    作者回复: 是指从ES6开始的JavaScript语法: ``` class X extends ParentCls { ... } ``` 在JavaScript中,尽管这个类继承语法仍然是通过原型继承来实现的,但是它的确在语义与概念上是类继承的,并且由于有了“使用super来指向父类”这样的特性,所以在类继承的概念上也是完整的。

    2020-02-20
    1
  • 小胖
    {Foo () {}}创建的Foo方法不能使用new关键字调用; 但{Foo: function Foo() {}}是可以的。 所以说,ES6提供的方法简写形式添加的方法和不使用简写形式添加的方法是有区别的。

    作者回复: 不。 ES6提供的并不“仅仅是”方法的简写。这种语法风格声明是的“真正的”对象方法。——在ES6之后,真正的对象方法(Method)是与传统的函数在性质上不完全相同的。 而传统的声明方式,也就是“{foo: function ...}”被理解为“函数类型的对象属性”,它是一个Normal Functions,也就是一般的函数,而并非“ES6规范中的对象方法”。 方法是特殊的函数,就如同ES6中“类”是特殊的函数一样。

    2019-12-25
    1
  • Kids See Ghost
    请问老师,在规范里有没有具体哪里讲了如何比较两个object的?比如为什么两个empty object不想等 `{} !== {}` 在规范里找了很久没找到. 在规范里 https://tc39.es/ecma262/#sec-samevaluenonnumeric 只说了 "7. If x and y are the same Object value, return true. Otherwise, return false. ". 但是没有具体地说怎么样的object value算一样,怎么样算不一样。比如`{}` 和 `{}`就不一样。

    作者回复: 这就跟既有的知识是一样的了,比较两个对象,就是比较它们的地址(指针);比较两个值,就是比较地址上的数据。我想可能是因为这个原因,反正ECMAScript里面也没有写~~又也许,还与具体引擎的实现有关?就是说引擎想怎么实现就怎么实现……

    2022-01-20
收起评论
显示
设置
留言
18
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部