Принцип и реализация двусторонней привязки данных VUE

Vue.js

Что касается стека технологий Vue.js, наша первая мысль может заключаться в том, что он прост в использовании и удобен для новичков. Действительно, когда я впервые начал, я чувствовал, что это относительно легко, и в процессе его использования я также почувствовал его силу.

Недавно я готовился к интервью, и недостаточно знать только использование Vue.js, поэтому я начал анализировать исходный код Vue.js. Принцип и реализация объясняются шаг за шагом ниже.

сначала исходный кодgithubадрес.

1. Понимание Vue.js

Официальное введение: Vue.js — это прогрессивный фреймворк для создания пользовательских интерфейсов.Так как же понимать «прогрессивный»?

Основная функция Vue — механизм шаблонов представлений, но это не означает, что Vue не может быть фреймворком. Как показано на рисунке ниже, сюда включены все компоненты Vue На основе декларативного рендеринга (механизм шаблонов представлений) мы можем построить полную структуру, добавив системы компонентов, маршрутизацию на стороне клиента и крупномасштабное управление состоянием. . Что еще более важно, эти функции независимы друг от друга, и вы можете произвольно выбирать другие компоненты на основе основных функций, не обязательно все они интегрированы вместе. можно увидеть,Так называемый «прогрессивный» — это то, как используется Vue, и он также отражает концепцию дизайна Vue.

Смысл прогрессивного представительства таков: не делайте больше, чем ответственность.

Vue.js предоставляет только ядро ​​экосистемы vue-cli.система компонентова такжеДвусторонняя привязка данных (также называемая управляемой данными).

Во-вторых, принцип и реализация двусторонней привязки данных

Уведомление: Поскольку в реализации этой функции используется синтаксис ES6 и некоторые знания, которые обычно не используются, третья часть в основном объясняет используемые точки знаний.Вы можете сначала понять базовые знания, а затем посмотреть на реализацию этой части.

Мы все знаем, что два ядра Vue.js — этосистема компонентова такжеУправляемый данными (двусторонняя привязка данных), так что далее мы будемдвусторонняя привязка данныхОбъясни и реализуй.

Как показано на рисунке выше: общая реализация разделена на четыре этапа:

  1. Реализуйте компиляцию, проанализируйте инструкцию, инициализируйте представление, подпишитесь на изменения данных и привяжите функцию обновления.
  2. Реализовать Observer, перехватить данные и уведомить об изменениях данных
  3. Реализуйте Watcher в качестве промежуточной точки между двумя вышеупомянутыми, позвольте Dep добавить текущий Watcher, принимая изменения данных, и своевременно уведомите представление об обновлении.
  4. Реализовать MVVM, интегрировать три вышеуказанных функции в качестве входной функции.

Для лучшего понимания процесса внедрения размещена более подробная блок-схема:

Следующее объяснение в основном разделено на три части:Компиляция шаблона (компилятор),Перехват данных (наблюдатель),Наблюдатель.

Мы все знаем, что при использовании Vue.js нам всем нужноnew Vue({}), Vue в настоящее время — это класс, а содержимое, переданное в больших (фигурных) скобках, — это свойства и методы Vue. Для реализации двусторонней привязки данных требуются самые основные элементы:elа такжеdata, С компилируемыми шаблонами и данными мы можем выполнить следующую компиляцию шаблона и захват данных и, наконец, отслеживать изменения в данных в режиме реального времени с помощью наблюдателя для постоянного обновления представления. такVueРоль класса можно понимать как мост, соединяющий компиляцию шаблона и перехват данных.

Поскольку следующий код и содержание пояснений являются примерами функций, реализованных автором, класс Vue был переименован в MVVM, а базовые функции остались прежними (следующий код неполный, основная цель — объяснить основной процесс и основные функции письма).

1. Компиляция шаблона (Компилятор)

