共计 18900 个字符,预计需要花费 48 分钟才能阅读完成。
导航
[[深刻 01] 执行上下文](https://juejin.im/post/684490…
[[深刻 02] 原型链](https://juejin.im/post/684490…
[[深刻 03] 继承](https://juejin.im/post/684490…
[[深刻 04] 事件循环](https://juejin.im/post/684490…
[[深刻 05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490…
[[深刻 06] 隐式转换 和 运算符](https://juejin.im/post/684490…
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…
[[深刻 08] 前端平安](https://juejin.im/post/684490…
[[深刻 09] 深浅拷贝](https://juejin.im/post/684490…
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…
[[深刻 11] 前端路由](https://juejin.im/post/684490…
[[深刻 12] 前端模块化](https://juejin.im/post/684490…
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490…
[[深刻 14] canvas](https://juejin.im/post/684490…
[[深刻 15] webSocket](https://juejin.im/post/684490…
[[深刻 16] webpack](https://juejin.im/post/684490…
[[深刻 17] http 和 https](https://juejin.im/post/684490…
[[深刻 18] CSS-interview](https://juejin.im/post/684490…
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…
[[深刻 20] 手写函数](https://juejin.im/post/684490…
[[react] Hooks](https://juejin.im/post/684490…
[[部署 01] Nginx](https://juejin.im/post/684490…
[[部署 02] Docker 部署 vue 我的项目](https://juejin.im/post/684490…
[[部署 03] gitlab-CI](https://juejin.im/post/684490…
[[源码 -webpack01- 前置常识] AST 形象语法树](https://juejin.im/post/684490…
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程](https://juejin.im/post/684490…
[[源码] Redux React-Redux01](https://juejin.im/post/684490…
[[源码] axios ](https://juejin.im/post/684490…
[[源码] vuex ](https://juejin.im/post/684490…
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…
前置常识
一些单词
subject:指标对象
observer:观察者对象
pattern:模式
notify:告诉
Publisher:发布者
Subscriber:订阅者
directive:指令
compile:编译
Recursive:递归 adj
recursion:递归 n
factorial:阶乘
splice
arr.splice(start, count, addElement1, addElement2, ...);
start:开始地位,从 0 开始(如果是正数,示意从倒数地位开始删除)
count:删除的个数
addElement1...:被增加的元素
作用:splice 删除 (原数组) 的一部分成员,并能够在删除的地位 (增加) 新的数组成员
返回值:被删除的元素组成的数组,留神是一个数组
留神:splice 扭转原数组
特地:如果只有一个参数(第一个参数),则变相相当于把数组拆分成两个数组,一个是返回值数组,一个是扭转后的原数组
for…in 和 for…of
- <font color=red>for…in:能够用于 (数组) 或 (对象)</font>
-
<font color=red>for…of:只能用于(数组),因为对象没有部署 iterator 接口 </font>
for...in 和 for...of (1) for...in:能够用于数组 或 对象 for...of:只能用于数组,因为对象没有部署 iterator 接口
- 数组
- for(let i of arr) ———– i 示意值
- for(let i in arr) ———– i 示意下标
- 对象
- 对象只能用 for in 循环,不能用 for of 循环
- 因为对象没有部署 iterator 接口,不必应用 for of 循环
-
for (let i in obj) ———– i 示意 key
<font color=red> 如何优雅的遍历对象 </font>
- (Object 对象) 和 (Array.prototype 对象) 都部署了三个办法
keys
,values
,entries
- <font color=red>Object.keys(obj),Object.values(obj),Object.entries(obj) —- 静态方法 </font>
-
Array.prototype.keys(),Array.prototype.values(),Array.prototype.entries() - 返回 Iterator 遍历器对象
如何优雅的遍历对象 const arr = [{name: '123'},2,3,4] const obj = {name: '111', age: 222} 对象 // (留神 Object.keys() values() entries() 能够用于对象,也能够用于数组) for(let [key, value] of Object.entries(obj)) { // object || array console.log(key, value) // name 111 // age 222 // Object.entries(obj) 返回一个数组,每个成员也是一个数组,由 (键,值) 组成的数组 } // 数组 // for(let [key, value] of arr.entries()) { // 返回 iterator 对象接口,能够用 for...of 遍历 // console.log(key, value) // }
<font color=red>Element.children 和 Node.ChildNodes</font>
-
Element.children
- 返回一个相似数组的对象,<font color=red>(HTMLCollection )</font> 实例
- 包含 <font color=red> 以后元素节点的(所有子元素) —- 只包含元素节点 </font>
- 如果以后元素没有子元素,则返回的对象蕴含 0 个成员
- 留神:<font color=red> 返回的是以后元素的所有子(子元素节点),不包含其余节点 </font>
-
Node.childNodes
- 返回一个相似数组的对象,<font color=red>(NodeList )</font> 汇合
- 包含 <font color=red> 以后元素的所有(子节点) —- 包含元素节点,文本节点,正文节点 </font>
-
Node.childNodes 和 Element.children 的区别
Node.childNodes 是 NodeList 汇合,Element.children 是 HTMLCollection 汇合
Node.childNodes 包含以后节点的所有子节点,包含元素节点,文本节点,正文节点
Element.children 包含以后元素节点的所有子元素节点,只包含元素节点
- 留神:<font color=red> 相似数组的对象具备 length 属性,且 键 是 0 或正整数 </font>
<div id='content'> <div>111</div> <div> <div>222</div> <div>333</div> </div> <p1>444</p1> </div> <script> const contentHTMLCollection = document.getElementById('content') ---------- HTMLCollection console.log(content.children) // HTMLCollection(3) [div, div, p1] const contentNodeList = document.querySelector('div') --------------------- NodeList console.log(contentNodeList.childNodes) // NodeList(7) [text, div, text, div, text, p1, text] </script>
document.querySelector(css 选择器)
- 返回第一个匹配的元素节点
- document.querySelectorAll() 返回所有匹配的元素节点
<font color=red size=5> 递归 尾递归 尾调用 </font>
- 递归的含意:函数调用本身,
尾调用本身,尾递归
-
递归的条件:边界条件,递归后退段,递归返回段
- 不满足边界条件,递归后退
- 满足边界条件,递归返回
- 尾调用:函数外部最初一个动作是函数调用,该调用的返回值,间接返回给函数
-
尾调用和非尾调用的区别:
- 执行上下文栈变动不一样(<font color=red> 即函数调用栈不一样,内存占用不一样 </font>)
尾调用 和 非尾调用 尾调用 function a() {return b() } 非尾调用 function a() {return b() + 1 // 非尾调用 // 因为调用 b()时,a 函数并未执行完,未出栈 => b 执行完后 + 1,这时 a 函数才执行结束}
----
尾调用优化
----
// (1) 失常版 - 阶乘函数
function factorial(number) {if(number === 1) return number;
return number * factorial(number - 1)
}
const res = factorial(3)
console.log(res)
// 调用栈
// 1 ------------------------------ 3 * factorial(2)
// factorial(3)
// 2 ------------------------------ 3 * factorial(2)
// factorial(2)
// factorial(3)
// 3 ------------------------------ 3 * 2 * factorial(1)
// factorial(1)
// factorial(2)
// factorial(3)
// 4 ------------------------------ 3 * 2 * 1
// factorial(2)
// factorial(3)
// 5 ------------------------------ 6
// factorial(3)
// (2) 尾递归版 - 阶乘函数
function factorial(number, multiply) {if (number === 1) return multiply;
return factorial(number-1, number * multiply)
}
const res = factorial(4, 1)
console.log(res)
// factorial(4, 1)
// factorial(3, 4*1)
// factorial(2, 3 * (4 * 1))
// factorial(1, 2 * (3 * 4 * 1))
// return 1 * 3 * 4 * 1
// 12
// 每次都是尾递归,所以执行上线问栈中始终只有一个 factorial()
// 即下一个 factorial 调用时,外层的 factorial 曾经执行结束出栈了
// (3) 优化尾递归版 - 阶乘函数
function factorialTemp(multiply, number) {if (number === 1) return multiply;
return factorialTemp(number * multiply, number-1)
}
function partial(fn) { // --------------------------------------- 偏函数
let params = Array.prototype.slice.call(arguments, 1) // ------ 固定局部参数
return function closure() {params = params.concat([...arguments])
if (params.length < fn.length) { // 参数小于 fn 形参个数,就持续收集参数
return closure
}
return fn.apply(this, params) // 否则,证实参数收集结束,执行 fn
}
}
const factorial = partial(factorialTemp, 1) // 固定参数 1
const res = factorial(4)
console.log(res)
观察者模式
概念
- 对程序中的某个对象进行察看,并在其产生扭转时失去告诉
- 存在 (<font color=red> 观察者对象 </font>) 和 (<font color=red> 指标对象 </font>) 两种角色
- 指标对象:subject
- 观察者对象:observer
在观察者模式中,subject 和 observer 互相独立又互相分割
- <font color=red> 一个指标对象对应多个观察者对象 (一对多)</font>
- <font color=red> 观察者对象在指标对象中(订阅事件),指标对象(播送事件)</font>
-
Subject 指标对象:保护一个观察者实例组成的数组,并且具备 (增加,删除,告诉) 操作该数组的各种办法 -
Observer 观察者对象:仅仅只须要保护收到告诉后 (更新) 操作的办法
代码实现 – ES5
阐明:(1) Subject 构造函数
- Subject 构造函数的实例(指标对象),保护一个(观察者实例对象) 组成的数组
- Subject 构造函数的实例(指标对象),领有(增加,删除,公布告诉) 等操作观察者数组的办法
(2) Observer 构造函数
- Observer 构造函数的实例(观察者对象),仅仅只须要保护收到告诉后,更新的办法
---------
代码:function Subject() { // 指标对象的构造函数
this.observes = [] // 观察者对象实例组成的数组}
Subject.prototype = { // 原型上挂载操作数组的办法
add(...params) { // --------------------------------- 增加观察者
// 对象办法的缩写,增加观察者对象
// params 是 ES6 中的(rest 参数数组),用来代替 arguments 对象,能够接管多个参数
// this 在调用时确定指向,Subject 构造函数的实例在调用,所以实例具备 observes 属性
this.observes = this.observes.concat(params)
},
delete(obj) { // ------------------------------------ 删除观察者
const cashObservers = this.observes // ------------ 缓存能进步性能,因为上面有屡次用到
cashObservers.forEach((item, index) => {if (item === obj) cashObservers.splice(index, 1)
// 这里是三等判断,因为 obj 和 item 的指针都执行同一个堆内存地址
// 举例
// const a = {name: 'woow_wu7'}
// const b = [a, 1, 2]
// b[0] === a
// 下面的后果是 true - 因为 b[0]和 a 指向了同一个堆内存地址
// 留神:cashObservers.splice(index, 1)
// 删除 cashObservers 相当于删除 this.observes
// 因为:this.observes 赋值给了 cashObservers,并且 this.observes 是复合类型的数据,所以两个变量指针统一
})
},
notify() { // --------------------------------------- 告诉观察者,并执行观察者各自的更新函数
if(this.observes.length) this.observes.forEach(item => item.update())
}
}
Subject.prototype.constructor = Subject // ------------- 扭转 prototype 的同时批改 constructor,避免援用出错
function Observer(fn) { // ----------------------------- 观察者对象仅仅保护更新函数
this.update = fn
}
const obsObj1 = new Observer(() => console.log(111111))
const obsObj2 = new Observer(() => console.log(222222))
const subObj = new Subject()
subObj.add(obsObj1, obsObj2)
subObj.notify()
subObj.delete(obsObj1)
subObj.notify()
代码实现 – ES6
-----------
观察者模式 - es6 语法实现
class Subject {constructor() {this.observers = []
}
add = (...rest) => {this.observers = this.observers.concat(rest)
}
delete = (obj) => {
const cachObservers = this.observers
cachObservers.forEach((item, index) => {if (item === obj) cachObservers.splice(index, 1)
})
}
notify = () => {
this.observers.forEach(item => {item.update()
});
}
}
class Observer {constructor(fn) {this.update = fn}
}
const objserverIns = new Observer(() => console.log(1111))
const objserverIns2 = new Observer(() => console.log(2222))
const subjectIns = new Subject()
subjectIns.add(objserverIns, objserverIns2)
subjectIns.notify()
2021/4/7 观察者模式优化
- 优化 add => subscribe 订阅
-
优化 delete => unSubscribe 勾销订阅
<!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>Document</title> </head> <body> <script> function Subject() {this.observes = [] // 观察者实例对象 组成的数组 } Subject.prototype = {subscribe(...params) {this.observes = this.observes.concat(params) }, unSubscribe(obj) {this.observes.forEach((item, index) => {if (item === obj) {this.observes.splice(index, 1) } }) }, notify() {this.observes.length && this.observes.forEach(item => item.update()) } } Subject.prototype.constructor = Subject function Observer(fn) {this.update = fn} const fn1 = () => console.log('fn1') const fn2 = () => console.log('fn2') const observer1 = new Observer(fn1) const observer2 = new Observer(fn2) const subject = new Subject() subject.subscribe(observer1, observer2) // ------- 订阅 subject.notify() // ------------------------------ 公布 console.log(subject.observes) subject.unSubscribe(observer1) // ---------------- 勾销订阅 console.log(subject.observes) </script> </body> </html>
- 2020/12/28 温习
公布订阅模式
角色
- 发布者:Publisher
- 订阅者:Subscriber
- 中介:Topic/Event Channel
- <font color=red>(中介) 既要 ‘ 接管 ’ (发布者)所公布的音讯事件,又要将音讯 ‘ 派发 ’ 给(订阅者)</font>
- 所以中介要依据不同的事件,存储响应的订阅者信息
- <font color=red> 通过中介对象,齐全解耦了发布者和订阅者 </font>
公布订阅模式 es5 代码实现
公布订阅模式 - 代码实现
var pubsub = {}; // 中介对象
(function(pubsub) {var topics = {}
// topics 对象,寄存每个事件对应的 (订阅者对象) 组成的(数组)
// key: 事件名
// eventName:[{functionName: fn.name, fn: fn}]
pubsub.subscribe = function(eventName, fn) {
// ------------------------------------------------------------ 订阅
if (!topics[eventName]) topics[eventName] = [] // 不存在,新建
topics[eventName].push({
functionName: fn.name, // 函数名作为标识,留神函数名不能雷同
fn, // 更新函数
})
}
pubsub.publish = function(eventName, params) {
// ---------------- --------------------------------------------- 公布
if (!topics[eventName].length) return; // 如果空数组,即没有该事件对象的订阅者对象组成的数组,跳出整个函数
topics[eventName].forEach(item => {item.fn(params) // 数组不为空,就循环执行该数组所有订阅者对象绑定更新函数
})
}
pubsub.unSubscribe = function(eventName, fn) { // 这里其实能够有化成传入回调函数名而不必传入整个回调函数
// -------------------------------------------------------------- 勾销订阅
if (!topics[eventName]) {return}
topics[eventName].forEach((item, index) => {if (item.functionName = fn.name) {
// 如果:事件名 和 函数名 雷同
// 就:删除这个事件对应的数组中的订阅者对象,该对象蕴含函数名,fn 两个属性
topics[eventName].splice(index)
}
})
}
})(pubsub)
function closoleLog(args) { // 订阅者收到告诉后的更新函数
console.log(args)
}
function closoleLog2(args) {console.log(args)
}
pubsub.subscribe('go', closoleLog) // 订阅
pubsub.subscribe('go', closoleLog2)
pubsub.publish('go', 'home') // 公布
pubsub.unSubscribe('go', closoleLog) // 勾销公布
console.log(pubsub)
公布订阅模式 es6 代码实现
class PubSub {constructor() {this.topics = {}
}
subscribe(eventName, fn) {if (!this.topics[eventName]) {this.topics[eventName] = []}
this.topics[eventName].push({
fname: fn.name,
fn
})
}
publish(eventName, params) {if (!this.topics[eventName].length) {return}
this.topics[eventName].forEach((item, index) => {item.fn(params)
})
}
unSubscribe(eventName, fname) {if (!this.topics[eventName]) {return}
this.topics[eventName].forEach((item, index) => {if (item.fname === fname) {this.topics[eventName].splice(index, 1)
}
})
}
}
const pubsub = new PubSub()
function goFn1(params) {console.log('goFn1', params)
}
function goFn2(params) {console.log('goFn2', params)
}
pubsub.subscribe('go', goFn1)
pubsub.subscribe('go', goFn2)
pubsub.publish('go', '1') // 公布
pubsub.unSubscribe('go', goFn2.name) // 勾销订阅
pubsub.publish('go', '2')
2020/12/29 温习 - 公布订阅模式 ES5 实现
<!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>
<script>
// 中介对象
const pubsub = {}
// 留神:(小括号) 和 (中括号) 结尾的 (前一条语句) 必须加分号,或者在小括号或中括号的最后面加分号
;(function(pubsub) {const topic = {}
// 订阅
// subscribe(订阅的事件名, 事件触发的回调函数)
pubsub.subscribe = function(eventName, fn) {if (!topic[eventName]) topic[eventName] = [];
topic[eventName].push({
fnName: fn.name,
fn,
})
console.log('topic[eventName]', topic[eventName])
}
// 公布
// publish(事件名,事件触发对应的回调函数的参数)
pubsub.publish = function(eventName, params) {console.log('topic[eventName]', topic[eventName])
if (topic[eventName]) {topic[eventName].forEach(observer => {observer.fn(params)
})
}
}
// 勾销订阅
// unScribe(须要勾销的事件名, 须要勾销的回调函数名)
pubsub.unScribe = function(eventName, fnName) {if (topic[eventName]) {topic[eventName].forEach((observer, index) => {if (observer.fnName === fnName) {topic[eventName].splice(index, 1)
}
})
}
}
})(pubsub)
pubsub.subscribe('go', function go1(address1){console.log(`${address1}one`)})
pubsub.subscribe('go', function go2(address2){console.log(`${address2}two`)})
pubsub.publish('go', 'home')
pubsub.unScribe('go','go1') // 勾销订阅 go1 函数
pubsub.publish('go', 'work')
</script>
</body>
</html>
2020/12/29 温习 - 公布订阅模式 ES56 实现
<!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>
<script>
class PubSub {constructor() {this.topic = {}
}
subscribe = (eventName, fn) => {if (!this.topic[eventName]) this.topic[eventName] = [];
this.topic[eventName].push({
fnName: fn.name,
fn
})
}
publish = (eventName, params) => {if (this.topic[eventName]) {this.topic[eventName].forEach(observer => {observer.fn(params)
})
}
}
unSubscribe = (eventName, fnName) => {if (this.topic[eventName]) {this.topic[eventName].forEach((observer, index) => {if ( observer.fnName === fnName) {this.topic[eventName].splice(index, 1)
}
})
}
}
}
const pubSub = new PubSub()
pubSub.subscribe('go', function go1(params) {console.log(`${params+'1'}`)})
pubSub.subscribe('go', function go2(params) {console.log(`${params+'2'}`)})
pubSub.publish('go', 'home')
pubSub.unSubscribe('go', 'go1') // 勾销订阅
pubSub.publish('go', 'work')
</script>
</body>
</html>
观察者模式,公布 - 订阅模式的区别和分割
(1)区别
- 观察者模式:须要观察者本人定义事件产生时的响应函数
- 公布 - 订阅模式:在(发布者对象),和(订阅者对象)之间,减少了(中介对象)
(2)分割
- 二者都升高了代码的(耦合性)
- 都具备消息传递的机制,以(数据为核心)的设计思维
vue 双向数据绑定
前置常识:1. Element.children
- 返回一个相似数组的对象(HTMLCollection 实例)- 包含以后元素节点的所有子元素
- 如果以后元素没有子元素,则返回的对象蕴含 0 个成员
2. Node.childNodes
- 返回一个相似数组的对象(NodeList 汇合),成员包含以后节点的所有子节点
- NodeList 是一个动静汇合
3. Node.childNodes 和 Element.children 的区别
- Element.children 只蕴含元素类型的子节点,不蕴含其余类型的子节点
- Node.childNodes 蕴含元素节点,文本节点,正文节点
代码
-------------
<!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 v-text="name"></div>
<input type="text" v-model="name">
</div>
<script>
// 监听类
// 次要作用是:将扭转之后的 data 中的 (最新数据) 更新到 (ui 视图) 中
class Watcher {constructor(directiveName, el, vm, exp, attr) {
this.name = directiveName // 指令的名字,比方 'v-text','v-model'
this.el = el // 每个具体的 DOM 节点
this.vm = vm // MyVue 实例对象
this.exp = exp // el 中的 directiveName 属性对应的属性值
this.attr = attr // el 的属性,须要需改的属性
this._update() // 留神这里在实例化 Watcher 时,会执行_update()办法
}
_update() {this.el[this.attr] = this.vm.$data[this.exp]
// 将 MyVue 实例的 data 属性的最新值更新到 ui 视图中
}
}
class MyVue {constructor(options) {const {el, data} = this.options = options // 参数构造赋值
this.$el = document.querySelector(el) // 获取 el 选择器的元素节点
this.$data = data // data 数据
this._directive = {}
// key:data 对象中的 key,递归循环 data 从而获取每一层的属性作为 key
// value:数组,用来寄存 Watcher 实例
this._observes(this.$data)
// 1. 递归循环,获取 data 每一层的 key
// 2. 把_directive 对象,用每个 key 做(键),用一个 Watcher 实例组成的数组做(值)
// 3. 监听 data 中每个 key 对应的 value 是否变动
// 变动就执行 => _directive 对象中 key 对应的数组中的 Watcher 实例对象的_update()办法
this._compile(this.$el)
// 1. 递归循环,获取所有 $el 对象的元素节点的 所有子元素节点 Element.children
// 2. 如果元素节点有 v-text 指令,就把 Watcher 实例 push 进_directive 对象该指令值对应的数组
// 因为执行了 new Watcher(),所以 constructor 中调用了_update,所以会执行一次
// 即 push 实例的同时,也会执行_update(),曾经是会把 data 的数据写入到对应的指令所在的元素节点中
// 3. 如果元素节点有 v-model 指令,同理
// 不同的是:须要监听(input 事件),键最新的 input 框的 value 值,赋值给 data
// data 扭转,会被 Object.defineProperty 监听,从而执行更新函数
// 更新函数负责把最新的 data 中的数据更新到页面中
}
_observes(data) {for(let [key, value] of Object.entries(data)) {if (data.hasOwnProperty(key)) {this._directive[key] = []}
if (typeof value === 'object') { // 数组 或 对象
this._observes(value) // 递归调用
}
const _dir = this._directive[key]
Object.defineProperty(this.$data, key, { // ------------- 监听属性变动
enumerable: true,
configurable: true,
get() {return value},
set(newValue) {if (newValue !== value) { // ------------------------ 属性变动后执行所以订阅者对象的更新函数
value = newValue
_dir.forEach(item => item._update())
}
}
})
}
}
_compile(el) {for(let [key, value] of Object.entries(el.children)) {if (value.length) {_compile(value)
}
if (value.hasAttribute('v-text')) {const attrValue = value.getAttribute('v-text')
this._directive[attrValue].push(new Watcher('input', value, this, attrValue, 'innerHTML'))
}
if(value.hasAttribute('v-model') && (value.tagName === 'INPUT' || value.tagName === 'TEXTAREA')) {const attrValue = value.getAttribute('v-model')
this._directive[attrValue].push(new Watcher('v-model', value, this, attrValue, 'value'))
let that = this
value.addEventListener('input', function() { // input 事件监听
that.$data[attrValue] = value.value
// 获取最新的 input 框值, 赋值给 data => Object.defineProperty 监听到 data 变动 => 执行更新函数更新视图
})
}
}
}
}
// 实例化 MyVue
new MyVue({
el: '#app',
data: {name: 'woow_wu7'}
})
</script>
</body>
</html>
2020/12/29 vue 双向数据绑定 - 亲测可用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入 Vue 通过 CDN 引入 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
<div id="root">
<div type="text" v-text="name">v-text 的内容 </div>
<input type="text" v-model="name">
<script>
class MyVue {constructor(options) {const { el, data} = this.$options = options
this.$el = document.getElementById(el)
this.$data = data
this._directive = {}
// key:data 对象中的 key
// value:
this._observer(this.$data)
this._compile(this.$el)
}
_observer = (data) => {for (let [key, value] of Object.entries(data)) {
// key 就是 data 对象中的 key; value 就是 data 对象中每个 key 对应的值
// data: {name: 'woow_wu7'} => key=name,value='woow_wu7'
if (data.hasOwnProperty(key)) {this._directive[key] = [] // data 中每个 key 都对应一个数组}
if (typeof value === 'object') this._observer(value);
const that = this
Reflect.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get() {return value},
set(newValue) {if (value !== newValue) {
value = newValue
that._directive[key].forEach(item => item._update())
}
}
})
}
}
_compile = (el) => {for (let [key, value] of Object.entries(el.children)) {if (value.length) {this._compile(value)
}
if (value.hasAttribute('v-text')) {const attrubuteValue = value.getAttribute('v-text')
this._directive[attrubuteValue].push(new Watcher('input', value, this, attrubuteValue, 'innerHTML'))
// 留神:// attrubuteValue 是 v -text 对应的值 => 其实就是 data 中的 key 值,和_observer 中的申明保持一致了
}
if (value.hasAttribute('v-model') && value.tagName === 'INPUT' || value.tagName === 'TEXTAREA') {const attributeValue = value.getAttribute('v-model')
this._directive[attributeValue].push(new Watcher('v-model', value, this, attributeValue, 'value'))
const that = this
value.addEventListener('input', (e) => {
// 1. input 事件批改 data 中的属性
// 2. data 中的属性被批改,触发 Reflect.defineProperty 的 setter() 函数
this.$data[attributeValue] = e.target.value
}, false)
}
}
}
}
class Watcher {constructor(directiveName, el, vm, exp, attr) {
this.name = directiveName // 指令的名字,比方 'v-text','v-model'
this.el = el // 每个具体的 DOM 节点
this.vm = vm // MyVue 实例对象
this.exp = exp // el 中的 directiveName 属性对应的属性值
this.attr = attr // el 的属性,须要需改的属性
this._update()}
_update = () => {this.el[this.attr] = this.vm.$data[this.exp]
// 将 MyVue 实例的 data 属性的最新值更新到 ui 视图中
}
}
new MyVue({
el: 'root',
data: {name: 'woow_wu7'}
})
</script>
</body>
</html>
材料
具体 https://juejin.im/post/684490…
精简 https://juejin.im/post/684490…
实现 vue 数据双向绑定 https://juejin.im/post/684490…
https://segmentfault.com/a/11…
https://juejin.im/post/684490…
我的简书:https://www.jianshu.com/p/bdb…