function Circle(x, y, r) {
Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
this.r= r;
}
Circle.prototype= new Shape();
在向其添加方法之前:
Circle.prototype.toString= function() {
return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}
这个例子可以工作,你会在许多教程中看到类似的代码 . 但是,那个 new Shape() 很难看:即使没有创建实际的Shape,我们也要实例化基类 . 它恰好在这个简单的情况下工作,因为JavaScript是如此草率:它允许传入零参数,在这种情况下 x 和 y 变为 undefined 并分配给原型的 this.x 和 this.y . 如果构造函数正在做任何更复杂的事情,那么它的表面就会变得平坦 .
function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!
闭包方法也有很多变种 . 您可能更喜欢完全省略 this ,创建一个新的 that 并返回它而不是使用 new 运算符:
function Shape(x, y) {
var that= {};
that.x= x;
that.y= y;
that.toString= function() {
return 'Shape at '+that.x+', '+that.y;
};
return that;
}
function Circle(x, y, r) {
var that= Shape(x, y);
that.r= r;
var _baseToString= that.toString;
that.toString= function() {
return 'Circular '+_baseToString(that)+' with radius '+r;
};
return that;
};
var mycircle= Circle(); // you can include `new` if you want but it won't do anything
function MyThing(aParam) {
var myPrivateVariable = "squizzitch";
this.someProperty = aParam;
this.useMeAsACallback = function() {
console.log("Look, I have access to " + myPrivateVariable + "!");
}
}
// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
console.log(this.someProperty);
};
// Build-Reveal
var person={
create:function(_name){ // 'constructor'
// prevents direct instantiation
// but no inheritance
return (function() {
var name=_name||"defaultname"; // private variable
// [some private functions]
function getName(){
return name;
}
function setName(_name){
name=_name;
}
return { // revealed functions
getName:getName,
setName:setName
}
})();
}
}
// … no (instantiated) person so far …
var p=person.create(); // name will be set to 'defaultname'
p.setName("adam"); // and overwritten
var p2=person.create("eva"); // or provide 'constructor parameters'
alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"
function outer(outerArg) {
return inner(innerArg) {
return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context
}
}
不久之前,我偶然发现了Mozilla关于Closure的文章 . 以下是我的观点:“闭包允许您将一些数据(环境)与对该数据进行操作的函数相关联 . ” This has obvious parallels to object oriented programming, where objects allow us to associate some data (the object's properties) with one or more methods “ . 这是我第一次阅读闭包和经典OOP之间的并行性而没有引用原型 .
var Klass = function Klass() {
var thus = this;
var somePublicVariable = x
, somePublicVariable2 = x
;
var somePrivateVariable = x
, somePrivateVariable2 = x
;
var privateMethod = (function p() {...}).bind(this);
function publicMethod() {...}
// export precepts
this.var1 = somePublicVariable;
this.method = publicMethod;
return this;
};
var Super = function Super() {
...
this.inherited = true;
...
};
var Klass = function Klass() {
...
// export precepts
Super.apply(this); // extends this with property `inherited`
...
};
模型设计
var Model = function Model(options) {
var options = options || {};
this.id = options.id || this.id || -1;
this.string = options.string || this.string || "";
// ...
return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true); // > true
设计模式
var Singleton = new (function Singleton() {
var INSTANCE = null;
return function Klass() {
...
// export precepts
...
if (!INSTANCE) INSTANCE = this;
return INSTANCE;
};
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true); // > true
var test = {
useTitle : "Here we use 'a Title' to declare an Object",
'useString': "Here we use 'a String' to declare an Object",
onTitle : function() {
return this.useTitle;
},
onString : function(type) {
return this[type];
}
}
console.log(test.onTitle());
console.log(test.onString('useString'));
15 回答
在JavaScript中实现类和实例有两种模型:原型方法和闭包方式 . 两者都有优点和缺点,并且有很多扩展的变化 . 许多程序员和库具有不同的方法和类处理实用程序功能,可以用来描述该语言的一些较丑陋的部分 .
结果是,在混合公司中,你会有一个混杂的元类,所有表现都略有不同 . 更糟糕的是,大多数JavaScript教程材料都很糟糕,并提供某种中间折衷以覆盖所有基础,让你非常困惑 . (可能作者也很困惑.JavaScript的对象模型与大多数编程语言非常不同,并且在很多地方都是直接设计得很糟糕 . )
让我们从 the prototype way 开始 . 这是您可以获得的最多JavaScript本机:有最少的开销代码,instanceof将使用这种对象的实例 .
我们可以通过将它们写入此构造函数的
prototype
查找来为new Shape
创建的实例添加方法:现在将它子类化,尽可能多地调用JavaScript做的子类化 . 我们通过完全取代那种奇怪的魔法
prototype
属性来做到这一点:在向其添加方法之前:
这个例子可以工作,你会在许多教程中看到类似的代码 . 但是,那个
new Shape()
很难看:即使没有创建实际的Shape,我们也要实例化基类 . 它恰好在这个简单的情况下工作,因为JavaScript是如此草率:它允许传入零参数,在这种情况下x
和y
变为undefined
并分配给原型的this.x
和this.y
. 如果构造函数正在做任何更复杂的事情,那么它的表面就会变得平坦 .所以我们需要做的是找到一种方法来创建一个原型对象,该对象包含我们在类级别所需的方法和其他成员,而无需调用基类的构造函数 . 为此,我们将不得不开始编写帮助程序代码 . 这是我所知道的最简单的方法:
这会将其原型中的基类成员传递给新的构造函数,该函数不执行任何操作,然后使用该构造函数 . 现在我们可以写简单:
而不是
new Shape()
错误 . 我们现在有一组可接受的原语用于构建类 .我们可以在此模型下考虑一些改进和扩展 . 例如,这里是一个语法糖版本:
这两个版本都有缺点,即构造函数不能被继承,因为它在许多语言中都有 . 因此,即使您的子类没有为构造过程添加任何内容,它也必须记住使用基本所需的任何参数调用基础构造函数 . 这可以使用
apply
稍微自动化,但仍然需要写出来:因此,一个常见的扩展是将初始化内容分解为自己的函数而不是构造函数本身 . 这个函数可以从基数继承就好了:
现在我们必须继续键入它,例如而不是
Function.prototype.subclass
,将它转向并让基类的函数吐出子类:...它开始看起来更像其他语言,虽然语法略显笨拙 . 如果您愿意,可以添加一些额外的功能 . 也许你想要
makeSubclass
来记住一个类名并使用它来提供一个默认的toString
. 也许你想让构造函数检测到它在没有new
运算符的情况下被意外调用(否则通常会导致非常烦人的调试):也许你想要传递所有新成员并将
makeSubclass
添加到原型中,以节省你必须非常多地编写Class.prototype...
. 很多类系统都这样做,例如:您可能会考虑很多潜在的功能在一个对象系统中是理想的,没有人真正同意一个特定的公式 .
closure way ,然后 . 这可以避免JavaScript基于原型的继承问题,完全不使用继承 . 代替:
现在
Shape
的每个实例都有自己的toString
方法副本(以及我们添加的任何其他方法或其他类成员) .每个实例都有自己的每个类成员副本的坏处是它的效率较低 . 如果您正在处理大量的子类实例,原型继承可能会更好地为您服务 . 同样调用基类的方法有点烦人,你可以看到:我们必须记住在子类构造函数覆盖它之前该方法是什么,否则它会丢失 .
[也因为这里没有继承,
instanceof
运算符不起作用;如果需要,你必须提供自己的类嗅探机制 . 虽然你可以用与原型继承类似的方式来调整原型对象,但是它有点棘手,并且只是为了让instanceof
工作起来并不值得 .每个实例都有自己的方法的好处是该方法可以绑定到拥有它的特定实例 . 这很有用,因为JavaScript在方法调用中绑定
this
这种奇怪的方式,如果你从它的所有者那里分离出一个方法,结果是:然后
this
方法里面赢了't be the Circle instance as expected (it' ll实际上是全局window
对象,导致广泛调试祸患) . 实际上,这通常发生在采用方法并将其分配给setTimeout
,onclick
或EventListener
时 .使用原型方法,您必须为每个此类任务包含一个闭包:
或者,在将来(或现在,如果你攻击Function.prototype),你也可以使用
function.bind()
:如果你的实例是以闭包的方式完成的,那么绑定是通过实例变量的闭包来完成的(通常称为
that
或self
,虽然我个人会建议反对后者,因为self
已经在JavaScript中有另一个不同的含义) . 你没有在上面的代码片段中免费获得参数1, 1
,所以你仍然需要另一个闭包或bind()
如果你需要这样做 .闭包方法也有很多变种 . 您可能更喜欢完全省略
this
,创建一个新的that
并返回它而不是使用new
运算符:哪种方式“合适”?都 . 哪个是“最好的”?这取决于你的情况 . FWIW当我做强OO的东西时,我倾向于为真正的JavaScript继承进行原型设计,以及简单的一次性页面效果的闭包 .
但对于大多数程序员来说,这两种方式都是非常直观的 . 两者都有许多潜在的混乱变化 . 如果您使用其他人的代码/库,您将同时遇到这两种(以及许多中间和一般破坏的方案) . 没有一个普遍接受的答案 . 欢迎来到JavaScript对象的精彩世界 .
[这是为什么JavaScript不是我最喜欢的编程语言的第94部分 . ]
我经常使用这种模式 - 我发现它在我需要它时给了我非常大的灵活性 . 在使用中它与Java风格的类非常相似 .
这使用在创建时调用的匿名函数,返回新的构造函数 . 因为匿名函数只被调用一次,所以可以在其中创建私有静态变量(它们位于闭包内部,对类的其他成员可见) . 构造函数基本上是一个标准的Javascript对象 - 您在其中定义私有属性,公共属性附加到
this
变量 .基本上,这种方法将Crockfordian方法与标准Javascript对象相结合,以创建更强大的类 .
您可以像使用任何其他Javascript对象一样使用它:
Douglas Crockford在“好的部分”中广泛讨论了这个话题 . 他建议避免使用 new 运算符来创建新对象 . 相反,他建议创建自定义构造函数 . 例如:
在Javascript中,函数是一个对象,可以用于与 new 运算符一起构造对象 . 按照惯例,旨在用作构造函数的函数以大写字母开头 . 你经常看到这样的事情:
万一你忘记使用 new 运算符在实例化一个新对象时,你得到的是一个普通的函数调用,它被绑定到全局对象而不是新对象 .
继续bobince's answer
在es6中,您现在可以实际创建
class
所以现在你可以这样做:
因此,您可以执行以下操作:(如在另一个答案中):
在es6中结束了一点清洁,更容易阅读 .
这是一个很好的例子:
你也可以这样做,使用结构:
然后 :
当在构造函数调用期间使用关闭“this”的技巧时,它是为了编写一个函数,该函数可以被一些不想在对象上调用方法的其他对象用作回调 . 这与“使范围正确”无关 .
这是一个vanilla JavaScript对象:
您可能会阅读Douglas Crockford对JavaScript的评论 . John Resig也很精彩 . 祝好运!
另一种方式是http://jsfiddle.net/nnUY4/(我不知道这种处理对象创建和显示功能是否遵循任何特定模式)
Closure
是多才多艺的 . bobince 在创建对象时很好地总结了原型与闭包方法 . 但是,您可以使用函数编程方式中的闭包来模仿OOP
的某些方面 . 记住函数是JavaScript中的对象;所以以不同的方式使用函数作为对象 .这是一个关闭的例子:
不久之前,我偶然发现了Mozilla关于Closure的文章 . 以下是我的观点:“闭包允许您将一些数据(环境)与对该数据进行操作的函数相关联 . ” This has obvious parallels to object oriented programming, where objects allow us to associate some data (the object's properties) with one or more methods “ . 这是我第一次阅读闭包和经典OOP之间的并行性而没有引用原型 .
怎么样?
假设您要计算某些项目的增值税 . 增值税可能在申请期限内保持稳定 . 在OOP(伪代码)中执行此操作的一种方法:
基本上,您将VAT值传递给构造函数,并且您的计算方法可以通过闭包对其进行操作 . 现在,不要使用类/构造函数,将VAT作为参数传递给函数 . 因为您感兴趣的唯一东西是计算本身,所以返回一个新函数,即calculate方法:
在您的项目中,确定最适合计算增值税的顶级值 . 根据经验,无论何时打开和打开相同的参数,都有一种方法可以使用闭包来改进它 . 无需创建传统对象 .
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
创建一个对象
在JavaScript中创建对象的最简单方法是使用以下语法:
这非常适合以结构化方式存储数据 .
但是,对于更复杂的用例,创建函数实例通常更好:
这允许您创建共享相同“蓝图”的多个对象,类似于您在例如中使用类的方式 . Java的 .
然而,通过使用原型,这仍然可以更有效地完成 .
只要函数的不同实例共享相同的方法或属性,您就可以将它们移动到该对象的原型 . 这样,函数的每个实例都可以访问该方法或属性,但不需要为每个实例复制它 .
在我们的例子中,将方法
f
移动到原型是有意义的:继承
在JavaScript中进行继承的一种简单但有效的方法是使用以下双线程:
这类似于这样做:
两者之间的主要区别在于
A
的构造函数在使用Object.create时未运行,这更直观且更类似于基于类的继承 .在创建
B
的新实例时,您总是可以选择运行A
的构造函数,方法是将其添加到B
的构造函数中:如果要将
B
的所有参数传递给A
,还可以使用Function.prototype.apply():如果要将另一个对象混合到
B
的构造函数链中,可以将Object.create
与Object.assign组合:演示
注意
Object.create
可以安全地用于每个现代浏览器,包括IE9 .Object.assign
在任何版本的IE或某些移动浏览器中都不起作用 . 建议polyfillObject.create
和/或Object.assign
如果要使用它们并支持不实现它们的浏览器 .您可以找到
Object.create
here的polyfill和Object.assign
here的polyfill .除了2009年接受的答案 . 如果你可以针对现代浏览器,可以使用 Object.defineProperty .
要查看桌面和移动兼容性列表,请参阅Mozilla's Browser Compatibility list . 是的,IE9支持它以及Safari移动 .
你也可以试试这个
一种能为我服务的模式
首先,您可以更改向实例添加方法的首选项,而不是构造函数的
prototype
对象 . 我几乎总是在构造函数内部声明方法,因为我经常使用构造函数劫持来实现有关继承和装饰器的目的 .这是我如何决定在哪些声明中写入:
永远不要直接在上下文对象上声明方法(
this
)设
var
声明优先于function
声明让基元优先于对象(
{}
和[]
)设
public
声明优先于private
声明首选
Function.prototype.bind
thus
,self
,vm
,etc
避免在另一个类中声明一个类,除非:
显然两者是不可分割的
Inner类实现命令模式
Inner类实现Singleton模式
Inner类实现State Pattern
内部类实现了另一种保证这一点的设计模式
始终从关闭空间的词法范围内返回
this
.这就是为什么这些帮助:
构造函数劫持
模型设计
设计模式
正如你所看到的,我真的不需要
thus
因为我更喜欢Function.prototype.bind
(或.call
或.apply
)而不是thus
. 在我们的Singleton
类中,我们甚至没有将它命名为thus
,因为INSTANCE
传达了更多信息 . 对于Model
,我们返回this
,以便我们可以使用.call
调用构造函数来返回我们传递给它的实例 . 冗余地,我们将它分配给变量updated
,尽管它在其他场景中很有用 .另外,我更喜欢在上使用
new
关键字构造对象文字:首选
不首选
如您所见,后者无法覆盖其超类的“覆盖”属性 .
如果我向Class的
prototype
对象添加方法,我更喜欢对象文字 - 使用或不使用new
关键字:首选
不首选
我想提一下,我们可以使用Title或String来声明一个Object .
调用每种类型的方法有不同的方法 . 见下文:
基本上JS中没有类的概念,所以我们使用函数作为与现有设计模式相关的类构造函数 .
直到现在JS都不知道你想要创建一个对象,所以这里有新的关键字 .
参考:适用于Web开发人员的专业JS - Nik Z.