概念
Object.defineProperty(obj, prop, descriptor)
先用一张图来给大家解释下obj、prop、descriptor是什么
现在我有一个对象object1,我希望给它添加一个属性property1
就可以像这样操作

通过这个图,我们很清楚可以得到这个结论:
- obj:需要定义属性的对象
- prop:要定义 或 修改的属性名 或 Symbol
前两项并无太多可说,那descriptor是什么呢?接下来,我们就重点说一下它 —— 描述符。
它包含以下属性:
- configurable:当且仅当该属性的
configurable键值为true时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 enumerable:当且仅当该属性的
enumerable键值为true时,该属性才会出现在对象的枚举属性中。- 数据描述符还具有下面两个可选键值:
- value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
- writable:当且仅当该属性的
writable键值为true时,属性的值,也就是上面的value,才能被赋值运算符改变。
- 存取描述符还具有下面两个可选键值:
- get:属性的 getter 函数,如果没有 getter,则为
undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 - set:属性的 setter 函数,如果没有 setter,则为
undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this对象。 如果一个描述符不具有value、writable、get和set中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有value或writable和get或set键,则会产生一个异常。重点
一个描述符只可能是两者之一,绝不可能同时是两者。
基础用法
```javascript // 数据描述符 Object.defineProperty({}, “key”, { enumerable: false, configurable: false, writable: false, value: “static” }); // 存取描述符 Object.defineProperty({}, “key”, { enumerable: false, configurable: false, get() { return “static” }, set() {
- get:属性的 getter 函数,如果没有 getter,则为
} });
<a name="gUmrv"></a># 简单的例子输入框中的值发生改变时,对应显示在页面上```javascript<input id="input" type="text" value="1"><p id="demo"></p><script>const input = document.querySelector('#input')let obj = {text: ''}let val = input.valueObject.defineProperty(obj,'text',{get(){return val},set(v){val = vdocument.querySelector('#demo').innerHTML = v}})input.addEventListener('input',(e) =>{obj.text = e.target.value})</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有哪些元素
提示:如果 elements[] 数组具有名称(input 标签的 id 或 name 属性),那么该元素的名称就是 formObject 的一个属性,因此可以使用名称而不是数字来引用 input 对象。
举例,假设 x 是一个 form 对象,其中的一个 input 对象的名称是 fname,则可以使用 x.fname 来引用该对象
所以我们获取form表单元素有两种方式- 数据描述符还具有下面两个可选键值:
遍历数组获取表单元素
for (let i = 0; i < form.length; i++) { let item = form.elements[i] console.log(item); }
通过input的name值来获取(也就是通过对象的方式来获取)
for (const formKey in formData) { const item = form[formKey] // form表单元素 let value = item.value console.log(item); }给表单元素添加事件
// 生成伪数组, 根据类型不同,绑定不同的事件 [...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中修改数组中的某一条无法更新的原因

