Вопросы для фронтенд-интервью: Насколько я понимаю, это MVVM, пожалуйста, ознакомьтесь с ним.

JavaScript MVVM

Что такое шаблон MVVM? Как вы понимаете принцип MVVM?Понимание его не только для собеседований, но и для лучшего понимания шаблонов проектирования фреймворков VUE, Backbone.js, angular, Ember и avalon.Возможно, что следующий популярный фреймворк будет вашим шедевром. ~~В конце этой статьи я также реализую собственную простую библиотеку MVVM, которая реализует основные функции библиотеки mvvm~

В настоящее время популярным интерфейсным фреймворком являетсяVue, React, angular, отправляя резюме, мы все видим, что требования к вакансии будут как минимум знакомы с одним из этих фреймворков.Освоение этих фреймворков похоже на наличие дополнительного колеса или лишней машины.Фреймворк может Однако мы можем быстро использовать и повторно использовать для решения некоторых проблем. Конечно, на собеседовании вас спросят не только о вашем владении ими, но и о вашем понимании фреймворка, потому что вам нужно знать причину, по которой вы это знаете.Давайте узнаем о модели фреймворка MVVM в этой статье. ~ Если есть какие-либо вопросы по статье, пожалуйста, также спрашивайте Пожалуйста, поправьте меня, не бейте меня~~

Во-первых, концепция MVVM

Определение Mvvm MVVM — это сокращение от Model-View-ViewModel. — это шаблон проектирования архитектуры программного обеспечения, разработанный архитекторами Microsoft WPF и Silverlight Кеном Купером и Тедом Питерсом, подход к программированию, управляемый событиями, который упрощает пользовательские интерфейсы. Опубликовано в его блоге в 2005 году Джоном Госсманом (также архитектором WPF и Silverlight). А именно Модель-Вид-ВьюМодель.

Во-вторых, история развития MVVM

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

В прошлом веке, в 1989 году, физик ЦЕРН Тим Бернерс-Ли изобрел Язык разметки гипертекста (HyperText Markup Language), или сокращенно HTML, и в 1993 году он стал проектом Интернета. С тех пор Интернет быстро коммерциализировался, и появилось большое количество коммерческих веб-сайтов.

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

Если вы хотите отображать разные страницы для разных пользователей, очевидно, невозможно подготовить тысячи разных HTML-файлов для тысяч пользователей, поэтому сервер должен динамически генерировать разные HTML-файлы для разных пользователей. Одна из самых прямых идей — использовать языки программирования, такие как C и C++, для прямого вывода склеенной строки в браузер. Эта технология называется CGI: Common Gateway Interface.

Очевидно, что сложный HTML-код, такой как домашняя страница Sina, не может быть получен путем написания строк. В результате люди обнаружили, что при написании строк большая часть строк представляет собой HTML-фрагменты, которые неизменны, и лишь некоторые пользовательские данные изменяются, поэтому появился новый способ создания динамического HTML: ASP, JSP и PHP — разработан Microsoft, SUN и сообществом открытого исходного кода соответственно.

В ASP файл asp — это просто HTML, однако переменные, которые необходимо заменить, используют специальный <%>
Размеченный, в сочетании с циклами и условными суждениями, гораздо проще создать динамический HTML, чем CGI.

Однако после того, как браузер отобразил HTML-страницу, единственный способ обновить содержимое страницы — повторно получить новое содержимое HTML с сервера. Если браузеры хотели сами изменять содержимое HTML-страниц, им приходилось ждать до конца 1995 года, когда в браузерах появился JavaScript.

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

Управление HTML в браузере с помощью JavaScript прошло несколько этапов разработки:

На первом этапе узлы DOM управляются непосредственно с помощью JavaScript, используя собственный API, предоставляемый браузером:

var dom = document.getElementById('name');
dom.innerHTML = 'Homer';
dom.style.color = 'red';

На втором этапе, поскольку нативный API не прост в использовании и необходимо учитывать совместимость с браузерами, родился jQuery, который быстро завоевал сердца фронтенд-разработчиков своим лаконичным API:

$('#name').text('Homer').css('color', 'red');

