JavaScript เทียบเท่ากับวิธีการขยายของ jQuery


94

พื้นหลัง

ฉันมีฟังก์ชันที่ใช้configวัตถุเป็นอาร์กิวเมนต์ ภายในฟังก์ชันฉันยังมีdefaultวัตถุ แต่ละออบเจ็กต์เหล่านั้นมีคุณสมบัติที่ทำงานเป็นการตั้งค่าสำหรับส่วนที่เหลือของโค้ดภายในฟังก์ชัน เพื่อป้องกันไม่ให้ระบุการตั้งค่าทั้งหมดภายในconfigออบเจ็กต์ฉันใช้extendวิธีการของ jQuery เพื่อกรอกข้อมูลในวัตถุใหม่settingsด้วยค่าเริ่มต้นใด ๆ จากdefaultวัตถุหากไม่ได้ระบุไว้ในconfigวัตถุ:

var config = {key1: value1};
var default = {key1: default1, key2: default2, key 3: default 3};

var settings = $.extend(default, config);

//resulting properties of settings:
settings = {key1: value1, key2: default2, key 3: default 3};

ปัญหา

ใช้งานได้ดี แต่ฉันต้องการสร้างฟังก์ชันนี้ซ้ำโดยไม่ต้องใช้ jQuery มีวิธีการที่สง่างามเท่าเทียมกัน (หรือใกล้เคียงกับ) ในการทำสิ่งนี้ด้วยจาวาสคริปต์ ol ธรรมดาหรือไม่?


แก้ไข: เหตุผลที่ไม่ซ้ำกัน

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


8
เพียงแค่ขโมยวิธีที่ jQuery ทำjames.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat

ความคิดที่ดี @RocketHazmat โปรดทราบว่าหากคุณ copypasta ซึ่งฟังก์ชันนั้นมีการพึ่งพา jQuery อื่น ๆ เช่นjQuery.isPlainObjectและjQuery.isFunction
Andy Raddatz

คำตอบ:


134

เพื่อให้ได้ผลลัพธ์ในรหัสของคุณคุณต้องทำ:

function extend(a, b){
    for(var key in b)
        if(b.hasOwnProperty(key))
            a[key] = b[key];
    return a;
}

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

$.extend({}, default, config)

โซลูชันที่มีประสิทธิภาพมากขึ้นซึ่งเลียนแบบการทำงานของ jQuery จะเป็นดังนี้:

function extend(){
    for(var i=1; i<arguments.length; i++)
        for(var key in arguments[i])
            if(arguments[i].hasOwnProperty(key))
                arguments[0][key] = arguments[i][key];
    return arguments[0];
}

1
ฉันต้องการดูตัวอย่างโค้ดที่ใช้งานจริงอาจจะเป็นเสียงซอเพราะ js ธรรมดานั้นเจ๋งกว่ามาก แต่ก็เป็นนามธรรมมากขอบคุณล้าน
thednp

3
สิ่งนี้ไม่เรียกคืนเช่น $ .extend
ekkis

33

คุณสามารถใช้ตัวดำเนินการกระจาย ECMA 2018 ในอ็อบเจกต์ลิเทอรัล ...

var config = {key1: value1};
var default = {key1: default1, key2: default2, key 3: default 3};

var settings = {...default, ...config}

//resulting properties of settings:
settings = {key1: value1, key2: default2, key 3: default 3};

BabelJS รองรับเบราว์เซอร์รุ่นเก่า


3
เป็นวิธีที่สะอาดและสะอาดที่สุด!
Velojet

30

คุณสามารถใช้ Object.assign

var defaults = {key1: "default1", key2: "default2", key3: "defaults3"};
var config = {key1: "value1"};

var settings = Object.assign({}, defaults, config); // values in config override values in defaults
console.log(settings); // Object {key1: "value1", key2: "default2", key3: "defaults3"}

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

Object.assign(target, ...sources)

ใช้งานได้ในเบราว์เซอร์เดสก์ท็อปทั้งหมดยกเว้น IE (แต่รวมถึง Edge) ได้ลดการสนับสนุนมือถือ

ดูตัวคุณเองที่นี่: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

เกี่ยวกับสำเนาลึก

อย่างไรก็ตาม Object.assign ไม่มีตัวเลือก deep ที่วิธีการขยายของ jQuery มี

หมายเหตุ: โดยทั่วไปคุณสามารถใช้ JSON สำหรับเอฟเฟกต์ที่คล้ายกันได้

var config = {key1: "value1"};
    var defaults = {key1: "default1", key2: "default2", keyDeep: {
        kd1: "default3",
        kd2: "default4"
    }};
    var settings = JSON.parse(JSON.stringify(Object.assign({}, defaults, config))); 
    console.log(settings.keyDeep); // Object {kd1: "default3", kd2: "default4"}

ใช่ แต่ที่นี่มันกระโดดข้าม key1 จากค่าเริ่มต้นและไม่เพิ่มลงในวัตถุใหม่
Ionut Necula

@Anonymous จริงๆแล้วมันเพิ่มได้ แต่ถูกแทนที่ด้วยค่า key1 จาก config array
หลิง

ฉันคิดว่ามันกำลังเกิดขึ้น ดังนั้นจึงไม่ได้เพิ่มหลังจากนั้น
Ionut Necula

5

