Advanced-Frontend / Daily-Interview-Question

我是依扬(木易杨),公众号「高级前端进阶」作者,每天搞定一道前端大厂面试题,祝大家天天进步,一年后会看到不一样的自己。
https://muyiy.cn/question/
27.34k stars 3.29k forks source link

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

Open zeroone001 opened 5 years ago

GuoYuFu123 commented 5 years ago

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

twosugar commented 5 years ago
<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 commented 5 years ago

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 commented 5 years ago
<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 commented 5 years ago
        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 commented 5 years ago

利用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 commented 5 years ago
<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 commented 4 years ago

daiyunchao commented 4 years ago
<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 commented 4 years ago

监听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 commented 4 years ago
<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 commented 4 years ago
  <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 commented 3 years ago
<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 commented 3 years ago
<!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 commented 3 years ago
    <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 commented 2 years ago

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);