JavaScript原型系统可以做什么超越模仿古典类系统?

原型系统比传统的课堂系统看起来更加灵活,但是人们似乎对所谓的“最佳实践”感到满足,它模仿了传统的课堂系统:

function foo() { // define instance properties here } foo.prototype.method = //define instance method here new foo() 

原型系统必须具有其他所有的灵活性。

除了模仿课程之外,是否有用于原型系统? 什么样的事情可以做原型做哪些类不能或没有?

原型系统提供了一个迷人的元编程模型,通过实现标准对象的inheritance。 当然,这主要是用来expression实例类的既定和简单的概念,但是没有类作为需要特定语法来创build它们的语言级不可变结构。 通过使用简单的对象,你可以对对象做的所有事情(你可以做所有事情),你现在可以做“类” – 这是你所说的灵活性。

然后,这种灵活性被用来以编程方式扩展和改变类,只使用JavaScript给定的对象变异function:

  • mixin和性状用于多重遗传
  • 原型可以在从它们inheritance的对象被实例化之后被修改
  • 更高级的函数和方法装饰器可以很容易地用于创build原型

当然,原型模型本身比实现类更强大。 这些特性很less被使用,因为这个类的概念是非常有用和广泛的,所以原型inheritance的实际能力并不是众所周知的(在JS引擎中没有很好的优化: – /)

  • 切换出现有对象的原型可以用来显着改变它们的行为。 (完全支持与ES6 Reflect.setPrototypeOf
  • 一些软件工程模式可以直接用对象来实现。 例子是具有属性的flyweight模式 ,包括dynamic链的责任链,哦,当然还有原型模式 。

    最后一个很好的例子是具有默认值的选项对象。 每个人都使用它们创build

     var myOptions = extend({}, defaultOptions, optionArgument); 

    但更dynamic的方法是使用

     var myOptions = extend(Object.create(defaultOptions), optionArgument); 

回到2013年6月,我回答了一个关于原型遗传相对于古典遗传的好处的问题。 从那以后,我花了很多时间思考inheritance,无论是原型的还是经典的,我都广泛地写了关于原型 类同 构的内容 。

是的,原型inheritance的主要用途是模拟类。 但是,它可以被用于比模拟类更多的地方。 例如,原型链与范围链非常相似。

原型范围也是同构的

JavaScript中的原型和范围有很多共同之处。 JavaScript中有三种常见的链式:

  1. 原型链。

     var foo = {}; var bar = Object.create(foo); var baz = Object.create(bar); // chain: baz -> bar -> foo -> Object.prototype -> null 
  2. 范围链。

     function foo() { function bar() { function baz() { // chain: baz -> bar -> foo -> global } } } 
  3. 方法链。

     var chain = { foo: function () { return this; }, bar: function () { return this; }, baz: function () { return this; } }; chain.foo().bar().baz(); 

三种原型链和范围链是最相似的。 事实上,您可以使用臭名昭 with声明将原型链附加到作用域链。

 function foo() { var bar = {}; var baz = Object.create(bar); with (baz) { // chain: baz -> bar -> Object.prototype -> foo -> global } } 

那么原型范围同构的用法是什么呢? 一个直接的用途是使用原型链build立范围链。 这正是我为自己的编程语言Bianca所做的,我在JavaScript中实现了它。

我首先定义了Bianca的全局范围,在适当命名为global.js的文件中填充了一些有用的math函数,如下所示:

 var global = module.exports = Object.create(null); global.abs = new Native(Math.abs); global.acos = new Native(Math.acos); global.asin = new Native(Math.asin); global.atan = new Native(Math.atan); global.ceil = new Native(Math.ceil); global.cos = new Native(Math.cos); global.exp = new Native(Math.exp); global.floor = new Native(Math.floor); global.log = new Native(Math.log); global.max = new Native(Math.max); global.min = new Native(Math.min); global.pow = new Native(Math.pow); global.round = new Native(Math.round); global.sin = new Native(Math.sin); global.sqrt = new Native(Math.sqrt); global.tan = new Native(Math.tan); global.max.rest = { type: "number" }; global.min.rest = { type: "number" }; global.sizeof = { result: { type: "number" }, type: "function", funct: sizeof, params: [{ type: "array", dimensions: [] }] }; function Native(funct) { this.funct = funct; this.type = "function"; var length = funct.length; var params = this.params = []; this.result = { type: "number" }; while (length--) params.push({ type: "number" }); } function sizeof(array) { return array.length; } 

请注意,我使用Object.create(null)创build了全局作用域。 我这样做是因为全局范围没有任何父范围。

之后,我为每个程序创build了一个单独的程序范围,它包含程序的顶级定义。 代码存储在一个名为analyzer.js的文件中,这个文件太大而不适合一个答案。 这里是文件的前三行:

 var parse = require("./ast"); var global = require("./global"); var program = Object.create(global); 

如您所见,全局范围是程序范围的父项。 因此, programglobalinheritance,使得范围variables查找与对象属性查找一样简单。 这使得语言的运行时变得更简单。

程序范围包含程序的顶层定义。 例如,考虑存储在matrix.bianca文件中的以下matrix乘法程序:

 col(a[3][3], b[3][3], i, j) if (j >= 3) a a[i][j] += b[i][j] col(a, b, i, j + 1) row(a[3][3], b[3][3], i) if (i >= 3) a a = col(a, b, i, 0) row(a, b, i + 1) add(a[3][3], b[3][3]) row(a, b, 0) 

顶级定义是colrowadd 。 每个函数都有自己的函数范围,并且从程序范围inheritance。 这个代码可以在analyzer.js的第67行find:

 scope = Object.create(program); 

例如, add的函数范围具有matrixab的定义。

因此,除了类之外,原型对build模函数作用域也是有用的。

用于build模代数数据types的原型

类不是唯一可用的抽象types。 在函数式编程语言中,数据是使用代数数据typesbuild模的。

代数数据types的最好例子是一个列表:

 data List a = Nil | Cons a (List a) 

这个数据定义仅仅意味着a的列表可能是一个空列表(即Nil ),或者是一个插入a列表(即Cons a (List a) )的types“a”的值。 例如,以下是所有列表:

 Nil :: List a Cons 1 Nil :: List Number Cons 1 (Cons 2 Nil) :: List Number Cons 1 (Cons 2 (Cons 3 Nil)) :: List Number 

数据定义中的typesvariablesa使参数多态 (即允许列表保存任何types的值)。 例如, Nil可以专门用于数字列表或布尔值列表,因为它具有typesList a ,其中a可以是任何东西。

这允许我们创buildlength等参数函数:

 length :: List a -> Number length Nil = 0 length (Cons _ l) = 1 + length l 

length函数可以用来查找任何列表的长度,而不pipe它包含的值的types,因为length函数根本不关心列表的值。

除参数多态外,大多数函数式编程语言还具有某种forms的特定多态性 。 在ad-hoc多态中,根据多态variables的types来select函数的一个具体实现。

例如,JavaScript中的+运算符用于添加和string连接,具体取决于参数的types。 这是一种特殊的多态性。

同样,在函数式编程语言中, map函数通常被重载。 例如,你可能有不同的列表map实现,集合的不同实现等等。types类是实现ad-hoc多态的一种方法。 例如, Functortypes类提供了map函数:

 class Functor f where map :: (a -> b) -> fa -> fb 

然后,我们为不同的数据types创buildFunctor特定实例:

 instance Functor List where map :: (a -> b) -> List a -> List b map _ Nil = Nil map f (Cons al) = Cons (fa) (map fl) 

JavaScript中的原型允许我们对代数数据types和ad-hoc多态性进行build模。 例如,上面的代码可以一对一转换为JavaScript,如下所示:

 var list = Cons(1, Cons(2, Cons(3, Nil))); alert("length: " + length(list)); function square(n) { return n * n; } var result = list.map(square); alert(JSON.stringify(result, null, 4)); 
 <script> // data List a = Nil | Cons a (List a) function List(constructor) { Object.defineProperty(this, "constructor", { value: constructor || this }); } var Nil = new List; function Cons(head, tail) { var cons = new List(Cons); cons.head = head; cons.tail = tail; return cons; } // parametric polymorphism function length(a) { switch (a.constructor) { case Nil: return 0; case Cons: return 1 + length(a.tail); } } // ad-hoc polymorphism List.prototype.map = function (f) { switch (this.constructor) { case Nil: return Nil; case Cons: return Cons(f(this.head), this.tail.map(f)); } }; </script> 

我认为原型inheritance系统允许更加dynamic地添加方法/属性。

你可以很容易地扩展其他人编写的类,例如所有的jQuery插件,你也可以很容易地添加到本地类,添加实用函数的string,数组,以及任何东西。

例:

 // I can just add whatever I want to anything I want, whenever I want String.prototype.first = function(){ return this[0]; }; 'Hello'.first() // == 'H' 

你也可以复制其他类的方法,

 function myString(){ this[0] = '42'; } myString.prototype = String.prototype; foo = new myString(); foo.first() // == '42' 

这也意味着你可以一个对象inheritance它之后扩展一个原型,但是这些改变将被应用。

而且,个人而言,我发现原型非常方便和简单,在一个物体中放置方法真的很吸引我;)

在JavaScript中,没有这样的Class的概念。 这里一切都是对象。 而且JavaScript中的所有对象都是Object的inheritance者。 原型属性有助于inheritance,当我们以面向对象的方式开发应用程序。 原型中比传统的面向对象的结构有更多的特征。

在原型中,你可以添加属性到其他人写的function。

例如。

 Array.prototype.print=function(){ console.log(this); } 

在inheritance中使用:

您可以通过使用prototype属性来使用inheritance。 这里是如何使用JavaScript的inheritance。

在传统的类系统中,一旦定义了类,就不能修改。 但是在你可以用JavaScript来做原型系统。