不使用 JSSDK 的页面,微信分享后的样子会变得很糟糕。今天来为文章页加上这个功能,以后分享内容给朋友,分享卡片也会变得美美哒。

这里需要准备一些必要的东西:

测试号

使用微信扫码进入测试号页面就可以拿到 appIDappsecret

测试号信息-
appIDwx7e2dbb91447*
appsecret0f29148f623301adc7b2ccfd2c2*
关注测试号

为了后续功能的开发与测试,扫描页面测试号的二维码,关注自己的测试号。

设置JS接口安全域名 misaka.im

只有该域名下的代码才能使用 JSSDK 的功能,否则会出现域名无效的错误。

后端

这里有个比较奇葩的操作,要在微信中使用 JSSDK 的功能,必须先发送浏览器当前 URL 到后端进行签名,得到 JSSDK 初始化必要的信息,才能进行初始化。

路由

使用 test.misaka.im 子域来做签名处理,使用 laravel/lumen 框架,避免与博客环境冲突。

$router->group(['prefix' => 'weixin'], function () use ($router){
    $router->get('/sign', 'wxController@sign');
});

添加路由,就是前端请求获得配置参数的地址 https://test.misaka.im/weixin/sign?url=,额外的 url 参数通过控制器函数获得。

控制器

直接使用别人写好的轮子 thenbsp/wechat,集成了微信开发的大部分功能。

namespace App\Http\Controllers;

use Doctrine\Common\Cache\FilesystemCache;

use Illuminate\Http\Request;

use Thenbsp\Wechat\Wechat\AccessToken;

use Thenbsp\Wechat\Wechat\Jsapi;

//AppSecret
const AppSecret = '0f29148f623301adc7b2ccfd2c2*****';
//AppId
const AppId = 'wx7e2dbb91447*******';


protected $cacheDriver;

/**

 * wxController constructor.

 */

public function __construct()
{
    $this->cacheDriver = new FilesystemCache('../cache');
}


public function sign(Request $request)
{
    $accessToken = new AccessToken(static::AppId, static::AppSecret);

    if (!$accessToken->getTokenString()) {
        $accessToken->setCache($this->cacheDriver);
    }

    $jsapi = new Jsapi($accessToken);

    $jsapi->setCache($this->cacheDriver);

    if ($request->has('url')) {
        $jsapi->setCurrentUrl($request->get('url'));
    }

    if ($request->has('apis')) {
        foreach (explode(',', $request->get('apis')) as $api) {
            try {
                $jsapi->addApi($api);
            } catch (\Exception $e) {
                return response($e->getMessage(), 400);
            }
        }
    }

    return response(['data' => $jsapi->getConfig(true)])->header('Access-Control-Allow-Origin', '*');
}

逻辑代码中填写测试号的 AppSecretAppId 。通过获取 url 参数来签名,返回 JSSDK 所需的初始化参数。

因为前端请求域名不是主站,存在跨域问题,另外返回了允许跨域的 HTTP header。

响应结果

浏览器访问 https://test.misaka.im/weixin/sign?url=https%3A%2F%2Fmisaka.im%2Findex.php%2Farchives%2F41%2F 可以测试签名结果:

{
    "data": {
        "appId"    : "wx7e2dbb91447*****",
        "nonceStr" : "vFMyHt6OoE",
        "timestamp": "1544497646",
        "signature": "bc29b6bbffea958f1928bbf083828f4bf87cace8",
        "jsApiList": [],
        "debug"    : false
    }
}

前端

官方 demo 使用了后端渲染的页面直接获取当前浏览器的 URL ,当多数情况是通过接口方式获取前端的:

encodeURIComponent(window.location.href)

如果前端使用 Vue 这些 SPA 框架时,这个情况变得更加复杂:

同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化(路由)时进行调用
引入 JSSDK

在主题模板 header.php 中引入

<script src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
初始化

Typecho 中使用了 jQuery 1.9 ,直接使用 jQuery Ajax 来发起请求。

在模板 footer.php 中加入:

