Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 74 题: 使用 JavaScript Proxy 实现简单的数据绑定 #123

Open
zeroone001 opened this issue May 15, 2019 · 16 comments
Open

第 74 题: 使用 JavaScript Proxy 实现简单的数据绑定 #123

zeroone001 opened this issue May 15, 2019 · 16 comments

Comments

@zeroone001
Copy link

No description provided.

@GuoYuFu123
Copy link

这里,欢迎star
https://github.com/GuoYuFu123/test-project/blob/master/proxy/proxyvue.html

@twosugar
Copy link

<body>
  hello,world
  <input type="text" id="model">
  <p id="word"></p>
</body>
<script>
  const model = document.getElementById("model")
  const word = document.getElementById("word")
  var obj= {};

  const newObj = new Proxy(obj, {
      get: function(target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
      },
      set: function(target, key, value, receiver) {
        console.log('setting',target, key, value, receiver);
        if (key === "text") {
          model.value = value;
          word.innerHTML = value;
        }
        return Reflect.set(target, key, value, receiver);
      }
    });

  model.addEventListener("keyup",function(e){
    newObj.text = e.target.value
  })
</script>

@kangkai124
Copy link

Proxy实现一个简单的双向绑定的 todo list

<div id="app">
    <input type="text" id="input">
    <div>
      TODO:
      <span id="text"></span>
    </div>
    <div id="btn">Add To Todo List</div>
    <ul id="list"></ul>
  </div>
const input = document.getElementById('input')
    const text = document.getElementById('text')
    const list = document.getElementById('list')
    const btn = document.getElementById('btn')

    let render

    const inputObj = new Proxy({}, {
      get (target, key, receiver) {
        return Reflect.get(target, key, receiver)
      },
      set (target, key, value, receiver) {
        if (key === 'text') {
          input.value = value
          text.innerHTML = value
        }
        return Reflect.set(target, key, value, receiver)
      }
    })

    class Render {
      constructor (arr) {
        this.arr = arr
      }
      init () {
        const fragment = document.createDocumentFragment()
        for (let i = 0; i < this.arr.length; i++) {
          const li = document.createElement('li')
          li.textContent = this.arr[i]
          fragment.appendChild(li)
        }
        list.appendChild(fragment)
      }
      addList (val) {
        const li = document.createElement('li')
        li.textContent = val
        list.appendChild(li)
      }
    }

    const todoList = new Proxy([], {
      get (target, key, receiver) {
        return Reflect.get(target, key, receiver)
      },
      set (target, key, value, receiver) {
        if (key !== 'length') {
          render.addList(value)
        }
        return Reflect.set(target, key, value, receiver)
      }
    })

    window.onload = () => {
      render = new Render([])
      render.init()
    }

    input.addEventListener('keyup', e => {
      inputObj.text = e.target.value
    })

    btn.addEventListener('click', () => {
      todoList.push(inputObj.text)
      inputObj.text = ''
    })

@wingmeng
Copy link

<b id="count"></b>
<button onclick="increase()">+</button>
<button onclick="decrease()">-</button>
const data = { count: 0 };
const proxy = new Proxy(data, {
  get(target, property) {
    return target[property];
  },
  set(target, property, value) {
    target[property] = value;
    render(value);
  }
});

render(proxy.count);

function render(value) {
  document.getElementById('count').innerHTML = value;
}

function increase() {
  proxy.count += 1;
}

function decrease() {
  proxy.count -= 1; 
}

@Jesse121
Copy link

        let person = {
            name:'jesse',
            age:25
        }
        let proxy = new Proxy(person,{
            get(target,prop){
                console.log('get')
                return target[prop]
            },
            set(obj,prop,value){
                if(value>=30){
                    throw new Error('invalid')
                }
                obj[prop] = value
            }
        })
        console.log(proxy.name) //get jesse
        proxy.age = 30   //Uncaught Error: invalid

@CHristopherkeith
Copy link

利用Proxy实现一个简化版的MVVM
参照vue的响应式设计模式,将数据劫持部分的Obejct.defineProperty替换为Proxy即可,其他部分,如compile(编译器没有实现,用写好的html模拟已完成编译),watcher,dep,事件监听等基本保持不变,简单实现代码如下:

