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. Java学习笔记:2022年1月10日

    Java学习笔记:2022年1月10日 ​ 摘要:这篇笔记主要记录了学习<Java核心技术 卷一>的第四章时的一些心得,主要阐述了对象与类这一部分的内容.需要注意的是,这一章的内容需要精心 ...

  2. 刺激,线程池的一个BUG直接把CPU干到100%了。

    你好呀,我是歪歪. 给大家分享一个关于 ScheduledExecutorService 线程池的 BUG 啊,这个 BUG 能直接把 CPU 给飚到 100%,希望大家永远踩不到. 但是,u1s1, ...

  3. 02-逻辑仿真工具VCS使用

    逻辑仿真工具VCS使用 1 Makefile执行VCS仿真 # Makefile for simulating the full_adder.v with the simulator VCS # -- ...

  4. LRU 居然翻译成最近最少使用?真相原来是这样!

    前言 相信有很多同学和我一样,第一次碰到 LRU(Least Recently Used) 的这个解释「最近最少使用」都不知道是什么意思,用汤老师的话来说: 我真的感到匪夷所思啊! 最近是表示时间,最 ...

  5. java介绍、环境搭建与Hello,World!

    java的诞生 C与C++ C语言 1972年贝尔实验室 操作系统.编译器等偏底层应用 指针和内存管理漏洞 C++ 1982年 面向对象 对C兼容 在图形领域.游戏领域等方面常用 java 桌面.手机 ...

  6. MD5在Python中的简单使用

    MD5不是加密 https://draveness.me/whys-the-design-password-with-md5/ 参考为什么这么设计 Message-Digest Algorithm 5 ...

  7. redis06-事务

    1 基本说明 可以一次执行多个命令,本质是一组命令的集合.一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞. 2 事务相关的几个命令 2.1 MULTI 标记一个事务块 ...

  8. PostgresSQL 常用操作方法

    1.后台生成XML作为参数然后数据库解析获取数据 var idList = ids.Split(new string[] { "," }, StringSplitOptions.R ...

  9. Javaweb-1note C/S B/S HTML CSS javaScript一点点语法

    ------------恢复内容开始------------ Java web概念: *javaweb:使用java语言开发基于互联网的项目 *软件架构: 1.c/s:Clienr/Server 客户 ...

  10. Ubuntu18.04 下使用Flatpak稳定安装TIM、微信、迅雷和百度云

    https://gitee.com/wszqkzqk/deepin-wine-for-ubuntu git clone https://gitee.com/wszqkzqk/deepin-wine-c ...