var desc        = "一眨眼一年,我的钱呢?";
var share_title = $(".content > h1").text();
var share_icon  = 'http://ww1.sinaimg.cn/large/8b2a5a08gy1fy1s3tn6j8j203u03saa0.jpg';
var share_link  = window.location.href.split("#")[0].split("?")[0];

$.getJSON('//test.misaka.im/weixin/sign?url=' + encodeURIComponent(window.location.href), function (res) {
  console.log(res)
  
  wx.config({
    debug:     false,              // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId:     res.data.appId,     // 必填,企业号的唯一标识,此处填写企业号corpid
    timestamp: res.data.timestamp, // 必填,生成签名的时间戳
    nonceStr:  res.data.nonceStr,  // 必填,生成签名的随机串
    signature: res.data.signature, // 必填,签名,见附录1
    jsApiList: [
      'onMenuShareTimeline',       // 分享给好友
      'onMenuShareAppMessage',     // 分享到朋友圈
      'onMenuShareQQ',             // 分享到QQ
    ]                              // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
  })
  
  wx.ready(function () {
  
    wx.onMenuShareTimeline({       // 例如分享到朋友圈的API
      title:    share_title,       // 分享标题
      link:     location.href,     // 分享链接  
      imgUrl:   share_icon,        // 分享图标
      success:  function () {},
      cancel:   function () {}
    })
    
    wx.onMenuShareAppMessage({
      title:    share_title,       // 分享标题
      desc:     desc,              // 分享描述
      link:     location.href,     // 分享链接
      imgUrl:   share_icon,        // 分享图标
      type:     '',                // 分享类型,music、video或link,不填默认为link
      dataUrl:  '',                // 如果type是music或video,则要提供数据链接,默认为空
      success:  function () {},
      cancel:   function () {}
    })
    
    wx.onMenuShareQQ({
      title:    share_title,       // 分享标题
      desc:     desc,              // 分享描述
      link:     location.href,     // 分享链接
      imgUrl:   share_icon,        // 分享图标
      success:  function () {},
      cancel:   function () {}
    })
  })
  
  wx.error(function (res) {
    console.log(res.errMsg);       // 打印错误消息。及把 debug:false,设置为debug:ture就可以直接在网页上看到弹出的错误提示
  })
})

使用开发者工具调试

鉴于国内的特殊情况,微信成了独立的一种生态环境,针对它形成的产品场景也不少。使用微信web开发者工具 来调试应用,这样就不用每次提交代码到线上再用手机测试啦。

微信web开发者工具
使用公众号网页调试,开发者可以调试微信网页授权和微信JS-SDK

打开这个工具之后提示使用微信扫码登录,有确认登录界面,并显示了你拥有哪些公众号开发者权限

看界面就是套了个 Chromium,聚合了微信大部分 SDK 功能,模拟 SDK 输入和输出。

访问文章页

修改了模板文件 header.phpfooter.php ,来测试下效果。调试工具直接访问 https://misaka.im/index.php/archives/41/

页面加载完后,会发出一条 XHR 请求,url 参数是 urlencode 之后的当前地址

点击展开详细面板

当 JSSDK 初始化完成后会在 Console 调试台中打印出相关信息

  • wx.config begin 表示 wx.config() 被调用,及传入的参数
  • wx.config end 表示 JSSDK 初始化完成, errMsg:ok 代表初始成功
  • 可用的操作权限列表
模拟分享操作

点击右上角更多的操作,点击发送给朋友或朋友圈

分享前后, Console 调试台会打印出分享的具体信息:

  • wx.onMenuShareAppMessage begin 触发 onMenuShareAppMessage() 配置好的分享信息
  • wx.onMenuShareAppMessage end 触发 onMenuShareAppMessage() 配置好的分享回调
DEMO

使用微信web开发者工具打开 前端 demohttp://203.195.235.76/jssdk/), 右侧调试面板显示了代码是如何运作的。

至于签名的一些逻辑操作,官方那个给出了签名的正确姿势前端 demo

在前端构建工具兴起的同时,一大堆基于构建的UI和应用框架也随之吸引了了广大开发者的目光。

