JavaScript中如何从对象中弹出(删除)元素?

55

我写了下面的代码,想要从一个对象中"弹出"一个属性,就像从数组中弹出元素一样。这看起来是那种会让更认真的程序员狠抽我的代码,所以我想知道正确的做法:

// wrong way to pop:
for( key in profiles ){
    var profile = profiles[key];  // get first property
    profiles[key] = 0;            // Save over property just in case "delete" actually deletes the property contents instead of just removing it from the object
    delete profiles[key];         // remove the property from the object
    break;                        // "break" because this is a loop
}

我之前应该提到,与真正的“弹出(pop)”不同,我不需要对象按照任何特定顺序出现。我只需要取出一个对象并将其从其父对象中删除。


2
将代码主体包裹在 if (profiles.hasOwnProperty(key) {.. 中,并删除 = 0 - Sean Kinsey
5
你具体想做什么?是要删除一个对象中添加的第一个属性吗?这并不能保证成功,因为在for..in循环中检索属性的顺序是不确定的。除此之外,你应该使用hasOwnProperty来确保它不是原型的一部分,并且你只需要使用delete来从对象中删除属性,无需执行其他操作。 - Jamie Treworgy
我认为你的意思是模仿 shift 数组方法,该方法删除(并返回)数组中的第一个元素。pop 删除最后一个元素,因为 push/pop 数组方法作为 FILO。 - jbyrd
8个回答

73
现今,你可以简单地使用spread运算符及其Rest方式:
const { key, ...profilesWithoutKey } = profiles;

感谢这篇博客文章


这会提取第一个条目,但不会提取 profiles[key] - Friedrich -- Слава Україні
@Friedrich,它确实提取了“key”。请再次检查。 - Noam Gal
2
@Friedrich,我的代码已经完全实现了这一点,你不需要指定 {key: key, ...rest}。如果 profiles = {key: 0, a: 1, b: 2},那么执行解决方案后,你将得到:key = 0profilesWithoutKey = {a: 1, b: 2} - Noam Gal
2
我认为正确的语法应该是 const {[key]: valueOfKey, ...profileWithoutKey } = profiles;,因为在OP的问题中,profiles对象不一定有一个名为key的键,而实际上他正在迭代对象并“弹出”对象的第一个键。 - hellopeach
1
@Friedrich,实际上这个答案提取的是profiles.key,而不是profiles[key],它们是非常不同的,具有讽刺意味的是,从示例中OPʻs的意图实际上确实是尝试提取对象的第一个条目的值。 - hellopeach
显示剩余2条评论

17
for( key in profiles ){

你应该将key声明为一个var

profiles[key] = 0;            // Save over property just in case "delete" actually deletes the property contents instead of just removing it from the object

删除操作是不必要的。它不会改变属性的值(或者在有setter但没有getter的情况下,甚至要求其具有一个值)。

如果对象的原型上有任何可枚举属性,则这将导致一些奇怪的事情发生。 考虑以下例子:

Object.prototype.foo = 42;

function take(obj) {
  for (var key in obj) {
    // Uncomment below to fix prototype problem.
    // if (!Object.hasOwnProperty.call(obj, key)) continue;
    var result = obj[key];
    // If the property can't be deleted fail with an error.
    if (!delete obj[key]) { throw new Error(); }
    return result;
  } 
}

var o = {};
alert(take(o));  // alerts 42
alert(take(o));  // still alerts 42

这个应该真正被称为“移位”,因为它删除的是第一个元素而不是最后一个。我理解OP错误地命名了他的请求 - 也许你可以在你的回答中注明。 - jbyrd
1
@jbyrd关于“不需要以任何特定顺序输出对象”的问题,也许可以使用“取”? - Mike Samuel
哎呀!我想我应该更仔细地阅读那部分内容。有道理。在这种情况下,“take”不是一个坏主意! - jbyrd

5

对象中的属性不是存储在堆栈中的,因此基本概念不能可靠地工作(除了上面评论中提到的其他问题)。

如果您真的需要这样的构造,请尝试类似以下的代码。

var ObjectStack = function(obj) {
    this.object = obj;
    this.stack=[];
};
ObjectStack.prototype.push = function(key,value) {
    this.object[key]=value;
    this.stack.push(key);
};
ObjectStack.prototype.pop = function() {
    var key = this.stack.pop();
    var prop = this.object[key];
    delete this.object[key];
    return prop;
};

var my_obj = {};
var my_stack = new ObjectStack(my_obj);
my_stack.push("prop1",val1);
my_stack.push("prop2",val2);

var last_prop = my_stack.pop(); //val2

Demo: http://jsfiddle.net/a8Rf6/5/


这个实现的一个问题是我们有一个堆栈,但内部数组是公开的。任何人都可以以任何方式更改它,甚至用不是数组的其他东西替换它。 - yckart
4
JavaScript 本质上是动态的。如果我选择,我可以更改 Array.prototype.length。虽然使用闭包来隐藏内部堆栈可以创建更复杂的实现,但这只是一个学习示例,在实际应用中,这会增加大量额外的代码保护您的自身安全,而原生语言通常并不需要这么做。 - Jamie Treworgy

3

Thus without loop and "function" noise, simply

key = 'b'
profile = {a:1, b:2, c:3}

val = profile[key] 
delete profile[key]

console.log(profile, key, val)   // Object { a: 1, c: 3 } b 2

或者,根据@hellopeach的评论,使用动态密钥解包。

profile = {a:1, b:2, c:3}
key = 'b'
var {[key]: val, ...profile} = profile
// var {b: val, ...profile} = profile 

console.log(profile, key, val)   // Object { a: 1, c: 3 } b 2


3

经过研究以上所有评论和解决方案,我可以基于它们提供一个完全可用的解决方案:

Object.prototype.pop = function() {
    for (var key in this) {
        if (!Object.hasOwnProperty.call(this, key)) continue;
        var result = this[key];
        if (!delete this[key]) throw new Error();
        return result;
    }
};

var obj = {
    a: '1',
    b: '2',
    c: '3'
};

console.log(obj.pop()); // 1
console.log(obj.pop()); // 2
console.log(obj.pop()); // 3
console.log(obj); // Object {  }

或许会对某些人有所帮助 ; )


PS. 如果您使用我提供的代码以及jQuery库,可能会在控制台中遇到错误。这种情况下,下面给出的选项是合适的:

function _pop() {
    for (var key in this) {
        if (!Object.hasOwnProperty.call(this, key)) continue;
        if (key === 'pop') continue;
        var result = this[key];
        if (!delete this[key]) throw new Error();
        return result;
    }
}

var obj = {
    a: '1',
    b: '2',
    c: '3'
};

obj.pop = _pop;

console.log(obj.pop()); // 1
console.log(obj.pop()); // 2
console.log(obj.pop()); // 3
console.log(obj); // Object { pop: _pop() }

2
一种更好的方法是不直接修改输入数组。例如:
let arr = [{label: "a"}, {label: "b"}, {label: "p"}, {label: "c"}] 
let newArr = arr.filter(p => { return p.label !== "p";});

2

在不同的浏览器中,for in循环遍历对象的顺序是不固定的。有些浏览器按照属性添加的顺序进行遍历,而其他浏览器则先按照数值索引进行遍历。因此,如果没有创建自定义对象,就无法保证遍历的顺序。


1
你可以像这样创建弹出方法:。
Object.defineProperty(Object.prototype, 'pop',{
    writable: false
    , configurable: false
    , enumerable: false
    , value: function (name) {
        var value = this[name];
        delete this[name];
        return value;
    }
});

由于某些原因,仅使用Object.prototype.pop = function ...会破坏JQuery。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接