Programming

프로토 타입 상속-작성

procodes 2020. 7. 2. 08:12
반응형

프로토 타입 상속-작성


그래서 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

두 햄스터 모두 동일합니다. 즉, 같은 위를 가지고 있습니다.



이것으로부터 나는 존재하지 않는 새로운 속성을 작성하고 추가 할 때, 인터프리터는 속성을 찾을 때까지 프로토 타입 체인을 올라가서 변경할 수 있다는 것을 이해합니다.하지만 예에서 다른 한 일이 발생 :

 

우리가 실행

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을 사용하여 인스턴스를 생성 할 때는 새로운 키워드를 사용해야합니다.

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 인스턴스 공유 걷기 기능은 사용하는 도보 함수에서 사용하기 때문에 인스턴스마다 기능이 다르게 작동합니다

this

. 의 값은

this

호출 객체입니다. 지금은 이것이 현재 인스턴스라고 말하면

bob.walk()

"this"는 bob이됩니다. ( "this"와 나중에 호출 객체에 대한 자세한 내용).벤이 붉은 빛을 기다리고 있었고 밥이 초록 빛에 있다면; 그런 다음 ben과 bob에서 walk ()를 호출하면 분명히 ben과 bob에 다른 일이 발생합니다.우리가 뭔가를 할 때 회원을 섀도 잉하는 일이

ben.walk=22

밥과 벤 점유율에도 불구하고, 할당 ben.walk 22의이 bob.walk 영향을 미치지 않습니다. 이는 해당 명령문이 ben에서 직접 호출 멤버를 작성하고 22의 값을 지정하기 때문입니다. ben.walk 및 Person.prototype.walk라는 두 개의 다른 워크 멤버가 있습니다.

walk

 

walk

bob.walk를 요청하면

walk

bob에서 찾을 수 없기 때문에 Person.prototype.walk 기능을 사용할 수 있습니다. 그러나 ben.walk를 요청하면 멤버 워크가 ben에서 작성되었으므로 JavaScript가 ben에서 walk를 찾은 이후 Person.prototype을 찾지 않으므로 값 22를 얻게됩니다.2 개의 인수와 함께 Object.create를 사용하는 경우 Object.defineProperty 또는 Object.defineProperties 섀도 잉은 약간 다르게 작동합니다.

여기

에 대한 자세한 정보 .

프로토 타입에 대한 추가 정보

프로토 타입을 사용하여 객체를 다른 객체에서 상속 할 수 있습니다. 을 사용하여 다른 객체와 함께 객체의 프로토 타입을 설정할 수 있습니다

Object.create

. 생성자 함수 소개에서 우리는 객체에서 멤버를 찾을 수 없으면 JavaScript가 프로토 타입 체인을 찾습니다.이전 부분에서 인스턴스 프로토 타입 (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"]

위의 코드는 벤과 밥이 사람의 구성원을 공유한다는 것을 보여줍니다. 사람은 한 명 뿐이며 bob 및 ben의 프로토 타입으로 설정됩니다 (사람은 프로토 타입 체인에서 첫 번째 오브젝트로 사용되어 인스턴스에 존재하지 않는 요청 된 멤버를 찾습니다). 위 코드의 문제점은 bob과 ben이 자신의

food

멤버를 가져야한다는 것 입니다. 생성자 함수가 나오는 곳입니다. 인스턴스 특정 멤버를 만드는 데 사용됩니다. 이 인스턴스 특정 멤버의 값을 설정하기 위해 인수를 전달할 수도 있습니다.다음 코드는 생성자 함수를 구현하는 다른 방법을 보여줍니다. 구문은 다르지만 아이디어는 동일합니다.

  1. 많은 인스턴스에 대해 동일한 멤버가있는 오브젝트를 정의하십시오 (사람은 bob 및 ben의 청사진이며 jilly, marie, clair ...).
  2. 인스턴스 (bob 및 ben)에 고유해야하는 인스턴스 특정 멤버를 정의하십시오.
  3. 2 단계에서 코드를 실행하는 인스턴스를 작성하십시오.

생성자 함수를 사용하면 다음 코드에서 2 단계에서 프로토 타입을 설정하고 3 단계에서 프로토 타입을 설정합니다.이 코드에서는 인스턴스를 만들 때 거의 즉시이를 그림자로 만들 가능성이 높기 때문에 프로토 타입과 음식에서 이름을 제거했습니다. 이름은 이제 생성자 함수에 기본값이 설정된 인스턴스 별 멤버입니다. 음식 멤버도 프로토 타입에서 인스턴스 특정 멤버로 이동하기 때문에 벤에 음식을 추가 할 때 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);//=[]

