
组件常用通信方式
props
// child
props: { msg: String }
// parent
<Helloworld msg="hello world" />
事件总线 任意两个组件之间传值常用事件总线或 Vuex 的方式
// Bus 事件派发、监听和回调管理,实际使用中会用Vue代替Bus
class Bus {
constructor() {
this.callbacks = {};
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus();
// child1
this.$bus.$on('foo', handle);
// child2
this.$bus.$emit('foo')
Vuex
自定义事件
子给父传值
// child
this.$emit('add', good);
// parent
<Cart @add="cartAdd($event)"></Cart>
$parent/$root
// brother1
this.$parent.$on('foo', handle);
// brother2
this.$parent.$emit('foo')
$children
$children 访问子组件实现父子通信// parent
this.$children[0].xxx = 'xxx';
$attrs/$listeners
含了父作用域中不作为 prop 被识别(且获取)的特性绑定(class 和 style 除外)
当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定(class 和 style除外),并且可以通过 v-bind="$attrs" 传入内部组件
这些特性在创建高级别的组件时非常有用
// child: 没有在props中声明foo
<p>{{$attrs.foo}}</p>
// parent
<Helloworld foo="foo">$refs
// parent
<Helloworld ref="hw">
mounted() {
this.$refs.hw.xx = 'xxx';
}
provide/inject
// ancestor
provide() {
return { foo: 'foo' }
}
// descendant
inject: ['foo']插槽语法是 Vue 实现的内容分发 API,用于复合组件开发。
匿名插槽
// comp1
<div>
<slot></slot>
</div>
// parent
<comp>hello</comp>
具名插槽
// comp2
<div>
<slot></slot>
<slot name="content"></slot>
</div>
// parent
<comp2>
<!-- 默认插槽用default做参数 -->
<template v-slot:default>默认插槽</template>
<!-- 具名插槽用插槽名做参数 -->
<template v-slot:content>具名插槽</template>
</comp2>
作用域插槽
// comp3
<div>
<slot :foo="foo"></slot>
</div>
// parent
<comp3>
<!--把v-slot的值指定为作用域上下文对象-->
<template v-slot:default="slotProps">
来自子组件数据:{{slotProps.foo}}
</template>
</comp3>
Object.defineProperty()Proxy{{}}v-bind,v-on,v-model,v-for,v-if// defProp.js
// 响应式
const obj = {};
function defineReactive(obj, key, val) {
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get' + key);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set ' + key + ':' + newVal);
val = newVal;
}
}
});
}
defineReactive(obj, 'foo', 'foo');
obj.foo;
obj.foo = 'foooo';
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>
const obj = {};
function defineReactive(obj, key, val) {
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get' + key);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set ' + key + ':' + newVal);
val = newVal;
// 更新函数
update();
}
}
});
}
function update() {
app.innerText = obj.foo;
}
defineReactive(obj, 'foo', '');
obj.foo = new Date().toLocaleTimeString();
setInterval(() => {
obj.foo = new Date().toLocaleTimeString();
}, 1000);
</script>
</body>
</html>
function defineReactive(obj, key, val) {
// 递归
observe(val);
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get ' + key);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set ' + key + ':' + newVal);
// 如果传入的newVal依然是obj,需要做响应化处理
observe(newVal);
val = newVal;
}
}
});
}
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return;
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
})
}
function set(obj, key, val) {
defineReactive(obj, key, val);
}
const obj = { foo: 'foo', bar: 'bar', baz: { a: 1 } };
// defineReactive(obj, 'foo', 'foo');
oba=observe(obj);
obj.foo;
obj.foo = 'foooo';
obj.bar;
obj.bar = 'barrrr';
obj.baz.a;
obj.baz.a = 3;
obj.baz = {a:1,b:2}
obj.baz.b = 99
set(obj, 'dong', 'dong');
obj.dong;
new Vue() 首先执行初始化,对 data 执行响应化处理,这个过程发生在 Observer 中data 中获取并初始化视图,这个过程发生在 compile 中Watcher,将来对应数据变化时 Watcher 会调用 更新函数data 的某个 key 在视图中可以出现多次,所以每个 key 都需要一个管家 Dep 来管理多个 Watcherdata 中的数据一旦发生变化,会首先找到对应的 Dep ,通知所有 Watcher 执行更新函数
职责划分
// cvue.js
class CVue {
constructor(options) {
// 保存选项
this.$options = options;
this.$data = options.data;
// 响应化处理
observe(this.$data);
// 代理
Proxy(this, '$data')
}
}
// 代理函数,提供对$data中数据的直接访问
function Proxy(vm, sourceKey) {
Object.keys(vm[sourceKey]).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm[sourceKey][key];
},
set(newVal) {
vm[sourceKey][key] = newVal;
}
})
})
}
function defineReactive(obj, key, val) {
// 递归
observe(val);
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get ' + key);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set ' + key + ':' + newVal);
// 如果传入的newVal依然是obj,需要做响应化处理
observe(newVal);
val = newVal;
}
}
});
}
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return;
}
// 创建Observer实例
new Observer(obj);
}
// 根据对象类型决定如何做响应化
class Observer {
constructor(value) {
this.value = value;
// 判断其类型
if (typeof value === 'object') {
this.walk(value);
}
}
// 对象数据响应化
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
})
}
// TODO 数组数据响应化
}
编译模板中vue模板特殊语法,初始化视图、更新视图

