defineProperty 및 Proxy에 대한 자세한 설명 (양방향 데이터 바인딩의 간단한 구현)

머리말

"데이터 바인딩"의 핵심은 데이터 변경 사항을 모니터링하는 것입니다. Vue 데이터 양방향 바인딩은 게시자-구독자 모델과 결합 된 데이터 하이재킹을 통해 이루어집니다. 실제로 ES5의 Object.defineProperty 메서드는 주로 개체의 속성을 추가하거나 수정하여 뷰를 업데이트하는 작업을 가로채는 데 사용됩니다.

vue3.0이 Object.defineProperty () 메서드를 프록시로 대체한다고 들었습니다. 따라서 몇 가지 사용법을 미리 이해해야합니다. 프록시는 개체의 속성이 아닌 전체 개체를 직접 가로 챌 수 있으며이를 가로채는 방법은 여러 가지가 있습니다. 그리고 결국 하이재킹 후 새 개체로 돌아갑니다. 따라서 상대적으로 말하면이 방법은 사용하기 매우 쉽습니다. 그러나 호환성은 그리 좋지 않습니다.

一 、 defineProperty

ES5는 객체에 대한 새 속성을 정의하거나 객체의 기존 속성을 수정하고 객체를 반환 할 수있는 Object.defineProperty 메서드를 제공합니다.

[1] 문법

Object.defineProperty(obj, prop, descriptor)

매개 변수 :

obj : 필수, 대상 개체

prop : 필수, 정의하거나 수정할 속성의 이름

설명자 : 필수, 정의하거나 수정할 속성의 설명자

반환 값 :

함수에 전달 된 객체, 즉 첫 번째 매개 변수 obj

[2] 설명자 매개 변수 분석

함수의 세 번째 매개 변수 설명자가 나타내는 속성 설명자는 데이터 설명자와 액세스 설명 자의 두 가지 형식을 갖습니다.

데이터 설명 : 개체의 속성을 수정하거나 정의 할 때 속성에 몇 가지 특성을 추가하면 데이터 설명의 속성은 선택 사항입니다.

  • value : 모든 유형의 값이 될 수있는 속성에 해당하는 값, 기본값은 정의되지 않음
  • 쓰기 가능 : 속성 값을 다시 쓸 수 있는지 여부입니다. true로 설정하면 무시할 수 있습니다. false로 설정하면 무시할 수 없습니다. 기본값은 거짓입니다.
  • enumerable : 이 속성을 열거 할 수 있는지 여부 (for ... in 또는 Object.keys () 사용). 열거하려면 true로 설정하고 열거하지 않으려면 false로 설정합니다. 기본값은 거짓입니다.
  • configurable : 대상 속성을 삭제할 수 있는지 또는 속성 (쓰기 가능, 구성 가능, 열거 가능)을 다시 수정할 수 있는지 여부입니다. true로 설정하면 기능을 삭제하거나 재설정 할 수 있으며 false로 설정하면 기능을 삭제하거나 재설정 할 수 없습니다. 기본값은 거짓입니다. 이 속성은 두 가지 역할을합니다. 1. 삭제를 사용하여 대상 속성을 삭제할 수 있는지 여부 2. 대상 속성을 다시 설정할 수 있는지 여부

액세스 설명 : 접근자를 사용하여 특성의 속성을 설명하는 경우 다음 속성을 설정할 수 있습니다.

  • get : 속성의 getter 함수로, getter가 없으면 정의되지 않습니다. 이 속성에 액세스하면이 함수가 호출됩니다. 실행 중에 매개 변수가 전달되지 않지만이 객체는 전달됩니다 (상속성 때문에 여기에서 속성을 정의하는 객체 일 필요는 없습니다). 이 함수의 반환 값은 속성 값으로 사용됩니다. 기본값은 정의되지 않습니다.
  • set : 속성의 setter 함수로 setter가 없으면 정의되지 않습니다. 속성 값이 수정되면이 함수가 호출됩니다. 이 메서드는 매개 변수 (즉, 할당 할 새 값)를 받아들이고 할당시 this 개체를 전달합니다. 기본값은 정의되지 않습니다.