Третий этап, режим MVC, требует взаимодействия на стороне сервера, и JavaScript может изменять данные, отображаемые сервером во внешнем интерфейсе.

Теперь, когда внешние страницы становятся все более и более сложными, а пользователи предъявляют все более высокие требования к интерактивности, jQuery недостаточно, чтобы написать страницу, подобную Gmail. Появилась модель MVVM.

MVVM был впервые предложен Microsoft.Он основан на идее MVC настольных приложений.На странице интерфейса Модель представлена ​​чистым объектом JavaScript, а представление отвечает за отображение.Два добились максимального разделение.

ViewModel — это то, что связывает модель и представление. ViewModel отвечает за синхронизацию данных модели с представлением для отображения, а также за синхронизацию изменений представления с моделью.

Как пишется ViewModel? Общая ViewModel должна быть написана на JavaScript, чтобы можно было повторно использовать всю модель MVVM.

3. Формальное понимание MVVM

  • Шаблон MVVM

Появление MVVM способствует разделению фронтенд-разработки с графическим интерфейсом и внутренней бизнес-логики, что значительно повышает эффективность фронтенд-разработки.Ядром MVVM является слой ViewModel., это похоже на станцию ​​передачи (преобразователь значений), отвечающую за преобразование объектов данных в модели, чтобы упростить управление данными и их использование, этот уровень выполняет двустороннюю привязку данных со слоем представления вверх и взаимодействует с моделью. слой вниз Запрос на взаимодействие данных, который играет роль в увязке вверх и вниз. Как показано ниже:


  •  Компоненты МВВМ


# Просмотр слоя

Представление — это уровень представления, представляющий собой пользовательский интерфейс. Внешний интерфейс в основном построен на HTML и CSS.Для более удобного отображения данных слоя ViewModel или Model были созданы различные языки шаблонов внешнего и внутреннего интерфейса, такие как FreeMarker, Marko, Pug , Jinja2 и т. д. Основные MVVM-фреймворки, такие как Avalon, Vue, Angular и т. д., также имеют собственные встроенные языки шаблонов для построения пользовательских интерфейсов.

# Слой модели

Модель относится к модели данных, которая обычно относится к различной обработке бизнес-логики и обработке данных, выполняемой серверной частью, в основном вокруг системы базы данных.

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

# Слой ViewModel

ViewModel — это уровень данных представления, который создается и поддерживается организацией разработчиков интерфейса. Ядро режима mvvm, это мост, соединяющий представление и модель. На этом уровне внешний разработчик преобразует данные модели, полученные от серверной части, и выполняет вторичную инкапсуляцию для создания модели данных представления, которая соответствует ожиданиям уровня представления. Следует отметить, что модель данных, инкапсулированная ViewModel, включает в себя состояние и поведение представления, в то время как модель данных слоя Model содержит только состояние, например, что отображается в этой части страницы, а что отображается в этой части. Они относятся к состоянию просмотра (Отображение), и что происходит, когда страница загружается, что происходит, когда вы нажимаете на эту часть, и что происходит, когда эта часть прокручивается, — все это поведение просмотра (взаимодействия) и состояния просмотра. и поведение инкапсулировано в ViewModel. Эта инкапсуляция позволяет ViewModel полностью описывать уровень представления. Благодаря реализации двусторонней привязки содержимое ViewModel будет отображаться в слое View в режиме реального времени, что очень удобно, поскольку разработчикам интерфейса больше не нужно неэффективно и хлопотно манипулировать DOM для обновления представления. Фреймворк MVVM уже сделал самое грязное и утомительное. После того, как эта часть будет выполнена, нашим разработчикам нужно будет только обработать и поддерживать ViewModel, а обновленное представление данных будет автоматически обновляться соответствующим образом, действительно реализуя разработку, управляемую данными. Видите, слой View отображает не данные слоя Model, а данные ViewModel. ViewModel отвечает за взаимодействие со слоем Model, что полностью разделяет слой View и слой Model. Эта развязка очень важна. Это важная часть реализации схемы разделения интерфейса и сервера.

------Я разделительная линия-----------------------------------

Мысль: конечно, если только Vue и React — это фреймворки, работающие на внешнем интерфейсе, почему приведенная выше модель разделяет внутренний бизнес? Разве это не противоречит? ? ?