从零上手构建工具 webpack 是每个前端开发者的噩梦,其余框架的构建配置也不能涵盖全部开发需求。

对于传统后台管理应用,laravel 等主流框架仍提供页面渲染功能,但官方也提供了使用前端框架的一种加载方式。官方通过改进 webpack 构建工具,支持 vue 框架,sass 样式构建,使得前端工程化在整个项目中变得简单。

创建前端应用时,有以下几点需求

  • 支持较新语法的 JS 代码
  • 支持打包样式
  • 支持现代应用框架及基于框架的 UI 组件
  • 拥有依赖后端或者静态的部署方案

image

当我们使用 laravel 框架开发时,laravel-mix 已被包含在项目里,安装好 NodeYarn 解决依赖后开箱即用。

├── app
├── artisan
├── bootstrap
├── config
├── database
├── package.json  <----- project desc file of npm
├── phpunit.xml
├── public        <----- build file will in the public
├── resources     <----- frontend source code in this 
├── routes
├── server.php
├── storage
├── tests
├── vendor
└── webpack.mix.js <----- laravel-mix config file

image

通过包裹 webpack 实现了大部分功能,以便满足前端在项目中的工程化需求。

package.json 中有默认的一些开发和部署构建命令,使用 cross-env 来设置垮平台的环境变量。

  "scripts": {
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
  },

webpack.mix.js

使用 webpack 的构建功能 ,先通过配置 webpack.mix.js 。在这里可以针对开发、生产环境做配置,甚至从底层重新配置 webpack 。

//webpack.mix.js
const { mix } = require('laravel-mix');

mix.setPublicPath('public')
    .js('resources/assets/js/app.js', 'js/').sourceMaps()
   .sass('resources/assets/sass/app.scss', 'css/')

默认情况下使用项目自带的命令 yarn run watch-poll 产出

dist
│  mix-manifest.json
│  
├─css
│      app.css
│      
└─js
        app.js

产出 mix-manifest.json 的作用是,结合模板渲染的方式,有组织地加载构建后的文件。

mix() 函数读取产出 mix-manifest.json 文件,替换对用路径为实际路径。但在生产模式下,为解决资源缓存问题,情况会更复杂。

{
    "/js/app.js": "/js/app.js",
    "/css/app.css": "/css/app.css"
}
<!-- welcome.blade.php -->
<link rel="stylesheet" href="{{ mix('css/app.css') }}"> 

<script src="{{ mix('js/app.js') }}"></script>

近年由于前端技术变化较快,其大呼的“前后端分离”,运用框架独有的构建方式,使得前端代码独立于后端渲染。这是一件好事情,但构建后的静态项目,部署往往更多时间去解决 “跨域” “文件缓存”等问题。

现在,部署依然可附属在整个后端项目中,作为现有模式的一个过渡,不必花太多时间在构建和分离部署上。

Vue logo

请求方法

GET

用来请求访问已被 URI 识别的资源。如浏览器请求某个地址的资源。

POST

用来传输实体参数的方法。用法上与 GET 很相似,但不是为了获取相应结果。

PUT

用来传输文件。但在 Web 应用中配合 RESTful 架构实现资源更新。

HEAD

用于确认 URI 及资源的响应头部。

DELETE

与 PUT 方法相反,用于删除文件。

OPTIONS

用于查询请求 URI 资源是否支持指定的方法。常出现跨域资源共享(CORS)中。

返回状态码

从服务器端返回的状态码,描述了当前请求是否被正常处理,还是出现错误。

2XX 成功
200 OK

请求被服务器正常处理,GET 请求会返回资源实体。

3XX 重定向
301 Moved Permanently
302 Found

当浏览器遇到 302 响应时,响应头带上了 Location 字段,用于资源重定向到新的 URI。

Laravel 中可以使用 return redirect()->away('https://www.baidu.com'); 来重定向到新的地址。

当 POST 请求被拒返回 302 时,几乎所有浏览器都会再发出一条 GET 请求到 Location 上。
304 Not Modified

