自己写一个小小vue - getter与setter

文章在个人网站中发布,原文链接:自己写一个小小vue - getter与setter

我相信各位前端大佬和小佬们都听过这句话:

vue2使用getter与setter来控制数据,vue3则直接使用proxy。

肯定有很多人像我一样,只管爽用vue就行,我管你什么getter、proxy的。不过这个东西在实际开发中是非常实用的,可以实现一些非常优雅的封装。不过首先,让我们先了解一下这俩东西。

getter与setter

这俩东西就相当于一个监听器,当某个数据被修改或者被读取时,你设置的getter和setter就会运行。

例如我现在定义了一个box(盒子),里边的content(内容)是一个‘banana🍌’

let box = {
  content: 'banana🍌'
}

我们试试监听这个盒子

let box = {
  content: 'banana🍌',
  // 定义getter非常简单,就是一个函数,函数名就是需要被监听的变量
  get boxContent() {
    alert('有人查看了盒子里的东西,是个'+this.content)
  },
  // 定义setter也是一样
  set boxContent(newContent) {
    this.content = newContent
    alert('有人换掉了盒子里的东西,换成了'+newContent)
  }
}

这里有个需要注意的点就是getter和setter的名字不能和原变量相同,不过在firefox浏览器中却是可以这么写的,不过鉴于兼容性原因我们不建议这么写

这时候一旦有人尝试读取和修改boxContent,就会触发getter和setter。

//如果盒子里有东西
if(box.boxContent){
  //就把它换成苹果
  box.boxContent = 'apple🍎'
}

第一行的if读取了box.boxContent,触发getter,然后弹出提示。
第一行的if读取了box.boxContent,触发getter,然后弹出提示。
紧接我们修改了box.boxContent,触发setter,然后弹出提示。
紧接着第二行代码修改了box.boxContent,触发setter,然后弹出提示。

好了,你已经学会了getter和setter了,让我们来实现一个vue把。

实现一个小小vue

来个经典案例,小黑记事本:

样式布局我不多赘述,都挺简单的。具体代码可以上码上掘金或者来github查看

说是实现小小vue,实际上只是通过getter和setter来尝试实现v-model的功能,毕竟咱也不是尤雨溪,咱就来简单点的。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="style.css">
    <title>Demo</title>
  </head>
  <body>
    <div class="outter">
      <div class="title">Demo</div>
      <div class="list">
        <div class="item">
          <span>2333</span>
          <button class="delete">Delete</button>
        </div>
      </div>
      <div class="add">
        <!-- 我们设置一个bind属性,它就相当于v-model,绑定到input变量 -->
        <input bind="input"/>
        <button onclick="add()" class="add-btn">Add</button>
      </div>
      <!-- 这里也来一个bind,也绑定到input变量 -->
      <div>input的值:<span bind="input"></span></div>
    </div>
  </body>
  <script src="index.js"></script>
</html>

js部分如下,我们只关注前面部分

// 我们先定义好数据
let data = {
  list:[],
  input:''
}

// 实现绑定
// 此处获得所有拥有bind属性的元素
let binds = document.querySelectorAll('[bind]');
// 遍历这些元素
binds.forEach((item) => {
  // 绑定input事件
  item.oninput = (e) => {
    // 当input时更新其值
    data[item.getAttribute('bind')] = e.target.value
  }
})

// 实现数据监听
let myData = {}
// 因为getter和setter不能和原变量同名,咱把原变量包裹起来一下
myData.data = data
// 我们遍历data里的每个对象
for(let item in data){
  //为每个item都定义一个getter和setter
  Object.defineProperty(myData, item, {
    // 这里通过defineProperty来设置getter和setter
    get(){
      return myData.data[item]
    },
    set(val){
      myData.data[item] = val
      // 当值更新时,我们遍历所有有bind属性的元素
      binds.forEach((bindsItem) => {
        // 如果这个元素有bind属性,并且属性名等于item,就修改它的值
        if(bindsItem.getAttribute('bind') === item){
          bindsItem.value = val
          bindsItem.innerHTML = val
        }
      })
      // 当 list 更新时,重新渲染列表
      if(item === 'list') {
        renderList()
      }
    }
  })
}
// 把myData赋值给data,这样我们的data就变成一个代理对象了
data = myData

//之后的代码感兴趣的可以看看

// 渲染列表函数
function renderList() {
  const listContainer = document.querySelector('.list')
  listContainer.innerHTML = data.list.map((item, index) => `
    <div class="item">
      <span>${item}</span>
      <button class="delete" onclick="deleteItem(${index})">Delete</button>
    </div>
  `).join('')
}

// 添加项目函数
function add() {
  if(data.input.trim()) {
    data.list = [...data.list, data.input]
    data.input = '' // 清空输入框
  }
}

// 删除项目函数
function deleteItem(index) {
  data.list = data.list.filter((_, i) => i !== index)
}

看看效果

这样我们就用原生js实现了一个v-modle,是不是成就感满满呢
最终效果

相关知识

getter

setter

defineProperty

posted @ 2025-06-15 13:45  粥粥粥菌  阅读(13)  评论(0)    收藏  举报