Tokyo散策

Tokyo散策 查看完整档案

海外编辑大连海事大学  |  软件工程 编辑楽天  |  程序员 编辑 transjp.info 编辑
编辑

爱运动爱读书,在日创业ing!

个人动态

Tokyo散策 赞了文章 · 2019-11-15

你不知道的Virtual DOM(一):Virtual Dom介绍

欢迎关注我的公众号睿Talk,获取我最新的文章:
clipboard.png

一、前言

目前最流行的两大前端框架,React和Vue,都不约而同的借助Virtual DOM技术提高页面的渲染效率。那么,什么是Virtual DOM?它是通过什么方式去提升页面渲染效率的呢?本系列文章会详细讲解Virtual DOM的创建过程,并实现一个简单的Diff算法来更新页面。本文的内容脱离于任何的前端框架,只讲最纯粹的Virtual DOM。敲单词太累了,下文Virtual DOM一律用VD表示。

这是VD系列文章的开篇,以下是本系列其它文章的传送门:
你不知道的Virtual DOM(一):Virtual Dom介绍
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新优化
你不知道的Virtual DOM(四):key的作用
你不知道的Virtual DOM(五):自定义组件
你不知道的Virtual DOM(六):事件处理&异步更新

二、VD是什么

本质上来说,VD只是一个简单的JS对象,并且最少包含tag、props和children三个属性。不同的框架对这三个属性的命名会有点差别,但表达的意思是一致的。它们分别是标签名(tag)、属性(props)和子元素对象(children)。下面是一个典型的VD对象例子:

{
    tag: "div",
    props: {},
    children: [
        "Hello World", 
        {
            tag: "ul",
            props: {},
            children: [{
                tag: "li",
                props: {
                    id: 1,
                    class: "li-1"
                },
                children: ["第", 1]
            }]
        }
    ]
}

VD跟dom对象有一一对应的关系,上面的VD是由以下的HTML生成的

<div>
    Hello World
    <ul>
        <li id="1" class="li-1">
            第1
        </li>
    </ul>
</div>

一个dom对象,比如li,由tag(li), props({id: 1, class: "li-1"})children(["第", 1])三个属性来描述。

三、为什么需要VD

VD 最大的特点是将页面的状态抽象为 JS 对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。如 React 就借助 VD 实现了服务端渲染、浏览器渲染和移动端渲染等功能。

此外,在进行页面更新的时候,借助VD,DOM 元素的改变可以在内存中进行比较,再结合框架的事务机制将多次比较的结果合并后一次性更新到页面,从而有效地减少页面渲染的次数,提高渲染效率。我们先来看下页面的更新一般会经过几个阶段。
clipboard.png

从上面的例子中,可以看出页面的呈现会分以下3个阶段:

  • JS计算
  • 生成渲染树
  • 绘制页面

这个例子里面,JS计算用了691毫秒,生成渲染树578毫秒,绘制73毫秒。如果能有效的减少生成渲染树和绘制所花的时间,更新页面的效率也会随之提高。
通过VD的比较,我们可以将多个操作合并成一个批量的操作,从而减少dom重排的次数,进而缩短了生成渲染树和绘制所花的时间。至于如何基于VD更有效率的更新dom,是一个很有趣的话题,日后有机会将另写一篇文章介绍。

四、如何实现VD与真实DOM的映射

我们先从如何生成VD说起。借助JSX编译器,可以将文件中的HTML转化成函数的形式,然后再利用这个函数生成VD。看下面这个例子:

function render() {
    return (
        <div>
            Hello World
            <ul>
                <li id="1" class="li-1">
                    第1
                </li>
            </ul>
        </div>
    );
}

这个函数经过JSX编译后,会输出下面的内容:

function render() {
    return h(
        'div',
        null,
        'Hello World',
        h(
            'ul',
            null,
            h(
                'li',
                { id: '1', 'class': 'li-1' },
                '\u7B2C1'
            )
        )
    );
}

这里的h是一个函数,可以起任意的名字。这个名字通过babel进行配置:

// .babelrc文件
{
  "plugins": [
    ["transform-react-jsx", {
      "pragma": "h"    // 这里可配置任意的名称
    }]
  ]
}

接下来,我们只需要定义h函数,就能构造出VD

function flatten(arr) {
    return [].concat.apply([], arr);
}

function h(tag, props, ...children) {
    return {
        tag, 
        props: props || {}, 
        children: flatten(children) || []
    };
}

h函数会传入三个或以上的参数,前两个参数一个是标签名,一个是属性对象,从第三个参数开始的其它参数都是children。children元素有可能是数组的形式,需要将数组解构一层。比如:

function render() {
    return (
        <ul>
            <li>0</li>
            {
                [1, 2, 3].map( i => (
                    <li>{i}</li>
                ))
            }
        </ul>
    );
}

// JSX编译后
function render() {
    return h(
        'ul',
        null,
        h(
            'li',
            null,
            '0'
        ),
        /*
         * 需要将下面这个数组解构出来再放到children数组中
         */
        [1, 2, 3].map(i => h(
            'li',
            null,
            i
        ))
    );
}

继续之前的例子。执行h函数后,最终会得到如下的VD对象:

{
    tag: "div",
    props: {},
    children: [
        "Hello World", 
        {
            tag: "ul",
            props: {},
            children: [{
                tag: "li",
                props: {
                    id: 1,
                    class: "li-1"
                },
                children: ["第", 1]
            }]
        }
    ]
}

下一步,通过遍历VD对象,生成真实的dom

// 创建dom元素
function createElement(vdom) {
    // 如果vdom是字符串或者数字类型,则创建文本节点,比如“Hello World”
    if (typeof vdom === 'string' || typeof vdom === 'number') {
        return doc.createTextNode(vdom);
    }

    const {tag, props, children} = vdom;

    // 1. 创建元素
    const element = doc.createElement(tag);

    // 2. 属性赋值
    setProps(element, props);

    // 3. 创建子元素
    // appendChild在执行的时候,会检查当前的this是不是dom对象,因此要bind一下
    children.map(createElement)
            .forEach(element.appendChild.bind(element));

    return element;
}

// 属性赋值
function setProps(element, props) {
    for (let key in props) {
        element.setAttribute(key, props[key]);
    }
}

createElement函数执行完后,dom元素就创建完并展示到页面上了(页面比较丑,不要介意...)。

