frr*_*lod 130 javascript inheritance prototype
所以我有这两个例子,来自javascript.info:
例1:
var animal = {
eat: function() {
alert( "I'm full" )
this.full = true
}
}
var rabbit = {
jump: function() { /* something */ }
}
rabbit.__proto__ = animal
rabbit.eat()
Run Code Online (Sandbox Code Playgroud)
例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 (!??)
Run Code Online (Sandbox Code Playgroud)
从示例2开始:当代码到达时speedy.found,它找不到found属性speedy,因此它爬上原型并在那里进行更改.这就是为什么food.length两只仓鼠都是平等的,换句话说,它们有相同的胃.
据我所知,在编写和添加一个不存在的新属性时,解释器将上升到原型链,直到找到属性,然后更改它.
但是在示例1中还发生了其他事情:
我们运行rabbit.eat,这会发生变化rabbit.full.full属性无处可寻,所以它应该上升到原型链(对象??),好吧,我不知道这里发生了什么.在此示例中full,rabbit创建并更改了属性,而在第一个示例中,它上升了原型链,因为它找不到属性.
我很困惑,也看不出为什么会这样.
HMR*_*HMR 173
构造函数介绍
您可以使用函数作为构造函数来创建对象,如果构造函数名为Person,则使用该构造函数创建的对象是Person的实例.
var Person = function(name){
this.name = name;
};
Person.prototype.walk=function(){
this.step().step().step();
};
var bob = new Person("Bob");
Run Code Online (Sandbox Code Playgroud)
Person是构造函数.使用Person创建实例时,必须使用new关键字:
var bob = new Person("Bob");console.log(bob.name);//=Bob
var ben = new Person("Ben");console.log(ben.name);//=Ben
Run Code Online (Sandbox Code Playgroud)
属性/成员name是特定于实例的,对于bob和ben来说是不同的
该成员walk是Person.prototype的一部分,并且对于所有实例共享bob和ben是Person的实例,因此它们共享walk成员(bob.walk === ben.walk).
bob.walk();ben.walk();
Run Code Online (Sandbox Code Playgroud)
因为在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,即使bob和ben分享22到ben.walk walk的分配也不会影响bob.walk.这是因为该语句将walk直接创建一个名为ben 的成员,并为其赋值22.将有2个不同的walk成员:ben.walk和Person.prototype.walk.
当要求bob.walk时,你将得到Person.prototype.walk函数,因为walk在bob上找不到.然后要求ben.walk将获得值22,因为成员遍历已在ben上创建,并且因为JavaScript发现在Ben上行走它不会在Person.prototype中查找.
当使用带有2个参数的Object.create时,Object.defineProperty或Object.defineProperties阴影效果会有所不同.关于这里的更多信息.
更多关于原型
对象可以通过使用原型从另一个对象继承.您可以使用任何其他对象设置任何对象的原型Object.create.在构造函数函数介绍中,我们已经看到如果在对象上找不到成员,那么JavaScript将在prototpe链中查找它.
在前面的部分中,我们已经看到重新分配来自实例原型(ben.walk)的成员将影响该成员(在ben上创建walk而不是更改Person.prototype.walk).
如果我们不重新分配但改变成员怎么办?变异是(例如)改变Object的子属性或调用将改变对象值的函数.例如:
var o = [];
var a = o;
a.push(11);//mutate a, this will change o
a[1]=22;//mutate a, this will change o
Run Code Online (Sandbox Code Playgroud)
以下代码通过变更成员来演示原型成员和实例成员之间的区别.
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"]
Run Code Online (Sandbox Code Playgroud)
上面的代码显示ben和bob共享来自person的成员.只有一个人,它被设置为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);//=[]
Run Code Online (Sandbox Code Playgroud)
您可能会遇到类似的模式,这些模式更强大,可以帮助创建对象和定义对象.
遗产
The following code shows how to inherit. The tasks are basically the same as in code before with a little extra
Using a pattern some would call "classical inheritance". If you are confused by the syntax I'll be happy to explain more or provide different patterns.
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
Run Code Online (Sandbox Code Playgroud)
Object.create to set prototype part of inheritance
Here is the documentation about Object.create, it basically returns the second argument (not supported in the polyfil) with the first argument as the returned object's prototype.
If no second argument was given it'll return an empty object with first argument to be used as the returned object's prototype (the first object to be used in the returned object's prototype chain).
Some would set the prototype of RussionMini to an instance of Hamster (RussionMini.prototype = new Hamster()). This is not desirable because even though it accomplishes the same (RussionMini.prototype's prototype is Hamster.prototype) it also sets Hamster instance members as members of RussionMini.prototype. So RussionMini.prototype.food will exist but is a shared member (remember bob and ben in "More about prototype"?). The food member will be shadowed when creating a RussionMini because Hamster code is run with Hamster.apply(this,arguments); that in turn runs this.food = [] but any Hamster members will still be members of RussionMini.prototype.
Another reason could be that to create a Hamster a lot of complicated calculations need be done on passed arguments that may be not available yet, again you could pass in dummy arguments but it could unnecessarily complicate your code.
Extending and overriding Parent functions
Sometimes children need to extend parent functions.
You want the 'child' (=RussionMini) to do something extra. When RussionMini can call the Hamster code to do something and then do something extra you don't need to copy and paste Hamster code to RussionMini.
In the following example we assume that a Hamster can run 3km an hour but a Russion mini can only run half as fast. We can hard code 3/2 in RussionMini but if this value were to change we have multiple places in code where it needs changing. Here is how we use Hamster.prototype to get the parent (Hamster) speed.
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.
Run Code Online (Sandbox Code Playgroud)
The disadvantage is that you hard code Hamster.prototype. There may be patterns that will give you the advantage of super as in Java.
Most of the patterns I've seen will either break when inheritance level is more than 2 levels (Child => Parent => GrandParent) or use more resources by implementing super through closures.
To override a Parent (=Hamster) method you do the same but don't do Hamster.prototype.parentMethod.call(this,....
this.constructor
The constructor property is included in the prototype by JavaScript, you can change it but it should point to the constructor function. So Hamster.prototype.constructor should point to Hamster.
If after setting prototype part of inheritance you should have it point to the right function again.
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
Run Code Online (Sandbox Code Playgroud)
"Multiple inheritance" with mix ins
Some things are better not to be inherited, if a Cat can move and then a Cat should not inherit from Movable. A Cat is not a Movable but rather a Cat can move. In a class based language Cat would have to implement Movable. In JavaScript we can define Movable and define implementation here, Cat can either override, extend it or us it's default implementation.
For Movable we have instance specific members (like location). And we have members that are not instance specific (like the function move()). Instance specific members will be set by calling mxIns (added by mixin helper function) when creating an instance. Prototype members will be copied one by one on Cat.prototype from Movable.prototype using the mixin helper function.
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();
Run Code Online (Sandbox Code Playgroud)
The above is a simple implementation that replaces same named functions with whatever mix in is mixed in last.
The this variable
In all the example code you'll see this referring to the current instance.
The this variable actually refers to the invoking object, it refers to the object that came before the function.
To clarify see the following code:
theInvokingObject.thefunction();
Run Code Online (Sandbox Code Playgroud)
The instances where this would refer to the wrong object are usually when attaching event listeners, callbacks or timeouts and intervals. In the next 2 lines of code we pass the function, we don't invoke it. Passing the function is: someObject.aFunction and invoking it is: someObject.aFunction(). The this value does not refer to the object the function was declared on but on the object that invokes it.
setTimeout(someObject.aFuncton,100);//this in aFunction is window
somebutton.onclick = someObject.aFunction;//this in aFunction is somebutton
Run Code Online (Sandbox Code Playgroud)
To make this in the above cases refer to someObject you can pass a closure instead of the function directly:
setTimeout(function(){someObject.aFuncton();},100);
somebutton.onclick = function(){someObject.aFunction();};
Run Code Online (Sandbox Code Playgroud)
I like to define functions that return a function for closures on the prototype to have fine control over the variables that are included in the closure scope.
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");
Run Code Online (Sandbox Code Playgroud)
Passing (constructor) arguments
When Child calls a Parent (Hamster.apply(this,arguments);) we assume that Hamster uses the same arguments as RussionMini in the same order. For functions that call other functions I usually use another way to pass arguments.
I usually pass one object to a function and have that function mutate whatever it needs (set defaults), then that function will pass it to another function that will do the same and so on and so on. Here is an example:
//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
Run Code Online (Sandbox Code Playgroud)
This way of passing arguments in a function chain is useful in many cases. When you're working on code that would calculate a total of something and later you'd like to re factor the total of that something to be in a certain currency you may have to change a lot of functions to pass the value for currency. You could up scope a currency value (even to global like window.currency='USD') but that's a bad way to solve it.
With passing an object you could add currency to args whenever it's available in the function chain and mutate/use it whenever you need it without changing the other functions (explicitly have to pass it in the function calls).
Private variables
JavaScript doesn't have a private modifier.
I agree with the following: http://blog.millermedeiros.com/a-case-against-private-variables-and-functions-in-javascript/ and personally have not used them.
You can indicate to other programmers a member is meant to be private by naming it _aPrivate or putting all the private variables in an object variable called _.
You can implement private members through closures but instance specific private members can only be accessed by functions that are not on the prototype.
Not implementing privates as closures would leak implementation and enable you or users extending your code to use members that are not part of your public API. This can be both good and bad.
It's good because it enables you and others to mock certain members for testing easily. It gives others a chance to easily improve (patch) your code but this is also bad because there is no guarantee that a next version of your code has the same implementation and or private members.
By using closures you do not give others a choice and by using the naming convention with documentation you do. This is not specific to JavaScript, in other languages you can decide not to use private members as you trust others to know what they are doing and give them the choice to do as they want (with risks involved).
If you still insist on privates then the following pattern may help. It doesn't implement private though but implements protected.
BLS*_*lly 15
原型不是实例化对象的每个实例.
Hamster.prototype.food = []
Run Code Online (Sandbox Code Playgroud)
Hamster的每个实例都将共享该数组
如果您需要(在这种情况下),每个Hamster的食物集合的单独实例,您需要在实例上创建属性.例如:
function Hamster() {
this.food = [];
}
Run Code Online (Sandbox Code Playgroud)
要回答关于示例1的问题,如果它没有在原型链中的任何位置找到该属性,则会在目标对象上创建该属性.