作为缓存策略的一部分,当客户端附带了 If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since 其中一种,告知该资源的本地缓存还可以被使用。

典型值为
If-Modified-Since: {Last-Modified}
If-None-Match: {ETag}

HTTP 协议的产生就是被设计用于 Web 浏览器和服务器之间的通信,也直接基于 TCP/IP 协议,这保证传输时比 UDP 可靠。同时还有以下优点

  • 简单
  • 可扩展
  • 无状态
sequenceDiagram
客户端->>服务器: 请求 (Requst)
服务器->>客户端: 响应 (Response)

由像浏览器这样的客户端发出的消息叫做 Requsts,被服务端回应的消息叫做 Responses。

从百度开始,当我们敲下回车键时,浏览器就发出了一条请求:

GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

Chrome 在请求时,使用了 GET 方法来获取在 www.baidu.com 上的资源,没有强制的地写成 http://www.baidu.com:80,并告诉服务当前协议是 1.1 版本。

Host 的 Host: www.baidu.com 正好和 GET 后面的 / 组成 www.baidu.com/ ,如果请求该主机下的其他目录,这个位置也会有相应的变化。

使用 wireshark 可以捕获这个请求,但最直观方法是打开 Chrome Network 面板来查看

image

在请求头总还有一些字段,

User-Agent 虽然能判断客户端的版本和一些信息,显得非常糟糕的是,它经常在业务上被用来区别和提供不同的页面或者服务。

Accept 客户端告诉服务器可以处理的内容类型,通常这里会用 MIME 类型来表示;反过来,服务器通过响应头中的 Content-Type 来告诉客户端它返回内容的类型。

当服务器收到这个请求时候,经由 Nginx 或 Apache 响应之后,可能还会经过 PHP 或 Java 语言做数据存取处理,最终响应回到了客户端

HTTP/1.1 302 Found
Connection: Keep-Alive
Content-Length: 225
Content-Type: text/html
Date: Sun, 02 Sep 2018 03:57:06 GMT
Location: https://www.baidu.com/
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: BWS/1.1
Set-Cookie: BAIDUID=C267DF134F37696F3274F4649989CCD4:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=C267DF134F37696F3274F4649989CCD4; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1535860626; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BD_LAST_QID=13976029323002

常规 General

Request Method , 根据数据流动的方向和交互,HTTP 请求类型分为好几种类型

Request Method常用场景
GET获取 HTML CSS JS 图片等资源文件,也用于文本数据(JSON、XML)的获取
OPTIONS询问服务器某个资源支持的 Request Method ,返回头中会有 Allow 或者 Access-Control-Allow-Methods。发起跨域请求时,一些应用框架会发起该请求
POST发送数据给服务器常用的方法,在数据提交时和跨域请求场景下变得复杂

请求 Requst

客户端发出请求时,不论上述的 GET/POST 还是其他方式,下面的请求头都有可能出现:

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

发送数据给服务器常用的方法,需指定请求头中的 Content-Type 来告诉服务器发送内容的实际编码类型,与后端进行数据交换时,需要提前约定好数据类型及结构,这样能高效地推进项目进度。在表单提交和资源上传时尤为重要;

Content-Type说明
text/html; charset=UTF-8还记得我们编写 HTML 时候首先就是声明了文档类型: <!DOCTYPE html> <meta charset="UTF-8"> 也在这里得到了体现
text/cssCSS 样式文件
application/javascriptJavaScript 文件
image/pngpng 图片资源文件
application/x-www-form-urlencoded请求实体中的数据内容类型以 urldecode 方式带上,GET 时将 encode 后的字符作为 URL 的查询参数https://92yo6ypwz4.codesandbox.io/abc?aa=111&bb=222;POST 时 encode 内容存在于实体中(表单提交 )aa=111&bb=222
application/json;charset=UTF-8告诉服务端请求实体 POST 中带上的数据内容类型为 JSON

响应头中也存在Content-Type,这样编程时才能预料服务器返回的内容;

Referer: http://www.baidu.com/

当前请求来源页面的地址,某些场景下服务端会校检该字段来作统计分析、日志记录以及缓存优化等。

