ทดสอบการมีอยู่ของคีย์วัตถุ JavaScript ที่ซ้อนกัน


692

ถ้าฉันมีการอ้างอิงถึงวัตถุ:

var test = {};

ที่อาจเกิดขึ้น (แต่ไม่ทันที) มีวัตถุซ้อนกันบางอย่างเช่น:

{level1: {level2: {level3: "level3"}}};

วิธีที่ดีที่สุดในการตรวจสอบการมีอยู่ของทรัพย์สินในวัตถุที่ซ้อนกันอย่างล้ำลึกคืออะไร?

alert(test.level1);ผลผลิตundefinedแต่alert(test.level1.level2.level3);ล้มเหลว

ฉันกำลังทำสิ่งนี้:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

แต่ฉันสงสัยว่ามีวิธีที่ดีกว่านี้หรือไม่


1
คุณอาจต้องการตรวจสอบคำถามที่เกี่ยวข้องกันซึ่งถูกถามเมื่อเร็ว ๆ นี้stackoverflow.com/questions/2525943/…
Anurag


ข้อเสนอสองสามข้อ: stackoverflow.com/a/18381564/1636522
ใบไม้

วิธีการปัจจุบันของคุณมีปัญหาที่อาจเกิดขึ้นหากคุณสมบัติระดับ 3 เป็นเท็จในกรณีนั้นแม้ว่าทรัพย์สินที่มีอยู่จะกลับมาอีกครั้งโปรดดูตัวอย่างนี้โปรดjsfiddle.net/maz9bLjx
GibboK

10
เพียงคุณสามารถใช้ลองจับด้วย
Raghavendra

คำตอบ:


489

คุณต้องทำทีละขั้นถ้าคุณไม่ต้องการTypeErrorเพราะถ้าหนึ่งในสมาชิกคือnullหรือundefinedและคุณพยายามเข้าถึงสมาชิกข้อยกเว้นจะถูกโยน

คุณสามารถcatchยกเว้นได้อย่างง่ายดายหรือสร้างฟังก์ชั่นเพื่อทดสอบการมีอยู่ของหลาย ๆ ระดับดังนี้:

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

อัพเดท ES6:

นี่คือฟังก์ชั่นดั้งเดิมที่สั้นกว่าโดยใช้คุณสมบัติ ES6 และการเรียกซ้ำ (ยังอยู่ในรูปแบบการเรียกหางที่เหมาะสม ):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

อย่างไรก็ตามหากคุณต้องการรับค่าของคุณสมบัติซ้อนกันและไม่เพียง แต่ตรวจสอบการมีอยู่ของมันนี่คือฟังก์ชั่นหนึ่งบรรทัดง่ายๆ:

function getNested(obj, ...args) {
  return args.reduce((obj, level) => obj && obj[level], obj)
}

const test = { level1:{ level2:{ level3:'level3'} } };
console.log(getNested(test, 'level1', 'level2', 'level3')); // 'level3'
console.log(getNested(test, 'level1', 'level2', 'level3', 'length')); // 6
console.log(getNested(test, 'level1', 'level2', 'foo')); // undefined
console.log(getNested(test, 'a', 'b')); // undefined

ฟังก์ชั่นด้านบนอนุญาตให้คุณรับค่าคุณสมบัติซ้อนกันมิฉะนั้นจะส่งคืน undefinedฟังก์ชั่นดังกล่าวข้างต้นจะช่วยให้คุณได้รับค่าของคุณสมบัติที่ซ้อนกันมิฉะนั้นจะกลับมา

อัพเดท 2019-10-17:

ข้อเสนอการผูกมัดตัวเลือกถึง 3 ขั้นตอนในกระบวนการของคณะกรรมการ ECMAScriptนี้จะช่วยให้คุณได้อย่างปลอดภัยการเข้าถึงคุณสมบัติที่ซ้อนกันอย่างลึกซึ้งโดยใช้โทเค็น?.ใหม่ผู้ประกอบการผูกมัดตัวเลือก :

const value = obj?.level1?.level2?.level3 

หากระดับการเข้าถึงใด ๆ เป็นnullหรือundefinedการแสดงออกจะแก้ไขundefinedด้วยตัวเอง

ข้อเสนอนี้ยังช่วยให้คุณสามารถจัดการวิธีการโทรได้อย่างปลอดภัย:

obj?.level1?.method();

การแสดงออกดังกล่าวข้างต้นจะผลิตundefinedถ้าobj, obj.level1หรือobj.level1.methodมีnullหรือundefinedมิฉะนั้นจะเรียกใช้ฟังก์ชัน

คุณสามารถเริ่มเล่นด้วยคุณสมบัตินี้กับ Babel โดยใช้ปลั๊กอินเสริมที่ผูกมัดตัวเลือกการผูกมัดปลั๊กอิน

ตั้งแต่Babel 7.8.0 , ES2020 ได้รับการสนับสนุนโดยค่าเริ่มต้น

ตรวจสอบตัวอย่างนี้ใน Babel REPL

🎉🎉UPDATE: ธันวาคม 2562 🎉🎉

ในที่สุดข้อเสนอที่เป็นตัวเลือกถึงขั้นตอนที่ 4ในการประชุมธันวาคม 2558 ของคณะกรรมการ TC39 นี่หมายความว่าฟีเจอร์นี้จะเป็นส่วนหนึ่งของมาตรฐานECMAScript 2020