[3] 예

  let obj = {}
  // 不设置value属性
  Object.defineProperty(obj, "name", {});
  console.log(obj.name); // undefined

  // 设置value属性
  Object.defineProperty(obj, "name", {
    value: "Demi"
  });
  console.log(obj.name); // Demi
  • 쓰기 가능
  let obj = {}
  // writable设置为false,不能重写
  Object.defineProperty(obj, "name", {
    value: "Demi",
    writable: false
  });
  //更改name的值(更改失败)
  obj.name = "张三";
  console.log(obj.name); // Demi 

  // writable设置为true,可以重写
  Object.defineProperty(obj, "name", {
    value: "Demi",
    writable: true
  });
  //更改name的值
  obj.name = "张三";
  console.log(obj.name); // 张三 
  • 열거 할 수있는
  let obj = {}
  // enumerable设置为false,不能被枚举。
  Object.defineProperty(obj, "name", {
    value: "Demi",
    writable: false,
    enumerable: false
  });

  // 枚举对象的属性
  for (let attr in obj) {
    console.log(attr);
  }

  // enumerable设置为true,可以被枚举。
  Object.defineProperty(obj, "age", {
    value: 18,
    writable: false,
    enumerable: true
  });

  // 枚举对象的属性
  for (let attr in obj) {
    console.log(attr); //age
  }
  • 구성 가능 
  //-----------------测试目标属性是否能被删除------------------------//
  let obj = {}
  // configurable设置为false,不能被删除。
  Object.defineProperty(obj, "name", {
    value: "Demi",
    writable: false,
    enumerable: false,
    configurable: false
  });
  // 删除属性
  delete obj.name;
  console.log(obj.name); // Demi

  // configurable设置为true,可以被删除。
  Object.defineProperty(obj, "age", {
    value: 19,
    writable: false,
    enumerable: false,
    configurable: true
  });
  // 删除属性
  delete obj.age;
  console.log(obj.age); // undefined


  //-----------------测试是否可以再次修改特性------------------------//
  let obj2 = {}
  // configurable设置为false,不能再次修改特性。
  Object.defineProperty(obj2, "name", {
    value: "dingFY",
    writable: false,
    enumerable: false,
    configurable: false
  });

  //重新修改特性
  Object.defineProperty(obj2, "name", {
      value: "张三",
      writable: true,
      enumerable: true,
      configurable: true
  });
  console.log(obj2.name); // 报错:Uncaught TypeError: Cannot redefine property: name

  // configurable设置为true,可以再次修改特性。
  Object.defineProperty(obj2, "age", {
    value: 18,
    writable: false,
    enumerable: false,
    configurable: true
  });

  // 重新修改特性
  Object.defineProperty(obj2, "age", {
    value: 20,
    writable: true,
    enumerable: true,
    configurable: true
  });
  console.log(obj2.age); // 20
  • 설정하고 얻을
  let obj = {
    name: 'Demi'
  };
  Object.defineProperty(obj, "name", {
    get: function () {
      //当获取值的时候触发的函数
      console.log('get...')
    },
    set: function (newValue) {
      //当设置值的时候触发的函数,设置的新值通过参数value拿到
      console.log('set...', newValue)
    }
  });

  //获取值
  obj.name // get...

  //设置值
  obj.name = '张三'; // set... 张三

두, 프록시 

Proxy 객체는 기본 작업 (속성 조회, 할당, 열거, 함수 호출 등)의 사용자 정의 동작을 정의하는 데 사용됩니다.
실제로 대상 객체의 작업 전에 가로 채기를 제공하며이를 필터링하고 다시 작성할 수 있습니다. 외부 작업, 특정 동작 수정 이러한 작업의 기본 동작은 객체 자체를 직접 조작 할 수없고 원하는 목적을 달성하기 위해 조작 된 객체의 프록시 객체를 통해 간접적으로 객체를 조작합니다 ~

