Vue入门--引包方式、手写微型Vue(体现new Vue()时到底做了什么)、启动方式

239 阅读2分钟

1. 引包方式

vue2的引包及入门使用方式可以参考官网安装 — Vue.js (vuejs.org),这里也做一下简单的总结;

1.1 普通引包(直接用 script标签 引入)
  • 直接下载并用 script标签引入,Vue会被注册为一个全局变量;
  • 在开发环境下不要使用压缩版本,不然你就失去了所有常见错误相关的警告;
  • 用来开发选择适合自己项目的版本吗或者官网推荐的稳定版本引入;
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
  • 也可以将其下载下来放在本地项目目录中,然后引用;
// vue.min.js就是自己保存的vue包的文件名
<script src="./vue.min.js"></script>
1.2 使用原生ES Modules
<script type="module">  
import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.esm.browser.js'  
</script>
1.3 单文件+webpack模块打包器
  • 单文件(.vue)编写 + webpack(vue-template-compiler)编译器;
  • 单文件的方式 vue运行时 , 非运行时(写代码时): webpack 使用编译器转化.vue文件;

下面补充几个官网提供的术语

  • 完整版:同时包含编译器和运行时的版本;
  • 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码;
  • 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切;

2. 手写微型Vue(体现new Vue()时到底做了什么)

  1. new Vue()时,传递两个必须的参数:dom元素(或者字符串,定位要更新的部分)、数据(要将什么数据更新上去);
new Vue({
    el: 'div.box',// 字符串或dom元素(据说 直接传递DOM元素效率更高)
    data() { // 这里data传递函数与传递对象相比,可以保证数据更新时不会影响其他元素数据
        return {
            text: 'hello world'
        }
    }
});
  1. 定义一个我们自己的Vue类,接收传进来的参数,这里注意
  • $$ : 内部元素,不支持外部访问;
  • $ :是外部可以使用的元素;
class Vue {
    constructor(option) {
        // $$内部不被外部访问  $是外部可以使用的
        // 拿到dom元素并保存
        this.$$el = this.$el = option.el instanceof HTMLElement ?
            option.el : document.querySelector(option.el);
        // 拿到data数据并保存
        this.$options = {
            data: option.data
        };
        this.$$data = this.$data = this.$options.data();
}
  1. dom元素所有子元素并遍历拿出所有子节点,这里注意
  • dom元素有时会含有空字符文本节点,定义EMPTY_TEXT = "\n "静态变量来识别并排除此类节点;
class Vue {
    constructor(option) {
        // $$内部不被外部访问  $是外部可以使用的
        // 拿到dom元素并保存
        this.$$el = this.$el = option.el instanceof HTMLElement ?
            option.el : document.querySelector(option.el);
        // 拿到data数据并保存
        this.$options = {
            data: option.data
        };
        this.$$data = this.$data = this.$options.data();
        // 获取所有子节点
        this.elements = this.$$el.childNodes;
        // 遍历所有子元素
        this.elements.forEach(ele => {}
}
  1. 判断节点是否为文本节点,或者是否为空文本节点,如果是文本节点就更新文本节点显示的文字内容;
class Vue {
    constructor(option) {
        // $$内部不被外部访问  $是外部可以使用的
        // 拿到dom元素并保存
        this.$$el = this.$el = option.el instanceof HTMLElement ?
            option.el : document.querySelector(option.el);
        // 拿到data数据并保存
        this.$options = {
            data: option.data
        };
        this.$$data = this.$data = this.$options.data();
        // 获取所有子节点
        this.elements = this.$$el.childNodes;
        // 遍历所有子元素
        this.elements.forEach(ele => {
            let isTextNode = ele.nodeType === Node.TEXT_NODE;
            if (isTextNode && ele.nodeValue === Vue.EMPTY_TEXT) return; // 排除空的文本节点
            
            // 如果是文本节点
            if (isTextNode) {
                let textRegex = /\{\{(.*)\}\}/;
                let res = textRegex.exec(ele.nodeValue);
                if (!res) return;
                let key = res[1].trim();
                ele.nodeValue = ele.nodeValue.replace(res[0], this.$$data[key])
            }
        }
}
  1. 如果不是文本节点,是组件或者元素,控制组件显示的值为输入的值
class Vue {
    constructor(option) {
        // $$内部不被外部访问  $是外部可以使用的
        // 拿到dom元素并保存
        this.$$el = this.$el = option.el instanceof HTMLElement ?
            option.el : document.querySelector(option.el);
        // 拿到data数据并保存
        this.$options = {
            data: option.data
        };
        this.$$data = this.$data = this.$options.data();
        // 获取所有子节点
        this.elements = this.$$el.childNodes;
        // 遍历所有子元素
        this.elements.forEach(ele => {
            let isTextNode = ele.nodeType === Node.TEXT_NODE;
            if (isTextNode && ele.nodeValue === Vue.EMPTY_TEXT) return; // 排除空的文本节点
            
            // 如果是文本节点
            if (isTextNode) {
                let textRegex = /\{\{(.*)\}\}/;
                let res = textRegex.exec(ele.nodeValue);
                if (!res) return;
                let key = res[1].trim();
                ele.nodeValue = ele.nodeValue.replace(res[0], this.$$data[key])
            }
            // 如果不是文本节点
            else if (ele.nodeType === Node.ELEMENT_NODE) {
                if (ele.tagName === 'INPUT') {
                    let vModelValeue = ele.getAttribute('v-model');
                    // value
                    ele.value = this.$$data[vModelValeue];
                    // 输入事件
                    ele.oninput = e => {
                        // 为了简化功能而写死...
                        this.elements[0].nodeValue = e.target.value;
                    }
                }
            }
        });
}
  1. 附上页面测试元素代码
<div class="box">
    {{ text }}
    <input type="text" v-model="text" />
</div>

3. 启动方式

  • 有template优先template替换el,没有就沿用el内的元素;
  • 有el就立刻渲染, 没有就$mount(el) 渲染;
3.1 内含html
  • 没有template就沿用el内的元素
<div class="box"> {{ text }} </div>

<script>
    new Vue({
        el: 'div.box',
    data() {
        return {
        text: 'hello world'
        }
    }
});
</script>
3.2 传递template
  • 有template替换当前的el
<div class="box"> {{ text }} </div>

<script>
    new Vue({
        el: 'div.box',
    template:`
    <button> {{ text }} </button>
    `,
    data() {
                    return {
        text: 'hello world'
                    }
                }
            });
</script>
3.3 后续再启动装载
  • 有el就立刻渲染, 没有就$mount(el) 渲染;
<script>
    let vm = new Vue({
        template:`
    <button> {{ text }} </button>
    `,
    data() {
                    return {
        text: 'hello world'
                    }
                }
            }).$mount('div.box');
</script>