<!-- html部分 -->
<div id="foo"></div>
<input type="text" name="" id="bar"/>
// js部分
class Watcher{
	constructor(cb){
		this.cb = cb;
	}
	update(){
		this.cb()
	}
}
class Dep{
	constructor(){
		this.subs = [];
	}
	publish(){
		this.subs.forEach((item)=>{
			item.update && item.update();
		})
	}
}
class MVVM{
	constructor(data){
		let that = this;
		this.dep = new Dep();
		this.data = new Proxy(data,{
			get(obj, key, prox){
				that.dep.target && that.dep.subs.push(that.dep.target);
				return obj[key]
			},
			set(obj, key, value, prox){
				obj[key] = value;
				that.dep.publish();
				return true;
			}
		})
		this.compile();
	}
	compile(){
		
		let divWatcher = new Watcher(()=>{
			this.compileUtils().div();
		})
		this.dep.target = divWatcher;
		this.compileUtils().div();
		this.dep.target = null;
		
		let inputWatcher = new Watcher(()=>{
			this.compileUtils().input();
		})
		this.dep.target = inputWatcher;
		this.compileUtils().input();
		this.compileUtils().addListener();
		this.dep.target = null;
	}
	compileUtils(){
		let that = this;
		return {
			div(){
				document.getElementById('foo').innerHTML = that.data.foo;
			},
			input(){
				document.getElementById('bar').value = that.data.bar;
			},
			addListener(){
				document.getElementById('bar').addEventListener('input', function(){
					that.data.bar = this.value;
				})
			}
		}
	}
}
let mvvm = new MVVM({foo: 'foo233', bar: 'bar233'})

通过mvvm.data.foo或者mvvm.data.bar可以操作数据,可以观察到view做出了改变;在输入框改变输入值,也可以通过mvvm.data观察到数据被触发改变

@zwmmm
Copy link

zwmmm commented Jul 24, 2019

<b id="count"></b>
<button onclick="increase()">+</button>
<button onclick="decrease()">-</button>
const data = { count: 0 };
const proxy = new Proxy(data, {
  get(target, property) {
    return target[property];
  },
  set(target, property, value) {
    target[property] = value;
    render(value);
  }
});

render(proxy.count);

function render(value) {
  document.getElementById('count').innerHTML = value;
}

function increase() {
  proxy.count += 1;
}

function decrease() {
  proxy.count -= 1; 
}

set 方法必须返回 true 或者 false 你这样写是有问题的 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/set

@doctoray
Copy link

doctoray commented Jul 30, 2019

<input type="text" id="input" >
<p id="p"></p>
<script>
  const input = document.getElementById('input');
  const p = document.getElementById('p');
  const obj = {};
  const newObj = new Proxy(obj, {
    get: function(target, key, receiver) {
      console.log(`getting ${key}!`);
      return Reflect.get(target, key, receiver);
    },
    set: function(target, key, value, receiver) {
      console.log(target, key, value, receiver);
      if (key === 'text') {
        input.value = value;
        p.innerHTML = value;
      }
      return Reflect.set(target, key, value, receiver);
    },
  });

  input.addEventListener('keyup', function(e) {
    newObj.text = e.target.value;
  });
</script>

@daiyunchao
Copy link

<body>
  His Name:<span id="username"></span>
</body>
<script>
//定义需要监控的_data对象
  var _data = { name: "zhangsan", age: 18 };

//监控_data
  var data = new Proxy(_data, {
    set(obj, key, value) {
      obj[key] = value;
     //当有值的时候刷新显示区域
      render();
    },
    get(obj, key) {
      return obj[key];
    }
  })

  function render() {
    document.getElementById("username").innerHTML = data.name;
  }
  render();
</script>

可打开f12修改 data.name 查看改变情况

@aeolusheath
Copy link

监听dom的value变化 去更新 obj
obj的数据发生变化 去更新 dom


普通简易版本:

    名字:<input type="text" id="name"><br/>
    你的名字: <p id="pName"></p>


    <script type="text/javascript">
      var obj = {
        name: ''
      }
      Object.defineProperty(obj, 'name', {
        set: function(value) {
          document.getElementById('name').value = value
          document.getElementById('pName').innerHTML = value
        }
      })
      document.getElementById('name').addEventListener('input', function(e){
        obj.name = e.target.value
      })

    </script>

proxy版本 好像没啥特殊的:

    名字:<input type="text" id="name"><br/>
    你的名字: <p id="pName"></p>


    
    <script type="text/javascript">
      // Proxy
      var obj = {
        name: ''
      }
      var proxyObj = new Proxy(obj, {
        get: function(target, key, receiver) {
          return Reflect.get(target, key, receiver)
        },
        set: function(target, key, value, receiver) {
          if (key === 'name') {
            document.getElementById('name').value = value
            document.getElementById('pName').innerHTML = value
          }
          return Reflect.set(target, key, value, receiver)
        }
      })

      document.getElementById('name').addEventListener('input', function(e){
        proxyObj.name = e.target.value
      })
    </script>

