การขยายวัตถุใน Javascript


164

ขณะนี้ฉันกำลังเปลี่ยนจาก Java เป็น Javascript และเป็นเรื่องยากสำหรับฉันที่จะหาวิธีขยายวัตถุในแบบที่ฉันต้องการ

ฉันเคยเห็นหลายคนบนอินเทอร์เน็ตใช้วิธีที่เรียกว่าขยายบนวัตถุ รหัสจะมีลักษณะดังนี้:

var Person = {
   name : 'Blank',
   age  : 22
}

var Robot = Person.extend({
   name : 'Robo',
   age  : 4
)}

var robot = new Robot();
alert(robot.name); //Should return 'Robo'

ไม่มีใครรู้วิธีการทำงานนี้หรือไม่? ฉันได้ยินมาว่าคุณต้องเขียน

Object.prototype.extend = function(...);

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


กลับจริง แต่นั่นคือเหตุผลที่ผมขอให้ :)
Wituz

2
ฉันอยากจะแนะนำให้ไปถึง tuutorial ที่สวยงามบน MDN: - developer.mozilla.org/en/…
Pranav

ถ้าหลังจากอ่านเอกสารที่ดีเหล่านั้นแล้วคุณยังอยากรู้เกี่ยวกับextendฟังก์ชั่นฉันได้ตั้งตัวอย่างไว้ที่นี่: jsfiddle.net/k9LRd
Codrin Eugeniu

2
ฉันยังแนะนำไม่คิดเกี่ยวกับมันอย่างเคร่งครัดเป็น 'เปลี่ยนจากจาวาเป็น JavaScript' และอื่น ๆ เป็น 'เรียนรู้ภาษาใหม่จาวาสคริปต์ที่มีไวยากรณ์คล้ายกับจาวา'
Toni Leigh

คำตอบ:


195

คุณต้องการ 'สืบทอด' จากวัตถุต้นแบบของบุคคล:

var Person = function (name) {
    this.name = name;
    this.type = 'human';
};

Person.prototype.info = function () {
    console.log("Name:", this.name, "Type:", this.type);
};

var Robot = function (name) {
    Person.apply(this, arguments);
    this.type = 'robot';
};

Robot.prototype = Person.prototype;  // Set prototype to Person's
Robot.prototype.constructor = Robot; // Set constructor back to Robot

person = new Person("Bob");
robot = new Robot("Boutros");

person.info();
// Name: Bob Type: human

robot.info();
// Name: Boutros Type: robot

4
ฉันมีหนึ่งคำถาม: คอนPerson()สตรัคเตอร์ถูกเรียกเมื่อคุณทำnew Robot()อย่างไร? ดูเหมือนว่าผมว่าคุณควรจะเรียกว่าคอนสตรัคชั้นฐานแทนการทำthis.name = name;ในRobot()คอนสตรัค ...
อเล็กซิส Wilke

21
@AlexisWilke: Person.apply(this, arguments);อ๋อคุณควรจะเรียก นอกจากนี้ยังจะดีกว่าที่จะใช้แทนRobot.prototype = Object.create(Person.prototype); new Person();
เฟลิกซ์คลิง

18
ตามที่ระบุไว้โดยเฟลิกซ์ 'Robot.prototype = Person.prototype;' เป็นความคิดที่ไม่ดีถ้าใครต้องการประเภท 'หุ่นยนต์' ให้มีอินสแตนซ์ต้นแบบของตัวเอง การเพิ่มฟังก์ชั่นเฉพาะโรบ็อตใหม่จะเพิ่มไปยังบุคคล
James Wilkins

20
ตัวอย่างนี้ผิดอย่างสมบูรณ์ ด้วยการทำเช่นนั้นคุณได้เปลี่ยนแปลงต้นแบบของบุคคล นั่นไม่ใช่การสืบทอดและคุณมีความเสี่ยงที่จะทำเรื่องยุ่งเหยิงในคลาส Person ดูคำตอบที่แนะนำให้ใช้ Object.create () นั่นคือวิธีที่ถูกต้องในการทำสิ่งต่าง ๆ
nicolas-van

6
@osahyoun คำตอบนี้มีการจัดอันดับสูงในการค้นหาของ Google ฉันอยากจะแนะนำให้คุณแก้ไขรหัสและแก้ไขต้นแบบลูกโซ่ตามที่แนะนำโดยความคิดเห็นอื่น ๆ ที่นี่
raphaëλ

103

โลกที่ไม่มีคำหลัก "ใหม่"