clipboard.png

五、总结

本文介绍了VD的基本概念,并讲解了如何利用JSX编译HTML标签,然后生成VD,进而创建真实dom的过程。下一篇文章将会实现一个简单的VD Diff算法,找出2个VD的差异并将更新的元素映射到dom中去:你不知道的Virtual DOM(二):Virtual Dom的更新

P.S.: 想看完整代码见这里,如果有必要建一个仓库的话请留言给我:代码

查看原文

赞 405 收藏 308 评论 20

Tokyo散策 回答了问题 · 2019-09-25

解决mysql 几十万条数据如何优化查询。数据太多查询太慢了

几十万数据不多,已经添加索引了吗?没有的话加一下索引试试。
https://dev.mysql.com/doc/ref...

关注 7 回答 4

Tokyo散策 赞了问题 · 2019-09-25

redis使用hash存入json对象时,如何解决嵌套对象变成[object,object]问题。

我在使用ioredis 的hmset()方法存入json对象,如:

{
    id:"0001",
    name:"ande",
    position:{
        lat:113.88988,
        lon:257289
    }
    
}

当我获取该键值时,变成:

{
    id:"0001",
    name:"ande",
    position:[object,object]
    
}

我如何才能得到我的position的值;
以为我的position要实时更新,我不想直接转为字符串存。请问各位,如何解决呢~感谢,工作顺利!
具体代码如下:

const  Redis = require("ioredis");
const  redis = new Redis();

 // 创建用户对象(可覆盖)
exports.user_obj = async(accountID,nameID,obj) => {
    //存入obj对象
    //obj = {
    // accountID:'',
    // name:'',
    // position:{
    //   lat:'26.26890',
    //   lon:'113.27930'
    // }
    //}
    redis.hmset('user:'+accountID+':'+nameID,obj);

}
//读取用户对象时
/* 获取单个用户 */
var user_get = (query) => {

return new Promise((resolve,reject) => {
    redis.hgetall(query,(err,result) => {
        if(err){
            reject(err);
        }
        resolve(result);
    })
   })
}

读出来,obj对象就变成:
{
    accountID:'',
    name:'',
    position:[object,object]
}

请问我应该如何解决该问题呢

关注 2 回答 1

Tokyo散策 赞了回答 · 2019-09-25

redis使用hash存入json对象时,如何解决嵌套对象变成[object,object]问题。

RedisHash类型不支持多级的吧,所以你的position必须得转成字符串。

关注 2 回答 1

Tokyo散策 赞了文章 · 2019-09-25

Node.js进阶:5分钟入门非对称加密方法

前言

刚回答了SegmentFault上一个兄弟提的问题《非对称解密出错》。这个属于Node.js在安全上的应用,遇到同样问题的人应该不少,基于回答的问题,这里简单总结下。

非对称加密的理论知识,可以参考笔者前面的文章《NODEJS进阶:CRYPTO模块之理论篇》

完整的代码可以在 《Nodejs学习笔记》 找到,也欢迎大家关注 程序猿小卡的GitHub

加密、解密方法

在Node.js中,负责安全的模块是crypto。非对称加密中,公钥加密,私钥解密,加解密对应的API分别如下。

加密函数:

crypto.publicEncrypt(key, buffer)

解密函数:

crypto.privateDecrypt(privateKey, buffer)

入门例子

假设有如下utils.js

// utils.js
const crypto = require('crypto');

// 加密方法
exports.encrypt = (data, key) => {
  // 注意,第二个参数是Buffer类型
  return crypto.publicEncrypt(key, Buffer.from(data));
};

// 解密方法
exports.decrypt = (encrypted, key) => {
  // 注意,encrypted是Buffer类型
  return crypto.privateDecrypt(key, encrypted);
};

测试代码app.js

const utils = require('./utils');
const keys = require('./keys');

const plainText = '你好,我是程序猿小卡';
const crypted = utils.encrypt(plainText, keys.pubKey); // 加密
const decrypted = utils.decrypt(crypted, keys.privKey); // 解密

console.log(decrypted.toString()); // 你好,我是程序猿小卡

附上公钥、私钥 keys.js

exports.privKey = `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDFWnl8fChyKI/Tgo1ILB+IlGr8ZECKnnO8XRDwttBbf5EmG0qV
8gs0aGkh649rb75I+tMu2JSNuVj61CncL/7Ct2kAZ6CZZo1vYgtzhlFnxd4V7Ra+
aIwLZaXT/h3eE+/cFsL4VAJI5wXh4Mq4Vtu7uEjeogAOgXACaIqiFyrk3wIDAQAB
AoGBAKdrunYlqfY2fNUVAqAAdnvaVOxqa+psw4g/d3iNzjJhBRTLwDl2TZUXImEZ
QeEFueqVhoROTa/xVg/r3tshiD/QC71EfmPVBjBQJJIvJUbjtZJ/O+L2WxqzSvqe
wzYaTm6Te3kZeG/cULNMIL+xU7XsUmslbGPAurYmHA1jNKFpAkEA48aUogSv8VFn
R2QuYmilz20LkCzffK2aq2+9iSz1ZjCvo+iuFt71Y3+etWomzcZCuJ5sn0w7lcSx
nqyzCFDspQJBAN3O2VdQF3gua0Q5VHmK9AvsoXLmCfRa1RiKuFOtrtC609RfX4DC
FxDxH09UVu/8Hmdau8t6OFExcBriIYJQwDMCQQCZLjFDDHfuiFo2js8K62mnJ6SB
H0xlIrND2+/RUuTuBov4ZUC+rM7GTUtEodDazhyM4C4Yq0HfJNp25Zm5XALpAkBG
atLpO04YI3R+dkzxQUH1PyyKU6m5X9TjM7cNKcikD4wMkjK5p+S2xjYQc1AeZEYq
vc187dJPRIi4oC3PN1+tAkBuW51/5vBj+zmd73mVcTt28OmSKOX6kU29F0lvEh8I
oHiLOo285vG5ZtmXiY58tAiPVQXa7eU8hPQHTHWa9qp6
-----END RSA PRIVATE KEY-----
`;