支付场景(微信当前调起H5支付的referer为空)下校检了该字段防止非法跳转进入的支付。

Cookie: PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST

响应 Response

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://www.infzm.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: x-requested-with, content-type, origin, cache-control, pragma, authorization, accept, accept-encoding
Access-Control-Allow-Methods: PUT, POST, GET, OPTIONS, DELETE

Content-Type: application/json

响应头中也存在Content-Type,这样编程时才能预料服务器返回的内容

Content-Type: text/html; charset=UTF-8

keyvalue
data{ foo: 'bar', baz: 'qux' }
dataType指定响应返回的数据类型,默认识别 HTTP 中 MIME 类型
contentType请求内容的编码类型 默认为 application/x-www-form-urlencoded

编码类型

请求 Requst

contentType 指定了请求内容的编码格式,而 jQ 默认使用 application/x-www-form-urlencoded ,使得实体内容会经过 $.param() 序列化

    $.param({ foo: 'bar', baz: 'qux' }) // "foo=bar&baz=qux"

使得实体数据 encoded

    $.ajax({
        url     : "/api/user/register_by_phone",
        type    : "POST",
        data    : jQuery.param({
            phone      : $(el_arr[0]).val(),
            verify_code: $(el_arr[1]).val(),
            password   : md5($(el_arr[2]).val()),
        }),
        timeout : 50000,
        dataType: 'json',
        success : function (res) {
            if (res.code != 200) {
                resolve(res.msg)
            } else {
                resolve(text_success_register)
            }
        }
    })
响应 Response

dataType 指定服务器返回的实体数据类型,不指定时,就要确保返回的 Content-Type 与实体内容编码一致

Content-Type Content-Type: application/json

参考文献

HTTP - MDN

GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

HTTP/1.1 302 Found
Connection: Keep-Alive
Content-Length: 225
Content-Type: text/html
Date: Sun, 02 Sep 2018 03:57:06 GMT
Location: https://www.baidu.com/
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: BWS/1.1
Set-Cookie: BAIDUID=C267DF134F37696F3274F4649989CCD4:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=C267DF134F37696F3274F4649989CCD4; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1535860626; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BD_LAST_QID=13976029323002424979; path=/; Max-Age=1
X-Ua-Compatible: IE=Edge,chrome=1

<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<center><h1>302 Found</h1></center>
<hr><center>7a367f7b87705e16b985e34ca59b8ae8b1d28d47
Time : Tue Aug 21 10:55:16 CST 2018</center>
</body>
</html>

来广州第一年,时间过得真的快。工作步入正轨,好多方面还需努力。

今年折腾电脑也是够了。

工作

  • 新年的 H5 开发起来手忙脚乱,是今年第一个 H5 项目,也是最后一个
  • 广告系统炸了之后一直是手动上广告,4月终于把广告系统做好,运营爱怎么上怎么上吧
  • 由于账号问题,把主站的用户中心迁移到新项目上,解决了一直社交账号登陆不上的问题,然而还是很多问题 233
  • 付费工程的三个月确实加了不少班。来了几个新伙伴,觉得是整个项目沟通不足,结果就是效率不高,问题超多。好事就是,我们有了移动网站项目,还有了新的管理后台
  • 10月老哥回去生孩子了,留下来的框架帮了不少忙,一个人顶着开发任务弄个大半个月
  • 年末会陆续把录入后台做迁移,但年后回来才会迁移,目前在修理大跃进时候的遗留问题

硬件

主力机

升级工作不是一步到位,都是出现问题才去弄。

比如升级了显卡导致电源供电不足,最后强行将 E3 双塔和 1080Ti 强行塞入一个 MATX 机箱里头,还挂了 3T 硬盘和2个固态。

朋友寄来的 T610P 没有 INTEL 平台扣具,血亏用不上。不得不当晚去到实体店购买千石船。

夏天显卡动不动上 80+ ,帮几位朋友换上水冷反馈都较好,自己也入手了一个。原厂散热是供电和显存全覆盖的,担心游戏负载导致高温,另外买了些散热片贴上去。

