GithubHelp home page GithubHelp logo

silencehvk / blog Goto Github PK

View Code? Open in Web Editor NEW
231.0 231.0 9.0 46.49 MB

:books: :octocat: Github static blog post, experience the fun of using Issues.Welcome star( 静态博客文章,体验一下使用 Issues 的乐趣,欢迎 star )个人博客地址:blog.hvkcoder.me/love

Home Page: https://github.com/SilenceHVK/Articles/issues

License: MIT License

Dockerfile 0.66% Shell 4.13% Batchfile 1.00% Java 32.98% Go 8.45% HTML 0.49% PLpgSQL 0.10% CSS 0.28% Scala 1.60% Python 0.40% Jupyter Notebook 49.69% Lua 0.01% Rust 0.21% JavaScript 0.01%
blog docker golang java javascript linux vertx

blog's Introduction

blog's People

Contributors

dependabot[bot] avatar silencehvk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

【Tools】Nginx 入门到实践

sendfile 模型图

Nginx 是一个开源且高性能、可靠的 HTTP 中间件、代理服务。

Nginx 特性

  • I/O 多路复用 epoll

I/O 多路复用模型图

  多个描述符的 I/O 操作都能在一个线程内并发交替地顺序完成,就叫做“ I/O 多路复用”。这里的“复用”是指同一个线程。

I/O 多路复用使用的方式:

select 模型

1. 能够监视文件描述的数量存在最大限制;
2. select 模型采用线性遍历方式,使得扫描效率低下;

epoll 模型

1. 最大连接无限制;
2. 当每个 FD 就绪,采用系统的回调函数之间将 FD 放入,效率更高;
  • 轻量级

    • 功能模块少;
    • 代码模块化;
  • CPU 亲和(affinity)

CPU 亲和是一种把 CPU 核心和 Nginx 工作进程绑定,把每个 worker 进程固定在一个 cpu 上执行,减少切换 cpu 的 cache miss,获得更好的性能。

  • sendfile

Nginx 在传递静态文件时,直接通过内核空间传递给 Socket ,响应给用户。

sendfile 模型图

Nginx 快速安装

  • nginx 发行版本

    • Mainline version 开发版;
    • Stable version 稳定版;
    • Legacy version 历史版;
  • RHEL/CentOS 通过 yum 安装

    1. 初始化系统
      ## 关闭防火墙
      sudo systemctl stop firewalld
      sudo systemctl disable firewalld
      sudo iptables -F && sudo iptables -X && sudo iptables -F -t nat && sudo iptables -X -t nat
      sudo sudo iptables -P FORWARD ACCEPT
      
      ## 关闭 SELinux
      sudo setenforce 0
      sudo sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
      
    2. sudo yum install yum-utils
    3. 创建文件 /etc/yum.repos.d/nginx.repo 并输入以下内容:
    [nginx-stable]
    name=nginx stable repo
    baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
    gpgcheck=1
    enabled=1
    gpgkey=https://nginx.org/keys/nginx_signing.key
    
    [nginx-mainline]
    name=nginx mainline repo
    baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
    gpgcheck=1
    enabled=0
    gpgkey=https://nginx.org/keys/nginx_signing.key
    

    默认情况下,yum 使用的是 nginx 稳定版库,如果要使用开发版库,可以执行命令 sudo yum-config-manager --enable nginx-mainline

    1. sudo yum install nginx
    2. rpm -ql nginx 查看nginx 安装目录;
    nginx 安装目录 图片

Nginx 基础配置

  nginx 默认配置路径 /etc/nginx/nginx/conf.

  • 基础模块配置
user		        设置 nginx 服务的系统使用用户
worker_processes    工作进程数
error_log           nginx 的错误日志
pid                 nginx 服务启动时候的pid
  • 事件模块
events {
    worker_connections   每个进程允许最大连接数
    use                  工作进程数
}
  • http 协议模块配置
http {
  ## 每个 server 为每个独立的站点
  server {
     	listen    80;  ## 端口号
		server_name	localhost; ## 域名  

		## 配置默认访问的路径配置
		location / {
			root  	页面根目录路径
			index   首页路径
		}  

		## 错误页面配置
    	error_page   500 502 503 504  /50x.html;
    	location = /50x.html {
        	root   /usr/share/nginx/html;
    	}
  }
  server{
  }
}

Nginx 的日志

  • error.log
主要用于记录 nginx 每次 HTTP 请求的状态与自身服务运行的状态。
  • access.log
主要用于记录 nginx 每次 HTTP 请求的响应状态。

error.logaccess.log 主要依赖于 log_format 的配置,log_format 只能配置在 http 模块下。

  • log_format
普通格式配置

log_format main '$remote_addr - $remote_user [$time_local] $request '
                '"$status" $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for" "$request_time" "$upstream_response_time"';

json 格式配置

log_format logJson '{
                         "@timestamp": "$time_local", '
                         '"@fields": { '
                         '"remote_addr": "$remote_addr", '
                         '"remote_user": "$remote_user", '
                         '"body_bytes_sent": "$body_bytes_sent", '
                         '"request_time": "$request_time", '
                         '"status": "$status", '
                         '"request": "$request", '
                         '"request_method": "$request_method", '
                         '"http_referrer": "$http_referer", '
                         '"body_bytes_sent":"$body_bytes_sent", '
                         '"http_x_forwarded_for": "$http_x_forwarded_for", '
                         '"http_user_agent": "$http_user_agent" }
                    }';
字段 说明
$remote_addr 客户端地址
$remote_user 客户端用户名称
$time_local 访问的时间和时区
$request 请求的 URI 和 HTTP 协议
$http_host 请求地址,即浏览器中输入的地址或域名
$status HTTP 请求状态
$upstream_status upstream 状态
$body_bytes_sent 发送给客户端文件内容大小
$http_referer url 跳转来源
$http_user_agent 用户终端浏览器等信息
$ssl_protocol SSL 协议版本
$ssl_cipher 交换数据中的算法
$upstream_addr 后台 upstream 地址,即真正提供服务主机地址
$request_time 整个请求的总时间
$upstream_response_time 请求过程中 upstream 的响应时间

指定日志格式

error_log  /var/log/nginx/error.log logJson buffer=32k;
access_log  /var/log/nginx/access.log  main buffer=32k;

Nginx 中间件架构

  • http_stub_status_module
用于监控 nginx 的运行状态,配置语法:

location /status {
    stub_status;
}

相关数据:

Active connections: 对后端发起的活动连接数
Server accepts handled requests:
Nginx 处理的连接个数 创建的握手次数 处理请求的个数

Reading: Nginx 读取到客户端的 Header 信息数
Writing: Nginx 返回客户端的 Header 信息数
Waiting: 开启 keep-alive 的情况下,这个值等于 active - (reading + writing), 意思就是 Nginx 已经处理完成,正在等待下一次请求指令的驻留连接
  • http_random_index_module
在目录中选择一个随机主页,但是不能选择隐藏文件,配置语法

location / {
    root 页面路径
    random_index on;
}
  • http_sub_module
对 HTTP 内容替换

locatoin / {
    sub_filter 要替换的内容  要替换后的内容;
    
    # 用于设置网页内替换后是否有更新,主要用于缓存的场景
    sub_filter_last_modified on; 
    
    # 字符串替换一次还是多次,默认替换一次,将其关闭为替换所有
    sub_filter_once no;
}
  • Nginx 的请求限制

    • 连接频率限制 limit_conn_module
    • 请求频率限制 limit_req_module

【Golang 基础】Go 语言的操作符

Go 语言的运算符

算术运算符

  • +:相加;
  • -:相减;
  • *:相乘;
  • /:相除;
  • %:求余;
  • ++:自增;
  • --:自减;

其中,++-- 不能用于赋值表达式, 如: count2 := count++;并且在 Go 语言中,不存在如:++count 表达式

关系运算符

  • ==:检查两个值是否相等,如果相等返回 true,否则返回 false
  • !=:检查两个值是否不相等,如果不相等返回 true,否则返回 false
  • >:检查左边值是否大于右边值,如果是返回 true,否则返回 false
  • <:检查左边值是否小于右边值,如果是返回 true,否则返回 false
  • >=:检查左边值是否大于等于右边值,如果是返回 true,否则返回 false
  • <=:检查左边值是否小于等于右边值,如果是返回 true,否则返回 false

逻辑运算符

  • &&:逻辑 AND 运算符。如果两边的操作数都是 true,则条件为 true,否则为 false
  • ||:逻辑 OR 运算符。如果两边的操作数有一个 true,则条件为 true,否则为 false
  • !:逻辑 NOT 运算符。如果条件为 true,则逻辑 NOT 添加为 true,否则为 false

位运算符

位运算符是对整数在内存中的二进制进行操作。

  • &:按位与运算符。其功能是参与运算的两个数的二进制按位对齐,当对应位都为 1 时,才返回 1
fmt.Println(3 & 4)  // 0

// 计算过程
//   0011     => 3 的二进制
//   0100     => 4 的二进制
// &
// ---------------------------
//   0000     => 0 的二进制
  • |:按位或运算符。其功能是参与运算的两个数的二进制按位对齐,当对应位中只要有一位是 1,就返回 1
fmt.Println(3 | 4) // 7

// 计算过程
//   0011     => 3 的二进制
//   0100     => 4 的二进制
// &
// ---------------------------
//   0111     => 7 的二进制
  • ^:按位异或运算符。其是参与运算的两个数的二进制按位对齐,当对应位有一位是 1,就返回 1;如果对应两位都是 1 或 0,就返回 0
fmt.Println(25 ^ 3) // 26

// 计算过程
//   0001 1001     => 25 的二进制
//   0000 0011     => 3 的二进制
// ^
// ---------------------------
//   0001 1010     => 26 的二进制
  • <<:左移运算符。其功能是将数值的二进制所有位向左移动指定的位数
fmt.Println(3 << 3) // 24

// 计算过程
//      0000 0011     => 3 的二进制
//              3
// <<
// ---------------------------
//      0001 1000     => 24 的二进制
  • >>:右移运算符。其功能是将数值的二进制所有位向右移动指定的位数
fmt.Println(3 >> 3) // 0

// 计算过程
//      0000 0011     => 3 的二进制
//              3
// >>
// ---------------------------
//      0000 0000     => 0 的二进制

赋值运算符

  • =:简单的赋值运算符,将一个表达式的值赋给一个左值;
  • +=:相加后再赋值;
  • -=:相减后再赋值;
  • *=:相乘后再赋值;
  • /=:相除后再赋值;
  • %=:取余后再赋值;
  • &=:按位与后赋值;
  • |=:按位或后赋值;
  • ^=:按位异或后赋值;
  • <<=:左位移后赋值;
  • >>=:右位移后赋值;

【Linux】CentOS 7 设置时间

1.设置系统时间为**时区并启用NTP同步

yum install -y ntp //安装ntp服务

systemctl enable ntpd //开机启动服务

systemctl start ntpd //启动服务

timedatectl set-timezone Asia/Shanghai //更改时区

timedatectl set-ntp yes //启用ntp同步

ntpq -p //同步时间

2.timedatectl 命令

## 读取时间
timedatectl  //等同于 timedatectl status

## 列出所有时区
timedatectl list-timezones

## 设置时区
timedatectl set-timezone Asia/Shanghai

## 设置是否与NTP服务器同步
timedatectl set-ntp yes  //yes或者no

## 将硬件时钟调整为与本地时钟一致
hwclock --systohc --localtime 或 timedatectl set-local-rtc 1

## 将硬件时间设置成 UTC
hwclock --systohc --utc 或 timedatectl set-local-rtc 1

3.时钟概念

在CentOS 6版本,时间设置有date、hwclock命令,从CentOS 7开始,使用了一个新的命令timedatectl。

  • UTC

   整个地球分为二十四时区,每个时区都有自己的本地时间。在国际无线电通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(UTC, Universal Time Coordinated)。

  • GMT

   格林威治标准时间 (Greenwich Mean Time)指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。(UTC与GMT时间基本相同,本文中不做区分)

  • CST

   **标准时间 (China Standard Time)【GMT + 8 = UTC + 8 = CST】

  • DST

   夏令时(Daylight Saving Time) 指在夏天太阳升起的比较早时,将时钟拨快一小时,以提早日光的使用。(**不使用)

硬件时钟:

  RTC(Real-Time Clock)或CMOS时钟,一般在主板上靠电池供电,服务器断电后也会继续运行。仅保存日期时间数值,无法保存时区和夏令时设置。

系统时钟:

  一般在服务器启动时复制RTC时间,之后独立运行,保存了时间、时区和夏令时设置。

CSS 块元素和内嵌元素

块元素与内嵌元素的区别

块元素的特征

  1. 默认独占一行
  2. 没有宽度时,默认撑满一排
  3. 支持所有css命令

内嵌元素的特征

  1. 同排可以继续跟同类的标签
  2. 内容撑开宽度
  3. 不支持宽高样式
  4. 不支持上下的 margin 和 padding 样式
  5. 代码换行被解析

inline-block(一行内的块)特性和问题

特性

  1. 块在一行显示
  2. 行内属性标签支持宽高
  3. 没有宽度的时候内容撑开宽度

问题

  1. 代码换行被解析
  2. IE6 7 不支持块元素标签的 inline-block

【Golang 基础】 Go 语言 函数式编程

Go 语言函数式编程

  在 Go 语言中函数是一等公民,它作为一个变量、类型、参数、返回值,甚至可以去实现一个接口,但是 Go 语言中函数不支持重载、嵌套和默认参数。

  • 通过使用 func 关键字来定义函数
package main

func main(){
	
}

函数支持的特性

  • 作为变量;
func test(){
    // 函数体
}

funcTest := test

fmt.Println(funcTest())
  • 匿名函数
test := func(){
    // 函数体
}
  • 作为类型;
package main

import "fmt"

type iAdder func(int, int) int

func main(){
	var adder iAdder = func(a int, b int) int {
		return a + b
	}
	
	fmt.Println(adder(1,2)) // 3
}
  • 不定长度变参;
func test(num ...int){
    fmt.Println(num) // [1 2 3 4]
}

test(1,2,3,4)
  • 多返回值;
func test() (string,int,bool){
    return "Hello World", 100, true
}

v1, v2, v3 := test()

fmt.Println(v1, v2, v3) // Hello World 100 true
  • 命名返回值参数;
func test() (a string, b bool, c int) {
    a = "Golang"
    b = false
    c = 200
    return
}

v1, v2, v3 := test()

fmt.Println(v1, v2, v3) // Golang false 200
  • 闭包
package main

import "fmt"

// 使用 闭包实现 斐波那契数列
func fibonacci() func() int {
	a, b := 0, 1

	return func() int {
		a, b = b, a +b
		return a
	}
}

func main(){
	f := fibonacci()

	fmt.Println(f()) // 1
	fmt.Println(f()) // 1
	fmt.Println(f()) // 2
	fmt.Println(f()) // 3
	fmt.Println(f()) // 5
}
  • 使用函数实现接口

  还是以 斐波那契数列为例,将其当做一个文件流来读取.

package main

import (
	"fmt"
	"io"
	"bufio"
	"strings"
)

// 使用 闭包实现 斐波那契数列
func fibonacci() intGen {
	a, b := 0, 1
	return  func() int {
		a, b = b, a + b
		return a
	}
}

// 定义一个类型
type intGen func() int

// 实现一个 Reader 接口
func (g intGen) Read(p []byte) (n int, err error) {
	// 获取下一个元素值
	next := g()
	if next > 10000 {
		return  0, io.EOF
	}
	// 将一个数值转为字符串
	s := fmt.Sprintf("%d/n", next)
	return strings.NewReader(s).Read(p)
}

// 使用 Reader 读取的方法
func printFileContents(reader io.Reader){
	scanner := bufio.NewScanner(reader)
	for scanner.Scan()  {
		fmt.Println(scanner.Text())
	}
}


func main(){
	f := fibonacci()
	
	printFileContents(f)
}

MongoDB 简介与部署

MongoDB 简介

  MongoDB 是由 C++ 编写的一个就分布式文件存储的开源数据库。旨在为 Web 应用提供可扩展的高性能数据存储解决方案。

  MongoDB 将数据存储为一个文档,结构由键值对组成类似一个 JSON 对象,如:

{
    name:'hvkcoder',
    age :18
    language:['JavaScript','Node.js','C#','Java']
}

MongoDB 的特点

  • 易于使用
  1. MongoDB是一个面向文档(document-oriented) 的数据库,而不是关系型数据库。
  2. 与关系型数据库相比,面向文档的数据库不再有“行”的概念,取而代之的是更为灵活的“文档”模型。
  3. 不再有预定模式:文档的键和值不再是固定的类型和大小。
  • 易于扩展
  1. 纵向扩展就是使用计算能力更强的机器,横向扩展就是通过分区将数据分散到更多机器上。
  2. MongoDB 的设计采用横向扩展。面向文档的数据模型使它能很容易的在多台服务器之间进行数据分割。
  3. MongoDB 能自动处理跨集群的数据和负载,自动重新分配文档,以及将用户请求路由到正确的机器上。
  • 丰富的功能
  1. 索引(indexing): MongoDB 支持通用二进制索引,允许多种快速查询,且提供唯一索引、地理空间索引,以及全文索引。
  2. 聚合(agregation): MongoDB 支持“聚合管道”(aggregation pipeline)。用户能通过简单的片段创建复杂的聚合,并通过数据库自动优化
  3. 特殊的集合类型:MongoDB 支持存在时间有限的集合,用于保存某时刻的过期数据。同样也支持固定大小集合,用于保存近期数据。
  4. 文件存储
  • 卓越的性
  1. MongoDB能够对文档进行动态填充。
  2. MongoDB能够预分配数据文件,利用额外的空间来换取稳定的性能。
  3. MongoDB将尽可能多的内存用作缓存,试图每次查询自动选择正确的索引。

MongoDB 配置与启动

  创建文件目录结构

.
├── db                   //数据存储位置
├── log                 //数据日志
├── config             //配置文件
│   └── mongod.conf   //数据库配置文件

  打开文件 mongod.log 并输入以下内容

port = 12345  //mongodb数据库的端口号
dbpath = db  //mongodb数据库存储位置
logpath = log/mongod.log //mongodb日志
fork = ture //服务后台运行 仅对 Unix 系统有效

  在命令行中输入以下命令,用于启动 MongoDB数据库

mongod -f config/mongod.conf

  在命令行中输入以下命令,用于连接 MongoDB数据库

mongo 127.0.0.1:12345

  在命令行中输入以下命令,用于关闭MongoDB数据库

db.shutdownServer()

重新启动 MongoDB 服务,需要删除 lock 文件 和日志文件。再输入启动 MongoDB 服务命令

未能加载文件或程序集Newtonsoft.Json, Version=4.5.0.0

  1. 打开 程序管理器控制台 输入 PM> install-package newtonsoft.json
  2. 查看bin文件中是否有 newtonsoft.json.dll文件
  3. 在Web.config 中添加
<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  1. 重新编译后运行

【Docker】Docker 命令

导出 docker images

$ docker save -o path.tar imageName

导入 docker images

$ docker load < image.tar

监控容器资源消耗

$ docker stats [Options] [containerID/containerName]

默认情况下,stats 命令会每隔 1s 刷新输出

  • [CONTAINER]:以短格式显示容器的 ID。
  • [CPU %]:CPU 的使用情况。
  • [MEM USAGE / LIMIT]:当前使用的内存和最大可以使用的内存。
  • [MEM %]:以百分比的形式显示内存使用情况。
  • [NET I/O]:网络 I/O 数据。
  • [BLOCK I/O]:磁盘 I/O 数据。
  • [PIDS]:PID 号。

Options

--no-stream 只返回当前的状态

--format 格式化输出结果

$ docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
  • .Container: 根据用户指定的名称显示容器的名称或 ID。
  • .Name: 容器名称。
  • .ID: 容器 ID。
  • .CPUPerc: CPU 使用率。
  • .MemUsage: 内存使用量。
  • .NetIO: 网络 I/O。
  • .BlockIO: 磁盘 I/O。
  • .MemPerc: 内存使用率。
  • .PIDs: PID 号。

【JavaScript Ajax】AJAX 跨域详解

产生 AJAX 跨域的原因

  1. 浏览器的限制:浏览器出于安全考虑,当它发现请求是跨域时,会进行一些校验,如果校验不通过就回报跨域安全问题。
  2. 跨域:发出的请求不是本域下的,协议、域名、端口任何一个不一样,浏览器就会认为跨域。
  3. XHR(XMLHttpRequest)请求:XHR 能够在不重新加载页面的情况下更新网页,多用于 AJAX 请求,但会受到同源策略的影响,而导致跨域问题。

解决方案

  1. 禁止浏览器检查(需要对浏览器单独设置,并且不安全,不推荐)

  以 chrome 为例,通过命令行关闭其安全模式:

  • Linux: google-chrome --disable-web-security
  • Mac: open -a Google\ Chrome --args --disable-web-security
  • Windows: "chrome安装地址/chrome.exe" --disable-web-security
  1. 使用 JSONP 解决跨域问题:

  JSONP(JSON with Padding)json 的一种“使用模式”,是一种非正式传输协议。其实现原理是利用了 <script> 标签没有跨域限制的“漏洞”,来达到与第三方通讯的目的。此时服务器端将不再返回 JSON 格式的数据,而是返回一段调用某个函数的代码。

Node.js 接入微信公众平台开发

一、写在前面的话

  Node.js是一个开放源代码、跨平台的JavaScript语言运行环境,采用Google开发的V8运行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于数据密集的事实应用程序。——来自维基百科

  最近花了差不多近一个月的时间去学习Node.js,由于它的代码语言是 Javascript ,因此对于语法上就没有过多的去研究,毕竟做过Web开发的程序员,很少有不会Javascript的。而写这篇文章,也只是为了
记录装逼的过程

如有不正确的地方,希望大家指正。

二、准备工作

  在正式开始码代码之前,我们需要准备以下东西:

  1. 搭建 Node 环境。Node的安装过程太过简单,网上也有太多的教程,大家可以自行百度;
  2. 申请微信公众号,同样这里也不做介绍,大家自行百度。
  3. 服务器和域名。
  4. 打开 Node.js中文网文档
  5. 打开 微信开发者文档

三、接入微信公众平台

1.创建Node.js 项目

  首先我们在电脑的任意磁盘上创建文件夹,命名随意,我这命名为 wechat;
  随后在文件夹中创建两个文件,一个是config.json,另一个为app.js。如下图所示:
Node.js项目
PS:这里我的 IDE 是 VSCode,各位可随意使用自己喜欢的 IDE。

  为了后续功能的扩展,我加入了Express框架,具体操作如下:

  1. 电脑打开运行界面,快捷键为:win+R,输入cmd后回车,进入dos界面,输入命令
 npm install -g express

进行全局安装;由于 Express 自 4.x 版本中将命令工具分离出来,因此还需要输入下一个命令

npm install -g express-generator@4

安装成功后,在dos界面中输入以下 命令

express -h

结果如下图所示:

express 安装成功

震惊什么?你的运行结果提示:express不是内部或外部命令。那你必须要检查一下安装 node.js 的时候有没有添加环境变量。点击解决express不是内部或外部命令问题

2.微信文档步骤

  如果在没有考虑清楚之前,就开始码代码的话,这样做是非常危险的。我们先打开微信文档,点击 开始开发 中的 接入指南,如图:
接入指南

跳过第一步,直接看第二步,如图:
验证消息来自微信服务器

正如帮助文档所说的那样,我们总结以四个步骤:

  1. 获取微信服务器Get请求的参数 signature、timestamp、nonce、echostr
  2. 将token、timestamp、nonce三个参数进行字典序排序
  3. 将三个参数字符串拼接成一个字符串进行sha1加密
  4. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

3.接入功能的实现

   整理好思路后我们就按照上一节的步骤去实现。首先我们在打开 config.json 文件,输入以下内容:

{
    "token":"wechat"
}

   config.json 文件是我们程序的配置文件,在后面的文章中,我们会将程序名称、微信定义的参数名称、请求地址等配置内容存放在该文件中。
   打开 app.js 文件,该文件属于我们程序的入口,在文件启动及以后路由配置都是再次实现的,首先我们导入重要的模块:

const express = require('express'), //express 框架 
       crypto = require('crypto'),  //引入加密模块
       config = require('./config');//引入配置文件

上面的代码不难理解,require 就是导入模块的意思。这里主要针对我们自定义的config.json文件讲解一下:
 "./" 表示与 app.js 在同一及目录下,为什么要这么写呢?原因很简单就是在我们通过 npm 命令安装模块时,如果我们指定了全局安装,也就是 -g,安装后的文件则会保存在我们 node.js 安装路径下的 node_modules 文件夹中;同理,我们不指定全局安装,安装后的文件则会保存在我们安装命令输入时所在的文件夹根目录下的 node_modules 文件夹中,此时如果没有该文件,系统会自动创建。

 这里我们都是使用 require 去导入模块的,node.js 怎么分辨系统模块和我们自定义的模块呢,聪明的你一定想到了,没错就是 "./" 这个,如果你不想用它的话,你也可以把自定义的模块文件移到 node_modules 文件夹中。

  require 是通过模块名称去导入模块文件的,因此在引入的时候不需要写入文件的后缀名。如果两个文件重名,但后缀名不同, require 会按照 Node.js 的加载优先级顺序进行导入,即 js文件 > json文件 > node文件。

原来如此
 实际上就是这么简单,我们也就不再纠结这个问题,继续下面的工作。

   实例 express 以及 创建服务器

//实例 express
var app = express();
//用于处理所有进入 3000 端口 get 的连接请求
app.get('/',function(req,res){
	//对请求连接返回内容
    res.send("Hello Node.js");
});
//监听3000端口
app.listen(3000);

  上面的代码通过注释,我们就能明白其意思,这里我就不再做细致的讲解。再次进入 dos 界面,通过命令进入我们项目的文件的根目录下,如图:
进入项目根目录下
输入命令,启动我们的Node.js项目

node app.js

启动成功后,我们打开浏览器输入地址:http://localhost:3000 访问我们的node.js项目,如图
启动node.js项目

范文结果

小技巧 :
  在每次更改完 node.js 项目后,我们都需要先将 node.js停止(快捷键: Ctrl+C),然后再通过命令再次运行,这样特别麻烦。这里我推荐使用 supervisor 工具,npm 安装命令为:npm install -g supervisor。这样我们启动 node.js 项目命令改为 supervisor app.js,更改项目后只需要保存,刷新浏览器页面就可以得到更改后的结果了。

  完成上面的工作后,我们就可以正式开始写接入微信公众平台的主要代码,废话不多说直接贴代码
后退!我要开始装逼了

const express = require('express'), //express 框架 
       crypto =  require('crypto'), //引入加密模块
       config = require('./config');//引入配置文件

var app = express();//实例express框架

//用于处理所有进入 3000 端口 get 的连接请求
app.get('/',function(req,res){
    //1.获取微信服务器Get请求的参数 signature、timestamp、nonce、echostr
    var signature = req.query.signature,//微信加密签名
        timestamp = req.query.timestamp,//时间戳
            nonce = req.query.nonce,//随机数
          echostr = req.query.echostr;//随机字符串

    //2.将token、timestamp、nonce三个参数进行字典序排序
    var array = [config.token,timestamp,nonce];
    array.sort();

    //3.将三个参数字符串拼接成一个字符串进行sha1加密
    var tempStr = array.join('');
    const hashCode = crypto.createHash('sha1'); //创建加密类型 
    var resultCode = hashCode.update(tempStr,'utf8').digest('hex'); //对传入的字符串进行加密

    //4.开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
    if(resultCode === signature){
        res.send(echostr);
    }else{
        res.send('mismatch');
    }
});

//监听3000端口
app.listen(3000);

4.部署项目

   完成了代码后我们就可以把项目发布到外网上了,这里我用的是花生壳内网映射外网的软件,各位可以随意使用其他工具。
1.打开花生壳的软件,点击内网穿透:
花生壳
2.点击添加映射
点击添加映射
3.配置映射
这里写图片描述
  由于微信只接受80端口,我们在映射类型选择为 网站80端口;内网主机就是我们电脑的 IP 地址;内网端口号就是node.js的监听端口;点击确定后,就成功映射到外网了,可以通过花生壳提供的域名进行访问。

5.接入验证

  再次进入 微信公众平台
在左侧菜单点击基本配置,如图:
基本配置

修改服务器配置

点击修改配置。其中服务器地址,就是我们外网映射的地址;令牌(Token)就是我们在 config.json 文件中 定义的 token 值;消息加密可以点击随机生成按钮,当然你也可以随便定义;消息加解密方式这块可以随便选择,这里我就使用默认的,如图:
微信服务器配置

微信公众平台接入

  点击提交按钮,提示 提交成功,那么我就要祝贺你了哎呦不错哦
  你已经成功的步入了Node.js开发微信的开发的第一步。

   文章源代码:https://github.com/SilenceHVK/wechatByNode 。对文章有不正确之处,请给予纠正。github源代码请顺手给个 Star,最后感谢您的阅读。

【CSS 】CSS 响应式布局

  响应式开发的本质是针对多种屏幕做适配,首先需要掌握几个基本概念:

  • 物理像素:设备的屏幕实际像素点,如常说的 iPhone 6 Plus 的分辨率是 1920 * 1080 像素。
  • 设备独立像素:逻辑像素,用于定义应用的UI。
  • 屏幕像素比(devicePixeRatio):物理像素与设备独立像素的比值。

使用 rem 实现响应式布局

  rem(font size of the root element)是 CSS 的计量单位,表示相对于根(即 html)元素的字体大小。其主要用于移动 Web 开发,以适配不同尺寸的屏幕。

  rem 的兼容可以通过 caniuse 查询

rem 兼容

  由于 rem 单位是相对于网页根元素的字号大小而定,所以实现 rem 布局开发时,首先要做的就是对根元素的字号赋值。

    html{ font-size:12px; }

我们将网页根元素的字号设置为 12px,此时 rem 相对于网页根元素字号为 1rem = 12px。故此转换 rem 的公式则为

    rem值 = 元素实际 px 值 / 网页根元素的字号

下面通过 rem 实现一个简单的布局 【预览

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta http-equiv="X-UA-Compatible" content="ie=edge">
            <title>Rem Case</title>
            <style>
                body {
                    font-size: 12px;
                    margin: auto;
                }
        
                .btns {
                    width: 10rem;
                    margin: 0 auto;
                }
        
                .btns > a {
                    float: left;
                    width: 2.5rem;
                    text-align: center;
                    padding-top: 0.2rem;
                }
        
                .btns > a > i {
                    display: inline-block;
                    width: 1.2rem;
                    height: 1.2rem;
                    background: gray;
                    border-radius: 50%;
                }
        
                .btns>a>span {
                    display: block;
                    line-height: 0.8rem;
                    font-size: 14px;
                }
            </style>
        </head>
        <body>
            <div class="btns">
                <a>
                    <i></i>
                    <span>英语</span>
                </a>
                <a>
                    <i></i>
                    <span>日语</span>
                </a>
                <a>
                    <i></i>
                    <span>德语</span>
                </a>
                <a>
                    <i></i>
                    <span>法语</span>
                </a>
                <a>
                    <i></i>
                    <span>韩语</span>
                </a>
                <a>
                    <i></i>
                    <span>小语种</span>
                </a>
                <a>
                    <i></i>
                    <span>教学</span>
                </a>
                <a>
                    <i></i>
                    <span>职场</span>
                </a>
            </div>
        </body>
    </html>

最后加上最关键的重置元素字号脚本

    (function (window, document) {
        'use strict';
        // 获取网页根元素
        var html = document.documentElement || document.querySelector('html');
        // 重置根元素字号
        function resetFontSize() {
            // 获取根元素的宽度
            var width = html.getBoundingClientRect().width;
            // 设置一个最大宽度值
            if (width > 640) width = 640;
            html.style.fontSize = (width / 10) + 'px';
        }

        resetFontSize();
        window.addEventListener('resize', resetFontSize, false);
    })(window, document);

  rem 确实有效的解决了响应式布局,但却并非完美:

  • 对于脚本的依赖,需要使用 js 脚本对其根元素的字号动态计算与赋值。
  • 只能针对一个基准值去动态计算根元素字号大小,面对于宽高自适应且不出现滚动条这种布局,rem 则显得无能为力,因为它基准值只能是一个,高度或宽度。

使用 vw、vh、vmin、vmax 实现响应式布局

  vwvhvminvmaxrem 相同都是 CSS3 中新引入的一种计量单位。不同于 rem 它们所表达的含义如下:

单位 含义
vw 等于视口宽度的 1%
vh 等于视口高度的 1%
vmin 相视的宽度或高度,取决于哪个更小
vmax 相对于视的宽度或高度,取决于哪个更大

  vwvhvminvmax 的兼容可以通过 caniuse 查询

vm 兼容

  在使用 vwvhvminvmax 之前我们需要认识一下视口。

  以 PPK大神 在其文章 A tale of two viewports (一)A tale of two viewports (二) 以及 Meta viewport 三篇文章 中提出关于视口的解释:

  • 在桌面端:

The function of the viewport is to constrain the element,which is the uppermost containing block of your site.

译:视口的功能是约束 html 元素,它是网站上的包含区块。

The viewport, in turn, is exactly equal to the browser window: it’s been defined as such. The viewport is not an HTML construct, so you cannot influence it by CSS. It just has the width and height of the browser window — on desktop.

译:视口与浏览器窗口完全相同,但它并不是 HTML 结构,因此你不能通过 CSS 来影响它。在桌面端,视口只是具有浏览器窗口的高度与宽度。

  以上是我在原文中截取的两段关于桌面端的视口概念,从中总结得知:在桌面端,视口就是浏览器的可视化区域,其只是具有浏览器窗口的高度和宽度,使用 document.documentElement.clientWidth/Height 获取视口宽高

  • 在移动端:

Imagine the layout viewport as being a large image which does not change size or shape. Now image you have a smaller frame through which you look at the large image. The small frame is surrounded by opaque material which obscures your view of all but a portion of the large image. The portion of the large image that you can see through the frame is the visual viewport. You can back away from the large image while holding your frame (zoom out) to see the entire image at once, or you can move closer (zoom in) to see only a portion. You can also change the orientation of the frame, but the size and shape of the large image (layout viewport) never changes.

译:设想布局视口是一个不改变形状和大小的大图像,现在你有一个更小的框架通过它你可以看到大的图像。这个框架被不透明的材料包围,遮挡了除大图像的一部分之外的其他部分。你可以通过框架看到的大图像的部分就是视觉视口( visual viewport)。你可以缩小大图像直至看到整个大图像,或者你可以放大到只看其一部分。你可以去改变框架的方向,但是这个大图像(布局视口)的大小和形状永远不会改变.

The visual viewport is the part of the page that’s currently shown on-screen.The user may scroll to change the part of the page he sees, or zoom to change the size of the visual viewport.

译:视觉视口就是当前显示在屏幕上页面的一部分。用户可以通过滚动改变看到的页面的一部分,或者缩放以更改视觉视口的大小。

However, the CSS layout, especially percentual widths, are calculated relative to the layout viewport, which is considerably wider than the visual viewport.

译:然而,CSS 布局,特别是百分比的宽度,是相对于布局视口计算的,它要比视觉视口宽的多。

How wide is the layout viewport? That differs per browser. Safari iPhone uses 980px, Opera 850px, Android WebKit 800px, and IE 974px.

译:布局视口有多宽呢?每个浏览器是不同的,Safari 使用 980px,Opera 850px, Android WebKit 800px, IE 974px。

The ; originally an Apple extension but meanwhile copied by many more browsers. It is meant to resize the layout viewport.

译:,最初是苹果的扩展,但同时被多个浏览器采纳,它是用于调整布局视口端口的大小。

  以上是我在原文中对移动端视口概念的截取,从中总结可得知:

移动端的视口分为三部分:

  1. 视觉视口(visual viewport):就是设备的屏幕区域,但是它所对应的并不是指屏幕区域里的物理像素,而是 CSS 像素。当用户缩小或放大时,测量会发生变化,因为更多或更少的 CSS 像素会融入屏幕。使用 window.innerWidth/Height 获取视觉视口的宽高。
  2. 布局视口(layout viewport):与视觉视口不一样,它是为了解决PC 端网站在移动端显示不佳的一个解决方案,它宽高不会改变,使用 document.documentElement.clientWidth/Height 来获取布局视口的宽高。
  3. 理想视口(ideal viewport):它是基于布局视口的,用于调整布局视口端口的大小。

  既然提到了 理想视口(ideal viewport),那么就不得不普及一下 <meta name="viewport">

viewport 属性

  viewport 是指屏幕上能用来显示网页的区域,默认情况下大多数设备的 viewport 的宽度都是 980 像素,可以通过在 heade 元素中增加 meta 标签来设置 viewport 属性:

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body></body>
    </html>

viewport 下包含以下属性:

  • width:设置 viewport 的宽度,为正整数,或者字符串 “device-width”。
  • initial-scale:设置 viewport 的初始缩放值,为数字,可以带小数。
  • minimum-scale:设置 viewport 的最小缩放值,为数字,可以带小数。
  • maximum-scale:设置 viewport 的最大缩放值,为数字,可以带小数。
  • height:设置 viewport 的高度。
  • user-scalable:是否允许用户缩放,值为 "yes" 或 "no"。

通过设置 viewport 属性,可以调整用户界面的逻辑大小,页面 CSS 中的大小均以 viewport 为基准。

vw、vh、vmin、vmax 的使用

  基础的东西说完了,接着回到 vwvhvminvmax 的使用,它们相对于 PC 端浏览器的视口就是浏览器的可视化区域 ,而在移动端则为布局视口,还是以第一个案例为例,使用 vw 实现布局【预览】:。

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>VW Case</title>
    <link rel="shortcut icon" href="../../assets//images/icon/favicon.ico" type="image/x-icon">
    <style>
        body {
            font-size: 12px;
            margin: auto;
        }

        .btns {
            width: 80vw;
            margin: 0 auto;
        }

        .btns>a {
            float: left;
            width: 20vw;
            text-align: center;
            padding-top: 10px;
        }

        .btns>a>i {
            display: inline-block;
            width: 10vw;
            height: 10vw;
            background: gray;
            border-radius: 50%;
        }

        .btns>a>span {
            display: block;
            line-height: 3vw;
            font-size: 14px;
        }
    </style>
</head>

<body>
    <div class="btns">
        <a>
            <i></i>
            <span>英语</span>
        </a>
        <a>
            <i></i>
            <span>日语</span>
        </a>
        <a>
            <i></i>
            <span>德语</span>
        </a>
        <a>
            <i></i>
            <span>法语</span>
        </a>
        <a>
            <i></i>
            <span>韩语</span>
        </a>
        <a>
            <i></i>
            <span>小语种</span>
        </a>
        <a>
            <i></i>
            <span>教学</span>
        </a>
        <a>
            <i></i>
            <span>职场</span>
        </a>
    </div>
</body>

</html>

  vwvhvminvmax 的出现给我的感觉显得有些鸡肋,有点像 % 一样,但与百分比最大的不同则是 % 是相对于父元素的大小设定的比率,vwvh 是视口大小决定的。在使用它的过程中,个人认为它并不适合去做布局,而是去做一些元素大小的限制。当然,也是因为个人能力有限,并没有悟透,希望能够得到大神的指点。

Flex 弹性盒布局

  在 Felx 出现之前,布局基于盒模型,依赖 displaypositionfloat 样式属性。但是使用时需要清除浮动,并且对于一些特定布局的实现非常不方便。

  采用 Flex 布局的元素,称为 Flex 容器

【Golang 基础】Go 语言的程序结构

Go 语言的项目结构

   一般,一个 Go 项目在 GOPATH 下,会有如下三个目录

.
├── bin   // 存放编译后的可执行文件
├── pkg   // 存放编译后的包文件
└── src   // 存放项目源文件

一般情况下,bin 和 pkg 目录可以不创建,go 命令会自动构建(如 go install),只需要创建 src 目录即可。

Go 程序的一般结构

// 当前程序的包名
package main

// 导入其他包
import "fmt"

// 常量的声明
const _PI float64 = 3.14

// 全局变量声明
var title string = "Go 语言学习笔记"

// 一般类型声明
type newType int

// 结构声明
type Student struct{

}

// 接口的声明
type ILearn interface{

}

// 由 main 作为程序入口的启动点
func main(){
	fmt.Println("Hello Golang")
}
  • Go 程序是通过 package 来组织的(与 python 类似),package 是 最基本的分发单位 和 工程管理中依赖关系的体现;
  • 每个 Go 语言源代码文件开头必须拥有一个 pakcage 声明,表示源码文件所属代码包。默认情况下,除 mainpackage 包外,其他的包名对应文件夹名称;
  • 要生成 Go 语言的可执行程序,必须有 mainpackage 包,且必须在该包下面有 main() 函数;
  • 同一个路径下只能存在一个 package,一个 package 可以拆分成多个源文件
  • 通过 import 关键字来导入其他非 main 包;
  • 通过 const 关键字来定义常量;
  • 通过在函数体外部使用 var 关键字定义全局变量;
  • 通过 type 关键字来进行结构(struct)或接口(interface)的声明;
  • 通过 func 关键字来声明函数;

Go 语言中 import 详解

  • import 语句可以导入源代码文件中所依赖的 package 包,导入包后可以使用 <PackageName>.<FuncName> 对包中的函数进行调用;

  • 如果导入包之后未调用其中的函数或类型将会报编译错误;

  • import 可以使用以下两种方式:

    • 单行导入
      package main
    
      import "fmt"
      import "os"
      import "time"
      import "io"
    • 多行导入
    package main
    
    import (
      "fmt"
      "os"
      "time"
      "io"
    )
  • 如果一个 main 包导入其他包,包将被顺序导入;

  • 如果导入的包依赖其他包(如:包 B),会首先导入包 B,然后初始化包 B 中的常量和变量,最后如果包 B 中有init 函数,将会自动执行 init 函数;

  • 所有包导入完成后才会对 main 中变量和常量进行初始化,然后执行 maininit 函数(如果存在),最后才会执行 main 函数;

  • 如果一个包被导入多次,则该包只会被导入一次;

在使用 import 导入 package 包时,可以为其设置别名:

  • 自定义别名:
package main

import io "fmt"

func main(){
	io.Println("Hello Golang")
}
package main

import (
	io "fmt"
)

func main(){
	io.Println("Hello Golang")
}
  • 点(.)标识的导入包后,调用该包中的函数时,可以省略报名前缀名称(不建议使用)
package main

import . "fmt"

func main(){
	Println("Hello Golang")
}
  • 下划线(_)标识符导入包时,并不是导入整个包,而是执行该包中的 init 函数,因此无法通过包名来调用包中的其他函或属性。使用下划线(_)操作通常是未来注册包里的引擎,外部可以方便的使用

以上三点不可以同时使用。

【Golang 基础】Go 语言的变量与常量

Go 语言的变量与常量

变量声明,初始化与赋值

  • 变量的声明格式:var <变量名称> [变量类型]
  • 变量的赋值格式:<变量名称> = <值,表达式,函数等>
  • 声明和赋值同时进行:var <变量名称> [变量类型] = <值,表达式,函数等>
  • 分组声明的格式
package basic

import "fmt"

func DeclaratGroupVariable(){
	// 分组声明变量
	var (
		name string
		age int = 18
	)

	name = "hvkcoder"

	fmt.Printf("Hello!My name'is %d. I'm %d years old", name, age)
}
  • 简写声明格式:<变量名称> := <值,表达式,函数等>,只能在局部变量声明时使用,并且必须赋值;
  • 声明多个变量,需要以 , 隔开;
package basic

import "fmt"

func DeclaratMultiVariable(){
	// 声明多个变量
	var a , b, c int
	// 为多个变量赋值
	a, b, c = 1 ,2, 3
	fmt.Printf("a = %d , b = %d , c = %d", a, b, c)

	// 声明多个变量并赋值
	var e, f, g int = 4, 5, 6
	fmt.Printf("e = %d , f = %d , g = %d", e, f, g)

	// 简写声明
	h, i, j := 7, 8, 9
	fmt.Printf("h = %d , i = %d , j = %d", h, i, j)
}

声明全局变量时,不能使用简写声明,且必须使用 var 关键字。变量名是 下划线(_) 表示忽略。

常量定义的形式,类型范围

  • 常量定义从形式上可以分为 显式 和 隐式;

    • 显式:const <常量名称> [type] = value
    • 隐式:const <常量名称> = value
  • 一般情况下常量名称需大写,若该常量为私有常量,只需在常量名前加 下滑线(_),如:const _PI = 3.14

  • 常量可以使用内置表达式,如:len()unsafe.SizeOf() 等;

package basic

import "unsafe"


func DeclaratExpression(){
	const (
		_NAME string = "hvkcoder"
		_NAME_LENGTH int = len(_NAME)
		_NAME_SIZE  = unsafe.Sizeof(_NAME)
	)
}
  • 多组常量声明时,常量默认取上一个常量的值;
package basic

import "fmt"

func DeclaratGroupConstant(){
	const (
		_NAME string = "hvkcoder"
		_FIRST_NAME
	)

	fmt.Println(_NAME)
	fmt.Println(_FIRST_NAME)
}

常量类型范围目前只支持 布尔型、数字型和字符串类型

特殊常量 iota

  • iota 只能在常量的表达式中使用,在 const 关键字出现时, iota将被重置为 0;

  • const 中每新增一行常量声明,将使 iota 计数一次;

  • iota 常见用法:

    • 跳值使用法
    package basic
    
    import "fmt"
    
    func JumpValueByIota(){
        const (
            a = 3.14
            b
            c = iota  // 2
            d         // 3
        )
        fmt.Println(a,b,c,d)
    }
    • 插值使用法
    package basic
    
    import "fmt"
    
    func InsertValueByIota(){
    	const (
    		a = iota   // 0
    		b = 3.14
    		c = iota   // 2
    		d          // 3
    	)
    	fmt.Println(a,b,c,d)
    }
    • 表达式隐式使用法
    package basic
    
    import "fmt"
    
    func ExpressionByIota(){
    	const (
    		B = 1 << (10 * iota)  // 1
    		KB                    // 1024
    		MB                    // 1048576
    		GB                    // 1073741824
    		TB                    // 1099511627776
    		PB                    // 1125899906842624
    	)
    
    	fmt.Println(B,KB,MB,GB,TB,PB)
    }
    • 单行使用法
    package basic
    
    import "fmt"
    
    func SingleLineByIota(){
    	const (
    		a , b = iota + 1, iota + 2
    		c, d = iota + 3, iota + 4
    
    		// a => 0 + 1
    		// b => 0 + 2
    		// c => 1 + 3
    		// d => 1 + 4
    	)
    	fmt.Println(a,b,c,d)
    }

变量的类型转换

  • Go 语言中不存在隐式转换,类型转换必须是显式的;
  • 类型转换只能发生在两种兼容类型之间;
  • 类型转换的格式:var <变量名称> [:]= <目标类型>(<需要转换的变量>);

【Golang 基础】Go 语言的数组

Go 语言中的数组

  • 定义数组的格式:var <arrayName> [n]<type>, 其中 n >= 0;
package basic

import "fmt"

func DeclaratArray(){
	
	// 定义指定长度的数组
	var arr1 [5]int
	fmt.Println(arr1) // [0 0 0 0 0]
	
	// 不设置数组长度
	arr2 := [...]int{}
	fmt.Println(arr2) // []
	
	// 定义指定长度的数组,并赋值
	arr3 := [2]int{ 1, 5 }
	fmt.Println(arr3) // [1 5]
}
  • 通过 new 关键字声明数组,返回一个指向该数组的指针;
package basic

import "fmt"

func DeclaratArrayByNew(){
	
	// 通过 new 声明数组 ,返回一个指向该数组的指针
	arr1 := new([5]int)
	fmt.Println(arr1) // &[0 0 0 0 0]
}

指向数组的指针 与 指针数组的区别是:

  • 指向数组的指针:是指一个指针指向某个数组;
  • 指针数组:数组包含的类型是指针;

数组长度也是数组类型的一部分,因此具有不同长度的数组为不同类型。

数组是值类型

  Go 语言中的数组是值类型的,也就意味着两个相同类型的数组可以使用 ==!= 运算符判断两个数组是否相等,但是不能使用 <> 运算符;

package basic

import "fmt"

func JudgeArray(){
	arr1 := [5]int{1, 3, 5}
	arr2 := [5]int{2, 4, 6}
	
	fmt.Println(arr1 == arr2) // false
	fmt.Println(arr1 != arr2) // true
}

  由于数组是值类型,因此向一个函数传递数值时,需要传递一个指向该数组的指针,才能在函数内更改该数组的值;

package main

import "fmt"

// 更改数组内的值
func UpdateArray(array *[5]int){
	array[0] = 100
}

func main(){
	array := [5]int{1, 3, 4, 5}
	UpdateArray(&array)
	fmt.Println(array) // [100 3 4 5 0]
}

实际上在 Go 语言中一般不直接使用数组,而是使用切片。

多维数组

package basic

import "fmt"

func MultidimensionalArray() {
	array := [2][3]int{
		{1, 3, 5},
		{2, 4, 6},
	}
	fmt.Println(array) // [[1 3 5] [2 4 6]]
}

Learn-Webpack

什么是 webpack

   webpack 是德国开发者 Tobias Koppers 开发的模块加载器

  在 webpack 中所有的文件都将被当做模块使用。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有的这些模块打包成一个或多个 bundle。如图所示:
webpack能做什么

与 Gulp/Grunt 对比

  webpack 与 Gulp/Grunt 是没有对比性的,因为 Gulp/Grunt 是一种能够优化前端的开发流程的工具,而 webpack 是一种模块化的解决方案。不过 Webpack 的优点使得 Webpack 在很多场景下可以替代 Gulp/Grunt 类的工具。

  Grunt 和 Gulp 的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。

Grunt和Gulp工作图

  webpack 的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack 将从这个文件开始找到你的项目的所有依赖文件,使用 loaders 处理它们,最后打包为一个(或多个)浏览器可识别的 JavaScript 文件。

webpack工作图

