实现Vue的v-for

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
├── app.js
├── compileTemplate.js
├── components
│   ├── TestA
│   │   └── index.js
│   └── TestB
│   └── index.js
├── handler.js
├── index.html
├── index.js
├── reactive.js
└── utils.js

index.html

1
2
3
4
<body>
<div id="app"></div>
<script type="module" src="./index.js"></script>
</body>

index.js

1
2
3
4
5
6
7
8
9
import { createApp } from './app.js'
import TestA from './components/TestA/index.js'
import TestB from './components/TestB/index.js'

const app = createApp({
components: [TestA, TestB]
})

app.mount('#app')

components/TestA/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { createReative } from '../../reactive.js'

const template = `
<ul class='list'>
<h1>{{ title }}</h1>
{{ dateTime }}
<for data="list" tag="li" class="item">
<span>姓名:{ name }</span>
</for>
</ul>
`

function TestA() {
const state = createReative({
title: '测试标题A',
dateTime: '2012-12-12 12:12',
list: [
{
id: 1,
name: 'zs',
age: 11
},
{
id: 2,
name: 'ls',
age: 22
},
{
id: 3,
name: 'ww',
age: 33
},
]
})

return [template, state]
}


export default TestA

components/TestB/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { createReative } from '../../reactive.js'

const template = `
<ul class='list'>
<h1>{{ title }}</h1>
{{ dateTime }}
<for data="list" tag="li" class="item">
<span>姓名:{ name }</span>
</for>
</ul>
`

function TestB() {
const state = createReative({
title: '测试标题B',
dateTime: '2012-12-12 12:12',
list: [
{
id: 1,
name: 'zs',
age: 11
},
{
id: 2,
name: 'ls',
age: 22
},
{
id: 3,
name: 'ww',
age: 33
},
]
})

return [template, state]
}


export default TestB

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { createReative } from './reactive.js'
import { compileTemplate } from './compileTemplate.js'

const domNodePool = [];

export function createApp(options) {
for (let option in options) {
switch (option) {
case 'components':
initComponent(options[option])
break;
default:
break;
}
}

return {
mount
}
}

function initComponent(components) {
for (let component of components) {
let [template, state] = component()
const node = compileTemplate(template, state)
domNodePool.push(node)
}

}

function mount(el) {
const app = document.querySelector(el)
const oFlag = document.createDocumentFragment()

domNodePool.forEach(node => {
oFlag.appendChild(node)
})

app.appendChild(oFlag)
}

reactive.js

1
2
3
4
5
6
7
8
9
10
11
import { isObject } from './utils.js'
import { proxyHandler } from './handler.js'

export function createReative(state) {
return createReactiveData(state, proxyHandler)
}

function createReactiveData(data, proxyHandler) {
if (!isObject(data)) return data;
return new Proxy(data, proxyHandler)
}

utils.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function isObject(value) {
return typeof value === 'object' && value !== null;
}

function hasOwnProperty(target, key) {
return Object.prototype.hasOwnProperty.call(target, key);
}

function isEqual(newValue, oldValue) {
return newValue === oldValue;
}

export {
isObject,
hasOwnProperty,
isEqual
}

handler.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { isObject, hasOwnProperty, isEqual } from './utils.js'
import { createReative } from './reactive.js'

const get = createGetter();
const set = createSetter();

function createGetter() {
return function get(target, property, receiver) {
const res = Reflect.get(target, property, receiver);

if (isObject(res)) {
return createReative(res)
}

console.log('#Reactive get the Property: ' + target[property]);
return res;
}
}

function createSetter() {
return function set(target, property, value, receiver) {
const isKeyExist = hasOwnProperty(target, property);
const oldValue = target[property];
const res = Reflect.set(target, property, value, receiver);

if (!isKeyExist) {
console.log('#Reactive add the Property: ' + property + '-----to---->' + value);
} else if (!isEqual(value, oldValue)) {
console.log('#Reactive set the Property: ' + property + '-----to---->' + value);
}
return res;
}
}

export const proxyHandler = {
get,
set
}


compileTemplate.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
const customTags = ['if', 'for']
const reg_single_bracket = /\{(.*?)\}/g;
const reg_double_bracket = /\{\{(.*?)\}\}/g

export function compileTemplate(template, data) {
template = replaceVar(template, data, reg_double_bracket)
const _node = document.createElement('div')
_node.innerHTML = template
return compileNode(_node, data)
}

function compileNode(node, data) {
const allNodes = node.querySelectorAll('*')

allNodes.forEach(item => {
const tagName = item.tagName.toLowerCase()
if (customTags.includes(tagName)) {
replaceNode(item, tagName, data)
}
})

return [...node.childNodes].find(item => item.nodeType === 1)
}

function replaceNode(node, tag, data) {
const dataKey = node.getAttribute('data')
const className = node.className;
const realTag = node.getAttribute('tag')

switch (tag) {
case 'for':
vFor(node, data, dataKey, className, realTag);
break;
default:
break;
}
}

function vFor(node, data, dataKey, className, realTag) {

const oFrag = document.createDocumentFragment();

data[dataKey].forEach(item => {
const el = document.createElement(realTag)
el.className = className ? className : '';
el.innerHTML = replaceVar(node.innerHTML, item, reg_single_bracket)
oFrag.appendChild(el)
})

node.parentNode.replaceChild(oFrag, node)
}

function replaceVar(html, data, reg) {
return html.replace(reg, (node, key) => {
const obj = {}
key = key.trim()
return obj[key] = data[key]
})
}

仓库地址