และไวยากรณ์ "prose-like" ที่ง่ายขึ้นด้วย Object.create ()

* ตัวอย่างนี้ถูกอัพเดตสำหรับคลาส ES6

ปิดครั้งแรกจำได้ว่า JavaScript เป็นภาษา prototypal มันไม่ได้เป็นคลาส ดังนั้นการเขียนในรูปแบบต้นแบบแสดงให้เห็นถึงลักษณะที่แท้จริงของมันและสามารถง่ายมากร้อยแก้วเหมือนและมีประสิทธิภาพ

TLDR;

const Person = { name: 'Anonymous' } // person has a name

const jack = Object.create(Person)   // jack is a person
jack.name = 'Jack'                   // and has a name 'Jack'

ไม่มีคุณไม่จำเป็นต้องก่อสร้างไม่newinstantiation ( อ่านเหตุผลที่คุณไม่ควรใช้new ) ไม่มีไม่มีตลกตลกsuper __constructคุณเพียงแค่สร้างออบเจ็กต์จากนั้นขยายหรือปรับเปลี่ยน

( ถ้าคุณทราบเกี่ยว getters และ setters ดู "อ่านเพิ่มเติม" เพื่อดูว่ารูปแบบนี้จะช่วยให้คุณฟรีและ getters setters ในทางจาวาสคริเพิ่งเดิมไว้สำหรับการและวิธีการที่มีประสิทธิภาพที่พวกเขามี .)

ไวยากรณ์แบบร้อยแก้วที่เหมือนกัน: ฐาน protoype

const Person = {

   //attributes
   firstName : 'Anonymous', 
   lastName: 'Anonymous',
   birthYear  : 0,
   type : 'human',

   //methods
   name() { return this.firstName + ' ' + this.lastName },
   greet() {
       console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' )
   },
   age() {
      // age is a function of birth time.
   }
}

const person = Object.create(Person). // that's it!

มองดูได้อย่างรวดเร็ว

ส่วนขยายการสร้างการสืบทอด Person

* เงื่อนไขที่ถูกต้องและของพวกเขาprototypes descendantsไม่มีและไม่มีความจำเป็นในการclassesinstances

const Skywalker = Object.create(Person)
Skywalker.lastName = 'Skywalker'

const anakin = Object.create(Skywalker)
anakin.firstName = 'Anakin'
anakin.birthYear = '442 BBY'
anakin.gender = 'male' // you can attach new properties.
anakin.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'

Person.isPrototypeOf(Skywalker) // outputs true
Person.isPrototypeOf(anakin) // outputs true
Skywalker.isPrototypeOf(anakin) // outputs true

วิธีหนึ่งในการสร้างวิธีการ "เริ่มต้น" ในการสร้างdescendantคือการแนบ#createเมธอด:

Skywalker.create = function(firstName, gender, birthYear) {

    let skywalker = Object.create(Skywalker)

    Object.assign(skywalker, {
        firstName,
        birthYear,
        gender,
        lastName: 'Skywalker',
        type: 'human'
    })

    return skywalker
}

const anakin = Skywalker.create('Anakin', 'male', '442 BBY')

วิธีด้านล่างมีความสามารถในการอ่านต่ำกว่า:

เปรียบเทียบกับ "คลาสสิค" ที่เทียบเท่า:

function Person (firstName, lastName, birthYear, type) {
    this.firstName = firstName 
    this.lastName = lastName
    this.birthYear = birthYear
    this.type = type
}

// attaching methods
Person.prototype.name = function() { return firstName + ' ' + lastName }
Person.prototype.greet = function() { ... }
Person.prototype.age = function() { ... }

function Skywalker(firstName, birthYear) {
    Person.apply(this, [firstName, 'Skywalker', birthYear, 'human'])
}

// confusing re-pointing...
Skywalker.prototype = Person.prototype
Skywalker.prototype.constructor = Skywalker

const anakin = new Skywalker('Anakin', '442 BBY')

Person.isPrototypeOf(anakin) // returns false!
Skywalker.isPrototypeOf(anakin) // returns false!

การอ่านรหัสโดยใช้สไตล์ "คลาสสิค" นั้นไม่ดี

คลาส ES6

เป็นที่ยอมรับว่าปัญหาเหล่านี้บางส่วนถูกกำจัดให้หมดโดยคลาส ES6 แต่ยัง:

