mixin的实现原理

在Vue.mixin()中的内容会被维护到Vue.options静态属性里面

然后通过mergeOptions以特定的合并策略将全局的属性和用户属性合并起来

在获取用户选项的时候, 调用mergeOptions合并全局Vue.mixin()里面的内容

就能使用Vue.mixin()里面拓展的功能了

新增一个全局api, 传入Vue

src/index.js

import { initGlobalApi } from "./globalApi"

initGlobalApi(Vue)

实现方法, 新增文件 src/globalApi.js

import { mergeOptions } from "./utils"

export function initGlobalApi(Vue) {

    // 静态方法 或属性
Vue.options = {} Vue.mixin = function (mixin) {
// console.log('this:minxi', this) // 静态方法里面的this指向Vue的构造函数
// 将用户的选项和全局的options进行合并
// 第一次 {} 和 用户的 created 合并 created: [fn]
// 第二次: 上一次的结果和 created 合并 , created: [fn, fn]
this.options = mergeOptions(this.options, mixin) // 这里的this指向Vue的构造函数
return this
}
}

新增文件 src/utils.js



const strats = {} // 使用策略
const LIFECYCLE = ['brforeCreate', 'created'] // 这里先讨论生命周期, data比较复杂 LIFECYCLE.forEach(hook => {
strats[hook] = function (p, c) { // p 和 c 指parent 和children
if (c) {
if (p) { // 如果父子都有, 注意, 如果p 有, 则p是一个数组
return p.concat(c)
} else {
return [c]
}
} else {
return p
}
}
}) export function mergeOptions(parent, child) {
const options = {} for (let key in parent) {
mergeField(key)
}
for (let key in child) {
if (!parent.hasOwnProperty(key)) {
mergeField(key)
}
} function mergeField(key) {
// 使用策略模式
if (strats[key]) {
options[key] = strats[key](parent[key], child[key])
} else {
options[key] = child[key] || parent[key]
}
} return options }

在init.js里面初始化的时候合并选项


Vue.prototype._init = function(options) {
// 获取vue实例, 这里的this指向vue实例
const vm = this
// 获取用户选项, 方便后续获取参数, 很多地方都是挂载到vue上面的
// vm.$options = options
vm.$options = mergeOptions(this.constructor.options, options) // 合并全局的mixin ...
}

在html文件vm实例的上方添加内容

Vue.mixin({
created() {
console.log('minix---1')
},
a:1,
b:1 })
Vue.mixin({
created() {
console.log('minix---2')
},
a: 2,
c: 2 })
// 最终上面的created会被维护成一个数据, 放在 Vue.options.created中
console.log(Vue.options)
const vm = new Vue({...})

在lifecycle.js中添加生命周期执行的方法

// 调用哪个实例上的哪个hook
export function callHook(vm, hook) {
const handlers = vm.$options[hook]
if(handlers) {
handlers.forEach(hanlder => hanlder.call(vm)) // 生命周期的钩子指向实例
}
}

在init.js里面使用

 Vue.prototype._init = function(options) {
// 获取vue实例, 这里的this指向vue实例
const vm = this
// 获取用户选项, 方便后续获取参数, 很多地方都是挂载到vue上面的
// vm.$options = options
vm.$options = mergeOptions(this.constructor.options, options) // 合并全局的mixin callHook(vm, 'beforeCreate') // 初始化状态
initState(vm) callHook(vm, 'created') // 如果有元素的话, 执行挂载方法,然后添加该方法
if(options.el) {
vm.$mount(options.el)
}
}

注意 Vue.mixin()的使用位置

完整文件

dist/5.mixin.html

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mixin</title>
</head> <body>
<div id="app" style="color:yellow;backgroundColor:blue;">
{{name}} hello {{name}} {{age}} {{name}}
</div>
<script src="vue.js"></script>
<script>
Vue.mixin({
created() {
console.log('minix---1')
},
a:1,
b:1 })
Vue.mixin({
created() {
console.log('minix---2')
},
a: 2,
c: 2 })
// 最终上面的created会被维护成一个数据, 放在 Vue.options.created中
console.log(Vue.options)
const vm = new Vue({
data() {
return {
name: 'ywj',
age: 18,
address: {
num: 20
},
hobby: ['eat', 'drink']
}
},
el: '#app', // 将数据解析到el元素上
created() {
console.log('created:', this.xxx) // 数据来源不明确
}
})
</script>
</body> </html>

