使用 private/protected/public 字段和方法扩展 class
Extend a class with private/protected/public fields and methods
我正在尝试找到一种在 ES6 中创建类似抽象的方法 class。到目前为止,我尝试的一切总是遇到语言的限制 and/or 其语法(也是我对原型制作的有限知识)。
基本 oop;我们声明一个 class 并扩展它。 final class 必须从它的 superclass 访问一些字段和方法,但不是全部。它还变形 public 方法...
class 声明应该是完美的封装,所以除了这段代码之外没有其他东西能够到达它(类似于命名空间的东西)。
到目前为止,我在 ES5 中的实验是错误的...非常感谢您的建议和帮助。
(function(){
// ==================================
function AbstractClass(params) {
var _myParams = params;
var _privateField = "Only AbstractClass can see me";
this.publicField = "Everybody can see me";
function privateFunc() {
// Does private stuff
}
}
AbstractClass.prototype.publicFunc = function() {
// Does public stuff
privateFunc(); // Works?
}
// ==================================
function FinalClass(params) {
// How to pass the params to the superclass?
}
FinalClass.prototype.publicFunc = function() {
// Override and calls the superclass.publicFunc()?
// How can I touch _privateField ? publicField ?
}
FinalClass.prototype = Object.create(AbstractClass.prototype);
// ==================================
var foo = new FinalClass("hello world!");
foo.publicFunc();
})();
你能告诉我这段代码有什么问题以及如何解决吗?
奖励问题:如何在 ES6 中正确执行此操作?
三重福利:受保护的领域和方法呢?
谢谢。
这实际上是一个很好的问题,我会尽量给你一个有见地的答案...
正如我已经在 Stack Overflow 的某个地方解释过的那样,JavaScript 并不是真正基于 class 的语言。它基于 原型 。这是一个完全不同的编程范例,您应该考虑到这一点。所以当你用 Vanilla JS 写东西时,忘记(只是一点点)你对 Java 或 C++ 的了解通常是个好主意。
但是,JavaScript 是一种非常灵活的语言,您可以随心所欲地进行编程。在我看来,Java脚本编程有两种主要风格:惯用风格和classic风格。
- 惯用风格大量使用对象字面量、鸭子类型、工厂函数和组合。
- classic 风格试图模仿基于 class 的语言的行为,并为 classes 和 IIFE(立即调用的函数表达式)构造函数以进行封装。强调继承和多态。
你要的是摘要class。抽象 class 是无法实例化的 class,仅用作派生 classes 的模型。如果你关心严格封装,这就是你如何在 ES5 中实现它:
// ==============================
// ABSTRACT "CLASS"
// ==============================
var OS = (function (n) {
// Here "name" is private because it is encapsulated in the IIFE
var name = "";
// Constructor
function OS (n) {
// If "OS" is called with "new", throw an error
if (this.constructor === OS) {
throw new Error('You cannot instantiate an abstract class!');
}
name = n;
}
// We cannot call this method directly (except with "call" or "apply") because we cannot have direct instances of "OS"
OS.prototype.boot = function () {
return name + ' is booting...';
};
// This is an abstract method. It will be in the prototype of derived objects but should be overriden to work
OS.prototype.shutdown = function () {
throw new Error('You cannot call an abstract method!');
};
// Getter for "name"
OS.prototype.getName = function () {
return name;
};
// The constructor must be returned to be public
return OS;
})();
// ==============================
// CONCRETE "CLASS"
// ==============================
var LinuxDistro = (function (name) {
// Constructor
function LinuxDistro(name) {
// Here we call the constructor of "OS" without "new", so there will not be any error
OS.call(this, name);
}
// Here "Linux Distro" inherits from "OS"
LinuxDistro.prototype = Object.create(OS.prototype);
LinuxDistro.prototype.constructor = LinuxDistro;
// Private function/method
function textTransform(str, style) {
return style === 'lowercase' ? str.toLowerCase() : str.toUpperCase();
}
// The parent method is used and overriden
LinuxDistro.prototype.boot = function () {
return OS.prototype.boot.call(this) + ' Welcome to ' + textTransform(this.getName());
};
// The abstract method is implemented
LinuxDistro.prototype.shutdown = function () {
return 'Shutting down... See you soon on ' + textTransform(this.getName());
};
// The constructor must be returned to be public
return LinuxDistro;
})();
// ==============================
// CLIENT CODE
// ==============================
var arch = new LinuxDistro('Arch Linux');
console.log(arch.getName());
console.log(arch.boot());
console.log(arch.shutdown());
现在你想用 ES6 做同样的事情。好的一点是 ES6 提供了很好的语法糖来与 classes 一起工作。同样,如果你关心严格封装,你可以有以下实现:
// ==============================
// ABSTRACT "CLASS"
// ==============================
const OS = (n => {
// Here "name" is private because it is encapsulated in the IIFE
let name = "";
class OS {
constructor(n) {
// If "OS" is called with "new", throw an error
if (new.target === OS) {
throw new Error('You cannot instantiate an abstract class!');
}
name = n;
}
// We cannot call this method directly (except with "call" or "apply") because we cannot have direct instances of "OS"
boot() {
return `${name} is booting...`;
}
// This is an abstract method. It will be in the prototype of derived objects but should be overriden to work
shutdown() {
throw new Error('You cannot call an abstract method!');
}
// Getter for "name"
get name() {
return name;
}
}
// The class must be returned to be public
return OS;
})();
// ==============================
// CONCRETE "CLASS"
// ==============================
const LinuxDistro = (name => {
// Private function/method
function textTransform(str, style) {
return style === 'lowercase' ? str.toLowerCase() : str.toUpperCase();
}
class LinuxDistro extends OS {
constructor(name) {
// Here we call the constructor of "OS" without "new", so there will not be any error
super(name);
}
// The parent method is used and overriden
boot() {
return `${super.boot()} Welcome to ${textTransform(this.name)}`;
}
// The abstract method is implemented
shutdown() {
return `Shutting down... See you soon on ${textTransform(this.name)}`;
}
}
// The class must be returned to be public
return LinuxDistro;
})();
// ==============================
// CLIENT CODE
// ==============================
const arch = new LinuxDistro('Arch Linux');
console.log(arch.name); // This is not a direct access to "name". The getter is used...
console.log(arch.boot());
console.log(arch.shutdown());
当然,这些片段并不完美,可能看起来有点吓人。但我认为这是我们能做的最好的,因为 JavaScript.
的原型性质
正如您可能看到的,class 成员要么是私有的(由于 IIFE 和闭包),要么是 public(由于对象的创建方式,具有自己的属性和原型链)。如果你真的想要受保护的成员,这是另一回事...
当您为 Java 脚本代码考虑 OOP 模型时,我建议您使用 TypeScript。这比上面提供的代码更方便、可读和可维护。
最后,如果您想进一步了解如何在 JavaScript 中实现所有传统的 OOP 设计模式(尤其是 GoF 模式),我邀请您看一下我的一个项目GitHub: PatternifyJS
我正在尝试找到一种在 ES6 中创建类似抽象的方法 class。到目前为止,我尝试的一切总是遇到语言的限制 and/or 其语法(也是我对原型制作的有限知识)。
基本 oop;我们声明一个 class 并扩展它。 final class 必须从它的 superclass 访问一些字段和方法,但不是全部。它还变形 public 方法...
class 声明应该是完美的封装,所以除了这段代码之外没有其他东西能够到达它(类似于命名空间的东西)。
到目前为止,我在 ES5 中的实验是错误的...非常感谢您的建议和帮助。
(function(){
// ==================================
function AbstractClass(params) {
var _myParams = params;
var _privateField = "Only AbstractClass can see me";
this.publicField = "Everybody can see me";
function privateFunc() {
// Does private stuff
}
}
AbstractClass.prototype.publicFunc = function() {
// Does public stuff
privateFunc(); // Works?
}
// ==================================
function FinalClass(params) {
// How to pass the params to the superclass?
}
FinalClass.prototype.publicFunc = function() {
// Override and calls the superclass.publicFunc()?
// How can I touch _privateField ? publicField ?
}
FinalClass.prototype = Object.create(AbstractClass.prototype);
// ==================================
var foo = new FinalClass("hello world!");
foo.publicFunc();
})();
你能告诉我这段代码有什么问题以及如何解决吗?
奖励问题:如何在 ES6 中正确执行此操作?
三重福利:受保护的领域和方法呢?
谢谢。
这实际上是一个很好的问题,我会尽量给你一个有见地的答案...
正如我已经在 Stack Overflow 的某个地方解释过的那样,JavaScript 并不是真正基于 class 的语言。它基于 原型 。这是一个完全不同的编程范例,您应该考虑到这一点。所以当你用 Vanilla JS 写东西时,忘记(只是一点点)你对 Java 或 C++ 的了解通常是个好主意。
但是,JavaScript 是一种非常灵活的语言,您可以随心所欲地进行编程。在我看来,Java脚本编程有两种主要风格:惯用风格和classic风格。
- 惯用风格大量使用对象字面量、鸭子类型、工厂函数和组合。
- classic 风格试图模仿基于 class 的语言的行为,并为 classes 和 IIFE(立即调用的函数表达式)构造函数以进行封装。强调继承和多态。
你要的是摘要class。抽象 class 是无法实例化的 class,仅用作派生 classes 的模型。如果你关心严格封装,这就是你如何在 ES5 中实现它:
// ==============================
// ABSTRACT "CLASS"
// ==============================
var OS = (function (n) {
// Here "name" is private because it is encapsulated in the IIFE
var name = "";
// Constructor
function OS (n) {
// If "OS" is called with "new", throw an error
if (this.constructor === OS) {
throw new Error('You cannot instantiate an abstract class!');
}
name = n;
}
// We cannot call this method directly (except with "call" or "apply") because we cannot have direct instances of "OS"
OS.prototype.boot = function () {
return name + ' is booting...';
};
// This is an abstract method. It will be in the prototype of derived objects but should be overriden to work
OS.prototype.shutdown = function () {
throw new Error('You cannot call an abstract method!');
};
// Getter for "name"
OS.prototype.getName = function () {
return name;
};
// The constructor must be returned to be public
return OS;
})();
// ==============================
// CONCRETE "CLASS"
// ==============================
var LinuxDistro = (function (name) {
// Constructor
function LinuxDistro(name) {
// Here we call the constructor of "OS" without "new", so there will not be any error
OS.call(this, name);
}
// Here "Linux Distro" inherits from "OS"
LinuxDistro.prototype = Object.create(OS.prototype);
LinuxDistro.prototype.constructor = LinuxDistro;
// Private function/method
function textTransform(str, style) {
return style === 'lowercase' ? str.toLowerCase() : str.toUpperCase();
}
// The parent method is used and overriden
LinuxDistro.prototype.boot = function () {
return OS.prototype.boot.call(this) + ' Welcome to ' + textTransform(this.getName());
};
// The abstract method is implemented
LinuxDistro.prototype.shutdown = function () {
return 'Shutting down... See you soon on ' + textTransform(this.getName());
};
// The constructor must be returned to be public
return LinuxDistro;
})();
// ==============================
// CLIENT CODE
// ==============================
var arch = new LinuxDistro('Arch Linux');
console.log(arch.getName());
console.log(arch.boot());
console.log(arch.shutdown());
现在你想用 ES6 做同样的事情。好的一点是 ES6 提供了很好的语法糖来与 classes 一起工作。同样,如果你关心严格封装,你可以有以下实现:
// ==============================
// ABSTRACT "CLASS"
// ==============================
const OS = (n => {
// Here "name" is private because it is encapsulated in the IIFE
let name = "";
class OS {
constructor(n) {
// If "OS" is called with "new", throw an error
if (new.target === OS) {
throw new Error('You cannot instantiate an abstract class!');
}
name = n;
}
// We cannot call this method directly (except with "call" or "apply") because we cannot have direct instances of "OS"
boot() {
return `${name} is booting...`;
}
// This is an abstract method. It will be in the prototype of derived objects but should be overriden to work
shutdown() {
throw new Error('You cannot call an abstract method!');
}
// Getter for "name"
get name() {
return name;
}
}
// The class must be returned to be public
return OS;
})();
// ==============================
// CONCRETE "CLASS"
// ==============================
const LinuxDistro = (name => {
// Private function/method
function textTransform(str, style) {
return style === 'lowercase' ? str.toLowerCase() : str.toUpperCase();
}
class LinuxDistro extends OS {
constructor(name) {
// Here we call the constructor of "OS" without "new", so there will not be any error
super(name);
}
// The parent method is used and overriden
boot() {
return `${super.boot()} Welcome to ${textTransform(this.name)}`;
}
// The abstract method is implemented
shutdown() {
return `Shutting down... See you soon on ${textTransform(this.name)}`;
}
}
// The class must be returned to be public
return LinuxDistro;
})();
// ==============================
// CLIENT CODE
// ==============================
const arch = new LinuxDistro('Arch Linux');
console.log(arch.name); // This is not a direct access to "name". The getter is used...
console.log(arch.boot());
console.log(arch.shutdown());
当然,这些片段并不完美,可能看起来有点吓人。但我认为这是我们能做的最好的,因为 JavaScript.
的原型性质正如您可能看到的,class 成员要么是私有的(由于 IIFE 和闭包),要么是 public(由于对象的创建方式,具有自己的属性和原型链)。如果你真的想要受保护的成员,这是另一回事...
当您为 Java 脚本代码考虑 OOP 模型时,我建议您使用 TypeScript。这比上面提供的代码更方便、可读和可维护。
最后,如果您想进一步了解如何在 JavaScript 中实现所有传统的 OOP 设计模式(尤其是 GoF 模式),我邀请您看一下我的一个项目GitHub: PatternifyJS