class Compile {
    //vm-->MVVM中传入的第二个参数就是MVVM的实例,即new MVVM()
    constructor(el, vm) {
        //传入的可能是 #app或者document.getElementById('app'),所以需要进行判断
        this.el = this.isElementNode(el) ? el : document.querySelector(el); 
        this.vm = vm;
        //防止用户输入的既不是“#el”字符串也不是document节点
        if (this.el) {
            //如果这个元素能够获取到,我们才开始编译
            
            //1.先把真实的DOM移入到内存中(优化性能) -->使用节点碎片 fragment
            let fragment = this.nodeToFragment(this.el);
            
            //2.编译=>提取想要的元素节点(v-model)和文本节点{{}}
            this.compile(fragment)
            
            //3.把编译好的fragment在放回到页面中
            this.el.appendChild(fragment)

        }
    }

После определения того, что у вас есть компилируемый шаблон, следующие три шага:

1.1 Переместите настоящий DOM в память

Непосредственная работа с DOM-узлами очень требовательна к производительности, не говоря уже о том, что для реальной страницы или проекта на уровне DOM будет много узлов и вложенных узлов, поэтому, если мы напрямую работаем с DOM, вполне возможно, что производительность упадет. становятся очень высокими. Здесь мы будем использоватьfragmentФрагментация узлов, чтобы уменьшить проблемы с производительностью, вызванные прямыми массовыми манипуляциями с DOM. (Этот процесс можно просто понимать как перемещение всех узлов DOM в память и выполнение ряда операций над узлами DOM в памяти, что улучшит производительность)

  nodeToFragment(el) { //需要将el中的内容全部放入到内存中
        //文档碎片,不是真正的DOM,是内存中的节点
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            //将el中的真实节点一个一个的移入到文档碎片中(el.firstChild指文档中的第一个节点,这一个节点里面可能嵌套很多个节点,但是都没关系,都会一次取走)
            fragment.appendChild(firstChild);
        }
        return fragment; // 内存中的节点
    }

Приведенный выше фрагмент кода представляет собой процесс перемещения узлов DOM в память.После выполнения приведенного выше фрагмента кода откройте консоль браузера, и вы обнаружите, что предыдущие узлы были отправлены (сохранены в памяти).

1.2 Компилировать => (извлечь узлы элементов и текстовые узлы, которые необходимо скомпилировать)

    compile(fragment) {
        //需要递归
        let childNodes = fragment.childNodes; //只拿到第一层(父级),拿不到嵌套层的
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                //这里的需要编译元素
                this.compileElement(node);
                //是元素节点,还需要继续深入的检查(如果是元素节点,有可能节点里面会嵌套节点,所以要使用递归)
                this.compile(node) //因为外层是箭头函数,所以this始终指向Compile实例
            } else {
                //是文本节点
                //这里需要编译文本
                this.compileText(node)
            }
        })

    }
    compileElement(node) {
        //编译带v-model、v-text等的(取节点的属性)
        let attrs = node.attributes; //取出当前节点的属性
        Array.from(attrs).forEach(attr => {
            //判断属性名字是不是包含v-
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                //取到对应的值(即从data中取到message(示例)),放到节点中
                let expr = attr.value;
                let [, type] = attrName.split('-') //解构赋值
                //node  this.vm.$data expr  //这里可能有v-model或v-text  还有可能有v-html(这里只处理前两种)
                CompileUtil[type](node, this.vm, expr)
            }
        })
    }
    compileText(node) {
        //编译带{{}}
        let expr = node.textContent; //取文本中的内容
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(expr)) {
            //node this.vm.$data expr
            CompileUtil['text'](node, this.vm, expr)
        }
    }

Конечным результатом трех вышеуказанных функций является получение узла элемента, который необходимо скомпилировать, и, наконец, входящегоdataСоответствующие данные отображаются в шаблоне.

        //文本更新
        textUpdater(node, value) {
            node.textContent = value
        },
        //输入框更新
        modelUpdater(node, value) {
            node.value = value
        }