객체 생성 및 객체 정의에 도움이되는보다 강력한 유사한 패턴이 나타날 수 있습니다.

계승

다음 코드는 상속하는 방법을 보여줍니다. 작업은 기본적으로 약간의 추가로 코드와 동일합니다.

  1. 객체의 인스턴스 특정 멤버를 정의합니다 (함수 Hamster 및 RussionMini).
  2. 상속의 프로토 타입 부분을 설정합니다 (RussionMini.prototype = Object.create (Hamster.prototype))
  3. 인스턴스간에 공유 할 수있는 멤버를 정의하십시오 (Hamster.prototype 및 RussionMini.prototype)
  4. 1 단계에서 코드를 실행하는 인스턴스를 만들고 상속되는 객체에 대해 부모 코드도 실행하도록합니다 (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는 존재하지만 공유 멤버입니다 ( "prototype에 대한 추가 정보"에서 bob 및 ben을 기억하십니까?). 햄스터 코드가

Hamster.apply(this,arguments);

차례로 실행 되기 때문에 RussionMini를 만들 때 음식 멤버가 음영 처리

this.food = []

되지만 햄스터 멤버는 여전히 RussionMini.prototype의 멤버가됩니다.또 다른 이유는 Hamster를 만들려면 아직 사용할 수없는 전달 된 인수에 대해 복잡한 계산을 많이 수행해야하기 때문에 더미 인수를 전달할 수 있지만 불필요하게 코드가 복잡해질 수 있습니다.

부모 함수 확장 및 재정의

때때로 기능

children

을 확장해야

parent

합니다.'자식'(= RussionMini)이 추가 작업을 수행하려고합니다. RussionMini가 Hamster 코드를 호출하여 작업을 수행 한 다음 추가 작업을 수행 할 때 Hamster 코드를 복사하여 RussionMini에 붙여 넣을 필요가 없습니다.다음 예에서 햄스터는 한 시간에 3km를 달릴 수 있지만 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을 하드 코딩한다는 것입니다.

super

Java에서 같이 이점을 제공하는 패턴이있을 수 있습니다 .내가 본 대부분의 패턴은 상속 레벨이 2 레벨 이상일 때 깨지거나 (Child => Parent => GrandParent) super through

closures

를 구현하여 더 많은 리소스를 사용 합니다.Parent (= Hamster) 메서드를 재정의하려면 동일하게 수행하지만 Hamster.prototype.parentMethod.call (this, ....)은 수행하지 마십시오.

this. 생성자

생성자 속성은 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을 구현해야합니다. JavaScript에서 Movable을 정의하고 여기에서 구현을 정의 할 수 있습니다. Cat은이를 재정의하거나 확장하거나 기본 구현입니다.움직일 수 있도록 인스턴스 특정 멤버 (예 :)가 있습니다

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();

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

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();};

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");

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

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.


Prototypes are NOT instantiated for each instance of an object.

Hamster.prototype.food = []

Every instance of Hamster will share that array

If you need (and you do in this case) separate instances of food collections for each Hamster, you need to create the property on the instance. For example:

function Hamster() {
  this.food = [];
}

To answer your question about Example 1, if it doesn't find the property anywhere in the prototype chain, it creates the property on the target object.

참고URL : https://stackoverflow.com/questions/16063394/prototypical-inheritance-writing-up

반응형