javascript – 原型继承 – 写作[重复]

这个问题已经有一个答案在这里:            >            Javascript object members that are prototyped as arrays become shared by all class instances                                    2个答案                            所以我有这两个例子,从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时,它在快速中找不到找到的属性,因此它爬到原型并改变它。这就是为什么food.length对于两个仓鼠是相等的,换句话说,他们有同样的胃。
从这里我明白,当编写和添加一个新的属性不存在,解释器将上升原型链,直到找到属性,然后改变它。

但是在示例1中,会发生其他情况:
我们运行rabbit.eat,这会改变rabbit.full。全属性是无处可找,所以它应该去原型链(到对象??),以及,我不知道这里发生了什么。在这个例子中,充满了兔子的属性被创建和改变,而在第一个例子中,它上升原型链,因为它找不到属性。

我很困惑,不明白为什么会发生这种情况。

构造函数介绍

您可以使用函数作为构造函数创建对象,如果构造函数名为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

属性/成员名称是特定于实例的,对于bob和ben是不同的

成员遍历是Person.prototype的一部分,并且对所有实例共享bob和ben是Person的实例,所以他们共享了walk成员(bob.walk === ben.walk)。

bob.walk();ben.walk();

因为walk()在bob上找不到,JavaScript会在Person.prototype中寻找它,因为这是bob的构造函数。如果不能找到它,它会看Object.prototype。这被称为原型链。继承的原型部分是通过延长这个链来完成的;例如bob => Employee.prototype => Person.prototype => Object.prototype(更多关于继承)。

即使bob,ben和所有其他创建的Person实例共享walk,函数将对每个实例的行为不同,因为在walk函数中它使用这个。这个值将是调用对象;现在让我们说它是当前实例,所以对bob.walk()“this”将是bob。 (更多关于“this”和调用对象)。

如果ben正在等待一个红灯,而bob是在绿灯;然后你会调用walk()在ben和bob明显不同会发生在ben和bob。

当我们做类似ben.walk = 22这样的事情时,会发生遮蔽成员,即使bob和ben共享行走22到ben.walk也不会影响bob.walk。这是因为该语句将直接创建一个名为walk on ben的成员,并将其值赋值为22.将有2个不同的walk成员:ben.walk和Person.prototype.walk。

当要求bob.walk时,你会得到Person.prototype.walk函数,因为在bob上找不到walk。然而,请求ben.walk将得到值22,因为成员步行已创建在本,并且自从JavaScript发现行走在本它不会看到Person.prototype。

当使用带有2个参数的Object.create时,Object.defineProperty或Object.defineProperties影子工作有点不同。更多信息here

更多关于原型

一个对象可以通过使用原型从另一个对象继承。您可以使用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共享个人的成员。只有一个人,它被设置为bob和ben的原型(人被用作原型链中的第一个对象来查找实例上不存在的请求成员)。上面的代码的问题是bob和ben应该有自己的食物成员。这是构造函数的来源。它用于创建实例特定的成员。您还可以传递参数到它设置这些实例特定成员的值。

下面的代码显示了实现构造函数的另一种方式,语法是不同的,但想法是一样的:

>定义一个对象,其成员对于许多实例是相同的(人是bob和ben的蓝图,可以是jilly,marie,clair …)
>定义实例(bob和ben)应该是唯一的特定于实例的成员。
>在第2步中创建运行代码的实例。

使用构造函数,您将在以下代码中的步骤2中设置原型,我们在步骤3中设置原型。

在这段代码中,我从原型和食物中删除了名称,因为你最有可能在创建实例时几乎立即产生阴影。 Name现在是一个特定于实例的成员,在构造函数中设置了默认值。 Becaus食品成员也从原型到实例特定成员,它不会影响bob.food当添加食物给ben。

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);//=[]

你可能会遇到类似的模式,更健壮以帮助对象创建和对象定义。

遗产

以下代码显示了如何继承。任务基本上与代码中一样,有一点额外

>定义对象的实例特定成员(函数Hamster和RussionMini)。
>设置继承的原型部分(RussionMini.prototype = Object.create(Hamster.prototype))
>定义可以在实例之间共享的成员(Hamster.prototype和RussionMini.prototype)
>创建一个在第1步中运行代码的实例,对于继承它们的对象,还要运行Parent代码(Hamster.apply(this,arguments);)

使用模式一些将称为“经典继承”。如果你对语法感到困惑,我会乐于解释更多或提供不同的模式。

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时,食物成员会被遮住,因为Hamster代码是用Hamster.apply(this,arguments)运行的。这反过来运行this.food = [],但任何仓鼠成员仍然是RussionMini.prototype的成员。