class Person {
    constructor(firstName, lastName, birthYear, type) {
        this.firstName = firstName 
        this.lastName = lastName
        this.birthYear = birthYear
        this.type = type
    }
    name() { return this.firstName + ' ' + this.lastName }
    greet() { console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' ) }
}

class Skywalker extends Person {
    constructor(firstName, birthYear) {
        super(firstName, 'Skywalker', birthYear, 'human')
    }
}

const anakin = new Skywalker('Anakin', '442 BBY')

// prototype chain inheritance checking is partially fixed.
Person.isPrototypeOf(anakin) // returns false!
Skywalker.isPrototypeOf(anakin) // returns true

การแตกกิ่งต้นแบบ

// create a `Robot` prototype by extending the `Person` prototype:
const Robot = Object.create(Person)
Robot.type = 'robot'
Robot.variant = '' // add properties for Robot prototype

แนบวิธีการเฉพาะ Robot

// Robots speak in binaries, so we need a different greet function:
Robot.machineGreet = function() { /*some function to convert strings to binary */ }

// morphing the `Robot` object doesn't affect `Person` prototypes
anakin.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'
anakin.machineGreet() // error

ตรวจสอบมรดก

Person.isPrototypeOf(Robot) // outputs true
Robot.isPrototypeOf(Skywalker) // outputs false

คุณมีทุกสิ่งที่คุณต้องการแล้ว! ไม่มีสิ่งก่อสร้างไม่มีการสร้างอินสแตนซ์ ทำความสะอาดร้อยแก้วที่ชัดเจน

อ่านเพิ่มเติม

Writability, Configurability และ Getters และ Setters ฟรี!

สำหรับ getters และ setters ฟรีหรือการกำหนดค่าเพิ่มเติมคุณสามารถใช้อาร์กิวเมนต์ที่สองของ Object.create () 'aka aka PropertiesObject มันยังมีอยู่ใน# Object.definePropertyและ# Object.defineProperties

เพื่อแสดงให้เห็นว่ามีประสิทธิภาพเพียงใดสมมติว่าเราต้องการให้ทุกคนRobotทำจากโลหะอย่างเคร่งครัด (ผ่านwritable: false) และสร้างpowerConsumptionค่ามาตรฐาน(ผ่านตัวรับและตัวตั้งค่า)

const Robot = Object.create(Person, {
    // define your property attributes
    madeOf: { 
        value: "metal",
        writable: false,
        configurable: false,
        enumerable: true
    },
    // getters and setters, how javascript had (naturally) intended.
    powerConsumption: {
        get() { return this._powerConsumption },
        set(value) { 
            if (value.indexOf('MWh')) return this._powerConsumption = value.replace('M', ',000k') 
            this._powerConsumption = value
            throw new Error('Power consumption format not recognised.')
        }  
    }
})

const newRobot = Object.create(Robot)
newRobot.powerConsumption = '5MWh'
console.log(newRobot.powerConsumption) // outputs 5,000kWh

และต้นแบบทั้งหมดRobotไม่สามารถอย่างอื่นเพราะmadeOfwritable: false

const polymerRobot = Object.create(Robot)

polymerRobot.madeOf = 'polymer'

console.log(polymerRobot.madeOf) // outputs 'metal'

Mixins (ใช้ # Object.assign) - Anakin Skywalker

คุณรู้สึกได้ไหมว่านี่กำลังจะเกิดอะไรขึ้น ... ?

const darthVader = Object.create(anakin)
// for brevity, property assignments are skipped because you get the point by now.

Object.assign(darthVader, Robot)

Darth Vader ได้รับวิธีการRobot:

darthVader.greet() // inherited from `Person`, outputs "Hi, my name is Darth Vader..."
darthVader.machineGreet() // inherited from `Robot`, outputs 001010011010...

พร้อมกับสิ่งแปลก ๆ อื่น ๆ :

console.log(darthVader.type) // outputs robot.
Robot.isPrototypeOf(darthVader) // returns false.
Person.isPrototypeOf(darthVader) // returns true.

เอาละไม่ว่า Darth Vader จะเป็นคนหรือเครื่องจักรจริง ๆ

“ ตอนนี้เขามีกลไกมากกว่ามนุษย์บิดและชั่ว” - โอบีวันเคโนบี

"ฉันรู้ว่าคุณเก่ง" - Luke Skywalker

พิเศษ - ไวยากรณ์ที่สั้นกว่าเล็กน้อยด้วย # Object.assign

รูปแบบนี้จะทำให้ไวยากรณ์ของคุณสั้นลง แต่ ES6 # Object.assign สามารถตัดให้สั้นลงได้อีก (สำหรับ polyfill เพื่อใช้กับเบราว์เซอร์รุ่นเก่าดูMDN บน ES6 )

//instead of this
const Robot = Object.create(Person)
Robot.name = "Robot"
Robot.madeOf = "metal"

//you can do this
const Robot = Object.create(Person)
Object.assign(Robot, {
    name: "Robot",
    madeOf: "metal"
    // for brevity, you can imagine a long list will save more code.
})

8
มี upvote ที่ไม่ได้ใช้ฟังก์ชั่นคอนสตรัค
nsmarks

1
โปรแกรมเมอร์ "ฝึกหัดคลาสสิก" คุณหมายถึงอะไร
Petra

1
ฉันมาจากแนวคิด OOP แบบดั้งเดิมและคำตอบนี้ช่วยฉันได้มาก สองคำถามเกี่ยวกับรหัส: 1) ES2015 ในปัจจุบันObject.assign(Robot, {a:1}เป็นทางเลือกที่ดีสำหรับextend()วิธีการของคุณหรือไม่? 2) วิธีการแทนที่greet()วิธีการเพื่อให้ส่งกลับข้อความเดียวกัน แต่มี "ผนวกแทนที่" ทักทาย?
Barry Staes

2
1) #Object.assignทำตัวเหมือนเป็นทางเลือกที่ดี แต่การสนับสนุนเบราว์เซอร์ต่ำกว่า atm 2) คุณจะใช้__proto__คุณสมบัติของวัตถุเพื่อเข้าถึงฟังก์ชั่นทักทายของต้นแบบ จากนั้นคุณเรียกฟังก์ชันต้นแบบทักทายด้วยขอบเขตของผู้ถูกส่งผ่านในกรณีนี้ฟังก์ชันเป็นบันทึกคอนโซลดังนั้นจึงไม่สามารถ "ผนวก" แต่ด้วยตัวอย่างนี้ฉันคิดว่าคุณได้รับการดริฟท์ skywalker.greet = function() { this.__proto__.greet.call(this); console.log('a greet override'); }
Calvintwr