webpack 的安装及使用 (Demo1 Source

  1. 通过 npm 全局安装 webapck
    $ npm install -g webpack
  1. 创建项目并初始化 package.json 文件
    $ mkdir demo1 && cd demo1
    $ npm init
  1. 在项目中安装 webpack
    $ npm install webpack --save-dev

--save-dev 是开发时候依赖的东西,--save 是发布之后还依赖的东西

  1. 在项目中创建如下文件结构

    .
    ├── index.html  // 显示的网页
    ├── main.js    // webpack 入口
    └── bundle.js // 通过 webpack 命令生成的文件,无需创建
    
  2. 通过命令对项目中依赖的 js 文件进行打包

    # webpack 要打包的 js 文件名  打包后生成的js文件名
    $ webpack main.js bundle.js

  在 webpack 命令后面还可以加入以下参数

  • --watch 实时打包
  • --progress 显示打包进度
  • --display-modules 显示打包的模块
  • --display-reasons 显示模块包含在输出中的原因

  更多参数可以通过命令 webpack --help 查看

webpack 中的四个核心概念 (Demo2 Source

  • Entry 入口
  • Output 输出
  • Loaders
  • Plugins 插件

  webpack 中默认的配置文件名称是 webpack.config.js,因此我们需要在项目中创建如下文件结构:

.
├── index.html            // 显示的页面
├── main.js              // webpack 入口
├── webpack.config.js   //  webpack 中默认的配置文件
└── bundle.js          //  通过 webpack 命令生成的文件,无需创建

entry 入口

  入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后。 webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

  可以在 webpack.config.js 中 配置 entry 属性,来指定一个入口或多个起点入口,代码如下:

moudle.exports = {
  entry: './path/file.js',
};

output 输出

   output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。你可以通过在配置指定一个 output 字段,来配置这些过程:

const path = require('path');

moudle.exports = {
  entry: './path/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-webpack.bundle.js',
  },
};

  其中 output.path 属性用于指定生成文件的路径,output.filename 用于指定生成文件的名称。

Loaders

   Loaderswebpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后可以利用 webpack 的打包能力,对它们进行处理。

  本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图可以直接引用模块。在更高层面上,在 webpack 的配置中 loader 有两个目标:

  1. 识别应该被对应的 loader 进行转换的那些文件(使用 test 属性)
  2. 转换这些文件,从而使其能够被添加到依赖图中(并且最终添加到 bundle 中)(use 属性)

  在开始下面的代码之前,我们需要安装 style-loadercss-loader

    $ npm install --save-dev style-loader css-loader

并在项目中创建 style.css 样式文件:

h1 {
  color: red;
}

  然后在 webpack.config.js 中输入以下代码:

const path = require('path');

module.export = {
  entry: './main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
      },
    ],
  },
};

Plugins 插件

   Loaders 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。

  想要使用一个插件,需要 require() 它,然后把它添加到 Plugins 数组中,多数插件可以通过选项自定义。也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的实例。

  在开始下面的代码之前,我们需要安装 html-webpack-plugin 插件:

    $ npm install html-webpack-plugin --save-dev

它可以简化 HTML 文件的创建,为您的 webpack 包提供服务。

  然后在 webpack.config.js 中输入以下代码:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

const config = {
  entry: './main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
      },
    ],
  },
  plugins: [new HtmlWebpackPlugin({ template: './index.html' })],
};

module.exports = config;

运行与配置

   最后我们可以直接通过 webpack 命令编译打包,如果想要在其命令后加入参数,可以通过配置 package.json 文件中的 scripts 属性:

{
  "scripts": {
    "build": "webpack --config webpack.config.js --progress --display-modules"
  }
}

当然如果你想要更改默认的配置文件名称,可以将 --config 后面的 webpack.config.js 配置文件名改为你自定义的名称。

  通过以下命令执行:

    $ npm run build

多入口设置与 html-webpack-pugin 插件详解(Demo3 Source)

  我们可以为 entry 指定多个入口。在开始代码之前,我们需要创建如下目录解构

.
├── index.html            // 显示的页面
├── main1.js             // webpack 入口1
├── main1.js            // webpack 入口2
├── style.css          // 样式文件
└── webpack.config.js // webpack 中默认的配置文件

  我们在 index.html 文件中输入以下内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>demo3</title>
  </head>
  <body></body>
</html>

  我们在 main1.js 文件中输入以下内容:

    improt './style.css'

    var h1 = document.createElement('h1');
    h1.innertHTML = '这是 main1.js 中的内容';
    document.body.appendChild(h1);

  我们在 main2.js 文件中输入以下内容:

    improt './style.css'

    var h2 = document.createElement('h2');
    h2.innertHTML = '这是 main2.js 中的内容';
    document.body.appendChild(h2);

  我们在 style.css 文件中输入以下内容:

h1 {
  color: red;
}
h2 {
  color: blue;
}

  我们在 webpack.config.js 文件中输入以下内容:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

const config = {
  entry: {
    bundle1: './main1.js',
    bundle2: './main2.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
  module: {
    rules: [{ test: /\.css$/, loader: 'style-loader!css-loader' }],
  },
  pugins: [new HtmlWebpackPlugin({ template: './index.html' })],
};

module.exports = config;

  完成上面的代码工作后,运行 webapck 命令,我们打开 dist 文件中的 index.html
index.html 运行结果

运行的结果并不是我们预期的那样展示 h1 的内容在前,h2 内容在后,打开生成后的 index.html 源码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>demo3</title>
  </head>
  <body>
    <script type="text/javascript" src="bundle2.js"></script>
    <script type="text/javascript" src="bundle1.js"></script>
  </body>
</html>

从源码中便可得知,先引入的 bundle2.js 文件,也就是 main2.js 的内容,后引入的 bundle1.js 文件,也就是 main1.js 的内容。

  我们并没有在 index.html 中输入任何引入 JavaScript 文件的代码,那么使用 webpack 打包后生成的文件,是怎么引入 JavaScript 文件的呢。事实上就是通过 html-webpack-plugin 为我们生成的 index.html

html-webpack-plugin 中的参数详解

  通过 npm 中的介绍,html-webpack-plugin 是一个 webpack 插件,可以简化 HTML 文件的创建,为我们的 webpack 包提供服务,它包含了一个改变每个编译的文件名参数。使用 lodash 模板提供我们自己的模板或者使用自己的 loader

  我们可以配置以下参数传递给 HtmlWebpackPlugin

  • title: 用于生成的 HTML 文档的标题。
  • filename: 要写入 HTML 的文件。默认为 index.html 。你也可以在这里指定一个子目录(例如:assets / admin.html)。
  • template: 引入的模板文件,具体内容可以查看文档
  • inject: true | 'head' | 'body' | false,指定引入 JavaScript 脚本文件,在生成的HTML 中的位置。默认为 true,指JavaScript 脚本文件在 <body> 元素中引入;head ,指JavaScript 脚本文件在 <head> 元素中引入,bodytrue 值相同;false 指只生成 HTML 文件,不引入任何JavaScript 脚本文件。
  • favicon: 生成的 HTML 文件中的图标路径。
  • minify: {...} | false 是否对生成的 HTML 文件压缩,默认为 false,具体配置可查看 html-minifier
  • hash: true | false ,如果为 true ,给生成的 js 文件一个独特的 hash 值,该 hash 值是该次 webpack 编译的 hash 值,这对缓存清除非常有用。默认值为 false
  • cache: true | false, 如果为 true 则只编译生成更改的内容将文件,默认值为 true
  • showErrors:true | false,如果为 true,则将错误内容添加到 HTML 中,默认值为 true
  • chunks: 指定引入的 JavaScript 脚本文件(例如:[ 'bundle1', 'bundle2' ])。
  • chunksSortMode: 'none' | 'auto' | 'dependency' |'manual' | {function} - default: 'auto',对引入的 chunks 进行排序,具体可以查看该文档
  • excludeChunks: 排除掉指定的 JavaScript 脚本文件(例如:[ 'bundle1', 'bundle2' ])。
  • xhtml: true | false,默认值是 false ,如果为 true ,则以兼容 xhtml 的模式引用文件。

  现在我们知道了 html-webpack-plugin 中的参数,下面我们就来修改 webpack.config.js 中的内容:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

const config = {
  entry: {
    bundle1: path.resolve(__dirname, 'main1.js'),
    bundle2: path.resolve(__dirname, 'main2.js'),
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
  module: {
    rules: [{ test: /\.css$/, loader: 'style-loader!css-loader' }],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: '多文件引入', // 生成 html 的标题
      filename: 'index.html', // 生成 html 文件的名称
      template: path.resolve(__dirname, 'index.html'), // 根据自己的指定的模板文件来生成特定的 html 文件
      // inject: true, // 注入选项 有四个值 ture: 默认值,script标签位于html文件的 body 底部, body: 同 true, head: script标签位于html文件的 head 底部,false:不注入script标签
      favicon: path.resolve(__dirname, 'favicon.ico'), // 生成的 html 文件设置 favicon
      minify: {
        caseSensitive: false, //是否大小写敏感
        collapseBooleanAttributes: true, //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled
        collapseWhitespace: true, //是否去除空格
      },
      hash: true, // hash选项的作用是 给生成的 js 文件一个独特的 hash 值,该 hash 值是该次 webpack 编译的 hash 值。默认值为 false
      cache: true, // 默认值是 true。表示只有在内容变化时才生成一个新的文件
      showErrors: true, // showErrors 的作用是,如果 webpack 编译出现错误,webpack会将错误信息包裹在一个 pre 标签内,属性的默认值为 true
      chunks: ['bundle1', 'bundle2'], // 指定引入的 js 文件
      //excludeChunks:[ 'bundle1' ], // 排除掉某些 js 文件
      /**
       * script 标签的引用顺序
       * 'dependency' 按照不同文件的依赖关系来排序
       * 'auto' 默认值,插件的内置的排序方式
       * 'none'
       * 'manual'
       * funciton 自定义排序,与JS中自定义数组的sort回调一个含义, 具体可以看 https://github.com/jantimon/html-webpack-plugin/issues/481
       */
      chunksSortMode: function(chunk1, chunk2) {
        var orders = ['bundle1', 'bundle2'];
        var order1 = orders.indexOf(chunk1.names[0]);
        var order2 = orders.indexOf(chunk2.names[0]);
        return order1 - order2;
      },
      xhtml: false, // 一个布尔值,默认值是 false ,如果为 true ,则以兼容 xhtml 的模式引用文件
    }),
  ],
};

module.exports = config;

  完成上面的代码工作后,运行 webapck 命令,我们打开 dist 文件中的 index.html。
index.html 运行结果
  Nice!与我们的预期效果显示一致。在对 html-webpack-plugin 的介绍中,提到了 lodash 模板, 那么该怎么用呢?我们再次修改 webpack.config.js 中的内容,为 HtmlWebpackPlugin 传入 Date 参数:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

const config = {
  entry: {
    bundle1: path.resolve(__dirname, 'main1.js'),
    bundle2: path.resolve(__dirname, 'main2.js'),
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
  module: {
    rules: [{ test: /\.css$/, loader: 'style-loader!css-loader' }],
  },
  plugins: [
    new HtmlWebpackPlugin({
      date: new Date(),
      title: '多文件引入', // 生成 html 的标题
      filename: 'index.html', // 生成 html 文件的名称
      template: path.resolve(__dirname, 'index.html'), // 根据自己的指定的模板文件来生成特定的 html 文件
      // inject: true, // 注入选项 有四个值 ture: 默认值,script标签位于html文件的 body 底部, body: 同 true, head: script标签位于html文件的 head 底部,false:不注入script标签
      favicon: path.resolve(__dirname, 'favicon.ico'), // 生成的 html 文件设置 favicon
      minify: {
        caseSensitive: false, //是否大小写敏感
        collapseBooleanAttributes: true, //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled
        collapseWhitespace: true, //是否去除空格
      },
      hash: true, // hash选项的作用是 给生成的 js 文件一个独特的 hash 值,该 hash 值是该次 webpack 编译的 hash 值。默认值为 false
      cache: true, // 默认值是 true。表示只有在内容变化时才生成一个新的文件
      showErrors: true, // showErrors 的作用是,如果 webpack 编译出现错误,webpack会将错误信息包裹在一个 pre 标签内,属性的默认值为 true
      chunks: ['bundle1', 'bundle2'], // 指定引入的 js 文件
      //excludeChunks:[ 'bundle1' ], // 排除掉某些 js 文件
      /**
       * script 标签的引用顺序
       * 'dependency' 按照不同文件的依赖关系来排序
       * 'auto' 默认值,插件的内置的排序方式
       * 'none'
       * 'manual'
       * funciton 自定义排序,与JS中自定义数组的sort回调一个含义, 具体可以看 https://github.com/jantimon/html-webpack-plugin/issues/481
       */
      chunksSortMode: function(chunk1, chunk2) {
        var orders = ['bundle1', 'bundle2'];
        var order1 = orders.indexOf(chunk1.names[0]);
        var order2 = orders.indexOf(chunk2.names[0]);
        return order1 - order2;
      },
      xhtml: false, // 一个布尔值,默认值是 false ,如果为 true ,则以兼容 xhtml 的模式引用文件
    }),
  ],
};

module.exports = config;

更改 index.html 中的内容,lodash 模板默认支持的是 ejs 模板的语法:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>demo3</title>
  </head>
  <body>
    <%= htmlWebpackPlugin.options.date %>
  </body>
</html>

  完成上面的代码工作后,运行 webapck 命令,我们打开 dist 文件中的 index.html。
index.html 运行结果

  通过运行结果,我们可以发现在顶部输出了当前时间,也就是 HtmlWebpackPlugin 传入的参数,实际上 HtmlWebpackPlugin 中的参数都可以通过 htmlWebpackPlugin.options.参数名称 输出,我就不一一列举。

Babel

  Babel 是一个工具链,主要用于在旧的浏览器或环境中将 ECMAScript 2015+ 代码转换为向后兼容版本的 JavaScript 代码。

安装依赖包

npm i -D babel-loader @babel/core @babel/preset-env
  • babel-loader:用于 babel 在 webapck 中的加载模块。
  • @babel/core:babel 编译工具;
  • @babel/preset-env:babel 生成指定支持浏览器版本的编译工具。

babel 支持两种配置,一种是在项目根目录下创建 .babelrc 配置文件,另一种是通过 webpack loader options 的方式配置

  • .babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        // 用于指定浏览器版本号
        "targets": {
          "browsers": "last 2 version"
        }
      }
    ]
  ]
}
  • webpack loader options 配置
{
  "module": {
    "rules": [
      {
        "test": /\.js$/,
        "exclude": /(node_modules)/,
        "loader": "babel-loader",
        "options": {
          "presets": [
            [
              "@babel/preset-env",
              {
                // 用于指定浏览器版本号
                "targets": {
                  "browsers": "last 2 version"
                }
              }
            ]
          ]
        }
      }
    ]
  }
}

Babel 默认只转换语法,而不转换新的 API,如 Set、Promise、Map 等或是 ES6 对 Array、String 等扩展,如果需要使用新的 API 还需要使用对相应的 转换插件 或 polyfill

  • Babel Polyfill
npm i --save @babel/polyfill

只需在入口头部引入即可

import '@babel/polyfill';
// 或
require('@babel/polyfill');

上面的使用方法,会导致打包后的文件过大,由于是在入口文件直接引入 polyfill,从而将会导入 polyfill 整个包,增加了无用代码。

@babel/preset-envuseBuiltIns: 'usage' 按需引入,就是用于解决上面的问题。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        // 用于指定浏览器版本号
        "targets": {
          "browsers": "last 2 version"
        },
        "useBuiltIns": "usage"
      }
    ]
  ]
}

@babel/polyfill 是通过改写全局 prototype 的方式对新的 API 支持,比较适合单独运行的项目。

  • Babel RunTime Transform
npm i @babel/runtime --save
npm i @babel/plugin-transform-runtime --save-dev
{
  "presets": [
    [
      "@babel/preset-env",
      {
        // 用于指定浏览器版本号
        "targets": {
          "browsers": "last 2 version"
        }
      }
    ]
  ],
  "plugins": ["@babel/plugin-transform-runtime"]
}

@babel/runtime 的 polyfill 对象是临时构造并 import/require 的,因此并不是真正的全局引用,由于不是全局引用,对于实例化对象的方法,并不能生效。比较适合编写 第三方类库。

【Golang 基础】Go 语言 面向对象

Go 语言的面向对象

  Go 语言的面向对象非常简单,仅支持封装,不支持继承和多态。继承和多态是在接口中实现的。

  因此 Go 语言中没有 class,而是通过 struct(结构体) 对相同类型或不同类型的数据进行封装。

  • 通过 type <structName> struct {} 格式定义结构体;
type User struct {
	Name     string
	Age      int
	IsActive bool
}
  • 定义后的结构体就可以作为类型使用;
hvkcoder := User{Name: "hvkcoder", Age: 18, IsActive: true}
fmt.Println(hvkcoder) // {hvkcoder 18 true}

hvkcoder := User{"hvkcoder", 18, true}
fmt.Println(hvkcoder) // {hvkcoder 18 true}
  • 通过 结构体.成员名称 访问结构体中的对象;
fmt.Println(hvkcoder.Name) // hvkcoder
  • 结构体是值类型,因此支持比较运算符的使用;
hvkcoder := User{Name: "hvkcoder", Age: 18, IsActive: true}
jason := User{Name: "jason", Age: 20, IsActive: true}
    
fmt.Println(hvkcoder == jason) // false
  • 由于结构体是值类型,因此需要向函数传递 结构体指针,才能去改变结构体中的值;
package main

import "fmt"

type User struct {
	Name     string
	Age      int
	IsActive bool
}

func setName(user *User,name string){
	user.Name = name
}

func main(){
	test := User{Name: "jason", Age: 18, IsActive: true}
	fmt.Println(test.Name) // jason
	setName(&test, "hvkcoder")
	fmt.Println(test.Name) // hvkcoder
}
  • 结构体也可以嵌套
package main

import "fmt"

type Address struct {
	city, town string
}

type User struct {
	Name     string
	Age      int
	IsActive bool
	UAddress Address
}

func main() {
	user := User{
		Name:     "hvkcoder",
		Age:      18,
		IsActive: false,
		UAddress: Address{"北京", "海淀"},
	}

	fmt.Println(user) // {hvkcoder 18 false {北京 海淀}}
}

Go 语言中的方法

  Go 语言中的方法从某种意义上来说就是函数的语法糖,receiver 作为方法的第一个强制类型参数传入,这也就是 Method ValueMethod Expression 的区别。

package main

import "fmt"

type Student struct {
	Name string
}

// receiver 定义结构方法
func (student Student) SayHi(){
	fmt.Println("Hello! My name's", student.Name)
}

func main(){

	student := Student{"hvkcoder"}

	// 使用 Method Value 方式调用方法
	student.SayHi() // Hello! My name's hvkcoder
	
	// 使用 Method Expression 方法调用方法
	(Student).SayHi(student) // Hello! My name's hvkcoder
}
  • 既然说了 Go 语言中的方法实际上就是函数的语法糖,因此想通过方法去改变结构体的值,仍然需要传递指针;
package main

import "fmt"

type User struct {
	Name     string
	Age      int
	IsActive bool
}

func (user *User) setName(name string){
	user.Name = name
}

func main(){
	test := User{Name: "jason", Age: 18, IsActive: true}
	fmt.Println(test.Name) // jason
	
	test.setName("hvkcoder")
	fmt.Println(test.Name) // hvkcoder
}
  • Go 中不存在方法重载;
  • 如果外部结构和内嵌结构存在同名方法,则优先调用外部结构的方法;

使用 Go 实现一个二叉排序树

package algorithm

import "fmt"

// 二叉树节点结构
type Node struct {
	Value       int
	Left, Right *Node
}

// 添加二叉排序树节点
func (node *Node) Insert(newNode *Node) {

	// 根据二叉排序树的特点,左子树的所有节点均小于根节点,右子树的所有节点均大于根节点
	if node.Value > newNode.Value {
		// 判断左子树是否有值
		if node.Left == nil {
			node.Left = newNode
		} else {
			node.Left.Insert(newNode)
		}
	} else {
		// 判断右子树是否有值
		if node.Right == nil {
			node.Right = newNode
		} else {
			node.Right.Insert(newNode)
		}
	}
}

// 前序遍历二叉排序树节点
func (node *Node) PreorderTraversal(){
	if node != nil{
		fmt.Printf("%d ", node.Value)
		node.Left.PreorderTraversal()
		node.Right.PreorderTraversal()
	}
}

// 中序遍历二叉排序树节点
func (node *Node) InorderTraversal(){
	if node != nil{
		node.Left.InorderTraversal()
		fmt.Printf("%d ", node.Value)
		node.Right.InorderTraversal()
	}
}

// 后序遍历二叉排序树节点
func (node *Node) PostorderTraversal(){
	if node != nil{
		node.Left.PostorderTraversal()
		node.Right.PostorderTraversal()
		fmt.Printf("%d ", node.Value)
	}
}


// 创建二叉排序树
func NewBinarySortTree(treeNode []int) *Node{
	var root *Node
	if len(treeNode) > 0 {
		for _,value := range treeNode {
			node := Node{Value:value}
			if root == nil {
				root = &node
			}else {
				root.Insert(&node)
			}
		}
	}
	return root
}
package main

import (
	"./algorithm"
	"fmt"
)

func main() {
	// 声明一个二叉树节点 Slice
	treeNode := []int{8, 3, 10, 1, 4, 13, 16, 14}
	// 创建二叉排序树
	binarySortTree := algorithm.NewBinarySortTree(treeNode)
	// 使用 前序方式遍历 二叉树
	binarySortTree.PreorderTraversal() // 8 3 1 4 10 13 16 14
	fmt.Println()

	// 使用 中序方式遍历 二叉树
	binarySortTree.InorderTraversal() // 1 3 4 8 10 13 14 16
	fmt.Println()

	// 使用 后序方式遍历 二叉树
	binarySortTree.PostorderTraversal() // 1 4 3 14 16 13 10 8
}

Node.js access_token的获取、存储及更新

一、写在前面的话

  上一篇文章中,我们使用 Node.js 成功的实现了接入微信公众平台功能。在这篇文章中,我们将实现微信公众平台一个非常重要的参数 access_token ,它是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用 access_token。

  在开始之前,让我们先按捺住自己激动的心情、调整好呼吸,因为我们要将上一篇文章的代码重新整理一下。一个好的项目结构,更能有助于我们理清业务逻辑以及将来维护代码的便捷。OK!
Are you ready?

二、整理项目结构

1.打开我们的项目,并在项目中添加文件夹,命名为 wechat ,如图:
这里写图片描述

2.在 wechat 文件夹中添加文件并命名为 wechat.js。wechat.js 主要用于封装开发微信公众平台的所有方法。首先我们构建这个模块的结构,代码如下:

'use strict' //设置为严格模式

//构建 WeChat 对象 即 js中 函数就是对象
var WeChat = function(config){
    //设置 WeChat 对象属性 config
    this.config = config;
    
    //设置 WeChat 对象属性 token
    this.token = config.token;
}

//暴露可供外部访问的接口
module.exports = WeChat;

 严格模式:是在 ECMAScript 5 中引入的概念。严格模式是为 Javascript 定义了一种解析与执行模型。

 module.exports :暴露接口用于外部操作。实际上我们定义模块后,使用 node.js 的 require 引用时,node.js 会自动在我们定义的模块外层加入以下代码

/**
 * exports  module.exports 的一个简短的引用
 * require  用于引入模块
 * module   当前模块的引用
 * __filename  当前模块的文件名
 * __dirname   当前模块的目录名
 */
(function (exports, require, module, __filename, __dirname) {
    //自定义模块的代码块
})();

相信对于有过 javascript 开发经验的同学,上面的代码并不陌生。我们可以将它理解为一个闭包,是一个匿名方法的调用,避免污染全局变量。

小知识:

  在上面的代码中,除了我们所使用的 module.exports 对象,还有另一个用于暴露接口的 变量 exports (官方文档将 module.exports 称为对象,exports 称为 属性,我在这里也就这样称呼了),那么 module.exports 与 exports 有什么区别呢?

  module.exports 对象是由模块系统创建的,exports 变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋于 module.exports 的值。——来自Node.js官方文档

  也就是说 exports 是 module.exports 的引用,而 module.exports 才是真正用于暴露接口的对象。 exports 赋值的所有属性与方法都赋值给了 module.exports 对象。

  如果 module.exports 与 exports 将值赋值给了相同的属性,则按照赋值的先后顺序,取最后一个赋值;如果我们给 module.exports 赋值的是一个对象,则会覆盖 exports 的所有方法与属性。

  因此我们在暴露接口的使用上,如果只是单一属性或方法的话,建议使用exports.属性/方法,要是导出多个属性或方法或使用对象构造方法,建议使用 module.exports。

  具体详解可以点击查看该文章 -> Module.exports和exports的区别

3.为 WeChat 对象添加一个方法 auth,并将 app.js 中的验证方法粘贴进去

'use strict' //设置为严格模式

const crypto = require('crypto'); //引入加密模块

//构建 WeChat 对象 即 js中 函数就是对象
var WeChat = function(config){
    //设置 WeChat 对象属性 config
    this.config = config;

    //设置 WeChat 对象属性 token
    this.token = config.token;
}

/**
 * 微信接入验证
 */
WeChat.prototype.auth = function(req,res){
     //1.获取微信服务器Get请求的参数 signature、timestamp、nonce、echostr
        var signature = req.query.signature,//微信加密签名
            timestamp = req.query.timestamp,//时间戳
                nonce = req.query.nonce,//随机数
            echostr = req.query.echostr;//随机字符串

        //2.将token、timestamp、nonce三个参数进行字典序排序
        var array = [this.token,timestamp,nonce];
        array.sort();

        //3.将三个参数字符串拼接成一个字符串进行sha1加密
        var tempStr = array.join('');
        const hashCode = crypto.createHash('sha1'); //创建加密类型 
        var resultCode = hashCode.update(tempStr,'utf8').digest('hex'); //对传入的字符串进行加密

        //4.开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
        if(resultCode === signature){
            res.send(echostr);
        }else{
            res.send('mismatch');
        }
}

//暴露可供外部访问的接口
module.exports = WeChat;

4.整理 app.js 文件的中的代码,如下:

const express = require('express'), //express 框架 
      wechat  = require('./wechat/wechat'), 
       config = require('./config');//引入配置文件

var app = express();//实例express框架

var wechatApp = new wechat(config); //实例wechat 模块

//用于处理所有进入 3000 端口 get 的连接请求
app.get('/',function(req,res){
    wechatApp.auth(req,res);
});

//监听3000端口
app.listen(3000);

嗯!这样代码看着是不是舒服多了呢。机智如我