另一个原因可能是创建一个仓鼠,很多复杂的计算需要完成传递的参数,可能还不可用,再次,你可以传递虚拟参数,但它可能不必要的复杂的代码。

扩展和覆盖父功能

有时孩子需要扩展父函数。

你想让’child'(= RussionMini)做一些额外的事情。当RussionMini可以调用Hamster代码做某事,然后做一些额外的事情,你不需要复制和粘贴Hamster代码到RussionMini。

在下面的例子中,我们假设一个仓鼠可以跑3公里每小时,但是一个Russion迷你只能跑一半。我们可以在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。有可能有模式,将给你的优势,如在Java。

我看到的大多数模式将在继承级别超过2级(Child => Parent => GrandParent)时中断,或者通过实现超级closures使用更多资源。

要覆盖父(=仓鼠)方法,你也做同样的,但不要做Hamster.prototype.parentMethod.call(this,….

this.constructor

构造函数属性通过JavaScript包含在原型中,您可以更改它,但它应该指向构造函数。所以Hamster.prototype.constructor应该指向Hamster。

如果在设置继承的原型部分之后,你应该让它指向正确的函数。

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

“多继承”与混合

有些东西最好不要继承,如果一个猫可以移动,然后一个猫不应继承Movable。猫不是可移动的,而是猫可以移动。在基于类的语言中,Cat将必须实现Movable。在JavaScript中,我们可以定义Movable并在这里定义实现,Cat可以覆盖,扩展它,或者它是默认实现。

对于Movable,我们有实例特定成员(如位置)。我们有不是实例特定的成员(如函数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();

上面是一个简单的实现,用最后混合的混合替换相同的命名函数。

这个变量

在所有示例代码中,您将看到这涉及当前实例。

这个变量实际上是指调用对象,它指的是函数之前的对象。

澄清一下看下面的代码:

theInvokingObject.thefunction();

引用错误对象的实例通常是在附加事件侦听器,回调或超时和间隔时。在接下来的两行代码中我们传递函数,我们不调用它。传递函数是:someObject.aFunction并且调用它是:someObject.aFunction()。这个值不是指函数声明的对象,而是引用它的对象。

setTimeout(someObject.aFuncton,100);//this in aFunction is window
somebutton.onclick = someObject.aFunction;//this in aFunction is somebutton

为了在上面的情况下,参考someObject你可以传递一个closure而不是直接的函数:

setTimeout(function(){someObject.aFuncton();},100);
somebutton.onclick = function(){someObject.aFunction();};

我喜欢定义返回原型上的closures的函数的函数,以对包含在closure作用域中的变量进行精细控制。

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调用父类(Hamster.apply(this,arguments);)时,我们假设Hamster以相同的顺序使用与RussionMini相同的参数。对于调用其他函数的函数,我通常使用另一种方法来传递参数。

我通常传递一个对象到一个函数,并让该函数mutate任何它需要的(设置默认值),然后该函数将它传递给另一个函数,将做同样的依此类推。这里是一个例子:

//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 =’美元’),但这是一个坏的方法来解决它。

通过传递一个对象,你可以添加货币到args当它在函数链中可用,mutate /使用它,当你需要它,而不改变其他函数(显式必须传递它在函数调用)。

私有变量

JavaScript没有私有修饰符。

我同意以下:http://blog.millermedeiros.com/a-case-against-private-variables-and-functions-in-javascript/和个人没有使用它们。

你可以通过命名_aPrivate或将所有私有变量放在一个称为_的对象变量中来向其他程序员指示一个成员是私有的。

您可以通过closures实现私有成员,但是实例特定的私有成员只能由不在原型上的函数访问。

不实现私有作为闭包将泄露实现,并允许您或用户扩展您的代码使用不属于您的公共API的成员。这可以是好也坏。

它是好的,因为它使你和其他人模拟某些成员进行测试轻松。它让其他人有机会轻松地改进(补丁)你的代码,但这也是坏的,因为不能保证下一个版本的代码有相同的实现和/或私人成员。

通过使用闭包,你不会给他人一个选择,并使用命名约定与文档。这不是特定于JavaScript,在其他语言中,您可以决定不使用私人成员,因为您信任他人知道他们在做什么,并给他们选择做他们想要的(有风险)。

如果你仍然坚持私人,那么following模式可能有帮助。它不实现私有,但实现保护。

http://stackoverflow.com/questions/16063394/prototypical-inheritance-writing-up

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:javascript – 原型继承 – 写作[重复]