更换大板后,USB 保护 IC 经常失灵,不过自检,无奈寄回给卖家维修,临时用 B85 顶了几周。

在年底脑子抽了一下,以为断电重启是电源的锅,咬牙跺脚买了酷妈的750,最后判定为双通内存不兼容。

朋友一 1700X + X370 + 1070

另外有个 i5 笔记本升级成 i7 2720QM 更换新散热风扇,换新键盘,不料几个月后就炸了供电,基本报废。

厂里的两个极路由挂掉一个,主路由更换为 网件R7000 (夜鹰 AC1900)

目前我买到装机体验最好的机箱,走线,散热适应性等

朋友二 8100 + H110 + 1080Ti

XXX 广州工作,帮配了台八代 i3 ,后来他自己买了超级冰龙。

朋友三 E3-1230 v3 + B85 + 1080Ti

钢化玻璃除了好看一无是处,要保证有 两个 3.5 盘位。夜魔的装机体验很差。

朋友四 2600x + X370

年内第二台 AMD 主机

朋友五 6700K + Z270 + 1080Ti
  • 技嘉 GTX 1080 Ti Gaming 11G 风冷改 ID-COOLING ICEKIMO 240VGA 水冷
  • 更换主板为 华擎 Z270 杀手版 SLI
  • 山寨双塔散热更换为大镰刀(SCYTHE)千石船

XXX 的主板炸了,过去他新家逛逛,急着要修好,当天从岗顶提货。

朋友六 E3-1230 v3 + B85 + RX480

因公司分配了开发用的电脑,17年末买的一台主机转手给哥们。另外恭喜他终于找到了工作。

家里
  • 扩展到 8G 内存
  • 浦科特 M6S 128G 和 英睿达 MX300 275G 继续服役(我怎么有两块 275),替换下来两块光威 T300 120G
  • 买了个小米盒子3C 丢家里,一直在吃尘
  • 客厅装上了小米智能摄像机云台版
其他
  • 给老妈换了新手机 OPPO R11S (从桦总那儿1K5便宜收来的)换掉了老掉牙的华为
  • 体验了 HTC VR
  • 攒钱入手了港版 iPhone 8 64G 香槟金
  • 从朋友公司捡来三台4代主机,配件拆出来,机箱丢掉

旅游

10月跟朋友们去了湖南长沙,长这么大走得最远的地方了!第一次坐绿皮火车!卧铺一晚上!8小时才到长沙。

去了湖南大学,岳麓书院,橘子洲,尝了老长沙臭豆腐,各种很辣的小吃。网红奶茶店居多,还有 PAP 的真香警告。

高铁的速度真的有 300KM/H,3个小时回到了夏天。

学习

  • 从 Vue-Cli2 开始制作 H5,懒加载和 loader 对于用户交互来说都很重要
  • 在 Laravel 项目里 使用 laravel-mix ,最后还是独立迁移到 Vue-Cli3 ,尝试 NUXT 框架
  • 微信授权登录要在业务层达到一个共识,也许大家都没搞懂啥时候用微信登录
  • 注册登录的实现比想象中复杂
  • iview 和 Vant UI 组件库能学习到 Vue 的特性
  • HTTP 和 控制台仍是调试 bug 的入手点
  • Windows 10 不太适合系统做迁移,新机的话,没得选
  • 报考了专升本,趁还学得动,努力学习吧
  • 博客拥抱了 Typecho 升级到木有卵用的 HTTPS

驾驶

  • SUV 比轿车视野好太多
  • 重度依赖倒车雷达会忘掉倒车入库的技巧,尤其是全景摄像头
  • 每3000公里做一次机油检查
  • 调整后视镜,座椅,方向盘能提高驾驶体验

电影

烧了不少钱去买电影票,尤其在广州。

  • 古墓丽影:起源之战
  • 黑豹
  • 环太平洋:雷霆再起
  • 复仇者联盟3:无限战争(二刷)
  • 蚁人:黄蜂女现身
  • 碟中谍6:全面瓦解
  • 无双
  • 飓风奇劫
  • 铁血战士
  • 毒液:致命守护者(二刷)
  • 憨豆特工3
  • 海王
  • 蜘蛛侠:平行宇宙