4
argumentsไม่ใช่อาร์เรย์จริงๆ Array.prototype.slice.call(arguments)แปลงเป็นอาร์เรย์อย่างเป็นทางการ เรียนรู้
deefour

23
สิ่งนี้จะมีประสิทธิภาพมากขึ้นในการทำvar obj = arguments[0];และเริ่มvar i = 1แทนการคัดลอกargumentsวัตถุ
Claudiu

2
ฉันรวบรวมเวอร์ชันด้วยลอง / จับเพื่อความเข้มงวดและไม่แปลกใจเลย - ประสิทธิภาพแย่มาก (ยกเว้นใน Safari ด้วยเหตุผลบางอย่าง) มีคำตอบบางข้อด้านล่างซึ่งเป็นนักแสดงที่น่ารักพร้อมกับการดัดแปลงของ Claudiu ซึ่งมีประสิทธิภาพมากกว่าคำตอบที่เลือก ดู jsperf ที่นี่jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

3
ใน ES6 การargsประกาศตัวแปรสามารถลบและและ...args สามารถใช้เป็นอาร์กิวเมนต์ที่สองสำหรับcheckNestedวิธีการ developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon

7
นี่เป็นสิ่งที่ไม่อาจปฏิเสธได้ ถ้าคีย์คุณสมบัติใด ๆ เปลี่ยน (พวกมันจะ) devs ทั้งหมดในโครงการจะต้อง 'ค้นหาสตริง' codebase ทั้งหมด นี่ไม่ใช่วิธีการแก้ปัญหาจริงๆเพราะมันนำเสนอปัญหาที่ใหญ่กว่ามาก
Drenai

357

นี่คือรูปแบบฉัน เลือกจาก Oliver Steele :

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

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


8
@wared ฉันคิดว่ามันน่าสนใจเป็นส่วนใหญ่ว่ากระชับอย่างไร มีการอธิบายรายละเอียดเกี่ยวกับคุณสมบัติด้านประสิทธิภาพในโพสต์ที่เชื่อมโยง ใช่มันทำการทดสอบทุกครั้งเสมอ แต่จะหลีกเลี่ยงการสร้าง temp vars และคุณสามารถใช้นามแฝง {} ไปยัง var ได้หากคุณต้องการป้องกันค่าใช้จ่ายในการสร้างวัตถุเปล่าใหม่ทุกครั้ง ใน 99% ของกรณีฉันจะไม่คาดหวังความเร็วเรื่องและในกรณีที่มันไม่มีทดแทนสำหรับการทำโปรไฟล์
Gabe Moothart

9
@MuhammadUmer ไม่มีจุดคือว่าถ้าการทดสอบจะไม่ได้กำหนดแล้วคุณกำลังทำ(test || {}) ({}.level1 || {})แน่นอน{}.level1ไม่ได้กำหนดไว้นั่นหมายความว่าคุณกำลังทำ{}.level2อยู่เรื่อย ๆ
Joshua Taylor

3
@ โจชัวเทย์เลอร์: ฉันคิดว่าเขาหมายถึงถ้าtestไม่ได้รับการประกาศจะมีReferenceErrorแต่นั่นไม่ใช่ปัญหาเพราะถ้ามันไม่ได้ประกาศจะมีการแก้ไขข้อผิดพลาดดังนั้นข้อผิดพลาดเป็นสิ่งที่ดี

34
คุณกล่าวว่า"ซึ่งไม่ได้เป็นเรื่องยากที่จะอ่านครั้งเดียวที่คุณจะได้รับใช้มัน" ดีเหล่านี้เป็นสัญญาณที่คุณรู้อยู่แล้วนี้เป็นระเบียบ ถ้าอย่างนั้นทำไมถึงแนะนำโซลูชั่นนี้? มันมีแนวโน้มที่จะพิมพ์ผิดและให้อะไรอย่างที่อ่านง่าย แค่มองมัน! ถ้าฉันต้องเขียนเส้นน่าเกลียดก็ควร asswell จะสามารถอ่านได้ ; ดังนั้นฉันจะไปติดกับif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky

8
ถ้าฉันไม่มีอะไรหายไปสิ่งนี้จะใช้ไม่ได้กับคุณสมบัติสิ้นสุดบูลีนที่อาจเป็นเท็จ ... เศร้า มิฉะนั้นฉันรักสำนวนนี้
T3db0t

261

ปรับปรุง

ดูเหมือนว่า lodash ได้เพิ่ม _.getความต้องการที่อยู่อาศัยทั้งหมดของคุณ

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


คำตอบก่อนหน้า

ผู้ใช้lodashอาจเพลิดเพลินไปกับlodash.contribซึ่งมีวิธีการสองสามอย่างที่ช่วยลดปัญหานี้ได้ได้

getPath

ลายเซ็น: _.getPath(obj:Object, ks:String|Array)

รับค่าที่ระดับความลึกใด ๆ ในวัตถุที่ซ้อนกันตามเส้นทางที่อธิบายโดยคีย์ที่กำหนด คีย์อาจได้รับเป็นอาร์เรย์หรือเป็นสตริงที่คั่นด้วยจุด ส่งคืนundefinedหากไม่สามารถเข้าถึงพา ธ

var countries = {
        greece: {
            athens: {
                playwright:  "Sophocles"
            }
        }
    }
};

