概念

  1. Object.defineProperty(obj, prop, descriptor)

先用一张图来给大家解释下objpropdescriptor是什么

现在我有一个对象object1,我希望给它添加一个属性property1

就可以像这样操作

Object.defineProperty - 图1
通过这个图,我们很清楚可以得到这个结论:

  • obj:需要定义属性的对象
  • prop:要定义 或 修改的属性名 或 Symbol

前两项并无太多可说,那descriptor是什么呢?接下来,我们就重点说一下它 —— 描述符
它包含以下属性:
Object.defineProperty - 图2

  • configurable:当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
  • enumerable:当且仅当该属性的 enumerable键值为true时,该属性才会出现在对象的枚举属性中。

    • 数据描述符还具有下面两个可选键值:
      • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)
      • writable:当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
    • 存取描述符还具有下面两个可选键值:
      • get:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值
      • set:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 如果一个描述符不具有 valuewritablegetset 中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 valuewritablegetset 键,则会产生一个异常

        重点

        一个描述符只可能是两者之一,绝不可能同时是两者。
        Object.defineProperty - 图3

        基础用法

        ```javascript // 数据描述符 Object.defineProperty({}, “key”, { enumerable: false, configurable: false, writable: false, value: “static” }); // 存取描述符 Object.defineProperty({}, “key”, { enumerable: false, configurable: false, get() { return “static” }, set() {

    } });

    1. <a name="gUmrv"></a>
    2. # 简单的例子
    3. 输入框中的值发生改变时,对应显示在页面上
    4. ```javascript
    5. <input id="input" type="text" value="1">
    6. <p id="demo"></p>
    7. <script>
    8. const input = document.querySelector('#input')
    9. let obj = {
    10. text: ''
    11. }
    12. let val = input.value
    13. Object.defineProperty(obj,'text',{
    14. get(){
    15. return val
    16. },
    17. set(v){
    18. val = v
    19. document.querySelector('#demo').innerHTML = v
    20. }
    21. })
    22. input.addEventListener('input',(e) =>{
    23. obj.text = e.target.value
    24. })
    25. </script>

    实战案例

    静态页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
        <script src="https://unpkg.com/vue"></script>
    </head>
      <style>
          form {
              border: 1px solid #000;
              width: 200px;
              margin-top: 20px;
              padding: 20px;
          }
    
          form > label {
              display: block;
              margin-top: 20px;
          }
    
          form > label:nth-child(1) {
              margin-top: 0;
          }
      </style>
    <body>
    <div id="app">
      <table>
          <thead>
          <tr>
              <th>姓名</th>
              <th>性别</th>
              <th>年龄</th>
              <th>爱好</th>
          </tr>
          </thead>
          <tbody>
    
          </tbody>
      </table>
      <section>
          <form autocomplete="off">
              <label>
                  <span>姓名:</span>
                  <input type="text" name="name">
              </label>
              <label>
                  <span>性别:</span>
                  <label>
                      <span>男</span>
                      <input type="radio" name="sex" value="男" checked>
                  </label>
                  <label>
                      <span>女</span>
                      <input type="radio" name="sex" value="女">
                  </label>
              </label>
              <label>
                  <span>年龄:</span>
                  <input type="number" name="age">
              </label>
              <label>
                  <span>爱好:</span>
                  <input type="text" name="hobby">
              </label>
              <button type="submit">提交</button>
          </form>
      </section>
      <div>
          <button class="changeFirst">修改第一条数据</button>
      </div>
    </div>
    </body>
    </html>
    

    表单事件处理

    获取form表单

    参考资料:https://www.w3school.com.cn/jsref/coll_form_elements.asp

    // 对象劫持
    const observeObject = (formData,form) =>{
      console.log(formData,form);
      console.dir(form);
      for (const formKey in formData) {
        console.log(formKey);
        const item = form[formKey] // form表单元素
        let value = item.value
        console.log(item);
      }
    }
    const form = document.querySelector('form')
    const formData = {
      name: '',
      sex: '男',
      age: '',
      hobby: ''
    }
    
    observeObject(formData, form);
    

    这里的form,是我们的form表单,form[formKey]使我们form表单的每一个元素。
    通过console.dir(form);查找到elements可以知道我们form有哪些元素
    image.png
    提示:如果 elements[] 数组具有名称(input 标签的 id 或 name 属性),那么该元素的名称就是 formObject 的一个属性,因此可以使用名称而不是数字来引用 input 对象。
    举例,假设 x 是一个 form 对象,其中的一个 input 对象的名称是 fname,则可以使用 x.fname 来引用该对象
    所以我们获取form表单元素有两种方式

  1. 遍历数组获取表单元素

    for (let i = 0; i < form.length; i++) {
    let item = form.elements[i]
    console.log(item);
    }
    

    image.png

  2. 通过input的name值来获取(也就是通过对象的方式来获取)

    for (const formKey in formData) {
    const item = form[formKey] // form表单元素
    let value = item.value
    console.log(item);
    }
    

    image.png

    给表单元素添加事件

    // 生成伪数组, 根据类型不同,绑定不同的事件
    [...form].forEach(item=>{
    console.log(item);
    switch (item.type) {
     case 'text':
     case 'number': 
       item.oninput = e =>{
         formData[e.target.name] = e.target.value  //给form对象赋值
       }
       break;
     case 'radio':
       item.onchange = e => {
         formData[e.target.name] = e.target.value;
       };
       break;
     default:
       return;
    }
    })
    

    监听数据变化(对象劫持

    for (const formKey in formData) {
    const item = form[formKey] // form表单元素
    let value = item.value
    // 监听 formData数据的改变,更新对应元素的值
    Object.defineProperty(formData,formKey,{  
     get() {
       return value
     },
     set(v) {
       if(value === v) return
       value = v
       item.value // 表单元素赋值
     }
    }) 
    }
    

    表格事件处理

    渲染表格数据

    const state = [
    {
     name: '小明',
     sex: '男',
     age: '21',
     hobby: '篮球'
    }
    ]
    const render = () => {
    document.querySelector('tbody').innerHTML = state.map(item => (
     `
       <tr>
         <td class="name">
           ${item.name}
         </td>
         <td class="sex">
           ${item.sex}
         </td>
         <td class="age">
           ${item.age}
         </td>
         <td class="hobby">
           ${item.hobby}
         </td>
       </tr>
     `
    )).join('');
    for (const formDataKey in formData) {
     formData[formDataKey] = formDataKey === 'sex' ? '男' : '';
    }
    };
    render();
    

    监听数据变化(数组劫持

    const observeArray = (state,render) =>{
    const arrayMethods = Object.create(Array.prototype) //! 将数组原型对象创建为一个新的数组对象
    // 监听数组中的push方法
    Object.defineProperty(arrayMethods, 'push', {
     value(...args) {
       // 执行state的push方法,将ages添加到state中
       Array.prototype.push.apply(state, args);
       // 重新渲染页面
       render?.();
     }
    });
    state.__proto__ = arrayMethods;  //! 将state原型指向新的数组对象
    }
    

    提交数据,渲染表格

    form.addEventListener('submit', e => {
    e.preventDefault();
    console.log(formData);
    state.push({...formData});
    });
    

    完整代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
       <script src="https://unpkg.com/vue"></script>
    </head>
     <style>
         form {
             border: 1px solid #000;
             width: 200px;
             margin-top: 20px;
             padding: 20px;
         }
    
         form > label {
             display: block;
             margin-top: 20px;
         }
    
         form > label:nth-child(1) {
             margin-top: 0;
         }
     </style>
    <body>
    <div id="app">
     <table>
         <thead>
         <tr>
             <th>姓名</th>
             <th>性别</th>
             <th>年龄</th>
             <th>爱好</th>
         </tr>
         </thead>
         <tbody>
    
         </tbody>
     </table>
     <section>
         <form autocomplete="off">
             <label>
                 <span>姓名:</span>
                 <input type="text" name="name">
             </label>
             <label>
                 <span>性别:</span>
                 <label>
                     <span>男</span>
                     <input type="radio" name="sex" value="男" checked>
                 </label>
                 <label>
                     <span>女</span>
                     <input type="radio" name="sex" value="女">
                 </label>
             </label>
             <label>
                 <span>年龄:</span>
                 <input type="number" name="age">
             </label>
             <label>
                 <span>爱好:</span>
                 <input type="text" name="hobby">
             </label>
             <button type="submit">提交</button>
         </form>
     </section>
     <div>
         <button class="changeFirst">修改第一条数据</button>
     </div>
    </div>
    <script>
    const observeArray = (state,render) =>{
     const arrayMethods = Object.create(Array.prototype) //! 将数组原型对象创建为一个新的数组对象
     // 监听数组中的push方法
     Object.defineProperty(arrayMethods, 'push', {
             value(...args) {
         // 执行state的push方法,将ages添加到state中
         Array.prototype.push.apply(state, args);
         // 重新渲染页面
                 render?.();
             }
     });
     state.__proto__ = arrayMethods;  //! 将state原型指向新的数组对象
    }
    
    const observeObject = (formData,form) =>{
     for (const formKey in formData) {
       const item = form[formKey] // form表单元素
       let value = item.value
       // 监听 formData数据的改变,更新对应元素的值
       Object.defineProperty(formData,formKey,{  
         get() {
           return value
         },
         set(v) {
           if(value === v) return
           value = v
           item.value // 表单元素赋值
         }
       }) 
     }
     // 生成伪数组, 根据类型不同,绑定不同的事件
     [...form].forEach(item=>{
       switch (item.type) {
         case 'text':
         case 'number': 
           item.oninput = e =>{
             formData[e.target.name] = e.target.value  //给form对象赋值
           }
           break;
         case 'radio':
                     item.onchange = e => {
                         formData[e.target.name] = e.target.value;
                     };
                     break;
                 default:
                     return;
       }
     })
    
    }
    const form = document.querySelector('form')
    const formData = {
     name: '',
     sex: '男',
     age: '',
     hobby: ''
    }
    
    observeObject(formData, form);
    
    const state = [
     {
             name: '小明',
             sex: '男',
             age: '21',
             hobby: '篮球'
         }
    ]
    const render = () => {
         document.querySelector('tbody').innerHTML = state.map(item => (
             `
                 <tr>
                     <td class="name">
                         ${item.name}
                     </td>
                     <td class="sex">
                         ${item.sex}
                     </td>
                     <td class="age">
                         ${item.age}
                     </td>
                     <td class="hobby">
                         ${item.hobby}
                     </td>
                 </tr>
             `
         )).join('');
         for (const formDataKey in formData) {
             formData[formDataKey] = formDataKey === 'sex' ? '男' : '';
         }
     };
    render();
    observeArray(state, render);
    
    form.addEventListener('submit', e => {
     e.preventDefault();
     console.log(formData);
         state.push({...formData});
     });
    </script>
    </body>
    </html>
    

    思考

    点击提交,我们可以把我们表单的数据添加到表格数据中,如果我们修改数组中的某一条数组,是否也可以更新呢?
    现在我们来加上一个修改原有第一条数据的功能,然后点击修改之后看一下到底变化了什么吧~

    const changeFirst = document.querySelector('.changeFirst');
    changeFirst.addEventListener('click', () => {
    console.log(state, '修改了')
    state[0].age = '23';
    state[0] = {
     ...state[0],
     hobby: '编程'
    }
    });
    

    提示:原有的数据发生了改变,但是视图没有发生更新,这也是vue2中修改数组中的某一条无法更新的原因
    image.png