Таким образом, можно также сказать следующее: MVVM делит V или View в предыдущем MVC (внутренняя разработка MVC: данные в M: база данных модели, V: интерфейсная страница просмотра, C: внутренний контроллер контроллера) на MVVM. режим :

Этот режим разделения: M: данные в js в модели, V: страница интерфейса, VM: режим просмотра модели представления;

Режим MVVM: пользователю не требуется вручную управлять домом, в основном для достижения двусторонней привязки данных.

———————————————————————————————————

С этого аспекта

  • Преимущества и недостатки шаблона проектирования MVVM:

преимущество:

1,Конечно, это самая важная технология двустороннего связывания.Односторонняя привязка и двусторонняя привязка.

так называемый"«Односторонняя привязка» означает, что при изменении ViewModel представление автоматически обновляется.

так называемый"«Двусторонняя привязка» заключается в автоматическом обновлении ViewModel при изменении представления на основе односторонней привязки.

Мы можем сначала увидеть разницу между инфраструктурой MVVM и манипуляцией DOM с помощью jQuery?

оригинальный html

<p>Hello, <span id="name">LEE</span>!</p><p>You are <span id="age">18</span>.</p>

экспонат


Измените содержимое узлов имени и возраста с помощью jQuery:

var name = '修改';
var age =100;
 
$('#name').text(name);
$('#age').text(age);

Если мы используем инфраструктуру MVVM для выполнения той же функции, нас в первую очередь волнует не структура DOM, а то, как хранятся данные. Самый простой способ хранения данных — использовать:

var person = {
    name: 'LEEt',
    age: 18
};

Мы рассматриваем переменную person как Model, а некоторые узлы DOM в HTML как View и предполагаем, что они связаны.

Чтобы изменить отображаемое имя из LEE и изменить отображаемый возраст с 18 до 100 лет, мы не манипулируем DOM, а напрямую модифицируем объект JavaScript:

person.name = '修改';

person.age = 100;

Видно, что наше внимание изменилось с того, как манипулировать DOM, на то, как обновлять состояние объектов JavaScript, а манипулирование объектами JavaScript намного проще, чем DOM!

MVVMДизайн-мышление: ФокусModelменяется, пустьMVVMФреймворк для автоматического обновленияDOMсостояние, тем самым выводя отправителя из операцииDOMОсвободите себя от утомительных шагов!

2. Так как большинство функций контроллера перенесено в View для обработки, контроллер сильно уменьшился в размерах.

3. Функцию можно абстрагировать от части обработки данных View или ViewController для обработки модели. Таким образом, они специализируются на макете страницы и переходе между страницами, и их необходимо еще больше упростить.

4. Улучшить ремонтопригодность

5. Тестируемый. Интерфейсы всегда было трудно тестировать, и теперь тесты можно писать для ViewModels.

6. Низкая связанность и возможность повторного использования: представление можно изменять и модифицировать независимо от модели. Модель представления может быть привязана к различным «представлениям». Модель меняется постоянно. Вы можете поместить некоторую логику представления в ViewModel и позволить многим представлениям повторно использовать эту логику представления.

недостаток:

  1. Ошибки трудно отлаживать. Из-за двустороннего режима привязки, когда вы видите аномалию в интерфейсе, может быть ошибка в коде вашего Представления или может быть проблема с кодом Модели. Привязка данных позволяет быстро распространять ошибки в одном месте в другие места, что затрудняет обнаружение исходной проблемы. Кроме того, объявление привязки данных императивно написано в шаблоне представления, и нет возможности прервать отладку этого содержимого.
  2. Модель в большом модуле тоже будет очень большая.Хоть она и удобна в использовании, но легко обеспечить непротиворечивость данных.В то время, если ее держать долго, будет стоить больше памяти, если память не освобождается.
  3. Для крупномасштабных графических приложений существует множество состояний просмотра, а затраты на создание и обслуживание ViewModel будут относительно высокими.

  • Сфера применения МВВМ

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

