前端及开发文章及欣赏

JS实现xml与json互转且基本保持原样

seo达人



如果非要代码实现的话,github上一个不错的js库(X2JS):https://github.com/abdolence/x2js

自己写了demo测试了一下:



<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title></title>

</head>

<body>

</body>

<script src="js/xml2json.js" type="text/javascript" charset="utf-8"></script>

<script type="text/javascript">

var xmlText =

'<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="2" value="" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1"><mxGeometry x="220" y="90" width="120" height="80" as="geometry"/></mxCell><mxCell id="3" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1"><mxGeometry x="410" y="110" width="80" height="80" as="geometry"/></mxCell></root></mxGraphModel>';

console.log("原始数据xml:"+xmlText);

var x2js = new X2JS();

var jsonObj = x2js.xml_str2json( xmlText );

console.log(jsonObj);

var xmlAsStr = x2js.json2xml_str( jsonObj );

console.log(xmlAsStr);

</script>

</html>

效果如下:







基本能还原,只是""变成了'',这个应该问题不大的。






DataGridView(VS中表格)删除和刷新

seo达人

功能描述:右击表格中对应的行,进行删除或者刷新的操作。

先往DataGridView上拖一个ContextMenuStrip控件





在下面分别输入删除与刷新





双击删除,输入代码:



  private void 删除ToolStripMenuItem_Click_1(object sender, EventArgs e)

        {

            try

            {

                DialogResult dr = MessageBox.Show("确定删除吗?", "提示", MessageBoxButtons.OKCancel);

                if (dr == DialogResult.OK)

                {

                    //获取选中行的数据

                    Facade.FoodMenuCateFaçade façade = new Facade.FoodMenuCateFaçade();

                    Entity.T_FoodMenuCate t_Food = new Entity.T_FoodMenuCate();

                    t_Food.CateName = dataFood.CurrentRow.Cells[1].Value.ToString();

                    int list1 = façade.DeleteFoodMenu(t_Food);



                    frmTips f = frmTips.GetInstance("删除完成");

                    f.Show();

                }



            }

            catch (Exception ex)

            {

                MessageBox.Show(ex.Message);

            }

        }



双击刷新,输入代码:

        private void 刷新ToolStripMenuItem_Click(object sender, EventArgs e)

        {//通过走七层查询出数据库中新的内容:

            Facade.FoodMenuCateFaçade façade = new Facade.FoodMenuCateFaçade();

            Entity.T_FoodMenuCate t_Food = new Entity.T_FoodMenuCate();

            List<Entity.T_FoodMenuCate> list = façade.SelectFoodMenu(t_Food);

            //把值赋给表格

            dataFood.DataSource = list;

        }


前端解决跨域问题的常用方法

seo达人

首先,跨域是什么?



只要协议、域名、端口有任何一个不同,都被当作是不同的域。为什么三者任何一个不同就会产生跨域呢,想想也很容易知道,要是很随便引用什么外部文件,不同标签下的页面引用类似的彼此的文件,浏览器很容易懵逼的,保障不了安全问题,但在安全限制的同时也给注入iframe或是ajax请求上带来了不少麻烦。所以我们要通过一些方法使本域的js能够操作其他域的页面对象或者使其他域的js能操作本域的页面对象



但有两点至少要清楚:



如果是协议和端口造成的跨域问题“前台”是无能为力的;

在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。

(“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。)

1.通过HTML5的postMessage方法跨域



页面M通过postMessage方法发送消息如下:



window.onload = function() {  

    var iframe_dom = document.getElementById('iframId');  

    var targetOrigin = "http://www.baidu.com";&nbsp;&nbsp;

    iframe_dom.contentWindow.postMessage('hello world!', targetOrigin);  

};

备注:



postMessage的使用方法:



originwindow.postMessage(message, targetOrigin);



originwindow:是说的目标窗口,即要给某个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口

message: 是要发送的消息,类型为 String、Object (但IE8、9 不支持)

targetOrigin: 是限定消息接收范围,不限制请使用 '*

页面N通过message事件监听并接受消息如下:



let onmessage = function (event) {  

  var data = event.data;//由发送窗口传过来的消息内容  

  var origin = event.origin;//由发送窗口传过来的消息来源地址  

  var source = event.source;//源Window对象  

  if(origin=="http://www.baidu.com"){&nbsp;&nbsp;

    console.log(data);//hello world!  

  }  

};  

if (typeof window.addEventListener != 'undefined') {  

  window.addEventListener('message', onmessage, false);  

} else if (typeof window.attachEvent != 'undefined') {  

  //for ie  

  window.attachEvent('onmessage', onmessage);  

}

或者为了防止接入方的命名冲突,也可以约定事件名,以此加以区分



例如



window.addEventListener("message", function(event) {

  if (

    event &&

    typeof event.data == "object" &&

    event.data.event == "FUNCTION_NAME"

){

document.getElementById("val").innerHTML = event.data.value;

} });

2.通过JSONP



上面那种方式的通信是双向的,页面与iframe或是页面与页面之间的



JSONP主要是封装好的请求方式添加callback,这个callback是由前后端约定好的



它的优劣势:



JSONP的优点:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。

JSONP的缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题;无法判断它是否请求成功,只能通过timeout

3.CORS跨域



实现CORS通信的关键是服务器端,只要服务端那边实现了CORS接口,就可以跨源通信



CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉



服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,便可以允许Ajax进行跨域的访问



 



CORS和JSONP对比



JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。



使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。



JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。



CORS与JSONP相比,显然更为先进、方便和可靠。



4.设置代理



目前市场上用vue技术不在少数,下面介绍一种配置代理方式



在vue.config.js该文件里面配置如下:



 devServer: {

        port: 8001,

        open: true,

        disableHostCheck: true,

        proxy: {

            '/api': {

                target: 'https:/xxx.com',

                secure: true, // false为http访问,true为https访问

                ws: true,

                changeOrigin: true,

                pathRewrite: {

                    '^/api': ''

                }

             }

        }

 }

 



后面请求是需要带上‘/api’请求即可


ES6的解构赋值的用途总结

seo达人

二 - ES6的解构赋值的用途总结

2 - 什么是ES6解构

在ES6中添加了一个新属性:解构,允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。解构是一种打破数据结构,将其拆分为更小部分的过程,允许我们将右边的表达式看起来也像变量声明一般,然后在左边将值一一提取。 解构这种赋值语法较为简洁,比传统的属性访问更为清晰。

还不了解什么是解构赋值的,请参考:https://blog.csdn.net/azxqwe123/article/details/103296603

下面只讲怎么应用:



2.1 - ES6解构赋值7种场景案例—用途

(1)交换变量的值



[x, y] = [y, x];

上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。



(2)从函数返回多个值



函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。



// 返回一个数组



function example() {

  return [1, 2, 3];

}

var [a, b, c] = example();



// 返回一个对象



function example() {

  return {

    foo: 1,

    bar: 2

  };

}

var { foo, bar } = example();



(3)函数参数的定义



解构赋值可以方便地将一组参数与变量名对应起来。



// 参数是一组有次序的值

function f([x, y, z]) { ... }

f([1, 2, 3]);



// 参数是一组无次序的值

function f({x, y, z}) { ... }

f({z: 3, y: 2, x: 1});



(4)提取JSON数据



解构赋值对提取JSON对象中的数据,尤其有用。



var jsonData = {

  id: 42,

  status: "OK",

  data: [867, 5309]

};



let { id, status, data: number } = jsonData;



console.log(id, status, number);

// 42, "OK", [867, 5309]

上面代码可以快速提取JSON数据的值。



(5)函数参数的默认值



jQuery.ajax = function (url, {

  async = true,

  beforeSend = function () {},

  cache = true,

  complete = function () {},

  crossDomain = false,

  global = true,

  // ... more config

}) {

  // ... do stuff

};





指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。



(6)遍历Map结构



任何部署了Iterator接口的对象,都可以用for…of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。



var map = new Map();

map.set('first', 'hello');

map.set('second', 'world');



for (let [key, value] of map) {

  console.log(key + " is " + value);

}

// first is hello

// second is world

如果只想获取键名,或者只想获取键值,可以写成下面这样。



// 获取键名

for (let [key] of map) {

  // ...

}



// 获取键值