剩下的就是去微信公众平台接入验证了,在上一篇文章中有详细的教程,这里我就不再演示了

就是这么懒

三、access_token的获取、存储及更新

1.微信文档步骤

  在开始码代码之前,我们依然是先理清实现的思路,在开始编写实现代码。打开 微信帮助文档 ,点击左侧菜单中的开始开发,点击其子菜单获取access_token,如图:
获取access_token

获取access_token 帮助文档

通过上面的 API 的描述,我们总结出以下步骤:

  1. 实现 https Get 请求
  2. 获取 access_token 并存储 如果 当前 access_token 过期则更新

2.access_token的获取、存储及更新 代码实现

  整理好思路后我们就按照上一节的步骤去实现。通过帮助文档我们将用于请求微信API 的请求地址与参数,存放到 config.json 文件。

  其中 appid 与 secret 两个参数 位于 微信公众平台 左侧菜单的基本配置中,如图:
微信公众平台 - 基本配置

APPID 与 AppSecret

开发者密码 点击重置,用手机微信扫面二维码后便可得到。config.json 代码如下

{
    "token":"wechat",
    "appID":"wx154f********764da",
    "appScrect":"59de4266*******8dbe9de4b798cd372",
    "apiDomain":"https://api.weixin.qq.com/",
    "apiURL":{
        "accessTokenApi":"%scgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
    }
}

由于微信 API 请求连接的域名是公用的,我们将它提出来,在请求地址中使用 %s(字符串) 占位符占位。

  微信所有请求连接都是 https 协议,很幸运的是 Node.js 系统包中为我们提供了 https 的包,由于后面的请求会多次用到 https ,因此我们将它封装为一个公用的方法,以便以后的使用,再次打开 wechat.js 在构造方法中,引入 https 模块,并在构造函数内部添加 requestGet 方法

//用于处理 https Get请求方法
    this.requestGet = function(url){
        return new Promise(function(resolve,reject){
            https.get(url,function(res){
                var buffer = [],result = "";
                //监听 data 事件
                res.on('data',function(data){
                    buffer.push(data);
                });
                //监听 数据传输完成事件
                res.on('end',function(){
                    result = Buffer.concat(buffer,buffer.length).toString('utf-8');
                    //将最后结果返回
                    resolve(result);
                });
            }).on('error',function(err){
                reject(err);
            });
        });
    }

提示:

    npm 提供了很多用于请求的工具包,比如 request ( 安装命令 npm install request ) 等。这里我只是用系统包去做请求处理。

  由于 https 是异步请求的,我在这里面使用了 ES6 的 Promise 对象

  完成了 requestGet方法后,我们的第1步骤也就完成了。下面开始第2步,获取 access_token 并存储 如果 当前 access_token 过期则更新。

  在这之前我是想将 access_token 的存储位置依然放在 config.json 文件中,由于 access_token 在更新后 需要将文件重写,可能容易造成 config.json 文件的格式的紊乱,因此在 wechat 中重新创建一个 accessToken.json 文件用于存储 access_token
这里写图片描述

{
    "access_token":"",
    "expires_time":0
}

   其中 access_token 用于存储 我们 GET 请求后access_token 的值,expires_time 用于存储 access_token 的过期时间,保存为时间戳。

  在 wechat.js 引入 fs 模块用于操作文件、util 工具模块用于处理占位符、 accessToken.json 文件

'use strict' //设置为严格模式

const crypto = require('crypto'), //引入加密模块
       https = require('https'), //引入 htts 模块
        util = require('util'), //引入 util 工具包
accessTokenJson = require('./access_token'); //引入本地存储的 access_token

//构建 WeChat 对象 即 js中 函数就是对象
var WeChat = function(config){
    //设置 WeChat 对象属性 config
    this.config = config;
    //设置 WeChat 对象属性 token
    this.token = config.token;
    //设置 WeChat 对象属性 appID
    this.appID = config.appID;
    //设置 WeChat 对象属性 appScrect
    this.appScrect = config.appScrect;
    //设置 WeChat 对象属性 apiDomain
    this.apiDomain = config.apiDomain;
    //设置 WeChat 对象属性 apiURL
    this.apiDomain = config.apiURL;

    //用于处理 https Get请求方法
    this.requestGet = function(url){
        return new Promise(function(resolve,reject){
            https.get(url,function(res){
                var buffer = [],result = "";
                //监听 data 事件
                res.on('data',function(data){
                    buffer.push(data);
                });
                //监听 数据传输完成事件
                res.on('end',function(){
                    result = Buffer.concat(buffer,buffer.length).toString('utf-8');
                    //将最后结果返回
                    resolve(result);
                });
            }).on('error',function(err){
                reject(err);
            });
        });
    }
}

  在 wechat.js 添加获取 access_token 的方法 getAccessToken

/**
 * 获取微信 access_token
 */
WeChat.prototype.getAccessToken = function(){
    var that = this;
    return new Promise(function(resolve,reject){
        //获取当前时间 
        var currentTime = new Date().getTime();
        //格式化请求地址
        var url = util.format(that.apiURL.accessTokenApi,that.apiDomain,that.appID,that.appScrect);
        //判断 本地存储的 access_token 是否有效
        if(accessTokenJson.access_token === "" || accessTokenJson.expires_time < currentTime){
            that.requestGet(url).then(function(data){
                var result = JSON.parse(data); 
                if(data.indexOf("errcode") < 0){
                    accessTokenJson.access_token = result.access_token;
                    accessTokenJson.expires_time = new Date().getTime() + (parseInt(result.expires_in) - 200) * 1000;
                    //更新本地存储的
                    fs.writeFile('./wechat/access_token.json',JSON.stringify(accessTokenJson));
                    //将获取后的 access_token 返回
                    resolve(accessTokenJson.access_token);
                }else{
                    //将错误返回
                    resolve(result);
                } 
            });
        }else{
            //将本地存储的 access_token 返回
            resolve(accessTokenJson.access_token);  
        }
    });
}

  在 app.js 中添加新的监听链接用于测试 我们获取的token

//用于请求获取 access_token
app.get('/getAccessToken',function(req,res){
    wechatApp.getAccessToken().then(function(data){
        res.send(data);
    });    
});

获取access_token的效果图

  这样我们就大功告成了!

年轻人恭喜你

  文章源代码:https://github.com/SilenceHVK/wechatByNode 。对文章有不正确之处,请给予纠正。github源代码请顺手给个 Star,最后感谢您的阅读。

【Dev-Environment】MacOS 开发环境搭建

基础设置

  • 关闭 Rootless
1.系统启动进入安全模式(cmd + R)
2.找到实用工具 -> ternimal
输入:csrutil disable (关闭) 或 csrutil enable(启动)
3.重启系统
  • 设置 root 用户
## 创建 root 用户
sudo passwd root 

## 登录 root 用户
su root
  • 显示隐藏文件
defaults write com.apple.finder AppleAllFiles -bool true
## OR
defaults write com.apple.finder AppleShowAllFiles YES

## 重启 finder
  • 解决文件已损坏,打不开的问题
sudo spctl --master-disable
  • 设置 CommandLineTools
## 安装 XCode

## 设置 CommandLineTools 路径
sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

Homebrew

## 安装 Homebrew
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

## 安装:brew install 软件名称
## 卸载:brew uninstall 软件名称
## 已安装的软件目录: brew list
## 更新 brew :brew update
## 清理:brew cleanup
## 检查更新版本:brew outdated
## 升级:brew upgrade

Homebrew Cask

## 安装 Homebrew
brew tap caskroom/cask  // 添加 Github 上的 caskroom/cask 库
brew install brew-cask  // 安装 brew-cask

## 搜索:brew cask search 软件名称
## 安装:brew cask install 软件名称

## 推荐安装软件
brew cask install alfred
brew cask install google-chrome

iTerm2 + zsh

## 安装iTerm2
brew cask install iTerm2 

## 安装 zsh
brew install zsh zsh-completions

## 安装oh-my-zsh
curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | ZSH=~/.oh-my-zsh/ sh

## 创建zsh的配置文件
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

## 设置 zsh 为默认的shell
chsh -s /bin/zsh

## 推荐插件
cd ~/.oh-my-zsh/custom/plugins
## 语法高亮
git clone git://github.com/zsh-users/zsh-syntax-highlighting.git
## 代码自动补全
git clone git://github.com/zsh-users/zsh-autosuggestions

Mac Node.js 环境搭建

## nvm 安装 用于管理 node 版本
## 删除全局 node_modules 目录
sudo rm -rf /usr/local/lib/node_modules 

## 删除 node
sudo rm /usr/local/bin/node 

## 删除全局 node 模块注册的软链
cd  /usr/local/bin && ls -l | grep "../lib/node_modules/" | awk '{print $9}'| xargs rm 

## 安装 nvm
git clone https://github.com/creationix/nvm.git ~/.nvm && cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`


## 安装指定版本,可模糊安装
nvm install <version> 
## 删除已安装的指定版本
nvm uninstall <version> 
## 设置全局默认版本
nvm alias default <version> 
## 当前命令行使用 node 版本
nvm use <version> 
## 列出所有安装的版本
nvm ls 
## 列出所以远程服务器的版本
nvm ls-remote 
## 显示当前的版本
nvm current
## 删除已定义的别名
nvm unalias 
## 在当前版本node环境下,重新全局安装指定版本号的npm包
nvm reinstall-packages <version> 


## cnpm 安装
npm install -g cnpm --registry=https://registry.npm.taobao.org

## node 需要安装 npm 包
npm install -g node-gyp
npm install -g node-pre-gyp

SDKMan 管理




## 安装 sdkman

## 设置默认路径
export SDKMAN_DIR="/usr/local/sdkman" && curl -s "https://get.sdkman.io" | bash

source "$HOME/sdkman/bin/sdkman-init.sh"

## 查看支持的软件
sdk list

## 列出软件版本
sdk list 软件名称

## 安装本地包
sdk install 软件名称 版本号 本地地址

## 选择终端使用版本
sdk use 软件名称 版本号

## 设置默认版本
sdk default 软件名称 版本号

## 查看当前使用的版本
sdk current 软件名称

Mac Java 环境搭建

## maven 安装
brew install maven

## idea 破解
1. 打开IDEA的JVM配置文件,一般会在C:\Users\用户名\.IntelliJIdea2018.1\config下的idea64.exe.vmoptions文件(32位idea.exe.vmoptions),如果找不到可以在IDEA中点击”Configure” -> “Edit Custom VM Options …”自动打开或者自己新建一个

2. 在该文件最后添加一句:-javaagent:{你刚刚下载的补丁的路径} 例如:-javaagent:C:\Users\34862\Downloads\JetbrainsCrack.jar

3. 重启IDEA

4. 在激活对话框中选Activation code 随便输入 点击OK

{
    "licenseId":"ThisCrackLicenseId",//随便填
    "licenseeName":"hvkcoder",//你的名字
    "assigneeName":"hvkcoder",//你的名字
    "assigneeEmail":"[email protected]",//你的邮箱
    "licenseRestriction":"Thanks Rover12421 Crack",//激活信息
    "checkConcurrentUse":false,
    "products":[//各个产品的代码以及过期时间
        {"code":"II","paidUpTo":"2099-12-31"},
        {"code":"DM","paidUpTo":"2099-12-31"},
        {"code":"AC","paidUpTo":"2099-12-31"},
        {"code":"RS0","paidUpTo":"2099-12-31"},
        {"code":"WS","paidUpTo":"2099-12-31"},
        {"code":"DPN","paidUpTo":"2099-12-31"},
        {"code":"RC","paidUpTo":"2099-12-31"},
        {"code":"PS","paidUpTo":"2099-12-31"},
        {"code":"DC","paidUpTo":"2099-12-31"},
        {"code":"RM","paidUpTo":"2099-12-31"},
        {"code":"CL","paidUpTo":"2099-12-31"},
        {"code":"PC","paidUpTo":"2099-12-31"},
        {"code":"DB","paidUpTo":"2099-12-31"},
        {"code":"GO","paidUpTo":"2099-12-31"},
        {"code":"RD","paidUpTo":"2099-12-31"}
    ],
    "hash":"2911276/0",
    "gracePeriodDays":7,
    "autoProlongated":false
}

## idea 配置 spring boot 热更新
1. setting 勾选 Compiler > Build project automatically
2. cmd + shift+ a 勾选 Registry... > compiler.automake.allow.when.app.running

Docker 安装与 Kubernetes 单节点部署 (可选)

## 安装 Docker
brew cask install docker

## Kubernetes 需要的镜像
k8s.gcr.io/kube-proxy-amd64:v1.10.3=registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy-amd64:v1.10.3
k8s.gcr.io/kube-controller-manager-amd64:v1.10.3=registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager-amd64:v1.10.3
k8s.gcr.io/kube-scheduler-amd64:v1.10.3=registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler-amd64:v1.10.3
k8s.gcr.io/kube-apiserver-amd64:v1.10.3=registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver-amd64:v1.10.3
k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64:1.14.8=registry.cn-hangzhou.aliyuncs.com/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.8
k8s.gcr.io/k8s-dns-sidecar-amd64:1.14.8=registry.cn-hangzhou.aliyuncs.com/google_containers/k8s-dns-sidecar-amd64:1.14.8
k8s.gcr.io/k8s-dns-kube-dns-amd64:1.14.8=registry.cn-hangzhou.aliyuncs.com/google_containers/k8s-dns-kube-dns-amd64:1.14.8
k8s.gcr.io/pause-amd64:3.1=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1
k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.0=registry.cn-hangzhou.aliyuncs.com/google_containers/kubernetes-dashboard-amd64:v1.10.0
k8s.gcr.io/etcd-amd64:3.1.12=registry.cn-hangzhou.aliyuncs.com/google_containers/etcd-amd64:3.1.12

批量下载镜像脚本

#/bin/bash

file="images"

if [ -f "$file" ]
then
  echo "$file found."

  while IFS='=' read -r key value
  do
    #echo "${key}=${value}"
    docker pull ${value}
    docker tag ${value} ${key}
    docker rmi ${value}
  done < "$file"

else
  echo "$file not found."
fi

安装 dashboard

kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml

## 访问地址
http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/

安装 helm

brew install kubernetes-helm

helm init --upgrade --tiller-image registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.12.0 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

kubectl create serviceaccount --namespace kube-system tiller

kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

Mac 查看 vmnet8 的网关地址

## 查找 vmnet8 存放的位置
find / -name vmnet8

## 进入 目录,查看 nat.conf 文件
cd /Library/Preferences/VMware\ Fusion/vmnet8

Mac 常用命令

## 查看指定端口号进程
lsof -i tcp:端口号

## 杀死进程
kill pid

## 查看软件安装目录
which softwareName

【Docker】搭建 Docker 私有仓库 harbor

  1. 安装 docker-compose
curl -L https://get.daocloud.io/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose
  1. 安装 docker
yum install -y docker
  1. 配置 daemon.json
cat > /etc/docker/daemon.json >> EOF
{
    "registry-mirrors": ["https://hub-mirror.c.163.com", "https://docker.mirrors.ustc.edu.cn"],
    // 不安全的注册表
    "insecure-registries": ["harbor ip地址"],
    "max-concurrent-downloads": 20
}
EOF
  1. 启动 docker
systemctl daemon-reload
systemctl restart docker
  1. 下载 harbor
wget https://storage.googleapis.com/harbor-releases/release-1.6.0/harbor-online-installer-v1.6.0.tgz

tar -zxvf harbor-online-installer-v1.6.0.tgz
  1. 安装 harbor
  • 修改 harbor.cfg
hostname = // harbor ip 地址
  • 执行安装
source install.sh   --with-clair
  1. 在浏览器中输入 harbor ip 地址,用户名: admin,密码:Harbor12345

harbor

【ECMAScript 6】1.let 和 const 命令

let 命令

  ES6新增了 let 命令,用来声明变量。它的用法类似于 var,但是所声明的变量,只能在 let 命令所在的代码块内有效。

    {
        var a = 10;
        let b = 1;
    }
    console.log(a);//10
    console.log(b);//ReferenceError: b is not defined.

  let 不像 var 那样会发生 “变量提升” 现象。所以,变量一定要在声明后使用,否则会报错。

    console.log(foo);// undefined
    var foo = 2;

    console.log(bar);//ReferenceError: bar is not defined
    let bar = 2;

  ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量从一开始就形成了封闭作用域,这些变量将不受外界影响,凡是在声明前就使用这些变量,就会报错。这种语法我们称为 “暂时性死区(temporal dead zone 简称 TDZ)”

    var tmp = 123;
    if (true) {
        tmp = 'abc'; // ReferenceError: tmp is not defined  
        let tmp;
    }

TDZ 也就意味着 typeof 不再是一个百分百安全的操作。

    console.log(typeof testType); //ReferenceError: testType is not defined
    let testType;

TDZ 的本质就是:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

  let 不允许在同一个作用域中,重复声明相同的变量

    //报错
    function func(parm){
        let parm = 'Hello';//SyntaxError: Identifier 'parm' has already been declared
    };

    //不报错
    function func2(parm){
        {
            let parm = 'Hello';//Hello
        }
    };

块级作用域

  为什么需要块级作用域?

  • ES5 只有全局作用域和函数作用域,没有块级作用域,这样会带来很多不合理的场景

    • 第一种场景,内层变量可能会覆盖外层变量
        var scop_one = new Date();
        function showScopOne(){
            console.log(scop_one);
            if(false){
                var scop_one = 'Hello World';
            }
        };
        showScopOne(); // undefined

    上面代码中,执行 showScopOne 函数输出结果为 undefined,原因在于变量提升,导致内层的 scop_one 变量覆盖了外层的 scop_one 变量。

    • 第二种场景,用来计数的循环变量泄露为全局变量
        var scop_two = 'Hello';
        for(var i = 0; i< scop_two.length; i++);
        console.log(i); //5

    变量 i 只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

  • ES6 let 命令为 javascript 新增了块级作用域

    • 实际上使得获取广泛应用的立即执行匿名函数(IIFE)不再必要了
        //IIFE 写法
        (function(){
            var temp = ....
        })();
    
        //块级作用域写法
        {
            let temp = ....
        };
    • ES6 明确允许在块级作用域中声明函数
        'use strict';
        if(true){
            function f(){};
        };
    • 并且 ES6 规定,块级作用域中,函数声明的语句类似于 let命令,在块级作用之外不可引用
        function foo() { console.log('I am outside!'); }
        (function(){
            if(false){
                function foo(){console.log('I am inside!'); };
            }
            foo(); //I am outside!
        });

const 命令

  const 命令声明了一个只读的常量,一旦声明,常量的值不能改变。这也就意味着,const 一旦声明,就必须立即初始化赋值。

    const PI = 3.1415;
    PI = 3; //TypeError: Assignment to constant variable.

   const 的作用域与 let 命令相同,只在声明所在的块级作用域内有效。

    {
        const SECRET = 'Hello Const';
    };
    console.log(SECRET); //SECRET is not defined

   const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用,同样也不能重复声明。

  const 命令声明的对象是可以更改其属性值的。

    const USER = {
        name:'H_VK'
    }
    USER.password = '123456';
    console.log(USER);// {name: "H_VK", password: "123456"}

这是因为对象是引用类型,const 只针对其引用的内存地址不可更改,而不针对其属性值更改。

小结

  let 用来声明块级变量,当一个变量已经用let声明了,当我们再次用let或var进行声明就会报错。块级{}中的let只在快中有效。在es6 中 var,let可以一次性声明多个变量,对象或数组的格式为多个变量赋值。

  关于常量的声明const,可以一次性声明多个常量,const声明一个只读的常量。一旦声明,常量的值就不能改变。但对于声明的常量是一个字面量的对象时,是可以修改常量的属性值。

Android WebView 不支持 H5 input type="file" 解决方法

  最近因为赶项目进度,因此将本来要用原生控件实现的界面,自己做了H5并嵌入webview中。发现点击H5中 input type="file" 标签 不能打开android资源管理器。

  通过网络搜索发现是因为 android webview 由于考虑安全原因屏蔽了 input type="file" 这个功能 。

  经过不懈的努力,以及google 翻译的帮助 在 stackoverflow 中找到了解决的方法,具体可以理解为 重写webview 的WebChromeClient ,废话不多说直接贴代码:

private ValueCallback<Uri> mUploadMessage;
public ValueCallback<Uri[]> uploadMessage;
public static final int REQUEST_SELECT_FILE = 100;
private final static int FILECHOOSER_RESULTCODE = 2;

webview.setWebChromeClient(new WebChromeClient(){

        // For 3.0+ Devices (Start)
        // onActivityResult attached before constructor
        protected void openFileChooser(ValueCallback uploadMsg, String acceptType)
        {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE);
        }


        // For Lollipop 5.0+ Devices
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)
        {
            if (uploadMessage != null) {
                uploadMessage.onReceiveValue(null);
                uploadMessage = null;
            }

            uploadMessage = filePathCallback;

            Intent intent = fileChooserParams.createIntent();
            try
            {
                startActivityForResult(intent, REQUEST_SELECT_FILE);
            } catch (ActivityNotFoundException e)
            {
                uploadMessage = null;
                Toast.makeText(getBaseContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show();
                return false;
            }
            return true;
        }

        //For Android 4.1 only
        protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
        {
            mUploadMessage = uploadMsg;
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType("image/*");
            startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE);
        }

        protected void openFileChooser(ValueCallback<Uri> uploadMsg)
        {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

    });
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent)
{

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
    {
        if (requestCode == REQUEST_SELECT_FILE)
        {
            if (uploadMessage == null)
                return;
            uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
            uploadMessage = null;
        }
    }
    else if (requestCode == FILECHOOSER_RESULTCODE)
    {
        if (null == mUploadMessage)
            return;
        // Use MainActivity.RESULT_OK if you're implementing WebView inside Fragment
        // Use RESULT_OK only if you're implementing WebView inside an Activity
        Uri result = intent == null || resultCode != MainActivity.RESULT_OK ? null : intent.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    }
    else
        Toast.makeText(getBaseContext(), "Failed to Upload Image", Toast.LENGTH_LONG).show();
}

前端面试题

  1. HTML document 是什么?

答:每个载入浏览器的 HTML 文档都会成为一个 Document 对象,它可以使我们从脚本中对 HTML 页面中的所有元素进行访问。 W3School document 详解

  1. Doctype?严格模式与混杂模式-如何触发这两个模式,区分它们有何意义?

答:Doctype(Document Type,文档类型),它的责任就是告诉浏览器文档使用哪种html或者xhtml规范。
根据 Doctype 是否存在以及使用哪种 DTD 来触发其不同的模式,如果在文档开始处没有发现文档类型声明,浏览器则会默认开启混杂模式。如果不使用某些 hack 技术,跨浏览器的行为根本就没有一致性可言

  • HTML 4.01 严格类型
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
    "http://www.w3.org/TR/html4/strict.dtd">
  • XHTML 1.0 严格模式
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  1. CSS 引入的方式有哪些? link 和 @import 的区别是什么?

