ค้นหาเว็บไซต์

javascript - การสืบทอดแบบโปรโตไทป์ - การเขียนขึ้น


ดังนั้นฉันจึงมีตัวอย่าง 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 มันไม่พบคุณสมบัติ found ใน speedy ดังนั้นมันจึงปีนขึ้นไปที่โปรโตไทป์และเปลี่ยนมันที่นั่น นี่คือเหตุผลที่ food.length เท่ากันสำหรับแฮมสเตอร์ทั้งสอง พูดง่าย ๆ ก็คือพวกมันมีกระเพาะอาหารเดียวกัน

จากสิ่งนี้ฉันเข้าใจว่า เมื่อเขียนขึ้นและเพิ่มคุณสมบัติใหม่ที่ไม่มีอยู่ นักแปลคำสั่งจะขึ้นไปตามสายโซ่โปรโตไทป์จนกว่าจะพบคุณสมบัติ แล้วค่อยเปลี่ยนมัน

แต่ในตัวอย่างที่ 1 เกิดสิ่งอื่นขึ้น:
เราเรียกใช้ rabbit.eat ซึ่งเปลี่ยน rabbit.full คุณสมบัติ full ไม่พบที่ใด ๆ ดังนั้นมันควรไปขึ้นที่โพรโทไทป์เชน (ไปที่ object ??) และเอ่อ ผมไม่แน่ใจว่าจะเกิดอะไรขึ้นที่นี่ ในตัวอย่างนี้ คุณสมบัติ full ของ rabbit ถูกสร้างและเปลี่ยนแปลง ในขณะที่ในตัวอย่างแรกมันไปขึ้นที่โพรโทไทป์เชนเพราะไม่สามารถหาคุณสมบัติได้.

ฉันสับสนและไม่เข้าใจว่าทำไมสิ่งนี้ถึงเกิดขึ้น

คำตอบ #1

การแนะนำฟังก์ชันตัวสร้าง

คุณสามารถใช้ฟังก์ชันเป็นตัวสร้างเพื่อสร้างวัตถุได้ หากฟังก์ชันตัวสร้างมีชื่อว่า 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();

เนื่องจากไม่สามารถหาฟังก์ชัน walk() บน bob โดยตรง JavaScript จะค้นหามันใน Person.prototype เนื่องจากนี่คือ constructor ของ bob หากไม่พบที่นั่น มันจะไปค้นหาใน Object.prototype สิ่งนี้เรียกว่าโพรโทไทป์เชน ส่วนของการสืบทอดโดยโพรโทไทป์จะเกิดขึ้นโดยการขยายเชนนี้; ตัวอย่างเช่น bob => Employee.prototype => Person.prototype => Object.prototype (จะพูดถึงการสืบทอดเพิ่มเติมในภายหลัง)

แม้ว่าบ็อบ, เบ็น และตัวอย่าง Person ที่สร้างขึ้นอื่น ๆ ทั้งหมดจะแชร์ฟังก์ชัน walk แต่ฟังก์ชันนี้จะทำงานแตกต่างกันในแต่ละตัวอย่าง เพราะในฟังก์ชัน walk จะใช้ this ค่า this จะเป็นวัตถุที่เรียกใช้; ตอนนี้สมมติว่ามันคืออินสแตนซ์ปัจจุบัน ดังนั้นสำหรับ bob.walk() “this” จะเป็นบ็อบ (จะอธิบายเพิ่มเติมเกี่ยวกับ “this” และวัตถุที่เรียกใช้ในภายหลัง)

ถ้าเบนกำลังรอสัญญาณไฟแดงและบ็อบอยู่ที่สัญญาณไฟเขียว; แล้วคุณจะเรียกใช้ walk() กับทั้งเบนและบ็อบ ชัดเจนว่าจะมีสิ่งที่แตกต่างเกิดขึ้นกับเบนและบ็อบ

