kangjs7854 / records Goto Github PK
View Code? Open in Web Editor NEWrecords & blog
records & blog
一度丧失了对前端的热爱,沦为一个没有感情的切图仔,疯狂的产出页面和基本交互,都不需要我处理逻辑,彷佛穿越回了零几年。看着组里的java大佬们jsp写得风生水起,不亦乐乎,唯一的前端仔暗自神伤,独自忧伤。
其实项目需求很简单,就是展示商品列表,点击弹窗显示详情,tab栏切换不同频道。需求评审的时候我甚至在脑子里用vue和react各写了几遍,心想就这?然后大佬说要用jsp开发,主要是迭代维护之前的老项目,我就负责开发页面和基本交互,用经典的网页三剑客,html,css,js,开发完就把文件夹丢给java大佬处理业务逻辑。
我心想倒也简单,三下除以五写完了样式了,作为一个稍微有点代码洁癖的菜鸟,写原生js也不忘规范
function init() {
switchBar()
closeDialog()
openDialog()
handleSearch()
handleLoadMore()
}
function switchBar(){
//balabala
}
function closeDialog(){
//balabala
}
function openDialog(){
//balabala
}
function handleSearch(){
//balabala
}
functuon handleLoadMore(){
//balabala
}
function initListDom(){
//balabala
}
init()
写完之后看着自己的代码,两个字,优雅~看来面向对象的编程**已经扎根于心,成为中级cv工程师指日可待,内心不禁窃喜,直到半天后java大佬捧着他的macbookpro过来找我,开口就是一句你这写得有问题啊,
没想到我和mbp第一次的邂逅这么的尴尬,想到这有点恼火,差点脱口而出”你在教我写代码?“
定睛一看,原来是之前写的批量插入dom节点的函数,在切换导航条时获取到的数据,也是直接插入到dom节点的后面,但是这个时候应该是要覆盖掉原来的节点,这还不简单,脱口而出”我记得原生js有一个repalceChild的方法,你可以试试“,然后过了五分钟java大佬又过来了,说第二个参数传啥,替不了。后来我百度了一番,时间滴滴答答的走,研发经理下班前过来看了一眼,意味深长的眼神让我脑子里一片空白”不会吧不会把,不会还没过试用期就要被辞退了嘛,这脸丢大了呀“。
不行,我一定要找到办法,于是开始了面向百度编程,替换节点不行那就删除掉再插...又感觉遍历li标签挨个删除又插入实在性能杀手,有悖优雅,遂弃之.......
晚上八点多的时候,办公司人都走光了,一片祥和,原本躁动的心也慢慢地平复下来,思路也清晰了,才想到可以使用拼接字符串的方式插入dom元素,这样视图上的内容就是根据数据的内容动态渲染,问题解决
function initListDom(){
let liDomStr = ''
listArr.forEach(el=>{
liDomStr += `
<li>
${el.tradeName}
</li>
`
})
listDom.innerHTML = liDomStr
}
突然意识到,这不就是现代框架的一个理念嘛,避免操作dom节点去更新视图,用数据去驱动视图,传说中的UI=render(data)公式竟然被我不小心悟到了,想到这我就心满意足的下班了
后来我又写了一个jsp 的页面,还是一样的套路,三下除以五写完了,但是又感觉少了点什么,突然意识到是不是可以通过引用vue cdn连接的方式和我讨厌的dom节点说拜拜。 于是到了公司马上咨询了大佬是否可行,然后我把之前的两个页面改成了vue版本,发给他查看后,一番攀谈交心,毅然走上了改革开放新的道路。
到这故事就结束了,只是特别感慨,感谢尤大大开发了这么好用的框架
都是通过代理数据对象的getter和setter
mobx会为每个组件创建一个Watcher,在数据的getter和setter上加钩子,当组件渲染的时候(例如,调用render方法)会触发getter,然后把这个组件对应的Watcher添加到getter相关的数据的依赖中(例如,一个Set)。当setter被触发时,就能知道数据发生了变化,然后同时对应的Watcher去重绘组件。
在mobx中需要将数据声明成可观察的对象(推荐使用装饰器的写法);要改变状态得通过action的装饰器,这里和vuex十分相似;通过@Inject可以在任意组件使用store的状态,我觉得这个api特别的好,不仅易于理解,而且使用起来十分方便。
在使用了上述几个api后就可以愉快的管理状态,再也不用烦恼组件间的状态处理。但是很快就遇到了调试上的麻烦,mobx将变量用proxy代理之后在控制台上输出的都是一个proxy对象,在chrom浏览器里调试变量也十分不方便。就需要使用到一些mobx的调试工具。
安装npm i mobx-react-devtools -D
后在react中引入DevTools
这个组件就可以使用。
这里注意版本兼容,由于mobx-react-devtools
这个包不兼容mobx-react@^6
以上的版本,需要手动安装低版本的对其兼容。否则就会报Cannot read property 'on' of undefined
的错误。
当业务逻辑一复杂,很容易就陷入了回调地狱,可读性和可维护性都特别差。
好在小程序开发者工具支持es6转es5,我们利用es6的promise特性来封装网络请求
promise会返回两种状态,已完成和未完成,并且状态只能改变一次,所以我们可以把成功和失败的回调放到这两个状态中去执行
import md5 from './md5'
const app = getApp()
const WX = app.proxy()
module.exports = {
/**
* 默认post请求,将小程序的request封装成promise,解决回调嵌套过深的问题
* @param {String} url 请求路径
* @param {Object} params 请求参数
* @param {String} token 身份认证信息标识
*/
http(url, params) {
//时间戳
const requestTimestamp = new Date().getTime();
//唯一的id
const requestId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
})
//生成签名
const paramsArr = Object.keys(params)
let signStr = ''
//ASCII排序参数
paramsArr.sort()
paramsArr.forEach(el=>{
signStr += `${el}=${params[el] || ''}&`
})
const key = '' //与服务端协商好的key
//最掉最后一个参数的&
signStr = signStr.slice(0,-1) + key
const sign = md5(signStr).toUpperCase()
const header = {
'content-type': 'application/json',
requestTimestamp,
requestId,
sign
}
//如果token存在,写入请求头
const userInfo = wx.getStorageSync("userInfo")
userInfo.token && Object.assign(header, { requestToken: userInfo.token })
return WX.request({
url,
data: params,
header,
method: 'POST',
dataType: 'json',
responseType: 'text',
}).then(res => {
if (res.statusCode == 200) {
return res.data
}
app.toast(res.statusCode)
return res
}).catch(err => {
app.toast(res.statusCode)
return err
})
}
}
使用es6默认参数的新特性,在编码时像vscode这样的编译器会提供参数提醒等功能,方便了接口联调,不需要在接口文档和编辑器之间反复横跳
import { http } from './http'
const baseUrl = 'http://localhost:3000'//请求的域名
module.exports = {
//测试
test(params = { name }) {
return http(baseUrl + "/api/test", params)
},
getUserInfo(params = { code }) {
return http(baseUrl + "/api/wxLogin", params)
},
}
import api from "../../utils/api"
onLoad() {
api.test({name:"kjs"}).then((res)=>{
console.log(res);
})
},
由于数据传输中容易被抓包导致数据泄露,被篡改,需要一些安全措施来保证数据传输的安全性
由于这些信息在前端都可以修改,所以不是很可靠
由于http是无状态的协议,通过一应一答的方式进行数据传输。但是遇到需要保存状态的时候,可以使用cookie,seesion,token作为客户端和服务端会话验证的凭证
cookie
缺点:cookie容量比较小;api不灵活,需要自行封装;每次请求都会发送到服务端,造成性能困扰等问题;在http-only属性设置为fasle时容易遭受XSS攻击;容易遭受跨站请求攻击(CSRF)
seesion
服务端生成,在验证通过后在seeion里保存信息,并将seesionId写入响应头返回给前端,之后再次请求时服务器会读取这个sessionId,与存储的用户信息进行验证匹配。
缺点:大量的用户信息读写会给内存造成压力,并且面对服务器集群无法共享。
token
token为一个身份标识的令牌,为当前比较流行的登录验证的方式,由于不需要把存储数据在服务端,减轻了服务端的压力。在身份信息认证成功后返回给前端保存,在每次请求的时候携带这个token标识身份。
感觉可以简单理解成一个接口通行证的令牌
对称加密
加密和解密的私钥为同一个,计算速度快,但是一旦泄露就不安全了
非对称加密
由公钥和私钥成对组成,如果使用公钥加密,只有对应的私钥才能解密
//生成签名
const paramsArr = Object.keys(params)
let signStr = ''
//ASCII排序参数
paramsArr.sort()
paramsArr.forEach(el=>{
signStr += `${el}=${params[el] || ''}&`
})
const key = '' //与服务端协商好的key
//最掉最后一个参数的&
signStr = signStr.slice(0,-1) + key
const sign = md5(signStr).toUpperCase()
``
5. 时间戳
为了防止DDOS的攻击,增加时间戳的请求时间标识来让服务端判断,但是客户端的时间并不可靠
const requestTimestamp = new Date().getTime();
假设现在有一个网站需要github登录
我们需要在github的网站上申请一个client_id
及client_secret
,作为应用的标识
点击 登记,填写信息后生成上述两密钥信息。github授权最棒的一点是也支持本地服务的域名,所以我们可以使用localhost来实验。
前端就是在登录页面使用一个a标签链接到授权页面,记得携带上面申请的client_id
和redirect_uri
,后者为授权成功后重定向的网站,这里我重定向回首页。
window.location.href= 'https://github.com/login/oauth/authorize?client_id=50ab343567bd310005df&redirect_uri=http://127.0.0.1:9000'
授权成功重定向首页后会发现url上携带了code=xxxxxxxxx
的信息。我们需要使用js截取获得该授权码,将其发送到服务端进行验证。
const code = this.getUrlParams('code')
if(!code) return
const res = await api.sendAuthCode(code)
后端要做的就是验证该授权码是否过期,换取令牌,请求用户信息,保存并返回给前端展示
router.post('/auth', async (req, res, next) => {
const { code } = req.body
if (!code) return
const clientID = '50ab343567bd310005df'
const clientSecret = 'deea41faa0a55396c16f7679e16e61c2229f2f6a'
//根据临时code换取令牌
const tokenResponse = await axios({
method: 'post',
url: 'https://github.com/login/oauth/access_token?' +
`client_id=${clientID}&` +
`client_secret=${clientSecret}&` +
`code=${code}`,
headers: {
accept: 'application/json'
}
});
const accessToken = tokenResponse.data.access_token;
if(!accessToken){
return res.json(tokenResponse.data)
}
//使用令牌请求gitHub的接口
const result = await axios({
method: 'get',
url: `https://api.github.com/user`,
headers: {
accept: 'application/json',
Authorization: `token ${accessToken}`
}
});
res.json(result.data)
//存储用户数据到数据库
const query = { id:result.data.id }
const payload = { ...result.data }
await authController.insert(query, payload)
})
/**
* @param {String} key 缓存字段
* @param {Object} value 缓存内容
* @param {Number} expires 缓存时间/h
*/
export default class LocalCache {
constructor() {
this.now = new Date().getTime()
}
setItem(key, value, expires) {
expires && Object.assign(value, {
expiresTime: this.now + parseInt(Number(expires) * 60 * 60 * 1000)
})
wx.setStorageSync(key, value);
}
getItem(key) {
const value = wx.getStorageSync(key)
if (!value.expiresTime) return value
return value.expiresTime >= this.now
? value
: wx.removeStorageSync(key);
}
removeItem(key) {
wx.removeStorageSync(key);
}
clearAll() {
wx.clearStorageSync();
}
}
在app.js 引入并创建实例
//app.js
import LocalCache from './utils/cache'
App({
localCache : new LocalCache()
})
app.localCache.setItem('test',{name:'kjs'},1)
const data = app.localCache.getItem('test')
app.localCache.removeItem('test')
``
如果mongodb的目录没有data文件夹,记得新建一个,为下方dbpath的路径
#mongo.conf文件
# 新增的数据库的文件夹路径,根据自己的安装路径填写
dbpath=D:\Program Files\MongoDB\Server\4.2\data
# 新增的日志记录文件
logpath=D:\Program Files\MongoDB\Server\4.2\log\mongo.log
#错误日志采用追加模式
logappend=true
#启用日志文件,默认启用
journal=true
#这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false
quiet=true
#端口号 默认为27017
port=27017
mongod.exe --config "//此处为第一步配置的mongo.conf文件的路径" --install
net start mongodb
//显示启动成功即可通过 http://localhost:27017/ 访问
mongod --config "//此处为第一步配置的mongo.conf文件的路径 + mongo.conf" --install --serviceName "mongodb"
net start mongodb
老实说在之前我一直觉得测试就是点点点,通过重复繁琐的操作的查找bug的。平日里开发也少不了自测的环节,大部分时候通过点击的操作难以覆盖所有的情景和环节。而且确实实在繁琐,令人生厌,所以也想好好学习使用单元测试来解决这个问题,毕竟很多时候我们更愿意相信数据。人在主观上太容易陷入“应该没问题”这个误区,但是没有任何人能保证程序不出错误,我们只能通过测试来减少错误发生的概率。
框架使用的vue,所以用到了Vue Test Utils。现在主流的js测试运行期它都支持,于是我选用了jest来学习和使用。
$ npm install --save-dev jest @vue/test-utils
package.json
中定义一个单元测试的脚本。// package.json
{
"scripts": {
"test": "jest"
}
}
*.vue
文件,需要安装和配置 vue-jest
预处理器 // ...
"jest": {
"moduleFileExtensions": [
"js",
"json",
// 告诉 Jest 处理 `*.vue` 文件
"vue"
],
"transform": {
// 用 `vue-jest` 处理 `*.vue` 文件
".*\\.(vue)$": "vue-jest"
}
}
babel新版本的名称有所不同,要注意区别。babel7以上的版本需要安装 ```` babel-core@^7.0.0-bridge.0 babel-jest```进行兼容
npm i - D @babel/core @babel/preset-env babel-core@^7.0.0-bridge.0 babel-jest
创建.babelrc
文件。在package.json
添加jest配置文件,来告诉 Jest 用 babel-jest 处理 JavaScript 测试文件
//.babelrc
{
"presets": [["@babel/preset-env", { "modules": false }]],
"env": {
"test": {
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
}
}
}
//package.json
{
// ...
"jest": {
// ...
"transform": {
// ...
// 用 `babel-jest` 处理 js
"^.+\\.js$": "<rootDir>/node_modules/babel-jest"
}
// ...
}
}
创建test目录;Jest 将会递归的找到整个工程里所有 .spec.js
或.test.js
扩展名的文件
新建一个测试组件
<template>
<div>
{{ count }}
<button @click="increment">自增</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
import Vue from 'vue'
import { shallowMount } from '@vue/test-utils'
import Counter from '../src/Counter.vue'
describe('Counter.vue', () => {
it('increments count when button is clicked', async () => {
const wrapper = shallowMount(Counter)
wrapper.find('button').trigger('click')
await Vue.nextTick()
expect(wrapper.find('div').text()).toMatch('1')
})
})
npm run test
然后在app.json全局引入该组件供项目使用
这个时候小程序顶部只剩下右侧的胶囊菜单
"window": {
"navigationStyle":"custom"
},
假设需求需要在导航条左侧放置一个 提供返回上一级页面 及 返回首页的胶囊按钮。
1) 实现左右两侧胶囊及标题的对齐
2)高度适配各个尺寸的移动端设备
3)提供更加灵活多变的返回操作
通过小程序的api获取胶囊及状态栏的位置信息,计算自定义导航条的高度及边距
当导航条设为固定布局时,如何防止覆盖下方的内容?
这里要理解为什么会覆盖,由于position:fixed;top:0时,导航条的位置是脱离了文档流固定在屏幕窗口的最顶部。但是原本导航条下的内容会根据文档流渲染,因此覆盖了一部分在导航条之下。
解决方案也很简单,通过设置一个底部边距将下方内容撑开,这个边距为状态栏的高度和导航条的高度及上下边距即可。
通过微信的api获取胶囊和状态栏的位置信息
//获取右侧胶囊的的位置信息
const capsulePosition = wx.getMenuButtonBoundingClientRect()
//获取设备信息
wx.getSystemInfo({
success: (res) => {
const {
statusBarHeight,//顶部状态栏的高度
windowWidth //屏幕宽度
} = res
//存储到全局变量中方便使用
this.globalData.navBarInfo = {
height: capsulePosition.height //导航条的高度
paddingTop: capsulePosition.top,//导航条的上边距
paddingLeft: windowWidth - capsulePosition.right, //导航条的左边距
paddingBottom: capsulePosition.top - statusBarHeight,//导航条的下边距,使用胶囊的上边距减去状态栏高度可得
}
},
})
计算整个头部的高度,用于作为导航条的下边距撑开被覆盖的内容
//navBar.js
const app = getApp()
const { navBarInfo } = app.globalData
//整个头部的高度 = 导航条的高度+上边距+下边距
const topHeight = navBarInfo.height + navBarInfo.paddingTop + navBarInfo.paddingBottom
假如你的项目要实现一个用户登录功能
服务端根据小程序传过去的openid,在自己的用户中心进行用户的注册或检索,其实可以和第二步合在一起处理。具体还是根据公司业务来。
可以看到,要获取用户信息就需要调用三个api,而微信的api全是回调!
再假如我们获取用户信息后还要保存到本地,在调用wx.login时还需要检查登录态是否过期,防止重复调用产生不同的seesion_key导致某些接口报 session_key无效的错误。这就又多了两个回调函数,不管是开发还是后期维护都非常的不方便。
wx.login({
timeout:10000,
success: (res) => {
wx.request({
url: '/api/getOpenid',
data: {code: res.code},
header: {'content-type':'application/json'},
method: 'POST',
dataType: 'json',
responseType: 'text',
success: (result) => {
wx.request({
url: '/api/getUserInfo',
data: {},
header: {'content-type':'application/json'},
method: 'GET',
dataType: 'json',
responseType: 'text',
success: (res) => {
wx.setStorage({
key: 'userInfo',
data: res.data,
success: (result) => {
},
fail: () => {},
complete: () => {}
});
},
fail: () => {},
complete: () => {}
});
},
fail: () => {},
complete: () => {}
});
},
fail: () => {},
complete: () => {}
});
小程序开发者工具内置es6转es5,所以使用es6的新特性完全没问题,我们可以利用promise来解决回调函数的问题
/**
* 传入小程序的api,转成promise
* 第一版,传入整个小程序的api函数
* @param {Function} fn 小程序的api
* @return {Function} 返回的promise函数
*/
promisify: (fn) => {
return (args = {}) => {
return new Promise((resovled, rejected) => {
args.success = res => {
resovled(res)
}
args.fail = err => {
rejected(err)
}
fn(args)
})
}
},
通过把需要转换的小程序api传入promisify函数,返回一个promise对象
//使用
const wxLogin = promisify(wx.login)
const wxRequest = promisify(wx.request)
const wxSetStorage = promisify(wx.setStorage)
wxLogin()
.then(res => {
return wxRequest({
url: "/api/getOpenid",
data: { code: res.code },
header: { 'content-type': 'application/json' },
method: 'GET',
dataType: 'json',
responseType: 'text',
})
})
.then(res => {
return wxRequest({
url: "/api/getUserInfo",
data: { openid: res.openid },
header: { 'content-type': 'application/json' },
method: 'GET',
dataType: 'json',
responseType: 'text',
})
})
.then(res => {
return wxSetStorage({
key: 'userInfo',
data: res.data,
})
})
.then(res=>{
//弹窗提醒 or 做啥都行
})
这样处理之后代码的逻辑和结构是不是清晰了很多,promise真香!
虽然上方的处理解决了回调函数的问题,但是每次都需要调用这个函数来包装小程序的api,比较麻烦
这里就需要使用es6的proxy,也就是vue3版本代理object.defindproperty实现数据监听的新方法。
其实整个proxy人如其名,就是代理的意思,不过他比劫持数据对象的get和set更加强大,不仅仅支持对象,还能
代理数组等。欸,小程序的api不都是定义在wx这个对象里面嘛,是不是突然有了思路
通过代理wx这个对象在使用其对应的api时,包上一层promisify函数,转成promsie对象,我们不就可以实现 和上面一样的功能,甚至更加优雅。
/**
* 将小程序的api代理成promise形式第二版
* @return {Object} 返回一个proxy函数,代理wx这个对象
* 然后当这个代理对象使用小程序api时,会把原生的api转成promise对象
* @example const WX = proxy()
* WX.showToast({title:'666'}).then(res=>WX.showLoading())
*/
proxy: () => {
function promisify(fn) {
return (args = {}) => {
return new Promise((resovled, rejected) => {
wx[fn]({
...args,
success: resovled,
fail: rejected,
})
})
}
}
return new Proxy(wx, {
get: (target, prop) => {
return promisify(prop)
}
})
}
由于小程序没有全局变量,例如window或者global之类的,我们只能退而求其次,在需要使用到的引入,可以把需
要这个proxy函数放到app.js中方便引用
//使用
const app = getApp()
const WX = app.proxy()
WX.login()
.then(res => {
return WX.request({
url: "/api/getOpenid",
data: { code: res.code },
header: { 'content-type': 'application/json' },
method: 'GET',
dataType: 'json',
responseType: 'text',
})
})
.then(res => {
return WX.request({
url: "/api/getUserInfo",
data: { openid: res.openid },
header: { 'content-type': 'application/json' },
method: 'GET',
dataType: 'json',
responseType: 'text',
})
})
.then(res=>{
return WX.setStorage({
key: 'userInfo',
data: res.data,
})
})
.then(res=>{
//弹窗提醒 or 做啥都行
})
是不是更加优雅了呢!
虽然将api转换成promise的形式解决了回调嵌套过深导致的可读性可维护性都很差的问题,但是promise链式调用导致的调试困难还是难以避免,这里由于把wx这个对象的api的回调转换成了promise,我们可以使用async和await来处理,写法更加优雅,符合开发者的阅读开发习惯。
async function handleLogin() {
const app = getApp()
const WX = app.proxy()
const res = await WX.login()
const loginIngo = await WX.request({
url: "/api/getOpenid",
data: { code: res.code },
header: { 'content-type': 'application/json' },
method: 'GET',
dataType: 'json',
responseType: 'text',
})
const userInfo = await WX.request({
url: "/api/getUserInfo",
data: { openid: loginIngo.openid },
header: { 'content-type': 'application/json' },
method: 'GET',
dataType: 'json',
responseType: 'text',
})
await WX.setStorage({
key: 'userInfo',
data: userInfo,
})
}
通过下述步骤对使用node开发web服务有一个大概的认识
其实很简单,就三步。通过一些代码层面的优化,甚至只需要我们实现第二步定义好我们所需要的数据结构之后就能生成restful风格的api。
宁静的数据接口?什么鬼?
先说api
,其实是一个很广的命题。这里我们理解的api
是打通前后端的交互的数据接口。而在有一些语义下,它可以是你封装的一个工具函数方法。
而restful
风格我们可以这么理解
post、delete、put、get
这四个http动词。get /api/user/
=> 查看/api/user
这个路径下所有用户信息的资源其实开发web服务,大部分操作就是围绕着操作数据库,而操作数据库肯定有额外的学习成本。毕竟这是很多前端er没接触过的。
就我个人而言,学习一个新的知识点,查阅官方文档无疑是最好的办法。
假设已经安装完了mongodb
,我们要操作数据库还需要另一个东西:mongoose
,翻译过来是猫鼬,又是一个不知道什么鸟东西,在这儿可以简单的理解,通过它可以更加方便的操作数据库。
其实官方的例子特别的好,跟着猫鼬一起云养猫
还记得前面将的思路和步骤嘛,首先我们要使用node
的一个框架express
生成项目模板
routes
文件夹创建一个cat.js
文件,这个就是我们要访问的资源;express
框架的路由功能,根据http动词触发对应函数操作数据库;const express = require('express');
const router = express.Router();
const mongoose = require("mongoose")
//猫的模式:假设每一只猫都有名字和颜色的属性。
const catSchema = mongoose.Schema({
name: String,
color:String
})
//猫的模型:通过传入猫的模式,我们有了一个模型,然后就可以根据该模型生成好多猫了!
const CatModel = mongoose.model('Cat', catSchema)
router.get('/cat', function (req, res, next) {
//后来猫越来越多,就可以通过模型来查找小猫
CatModel.find((err, allCats) => res.json(allCats))
});
router.post('/cat', ((req, res, next) => {
//假设我们要增加一只小猫,猫咪的信息通过post请求的请求体传输到服务端。
const catInfo = req.body //假如请求体内容为{name:'小白',color:'黑色'}
//根据猫的模型生成的小猫就有了 名字为小白,颜色为黑色的属性。
const lititleCat = new CatModel(catInfo)
//小猫在调用save方法后保存到数据库
lititleCat.save((err, saved) => res.json(saved))
}))
router.delete('/cat', ((req, res, next) => {
const { id } = req.body
//根据传入的猫的id,删除该猫的信息
CatModel.findByIdAndRemove(id, (err, removed) => res.json(removed))
}))
router.put('/cat', ((req, res, next) => {
const { id } = req.body
//根据传入的猫的id,更新猫的信息
CatModel.findByIdAndUpdate(id, { ...req.body }, { new: true }, (err, updated) => res.json(updated))
}))
这个时候启动服务,其实未生效,这是因为我们写了一大堆,并没有触发,需要在入口文件app.js
引入我们的路由文件。
//引入猫的路由
const catRouter = require('./routes/cat');
//app就是我们的服务器,当服务器资源路径 /api被调用时,就会映射到我们猫的路由
app.use('/api', catRouter);
// localhoset:3000/api/cat 即是我们要请求猫的资源的 路径,最后通过http动词,触发对应的数据库操作,我们就生成了第一个符合restful风格的api。
async await
来优化接下来修改一下上面生成猫的例子
const express = require('express');
const router = express.Router();
const mongoose = require("mongoose")
const mongoose = require('mongoose')
+ const feederSchema = mongoose.Schema({
+ feederName: String,
+ cat: {
+ type: mongoose.Schema.Types.ObjectId,//联表查询必须这样的格式来存储对应表的_id
+ ref: 'Cat'//联表关系的表名,注意是生成模型的类,与模型名区分开
+ }
+ })
//猫的模式:假设每一只猫都有名字和颜色的属性。
const catSchema = mongoose.Schema({
name: String,
color:String
})
+ const feederModel = mongoose.model("Feeder", feederSchema)
//猫的模型:通过传入猫的模式,我们有了一个模型,然后就可以根据该模型生成好多猫了!
const CatModel = mongoose.model('Cat', catSchema)
router.get('/cat', (req, res, next)=> {
//后来猫越来越多,就可以通过模型来查找小猫
CatModel.find((err, allCats) => res.json(allCats))
});
router.post('/cat', async((req, res, next) => {
//假设我们要增加一只小猫,猫咪的信息通过post请求的请求体传输到服务端。
+ const { name, color, feederName } = req.body //假如请求体内容为{name:'小白',color:'黑色',feederName:"张三"}
+ const catInfo = { name, color } //猫的信息
+ const feederInfo = { feederName } //饲养员信息
//根据猫的模型生成的小猫就有了 名字为小白,颜色为黑色的属性。
const lititleCat = new CatModel(catInfo)
//小猫在调用save方法后保存到数据库
+ const { _id } = await lititleCat.save()
//将小猫的id与用户的猫关联起来
+ const newFeeder = new feederModel({ ...feederInfo, cat: _id })
//保存用户信息
+ await newFeeder.save()
//通过populate()可以查询联表的属性,exec()可以更好的追踪堆栈,让查询的函数返回的是一个完整的promise对象
+ const data = await feederModel.find().populate("cat").exec()
+ res.json(data)
}))
router.delete('/cat', (req, res, next) => {
const { id } = req.body
//根据传入的猫的id,删除该猫的信息
CatModel.findByIdAndRemove(id, (err, removed) => res.json(removed))
})
router.put('/cat', (req, res, next) => {
const { id } = req.body
//根据传入的猫的id,更新猫的信息
CatModel.findByIdAndUpdate(id, { ...req.body }, { new: true }, (err, updated) => res.json(updated))
})
可以下载postman测试一下代码
当你使用postman测试了几次代码之后你会发现每次post请求都新增重复数据,哪怕内容是没有变化的,mongoose会为保存时的数据添加唯一的_id标识。
按照正常的逻辑应该是
所以我们可以使用findOneAndUpdate
这个api插入或更新我们的数据,修改post
请求的代码
router.post('/cat', async((req, res, next) => {
//假设我们要增加一只小猫,猫咪的信息通过post请求的请求体传输到服务端。
const { name, color, feederName } = req.body //假如请求体内容为{name:'小白',color:'黑色',feederName:"张三"}
const catInfo = { name, color } //猫的信息
const feederInfo = { feederName } //饲养员信息
//这里根据猫的名称作为插入或更新的查询标识
+ const {_id} = await CatModel.findOneAndUpdate({name},catInfo,{ upsert: true, new: true, setDefaultsOnInsert: true }).exec()
//这里根据饲养员的名称作为插入或更新的查询标识
+ await feederModel.findOneAndUpdate({feederName},{...feederInfo,cat:_id},{ upsert: true, new: true, setDefaultsOnInsert: true })
//通过populate()可以查询联表的属性,exec()可以更好的追踪堆栈,让查询的函数返回的是一个完整的promise对象
const data = await feederModel.find().populate("cat").exec()
res.json(data)
}))
当然,这些都是node最基础的操作,但是这些特别简单的东西,扩展了很多知识,通过断点调试你可以更加深刻地了解到http请求通信过程中的数据传输、数据库模型的操作等等,甚至你还会开始思考如何使用node做更多的东西。
例如:
快速的接口mock,在拟定好协议之后使用node开发数据接口,提高接口联调的效率;
甚至搭一个可视化的mock数据的平台,通过可编辑表格操作去生成数据接口;
又或者你可以新建一个自己的网站,使用node搭建自己的服务器等等等。
最后贴上自己的github地址:
https://github.com/kangjs7854/node-server-template
臭不要脸的求个star,万分感谢~
大部分的授权可以通过调用 wx.authorize 发起弹窗申请,部分授权只能通过用户点击来实现
例如:
使用到了之前把小程序api转换成promise的代理
async authorize(permissions) {
const WX = app.proxy()
try {
const res = await WX.getSetting()
const isAuth = res.authSetting['scope[permissions]']
if (isAuth) return
const authorizeInfo = await WX.authorize({ scope: 'scope[permissions]' })
console.log(authorizeInfo);
} catch (error) {
//授权失败则引导至设置页面手动打开
console.log(error);
const modelInfo = await WX.showModal({
title: '提示',
content: '小程序需要您开启授权,请打开设置选项主动进行授权',
})
if (!modelInfo.confirm) return
await WX.openSetting()
}
},
位置信息所需要的配置
//app.json
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序定位"
}
},
"requiredBackgroundModes": [
"location"
]
加入原生button组件,完成授权操作
核心代码在 <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
通过设置对应的类型和回调获取授权
<view class="confirm-dialog" wx:if="{{isShowConfirm}}">
<view class="inner">
<text>我们需要您的手机号授权信息</text>
<view class="confirm-footer">
<view class="btn" bindtap="handleCancel">
拒绝
</view>
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
确认
</button>
</view>
</view>
</view>
遇到过一道面试题,如何让vue中的watch停止观察?
第一次听到的时候楞了几秒,脑子里搜索那些看过的博客文章文档,好像是第一次遇到这样奇怪的题目。
为了不冷场就从vue双向数据绑定说起,说完面试官表示很不满意,随口说了一句:数据绑定的时候不是用get,set劫持了数据对象嘛,再把这个对象设为null,这个watch就不就失效了。
当时的我觉得哇好有道理,点到哈腰,“哇,原来如此,谢谢面试官指点,现在如同醍醐灌顶”。现在仔细一想我好像被他忽悠了,都设置为null,这个对象都不存在了啊,我又百度了一番,好像没有找到类似的题目,大家搜的都是“为什么我的watch失效了,为什么不能监听数据对象了?”之类的。
额...答案好像已经出来了,不就是网友百度的那些问题嘛?感觉自己好傻,一是没get到面试官的点,可能他一开始就是想考察一下watch的注意事项才这么提问。二是在我吹了一波双向数据绑定后他突发奇想回答了那个奇淫技巧,整的我云里雾里。
export default {
data() {
return {
count: 1
}
},
watch: {
count(newVal) {
console.log('count 新值:'+newVal)
}
}
}
export default {
data() {
return {
count: 1
}
},
created() {
this.$watch('count', function(){
console.log('count 新值:'+newVal)
})
}
}
let unwatchFn = this.$watch('count', function(){
console.log('count 新值:'+newVal)
})
this.count = 2 // log: count 新值:2
unwatchFn()
this.count = 3 // 没有打印
箭头函数会在预编译阶段绑定父级作用域的this,普通函数的this则是指向函数运行时所在的对象
因为js代码分为预编译和执行阶段,在预编译时遇到函数声明会提前进行解析,由于 let vm = new Vue({...})
这句代码在预编译时还未执行,此时箭头函数绑定的this没有指向vue的实例,所以watch失效
3.由于Object.defineProperties的限制,在监听深层次对象时不添加deep属性watch会失效
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.