1.3 Скомпилированный фрагмент помещается обратно на страницу

После отображения данных на узле мы обнаружили, что на странице не отображаются данные, а узел элемента не существует, то есть потому, что вышеописанный ряд операций выполняется в памяти, и, наконец, нам нужно скомпилировать скомпилированныйfragmentВерните его на страницу.

 this.el.appendChild(fragment)

На этом часть компиляции шаблона завершена, и в это время мы обнаружим, что данные, которые мы определили в data, были полностью отображены на странице.

Далее продолжаем реализациюзахват данных

2. Перехват данных (наблюдатель)

Как следует из названия, захват данныхdataКаждое значение атрибута в мониторе отслеживается, и пока данные изменяются, необходимо выполнять соответствующие действия (вот вид обновления). Нечего сказать, сначала вставьте код и объясните несколько важных моментов.

class Observer {
    constructor(data) {
        this.observer(data)
    }
    observer(data) {
        //要对这个data数据原有的属性改成set和get的形式
        if (!data || typeof data !== 'object') { //如果数据不存在或者不是对象
            return;
        }
        //要将数据一一劫持,先获取到data的key和value
        Object.keys(data).forEach(key => { //该方法是将对象先转换成数组,再循环
            //劫持(定义一个函数,数据响应式)
            this.defineReactive(data, key, data[key]);
            //深度递归劫持,这里的递归只会为初始的data中的数据进行劫持(添加set和get方法),如果在defineReactive函数中使用set新增加则不会进行劫持
            this.observer(data[key]);
        })
    }
    //定义响应式
    defineReactive(obj, key, value) {
        //在获取某个值的时候,可以在获取或更改值的时候,做一些处理
        let that = this;
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() { //当取值时,调用的方法
                return value;
            },
            set(newValue) { //当给data属性中设置值的时候,更改获取的属性的值
                if (newValue !== value) {
                    console.log(this, 'this'); //这个this指向的是被修改的值
                    //但是这里的this不是Observer的实例,所以需要在最初保存一下当前this指向
                    that.observer(newValue); //如果是对象继续劫持
                    value = newValue;
                }
            }
        })
    }

    /**
     * 以上就实现了数据劫持
     */
}

Часть захвата данных относительно проста, в основном с использованиемObject.defineProperty()Перечисленные ниже места для заметок:

  • Измените исходный атрибут в данных данных наgetа такжеsetПрежде необходимо судить о данных, чтобы исключить случаи, когда это не объект, а данные не существуют.
  • Поскольку данные в data могут быть многоуровневыми вложенными объектами, требуется глубокая рекурсия, но рекурсия здесь захватит только исходные данные в data, а не вновь добавленные.
  • Основываясь на вышеуказанном дефекте, нам нужно добавитьsetПри вызове метода данные также перехватываются (поскольку это указывает на измененное значение в это время, необходимо сохранить текущее значение this в начале метода)

Компиляция основного шаблона и перехват данных завершены, и обе части могут выполнять свои собственные функции, но как связать их для достижения окончательного эффекта двусторонней привязки?

Ниже приведена комбинация двухWatcherРодная земля! ! !

3. Наблюдатель

СоздайтеWatcherНаблюдатель сравнивает новое значение со старым значением, и если есть изменение, вызывается метод обновления для обновления представления.

class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        //先获取一下老的值
        this.value = this.get();
    }
    getVal(vm, expr) { //获取实例上对应的数据
        expr = expr.split('.');
        return expr.reduce((prev, next) => { //vm.$data.a....
            return prev[next];
        }, vm.$data)
    }
    get() {
        Dep.target = this; //将当前watcher实例放入到tartget中
        let value = this.getVal(this.vm, this.expr);
        Dep.target = null;
        return value;
    }
    //对外暴露的方法
    update() {
        let newValue = this.getVal(this.vm, this.expr);
        let oldValue = this.value;
        if (newValue !== oldValue) {
            this.cb(newValue); //对应watch的callback
        }
    }
}