src/index.js

// Vue 类是通过构造函数来实现的
// 如果通过 class来实现, 里面的类和方法就会有很多, 不利于维护
// 1. 新建一个Vue构造函数, 默认导出, 这样就有了全局 Vue
// 2. Vue中执行一个初始化方法, 参数是用户的选项
// 3. 在Vue的原型上添加这个方法, (注意: 添加的这个方法在引入vue的时候就执行了, 而不是在new Vue()的时候执行的) import { initGlobalApi } from "./globalApi"
import { initMixin } from "./init"
import { initLifeCycle } from "./lifecycle"
import { nextTick } from "./observe/watcher" function Vue(options) {
this._init(options)
} initMixin(Vue)
initLifeCycle(Vue)
initGlobalApi(Vue) Vue.prototype.$nextTick = nextTick export default Vue

src/globalApi.js

import { mergeOptions } from "./utils"

export function initGlobalApi(Vue) {

    // 静态方法 或属性
Vue.options = {} Vue.mixin = function (mixin) {
// console.log('this:minxi', this) // 静态方法里面的this指向Vue的构造函数
// 将用户的选项和全局的options进行合并
// 第一次 {} 和 用户的 created 合并 created: [fn]
// 第二次: 上一次的结果和 created 合并 , created: [fn, fn]
this.options = mergeOptions(this.options, mixin) // 这里的this指向Vue的构造函数
return this
}
}

src/utils.js



const strats = {} // 使用策略
const LIFECYCLE = ['brforeCreate', 'created'] // 这里先讨论生命周期, data比较复杂 LIFECYCLE.forEach(hook => {
strats[hook] = function (p, c) { // p 和 c 指parent 和children
if (c) {
if (p) { // 如果父子都有, 注意, 如果p 有, 则p是一个数组
return p.concat(c)
} else {
return [c]
}
} else {
return p
}
}
}) export function mergeOptions(parent, child) {
const options = {} for (let key in parent) {
mergeField(key)
}
for (let key in child) {
if (!parent.hasOwnProperty(key)) {
mergeField(key)
}
} function mergeField(key) {
// 使用策略模式
if (strats[key]) {
options[key] = strats[key](parent[key], child[key])
} else {
options[key] = child[key] || parent[key]
}
} return options }

src/lifecycle.js

import { Watcher } from "./observe/watcher"
import { createElementVNode, createTextVNode } from "./vdom" export function mountComponent(vm, el) {
// 将挂载的元素也放到实例上
vm.$el = el
// 1. 调用render方法产生虚拟节点
// vm._render() 生成虚拟节点 vm._update 生成真实节点 需要先扩展这两个方法
// vm._update(vm._render())
// 2. 虚拟dom产生真实dom
// 3. 插入到el元素中 // const vdom = vm._render() // vm._update(vm._render())
// 将渲染方法封装到updateComponent里面
const updateComponent = () => {
vm._update(vm._render())
} // 生成一个渲染watcher的实例, true表示渲染watcher
new Watcher(vm, updateComponent, true)
} export function initLifeCycle(Vue) {
Vue.prototype._render = function() {
const vm = this
// debugger
// 返回的结果是虚拟dom
// 注意this的指向, 需要call this
// 就是执行$options里面的render方法
// 需要拓展 _s _v _c方法
return vm.$options.render.call(vm)
}
Vue.prototype._c = function() {
// 返回一个元素的虚拟节点
return createElementVNode(this, ...arguments)
}
// _v(text)
Vue.prototype._v = function() {
// 返回一个文本的虚拟节点
return createTextVNode(this, ...arguments)
}
// 将数据转换成字符串
Vue.prototype._s = function(value) {
// 如果不是对象的话, 就直接返回, 不然字符串可会被加上""
if(typeof value !== 'object') return value
return JSON.stringify(value)
} Vue.prototype._update = function(vnode) { const vm = this
const el = vm.$el vm.$el = patch(el, vnode) }
} function patch(oldVNode, vnode) {
// 现在是初次渲染
// 需要判断是不是真实节点
const isRealElement = oldVNode.nodeType // nodeType是原生
if(isRealElement) {
const elm = oldVNode // 获取真实元素
const parentElm = elm.parentNode // 拿到父元素
// 创建真实元素
let newElm = createElm(vnode)
parentElm.insertBefore(newElm, elm.nextSibling)
parentElm.removeChild(oldVNode) return newElm // 如果是真实dom, 先返回一个新的dom, 暂时
} else {
// diff算法
}
} function createElm(vnode) {
let {tag, data, children, text} = vnode
if(typeof tag === 'string') { // 如果tag是string, 说明是一个标签, 如div
vnode.el = document.createElement(tag) // 生成一个真实节点, 并将真实节点挂载到虚拟节点上. 将虚拟节点和真实节点意义对应, 后续如果修改了属性, 可以直接找到虚拟节点对应的真实节点 // 更新属性, 属性在data里面
patchProps(vnode.el, data) // 标签会有儿子, 要处理儿子
children.forEach(child => {
// 同样生成元素并且插入到父元素的真实节点中, 递归调用
vnode.el.appendChild(createElm(child))
}) } else { // 不是元素就是文本
vnode.el = document.createTextNode(text) // 创建文本
}
// 这里返回一个真实dom是为了方便递归调用, 并且使用dom的方法
return vnode.el
} /**
*
* @param {真实元素} el
* @param {属性} props 是一个对象
*/
function patchProps(el, props) {
for(let key in props) { // style单独处理
if(key === 'style') {
for(let styleName in props.style) {
el.style[styleName] = props.style[styleName]
}
} else {
el.setAttribute(key, props[key])
}
}
} // 调用哪个实例上的哪个hook
export function callHook(vm, hook) {
const handlers = vm.$options[hook]
if(handlers) {
handlers.forEach(hanlder => hanlder.call(vm)) // 生命周期的钩子指向实例
}
}