答:有 4 种方式可以在 HTML 中引入 CSS:

  1. 内联方式:<div style="width:400px"></div>
  2. 嵌入方式:<style></style>
  3. 链接方式:<link href="" rel="stylesheet"/>
  4. 导入方式:```<style> @import url(style.css) </style>````

@import<link/> 的区别:

  1. <link/> 是 XHTML 标签,出了加载 CSS 外,还可以定义 RSS 等其他业务;@import 属于 CSS 范畴,只能用于加载 CSS。
  2. <link/> 加载 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入后再加载。
  3. <link/> 是 XHTML 标签,无兼容问题;@import 是 CSS 2.1 提出的,低版本浏览器不支持。
  4. <link/> 支持 JavaScript 控制 DOM 改变样式; @import 不支持。

Node.js 微信消息管理

一、写在前面的话

  当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应。

  消息推送也是微信公众号开发更为有趣的功能,涉及到文本消息、图片消息、语音消息、视频消息、音乐消息以及图文消息。并且最为有趣的功能当属消息加解密了,当然由于篇文章篇幅的原因我会在下一篇文章中去着重说明。

我们接着来,微信消息管理

二、微信消息管理

1.捕获消息信息

  在文章的第一句话中,为我们指明了微信消息产生的请求方式为 POST,因此首先我们就去对 Node.js 的 Post 请求进行监听。

  在我们的 app.js 文件中添加一个POST监听,并将获取的结果输出:

//用于处理所有进入 3000 端口 post 的连接请求
app.post('/',function(req,res){
    var buffer = [];
    //监听 data 事件 用于接收数据
    req.on('data',function(data){
        buffer.push(data);
    });
    //监听 end 事件 用于处理接收完成的数据
    req.on('end',function(){
    //输出接收完成的数据   
         console.log(Buffer.concat(buffer).toString('utf-8'));
    });
});

  随后将 Node.js 启动后映射至外网,关注我们的微信公众号,在控制台中则会看到:
输出结果

  打开 微信帮助文档 ,点击左侧菜单的消息管理,选择其子菜单 接收事件推送,如图:
消息管理 -  接收事件推送

微信接收事件推送

  从上图我们不难看出,微信 接收事件推送 确实很多,而我们最终目标是要实现,在用户触发事件时返回其相应的回复消息。因此我们总结一下我们要实现的步骤:

  1. 解析 XML ,使用 Event 参数判断事件类型
  2. 返回相应的事件信息

总结完实现步骤后,我们就开始动手实现第一个被动回复消息吧。

别拦我,让我去装逼

2.以关注事件为例,实现第一个被动回复

  解析 XML 我这里使用了 第三方的包 xml2js(npm install xml2js ),并在 wechat.js 中引入。

 parseString = require('xml2js').parseString;//引入xml2js包

  为 WeChat 对象添加一个消息处理的方法 handleMsg,将 app.js 中捕获 POST 实现的写入在其代码块中,并使用 xml2js 解析,代码如下

/**
 * 微信消息
 */
WeChat.prototype.handleMsg = function(req,res){
    var buffer = [];
    //监听 data 事件 用于接收数据
    req.on('data',function(data){
        buffer.push(data);
    });
    //监听 end 事件 用于处理接收完成的数据
    req.on('end',function(){
        var msgXml = Buffer.concat(buffer).toString('utf-8');
        //解析xml
        parseString(msgXml,{explicitArray : false},function(err,result){
            if(!err){
                //打印解析结果
                console.log(result);
            }else{
                 //打印错误信息
                console.log(err);
            }
        })
    });
}

  在 app.js 中调用 handleMsg 方法

//用于处理所有进入 3000 端口 post 的连接请求
app.post('/',function(req,res){
    wechatApp.handleMsg(req,res);
});

  完成了代码的编写后,将公众号重新关注
运行结果

最后打印为一个 JSON 格式的结果,也就是预示着我们第1步工作已经完成。下面开始我们的第2步,微信被动回复。

  在文章的第一句话的后边提到 开发者可以在响应包(Get)中返回特定XML结构,那么这个特定的 XML 结构在哪呢?再次打开微信帮助文档 ,点击左侧菜单的消息管理,选择其子菜单 被动回复消息,如图:
消息管理 - 被动回复消息

  直接来到 回复文本消息:
回复文本消息

  拿到回复文本消息格式后,我们就来为关注我们公众号的同学打声招呼吧。在 wechat 文件中 创建 msg.js 文件用于消息的管理。
这里写图片描述

并在 msg.js 中添加处理文本消息的接口,并在 wechat.js 中引用

'use strict' //设置为严格模式

//回复文本消息
exports.txtMsg = function(toUser,fromUser,content){
    var xmlContent =  "<xml><ToUserName><![CDATA["+ toUser +"]]></ToUserName>";
        xmlContent += "<FromUserName><![CDATA["+ fromUser +"]]></FromUserName>";
        xmlContent += "<CreateTime>"+ new Date().getTime() +"</CreateTime>";
        xmlContent += "<MsgType><![CDATA[text]]></MsgType>";
        xmlContent += "<Content><![CDATA["+ content +"]]></Content></xml>";
    return xmlContent;
}

修改 wechat.js 中 handleMsg 方法

/**
 * 微信消息
 */
WeChat.prototype.handleMsg = function(req,res){
    var buffer = [];
    //监听 data 事件 用于接收数据
    req.on('data',function(data){
        buffer.push(data);
    });
    //监听 end 事件 用于处理接收完成的数据
    req.on('end',function(){
        var msgXml = Buffer.concat(buffer).toString('utf-8');
        //解析xml
        parseString(msgXml,{explicitArray : false},function(err,result){
            if(!err){
                   result = result.xml;
                   var toUser = result.ToUserName; //接收方微信
                   var fromUser = result.FromUserName;//发送仿微信
                   //判断事件类型
                   switch(result.Event.toLowerCase()){
                      case 'subscribe':
                             //回复消息
                             res.send(msg.txtMsg(fromUser,toUser,'欢迎关注 hvkcoder 公众号,一起斗图吧'));
                             break;
                   }
            }else{
                 //打印错误信息
                console.log(err);
            }
        })
    });
}

实现结果

   没错就是这么简单。这里有个逻辑是这样的 toUser 表示接收方,也就是咱们的微信公众号;fromUser 表示发送方,也就是触发事件的用户。而我们要回复用户时,此时 接收方 就是 触发事件的用户,而发送方则是 我们的微信公众号。这块比较绕,大家可以慢慢去理解。

  由于我们还没有对微信的素材管理进行讲解,这里我们暂时跳过 图片消息、语音消息、视频消息、以及音乐消息。直接实现图文消息的推送。

3.图文消息
这里写图片描述
  在 msg.js 文件中添加图文XML模板

//回复图文消息
exports.graphicMsg = function(toUser,fromUser,contentArr){
     var xmlContent =  "<xml><ToUserName><![CDATA["+ toUser +"]]></ToUserName>";
        xmlContent += "<FromUserName><![CDATA["+ fromUser +"]]></FromUserName>";
        xmlContent += "<CreateTime>"+ new Date().getTime() +"</CreateTime>";
        xmlContent += "<MsgType><![CDATA[news]]></MsgType>";
        xmlContent += "<ArticleCount>"+contentArr.length+"</ArticleCount>";
        xmlContent += "<Articles>";
        contentArr.map(function(item,index){
            xmlContent+="<item>";
            xmlContent+="<Title><![CDATA["+ item.Title +"]]></Title>";
            xmlContent+="<Description><![CDATA["+ item.Description +"]]></Description>";
            xmlContent+="<PicUrl><![CDATA["+ item.PicUrl +"]]></PicUrl>";
            xmlContent+="<Url><![CDATA["+ item.Url +"]]></Url>";
            xmlContent+="</item>";
        });
        xmlContent += "</Articles></xml>";
    return xmlContent;
}
}

  更改 wechat.js 文件中的 handleMsg 方法,将图消息推送响应在点击事件中

  case 'click':
                                var contentArr = [
                                    {Title:"Node.js 微信自定义菜单",Description:"使用Node.js实现自定义微信菜单",PicUrl:"http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72868520"},
                                    {Title:"Node.js access_token的获取、存储及更新",Description:"Node.js access_token的获取、存储及更新",PicUrl:"http://img.blog.csdn.net/20170528151333883?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72783631"},
                                    {Title:"Node.js 接入微信公众平台开发",Description:"Node.js 接入微信公众平台开发",PicUrl:"http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72765279"}
                                ];
                               //回复图文消息
                               res.send(msg.graphicMsg(fromUser,toUser,contentArr));
                            break;

  点击菜单下的 今日推荐
今日推荐

  图文推送就是这么简单的被我们给实现了。

4.接收普通消息

  微信除了为我们接收事件推送外,千万不要忘了微信还能通过发送文字。而这一节我们也就来玩玩微信接收普通消息。

  打开 微信帮助文档 ,点击左侧菜单的消息管理,选择其子菜单 接收普通消息,如图:
消息管理 - 接收普通消息

  依然如接收事件推送的套路,不同的是参数发生了改变,但这并步影响我们的开发,只需要几步就能够完美的解决。更改 wechat.js 文件 handleMsg方法,这里我先暂时只针对用户输入的文本消息做处理,其他的跟其类似。

//判断消息类型
                   if(result.MsgType.toLowerCase() === "event"){
                        //判断事件类型
                        switch(result.Event.toLowerCase()){
                            case 'subscribe':
                                    //回复消息
                                    var content = "欢迎关注 hvkcoder 公众号,一起斗图吧。回复以下数字:\n";
                                        content += "1.你是谁\n";
                                        content += "2.关于Node.js\n";
                                        content += "回复 “文章”  可以得到图文推送哦~\n";
                                    res.send(msg.txtMsg(fromUser,toUser,''));
                                    break;
                            case 'click':
                                        var contentArr = [
                                            {Title:"Node.js 微信自定义菜单",Description:"使用Node.js实现自定义微信菜单",PicUrl:"http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72868520"},
                                            {Title:"Node.js access_token的获取、存储及更新",Description:"Node.js access_token的获取、存储及更新",PicUrl:"http://img.blog.csdn.net/20170528151333883?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72783631"},
                                            {Title:"Node.js 接入微信公众平台开发",Description:"Node.js 接入微信公众平台开发",PicUrl:"http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72765279"}
                                        ];
                                    //回复图文消息
                                    res.send(msg.graphicMsg(fromUser,toUser,contentArr));
                                    break;
                        }
                   }else{
                       //判断消息类型为 文本消息
                       if(result.MsgType.toLowerCase() === "text"){
                           //根据消息内容返回消息信息
                           switch(result.Content){
                               case '1':
                                        res.send(msg.txtMsg(fromUser,toUser,'Hello !我的英文名字叫 H-VK'));
                                    break;
                               case '2':
                                        res.send(msg.txtMsg(fromUser,toUser,'Node.js是一个开放源代码、跨平台的JavaScript语言运行环境,采用Google开发的V8运行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于数据密集的事实应用程序'));
                                    break;
                               case '文章':
                                      var contentArr = [
                                            {Title:"Node.js 微信自定义菜单",Description:"使用Node.js实现自定义微信菜单",PicUrl:"http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72868520"},
                                            {Title:"Node.js access_token的获取、存储及更新",Description:"Node.js access_token的获取、存储及更新",PicUrl:"http://img.blog.csdn.net/20170528151333883?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72783631"},
                                            {Title:"Node.js 接入微信公众平台开发",Description:"Node.js 接入微信公众平台开发",PicUrl:"http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",Url:"http://blog.csdn.net/hvkcoder/article/details/72765279"}
                                        ];
                                        //回复图文消息
                                        res.send(msg.graphicMsg(fromUser,toUser,contentArr));
                                    break;
                                default :
                                         res.send(msg.txtMsg(fromUser,toUser,'没有这个选项哦'));
                                    break;
                           }
                       }
                   }

微信消息 文本消息处理

微信消息 文本消息处理

测试微信公众号

  OK !至此我们就完成了微信消息管理的讲解,似乎真的没有什么难度。预留了一章,主要想要去细说一下说消息加解密,因为在网上涉及 Node.js 微信消息加解密的文章确实很少,微信帮助文档给的案例也没有 Node.js 的详细说明。

  最后文章代码部分,由于网上编辑器的代码换行做的不是很好可能有些乱,建议可以去我的 github 上查看源码。

  文章源代码:https://github.com/SilenceHVK/wechatByNode 。对文章有不正确之处,请给予纠正。github源代码请顺手给个 Star,最后感谢您的阅读。

CSS 浏览器兼容问题

IE6 下双边距问题

  问题描述:在 IE6 下,块元素有浮动和横向的 margin,横向的 margin 值会被放大成两倍。

  解决方法:可以将块级元素设置为内嵌元素, display:inline

IE6,7 下的 li 间隙问题

  问题描述:在 IE6,7 下 li 本身没有浮动,但是内容浮动了就会多出几px间隙。

  解决方法:

  1. li 添加浮动;
  2. li 添加 vertival-align 样式;

IE6 下的最小高度问题

  问题描述:在 IE6 下高度小于 19px 的元素,高度会被当做 19px 处理。

  解决方法:通过给元素添加 overflow:hidden 样式。

在 IE6 下父级的 overflow:hidden;包不住子级的 relative;

  解决方法: 将父级设置定位属性;

在 IE6 下定位元素的父级宽高都是奇数那么在 IE6 下定位元素的 right 和 bottom 都有 1 像素的偏差;

  解决方法:尽量规避父级宽高值不为奇数;

在 IE 6 下内容会撑开设置好的宽高

  解决方法:计算精确,不要让内容宽高超出设置好的父元素宽高

【ECMAScript 6】4.Symbol 的使用

Symbol 的概念

  Symbol 是 ES6 提供的一个新的基本数据类型,表示独一无二的值。

  在实际开发过程中,我们可能使用到别人提供的对象,但又想要为这个对象添加新的属性,那么这个新的属性就有可能与现有属性冲突。如果有一种机制,可以从根本根本上防止属性名冲突就好了,这也就是 Symbol 出现的原因。

Symbol 的使用

  Symbol 值通过 Symbol 函数生成。因为 Symbol 是 ES6 提供的一种新的 数据类型,因此使用 Symbol 时,前面不能加 new,它基本上类似于字符串的数据类型。

    const s1 = Symbol('foo');
    console.log(s1); // Symbol(foo)
  • Symbol 函数的参数只是表示当前 Symbol 值的描述,因此相同的 Symbol 函数的返回值不相等。
    const s1 = Symbol('foo');
    const s2 = Symbol('foo');
    console.log(s1 === s2); // false
  • Symbol 值不能与其它类型的值进行运算,会报错。
  • Symbol 可以显示的转换字符串类型、布尔类型,但不能转为数值类型。
    let s1 = Symbol(0);
    console.log(s1.toString()); // 'Symbol(My symbol)'
    console.log(Boolean(s1)); // true

Symbol 作为属性名

  由于每一个 Symbol 的值都是不相等的,因此 Symbol 可以作为标识符。

    const mySymbol = Symbol();
    
    // 第一种写法
    const a = {};
    a[mySymbol] = 'Hello!';
    
    // 第二种写法
    const a = {
        [mySymbol]: 'Hello!'
    };
    
    // 第三种写法
    const a = {};
    Object.defineProperty(a,mySymbol,'Hello!');
    
    // 以上的写法都得到同样的结果
    console.log(a[mySymbol]); // "Hello!"

Symbol 作为对象的属性名时,不能使用点运算符。

在对象内部,使用 Symbol 定义属性时, Symbol 值必须放在方括号之中。

    const mySymbol = Symbol();
    
    const obj = {
        [mySymbol] : function(arg){
            console.log(arg);
        }
    };

    obj[mySymbol]('Hello'); // Hello

Symbol 作为对象属性时,该属性为公开属性,不是私有属性。

遍历属性名

  Symbol 作为属性名时,该属性不会出现在 for...infor...of 循环中,也不会在 Object.keys()Object.getOwnPropertyNames()Json.stringify()返回。但可以通过下面的 API 获取 指定对象的所有 Symbol 属性名。

  • Object.getOwnPropertySymbols 方法返回一个数组,成员为当前对象的所有用作属性名的 Symbol
    const obj = {
        [Symbol('name')]: 'hvkcoder',
        [Symbol('age')]: 18
    };
    const keys = Object.getOwnPropertySymbols(obj);
    console.log(keys); // [ Symbol(name), Symbol(age) ]
  • Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和 Symbol 键名
    const obj = {
        [Symbol('name')]: 'hvkcoder',
        [Symbol('age')]: 18,
        job: 'coder'
    };
    const keys = Reflect.ownKeys(obj);
    console.log(keys); // [ 'job', Symbol(name), Symbol(age) ]

Symbol.for()、Symbol.keyFor()

  • Symbol.for

  有时我们可能需要重新使用同一个 Symbol 的值,Symbol.for 接受一个字符串作为参数,然后通过该参数去查找是否存在以该参数作为名称的 Symbol 值,如果存在则就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

    const s1 = Symbol.for('foo');
    const s2 = Symbol.for('foo');
    
    console.log(s1 === s2); // true

Symbol.for() 会被登记在全局环境中供搜索,因此可以在 iframeserver worker 中获取到同一个值。

  • Symbol.keyFor

  Symbol.keyFor() 会返回已登记的 Symbol 类型值的key。

    const s1 = Symbol('foo');
    const key = Symbol.keyFor(s1);
    console.log(key); // foo

内置的 Symbol 值

  • Symbol.hasInstance

  对象的 Symbol.hasInstance 属性,指向一个内部方法。当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。

    class MyClass {
        [Symbol.hasInstance](foo) {
            return foo instanceof Array;
        }
    }
    console.log([1, 2, 3] instanceof new MyClass()); // true
  • Symbol.isConcatSpreadable

  对象的 Symbol.isConcatSpreadable 属性等于一个布尔值,表示该对象用于 Array.prototype.concat() 时,是否可以展开。

    const obj = {length: 2, 0: 'c', 1: 'd'};
    ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']

    obj[Symbol.isConcatSpreadable] = true;
    ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
  • Symbol.species

  对象的 Symbol.species 指向一个构造函数。创建衍生对象时,会使用该属性。

    class Test extends Array {
        constructor(args) {
            super(args);
        };
        static get [Symbol.species]() { return Array; }
    }
    const t1 = new Test();
    const t2 = t1.map(x => x);
    console.log(t1 instanceof Test); // true
    console.log(t2 instanceof Test); // false

  Symbol.species 的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。

  • Symbol.match

  对象的 Symbol.match 属性,指向一个函数。当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。

    class MyMatcher {
        [Symbol.match](string) {
            return 'hello world'.indexOf(string);
        }
    }
    console.log('e'.indexOf(new MyMatcher())); // -1
  • Symbol.replace

  对象的 Symbol.replace 属性,指向一个方法,当该对象呗 String.prototype.replace 方法调用时,会返回该方法的返回值。

    const x = {};
    x[Symbol.replace] = (...arg) => console.log(arg);
    
    'Hello'.replace(x,'World'); // ["Hello","World"]

   Symbol.replace 方法会收到两个参数,第一个参数是 replace 方法正在作用的对象,第二个参数是替换后的值。

  • Symbol.search

  对象的 Symbol.search 属性,指向一个方法,当该对象呗 String.prototype.search 方法调用时,会返回该方法的返回值。

    class MySearch{
        constructor(value){
            this.value = value;
        }
        [Symbol.search](string){
            return string.indexOf(this.value);
        }
    }
    'foobar'.search(new MySearch('foo')); // 0
  • Symbol.split

  对象的 Symbol.split 属性,指向一个方法,当该对象呗 String.prototype.split 方法调用时,会返回该方法的返回值。

    class MySplitter{
        constructor(value){
            this.value = value;
        }
        [Symbol.split](string){
            const index = string.indexOf(this.value);
            if(index === -1){
                return string;
            }
            return [
                string.substr(0, index),
                string.substr(index + this.value.length)
            ];
        }
    }
    
    'foobar'.split(new MySplitter('foo')); // ['', 'bar']
  • Symbol.iterator

  对象的 Symbol.iterator 属性,指向该对象的默认遍历器方法

    class Collection {
        *[Symbol.iterator]() {
            let i = 0;
            while (this[i] !== undefined) {
                yield this[i];
                ++i;
            }
        }
    };

    const myCollection = new Collection();
    myCollection[0] = 1;
    myCollection[1] = 2;
    
    for(let value of myCollection){
        console.log(value); // 1 2
    }
  • Symbol.toPrimitive

  对象的 Symbol.toPrimitive 属性,指向一个方法。该对象被转为原始属性时,会调用这个方法,返回该对象对应的原始属性。

Symbol.toPrimitive 被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式:

  1. Number:该场合需要转成数值
  2. String:该场合需要转成字符串
  3. Default:该场合可以转成数值,也可以转成字符串
  • Symbol.toStringTag

  对象的 Symbol.toStringTag 属性,指向一个方法。在该对象上面调用 Object.prototype.toString 方法时,如果这个属性存在,它的返回值会出现在 toString 方法返回的字符串之中,表示对象的类型。

  • Symbol.unscopables

  对象的 Symbol.unscopables 属性,指向一个对象。该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。

【Golang 基础】Go 语言的控制语句

Go 语言的控制语句

判断语句 if-else,支持初始化表达式;

package basic

import "fmt"

func main(){
	if num := 0; num == 0{
		fmt.Println("Zero")
	}else if num == 1 {
		fmt.Println("One")
	}else {
		fmt.Println("Other")
	}
}

循环语句 for,有 3 种形式

  • do-while 形式;
package basic

func DoWhile(){
	a := 1
	
	for{
		a++
		if a > 3 {
			break
		}
	}
}
  • while 形式;
package basic
    
func While(){
    a := 1
    
    for a < 3 {
    	a++
    }
}
  • for 形式;
package basic
         
func For(){
   for i := 0; i < 3; i++ {  }  
}

  使用 for + if 实现选择排序;

package basic

import "fmt"

func Selection(){
	array := []int{ 10, 25, 1, 6, 2, 5 }
	
	length := len(array)
	
	for i := 0; i < length; i++ {
		min := i
		for j := i + 1; j < length; j++ {
			if array[min] > array[j] {
				min = j
			}
		}
		
		if min != i {
			array[i], array[min] = array[min], array[i]
		}
	}
	
	fmt.Println(array)
}

控制语句 switch

   switch 支持任何类型或表达式作为条件语句,不需要写 break,条件成立自动终止;需要接着执行下一个 case,使用 fallthrough 语句。

package basic

import "fmt"

func Switch(){
	mark := 60
	
	// 不加条件判断
	switch mark {
	 case 90 :
	 	fmt.Println("A")
	 case 80:
	 	fmt.Println("B")
	 case 70, 60:
	 	fmt.Println("C")
	 default:
	    fmt.Println("D")
	}
	
	// 加入条件判断
	switch {
	 case mark >= 90:
	    fmt.Println("A")
	 case mark < 90 && mark >= 80:
	 	fmt.Println("B")
	 case mark < 80 && mark >= 60:
	 	fmt.Println("C")
	 default:
	    fmt.Println("D")
	}
}

goto

  goto 语句可以无条件地转移到当前函数内定义的标签,通常与条件语句配合使用。但是,在结构化程序设计中一般不主张使用 goto,以免造成程序流程的混乱。

package basic

import "fmt"

func GoTo(){
	var a int = 10
	LOOP:
		for a < 20 {
			if a == 15 {
				a = a + 1
				goto LOOP
			}
			fmt.Printf("a的值为 : %d\n", a)
			a++
	}
}

【Docker】容器自启动

restart policy (重启策略)

  Docker 提供了 restart policy 机制(重启策略),可以在容器或者 Docker 重启时控制器能够自启动。这种重启策略可以保证相关容器按照正确顺序启动。Docker 建议使用重启策略,并避免使用流程管理器启动容器。

  重启策略跟 dockerd 命令的 --live-restore 标志不同。使用 --live-restore 标志可以在 Docker 升级的时候保证容器继续运行,但是网络以及用户终端输入会被终端。

使用重启策略

  要为容器配置重启策略,使用 docker run 命令的时候添加 --restart 标志。--restart 标志有多个 value 可选

标志 描述
no 不自动重启容器(默认值)
on-failure 如果容器由于错误而退出,则将其重新启动,非零退出代码表示错误
unless-stopped 在容器已经stop掉或Docker stoped/restarted的时候才重启容器
always 只要容器停止,就重新启动

重启策略详情

  • 重启策略只在容器启动成功后生效。这种情况下,成功启动的意思容器至少运行 10秒以上,并且 Docker 已经开始监控它。这可以避免没有成功启动的容器陷入 restart 的死循环。
  • 如果手动的停止容器,它将被重启策略忽略,直到 Docker 守护进程重启或手动重启,这是为了避免重启循环的另一次尝试。
  • 重启策略只能用于容器,与 swarm 服务 的重启策略有不同的配置。

CSS 文本描边效果

text-shadow 实现文本描边

h1{
    text-shadow:
        -.025em -.025em 0 #444,
        .025em -.025em 0 #444,
        -.025em  .025em 0 #444,
        .025em  .025em 0 #444;
}

text-stroke 实现文本描边

  • text-stroke 是 ext-stroke-width 和 text-stroke-color 的两个属性简写写法
text-stroke:<width> <color>
  • text-stroke 属性常常与 text-fill-color (文本填充颜色) 一起使用
h1{
    -webkit-text-fill-color:transparent;
    -webkit-text-stroke:6px #f36;
}

实现渐变文本描边

h1{
    background:-linear-gradient(-86deg, #EEF85B 5%, #7AEC8D 53%, #09E5C3 91%);
    -webkit-background-clip:text;
    -webkit-text-fill-color:#fff;
    -webkit-text-stroke:6px transparent;
}

使用 SVG 实现描边

<svg width="100%" height="300">
    <text class="text" x="100" y="150">H_VK</text>
</svg>
.text{
    fill:transparent;
    stroke-width:90px;
    stroke:#096;
}

SVG 动画描边

<svg viewBox="0 0 1320 300">
    <symbol id="s-text">
        <text text-anchor="middle"  x="50%" y="50%" dy=".35em">
            H_VK
        </text>
    </symbol>
    <use xlink:href="#s-text" class="text"></use>
        <use xlink:href="#s-text" class="text"></use>
        <use xlink:href="#s-text" class="text"></use>
        <use xlink:href="#s-text" class="text"></use>
        <use xlink:href="#s-text" class="text"></use>
</svg>
.text {
    fill: none;
    stroke-width: 6;
    stroke-linejoin: round;
    stroke-dasharray: 70 330;
    stroke-dashoffset: 0;
    animation: stroke 6s infinite linear;
}

.text:nth-child(5n + 1) {
    stroke: #F2385A;
    animation-delay: -1.2s;
}
.text:nth-child(5n + 2) {
    stroke: #F5A503;
    animation-delay: -2.4s;
}

.text:nth-child(5n + 3) {
    stroke: #E9F1DF;
    animation-delay: -3.6s;
}

.text:nth-child(5n + 4) {
    stroke: #56D9CD;
    animation-delay: -4.8s;
}

.text:nth-child(5n + 5) {
    stroke: #3AA1BF;
    animation-delay: -6s;
}

@keyframes stroke {
    100% {
        stroke-dashoffset: -400;
    }
}

/* Other styles */
html, body {
    height: 100%;
}

body {
    background: #212121;
    background-size: .2em 100%;
    font: 14.5em/1 Open Sans, Impact;
    text-transform: uppercase;
    margin: 0;
}

svg {
    position: absolute;
    width: 100%;
    height: 100%;
}

实现 Canvas 实现描边

<canvas id="canvas"></canvas>
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d");
canvas.width = canvas.height = 800;
context.lineWidth = 6;
context.font = '8em/1 Bangers, sans-serif';
context.strokeStyle = '#f36';
context.strokeText("Hello H_VK",0, canvas.height / 2);

【ECMAScript 6】2.Destructuring (解构)

数组基本用法

  ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为 解构(Destructuring)

    var [a, b, c] = [1, 2, 3]; 
    console.log(a, b, c); // 1 2 3

以上代码表示,可以从数组中直接提取值,按照对应的位置,对应变量赋值。只要两边等号的模式相同,左边的变量就会被赋予对应的值,我们称为模式匹配。如果解构不成功,变量的值就等于 undefind

  等号左边模式,只要匹配一部分的等号右边的数组。这种情况下,解构依然成功,我们称为不完全解构

    var [x, y] = [1, 2, 3];
    console.log(x, y); // 1 2

  如果等号右边不是数组(或者严格地说,不是可遍历的结构)。这种情况,结构将会报错

    var [foo] = 1;

  结构赋值不仅适用于 var 命令,也适用于 let 和 const 命令

    let [x ,...y] = ['Hello' ,'ES6'];
    console.log(x, y); // Hello ['ES6']

    const [APPID,SECRET] = ['Destructuring','ESCMAScript6'];
    console.log(APPID, SECRET); // Destructuring ESCMAScript6

  对于 Set 结构,也可以使用数组解构赋值

    let [x, y, z] = new Set(['Hello' , 'ESCMAScript6','World']);
    console.log(x, y, z); // Hello ESCMAScript6 World

  事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式写解构赋值,如 Generator函数 :

    function* fibs(){
        let a = 0;
        let b = 1;
        while(true){
            yield a;
            [a, b] = [b , a + b];
        }
    }

    var [first, second, third, fourth, fifth, sixth] = fibs();
    console.log(sixth); // 5

默认值

  解构赋值允许指定默认值

    var [defaultValue = 10] = [];

  ES6 内部使用严格相等运算符( === ),判断一个位置是否有值。如果一个数组成员不严格等于 undefined,默认值是不会生效的

    var [defaultValue = 10] = [null];
    console.log(defaultValue); // null

  如果默认值是一个表达式,那么这个表达式是惰性求值,即只有在用到的时候,才会求值

    var f = function(){
        return 20;
    }
    //没有用到 表达式
    var [defaultValue3 = f()] =[1];
    console.log(defaultValue3); // 1

    //用到 表达式
    var [defaultValue4 = f()] =[];
    console.log(defaultValue4); // 20

  默认值可以引用解构赋值的其他变量,但是这个变量必须已经声明

    var [defaultValue5 = 1, defaultValue6 = defaultValue5] = [];
    console.log(defaultValue5,defaultValue6);// 1 1

对象的解构赋值

  解构不仅可以用于数组,还可以用于对象

    var {foo , bar} = {foo : 'aaa', bar : 'bbb' };
    console.log(foo, bar);// aaa bbb

对象解构与数组解构不同,数值解构是按照数组次序进行取值;对象解构是按照属性名取值,即变量名与属性名相同。

  如果变量名与属性名不相同时,必须写成以下方式

    var {foo : baz} = { foo :'Hello'};
    console.log(baz);// Hello

对象的解构赋值的内部机制,是先找到同名的属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

  变量的声明与赋值是一体的。对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。

    let foo;
    let {foo} = {foo:1}; // SyntaxError: Duplicate declaration "foo"

  解构也可以用于嵌套解构的对象

    var obj = {
        p:[
            "Hello",
            { world : "World" }
        ]
    };
    var { p :[hello,{world}]} = obj;
    console.log(hello,world); // Hello World

这时 p 是模式,不是变量,因此不会被赋值

字符串的解构赋值

  字符串也可以解构赋值。这是因为此时字符串被转换成了一个类似数组的对象

    const [H, E, L, L2, O] = 'hello';
    console.log(H,E,L,L2,O);// h e l l o

  类似数组的对象都有一个 length 属性,因此还可以对这个属性解构赋值

    let { length } = 'hello';
    console.log(length); // 5

用途

  1. 交换变量的值
    [x, y] = [y, x];
  1. 从函数返回多个值
    var example = function(){
        return ['hvkcoder', 18, 'coder'];
    };
    var [name, age, job] = example();
  1. 函数参数的定义
    //有次序的值
    function func([firstParm,secondParm]){
        console.log(firstParm,secondParm); // Hello World
    }
    func(['Hello','World']);

    //无次序的值
    function funcNo({userName,passWorld}){
        console.log(userName,passWorld);    // hvkcoder 1234
    }
    funcNo({userName:'hvkcoder',passWorld:'1234'});
  1. 提取 JSON 值,解构赋值对提取 JSON 对象中的数据,尤其有用
    var jsonData = {
        id: 1,
        data: [1,2]
    };
    let { id,data} = jsonData;// 1 [ 1, 2 ]
  1. 遍历Map结构
    var map = new Map();
    map.set('first','hello');
    map.set('second','world');
    for(let [key, value] of map){
        console.log(key + ':' + value); // first : hello second : world
    };
    //获取键
    for(let [key] of map){};

    //获取值
    for(let [,value] of map){}

小结

  利用var,let结合数组解构,可以一次声明多个变量。特别是json数据的提取和变量的转换上,以及函数的默认参数传递上可以给默认值。解构赋值允许指定默认的值,有效的解决了函数返回多个变量的赋值问题,同时也解决了函数参数默认值的问题。

【读书笔记】《高性能网站建设指南》阅读

1. 减少 HTTP 请求

  性能黄金法则:

只有 10% ~ 20% 的最终用户响应时间接收所请求的 HTML 文档上,剩下的 80% ~ 90% 时间花在 HTML 文档中引入的所有组件进行的 HTTP 请求上。

由此可见,改善响应时间最简单的途径就是,减少引用组件的数量,从而减少 HTTP 请求的数量。

  • 地图图片

  图片地图允许在一张图片中带有多个 URL,目标 URL 的选择取决于用户点击图片的位置。

  图片地图有两种:

  1. 服务端地图图片:将所有点击都请求在同一个 URL,向其传递 x,y 参数 ,通过后台对 x, y 判断其跳转位置;
  2. 客户端地图图片:将用户的点击映射到一个操作,无需向后台发送请求
    <img src="图片地址">
    <map name="map1">
        <area shap="rect (形状)" coords="0,0,13,13 (坐标)" href="跳转地址" title="标题">
    </map>

缺点:通过 DHTML 创建图片地图,不兼容 IE; 需要手动定位菜单在图片中的位置;图形只支持 rect

  • CSS Sprites (雪碧图)

  和地图图片一样,CSS Sprites 也可以合并图片,但是更为灵活。
通过样式对其操作:

    <style>
        .icon{
            background-img: url('./图片地址');
        }
        .pic-1{
            background-position:0 0; // 图标 在雪碧图中的位置 x, y
        }
    </style>
    
    <i class="icon pic-1"></i>
  • 内联图片

  可以通过 data:<mediatype>[;base64]<data> 的格式在页面中包含图片,无需额外的 HTTP 请求;

  可用在任何需要指定 URL 的地方,比如 <script><a> 元素上;缺点:不兼容 IE,可能存在数据大小的限制。

  • 合并脚本和样式

2. 使用内容发布网络

  内容发布网络(Content Delivery Networks, CDN)是一组分布在多个不同地理位置的 Web 服务器,用于更加有效地向用户发布内容。

  除了缩短响应时间外,CDN 还可以包括备份、扩展存储能力和进行缓存。

  CDN 用于发布静态内容,如:图片、脚本、样式表和 Flash 等。

3. 添加 Expires 头

  浏览器(和代理)使用缓存来减小 HTTP 响应的大小,使 Web 页面加载得更快。Web 服务器使用 Expires 头来告诉 Web 客户端它可以使用一个组件的当前副本,直到指定的时间为止。

  HTTP 规范中简要地称该头为 “在这一日期/时间之后,响应将被认为是无效的”。

  HTTP 1.1 引入了 Cache-Control 头来克服 Expires 头的限制,因为 Expires 头使用一个特定的时间,它要求服务器和客户端的时钟严格同步。另外,过期时间需要经常检查,并且一旦未来这一天的到来,还需要在服务器配置中提供一个新的时间。

  Cache-Control 使用 max-age 指令制定组件被缓存多久。它以秒为单位定义了一个更新窗。如果从组件被请求开始过去的秒数少于 max-age,浏览器就是用缓存的版本,这就避免了额外的 HTTP 请求。

  Cache-Control max-age 与 Expires 两者可同时出现,HTTP 规范规定 max-age 指令将重写 Expires 头。

4. 压缩组件

5. 将样式表放在顶部

  将样式表放在文档底部会导致在浏览器中阻止内容逐步呈现。为避免当样式变换时重绘页面中地元素,浏览器会阻塞内容逐步呈现。

  在浏览器和用户等待位于底部地样式表时,浏览器会延迟显示任何可视化组件,我们称之为 “白屏”。

6. 将脚本放在底部

  脚本也会引起类似阻碍页面逐步呈现地问题,最好地解决方案是将脚本从页面地顶部移动到底部,这样页面既可以逐步呈现,也可以提高下载地并行度。

7. 规避 CSS 表达式

  CSS 表达式时动态设置 CSS 属性地一种强大(并且危险)地方式。它受 IE 版本 5 和之后版本地支持。

    background-color: expression((new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00");

如上所示,expression 方法接受一个 JavaScript 表达式。CSS 属性被设置对 JavaScript 表达式进行求值的结果。

  CSS 表达式不只在页面呈现和大小改变时求值,当页面滚动、甚至用户鼠标在页面上移过时都要求值。我们可以通过两种方式来规避这个问题:

  1. 一次性表达式

  如果 CSS 表达式必须被求值一次,那么可以在这一次执行中重写它本身。

    <style>
        p{
            background-color: expression( altBgcolor(this) );
        }
    </style>
    <script type="text/javascript">
        function altBgcolor(elem){
            elem.style.backgroundColor = (new Date()).getHours() % 2 ? "#F080A00" : "##B8D4FF";
        }
    </script>

CSS 表达式调用了 altBgColor() 函数,而该函数样式的 background-color 属性设置为一个明确的值,并移除了 CSS 表达式。

  1. 事件处理器

  使用事件处理器来为特定的事件提供所期望的动态行为,这就避免了在无关事件发生时对表达式求值。

    function setMinWidth(){
        setCntr();
        var aElements = document.getElementsByTagName("p");
        for( var i = 0; i < aElements.length; i++ ){
            aElements[i].runtimeStyle.width = ( document.body.clientWidth < 600 ? "600px" : "auto");
        }
    }
    
    if(navigator.userAgent.indexOf("MSIE") > 0){
        window.onresize = setMinWidth;
    }

这会在浏览器大小改变时动态地设置宽度,但在第一次呈现时这并不能恰当地设置段落大小。因此,页面还需要使用 “一次性表达式” 中的方法。

8. 使用外部 JavaScript 和 CSS

  一般情况下,HTML 文档是包含动态内容,通常不会被配置为可以进行缓存,这样就会造成每次请求 HTML 文档都需要下载内联的 JavaScript 和 CSS。

  如果 JavaScript 和 CSS 是外部文件,浏览器就能缓存它们,从而可以减少 HTML 文档的大小,并且不会增加 HTTP 请求的数量。

  如果网站中的每个页面都使用了相同的 JavaScript 和 CSS,使用外部文件可以提高这些组件的重用率

9. 减少 DNS 查找

   DNS(Domain Name System)是将主机名映射到 IP 地址上。如果一个服务器被另一个具有不同 IP 地址的服务器替代,DNS 允许用户使用相同胡主机名来链接到新胡服务器。

  然而,DNS 也是有开销。通常浏览器查找一个给定的主机名要花费 20~120 毫秒。在 DNS 查找完成之前,浏览器不能从主机名那里下载到任何东西。响应时间依赖于 DNS 解析器(通常由你的 ISP 提供)、它所承担的请求压力、你与它之间的距离和你的带宽速度。

10. 精简 JavaScript

  精简(Minification)是从代码中移除不必要的字符以减少其大小,进而改善加载时间的时间。在代码被精简后,所有的注释以及不必要的空白字符(空格、换行和制表符)都将被移除。

  混淆(Obfuscation)是可以应用在源代码上的另一种优化方式。和精简一样,它也会移除注释和空白,同时它还会改写代码。作为改写的一部分,函数和变量的名字将被转换为更短的字符串,这时的代码更加精炼,也更难阅读。

11. 避免重定向

  重定向(Reddirect)用于将用户从一个 URL 重新路由到另一个 URL。重定向有很多种,其中 301 和 302 是最常用的两种。通常针对 HTML 文档进行重定向,但也可能用在请求页面中的组件(图片、脚本等)时。

  当 Web 服务器向浏览器返回一个重定向时,响应中就会拥有一个范围在 3xx 状态码,这表示用户代理必须执行进一步操作才能完成:

  • 300 Multiple Choices(基于 Content-Type);
  • 301 Moved Permancently
  • 302 Moved Temporarily
  • 303 Sess Other(对 302 的说明)
  • 304 Not Modified
  • 305 Use Proxy
  • 306(已不再使用)
  • 307 Temporary Redirect(对 302 的说明)

其中 “304 Not Modified” 并不真的是重定向 —— 它用来响应条件 GET 请求,避免下载已经存在与浏览器缓存中的数据;状态码 303 和 307 是在 HTTP 1.1 规范中添加的,用来澄清对 302 的滥用,但是几乎没有采用 303 和 307。

  响应体通常是空的,不管叫什么名字,301 和 302 响应在实际中不会被缓存,除非有附加的头 —— 如 Expires 或 Cache-Control 等。

  还可以通过 meta refresh 标签自动将用户重定向到其它 URL:

    <meta http-equiv="refresh" content="0; url=http://www.baidu.com" />

12. 移除重复脚本

  重复脚本损伤性能的方式有两种 —— 不必要的 HTTP 请求和执行 JavaScript 所浪费的时间。

13. 配置 ETag

  实体标签(Entity Tag,ETag)是 Web 服务器和浏览器用于确认缓存的有效性的一种机制。

  服务器在监测缓存的组件是否和原始服务器上的组件匹配时有两种方式:

  • 比较最新修改日期;
  • 比较实体标签;

ETag 在 HTTP 1.1 中引入。ETag 是唯一标识了一个组件的一个特定版本的字符串。为一个格式约束是改字符串必须用引号引起来。原始服务器使用 ETag 响应头来指定组件的 ETag。

  ETag 的问题在于,通常使用组件的某些属性来构造它,这些属性对于特定的、寄宿了网站的服务器来说是唯一的。

  Apache 1.3 和 2.x 的 ETag 格式是 inode-size-timestamp。文件系统使用 inode 来存储诸如:文件类型、所有者、组和访问模式等信息。尽管在多台服务器上一个给定的文件可能位于相同的目录、具有相同的文件大小、权限、时间戳等,从一台服务器到另一台服务器,其 inode 仍然是不同的。

  IIS 5.0 和 IIS 6.0 在 ETag 上有着类似的问题。IIS 上 ETag 的格式是 Filetimestamp:ChangeNumber。ChangeNumber 适用于跟踪 IIS 配置变化的计数器。对于一个网站背后的所有 IIS 服务器来说,ChangeNumber 不大可能相同。

14. 使 Ajax 可缓存

【ECMAScript 6】5.关于 Class

Class基本语法

  ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类:

    // 定义 Point 类
    class Point {
        // 定义 Ponit 类的构造函数
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
    
        // 定义方法
        toString() {
            console.log(`x:${this.x}, y:${this.y}`);
        }
    }

  ES6 的类,完全可以看作构造函数的另一种写法:

    class Point { 
        constructor() {
            
        }
    };
    
    console.log(typeof Point); // "function"
    
    console.log(Point === Point.prototype.constructor); // true
    

事实上,类的所有方法都定义在类的 prototype 属性上。在类的实例上调用方法,其实就是调用原型的方法。*

  Object.assign 方法可以向类添加多个方法:

    Object.assign(Point.prototype,{
        toValue() {},
        toString() {}
    })

ES6 中类的内部所有定义的方法,都是不可枚举的,这一点与 ES5 的行为不一致。

    class Point {
        constructor(){}
        toString(){}
    }
    
    console.log(Object.keys(Point.prototype)); // []

若想要获取其内部定义的所有方法,则可通过 Object.getOwnPropertyNames 方法获取

    console.log(Object.getOwnPropertyNames(Point.prototype));

  可使用表达式定义类的属性名

    let methodName = "getArea";
    
    class Square {
        constructor(){}
        
        [methodName](){}
    }

constructor 方法

  constructor 方法是类的默认方法,通过 new 命令生成对象实例。自动调用该方法。一个对象必须有 constructor 方法,如果没有显示定义,一个空的 constructor 方法会被默认添加。

  constructor 方法默认返回实例对象(即 this),完全可以指定返回另一个对象。

    class Foo {
        constructor() {
            return Object.create(null);
        }
    }

Class 表达式

  与函数一样, Class 也可以使用表达式的形式定义。

    const MyClass = class Me {
        getClassName() {
            return Me.name;
        }
    }

Class 的继承

  Class 之间可以通过 extends 关键字实现继承,这比 ES5 通过修改原型链实现继承,要清晰和方便很多:

    class ColorPoint extends Point {
        
        constructor(x,y) {
            super(x,y); // 用于表示 父类的构造函数,用来新建父类的 this 对象。
        }
    }

子类必须在 constructor 方法中调用 super 方法,否则新建实例会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。

ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this上(Parent.applu(this))。ES6 的继承机制完全不同,实质是先创造父类的实例对象 this(所以必须先调用 super 方法),然后再用子类的构造函数修改 this

类的 prototype 属性和 proto 属性

  大多数浏览器的 ES5 实现之中,每一个对象都有 __proto__ 属性,指向对应的构造函数的 prototype 属性。
Class 作为构造函数的语法糖,同时有 prototype__proto__ 属性,因此同时存在两条继承链:

  1. 子类的 __proto__ 属性,表示构造函数的继承,总是指向父类
  2. 子类 prototype 属性的 __proto__ 属性,表示方法的继承,总是指向父类的 prototype 属性。

Object.getPrototypeOf

  Object.getPrototypeOf 方法可以从子类上获取父类

    Object.getPrototypeOf(ColorPoint); // Ponit

super 关键字

  1. 作为函数调用时,super 代表父类的构造函数。
  2. 作为调用时,super 代表父类,可以直接引用父类实例的属性和方法,也可以引用父类的静态方法。

实例的 proto 属性

   子类实例的 __proto__ 属性的 __proto__ 属性,指向父类实例的 __proto 属性。

Class 的取值函数(getter)和存值函数(setter)

  与 ES5 一样,在 Class 内部可以使用 getset 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

    class MyClass {
        constructor() {
            this.element = '';
        }
        
        get prop() {
            return this.element;
        }
        
        set prop(value) {
            this.element = value;
        }
    }

Class 的静态方法

  在方法前加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用。

    class MyClass {
        constructor() {
            this.element = '';
        }
        static classMethod() {
            return false;
        }
    }
    
    MyClass.classMethod();

new.target 属性

  new.target 属性用于确定构造函数是怎么调用的。在构造函数中返回 new 命令作用于的那个对象函数,如果不是通过 new 命令调用的,则返回 undefined

CSS 相对定位与绝对定位

position

  • position:relative | absolute | fixed | static | inherit;
  • position: static; 默认值
  • position: inherit; 从父元素继承定位属性的值

relative 相对定位/定位偏移量

position:relative; 相对定位

  • 不影响元素本身的特性;
  • 不使元素脱离文档流;
  • 如果没有定位偏移量,对元素本身没有任何影响;

定位元素位置控制

   top/right/bottom/left 定位元素偏移量。

absolute 绝对定位/定位层级

position:absolute; 局对定位

  • 使元素完全脱离文档流;
  • 使内嵌支持宽高;
  • 块属性标签内容撑开宽度;
  • 如果有定位父级相对于定位父级发生偏移,如果没有定位父级相对于整个文档发生偏移;
  • 相对定位一般都是配合绝对定位使用的;

z-index:number; 定位层级

  • 定位层级越大,元素显示越靠前;
  • 定位元素 默认层级后者比前者高

遮罩滤镜/固定定位

遮罩滤镜

  • 标准不透明:opacity:0~1;
  • IE 滤镜:filter.alpha(opacity=0~1);

position:fixed; 固定定位

  与绝对定位的特性基本一致,差别是始终相对整个文档进行定位,但 IE6 不支持固定定位;

问题及解决方法

  • 在 IE6 下父级的 overflow:hidden;包不住子级的 relative;

解决方法: 将父级设置定位属性;

  • 在 IE6 下定位元素的父级宽高都是奇数那么在 IE6 下定位元素的 right 和 bottom 都有 1 像素的偏差;

解决方法:尽量规避父级宽高值不为奇数;

CSS 选择器、选择器的优先级和权重

以下总结内容摘自 《移动Web前端高效开发实战》

基本选择器

选择器 名 称 实 例 描 述 版 本
* 通用选择器(Universal selectors) * 匹配所有的元素 2.1
E 标签选择器(Type selectors) p 匹配所有的 <p> 1
.class 类选择器(Class selectors) .nav 匹配所有 class="nav" 的元素 1
#id ID选择器(ID selectors) #wrapper 匹配所有 id="wrapper" 的元素 1
E[attr] 属性选择器(Attribute selectors) a[data-url] 匹配所有 data-url 属性的 <a> 的元素 2.1
E[attr=val] 属性选择器(Attribute selectors) a[data-url='http'] 匹配所有 data-url="http" 属性的 <a> 的元素 2.1
E[attr~=val] 属性选择器(Attribute selectors) a[data-url~='http'] 匹配所有 data-url 属性包含 http<a> 元素 2.1
E[attr|=val] 属性选择器(Attribute selectors) a[data-url|='http'] 匹配所有 data-url 属性以 http 开头的 <a> 元素 2.1
E[attr^=val] 属性选择器(Attribute selectors) a[data-url^='http'] 匹配所有 data-url 属性以 http 开头的 <a> 元素 3
E[attr$=val] 属性选择器(Attribute selectors) a[data-url$='http'] 匹配所有 data-url 属性以 http 结尾的 <a> 元素 3
E[attr*=val] 属性选择器(Attribute selectors) a[data-url*='http'] 匹配所有 data-url 属性包含 http<a> 元素 3
E F 后代选择器(Descendant selectors) div p 匹配所有 <div> 元素下所有 <p> 元素 1
E > F 子选择器(Child selectors) div p 匹配所有 <div> 元素下所有子 <p> 元素 2.1
E + F 相邻兄弟选择器 label + input 匹配所有<label> 元素同级的第一个 <input> 元素 2.1
E ~ F 兄弟选择器 label ~ input 匹配所有<label> 元素同级的所有 <input> 元素 3
S1,S2,..... 选择器分组 label,input 匹配所有<label>,<input>元素 1

伪类和伪元素

  伪类(Pseudo-classes)用于指定选择器的某种特定状态或条件,伪类在 DOM 中并不存在,但对用户却是可见的。

动态伪类(Dynamic pseudo-classes)

  动态伪类对除了其名称、属性或内容之外的特性的元素进行分类,不会显示在文档源或文树中。

选择器 实 例 描 述 版 本
:link a:link 匹配未被访问的链接 1
:visited a:visited 匹配被访问过的链接 1
:hover a:hover 匹配鼠标指针在其浮动的元素 1
:active a:active 匹配鼠标指针在其上按下的元素 1
:focus input:focus 匹配获得焦点的元素 2.1

目标伪类(The target pseudo-classes)

  目标伪类指定当前活动的锚,使用目标伪类可以为活动的锚设置样式。

选择器 实 例 描 述 版 本
:target #tab1:target 匹配活动的锚 3

语言伪类(The language pseudo-classes)

  语言伪类向带有指定 lang 属性元素添加样式。

选择器 实 例 描 述 版 本
:lang(val) #p:lang(en) 匹配带有指定 lange="en"<p> 元素 3

UI元素状态伪类(The UI element states pseudo-classes)

  UI元素状态伪类主要用于指定表单中的元素状态。

选择器 实 例 描 述 版 本
:enabled input:enabled 匹配启动的元素 3
:disabled input:disabled 匹配禁用的元素 3
:checked input:checked 匹配被选中的元素 3

displayvisibility 属性对于UI元素状态伪类匹配 enabled/disabled 状态没有影响。

结构性伪类(Structural pseudo-classes)

  结构性伪类用于指定文档的特定结构。

选择器 实 例 描 述 版 本
:root :root 匹配文档的根元素 3
:nth-child(n) :nth-child(n) 匹配其父元素的第 n 个子元素 3
:nth-last-child(n) :nth-last-child(n) 匹配其父类倒数第 n 个子元素 3
:nth-of-type(n) :nth-of-type(n) 匹配其父类第 n 个有着相同选择器的子元素 3
:nth-last-of-type(n) :nth-last-of-type(n) 匹配其父类倒数第 n 个有着相同选择器的子元素 3
:first-child :first-child 匹配其父类元素的第一个子元素 3
:last-child :last-child 匹配其父类元素的最后一个子元素 3
:last-child :last-child 匹配其父类元素的最后一个子元素 3
:first-of-type :first-of-type 匹配其父类元素第一个有着相同选择器的子元素 3
:last-of-type :first-of-type 匹配其父类元素最后一个有着相同选择器的子元素 3
:only-child :only-child 匹配其父类的唯一子元素 3
:only-of-type :only-child 匹配其父类的唯一有着相同选择器的子元素 3
:empty :empty 匹配没有子元素(包括文字节点)的元素 3

:nth-child(n):nth-last-child(n):nth-of-type(n):nth-last-of-type(n)n 是从 0 开始的整数,表达式可写成 an+b, a 和 b 是 0 或正整数,表达式的写法相当于把每 a 个子元素分成一组,取每组的第 b 个元素;取第奇数、偶数个子元素可写表达式为 2n+1 或 even2n 或 odd

  否定伪类是用来选择所有非指定类型元素的其他元素。

选择器 实 例 描 述 版 本
:not(s) input:not([type="text"]) 匹配所有非指定类型的其他元素 3

伪元素

  伪元素(Pseudo-elements)是指不存在与文档树中的抽象。

选择器 实 例 描 述 版 本
::first-line ::first-line 匹配元素文本内容的首行 1
::first-letter ::first-letter 匹配元素文本内容的首个字母 1
::before ::before 元素之前 2.1
::after ::after 元素之后 2.1

在 CSS 1 和 CSS 2 中,伪类选择器中只有一个 ":",而 CSS 3 变为两个 "::",是为了区分伪类与伪元素,目前这两个写法效果一致。

::before::after 必须设置 content 属性,否则元素不能生效。

优先级和权重

   CSS 中的权重分别为 4 个等级:

  • 内联样式(HTML 文档中的 style 属性)
  • ID 选择器
  • 类、伪类、属性选择器
  • 元素、伪类元素

这 4 个等级由高到低代表不同的优先级,!important 写在 CSS 规则后,可以将对应的规则提升到最高权重。

  github 原文地址 欢迎 Star 和 Watch

Node.js 自定义微信菜单

一、写在前面的话

  上一篇文章中,我们使用 Node.js 成功的实现了access_token 的获取、存储以及更新,这篇文章我们来实现微信的自定义菜单功能。
接着来,微信自定义菜单

二、自定义微信菜单

1.微信文档步骤
  在开始码代码之前,我们依然是先理清实现的思路,再开始编写实现代码。打开 微信帮助文档 ,点击左侧菜单中的 自定义菜单,点击其子菜单 自定义菜单创建接口,如图:
自定义菜单- 自定义菜单创建接口

自定义菜单

  由上图我们总结以下步骤:

  1. 自定义微信请求是以 https POST请求方式
  2. 数据是以 JSON 格式传入

2.实现 https POST请求

  紧接着上一篇文章的代码,源码地址: https://github.com/SilenceHVK/wechatByNode ,克隆到本地文件中

git clone [email protected]:SilenceHVK/wechatByNode.git

  打开 wechat 文件夹中的 wechat.js 文件,并在 WeChat 构造函数内部添加 requestPost 方法

//用于处理 https Post请求方法
    this.requestPost = function(url,data){
        return new Promise(function(resolve,reject){
            //解析 url 地址
            var urlData = urltil.parse(url);
            //设置 https.request  options 传入的参数对象
            var options={
                //目标主机地址
                hostname: urlData.hostname, 
                //目标地址 
                path: urlData.path,
                //请求方法
                method: 'POST',
                //头部协议
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Content-Length': Buffer.byteLength(data,'utf-8')
                }
            };
            var req = https.request(options,function(res){
                var buffer = [],result = '';
                //用于监听 data 事件 接收数据
                res.on('data',function(data){
                    buffer.push(data);
                });
                 //用于监听 end 事件 完成数据的接收
                res.on('end',function(){
                    result = Buffer.concat(buffer).toString('utf-8');
                    resolve(result);
                })
            })
            //监听错误事件
            .on('error',function(err){
                console.log(err);
                reject(err);
            });
            //传入数据
            req.write(data);
            req.end();
        });
    }

  在上一篇文章中,我们使用到了 https 的 get 方法发。实际上 https 用于请求的底层方法则是 request 方法,而 get 方法 只是对它的一个封装,但是 Node.js 却没有对 post 进行封装,直到现在 Node.js 8.0 依然没有。具体详情请看 Node.js 中文文档

提示:

   npm 提供了很多用于请求的工具包,比如 request ( 安装命令 npm install request ) 等。这里我只是用系统包去做请求处理。

3.配置创建微信菜单的连接
  打开 项目文件中的 config.json 文件,在 apiURL 中添加配置:

"createMenu":"%scgi-bin/menu/create?access_token=%s"

4.微信菜单 JSON 格式
  完成了上面的工作后,我们就可以开始微信菜单的创建了。按照微信帮助中菜单示例格式,我们自己定义一个 JSON 格式:

{
     "button":[
        {	
          "type":"view",
          "name":"hvkcoder",
           "url":"http://www.cnblogs.com/hvkcode/"
        },
        {	
          "type":"click",
          "name":"今日推荐",
          "key":"today_recommend"
        },
        {	
          "name":"小工具",
          "sub_button":[{
               "type": "scancode_waitmsg", 
               "name": "扫一扫",
               "key": "scancode"
          },{
               "type": "pic_sysphoto", 
               "name": "系统拍照发图",
                "key": "take_photo"
          },{
            "type": "location_select", 
            "name": "发送位置",
            "key": "send_location"
        }]
        }
     ]
}

并将它存放在 wechat 文件夹中的 menus.json 文件,如图:
项目结构

5.请求创建菜单API
  将 menus.json 文件在 wechat.js 文件中引用。这块呢,我就直接在微信接入的方法中去做菜单的创建:

var that = this;
    this.getAccessToken().then(function(data){
        //格式化请求连接
        var url = util.format(that.apiURL.createMenu,that.apiDomain,data);
        //使用 Post 请求创建微信菜单
        that.requestPost(url,JSON.stringify(menus)).then(function(data){
            //将结果打印
            console.log(data);
        });
    });

  如果你目前用的是订阅号的话,那么不好意思朋友,你在运行结果就会看到:
api unauthorized hint

  错误意思是:api未经授权。腾讯本着“没钱,玩你麻痹”的态度,指明订阅号的朋友是不能通过 api 请求去自定义菜单的。
真是日了狗了

  但是不要伤心,因为腾讯依然很贴心的为我们准备测试公众号,再次打开 微信帮助文档,点击右侧的 开始开发,点击其子菜单 接口测试号申请,如图:
接口测试号申请

进入微信公众帐号测试号申请系统

  使用手机端微信,扫描二维码后,我们就得到了一个测试公众号。

测试公众号

  其他的信息我们都不需要去管,主要去修改 appID 和 appsecret,并将 access_token.json 所保存的数据更改为:

{"access_token":"","expires_time":0}

随后重新运行就可以了,是不是很简单呢。
运行结果

  扫面一下测试公众号二维码

测试公众号二维码位置

测试公众号 二维码

最终效果

  每次只需要对 menus.json 文件进行更改,重新请求。就能够实现菜单的更改效果了。

  文章源代码:https://github.com/SilenceHVK/wechatByNode 。对文章有不正确之处,请给予纠正。github源代码请顺手给个 Star,最后感谢您的阅读。

【Linux】CentOS 7 设置静态IP

  1. 验证网络管理器服务的状态
$ systemctl status NetworkManager.service
  1. 检查受网络管理器管理的网络接口
$ nmcli dev status 
  1. 进入 /etc/sysconfig/network-scripts 文件,找到配置文件
# 设置为静态
BOOTPROTO=static
# 定义 IP 地址
IPADDR=192.168.129.159
# 定义网卡
NETMASK=255.255.255.0
# 定义网关
GATEWAY=172.16.79.2
# 接口将通过该配置文件进行设置
NM_CONTROLLED=no
# 启动时开启该接口
ONBOOT=yes
  1. 编辑 /etc/resolv.conf
nameserver 0.0.0.0
nameserver 114.114.114.114
search localhost
  1. 重启 network 服务
$ systemctl restart network.service

【HTML5 高级API】1. Geolocation API

  使用 navigator.geolocation 对象来获取位置信息,navigator 对象包含有有关浏览器的信息,geolocation 对象包含一系列相关位置操作的方法。geolocation 兼容 IE9 及以上浏览器。

原理说明

  一般流程为:

  1. 用户从浏览器打开位置感应应用程序
  2. 程序执行,geolocation 对象获取位置信息,此时浏览器要求用户授权同意才能够获取信息。
  3. 浏览器内部通过数据源获取信息
  4. 浏览器将获取的信息发送给受信任的外部定位服务,返回位置信息到 geolocation 应用程序

Geolocation API 不指定设备使用哪种底层技术来定位应用程序的用户。设备可以使用 IP地址、GPS、WI-FI、GSM 或 CDMA。

Geolocation 中方法

getCurrentPosition(successCallback,errorCallback,positionOptions) 获取当前地址信息(请求一次)

  • successCallback 成功回调函数接受一个:位置对象。这个对象包含坐标(coords 属性)和一个获取位置数据时的时间戳。
属性 描述
coords.latitude 十进制数的维度
coords.longitude 十进制数的经度
coords.accuracy 位置精度
coords.altitude 海拔,海平面以上以米计
coords.altitudeAccuracy 位置的海拔精度
coords.heading 方向,从正北开始以度计
coords.speed 速度,以米/每秒计
timestamp 响应的日期/时间
  • errorCallback 错误回调函数,包含 code 属性、message 信息

code 可能值:

  1. 当属性值为 1 时,用户不允许地理定位(拒绝授权),"Permission denied"
  2. 当属性值为 2 时,无法获取当前位置,"Position unavailable"
  3. 当属性值为 3 时,操作超时,"Timeout"

  message 属性值为一个字符串,包含了错误信息。

  • positionOptions 是一个可选属性的列表,说明如下:
名称 描述
enableHighAccuracy 启用高精度模式,这个参数通知浏览器启用 HTML5 Geolocation 服务的高精度模式。默认值为 false
timeout 超时限制(单位为毫秒)。如果在该时间内未获取到地理位置信息,则返回错误
maximumAge 表示浏览器重新计算位置的时间间隔。单位为 ms,默认值为零,这意味着浏览器每次请求时都必须立即重新计算位置

watchPosition(successCallback,errorCallback,positionOptions) 监视当前地理位置(请求多次)

   watchPosition 可以定期地获取用户地理位置信息。该方法的使用方式与 getCurrentPosition 方法类似,这里调用该方法会返回一个数字,这个数字与 setInterval 方法的返回值用法类似,可以被 clearWatch 方法使用,以停止对当前地理位置信息的监视。

clearWatch(watchId) 清除监视功能

  如果应用程序不再需要接受 watchPosition 的持续位置更新,则只需调用 clearWatch 函数,watchIdwatchPosition 函数的返回值。

IE 不兼容 js indexOf 函数

  在使用 js 判断数组中是否存在该元素,我们会用到 indexOf 函数。而在 IE 上 indexOf 函数 无法兼容,通过以下方法解决,仅以文章记录一下

if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (elt /*, from*/) {
            var len = this.length >>> 0;
            var from = Number(arguments[1]) || 0;
            from = (from < 0)
                 ? Math.ceil(from)
                 : Math.floor(from);
            if (from < 0)
                from += len;
            for (; from < len; from++) {
                if (from in this &&
                    this[from] === elt)
                    return from;
            }
            return -1;
        };
    }

【Golang 基础】Go 语言简介

Go 语言简介

  Go 是一门开源、支持并发、垃圾回收的编译型系统编程语言,从 2007 年末由 Robert Griesemer,Rob Pike,Ken Thompson 主持开发,后来还加入了 lan Lance Taylor,Russ Cox 等人,并最终在 2009 年 11 月 开源,在 2012 年早些时候发布了 Go 1 稳定版本。

Go 语言的主要特点

  • 类型安全 和 内存安全;
  • 以非常直观和极低代价的方案实现高并发;
  • 高效的垃圾回收机制(内置 runtime);
  • 快速编译(同时解决 C 语言中头文件太多的问题);
  • 为多核计算机提供性能提升的方案;
  • UTF-8 编码支持;

Go 语言的应用

  • 服务器编程:处理日志、数据打包、文件系统等;
  • 分布式系统:数据库处理器,中间件等;
  • 网络编程:目前使用最多最广泛的一块,Web 应用、API 应用等;
  • 云平台:目前云平台逐步采用 Go 实现;

Go 语言中的常用命令

  • go get:获取远程包(需要提前安装 git 或 hg);
  • go run:直接运行程序;
  • go build:项目发布;
  • go fmt:格式化源码(部分 IDE 在保存时自动调用);
  • go install:编译包文件并编译整个程序;
  • go test:运行测试文件;
  • go doc:查看文档;
  • go help:查看 go 命令行;

Go 语言中的关键字,标识符

  Go 语言中保留关键字只有 25 个

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

  Go 语言中有 36 个预定的标识符,其中包括基础数据类型和系统内嵌函数

append bool byte cap close complex
complex64 complex128 copy false float32 float64
copy int int8 int16 int32 int64
imag uint uint8 uint16 uint32 uint64
uintprt iota len new nil panic
recover print println real string TRUE

Go 语言中的注释方法

  • //:单行注释

  • /* */:多行注释

Go 语言中可见性规则

   Go 语言中,使用大小写来决定该常量、变量、类型、接口、结构是否可以被外部所调用:根据约定,函数名首字母 小写 即为 private,函数名首字母 大写 即为 public

JavaScript 面试题

1. ECMAScrit 中数组的大小

   如题:

    var array = new Array();
    array[0] = 0;
    array[1] = 1;
    array[2] = 2;
    array[5] = 5;
    console.log(array.length); // 6
解析:
    
    ECMAScript 中数组的大小是可以动态调整的,既可以随着数据的添加自动增长以容纳新增的数据。
    当把一个值放在超出当前数组大小的位置上时,数组就会重新计算长度值,
    即长度值等于最后一项的索引加一,前面的值都将被自动赋值为 undefined。

2. RegExp 对象的方法

   如题:下面选项中不是RegExp 对象的方法的是 :

   A. test B. match C. exec D. compile

  正确答案选择 B.

解析:

    JavaScript RegExp 对象有 3 个方法:test()、 exec()、compile()。
    
    1. test() 方法用于检测一个字符串是否匹配某个正则表达式,如果匹配,返回 true ,否则返回 false;
    2. exec() 方法用来检测字符串中的正则表达式匹配的值,exec() 方法返回一个数组,其中存放匹配的结果。
    如果未找到匹配的值,则返回 null;
    3.compile() 方法可以在脚本执行过程中编译正则表达式,也可以改变已有的表达式。

3. 下面哪些方法可以用于 JavaScript 的异步编程

   A. 回调函数 B. 事件监听 C. 监听/订阅 D. Promise 对象

  正确答案选择 A,B,C,D.

解析:

  回调函数是异步编程的基础。
  
  事件监听,另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而是取决于事件是否发生。
  
  监听/订阅,上一节的“事件”可以理解为“型号”。
  
  Promise 对象,是 CommonJS 工作组提出的一种规范,目的是为异步编程提供统一接口

4. w3c 为 JavaScript 定制的标准事件模型,以下正确的顺序和描述是

  • A. 事件捕获 -> 事件冒泡
  • B. 事件捕获 -> 事件处理 -> 事件冒泡
  • C. 事件冒泡 -> 事件处理
  • D. 事件冒泡 -> 事件处理 -> 事件捕获
  • E. 事件处理 -> 事件捕获 -> 事件冒泡

  正确答案选择 B.

解析:
    
    先事件捕获从 windows > document 往下级直到特定的事件节点,然后进行事件处理,再事件冒泡,
    从特定节点往上级,这个完整的过程。

5. 下面关于 DNS 说法正取的是

  • A. DNS 的作用是域名与 IP 地址的相互映射
  • B. DNS 协议 运行在 UDP 协议之上
  • C. DNS 协议的端口号是 53
  • D. DNS 默认缓存时间为 1 小时

  正确答案选择 B,C.

解析:
    
    DNS(Domain Name System,域名系统),因特网上作为域名与 IP 地址的相互映射的一个分布式数据库,能够使用户更方便的使用互联网,
    而不用去记住能够被机器直接读取的 IP 数串。
    通过主机名,最终得到该主机名对应的 IP 地址的过程叫做域名解析(或主机名解析)。
    DNS 协议运行在 UDP 协议之上,使用端口号为 52。

6. 编写获取 Url 参数值的代码,以键值对返回。

  例:
url = "https://cn.bing.com/search?q=LeetCode&qs=n"

  返回结果 { q: "LeetCode", qs: "n" }

  实现代码:

  • 使用截取字符串的方式实现
    /**
     * 通过截取字符串获取 url 参数
     * @param {*} requestUrl 
     */
    function queryURLParameter(requestUrl) {
        const obj = {};
        let index = 0;
        // 判断 url 地址是否有参数
        if ((index = requestUrl.indexOf('?')) > 0) {
            // 截取 ? 后的字符串
            requestUrl = requestUrl.substr(index + 1);
            // 切分 & 符号后的参数
            requestUrl.split('&').forEach(item => {
                const param = item.split('=');
                obj[param[0]] = param[1];
            });
        }
        return obj;
    }
  • 使用正则表达式的方式实现
    /**
     * 通过正则表达式来获取 url 参数
     * @param {*} requestUrl 
     */
    function queryURLParameter(requestUrl) {
        // 编写获取 url 参数的正则表达式
        let reg = /([^?&=]+)=([^?&=]+)/g;
        const obj = {};
        // 通过配置正则获取数值
        requestUrl.replace(reg, (...arg)=>{
            obj[arg[1]] = arg[2];
        });
        return obj;
    }

7. 以下代码的输出结果。

    var a = 4;
    function b(x, y, a){
        console.log(a);
        arguments[2] = 10;
        console.log(a);
    }
    a = b(1, 2, 3);
    console.log(a);
  • A. 3 3 4
  • B. 3 10 4
  • C. 3 10 10
  • D. 3 10 undefined

  正确答案选择 D.

解析:
    
    在 JavaScript 的非严格模式下,函数的实参集合与形参变量存在“映射”关系:无论其中谁发生改变,另一个也会跟着改变;
    
    在 JavaScript 的严格模式下,arguments 和形参变量的映射关系被切断,互相之间互不干扰。

8. 请分别说出下列代码中 a、b、c 输出的值

    function fun(n, o) {
        console.log(o);
        return function (m) {
            return fun(m, n);
        };
    };
    
    const a = fun(0);
    a(1);
    a(2);
    a(3);
    
    const b = fun(0)(1)(2)(3);
    
    const c = fun(0)(1);
    c(2);
    c(3);

  正确答案:

    a 输出结果: undefined,0,0,0
    
    b 输出结果: undefined,0,1,2
    
    c 输出结果: undefined,0,1,1
解析:
    a 的运行中,第一次 fun(0) 所调用的是第一层函数,由于参数 o 并未传值,此时 o = undefined;
    a(1) 是在调用前一个 fun(0) 的返回值也就是 function(m){ return fun(m,n) };并内部调用 fun(m,n),此时内部 fun(m,n) 中的 n 闭包了外层 fun(n,o) 中的 n,由于第一次调用 n = 0;即 m = 1,n = 0;
    同理 a(2) 中调用的是前一个 a(1) 的返回值,即 fun(m,n),所以还是闭包了第一次 n 的值,即 m = 2, n = 0;
    以此类推
    
    b 的运行中,第一次 fun(0) 所调用的是第一层函数,由于参数 o 并未传值,此时 o = undefined;
    b(1) 是在调用前一个 fun(0) 的返回值也就是 function(m){ return fun(m,n) };并内部调用 fun(m,n),此时内部 fun(m,n) 中的 n 闭包了外层 fun(n,o) 中的 n,由于第一次调用 n = 0;即 m = 1,n = 0;
    b(1)(2) 此时调用的并不是上一层返回的值,而是直接调用的上一层返回值的内部函数,即 fun(m,n);由于 fun(m,n) 中的 n 闭包了外层 fun(n,o) 中的 n,即 b(1),因此 n = 1;
    以此类推
    
    c 的运行中,c(0)(1) 与 b(1)(2) 的推断相似,只不过此时的 fun(m,n) 中的 n 闭包了外层 fun(n,o) 中的 n,即 c(0),因此 n = 0;
    c(2) 与 a(1) 的推断相似,只不过 只不过此时的 fun(m,n) 中的 n 闭包了外层 fun(n,o) 中的 n,即 c(1),因此 n = 1;
    以此类推

9. 以下哪一项不属于浏览器 Response Headers 字段

  • A. Referer
  • B. Connection
  • C. Content-Type
  • D. Server

  正确答案选择 A.

解析:
    
    常见的请求头部和响应头部
    
    请求(客户端 -> 服务端[request])
    
        GET(请求的方式) /newcoder/hello.html(请求的目标资源) HTTP/1.1(请求采用的协议和版本号) 
        Accept: */*(客户端能接收的资源类型) 
        Accept-Language: en-us(客户端接收的语言类型) 
        Connection: Keep-Alive(维护客户端和服务端的连接关系)
        Host: localhost:8080(连接的目标主机和端口号)
        Referer: http://localhost/links.asp(告诉服务器我来自于哪里)
        User-Agent: Mozilla/4.0(客户端版本号的名字)
        Accept-Encoding: gzip, deflate(客户端能接收的压缩数据的类型) 
        If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(缓存时间)  
        Cookie(客户端暂存服务端的信息) 
        Date: Tue, 11 Jul 2000 18:23:51 GMT(客户端请求服务端的时间)
        
    响应(服务端->客户端[response])
    
        HTTP/1.1(响应采用的协议和版本号) 200(状态码) OK(描述信息)
        Location: http://www.baidu.com(服务端需要客户端访问的页面路径)
        Server:apache tomcat(服务端的Web服务端名)
        Content-Encoding: gzip(服务端能够发送压缩编码类型) Content-Length: 80(服务端发送的压缩数据的长度) 
        Content-Language: zh-cn(服务端发送的语言类型) 
        Content-Type: text/html; charset=GB2312(服务端发送的类型及采用的编码方式)
        Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务端对该资源最后修改的时间)
        Refresh: 1;url=http://www.it315.org(服务端要求客户端1秒钟后,刷新,然后访问指定的页面路径)
        Content-Disposition: attachment; filename=aaa.zip(服务端要求客户端以下载文件的方式打开该文件)
        Transfer-Encoding: chunked(分块传递数据到客户端)
        Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务端发送到客户端的暂存数据)
        Expires: -1//3种(服务端禁止客户端缓存页面数据)
        Cache-Control: no-cache(服务端禁止客户端缓存页面数据)  
        Pragma: no-cache(服务端禁止客户端缓存页面数据) 
        Connection: close(1.0)/(1.1)Keep-Alive(维护客户端和服务端的连接关系)  
        Date: Tue, 11 Jul 2000 18:23:51 GMT(服务端响应客户端的时间)
        
    在服务器响应客户端的时候,带上Access-Control-Allow-Origin头信息,解决跨域的一种方法。
        

10. 对任意给定的 32 位整数,转换二进制并统计 1 出现的个数

  实现代码:

    num.toString(2).match(new RegExp('1','g')).length;

11. 根据包名,在指定空间中创建对象

例:

    输入:namespace({a: {test: 1, b: 2}}, 'a.b.c.d')
    
    输出:{a: {test: 1, b: {c: {d: {}}}}}

  实现代码:

    function namespace(oNamespace, sPackage) {
        var keys = sPackage.split('.');
        var tempSpace = oNamespace;
        keys.forEach(function(key){
            if(!(tempSpace[key] instanceof Object)){
                tempSpace[key] = {};
            }
            tempSpace = tempSpace[key];
        });
        return oNamespace;
    }

12. 为 Array 对象添加一个去除重复项的方法

例:

    输入:[false, true, undefined, null, NaN, 0, 1, {}, {}, 'a', 'a', NaN]
    
    输出:[false, true, undefined, null, NaN, 0, 1, {}, {}, 'a']
解析:
    
    在 indexOf 判断中,NaN 和空对象 {} 均返回 -1,因此要判断 NaN 实现对其去重。
    
    由于空对象 {} 所指向的内存地址不一致,因此可以不对其考虑。
    
    在 JavaScript 中存在 6 个假值:flase, 0, null, "", undefined, NaN。
    
    前 5 个的判断可直接使用严格相等 “===” 判断,而 NaN 特殊性在于不等于其自身。

  ES5 实现代码:

    Array.prototype.uniq = function(){
        return this.filter(function(item,index){
            return item != item ? this.flag = this.flag === undefined : this.indexOf(item) === index;
        },this);
    }

  ES6 实现代码:

    Array.prototype.uniq = function(){
        [...new Set(this)]
    }

SQL Server 中操作时间的sql语句

使用 datepart 函数从日期中提取值

  1. 取年:
    select datepart(yy,gatdate())
  1. 取月:
    select datepart(mm,gatdate())
  1. 取日:
    select datepart(dd,gatdate())
  1. 取年中的天:
    select datepart(dy,gatdate())
  1. 取年中的周:
    select datepart(wk,gatdate())
  1. 取周中的天:
    select datepart(dw,gatdate())
  1. 取年中的季度:
    select  datepart(qq,gatdate())
  1. 取小时:
    select  datepart(hh,gatdate())
  1. 取分钟:
    select  datepart(mi,gatdate())
  1. 取秒:
    select  datepart(ss,gatdate())

【Golang 基础】Go 语言中的基本类型

Go 语言中的基础类型

  在 Go 编程语言中,数据类型用于声明函数和属性。数据类型的出现时为了把数据分成所需要用大数据的时候才需要申请大内存,这样可以充分的列用内存。

数值类型

  • 布尔型

    bool 布尔型的值只可以是常量 true 或者 false,默认值为 false

  • 字符串类型

    string 编码统一为 UTF-8 编码标识 Unicode 文本,默认值为空字符串。

  • 整型(默认值为 0)

    • uint8: 无符号 8 位整型(0 ~ 255);
    • uint16:无符号 16 位整型(0 ~ 65535);
    • uint32:无符号 32 位整型(0 ~ 4294967295);
    • uint64:无符号 64 位整型(0 ~ 18446744073709551615);
    • int8:有符号 8 位整型(-128 ~ 127);
    • int16:有符号 16 位整型(-32768 ~ 32767);
    • int32:有符号 32 位整型(-2147483648 ~ 2147483647);
    • int64:有符号 64 位整型(-9223372036854775808 ~ 9223372036854775807)
  • 浮点型(默认值为 0)

    • float32IEEE-754 32 位浮点数;
    • float64IEEE-754 64 位浮点数;
    • complex64:32 位实数和虚数;
    • complex128:64 位实数和虚数;
  • 其他数值类型

    • byte:类似 uint8
    • rune:类似 int32
    • uint:32 或 64 位;
    • int:与 uint 一样大小;
    • uintptr:无符号整型,用于存放一个指针;

派生类型

  • 指针类型(Pointer)
  • 数组类型
  • 结构化类型(struct)
  • Channel 类型(chan)
  • 函数类型(func)
  • 切片类型(slice)
  • 接口类型(interface)
  • Map 类型(map)

【Golang 基础】Go 语言的 Map

Go 语言中的Map

  Map 是一个无序的键值对数据集合,通过 key 快速检索数据。

  • 通过 map[keyType]valueType 格式声明 Map
package basic

import "fmt"

func DeclaratMap(){
    // 声明一个空的 map
    m1 := map[int]string{}
    fmt.Println(m1) // map[]

    // 声明一个 map 并赋初始值
    m2 := map[int]string{ 0: "Java", 1: "C#", 2: "Python", 3: "Golang" }
    fmt.Println(m2) // map[3:Golang 0:Java 1:C# 2:Python]
}
  • 通过 make(map[keyType]ValueType, cap) 格式声明 Map其中 cap 表示容量,可以省略,当 Map 超过设置的容量时,会自动扩展
package basic

import "fmt"

func DeclaratMapByMake() {
	m := make(map[int]string, 3)
	m[0] = "Java"
	m[1] = "C#"
	m[2] = "Python"

	fmt.Println(m, len(m)) // map[1:C# 2:Python 0:Java] 3

	m[3] = "Golang"
	fmt.Println(m, len(m)) // map[0:Java 1:C# 2:Python 3:Golang] 4
}

Mapkey 必须是支持 ==!= 比较运算符的数据类型。

  • 通过使用 for range 遍历 Map;
func TraverseMap() {
    m2 := map[int]string{0: "Java", 1: "C#", 2: "Python", 3: "Golang"}
    
    for key, value := range m2 {
        fmt.Println(key, ":", value)
    }
}
  • Map 是支持嵌套的,但是需要对嵌套后的 Map 进行初始化;
func NestedMap() {
    var array = [...]string{"Java", "C#", "Python", "Golang"}
    
   // 声明一个 嵌套 map 父级 key  类型为 int,value 为map
    m := make(map[int]map[string]int)
    
    for i, value := range array {
       // 判断嵌套的 map 是否初始化
        _, isMake := m[i][value]
        if !isMake {
            // 对嵌套的 map 初始化
            m[i] = make(map[string]int)
        }
        m[i][value] = i
    }
    
    fmt.Println(m) // map[0:map[Java:0] 1:map[C#:1] 2:map[Python:2] 3:map[Golang:3]]
}

Map 的操作

  • Map 的取值

  可以直接通过 map[key] 的方式取 Map 中的值,当 key 值不存在时,则会返回一个 value 的初始值;

func GetMapValue() {
    m1 := map[string]int{"Golang": 20, "Java": 30, "C#": 40}
    fmt.Println(m1["Python"]) // 0
}

   那么问题来了,如果我们想要通过用户提供的 key 获取的值做判断,key 如果不存在,则添加,该如何判断呢?

实际上, map[key] 有两个返回值,一个是根据 key 返回的值,另一个则是用于判断 key 是否存在

func GetMapValue() {
    m1 := map[string]int{"Golang": 20, "Java": 30, "C#": 40}
    if _, isExist := m1["Python"]; !isExist {
    	m1["Python"] = 10
    }
    fmt.Println(m1) // map[C#:40 Python:10 Golang:20 Java:30]
}
  • 删除 Map 中的数据

  使用 delete(map, key) 方法删除 Map 中的数据;

func DeleteMapData(){
    m1 := map[string]int{"Golang": 20, "Java": 30, "C#": 40}
    fmt.Println(m1) // map[Golang:20 Java:30 C#:40] 
    delete(m1, "Java")
    fmt.Println(m1) // map[Golang:20 C#:40]
}

【CSS 学习】transform 属性详解

transform 属性的值

  • translate(x,y)、translateX(x)、translateY(y)、translateZ(z)、translate3d(x,y,z) 定义位置的移动距离

  • scale(x,y)、scaleX(x)、scaleY(y)、scaleZ(z)、scale3d(x,yz) 定义元素的缩放比例

  • rotate(angle)、rotateX(angle)、rotateY(angle)、rotateZ(angle)、rotate3d(x,y,z,angle) 定义元素的旋转度

  • skew(x-angle,y-angle)、skewX(angle)、skewY(angle) 定义元素的倾斜度

3D效果认知

3D认知图片

perspective 属性

  该属性用于激活一个3D空间,其子元素都会获得透明效果,一般 perspective 属性用于父元素。

  • 取值为 none 或 不设置,则为不激活3D空间
  • 取值越小,3D效果越明显,建议取值为元素的宽度

transform-origin 属性

  用来改变元素原点的位置,取值:

  • center 默认值 等价于( center center / 50% 50%)
  • top/right/bottom/left
  • transform-origin : x y z

使用 transform 实现 3D 立方体 预览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>transform 实现 3D 立方体</title>
    <style>
        *{ margin: 0 auto; padding: 0; }
        html{
            cursor: pointer;
            background: #3023ae;
            background: -moz-linear-gradient(-45deg,  #3023ae 0%, #c86dd7 100%);
            background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#3023ae), color-stop(100%,#c86dd7));
            background: -webkit-linear-gradient(-45deg,  #3023ae 0%,#c86dd7 100%);
            background: -o-linear-gradient(-45deg,  #3023ae 0%,#c86dd7 100%);
            background: -ms-linear-gradient(-45deg,  #3023ae 0%,#c86dd7 100%);
            background: linear-gradient(135deg,  #3023ae 0%,#c86dd7 100%);
            background-attachment: fixed;
        }
        .wrap{
            margin-top: 100px;
            perspective: 800px;
            perspective-origin: 50% 100px;
        }
        .cube{
            margin: 0 auto;
            position: relative;
            width: 200px;
            height: 200px;
            color: #fff;
            font-size: 2rem;
            line-height: 200px;
            text-align: center;
            transform-style: preserve-3d;
            -webkit-user-select: none;
            -moz-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }
        .cube div{
            position: absolute;
            width: 200px;
            height: 200px;
            background-color: #333;
            opacity: 0.5;
            border: 1px solid #fff;
        }
        .front{
            -webkit-transform:translateZ(100px);
            -moz-transform: translateZ(100px);
            -o-transform: translateZ(100px);
            -ms-transform: translateZ(100px);
            transform: translateZ(100px);
        }
        .back{
            -webkit-transform:translateZ(-100px) rotateY(180deg);
            -moz-transform: translateZ(-100px) rotateY(180deg);
            -o-transform: translateZ(-100px) rotateY(180deg);
            -ms-transform: translateZ(-100px) rotateY(180deg);
            transform: translateZ(-100px) rotateY(180deg);
        }
        .left{
            -webkit-transform:translateX(-100px) rotateY(90deg);
            -moz-transform: translateX(-100px) rotateY(90deg);
            -o-transform: translateX(-100px) rotateY(90deg);
            -ms-transform: translateX(-100px) rotateY(90deg);
            transform: translateX(-100px) rotateY(90deg);
        }
        .right{
            -webkit-transform:translateX(100px) rotateY(-90deg);
            -moz-transform: translateX(100px) rotateY(-90deg);
            -o-transform: translateX(100px) rotateY(-90deg);
            -ms-transform: translateX(100px) rotateY(-90deg);
            transform: translateX(100px) rotateY(-90deg);
        }
        .top{
            -webkit-transform: translateY(-100px) rotateX(90deg);
            -moz-transform:  translateY(-100px) rotateX(90deg);
            -o-transform:  translateY(-100px) rotateX(90deg);
            -ms-transform: translateY(-100px) rotateX(90deg);
            transform: translateY(-100px) rotateX(90deg);
        }
        .bottom{
            -webkit-transform:  translateY(100px) rotateX(-90deg);
            -moz-transform: translateY(100px) rotateX(-90deg);
            -o-transform: translateY(100px) rotateX(-90deg);
            -ms-transform: translateY(100px) rotateX(-90deg);
            transform: translateY(100px) rotateX(-90deg);
        }
    </style>
</head>
<body>
    <div class="wrap">
        <div class="cube">
            <div class="front"></div>
            <div class="back"></div>
            <div class="left"></div>
            <div class="right"></div>
            <div class="top"></div>
            <div class="bottom"></div>
        </div>
    </div>
    <script>
        var mouseDown = false;
        var mousePoint = { x : 0 , y : 0 };
        var cubeRotate = { x : 0 , y : 0};

        window.onload = function(){
            document.onmousedown = function(e){
                mouseDown = true;
                mousePoint.x = e.pageX;
                mousePoint.y = e.pageY;
            }
            document.onmousemove = function(e){
                if(mouseDown){
                    let x = e.pageX - mousePoint.x;
                    let y = e.pageY - mousePoint.y;
                    cubeRotate.x += x / 30;
                    cubeRotate.y += y / 30;
                    document.querySelector('.cube').style = `transition:linear;transform:rotateX(${cubeRotate.x}deg) rotateY(${cubeRotate.y}deg)`;
                }  
            }
            document.onmouseup = function(e){
                mouseDown = false;
            }
        }
    </script>
</body>
</html>

【ECMAScript 6】3.Promise 的使用

Promise 的作用

  Promise 用于异步计算,可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。

  可以在对象之间传递和操作 Promise,帮助我们处理队列。

Promise 的声明

  new Promise(function(resolve,reject){
      resolve(); // 数据处理完成
      reject(); //数据处理出错
  }).then(function(res){
      // 成功数据 res
  },function(error){
      // 处理错误 error
  });
  • Promise 是一个代理对象,它和原先要进行的操作并无关系。
  • Promise 将回调函数改为链式调用解决回调地狱问题。

Promise 的状态

  一个 Promise 可能有三种状态:

  • 等待(pending):初始化一个 Promise 对象时。
  • 已完成(fulfilled):操作成功时。
  • 已拒绝(rejected):操作失败时。

一个 Promise 状态只可能从“等待”转到“完成”或“拒绝”,不能逆向转换,同时“完成”和“拒绝”不能相互转换。

  当 Promise 状态发生改变,就会触发 then 函数。

  then函数接受两个参数,第一个参数是成功时的回调,在 Promise 由“等待“状态转换到”完成“状态时调用;第二个参数是失败时回调,在 Promise 由“等待”状态转换为“拒绝”状态时调用。

  then可以接受另加一个 Promise 传入,也接受一个 “类then” 的对象或方法,即 thenable 对象。

  同一个 Promise 的 then 可以调用多次,并且回调的执行顺序跟它们被定义的顺序一致。

Promise 常用的函数

Promise.then()

  • Promise.then() 接受两个函数作为参数,分别代表 fulfilled 状态下的响应函数 和 rejected 状态下的响应函数。
  • Promise.then() 返回一个新的 Promise 实例,所以支持链式调用。
  • Promise 状态发生改变时,Promise.then() 会根据其返回的最终状态,调用相应的响应函数。
  • Promise.then() 中可以返回一个新的 Promise 或其他值。
  • 如果返回值为新的 Promise 时,那么下一级 Promise.then() 会在新的 Promise 状态改变后执行。
  • 如果返回值为其他值,那么下一级 Promise.then() 会立即执行。

在嵌套 Promise.then()时 ,由于 Promise.then() 返回的还是一个 Promise,所以下一级的 Promise.then() 会等到里面的 Promise.then() 执行完后再执行。

Promise.catch()

  Promise.catch()Promise.then(resolve,reject)reject 的别名,用于指定发生错误的回调函数。用法如下:

    new Promise((resolve,reject)=>{
    
    }).then(res=>{
        
    }).catch(error=>{
        
    });

Promise.catch() 也会返回一个 Promise,并且其状态也为 pending

reject 的作用等同于抛出一个异常。如果 Promise 状态已经变成 resolved ,再抛出错误是无效的

Promise.finally()

  Promise.finally() 是用于指定无论 Promise 最终状态如何,都会执行的操作,是在 ES2018 中引入的。用法如下:

    new Promise((resolve,reject)=>{
    
    }).then(res=>{
        
    }).catch(error=>{
        
    }).finally(()=>{
        
    });

Promise.finally()方法的回调函数不接受任何参数,因此Promise.finally()方法里面的操作,与Promise的状态无关。

Promise.finally() 本质上是 Promise.then() 的特例,Promise.finally() 方法总是会返回原来的值。

Promise.all()

  Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。使用方法如下:

    new Promise.all([p1,p2,p3]);

Promise.all 的参数可以不是数组,但参数必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

  Promise.all 的状态由传入的参数决定,分为两种情况:

  • 当数组中所有 Promise 状态完成,该 Promise 才算完成,其返回结果为全部值的数组。
  • 当其中任何一个失败,该 Promise 则失败,此时第一个被 reject 的实例的返回值。

Promise.race()

  Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。使用方法如下:

    new Promise.race([p1,p2,p3]);

只要参数中的任何一个状态更改,其返回结果则为率先改变的 Promise 实例。

Promise.resolve()

  Promise.resolve 方法可以将现有对象转为 Promise 对象,如下:

    const jsPromise = Promise.resolve($.ajax('/whatever.json'));

  Promise.resolve 方法的参数分为四种结果:

  • 参数是一个 Promise 实例
      Promise.resolve将不做任何修改,原封不懂的返回。

  • 参数是一个 thenable 对象

  thenable 对象指的是具有 then 方法的对象,如下:

    const thenable = {
        then: function(resolve,reject){
            resolve();
        }
    };

Promise.resolve 会将这个方法转为 Promise 对象然后立即执行 thenable 对象中的 then 方法。

  • 参数不是具有 then 方法的对象,或根本就不是对象
      Promise.resolve 会返回一个新的 Promise 对象的实例,其状态为 fulfilled

  • 不带有任何参数
      Promise.resolve 方法允许调用时不带参数,它回直接返回一个状态为 fulfilledPromise 实例。

Promise.reject()

   Promise.reject 也会返回一个 Promise 实例,状态为 rejected

     const jsPromise = Promise.reject('Error');

Promise.try()

   Promise.try就是模拟 try 代码块。

js 清空 input:file 的值

  由于javascript 不能清除 input:file 上传控件的值,因此最好的方法是在 input:file 上传控件的外层嵌入 <form> 元素,使用 <form> 元素的 reset() 方法来清除input:file 上传控件的值。代码如下:

    function clearFileInput(file) {
        var form = document.createElement('form');
        document.body.appendChild(form);
        var pos = file.nextSibling;
        form.appendChild(file);
        form.reset();
        pos.parentNode.insertBefore(file, pos);
        document.body.removeChild(form);
    }

【Golang 基础】Go 语言的指针

Go 语言的指针

  变量是一种使用方便的占位符,用于引用计算机地址,而在 Go 语言中可以通过 & 符号获取一个变量在计算机中对应的内存地址。

package basic

import "fmt"

func main(){
	a := 1
	fmt.Println(&a) // 0xc4200180a0
}

  一个指针变量指向了一个值的内存地址。Go 语言中的指针语法与 C++ 类似,都是使用 * 符号声明指针变量;

package basic

import "fmt"

func main(){
	a := 1
	var p *int = &a

	fmt.Printf("获取变量内存地址 %x\n", p) // 获取变量内存地址 c4200180a0
	fmt.Printf("获取指针变量值 %v", *p) // 获取指针变量值 1
}

Go 语言虽然有指针,但是没有指针算数,不能对其进行加减,但可以把指针值赋给另一个指针。这也就是 Golang 中的指针与 C++ 中指针的最大区别

可以通过将 unsafe.Pointer 转换为 uintptr,然后做变相指针运算。 uintptr 可以转换为整数。

值传递?引用传递?

  在学习引用类型语言时,我们首先要搞清楚,当给一个函数/方法传参的时候,使用的是指传递还是引用传递。实际上,大部分引用类型语言,参数为基本类型时,使用的是值传递。也就是另外复制了一份参数到当前的函数调用栈。参数为高级类型时,使用的是引用传递。这个主要是因为虚拟机的内存管理导致的。

  内存管理中的内存区域一般包括 堆(heap) 和 栈(stack) 主要用来存储当前调用栈用到的简单数据类型:string、boolean、int、float 等。这些类型的内存占用小,容易回收,基本上它们的值和指针占用的空间差不多,因此可以直接复制,GC 也比较容易做针对性的优化。复杂的高级类型占用的内存往往相对较大,存储在 堆(heap) 中,GC 回收率相对较低,代价也较大,因此传 引用/指针 可以避免进行成本较高的复制操作,并且节省内存,提高程序运行效率。

  因此,在以下情况下可以考虑使用指针:

  1. 需要改变参数的值;
  2. 避免复制操作;
  3. 节省内存;

而在 Golang 中,具体到高级类型 struct,slice,map 也各有不同。实际上,只有 struct 的使用有点复杂,slice,map,chan 都可以直接使用,不用考虑是值还是指针。

【算法详解】二叉树(Binary Tree)

什么是二叉树

  二叉树是一种具有层级特性的数据结构,它是由一系列节点组成,其每个节点最多只能有两个子节点。 如图所示:
Binary Tree

  • 节点值:节点所表示的值;

  • 孩子节点:一个节点的子节点,称之为该节点的 孩子节点;节点的左子节点,称为 左孩子节点;节点的右子节点,称为 右孩子节点

  • 根节点:一个节点位于树的顶部,并没有父节点;

  • 叶子节点:一个节点位于树的最底部,并没有孩子节点;

  • 节点层:根节点的层定义为 1;根的孩子节点为第二层,一次类推;

  • 树的深度:树中最大的节点层;

二叉排序树

  二叉排序树,又称为二叉查找树二叉搜索树。其特点为,左子树上所有的节点值均小于它的根节点值,右子树上所有的节点值均大于它的根节点值。且没有相等的节点值 如图所示:

Binary Sort Tree

二叉排序树的创建

  • JavaScript
'use strict'

class Node {
    constructor(key) {
        this.key = key;
        this.left = null;
        this.right = null;
    }

    insertNode(newNode) {
        /**
         * 二叉排序树的定义:
         * 左子树上所有子节点均小于其根节点,
         * 右子树上所有的子节点均大于其根节点
         */
        if (this.key > newNode.key) {
            if (this.left === null) {
                this.left = newNode;
            } else {
                this.left.insertNode(newNode);
            }
        } else {
            if (this.right === null) {
                this.right = newNode;
            } else {
                this.right.insertNode(newNode);
            }
        }
    }
}

function BinarySortTree(nodes) {
    let root = null;
    if (nodes && nodes.length > 0) {
        nodes.forEach(item => {
            const newNode = new Node(item);
            if (root === null) {
                root = newNode;
            } else {
                root.insertNode(newNode);
            }
        });
    }
    return root;
}

【读书笔记】《大话数据结构》 阅读

第一章 数据结构绪论

什么是数据结构

  数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关的问题的学科。

基本的概念

    程序设计 = 数据结构 + 算法

  数据:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。

  数据元素:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。也被称为记录。

  数据项:一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位。

  数据对象:是性质相同的数据元素集合,是数据的子集合。

  数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。

逻辑结构

  逻辑结构是指数据对象中数据元素之间的相互关系。逻辑结构可分为以下四种:

  1. 集合结构

  集合结构中的数据元素除了属于同一个集合外,它们之间没有其他关系。

  1. 线性结构

  线性结构中的数据元素之间是一对一的关系。

  1. 树形结构

  树形结构中的数据元素之间是一对多的关系。

  1. 图形结构

  图形结构中的数据元素之间是多对多的关系。

物理结构

  物理结构,又被称为存储结构。是指数据的逻辑结构在计算机中的存储形式。形式分为两种:

  1. 顺序存储结构

  顺序存储结构是指将数据元素存储在地址连续的存储单元内,其数据间的逻辑关系和数据关系是一致的。

  1. 链式存储结构

  链式存储结构是指将数据元素存储在任意的存储单元内,这组存储单元可以是连续的,也可以不是连续的。

  由于数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存储数据元素的地址,这样可以通过地址就可以找到相关联数据元素的位置。

逻辑结构 与 物理结构 的总结

  逻辑结构是面向问题,而物理结构是面向计算机的,其基本的目标就是将数据及其逻辑关系存储到计算机的内存中。

抽象数据类型

  • 数据类型

  数据类型是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。

  • 抽象

  抽象是指取出事物具有的普遍性的本质。其意义在于,数据类型的数学抽象特性。

  • 抽象数据类型

  抽象数据类型(ADT, Abstract Date Type)是指数学模型及定义在该模型上的一组操作。抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关。

  抽象数据类型体现了程序设计中的问题分解,抽象和信息隐藏的特性。

总结

  1. 介绍了数据结构的相关概念
  • 数据
    • 数据对象
      • 数据元素
        • 数据项
  1. 数据结构是相互之间存在一种或多种特定关系的数据元素结合。并对结构分类
  • 逻辑结构:集合结构、线性结构、树形结构、图形结构
  • 物理结构:顺序存储结构、链式存储结构
  1. 抽象数据类型

第二章 算法(Algorithm)

算法的定义

  算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。

算法的特性

  算法具有五个基本特性:输入、输出、有穷性、确定性和可行性。

  • 输入输出:算法具有零个或多个输入参数,和至少一个或多个输出结果。
  • 有穷性:指算法在执行有限步骤之后,自动结束且每个步骤在可接受的时间内完成。
  • 确定性:算法的每一步骤都具有确定的含义,不会出现二义性。
  • 可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过有限次数完成

算法设计的要求

  • 正确性:算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。
  • 可读性:算法设计的另一个目的是为了阅读、理解和交流。
  • 健壮性:当输入数据不合法时,算法也能做出相应处理,而不是残生异常或莫名其妙的结果。
  • 时间效率高和存储量低:时间效率是指算法执行时间,存储量是指算法在执行过程中需要的最大存储空间。好的算法需要满足时间效率高和存储量低的需求。

【Golang 基础】Go 语言的切片

Go 语言的切片

Slice 是一个通过指向数组底层,来进行变长数组的实现。

  • 定义切片的格式:var <sliceName> []<type>
package basic

import "fmt"

func DeclaratSlice(){
    
    // 定义一个空 slice
    var slice1 []int
    fmt.Println(slice1) // []

    // 定义一个 slice 并赋初始值
    slice2 := []int{ 1, 3, 4 }
    fmt.Println(slice2) // [1 3 4]
}
  • 通过 make([]<type>, len, cap) 格式来创建 slice。其中,len 表示 slice 的长度,cap 表示 slice 的容量;cap 的值默认情况下与 len 相等,cap 可以省略
package basic

import "fmt"

// 通过 make 声明 切片
func DeclaratSliceByMake() {
    var slice1 = make([]int, 6)
    fmt.Println(slice1) // [0 0 0 0 0 0]
}

可以通过内置函数 len()cap() 可以获取 slicelencap 的值

// 通过 make 声明 切片
func DeclaratSliceByMake() {
    var slice1 = make([]int, 6)
    fmt.Println(slice1) // [0 0 0 0 0 0]

    fmt.Println("len(slice1) = ", len(slice1)) // len(slice1) =  6
    fmt.Println("cap(slice1) = ", cap(slice1)) // cap(slice1) =  6
}

Reslice

  Reslice 表示将一个 Slice 再次 Slice

  • Resliceslice 都是指向同一底层数组;
  • Reslice 的索引以 slice 的索引为准,其最大容量(cap)为 slice 的容量;
  • 索引越界不会导致底层数组的重新分配,而是引发错误。
package basic

import "fmt"

// Reslice
func Reslice() {
    array := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    slice1 := array[2:5]
    slice2 := slice1[0:6]

    fmt.Println("array=", array, "len=", len(array), "cap=", cap(array)) // array= [0 1 2 3 4 5 6 7 8 9] len= 10 cap= 10
    fmt.Println("slice1=", slice1, "len=", len(slice1), "cap=", cap(slice1)) // slice1= [2 3 4] len= 3 cap= 8
    fmt.Println("slice2=", slice2, "len=", len(slice2), "cap=", cap(slice2)) // slice2= [2 3 4 5 6 7] len= 6 cap= 8
}

Slice 的操作

  • append

   append() 用于在 slice 后最追加新的元素,这些元素保存到底层数组,并不会影响原 slicez,它返回变更后新的 slice 对象。

  如果追加的元素超出之前的 slice 容量,则重新分配数组并拷贝原数据,并不影响底层数组。

package basic

import "fmt"

func SliceByAppend() {
    array := [...]int{0, 1, 2, 3}

    slice := array[:2]
    fmt.Printf("%p %v, cap = %d\n", slice, slice, cap(slice)) // 0xc42001c160 [0 1], cap = 4
    
    slice = append(slice, 10)
    fmt.Printf("%p %v\n", &array, array) // 0xc42001c160 [0 1 10 3] cap = 4
    fmt.Printf("%p %v, cap = %d\n", slice, slice, cap(slice)) // 0xc42001c160 [0 1 10], cap = 4
    
    slice = append(slice, 15, 20)
    fmt.Printf("%p %v cap = %d \n", &array, array, cap(array)) // 0xc42001c160 [0 1 10 3] cap = 4
    fmt.Printf("%p %v, cap = %d\n", slice, slice, cap(slice)) // 0xc420012240 [0 1 10 15 20], cap = 8
}
  • copy

  copy(目标 slice, 被拷贝的 slice),用于拷贝 slice,返回值为拷贝的个数。

package basic

import "fmt"

func SliceByCopy() {
    sliceA := []int{1, 2, 3, 4, 5}
    sliceB := []int{5, 4}
    
    copyCount := copy(sliceA, sliceB)
    
    fmt.Println(copyCount) // 2
    
    fmt.Println(sliceA) // [5 4 3 4 5]
    fmt.Println(sliceB) // [5 4]
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.