Также вставьте сюда часть знаний, модель публикации-подписки:

//observer.js
/**
 * 发布订阅
 */
class Dep {
    constructor() {
        //订阅的数组
        this.subs = [];
    }
    //添加订阅者
    addSub(watcher) {
        this.subs.push(watcher);
    }
    //通知
    notify() {
        this.subs.forEach(watcher => {
            watcher.update()
        })
    }
}

Роль публикации и подписки здесь: потому чтоWatcherЭто наблюдать за изменением данных, то есть подписчиков. Поскольку данные могут использоваться в нескольких местах в шаблоне, данные будут иметь несколько мониторов. То есть можно понять, что на данные есть несколько подписчиков, тогда при изменении данных все подписчики могут узнать результат сообщения одним уведомлением. (То есть изменение данных, изменилось значение данных, используемых в шаблоне)

Приведенный выше абзац является моим личным пониманием на данном этапе.Если есть какая-либо ошибка, я надеюсь, что смогу ее выдвинуть и совместно улучшить и усердно работать~~~

Объединив две вышеупомянутые функции, все данные могут быть связаны в обоих направлениях:

при создании в компиляции шаблонаWatcherнапример, эта строка кодаDep.target = this; //将当前watcher实例放入到tartget中Подписчики, отслеживающие это изменение данных, будут включены в массив подписчиков защиты от кражи.Обратите внимание, что, поскольку в Dep нет целевого атрибута, после его использования не забудьте освободить ненужное пространство памяти.Dep.target = null;На этом шаге мы сначала помещаем всех подписчиков в массив подписчиков.

// compile.js
 //这里应该加一个监控,数据变化了,应该调用这个watch的callback
        new Watcher(vm, expr, (newValue) => {
            //当值变化后,会调用cb将新值传递过来()
            updateFn && updateFn(node, this.getVal(vm, expr))
        })
//observer.js
    //定义响应式
    defineReactive(obj, key, value) {
        //在获取某个值的时候,可以在获取或更改值的时候,做一些处理
        let that = this;
        console.log(that, this);
        
        let dep = new Dep(); //每个变化的数据都会对应一个数组,这个数组是存放所有更新的操作
        
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() { //当取值时,调用的方法
            
                Dep.target && dep.addSub(Dep.target);
                
                return value;
            },
            set(newValue) { //当给data属性中设置值的时候,更改获取的属性的值
                if (newValue !== value) {
                    console.log(this, 'this'); //这个this指向的是被修改的值
                    //但是这里的this不是Observer的实例,所以需要在最初保存一下当前this指向
                    that.observer(newValue); //如果是对象继续劫持
                    value = newValue;
                    dep.notify(); //通知所有人数据更新了
                }
            }
        })
    }

Определите массив в разделе захвата данныхDep.target && dep.addSub(Dep.target);, который содержит подписчиков, которых необходимо обновить.

При получении значения поместите этих подписчиков в массив, определенный выше,Dep.target && dep.addSub(Dep.target);

При изменении значения будет вызыватьсяdep.notify(); //通知所有人数据更新了, косвенный вызовwatcher.update()для обновления данных.

Пока в основном реализована двусторонняя привязка данных, и ниже есть два простых пункта.

Добавить событие клика в поле ввода

        //为节点添加点击事件
        node.addEventListener('input', e => {
            let newValue = e.target.value;
            this.setVal(vm, expr, newValue);
        })

добавить прокси

Когда мы обращаемся к данным экземпляра, мы все передаемthis.$data.messageмогут быть доступны, потому что наши данные$dataвнутри, если мы хотим достичьthis.messageВы можете получить доступ к данным, вам нужно использовать слой агента в это время.

    proxyData(data) {
        Object.keys(data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set(newValue) {
                    data[key] = newValue
                }
            })
        })
    }