1
นั่นคือการอภิปรายที่ควรมีกับผู้ดูแลข้อมูลจำเพาะภาษา ECMAScript โดยทั่วไปฉันเห็นด้วย แต่ฉันต้องทำงานกับสิ่งที่ฉันมี

51

หากคุณยังไม่ได้หาวิธีใช้คุณสมบัติการเชื่อมโยงของวัตถุ JavaScript เพื่อเพิ่มฟังก์ชั่นการขยายไปยังที่Object.prototypeแสดงด้านล่าง

Object.prototype.extend = function(obj) {
   for (var i in obj) {
      if (obj.hasOwnProperty(i)) {
         this[i] = obj[i];
      }
   }
};

จากนั้นคุณสามารถใช้ฟังก์ชั่นนี้ตามที่แสดงด้านล่าง

var o = { member: "some member" };
var x = { extension: "some extension" };

o.extend(x);

18
ระวังว่าสิ่งนี้จะสร้างพอยน์เตอร์ให้กับออบเจ็กต์ดั้งเดิมในคลาส 'child' เมื่อใช้ออบเจ็กต์ / อาร์เรย์ในคลาส 'parent' ในการทำอย่างละเอียด: หากคุณมีวัตถุหรืออาเรย์ในคลาสพาเรนต์ของคุณให้แก้ไขในคลาสย่อยที่ขยายบนฐานนั้นจริง ๆ แล้วจะแก้ไขมันสำหรับคลาสย่อยทั้งหมดที่ขยายในคลาสพื้นฐานเดียวกันนี้
แฮโรลด์

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

30

วิธีการที่แตกต่าง: Object.create

ตาม @osahyoun คำตอบฉันค้นหาต่อไปนี้เป็นวิธีที่ดีกว่าและมีประสิทธิภาพในการ 'สืบทอด' จากวัตถุต้นแบบของบุคคล:

function Person(name){
    this.name = name;
    this.type = 'human';
}

Person.prototype.info = function(){
    console.log("Name:", this.name, "Type:", this.type);
}

function Robot(name){
    Person.call(this, name)
    this.type = 'robot';
}

// Set Robot's prototype to Person's prototype by
// creating a new object that inherits from Person.prototype,
// and assigning it to Robot.prototype
Robot.prototype = Object.create(Person.prototype);

// Set constructor back to Robot
Robot.prototype.constructor = Robot;

สร้างอินสแตนซ์ใหม่:

var person = new Person("Bob");
var robot = new Robot("Boutros");

person.info(); // Name: Bob Type: human
robot.info();  // Name: Boutros Type: robot

