# obj.prop(path)

{% hint style="success" %}
如果 JavaScript 引擎不支援 [optional-chaining](../optional-chaining "mention")，就可以用下面的程式。
{% endhint %}

{% tabs %}
{% tab title="💾 程式" %}
{% hint style="warning" %}
由於使用 [mixin](../../../extend/mixin "mention") 的方式，所以當我們使用 <mark style="color:blue;">`user.prop('info.age')`</mark> 的方式來讀取物件屬性時，<mark style="color:blue;">`user`</mark> 本身已經<mark style="color:yellow;">**確定是個物件**</mark>(不會是 null 或 undefined)，因此我們在 <mark style="color:blue;">`prop(path)`</mark> 這個 method 內部並沒有去檢查 <mark style="color:purple;">`this`</mark> 是不是 null 或 undefined。
{% endhint %}

* replit ⟩ [obj.prop(path)](https://replit.com/@pegasusroe/JS-objproppath#index.js)

```javascript
// ⭐ mixin for objects
const ObjectTools = {
    
    // obj.prop(path)
    prop(path) {
        
        let value = this;
        let components = path.split('.');
        
        while (components.length) {
            
            // pull first component
            let component = components.shift();
            // check if last character === '?'
            const isValueOptional = component.slice(-1) === '?';

            if (isValueOptional) {
                // remove "?"
                component = component.slice(0, -1);
                // get nested property
                value = value[component];
                // return early if value is nullish
                if (value === null || value === undefined) return undefined;
            } else {
                // if not optional, get property directly
                value = value[component];
            }
            
        }
        
        return value;
    }
};

// for node.js module
module.exports = objectTools;
```

💈範例：

```javascript
"use strict";
const { log } = console;

// ⭐ mixin for objects
const ObjectTools = require('./ObjectTools.js');

// ---------- main ------------

// test object
const user = { 
    name: 'Joy',
    info: { 
        address: { 
            street: '5th Street',
            zip: 333,
        } 
    } 
};

// ⭐ apply mixin to `obj`
Object.assign(user, ObjectTools);

// ---------- log ------------
;
[
    user.prop("info.address"),           // { street: '5th Street', zip: 333 }
    user.prop("info.address.street"),    // '5th Street'
    
    user.prop("info.address.street.bar"),// undefined
    user.prop("info.age"),               // undefined
    user.prop("info.age?.details"),      // undefined
    user.prop("bar?.age?.details"),      // undefined
    
    // user.prop("bar.age"),             // ⛔ TypeError: 
    //                ^^^  <----  Cannot read property 'age' of undefined

    user === global.user,                // false: `const` doesn't create property of `global`
    
].forEach(x => log(x));

// user.prop("info.address.street")
// ---------------------------------
// initial states:
//
//   • value = user
//   • components = [ 'info', 'address', 'street' ] 
//
// while loop:
// --------------------------------------------------------------------
// length  shift()    components             value           
// --------------------------------------------------------------------
//   3     'info'    ['address', 'street']    user.info
//   2     'address' ['street']               user.info.address
//   1     'street'  [ ]                      user.info.address.street
//   0    <---- exit while loop              ╰── return value ───╯
// --------------------------------------------------------------------
```
{% endtab %}

{% tab title="📘 手冊" %}
* [eval()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) ⟩ [accessing member properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#accessing_member_properties) ⭐️
* [String.prototype.slice()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice)
* [Global object](https://developer.mozilla.org/en-US/docs/Glossary/Global_object)\
  unlike [`var`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var), [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) and [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) <mark style="color:red;">**do not**</mark> create **properties** of the <mark style="color:yellow;">**global object**</mark>.
{% endtab %}

{% tab title="👥 相關" %}
* use the concept of [mixin](../../../extend/mixin "mention").
{% endtab %}

{% tab title="🗣 討論" %}
*   [How to get the last character of a string?](https://stackoverflow.com/a/3884711/5409815)  Ans:&#x20;

    ```javascript
    str.slice(-1);
    ```
* [JavaScript check if variable exists (is defined/initialized)](https://stackoverflow.com/questions/5113374/javascript-check-if-variable-exists-is-defined-initialized)
* [JavaScript: How to check if variable exists by variable name?](https://stackoverflow.com/questions/9928914/javascript-how-to-check-if-variable-exists-by-variable-name)
* [String to variable name without eval](https://stackoverflow.com/questions/56653910/string-to-variable-name-without-eval)
{% endtab %}
{% endtabs %}