编译器
// compile.js
class Compiler {
/**
* @param el 宿主元素
* @param vm CVue实例
*/
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
if (this.$el) {
// 执行编译
this.compile(this.$el);
}
}
compile(el) {
// 递归遍历DOM
const childNodes = el.childNodes;
Array.from(childNodes).forEach((node) => {
// 判断节点类型
// 如果是元素,则遍历其属性判断是否是指令或事件,然后递归子元素
if (this.isElement(node)) {
// console.log('编译元素' + node.nodeName);
this.compileElement(node);
} else if(this.isInter(node)) { // 如果是文本,则判断是否插值绑定
// console.log('编译插值绑定' + node.textContent);
this.compileText(node);
}
// 递归子节点
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node);
}
});
}
isElement(node) {
return node.nodeType === 1;
}
isInter(node) {
// 文本标签且内容为{{xx}}
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
compileElement(node) {
// 遍历属性列表
const nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
// 约定指令格式 c-xx="yy"
const attrName = attr.name; // c-xx
const exp = attr.value; // yy
if (this.isDirective(attrName)) {
const dir = attrName.substring(2); // xx
// 执行指令
this[dir] && this[dir](node, exp);
}
})
}
isDirective(attr) {
return attr.indexOf('c-') === 0;
}
// k-text
text(node, exp) {
node.textContent = this.$vm[exp];
}
// k-html
html(node, exp) {
node.innerHTML = this.$vm[exp];
}
compileText(node) {
node.textContent = this.$vm[RegExp.$1];
}
}
使用编译器
// cvue.js
class CVue {
constructor(options) {
// 保存选项
this.$options = options;
this.$data = options.data;
// 响应化处理
observe(this.$data);
// 代理
Proxy(this, '$data');
// 创建编译器
new Compiler(this.$options.el, this);
}
}
// ...
测试
<div id="app">
<!-- 插值 -->
<p>{{counter}}</p>
<!-- 指令 -->
<p c-text="counter"></p>
<p c-html="desc"></p>
</div>
<script src="cvue.js"></script>
<script src="compile.js"></script>
<script>
const app = new CVue({
el: '#app',
data: {
counter: 1,
desc: '<span style="color:blue;">hello cvue</span>',
},
});
setInterval(() => {
app.counter++;
}, 1000);
</script>
视图中会用到 data 中某 key,叫依赖。同一个 key 可能出现多次,每次都需要收集出来用一个 watcher 来维护它,这个过程为依赖收集。多个 watcher 需要一个 Dep 来管理,需要更新时由 Dep 统一通知。

定义 watcher 并在数据更新时触发 watcher 的 update
// cvue.js
// 观察者:保存更新函数,值发生变化调用更新函数
const watchers = [];
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
watchers.push(this);
}
update() {
this.updateFn.call(this.vm, this.vm[this.key]);
}
}
function defineReactive(obj, key, val) {
// 递归
observe(val);
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get ' + key);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set ' + key + ':' + newVal);
// 如果传入的newVal依然是obj,需要做响应化处理
observe(newVal);
val = newVal;
// 执行更新函数
watchers.forEach(w => w.update());
}
}
});
}
编译时绑定 watcher
// compile.js
// 编译器
class Compiler {
// ...
compileText(node) {
// node.textContent = this.$vm[RegExp.$1];
this.update(node, RegExp.$1, 'text');
}
textUpdater(node, value) {
node.textContent = value;
}
// k-text
text(node, exp) {
// node.textContent = this.$vm[exp];
this.update(node, exp, 'text');
}
// k-html
html(node, exp) {
// node.innerHTML = this.$vm[exp];
this.update(node, exp, 'html');
}
htmlUpdater(node, value) {
node.innerHTML = value;
}
update(node, exp, dir) {
// 初始化
// 指令对应的更新函数xxUpdater
const fn = this[dir + 'Updater'];
fn && fn(node, this.$vm[exp]);
// 更新 封装一个更新函数,可以更新对应dom元素
new Watcher(this.$vm, exp, function(val) {
fn && fn(node, val);
});
}
}
// cvue.js
function defineReactive(obj, key, val) {
// 递归
observe(val);
// 创建一个Dep和当前的key一一对应
const dep = new Dep();
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get ' + key);
// 依赖收集
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log('set ' + key + ':' + newVal);
// 如果传入的newVal依然是obj,需要做响应化处理
observe(newVal);
val = newVal;
// 执行更新函数
// watchers.forEach(w => w.update());
dep.notify();
}
}
});
}
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
// Dep.target静态属性上设置为当前watcher实例
Dep.target = this;
this.vm[this.key]; // 读取触发getter
Dep.target = null; // 收集完就置空
}
update() {
this.updateFn.call(this.vm, this.vm[this.key]);
}
}
// Dep 依赖,管理某个key相关所有的watcher实例
class Dep {
constructor() {
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
notify() {
this.deps.forEach(dep => dep.update());
}
}
// compile.js
// 编译器
class Compiler {
// ...
compileElement(node) {
// 遍历属性列表
const nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
// 约定指令格式 c-xx="yy"
const attrName = attr.name; // c-xx
const exp = attr.value; // yy
if (this.isDirective(attrName)) {
const dir = attrName.substring(2); // xx
// 执行指令
this[dir] && this[dir](node, exp);
}
// 事件处理
if (this.isEvent(attrName)) {
// @click="onClick"
const dir = attrName.substring(1) // "click"
// 事件监听
this.eventHandler(node, exp, dir);
}
})
}
isEvent(dir) {
return dir.indexOf('@') === 0;
}
eventHandler(node, exp, dir) {
// 在实例中 methods: { onClick: function(){} }
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];
node.addEventListener(dir, fn.bind(this.$vm));
}
// ...
}
value 设定,事件监听// compile.js
// 编译器
class Compiler {
// ...
// c-model="xx"
model(node, exp) {
// update方法
this.update(node, exp, 'model');
// 事件监听
node.addEventListener('input', e => {
// 新的值赋值给数据
this.$vm[exp] = e.target.value;
});
}
modelUpdater(node, value) {
// 表达元素赋值
node.value = value;
}
}