15 | return Object.create(new.target.prototype):做框架设计的基本功:写一个根类
该思维导图由 AI 生成,仅供参考
为什么需要定义自己的构建过程
- 深入了解
- 翻译
- 解释
- 总结
JavaScript框架设计的基本功是编写一个根类。本文介绍了在JavaScript中定义自己的构建过程的必要性,以及在构造器中使用`new.target`元属性的重要性。作者指出,JavaScript使用原型继承来构建面向对象的继承体系,而在ECMAScript 6中的类创建过程中,`new.target`的传递是至关重要的。文章还讨论了在没有声明构造方法时,JavaScript引擎会自动插入一个默认的构造方法,并且非派生类不需要调用`super()`。总的来说,文章强调了在JavaScript框架设计中,编写根类是至关重要的基本功,同时也解释了相关技术特点。 在JavaScript类构建过程中,`new.target`元属性的使用至关重要。它与普通属性访问有所不同,能够在构造器中指示正在构造的实例的目标类。此外,文章还介绍了在特殊情况下,类的构造方法中可能没有`this`引用,需要通过返回`Object.create(new.target.prototype)`来创建实例。父类对子类实例的原型关系并不关心时,返回任何对象都是可以的。文章还提出了一个思考题,探讨了`new.target`作为元属性与普通属性访问的不同之处。 总的来说,本文深入探讨了JavaScript框架设计中根类的编写以及相关技术特点,对于理解JavaScript类构建过程具有重要的参考价值。
《JavaScript 核心原理解析》,新⼈⾸单¥59
全部留言(9)
- 最新
- 精选
- 青史成灰老师,最后的这个例子: ``` class MyClass { constructor() { return new Date }; } class MyClassEx extends MyClass { constructor() { super() }; // or default foo() { console.log('check only'); } } var x = new MyClassEx; console.log('foo' in x); // false ``` 因为`foo`并不在`x`实例上,那假如我要访问`foo`,那得通过什么方式?或者说,那我这个类中定义的`foo`定义到哪里去了?
作者回复: 这个示例中,x与MyClassEx/MyClass这两个类是没有继承关系的,原型继承或类继承的逻辑在这里没有用。例如: ``` > x instanceof MyClass false > x instanceof MyClassEx false ``` 你需要自已来维护原型链/继承关系,例如: ``` class MyClass { constructor() { return Object.setPrototypeOf(new Date, new.target.prototype); } } ... console.log('foo' in x); //true console.log(x instanceof MyClassEx); // true console.log(x instanceof MyClass); // false ``` 因为继承关系跟类声明分开了,所以你可以有很多方法来灵活处理了。
2020-01-1523 - Astrogladiator-埃蒂纳度斯new.target为什么称为元属性,它与a.b(例如 super.xxx,或者’a’.toString)有什么不同? 个人理解是new.target是用来描述构造器本身的属性,指代是当前这个构造器函数this, 它不属于实例对象的一部分,它可以由super函数传递至根类,并最终由根类创建带有子类实例的对象。
作者回复: 谢谢。确实是正确答案之一。
2019-12-193 - 行问这里的代码在 Chrome 或 Node 是报错的 class MyClass extends Object { constructor() { return 1; } } function MyConstructor() { return 1; } console.log(new MyClass) console.log(new MyConstructor)
作者回复: Oh... 这个问题在之前的课程中讲过,所以这里没有说很细。在第13讲中, > 因此到了 ECMAScript 6 之后,那些一般函数,以及非派生类,就延续了这一约定:使用已经创建的this对象来替代返回的无效值。 > ... ... > 对于那些派生的子类(即声明中使用了extends子句的类),ECMAScript 要求严格遵循“不得在构造器中返回非对象值(以及 null 值)”的设计约定,并在这种情况下直接抛出异常。 所以简单地说,就是如果class声明中不使用extends,那么它就跟你示例中的MyConstructor()一样,不会报错。而如果像你的MyClass那样使用了extends,就会报错了。
2019-12-1823 - 小童老师,我在看规范的时候,有那么一句话不理解,状态和方法都会被对象承载,结构,行为和状态都能被继承。 这句怎么理解呢? 比如: function Car(){ } Car.prototype.name="car1"; Car.prototype.run=function(){ console.log("run") } var car1=new Car(); //对象 承载状态和方法 指name 状态 run方法 function Dog(){ } Dog.prototype=Car.prototype; //继承 Dog 继承了Car对象的结构 行为指继承了run方法吗? 状态是name属性吗?
作者回复: 特意找了找你说的这段ECMScript规范的文本。应该是指如下: > In a class-based object-oriented language, in general, state is carried by instances, methods are carried by classes, and inheritance is only of structure and behaviour. > In ECMAScript, the state and methods are carried by objects, while structure, behaviour, and state are all inherited. 这里分成两段,分别是指的基于类继承的对象系统和ECMAScript中的对象系统。在这里,讲述ECMAScript中的(基于原型继承的)对象系统时也使用了原来在类继承中的概念,例如state、methods、structure和behaviour等。 首先,“状态(state)”这种说法来自结构化程序设计,我在《程序原本》中也用了“状态”这个概念。如果放在JS里面,状态就是一般属性,包括作为一般属性来理解的“函数类型的属性”。状态就是数据(因为数据是可变的,所以称为状态),所以JavaScript的对象是“属性包”,也就可以理解为“状态(数据)的集合”。 第二个需要说明的概念是“结构(structure)”。这个结构就是“结构化程序设计”中的结构。在这个概念体系里面,结构就是“数据被组织起来的样式”,所以“对象的结构”,本质上就是“对象包括属性的样式”。例如说,我们下面的代码: ``` obj = { a: 1, b: 2 } ``` 这是定义了对象obj的结构。而其中: ``` obj.a = 100 obj.b = 200 ``` 则是定义或修改了obj的两个状态(属性)。 那么为什么要这样来区分概念呢?这是因为到了ES6之后,解构赋值或解构表达式来声明的参数表,就是利用了对象的“结构性”而实现的功能。例如: ``` // 解构赋值,利用对象obj的结构性 let {a, b} = obj console.log(a, b) // 在参数上的声明 function foo({a}) { ... } foo(obj); ``` 所以,结构就是指类似上述赋值模板中的“结构声明”。它的继承性就是指:子类对象与原型具有相似的结构。 回到你的问题,还有一个就是“方法/行为”。“方法(method)”这个词在OOP中是特指的,就是指对象实例上可以调用的那些函数——当然,这包括函数类型的属性,以及在类声明中直接声明的方法以及get/setter。 状态和方法都会被对象承载(state and methods are carried by objects),这句话反过来说就是“对象(objects)包括方法和状态(属性)”。 而“行为”则是“在概念层面上讨论OOP”时的一个用词。它指的是一个对象的能力,比如被调用或者被别的对象关联等等。我在讲OOP——例如第14讲——的时候也会用到“行为/能力”这个词,表达的都是相同的意思。说得简单一点,“行为”就是“方法(的能力)”的抽象概念。 所以“结构,行为和状态都能被继承(structure, behaviour, and state are all inherited)”意思就是指一个对象的表现样式、行为方法和状态属性,都是可以继承的。 最后,最开始的两段英文是对比着来读的。它的意思是类继承通常只继承样式和行为,而不继承属性;而JS的原型继承是三者都继承的。简单的示例如下: ``` // 原型继承 parent = { a: 100, foo: function() {} } x = Object.create(parent) console.log(x.a); // 继承属性 console.log(x.foo); // 继承行为 console.log('a' in obj, 'foo' in obj); // 继承结构 // 类继承(某些经典系统中) x = new MyObject() x.foo(); // okay `foo` in x; // okay console.log(x.a); // maybe support ... ``` 最后这个例子是指:在某些类继承体系中,“继承来的属性”可能并没有初始化,而仅仅是一个未初始化的状态(例如在Delphi中就有类似的性质)。
2020-04-2422 - James老师,在看react源码的context时候,遇到一个问题,简化如下,如果var a = {}, a.a =a,最终a.a.a...好像会无限下去,这样,会不会执行这个代码的时候,就内存泄漏了啊,如果造成内存泄漏的化,怎么会在react源码里面呢? export function createContext<T>( defaultValue: T, calculateChangedBits: ?(a: T, b: T) => number, ): ReactContext<T> { const context: ReactContext<T> = { $$typeof: REACT_CONTEXT_TYPE, _calculateChangedBits: calculateChangedBits, _currentValue: defaultValue, _currentValue2: defaultValue, Provider: (null: any), Consumer: (null: any), }; context.Provider = { $$typeof: REACT_PROVIDER_TYPE, _context: context, }; let hasWarnedAboutUsingNestedContextConsumers = false; let hasWarnedAboutUsingConsumerProvider = false; context.Consumer = context; return context; }
作者回复: 如果在一个循环中,并且是在该循环中深度遍历a.a,那么就会死循环。 这个正好在编程概念中是有的,叫做“循环引用(deep-circular-references)”。循环引用不仅仅发生在你的例子中,在一般的对象声明,以及JSON格式的数据中都可以存在,在数据结构中,也是数据常见的性质。 如果一个数据内有循环引用,那么它的数据遍历就会死循环。因此需要在代码中尝试去检测“遍历历史中是否存在相同结点”,这个代价是极高的。因此绝大多数系统中都会要求数据在产生时不要搞成循环引用的。——也就是保证数据干净。但某些情况下,也需要处理未知的、无法确保干净的数据,这时要么不做遍历,要么就在遍历中处理。——在数据结构的图论中,有许多“有向有环图”的处理,就是这类算法与机制。 回到你的问题本身。“循环引用”本身的存在,并不会导致泄露或溢出。这很简单,任何一个结点,其.next属性指向自身,那么就简单地构成了这种属性引用(也就是a.a = a),那么它的内存占用就恒为“2个元素(自身和next属性)”,这怎么可能泄露或溢出呢?很简单的、有限个成员的数据结构而已嘛。 所以不深度遍历它就不会出问题。又如果需要深度遍历,那么就需要相应的算法或处理机制。
2020-03-232 - Elmer定制的构造方法中,如果返回通过return传出的对象(也就是一个用户定制的创建过程),这个时候返回的对象原型并不是子类的原型,那不是不需要再设置this的原型了吗。。 function test (){ return {a: 1}; } const b = new test(); b instanceof test // false;此时如果test有父类也不需要设置this的原型?
作者回复: 是的。不需要。JavaScirpt确实允许“类”返回“任意对象”。从JavaScript 1.2的设计开始,就一直如此。
2020-01-071 - 小炭“迄今为止,new.target是 JavaScript 中唯一的一个元属性。” 对这句话有疑惑,就是下面这些不是元属性吗? { value: 123, writable: false, enumerable: true, configurable: false, get: undefined, set: undefined }
作者回复: 至ecmascript 2019规范中只有一个明确称为元属性的东西,就是new.target。
2020-11-10 - HoSalt// 在JavaScript内置类Date()中可能的处理逻辑 function _Date() { this = Object.Create(Date.prototype, { _internal_slots }); Object.setPrototypeOf(this, new.target.prototype); ... } 1. Create应该是小写 2. 这段代码前面设置的__proto__会被后面的覆盖吧,下面这样实现没问题吧,和继承null那个例子类似 function _Date() { this = Object.create(new.target.prototype); ... }
作者回复: 1. 谢谢。我请编辑改一下。 2. 不对的。_Date()表达的意思就是 1). 拿Date.prototype来创建实例,因为Date()内部根本不会用一个外部的原型来创建实例。 2). 将创建实例this的原型指向new.target.prototype 根本上来说,Date()只能保证用Date.prototype来创建实例是“正确的”,而不保证能用new.target.prototype创建出正确的实例。设计上来说,new.target.prototype是什么,是未知的,对于Date()来说它不可依赖。
2020-05-25 - qqq因为可以改变默认的原型继承行为2019-12-18