[1] 문법

const p = new Proxy(target, handler)

【2】 모수

target : 프록시에 의해 래핑 될 대상 개체 (기본 배열, 함수 또는 다른 프록시를 포함한 모든 유형의 개체 일 수 있음)

handler : 또한 객체이며 해당 속성은 작업이 수행 될 때 에이전트의 동작, 즉 사용자 지정 동작을 정의하는 함수입니다.

[3] 핸들러 방식

핸들러 개체는 특정 속성의 일괄 처리를 보유하는 자리 표시 자 개체입니다. Proxy의 다양한 트랩이 포함되어 있으며 모든 트랩은 선택 사항입니다. 캐처가 정의되지 않은 경우 소스 개체의 기본 동작이 유지됩니다.

handler.getPrototypeOf ()  Object.getPrototypeOf 메소드의 캐처입니다.
handler.setPrototypeOf ()     Object.setPrototypeOf 메서드의 포수입니다.
handler.isExtensible () Object.isExtensible 메서드의 캐처입니다.
handler.preventExtensions ()  Object.preventExtensions 메서드의 캐처입니다.

handler.getOwnPropertyDescriptor ()  

Object.getOwnPropertyDescriptor 메소드의 캐처입니다.
handler.defineProperty ()     Object.defineProperty 메서드의 캐처입니다.
handler.has ()   in 연산자의 포수입니다.
handler.get ()    속성 읽기 작업의 캐처입니다.
handler.set ()  속성 설정 작업을위한 캐처입니다.
handler.deleteProperty () 삭제 연산자의 캐처입니다.
handler.ownKeys ()  Object.getOwnPropertyNames 메서드 및 Object.getOwnPropertySymbols 메서드의 캐처입니다.
handler.apply ()  함수 호출 작업을위한 캐처입니다.
handler.construct ()  새로운 대원을위한 포수.

[4] 예

  let obj = {
    name: 'name',
    age: 18
  }

  let p = new Proxy(obj, {
    get: function (target, property, receiver) {
      console.log('get...')
    },
    set: function (target, property, value, receiver) {
      console.log('set...', value)
    }
  })


  p.name // get...
  p = {
    name: 'dingFY',
    age: 20
  }
  // p.name = '张三' // set... 张三

  

셋, defineProperty 및 프록시 비교

  1. Object.defineProperty는 개체의 속성 만 하이재킹 할 수 있으며 Proxy는 직접 프록시 개체입니다.
    Object.defineProperty는 속성 만 하이 잭 할 수 있으므로 객체의 각 속성을 트래버스해야하며 속성 값도 객체 인 경우 심층 탐색이 필요합니다. 프록시는 객체를 직접 프록시하므로 순회 작업이 필요하지 않습니다.

  2. Object.defineProperty에는 새 속성에 대한 수동 관찰이 필요합니다.
    Object.defineProperty는 개체의 속성을 가로 채기 때문에 속성을 추가 할 때 개체를 다시 탐색 한 다음 (속성을 변경해도 setter가 자동으로 트리거되지 않음) Object.defineProperty를 사용하여 새 속성을 가로 채야합니다.
    vue를 사용하여 데이터의 배열 또는 개체에 속성을 추가 할 때 vm. $ set을 사용하여 추가 된 속성도 응답하는지 확인해야하기 때문입니다.

  3. defineProperty는 원래 객체를 오염시킵니다. (주요 차이점)
    프록시 프록시는 ob로, 원래 객체 ob를 변경하지 않고 새 프록시 객체를 반환합니다. 반면 defineproperty는 메타 객체를 수정하고 메타 객체의 속성을 수정하는 것입니다. 오브젝트, 프록시는 메타 오브젝트 프록시 전용이며 새 프록시 오브젝트를 제공합니다.

