写作
这个问题在这里已经有了答案:
构造函数的介绍
如果构造函数名为Person,那么可以使用函数作为构造函数来创建对象,然后使用该构造函数创建的对象就是Person的实例。
var Person = function(name){
this.name = name;
};
Person.prototype.walk=function(){
this.step().step().step();
};
var bob = new Person("Bob");
Person是构造函数。 当您使用Person创建实例时,您必须使用new关键字:
var bob = new Person("Bob");console.log(bob.name);//=Bob
var ben = new Person("Ben");console.log(ben.name);//=Ben
属性/成员name
是特定于实例的,它与bob和ben不同
成员walk
是Person.prototype的一部分,并且为所有实例共享bob和Ben是Person的实例,因此他们共享漫步成员(bob.walk === ben.walk)。
bob.walk();ben.walk();
因为直接在bob上找不到walk(),JavaScript会在Person.prototype中查找它,因为这是bob的构造函数。 如果找不到,它会在Object.prototype上查找。 这被称为原型链。 继承的原型部分是通过延长这个链来完成的; 例如bob => Employee.prototype => Person.prototype => Object.prototype(更多关于继承)。
即使bob,ben和所有其他创建的Person实例共享walk,该函数的行为在每个实例中都会有所不同,因为在walk函数中它会使用this
。 的值this
将是调用对象; 现在让我们说这是当前的实例,所以对于bob.walk()
“这个”将是bob。 (更多关于“this”和稍后的调用对象)。
如果本正在等待红灯,并且鲍勃处于绿灯下, 那么你会在ben和bob上调用walk(),但明显和ben和bob会有不同。
阴影成员时,我们做这样的事情发生ben.walk=22
,即使鲍勃和Ben股walk
的22分配给ben.walk不会影响bob.walk。 这是因为该声明将创建一个名为walk
on ben的成员,并将其赋值为22.将会有2个不同的步行成员:ben.walk和Person.prototype.walk。
当询问bob.walk时,您将获得Person.prototype.walk功能,因为在bob上无法找到walk
。 然而,询问ben.walk会给你带来22的价值,因为会员散步已经在ben上创建,并且由于JavaScript发现它走在ben上,它不会在Person.prototype中查看。
当使用带有2个参数的Object.create时,Object.defineProperty或Object.defineProperties的阴影效果有点不同。 更多信息在这里。
更多关于原型
一个对象可以通过使用原型继承另一个对象。 您可以使用Object.create
设置任何其他对象的原型。 在构造函数的介绍中,我们已经看到,如果在对象上找不到成员,那么JavaScript会在prototpe链中查找它。
在前面的部分中,我们已经看到,重新分配来自实例原型(ben.walk)的成员将会影响该成员(创建散步而不是更改Person.prototype.walk)。
如果我们不重新分配但改变成员呢? 突变是(例如)改变对象的子属性或调用将改变对象值的函数。 例如:
var o = [];
var a = o;
a.push(11);//mutate a, this will change o
a[1]=22;//mutate a, this will change o
以下代码通过变异成员来演示原型成员和实例成员之间的区别。
var person = {
name:"default",//immutable so can be used as default
sayName:function(){
console.log("Hello, I am "+this.name);
},
food:[]//not immutable, should be instance specific
// not suitable as prototype member
};
var ben = Object.create(person);
ben.name = "Ben";
var bob = Object.create(person);
console.log(bob.name);//=default, setting ben.name shadowed the member
// so bob.name is actually person.name
ben.food.push("Hamburger");
console.log(bob.food);//=["Hamburger"], mutating a shared member on the
// prototype affects all instances as it changes person.food
console.log(person.food);//=["Hamburger"]
上面的代码显示ben和bob从人员共享成员。 只有一个人,它被设置为bob和ben的原型(人被用作原型链中的第一个对象来查找实例中不存在的请求的成员)。 上述代码的问题在于bob和ben应该有自己的food
成员。 这是构造函数的功能。它用于创建特定于实例的成员。 您也可以传递参数来设置这些实例特定成员的值。
接下来的代码展示了实现构造函数的另一种方式,语法不同,但想法是一样的:
使用构造函数,您将在以下代码中的第2步中设置原型,我们在第3步中设置原型。
在这段代码中,我已经从原型以及食物中删除了名字,因为无论如何,当创建实例时,您很可能会立即忽略它。 Name现在是一个实例特定的成员,其默认值在构造函数中设置。 Becaus的食品成员也从原型转移到特定成员的实例,在向本食品添加食物时不会影响bob.food。
var person = {
sayName:function(){
console.log("Hello, I am "+this.name);
},
//need to run the constructor function when creating
// an instance to make sure the instance has
// instance specific members
constructor:function(name){
this.name = name || "default";
this.food = [];
return this;
}
};
var ben = Object.create(person).constructor("Ben");
var bob = Object.create(person).constructor("Bob");
console.log(bob.name);//="Bob"
ben.food.push("Hamburger");
console.log(bob.food);//=[]
您可能会遇到类似的模式,这些模式更有助于创建对象和定义对象。
遗产
以下代码显示了如何继承。 这些任务与之前的代码基本相同,只是稍微多一点
使用模式有些人会称之为“古典继承”。 如果你对语法感到困惑,我会很乐意解释更多或者提供不同的模式。
function Hamster(){
this.food=[];
}
function RussionMini(){
//Hamster.apply(this,arguments) executes every line of code
//in the Hamster body where the value of "this" is
//the to be created RussionMini (once for mini and once for betty)
Hamster.apply(this,arguments);
}
//setting RussionMini's prototype
RussionMini.prototype=Object.create(Hamster.prototype);
//setting the built in member called constructor to point
// to the right function (previous line has it point to Hamster)
RussionMini.prototype.constructor=RussionMini;
mini=new RussionMini();
//this.food (instance specic to mini)
// comes from running the Hamster code
// with Hamster.apply(this,arguments);
mini.food.push("mini's food");
//adding behavior specific to Hamster that will still be
// inherited by RussionMini because RussionMini.prototype's prototype
// is Hamster.prototype
Hamster.prototype.runWheel=function(){console.log("I'm running")};
mini.runWheel();//=I'm running
Object.create设置继承的原型部分
这里是关于Object.create的文档,它基本上返回第二个参数(在polyfil中不支持),第一个参数作为返回对象的原型。
如果没有给出第二个参数,它将返回一个带有第一个参数的空对象作为返回对象的原型(在返回对象的原型链中使用的第一个对象)。
有些人会将RussionMini的原型设置为仓鼠实例(RussionMini.prototype = new Hamster())。 这是不可取的,因为即使它实现相同(RussionMini.prototype的原型是Hamster.prototype),它也会将Hamster实例成员设置为RussionMini.prototype的成员。 因此,RussionMini.prototype.food将存在,但是是一个共享成员(请记住“更多关于原型”的bob和ben?)。 当创建一个RussionMini时,食物成员将被遮蔽,因为仓鼠代码与Hamster.apply(this,arguments);
反过来运行this.food = []
但任何仓鼠成员仍然是RussionMini.prototype的成员。
另一个原因可能是,为了创建一个仓鼠,需要对传递的参数进行很多复杂的计算,这些参数可能还不可用,再次,您可以传递虚拟参数,但它可能会不必要地使代码复杂化。
扩展和覆盖父函数
有时children
需要延长parent
职责。
你想要'孩子'(= RussionMini)做额外的事情。 当RussionMini可以调用仓鼠代码来做一些事情,然后做一些额外的事情时,您不需要将仓鼠代码复制并粘贴到RussionMini。
在下面的例子中,我们假设一只仓鼠可以以每小时3公里的速度运行,但Russion mini只能运行一半。 我们可以在RussionMini中硬编码3/2,但如果这个值改变了,我们在需要改变的代码中有多个地方。 以下是我们如何使用Hamster.prototype获得父母(仓鼠)的速度。
var Hamster = function(name){
if(name===undefined){
throw new Error("Name cannot be undefined");
}
this.name=name;
}
Hamster.prototype.getSpeed=function(){
return 3;
}
Hamster.prototype.run=function(){
//Russionmini does not need to implement this function as
//it will do exactly the same as it does for Hamster
//But Russionmini does need to implement getSpeed as it
//won't return the same as Hamster (see later in the code)
return "I am running at " +
this.getSpeed() + "km an hour.";
}
var RussionMini=function(name){
Hamster.apply(this,arguments);
}
//call this before setting RussionMini prototypes
RussionMini.prototype = Object.create(Hamster.prototype);
RussionMini.prototype.constructor=RussionMini;
RussionMini.prototype.getSpeed=function(){
return Hamster.prototype
.getSpeed.call(this)/2;
}
var betty=new RussionMini("Betty");
console.log(betty.run());//=I am running at 1.5km an hour.
缺点是你硬编码Hamster.prototype。 有些模式可以为您提供super
Java的优势。
当继承级别超过2级时(Child => Parent => GrandParent),或者通过实现超级关闭来使用更多资源,我所看到的大多数模式都会中断。
要重写Parent(= Hamster)方法,你可以做同样的事情,但不要做Hamster.prototype.parentMethod.call(this,....
this.constructor
构造函数属性被JavaScript包含在原型中,你可以改变它,但它应该指向构造函数。 所以Hamster.prototype.constructor
应该指向仓鼠。
如果在设置继承的原型部分之后,您应该再次指向正确的功能。
var Hamster = function(){};
var RussionMinni=function(){
// re use Parent constructor (I know there is none there)
Hamster.apply(this,arguments);
};
RussionMinni.prototype=Object.create(Hamster.prototype);
console.log(RussionMinni.prototype.constructor===Hamster);//=true
RussionMinni.prototype.haveBaby=function(){
return new this.constructor();
};
var betty=new RussionMinni();
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//false
console.log(littleBetty instanceof Hamster);//true
//fix the constructor
RussionMinni.prototype.constructor=RussionMinni;
//now make a baby again
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//true
console.log(littleBetty instanceof Hamster);//true
混合插件的“多重继承”
有些东西最好不要继承,如果猫可以移动,然后Cat不应该继承Movable。 猫不是一只可移动的猫,而是一只猫可以移动。 在基于类的语言中,Cat必须实现Movable。 在JavaScript中,我们可以定义Movable并在这里定义实现,Cat可以覆盖,扩展它,也可以是默认实现。
对于Movable,我们有实例特定的成员(如location
)。 而且我们有不是实例特定的成员(如函数move())。 创建实例时,通过调用mxIns(由mixin帮助器函数添加)来设置实例特定成员。 原型成员将使用mixin帮助函数逐个从Movable.prototype的Cat.prototype复制。
var Mixin = function Mixin(args){
if(this.mixIns){
i=-1;len=this.mixIns.length;
while(++i<len){
this.mixIns[i].call(this,args);
}
}
};
Mixin.mix = function(constructor, mix){
var thing
,cProto=constructor.prototype
,mProto=mix.prototype;
//no extending, if multiple prototypes
// have members with the same name then use
// the last
for(thing in mProto){
if(Object.hasOwnProperty.call(mProto, thing)){
cProto[thing]=mProto[thing];
}
}
//instance intialisers
cProto.mixIns = cProto.mixIns || [];
cProto.mixIns.push(mix);
};
var Movable = function(args){
args=args || {};
//demo how to set defaults with truthy
// not checking validaty
this.location=args.location;
this.isStuck = (args.isStuck===true);//defaults to false
this.canMove = (args.canMove!==false);//defaults to true
//speed defaults to 4
this.speed = (args.speed===0)?0:(args.speed || 4);
};
Movable.prototype.move=function(){
console.log('I am moving, default implementation.');
};
var Animal = function(args){
args = args || {};
this.name = args.name || "thing";
};
var Cat = function(args){
var i,len;
Animal.call(args);
//if an object can have others mixed in
// then this is needed to initialise
// instance members
Mixin.call(this,args);
};
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Mixin.mix(Cat,Movable);
var poochie = new Cat({
name:"poochie",
location: {x:0,y:22}
});
poochie.move();
上面是一个简单的实现,用最后混合的任何混合替换相同的命名函数。
这个变量
在所有示例代码中,您将看到this
指的是当前实例。
这个变量实际上指的是调用对象,它指的是该函数之前的对象。
澄清请参阅以下代码:
theInvokingObject.thefunction();
这会引用错误对象的实例通常是附加事件监听器,回调函数或超时时间间隔。 在接下来的两行代码中,我们pass
函数,我们不调用它。 传递函数是: someObject.aFunction
并调用它: someObject.aFunction()
。 this
值并不是指函数被声明的对象,而是指向invokes
它的对象。
setTimeout(someObject.aFuncton,100);//this in aFunction is window
somebutton.onclick = someObject.aFunction;//this in aFunction is somebutton
为了在上面的情况下做到this
,请参考someObject,您可以直接传递闭包而不是函数:
setTimeout(function(){someObject.aFuncton();},100);
somebutton.onclick = function(){someObject.aFunction();};
我喜欢定义返回原型上闭包函数的函数,以精确控制包含在闭包范围中的变量。
var Hamster = function(name){
var largeVariable = new Array(100000).join("Hello World");
// if I do
// setInterval(function(){this.checkSleep();},100);
// then largeVariable will be in the closure scope as well
this.name=name
setInterval(this.closures.checkSleep(this),1000);
};
Hamster.prototype.closures={
checkSleep:function(hamsterInstance){
return function(){
console.log(typeof largeVariable);//undefined
console.log(hamsterInstance);//instance of Hamster named Betty
hamsterInstance.checkSleep();
};
}
};
Hamster.prototype.checkSleep=function(){
//do stuff assuming this is the Hamster instance
};
var betty = new Hamster("Betty");
传递(构造函数)参数
当Child调用Parent( Hamster.apply(this,arguments);
)时,我们假设仓鼠使用与RussionMini相同的参数。 对于调用其他函数的函数,我通常使用另一种方式来传递参数。
我通常将一个对象传递给一个函数,并让该函数改变它所需的任何设置(设置默认值),然后该函数将它传递给另一个函数,它将执行相同的操作,依此类推。 这里是一个例子:
//helper funciton to throw error
function thowError(message){
throw new Error(message)
};
var Hamster = function(args){
//make sure args is something so you get the errors
// that make sense to you instead of "args is undefined"
args = args || {};
//default value for type:
this.type = args.type || "default type";
//name is not optional, very simple truthy check f
this.name = args.name || thowError("args.name is not optional");
};
var RussionMini = function(args){
//make sure args is something so you get the errors
// that make sense to you instead of "args is undefined"
args = args || {};
args.type = "Russion Mini";
Hamster.call(this,args);
};
var ben = new RussionMini({name:"Ben"});
console.log(ben);// Object { type="Russion Mini", name="Ben"}
var betty = new RussionMini();//Error: args.name is not optional
在许多情况下,这种在函数链中传递参数的方法很有用。 当你在编写计算总数的代码时,如果你希望以某种货币为单位重新计算某个货币的总和,则可能需要更改许多函数来传递货币价值。 您可以将货币值(即使是像window.currency='USD'
这样的全球货币值)范围扩大,但这是一个解决问题的好方法。
通过传递一个对象,只要在函数链中可用,就可以将货币添加到args
中,并在需要时随时改变/使用它,而不必更改其他函数(显式地必须在函数调用中传递它)。
私有变量
JavaScript没有私人修饰符。
我同意以下内容:http://blog.millermedeiros.com/a-case-against-private-variables-and-functions-in-javascript/并且个人没有使用过它们。
你可以通过给其他程序员指出一个私人的名字_aPrivate
或者把所有的私有变量放在一个名为_
的对象变量中。
您可以通过闭包实现私有成员,但实例特定的私有成员只能由不在原型上的函数访问。
不实施私有作为闭包会泄漏实现,并使您或用户扩展您的代码以使用不属于您的公共API的成员。 这可以是好的也可以是坏的。
这很好,因为它可以让你和其他人轻松地模拟某些成员进行测试。 它为其他人提供了一个轻松改进(修补)代码的机会,但这也很糟糕,因为不能保证下一版本的代码具有相同的实现和/或私有成员。
通过使用闭包,你不会给别人一个选择,并且使用命名约定和你所做的文档。 这不是特定于JavaScript的,在其他语言中,您可以决定不使用私人成员,因为您相信别人知道他们在做什么,并让他们选择按自己的意愿去做(涉及风险)。
如果你仍然坚持私人,那么以下模式可能会有所帮助。 它不实现私有,但实现保护。
对于对象的每个实例,原型都没有实例化。
Hamster.prototype.food = []
仓鼠的每个实例都将共享该阵列
如果你需要(并且你在这种情况下)为每个仓鼠单独的食物收集实例,你需要在实例上创建属性。 例如:
function Hamster() {
this.food = [];
}
要回答关于示例1的问题,如果它没有在原型链中的任何位置找到属性,它将在目标对象上创建属性。
链接地址: http://www.djcxy.com/p/5097.html上一篇: writing up