gsk*_*lee 65 javascript prototype prototype-programming
原型系统看起来比传统的类系统更灵活,但人们似乎对所谓的"最佳实践"感到满意,它模仿了传统的类系统:
function foo() {
// define instance properties here
}
foo.prototype.method = //define instance method here
new foo()
Run Code Online (Sandbox Code Playgroud)
原型系统必须具备其他所有灵活性.
在模仿课程之外是否有用于原型系统的用途?什么样的东西原型可以做哪些类不能,或者没有?
Ber*_*rgi 46
原型系统通过标准对象实现继承,提供了一种迷人的元编程模型.当然,这主要用于表达已建立的简单实例类概念,但没有类作为语言级不可变结构,需要特定语法来创建它们.通过使用普通对象,您可以对对象执行所有操作(并且您可以执行所有操作),现在您可以对"类"执行操作 - 这是您所说的灵活性.
然后,仅使用JavaScript的给定对象变异功能,以编程方式使用这种灵活性来扩展和更改类:
当然,原型模型本身比仅实现类更强大.这些特性很少使用,因为类概念非常有用且广泛,因此原型继承的实际功能并不为人所知(并且在JS引擎中没有很好地优化: - /)
Reflect.setPrototypeOf)一些软件工程模式可以直接用对象实现.例如具有属性的flyweight模式,包括动态链的职责链,哦,当然还有原型模式.
最后一个的一个很好的例子是具有默认值的选项对象.每个人都用它来创造它们
var myOptions = extend({}, defaultOptions, optionArgument);
Run Code Online (Sandbox Code Playgroud)
但更有活力的方法是使用
var myOptions = extend(Object.create(defaultOptions), optionArgument);
Run Code Online (Sandbox Code Playgroud)Aad*_*hah 32
早在2013年6月,我回答了一个关于原型继承优于经典原型的问题.从那以后,我花了很多时间思考继承,包括原型和经典,并且我写了很多关于原型 - 类同 构的文章.
是的,原型继承的主要用途是模拟类.但是,它可以用于更多,而不仅仅是模拟类.例如,原型链与范围链非常相似.
JavaScript中的原型和范围有很多共同之处.JavaScript中有三种常见的链类型:
原型链.
var foo = {};
var bar = Object.create(foo);
var baz = Object.create(bar);
// chain: baz -> bar -> foo -> Object.prototype -> null
Run Code Online (Sandbox Code Playgroud)范围链.
function foo() {
function bar() {
function baz() {
// chain: baz -> bar -> foo -> global
}
}
}
Run Code Online (Sandbox Code Playgroud)方法链.
var chain = {
foo: function () {
return this;
},
bar: function () {
return this;
},
baz: function () {
return this;
}
};
chain.foo().bar().baz();
Run Code Online (Sandbox Code Playgroud)在这三个中,原型链和范围链是最相似的.实际上,您可以使用臭名昭着的 with语句将原型链附加到范围链.
function foo() {
var bar = {};
var baz = Object.create(bar);
with (baz) {
// chain: baz -> bar -> Object.prototype -> foo -> global
}
}
Run Code Online (Sandbox Code Playgroud)
那么原型范围同构的用途是什么?一个直接用途是使用原型链对范围链进行建模.这正是我为自己的编程语言Bianca所做的,我用JavaScript实现了它.
我首先定义了Bianca的全局范围,在一个名为global.js的文件中用一堆有用的数学函数填充它,如下所示:
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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,我使用了创建全局范围Object.create(null).我这样做是因为全局范围没有任何父范围.
之后,对于每个程序,我创建了一个单独的程序范围,其中包含程序的顶级定义.代码存储在名为analyzer.js的文件中,该文件太大而无法放入一个答案中.以下是该文件的前三行:
var parse = require("./ast");
var global = require("./global");
var program = Object.create(global);
Run Code Online (Sandbox Code Playgroud)
如您所见,全局范围是程序范围的父级.因此,program继承自global使范围变量查找与对象属性查找一样简单.这使得语言的运行时间更加简单.
程序范围包含程序的顶级定义.例如,考虑以下矩阵乘法程序,该程序存储在matrix.bianca文件中:
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)
Run Code Online (Sandbox Code Playgroud)
顶级定义是col,row和add.这些函数中的每一个都有自己的函数作用域,它继承自程序范围.可以在analyzer.js的第67行找到该代码:
scope = Object.create(program);
Run Code Online (Sandbox Code Playgroud)
例如,函数范围add具有矩阵a和b.的定义.
因此,除了类之外,原型对于函数范围的建模也很有用.
类不是唯一可用的抽象类型.在函数式编程语言中,使用代数数据类型对数据进行建模.
代数数据类型的最佳示例是列表:
data List a = Nil | Cons a (List a)
Run Code Online (Sandbox Code Playgroud)
该数据定义仅仅意味着a的列表可以是空列表(即Nil),或者是插入到列表中的类型"a"的值(即Cons a (List 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
Run Code Online (Sandbox Code Playgroud)
a数据定义中的类型变量启用参数多态(即它允许列表保存任何类型的值).例如,Nil可能是专门为数字列表或布尔值的列表,因为它的类型是List a在那里a可以是任何东西.
这允许我们创建参数函数,如length:
length :: List a -> Number
length Nil = 0
length (Cons _ l) = 1 + length l
Run Code Online (Sandbox Code Playgroud)
该length函数可用于查找任何列表的长度,而不管其包含的值的类型,因为该length函数根本不关心列表的值.
除参数多态性外,大多数函数式编程语言也具有某种形式的ad-hoc多态性.在ad-hoc多态性中,根据多态变量的类型选择函数的一个特定实现.
例如,+JavaScript中的运算符用于加法和字符串连接,具体取决于参数的类型.这是ad-hoc多态的一种形式.
类似地,在函数式编程语言中,map函数通常被重载.例如,您可能有不同的map列表实现,集合的不同实现等.类型类是实现ad-hoc多态的一种方法.例如,Functor类类提供了以下map功能:
class Functor f where
map :: (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
然后,我们Functor为不同的数据类型创建特定的实例:
instance Functor List where
map :: (a -> b) -> List a -> List b
map _ Nil = Nil
map f (Cons a l) = Cons (f a) (map f l)
Run Code Online (Sandbox Code Playgroud)
JavaScript中的原型允许我们对代数数据类型和ad-hoc多态进行建模.例如,上面的代码可以一对一翻译成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));Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
虽然类也可用于建模ad-hoc多态,但所有重载函数都需要在一个地方定义.使用原型,您可以在任何地方定义它们.
如您所见,原型非常通用.是的,它们主要用于模拟类.但是,它们可以用于许多其他事情.
原型的一些其他东西可用于:
使用结构共享创建持久数据结构.
结构共享的基本思想是,不是修改对象,而是创建一个从原始对象继承并进行所需修改的新对象.原型继承擅长于此.
正如其他人所提到的,原型是动态的.因此,您可以追溯添加新的原型方法,它们将在原型的所有实例上自动提供.
希望这可以帮助.
the*_*sti 11
我认为原型继承系统允许更加动态地添加方法/属性.
您可以轻松扩展其他人编写的类,例如所有jQuery插件,您还可以轻松添加到本机类,将实用程序函数添加到字符串,数组以及任何内容.
例:
// I can just add whatever I want to anything I want, whenever I want
String.prototype.first = function(){ return this[0]; };
'Hello'.first() // == 'H'
Run Code Online (Sandbox Code Playgroud)
您还可以从其他类复制方法,
function myString(){
this[0] = '42';
}
myString.prototype = String.prototype;
foo = new myString();
foo.first() // == '42'
Run Code Online (Sandbox Code Playgroud)
它还意味着您可以在对象从其继承之后扩展原型,但是将应用这些更改.
而且,就个人而言,我发现原型非常方便和简单,在对象中放置方法对我来说真的很吸引人;)
| 归档时间: |
|
| 查看次数: |
2244 次 |
| 最近记录: |