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爬虫框架,你只需要编写几个组件就可以实现网页数据的爬取.但是当我们要爬取的页面非常多的时候,单个主机的处理能力就不能满足我们的需求了(无论是处理速度还是网络请求 ...
随机推荐
- 【力扣】2400. 恰好移动 k 步到达某一位置的方法数目
题目 2400. 恰好移动 k 步到达某一位置的方法数目 解题思路 观察上面示例,容易画出下面的递归树,因此可以考虑DFS. DFS 很容易写出DFS的代码 class Solution { int ...
- Redis--回顾提要
一.写在前 知识学了就忘!不用就忘!我太健忘!特此记录!用于复习打卡!Redis干就完事了! 二.来辣! Redis做异步队列:一般list结构做队列,rpush生产消息,lpop消费消息,当lpop ...
- DQL_排序查询-DQL_聚合函数
DQL_排序查询 排序查询 语法: order by 子句 order by 排序字段1 排序方式1 , 排序字段2 排序方式2 , 排序字段3 排序方式3 ..... 排序方式 : A ...
- vue学习笔记(七)---- vue中的路由
一.什么是路由 对于普通的网站来说,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源 对应单页面的应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,h ...
- Swagger2多包扫描
package com.xf.config; import org.springframework.context.annotation.Bean; import org.springframewor ...
- 电商网站Web自动化测试实战( 编写京东搜索脚本python+selenium框架)
电商网站Web自动化测试实战( 编写京东搜索脚本) 1,打开京东页 京东首页地址:https://www.jd.com/,故进入京东首页如下: 2,打开浏览器开发者模式 定位元素前需先打开浏览器开发者 ...
- Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 1x.x
出现问题原因: nodejs和node-sass版本不匹配 解决办法: 下载node-sass指定版本的nodejs 1)node-sass的节点版本支持政策 ① 支持的 Node.js版本因版本而异 ...
- STM32F4寄存器串口DMA汇总
1.初始化 //RCC RCC->APB1ENR|=1<<20; //使能串口5时钟 RCC->APB1ENR|=1<<19; //使能串口4时钟 RCC-> ...
- Linux的简介、历史
开始linux Java开发之路: javaSE.Mysql. 前端.(HTML\CSS\JS).javaWeb.SSM框架.SpringBoot.Vue.SpringCloud Linux.中间件 ...
- 微信小程序 添加域名
1.不校验合法域名.web-view (业务域名).TLS版本以及 HTTPS证书 2.小程序上的请求=>服务器的根地址=>都需要添加微信公众平台(否则会无法发送请求,导致代码报错)