精华内容
下载资源
问答
  • vue重点难点

    2020-03-19 09:31:44
    1.安装环境以及初始化单页项目 (1)安装node.js node中会自带npm (2)网络不好时可用cnpm代替npm cnpm install npm -g (3)安装vue cnpm install vue ...(5)初始化vue项目 vue init webpack my-proj...

    vue重点难点
    1.安装环境以及初始化单页项目
    (1)安装node.js node中会自带npm
    (2)网络不好时可用cnpm代替npm cnpm install npm -g
    (3)安装vue cnpm install vue
    (4)安装vue-cli,用于快速搭建大型单页应用 cnpm install --global vue-cli
    (5)初始化vue项目 vue init webpack my-project
    (6)安装依赖包 cnpm install
    (7)运行项目 cnpm run dev
    (8)编译项目,编译项目后在dist文件夹下 cnpm run build
    2.MVVM
    MVVM是Model-View-ViewModel的简写。即模型-视图-视图模型。【模型】指的是后端传递的数据。【视图】指的是所看到的页面。【视图模型】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
    3.vue数据绑定
    (1)插值绑定
    文本插值–{{value}} data中声明变量value

    html插值<div v-html="innerhtml"></div>  
    

    data中声明变量用于存储html数据 innerhtml=<h1>这是一个标题</h1>
    (2)属性绑定
    v-bind指令 dom节点属性基本都可以用v-bind进行绑定

    <div v-bind:class="className" v-bind:title="title"></div> 
    <button v-bind:disabled="10+10==20"></button>
     <input v-bind:tyep="'text'"></input>
    

    v-bind可以省略 例如

    <p :class="className" :title="title"></p>
    

    (3)事件绑定

    v-on <button v-on:click="handle()">click</button> 
    

    也可简写为

    <button @click="handle()">click</button>
    

    按键

     <input type="text" @keyup.enter="handle()"></input>
    

    (4)双向绑定

    <input type="text" v-model="inputText"></input>
    

    (5)条件及列表
    v-if v-show v-if无实际dom节点 v-show存在dom节点,只是display:none
    v-if

    <h1 v-if="true">This is tittle</h1>
    

    v-if v-else-if

    <div v-if="type === 'A'">A</div>
    <div v-else-if="type === 'B'">B</div>
    <div v-else>C</div>
    

    v-for

    <ul>
    		<li v-for="(value, key) in lists">
    		{{ key }} : {{ value }}
    		</li>
    	</ul>
    

    4.声明周期
    在这里插入图片描述

    beforeCreate: function() {
    console.group(‘beforeCreate-------’);
    },
    created: function() {
    console.group(‘reated-----’);
    },
    beforeMount: function() {
    console.group(‘beforeMount-------’);
    },
    mounted: function() {
    console.group(‘mounted------’);
    },
    beforeUpdate: function () {
    console.group(‘beforeUpdate-----------’);
    },
    updated: function () {
    console.group(‘updated---------------’);
    },
    beforeDestroy: function () {
    console.group(‘beforeDestroy---------------------’);
    },
    destroyed: function () {
    console.group(‘destroyed -------------------’);
    }

    5.使用外部组件
    (1)js中引入子组件 import child from “child”
    (2)components中声明 components: {TreeGraph}
    (3)使用子组件
    6.组件间传值
    (1)js中引入子组件 import child from “child”
    (2)components中声明 components: {TreeGraph}
    (3)使用子组件
    (4)子组件通过props获取值 props: {data: Array}
    (5)使用data {{data}} this.data
    7.路由
    (1)通过npm安装vue-router
    (2)新建router.js import VueRouter from ‘vue-router’
    (3)

     Vue.use(VueRouter)
    	export default new VueRouter({
    		routes: [
    			{
    				path: '/parent',
    				name: 'parent',
    				component: parent
    			},
    			{
    				path: '/parent1',
    				name: 'parent1',
    				component: parent1,
    				children: [
    					{
    						path: '/child',
    						name: 'child',
    						component: child
    					} 
    				]
    			},
    		]
    	}) 
    

    (4)引入路由import router from ‘./router’
    (5)

     new Vue({
    	  el: '#app',
    	  router,
    	  components: { App },
    	  template: '<App/>',
    	  created() {
    	  }
    	})
    

    (6)页面使用路由

    <router-link to="/parent">路由测试</router-link>
      <div><router-view></router-view></div>
    

    8.获取接口资源

    npm install axios
    axios
          .get('url')
          .then(response => (this.info = response))
          .catch(function (error) { // 请求失败处理
            console.log(error);
          });
     axios
          .post("url", postJson)
          .then(response => {
            if (response.status == 200) {
                );
              });
            }
          })
    

    9.vuex

    展开全文
  • 1、组件三种挂载方式自动挂载var app3 = new Vue({el: '#app-3',data: {seen: true}})手动挂载// 可以实现延迟按需挂载 {{name}} 挂载var obj= {name: '张三'}var vm = new Vue({data: obj})function test() {vm.$...

    1、组件三种挂载方式

    自动挂载

    var app3 = new Vue({

    el: '#app-3',

    data: {

    seen: true

    }

    })

    手动挂载

    // 可以实现延迟按需挂载

    {{name}}

    挂载

    var obj= {name: '张三'}

    var vm = new Vue({

    data: obj

    })

    function test() {

    vm.$mount("#app");

    }

    // Vue.extend()创建没有挂载的的子类,可以使用该子累创建多个实例

    var app= Vue.extend({

    template: '

    {{message}}

    ',

    data: function () {

    return {

    message: 'message'

    }

    }

    })

    new app().$mount('#app') // 创建 app实例,并挂载到一个元素上

    2、路由传递参数的方式

    亲,请登录

    免费注册

    3、对render:h => h(App)的理解

    render:h=>h(App)是ES6中的箭头函数写法,等价于render:function(h){return h(App);}.

    1.箭头函数中的this是 指向 包裹this所在函数外面的对象上。

    2.h是creatElement的别名,vue生态系统的通用管理

    3.template:‘',components:{App}配合使用与单独使用render:h=>h(App)会达到同样的效果

    前者识别标签,后者直接解析template下的id为app的div(忽略template的存在)

    new Vue({

    el: '#app', // 相当于 new Vue({}).$mount('#app');

    template: '', // 1、可以通过在 #app 内加入替代 2、或者 通过 render: h => h(App) 渲染一个视图,然后提供给el挂载

    components: { // vue2中可以通过 render: h => h(App) 渲染一个视图,然后提供给el挂载

    App

    }

    });

    4、Vue.nextTick()的理解

    与DOM相关操作写在该函数回调中,确保DOM已渲染

    nextTick的由来:

    由于VUE的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

    nextTick的触发时机:

    在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)内的回调。

    应用场景:

    需要在视图更新之后,基于新的视图进行操作。

    在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因是什么呢,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

    在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。

    简单总结事件循环:

    同步代码执行 -> 查找异步队列,推入执行栈,执行callback1[事件循环1] ->查找异步队列,推入执行栈,执行callback2[事件循环2]...即每个异步callback,最终都会形成自己独立的一个事件循环。结合nextTick的由来,可以推出每个事件循环中,nextTick触发的时机:

    展开全文
  • 我们再使用mpvue开发小程序会遇到许多困难,下面就一些困难做一个关于mpvue开发小程序的难点重点总结框架的选择原生的小程序我本人并没有学习过,更别提拿来开发一款商用的小程序了,刚好还在前公司时,当时的前端...

    我们再使用mpvue开发小程序会遇到许多困难,下面就一些困难做一个关于mpvue开发小程序的难点重点总结

    框架的选择

    原生的小程序我本人并没有学习过,更别提拿来开发一款商用的小程序了,刚好还在前公司时,当时的前端团队在提到小程序的解决方案时有分享了mpvue,到了新公司之后技术老大也有提到mpvue,而我本人过去一年多也一直在写vue,对vue写法比较熟悉,而且新公司团队对小程序期待已久,希望尽快上架,所以选择mpvue来开发也是最快最合理的了!

    项目的搭建

    看了mpvue的官方文档,项目的搭建自然也选择了官方推荐的 vue-cli , 在看了五分钟上手教程后,使用命令

    vue init mpvue/mpvue-quickstart my-project

    生成了基本的项目,在后来的开发中,项目的配置基本没做改动,只是添加了less-loader。

    目录结构

    基本上是vue-cli生成的目录结构,加了部分文件夹,主要是与后台进行数据交互所使用的框架flyio的配置文件夹(api文件夹),以及整个项目数据管理所使用的vuex(store文件夹),整体目录结构如下:

    project

    └───build

    └───config

    └───dist

    └───node_modules

    └───src

    └───api

    | ajax.js // flyio请求与响应拦截器的配置文件

    | config.js // 请求的配置文件

    | index.js // 生成请求api实例文件

    | Server.js // 项目的数据请求统一管理文件

    └───components

    └───pages

    └───store

    └───modules // vuex模块文件夹

    | index.js // vuex处理文件

    | App.vue

    | config.js

    | main.js

    └───static

    └───images

    └───lib

    └───weui

    │ README.md

    │ package.json

    │ package-lock.json

    复制代码

    踩到的坑

    相信很多使用过mpvue的同学都或多或少猜到了一些坑,我也是踩到了不少的坑浪费了不少的宝贵时间,虽然网上关于mpvue的踩坑的文章一搜一箩筐,但我还是要写一下。。。下面就是我在本次小程序开发过程中遇到的坑(们)以及针对它们的解决方案:

    ### tabBar图标问题

    复制代码

    在 配置小程序原生的底部tabBar 时,遇到了第一个问题:在将设计师给我的图标icon路径设置正确的情况下, 开发者工具上的tabBar的图标总是会很大,而且几乎占满了整个高度 ,相当难看,搜了很多博客都没有找到解决办法,期间还尝试了自己实现tabBar,但是在看到那令人呕呕呕的效果之后,我还是放弃了,又回到原生的tabBar,然后静下心来想了想,最后在对比github上的一些mpvue的项目之后,发现原来是图标icon的问题,最后成功解决: 就是icon尺寸保持不变,然后四周留出合适的透明(?)空白 ...很简单有木有?就这浪费我很多脑细胞,原谅我的愚钝(智障脸)。。。当然了,原生的tabBar其实还有一个问题就是, tabBar的标题文字在真机上会离底部特别特别近 ,这个我没找到解决办法,除了自己实现tabBar。。。

    ### 详情页数据保留之前旧数据的问题

    复制代码

    这个问题我想很多同学都遇到过了,而且我看到mpvue github上的issues里面有很多人都遇到了这个问题并且都在持续关注,足以说明这是个痛点问题,谁让它会影响小程序的用户体验呢。。。到目前为止看到的比较统一的解决办法就是:在(详情)页面onLoad的时候,将要在本页面展示的数据初始化并且进行新的赋值,举:chestnut:如下:

    import htmlText from xxxxx

    export default {

    components: {

    htmlText

    },

    data () {

    return {

    htmltext: ''

    }

    },

    onLoad () {

    this.htmltext = ''

    this.$http.get('xxxxxxxx').then((res) => {

    this.htmltext = res.htmltext

    })

    }

    }

    复制代码

    其他数组或者对象类型的处理可能会麻烦一些,但是方法类似,在数据请求返回之前的这段时间内不想留空白尬对用户的话就自己做一些loading,总是要强过用户先面对旧数据再一闪跳到新数据的体验。。。

    ### created钩子函数在项目初始化时就全部执行的问题

    复制代码

    这个我想应该是mpvue的一个bug吧?该钩子函数在页面内还是不要随便用的好。。。

    ### 目前mpvue对于复杂富文本的支持目前性能较差的问题

    复制代码

    ### 微信原生的路由跳转navigateTo(),redirectTo(),navigateBack(),switchTab(),reLaunch()等,在真机上的表现较为怪异

    复制代码

    对于参数的传递,我也遇到过类似于旧数据的问题,最后不得已借助于vuex才得以解决。另外小程序的页面栈个数实在有限,所以在开发时一定要注意页面栈的管理。

    ### onShow()的使用要注意

    复制代码

    要记得该钩子函数里的js代码不只是刚进入页面时会执行,在息屏后再次点亮后也将会执行。

    对于mpvue的坑突然能想起来的不多了,目前就先写这么多,后面想起来了再来更新吧。

    Flyio的使用

    在小程序的开发中,并没有使用小程序原生的wx.request()来进行数据交互,而是选择了mpvue文档里推荐使用的Flyio,Flyio的介绍就不多做介绍,打架可以自己看文档,这里我主要说一下的 请求和响应拦截器的构造 :

    文档里其实有很详细的介绍以及代码,但是我根据代码写下来之后在遇到登录失效的问题时并没有按照预想的解决:先锁住请求然后重新请求拿到新的cookie之后再重新进行之前的请求,再和其他人讨论之后使用promise解决了这一问题,具体可见代码:

    src/api/ajax.js:

    /**

    * http请求拦截器

    */

    const Fly = require('flyio/dist/npm/wx')

    const config = require('./config')

    const ajaxUrl =

    process.env.NODE_ENV === 'development'

    ? config.Host.development

    : process.env.NODE_ENV === 'production'

    ? config.Host.production

    : config.Host.test

    let fly = new Fly()

    let loginFly = new Fly()

    // 定义公共headers

    const headers = {

    ...

    }

    Object.assign(fly.config, {

    headers: headers,

    baseURL: 'xxxxxx',

    timeout: 10000,

    withCredentials: true

    })

    loginFly.config = fly.config

    // session失效后本地重新登录

    const login = () => {

    return new Promise((resolve, reject) => {

    wx.login({

    success: res => {

    let loginParams = {

    ...

    }

    loginFly.post('/api/locallogin/url', loginParams).then(d => {

    if (d.headers && typeof d.headers['set-cookie'] !== 'undefined') {

    // 更新session

    wx.setStorageSync('sessionid', d.headers['set-cookie'])

    }

    resolve()

    }).catch(error => {

    log(error)

    reject(res.data)

    })

    },

    fail: res => {

    console.error(res.errMsg)

    },

    complete: res => {}

    })

    })

    }

    // 请求拦截器

    fly.interceptors.request.use(request => {

    if (wx.getStorageSync('sessionid')) {

    request.headers.cookie = wx.getStorageSync('sessionid')

    }

    return request

    })

    // 响应拦截器

    fly.interceptors.response.use(

    response => {

    // session已经失效,需要重新登录小程序

    if (response.data.errCode === 100009) {

    // log('session失效,根据之前存储在本地的用户信息重新请求session...')

    // 锁定响应拦截器

    fly.lock()

    return login().then(() => {

    fly.unlock()

    // log(`重新请求:path:${response.request.url},baseURL:${response.request.baseURL}`)

    return fly.request(response.request)

    }).catch(err => {

    log(err)

    })

    } else {

    return response.data.data

    }

    },

    err => {

    log('error-interceptor', err)

    if (err.status) {

    wx.showToast({

    title: '出现未知错误',

    icon: 'none',

    duration: 3000

    })

    }

    }

    )

    export default fly

    复制代码

    vuex的使用

    因为是生活购物类小程序,涉及到 购物车 + 地址选择 等较为复杂的逻辑,很多地方都需要数据共用,在本期项目中vuex起了很大的作用,因为模块较多,如果将所有数据写在一个文件里无疑会为后期维护带来巨大困难,所以将各模块的数据单独划分写在各自的文件里,这样整体流程就清晰了很多,下面是划分模块的主文件的代码

    src/store/index.js:

    import Vue from 'vue'

    import Vuex from 'vuex'

    import modules1 from './modules/modules1'

    import modules2 from './modules/modules2'

    import modules3 from './modules/modules3'

    ...

    Vue.use(Vuex)

    export default new Vuex.Store({

    // 做模块化处理,每个功能一个store.js文件,然后统一在这边引入

    modules: {

    modules1,

    modules2,

    modules3,

    ...

    }

    })

    复制代码

    src/store/modules/modules1.js:

    import api from '@/api' // actions里请求用到

    const state = {

    aaaa,

    ...

    }

    const getters = {

    aaaa (state) {

    return state.aaaa

    },

    bbbb (state, getters, rootState) {

    return getters.aaaa

    },

    ...

    }

    // actions里可进行异步操作

    const actions = {

    async anExample ({state, getters, dispatch, commit}, {params}) {

    let res = await api.requestFunction({params})

    ...

    return res

    },

    ...

    }

    const mutations = {

    setStateX (state, Y) {

    state.X = Y

    },

    ...

    }

    export default {

    namespaced: true, // 很重要

    state,

    getters,

    actions,

    mutations

    }

    复制代码

    在.vue文件中调用

    src/pages/xxx.vue

    import { mapState, mapGetters } from 'vuex'

    export default {

    computed: {

    // 调用getters

    ...mapGetters('modules', [

    'aaaa',

    'bbbb'

    ])

    },

    methods: {

    // 调用action

    funcA () {

    this.$store.dispatch('modules1/anExample', {params}).then(res => {

    ...

    })

    },

    // 调用mutation

    funcB () {

    this.$store.commit('modules1/setStateX', Y)

    }

    }

    }

    复制代码

    HiShop小程序工具提供多类型商城/门店小程序制作,可视化编辑 1秒生成5步上线。通过拖拽、拼接模块布局小程序商城页面,所看即所得,只需要美工就能做出精美商城。更多小程序商店请查看:小程序商店

    展开全文
  • 本文分析vue1.0源代码从入口开始到编译输出到网页显示生效的整个过程,并重点分析一些vue源代码设计原理。 vue初始化根组件的入口代码: 对于没有路由的单页应用来说,入口就是new Vue(options),options就是根...

    本文分析vue1.0源代码从入口开始到编译输出到网页显示生效的整个过程,并重点分析一些vue源代码设计原理。

    vue初始化根组件的入口代码:

    对于没有路由的单页应用来说,入口就是new Vue(options),options就是根组件代码,对于有路由的项目来说,入口就是router.start(app),其中app是根组件。

    function Router() {} // router构造函数
    var router = new Router(); // new router实例
    router.start(app); // 项目真正的入口,app是根组件代码(对象)

    Router.prototype.start = function start(App, container, cb) {
      this._appContainer = container; //把app和占位元素container保存在router实例
      var Ctor = this._appConstructor = typeof App === 'function' ? App : Vue.extend(App); // 根组件App继承Vue类(js用构造函数和prototype属性模仿类)
      this.history.start();

    HashHistory.prototype.start = function start() {
      self.onChange(path.replace(/^#!?/, '') + query); //当浏览器地址中hash部分变化时触发执行listener(),listener会格式化浏览器地址中hash部分(hash如果没有/则加/前缀),再执行onChange,传入path(url地址的hash部分)

    onChange: function onChange(path, state, anchor) { // url地址中hask部分变化触发执行此方法执行进行路由组件切换,类似input元素的输入触发onchange
      _this._match(path, state, anchor);

    Router.prototype._match = function _match(path, state, anchor) {
      var _this3 = this; // this是router实例
      var route = new Route(path, this);  // 这是route实例,含路由切换信息from/to,不是router实例。
      var transition = new RouteTransition(this, route, currentRoute); //new transition实例时传入router实例,transition是路由切换from/to相关处理,还有一个transition是路由组件切换时的过渡效果,别搞混了
      if (!this.app) { // 如果是第一次执行,还没有new根组件实例
        _this3.app = new _this3._appConstructor({ //new根组件实例,构造函数是function VueComponent (options) { this._init(options) },new根组件实例保存在router实例中,router.app就是根组件实例,router实例会保存在根组件实例中。


    在new Vuecomponent()实例化时要执行Vuecomponent构造函数,Vuecomponent是继承Vue的,就是要执行Vue的_init初始化函数,就开始初始化根组件,编译根组件
    template。
    vue的钩子函数created()就是指new组件实例之后,而ready()就是指编译组件template之后,先new组件实例,再编译组件template,因为是在创建组件实例之后才
    执行构造函数Vue才执行_init()开始初始化编译,编译完成之后插入网页生效,是异步过程,所以执行ready()时可能插入网页生效还没有真正完成,如果ready()有
    代码需要在网页找组件元素,比如初始化轮播的代码,可能就需要延迟执行等插入网页生效完成,否则就会出现无法理解的异常现象。

    router实例会保存在根组件实例中,传递给各个子组件,因此vue的各个组件访问router实例非常方便。

    Vue.prototype._init就是vue组件入口/初始化函数,这是组件通用的初始化函数,是处理组件的核心函数,因为vue是从根组件开始递归编译所有的子组件,
    所有的子组件都要跑一遍这个方法,所以这个方法就是vue的顶层核心方法,在底层有几十几百个方法进行各种处理。

    先从_init开始把组件初始化过程从头到尾大概走一遍如下(只列举主要语句):

    Vue.prototype._init = function (options) {
      this._initState(); //之后el从id变为网页元素DOM(引用网页元素)
      if (options.el) {
        this.$mount(options.el); //如果根组件写了template,el就是根组件占位元素,否则el就是online template元素,从根组件开始递归编译所有的子组件子节点
      }

        Vue.prototype._initState = function () {
          this._initProps();
          this._initMeta();
          this._initMethods();
          this._initData();
          this._initComputed();
        };

          Vue.prototype._initProps = function () {
            // make sure to convert string selectors into element now
            el = options.el = query(el);
          }


    Vue.prototype.$mount = function (el) {

      this._compile(el); //从根组件开始递归编译所有的子组件子节点,然后插入网页生效
      return this;
    };


    Vue.prototype._compile = function (el) {
      var original = el;

      el = transclude(el, options);

      var rootLinker = compileRoot(el, options, contextOptions); // compile产生link函数
      var rootUnlinkFn = rootLinker(this, el, this._scope); // 执行link函数,插入网页生效 

      var contentUnlinkFn = contentLinkFn ? contentLinkFn(this, el) : compile(el, options)(this, el); //编译产生link()再执行link()


      if (options.replace) {
        replace(original, el); //可能需要替换更新
      }

        function compileRoot(el, options, contextOptions) {
          replacerLinkFn = compileDirectives(el.attributes, options); //root元素<div id=  没什么要编译的
          return function rootLinkFn(vm, el, scope) {
        }

          function compileDirectives(attrs, options) {}

        function compile(el, options, partial) {  // 编译组件的template,el是组件占位元素,options是组件代码
          var nodeLinkFn = compileNode(el, options) //编译根节点
          var childLinkFn = compileNodeList(el.childNodes, options) // 递归编译子节点
          return function compositeLinkFn(vm, el, host, scope, frag) {  // 编译返回的通用link函数

              function makeChildLinkFn(linkFns) { // 产生子节点link函数
                return function childLinkFn(vm, nodes, host, scope, frag) { // 子节点link函数返回给var childLinkFn


              function compileNodeList(nodeList, options) {  // 会递归调用自己,层层递归所有子节点
                nodeLinkFn = compileNode(node, options); //编译一个节点
                childLinkFn = compileNodeList(node.childNodes, options) //编译一个节点的子节点,递归编译子节点
                return linkFns.length ? makeChildLinkFn(linkFns) : null; //nodeLinkFn和childLinkFn在linkFns中

                    function compileNode(node, options) {  // 编译一个节点
                      var type = node.nodeType;
                      if (type === 1 && !isScript(node)) {
                        return compileElement(node, options);
                      } else if (type === 3 && node.data.trim()) {
                      return compileTextNode(node, options);

                          function compileElement(el, options) { // 到这里要解析处理页面元素表达式中的指令属性包括标签组件,细节本文忽略
                            // check terminal directives (for & if)
                            if (hasAttrs) {
                              linkFn = checkTerminalDirectives(el, attrs, options);
                            }
                            // check element directives
                            if (!linkFn) {
                              linkFn = checkElementDirectives(el, options);
                            }
                            // check component
                            if (!linkFn) {
                              linkFn = checkComponent(el, options);
                            }
                            // normal directives
                            if (!linkFn && hasAttrs) {
                              linkFn = compileDirectives(attrs, options);
                            }
                            return linkFn;

                              function checkTerminalDirectives(el, attrs, options) {  // 编译处理指令
                                def = resolveAsset(options, 'directives', matched[1]); //每种每个属性指令都有一套方法找出来
                                return makeTerminalNodeLinkFn(el, dirName, value, options, termDef, rawName, arg, modifiers);

                                  function makeTerminalNodeLinkFn(el, dirName, value, options, def, rawName, arg, modifiers) {
                                    var parsed = parseDirective(value); //指令表达式字符串如果没有|,就无需解析,直接返回指令表达式字符串
                                    var fn = function terminalNodeLinkFn(vm, el, host, scope, frag) {
                                      if (descriptor.ref) {
                                        defineReactive((scope || vm).$refs, descriptor.ref, null);
                                      }
                                      vm._bindDir(descriptor, el, host, scope, frag);
                                    };
                                    return fn;

                                        Vue.prototype._bindDir = function (descriptor, node, host, scope, frag) {
                                          this._directives.push(new Directive(descriptor, this, node, host, scope, frag));
                                        };

    至此解析编译节点的指令表达式结束,然后执行link函数:

    function compositeLinkFn(vm, el, host, scope, frag) {
      var dirs = linkAndCapture(function compositeLinkCapturer() {
        if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag);
        if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag);
      }, vm);

    function linkAndCapture(linker, vm) {
      linker(); //增加新解析的directive实例存储到vm._directives中
      var dirs = vm._directives.slice(originalDirCount); //取最新增加的directive实例
      sortDirectives(dirs);
      for (var i = 0, l = dirs.length; i < l; i++) {
        dirs[i]._bind(); //执行每个directive实例的_bind方法,比如v-for是一个directive实例,每个循环项又是一个directive实例,组件标签也是一个directive实例
      }
      return dirs;
    }

    linker()就是要执行编译阶段产生的link函数:
    function childLinkFn(vm, nodes, host, scope, frag) {
      nodeLinkFn(vm, node, host, scope, frag);
      childrenLinkFn(vm, childNodes, host, scope, frag); //childrenLinkFn就是childLinkFn,子节点递归调用

      nodeLinkFn()就是编译阶段产生的那个fn:

      var fn = function terminalNodeLinkFn(vm, el, host, scope, frag) {
      if (descriptor.ref) {
        defineReactive((scope || vm).$refs, descriptor.ref, null);  // 如果有v-ref="xxx" 引用组件对象,这也是一种指令属性
      }
      vm._bindDir(descriptor, el, host, scope, frag);  // bind就是初始化方法,bindDir就是初始化指令的方法
    };

    要执行new Directive(descriptor, this, node, host, scope, frag),然后把directive实例保存到vm._directives中,
    new Directive时只是把之前compile/link阶段产生的数据方法保存到directive实例中,并无其它处理,因此只是准备数据,保存到实例,后面再执行实例中的初始化方法初始化指令。


    Directive.prototype._bind = function () { // 这就是dirs[i]._bind()方法代码,元素节点每个属性指令都有一个directive实例都有一个通用的bind初始化方法
      // setup directive params
      this._setupParams();
      this.bind(); //每个指令实例中的bind方法是specific bind方法,通用_bind方法调用specific bind方法,每个指令都不一样。
        var watcher = this._watcher = new Watcher(this.vm, this.expression, this._update, // 凡是要获取表达式/变量值都要创建watcher,会把watcher加入到在求值过程中依赖的变量属性的dep中。
        this.update(watcher.value); //更新属性指令网页数据,比如id="$index"要把变量值插入,比如{{item.name}}要把变量值插入,网页数据更新之后就处理完了,更新网页是最后一步,每种属性指令的update方法都不一样。

    可见vue处理指令的关键成分是watcher和指令实例中的bind/update初始化函数方法,在编译阶段处理所有的指令属性,new directive实例,保存起来,在link阶段只要把所有保存的指令实例都执行一遍bind方法进行初始化就完成了所有的指令初始化工作,包括标签组件也是要走指令流程的,只不过标签组件还要走一遍component处理流程,要new Vuecomponent()实例执行构造函数从_init()开始把compile()流程再走一遍完成组件template的编译,组件template中可能又有指令属性,就又要把上述流程走一遍完成对指令属性的编译处理,比如v-for="item in items"。

    所以vue的通用指令处理程序和组件处理程序会从编译根组件开始被反复执行多次,递归所有的子组件,所有的子节点,层层嵌套,每个指令每个子组件都要执行一遍核心编译处理程序,如果在核心编译程序debug看,会有几十几百次输出,能看到所有的指令/组件的数据,还是非常复杂的。

    以v-for指令为例来看一段指令的源代码:
    var vFor = { //
      bind: function bind() {
        if ('development' !== 'production' && this.el.hasAttribute('v-if')) {
          warn('<' + this.el.tagName.toLowerCase() + ' v-for="' + this.expression + '" v-if="' + this.el.getAttribute('v-if') + '">: ' + 'Using v-if and v-for on the same element is not recommended - ' + 'consider filtering the source Array instead.', this.vm);
        } //不推荐同时使用v-for和v-if是在这儿检查告警的
        this.start = createAnchor('v-for-start'); //text节点
        this.end = createAnchor('v-for-end');
        replace(this.el, this.end); //先用占位元素替换掉v-for元素,等编译好v-for元素之后再插入网页到占位元素位置生效
        before(this.start, this.end);
        this.factory = new FragmentFactory(this.vm, this.el); //此过程会compile(this.el)生成linker保存在fragment实例中

          function FragmentFactory(vm, el) {
            linker = compile(template, vm.$options, true); // 编译指令template,v-for指令是一个数组循环产生一个列表


    router切换路由组件时涉及transition过渡效果,所以相关代码是封装在transition中:
    var startTransition = function startTransition() {
      transition.start(function () {
      _this3._postTransition(route, state, anchor);

        RouteTransition.prototype.start = function start(cb) {
          var view = this.router._rootView; //_rootView是<router-view>指令实例,里面有app组件构造器
          activate(_view, transition, depth, cb); //transition里面有要切换的子页面路由和component构造函数对象,里面有options(组件代码)

            function activate(view, transition, depth, cb, reuse) { //depth对应子路由
              var handler = transition.activateQueue[depth];
              if (!handler) {
                view.setComponent(null);
                component = view.build({ //view就是占位标签router-view指令实例,build方法就是var component={}中的build方法

                  build: function build(extraOptions) {
                    var child = new this.Component(options); //options就是之前准备好的组件构造器含组件代码,这就是new 路由组件实例化
                    return child;

                      Vue[type] = function (id, definition) { //这就是vue component的构造函数的构造函数
                        definition = Vue.extend(definition);
                        this.options[type + 's'][id] = definition;
                        return definition;

    view.transition(component);
      component.$before(view.anchor, null, false);

      //Actually swap the components
      transition: function transition(target, cb) { //var component={}中的transition方法,target是组件实例,实例中el是已经编译产生的DOM对象,可以直接插入网页生效。
        self.remove(current);
        target.$before(self.anchor, cb);

          Vue.prototype.$before = function (target, cb, withTransition) { //此时target变为anchor占位元素(text节点)
            return insert(this, target, cb, withTransition, beforeWithCb, beforeWithTransition);

              function insert(vm, target, cb, withTransition, op1, op2) {

                function beforeWithCb(el, target, vm, cb) {
                  before(el, target);
                  if (cb) cb();
                }

                function beforeWithTransition(el, target, vm, cb) {
                  applyTransition(el, 1, function () {
                    before(el, target);
                  }, vm, cb);


    有两个细节提一下:
    function View (Vue) {
      Vue.elementDirective('router-view', viewDef);

    在View构造函数中定义了router-view这个指令/组件。


    下面是vue的on方法,是用底层js事件方法:
    function on(el, event, cb, useCapture) {
      el.addEventListener(event, cb, useCapture);
    }

    vue没有再定义一套事件机制,就是从上层封装直接调用底层js方法来定义事件绑定,比肩简单。
    要注意,cb中已经引用组件实例,所有的组件实例都是保存在根组件实例中,按id索引。当点击页面执行方法时,页面并没有作用域指引,方法也不在全局空间,
    就是在js的events[]中找handler执行,关键关键关键是handler方法代码中已经引用组件实例,这就是作用域。
    它如何引用组件实例呢?估计很少有人想过这个问题。
    先new router实例,要么在全局空间创建router实例,要么把router实例保存在全局,再new 根组件实例,保存在router实例,再new 组件实例,保存在根组件,组件实例的方法用this引用组件实例,因此引用组件实例就是引用保存在全局的router实例中的属性,js对象引用机制会导致能引用
    到组件实例,这个可是很关键的,否则就全乱套了。


    其它的框架大都自己又定义了一套事件机制,就复杂了。

    组件构造函数Vuecomponent用Vue.extend继承vue构造函数,每个组件只是options不同,源代码中组件的初始化方法:
    var component = {
      bind: function bind() {
        this.setComponent(this.expression);

          build: function build(extraOptions) { //入口参数就是组件的代码
            extend(options, extraOptions);
            var child = new this.Component(options); //就是new VueComponent(),就是new Vue()实例,只不过是子实例。

          setComponent: function setComponent(value, cb) {
            self.mountComponent(cb);

              mountComponent: function mountComponent(cb) {
                var newComponent = this.build();


    路由组件与页面组件不同,页面组件在编译template时会创建一个directve指令实例,里面有组件的id和descriptor,而所有的组件定义代码都在components[]中可以找到。
    路由组件是new Vuecomponent()创建组件实例时执行_init() -> $mount(el)开始编译template,指令组件是在编译template过程中层层递归扫描html元素节点找到指令标签再编译处理指令标签。

     

    根组件实例只在执行入口初始化时创建一次,每次路由变化时,会执行一次_match,this.app指根组件/主模块,根组件实例创建之后一直存在,不会再执行new _appConstructor()初始化根组件。

    vue处理的对象主要是组件和指令,主要用vue构造函数和directive构造函数来处理组件和指令,new实例时就是初始化组件和指令,编译组件时从根节点开始递归循环
    扫描所有的子节点,只要有组件/指令都是用同样的通用代码处理,所以只要上述两个构造函数对象写ok了,把编译循环递归写ok了,就基本成功了,不管template有
    多复杂有多少层节点嵌套,有多少层组件嵌套,都是用通用方法和循环递归处理。
    还有watcher构造函数,是为了实现数据更新同步。

    Vue内置指令router-view,slot,v-for,v-if,v-model,是重要指令,每个指令有一套方法比如bind,update,都已经设计好,如果自定义指令,需要自己写方法,最起码
    要写bind方法(初始化方法)。

    从初始化根组件开始执行vue._init,如果根元素页面有调用组件,则每个组件都再执行一遍vue._init,从根组件开始每个组件
    都初始化一个组件实例,从根元素开始每个节点都要编译处理一遍,编译之后产生link函数再执行link函数。对于每个属性指令
    则执行一遍new Directive()创建一个directive实例,相关数据方法都保存在实例中,主要是el和update方法,
    如果属性指令写在组件标签元素内,则el是编译之后插入网页的<div>元素,执行update方法更新网页时是针对网页中这个<div>
    进行属性修改,指令实例保存在所属的组件实例中,路由组件中定义/调用的子组件都保存在组件实例的options.components下。

    页面每个表达式都都会创建watcher,组件中被页面绑定的每个属性变量都会创建set/get方法,包含所有依赖自己的watcher,
    watcher中的update方法会调用指令的update方法更新页面,比如:class指令的update方法就是修改所在元素的class属性,
    class="{{}}"也是指令,更新时也是要修改所在元素的class属性。
    vue自带指令保存在options.directives中,对于<test>这样的自定义标签,是已经用component定义的组件,它是指令组件,
    既是指令也是组件,在当前页面组件实例的options.components下有,在_directives下也有,指令组件要把component流程
    和directive流程都走一遍。


    编译一个节点/属性,只是准备数据,产生link函数,执行link函数才初始化生效,比如执行指令的初始化方法,
    对于组件指令,初始化方法最后要执行mountComponent才把template元素插入网页生效,之前的所有处理都是在做数据准备


    每个指令都是事先定义好的,有bind/update方法,自定义指令也有方法,在页面调用指令时,
    编译时查指令集合,获取指令的相关数据,然后new directive创建一个指令实例,保存在当前子页面组件实例中,编译就
    大功告成了,然后执行link函数,把当前页面组件中调用的所有指令_directives[]都执行一遍初始化方法即可,指令的初始化
    方法也是更新方法,就是是设置/修改指令所在的元素的属性,对于组件指令,就更复杂,要new component()创建组件实例,
    再执行组件指令的初始化方法,执行mountComponent完成最后一个环节,把组件template元素插入网页生效,所以组件指令
    既是组件又是指令,component和directive两种流程都要走一遍,非常复杂。在组件指令写属性,处理起来是最复杂的,
    要把<test>变成<div>,标签原始属性复制到<div>,但标签原始属性的作用域是调用标签的页面组件,也就是标签组件的父组件,
    而<div>中replacer属性,也就是写在template中的<div>中的属性,作用域是标签组件本身,除此之外,所有组件/指令处理
    流程都是一样的,只是不同的组件/指令,它们的数据方法不同。


    再小结一下:

    组件走Vue构造器流程,指令走Directive构造器流程,组件指令两个流程都走,看Vue和Directive代码,要清楚每个组件,每个指令,都要执行‘
    一遍同样的代码,代码会被多次执行,每次执行都是针对一个不同的组件/指令。编译方法中compileNodesList要递归所有的子节点,包括看不见的文本节点,
    compileNode编译每个节点时要遍历所有的属性,html属性,指令属性,props属性,处理方法是不同的。组件指令既是组件又是指令,两种流程都要走一遍。
    组件/指令都是事先定义好的,数据方法都已经有了,都保存在根组件Vue实例中,再继承到各个子组件实例中,编译时就是要判断标签/属性是不是已知指令,
    如果是,就把数据准备一下,然后new directive实例保存在当前组件实例中,再执行link函数,link函数会执行指令的初始化函数,初始化函数会最后完成
    网页更新,对于组件指令就是要把template元素插入网页生效。
    为了实现数据同步,还要针对组件的属性和页面绑定的变量包括方法创建watcher,在初始化指令时要进行watcher相关处理,这是另外一种主要的流程,贯穿在
    所有主要流程中。



    vuex/store是vue作者参考flux/mutation实现原理自己写了一套代码实现了缓存数据方法,并不是第三方插件,调用
    vuex/store初始化缓存的方式就是:

    new Vuex.Store({
      modules: {
       userInfo: {
        state: {
          userInfo : {
            username: ''
          }

        }
      },
      mutations: {
        ['SETUSERINFO'] (state, data) {
          state.userInfo.username = data.username;
        }
      }
    }
    },


    组件调用vuex/store数据方法的方式是:
    import {getMenus} from '../vuex/getters/user_getters'
    import {setUserInfo} from '../vuex/actions/user_actions'
    store : store,
    vuex : {
      getters : {
        getMenus : (state) => {
          return state.userInfo.globle.menus;
        }
      },
      actions : {
        setUserInfo : ({dispatch},data) => {
          dispatch('SETUSERINFO',data);
        }

      }
    }

    vuex安装到vue的相关源代码:
    function vuexInit() {
      var actions = vuex.actions;
      options.methods[_key] = makeBoundAction(this.$store, actions[_key], _key); // 给action方法增加一层封装,所以你debug看组件实例里面的action方法并不是你自己写的原始方法,而是一个封装方法,任何框架只要访问全局store数据本质上都是加一层封装,由封装方法再调用真正的action方法,由于封装方法在store相关的程序空间,可以访问store/dispatch,可以传递给原始action方法,因此你写的原始action方法无需涉及store/state,可以写形参dispatch,实际调用时会自动传递dispatch,这就是action方法的奥秘,react的reducer/action方法本质原理上也是一样的。


    store.js:
    Vue.use(Vuex); //初始化时会安装vuex插件到vue,也就是执行vuex的install程序。

    vuex.js:
    function install(_Vue) {
      override(Vue); //改写vue
    }
      function override (Vue) {
        var _init = Vue.prototype._init;


          Vue.prototype._init = function () { //改写vue原来的_init方法,其实还是调用原来的_init,但给options增加init=vuexinit方法
            var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
            options.init = options.init ? [vuexInit].concat(options.init) : vuexInit;
            _init.call(this, options);

              Vue.prototype._init = function (options) {
                this._callHook('init'); //会调用options.init,也就是调用vuexinit方法

                  Vue.prototype._callHook = function (hook) {
                    this.$emit('pre-hook:' + hook);
                    var handlers = this.$options[hook];
                    if (handlers) {
                      for (var i = 0, j = handlers.length; i < j; i++) {
                        handlers[i].call(this);

    因此vuex扩展到vue之后,在new Vue(options)创建组件实例时,会执行vuexinit方法处理options中的vuex:{}表达式,会在组件中创建setUserInfo方法,
    创建的方法代码是在原始action方法加了一层封装。


    vue用set/get方法实现数据响应,其关键代码在于:
    function defineReactive (obj, key, val) {
      var dep = new Dep()
      var childOb = observe(val) //如果属性有层次,要递归处理每一层属性

      Object.defineProperty(obj, key, { //这是针对数据对象和里面的属性创建set/get,是为了实现同步功能,
    //组件定义的数据属性在new Vue()时复制到this._data。
        enumerable: true,
        configurable: true,
        get: function(){
          if(Dep.target){ // 在针对页面表达式创建new watcher()时会调用get方法取值,并把new watcher实例保存在Dep.target,Dep.target就是当前正在创建的watcher实例,这个地方挺不容易看懂的,因为要懂整体设计逻辑才能看懂,
            dep.addSub(Dep.target); //把watcher实例保存到属性对应的dep中,因此依赖obj的属性的watcher都会保存到属性中
            Dep.target = null;

          }
          return val
        },
        set: function(newVal){
          var value = val
          if (newVal === value) {
            return
          }
          val = newVal
          childOb = observe(newVal)
          dep.notify() // 执行属性中保存的watcher的update方法更新组件/指令的页面
        }
      })
    }

    其中dep的方法层层封装非常绕,详细代码本文忽略。

    vuex/store也是用get/set方法实现数据响应,是通过加了一层computed方法实现的,computed方法再调用getter方法访问store的属性数据,computed方法会创建
    watcher,当调用vuex getter方法获取属性的值时会把自身watcher实例保存在属性中,当set属性时就会执行watcher的update方法更新data属性,页面可以绑定
    data属性,当data属性变化时,页面会更新,因为vue已经针对页面绑定表达式创建了watcher,针对data属性创建了get/set方法,已经建立了数据响应机制。
    所以当使用computed方法时,还是很复杂很绕的,当数据变化时,从store到页面是通过好几层watcher实现数据响应的,页面表达式有一层watcher,cb是指令的
    update方法,比如对于{{title}}这样最简单的表达式其实就是一个文本指令,其update方法就是要取title的值插入做为文本内容,computed方法又有一层watcher,
    cb=null,因为computed方法只是执行方法代码获取值,不同于指令,没有cb,无需处理网页元素。

    下面是vue构造comptedGetter方法的代码:
    function makeComputedGetter(store, getter) {
      var id = store._getterCacheId;
      if (getter[id]) {
        return getter[id];
      }
      var vm = store._vm;
      var Watcher = getWatcher(vm);
      var Dep = getDep(vm);
      var watcher = new Watcher(vm, function (vm) { // 创建watcher,在执行getter方法取值时把自身保存到属性中
        return getter(vm.state);
      }, null, { lazy: true });
      var computedGetter = function computedGetter() { //computedGetter
        if (watcher.dirty) {
          watcher.evaluate();
        }
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value;
      };
      getter[id] = computedGetter;
      return computedGetter;
    }

    watcher实例里面,proto里面有一个update方法,这是watcher通用update方法,还有一个cb,这是指令通用update方法,
    指令通用update方法会调用指令specific update方法(如果有的话)。


    下面是关于在组件标签用v-on:绑定事件的代码细节:
    //Register v-on events on a child component
    function registerComponentEvents(vm, el) {
      name = attrs[i].name;
      if (eventRE.test(name)) {
        handler = (vm._scope || vm._context).$eval(value, true); //v-on=后面的写法需要解析处理一下
        handler._fromParent = true; //事件在当前初始化的组件,方法在父组件
        vm.$on(name.replace(eventRE), handler); // 绑定逻辑事件,逻辑事件用$emit触发

    事件用addeventlistener和$on两种方法绑定,如果是物理事件,比如物理click事件,前者有效,如果是逻辑事件,前者
    无效,后者有效,后者是vue自己建立的_events[]数据,handler方法在父组件。
    在标签写v-on只能绑定组件里面的逻辑事件,组件里面用$emit触发逻辑事件,执行父组件里面的handler方法,不能监听子组件里面的物理click事件,
    在子组件里面才能写v-on绑定物理click事件。



    下面是关于checkbox元素用v-model双向绑定的源代码分析,checkbox handler代码:
    var checkbox = {
      bind: function bind() {

        this.listener = function () {
          var model = self._watcher.get(); //获取v-model=表达式的值,也就是变量属性值option.checked,涉及v-for循环变量/scope
          if (isArray(model)) {
          } else {
            self.set(getBooleanValue()); //获取元素checked属性值,传递给set方法
          }

    Directive.prototype.set = function (value) {
      if (this.twoWay) {
        this._withLock(function () {
          this._watcher.set(value);

            Watcher.prototype.set = function (value) {
              this.setter.call(scope, scope, value); //调用变量属性的setter方法设置值

                function (scope, val) { //watcher的setter
                  setPath(scope, path, val);

                    function setPath(obj, path, val) {
                      obj[key] = val;
                      set(obj, key, val);//如果属性不存在,就调Vue.set添加属性?
                      this.getValue = function () {
                        return el.hasOwnProperty('_value') ? el._value : self.params.number ? toNumber(el.value) : el.value;
                      };

                        function getBooleanValue() {
                          var val = el.checked;
                          if (val && el.hasOwnProperty('_trueValue')) {
                            return el._trueValue;
                          }
                          if (!val && el.hasOwnProperty('_falseValue')) {
                            return el._falseValue;
                          }
                          return val;
                         }
                      this.on('change', this.listener);


    可见v-model底层就是绑定'change'事件,就是获取元素checked属性值,可以重新定义checked属性值,那就取定义的值,一般不。
    当点击checkbox元素时,其checked属性会变化,v-model获取元素checked属性值同步到变量,反之则是根据变量值设置元素
    的checked属性值,从而实现两个方向的数据同步。

    vue初始化组件时处理template的代码:

    function transcludeTemplate(el, options) {
      var template = options.template;
      var frag = parseTemplate(template, true);

        function parseTemplate(template, shouldClone, raw) {
          if (typeof template === 'string') {
            //template如果写#id,则去网页按id找元素,这已经是DOM对象了
            //如果template是html字符串,则用innerHTML编译为DOM对象再插入到frag
            frag = stringToFragment(template, raw);

                function stringToFragment(templateString, raw) {
                  node.innerHTML = prefix + templateString + suffix; //前后缀是有可能包裹一层标签
                  while (child = node.firstChild) {
                    frag.appendChild(child);
                    //这是移动子元素,循环结果把所有子元素都移动插入到frag中去,template可以写多个<div>

                      function nodeToFragment(node) {
                        // script template,template可以写在网页的<script>标签中
                        if (node.tagName === 'SCRIPT') {
                          return stringToFragment(node.textContent);
                        }

    因此在组件中写template=只能是html代码字符串,或者#id指向网页中的元素,没有其它写法,不能写js表达式,
    也不能用js代码修改template,按id找元素可以把template写在网页的<script>中,类似angular,也可以写在网页中的
    <template>标签元素中:
    <template id="t1">
    <div>{{context}}</div>
    </template>

    如果构造<template>插入网页,<template>类似frag,本身是虚拟节点,在网页不占节点。

     

    本文到此就差不多结束了,本文写的比较粗糙,因为vue 1.0源码是以前研究的,当时没有很好地整理出来,后来复制了几段源代码分析记录,大概编辑了一下就此发表,总比烂在肚子里强,本人对vue 1.0和2.0进行了深入的研究,进行了无数次debug看数据,因为本人认为vue非常优秀实用,值得学习,而angular和react太高大上,一般人没法学习,文中错误欢迎批评指正交流。

     

    转载于:https://www.cnblogs.com/pzhu1/p/9007441.html

    展开全文
  • VUE重点问题总结

    2020-08-27 20:23:12
    本篇内容给大家总结了VUE的重要难点,并把代码做了详细分享,有兴趣的朋友参考学习下。
  • 摘要 关于vue 2.0源代码分析,已经有不少文档分析...本文旨在分享本人研究vue 2.0源代码重点难点之结果,不涉及每段源代码具体分析,源代码功能段每个人都可以去分析,只要有耐心,再参考已有高手发表的源代...
  • 一、vue相关 Q:vue的底层原理? A:Vue是采用数据劫持配合发布者-订阅者模式,通过Object.defineProperty来()来劫持各个属性的getter和setter。 在数据发生变化的时候,发布消息给依赖收集器,去通知观察者,做出...
  • vue使用axios进行http通讯,类似jquery/ajax的作用,类似angular http的作用,axios功能强大,使用方便,是一个优秀的http软件,本文旨在分享axios源代码重点难点分析,无意从头到尾详细分析源代码的各个细节。...
  • 摘要 vue和axios都可以使用es6-promise来实现f1().then(f2).then(f3)这样的连写形式,es6-promise其实现代浏览器已经支持,无需加载外部文件。...es6-promise源代码重点难点分析 本文以axios中的http re...

空空如也

空空如也

1 2 3 4
收藏数 71
精华内容 28
关键字:

vue重点难点

vue 订阅