for (let [,value] of map) {

  // ...

}



(7)输入模块的指定方法



加载模块时,往往需要指定输入那些方法。解构赋值使得输入语句非常清晰。



const { SourceMapConsumer, SourceNode } = require("source-map");


vue项目刷新当前页面的几种方式

seo达人

在vue项目中,经常会遇到需要刷新当前页面的需求。

因为vue-router判断如果路由没有变化,是不会刷新页面获取数据的。



方式1:go(0)和reload()

通过location.reload()或是this.$router.go(0)两种强制刷新方式,相当于按F5,会出现瞬间白屏,体验差,不推荐。



方式2:定义一个空白路由页面,路由跳转到该空白页后立马跳回当前页,实现路由刷新。

在router路由表中定义一个空白路由,



 // 强制刷新当前页所用的中间跳转页

   {

        path: '/redirect/:path*',

        component: () => import('@/views/redirect/index')

  }



写一个空白路由组件



//redirect/index

<script>

export default {

  created() {

    const { params, query } = this.$route

    const { path } = params

    this.$router.replace({ path: '/' + path, query })

  },

  render: function(h) {

    return h() // avoid warning message

  }

}

</script>





在需要刷新的页面使用



refresh() {

      // 刷新当前路由

      const { fullPath } = this.$route

      this.$router.replace({

        path: '/redirect' + fullPath

      })

    }



这种方式,基本上能够应付绝大多数情况,推荐使用。

但是,有时候,有一些极端情况下,这种刷新不起作用,而又不想用第一种那种毛子般的简单粗暴的方式的话,下面的方式可以选择使用。



方式3:provede/inject 方式

vue官方文档说了,这个依赖注入方式是给插件开发使用的,普通应用中不推荐使用。

但是,效果却很好。

app.vue修改



<template>

  <div id="app">

    <router-view v-if="isRouterAlive" />

  </div>

</template>

<script>

export default {

  name: 'App',

  provide() {

    return {

      reload: this.reload

    }

  },

  data() {

    return {

      isRouterAlive: true

    }

  },

  methods: {

    reload() {

      this.isRouterAlive = false

      this.$nextTick(function(){

        this.isRouterAlive = true

      })

    }

  }

}

</script>





使用的时候:

demo.vue



<template>

  <div class="container">

  xxx

  </div>

</template>



<script>

