JS中的对象属性

JS的对象有两种属性进行描述 分别是数据属性和访问器属性。
数据属性有四个值:

  • [[Configurable]] 表示是否可以删除定义,是否可以修改属性,默认为true
  • [[Enumberable]] 是否可以迭代
  • [[Writable]] 是否可以被修改
  • [[Value]] 对象具体的值

而访问器属性也有四个值:

  • [[Configurable]] 表示是否可以删除定义,是否可以修改属性,默认为true
  • [[Enumberable]] 是否可以迭代
  • [[Get]] 获取函数,读取属性值时使用
  • [[Set]] 设置函数,写入属性时调用

那么如何实现数据响应呢?

利用Object.defineProperty()进行数据劫持

实现响应式的前提是可以捕捉到到数据的更改,获取数据同理,这就需要利用JS对象的访问器属性,而更改这些属性 就要用到JS中的一个方法 Object.defineProperty() 上述的属性在更改时,哪怕更改一个属性,所有属性都会变为默认值。

具体使用方法如下:

let obj = {name:'Ton', age: 19}
_value = obj.name
Object.defineProperty(obj, name, {
    get(){
        return _value
    },
    set(newValue){
       _value = newValue
    }
})

方法的参数分别是 Object.defineProperty(对象名, 属性名, {执行器})

而get 函数在读取该对象属性时调用,返回的值为读取的值; set 函数会在设置新值时调用 传入的newValue为新值。
在Vue中 会用data一个对象包裹所有的值,因此可以用遍历的方法给每个属性加上该方法。
将该逻辑封装到一个函数中:

let data = {
    name: 'Ton',
    age: 19,
    salary: '10k'
}

Object.keys(data).forEach( key => { 
    observe(data, key, data[key])
})
//形成闭包 内部的变量不会消失
function observe(obj, key, value){
    Object.defineProperty(obj, key, {
        get(){
            return value
        },
        set(newValue){
            value = newValue
        }
    })
}

这样data中的所有变量都会被绑定,但如果嵌套对象或数组,内部的对象不会被检测到。(可以用vue提供的 $set和 $delate 处理或 使用内部重写的数组方法)

与标签联动

与标签联动的方法有许多,最简单的是使用id 来绑定,而Vue中是使用指令的方式。
过程主要分两步:

  1. 获取dom
  2. 将数据放上去
...
<div id="app">
    <div v-test="name" class="box"></div>
    <div v-test="age" class="box"></div>
</div>
...
function compile(){
    let app = document.getElementById('app')
    //获取所有的子节点 值为3的是text节点 1为子标签节点
    app.childNodes.forEach( node => {
        if(node.nodeType === 1){
            //遍历该节点的属性 找出 v-text 
            //node.attributes是个类数组对象, 先转化为数组
            Array.from(node.attributes).forEach( key => {
                //解构对象 nodeName 找到属性
                let { nodeName, nodeValue } = key 
                 //如果存在 则修改值(关键步骤)
                if(nodeName === 'v-test'){
                	node.innerText = data[nodeValue]
               	}
            })
        }
    })
}
compile()
...

此时可以获取节点,并赋值数据,与数据劫持联动,最终结果如下:

let data = { name: 'Ton', age: 19}
//劫持数据
Object.keys(data).forEach(key => {
    observe(data, key, data[key])
})
//形成闭包 内部的变量不会消失
function observe(obj, key, value) {
    Object.defineProperty(obj, key, {
        get() {
            return value 
        },
        set(newValue) {
            value = newValue
            compile()
        }
    })
}
//获取元素 将数据放入
function compile(){
    let app = document.getElementById('app')
    //获取所有的子节点 包括很多节点 3 为text 节点 1为子标签节点
    app.childNodes.forEach( node => {
        if(node.nodeType === 1){
            //遍历该节点的属性 找出 v-text 
            //node.attributes是个类数组对象, 先转化为数组
            let result = Array.from(node.attributes).filter( key => {
                //结构属性的 nodeName 找到属性
                let { nodeName } = key 
                return nodeName === 'v-test'
            })
            if(result){
                node.innerText = data[result[0].nodeValue]
            }
        }
    })

}
compile()

每次修改也会引起DOM元素的修改,实现响应式。

v-model的实现

同v-test(v-on) 的不同,v-model要实现双向绑定,即 input框输入的值也需要传回data。实现的逻辑前面是相同的,都需要获取元素,但需要新增将input输入框的内容,传回。

let data = { name: 'Ton', age: 19}
//劫持数据
Object.keys(data).forEach(key => {
    observe(data, key, data[key])
})
//形成闭包 内部的变量不会消失
function observe(obj, key, value) {
    Object.defineProperty(obj, key, {
        get() {
            return value 
        },
        set(newValue) {
            value = newValue
            compile()
        }
    })
}
//获取元素 将数据放入
function compile(){
    let app = document.getElementById('app')
    //获取所有的子节点 包括很多节点 3 为text 节点 1为子标签节点
    app.childNodes.forEach( node => {
        if(node.nodeType === 1){
            //遍历该节点的属性 找出 v-text 
            //node.attributes是个类数组对象, 先转化为数组
            let result = Array.from(node.attributes).filter( key => {
                //结构属性的 nodeName 找到属性
                let { nodeName } = key 
                return nodeName === 'v-model'
            })
            if(result){
                node.value = data[result[0].nodeValue]
                addEventLisener('input', e => {
						data[result[0].nodevalue] = e.target.value 
					}
                )
            }
        }
    })

}
compile()

但目前的方法并不完美,需要添加一个防抖函数

let data = { name: 'Ton', age: 19}
//劫持数据
Object.keys(data).forEach(key => {
    observe(data, key, data[key])
})
//形成闭包 内部的变量不会消失
function observe(obj, key, value) {
    Object.defineProperty(obj, key, {
        get() {
            return value 
        },
        set(newValue) {
            value = newValue
            compile()
        }
    })
}
//获取元素 将数据放入
function compile(){
    let app = document.getElementById('app')
    //获取所有的子节点 包括很多节点 3 为text 节点 1为子标签节点
    app.childNodes.forEach( node => {
        if(node.nodeType === 1){
            //遍历该节点的属性 找出 v-text 
            //node.attributes是个类数组对象, 先转化为数组
            let result = Array.from(node.attributes).filter( key => {
                //结构属性的 nodeName 找到属性
                let { nodeName } = key 
                return nodeName === 'v-model'
            })
            if(result){
                node.value = data[result[0].nodeValue]
                addEventLisener('input', debounce(handel, result[0].nodeValue)
            }
        }
    })
	function debounce(fn, key, timer = 1000){
		let t = null
		return function(){
			if(t) { clearTimeout(t) }
			t= setTimeOut( _ => {
				t = null
				fn.call(this, key, arguments)
			},timer)
		}
	}
	function handel(key, event){
		data[key] = event.target.value
	}
}
compile()
Logo

前往低代码交流专区

更多推荐