การเกิด shadowing ของสมาชิกเกิดขึ้นเมื่อเราทำอะไรบางอย่างเช่น ben.walk=22 ถึงแม้ว่า bob และ ben จะแชร์ walk การ กำหนดค่า ของ 22 ให้กับ ben.walk จะไม่ส่งผลกระทบต่อ bob.walk เนื่องจากคำสั่งนั้นจะสร้างสมาชิกชื่อ walk บน ben โดยตรงและกำหนดค่าของมันเป็น 22 จะมีสมาชิก walk สองตัวที่แตกต่างกัน: ben.walk และ Person.prototype.walk

เมื่อขอ bob.walk คุณจะได้รับฟังก์ชัน Person.prototype.walk เพราะ walk ไม่สามารถพบได้บน bob อย่างไรก็ตาม การขอ ben.walk จะได้ค่า 22 เพราะสมาชิก walk ถูกสร้างบน ben และเนื่องจาก JavaScript พบ walk บน ben มันจะไม่ไปค้นหาใน Person.prototype

เมื่อใช้ Object.create กับอาร์กิวเมนต์ 2 ตัว การใช้งาน Object.defineProperty หรือ Object.defineProperties ในการซ้อนกันจะทำงานแตกต่างออกไปเล็กน้อย ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้อยู่ที่นี่

เพิ่มเติมเกี่ยวกับต้นแบบ

วัตถุสามารถสืบทอดจากวัตถุอื่นผ่านการใช้โปรโตไทป์ คุณสามารถตั้งค่าโปรโตไทป์ของวัตถุใดก็ได้ด้วยวัตถุอื่นใดก็ได้โดยใช้ Object.create ในบทแนะนำฟังก์ชันคอนสตรัคเตอร์ เราได้เห็นแล้วว่าถ้าสมาชิกไม่สามารถพบในวัตถุ JavaScript จะค้นหาในสายโซ่โปรโตไทป์แทน

ในส่วนก่อนหน้านี้เราได้เห็นแล้วว่าการมอบหมายใหม่ให้กับสมาชิกที่มาจากโปรโตไทป์ของอินสแตนซ์ (ben.walk) จะบังสมาชิกนั้น (สร้าง walk บน ben แทนการเปลี่ยน 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"]

โค้ดข้างต้นแสดงให้เห็นว่า เบ็น และ บ็อบ แชร์สมาชิกจาก person มีเพียง person เดียว ซึ่งถูกตั้งค่าเป็นโปรโตไทป์ของบ็อบและเบ็น (person ถูกใช้เป็นอ็อบเจ็กต์แรกในโพรโทไทป์เชนเพื่อค้นหาสมาชิกที่ร้องขอซึ่งไม่มีอยู่ในอินสแตนซ์) ปัญหาของโค้ดข้างต้นคือ บ็อบและเบ็นควรมีสมาชิก food ของตัวเอง นี่คือที่มาของฟังก์ชันคอนสตรัคเตอร์ มันถูกใช้เพื่อสร้างสมาชิกเฉพาะของอินสแตนซ์ คุณยังสามารถส่งอาร์กิวเมนต์ให้กับมันเพื่อกำหนดค่าของสมาชิกเฉพาะดังกล่าว

โค้ดถัดไปแสดงวิธีอีกวิธีในการใช้งานฟังก์ชันตัวสร้าง รูปแบบการเขียนต่างกันแต่แนวคิดเหมือนกัน:

  1. กำหนดวัตถุที่มีสมาชิกซึ่งจะเหมือนกันสำหรับหลายอินสแตนซ์ (คนคือแบบแผนสำหรับบ็อบและเบ็น และสามารถเป็นสำหรับจิลลี่, มารี, แคลร์ ...)
  2. กำหนดสมาชิกเฉพาะของอินสแตนซ์ที่ควรเป็นเอกลักษณ์สำหรับอินสแตนซ์ (บ็อบและเบ็น)
  3. สร้างอินสแตนซ์ที่รันโค้ดในขั้นตอนที่ 2

ด้วยฟังก์ชันคอนสตรักเตอร์คุณจะตั้งค่าโปรโตไทป์ในขั้นตอนที่ 2 ในโค้ดด้านล่างนี้เราตั้งค่าโปรโตไทป์ในขั้นตอนที่ 3

ในโค้ดนี้ฉันได้ลบชื่อออกจากโปรโตไทป์รวมถึงอาหารด้วย เพราะคุณน่าจะเป็นคนที่ตั้งค่ามันใหม่เกือบจะทันทีเมื่อสร้างอินสแตนซ์ ชื่อกลายเป็นสมาชิกเฉพาะอินสแตนซ์ที่มีค่าดีฟอลต์ตั้งไว้ในฟังก์ชันคอนสตรักเตอร์ เนื่องจากสมาชิกอาหารก็ถูกย้ายจากโปรโตไทป์ไปยังสมาชิกเฉพาะอินสแตนซ์เช่นกัน มันจะไม่ส่งผลต่อ 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);//=[]