??

??? ???? ????

19 Todo List

  • 存钱 ?W
  • 增重到 110斤
  • 阅读 2 本技术书籍
  • 每月一篇博文
  • 更新旧项目(如果有时间)
  • 通过一阶段专升本考试

Array.map()

使用 map 函数,把一个数据集合转换或映射成另一个数据集合。

取出数组所有元素中某个字段

const channels = [
    {"id": 4, "title": "文化", "platform": "app", "type": "channel", "ordering": 4},
    {"id": 1, "title": "推荐", "platform": "app", "type": "channel", "ordering": 1},
    {"id": 7, "title": "人物", "platform": "app", "type": "channel", "ordering": 5}
]

const mapped = channels.map(item => item.title)

console.log(mapped)

> Array ["文化", "推荐", "人物"]

callback 函数返回值组成一个数组,这里 ES6 箭头写法省略了 return

增删所有元素中的字段

const channels = [
    {"id": 4, "title": "文化", "platform": "app", "type": "channel", "ordering": 4},
    {"id": 1, "title": "推荐", "platform": "app", "type": "channel", "ordering": 1},
    {"id": 7, "title": "人物", "platform": "app", "type": "channel", "ordering": 5}
]

const mapped = channels.map(item => {
    const { platform, ordering, ...rest } = item 
    
    return {
        ...rest,
        author: '',
        link: `//www.adc.com/channel/${item.id}`
    }
    
})

console.log(mapped)

> Array [
    Object { id: 4, title: "文化", type: "channel", author: "", link: "//www.adc.com/channel/4" },
    Object { id: 1, title: "推荐", type: "channel", author: "", link: "//www.adc.com/channel/1" }, 
    Object { id: 7, title: "人物", type: "channel", author: "", link: "//www.adc.com/channel/7" }
]

配合扩展运算符、rest 参数、对象解构将需要的字段重新组合成新的数组。

Array.filter()

和 Map 一样,接受一个函数,函数内返回 布尔值 truefalse ,第二个参数作为函数内 this

过滤/筛选数组元素中适合的元素

const channels = [
    { "id": 4, "title": "文化", "platform": "pc",  "type": "channel", "ordering": 4},
    { "id": 1, "title": "推荐", "platform": "app", "type": "channel", "ordering": 1},
    { "id": 7, "title": "人物", "platform": "app", "type": "channel", "ordering": 5}
]

const filtered = channels.filter(item => { 
    return item.platform === 'pc' && item.type === 'channel'
})

console.log(filtered)

> Array [Object { id: 4, title: "文化", platform: "pc", type: "channel", ordering: 4 }]

可以在匿名函数中编写规则,匹配某个字段,筛选出你需要的元素。

Array.find()

找到数组元素中某符合项

const status = [
    { id: "3", name: "草稿" },
    { id: "0", name: "未发布" },
    { id: "1", name: "已发布" },
    { id: "2", name: "等待发布" },
    { id: "4", name: "已撤稿" },
]

const item_status = 1

status.find(option => option.id == item_status)
// (option) => { return option.id == item_status }

> Object { id: "1", name: "已发布" }

Array.reduce()

利用函数自带的累计器,统计出一些字段值出现的次数。第二参数作为累计器 acc 的初始值。

let channels = [
    { "id": 4, "title": "文化", "platform": "pc",  "type": "channel", "ordering": 4},
    { "id": 1, "title": "推荐", "platform": "app", "type": "channel", "ordering": 1},
    { "id": 7, "title": "人物", "platform": "app", "type": "channel", "ordering": 5}
]

const obj = channels.reduce((acc, item) => { 
    const platform = item.platform;
    const platformCount = acc[platform] ? acc[platform] + 1 : 1
    
    return {
        ...acc,
        [platform]: platformCount
    }
}, {})

console.log(obj)

> Object { pc: 1, app: 2 }
acc value records
> acc value
> Object {  }
> Object { pc: 1 }
> Object { pc: 1, app: 1 }