_.getPath(countries, "greece.athens.playwright");
// => "Sophocles"

_.getPath(countries, "greece.sparta.playwright");
// => undefined

_.getPath(countries, ["greece", "athens", "playwright"]);
// => "Sophocles"

_.getPath(countries, ["greece", "sparta", "playwright"]);
// => undefined

Lodash ต้องการเมธอด _.isPathDefined (obj, pathString) จริงๆ
Matthew Payne

@MatthewPayne มันอาจจะดี แต่มันไม่จำเป็นจริงๆ คุณสามารถทำได้ด้วยตัวคุณเองอย่างง่ายดายfunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no

11
Lodash มีฟังก์ชันการทำงานเดียวกันนี้เอง:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
ทอม

ดียิ่งขึ้นจะเป็น _.result
Shishir Arora

หากคุณจำเป็นต้องพิจารณาเส้นทางที่แตกต่างกันให้พิจารณา: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison

210

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

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

ข้อสงวนสิทธิ์ # 2เรากำลังพูดถึงการดำเนินงานหลายล้านต่อมิลลิวินาทีที่นี่ มันไม่น่าเป็นไปได้ที่สิ่งเหล่านี้จะสร้างความแตกต่างอย่างมากในกรณีการใช้งานส่วนใหญ่ เลือกวิธีที่เหมาะสมที่สุดที่จะรู้ถึงข้อ จำกัด ของแต่ละข้อ สำหรับฉันฉันจะไปกับบางสิ่งบางอย่างที่reduceไม่สะดวก

การห่อวัตถุ (โดย Oliver Steele) - 34% - เร็วที่สุด

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

โซลูชันดั้งเดิม (แนะนำในคำถาม) - 45%

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;

checkNested - 50%

function checkNested(obj) {
  for (var i = 1; i < arguments.length; i++) {
    if (!obj.hasOwnProperty(arguments[i])) {
      return false;
    }
    obj = obj[arguments[i]];
  }
  return true;
}

get_if_exist - 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}

validChain - 54%

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

objHasKeys - 63%

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

nestedPropertyExists - 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_.get - 72%

deeptest - 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

ตัวตลกเศร้า - 100% - ช้าที่สุด

var o = function(obj) { return obj || {} };

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);

16
ควรสังเกตว่ายิ่งมีการทดสอบ% มากเท่าไหร่ - ช้าลง
avalanche

2
แล้ว Lodash _.get()ล่ะ มันเปรียบเทียบกับคำตอบเหล่านั้นอย่างไร
beniutek

1
แต่ละวิธีจะช้ากว่าหรือเร็วกว่าวิธีอื่นทั้งนี้ขึ้นอยู่กับสถานการณ์ หากพบกุญแจทั้งหมดแล้ว "การห่อวัตถุ" อาจเร็วที่สุด แต่หากไม่พบปุ่มใดปุ่มหนึ่ง "การแก้ปัญหาดั้งเดิม / โซลูชันดั้งเดิม" อาจเร็วขึ้น
evilReiko

1
@evilReiko วิธีการใด ๆ จะช้าลงหากไม่พบปุ่ม แต่ตามสัดส่วนซึ่งกันและกันก็ยังค่อนข้างเหมือนเดิม อย่างไรก็ตามคุณพูดถูก - นี่เป็นการออกกำลังกายทางปัญญามากกว่าสิ่งอื่นใด เรากำลังพูดถึงหนึ่งล้านซ้ำต่อมิลลิวินาทีที่นี่ ฉันไม่เห็นกรณีการใช้งานที่มันจะสร้างความแตกต่างมาก ส่วนตัวฉันจะไปreduceหรือtry/catchเพื่อความสะดวก
unitario

มันเปรียบเทียบกับนักแสดงได้อย่างไรtry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex

46

คุณสามารถอ่านคุณสมบัติวัตถุที่ระดับความลึกใด ๆ 't.level1.level2.level3'หากคุณจัดการกับชื่อเช่นสตริง:

window.t={level1:{level2:{level3: 'level3'}}};