ตอนนี้โดยใช้Object.create :

Person.prototype.constructor !== Robot

ตรวจสอบเอกสารMDNด้วย


2
เพียงแค่ต้องการบอกว่า @GaretClaborn ทำงานได้อย่างถูกต้อง แต่คุณไม่ได้ส่งnameพารามิเตอร์ไปยังผู้สร้างหลักเช่นนี้: jsfiddle.net/3brm0a7a/3 (ความแตกต่างอยู่ในบรรทัดที่ 8)
xPheRe

1
@xPheRe Ah ฉันเห็นขอบคุณ ฉันแก้ไขคำตอบเพื่อสะท้อนการเปลี่ยนแปลงนั้น
Garet Claborn

1
@ xPheRe ฉันเดาว่าฉันให้ความสำคัญกับการพิสูจน์จุดมากขึ้นเมื่อฉันเพิ่มโซลูชันนี้ ขอบคุณ
Lior Elrom

1
คำตอบที่ดี +1 คุณสามารถดู ECMAScript 6 คลาสคำหลักและส่วนขยายมีอยู่: developer.mozilla.org/en-US/docs/Web/JavaScript/ …
Benjamin Poignant

26

ใน ES6คุณสามารถใช้ตัวดำเนินการแบบกระจายได้

var mergedObj = { ...Obj1, ...Obj2 };

โปรดทราบว่า Object.assign () ทริกเกอร์ setters ในขณะที่ไวยากรณ์การแพร่กระจายไม่ได้

สำหรับข้อมูลเพิ่มเติมโปรดดูที่ลิงก์MDN - ไวยากรณ์การแพร่กระจาย


คำตอบเก่า:

ใน ES6มีObject.assignไว้สำหรับการคัดลอกค่าคุณสมบัติ ใช้{}เป็นพารามิเตอร์แรกหากคุณไม่ต้องการแก้ไขวัตถุเป้าหมาย (พารามิเตอร์แรกผ่านไป)

var mergedObj = Object.assign({}, Obj1, Obj2);

ดูรายละเอียดเพิ่มเติมได้ที่ลิงค์MDN - Object.assign ()

ในกรณีที่คุณต้องการPolyfill สำหรับ ES5ลิงก์ก็มีให้เช่นกัน :)


18

และอีกหนึ่งปีต่อมาฉันสามารถบอกคุณได้ว่ามีคำตอบที่ดีอีกข้อหนึ่ง

หากคุณไม่ชอบวิธีการทำต้นแบบในการขยายวัตถุ / คลาสให้ทำตามนี้: https://github.com/haroldiedema/joii

ตัวอย่างรหัสย่อของความเป็นไปได้ (และอีกมากมาย):

var Person = Class({

    username: 'John',
    role: 'Employee',

    __construct: function(name, role) {
        this.username = name;
        this.role = role;
    },

    getNameAndRole: function() {
        return this.username + ' - ' + this.role;
    }

});

var Manager = Class({ extends: Person }, {

  __construct: function(name)
  {
      this.super('__construct', name, 'Manager');
  }

});

var m = new Manager('John');
console.log(m.getNameAndRole()); // Prints: "John - Manager"

ดีฉันยังคงมี 2 เดือนจนถึง 2 ปีขึ้น: P ทั้งสองวิธี JOII 3.0 เป็นเรื่องเกี่ยวกับที่จะปล่อย :)
แฮโรลด์

1
ทำให้ 3 ปีต่อมา

แนวคิดที่น่าสนใจ แต่ไวยากรณ์นั้นดูน่าเกลียดมาก คุณน่าจะดีกว่ารอการเรียน ES6 ให้เสถียร
sleepycal

ฉันเห็นด้วยอย่างสมบูรณ์ @leepycal แต่น่าเสียดายที่อย่างน้อย 5 ปีก่อนที่เบราว์เซอร์หลัก / เบราว์เซอร์ทั่วไปจะใช้งานได้ ดังนั้นจนกว่าจะถึงเวลานั้นสิ่งนี้จะต้องทำ ...
แฮโรลด์

12

ผู้ที่ยังคงดิ้นรนหาวิธีที่ง่ายและดีที่สุดคุณสามารถใช้Spread Syntaxเพื่อขยายวัตถุ

var person1 = {
      name: "Blank",
      age: 22
    };

var person2 = {
      name: "Robo",
      age: 4,
      height: '6 feet'
    };
// spread syntax
let newObj = { ...person1, ...person2 };
console.log(newObj.height);