Но MVVM — не панацея, его цель — решить сложную фронтенд-логику. Для страниц, которые в основном отображают логику, таких как новости, блоги, документы и т. д., MVVM нельзя использовать для отображения данных, потому что эти страницы нужно индексировать поисковыми системами, а поисковые системы не могут получить данные, загруженные через API с помощью MVVM.

Итак, нужноСтраницы SEO (поисковая оптимизация) не могут использовать MVVM для отображения данных. Страницы, которые не требуют SEO, если логика внешнего интерфейса сложна, целесообразно использовать MVVM для отображения данных, таких как страницы инструментов, страницы сложных форм, страницы, которыми можно управлять только после входа пользователей в систему и т. д. Конечно, сейчас может быть ssr.

Обычно используемые фреймворки MVVM:

Angular: разработан Google, известен, но сложен в освоении, подходит для ПК, структура кода будет более понятной;

Backbone.js: начать очень сложно, потому что слишком много API;

Ember: Большой и всеобъемлющий фреймворк, очень сложно написать Hello world.

Avalon: он легкий и имеет высокую степень поддержки старых браузеров с минимальной поддержкой IE6, поэтому он подходит для проектов, совместимых со старыми браузерами Liu;

Vue: в основном легкий, используется только как часть представления в MV *, преимущества легкие, простые в освоении и использовании, недостаток в том, что его сложнее использовать с другими фреймворками или библиотеками в больших проектах.

В-четвертых, библиотека js, реализующая MVVM.

В настоящее время существует несколько способов двусторонней привязки данных:

  1. Обнаружение грязного значения (угловое):

Представленный типичной инфраструктурой mvvm angularjs, angular выполняет операцию обновления слоя пользовательского интерфейса, проверяя грязные данные. Есть несколько вещей, которые нужно знать о грязном обнаружении angular:

  • l Механизм обнаружения загрязнения не использует определение времени.
  • l Момент грязного обнаружения — когда данные изменяются.
  • l Angular инкапсулирует часто используемые события dom, события xhr и т. д. и запускает процесс дайджеста, входящий в него angular.
  • l В процессе дайджеста он будет проходить из корневой области для проверки всех наблюдателей. (Для конкретного дизайна angular вы можете увидеть другие документы, здесь обсуждается только привязка данных), затем давайте посмотрим, как сделать грязное обнаружение: в основном через набор данных, чтобы найти все элементы, связанные с данными, а затем сравнить данные изменяется, и если она изменяется, выполняется операция инструкции.
2. Перехват данных во внешнем интерфейсе (Hijacking) (vue): основная идея: используйте Object.defineProperty() для захвата геттера и сеттера, соответствующего каждому свойству данных. Когда есть операции чтения и присваивания данных, вызываются инструкции узла, так что можно использовать наиболее распространенное присваивание знака равенства =.

3. Режим публикации-подписки (основной): связывание и мониторинг данных и представлений путем публикации и подписки на сообщения.

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


Идеи реализации кода: (аналогично реализацииVUEБиблиотека, реализующая MVVM)


Моделирование заключается в том, что библиотека MVVM VUE реализована с использованием идеи захвата данных MVVM, приведенный выше рисунок является основной идеей. Как показано на рисунке выше, мы видим, что общая реализация разделена на следующие шаги.

1. Реализовать Observer, перехватить данные и уведомить об изменении данных (основные моменты, которые нужно использовать: метод Object.defineProperty())

2. Реализовать компиляцию, разобрать инструкции, инициализировать представление, подписаться на изменения данных и привязать функцию обновления.

3. Реализовать Watcher в качестве промежуточной точки между двумя вышеуказанными.При получении изменений данных позволить Dep добавить текущего Watcher и вовремя уведомить представление об обновлении.

4. Реализовать некоторые другие функции VUE (вычисления, методы)

5. Реализовать MVVM, интегрировать вышеуказанные пункты в качестве функции входа

Ниже приведена часть кода:

HTML:

<!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">
<title>
实现MVVM的js库(模拟vue实现功能)</title>

<script src="./MVVM.js"></script>
</head>
 
<body>
 