function deeptest(s){
    s= s.split('.')
    var obj= window[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

alert(deeptest('t.level1.level2.level3') || 'Undefined');

ก็จะส่งกลับundefinedถ้าใด ๆ undefinedของกลุ่มคือ


3
น่าสังเกตว่าวิธีนี้มีประสิทธิภาพอย่างน้อยใน Chrome ในบางกรณีประสิทธิภาพสูงกว่า @Claudiu คำตอบที่เลือกในเวอร์ชั่นที่ปรับเปลี่ยนแล้ว ดูการทดสอบประสิทธิภาพได้ที่นี่: jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

28
var a;

a = {
    b: {
        c: 'd'
    }
};

function isset (fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isset(function () { return a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

หากคุณกำลังเข้ารหัสในสภาพแวดล้อม ES6 (หรือใช้6to5 ) จากนั้นคุณสามารถใช้ประโยชน์จากไวยากรณ์ฟังก์ชั่นลูกศร :

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

เกี่ยวกับประสิทธิภาพไม่มีการลงโทษประสิทธิภาพการใช้ try..catchบล็อกหากมีการตั้งค่าคุณสมบัติ มีผลกระทบต่อประสิทธิภาพหากคุณสมบัติไม่ได้ถูกตั้งค่า

พิจารณาโดยใช้เพียง_.has:

var object = { 'a': { 'b': { 'c': 3 } } };

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true

2
ฉันคิดว่าtry-catchวิธีนี้เป็นคำตอบที่ดีที่สุด มีความแตกต่างทางปรัชญาระหว่างการสอบถามวัตถุสำหรับประเภทของมันและสมมติว่า API นั้นมีอยู่และล้มเหลวตามนั้นหากไม่ได้ หลังมีความเหมาะสมมากขึ้นในภาษาที่พิมพ์หลวม ดูstackoverflow.com/a/408305/2419669 วิธีการยังไกลชัดเจนกว่าtry-catch if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }
yangmillstheory

24

เกี่ยวกับ

try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}

15
ฉันไม่คิดว่าลอง / จับเป็นวิธีที่ดีในการทดสอบการมีอยู่ของวัตถุ: ลอง / จับหมายถึงการจัดการข้อยกเว้นไม่ใช่เงื่อนไขปกติเช่นการทดสอบที่นี่ ฉันคิดว่า (typeof foo == "undefined") ในแต่ละขั้นตอนนั้นดีกว่า - และโดยทั่วไปอาจจำเป็นต้องทำการรีแฟคเตอร์ใหม่หากคุณทำงานกับคุณสมบัติที่ซ้อนกันอย่างลึกซึ้ง นอกจากนี้ลอง / จับจะทำให้เกิดการแตกใน Firebug (และในเบราว์เซอร์ที่เปิดใช้งานข้อผิดพลาด) หากมีข้อผิดพลาด
Sam Dutton

ฉันลงคะแนนในเรื่องนี้เนื่องจากเบราว์เซอร์จะตรวจสอบการมีอยู่สองครั้งหากคุณใช้วิธีแก้ไขปัญหาอื่น ๆ ให้บอกว่าคุณต้องการโทร´acb = 2´ เบราว์เซอร์จะต้องตรวจสอบการมีอยู่ก่อนที่จะแก้ไขค่า (ไม่เช่นนั้นจะเป็นข้อผิดพลาดของหน่วยความจำที่ระบบปฏิบัติการตรวจจับ)

4
คำถามยังคงอยู่: แม่มดหนึ่งเร็วกว่าสำหรับเบราว์เซอร์ที่จะตั้งค่าลองจับหรือโทรhasOwnProperty()n ครั้ง?

14
ทำไมสิ่งนี้ถึงเลวร้ายอีกครั้ง? นี่ดูสะอาดที่สุดสำหรับฉัน
Austin Pray

ฉันจะบอกว่า: หากคุณคาดหวังว่าทรัพย์สินนั้นมีอยู่มากกว่าที่จะสามารถห่อเป็นบล็อกลองได้ หากไม่มีอยู่แสดงว่าเป็นข้อผิดพลาด แต่ถ้าคุณขี้เกียจและใส่รหัสปกติลงใน catch catch สำหรับกรณีที่ไม่มีคุณสมบัติ try / catch ถูกนำไปใช้ในทางที่ผิด นี่คือ if / else หรือบางสิ่งที่คล้ายกันจำเป็นต้องมี
robsch

18

คำตอบ ES6 ทดสอบอย่างละเอียด :)

const propExists = (obj, path) => {
    return !!path.split('.').reduce((obj, prop) => {
        return obj && obj[prop] ? obj[prop] : undefined;
    }, obj)
}

→ดูCodepen พร้อมการทดสอบเต็มรูปแบบ


ฉันทำให้การทดสอบของคุณล้มเหลวในการตั้งค่าของเสาแบนที่ 0 คุณต้องใส่ใจเกี่ยวกับประเภทการข่มขู่
4326 germain

@Germain มันเหมาะกับคุณไหม? (ฉันเปรียบเทียบอย่างชัดเจน===สำหรับความผิดพลาดที่แตกต่างกันและเพิ่มการทดสอบหากคุณมีความคิดที่ดีกว่าโปรดแจ้งให้เราทราบ)
Frank Nocke

falseฉันทำทดสอบของคุณล้มเหลวในการตั้งค่าอีกครั้งค่าของเสาแบนไป แล้วคุณอาจต้องการที่จะมีค่าในวัตถุของคุณตั้งค่าเป็นundefined(ฉันรู้ว่ามันแปลก แต่เป็น JS) ฉันได้ตั้งค่าเป็นบวกที่ผิดพลาดเป็น'Prop not Found':const hasTruthyProp = prop => prop === 'Prop not found' ? false : true const path = obj => path => path.reduce((obj, prop) => { return obj && obj.hasOwnProperty(prop) ? obj[prop] : 'Prop not found' }, obj) const myFunc = compose(hasTruthyProp, path(obj))
germain

คุณสามารถแยก codepen ของฉัน (ขวาบนง่าย) แก้ไขและเพิ่มการทดสอบแล้วส่ง URL ของคุณมาให้ฉันได้หรือไม่? ขอบคุณ =)
Frank Nocke

วิ่งไปที่ห้องสมุดบุคคลที่ 3 (ใหญ่) ... เป็นไปได้ แต่ไม่ใช่ความชอบของฉัน
Frank Nocke

17

นอกจากนี้คุณยังสามารถใช้ข้อเสนอการผูกมัดเสริม tc39 ร่วมกับ babel 7 - tc39-proposal-optional-chaining

รหัสจะมีลักษณะเช่นนี้:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);