หมายเหตุ:โปรดจำไว้ว่าทรัพย์สินที่อยู่ไกลสุดทางด้านขวาจะมีลำดับความสำคัญสูงสุด ในตัวอย่างperson2นี้อยู่ทางด้านขวาดังนั้นnewObjจะมีชื่อRoboอยู่


8

คุณอาจต้องการที่จะต้องพิจารณาการใช้ห้องสมุดผู้ช่วยเช่นunderscore.jsซึ่งมีการดำเนินงานเป็นของตัวเองของextend()

และเป็นวิธีที่ดีในการเรียนรู้โดยดูจากซอร์สโค้ด หน้ารหัสต้นฉบับข้อเขียนเป็นประโยชน์มาก


1
ตัวอย่างของการ_.extend()ทำงานของ underscore.js ทำให้การทำงานของมันค่อนข้างชัดเจน: lostechies.com/chrismissal/2012/10/05/…
Lemmings19

6

Mozilla 'ประกาศ' วัตถุที่ขยายจาก ECMAScript 6.0:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends

หมายเหตุ: นี่เป็นเทคโนโลยีทดลองซึ่งเป็นส่วนหนึ่งของข้อเสนอ ECMAScript 6 (Harmony)

class Square extends Polygon {
  constructor(length) {
    // Here, it calls the parent class' constructor with lengths
    // provided for the Polygon's width and height
    super(length, length);
    // Note: In derived classes, super() must be called before you
    // can use 'this'. Leaving this out will cause a reference error.
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }

  set area(value) {
    this.area = value;     } 
}

เทคโนโลยีนี้มีให้ใน Gecko (Google Chrome / Firefox) - 03/2015 งานสร้างทุกคืน


4

ในส่วนของโครงการที่มีการดำเนินงานบางส่วนของวัตถุขยาย: ขีด jQuery, lodash: ขยาย

นอกจากนี้ยังมีการใช้จาวาสคริปต์ที่บริสุทธิ์ซึ่งเป็นส่วนหนึ่งของ ECMAscript 6: Object.assign : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objectsassign/assign


“ การใช้งานจาวาสคริปต์อย่างแท้จริง” ไม่ได้อ้างถึงสิ่งที่ใช้งานด้วยจาวาสคริปต์เพียงอย่างเดียวไม่ใช่ไปยังฟังก์ชั่นที่จัดเตรียมโดยสภาพแวดล้อมซึ่งอาจนำไปใช้จริง
binki

1
@binki, ฉันหมายถึงการใช้งานจาวาสคริปต์ดั้งเดิม - เป็นส่วนหนึ่งของมาตรฐาน ECMAScript 2015 (ES6)
Cezary Daniel Nowak

2
Function.prototype.extends=function(ParentClass) {
    this.prototype = new ParentClass();
    this.prototype.constructor = this;
}

แล้ว:

function Person() {
    this.name = "anonym"
    this.skills = ["abc"];
}
Person.prototype.profile = function() {
    return this.skills.length // 1
};

function Student() {} //well extends fom Person Class
Student.extends(Person)

var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

อัปเดต 01/2017:

ได้โปรดละเว้นคำตอบของฉันปี 2015 เนื่องจาก Javascript รองรับextendsคำหลักตั้งแต่ ES6 (Ecmasctipt6)

- ES6:

class Person {
   constructor() {
     this.name = "anonym"
     this.skills = ["abc"];
   }

   profile() {
    return this.skills.length // 1
   }

}

Person.MAX_SKILLS = 10;
class Student extends Person {


} //well extends from Person Class

//-----------------
var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

- ES7:

class Person {
    static MAX_SKILLS = 10;
    name = "anonym"
    skills = ["abc"];

    profile() {
      return this.skills.length // 1
    }

}
class Student extends Person {


} //well extends from Person Class

//-----------------
var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

1
โดยการเรียกnew ParentClass()ก่อนที่จะเขียนทับนวกรรมิกคุณได้ทำการสร้าง parent parent แล้ว ฉันไม่คิดว่ามันจะเป็นพฤติกรรมที่ถูกต้องหากคุณถามฉัน ...
Harold

1

สรุป:

จาวาสคริปต์ที่ใช้กลไกซึ่งเรียกว่ามรดก prototypal Prototypal inheritance ใช้เมื่อค้นหาคุณสมบัติบนวัตถุ เมื่อเราขยายคุณสมบัติในจาวาสคริปต์เรากำลังสืบทอดคุณสมบัติเหล่านี้จากวัตถุจริง มันทำงานในลักษณะดังต่อไปนี้:

  1. เมื่อมีการร้องขอคุณสมบัติของวัตถุ (เช่นmyObj.fooหรือmyObj['foo']) เอ็นจิน JS จะค้นหาคุณสมบัตินั้นบนตัววัตถุก่อน
  2. เมื่อไม่พบคุณสมบัตินี้บนตัววัตถุมันจะปีนต้นแบบลูกโซ่ดูที่วัตถุต้นแบบ หากคุณสมบัตินี้ยังไม่พบที่นี่มันจะยังคงปีนต้นแบบโซ่จนกว่าจะพบคุณสมบัติ หากไม่พบคุณสมบัติมันจะทำให้เกิดข้อผิดพลาดในการอ้างอิง

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

ตัวอย่าง:

1 Object.create()

Object.create()เป็นฟังก์ชั่นที่รับวัตถุเป็นอาร์กิวเมนต์และสร้างวัตถุใหม่ วัตถุที่ถูกส่งผ่านเป็นอาร์กิวเมนต์จะเป็นต้นแบบของวัตถุที่สร้างขึ้นใหม่ ตัวอย่างเช่น:

// prototype of the dog
const dogPrototype = {
  woof: function () { console.log('woof'); }
}

// create 2 dog objects, pass prototype as an argument
const fluffy = Object.create(dogPrototype);
const notFluffy = Object.create(dogPrototype);

// both newly created object inherit the woof 
// function from the dogPrototype
fluffy.woof();
notFluffy.woof();

2. การตั้งค่าคุณสมบัติต้นแบบอย่างชัดเจน

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

// Constructor function object
function Dog (name) {
   name = this.name;
}

// Functions are just objects
// All functions have a prototype property
// When a function is used as a constructor (with the new keyword)
// The newly created object will have the consturctor function's
// prototype as its prototype property
Dog.prototype.woof = function () {
  console.log('woof');
}

// create a new dog instance
const fluffy = new Dog('fluffyGoodBoyyyyy');
// fluffy inherits the woof method
fluffy.woof();

// can check the prototype in the following manner
console.log(Object.getPrototypeOf(fluffy));


0

คุณสามารถทำได้โดยใช้:

Object.prototype.extend = function(object) {
  // loop through object 
  for (var i in object) {
    // check if the extended object has that property
    if (object.hasOwnProperty(i)) {
      // mow check if the child is also and object so we go through it recursively
      if (typeof this[i] == "object" && this.hasOwnProperty(i) && this[i] != null) {
        this[i].extend(object[i]);
      } else {
        this[i] = object[i];
      }
    }
  }
  return this;
};

ปรับปรุง:ฉันตรวจสอบthis[i] != nullเพราะnullเป็นวัตถุ

จากนั้นใช้มันเหมือน:

var options = {
      foo: 'bar',
      baz: 'dar'
    }

    var defaults = {
      foo: false,
      baz: 'car',
      nat: 0
    }

defaults.extend(options);

สิ่งนี้ส่งผลให้:

// defaults will now be
{
  foo: 'bar',
  baz: 'dar',
  nat: 0
}

0

โปรดเพิ่มเหตุผลสำหรับ DownVOTE

  • ไม่จำเป็นต้องใช้ไลบรารีภายนอกเพื่อขยาย

  • ใน JavaScript ทุกอย่างเป็นวัตถุ (ยกเว้นสามประเภทข้อมูลดั้งเดิมและแม้กระทั่งจะถูกห่อด้วยวัตถุโดยอัตโนมัติเมื่อจำเป็น) นอกจากนี้วัตถุทั้งหมดสามารถเปลี่ยนแปลงได้

คลาสบุคคลใน JavaScript

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype = {
    getName: function() {
        return this.name;
    },
    getAge: function() {
        return this.age;
    }
}

/* Instantiate the class. */
var alice = new Person('Alice', 93);
var bill = new Person('Bill', 30);

ปรับเปลี่ยนอินสแตนซ์ / วัตถุเฉพาะ

alice.displayGreeting = function() 
{
    alert(this.getGreeting());
}

ปรับเปลี่ยนคลาส

Person.prototype.getGreeting = function() 
{
    return 'Hi ' + this.getName() + '!';
};

หรือพูดง่ายๆว่า: ขยาย JSON และ OBJECT ทั้งสองเหมือนกัน

var k = {
    name : 'jack',
    age : 30
}

k.gender = 'male'; /*object or json k got extended with new property gender*/

ขอบคุณ ross อันตราย, dustin diaz


-1

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

