因此,我有两个来自javascript.info的示例:
范例1:
var animal = {
eat: function() {
alert( "I'm full" )
this.full = true
}
}
var rabbit = {
jump: function() { /* something */ }
}
rabbit.__proto__ = animal
rabbit.eat()
范例2:
function Hamster() { }
Hamster.prototype = {
food: [],
found: function(something) {
this.food.push(something)
}
}
// Create two speedy and lazy hamsters, then feed the first one
speedy = new Hamster()
lazy = new Hamster()
speedy.found("apple")
speedy.found("orange")
alert(speedy.food.length) // 2
alert(lazy.food.length) // 2 (!??)
从示例2开始:当代码到达时speedy.found
,它在中找不到任何found
属性speedy
,因此它爬升到原型并在那里进行了更改。这就是为什么food.length
两只仓鼠都相等的原因,换句话说,它们的肚子也一样。
据此我了解,当编写并添加一个不存在的新属性时,解释器将沿着原型链向上移动,直到找到该属性,然后再进行更改。
但是在示例1中,发生了其他事情:
我们运行rabbit.eat
,它发生了变化rabbit.full
。full
属性是无处可寻的,因此它应该沿着原型链上升(到达对象?),而且,我不确定这里会发生什么。在此示例中full
,rabbit
创建和更改的属性,而在第一个示例中,由于找不到属性,该属性沿原型链向上移动。
我很困惑,无法理解为什么会这样。
构造函数介绍
您可以将函数用作构造函数来创建对象,如果该构造函数命名为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的所有实例都共享,因此它们共享walk成员(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()
“
this”将是bob。(有关“ this”和稍后调用对象的更多信息)。
如果ben在等待红灯,而bob在绿灯;那么您将在ben和bob上同时调用walk(),显然ben和bob会发生一些不同的事情。
当ben.walk=22
bob和ben walk
将22 的 分配分配 给ben.walk
时,当我们执行诸如之类的操作时,会出现阴影成员,这不会影响bob.walk。这是因为该语句将walk
直接创建一个在ben上调用的成员,并将其赋值为22。将有2个不同的walk成员:ben.walk和Person.prototype.walk。
当请求bob.walk时,您将获得Person.prototype.walk函数,因为walk
在bob上找不到该函数。但是,询问ben.walk会为您提供22的值,因为该成员walk是在ben上创建的,并且因为JavaScript在ben上找到了walk,所以它将不在Person.prototype中查找。
当使用带有2个参数的Object.create时,Object.defineProperty或Object.defineProperties阴影的工作原理有所不同。
有关原型的更多信息
一个对象可以通过使用原型从另一个对象继承。您可以使用设置其他任何对象的原型Object.create
。在构造函数介绍中,我们已经看到,如果在对象上找不到成员,则JavaScript会在原型链中查找该成员。
在上一部分中,我们已经看到,来自实例原型(ben.walk)的成员的重新分配将对该成员产生阴影(在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共享person的成员。只有一个人,它被设置为bob和ben的原型(person被用作原型链中的第一个对象,以查找实例中不存在的请求成员)。上面的代码的问题是bob和ben应该有自己的food
成员。这是构造函数进入的地方。它用于创建实例特定的成员。您还可以将参数传递给它,以设置这些实例特定成员的值。
下一个代码显示了另一种实现构造函数的方法,语法不同,但是思想相同:
使用构造函数,您将在以下代码的第2步中设置原型,在第3步中设置原型。
在这段代码中,我从原型和食物中删除了名称,因为无论如何在创建实例时,您很可能会立即将其隐藏起来。Name现在是实例特定的成员,并在构造函数中设置了默认值。因为食物成员也已从原型移动到实例特定成员,所以在向ben添加食物时不会影响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的原型设置为Hamster的实例(RussionMini.prototype = new
Hamster())。这是不希望的,因为即使它完成了相同的操作(RussionMini.prototype的原型是Hamster.prototype),它也将Hamster实例成员设置为RussionMini.prototype的成员。因此RussionMini.prototype.food将存在,但是是共享成员(还记得“更多关于原型”中的bob和ben吗?)。在创建RussionMini时,food成员将被遮盖,因为同时运行了Hamster代码Hamster.apply(this,arguments);
,this.food = []
但是任何Hamster成员仍将是RussionMini.prototype的成员。
另一个原因可能是,要创建仓鼠,需要对传递的参数(可能尚不可用)进行大量复杂的计算,同样可以传递虚拟参数,但这可能不必要地使您的代码复杂化。
扩展和覆盖父函数
有时children
需要扩展parent
功能。
您希望’child’(=
RussionMini)做一些额外的事情。当RussionMini可以调用Hamster代码执行某项操作,然后再执行其他操作时,您无需将Hamster代码复制并粘贴到RussionMini。
在下面的示例中,我们假设仓鼠每小时可以跑3公里,而Russion
mini只能跑一半速度。我们可以在RussionMini中对3/2进行硬编码,但是如果要更改此值,则在代码中有多个需要更改的地方。这是我们使用Hamster.prototype来获取父级(Hamster)速度的方法。
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),或者通过实现super through
闭包而使用更多资源。
要覆盖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可以移动,则Cat不应从Movable继承。猫不是可动的,而是猫可以移动。在基于类的语言中,Cat必须实现Movable。在JavaScript中,我们可以在此处定义Movable并定义实现,Cat可以覆盖,扩展它,也可以将其作为默认实现。
对于Movable,我们有特定于实例的成员(例如location
)。而且我们的成员不是特定于实例的(例如,函数move())。创建实例时,将通过调用mxIns(由mixin
helper函数添加)来设置实例特定的成员。原型成员将使用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
这种在函数链中传递参数的方法在许多情况下很有用。当您在编写可以计算总金额的代码时,后来又想将某种货币的总金额重新分解为某种货币,您可能必须更改许多函数才能传递货币值。您可以扩大货币价值的范围(甚至是像global这样的货币window.currency='USD'
),但这是解决它的一种不好的方法。
通过传递对象,您可以args
在功能链中的任何可用位置添加货币,并在需要时随时更改/使用它,而无需更改其他功能(必须在函数调用中明确传递)。
私有变量
JavaScript没有私有修饰符。
我同意以下内容:http : //blog.millermedeiros.com/a-case-
against-private-variables-and-functions-in-
javascript/并且个人没有使用过它们。
您可以通过命名成员_aPrivate
或将所有私有变量放在名为的对象变量中来向其他程序员表明该成员是私有成员_
。
您可以通过闭包来实现私有成员,但是特定于实例的私有成员只能由不在原型上的函数访问。
不将私有实现作为闭包将泄漏实现,并使您或用户将代码扩展为使用不属于公共API的成员。这可能是好事,也可能是坏事。
很好,因为它使您和其他人可以轻松模拟某些成员以进行测试。它使其他人有机会轻松地改进(修补)您的代码,但这也很糟糕,因为不能保证您的下一版本的代码具有相同的实现或私有成员。
通过使用闭包,您不会给其他选择,而在文档中使用命名约定。这不是特定于JavaScript的,在其他语言中,您可以决定不使用私有成员,因为您信任其他人知道他们在做什么,并给他们选择随心所欲的选择(涉及风险)。
如果您仍然坚持使用私有内容,则以下模式可能会有所帮助。它不是实现私有的,而是实现保护的。
本文向大家介绍再谈javascript原型继承,包括了再谈javascript原型继承的使用技巧和注意事项,需要的朋友参考一下 真正意义上来说Javascript并不是一门面向对象的语言,没有提供传统的继承方式,但是它提供了一种原型继承的方式,利用自身提供的原型属性来实现继承。 原型与原型链 说原型继承之前还是要先说说原型和原型链,毕竟这是实现原型继承的基础。 在Javascript中,每个函数都
在编程中,我们经常会想获取并扩展一些东西。 例如,我们有一个 user 对象及其属性和方法,并希望将 admin 和 guest 作为基于 user 稍加修改的变体。我们想重用 user 中的内容,而不是复制/重新实现它的方法,而只是在其之上构建一个新的对象。 原型继承(Prototypal inheritance) 这个语言特性能够帮助我们实现这一需求。 [[Prototype]] 在 Java
在传统的基于Class的语言如Java、C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass。 由于这类语言严格区分类和实例,继承实际上是类型的扩展。但是,JavaScript由于采用原型继承,我们无法直接扩展一个Class,因为根本不存在Class这种类型。 但是办法还是有的。我们先回顾Student构造函数: function Student(props) { t
本文向大家介绍JavaScript的原型继承详解,包括了JavaScript的原型继承详解的使用技巧和注意事项,需要的朋友参考一下 JavaScript是一门面向对象的语言。在JavaScript中有一句很经典的话,万物皆对象。既然是面向对象的,那就有面向对象的三大特征:封装、继承、多态。这里讲的是JavaScript的继承,其他两个容后再讲。 JavaScript的继承和C++的继承不大一样,C
本文向大家介绍JavaScript原型继承和原型链原理详解,包括了JavaScript原型继承和原型链原理详解的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了JavaScript原型继承和原型链原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在讨论原型继承之前,先回顾一下关于创建自定义类型的方式,这里推荐将构造函数和原型模式组
问题内容: 我是JavaScript OOP的新手。您能否解释以下代码块之间的区别?我测试了两个模块。什么是最佳做法,为什么? 第一块: 第二块: 为什么笔者添加和使用方法,并没有宣布他们的内部方法类和在类? 为什么需要重新设置为?是当覆盖的属性设置?我注释了这一行,没有任何改变。 为什么叫在构造函数?当我这样做时,不会被“继承”的属性和方法 问题答案: 这两个块的不同之处在于,在第一个示例中该实