累计器寄存了上次函数返回的值,通过扩展运算符将累计器内可遍历属性复制到本次返回对象里。

链式操作

let channels = [
    { "id": 4, "title": "文化", "platform": "pc",  "type": "channel", "ordering": 4},
    { "id": 1, "title": "推荐", "platform": "app", "type": "channel", "ordering": 1},
    { "id": 7, "title": "人物", "platform": "app", "type": "channel", "ordering": 5},
    [{ "id": 8, "title": "绿色", "platform": "pc", "type": "channel", "ordering": 6}]
]

const chained = channels.reduce((acc, currValue) => {
        return acc.concat(currValue);
    }, [])
    .map(item => {
        return { ...item, disabled: item.id > 1 };
    })
    .filter(item => {
        return !item.disabled;
    })
    .map(item => item.title).join(", ")

console.log(chained); 

> "推荐"

配合箭头函数

const names = ['Will', 'Jack', 'Peter', 'Steve', 'John', 'Hugo', 'Mike']

const newSet = names
    .map((name, index) => ({
        id: index, 
        name //name: name
    }))
    .filter(({id} = man) => id % 2 === 0 ) //filter(man => man.id % 2 === 0 )
    .map(({name} = man) => [ name ])       //map(man => [ man.name ])
    .reduce((a, b) => a.concat(b))

console.log(newSet) //=>  ["Will", "Peter", "John", "Mike"]
    
> ["Will", "Peter", "John", "Mike"]

数组去重 Remove Array Duplicates (From Array Of Objects)

使用数组的各种内置方法都可以完成简单结构的数组去重,包括 map、foreach 、filter 、reduce

使用 Set 数据结构
[...new Set([1, 1, 2, 3, 4, 5, 5])]

Set 数据结构可实现纯数字数组去重

搭配 filter 和 IndexOf
// usage example:
var myArray = ['a', 1, 'a', 2, '1'];
var unique = myArray.filter((e, i, a) => a.indexOf(e) === i);

console.log(unique); // unique is ['a', 1, 2, '1']

不排除还有字符串等元素,使用 filter 筛选出所有第一次出现的元素。但现实未经过处理的数据结构长得更原始。

对象数组
const arr = [
    {place: "here",  name: "x", other: "other stuff1" },
    {place: "there", name: "x", other: "other stuff2" },
    {place: "here",  name: "y", other: "other stuff4" },
    {place: "here",  name: "z", other: "other stuff5" }
]

function getUniqueListBy(arr, key) {
    return [...new Map(arr.map(item => [item[key], item])).values()]
}

const arr1 = getUniqueListBy(arr, 'place')

对象数组中往往有多个键值,从中筛选出不重复的集合,最好是指定主键标识。另外还有使用 filter map 组合的版本:

function unique(array, fieldKey="id") {
    var res = array.filter(function(item, index, array){
        return array.map(item=>item[fieldKey]).lastIndexOf(item[fieldKey]) === index;
    })
    return res;
}

找出相同值 Find Duplicates

参考 unique 函数的变体,它过滤掉了相同的元素,很容易,修改 ===!== 就能实现过滤不同的元素。实现 find duplicates

let strArray = [ "q", "w", "w", "w", "e", "i", "u", "r"];
let findDuplicates = arr => arr.filter((item, index) => arr.indexOf(item) !== index)

console.log(findDuplicates(strArray)) // All duplicates
console.log([...new Set(findDuplicates(strArray))]) // Unique duplicates

对于对象数组,也有

const arr = [
    {place: "here",  name: "x", other: "other stuff1" },
    {place: "there", name: "x", other: "other stuff2" },
    {place: "here",  name: "y", other: "other stuff4" },
    {place: "here",  name: "z", other: "other stuff5" }
]

function duplicates(array, fieldKey="id") {
    var res = array.filter(function(item, index, array){
        return array.map(item=>item[fieldKey]).lastIndexOf(item[fieldKey]) !== index;
    })
    return res;
}

console.log(duplicates(arr, 'place'))

参考