7. mixin的实现原理
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的实现原理的更多相关文章
- JavaScript面向对象之我见
序言 在JavaScript的大世界里讨论面向对象,都要提到两点:1.JavaScript是一门基于原型的面向对象语言 2.模拟类语言的面向对象方式.对于为什么要模拟类语言的面向对象,我个人认为:某些 ...
- Vue.mixin Vue.extend(Vue.component)的原理与区别
1.本文将讲述 方法 Vue.extend Vue.mixin 与 new Vue({mixins:[], extend:{}})的区别与原理 先回顾一下 Vue.mixin 官网如下描述: Vue. ...
- 从mixin到new和prototype:Javascript原型机制详解
从mixin到new和prototype:Javascript原型机制详解 这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客 继承是为了实现方法的复用 ...
- 用特征来实现混入(mix-in)式的多重继承
用特征来实现混入(mix-in)式的多重继承 Scala里相当于Java接口的是特征(Trait).Trait的英文意思是特质和性状(本文称其为特征),实际上他比接口还功能强大.与接口不同的是,它还可 ...
- JavaScript AMD 模块加载器原理与实现
关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ...
- Mock原理学习
同事搓蛋问了我一个问题,mock的原理是啥,没怎么想出来,于是花了点时间学习了一下. 从Moq这个库入手:https://github.com/moq/moq4 Moq用到了Castle的库用于Dyn ...
- react生命周期,中间件、性能优化、数据传递、mixin的使用
https://github.com/lulujianglab/blog/issues/34 一.生命周期 1,初始化的执行顺序,初始生命周期执行过程详解 class initSate extends ...
- Vue 数据响应式原理
Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...
- 关于React setState的实现原理(二)
React中的Transaction 大家学过sql server的都知道我们可以批量处理sql语句,原理其实都是基于上一篇我们说的Datch Update机制.当所有的操作均执行成功,才会执行修改操 ...
- Scrapy-redis实现分布式爬取的过程与原理
Scrapy是一个比较好用的Python爬虫框架,你只需要编写几个组件就可以实现网页数据的爬取.但是当我们要爬取的页面非常多的时候,单个主机的处理能力就不能满足我们的需求了(无论是处理速度还是网络请求 ...
随机推荐
- Java学习笔记:2022年1月10日
Java学习笔记:2022年1月10日 摘要:这篇笔记主要记录了学习<Java核心技术 卷一>的第四章时的一些心得,主要阐述了对象与类这一部分的内容.需要注意的是,这一章的内容需要精心 ...
- 刺激,线程池的一个BUG直接把CPU干到100%了。
你好呀,我是歪歪. 给大家分享一个关于 ScheduledExecutorService 线程池的 BUG 啊,这个 BUG 能直接把 CPU 给飚到 100%,希望大家永远踩不到. 但是,u1s1, ...
- 02-逻辑仿真工具VCS使用
逻辑仿真工具VCS使用 1 Makefile执行VCS仿真 # Makefile for simulating the full_adder.v with the simulator VCS # -- ...
- LRU 居然翻译成最近最少使用?真相原来是这样!
前言 相信有很多同学和我一样,第一次碰到 LRU(Least Recently Used) 的这个解释「最近最少使用」都不知道是什么意思,用汤老师的话来说: 我真的感到匪夷所思啊! 最近是表示时间,最 ...
- java介绍、环境搭建与Hello,World!
java的诞生 C与C++ C语言 1972年贝尔实验室 操作系统.编译器等偏底层应用 指针和内存管理漏洞 C++ 1982年 面向对象 对C兼容 在图形领域.游戏领域等方面常用 java 桌面.手机 ...
- MD5在Python中的简单使用
MD5不是加密 https://draveness.me/whys-the-design-password-with-md5/ 参考为什么这么设计 Message-Digest Algorithm 5 ...
- redis06-事务
1 基本说明 可以一次执行多个命令,本质是一组命令的集合.一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞. 2 事务相关的几个命令 2.1 MULTI 标记一个事务块 ...
- PostgresSQL 常用操作方法
1.后台生成XML作为参数然后数据库解析获取数据 var idList = ids.Split(new string[] { "," }, StringSplitOptions.R ...
- Javaweb-1note C/S B/S HTML CSS javaScript一点点语法
------------恢复内容开始------------ Java web概念: *javaweb:使用java语言开发基于互联网的项目 *软件架构: 1.c/s:Clienr/Server 客户 ...
- Ubuntu18.04 下使用Flatpak稳定安装TIM、微信、迅雷和百度云
https://gitee.com/wszqkzqk/deepin-wine-for-ubuntu git clone https://gitee.com/wszqkzqk/deepin-wine-c ...