src/init.js

import { compileToFunction } from "./compiler"
import { callHook, mountComponent } from "./lifecycle"
import { initState } from "./state"
import { mergeOptions } from "./utils" export function initMixin(Vue) { Vue.prototype._init = function(options) {
// 获取vue实例, 这里的this指向vue实例
const vm = this
// 获取用户选项, 方便后续获取参数, 很多地方都是挂载到vue上面的
// vm.$options = options
vm.$options = mergeOptions(this.constructor.options, options) // 合并全局的mixin
// 初始化状态, 也就是data里面的数据, vue的实例暂时长这样
// const vm = new Vue({
// data() {
// return {
// name: 'jerry',
// age: '杨'
// }
// }
// }) callHook(vm, 'beforeCreate') // 初始化状态
initState(vm) callHook(vm, 'created') // 如果有元素的话, 执行挂载方法,然后添加该方法
if(options.el) {
vm.$mount(options.el)
}
} // 挂载方法
Vue.prototype.$mount = function(el) {
// 获取实例
const vm = this
// 将el变成一个真实的元素
el = document.querySelector(el)
// 获取options
let ops = vm.$options
// 获取render方法, 没有就生成, 如果没有, 先获取template, 有template生成render方法
if(!ops.render) {
let template
if(!ops.template && el) {
template = el.outerHTML
} else {
if(el) {
template = ops.template
}
} // 将template转化为render方法
if(template) {
// 新建文件compiler/index.js文件, 添加compileToFunction方法
const render = compileToFunction(template) ops.render = render // 有了render之后, 挂载组件
// 就是执行一个render方法, 产生虚拟dom, 然后挂载到el中
mountComponent(vm, el)
}
} }
}