คุณอาจพบรูปแบบที่คล้ายกันซึ่งมีความแข็งแรงมากขึ้นเพื่อช่วยในการสร้างวัตถุและการกำหนดวัตถุ

มรดก

โค้ดต่อไปนี้แสดงวิธีการสืบทอด งานพื้นฐานก็เหมือนกับในโค้ดก่อนหน้านี้เพียงแต่มีเพิ่มเข้ามานิดหน่อย

  1. กำหนดสมาชิกเฉพาะตัวของอินสแตนซ์ของอ็อบเจ็กต์ (ฟังก์ชัน Hamster และ RussionMini)
  2. ตั้งค่าส่วนต้นแบบของการสืบทอด (RussionMini.prototype=Object.create(Hamster.prototype))
  3. กำหนดสมาชิกที่สามารถแชร์ระหว่างอินสแตนซ์ได้ (Hamster.prototype และ RussionMini.prototype)
  4. สร้างอินสแตนซ์ที่รันโค้ดในขั้นตอนที่ 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 โดยพื้นฐานมันจะส่งค่าของอาร์กิวเมนต์ตัวที่สอง (ไม่ได้รับการสนับสนุนใน polyfill) โดยมีอาร์กิวเมนต์ตัวแรกเป็น prototype ของวัตถุที่ส่งกลับ

ถ้าไม่ได้ระบุอาร์กิวเมนต์ที่สอง มันจะคืนค่าออบเจ็กต์ว่างโดยใช้อาร์กิวเมนต์แรกเป็นโปรโตไทป์ของออบเจ็กต์ที่คืนค่า (ออบเจ็กต์แรกที่จะใช้ในสายโซ่โปรโตไทป์ของออบเจ็กต์ที่คืนค่า)

บางคนอาจตั้ง prototype ของ RussionMini เป็นอินสแตนซ์ของ Hamster (RussionMini.prototype=new Hamster()) ซึ่งไม่เป็นที่ต้องการเพราะแม้ว่ามันจะทำงานได้เหมือนกัน (prototype ของ RussionMini คือ Hamster.prototype) แต่มันก็ทำให้สมาชิกอินสแตนซ์ของ Hamster กลายเป็นสมาชิกของ RussionMini.prototype ด้วย ดังนั้น RussionMini.prototype.food จะมีอยู่แต่เป็นสมาชิกที่แชร์ร่วมกัน (จำ bob และ ben ใน "ข้อมูลเพิ่มเติมเกี่ยวกับ prototype" ได้ไหม?) สมาชิก food จะถูกซ้อนเมื่อสร้าง RussionMini เพราะโค้ดของ Hamster จะทำงานด้วย Hamster.apply(this,arguments); ซึ่งทำให้ this.food=[] ทำงาน แต่สมาชิกอื่น ๆ ของ Hamster จะยังคงเป็นสมาชิกของ RussionMini.prototype.

อีกเหตุผลหนึ่งอาจเป็นเพราะว่าการสร้างแฮมสเตอร์ต้องมีการคำนวณที่ซับซ้อนมากบนอาร์กิวเมนต์ที่ส่งมา ซึ่งอาจยังไม่พร้อมใช้งาน คุณสามารถส่งอาร์กิวเมนต์จำลองเข้าไปได้ แต่ก็อาจทำให้โค้ดของคุณซับซ้อนเกินความจำเป็น