exports.pubKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFWnl8fChyKI/Tgo1ILB+IlGr8
ZECKnnO8XRDwttBbf5EmG0qV8gs0aGkh649rb75I+tMu2JSNuVj61CncL/7Ct2kA
Z6CZZo1vYgtzhlFnxd4V7Ra+aIwLZaXT/h3eE+/cFsL4VAJI5wXh4Mq4Vtu7uEje
ogAOgXACaIqiFyrk3wIDAQAB
-----END PUBLIC KEY-----
`;

小结

可以看到,通过Node.js进行非对称加密、解密还是挺方便的。更多用法,可以参考官方文档。

相关链接

程序猿小卡的GitHub

Nodejs学习笔记

非对称解密出错

https://nodejs.org/api/crypto.html

查看原文

赞 4 收藏 16 评论 0

Tokyo散策 赞了文章 · 2019-09-25

NodeJS 加密 —— crypto 模块

在这里插入图片描述


阅读原文


加密简介

加密是以某种算法改变原有的信息数据,使得未授权用户即使获得了已加密信息,因不知解密的方法,无法得知信息真正的含义,通过这种方式提高网络数据传输的安全性,加密算法常见的有哈希算法、HMAC 算法、签名、对称性加密算法和非对称性加密算法,加密算法也分为可逆和不可逆,比如 md5 就是不可逆加密,只能暴力破解(撞库),我们在 NodeJS 开发中就是直接使用这些加密算法,crypto 模块提供了加密功能,包含对 OpenSSL 的哈希、HMAC、加密、解密、签名以及验证功能的一整套封装,核心模块,使用时不需安装。


哈希算法

哈希算法也叫散列算法,用来把任意长度的输入变换成固定长度的输出,常见的有 md5sha1 等,这类算法实现对原数据的转化过程是否能被称为加密备受争议,为了后面叙述方便我们姑且先叫做加密。

// 查看哈希加密算法的种类
const crypto = require("crypto");

// getHashes 方法用于查看支持的加密算法
console.log(crypto.getHashes());

// [ 'DSA', 'DSA-SHA', 'DSA-SHA1', 'DSA-cSHA1-old',
//   'RSA-MD4', 'RSA-MD5', 'RSA-MDC2', 'RSA-RIPEMD160',
//   'RSA-SHA', 'RSA-SHA1', 'RSA-SHA1-2', 'RSA-SHA224',
//   'RSA-SHA256', 'RSA-SHA384', 'RSA-SHA512',
//   'dsaEncryption', 'dsaWithSHA', 'dsaWithSHA1', 'dss1',
//   'ecdsa-with-SHA1', 'md4', 'md4WithRSAEncryption',
//   'md5', 'md5WithRSAEncryption', 'mdc2', 'mdc2WithRSA',
//   'ripemd', 'ripemd160', 'ripemd160WithRSA', 'rmd160',
//   'sha', 'sha1', 'sha1WithRSAEncryption', 'sha224',
//   'sha224WithRSAEncryption', 'sha256',
//   'sha256WithRSAEncryption', 'sha384',
//   'sha384WithRSAEncryption', 'sha512',
//   'sha512WithRSAEncryption', 'shaWithRSAEncryption',
//   'ssl2-md5', 'ssl3-md5', 'ssl3-sha1', 'whirlpool' ]

md5 是开发中经常使用的算法之一,官方称为摘要算法,具有以下几个特点:

  • 不可逆;
  • 不管加密的内容多长,最后输出的结果长度都是相等的;
  • 内容不同输出的结果完全不同,内容相同输出的结果完全相同。

由于相同的输入经过 md5 加密后返回的结果完全相同,所以破解时通过 “撞库” 进行暴力破解,当连续被 md5 加密 3 次以上时就很难被破解了,所以使用 md5 一般会进行多次加密。

// md5 加密 —— 返回 Buffer
const crytpo = require("crytpo");

let md5 = crytpo.createHash("md5"); // 创建 md5
let md5Sum = md5.update("hello"); // update 加密
let result = md5Sum.digest(); // 获取加密后结果

console.log(result); // <Buffer 5d 41 40 2a bc 4b 2a 76 b9 71 9d 91 10 17 c5 92>

digest 方法参数用于指定加密后的返回值的格式,不传参默认返回加密后的 Buffer,常用的参数有 hexBase64hex 代表十六进制,加密后长度为 32Base64 的结果长度为 24,以 == 结尾。

// md5 加密 —— 返回十六进制
const crypto = require("crypto");

let md5 = crypto.createHash("md5");
let md5Sum = md5.update("hello");
let result = md5Sum.digest("hex");

console.log(result); // 5d41402abc4b2a76b9719d911017c592
// md5 加密 —— 返回 Base64
const crypto = require("crypto");

let md5 = crypto.createHash("md5");
let md5Sum = md5.update("hello");
let result = md5Sum.digest("Base64");

console.log(result); // XUFAKrxLKna5cZ2REBfFkg==

update 方法的返回值就是 this,即当前实例,所以支持链式调用,较长的信息也可以多次调用 update 方法进行分段加密,调用 digest 方法同样会返回整个加密后的值。

// 链式调用和分段加密
const crypto = require("crypto");

let result = crypto
    .createHash("md5")
    .update("he")
    .update("llo")
    .digest("hex");

console.log(result); // 5d41402abc4b2a76b9719d911017c592

由于可以使用 update 进行分段加密,就可以结合流来使用,其实 crypto 的本质是创建 Transform 类型的转化流,可以将可读流转化成可写流。

// 对可读流读取的数据进行 md5 加密
const crypto = require("crypto");
let fs = require("fs");

let md5 = crypto.createHash("md5");
let rs = fs.createReadSteam("./readme.txt", {
    highWaterMark: 3
});

// 读取数据并加密
rs.on("data", data => md5.update(data));

rs.on("end", () => {
    let result = md5.digest("hex");
    console.log(result);
});

使用场景 1:经常被使用在数据的校验,比如服务器与服务器之间进行通信发送的明文摘要加 md5 加密摘要后的暗文,接收端拿到数据以后将明文摘要按照相同的 md5 算法加密后与暗文摘要对比验证,目的是防止数据传输过程中被劫持并篡改。
使用场景 2:在浏览器缓存策略中,可以通过对静态资源的信息摘要使用 md5 加密,每次向服务器发送加密后的密钥进行比对就可以了,不至于对整个文件内容进行比较。

缺点:由于规定使用 md5 的哈希算法加密,别人可以使用同样的算法对信息进行伪造,安全性不高。


Hmac 算法

1、Hmac 算法的使用

Hmac 算法又称加盐算法,是将哈希算法与一个密钥结合在一起,用来阻止对签名完整性的破坏,同样具备 md5 加密的几个特点。

// 使用加盐算法加密
const crytpo = require("crytpo");

let hmac = crytpo.createHmac("sha1", "panda");
let result = hmac.update("hello").digest("Base64");

console.log(result); // 7spMLxN8WJdcEtQ8Hm/LR9pUE3YsIGag9Dcai7lwioo=

crytpo.createHmac 第一个参数同 crytpo.createHash,为加密的算法,常用 sha1sha256,第二个参数为密钥。

digest 方法生成的加密结果长度要大于 md5hex 生成的结果长度为 64Base64 生成的结果长度为 44,以 = 结尾。

安全性高于 md5,通过密钥来加密,不知道密钥无法破解,缺点是密钥传输的过程容易被劫持,可以通过一些生成随机密钥的方式避免。

2、创建密钥的方法

可以安装 openSSH 客户端,并通过命令行生成存储密钥的文件,命令如下。

openssl genrsa -out rsa_private.key 1024

openssl genrsa 代表生成密钥,-out 代表输出文件,rsa_private.key 代表文件名,1024 代表输出密钥的大小。

// 直接读取密钥文件配合加盐算法加密
const fs = require("fs");
const crytpo = require("crytpo");
const path = require("path");

let key = fs.readFileSync(path.join(__dirname, "/rsa_private.key"));
let hmac = crytpo.createHmac("sha256", key);

let result = hmac.update("hello").digest("Base64");

console.log(result); // bmi2N+6kwgwt5b+U+zSgjL/NFs+GsUnZmcieqLKBy4M=


对称性加密

对称性加密是发送数据时使用密钥和加密算法进行加密,接收数据时需要使用相同的密钥和加密算法的逆算法(解密算法)进行解密,也就是说对称性加密的过程是可逆的,crytpo 中使用的算法为 blowfish

// 对称性加密
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");

let key = fs.readFileSync(path.join(__dirname, "/rsa_private.key"));

// 加密
let cipher = crypto.createCipher("blowfish", key);
cipher.update("hello");

// final 方法不能链式调用
let result = cipher.final("hex");
console.log(result); // 3eb9943113c7aa1e

// 解密
let decipher = crypto.createDecipher("blowfish", key);
decipher.update(result, "hex");

let data = decipher.final("utf8");
console.log(data); // hello

加密使用 crypto.createCipher 方法,解密使用 crypto.createDecipher 方法,但是使用的算法和密钥必须相同,需要注意的是解密过程中 update 中需要在第二个参数中指定加密时的格式,如 hex,在 final 还原数据时需要指定加密字符的编码格式,如 utf8

注意:使用对称性加密的字符串有长度限制,不得超过 7 个字符,否则虽然可以加密成功,但是无法解密。

缺点:密钥在传输过程中容易被截获,存在安全风险。


非对称性加密

非对称性加密相也是可逆的,较于对称性加密要更安全,消息传输方和接收方都会在本地创建一对密钥,公钥和私钥,互相将自己的公钥发送给对方,每次消息传递时使用对方的公钥加密,对方接收消息后使用他的的私钥解密,这样在公钥传递的过程中被截获也无法解密,因为公钥加密的消息只有配对的私钥可以解密。

接下来我们使用 openSSH 对之前生成的私钥 rsa_private.key 产生一个对应的公钥,命令如下。

openssl rsa -in rsa_private.key -pubout -out rsa_public.key

上面的命令意思根据一个私钥生成对应的公钥,-pubout -out 代表公钥输出,rsa_public.key 为公钥的文件名。

// 非对称性加密
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");

// 获取公钥和私钥
let publicKey = fs.readFileSync(path.join(__dirname, "/rsa_public.key"));
let privateKey = fs.readFileSync(path.join(__dirname, "/rsa_private.key"));

// 加密
let secret = crytpo.publicEncrypt(publicKey, Buffer.from("hello"));

// 解密
let result = crytpo.provateDecrypt(privateKey, secret);

console.log(result); // hello

使用公钥加密的方法是 crytpo.publicEncrypt,第一个参数为公钥,第二个参数为加密信息(必须是 Buffer),使用私钥解密的方法是 crytpo.provateDecrypt,第一个参数为私钥,第二个参数为解密的信息。


签名

签名与非对称性加密非常类似,同样有公钥和私钥,不同的是使用私钥加密,对方使用公钥进行解密验证,以确保这段数据是私钥的拥有者所发出的原始数据,且在网络中的传输过程中未被修改。

在这里插入图片描述

我们还使用 rsa_public.keyrsa_private.key 作为公钥和私钥,crypto 实现签名代码如下。

// 签名
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");

// 获取公钥和私钥
let publicKey = fs.readFileSync(path.join(__dirname, "rsa_public.key"), "ascii");
let privateKey = fs.readFileSync(path.join(__dirname, "rsa_private.key"), "ascii");

// 生成签名
let sign = crypto.createSign("RSA-SHA256");
sign.update("panda");
let signed = sign.sign(privateKey, "hex");

// 验证签名
let verify = crypto.createVerify("RSA-SHA256");
verify.update("panda");
let verifyResult = verify.verify(publicKey, signed, "hex");

console.log(verifyResult); // true

生成签名的 sign 方法有两个参数,第一个参数为私钥,第二个参数为生成签名的格式,最后返回的 signed 为生成的签名(字符串)。

验证签名的 verify 方法有三个参数,第一个参数为公钥,第二个参数为被验证的签名,第三个参数为生成签名时的格式,返回为布尔值,即是否通过验证。

使用场景:经常用于对 cookie 签名返回浏览器,当浏览器访问同域服务器将 cookie 带过来时再进行验证,防止 cookie 被篡改和 CSRF 跨站请求伪造。


总结

各种项目在数据传输时根据信息的敏感度以及用途进行不同的加密算法和加密方式,在 NodeJS 中,crypto 的 API 完全可以实现我们的加密需求,也可以将上面的加密方案组合使用实现更复杂的加密方案。


查看原文

赞 7 收藏 2 评论 1

Tokyo散策 赞了文章 · 2019-09-02

使用 Laravel 的 API 资源功能来构建你的 API

image

文章转发在专业的Laravel开发者社区,原始链接:https://learnku.com/laravel/t...

在过去的2年时间里,我一直使用 Fractal 来进行 API 开发。

如果说一个我最希望 Laravel可以增加的功能的话,无疑是方便的数据转换,以便开发更好的 API 接口。

别误会, Fractal 很好用,然而我总是希望能只用框架进行开发。如果可以的话,我尽可能不使用类库!我不喜欢使用第三方类库使得开发复杂化。

在过去的一年里,我喜欢使用一些前端框架如 Vue 和 React 来进行开发。因此,我选择只使用 Laravel来建立 API 接口。 而当我需要建立 API 接口 的时候,Fractal是我首选的类库。 现在,情况发生了变化。

在 Laravel 5.5 中,我们有了 API 资源,对此,我真的是非常的兴奋。

在2小时前,Laravel 5.5 发布了,当时我正和朋友一起喝咖啡。当我在半小时前读到这条推的时候,脑子里的第一个想法就是用 API 资源来发布我的第一篇博客文章,我也确实这么做了。

Laravel 的 API 资源是基于 Fractal , 因此,我并没有花太多时间来了解如何使用它。 所以,让我们开始来了解它吧 ...

创建 Laravel 应用

用常用的命令行来创建 Laravel 应用

composer create-project laravel/laravel Laravel55Api

应用创建完成后,将 .env.example 重命名为 .env 并用以下命令生成 Laravel 密钥。

php artisan key:generate

启动服务

php artisan serve

很好,接下来是什么呢?

创建一个 Product 资源

API 资源是在 Laravel 中将你的模型以及模型集合转换为 JSON 的新特性。接下来让我们创建一个 Product 的资源。

php artisan make:resource Product

你可以在 app/Http/Resources 目录下看到你刚刚生成的 Product 资源

当然我们还需要 Product 的数据库迁移、模型和控制器。我们能用这个命令快速的创建这些。

php artisan make:model Product -mc

打开数据库迁移文件然后像这样修改 up 方法里面的内容:

public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->integer('price');
        $table->timestamps();
    });
}

注意这里的价格字段是整型的。 我拜托你们,永远不要用浮点型存储你的价格数据!

一定要用整型来存储!

现在用你的 Laravel 应用连接数据库并运行这个迁移以生成数据表。

本文不是 Laravel 一对一教学贴,所以我不会在连接数据库的问题上浪费你过多的时间。

接下来?

到目前为止,我们已经有模型,控制器,数据库迁移以及用以转换模型和模型集合为 JSON 的资源类。那么接下来呢?

在这之前,什么是资源类?我们在 resources 文件夹中创建的 Product 类又是什么?一个资源类表示了单个模型转换为 JSON 的结构。

结合上面所阐述的,让我们来打开 Product.php 资源类文件。

这里有一个 toArray 方法,这个就是在我们发送响应时返回需要转换为 JSON 的属性数组的方法。

我们来修改它,让我们可以有更好的点子。

public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'price' => $this->price,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

现在表示我们能够获得有 id, name, price, created_atupdated_at 这些字段的响应。

如果我们在 toArray 方法中去掉 price 字段,返回的 JSON 中就不会有 price 。很酷不是吗?

使用 Product 资源

我们刚刚更改了 toArray 方法,让我们继续在我们的控制器中使用 product 资源。

product 控制器是看起来是这样的:

<?php

namespace App\Http\Controllers;

use App\Product;
use App\Http\Resources\Product as ProductResource;

class ProductController extends Controller
{
    public function show ($id)
    {
        return new ProductResource(Product::find($id));
    }
}

为了转换 product ,我们仅仅在 product 资源类中传递了一个 product

让我们创建一个 show 方法的路由,看一看结果。

打开 api.php 文件,在中间件外部创建这个路由。

Route::get('/products/{id}', 'ProductController@show');

现在,手动的在你的 products 表里添加一个新的 product,然后访问http://127.0.0.1:8000/api/pro...看看一个简单的 product

你应该得到这样的结果:

image

现在让我们来修改一点我们的资源,假如你不想公开你的 productprice,你要做的就是简单的从你的 toArray 方法删除它。一旦你从 toArray 方法删除了 price,你应该得到这样的结果,当然不包括 price

image

就这些吗?

当然不是!因为 toArray 方法仅仅是一个方法,它意味着你可以包含额外的信息。

假如我们想要包括一个「test」信息,简单的改变你的 toArray 方法。

public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'test' => 'This is just a test',
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

这是结果:

image

然而,非常要重的是,你希望返回的数据类型总是正确的。在第一个截图上看看 price,它返回的是一个 integer,但是通过 (int) $this->price,我们仍然要强制它是一个integer

现在,看看 create_atupdated_at 时间戳。如果你想要的是返回一个实际时间戳的字符串?那么你可以像这个例子中,强制类型转换为字符串:

public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'test' => 'This is just a test',
        'created_at' => (string)$this->created_at,
        'updated_at' => (string)$this->updated_at,
    ];
}

现在的结果是这个:

image

结束语

这仅仅是使用 Laravel API 资源的一个小例子。

如果我继续写下去,这篇文章永远也写不完。

所以,这篇文章就写到这吧,正如你所知道的,我们还有更多话题需要讨论,比如分页、资源集合、关联以及数据包裹等。

查看原文

赞 7 收藏 5 评论 0

Tokyo散策 收藏了文章 · 2019-08-08

NodeJs 实现简单WebSocket 即时通讯

服务器的实现很简单,先装一个nodeJs的模块,叫nodejs-websocket , 直接在nodeJs命令行中敲入:npm install nodejs-websocket回车就可以安装好了,
然后就可以开始建立服务器了,因为有了nodejs-websocket模块,所以很多工作都不用我们自己做,直接调用别人封装好的方法就行了:
  • 服务端代码
  • 根据客户端传来的消息判断哪个是game1,哪个是game2,保存connection对象。
var ws = require("nodejs-websocket");
console.log("开始建立连接...")

var game1 = null,game2 = null , game1Ready = false , game2Ready = false;
var server = ws.createServer(function(conn){
    conn.on("text", function (str) {
        console.log("收到的信息为:"+str)
        if(str==="game1"){
            game1 = conn;
            game1Ready = true;
            conn.sendText("success");
        }
        if(str==="game2"){
            game2 = conn;
            game2Ready = true;
        }

        if(game1Ready&&game2Ready){
            game2.sendText(str);
        }

        conn.sendText(str)
    })
    conn.on("close", function (code, reason) {
        console.log("关闭连接")
    });
    conn.on("error", function (code, reason) {
        console.log("异常关闭")
    });
}).listen(8001)
console.log("WebSocket建立完毕")

【game1代码】:通过点击获取三个框的内容,传到服务器

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .kuang{text-align: center;margin-top:200px;}
        #mess{text-align: center}
        .value{width: 200px;height:200px;border:1px solid;text-align: center;line-height: 200px;display: inline-block;}
    </style>
</head>
<body>
    <div id="mess">正在连接...</div>
    <div class="kuang">
        <div class="value" id="value1">小明小明</div>
        <div class="value" id="value2">啦啦啦</div>
        <div class="value" id="value3">小张小张</div>
    </div>

    <script>
        var mess = document.getElementById("mess");
        if(window.WebSocket){
            var ws = new WebSocket('ws://127.0.0.1:8001');

            ws.onopen = function(e){
                console.log("连接服务器成功");
                ws.send("game1");
            }
            ws.onclose = function(e){
                console.log("服务器关闭");
            }
            ws.onerror = function(){
                console.log("连接出错");
            }

            ws.onmessage = function(e){
                mess.innerHTML = "连接成功"
                document.querySelector(".kuang").onclick = function(e){
                    var time = new Date();
                    ws.send(time + "  game1点击了“" + e.target.innerHTML+"”");
                }
            }
        }
    </script>
</body>
</html>

【game2代码】:获取服务推送来的消息,并且显示

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .kuang{text-align: center;margin-top:200px;}
        #mess{text-align: center}
    </style>
</head>
<body>
    <div id="mess"></div>

    <script>
        var mess = document.getElementById("mess");
        if(window.WebSocket){
            var ws = new WebSocket('ws://127.0.0.1:8001');

            ws.onopen = function(e){
                console.log("连接服务器成功");
                ws.send("game2");
            }
            ws.onclose = function(e){
                console.log("服务器关闭");
            }
            ws.onerror = function(){
                console.log("连接出错");
            }

            ws.onmessage = function(e){
                var time = new Date().t.toLocaleDateString();
                mess.innerHTML+=time+"的消息:"+e.data+"<br>"
            }
        }
    </script>
</body>
</html>

运行截图:
图片描述

查看原文

Tokyo散策 赞了文章 · 2019-08-08

NodeJs 实现简单WebSocket 即时通讯

服务器的实现很简单,先装一个nodeJs的模块,叫nodejs-websocket , 直接在nodeJs命令行中敲入:npm install nodejs-websocket回车就可以安装好了,
然后就可以开始建立服务器了,因为有了nodejs-websocket模块,所以很多工作都不用我们自己做,直接调用别人封装好的方法就行了:
  • 服务端代码
  • 根据客户端传来的消息判断哪个是game1,哪个是game2,保存connection对象。
var ws = require("nodejs-websocket");
console.log("开始建立连接...")

var game1 = null,game2 = null , game1Ready = false , game2Ready = false;
var server = ws.createServer(function(conn){
    conn.on("text", function (str) {
        console.log("收到的信息为:"+str)
        if(str==="game1"){
            game1 = conn;
            game1Ready = true;
            conn.sendText("success");
        }
        if(str==="game2"){
            game2 = conn;
            game2Ready = true;
        }

        if(game1Ready&&game2Ready){
            game2.sendText(str);
        }

        conn.sendText(str)
    })
    conn.on("close", function (code, reason) {
        console.log("关闭连接")
    });
    conn.on("error", function (code, reason) {
        console.log("异常关闭")
    });
}).listen(8001)
console.log("WebSocket建立完毕")

【game1代码】:通过点击获取三个框的内容,传到服务器

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .kuang{text-align: center;margin-top:200px;}
        #mess{text-align: center}
        .value{width: 200px;height:200px;border:1px solid;text-align: center;line-height: 200px;display: inline-block;}
    </style>
</head>
<body>
    <div id="mess">正在连接...</div>
    <div class="kuang">
        <div class="value" id="value1">小明小明</div>
        <div class="value" id="value2">啦啦啦</div>
        <div class="value" id="value3">小张小张</div>
    </div>

    <script>
        var mess = document.getElementById("mess");
        if(window.WebSocket){
            var ws = new WebSocket('ws://127.0.0.1:8001');

            ws.onopen = function(e){
                console.log("连接服务器成功");
                ws.send("game1");
            }
            ws.onclose = function(e){
                console.log("服务器关闭");
            }
            ws.onerror = function(){
                console.log("连接出错");
            }

            ws.onmessage = function(e){
                mess.innerHTML = "连接成功"
                document.querySelector(".kuang").onclick = function(e){
                    var time = new Date();
                    ws.send(time + "  game1点击了“" + e.target.innerHTML+"”");
                }
            }
        }
    </script>
</body>
</html>

【game2代码】:获取服务推送来的消息,并且显示

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .kuang{text-align: center;margin-top:200px;}
        #mess{text-align: center}
    </style>
</head>
<body>
    <div id="mess"></div>

    <script>
        var mess = document.getElementById("mess");
        if(window.WebSocket){
            var ws = new WebSocket('ws://127.0.0.1:8001');

            ws.onopen = function(e){
                console.log("连接服务器成功");
                ws.send("game2");
            }
            ws.onclose = function(e){
                console.log("服务器关闭");
            }
            ws.onerror = function(){
                console.log("连接出错");
            }

            ws.onmessage = function(e){
                var time = new Date().t.toLocaleDateString();
                mess.innerHTML+=time+"的消息:"+e.data+"<br>"
            }
        }
    </script>
</body>
</html>

运行截图:
图片描述

查看原文

赞 21 收藏 17 评论 5

Tokyo散策 赞了文章 · 2019-07-26

搞定PHP面试 - 常见排序算法及PHP实现

常见排序算法及PHP实现

全文代码使用PHP7.2语法编写

流程图生成工具:https://visualgo.net

0. 五种基础排序算法对比

五种基础排序算法对比

1. 冒泡排序(Bubble Sort)

冒泡排序 是一种交换排序,它的基本思想是:对待排序记录从后往前(逆序)进行多遍扫描,当发现相邻两条记录的次序与排序要求的规则不符时,就将这两个记录进行交换。这样,值较小的记录将逐渐从后面向前移动,就像气泡在水中向上浮一样。

算法描述

假设需要排序的记录有 n 个,其值保存在数组 A 中,使用冒泡排序法,需对数组 A 进行 n-1 次扫描,完成排序操作。具体过程如下:

  1. 将 A[n-1] 与 A[n] 进行比较,若 A[n] < A[n-1] ,则交换两元系的位置。
  2. 修改数组下标,使需要比较的两个元素为 A[n-1] 和 A[n-2] ,重复步骤(1),对这两个元素进行比较。重复这个过程,直到对 A[1] 和 A[0] 进行比较完为止。完成第1遍扫描。
  3. 经过第1遍扫描后,最小的元素已经像气泡一样“浮”到最上面,即位于元素 A[0] 中了。接下来重复前面的步骤,进行第2遍扫描,只是扫描结束位置到 A[2] 与 A[1] 进行比较完为止(因为A[0]中已经是最小的数据,不用再进行比较)。
  4. 通过 n-1 遍扫描,前 n-1 个数都已经排序完成,最后一个元素 A[n] 肯定就是最大的数了。至此,完成排序操作。

冒泡排序

代码实现

/**
 * 冒泡排序
 * @param array $arr
 */
function bubbleSort(array &$arr) : void
{
    $length = count($arr);

    // 外层循环,从数组首部开始,每完成一次循环,可确定 $arr[$i] 位置的元素
    for ($i = 0; $i < $length; $i++){

        // 内层循环,$j 从后往前循环
        for ($j = $length - 1; $j > $i; $j--) {

            // 若前面的值大于后面的值,则互换位置
            if ($arr[$j] < $arr[$j - 1]) {
                // 互换数组两个位置的值
                [$arr[$j], $arr[$j - 1]] = [$arr[$j - 1], $arr[$j]];
            }
        }
    }
}

2. 选择排序(Selection Sort)

选择排序是通过 n-i 次关键字间的比较,从 n-i+1 个记录中选出关键字最小的记录,并和第 i ( 1 <= i <= n ) 个记录交换。

算法描述

  1. 维护数组中最小的前 n 个元素的已排序序列。
  2. 每次从剩余未排序的元素中选取最小的元素,将其放在已排序序列的后面,作为序列的第 n+1 个记元素。
  3. 以空序列作为排序工作的开始,直到未排序的序列里只剩一个元素时(它必然为最大),只需直接将其放在已排序的记录之后,整个排序就完成了。

选择排序

代码实现

/**
 * 选择排序
 * @param array $arr
 */
function selectionSort(array &$arr) : void
{
    $length = count($arr);

    // 外层循环,从数组首部开始,每完成一次循环,可确定一个元素的位置
    for ($i = 0; $i < $length - 1; $i++) {
        // 选定的最小值的索引
        $minIdx = $i;
        // 从 $i + 1 位开始循环,判断当前选定的元素是否是当次循环的最小值
        for ($j = $i + 1; $j < $length; $j++) {
            // 若出现比选定的值还小的值,则替换最小值的索引
            if ($arr[$minIdx] > $arr[$j]) {
                $minIdx = $j;
            }
        }
        // 互换数组两个位置的值
        [$arr[$i], $arr[$minIdx]] = [$arr[$minIdx], $arr[$i]];
    }
}
/**
 * 选择排序 - 方法2
 * @param array $arr
 */
function selectionSort2(array &$arr) : void
{
    $length = count($arr);
    
    // 外层循环,从数组首部开始,每完成一次循环,依次确定数组元素的位置
    for ($i = 0; $i < $length; $i++) {
        // 从 $i + 1 位开始循环,依次判定 $arr[$i] 与 $arr[$j] 的大小
        for ($j = $i + 1; $j < $length; $j++) {
            // 若 $arr[$i] 比 $arr[$j] 大,则互换两个元素的位置
            if ($arr[$i] > $arr[$j]) {
                // 互换数组两个位置的值
                [$arr[$j], $arr[$i]] = [$arr[$i], $arr[$j]];
            }
        }
    }
}

3. 插入排序(Insertion Sort)

插入排序 是通过构建有序序列,从未排序数据中选择一个元素,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在从后向前扫描过程中,需要把已排序元素逐个向后移动,为最新元素提供插入空间。

算法描述

  1. 对于第1个元素,因为没有比较,将其作为已经有序的序列。
  2. 从数组中获取下一个元素,在已经排序的元素序列中从后向前扫描,并进行判断。
  3. 若排序序列的元素大于新元素,则将该元素向后移动一位。
  4. 重复步骤(3),直到在已排序的元素中找到小于或者等于新元素的元素,将新元素插入到该元素的后面。
  5. 重复步骤(2) ~ (4),直到完成排序。

插入排序

代码实现

/**
 * 插入排序
 * @param array $arr
 */
function insertionSort(array &$arr) : void
{
    $length = count($arr);

    // 从数组首部开始排序,每完成一次循环,可确定一个元素的位置
    for ($i = 0; $i < $length - 1; $i++) {
        // 内层循环从 $i + 1 个元素开始,一位一位向前比较
        // 若前面的值比自己大,则替换,直到前面的值比自己小了,停止循环
        for ($j = $i + 1; $j > 0; $j--) {
            if ($arr[$j] >= $arr[$j - 1]) {
                break;
            }
            [[$arr[$j], $arr[$j - 1]]] = [[$arr[$j - 1], $arr[$j]]];
        }

    }
}
/**
 * 插入排序 - 方法2
 * @param array $arr
 */
function insertionSort2(array &$arr) : void
{
    $length = count($arr);

    // 从数组首部开始排序,每完成一次循环,可确定一个元素的位置
    for ($i = 0; $i < $length - 1; $i++) {
        // 从第二个元素开始,选择固定位置的值作为基准值
        $currentVal = $arr[$i + 1];
        // 初始键位于选定值的前一个位置
        $preIdx = $i;

        // 拿基准值一步一步向前比较,直到基准值比前面的值小,则两值互换位置
        while ($preIdx >= 0 && $currentVal < $arr[$preIdx]) {
            $arr[$preIdx + 1] = $arr[$preIdx];
            $arr[$preIdx] = $currentVal;
            $preIdx--;
        }

    }
}

4. 快速排序(Quick Sort)

快速排序是通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

算法描述

快速排序使用分治策略来把待排序数据序列分为两个子序列,具体步骤如下:

  1. 从数列中挑出一个元素,以该元素为“基准”。
  2. 扫描一遍数列,将所有比“基准”小的元素排在基准前面,所有比“基准”大的元素排在基准后面。
  3. 通过递归,将各子序列划分为更小的序列,直到把小于基准值元素的子数列和大于基谁值元素的子数列排序。

快速排序

代码实现

/**
 * 快速排序
 * @param $arr
 */
function quickSort(& $arr) : void
{
    $length = count($arr);

    // 若数组为空,则不需要运行
    if ($length <= 1) {
        return;
    }

    $middle = $arr[0]; // 选定一个中间值

    $left = []; // 接收小于中间值
    $right = [];// 接收大于中间值

    // 循环比较
    for ($i = 1; $i < $length; $i++) {

        if ($middle < $arr[$i]) {
            // 大于中间值
            $right[] = $arr[$i];
        } else {
            // 小于或等于中间值
            $left[] = $arr[$i];
        }
    }

    // 递归排序划分好的左右两边
    quickSort($left);
    quickSort($right);

    $arr = array_merge($left, [$middle], $right);
}

5. 归并排序(Merge Sort)

算法描述

归并是一种典型的序列操作,其工作是把两个或更多有序序列合并为一个有序序列。基于归并的思想也可以实现排序,称为归并排序。基本方法如下:

  1. 初始时,把待排序序列中的 n 个元素看成 n 个有序子序列(因为只有1个元素的序列总是排好序的),每个子序列的长度均为1。
  2. 把序列组里的有序子序列两两归并,每完成一论归并,序列组里的序列个数减半,每个子序列的长度加倍。
  3. 对加长的有序子序列重复上面的操作,最终得到一个长度为 n 的有序序列。

这种归并方法也称为简单的二路归并排序,其中每次操作都是把两个有序序列合并为一个有序序列。也可考虑三路归并或更多路的归并。

归并排序

代码实现

/**
 * 归并排序
 * @param array $arr
 * @return array
 */
function mergeSort(array $arr)
{
    // 计算数组长度,若长度不大于1,则不需要排序
    $length = count($arr);
    if ($length <= 1) {
        return $arr;
    }

    // 获取数组中间位置的索引
    $midIdx = floor($length / 2);

    // 把数组从中间拆分成左右两部分
    $left = mergeSort(array_slice($arr, 0, $midIdx));
    $right = mergeSort(array_slice($arr, $midIdx));

    // 合并两部分,同时进行排序
    return merge($left, $right);
}

/**
 * 合并数组,同时进行排序
 * @param array $left
 * @param array $right
 * @return array
 */
function merge(array $left, array $right)
{
    // 分别计算左右两数组的长度
    $lLength = count($left);
    $rLength = count($right);

    // 左右两数组的索引
    $l = $r = 0;

    $lists = [];

    // 只有左右两数组都未遍历完成时,才有必要继续遍历
    // 当其中一个数组的元素遍历完成,说明另一个数组中未遍历过的值比遍历过的值都大
    while ($l < $lLength && $r < $rLength) {
        // 比较 $left[$l] 和 $right[$r],取其中较小的值加入到 $lists 数组中
        if ($left[$l] < $right[$r]) {
            $lists[] = $left[$l];
            $l++;
        } else {
            $lists[] = $right[$r];
            $r++;
        }
    }

    // 合并 $lists 和 $left、$right 中剩余的元素
    return array_merge($lists, array_slice($left, $l), array_slice($right, $r));
}
/**
 * 合并数组,同时进行排序 - 方法2
 * @param array $left
 * @param array $right
 * @return array
 */
function merge2(array $left, array $right)
{
    // 分别计算左右两数组的长度
    $lLength = count($left);
    $rLength = count($right);

    // 左右两数组的索引
    $l = $r = 0;

    $lists = [];

    // 只有左右两数组都未遍历完成时,才有必要继续遍历
    // 当其中一个数组的元素遍历完成,说明另一个数组中未遍历过的值比遍历过的值都大
    while ($l < $lLength && $r < $rLength) {
        // 比较 $left[$l] 和 $right[$r],取其中较小的值加入到 $lists 数组中
        if ($left[$l] < $right[$r]) {
            $lists[] = $left[$l];
            // PHP 中 unset 掉数组中的元素后,其他元素的键名不变
            unset($left[$l]);
            $l++;
        } else {
            $lists[] = $right[$r];
            unset($right[$r]);
            $r++;
        }
    }

    // 合并 $lists 和 $left、$right 中剩余的元素
    return array_merge($lists, $left, $right);
}
/**
 * 合并数组,同时进行排序 - 方法3
 * @param array $left
 * @param array $right
 * @return array
 */
function merge3(array $left, array $right)
{
    // 分别计算左右两数组的长度
    $lLength = count($left);
    $rLength = count($right);

    $lists = [];

    // 只有左右两数组都未遍历完成时,才有必要继续遍历
    // 当其中一个数组的元素遍历完成,说明另一个数组中未遍历过的值比遍历过的值都大
    while ($lLength > 0 && $rLength > 0) {
        // 比较 $left[$l] 和 $right[$r],取其中较小的值加入到 $lists 数组中
        if ($left[0] < $right[0]) {
            $lists[] = $left[0];
            // PHP中 unset 掉数组中的元素后,其他元素的键名不变
            unset($left[0]);
            // 重建数组索引,始终让比较的值在第一位
            $left = array_values($left);
            $lLength--;
        } else {
            $lists[] = $right[0];
            unset($right[0]);
            $right = array_values($right);
            $rLength--;
        }
    }

    // 合并 $lists 和 $left、$right 中剩余的元素
    return array_merge($lists, $left, $right);
}
查看原文

赞 43 收藏 31 评论 1

认证与成就

  • 获得 33 次点赞
  • 获得 20 枚徽章 获得 0 枚金徽章, 获得 6 枚银徽章, 获得 14 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-03-20
个人主页被 695 人浏览