<div id="app">
<input type="text" v-model="person.name">
<p>
hello,{{person.name}}
</p>
<p>You are:{{person.age}}</p>
<!-- computed属性如果数据不变化 视图不更新 -->
<p>{{getNewName}}</p>
<button type="button" name="button" v-on:click="testToggle">
修改名字</button>

</div>
 
<script>
let vm = new Vue({
el: '#app',
data: {
person: {
name: 'lee',
age: 18
}
},
methods: {
testToggle(){
this.person.name = '修改后的名字:哈哈';
}
},
computed: {
getNewName(){
return this.person.name+' 是要成为海贼王的人'
}
},
})
</script>
</body>
</html>


js:

// 2019-4-4
// lee 
// 草履虫的思考
// 简单模拟vue实现MVVM
/**
 * 实现一个Vue的类
 * 1、实现一个Observer,对数据进行劫持,通知数据的变化(将使用的要点为:Object.defineProperty()方法)
2、实现一个Compile,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数ComplieUtil解析指令的公共方法
3、实现一个Watcher,将其作为以上两者的一个中介点,在接收数据变更的同时,让Dep添加当前Watcher,并及时通知视图进行update
4、实现一些VUE的其他功能(Computed、menthods)
 */
// 观察者模式(发布订阅)
class Dep {
    constructor() {
        this.subs = []; //存放所有watcher
    }
    // 订阅 添加watcher
    addSub(watcher) {
        this.subs.push(watcher);
    }
    // 发布
    notify() {
        this.subs.forEach(watcher => watcher.update());
    }
}
// 观察者 vm.$watch(vm,'person.name',(newVal)=>{ })
class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        // 默认存储一个老值
        this.oldValue = this.get();
    }
 
    get() {
        Dep.target = this;
        // 取值 把这个观察者和数据关联起来
        let val = ComplieUtil.getVal(this.vm,this.expr);
        Dep.target = null;
        return val;
    }
    // 更新操作 数据变化后 会调用观察者中的update方法
    update() {
        let newVal = ComplieUtil.getVal(this.vm,this.expr);
        if (newVal !== this.oldValue) {
            this.cb(newVal);
        }
    }
}
 
// 实现数据劫持作用
class Observer {
    constructor(data) {
        this.observer(data);
    }
    observer(data) {
        // 如果是对象才观察
        if (data && typeof data === 'object') {
            for (let key in data) {
                this.defineReactive(data, key, data[key])
            }
        }
    }
 
    defineReactive(obj, key, value) {
        this.observer(value);
        // 给每个属性 都加上具有发布订阅的功能
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            enumerable: true,   // 可枚举
            configurable: true, // 可重新定义
            get() {
                // 创建watcher时 会取到对应的内容,并且把watcher放到全局上
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set: (newVal) => { // {person:{name:'lee'}
                // 数据没有变不需要更新
                if (newVal != value) {
                    // 需要递归
                    this.observer(newVal);
                    value = newVal;
                    dep.notify();
                }
            }
        })
    }
}
// 编译器
class Complier {
    constructor(el, vm) {
        // 判断el属性是不是一个元素 如果不是元素 那就获取他 (因为在vue的el中可能是el:'#app'
        // 或者document.getElementById('app')
 
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        // 把当前节点中的元素 获取到 放到内存中
        let fragment = this.nodeFragMent(this.el);
 
        // 把节点中的内容进行替换
 
        // 编译模板 用数据编译
        this.complie(fragment);
        // 把内容在塞到页面中
        this.el.appendChild(fragment);
    }
 