โปรดทราบว่าไวยากรณ์นี้จะเปลี่ยนไปอย่างแน่นอนเนื่องจากสมาชิก TC39 บางคนมีข้อขัดข้อง
jhpratt GOFUNDME RELICENSING

อาจเป็นไปได้ แต่จะมีให้ในบางรูปแบบในเวลาและนั่นเป็นสิ่งเดียวที่สำคัญ .. มันเป็นหนึ่งในคุณสมบัติที่ฉันคิดถึงมากที่สุดใน JS
Goran.it

11

ฉันลองใช้วิธีเรียกซ้ำ:

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

การ! keys.length ||เตะซ้ำจากการเรียกซ้ำดังนั้นจึงไม่เรียกใช้ฟังก์ชันโดยไม่มีปุ่มเหลือให้ทดสอบ แบบทดสอบ:

obj = {
  path: {
    to: {
      the: {
        goodKey: "hello"
      }
    }
  }
}

console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true
console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

ฉันใช้มันเพื่อพิมพ์มุมมอง html ที่เป็นมิตรของกลุ่มวัตถุที่มีคีย์ / ค่าที่ไม่รู้จักเช่น:

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';

9

ฉันคิดว่าสคริปต์ต่อไปนี้ให้การแสดงที่อ่านง่ายขึ้น

ประกาศฟังก์ชั่น:

var o = function(obj) { return obj || {};};

จากนั้นใช้แบบนี้:

if (o(o(o(o(test).level1).level2).level3)
{

}

ฉันเรียกมันว่า "เทคนิคตัวตลกเศร้า" เพราะใช้เครื่องหมาย o (


แก้ไข:

นี่คือเวอร์ชันสำหรับTypeScript

มันให้การตรวจสอบประเภทที่รวบรวมเวลา (เช่นเดียวกับ Intellisense ถ้าคุณใช้เครื่องมือเช่น Visual Studio)

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

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

o(o(o(o(test).level1).level2).level3

แต่เวลานี้ intellisense ทำงาน!

บวกคุณสามารถตั้งค่าเริ่มต้น:

o(o(o(o(o(test).level1).level2).level3, "none")

1
°0o <°(())))><
Daniel W.

1
ฉันชอบอันนี้เพราะมันซื่อตรงและทุ่ม "ไม่ได้กำหนด" ไว้ในหน้าของคุณเมื่อคุณไม่รู้จักObjectประเภทของคุณ +1

1
ตราบใดที่คุณรักษาคำแถลงคุณสามารถเรียกมันว่าเทคนิคตลกมีความสุข (o
Sventies

ขอบคุณ Sventies ฉันรักความคิดเห็นของคุณ มันเป็นมุมที่ค่อนข้างดีที่จะมองจาก - เงื่อนไขดังกล่าวส่วนใหญ่จะใช้ใน "ifs" และล้อมรอบด้วยวงเล็บภายนอกเสมอ ดังนั้นใช่มันเป็นตัวตลกส่วนใหญ่ที่แน่นอน :)))
VeganHunter

คุณต้องมีความรักในวงเล็บเพื่อที่จะได้รับสิ่งนี้ ...
Bastien7

7

สร้างโลกfunctionและใช้ในโครงการทั้งหมด

ลองนี้

function isExist(arg){
   try{
      return arg();
   }catch(e){
      return false;
   }
}

let obj={a:5,b:{c:5}};

console.log(isExist(()=>obj.b.c))
console.log(isExist(()=>obj.b.foo))
console.log(isExist(()=>obj.test.foo))

ถ้าเงื่อนไข

if(isExist(()=>obj.test.foo)){
   ....
}

ใช้งานได้ดี ง่ายและมีประสิทธิภาพ
gbland777

6

วิธีง่ายๆอย่างหนึ่งคือ:

try {
    alert(test.level1.level2.level3);
} catch(e) {
    alert("undefined");    // this is optional to put any output here
}

try/catchจับกรณีเมื่อใด ๆ ของระดับที่สูงกว่าวัตถุเช่นการทดสอบ test.level1, test.level1.level2 ยังไม่ได้กำหนด


6

ฉันไม่เห็นตัวอย่างของคนที่ใช้พร็อกซี่

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

รหัสด้านบนทำงานได้ดีสำหรับสิ่งที่ซิงโครนัส แต่คุณจะทดสอบบางสิ่งที่ไม่ตรงกันแบบอาแจ็กซ์นี้ได้อย่างไร คุณทดสอบได้อย่างไร เกิดอะไรขึ้นถ้าการตอบสนองไม่ json เมื่อมันกลับข้อผิดพลาด 500 http?

window.fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})

แน่ใจว่าคุณสามารถใช้ async / รอคอยเพื่อกำจัดการเรียกกลับบางอย่าง แต่ถ้าคุณทำมันได้อย่างน่าอัศจรรย์ยิ่งกว่านี้ล่ะ? สิ่งที่มีลักษณะเช่นนี้:

fetch('https://httpbin.org/get').json().headers['User-Agent']

คุณอาจสงสัยว่าสัญญา & .thenโซ่ทั้งหมดอยู่ที่ไหน... นี่อาจเป็นการปิดกั้นทุกสิ่งที่คุณรู้ ... แต่ใช้เทคนิคพร็อกซี่เดียวกันกับสัญญาคุณสามารถทดสอบเส้นทางที่ซับซ้อนที่ซ้อนกันอยู่จริงโดยไม่ต้องเขียนฟังก์ชั่นเดียว