окончательный эффект

3. Подготовьте знания

В процессе анализа исходного кода Vue.js выяснилось, что нижний слой Vue.js использует много знаний, которые мы обычно не используем при реализации кода Вот простой список в качестве введения.

1. Классы ES6 и их определения

Вопрос 1: Определение традиционных классов JS

Традиционным способом определения классов в JS является определение и создание новых объектов с помощью конструкторов.prototypeСвойства дают вам возможность добавлять свойства и методы к объектам.

Случай:

//Person.js
function Person(x,y){    
    this.x = x;   
    this.y = y;
}

Person.prototype.toString = function (){    
    return (this.x + "的年龄是" +this.y+"岁");
}

export {Person};

//index.js
import {Person} from './Person';
let person = new Person('张三',12);
console.log(person.toString()); /张三的年龄是12岁

Вопрос 2: Определение классов в ES6

ES6 представил концепцию Class (класса) как шаблона для объектов, черезclassключевое слово, которое может определять классы. В общем, ЕС6.ClassЕго можно рассматривать как просто синтаксический сахар, большинство его функций ES5 умеет, новыйClassМетод записи просто делает написание прототипа объекта более понятным и более похожим на синтаксис объектно-ориентированного программирования. Приведенный выше код, переписанный с использованием «классов» ES6, выглядит так:

//Person.js
class Person{    
    // 构造    
    constructor(x,y){        
        this.x = x;            
        this.y = y;    
    }
    toString(){        
        return (this.x + "的年龄是" +this.y+"岁");    
    }
}
export {Person};

//index.js
import {Person} from './Person';
let person = new Person('张三',12);
console.log(person.toString()); /张三的年龄是12岁

Код поверхности определяет «класс класса», вы можете видеть, что естьconstructorметод, который является методом конструктора, иthisКлючевое слово представляет экземпляр объекта. То есть конструктор ES5Person, соответствующий ES6Personконструктор класса.PersonВ дополнение к конструктору класс также определяетtoStringметод.

Обратите внимание, что при определении метода «класса» нет необходимости добавлять его передfunctionЭто ключевое слово, вы можете напрямую поместить в него определение функции. Кроме того, не нужно разделять методы запятыми, иначе будет сообщено об ошибке.

Класс должен иметь метод конструктора, если он не определен явно, будет добавлен метод конструктора по умолчанию. Таким образом, даже если вы не добавите конструктор, конструктор по умолчанию есть.

2. Фрагментация документа в JS

В браузерах мы обычно используемinnerHTML()илиappendChild()Вставьте узлы DOM на страницу, например:

for(var i=0;i<5;i++){

    var op = document.createElement("span"); 

    var oText = document.createTextNode(i); 

    op.appendChild(oText); 

    document.body.appendChild(op); 
}

Однако, если бы, когда мы должны былиdocumentПри добавлении большого количества данных (например, 1w) к узлу, если вы добавляете узлы один за другим, как в приведенном выше коде, этот процесс может быть очень медленным. Конечно, вы также можете создать новый узел, напримерdiv, первыйoPдобавить вdivна, а затемdivдобавить вbody, но в этом случаеbodyдобавить еще один<div></div>.но фрагментация документа не создает таких узлов.

var oDiv = document.createElement("div"); 

for(var i=0;i<10000;i++){ 

    var op = document.createElement("span"); 

    var oText = document.createTextNode(i); 

    op.appendChild(oText); 

    oDiv.appendChild(op);  
} 
document.body.appendChild(oDiv);

Чтобы решить эту проблему, JS представилcreateDocumentFragment()метод, его роль заключается в том, чтобы создать фрагмент документа, прикрепить к нему новый узел, который нужно вставить, а затем добавить его вdocumentсередина. код показывает, как показано ниже:

//先创建文档碎片

var oFragmeng = document.createDocumentFragment(); 