การขยายและการแทนที่ฟังก์ชันของพาเรนต์

บางครั้ง children จำเป็นต้องขยายฟังก์ชันของ parent

คุณต้องการให้ ‘child’ (=RussionMini) ทำบางสิ่งเพิ่มเติม เมื่อ RussionMini สามารถเรียกโค้ดของ Hamster เพื่อทำบางสิ่งแล้วจึงทำบางสิ่งเพิ่มเติมได้ คุณก็ไม่จำเป็นต้องคัดลอกและวางโค้ดของ Hamster ไปยัง RussionMini

ในตัวอย่างต่อไปนี้ เราสมมติว่า แฮมสเตอร์สามารถวิ่งได้ 3 กิโลเมตรต่อชั่วโมง แต่รัสเซียนมินิสามารถวิ่งได้เพียงครึ่งหนึ่งของความเร็ว เราสามารถระบุค่า 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 ระดับ (ลูก => พ่อ => ปู่) หรือใช้ทรัพยากรมากขึ้นโดยการใช้งาน super ผ่าน closures

ในการแทนที่เมธอดของ Parent (=Hamster) คุณต้องทำเหมือนกัน แต่ไม่ต้องใช้ Hamster.prototype.parentMethod.call(this,....

ตัวสร้างของสิ่งนี้

คุณสมบัติ constructor ถูกรวมอยู่ในโปรโตไทป์โดย JavaScript คุณสามารถเปลี่ยนมันได้แต่ควรชี้ไปยังฟังก์ชัน constructor ดังนั้น 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 แมวไม่ใช่ Movable แต่แมวสามารถเคลื่อนไหวได้ ในภาษาที่ใช้คลาส แมวจะต้องใช้การ implement Movable ใน JavaScript เราสามารถกำหนด Movable และกำหนดการ implement ที่นี่ แมวสามารถ override, ขยาย หรือใช้การ implement เริ่มต้นของมันได้

สำหรับ Movable เรามีสมาชิกเฉพาะตัวอย่าง (เช่น location) และเรามีสมาชิกที่ไม่เฉพาะตัวอย่าง (เช่น ฟังก์ชัน move()) สมาชิกเฉพาะตัวอย่างจะถูกตั้งค่าโดยการเรียก mxIns (ซึ่งเพิ่มโดยฟังก์ชัน mixin helper) เมื่อสร้างตัวอย่าง สมาชิกของ Prototype จะถูกคัดลอกทีละตัวไปยัง Cat.prototype จาก Movable.prototype โดยใช้ฟังก์ชัน mixin helper

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 หมายถึงอินสแตนซ์ปัจจุบัน

ตัวแปร this จริงๆ แล้วหมายถึงวัตถุที่เรียกใช้งาน มันหมายถึงวัตถุที่มาก่อนฟังก์ชัน

เพื่อความชัดเจน ดูโค้ดต่อไปนี้:

theInvokingObject.thefunction();

กรณีที่นี่จะอ้างถึงวัตถุผิดมักเกิดขึ้นเมื่อแนบตัวฟังเหตุการณ์ การเรียกกลับ หรือการตั้งเวลาและช่วงเวลา ใน 2 บรรทัดถัดไปของโค้ด เรา ส่งผ่าน ฟังก์ชัน, เราไม่ได้เรียกใช้ฟังก์ชัน การส่งผ่านฟังก์ชันคือ: someObject.aFunction และการเรียกใช้ฟังก์ชันคือ: someObject.aFunction() ค่า this ไม่ได้อ้างถึงวัตถุที่ประกาศฟังก์ชัน แต่จะอ้างถึงวัตถุที่ เรียกใช้ ฟังก์ชันนั้น

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

เพื่อให้ this ในกรณีข้างต้นอ้างอิงถึง someObject คุณสามารถส่ง closure แทนการส่งฟังก์ชันโดยตรงได้:

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);) เราสมมติว่า Hamster ใช้ arguments เดียวกันกับ RussionMini ในลำดับเดียวกัน สำหรับฟังก์ชันที่เรียกฟังก์ชันอื่น ๆ ผมมักจะใช้วิธีอื่นในการส่ง arguments