@w3cTim
Copy link

w3cTim commented Dec 30, 2019

<body>
  <div id="root">
    <input type="text" v-model="title">
    <input type="text" v-model="title">
    <div v-bind="title"> </div>
  </div>

  <script>
    'user strict'
    function View() {
      // 设置代理拦截
      let proxy = new Proxy({}, {
        get(obj, property) { },
        set(obj, property, value) {
          document.querySelectorAll(`[v-model='${property}'],[v-bind='${property}']`)
            .forEach(el => el.innerHTML = el.value = value)
        }
      })

      // 初始化 绑定元素
      this.run = function () {
        const elems = document.querySelectorAll("[v-model]");
        elems.forEach(el => {
          el.addEventListener('keyup', event => {
            proxy[event.target.getAttribute('v-model')] = event.target.value;
          })
        })
      }

    }

    let view = new View();
    view.run();
  </script>

</body>

@litokele2018
Copy link

  <input id="inp" type="text" oninput="handleChange()">
  <div id="app"></div>
  <script>

    let inp = document.getElementById('inp')
    let app = document.getElementById('app')
    
    let obj = {
      defaultValue: 'hello world'
    }
    let proxy = new Proxy(obj, {
      get: function(obj, key) {
        console.log('get')
        return obj[key]
      },
      set(obj, key, value) {
        obj.defaultValue = value
        notify()
      }
    })

    app.innerHTML = proxy.defaultValue
    inp.value = proxy.defaultValue
    
    function notify() { 
      app.innerHTML = proxy.defaultValue
    }

    function handleChange() {
      proxy.defaultValue = inp.value
    }
  </script>

@Liu-10004
Copy link

<body>
  hello,world
  <input type="text" id="model">
  <p id="word"></p>
</body>
<script>
  const model = document.getElementById("model")
  const word = document.getElementById("word")
  var obj= {};

  const newObj = new Proxy(obj, {
      get: function(target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
      },
      set: function(target, key, value, receiver) {
        console.log('setting',target, key, value, receiver);
        if (key === "text") {
          model.value = value;
          word.innerHTML = value;
        }
        return Reflect.set(target, key, value, receiver);
      }
    });

  model.addEventListener("keyup",function(e){
    newObj.text = e.target.value
  })
</script>

model.value = value; 这一行代码是不是不需要写

@blank1u
Copy link

blank1u commented Dec 9, 2020

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <span></span>
    <br>
    <input type="text" oninput="input(event)">
    <script>
        let spanEl = document.querySelector('span')
        let inputEl = document.querySelector('input')
        let data = {
            val: null
        }
        let proxy = new Proxy(data, {
            get(target, p, receiver) {
                return Reflect.get(target, p, receiver)
            },
            set(target, p, val, receiver) {
                spanEl.innerText = val
                inputEl.value = val
                return Reflect.set(target, p, val, receiver)
            }
        })

        function input(e) {
            proxy.val = e.target.value
        }

    </script>
</body>

</html>

@Brother-Yang
Copy link

Brother-Yang commented Dec 18, 2020

    <input type="text" id="input">
    <p id="text">hello world</p>

    <script>
        let input = document.getElementById('input');
        let text = document.getElementById('text');

        let obj = {};

        let proxy = new Proxy(obj, { 
            get(target, property){
                return Reflect.get(...arguments);
            },
            
            set(target, property, value){ 
                target[property] = value; 
                text.innerText = target[property]; 
                return Reflect.set(...arguments); 
            }
            
        })

        proxy.val = text.innerText;
        input.value = proxy.val;

        input.addEventListener('input', function(){
            proxy.val = this.value;
        })
    </script>

@ShopkeeperEgg
Copy link

let activeEffect = null;
let wm = new WeakMap();

function watchEffect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key);
      return Reflect.get(target, key, receiver)
    },
    set(target, key, val, receiver) {
      Reflect.set(target, key, val, receiver);
      trigger(target, key);
    }
  })
}

function track(target, key) {
  if (activeEffect) {
    let map = wm.get(target);
    if (!map) {
      map = new Map();
      wm.set(target, map);
    }
    let bucket = map.get(key);
    if (!bucket) {
      bucket = [];
      map.set(key, bucket);
    }
    bucket.push(activeEffect);
  }
}

function trigger(target, key) {
  let map = wm.get(target);
  if (map) {
    let bucket = map.get(key);
    if (bucket) {
      bucket.forEach(item => item());
    }
  }
}

const t1 = reactive({a: 1, b: 2});
watchEffect(() => {
  console.log(t1.a);
})
setTimeout(() => {
  t1.a = 222;
}, 3000);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests