设计模式以检查JavaScript对象是否已更改

时间:2016-07-09 01:26:29

标签: javascript design-patterns

我从服务器获取了一个对象列表

[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]

我将它们加载到html控件中供用户编辑。 然后有一个按钮可以将整个列表批量保存回数据库。

我只想发送已更改的对象子集,而不是发送整个列表。

它可以是数组中的任意数量的项目。我想做类似于像Angular这样的框架,它在没有对它进行任何更改时标记像“pristine”这样的对象属性。然后使用该标志仅向服务器发布不是“原始”的项目,即已修改的项目。

8 个答案:

答案 0 :(得分:10)

下面是一个函数,当提供旧的对象数组和一个新的对象数组时,它将返回一个已更改对象的数组:

getChanges = function(oldArray, newArray) {
var changes, i, item, j, len;
if (JSON.stringify(oldArray) === JSON.stringify(newArray)) {
  return false;
}
changes = [];
for (i = j = 0, len = newArray.length; j < len; i = ++j) {
  item = newArray[i];
  if (JSON.stringify(item) !== JSON.stringify(oldArray[i])) {
    changes.push(item);
  }
}
return changes;
};

例如:

var older = [{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
var newer = [{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:20}]
getChanges(older, newer)

(注意test03的年龄现在是20)将返回

[{name:'test03', age:20}]

您只需要导出客户端编辑值的完整列表,将其与旧列表进行比较,然后将更改列表发送到服务器。

希望这有帮助!

答案 1 :(得分:3)

以下是一些想法。

  1. 使用框架。你谈到了Angular。

  2. 使用Proxies,但Internet Explorer不支持。

  3. 可以使用Object.defineProperty的set / get来实现某种变更跟踪,而不是使用经典属性。

  4. 使用getter / setting函数存储数据而不是属性:getName()setName()。虽然这是defineProperty现在所做的旧方法。

  5. 每当您将数据绑定到表单元素时,请设置一个特殊属性,指示属性是否已更改。像__hasChanged这样的东西。如果对象上的任何属性发生更改,则设置为true。

  6. 旧学校强力方式:保留原始服务器数据列表,深入复制到另一个列表,将表单控件绑定到新列表,然后当用户单击提交时,比较对象在原始列表中新建列表中的对象,随时删除已更改的对象。可能是最简单的,但不一定是最干净的。

  7. 对#6的另一种看法:将特殊属性附加到始终返回对象原始版本的每个对象:

  8.     var myData = [{name: "Larry", age: 47}];
        var dataWithCopyOfSelf = myData.map(function(data) {  
            Object.assign({}, data, { original: data });
        });
        // now bind your form to dataWithCopyOfSelf.

    当然,这个解决方案假定了一些事情:(1)你的对象是扁平而简单的,因为Object.assign()没有深度复制,(2)你的原始数据集永远不会改变,并且( 3)没有任何东西触及original的内容。

    那里有很多解决方案。

答案 2 :(得分:1)

借助ES6,我们可以使用Proxy

完成此任务:拦截对象写入,并将其标记为脏。

代理允许创建一个 处理程序 对象,该对象可以捕获,进行操作,然后转发更改原始的 target 对象,基本上可以重新配置其行为。
我们要用于拦截对象写入陷阱handler set()

此时,我们可以添加不可枚举的属性 标志,例如:_isDirty使用Object.defineProperty()将我们的对象标记为已修改, 肮脏

使用陷阱(在我们的示例中为处理程序的set())时,不会应用更改或将更改反映到对象,因此我们需要转发参数值使用 Reflect.set() target 对象。

最后,要检索修改后的对象,请使用我们的代理对象 filter() 查找那些拥有own Property {{1 }}。

"_isDirty"

不使用// From server: const dataOrg = [ {id:1, name:'a', age:10}, {id:2, name:'b', age:20}, {id:3, name:'c', age:30} ]; // Mirror data from server to observable Proxies: const data = dataOrg.map(ob => new Proxy(ob, { set() { Object.defineProperty(ob, "_isDirty", {value: true}); // Flag return Reflect.set(...arguments); // Forward trapped args to ob } })); // From now on, use proxied data. Let's change some values: data[0].name = "Lorem"; data[0].age = 42; data[2].age = 31; // Collect modified data const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty")); // Test what we're about to send back to server: console.log(JSON.stringify(dataMod, null, 2));

如果由于某种原因您不愿意进入原始对象,添加额外的属性作为标志,则可以立即填充 .defineProperty()(带有修改对象的数组)的引用:

dataMod

答案 3 :(得分:0)

无需了解原型属性,只要表单控件元素检测到更改,就可以将它们存储在另一个数组中

有些事情:

var modified = [];
data.forEach(function(item){
   var domNode = // whatever you use to match data to form control element
   domNode.addEventListener('input',function(){
      if(modified.indexOf(item) === -1){
         modified.push(item);
      }
   });
});

然后在保存时将modified数组发送到服务器

答案 4 :(得分:0)

以下是如何跟踪记录已更改的角度示例。您可以通过增强对象来更加增强跟踪实际字段和值。这是一个例子:

<强> App.js

(function () {
    angular.module('myApp', [])

    .controller('myController', function ($scope) {

        $scope.data = [{ id: 1, name: 'test01', age: 10 }, { id: 2, name: 'test02', age: 20 }, { id: 3, name: 'test03', age: 30 }];
        $scope.isDirty = { changes: false, changedIds: [] };

        $scope.submit = function () {

            console.log($scope.isDirty);
        }

    })
    .directive('ngChangeListener', function () {
        return {
            restrict: 'A',
            link: function (scope, element, attr) {
                element.change(function (e) {
                    var recordId = $(e.currentTarget).attr('data-id');
                    scope.isDirty.changes = true;
                    scope.isDirty.changedIds.push(recordId);
                })
            }
        }
    })

}());

<强> HTML

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AngularJSFiddle.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" ng-app="myApp">
<head runat="server">

    <title></title>
</head>
<body ng-controller="myController">
    <div>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Age</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="item in data">
                    <td><input ng-change-listener type="text" data-id="{{item.id}}" value="{{item.name}}" /></td>
                    <td><input ng-change-listener type="text" data-id="{{item.id}}" value="{{item.age}}" /></td>
                </tr>
            </tbody>
        </table>
        <div>{{isDirty.changes}}</div>
        <button type="button" ng-click="submit()">Submit</button>
    </div>
    <script src="scripts/jquery.js"></script>
    <script src="scripts/angular.js"></script>
    <script src="scripts/app.js"></script>
</body>
</html>

一旦你建立了'跟踪更改'对象,那么只需更新正确的记录(应与ID一致),而不是进行批量更新。

答案 5 :(得分:0)

为什么不使用Ember.js可观察属性?您可以使用Ember.observer函数来获取和设置数据中的更改。

Ember.Object.extend({
  valueObserver: Ember.observer('value', function(sender, key, value,  rev) {
    // Executes whenever the "value" property changes
    // See the addObserver method for more information about the callback arguments
  })
});

Ember.object实际上为你做了很多繁重的工作。

定义对象后,添加如下的观察者:

object.addObserver('propertyKey', targetObject, targetAction)

答案 6 :(得分:0)

我的想法是对对象键进行排序并将对象转换为要比较的字符串:

// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
  let keys = Object.keys(obj)
  let results = []
  keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
  keys.forEach(key => {
    let value = obj[key]
    if (typeof value === 'object') {
      value = objectSerilize(value)
    }
    results.push({
      key,
      value,
    })
  })
  return results
}
// use this function to compare
function compareObject(a, b) {
  let aStr = JSON.stringify(objectSerilize(a))
  let bStr = JSON.stringify(objectSerilize(b))
  return aStr === bStr
}

这就是我的想法。

答案 7 :(得分:0)

这是最干净的方法,我认为在添加或删除或修改属性时,让对象发出事件。 一个简单的实现可能涉及一个带有对象键的数组。每当构造函数的setter或heck构造函数返回此值时,它都会首先调用一个静态函数返回一个promise;解析:使用数组中更改后的值进行映射:添加的内容,删除的内容或两者都不显示。这样一个人可以得到(“改变”)等等。返回一个数组。 同样,每个设置器都可以发出带有初始值和新值参数的事件。 假设使用了类,则可以在父泛型类中轻松地通过其构造函数调用静态方法,因此实际上您可以通过将对象传递给自身或通过super(checkMeProperty)传递给父类来简化大多数方法