ฉันมักจะส่งวัตถุหนึ่งไปยังฟังก์ชันและให้ฟังก์ชันนั้นแก้ไขสิ่งที่มันต้องการ (ตั้งค่าดีฟอลต์) จากนั้นฟังก์ชันนั้นจะส่งต่อไปยังฟังก์ชันอื่นที่ก็จะทำแบบเดียวกันไปเรื่อยๆ นี่คือตัวอย่าง:

//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 หรือใส่ตัวแปรส่วนตัวทั้งหมดในตัวแปรวัตถุที่ชื่อว่า _

คุณสามารถใช้คลอสเจอร์ในการสร้างสมาชิกส่วนตัวได้ แต่สมาชิกส่วนตัวเฉพาะตัวตัวอย่างสามารถเข้าถึงได้โดยฟังก์ชันที่ไม่ได้อยู่บนโปรโตไทป์เท่านั้น

การไม่ใช้ closures ในการทำ privates จะทำให้การใช้งานภายในรั่วไหลและทำให้คุณหรือผู้ใช้ที่ขยายโค้ดของคุณสามารถใช้สมาชิกที่ไม่ใช่ส่วนหนึ่งของ API สาธารณะของคุณได้ ซึ่งสิ่งนี้อาจเป็นทั้งข้อดีและข้อเสีย

มันดีเพราะมันทำให้คุณและคนอื่นสามารถล้อเลียนสมาชิกบางคนเพื่อทดสอบได้ง่าย มันให้โอกาสคนอื่นในการปรับปรุง (แก้ไข) โค้ดของคุณได้ง่าย แต่สิ่งนี้ก็ไม่ดีเช่นกันเพราะไม่มีการรับประกันว่าเวอร์ชันถัดไปของโค้ดของคุณจะมีการใช้งานเหมือนเดิมหรือสมาชิกส่วนตัวเหมือนเดิม

การใช้ closures คุณจะไม่ให้ทางเลือกกับผู้อื่น และการใช้การตั้งชื่อพร้อมเอกสาร คุณจะให้ทางเลือก สิ่งนี้ไม่ได้จำกัดเฉพาะ JavaScript ในภาษาอื่น ๆ คุณสามารถตัดสินใจไม่ใช้สมาชิกส่วนตัวเพราะคุณเชื่อใจผู้อื่นว่ารู้ว่ากำลังทำอะไรอยู่และให้ทางเลือกพวกเขาที่จะทำตามที่ต้องการ (พร้อมความเสี่ยงที่เกี่ยวข้อง)

ถ้าคุณยังยืนยันที่จะใช้ private อยู่ ลวดลายต่อไปนี้อาจช่วยได้ แม้ว่าจะไม่ได้ใช้ private แต่จะใช้ protected แทน

คำตอบ #2

โปรโตไทป์ จะไม่ถูกสร้างเป็นอินสแตนซ์สำหรับแต่ละอินสแตนซ์ของวัตถุ

Hamster.prototype.food = []

ทุกอินสแตนซ์ของแฮมสเตอร์จะแบ่งปันอาร์เรย์นั้น

ถ้าคุณต้องการ (และในกรณีนี้คุณต้องการ) การเก็บอาหารแยกกันสำหรับแต่ละแฮมสเตอร์ คุณต้องสร้างคุณสมบัติบนอินสแตนซ์นั้น ตัวอย่างเช่น:

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

เพื่อตอบคำถามของคุณเกี่ยวกับตัวอย่างที่ 1 หากมันไม่พบคุณสมบัติในห่วงโซ่ต้นแบบที่ไหน มันจะสร้างคุณสมบัติบนวัตถุเป้าหมาย

บทความที่เกี่ยวข้อง