Vue入门篇

158 阅读3分钟

框架vs库vs组件

  • 框架 vue react 我们写方法 ,框架帮我们调用
    
  • 库 jQuery 人家写方法,我们用
    
  • vue是双向数据绑定。单向数据流
    

start Vue

导入Vue

  • npm i vue --save (--save会生成对应的package.json)
  • npm init -y 初始化项目。会生成一个package.json
  • script引入:

new Vue

<body>
    <div id="app">
        {{name}}
    </div>
</body>
<script>
    new Vue({
        el:"#app",
        data:{
            name:"polikesen"
        },
        methods:{
            f(){
            }
        }
    })
</script>

小胡子语法

小胡子语法 ,里面只能写 Vue变量、表达式(不能写语句)

vue指令

在vue中 所有v-xxx的行内属性都是vue指令

  • v-text=“vue变量” 类似innerText 网速慢时页面上可能会显示小胡子 为了避免可以用v-text
  • v-html=“vue变量” 类似innerHTML 可以识别字符串中的DOM结构,里面的值 只会当作普通html, 不会当作vue模版进行渲染
  • v-once 后边不跟值 代表有这个属性的元素 里面的语法 vue只会渲染一次
  • v-model 表单元素使用 数据的双向绑定的核心
  • v-pre 后边不跟值 告诉vue:有这个指令的标签及其后代标签 都不进行编译,可以提升vue的编译速度
  • v-if
  • v-show
  • v-cloak 后边不跟值 就是利用了vue渲染完成之前 元素上还会有v-cloak属性 渲染完成之后 ,vue会吧v-cloak属性去掉 这样页面上就不会显示出小胡子语法 必须配合css的display:none;使用
  • v-bind(简写:) 修饰内置的行内属性
  • v-on :事件绑定(简写:@)

vue修饰符

  • 事件修饰符:.stop(阻止冒泡) .prevent(阻止默认事件) .capture(在冒泡阶段触发) .passive(先执行绑定事件,再触发默认事件) .self(触发绑定元素本身时才有用) .once(只触发一次)
  • 按键修饰符: .enter/.13
  • 系统修饰符: .ctrl
  • v-model的修饰符: .lazy(onchange事件 失去焦点时触发)) .number(parseFloat转换 转不出来还是字符串)) .trim(去除首尾空格)

vue自定义指令

全局指令:Vue.directive(指令名,function(el,obj))
directives:{
    指令名1(el,obj){

    },
    指令名2(el,obj){

    }
}

filters 过滤器方法

全局过滤器: Vue.filter(过滤器名,function(val,传递的其他参数){val就是管道符前边的值})

Vue数据对象

改变vue中的数据 能触发试图更新 前提是 该数据有set 和 get; 若没有get和set更改数据是不会触发试图的更新,但是 数据是改了的 给对象新增属性时, vue不会触发试图的更新,解决方法:

  1. 一开始就在data中写上对应的属性
  2. 整个对象的替换,替换后的对象中的每一个属性 都会被监听
  3. 通过$set(对象,属性名,属性值)
  4. 初始时 创建一个无关变量,在新增对象的属性时, 我们可以去更新刚才的无关变量

Vue双向数据绑定实现原理(v-model...)

响应式数据:data中准备的要在视图中渲染的数据(model)

  • 数据劫持

vue是通过利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。

  • Objec.defineProperty()
 let obj = {};
    let ary = [];
    let val = 0;
    Object.defineProperty(obj, 'qqq', {
        // value:'zhuefeng',
        // enumerable:true,//可枚举(可循环到)
        // configurable:true,//可删除
        // writable:true,//可改写
        get() {
            console.log('get')
            return val
        },
        set(value) { //value就是给对应属性设置的值  set里面写return 没啥用
            console.log('set', arguments)
            val = value;
            ary.forEach(fn=>{
                fn();
            })
            return 'set'
        }
    })

缺点: definePropertyf方法,只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历

  • Proxy代理
 let obj2 = {a:23,b:65};
    let o = new Proxy(obj2,{
        get(...arg){
            console.log(arg);
        },
        set(...arg){
            console.log(arg)
        }
    })

Proxy是Object.defineProperty的全方位加强版,Proxy可以直接监听对象而非属性,Proxy也可以直接监听数组的变化

mvvm实现原理

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

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="app">
        {{name}}{{age}}{{name}}
        <h2>{{age}}</h2>
        <input type="text" v-model="name">
        <input type="text" v-model="age">
        <h2>{{name}}{{age}}</h2>
    </div>