หากมีใครสนใจฉันเผยแพร่เวอร์ชัน async ในเวลา 23.00 น.
Endless

5

จากคำตอบนี้ฉันพบกับฟังก์ชั่นทั่วไปที่ใช้ES2015ซึ่งจะแก้ปัญหาได้

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

var test = {
  first: {
    second: {
        third: "This is not the key your are looking for"
    }
  }
}

if ( validChain( test, "first", "second", "third" ) ) {
    console.log( test.first.second.third );
}

1
นี่คือวิธีสุดท้ายของฉันfunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington

5

ฉันได้สร้างฟังก์ชั่นเล็ก ๆ น้อย ๆ เพื่อให้ได้รับคุณสมบัติของวัตถุที่ซ้อนกันอย่างปลอดภัย

function getValue(object, path, fallback, fallbackOnFalsy) {
    if (!object || !path) {
        return fallback;
    }

    // Reduces object properties to the deepest property in the path argument.
    return path.split('.').reduce((object, property) => {
       if (object && typeof object !== 'string' && object.hasOwnProperty(property)) {
            // The property is found but it may be falsy.
            // If fallback is active for falsy values, the fallback is returned, otherwise the property value.
            return !object[property] && fallbackOnFalsy ? fallback : object[property];
        } else {
            // Returns the fallback if current chain link does not exist or it does not contain the property.
            return fallback;
        }
    }, object);
}

หรือเวอร์ชันที่เรียบง่าย แต่อ่านไม่ได้เล็กน้อย:

function getValue(o, path, fb, fbFalsy) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? !o[p] && fbFalsy ? fb : o[p] : fb, o);
}

หรือแม้แต่สั้นกว่า แต่ไม่มีทางเลือกในการตั้งค่าสถานะที่ผิดพลาด:

function getValue(o, path, fb) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? o[p] : fb, o);
}

ฉันมีการทดสอบด้วย:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}

และนี่คือการทดสอบบางส่วน:

// null
console.log(getValue(obj, 'c.d', 'fallback'));

// array
console.log(getValue(obj, 'c.b.c', 'fallback'));

// array index 2
console.log(getValue(obj, 'c.b.c.2', 'fallback'));

// no index => fallback
console.log(getValue(obj, 'c.b.c.10', 'fallback'));

หากต้องการดูโค้ดทั้งหมดที่มีเอกสารประกอบและการทดสอบที่ฉันได้ลองคุณสามารถตรวจสอบ github gist ของฉันได้ที่: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js


4

คำตอบที่ยอดเยี่ยมของ @ CMS รุ่นสั้นลง ES5:

// Check the obj has the keys in the order mentioned. Used for checking JSON results.  
var checkObjHasKeys = function(obj, keys) {
  var success = true;
  keys.forEach( function(key) {
    if ( ! obj.hasOwnProperty(key)) {
      success = false;
    }
    obj = obj[key];
  })
  return success;
}

ด้วยการทดสอบที่คล้ายกัน:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

ปัญหาเดียวของเรื่องนี้คือถ้ามีคีย์ที่ไม่ได้กำหนดไว้หลายระดับคุณจะได้รับ TypeError เช่นcheckObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS

1
วิธีที่เหมาะสมกว่านั้นคือทุก ๆค่าที่สามารถส่งคืนได้โดยตรง
RobG

อาจจะเปลี่ยนไปsuccess = false; return falseคุณควรจะประกันตัวเมื่อคุณรู้ว่าตัวแบ่งมันไม่มีอะไรที่ลึกเกินกว่าจะมีอยู่เมื่อมันเป็นโมฆะหรือไม่ได้กำหนด วิธีนี้จะช่วยป้องกันข้อผิดพลาดในรายการที่ซ้อนกันลึกเนื่องจากเห็นได้ชัดว่าไม่มีอยู่
ลุย

4

ฉันกำลังมองหาค่าที่จะส่งคืนหากมีอสังหาริมทรัพย์อยู่ดังนั้นฉันจึงแก้ไขคำตอบโดย CMS ด้านบน นี่คือสิ่งที่ฉันมาด้วย:

function getNestedProperty(obj, key) {
  // Get property array from key string
  var properties = key.split(".");

  // Iterate through properties, returning undefined if object is null or property doesn't exist
  for (var i = 0; i < properties.length; i++) {
    if (!obj || !obj.hasOwnProperty(properties[i])) {
      return;
    }
    obj = obj[properties[i]];
  }

  // Nested property found, so return the value
  return obj;
}


Usage:

getNestedProperty(test, "level1.level2.level3") // "level3"
getNestedProperty(test, "level1.level2.foo") // undefined


3

คำตอบที่ได้รับจาก CMS ทำงานได้ดีกับการแก้ไขต่อไปนี้สำหรับการตรวจสอบค่า Null เช่นกัน

function checkNested(obj /*, level1, level2, ... levelN*/) 
      {
             var args = Array.prototype.slice.call(arguments),
             obj = args.shift();

            for (var i = 0; i < args.length; i++) 
            {
                if (obj == null || !obj.hasOwnProperty(args[i]) ) 
                {
                    return false;
                }
                obj = obj[args[i]];
            }
            return true;
    }

3

