导航
npm init -y
npm i express -S
npm i nodemon -g
"dev": "nodemon ./src/index.js"
src/data/question.json
[
{
"id": 1,
"question": "1 + 1 = ?",
"items": ["2", "5", "4", "3"],
"answer": 0
},
{
"id": 2,
"question": "1 + 2 = ?",
"items": ["5", "2", "4", "3"],
"answer": 3
},
{
"id": 3,
"question": "1 + 3 = ?",
"items": ["2", "4", "5", "3"],
"answer": 2
},
{
"id": 4,
"question": "1 + 4 = ?",
"items": ["3", "2", "5", "4"],
"answer": 2
}
]
const express = require('express');
const bodyParser = require('body-parser');
const { readFileSync } = require('fs');
const { resolve } = require('path');
const app = express();
let myResult = [];
// 具备POST请求能力
app.use(bodyParser.urlencoded({ extended: false }));
// extended:false 方法内部使用querystring模块处理请求参数的格式
// extended:true 方法内部使用第三方模块qs处理请求参数的格式
app.use(bodyParser.json());
app.all('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
app.post('/getQuestion', function(req, res) {
const order = req.body.order;
const questionData = JSON.parse(readFileSync(resolve(__dirname, 'data/question.json'), 'utf8'));
const questionResult = questionData[order];
if (questionResult) {
const { id, question, items } = questionResult;
res.send({
errorCode: 0,
msg: 'OK',
data: {
id,
question,
items
}
});
} else {
res.send({
errorCode: 1,
msg: 'NO_DATA',
data: myResult
});
myResult = [];
}
});
app.post('/uploadAnswer', function(req, res) {
const { order, myAnswer } = req.body;
const questionData = JSON.parse(readFileSync(resolve(__dirname, 'data/question.json'), 'utf-8'));
const { id, question, items, answer } = questionData[order];
myResult.push({
qid: id,
question,
myAnswer: items[myAnswer], // myAnswer 是下标
rightAnswer: items[answer],
isRight: myAnswer == answer,
});
res.send({
errorCode: 0,
msg: 'OK'
});
// res.send(myResult);
});
app.listen(8888, function() {
console.log('Welcome to use Express on 8888');
});
npm run dev
main.js
/**
* [
* {
* id: 1,
* question: 'xxxx',
* items: [2,3,4,5],
* answer: 2
* }
* ]
*
* data: {
* order: 0,
* questionData: 试题数据,
* myAnswer: items index,
* myResults: [
* {
* qid,
* question,
* myAnswer: items[index],
* rightAnswer: items[index],
* isRight: myAnswer == answer
* }
* ]
* }
*
* 视图:什么时候显示试题(切换)?什么时候显示答案集
* myResults.length
*
* 试题
*
* 编号
* 题目
* 4个选项(点击)-> selectAnswer -> 取到index -> 赋值给order
* 切换order -> 上传该题的order myAnswer -> 获取新order对应的题
* 如果切换到最后一道 -> 返回myResults
*/
// import qs from 'qs';
const App = {
data() {
return {
order: 0,
questionData: {},
myAnswer: -1,
myResults: []
}
},
template: `
<div>
<div v-if="myResults.length > 0">
<h1>考试结果</h1>
<ul>
<li v-for="(item, index) of myResults"
:key="item.qid">
<h2>编号:{{ item.qid }}</h2>
<p>题目:{{ item.question }}</p>
<p>你的答案:{{ item.myAnswer }}</p>
<p>正确答案:{{ item.rightAnswer }}</p>
<p>正确:{{ isRightText(item.isRight) }}</p>
</li>
</ul>
</div>
<div v-else>
<h1>编号{{ questionData.id }}</h1>
<p>{{ questionData.question }}</p>
<div>
<button
v-for="(item, index) of questionData.items"
:key="item"
@click="selectAnswer(index)"
>{{ item }}</button>
</div>
</div>
</div>
`,
mounted() {
this.getQuestion(this.order);
},
computed: {
isRightText() {
return function(isRight) {
return isRight ? '是' : '否';
}
}
},
watch: {
order(newOrder, oldOrder) {
this.uploadAnswer(oldOrder, this.myAnswer);
this.getQuestion(newOrder);
}
},
methods: {
getQuestion(order) {
axios.post(
'<http://localhost:8888/getQuestion>',
{ order },
).then(res => {
if (res.errorCode) {
this.myResults = res.data;
console.log(this.myResults);
return;
}
this.questionData = res.data;
});
},
uploadAnswer(order, myAnswer) {
axios.post('<http://localhost:8888/uploadAnswer>', {
order,
myAnswer
}).then(res => {
console.log(res);
});
},
selectAnswer(index) {
this.myAnswer = index;
this.order += 1;
},
}
};
const vm = Vue.createApp(App).mount('#app');
data、computed、watchinit() 函数初始化init()函数
initData 实现 data 数据响应式initComputed ,获取 computed 实例
update 方法用来更新 计算属性新值initWatch ,获取 watch 实例
invoke 方法用来重新执行 watch 回调函数initData 数据劫持
reactive(vm, __get__, __set__)initComputed
update(key, watch)
initWatch
invoke(key, newVal, oldVal)module
└── Vue
├── Computed.js
├── Watcher.js
├── index.js
└── reactive.js
src
└── main.js
import Vue from '../module/Vue';
const vm = new Vue({
el: '#app',
data() {
return {
a: 1,
b: 2
}
},
computed: {
total() {
console.log('computed total');
return this.a + this.b;
}
},
watch: {
total(newVal, oldVal) {
console.log('watch total', newVal, oldVal);
},
a(newVal, oldVal) {
console.log('watch a', newVal, oldVal);
},
b(newVal, oldVal) {
console.log('watch b', newVal, oldVal);
}
}
});
console.log(vm);
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
vm.a = 100;
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
vm.b = 200;
console.log(vm.total);
import { reactive } from './reactive';
class Vue {
constructor(options) {
const { data, computed, watch } = options;
this.$data = data();
this.init(this, computed, watch);
}
init(vm, computed, watch) {
this.initData(vm);
const computedIns = this.initComputed(vm, computed);
const watcherIns = this.initWatcher(vm, watch);
}
initData(vm) {
// 数据响应式
reactive(vm, (key, value) => {
console.log(key, value);
}, (key, newValue, oldValue) => {
console.log(key, newValue, oldValue);
});
}
// computed没暴露在实例上,需要通过传参
initComputed(vm, computed) {
// 枚举 computed ,存到 computedData 中去
// 返回computed实例 -> 实例里有update -> 更新 computedData 的 value
}
initWatcher(vm, watch) {
// 枚举 watcher -> 增加侦听器
// 返回watcher实例 -> 实例里有调用 watch 的方法 -> 执行侦听器
}
}
export default Vue;
/**
* Vue
*
* data -> 是个函数得执行 -> data() ->
* 得挂载到 vm.$data 上 -> 得实现响应式 reactive ->
* 利用 Object.defineProperty -> 挂载到vm上:vm.xxx
* get vm[key] -> 获取的是 vm.$data[key]
* set vm[key] -> 设置的是 vm.$data[key] = newVal
* 触发后 -> updateComputedProp -> value更新
* 触发后 -> updateWatchProp -> watch函数callback执行
*
*
* computed -> computedData = {
* value -> 通过 get 计算而来
* get -> method
* deps -> [a, b] method中当前computed属性的依赖
* 根据 setter 触发后的 key ,
* 对比下依赖里边有没有,有就重新执行get
* }
*
* watch -> watchPool -> 存的fn ->
* $data下的setter触发 -> 执行 callback
*/
export function reactive(vm, __get__, __set__) {
// 因为在 getter、setter 中要做很多事情,例如update
// 所以声明两个回调, __get__、__set__
const _data = vm.$data;
for (const key in _data) {
Object.defineProperty(vm, key, {
get() {
__get__(key, _data[key]);
return _data[key];
},
set(newValue) {
// 由于 watch 函数有 newVal 和 oldVal ,
// 所以得保存一份修改之前的数据
const oldValue = _data[key];
_data[key] = newValue;
__set__(key, newValue, oldValue);
}
});
}
}
import Vue from '../module/Vue';
const vm = new Vue({
el: '#app',
data() {
return {
a: 1,
b: 2
}
},
computed: {
// descriptor.value
total() {
console.log('computed total');
return this.a + this.b;
},
// descriptor.value.get
total2: {
get() {
console.log('computed total2');
return this.a + this.b;
}
}
},
watch: {
total(newVal, oldVal) {
console.log('watch total', newVal, oldVal);
},
a(newVal, oldVal) {
console.log('watch a', newVal, oldVal);
},
b(newVal, oldVal) {
console.log('watch b', newVal, oldVal);
}
}
});
console.log(vm);
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
vm.a = 100;
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
vm.b = 200;
console.log(vm.total);
class Computed {
constructor() {
/**
* // descriptor.value
* total() {
* console.log('computed total');
* return this.a + this.b;
* },
* // descriptor.value.get
* total2: {
* get() {
* console.log('computed total2');
* return this.a + this.b;
* }
* }
*
* {
* key: total,
* value: 3
* get: total fn
* dep: [a, b]
* }
*/
this.computedData = [];
}
addComputed(vm, computed, key) {
const descriptor = Object.getOwnPropertyDescriptor(computed, key),
descriptorFn = descriptor.value.get
? descriptor.value.get
: descriptor.value,
value = descriptorFn.call(vm),
get = descriptorFn.bind(vm), // 保证正确的 this 指向
dep = this._collectDep(descriptorFn);
this._addComputedProp({
key,
value,
get,
dep
});
console.log(this.computedData);
const computedItem = this.computedData.find(item => item.key === key);
Object.defineProperty(vm, key, {
get() {
return computedItem.value;
},
// this.total = 500 不生效,还是自身get计算过来的值
// 保证计算属性是正确的
set(newValue) {
computedItem.value = computedItem.get()
}
});
}
update(key, cb) {
// 遍历 computed 池,
// 找有没有哪个 computed 的 dep 依赖中有此 key
// 如果依赖(key)值变更了,
// 就重新执行相应 computed 的 value(利用get())
this.computedData.map(computed => {
const dep = computed.dep;
const find = dep.find(v => v === key);
if (find) {
computed.value = computed.get();
cb && cb(computed.key, computed.value);
// update值之后,可能还有事儿要干,所以
// 留一个cb,使其更新后调用
}
});
}
_addComputedProp(computedProp) {
this.computedData.push(computedProp);
}
_collectDep(fn) {
const matched = fn.toString().match(/this\\.(.+?)/g);
// console.log(matched);
// matched : ['this.a', 'this.b']
return matched.map(item => item.split('.')[1]);
}
}
export default Computed;
import { reactive } from './reactive';
import Computed from './Computed';
class Vue {
constructor(options) {
const { data, computed, watch } = options;
this.$data = data();
this.init(this, computed, watch);
}
init(vm, computed, watch) {
this.initData(vm);
const computedIns = this.initComputed(vm, computed);
const watcherIns = this.initWatcher(vm, watch);
this.$computed = computedIns.update.bind(computedIns); // 保证 update 的 this 指向 computedIns 实例
}
initData(vm) {
// 数据响应式
reactive(vm, (key, value) => {
// console.log(key, value);
}, (key, newValue, oldValue) => {
// console.log(key, newValue, oldValue);
// 更新依赖key的computed
this.$computed(key);
});
}
// computed没暴露在实例上,需要通过传参
initComputed(vm, computed) {
// 枚举 computed ,存到 computedData 中去
// 返回实例 -> 实例里有update -> 更新 computedData 的 value
const computedIns = new Computed();
for (const key in computed) {
computedIns.addComputed(vm, computed, key);
}
return computedIns;
}
initWatcher(vm, watch) {
// 枚举 watcher -> 增加侦听器
// 返回实例 -> 实例里有调用 watch 的方法 -> 执行侦听器
}
}
export default Vue;
/**
* Vue
*
* data -> 是个函数得执行 -> data() ->
* 得挂载到 vm.$data 上 -> 得实现响应式 reactive ->
* 利用 Object.defineProperty -> 挂载到vm上:vm.xxx
* get vm[key] -> 获取的是 vm.$data[key]
* set vm[key] -> 设置的是 vm.$data[key] = newVal
* 触发后 -> updateComputedProp -> value更新
* 触发后 -> updateWatchProp -> watch函数callback执行
*
*
* computed -> computedData = {
* value -> 通过 get 计算而来
* get -> method
* deps -> [a, b] method中当前computed属性的依赖
* 根据 setter 触发后的 key ,
* 对比下依赖里边有没有,有就重新执行get
* }
*
* watch -> watchPool -> 存的fn ->
* $data下的setter触发 -> 执行 callback
*/
import Vue from '../module/Vue';
const vm = new Vue({
el: '#app',
data() {
return {
a: 1,
b: 2
}
},
computed: {
// descriptor.value
total() {
console.log('computed total');
return this.a + this.b;
},
// descriptor.value.get
total2: {
get() {
console.log('computed total2');
return this.a + this.b;
}
}
},
watch: {
total(newVal, oldVal) {
console.log('watch total', newVal, oldVal);
},
a(newVal, oldVal) {
console.log('watch a', newVal, oldVal);
},
b(newVal, oldVal) {
console.log('watch b', newVal, oldVal);
}
}
});
console.log(vm);
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
vm.a = 100;
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
vm.b = 200;
console.log(vm.total);
class Watcher {
/**
* addWatcher(vm, watcher, key)
*
* this.watchers -> watch
*
*
* total(newVal, oldVal) {
* console.log('watch total', newVal, oldVal);
* }
*
*
* {
* key,
* fn
* }
*/
constructor() {
this.watchers = [];
}
addWatcher(vm, watcher, key) {
this._addWatchProp({
key,
fn: watcher[key].bind(vm)
});
// console.log(this.watchers);
}
// 调用 watcher
invoke(key, newValue, oldValue) {
this.watchers.map(item => {
if (item.key === key) {
item.fn(newValue, oldValue);
}
});
}
_addWatchProp(watchProp) {
this.watchers.push(watchProp);
}
}
export default Watcher;