for(var i=0;i<10000;i++){ 

    var op = document.createElement("span"); 

    var oText = document.createTextNode(i); 

    op.appendChild(oText); 

    //先附加在文档碎片中

    oFragmeng.appendChild(op);  

} 
//最后一次性添加到document中
document.body.appendChild(oFragmeng);

3. Деструктурирующее назначение

Процесс присвоения значений переменным из массива или объекта по определенному шаблону называется деструктурированием.

Приведенный выше код указывает на то, что вы можете брать значения из массива и присваивать значения переменным в соответствии с соответствующим отношением позиций.

4. Array.from и Array.reduce

1. В ES6Array.from()методArray.fromметод

Используется для преобразования двух типов объектов в реальные массивы: массивоподобный объект(array-like object)и проходимый(iterable)объекты (включая новые структуры данных ES6Setа такжеMap).

Ниже представлен массивоподобный объект,Array.fromПревратите его в настоящий массив:

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

В практических приложениях распространенными массивоподобными объектами являются коллекции NodeList, возвращаемые операциями DOM, а также коллекции внутри функций.argumentsобъект.Array.fromможет преобразовать их в реальные массивы.

/ NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

// arguments对象
function foo() {
  var args = Array.from(arguments);
  // ...
}

в приведенном выше коде,querySelectorAllМетод возвращает массивоподобный объект, который можно преобразовать в реальный массив и затем использоватьfilterметод.

больше ссылок

2. В ES5Array.reduce()метод

reduce()В качестве аккумулятора метод получает функцию, и каждое значение в массиве (слева направо) начинает объединяться в одно значение.

параметр описывать
callback Выполняет функцию для каждого значения в массиве с четырьмя аргументами
previousValue Значение, возвращаемое последним вызовом функции обратного вызова, или предоставленное начальное значение (initialValue).
currentValue текущий обрабатываемый элемент в массиве
index индекс текущего элемента в массиве
array Массив, для которого вызывается сокращение
initialValue в качестве первого аргумента первого вызова обратного вызова.

Подробное описание

  • reduceВыполнить функцию обратного вызова для каждого элемента в массиве по очереди, исключая элементы, которые удаляются или никогда не назначались в массиве, принимая четыре параметра: начальное значение (или возвращаемое значение последней функции обратного вызова), текущее значение элемента и текущий индекс, массив, для которого вызывается сокращение.

  • Когда функция обратного вызова выполняется в первый раз,previousValueа такжеcurrentValueможет быть значением, еслиinitialValueвызовreduceпредоставляется, то первыйpreviousValueравныйinitialValue,а такжеcurrentValueравно первому значению в массиве; еслиinitialValueне предусмотрено, тоpreviousValueравно первому значению в массиве,currentValueРавно второму значению в массиве.

  • если массив пуст и не указанinitialValue, броситTypeError. Если в массиве только один элемент (независимо от позиции) и нетinitialValue, или предоставитьinitialValueно массив пуст, то будет возвращено это уникальное значение иcallbackвыполняться не будет.

Пример использования:

var total = [0, 1, 2, 3].reduce(function(a, b) {
    return a + b;
});
console.log(total);//6

var total = [0, 1, 2, 3].reduce(function(a, b) {
    return a + b;
},10);
console.log(total);//16

5. Использование рекурсии

6. Obj.keys() и Obj.defineProperty()

Вопрос 1: Использование Obj.keys()

Метод Object.keys() возвращает массив собственных перечислимых свойств для данного объекта.

Пример использования:

var person = {
       firstName: "aaaaaa",
       lastName: "bbbbbb",
       others: "ccccc"
};
Object.keys(person).forEach(function(data) {
       console.log('person', data, ':', person[data]);
 });
//console.log:
//person firstName : aaaaaa
//person lastName : bbbbbb
//person others : ccccc

Вопрос второй:Obj.defineProperty()использование

Object.defineProperty()Метод определяет новое свойство непосредственно в объекте или изменяет существующее свойство объекта и возвращает этот объект.