นี่เป็นวิธีการที่แตกต่างกันเล็กน้อยของฉันกับสำเนาลึกที่ฉันคิดขึ้นในขณะที่พยายามกำจัดการพึ่งพา jQuery ส่วนใหญ่ได้รับการออกแบบมาเพื่อให้มีขนาดเล็กจึงอาจมีคุณสมบัติไม่ครบตามที่คาดหวัง ควรเข้ากันได้กับ ES5 อย่างสมบูรณ์ (เริ่มจาก IE9 เนื่องจากการใช้Object.keys ):

function extend(obj1, obj2) {
    var keys = Object.keys(obj2);
    for (var i = 0; i < keys.length; i += 1) {
      var val = obj2[keys[i]];
      obj1[keys[i]] = ['string', 'number', 'array', 'boolean'].indexOf(typeof val) === -1 ? extend(obj1[keys[i]] || {}, val) : val;
    }
    return obj1;
  }

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

นี่คือบางส่วนของการทดสอบมอคค่า / ชัยของฉันที่ควรพิสูจน์ว่ากรณีทั่วไปทำงานได้ที่นี่:

it('should extend a given flat object with another flat object', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: 42,
    prop3: true,
    prop4: 20.16,
  };
  const obj2 = {
    prop4: 77.123,
    propNew1: 'newVal1',
    propNew2: 71,
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'val1',
    prop2: 42,
    prop3: true,
    prop4: 77.123,
    propNew1: 'newVal1',
    propNew2: 71,
  });
});

it('should deep-extend a given flat object with a nested object', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: 'val2',
  };
  const obj2 = {
    propNew1: 'newVal1',
    propNew2: {
      propNewDeep1: 'newDeepVal1',
      propNewDeep2: 42,
      propNewDeep3: true,
      propNewDeep4: 20.16,
    },
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'val1',
    prop2: 'val2',
    propNew1: 'newVal1',
    propNew2: {
      propNewDeep1: 'newDeepVal1',
      propNewDeep2: 42,
      propNewDeep3: true,
      propNewDeep4: 20.16,
    },
  });
});

it('should deep-extend a given nested object with another nested object and deep-overwrite members', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: {
      propDeep1: 'deepVal1',
      propDeep2: 42,
      propDeep3: true,
      propDeep4: {
        propDeeper1: 'deeperVal1',
        propDeeper2: 777,
        propDeeper3: 'I will survive',
      },
    },
    prop3: 'lone survivor',
  };
  const obj2 = {
    prop1: 'newVal1',
    prop2: {
      propDeep1: 'newDeepVal1',
      propDeep2: 84,
      propDeep3: false,
      propDeep4: {
        propDeeper1: 'newDeeperVal1',
        propDeeper2: 888,
      },
    },
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'newVal1',
    prop2: {
      propDeep1: 'newDeepVal1',
      propDeep2: 84,
      propDeep3: false,
      propDeep4: {
        propDeeper1: 'newDeeperVal1',
        propDeeper2: 888,
        propDeeper3: 'I will survive',
      },
    },
    prop3: 'lone survivor',
  });
});

ฉันยินดีที่จะรับฟังความคิดเห็นหรือข้อเสนอแนะเกี่ยวกับการใช้งาน ขอบคุณล่วงหน้า!


2

คุณสามารถวนซ้ำคุณสมบัติของวัตถุโดยใช้forคำสั่ง

var settings = extend(default, config);

function extend(a, b){
    var c = {};
    for(var p in a)
        c[p] = (b[p] == null) ? a[p] : b[p];
    return c;
}

2
aไม่ว่าจะใช้เวลาในการคุณสมบัติของบัญชีที่ไม่ได้อยู่ใน แม้ว่าจะไม่ได้อยู่ในตัวอย่าง แต่$.extendก็ใช้งานได้จริงโดยผสานคุณสมบัติทั้งหมดของวัตถุทั้งหมด
Felix Kling

1
ในขณะที่ฉันอ่านรหัสฐาน jquery มันทำหน้าที่เรียกซ้ำเพื่อคัดลอกหรือโคลนค่า ดังนั้นจึงเป็นการโคลน / คัดลอกอย่างลึกซึ้งไม่ใช่แค่คัดลอกในระดับแรกเหมือนโค้ดด้านบน
aladine

2

ฉันชอบรหัสนี้ที่ใช้ forEachIn ทั่วไปของฉันและไม่รบกวนวัตถุแรก:

function forEachIn(obj, fn) {
    var index = 0;
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            fn(obj[key], key, index++);
        }
    }
}

function extend() {
    var result = {};
    for (var i = 0; i < arguments.length; i++) {
        forEachIn(arguments[i],
            function(obj, key) {
                result[key] = obj;
            });
    }
    return result;
}

หากคุณต้องการรวมสิ่งต่างๆลงในวัตถุแรกจริงๆคุณสามารถทำได้:

obj1 = extend(obj1, obj2);

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

0

มันช่วยฉันได้มากเมื่อฉันพัฒนาด้วยจาวาสคริปต์ล้วนๆ

function extends(defaults, selfConfig){
     selfConfig = JSON.parse(JSON.stringify(defaults));
     for (var item in config) {
         if (config.hasOwnProperty(item)) {
             selfConfig[item] = config[item];
         }
     }
     return selfConfig;
}

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