넷, 단순히 양방향 데이터 바인딩을 실현

[1] 새 myVue.js 파일을 만들고 myVue 클래스를 만듭니다.

class myVue extends EventTarget {
  constructor(options) {
    super();
    this.$options = options;
    this.compile();
    this.observe(this.$options.data);
  }

  // 数据劫持
  observe(data) {
    let keys = Object.keys(data);
    // 遍历循环data数据,给每个属性增加数据劫持
    keys.forEach(key => {
      this.defineReact(data, key, data[key]);
    })
  }

  // 利用defineProperty 进行数据劫持
  defineReact(data, key, value) {
    let _this = this;
    Object.defineProperty(data, key, {
      configurable: true,
      enumerable: true,
      get() {
        return value;
      },
      set(newValue) {
        // 监听到数据变化, 触发事件
        let event = new CustomEvent(key, {
          detail: newValue
        });
        _this.dispatchEvent(event);
        value = newValue;
      }
    });
  }

  // 获取元素节点,渲染视图
  compile() {
    let el = document.querySelector(this.$options.el);
    this.compileNode(el);
  }
  // 渲染视图
  compileNode(el) {
    let childNodes = el.childNodes;
    // 遍历循环所有元素节点
    childNodes.forEach(node => {
      if (node.nodeType === 1) {
        // 如果是标签 需要跟进元素attribute 属性区分v-html 和 v-model
        let attrs = node.attributes;
        [...attrs].forEach(attr => {
          let attrName = attr.name;
          let attrValue = attr.value;
          if (attrName.indexOf("v-") === 0) {
            attrName = attrName.substr(2);
            // 如果是 html 直接替换为将节点的innerHTML替换成data数据
            if (attrName === "html") {
              node.innerHTML = this.$options.data[attrValue];
            } else if (attrName === "model") {
              // 如果是 model 需要将input的value值替换成data数据
              node.value = this.$options.data[attrValue];

              // 监听input数据变化,改变data值
              node.addEventListener("input", e => {
                this.$options.data[attrValue] = e.target.value;
              })
            }
          }
        })
        if (node.childNodes.length > 0) {
          this.compileNode(node);
        }
      } else if (node.nodeType === 3) {
        // 如果是文本节点, 直接利用正则匹配到文本节点的内容,替换成data的内容
        let reg = /\{\{\s*(\S+)\s*\}\}/g;
        let textContent = node.textContent;
        if (reg.test(textContent)) {
          let $1 = RegExp.$1;
          node.textContent = node.textContent.replace(reg, this.$options.data[$1]);
          // 监听数据变化,重新渲染视图
          this.addEventListener($1, e => {
            let oldValue = this.$options.data[$1];
            let reg = new RegExp(oldValue);
            node.textContent = node.textContent.replace(reg, e.detail);
          })
        }
      }
    })
  }
}

[2] html 파일에 myVue.js를 도입하고 예제 생성

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script src="./mvvm.js" type="text/javascript"></script>
  <title>Document</title>
</head>

<body>
  <div id="app">
    <div>我的名字叫:{
   
   {name}}</div>
    <div v-html="htmlData"></div>
    <input v-model="modelData" /> {
   
   {modelData}}
  </div>

</body>
<script>
  let vm = new myVue({
    el: "#app",
    data: {
      name: "Demi",
      htmlData: "html数据",
      modelData: "input的数据"
    }
  })
</script>

</html>

 【3】 효과

기사는 매주 지속적으로 업데이트되며 WeChat에서 " Front-end Collection  "을 검색  하여 처음으로 읽고, [ 비디오 ] [ 도서 ]에 답장하여 200G 동영상 자료와 30 개의 PDF 도서 자료를받을 수 있습니다.

추천

출처blog.csdn.net/qq_38128179/article/details/111416502