export default {

  inject: ['reload], // 依赖注入

  name: 'Demo',

  computed: {

    message() {

      return '抱歉,您访问的页面地址有误或者该页面不存在...'

    }

  },

  methods: {

  handleReload() {

  this.reload() // 直接在需要刷新的方法中调用这个reload()

}

  }

}

</script>



<style lang="scss" scoped>

</style>



原理就是通过依赖注入的方式,在顶部app通过v-if的显示隐藏来强制切换显示,以此来让vue重新渲染整个页面,app中通过provide方式定义的reload方法,在它的后代组件中,无论嵌套多深,都能够触发调用这个方法。具体说明查看官方文档。


用three.js构建自己的后处理渲染器第一篇---抗锯齿的选择

seo达人

说到渲染引擎就不得不提到延迟渲染,基本上一个引擎如果没有实现延迟渲染就不能说是一个好的渲染引擎,不过可惜的是three.js并没有实现延迟渲染(ps:呼吁作者赶紧实现mrt吧)。由于没有mrt和延迟渲染,本来不打算写后处理的,但是即使没有,我们也希望能实现一些炫酷的效果,那就在现在的基础上对three.js进行简单的改造来实现一套高性能的后处理渲染器吧。

要实现后处理我们首先要考虑需求,是否要兼顾移动端,是否要兼顾大屏(4k),是否要支持webgl1,是否要在各种显卡中都有一个还算不错的性能。目前我主要考虑的是:性能要好,可以兼顾大屏,不打算完美支持webgl1,尽量多使用webgl2的特性。后面所有的性能测试为都以N卡作为性能测试指标,先不管AMD卡(AMD抗锯齿的处理性能会高些的,但是动态处理性能会偏低,这里有很多细节问题)

好了,我们关心的是好的性能,尽量多使用webgl2的特性,尽量能兼顾大屏(这里主要是要注意显存问题),现在开始准备我们的渲染器吧

要完成一个后处理渲染器,我们首先要考虑抗锯齿,常用的有超采样技术和多重采样技术,具体可以看这个介绍 添加链接描述

three.js已经实现了SSAA,SMAA,TAA这三种超采样技术,效果都还不错,具体实现three.js都有例子,就不详细说明了。SSAA抗锯齿效果是最好的,但是性能最差,现实情况下根本无法使用。SMAA性能会好一些,2000个物体差不多掉15帧吧(SMAA比FXAA计算稍微复杂一些,SMAA研究的少,不知道能不能解决line的锯齿问题,如果知道的欢迎留言)。SMAA效果基本可以接受,但是2000个物体掉的帧率还是有点多,这个也不是我们首选的。TAA效果很好,如果场景里面很少动的东西,它是个不错的选择,如果有运动的物体或者动画等等,基本上抗锯齿就没效果了,理论上应该可以实现动态的TAA,但是用目前的技术很难实现。所以TAA先不考虑加入我们的后处理渲染器(真实使用场景下一般都有动画或者贴图流动效果,所以TAA无法使用)。

SMAA:效果不错,开销15帧左右(2000物体)

SSAA:效果最好,开销太大 (2000物体基本已经没有了帧率)





TAA:效果基本和SSAA差不多,但目前只有静止的时候才有效果



再来看看多重采样MSAA,这个特性必须使用webgl2,是webgl提供的方式,和浏览器自身的抗锯齿原理一样,效果不错,和正常渲染的结果没有区别,性能开销也不是太大,前提是我们不要使用stencilbuffer。而且three.js MSAA这块的释放有些小问题,详细可以在deallocateRenderTarget这个接口中进行修改。现在我们可以把MSAA作为主要的抗锯齿技术。MSAA虽然性能开销不大,但是唯一的不足是比较吃显存,如果是大屏,而且显卡不好的话还是容易崩,无法开启。因此我们还要继续选择一个开销小不吃显存的抗锯齿(当然也可以考虑SMAA,目前由于帧率开销较大(2000物体掉了15帧左右),我们不考虑加入SMAA)

MSAA:基本没有帧率开销,效果不错,但是耗费显存(2000物体)



最后的备选方案就是FXAA了,FXAA可以参考这个文章:添加链接描述讲的很详细,FXAA性能开销很小,但是效果很一般,特别是细线的锯齿没法解决,转动摄影机边缘的闪动效果也无法解决,而且还有一个问题是由于FXAA就是靠边缘模糊抗锯齿,所以必然导致画面会略有模糊。但是它最大的好处是开销很小而且不耗费显存,并且集成到后处理渲染器中最简单。但是当显存不足显卡太差的时候它还是个不错的选择。因此需要加入FXAA。

FXAA:效果一般,开销很小,无法解决线的锯齿问题,带来模糊(2000物体)

我们看了下各大引擎,基本每个引擎都实现了FXAA,还有很多引擎实现了FXAA3,FXAA3效果会好一些,但是依然无法解决线的问题。cesium的抗锯齿就是完全采用FXAA3,效果还可以接受,所以目前我们把cesium的fxaa3_11拿过来用,最终引擎选择使用MSAA加FXAA3_11的抗锯齿策略(当然SMAA和TAA也可以选择)。


网页制作如何引入图标

seo达人

HTML中如何引入图标



在头部引用在HTML中里面引用一个东西,。在头部引用<script defer src="https://use.fontawesome.com/releases/v5.0.8/js/all.js"&gt;&lt;/script&gt;

然后在网上搜索font awesome。可以进那个官方(外国网站比较卡)。以下为点进那个中文网–字体图标



点击右上角图标库





3随便点一个进去





4复制<i class="fa fa-address-book-o" aria-hidden="true"></i>

引入你的代码中<span><i class="fa fa-address-book-o" aria-hidden="true"></i>></span>



<div><i class="fa fa-address-book-o" aria-hidden="true"></i>></div>

1

在div或span里引用,可以实现块状元素和行内元素的功能,不用管i标签,改变图标大小用font-size,调上下高度line-height,左右位置text-align。

部分图标可以引用不了,大致样式

3.完美是最无情的禁锢


js reduce()

seo达人

是什么

ES5提供的数组的方法。

reduce() 方法接收一个函数作为回调函数(accumulator),数组中的每个值(从左到右)开始缩减(其实就是从左往右开始对每个数执行回调函数),最终为一个值。



PS: 回调函数的返回结果类型和传入的初始值相同



语法以及参数

arr.reduce(  callback(accumulator, currentValue,index ,array ) ,initialValue )

1

initialValue 可选

如果有的话则作为,第一次调用 callback函数时的第一个参数的值。

如果没有提供初始值,callback则使用数组的第一个元素,作为第一次调用的初始值。

在没有初始值的空数组上调用 reduce 将报错。



accumulator

默认传入上一次调用回调函数的的返回值。

初始值: initialValue存在的话,则是initialValue 若没有则是数组的第一个元素



currentValue

数组中正在处理的元素。



index 可选

数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。



array可选

调用reduce()的数组



一个小小的例子

例1 无initialValue

var  arr = [1, 2, 3, 4, 5];

sum = arr.reduce(function(result, cur, index, arr) {

    console.log(result, cur, index,arr);

    return result+ cur;

})

console.log(sum) // 最后的结果是15



result cur index arr

第1次 1 2 1 [1, 2, 3, 4, 5]

第2次 3 3 2 [1, 2, 3, 4, 5]

第3次 6 4 3 [1, 2, 3, 4, 5]

第4次 10 5 4 [1, 2, 3, 4, 5]

例2 有initialValue 传入10

var  arr = [1, 2, 3, 4, 5];

sum = arr.reduce(function(result, cur, index, arr) {

    console.log(result, cur, index,arr);

    return result+ cur;

},10)

console.log(sum) // 最后的结果是25



result cur index arr

第1次 10 1 0 [1, 2, 3, 4, 5]

第2次 11 2 1 [1, 2, 3, 4, 5]

第3次 13 3 2 [1, 2, 3, 4, 5]

第4次 16 4 3 [1, 2, 3, 4, 5]

第5次 20 5 4 [1, 2, 3, 4, 5]

回调函数的返回值

上面的例子返回的都是一个整型数字,如果希望返回其他类型的数据呢?



这个就跟accumulator的初始值有关系了。

下面的例子我们传入的是一个object {sum: 0}



var items = [0,1,2,3,4];

var reducer = function add(sumT, item) {

  console.log(sumT)

  sumT.sum = sumT.sum + item;

  return sumT;

};

var total = items.reduce(reducer, {sum: 0});

console.log(total); // {sum:1130}



运行结果



 {sum: 0}

 {sum: 1}

 {sum: 3}

 {sum: 6}

 {sum: 10}



reduce()的应用

  1. 数组扁平化

    递归+reduce



    let arr = [1, 2, '3js', [4, 5, [6], [7, 8, [9, 10, 11], null, 'abc'], {age: 12}, [13, 14]], '[]'];



    function flatten(arr) {

      if(Array.isArray(arr)) {

        return arr.reduce((prev, cur) => {

           // 如果遍历的当前项是数组,递归调用flatten

          return Array.isArray(cur) ? prev.concat(flatten(cur)) : prev.concat(cur)

        }, [])

      } else {

        throw new Error(' 当前参数不是数组')

      }

    }

    console.log(flatten(arr));



    PS:这里的throw new Error只是用来判断一开始的arr,这是因为在递归只传入数组。


node系列之数据接口注册接口的实现(token验证登陆)

seo达人

node系列之数据接口注册登陆接口的实现

1、使用express脚手架创建项目

2、了解项目的目录结构

3、准备数据库相关文件

4、编写注册接口

5、编写登陆接口

6、验证登陆实现

7、预告

1、使用express脚手架创建项目

// 安装脚手架,只需安装一次

npm i express-generator -g

// 创建express项目

express myapp --view=ejs

cd myapp

// 安装依赖

npm i 

// 安装需要使用的模块

// 数据库模块 用户唯一id模块 密码加密模块 token模块

npm i mongoose node-uuid bcryptjs jsonwebtoken -S



2、了解项目的目录结构

bin

www ------- 服务器启动

node_modules ------- 项目的依赖文件

public ------- 静态资源文件夹

images ------- 静态图片

javascripts ------- 静态的js文件

stylesheets ------- 静态的样式表文件

routes ------- 路由文件

index.js ------- 默认的路由

users.js ------- 用户相关的路由

views ------- 路由对应的页面

index.ejs ------- 默认的首页

error.ejs ------- 错误页面

app.js ------- 使用中间件,注册路由

package.json ------- 描述文件

3、准备数据库相关文件

大勋在node系列之数据库mongoose的封装中给大家介绍了如何封装mongoose,可以先行查看如何封装,封装的文件夹为sql,如果不想看的,可以直接通过网盘下载该文件夹



将该sql文件放置项目的跟目录下


  • myapp

    - sql

    - collection

    users.js

    db.js

    index.js



    4、编写注册接口

    目标文件: myapp/routes/users.js



    实现思路:使用post提交数据的方式,先以手机号查询有没有该用户,如果有该用户,提示用户该账号已经注册过了;如果没有该用户,则可以完成注册,首先得将密码加密,加密完成后插入数据库



    代码实现:



    // 找到用户集合

    var User = require('./../sql/collection/users');

    // 找到数据库封装文件

    var sql = require('./../sql');

    // 状态码的封装

    var utils = require('./../utils')

    // 用户唯一标识的id

    var uuid = require('node-uuid');

    // 密码加密模块

    var bcrypt = require('bcryptjs');

    var salt = bcrypt.genSaltSync(10); // 加密级别



    // 实现注册接口 -- post提交方式

    router.post('/register', (req, res, next) => {

      // 1、先获取表单信息

      let { username, password, tel } = req.body;

      // 2、根据手机号查询 用户集合中是否有该用户,如果有,返回有该账户,如果没有注册继续

      sql.find(User, { tel }, { id: 0 }).then(data => {

        // 2.1 判断有没有该用户

        if (data.length === 0) {

          // 2.2 没有该用户----继续完成注册操作

          // 2.2.1 生成用户的id

          let userid = 'users
    ' + uuid.v1();

          // 2.2.2 对密码加密

          password = bcrypt.hashSync(password, salt)

          // 2.2.3 插入数据库

          sql.insert(User, { userid, username, password, tel}).then(() => {

            res.send(utils.registersuccess)

          })

        } else {

          // 2.3 已有该用户

          res.send(utils.registered)

        }

      })

    })



    附 状态码封装模块 myapp/utils/index.js

    module.exports = {

      registered: {

        code: '10000',

        message: '该用户已注册,请直接登录' 

      },

      registersuccess: {

        code: '10101',

        message: '注册成功' 

      }

    }



    5、编写登陆接口

    目标文件 myapp/routes/users.js

    实现思路:根据手机号查询有没有该用户,如果没有,提示用户未注册,如果有该用户,使用bcryptjs模块验证密码的有效性,如果有效,生成token,返回给前端相应的token值。

    var jwt = require('jsonwebtoken');

    // 实现登陆功能

    router.post('/login', (req, res, next) => {

      // 1、获取表单信息

      let { tel, password } = req.body;

      // 2、依据手机号查询有没有该用户

      sql.find(User, { tel }, { _id: 0 }).then(data => {

        // 2.1 判断有么有该用户

        if (data.length === 0) {

          // 2.2 没有该用户

          res.send(utils.unregister)

        } else {

          // 2.3 有该用户,验证密码

          // 2.3.1 获取数据库中的密码

          let pwd = data[0].password;

          // 2.3.2 比较 输入的 密码和数据库中的密码

          var flag = bcrypt.compareSync(password, pwd) // 前为输入,后为数据库

          if (flag) {

            // 2.3.3 密码正确,生成token

            let userid = data[0].userid

            let username = data[0].username

            let token = jwt.sign({ userid, username }, 'daxunxun', {

              expiresIn: 606024// 授权时效24小时

            })

            res.send({

              code: '10010',

              message: '登陆成功',

              token: token

            })

          } else {

            // 2.3.4 密码错误

            res.send({

              code: '10100',

              message: '密码错误'

            })

          }

        }

      })

    })



    6、验证登陆实现

    目标文件: myapp/app.js

    实现思路:很多的数据请求都需要登陆之后才能获取到,在此统一封装验证登陆

    // 引入token模块

    var jwt = require('jsonwebtoken');

    // 全局的路由匹配

    app.use((req, res, next) => {

     // 排除登陆注册页面

      if (req.url !== '/users/login' && req.url !== '/users/register') {

      // 不同形式获取token值

        let token = req.headers.token || req.query.token || req.body.token;

        // 如果存在token ---- 验证

        if (token) {

          jwt.verify(token, 'daxunxun', function(err, decoded) {

            if (err) {

              res.send({ 

                code: '10119', 

                message: '没有找到token.' 

              });

            } else {

              req.decoded = decoded;  

              console.log('验证成功', decoded);

              next()

            }

          }) 

        } else { // 不存在 - 告诉用户---意味着未登录

          res.send({ 

            code: '10119', 

            message: '没有找到token.' 

          });

        }

      } else {

        next()

      }

    })




Vue源码剖析(三)patch和Diff算法

seo达人

Patch和Diff算法

网上看了好多的博客和源码教程,感觉很多仔细的地方没有说清,而且在一些复杂的部分加了好多的描述,所以就想自己也写下心得, 方便自己, 方便他人,有兴趣的同学可以关注我的github里面有我之前一些博文 github/193Eric



我们知道的,在数据更改后,会触发getter,然后通过dep.notify()来通知watcher触发update进而更新视图,最终是通过Diff算法来对比新老Vnode的差异,并把差异更新到Dom视图上



Diff

我们知道的,Virtual DOM是一颗树,而diff算法主要把两颗树进行对比,找出之间的差异,来渲染页面



diff 算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有 O(n),是一种相当的算法



1.调用patch函数比较Vnode和OldVnode,如果不一样直接return Vnode即将Vnode真实化后替换掉DOM中的节点



2.如果OldVnode和Vnode值得进一步比较则调用patchVnode方法进行进一步比较,分为以下几种情况:



Vnode有子节点,但是OldVnode没有,则将Vnode的子节点真实化后添加到真实DOM上



Vnode没有子节点,但是OldVnode上有,则将真实DOM上相应位置的节点删除掉



Vnode和OldVnode都有文本节点但是内容不一样,则将真实DOM上的文本节点替换为Vnode上的文本节点



Vnode和OldVnode上都有子节点,需要调用updateChildren函数进一步比较



updateChildren比较规则



提取出Vnode和OldVnode中的子节点分别为vCh和OldCh,并且提出各自的起始和结尾变量标记为 oldS oldE newS newE



如果是oldS和newE匹配上了,那么真实dom中的第一个节点会移到最后



如果是oldE和newS匹配上了,那么真实dom中的最后一个节点会移到最前,匹配上的两个指针向中间移动



如果都没有,在没有key的情况下直接在DOM的oldS位置的前面添加newS,同时newS+1。在有key的情况下会将newS和Oldch上的所有节点对比,如果有相同的则移动DOM并且将旧节点中这个位置置为null且newS+1。如果还没有则直接在DOM的oldS位置的前面添加newS且newS+1

直到出现任意一方的start>end,则有一方遍历结束,整个比较也结束



updateChildren例子:



假设:



真实dom为 A、B、C

oldDom为 A1、B1、C1

newDom为 A2、D2、C2、B2



先确定oldS = A1 ; oldE = C1; newS = A2; newE = B2



先对比oldS和newS,oldE和newE,发现oldS = newS 所以真实dom的A固定不动。排序为 A、B、C

然后oldS = B1 ; oldE = C1; newS = D2; newE = B2



对比发现 oldS = newE , 所以真实dom,B要插入到后面去



真实dom排序为:A、C、B



然后oldS = C1; oldE = C1; newS = D2; newE = B2



对比发现两两都不对等,所以走第三步。

假设有key存在,所以newS直接和oldDom里面的所有节点对比,发现没有存在,然后插入到oldS前面,且newS+1

真实dom排序为:A、D、C、B

然后重新开始,oldS++ > oldE-- ,结束了。



这就是updateChildren,之后就是一直遍历,运行updateChildren直到没有


日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档