JavaScript tương đương với phương thức mở rộng của jQuery


92

Lý lịch

Tôi có một hàm lấy một configđối tượng làm đối số. Trong hàm, tôi cũng có defaultđối tượng. Mỗi đối tượng đó chứa các thuộc tính về cơ bản hoạt động như cài đặt cho phần còn lại của mã bên trong hàm. Để tránh phải chỉ định tất cả các cài đặt trong configđối tượng, tôi sử dụng extendphương thức của jQuery để điền vào một đối tượng mới,settings với bất kỳ giá trị mặc định nào từ defaultđối tượng nếu chúng không được chỉ định trong configđối tượng:

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

Vấn đề

Điều này hoạt động tốt, nhưng tôi muốn tái tạo chức năng này mà không cần jQuery. Có cách nào thanh lịch không kém (hoặc gần giống) để làm điều này với javascript đơn giản không?


Chỉnh sửa: Biện minh không trùng lặp

Câu hỏi này không trùng lặp với câu hỏi " Làm cách nào để hợp nhất động các thuộc tính của hai đối tượng JavaScript? " Trong khi câu hỏi đó chỉ đơn giản là muốn tạo một đối tượng chứa tất cả các khóa và giá trị từ hai đối tượng riêng biệt - tôi đặc biệt muốn giải quyết cách thực hiện điều này trong trường hợp cả hai đối tượng chia sẻ một số nhưng không phải tất cả các khóa và đối tượng nào sẽ được ưu tiên ( mặc định) cho đối tượng kết quả trong trường hợp có các khóa trùng lặp. Và thậm chí cụ thể hơn, tôi muốn giải quyết việc sử dụng phương thức của jQuery để đạt được điều này và tìm một cách thay thế để làm điều đó mà không cần jQuery. Mặc dù nhiều câu trả lời cho cả hai câu hỏi trùng nhau, nhưng điều đó không có nghĩa là bản thân các câu hỏi đều giống nhau.


8
Chỉ cần ăn cắp cách jQuery làm nó james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat

Ý tưởng tốt @RocketHazmat, lưu ý nếu bạn copypasta chức năng mà nó có một vài phụ thuộc jQuery khác như jQuery.isPlainObjectjQuery.isFunction
Andy Raddatz

Câu trả lời:


131

Để nhận được kết quả trong mã của bạn, bạn sẽ làm:

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

Hãy nhớ rằng cách bạn đã sử dụng mở rộng ở đó sẽ sửa đổi đối tượng mặc định. Nếu bạn không muốn điều đó, hãy sử dụng

$.extend({}, default, config)

Một giải pháp mạnh mẽ hơn bắt chước chức năng của jQuery sẽ như sau:

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
Tôi muốn xem một ví dụ mã đang hoạt động, có thể là khó hiểu, bởi vì js đơn giản thì mát mẻ hơn nhiều nhưng quá trừu tượng, cảm ơn một triệu.
thednp

3
điều này không lặp lại, giống như $ .extend thì có
ekkis

30

Bạn có thể sử dụng 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"}

Nó sao chép các giá trị của tất cả các thuộc tính riêng có thể liệt kê từ một hoặc nhiều đối tượng nguồn sang một đối tượng đích và trả về đối tượng đích.

Object.assign(target, ...sources)

Nó hoạt động trong tất cả các trình duyệt máy tính để bàn ngoại trừ IE (nhưng bao gồm cả Edge). Nó đã giảm thiểu hỗ trợ di động.

Tự xem tại đây: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Giới thiệu về bản sâu

Tuy nhiên, Object.assign không có tùy chọn sâu mà phương thức mở rộng của jQuery có.

Lưu ý: bạn thường có thể sử dụng JSON để có hiệu ứng tương tự

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"}

Vâng, nhưng ở đây nó nhảy qua key1 từ mặc định và không thêm nó vào đối tượng mới.
Ionut Necula

@Anonymous Trên thực tế, nó có thêm nó, nhưng nó bị ghi đè bởi giá trị key1 từ mảng cấu hình.
ling

Tôi cho rằng điều đó đang xảy ra. Vì vậy, nó không phải là thêm nó sau.
Ionut Necula


4

Đây là cách tiếp cận hơi khác của tôi với bản sao sâu mà tôi đã nghĩ ra khi cố gắng loại bỏ sự phụ thuộc của jQuery. Nó chủ yếu được thiết kế để nhỏ nên có thể không có tất cả các tính năng mà người ta mong đợi. Phải hoàn toàn tương thích với ES5 (bắt đầu từ IE9 do việc sử dụng 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;
  }

Bạn có thể tự hỏi dòng thứ năm chính xác làm gì ... Nếu obj2.key là một đối tượng theo nghĩa đen (tức là nếu nó không phải kiểu thông thường), chúng ta gọi đệ quy extension trên nó. Nếu một thuộc tính có tên đó chưa tồn tại trong obj1, trước tiên chúng tôi khởi tạo nó thành một đối tượng trống. Nếu không, chúng ta chỉ cần đặt obj1.key thành obj2.key.

Dưới đây là một số bài kiểm tra mocha / chai của tôi sẽ chứng minh các trường hợp phổ biến hoạt động ở đây:

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',
  });
});

Tôi rất vui về phản hồi hoặc nhận xét về việc triển khai này. Cảm ơn trước!


2

Bạn có thể lặp qua các thuộc tính của Object bằng forcâu lệnh.

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
Điều đó sẽ không tính đến các thuộc tính không tồn tại trong a. Mặc dù nó không có trong ví dụ, nhưng $.extendthực sự hoạt động theo cách đó, hợp nhất tất cả các thuộc tính của tất cả các đối tượng.
Felix Kling

1
Khi tôi đọc mã cơ sở jquery, nó thực sự thực hiện một hàm đệ quy để sao chép hoặc sao chép giá trị. Vì vậy, nó là clone / sao chép sâu sắc, không chỉ là sao chép ở cấp độ đầu tiên như đoạn mã trên.
aladine

2

Tôi thích mã này sử dụng forEachIn chung của tôi và không làm hỏng đối tượng đầu tiên:

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

Nếu bạn thực sự muốn hợp nhất mọi thứ vào đối tượng đầu tiên, bạn có thể làm:

obj1 = extend(obj1, obj2);

Đây là câu trả lời tốt nhất ở đây. Nó thực hiện bản sao sâu, không làm hỏng đối tượng đầu tiên, hỗ trợ các đối tượng vô hạn và có thể thay đổi kiểu trong khi mở rộng (nghĩa là nhiều câu trả lời ở đây sẽ không mở rộng một chuỗi cho một đối tượng, câu trả lời này sẽ). Điều này nhận được mọi dấu kiểm trong cuốn sách của tôi. thay thế mở rộng đầy đủ, và cực kỳ dễ hiểu.
Nick Steele

0

Nó giúp tôi rất nhiều khi tôi phát triển với javascript thuần túy.

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

0

Cách tiếp cận của Ivan Kuckir cũng có thể được điều chỉnh để tạo ra một nguyên mẫu vật thể mới:

Object.prototype.extend = function(b){
for(var key in b)
    if(b.hasOwnProperty(key))
        this[key] = b[key];
    return this;
}

var settings = default.extend(config);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.