7. mixin的实现原理的更多相关文章

  1. JavaScript面向对象之我见

    序言 在JavaScript的大世界里讨论面向对象,都要提到两点:1.JavaScript是一门基于原型的面向对象语言 2.模拟类语言的面向对象方式.对于为什么要模拟类语言的面向对象,我个人认为:某些 ...

  2. Vue.mixin Vue.extend(Vue.component)的原理与区别

    1.本文将讲述 方法 Vue.extend Vue.mixin 与 new Vue({mixins:[], extend:{}})的区别与原理 先回顾一下 Vue.mixin 官网如下描述: Vue. ...

  3. 从mixin到new和prototype:Javascript原型机制详解

    从mixin到new和prototype:Javascript原型机制详解   这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客 继承是为了实现方法的复用 ...

  4. 用特征来实现混入(mix-in)式的多重继承

    用特征来实现混入(mix-in)式的多重继承 Scala里相当于Java接口的是特征(Trait).Trait的英文意思是特质和性状(本文称其为特征),实际上他比接口还功能强大.与接口不同的是,它还可 ...

  5. JavaScript AMD 模块加载器原理与实现

    关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ...

  6. Mock原理学习

    同事搓蛋问了我一个问题,mock的原理是啥,没怎么想出来,于是花了点时间学习了一下. 从Moq这个库入手:https://github.com/moq/moq4 Moq用到了Castle的库用于Dyn ...

  7. react生命周期,中间件、性能优化、数据传递、mixin的使用

    https://github.com/lulujianglab/blog/issues/34 一.生命周期 1,初始化的执行顺序,初始生命周期执行过程详解 class initSate extends ...

  8. Vue 数据响应式原理

    Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...

  9. 关于React setState的实现原理(二)

    React中的Transaction 大家学过sql server的都知道我们可以批量处理sql语句,原理其实都是基于上一篇我们说的Datch Update机制.当所有的操作均执行成功,才会执行修改操 ...

  10. Scrapy-redis实现分布式爬取的过程与原理

    Scrapy是一个比较好用的Python爬虫框架,你只需要编写几个组件就可以实现网页数据的爬取.但是当我们要爬取的页面非常多的时候,单个主机的处理能力就不能满足我们的需求了(无论是处理速度还是网络请求 ...

随机推荐

  1. Web初级——JavaScript

    JavaScript JavaScript是一种基于对象的脚本语言,用于开发基于客户端和基于服务器的Internet应用程序 1.了解JS 1.1JavaScript的组成 JavaScript 的核 ...

  2. vue中wowjs的使用

    笔者亲测,在vue中使用wow.js如果不按照以下方法实施,会出现意想不到的BUG,网页刷新后图片就全部突然看不到了,被增加了一个隐藏属性,建议大家严格按照方法执行,不要随意使用 (1)通过npm安装 ...

  3. 【高并发】AQS中的CountDownLatch、Semaphore与CyclicBarrier用法总结

    CountDownLatch 概述 同步辅助类,通过它可以阻塞当前线程.也就是说,能够实现一个线程或者多个线程一直等待,直到其他线程执行的操作完成.使用一个给定的计数器进行初始化,该计数器的操作是原子 ...

  4. 【学习笔记】Tarjan 图论算法

    - 前言 本文主要介绍 Tarjan 算法的「强连通分量」「割点」「桥」等算法. 争取写的好懂一些. - 「强连通分量」 - 何为「强连通分量」 在有向图中,如果任意两个点都能通过直接或间接的路径相互 ...

  5. 如何解决github下载很慢的问题?(已经解决)

    目的是为了解决GitHub致命的下载速度慢的问题 方法 通过码云来导入github,通过码云下载 1.在github上面找到自己想要的项目 这一步略过 2.复制github项目上面的网页链接 3.打开 ...

  6. BUG日记--——Linux安装Docker

    1.yum makecache fast不适合Centos8 2.解决办法 3.doucke的使用 1.关闭防火墙 # 关闭 systemctl stop firewalld # 禁止开机启动防火墙 ...

  7. windows环境下安装es和kibana

    1 ES安装 1.1 下载地址 https://www.elastic.co/cn/downloads/elasticsearch 1.2 版本选择 注意选择合适的版本,ES依赖于JDK,需要有对应的 ...

  8. SpringCloud Sleuth链路追踪

    1.概要 一般的,一个分布式服务跟踪系统,主要有三部分: 数据收集 数据存储 数据展示 然而这三个部分其实不都是由SpringCloud Sleuth(下面我简称为Sleuth)完成的,Sleuth负 ...

  9. E - 树状数组 1【GDUT_22级寒假训练专题五】

    E - 树状数组 1 原题链接 题意 已知一个数列,你需要进行下面两种操作: 将某一个数加上 \(x\) 求出某区间每一个数的和 lowbit函数 定义一个函数\(f=lowbit(x)\),这个函数 ...

  10. python requests 最牛攻略

    目录 安装 Reuqests HTTP 简介 什么是 HTTP HTTP工作原理 HTTP的9种请求方法 HTTP状态码 requests 快速上手 requests 发起请求的步骤 requests ...