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爬虫框架,你只需要编写几个组件就可以实现网页数据的爬取.但是当我们要爬取的页面非常多的时候,单个主机的处理能力就不能满足我们的需求了(无论是处理速度还是网络请求 ...
随机推荐
- 微信小程序技巧-让特定组件首页始终展示修改编译条件即可,不用改json
- vue中wowjs的使用
笔者亲测,在vue中使用wow.js如果不按照以下方法实施,会出现意想不到的BUG,网页刷新后图片就全部突然看不到了,被增加了一个隐藏属性,建议大家严格按照方法执行,不要随意使用 (1)通过npm安装 ...
- webpack 中 loader 和 plugin 的区别
通俗点讲loader是转换,plugin是执行比转换更复杂的任务,比如合并压缩等 loader:让webpack能够处理非js文件,然后你就可以利用 webpack 的打包能力,对它们进行处理. 例如 ...
- 一个项目配置多个GIT仓库,一个项目自由上传码云和GITHUB
- js函数中的this指向
写代码的时候遇到这个问题了,在这里复习一下 非箭头函数 非箭头函数的this指向比较好理解,就是调用这个函数的对象,举个栗子: var obj = { foo: { bar: 3, foo:{ bar ...
- 五种传统IO模型
五种传统I/O模型 作者:tsing 本文地址:https://www.cnblogs.com/TssiNG-Z/p/17089759.html 简介 提起I/O模型, 就会说到同步/异步/阻塞/非阻 ...
- 【Spring】Bean注册注解
@Configuration 同@Component,将标注的类作为一个组件,区别是会使用Cglib动态代理,因此使用该注解的类不能是final的 与@Component的区别可见:Configura ...
- bind使用场景之一
- elasticsearch-head-master安装
1 简介 elasticsearch-head是一款专门针对于elasticsearch的客户端工具,elasticsearch-head是一个基于node.js的前端工程 2 依赖 需要安装node ...
- Idea的jdbc菜鸟的自我修养+踩坑经历
离谱了,换了电脑之后装好了mysql,导入了jar包,add了: 但是却找不到db1的表,报错内容:Unknown database 'db1' 他不认识我的数据表了!!!查了很多教程依旧没有解决 ...