    isElementNode(node) { //是不是元素节点
        return node.nodeType === 1;
    }
    //  把节点移动到内存中
    nodeFragMent(node) {
        let frag = document.createDocumentFragment();
        let firstChild;
        while (firstChild = node.firstChild) {
            // appendChild 具有移动性 
            frag.appendChild(firstChild);
        }
        return frag;
    }
    // 是不是指令 
    isDirective(attrName) {
        return attrName.startsWith('v-');
    }
    // 编译元素
    complieElement(node) {
        let attr = node.attributes;
        [...attr].forEach(item => {
            // item 有key = value ,type="text" v-model="person.name"
            let {
                name,
                value: expr
            } = item;
            if (this.isDirective(name)) {
                // v-mode v-html v-bind...
                let [, directive] = name.split('-');
               let [directiveName,eventName] = directive.split(':');
                console.log(node, expr, this.vm, eventName);
                // ComplieUtil[directive](node, expr, this.vm);
                ComplieUtil[directiveName](node, expr, this.vm, eventName);
            }
        })
    }
    // 编译文本
    // 判断当前文本节点中内容是否包括{{}}
    complieText(node) {
        let content = node.textContent;
        var reg = /\{\{(.+?)\}\}/;
        if (reg.test(content)) {
            ComplieUtil['text'](node, content,this.vm); //{{}}
        }
    }
    // 用来编译内存中的dom节点
    complie(node) {
        let childNode = node.childNodes;
        // childNode 是类数组 转换为数组
        [...childNode].forEach(item => {
            // 元素 查找v-开头
            if (this.isElementNode(item)) {
                this.complieElement(item);
                //如果是元素的话  需要把自己传进去
                // 在去遍历子节点
                this.complie(item);
                //    文本 查找{{}}内容
            } else {
                this.complieText(item);
            }
        })
 
    }
}
// 编译工具
ComplieUtil = {
    // 解析v-model指令
    // node是节点 expr是表达式 vm是实例 person.name vm.$data 解析v-model
    model(node, expr, vm) {
        // 给输入框赋予value属性 node.value = xxx
        let fn = this.updater['modelUpdater'];
        let val = this.getVal(vm, expr);
        // 给输入框加一个观察者 如果稍后数据更i性能了会触发此方法,数据会更新
        new Watcher(vm, expr, (newVal) => {
            fn(node, newVal);
        });
        // 输入事件
        node.addEventListener('input',(e)=>{
            let val = e.target.value; //获取用户输入的内容
            this.setVal(vm, expr, val);
        });
        fn(node, val);
    },
    html() {
 
    },
    // 返回了一个全的字符串
    getContentVal(vm, expr) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(vm, args[1]);
        });
    },
    text(node, expr, vm) { //expr {{a}} {{b}} {{person.name}}
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
        //给表达式{{}}都加上观察者    
            new Watcher(vm, args[1], () => {
                fn(node, this.getContentVal(vm, expr));
            });
            return this.getVal(vm, args[1]);
        });
        let fn = this.updater['textUpdater'];
        fn(node, content);
    },
    on(node, expr, vm,eventName){ //v-on:click
        console.log(node, expr, vm, eventName);
        node.addEventListener(eventName,(e)=>{
            vm[expr].call(vm,e );
        });
       
    },
    updater: {
        modelUpdater(node, value) {
            node.value = value;
        },
        htmlUpdater() {},
        // 处理文本节点
        textUpdater(node, value) {
            node.textContent = value;
        }
    },
    //根据表达式取到的对应的数据  vm.$data expr是如 'person.name'
    getVal(vm, expr) {
      return  expr.split('.').reduce((data, cur) => {
            return data[cur];
        }, vm.$data);
    },
    setVal(vm, expr,value){
        expr.split('.').reduce((data, cur,index,arr) => {
           if(index == arr.length-1){ //索引是最后一项 
               return data[cur] = value;
           }
            return data[cur];
        }, vm.$data);
    }
}
class Vue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        let computed = options.computed;
        let methods = options.methods;
        // 根元素存在在编译模板
        if (this.$el) {
            // 把数据 全部转化成用Object.defineProperty来定义
            new Observer(this.$data);
 
 
            // 实现methods中的方法
            for (let key in methods) { 
                Object.defineProperty(this, key, {
                    get() {
                        return methods[key]; //进行了转化操作
                    }
                });
            }
            // 实现computed中的方法
            for (let key in computed) { //有依赖关系
                Object.defineProperty(this.$data, key, {
                    get() {
                        return computed[key].call(this); //进行了转化操作
                    }
                });
            }
               // 把数据获取操作 都代理到vm.$data
            this.proxy(this.$data);
            new Complier(this.$el, this);
        }
 
    }
    // 代理 去掉$data
    proxy(data){
        for(let key in data){
            Object.defineProperty(this,key,{
                get(){
                    return data[key]; //进行了转化操作
                }
            });
        }
    }
}