ตัวเลือกต่อไปนี้ถูกอธิบายอย่างละเอียดโดยเริ่มจากคำตอบนี้ ต้นไม้เดียวกันสำหรับทั้งสอง:

var o = { a: { b: { c: 1 } } };

หยุดการค้นหาเมื่อไม่ได้กำหนด

var u = undefined;
o.a ? o.a.b ? o.a.b.c : u : u // 1
o.x ? o.x.y ? o.x.y.z : u : u // undefined
(o = o.a) ? (o = o.b) ? o.c : u : u // 1

ตรวจสอบให้แน่ใจแต่ละระดับหนึ่งโดยหนึ่ง

var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined

3

ฉันรู้ว่าคำถามนี้เก่า แต่ฉันต้องการที่จะนำเสนอส่วนขยายโดยการเพิ่มไปยังวัตถุทั้งหมด ฉันรู้ว่าคนมักจะขมวดคิ้วในการใช้ต้นแบบวัตถุสำหรับฟังก์ชั่นการขยายวัตถุ แต่ฉันไม่พบอะไรง่ายไปกว่าทำสิ่งนี้ นอกจากนี้ยังอนุญาตให้ใช้เมธอดObject.defineProperty

Object.defineProperty( Object.prototype, "has", { value: function( needle ) {
    var obj = this;
    var needles = needle.split( "." );
    for( var i = 0; i<needles.length; i++ ) {
        if( !obj.hasOwnProperty(needles[i])) {
            return false;
        }
        obj = obj[needles[i]];
    }
    return true;
}});

ตอนนี้เพื่อทดสอบคุณสมบัติในวัตถุใด ๆ ที่คุณสามารถทำได้:

if( obj.has("some.deep.nested.object.somewhere") )

นี่คือ jsfiddleเพื่อทดสอบและโดยเฉพาะอย่างยิ่งมันรวม jQuery บางอย่างที่แตกถ้าคุณแก้ไข Object.prototype โดยตรงเนื่องจากคุณสมบัติกลายเป็น enumerable สิ่งนี้น่าจะใช้ได้ดีกับห้องสมุดบุคคลที่สาม


3

ฉันคิดว่านี่เป็นการปรับปรุงเล็กน้อย (กลายเป็น 1 ซับ):

   alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )

สิ่งนี้ใช้งานได้เนื่องจากตัวดำเนินการ && ส่งคืนตัวถูกดำเนินการสุดท้ายที่ประเมิน (และวงจรสั้น)


คุณคัดลอกสิ่งที่พวกเขาบอกว่าพวกเขาทำตามปกติและต้องการหลีกเลี่ยง ...
Sean Kendle

3

ใช้งานได้กับวัตถุและอาร์เรย์ทั้งหมด :)

อดีต:

if( obj._has( "something.['deep']['under'][1][0].item" ) ) {
    //do something
}

นี่เป็นคำตอบของ Brian ที่ได้รับการปรับปรุง

ฉันใช้_hasเป็นชื่อคุณสมบัติเนื่องจากอาจขัดแย้งกับคุณสมบัติที่มีอยู่แล้ว (เช่นแผนที่)

Object.defineProperty( Object.prototype, "_has", { value: function( needle ) {
var obj = this;
var needles = needle.split( "." );
var needles_full=[];
var needles_square;
for( var i = 0; i<needles.length; i++ ) {
    needles_square = needles[i].split( "[" );
    if(needles_square.length>1){
        for( var j = 0; j<needles_square.length; j++ ) {
            if(needles_square[j].length){
                needles_full.push(needles_square[j]);
            }
        }
    }else{
        needles_full.push(needles[i]);
    }
}
for( var i = 0; i<needles_full.length; i++ ) {
    var res = needles_full[i].match(/^((\d+)|"(.+)"|'(.+)')\]$/);
    if (res != null) {
        for (var j = 0; j < res.length; j++) {
            if (res[j] != undefined) {
                needles_full[i] = res[j];
            }
        }
    }

    if( typeof obj[needles_full[i]]=='undefined') {
        return false;
    }
    obj = obj[needles_full[i]];
}
return true;
}});

นี่คือซอ


3

นี่คือสิ่งที่ฉันทำ - โซลูชันส่วนใหญ่ไม่สนใจกรณีของอาร์เรย์ที่ซ้อนกันใน:

    obj = {
        "l1":"something",
        "l2":[{k:0},{k:1}],
        "l3":{
            "subL":"hello"
        }
    }

ฉันอาจต้องการตรวจสอบ obj.l2[0].k

ด้วยฟังก์ชั่นด้านล่างคุณสามารถทำได้ deeptest('l2[0].k',obj)

ฟังก์ชั่นจะกลับจริงถ้าวัตถุที่มีอยู่เป็นเท็จอย่างอื่น

function deeptest(keyPath, testObj) {
    var obj;

    keyPath = keyPath.split('.')
    var cKey = keyPath.shift();

    function get(pObj, pKey) {
        var bracketStart, bracketEnd, o;

        bracketStart = pKey.indexOf("[");
        if (bracketStart > -1) { //check for nested arrays
            bracketEnd = pKey.indexOf("]");
            var arrIndex = pKey.substr(bracketStart + 1, bracketEnd - bracketStart - 1);
            pKey = pKey.substr(0, bracketStart);
			var n = pObj[pKey];
            o = n? n[arrIndex] : undefined;

        } else {
            o = pObj[pKey];
        }
        return o;
    }

    obj = get(testObj, cKey);
    while (obj && keyPath.length) {
        obj = get(obj, keyPath.shift());
    }
    return typeof(obj) !== 'undefined';
}