</body>
</html>
<script>
    /* 
        1. 把页面中的Vue语法  转成正常的数据
            - 先获取数据

    */
    function nodeToFragment($el, vm) { //vm是当前实例
        let fragment = document.createDocumentFragment(); //创建一个文档碎片,用来存储页面中的节点
        let child;
        while (child = $el.firstChild) {
            //界面中已经存在的元素  appendChild操作是移动元素位置  而不是添加
            compile(child, vm); //编译模板函数
            fragment.appendChild(child); //把child从#app中
        }
        //通过while循环 我们把要编译的模版转移到了文档碎片上 页面上成了空白
        //下一步  我们再编译模版 然后把编译好的模版放到页面上
        // console.log(fragment)
        $el.appendChild(fragment)
    }
    function compile(node, vm) {
        //这里需要我们判断node是文本节点 还是元素节点
        if (node.nodeType == 1) {
            //说明是元素节点
            let attrs = node.attributes; //获取node的所有行内属性
            // console.log(attrs);
            [...attrs].forEach(item => {
                // console.log(item.nodeName,item.nodeValue)
                if (/v\-/.test(item.nodeName)) {
                    let valN = item.nodeValue; //指令对应的vue变量名 ;name age
                    // console.log(valN,vm.$data)
                    let val = vm.$data[valN]; //vue对应的值:珠峰 10
                    //把val设置成对应的元素的值  node就是我们对应的元素
                    node.addEventListener('input', (e) => {
                        vm.$data[valN] = e.target.value;
                    }, false)
                    new Watcher(node, vm, valN)
                    node.value = val; //把元素对应的value设置成对应的val
                    // console.log(node)
                }
            });
            //若没有v-指令 则对该节点继续进行编译,编译该节点的自节点
            // console.log(node);
            [...node.childNodes].forEach(item => compile(item, vm))
        } else {
            //文本节点  我们要去查找小胡子语法 把小胡子语法对应的变量换成对应的值
            // console.log(node.textContent);//通过这个可以获取到对应的文本字符串
            let str = node.textContent;
            node.str = str; //str是带着小胡子的编译之前的值
            if (/\{\{(\w+)\}\}/.test(str)) {
                str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
                    // console.log($1)
                    new Watcher(node, vm, $1)
                    return vm.$data[$1]
                })
                // console.log(str);
                node.textContent = str;
            }
        }
    }

    function observe(obj) {
        //数据劫持
        let keys = Object.keys(obj); //获取obj中所有的属性名
        keys.forEach(key => {
            //执行真正的数据劫持
            defineReactive(obj, key, obj[key])
        })
    }

    function defineReactive(obj, key, value) {
        let dep = new Dep(); //针对每一个属性 各子创建了一个事件池 name事件池中存放的都是用到name的那些节点的更新操作
        Object.defineProperty(obj, key, {
            get() {
                // console.log(`${key}被使用了`)
                if (Dep.target) {
                    //存储的是更新的 对应的DOM的操作
                    dep.addSub(Dep.target)
                }
                return value;
            },
            set(newValue) {
                if (newValue == value) return; //若前后两次的数据没改变就不需要更新DOM
                value = newValue;
                // console.log(`${key}被设置了`)
                dep.notify();
            }
        })
    }
    //订阅器
    class Dep {
        constructor() {
            this.subs = [];
        }
        addSub(sub) {
            this.subs.push(sub)
        }
        notify() {
            this.subs.forEach(item => {
                item.update();
            })
        }
    }
    class Watcher {
        //订阅者 
        constructor(node, vm, key) {
            Dep.target = this;
            this.node = node;
            this.vm = vm;
            this.key = key;
            this.update();
            Dep.target = null;
        }
        update() {
            this.get(); //this.value存储的是 更改后的数据的新值
            let str = this.node.str;
            if (str) {
                str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
                    if ($1 == this.key) {
                        return this.value
                    }
                    return this.vm.$data[$1]
                })
                this.node.textContent = str; //把更新完的str 重新放回页面
            }else{
                //证明是input
                this.node.value = this.value;
            }

        }
        get() {
            this.value = this.vm.$data[this.key]
        }
    }

    function Vue(options) {
        this.$el = document.querySelector(options.el); //$el就是我们要操作的元素
        this.$data = options.data || {}; //$data中存储的值对应的是Vue中的变量
        observe(this.$data); //  对数据进行劫持
        nodeToFragment(this.$el, this); //把页面中的节点转移到文档碎片上
    }
    let vm = new Vue({
        el: '#app',
        data: {
            name: '珠峰',
            age: 10
        }
    })
</script>