function extend(object) {
    if (object === null)
        throw TypeError;
    if (typeof object !== "object" && typeof object !== "function")
        throw TypeError;
    if (Object.create)
        return Object.create(object);
    function f() {}
    ;
    f.prototype = p;
    return new f();
}

แต่ถ้าคุณต้องการที่จะขยายวัตถุของคุณโดยไม่ต้องแก้ไขมันพารามิเตอร์คุณสามารถเพิ่ม extendProperty ไปยังวัตถุของคุณ

var Person{
//some code
extend: extendProperty
}

//Enforce type checking an Error report as you wish
    function extendProperty(object) {
        if ((object !== null && (typeof object === "object" || typeof object === "function"))){
            for (var prop in object) {
                if (object.hasOwnProperty(prop))
                    this[prop] = object[prop];
            }
        }else{
            throw TypeError; //Not an object
        }
    }

-2

การทำต้นแบบเป็นวิธีที่ดี แต่ในบางครั้งต้นแบบนั้นค่อนข้างอันตรายและสามารถนำไปสู่ข้อบกพร่องได้ ฉันชอบที่จะสรุปสิ่งนี้ลงในวัตถุฐานเช่นเดียวกับ Ember.js ที่ทำกับ Ember.Object.extend และ Ember.Object.reopen ปลอดภัยกว่าการใช้งานมาก

ฉันสร้างส่วนสำคัญด้วยวิธีที่คุณจะตั้งค่าบางอย่างที่คล้ายกับที่ Ember.Object ใช้

นี่คือลิงค์: https://gist.github.com/WebCloud/cbfe2d848c80d4b9e9bd


9
Prototyping is a nice way, but prototype is quite dangerous sometimes and can lead to bugs.คุณหมายถึงอะไร? ใช้โซ่ต้นแบบใน JavaScript สามารถนำไปสู่ข้อบกพร่อง? มันเหมือนกับว่าการใช้คลาสบน Java สามารถนำไปสู่ข้อบกพร่องและไม่สมเหตุสมผล
HMR

@HMR เขาบอกว่าการขยายต้นแบบต้นแบบของวัตถุที่ให้สภาพแวดล้อมส่งผลให้เกิดรหัสที่เปราะบางซึ่งอาจขัดแย้งกับคุณลักษณะภาษาจาวาสคริปต์หลักในอนาคต หากคุณเพิ่มฟังก์ชั่นยูทิลิตี้ที่มีประโยชน์ให้กับทุกสิ่งโดยการขยายObjectต้นแบบของฟังก์ชั่นของคุณอาจมีชื่อเดียวกันกับฟังก์ชั่นจาวาสคริปต์ในอนาคตและทำให้รหัสของคุณระเบิดเมื่อทำงานในอนาคต ตัวอย่างเช่นสมมติว่าคุณเพิ่มrepeat()ฟังก์ชันObjectและเรียกใช้งานบนStringอินสแตนซ์และจากนั้นรันไทม์ JavaScript ของคุณอัปเดตเป็น ES6
binki

@binki ขอบคุณสำหรับความคิดเห็นของคุณ คุณกำลังพูดถึงการเปลี่ยนต้นแบบของคลาสที่คุณไม่ได้เป็นเจ้าของ "" ดังนั้นจึงเป็นการทำลายการอ้างอิงแบบรวม: developer.mozilla.org/en/docs/Web/JavaScript/ ...... JS ไม่มีตัวแปรส่วนตัวดังนั้น API ของคุณจึงทำให้สมาชิกการนำไปใช้งาน ซึ่งโดยปกติจะแก้ไขได้ด้วยการประชุม (เริ่มชื่อสมาชิกด้วยเครื่องหมายขีดล่าง) ไม่แน่ใจว่าเป็นปัญหาหลักที่ op หรือมีไวยากรณ์สับสนหรือไม่และผู้คนจำนวนมากไม่เข้าใจ
HMR

@HMR ผมอาจจะผิด แต่ฉันคิดว่า“แต่ต้นแบบเป็นอันตรายมาก” หมายถึงกรอบต้นแบบที่น่าอับอายซึ่งอาจละเมิดprototypeคุณลักษณะภาษา
binki

การทำต้นแบบนั้นเป็นสิ่งที่อันตรายเพราะถ้าคุณใช้วัตถุที่คุณไม่ได้สร้างคุณไม่เคยรู้เลยว่าผลข้างเคียงอะไรที่จะนำมาใช้เป็นต้นแบบ ดูซอนี้เช่นjsfiddle.net/fo6r20rg
Arkain
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.