var obj = {
    "l1":"level1",
    "arr1":[
        {"k":0},
        {"k":1},
        {"k":2}
    ],
    "sub": {
       	"a":"letter A",
        "b":"letter B"
    }
};
console.log("l1: " + deeptest("l1",obj));
console.log("arr1[0]: " + deeptest("arr1[0]",obj));
console.log("arr1[1].k: " + deeptest("arr1[1].k",obj));
console.log("arr1[1].j: " + deeptest("arr1[1].j",obj));
console.log("arr1[3]: " + deeptest("arr1[3]",obj));
console.log("arr2: " + deeptest("arr2",obj));


3

ตอนนี้เรายังสามารถใช้reduceวนลูปผ่านคีย์ที่ซ้อนกัน:

// @params o<object>
// @params path<string> expects 'obj.prop1.prop2.prop3'
// returns: obj[path] value or 'false' if prop doesn't exist

const objPropIfExists = o => path => {
  const levels = path.split('.');
  const res = (levels.length > 0) 
    ? levels.reduce((a, c) => a[c] || 0, o)
    : o[path];
  return (!!res) ? res : false
}

const obj = {
  name: 'Name',
  sys: { country: 'AU' },
  main: { temp: '34', temp_min: '13' },
  visibility: '35%'
}

const exists = objPropIfExists(obj)('main.temp')
const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz')

console.log(exists, doesntExist)


3

คุณสามารถทำได้โดยใช้ฟังก์ชั่นวนซ้ำ สิ่งนี้จะใช้ได้แม้ว่าคุณจะไม่ทราบชื่อคีย์ของวัตถุที่ซ้อนกันทั้งหมด

function FetchKeys(obj) {
    let objKeys = [];
    let keyValues = Object.entries(obj);
    for (let i in keyValues) {
        objKeys.push(keyValues[i][0]);
        if (typeof keyValues[i][1] == "object") {
            var keys = FetchKeys(keyValues[i][1])
            objKeys = objKeys.concat(keys);
        }
    }
    return objKeys;
}

let test = { level1: { level2: { level3: "level3" } } };
let keyToCheck = "level2";
let keys = FetchKeys(test); //Will return an array of Keys

if (keys.indexOf(keyToCheck) != -1) {
    //Key Exists logic;
}
else {
    //Key Not Found logic;
}

2

มีฟังก์ชั่นที่นี่ใน the codeabode (safeRead)ซึ่งจะทำสิ่งนี้ในลักษณะที่ปลอดภัย ... เช่น

safeRead(test, 'level1', 'level2', 'level3');

ถ้าคุณสมบัติใด ๆ เป็นโมฆะหรือไม่ได้กำหนดสตริงว่างจะถูกส่งกลับ


ฉันชอบวิธีการนี้ด้วยการสร้างเทมเพลตเพราะมันจะส่งคืนสตริงว่างถ้าไม่ได้ตั้งค่า
Lounge 9


2

ฉันเขียนฟังก์ชันของตัวเองที่ใช้เส้นทางที่ต้องการและมีฟังก์ชั่นโทรกลับที่ดีและไม่ดี

function checkForPathInObject(object, path, callbackGood, callbackBad){
    var pathParts = path.split(".");
    var currentObjectPath = object;

    // Test every step to see if it exists in object
    for(var i=0; i<(pathParts.length); i++){
        var currentPathPart = pathParts[i];
        if(!currentObjectPath.hasOwnProperty(pathParts[i])){
            if(callbackBad){
                callbackBad();
            }
            return false;
        } else {
            currentObjectPath = currentObjectPath[pathParts[i]];
        }
    }

    // call full path in callback
    callbackGood();
}

การใช้งาน:

var testObject = {
    level1:{
        level2:{
            level3:{
            }
        }
    }
};


checkForPathInObject(testObject, "level1.level2.level3", function(){alert("good!")}, function(){alert("bad!")}); // good

checkForPathInObject(testObject, "level1.level2.level3.levelNotThere", function(){alert("good!")}, function(){alert("bad!")}); //bad

แม้ว่าฉันจะให้เครดิตคุณกับแรงบันดาลใจในการปรับรหัสของคุณให้เป็นคำตอบของฉัน
davewoodhall

2
//Just in case is not supported or not included by your framework
//***************************************************
Array.prototype.some = function(fn, thisObj) {
  var scope = thisObj || window;
  for ( var i=0, j=this.length; i < j; ++i ) {
    if ( fn.call(scope, this[i], i, this) ) {
      return true;
    }
  }
  return false;
};
//****************************************************

function isSet (object, string) {
  if (!object) return false;
  var childs = string.split('.');
  if (childs.length > 0 ) {
    return !childs.some(function (item) {
      if (item in object) {
        object = object[item]; 
        return false;
      } else return true;
    });
  } else if (string in object) { 
    return true;
  } else return false;
}

var object = {
  data: {
    item: {
      sub_item: {
        bla: {
          here : {
            iam: true
          }
        }
      }
    }
  }
};

console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'x')); // false
console.log(isSet(object,'data.sub_item')); // false
console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'data.item.sub_item.bla.here.iam')); // true
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.