грамматика:

Object.defineProperty(obj, prop, descriptor)

Описание параметра:

obj:必需。目标对象 
prop:必需。需定义或修改的属性的名字
descriptor:必需。目标属性所拥有的特性

возвращаемое значение:

传入函数的对象。即第一个参数obj

Добавить описание свойства к свойствам объекта.В настоящее время предусмотрено две формы: описание данных и описание аксессора.

1. Описание данных

При изменении или определении свойства объекта добавьте некоторые свойства к свойству:

var obj = {
    test:"hello"
}
//对象已有的属性添加特性描述
Object.defineProperty(obj,"test",{
    configurable:true | false,
    enumerable:true | false,
    value:任意类型的值,
    writable:true | false
});
//对象新添加的属性的特性描述
Object.defineProperty(obj,"newKey",{
    configurable:true | false,
    enumerable:true | false,
    value:任意类型的值,
    writable:true | false
});

Краткое описание свойств настроек:

value: 设置属性的值
writable: 值是否可以重写。true | false
enumerable: 目标属性是否可以被枚举。true | false
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false

Уведомление:

  • Помимо установки свойств для вновь определенных свойств, вы также можете установить свойства для существующих свойств.

  • После того, как вы используете Object.defineProperty для добавления атрибута к объекту, то, если свойство не установлено, настраивается, перечисляется, доступно для записи. Эти значения по умолчанию являются ложными.

2. Описание аксессора

При использовании аксессоров для описания свойств свойства разрешается задавать следующие свойства свойства:

var obj = {};
Object.defineProperty(obj,"newKey",{
    get:function (){} | undefined,
    set:function (value){} | undefined
    configurable: true | false
    enumerable: true | false
});

Примечание. При использовании методов получения или установки использование записываемых свойств и свойств значения не допускается.

геттер/сеттер При установке или получении значения свойства объекта вы можете предоставить методы получения/установки.

  • Геттер — это способ получить значение свойства

  • Сеттер — это способ установить значение свойства. Используйте атрибут get/set в атрибуте, чтобы определить соответствующий метод.

var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
    get:function (){
        //当获取值的时候触发的函数
        return initValue;    
    },
    set:function (value){
        //当设置值的时候触发的函数,设置的新值通过参数value拿到
        initValue = value;
    }
});
//获取值
console.log( obj.newKey );  //hello
//设置值
obj.newKey = 'change value';
console.log( obj.newKey ); //change value

Примечание: get или set не обязательно должны стоять парами, вы можете написать любую из них. Если метод не установлен, значение по умолчанию для get и set не определено. Конфигурируемые и перечисляемые такие же, как и выше.

7. Модель публикации-подписки

Проблема 1: шаблон публикации-подписки, также известный как шаблон наблюдателя

Интерпретация концепции паттерна наблюдателя

Режим наблюдателя также называется режимом публикации/подписки (Publish/Subscribe), который определяет отношение «один ко многим», позволяя нескольким объектам-наблюдателям одновременно прослушивать определенный объект темы.Когда состояние этого объекта темы изменяется, он уведомит все объекты Observer, чтобы они могли автоматически обновляться.

Роль режима наблюдателя и меры предосторожности

Функция режима:

  • 1. Поддержка простой широковещательной связи, автоматическое уведомление всех подписанных объектов.

  • 2. После загрузки страницы целевой объект легко динамически связывается с наблюдателем, что повышает гибкость

  • 3. Абстрактное отношение связи между целевым объектом и наблюдателем может быть расширено и повторно использовано независимо друг от друга.

Меры предосторожности:

Послушайте перед запуском.

На данный момент принцип двусторонней привязки данных и идея реализации в основном завершены.В то же время также объясняются базовые знания, необходимые в реализации функции.Я надеюсь, что читатели смогут почерпнуть из этого знания.Я Также приветствуйте разные мнения и смиренно принимайте их~~~