前端森林

前端森林 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织 github.com/Cosen95 编辑
编辑

个人动态

前端森林 收藏了文章 · 2018-09-20

前端工程师,揭开HTTP的神秘面纱

原文博客地址:https://finget.github.io/2018/07/03/http/

我只是个初学者,图片文字有些来自网上,写的不对的地方,还望大佬指出!

浏览器输入URL后HTTP请求返回过程

网络协议分层

OSI七层协议

五层协议

五层协议只是OSI和TCP/IP的综合,实际应用还是TCP/IP的四层结构。

TCP/IP 协议

TCP(Transmission Control Protocol)传输控制协议

TCP/IP协议将应用层、表示层、会话层合并为应用层,物理层和数据链路层合并为网络接口层

三种模型结构

各层的作用

1.物理层:
主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
  
2.数据链路层:
定义了如何让格式化数据以进行传输,以及如何让控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据的可靠传输。  
 
3.网络层:
在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层。
  
4.传输层:
定义了一些传输数据的协议和端口号(WWW端口80等),如:
TCP(transmission control protocol –传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据)
UDP(user datagram protocol–用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
  
5.会话层:
通过运输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)   

6.表示层:
可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如,PC程序与另一台计算机进行通信,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通格式来实现多种数据格式之间的转换。   

7.应用层:
是最靠近用户的OSI层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。

HTTP 发展历史

HTTP/0.9

  • 只有一个命令GET
  • 响应类型: 仅 超文本
  • 没有header等描述数据的信息
  • 服务器发送完毕,就关闭TCP连接

HTTP/1.0

  • 增加了很多命令(post HESD )
  • 增加status code 和 header
  • 多字符集支持、多部分发送、权限、缓存等
  • 响应:不再只限于超文本 (Content-Type 头部提供了传输 HTML 之外文件的能力 — 如脚本、样式或媒体文件)

HTTP/1.1

  • 持久连接。TCP三次握手会在任何连接被建立之前发生一次。最终,当发送了所有数据之后,服务器发送一个消息,表示不会再有更多数据向客户端发送了;则客户端才会关闭连接(断开 TCP)
  • 支持的方法: GET , HEAD , POST , PUT , DELETE , TRACE , OPTIONS
  • 进行了重大的性能优化和特性增强,分块传输、压缩/解压、内容缓存磋商、虚拟主机(有单个IP地址的主机具有多个域名)、更快的响应,以及通过增加缓存节省了更多的带宽

HTTP2

  • 所有数据以二进制传输。HTTP1.x是基于文本的,无法保证健壮性,HTTP2.0绝对使用新的二进制格式,方便且健壮
  • 同一个连接里面发送多个请求不再需要按照顺序来
  • 头信息压缩以及推送等提高效率的功能

三次握手

客服端和服务端在进行http请求和返回的工程中,需要创建一个TCP connection(由客户端发起),http不存在连接这个概念,它只有请求和响应。请求和响应都是数据包,它们之间的传输通道就是TCP connection

位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)

第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;(第一次握手,由浏览器发起,告诉服务器我要发送请求了)

第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;(第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧)

第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功(第三次握手,由浏览器发送,告诉服务器,我马上就发了,准备接受吧)

谢希仁著《计算机网络》中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”

URI、URL、URN

URI: Uniform Resource Identifier/统一资源标识符
URL: Uniform Resource Locator/统一资源定位器
URN: Uniform Resource Name/永久统一资源定位符

web上的各种资源(html、图片、视频、音频等)都由一个URI标识定位。URI相当于它们的详细“家庭住址”。

URI包含了URL和URN。

URL是URI的一种,不仅标识了Web 资源,还指定了操作或者获取方式,同时指出了主要访问机制和网络位置。

URN是URI的一种,用特定命名空间的名字标识资源。使用URN可以在不知道其网络位置及访问方式的情况下讨论资源。

网上的一个例子:

// 这是一个URI
http://bitpoetry.io/posts/hello.html#intro

// 资源访问方式
http://

// 资源存储位置
bitpoetry.io/posts/hello.html

#intro // 资源

// URL
http://bitpoetry.io/posts/hello.html

// URN
bitpoetry.io/posts/hello.html#intro

HTTP报文

请求报文:

响应报文:

HTTP 各种特性

curl

curl命令是一个利用URL规则在命令行下工作的文件传输工具。它支持文件的上传和下载,所以是综合传输工具,但按传统,习惯称curl为下载工具。作为一款强力工具,curl支持包括HTTP、HTTPS、ftp等众多协议,还支持POST、cookies、认证、从指定偏移处下载部分文件、用户代理字符串、限速、文件大小、进度条等特征。做网页处理流程和数据检索自动化,curl可以祝一臂之力。

更详细的CURL,点这里。

curl 访问 baidu.com:

返回的内容中,html部分只有一个meta标签,<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">,这是因为我们访问的是baidu.com,在浏览器中,浏览器会自动解析这个meta标签并重定向到http://www.baidu.com/,然而命令行中并没有解析的功能。

curl 访问 www.baidu.com:

curl常用命令

-v 显示详细的请求信息

-X 指定请求方式

curl -X GET www.xxxx.com/xx/xx?xx=123

curl -X POST www.xxxx.com/xx/xx?xx=123

-o / -O 保存下载的文件

// 将文件下载到本地并命名为mygettext.html
curl -o mygettext.html http://www.gnu.org/software/gettext/manual/gettext.html

// 将文件保存到本地并命名为gettext.html
curl -O http://www.gnu.org/software/gettext/manual/gettext.html

CORS跨域请求的限制与解决

// server1.js
const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  const html = fs.readFileSync('test.html', 'utf8')
  response.writeHead(200, {
    'Content-Type': 'text/html'
  })
  response.end(html)
}).listen(8888)

console.log('server listening on 8888')
// server2.js
const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.end('123')
}).listen(8887)

console.log('server listening on 8887')
// test.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>Document</title>
</head>
<body>
    
</body>
<script>
  fetch('http://127.0.0.1:8887');
</script>
</html>

处理方法:
1.服务器端处理

// server2.js 服务器端设置允许跨域
response.writeHead(200, {
  'Access-Control-Allow-Origin': '*' // * 表示任何域名下都可以访问这个服务,也可以指定域名
})

2.jsonp

// test.html
<script data-original="http://127.0.0.1:8887"></script>
就算存在跨域,请求还是会发送,响应也会返回,只是浏览器端发现了存在跨域问题就将返回内容屏蔽了,并报错提示。

CORS 预请求

// test.html
<script>
  fetch('http://127.0.0.1:8887',{
    method: 'post',
    headers: {
      'X-Test-Cors': '123'
    }
  });
</script>

我们设置的请求头中X-Test-Cors在跨域请求的时候,不被允许。

虽然不允许跨域,但是请求仍然会发送,并返回成功。

默认允许的请求方法:

  • GET
  • HEAD
  • POST

其他的方法(PUT、DELETE)都需要预请求验证的。

默认允许的Content-Type:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

怎样设置允许我们设置的请求头:

// server2.js 
response.writeHead(200, {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'X-Test-Cors' // 加上这个设置
})

首先发送一个预请求,预请求就是告诉浏览器接下来要发送的post请求是被允许的。

设置允许的请求方法:

// server2.js 
response.writeHead(200, {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'X-Test-Cors',
  'Access-Control-Allow-Methods': 'POST, PUT, DELETE'
})

设置一个安全时间:

// server2.js 
response.writeHead(200, {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'X-Test-Cors',
  'Access-Control-Allow-Methods': 'POST, PUT, DELETE',
  'Access-Control-Max-Age': '1000'
})

Access-Control-Max-Age的单位是秒,意思就是在多少秒以内,我们设置的这些允许的请求头,请求方法,是不需要发送预请求验证的,直接就可以通过,并发送。

缓存Cache-Control

常用值:

Cache-Control说明
public所有内容都将被缓存(客户端和代理服务器都可缓存)
private内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
no-cache必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。
no-store所有内容都不会被缓存到缓存或 Internet 临时文件中
must-revalidation/proxy-revalidation如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
max-age=xxx (xxx is numeric)缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
// server.js
const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  if (request.url === '/') {
    const html = fs.readFileSync('test.html', 'utf8')
    response.writeHead(200, {
      'Content-Type': 'text/html'
    })
    response.end(html)
  }

  if (request.url === '/script.js') {
    response.writeHead(200, {
      'Content-Type': 'text/javascript',
      'Cache-Control': 'max-age=20,public' // 缓存20s 多个值用逗号分开
    })
    response.end('console.log("script loaded")')
  }
}).listen(8888)

console.log('server listening on 8888')
// test.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>Document</title>
</head>
<body>
    
</body>
<script data-original="/script.js"></script>
</html>

刷新会发现script.js是从缓存中获取的,请求时间也是0。

我们希望浏览器缓存我们的图片,文件、js代码,但是服务器端代码更新了,浏览器端还是在缓存中获取的旧的文件。这就诞生了,webpack打包中出现的文件名后加上hash值,当文件改变时hash值也改变,这样浏览器就会发送新的请求到服务器端。

缓存验证

验证头:

  • Last-Modified

上次修改时间
配合If-Modified-Since或者If-Unmodified-Since使用
对比上次修改时间以验证资源是否需要更新

  • Etag

数据签名(内容修改,签名就会改变)
配合If-Match或者If-Non-Match使用
对比资源的签名判断是否使用缓存

Redirect

const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  if (request.url === '/') {
    response.writeHead(302, {  // or 301
      'Location': '/new'
    })
    response.end()
  }
  if (request.url === '/new') {
    response.writeHead(200, {
      'Content-Type': 'text/html',
    })
    response.end('<div>this is content</div>')
  }
}).listen(8888)

console.log('server listening on 8888')

302临时跳转,301永久跳转,301从缓存种获取跳转,使用301之后,主动权就掌握在用户手里,如果用户不清理缓存,那就算服务器端改变了也没用。

Content Security Policy (网页安全政策)

阮一峰:Content Security Policy 入门教程

HTTPS

HTTPS和HTTP的区别主要为以下四点:
一、https协议需要到ca申请证书,一般免费证书很少,需要交费。
二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

查看原文

前端森林 关注了用户 · 2018-09-20

FinGet @bios

前端 —— 我一直在路上!

关注 102

前端森林 发布了文章 · 2018-09-20

深入http协议原理

对于Web开发者而言,掌握HTTP协议是技术成长的必经之路。只有夯实HTTP基础,才可能在Web这条路上走的更远。

网络协议分层

经典五层模型

图片描述

  • 应用层

为应用软件提供了很多服务
构建于TCP协议之上
屏蔽网络传输相关细节

  • 传输层

向用户提供可靠的端到端(End-to-End)服务
传输层向高层屏蔽了下层数据通信的细节

  • 网络层

网络层为数据在结点之间传输创建逻辑链路

  • 数据链路层

数据链路层在通信的实体间建立数据链路连接

  • 物理层

物理层主要作用是定义物理设备如何传输数据

Http协议发展历史

http/0.9

  • 只有一个命令GET
  • 没有HEADER等描述数据的信息
  • 服务器发送完毕,就关闭TCP连接

http/1.0

  • 增加了很多命令
  • 增加status code和header
  • 多字符集支持、多部分发送、权限、缓存等

http/1.1

  • 持久连接
  • pipeline
  • 增加host和其他一些命令

http2

  • 所有数据以二进制传输
  • 同一个连接里面发送多个请求不再需要按照顺序来
  • 头信息压缩以及推送等提高效率的功能

HTTP的三次握手

图片描述

URI、URL、URN

URI

  • Uniform Resource Identifier/统一资源标志符

URL

  • Uniform Resource Locator/统一资源定位器
  • http://user:pass@host.com:80/path?query=string#hash(此类格式的都叫做URL,比如ftp协议)

URN

  • 永久统一资源定位符
  • 在资源移动之后还能被找到
  • 目前还没有非常成熟的使用方案
查看原文

赞 1 收藏 0 评论 0

前端森林 收藏了文章 · 2018-09-20

Charles mac版配置

charles配置(mac版)

1. 配置mac浏览器抓包

(1)、首先打开 Charles:
图片描述
图片描述

(2)然后如下如操作:
图片描述

(3)、之后会弹出钥匙串,如果不弹出,请自行打开钥匙串,如下图:

图片描述

(4)系统默认是不信任 Charles 的证书的,此时对证书右键,在弹出的下拉菜单中选择『显示简介』,点击使用此证书时,把使用系统默认改为始终信任,如下图:

图片描述

(5)然后关闭,就会发现 charles 的证书已经被信任了,如下图:

图片描述

(6)、设置mac代理:macOS

图片描述

(7)、与上一步同一个页面,设置代理:Proxies

图片描述

(8)、打开 macOSProxy

图片描述

(9)、设置链接的WIFI

图片描述
图片描述

(10)、打开charles就可以获取到所有页面的请求了

2、配置移动设备抓包

如下图,选择在移动设备上安装 Charles 根证书:

图片描述

会弹出一个提示框,如下图:

图片描述

然后打开手机的浏览器,输入
charlesproxy.com/getssl 会弹出如下界面:

图片描述

点击安装即可。

进入手机设置界面:

图片描述

这里以简书为例…

图片描述

此时还是获取不到 https 的数据,各位童鞋不要着急,下面还有操作,接着还是进入 Charles ,如下图操作:

图片描述

如下图,勾选Enable SSL Proxying,点击添加,弹出下面的对话框,Host 表示你要抓取的 ip 地址或是链接,Port 填写 443 即可:

图片描述

设置完成后,就可以抓取数据啦,如下图:

图片描述

原文链接:https://blog.csdn.net/yangmen...
查看原文

前端森林 收藏了文章 · 2018-09-17

算法基础之简单算法

本文包括简单的线性算法和一些数值计算,还会继续更新

rgb 和 16进制互相转换

function rgb2hex(r,g,b){
  return "#" + ((r<<16)+(g<<8)+b).toString(16);
}
function hex2rgb(str){
  var arr = str.match(/[0-9a-f]{2}/ig);
  return {
    r: parseInt(arr[0], 16),
    g: parseInt(arr[1], 16),
    b: parseInt(arr[2], 16)
  };
}

找出整型数组中乘积最大的三个数

var original_array = [-10, -7, -29, -4, -1, -10, -70];
var result = findMPTN(original_array);
console.log(result);

function findMPTN(arr){    //findMaxiumProductorOfThreeNumbers
  var len = arr.length;
  sorted_arr = arr.sort(function(a,b){return a-b;});
  var pro1 = 1, pro2 = sorted_arr[len - 1];
  for(var i = 1; i <= 3; i++){
    pro1 *= sorted_arr[len - i];
  }
  pro2 *= sorted_arr[0];
  pro2 *= sorted_arr[1];

  return pro1 > pro2 ?
  [sorted_arr[len - 3], sorted_arr[len - 2], sorted_arr[len - 1]] :
  [sorted_arr[0], sorted_arr[1], sorted_arr[len - 1]];
}

判断大括号是否闭合

var expression = "{{}}{}{}"
var expressionFalse = "{}{{}";

console.log(isBalanced(expression)); // true
console.log(isBalanced(expressionFalse)); // false
console.log(isBalanced("")); // true

function isBalanced(exp){
  var stack = [];
  var arr = exp.split("");
  var len = arr.length, cur;
  while(cur = arr.shift()){
    if(cur === "{") stack.push(cur);
    if(cur === "}") stack.pop();
  }
  if(stack.length !== 0) return false;
  return true;
}

使用两个栈实现入队与出队

Array.prototype.enqueue = function(item){
  return this.push(item);
};
Array.prototype.dequeue = function(){
  var tempStack = [];
  var cur, temp;
  while(cur = this.pop()){
    tempStack.push(cur);
  }
  temp = tempStack.pop();
  while(cur = tempStack.pop()){
    this.push(cur);
  }
  return temp;
};

寻找连续数组中的缺失的多个数

var array = [2, 5, -1, 9, -6, 3, 7];
var result = findLost(array);
console.log(result);

function findLost(arr){
  if(arr.length <= 1) return null;
  var sortedArr = arr.sort(function(a,b){return a-b;});
  var i = sortedArr.shift();
  var cur = sortedArr.shift();
  var result = [];
  do{
    i++;
    if(cur === i) cur = sortedArr.shift();
    else result.push(i);
  }while(cur);
  return result;
}

数组中元素最大差值计算

给定某无序数组,求取任意两个元素之间的最大差值,注意,这里要求差值计算中较小的元素下标必须小于较大元素的下标。

var array = [7, 8, 4, 9, 9, 15, 3, 1, 10];
var result = findLargestDifference(array);
console.log(result);
function findLargestDifference(arr){
  var min = arr[0];
  var diff = 0;
  for(var i = 1, len = arr.length; i < len; i++){
    if(arr[i] < min){
      min = arr[i];
      continue;
    }
    if(arr[i] - min > diff){
      diff = arr[i] - min;
    }
  }
  return diff;
}

数组中元素乘积

给定某无序数组,要求返回新数组 output ,其中 output[i] 为原数组中除了下标为 i 的元素之外的元素乘积,要求以 O(n) 复杂度实现:

var firstArray = [2, 2, 4, 1];
var secondArray = [0, 0, 0, 2];
var thirdArray = [-2, -2, -3, 2];

console.log(productExceptSelf(firstArray)); // [8, 8, 4, 16]
console.log(productExceptSelf(secondArray)); // [0, 0, 0, 0]
console.log(productExceptSelf(thirdArray)); // [12, 12, 8, -12]

function productExceptSelf(arr){
  var result = [];
  var pro = 1;
  var len = arr.length;
  for(var i = 0; i < len; i++){
    result.push(pro);
    pro *= arr[i];
  }
  pro = 1;
  for(i = len - 1; i >= 0; i--){
    result[i] *= pro;
    pro *= arr[i];
  }
  return result;
}

数组扁平化

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

//传统方式
function flat(arr,result=[]){
  if(arr.constructor !== Array) return [arr];
  var length = arr.length;
  arr.forEach(function(item){
    if(item.constructor !== Array) result.push(item);
    else result = flat(item, result);
  });
  return result;
}
var flatted = flat(arr);
console.log(flatted);          //[1, 2, 1, 3, 2, 2, 3, 3, 2, 5, 6, 3]

//优雅方式
var arr=[1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]];
var flatter = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatter(b) : b), []);
console.log(flatter(arr));        //[1, 2, 1, 3, 2, 2, 3, 3, 2, 5, 6, 3]

//另一个方法,简单但有副作用:把数组内的值全部转换成了字符串类型
var flatten = a => a.join().split(',');
console.log(flatten(arr));     //["1", "2", "1", "3", "2", "2", "3", "3", "2", "5", "6", "3"]

查找字符串中出现次数最多的字符及数量

"ababccdeaeajxac".split('').sort().join('').match(/(\w)\1*/g).reduce(function(a,b){ return a.length > b.length ? a : {char: b[0], length: b.length};}, {char: '', length: 0});       //{char: "a", length: 5}

字符串查找

String.prototype.indexOf = String.prototype.indexOf || function(str){
  if(str.length > this.length) return -1;
  var len = 0;
  var _this = this.split(''), str = str.split('');
  var lenA = str.length, this_len = this.length;
  var temp;
  for(var i = 0, j = 0; j < lenA; i = 0, j = temp + 1, len = 0){
    debugger;
    while(str[i] !== _this[j] && j < this_len){
      j++;
    }
    temp = j;
    while(str[i] === _this[j] && j < this_len){
      len++;
      i++;
      j++;
    }
    if(len === lenA) return temp;
  }
  return -1;
}

字符串查找(KMP 算法)

String.prototype.indexOf = String.prototype.indexOf || function(str){
  var next = [];
  var n = this.length;
  var m = str.length;
  calcNext(str,next);
  for (var i = 0,q = 0; i < n; ++i){
      while(q > 0 && str[q] != this[i])
          q = next[q-1];
      if (str[q] === this[i]){
          q++;
      }
      if (q === m){
          return i - m + 1;
      }
  }
  return -1;


  function calcNext(str){
    var m = str.length;
    next[0] = 0;
    for(var q = 1, k = 0; q < m; ++q){
        while(k > 0 && str[q] != str[k])
            k = next[k-1];
        if (str[q] == str[k]){
            k++;
        }
        next[q] = k;
    }
  }
}

查看链表是否有环

function hasCircle(head){   //传入链表头
  var pos1 = head;
  var pos2 = head;
  while(pos2){
    pos1 = pos1.next;
    pos2 = pos2.next !== null ? pos2.next.next : null;
    if(pos1 === pos2) return true;
  }
  return false;
}

求一个数二进制中 1 的个数

function numberOf1(n){
    if(n < 0){
        n = n >>> 0;
    }
    var arr = n.toString(2).split('');
    return arr.reduce(function(a,b){
        return b === "1" ? a + 1 : a;
    },0);
}

翻转链表

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function reverseList(pHead){
    var newHead, temp;
    if(!pHead){
        return null;
    }
    if(pHead.next === null){
        return pHead;
    } else {
        newHead = ReverseList(pHead.next);
    }
    temp = pHead.next;
    temp.next = pHead;
    pHead.next = null;
    temp = null;
    return newHead;
}

判断是否栈序输出

例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
function isPopOrder(pushV, popV){
    if(!pushV.length || !popV.length){
        return false;
    }
    var temp = [];
    var popIndex = 0;
    var len = pushV.length;
    for(var i = 0; i < len; i++){
        temp.push(pushV[i]);
        while(temp.length && temp[temp.length - 1] === popV[popIndex]){
            temp.pop();
            popIndex++;
        }
    }
    return temp.length === 0;
}

获取字符串中字符的全排列(无重复)

function permutation(str){
    if(!str || str.length === 0){
        return [];
    }
    var result = [];
    var arr = str.split('');
    var temp = '';
    ordering(arr);
    result = result.filter(function(item, index){  //去重
      return result.indexOf(item) === index;
    });
    return result;

    function ordering(tempArr){
        if(tempArr.length === 0){
            result.push(temp);
            return;
        }
        for(var i = 0; i < tempArr.length; i++){
            temp += tempArr[i];
            insideArr = tempArr.concat();
            insideArr.splice(i,1);
            ordering(insideArr);
            temp = temp.substring(0, temp.length - 1);   //回溯
        }
    }
}

查找有环链表的入环节点

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function EntryNodeOfLoop(pHead){
    if(!pHead){
        return null;
    }
    var meeting = meetingNode(pHead);
    if(!meeting){
        return null;
    }
    var nodeLoop = 1;
    var node1 = meeting;
    while(node1.next != meeting){
        node1 = node1.next;
        nodeLoop++;
    }
    node1 = pHead;
    for(var i = 0; i < nodeLoop; i++){
        node1 = node1.next;
    }
    var node2 = pHead;
    while(node1 != node2){
        node1 = node1.next;
        node2 = node2.next;
    }
    return node1;

    function meetingNode(node){
        if(!node || !node.next){
            return null;
        }
        var slow = node.next;
        var fast = slow.next;

        while(fast && slow){
            if(fast === slow){
                return fast;
            }
            slow = slow.next;
            fast = fast.next;
            if(fast){
                fast = fast.next;
            }
        }
        return null;
    }
}
查看原文

前端森林 发布了文章 · 2018-09-17

react进阶

react生命周期

定义

生命周期函数指在某一个时刻组件会自动调用执行的函数

图片描述

Initialization

  • 进行props和state的初始化

Mounting:

componentWillMount ---> render ---> componentDidMount
  • render:当组件的state或者props发生改变的时候,render函数就会重新执行
  • componentWillMount:在组件即将被挂载到页面的时刻自动执行
  • componentDidMount:组件被挂载到页面之后,自动执行

Updation

props:componentWillReceiveProps ---> shouldComponentUpdate(为true继续执行,为false停止向下执行) ---> componentWillUpdate ---> render ---> componentDidUpdate
state:shouldComponentUpdate(为true继续执行,为false停止向下执行) ---> componentWillUpdate ---> render ---> componentDidUpdate
  • shouldComponentUpdate:组件更新之前,会自动被执行
  • componentWillUpdate :组件更新之前,会自动被执行,但他是在shouldComponentUpdate之后执行。如果shouldComponentUpdate返回true才执行,返回false则不会被执行。
  • componentDidUpdate:组件更新之后,会自动被执行
  • componentWillReceiveProps:执行应满足的条件1.组件要从父组件接受参数2.这个子组件第一次存在于父组件中,不会执行3.这个子组件之前已经存在于父组件中,才会执行。

UnMounting

  • componentWillUnmount:组件即将从页面中卸载时执行

使用 PropTypes 进行类型检查

React 有一些内置的类型检查功能。要检查组件的属性,你需要配置特殊的 propTypes 属性:

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

PropTypes 包含一整套验证器,可用于确保你接收的数据是有效的。在这个示例中,我们使用PropTypes.string。当你给属性传递了无效值时,JavsScript 控制台将会打印警告。出于性能原因,propTypes 只在开发模式下进行检查。

属性默认值defaultProps

你可以通过配置 defaultProps 为 props定义默认值:

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// 为属性指定默认值:
Greeting.defaultProps = {
  name: 'Stranger'
};

// 渲染 "Hello, Stranger":
ReactDOM.render(
  <Greeting />,
  document.getElementById('example')
);

defaultProps 用来确保 this.props.name 在父组件没有特别指定的情况下,有一个初始值。类型检查发生在 defaultProps 赋值之后,所以类型检查也会应用在 defaultProps 上面。

Fragment

React还提供了一个组件,用于在没有外层父元素包裹的情况下渲染多个元素:

import { Component, Fragment }from 'react';

class Greeting extends Component {
  render() {
    return (
        <Fragment>
          <img data-original="" alt="">
          <p>How are you?</p>
          <h1>Hello, world</h1>
        </Fragment>
    );
  }
}

虚拟DOM

原生实现

  • state数据
  • JSX模板
  • 数据+模板结合,生成真实的DOM,来显示
  • state发生改变
  • 数据+模板结合,生成真实的DOM,替换原始的DOM

缺陷:

  • 第一次生成了一个完整的DOM片段
  • 第二次生成了一个完整的DOM片段
  • 第二次的DOM替换第一次的DOM,非常耗性能

第一种方案

  • state数据
  • JSX模板
  • 数据+模板结合,生成真实的DOM,来显示
  • state发生改变
  • 数据+模板结合,生成真实的DOM,并不直接替换原始的DOM
  • 新的DOM和原始的DOM作比对,找出差异
  • 找出input变化的部分(假设是input发生了改变)
  • 只用新的DOM中的input发生改变的部分,替换老的DOM中的input部分

缺陷

性能的提升不够明显,DOM的比对也很耗性能

虚拟DOM实现

  • state数据
  • JSX模板
  • 数据+模板结合,生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)(损耗了性能)(['div',{id:'test'},['span',{},'hello jack']
  • 用虚拟DOM的结构生成真实的DOM,来显示(<div id="test"><span>hello jack</span></div>)
  • state发生变化
  • 数据+模板生成新的虚拟DOM,(极大的提升了性能)(['div',{id:'test'},['span',{},'hello lily']
  • 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容发生了改变(极大的提升了性能)
  • 直接操作DOM,改变span中的内容

react中间件

中间件的概念

为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?

(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

想来想去,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。举例来说,要添加日志功能,把 ActionState 打印出来,可以对store.dispatch进行如下改造。

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

上面代码中,对store.dispatch进行了重定义,在发送 Action 前后添加了打印功能。这就是中间件的雏形。

中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

图片描述

redux-logger

日志中间件:添加日志功能,把 ActionState 打印出来

store.js

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);

react-thunk

大家知道,Action是由store.dispatch方法发送的。而store.dispatch方法正常情况下,参数只能是对象,不能是函数。这时,就要使用中间件redux-thunk

store.js

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?  
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers(
  applyMiddleware(thunk),
);

const store = createStore(reducer, enhancer);

export default store;

actionCreators.js

import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST } from './actionTypes'
import axios from '../axios/index';

export const getInputChangeAction = (value)=> ({
    type: CHANGE_INPUT_VALUE,
    value
});

export const getAddItemAction = ()=> ({
    type: ADD_TODO_ITEM
});

export const getDeleteItemAction = (value)=> ({
    type: DELETE_TODO_ITEM,
    value
});

export const getInitList = (value)=> ({
    type: INIT_LIST,
    value
});

// 这里参数为函数
export const getTodoList = ()=> {
    return ((dispatch) => {
        axios.ajax({
            url: '/itemList',
            method: 'get',
            data:{
                params:{ id:156 }
            }
        }).then((res) => {
            const newArr = [];
            res.data.item_list.forEach(item => {
                newArr.push(item.name);
            })
            const action = getInitList(newArr);
            dispatch(action);
        })
    })
}

上面代码使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。

因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch

react-saga

redux-saga是一个用于管理redux应用异步操作的中间件,redux-saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件。

store.js

import { createStore, applyMiddleware, compose } from 'redux';
// import thunk from 'redux-thunk';
import createSagaMiddleware from 'redux-saga'
import reducer from './reducer';
import mySagas from './sagas';

const sagaMiddleware = createSagaMiddleware()

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?  
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers(
  applyMiddleware(sagaMiddleware),
);

const store = createStore(reducer, enhancer);

sagaMiddleware.run(mySagas);

export default store;

saga.js

import { takeEvery, put } from 'redux-saga/effects';
import { INIT_SAGA_LIST } from './actionTypes';
import { getInitList } from './actionCreators';
import axios from '../axios/index';
import 'antd/dist/antd.css';
import { Modal } from 'antd';

function* getInitSagaList() {
    try {
        const res = yield axios.ajax({
            url: '/itemList',
            method: 'get',
            data: {
                params: {
                    id: 156
                }
            }
        })
        const newArr = [];
        res.data.item_list.map(item => {
            newArr.push(item.name);
        })
        const action = getInitList(newArr);
        yield put(action);
    } catch (e) {
        Modal.info({
            title: '提示',
            content: '服务繁忙,请稍后重试'
        })
    }
    
}

// generator函数
function* mySaga() {
    yield takeEvery(INIT_SAGA_LIST, getInitSagaList);
}

export default mySaga;

react-redux

UI 组件

React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。

UI 组件有以下几个特征:

1.只负责 UI 的呈现,不带有任何业务逻辑
2.没有状态(即不使用this.state这个变量)
3.所有数据都由参数(this.props)提供
4.不使用任何 Redux 的 API

因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。

容器组件

容器组件的特征恰恰相反:

1.负责管理数据和业务逻辑,不负责 UI 的呈现
2.带有内部状态
3.使用 Redux 的 API

总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。

connect()

React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

import { connect } from 'react-redux';
const mapStateToProps = (state)=> {
    return {
        inputValue: state.inputValue,
        list: state.list
    }
}
// store.dispatch props
const mapDispatchToProps = (dispatch)=> {
    return {
        handleInputChange(e) {
            const action = {
                type: 'change_input_value',
                value: e.target.value
            }
            dispatch(action);
        },
        handleSubmit() {
            const action = {
                type: 'add_todo_item'
            }
            dispatch(action);
        },
        handleDelete(index) {
            const action = {
                type: 'delete_todo_item',
                index
            }
            dispatch(action);
        }
    }
}
export default connect(mapStateToProps,mapDispatchToProps)(TodoList);

上面代码中,connect方法接受两个参数:mapStateToPropsmapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action

mapStateToProps()

mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。

作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。请看下面的例子。

const mapStateToProps = (state)=> {
    return {
        inputValue: state.inputValue,
        list: state.list
    }
}

上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。这个对象有一个inputValue属性,代表 UI 组件的同名参数。

mapDispatchToProps()

mapDispatchToPropsconnect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store

const mapDispatchToProps = (dispatch)=> {
    return {
        handleInputChange(e) {
            const action = {
                type: 'change_input_value',
                value: e.target.value
            }
            dispatch(action);
        },
        handleSubmit() {
            const action = {
                type: 'add_todo_item'
            }
            dispatch(action);
        },
    }
}

从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,定义了 UI 组件的参数怎样发出 Action

<Provider> 组件

connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。

React-Redux 提供Provider组件,可以让容器组件拿到state

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
import { Provider } from 'react-redux';
import store from './store';

const App = (
    <Provider store={store}>
        <TodoList />
    </Provider>            
)

ReactDOM.render(App, document.getElementById('root'));

上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。

查看原文

赞 0 收藏 0 评论 0

前端森林 收藏了文章 · 2018-09-17

前端最实用书签(持续更新)

前言

一直混迹社区,突然发现自己收藏了不少好文但是管理起来有点混乱;
所以将前端主流技术做了一个书签整理,不求最多最全,但求最实用。

书签源码

书签导入浏览器效果截图
书签

书签源码请戳github地址
Chrome---设置---书签---导入书签就可在浏览器即可使用

1.ES6,ES7和ES8

2.vue

2.1 api

2.2 vue源码

2.3 插件

2.3 项目

2.4 vue-cli

2.5 vue高级

3.react

3.1 api

3.2 ui组件

3.3 项目

3.4 源码解析

4.微信小程序

4.1 api

4.2 插件

4.3 项目

电商项目1
小程序demo

5.Hybrid

5.1 通讯

5.2 适配和兼容

6.webpack

7.npm

npm配置

8.node.js

8.1 api

8.2 框架

8.3项目

9.高德地图

9.css

10.性能优化

11.安全

12.前端架构

13.算法

14.前端测试

15.常用工具

16.浏览器

17.大佬博客

18.面试

结语

后期会继续优化维护这些书签
为了便于知识归纳,所以将有些作者的文章标题修改了,谢谢优秀作者的文章分享!

查看原文

前端森林 赞了文章 · 2018-09-17

前端最实用书签(持续更新)

前言

一直混迹社区,突然发现自己收藏了不少好文但是管理起来有点混乱;
所以将前端主流技术做了一个书签整理,不求最多最全,但求最实用。

书签源码

书签导入浏览器效果截图
书签

书签源码请戳github地址
Chrome---设置---书签---导入书签就可在浏览器即可使用

1.ES6,ES7和ES8

2.vue

2.1 api

2.2 vue源码

2.3 插件

2.3 项目

2.4 vue-cli

2.5 vue高级

3.react

3.1 api

3.2 ui组件

3.3 项目

3.4 源码解析

4.微信小程序

4.1 api

4.2 插件

4.3 项目

电商项目1
小程序demo

5.Hybrid

5.1 通讯

5.2 适配和兼容

6.webpack

7.npm

npm配置

8.node.js

8.1 api

8.2 框架

8.3项目

9.高德地图

9.css

10.性能优化

11.安全

12.前端架构

13.算法

14.前端测试

15.常用工具

16.浏览器

17.大佬博客

18.面试

结语

后期会继续优化维护这些书签
为了便于知识归纳,所以将有些作者的文章标题修改了,谢谢优秀作者的文章分享!

查看原文

赞 737 收藏 621 评论 11

前端森林 收藏了文章 · 2018-09-17

用 Homebrew 带飞你的 Mac

Homebrew也称brewmacOS下基于命令行的最强大软件包管理工具,使用Ruby语言开发。类似于CentOSyum或者Ubuntuapt-getbrew能方便的管理软件的安装、更新、卸载,省去了手动编译或拖动安装的不便,更使得软件的管理更加集中化,解决了依赖包管理的烦恼。

原文地址:https://shockerli.net/post/ma...

资料

安装

Homebrew 依赖于Xcode Command Line Tools,所以需先执行:

xcode-select --install

在终端中执行:

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

检查是否已安装成功:

$ brew -v

Homebrew 1.6.1
Homebrew/homebrew-core (git revision 0aeb7; last commit 2018-04-12)

基本用法

基于brew安装的所有软件及其依赖均会安装到目录/usr/local/Cellar
  • Brew 帮助信息

    $ brew help
    
    Example usage:
      brew search [TEXT|/REGEX/]
      brew info [FORMULA...]
      brew install FORMULA...
      brew update
      brew upgrade [FORMULA...]
      brew uninstall FORMULA...
      brew list [FORMULA...]
    
    Troubleshooting:
      brew config
      brew doctor
      brew install --verbose --debug FORMULA
    
    Contributing:
      brew create [URL [--no-fetch]]
      brew edit [FORMULA...]
    
    Further help:
      brew commands
      brew help [COMMAND]
      man brew
      https://docs.brew.sh
  • 子命令帮助信息
    brew help [COMMAND]brew [COMMAND] -h 用于查看具体某个子命令的帮助信息。

    例如,查看install命令的帮助详情:

    $ brew install -h
    
    brew install [--debug] [--env=(std|super)] [--ignore-dependencies|--only-dependencies] [--cc=compiler] [--build-from-source|--force-bottle] [--include-test] [--devel|--HEAD] [--keep-tmp] [--build-bottle] [--force] [--verbose] formula [options ...]:
        Install formula.
    
        formula is usually the name of the formula to install, but it can be specified
        in several different ways. See [SPECIFYING FORMULAE](#specifying-formulae).
    
        If --debug (or -d) is passed and brewing fails, open an interactive debugging
        session with access to IRB or a shell inside the temporary build directory.
    
        If --env=std is passed, use the standard build environment instead of superenv.
    
        If --env=super is passed, use superenv even if the formula specifies the
        standard build environment.
    
        If --ignore-dependencies is passed, skip installing any dependencies of
        any kind. If they are not already present, the formula will probably fail
        to install.
    
        If --only-dependencies is passed, install the dependencies with specified
        options but do not install the specified formula.
    
        If --cc=compiler is passed, attempt to compile using compiler.
        compiler should be the name of the compiler's executable, for instance
        gcc-4.2 for Apple's GCC 4.2, or gcc-4.9 for a Homebrew-provided GCC
        4.9.
    
        If --build-from-source (or -s) is passed, compile the specified formula from
        source even if a bottle is provided. Dependencies will still be installed
        from bottles if they are available.
    
        If HOMEBREW_BUILD_FROM_SOURCE is set, regardless of whether --build-from-source was
        passed, then both formula and the dependencies installed as part of this process
        are built from source even if bottles are available.
    
        If --force-bottle is passed, install from a bottle if it exists for the
        current or newest version of macOS, even if it would not normally be used
        for installation.
    
        If --include-test is passed, install testing dependencies. These are only
        needed by formulae maintainers to run brew test.
    
        If --devel is passed, and formula defines it, install the development version.
    
        If --HEAD is passed, and formula defines it, install the HEAD version,
        aka master, trunk, unstable.
    
        If --keep-tmp is passed, the temporary files created during installation
        are not deleted.
    
        If --build-bottle is passed, prepare the formula for eventual bottling
        during installation.
    
        If --force (or -f) is passed, install without checking for previously
        installed keg-only or non-migrated versions
    
        If --verbose (or -v) is passed, print the verification and postinstall steps.
    
        Installation options specific to formula may be appended to the command,
        and can be listed with brew options formula.
    
    brew install --interactive [--git] formula:
        If --interactive (or -i) is passed, download and patch formula, then
        open a shell. This allows the user to run ./configure --help and
        otherwise determine how to turn the software package into a Homebrew
        formula.
    
        If --git (or -g) is passed, Homebrew will create a Git repository, useful for
        creating patches to the software.
  • 搜索软件
    brew search [TEXT|/REGEX/] 用于搜索软件,支持使用正则表达式进行复杂的搜索。

    例如,查询静态博客生成工具hugo

    $ brew search hugo
    
    ==> Searching local taps...
    hugo ✔
    ==> Searching taps on GitHub...
    ==> Searching blacklisted, migrated and deleted formulae...
  • 查看软件信息
    brew info [FORMULA...] 用于查询软件的详细信息。

    例如,查看软件hugo的详细信息:

    $ brew info hugo
    
    hugo: stable 0.38.2 (bottled), HEAD
    Configurable static site generator
    https://gohugo.io/
    /usr/local/Cellar/hugo/0.38.2 (32 files, 28.0MB) *
      Poured from bottle on 2018-04-19 at 10:11:29
    From: https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git/Formula/hugo.rb
    ==> Dependencies
    Build: dep ✔, go ✔
    ==> Options
    --HEAD
        Install HEAD version
    ==> Caveats
    Bash completion has been installed to:
      /usr/local/etc/bash_completion.d

    以上查询所得信息,包含了软件的最新可用版本,本机是否已安装,本机已安装的版本,安装的路径、大小、时间、Tap 源,所依赖的包,以及安装的可选项等详细信息。而这些信息可以帮助我们很方便快捷的了解如何对该软件进行相应的操作。

  • 安装软件包
    brew install FORMULA... 用于安装一个或多个软件。

    例如,安装软件hugo

    $ brew install hugo

    安装软件命令执行之前,brew 一般会先检查更新 Homebrew 自身及 Tap 源。

  • 更新软件包
    brew upgrade [FORMULA...] 用于更新一个或多个软件,不指定软件名则更新所有软件。
  • 卸载软件包
    brew uninstall FORMULA... 用于卸载指定的一个或多个软件

    brew uninstall --force FORMULA... 彻底卸载指定软件,包括旧版本

  • 已安装的软件列表
    brew list 用于查询本机已安装的软件列表
  • 服务管理
    brew services 用于方便的管理 brew 安装的软件软件,类似于 Linux 下的 service 命令。

    查看brew services帮助信息:

    $ brew services
    
    brew services command:
        Integrates Homebrew formulae with macOS' launchctl manager.
    
        [sudo] brew services list
        List all running services for the current user (or root)
    
        [sudo] brew services run formula|--all
        Run the service formula without starting at login (or boot).
    
        [sudo] brew services start formula|--all
        Start the service formula immediately and register it to launch at login (or boot).
    
        [sudo] brew services stop formula|--all
        Stop the service formula immediately and unregister it from launching at login (or boot).
    
        [sudo] brew services restart formula|--all
        Stop (if necessary) and start the service immediately and register it to launch at login (or boot).
    
        [sudo] brew services cleanup
        Remove all unused services.
    
        If sudo is passed, operate on /Library/LaunchDaemons (started at boot).
        Otherwise, operate on ~/Library/LaunchAgents (started at login).

    查询服务列表:

    $ brew services list
    
    Name      Status  User    Plist
    redis     stopped
    php56     started shocker /usr/local/opt/php56/homebrew.mxcl.php56.plist
    mongodb   started shocker /usr/local/opt/mongodb/homebrew.mxcl.mongodb.plist
    memcached stopped
    httpd     stopped
    nginx     started shocker /usr/local/opt/nginx/homebrew.mxcl.nginx.plist
    etcd      stopped
    mysql@5.6 started shocker /usr/local/opt/mysql@5.6/homebrew.mxcl.mysql@5.6.plist
  • 检查可更新的软件列表
    brew outdated 可查询有更新版本的软件

    brew outdated
    wget (1.18) < 1.19.4_1
    redis (3.2.8) < 4.0.9
    brotli (1.0.3) < 1.0.4
    glib (2.56.0) < 2.56.1
    etcd (3.2.6) < 3.3.3
    tmux (2.3_2) < 2.6
    openssl@1.1 (1.1.0e) < 1.1.0h
    mysql@5.6 (5.6.34) < 5.6.39
    libzip (1.5.0) < 1.5.1
    bash-completion (1.3_1) < 1.3_3
  • 清理软件
    brew cleanup -n 列出需要清理的内容
    brew cleanup 清理所有的过时软件
    brew cleanup [FORMULA] 清理指定软件的过时包

    $ brew cleanup
    
    Removing: /usr/local/Cellar/wget/1.18... (9 files, 1.6MB)
    Removing: /usr/local/Cellar/libxml2/2.9.4_2... (277 files, 9.8MB)
    Warning: Skipping redis: most recent version 4.0.9 not installed
    Warning: Skipping brotli: most recent version 1.0.4 not installed
    Removing: /usr/local/Cellar/icu4c/60.1... (249 files, 66.9MB)
    Removing: /usr/local/Cellar/icu4c/60.2... (249 files, 67MB)
    Warning: Skipping glib: most recent version 2.56.1 not installed
    Removing: /usr/local/Cellar/readline/7.0... (45 files, 2MB)
    Removing: /usr/local/Cellar/unixodbc/2.3.4... (39 files, 952.3KB)
    Removing: /Users/shocker/Library/Logs/Homebrew/go... (64B)
    Removing: /Users/shocker/Library/Logs/Homebrew/glide... (64B)
    ...
    ==> This operation has freed approximately 214.1MB of disk space.
  • 查看配置信息
    brew config 用于查看 brew 所在环境及相关的配置情况

    $ brew config
    
    HOMEBREW_VERSION: 1.6.1
    ORIGIN: https://github.com/Homebrew/brew.git
    HEAD: 5a2817cb023ca5e929fe030d423002992bdabf1b
    Last commit: 7 days ago
    Core tap ORIGIN: https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git
    Core tap HEAD: 0aeb738f9f10ba3edd4edda9551fe11441bec458
    Core tap last commit: 11 days ago
    HOMEBREW_PREFIX: /usr/local
    HOMEBREW_BOTTLE_DOMAIN: https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles
    CPU: quad-core 64-bit haswell
    Homebrew Ruby: 2.3.3 => /usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.3.3/bin/ruby
    Clang: 9.1 build 902
    Git: 2.15.1 => /Library/Developer/CommandLineTools/usr/bin/git
    Curl: 7.54.0 => /usr/bin/curl
    Java: 9.0.4
    macOS: 10.13.4-x86_64
    CLT: 9.3.0.0.1.1521514116
    Xcode: N/A
    XQuartz: N/A
  • 诊断问题
    brew doctor 诊断当前 brew 存在哪些问题,并给出解决方案
  • 仓库管理
    brew tap 已安装的仓库列表

    $ brew tap
    
    Updating Homebrew...
    caskroom/cask
    homebrew/core
    homebrew/services
    peckrob/php
    phinze/cask

    brew tap [--full] user/repo [URL] 添加仓库

    brew untap tap 移除仓库

源镜像

如果不用源镜像,那么就自带梯子或自建服务吧~

原文地址:https://shockerli.net/post/ma...
查看原文

前端森林 发布了文章 · 2018-09-15

mac 终端利器 iTerm2

之前一直使用的是mac自带的终端,由于之前命令操作较少,也就无所谓了。但是现在随着命令行操作的频繁,原有的终端开发效率太低,偶然发现了iTerm2,发现功能很强大也非常好用,下面总结一下安装步骤和相应的配置。

图片描述

安装iTerm2

下载地址:https://www.iterm2.com/downlo...

也可以通过Homebrew来安装

$ brew cask install iterm2

配置iTerm2主题

iTerm2 最常用的主题是 Solarized Dark theme

下载地址:http://ethanschoonover.com/so...

下载的是压缩文件,解压后,打开iTerm2,按Command+,键,打开Preferences配置页面,然后Profiles -> Colors -> Color

Presets -> Import,选择刚解压的压缩文件solarized->iterm2-colors-solarized->Solarized Dark.itermcolors文件,导

入成功,最后选择Solarized Dark 主题,就可以了。
图片描述

配置 Oh My Zsh

Oh My Zsh 是对主题的进一步扩展,地址:https://github.com/robbyrusse...

一键安装:

$ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

安装好之后,需要把 Zsh 设置为当前用户的默认 Shell(这样新建标签的时候才会使用 Zsh):

$ chsh -s /bin/zsh

然后,我们编辑vim ~/.zshrc文件,将主题配置修改为ZSH_THEME="agnoster"

clipboard.png

agnoster是比较常用的 zsh 主题之一,你可以挑选你喜欢的主题,zsh 主题列表:https://github.com/robbyrusse...
配置后,效果如下:

clipboard.png

配置 Meslo 字体

使用上面的主题,需要 Meslo 字体支持,要不然会出现乱码的情况,字体下载地址:Meslo LG M Regular for Powerline.ttf

下载好之后,直接在 Mac OS 中安装即可。

然后打开 iTerm2,按Command + ,键,打开 Preferences 配置界面,然后Profiles -> Text -> Font -> Chanage Font,选择

Meslo LG M Regular for Powerline 字体

图片描述

自动提示填充

这个功能非常的实用,可以提高我们的开发效率

先克隆zsh-autosuggestions项目,到指定目录:

$ git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions

然后编辑vim ~/.zshrc文件,找到plugins配置,增加zsh-autosuggestions插件。

clipboard.png

iTerm2 快捷命令

命令说明
command + t新建标签
command + w关闭标签
command + 数字 command + 左右方向键切换标签
command + enter切换全屏
command + f查找
command + d垂直分屏
command + shift + d水平分屏
command + option + 方向键 command + [ 或 command + ]切换屏幕
command + ;查看历史命令
command + shift + h查看剪贴板历史
ctrl + u清除当前行
ctrl + l清屏
ctrl + a到行首
ctrl + e到行尾
ctrl + f/b前进后退
ctrl + p上一条命令
ctrl + r搜索命令历史

欢迎关注我的微信公众号~前端森林

前端森林.jpeg

查看原文

赞 9 收藏 6 评论 1

前端森林 发布了文章 · 2018-09-14

手动配置webpack

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。现在框架对于webpack都进行了

集成,但是作为开发的我们,应该手动配置webpack,以此对其打包编译原理有更详细的认识。

最简单的webpack配置

webpack四个核心概念:

  • 入口(entry)

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,

webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

  • 输出(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本

上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个 output 字

段,来配置这些处理过程:

  • loader

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以

将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行

处理。

  • 插件(plugins)

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压

缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项

(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作

符来创建它的一个实例。

webpack.config.js

const path = require('path');

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

HtmlWebpackPlugin

HtmlWebpackPlugin简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着

编译而发生变化哈希的 webpack bundle 尤其有用。

安装

npm install html-webpack-plugin@2.30.1 --save-dev
yarn add html-webpack-plugin@2.30.1 --dev

基本用法

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

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
  plugins: [new HtmlWebpackPlugin()]
};

会生成一个包含以下内容的文件 dist/index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack App</title>
  </head>
  <body>
    <script data-original="index_bundle.js"></script>
  </body>
</html>

babel-loader

将es6代码解析成es5代码

安装

npm install babel-loader babel-core babel-preset-env --save-dev
yarn add babel-loader@7.1.2 babel-core@6.26.0 babel-preset-env@1.6.1 --dev

基本使用

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]
}

babel-preset-react

安装

yarn add babel-preset-react@6.24.1 --dev

基本使用

module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['env','react']
          }
        }
      }
    ]
},

style-loader css-loader

为了从 JavaScript 模块中 import 一个 CSS 文件,需要在 module 配置中 安装并添加 style-loader和

css-loader。

安装

npm install --save-dev style-loader css-loader
yarn add style-loader@0.19.1  css-loader@0.28.8 --dev

基本使用

const path = require('path');

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

ExtractTextWebpackPlugin

它会将所有的入口 chunk(entry chunks)中引用的 *.css,移动到独立分离的 CSS 文件。因此,你的样式将不

再内嵌到 JS bundle 中,而是会放到一个单独的 CSS 文件(即 styles.css)当中。 如果你的样式文件大小较

大,这会做更快提前加载,因为 CSS bundle 会跟 JS bundle 并行加载。

安装

npm install --save-dev extract-text-webpack-plugin@1.0.1
 yarn add extract-text-webpack-plugin@3.0.2 --dev

基本使用

const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("styles.css"),
  ]
}

sass-loader

node-sass 和 webpack 是 sass-loader 的 peerDependency,因此能够精确控制它们的版本。

安装

npm install sass-loader node-sass webpack --save-dev

基本使用

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          //如果需要,可以在 sass-loader 之前将 resolve-url-loader 链接进来
          use: ['css-loader', 'sass-loader']
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('style.css')
    //如果想要传入选项,你可以这样做:
    //new ExtractTextPlugin({
    //  filename: 'style.css'
    //})
  ]
}

font-awesome

图片描述

Font Awesome 字体提供了可缩放矢量图标,它可以被定制大小、颜色、阴影以及任何可以用CSS的样式。webpack中配置字体图标前需要提前引入font-awesome库,安装也很简单:

yarn add font-awesome

url-loader

处理图片、字体图标,url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL

安装

yarn add file-loader@1.1.6 url-loader@0.6.2 --dev

基本使用

module.exports = {
  module: {
    rules: [
    // 图片的处理
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      },
      // 字体图标的处理
      {
            test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
            use: [
                {
                    loader: 'url-loader',
                    options: {
                        limit: 8192
                    }

                }
            ]
      }
    ]
  }
}

CommonsChunkPlugin

CommonsChunkPlugin 插件,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。

通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存中供后续使用。这个带来页面速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。

new webpack.optimize.CommonsChunkPlugin(options);

 // 提出公共模块
new webpack.optimize.CommonsChunkPlugin({
    // (公共 chunk(commnon chunk) 的名称)
    name: 'common',
    // (公共chunk 的文件名)
    filename: 'js/base.js'
})

webpack-dev-server

webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。

安装

yarn add webpack-dev-server@2.9.7 --dev

基本使用

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

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
     devtool: 'inline-source-map',
     devServer: {
       // contentBase: './dist',
       // 修改端口号
       port: 8086
   },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Development'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

完整版webpack.config.js

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: './src/app.jsx',
    output: {
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/dist/',
        filename: 'js/app.js'
    },
    module: {
        rules: [
            // react(jsx)处理
            {
                test: /\.jsx$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['env', 'react']
                    }
                }
            },
            // css处理
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: "style-loader",
                    use: "css-loader"
                })
            },
            // scss处理
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'sass-loader']
                })
            },
            // 图片的处理
            {
                test: /\.(png|jpg|gif)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        name: 'resource/[name].[ext]'
                    }
                }]
            },
            //字体图标的处理
            {
                test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        name: 'resource/[name].[ext]'
                    }

                }]
            }
        ]
    },
    plugins: [
        // 处理html文件
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        // 独立css文件
        new ExtractTextPlugin("css/[name].css"),
        // 提出公共模块
        new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            filename: 'js/base.js'
        })
    ],
    devServer: {
        // contentBase: './dist'
        port: 8086
    },
};

package.json

{
  "name": "react-e-commerce-manage",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "",
  "author": "",
  "license": "MIT",
  "scripts": {
    "dev": "node_modules/.bin/webpack-dev-server",
    "build": " node_modules/.bin/webpack"
  },
  "devDependencies": {
    "babel-core": "6.26.0",
    "babel-loader": "7.1.2",
    "babel-preset-env": "1.6.1",
    "babel-preset-react": "6.24.1",
    "css-loader": "0.28.8",
    "extract-text-webpack-plugin": "3.0.2",
    "file-loader": "1.1.6",
    "html-webpack-plugin": "2.30.1",
    "node-sass": "^4.9.3",
    "sass-loader": "6.0.6",
    "style-loader": "0.19.1",
    "url-loader": "0.6.2",
    "webpack": "3.10.0",
    "webpack-dev-server": "2.9.7"
  },
  "dependencies": {
    "font-awesome": "^4.7.0",
    "react": "16.2.0",
    "react-dom": "16.2.0"
  }
}
查看原文

赞 3 收藏 3 评论 0

前端森林 发布了文章 · 2018-09-14

Markdown---常用语法

Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用。Markdown 可谓是十分轻量的,学习成本也不需要太多,且一旦熟悉这种语法规则,会有一劳永逸的效果。

语法规则

标题

clipboard.png

标题是每篇文章都需要也是最常用的格式,在 Markdown 中,如果一段文字被定义为标题,只要在这段文字前加 # 号即可。

# 一级标题

## 二级标题

### 三级标题

以此类推,总共六级标题,建议在井号后加一个空格,这是最标准的 Markdown 语法。

列表

clipboard.png

熟悉 HTML 的同学肯定知道有序列表与无序列表的区别,在 Markdown 下,列表的显示只需要在文字前加上 - 或 * 即可变为无序列表,有序列

表则直接在文字前加1. 2. 3. 符号要和文字之间加上一个字符的空格。

引用

clipboard.png

如果你需要引用一小段别处的句子,那么就要用引用的格式。

例如这样

只需要在文本前加入 > 这种尖括号(大于号)即可

图片与链接

clipboard.png

插入链接与插入图片的语法很像,区别在一个 !号

  • 图片为:![]()
  • 链接为:[]()

粗体/斜体

Markdown 的粗体和斜体也非常简单,用两个 包含一段文本就是粗体的语法,用一个 包含一段文本就是斜体的语法。

表格

表格相对来说麻烦一点

| 城市        | 景点           | 最佳观光时间  |
| ------------- |:-------------:| -----:|
| 北京      | 长城 | 九月 |
| 上海      | 迪士尼      |  七月 |
| 厦门 | 鼓浪屿      |    六月 |
城市景点最佳观光时间
北京长城九月
上海迪士尼七月
厦门鼓浪屿六月

代码(程序员必备。。。。)

Markdown下实现代码块分两种情况:

  • 单行代码:用``将代码包裹

hello world

  • 多行代码:将``` 置于这段代码的首末两行
export default const judgeArr=(arr)=>{
    if(Array.isArray(arr)){
        return true;
    }
}

分割线

Markdown中的分割线用三个*号包裹即可。


查看原文

赞 0 收藏 0 评论 0

前端森林 评论了文章 · 2018-09-14

前端开发实用工具类函数

抽空整理了一下日常开发中会经常用到的工具函数

检测是否是数组

export default const judgeArr=(arr)=>{
    if(Array.isArray(arr)){
        return true;
    }
}

数组的”短路运算”every和some

情况一:全部满足
 
export const allTrueArr=(arrs)=>{
  return arr.every((arr)=>{
     return arr>20;//如果数组的每一项都满足则返回true,如果有一项不满足返回false,终止遍历
  })  
}
 
情况二:有一个满足
export default const OneTrueArr=(arrs)=>{
  return arr.some((arr)=>{
     return arr>20;//如果数组有一项满足则返回true,终止遍历,每一项都不满足则返回false
  })  
}

多维数组转一维

// 递归
let newArr = [];

function flatten(arr) {
    for(let i = 0; i < arr.length; i++) {
        if(Array.isArray(arr[i])) {
            flatten(arr[i])
        } else {
            newArr.push(arr[i])
        }
    }
}

// reduce+递归
let arr = [1, [2, [[3, 4], 5], 6]];
const flatten = arr => arr.reduce((total,current) => {
    total.concat(Array.isArray(current) ? flatten(current) : current)
}, [])

获取当前的时间yyyy-MM-dd HH:mm:ss(没有满10就补0)

export default const obtainDate=()=>{
  let date = new Date();
  let year = date.getFullYear();
  let month = date.getMonth() + 1;
  let day=date.getDate();
  let hours=date.getHours();
  let minu=date.getMinutes();
  let second=date.getSeconds();
  //判断是否满10
  let arr=[month,day,hours,minu,second];
  arr.forEach(item=>{
    item< 10?"0"+item:item;
  })
  return year+'-'+arr[0]+'-'+arr[1]+' '+arr[2]+':'+arr[3]+':'+arr[4]      
}

函数防抖(debounce)和节流(throttle)

Debounce
原理:通过重复调用函数,清空定时器,在函数不再被调用的时候触发一次

function debounce(method,delay){
    var timer = null;
    return function(){
        vat _that = this;
        var args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function(){
            method.apply(_that,args);
        },delay)
    }
}

Throttle
原理:通过判断时间间隔、间隔一定的时间频率调用函数

function throttle(fn,delay,duration){
    // fn 待执行函数 / delay 定时器延时时间 / duration 间隔时间
    var timer = null;
    var begin = +new Date();
    return function(){
        var _that = this;
        var now = +new Date();
        clearTimeout(timer);
        if(duration){
            if(now - begin >= duration){
                fn.apply(_that,arguments);
                begin = now;
            }
        } else {
            timer = setTimeout(fn,delay);
        }
    }
}

setTimeout(f, 0)

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f, 0),那么会立刻执行吗?

答案是不会。因为必须要等到当前脚本的同步任务,全部处理完以后,才会执行setTimeout指定的回调函数f。也就是说,setTimeout(f, 0)会

在下一轮事件循环一开始就执行。

setTimeout(function () {
  console.log(1);
}, 0);
console.log(2);
// 2
// 1

上面代码先输出2,再输出1。因为2是同步任务,在本轮事件循环执行,而1是下一轮事件循环执行。

总之,setTimeout(f, 0)这种写法的目的是,尽可能早地执行f(放在异步任务队列的前面优先执行),但是并不能保证立刻就执行f。

获取url参数(QueryString)

正则法:

function getQueryString (name) {
    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)','i');
    var r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return unescape(r[2]);
    }
    return null;
}
//调用
getQueryString ("参数名");

        OR

var getParam = function (name) {
    var search = document.location.search;
    //alert(search);
    var pattern = new RegExp("[?&]" + name + "\=([^&]+)", "g");
    var matcher = pattern.exec(search);
    var items = null;
    if (null != matcher) {
        try {
            items = decodeURIComponent(decodeURIComponent(matcher[1]));
        } catch (e) {
            try {
                items = decodeURIComponent(matcher[1]);
            } catch (e) {
                items = matcher[1];
            }
        }
    }
    return items;
};

split拆分法:

function GetRequest() {
    var url = location.search; //获取url中"?"符后的字串
    var theRequest = new Object();
    if (url.indexOf("?") != -1) {
        var str = url.substr(1);
        strs = str.split("&");
        for(var i = 0; i < strs.length; i ++) {
            theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
        }
    }
    return theRequest;
}
var Request = new Object();
Request = GetRequest();

// 调用
// var 参数1,参数2,参数3,参数N;
// 参数1 = Request['参数1'];
// 参数2 = Request['参数2'];
// 参数3 = Request['参数3'];
// 参数N = Request['参数N'];

后续会有更多实用工具函数。。。。。

查看原文

前端森林 发布了文章 · 2018-09-13

IconFont图标引用

前端开发会经常用到一些图标。当ui妹子给你提供的图标不能满足你的需求时,可以在 iconfont.cn 上采集并生成自己的业务图标库,再进行使用。

一、生成图标库代码

首先,搜索并找到你需要的图标,将它采集到你的购物车里,在购物车里,你可以将选中的图标添加到项目中(没有的话,新建一个),后续生成

的资源/代码都是以项目为维度的。

clipboard.png

来到刚才选中的项目页,点击『生成代码』的链接,会在下方生成不同引入方式的代码,下面会分别介绍。

clipboard.png

二、引入

有三种引入方式供你选择:SVG Symbol、Unicode 及 Font class。我们推荐在现代浏览器下使用 SVG Symbol 方式引入。

SVG Symbol

SVG 符号引入是现代浏览器未来主流的图标引入方式。其方法是预先加载符号,在合适的地方引入并渲染为矢量图形。有如下特点:

  1. 支持多色图标,不再受到单色图标的限制
  2. 通过一些技巧,支持像字体那样,通过 font-size、color 来调整样式
  3. 支持IE 9+ 及现代浏览器

使用步骤如下:

  • 切换到 Symbol 页签,复制项目生成的地址代码:
//at.alicdn.com/t/font_835630_0rudypqb4a.js
  • 加入图标样式代码,如果没有特殊的要求,你可以直接复用 Ant Design 图标的样式
.icon {
  width: 1em;
  height: 1em;
  fill: currentColor;
  vertical-align: -.125em;
}
  • 挑选相应图标并获取类名,应用于页面
<svg class="icon" aria-hidden="true">
    <use xlink:href="#icon-ali-pay"></use>
</svg>

你也可以通过使用 Ant Design 图标组件提供的 Icon.createFromIconfontCN({...}) 方法来更加方便地使用图标,使用方式如下:

  • 配置项目地址,创建图标组件
import { Icon } from 'antd';

const IconFont = Icon.createFromIconfontCN({
  scriptUrl: '//at.alicdn.com/t/font_405362_lyhvoky9rc7ynwmi.js'
});

export default IconFont;
  • 之后可以像使用 <Icon /> 组件一样方便地使用,支持配置样式
<IconFont type="icon-ali-pay" style={{ fontSize: '16px', color: 'lightblue' }} />

Unicode

这是最原始的方式,需要三步来完成引入:

  • 拷贝项目生成的字体库代码,你可以新建一个样式文件来放置图标相关的样式。
@font-face {
  font-family: 'iconfont';
  src: url('//at.alicdn.com/t/font_405362_lyhvoky9rc7ynwmi.eot');
  src: url('//at.alicdn.com/t/font_405362_lyhvoky9rc7ynwmi.eot?#iefix') format('embedded-opentype'),
  url('//at.alicdn.com/t/font_405362_lyhvoky9rc7ynwmi.woff') format('woff'),
  url('//at.alicdn.com/t/font_405362_lyhvoky9rc7ynwmi.ttf') format('truetype'),
  url('//at.alicdn.com/t/font_405362_lyhvoky9rc7ynwmi.svg#iconfont') format('svg');
}
  • 加入图标样式代码,如果没有特殊的要求,你可以直接复用 Ant Design 图标的样式。
.iconfont {
  display: inline-block;
  font-style: normal;
  vertical-align: baseline;
  text-align: center;
  text-transform: none;
  line-height: 1;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  &:before {
    display: block;
    font-family: "iconfont" !important;  /* 注意与 font-face 中的匹配 */
  }
}
  • 在项目中鼠标移动到要用的图标上,点击『复制代码』,就得到了图标对应的字体编码,现在可以直接引入了:
<i class="iconfont">&#xe66b;</i>

Font Class

  • 切换到 Font class 页签,在页面头部引入下面生成的 css 代码:
//at.alicdn.com/t/font_835630_0rudypqb4a.css
如果不喜欢标签引入的方式,也可以直接拷贝上面链接中的代码到你的样式文件中。如果不喜欢网站默认生成的类名,自己重写这部分代码即可,比如:
 - .icon-ali-pay:before { content: "\e66b"; }              // 修改前
 - .monitor-icon-alipay:before { content: "\e66b"; }       // 修改后
  • 这时你可以选择拷贝图标对应代码(就是类名,如果类名被重写过,这里记得用修改后的),直接使用:
<i class="iconfont icon-ali-pay"></i>

不过我们更推荐将它封装一下:

import React from 'react';

const BizIcon = (props) => {
  const { type } = props;
  return <i className={`iconfont icon-${type}`} />;
};
export default BizIcon;

现在可以更加方便地使用:

<BizIcon type="ali-pay" />

Unicode 和 Font Class 本质上就是字体,你可以通过一些字体的样式属性去控制这种图标的展现,同时浏览器兼容性很好,但不支持多色图标。

查看原文

赞 3 收藏 1 评论 0

前端森林 发布了文章 · 2018-09-12

前端开发实用工具类函数

抽空整理了一下日常开发中会经常用到的工具函数

检测是否是数组

export default const judgeArr=(arr)=>{
    if(Array.isArray(arr)){
        return true;
    }
}

数组的”短路运算”every和some

情况一:全部满足
 
export const allTrueArr=(arrs)=>{
  return arr.every((arr)=>{
     return arr>20;//如果数组的每一项都满足则返回true,如果有一项不满足返回false,终止遍历
  })  
}
 
情况二:有一个满足
export default const OneTrueArr=(arrs)=>{
  return arr.some((arr)=>{
     return arr>20;//如果数组有一项满足则返回true,终止遍历,每一项都不满足则返回false
  })  
}

多维数组转一维

// 递归
let newArr = [];

function flatten(arr) {
    for(let i = 0; i < arr.length; i++) {
        if(Array.isArray(arr[i])) {
            flatten(arr[i])
        } else {
            newArr.push(arr[i])
        }
    }
}

// reduce+递归
let arr = [1, [2, [[3, 4], 5], 6]];
const flatten = arr => arr.reduce((total,current) => {
    total.concat(Array.isArray(current) ? flatten(current) : current)
}, [])

获取当前的时间yyyy-MM-dd HH:mm:ss(没有满10就补0)

export default const obtainDate=()=>{
  let date = new Date();
  let year = date.getFullYear();
  let month = date.getMonth() + 1;
  let day=date.getDate();
  let hours=date.getHours();
  let minu=date.getMinutes();
  let second=date.getSeconds();
  //判断是否满10
  let arr=[month,day,hours,minu,second];
  arr.forEach(item=>{
    item< 10?"0"+item:item;
  })
  return year+'-'+arr[0]+'-'+arr[1]+' '+arr[2]+':'+arr[3]+':'+arr[4]      
}

函数防抖(debounce)和节流(throttle)

Debounce
原理:通过重复调用函数,清空定时器,在函数不再被调用的时候触发一次

function debounce(method,delay){
    var timer = null;
    return function(){
        vat _that = this;
        var args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function(){
            method.apply(_that,args);
        },delay)
    }
}

Throttle
原理:通过判断时间间隔、间隔一定的时间频率调用函数

function throttle(fn,delay,duration){
    // fn 待执行函数 / delay 定时器延时时间 / duration 间隔时间
    var timer = null;
    var begin = +new Date();
    return function(){
        var _that = this;
        var now = +new Date();
        clearTimeout(timer);
        if(duration){
            if(now - begin >= duration){
                fn.apply(_that,arguments);
                begin = now;
            }
        } else {
            timer = setTimeout(fn,delay);
        }
    }
}

setTimeout(f, 0)

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f, 0),那么会立刻执行吗?

答案是不会。因为必须要等到当前脚本的同步任务,全部处理完以后,才会执行setTimeout指定的回调函数f。也就是说,setTimeout(f, 0)会

在下一轮事件循环一开始就执行。

setTimeout(function () {
  console.log(1);
}, 0);
console.log(2);
// 2
// 1

上面代码先输出2,再输出1。因为2是同步任务,在本轮事件循环执行,而1是下一轮事件循环执行。

总之,setTimeout(f, 0)这种写法的目的是,尽可能早地执行f(放在异步任务队列的前面优先执行),但是并不能保证立刻就执行f。

获取url参数(QueryString)

正则法:

function getQueryString (name) {
    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)','i');
    var r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return unescape(r[2]);
    }
    return null;
}
//调用
getQueryString ("参数名");

        OR

var getParam = function (name) {
    var search = document.location.search;
    //alert(search);
    var pattern = new RegExp("[?&]" + name + "\=([^&]+)", "g");
    var matcher = pattern.exec(search);
    var items = null;
    if (null != matcher) {
        try {
            items = decodeURIComponent(decodeURIComponent(matcher[1]));
        } catch (e) {
            try {
                items = decodeURIComponent(matcher[1]);
            } catch (e) {
                items = matcher[1];
            }
        }
    }
    return items;
};

split拆分法:

function GetRequest() {
    var url = location.search; //获取url中"?"符后的字串
    var theRequest = new Object();
    if (url.indexOf("?") != -1) {
        var str = url.substr(1);
        strs = str.split("&");
        for(var i = 0; i < strs.length; i ++) {
            theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
        }
    }
    return theRequest;
}
var Request = new Object();
Request = GetRequest();

// 调用
// var 参数1,参数2,参数3,参数N;
// 参数1 = Request['参数1'];
// 参数2 = Request['参数2'];
// 参数3 = Request['参数3'];
// 参数N = Request['参数N'];

后续会有更多实用工具函数。。。。。

查看原文

赞 3 收藏 2 评论 2

前端森林 发布了文章 · 2018-09-09

git commit基本规范

出发点

git在日常生活工作中会经常回到,git的历史可读性及其重要,规范化的commit记录对于统一团队标准和后续代码

review和版本发布都极其重要。

基本规范

  • feat: 新增feature
  • fix: 修复bug
  • docs: 仅仅修改了文档,如readme.md
  • style: 仅仅是对格式进行修改,如逗号、缩进、空格等。不改变代码逻辑。
  • refactor: 代码重构,没有新增功能或修复bug
  • perf: 优化相关,如提升性能、用户体验等。
  • test: 测试用例,包括单元测试、集成测试。
  • chore: 改变构建流程、或者增加依赖库、工具等。
  • revert: 版本回滚
查看原文

赞 0 收藏 0 评论 0

前端森林 发布了文章 · 2018-09-07

git基本使用

git在日常开发中是必不可少的工具,本文总结了日常开发常用的git命令

Git是什么?

Git是目前世界上最先进的分布式版本控制系统(没有之一)。

Git有什么特点?简单来说就是:高端大气上档次!

git与svn的区别

1、GIT是分布式的,SVN不是:这是GIT和其它非分布式的版本控制系统,例如SVN,CVS等,最核心的区别。

2、GIT把内容按元数据方式存储,而SVN是按文件:所有的资源控制系统都是把文件的元信息隐藏在一个类似.svn,.cvs等的文件夹里。

3、GIT分支和SVN的分支不同:分支在SVN中一点不特别,就是版本库中的另外的一个目录。

4、GIT没有一个全局的版本号,而SVN有:目前为止这是跟SVN相比GIT缺少的最大的一个特征。

5、GIT的内容完整性要优于SVN:GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

全局配置

$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

仓库创建

git init

git add .

git commit -m

git status

git diff

初始化一个Git仓库,使用git init命令。

第一步,使用命令git add .,添加全部 ,注意,可反复多次使用,添加多个文件;

第二步,使用命令git commit -m "提交的注释",完成。

运行git status命令看看结果

运行git diff这个命令可查看我们修改了什么内容

工作区、暂存区和版本库

  • 工作区:就是你在电脑里能看到的目录。
  • 暂存区:英文叫stage, 或index。一般存放在 ".git目录下"
    下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
  • 版本库:工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。

前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:

  • 第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
  • 第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。

因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。

你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。

版本回退

git log

git reset --hard commit_id

通过git log来查看版本(加上--pretty=oneline参数查看关键信息)。

git中,用HEAD表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。

  • HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id。
  • 穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
  • 要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。

撤销修改

git checkout -- file

git checkout -- readme.txt

git reset HEAD <file>

把readme.txt文件在工作区的修改全部撤销,这里有两种情况:

  • 一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
  • 一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

总之,就是让这个文件回到最近一次git commit或git add时的状态。

git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令

如果你想要修改,已经git add到暂存区,但还没有commit的内容。

我们可以使用git reset HEAD <file>把暂存区的修改撤销掉(unstage),重新放回工作区。

git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。

文件删除

一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了

$ rm test.txt

这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了。

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    test.txt

no changes added to commit (use "git add" and/or "git commit -a")

现在你有两个选择,

  • 一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit:
$ git rm test.txt
rm 'test.txt'

$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
 1 file changed, 1 deletion(-)
 delete mode 100644 test.txt
  • 另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt

git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

远程仓库

本地仓库关联远程仓库

$ git remote add origin git@github.com:jack-cool/learngit.git

本地代码推送远程仓库

$ git push -u origin master

把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

从远程仓库克隆

$ git clone git@github.com:jack-cool/learngit.git

Git支持多种协议,包括https,但通过ssh支持的原生git协议速度最快

分支管理

查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并某分支到当前分支:git merge <name>

删除分支:git branch -d <name>

用git branch命令查看当前分支

$ git branch
* dev
  master

git branch命令会列出所有分支,当前分支前面会标一个*号。

创建dev分支,然后切换到dev分支

$ git checkout -b dev
Switched to a new branch 'dev'

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

$ git branch dev
$ git checkout dev
Switched to branch 'dev'

定制git

忽略特殊文件

好在Git考虑到了大家的感受,这个问题解决起来也很简单,在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名
填进去,Git就会自动忽略这些文件。

不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/git...

忽略文件的原则是:

  • 忽略操作系统自动生成的文件,比如缩略图等;
  • 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
  • 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
配置别名(偷懒,敲代码很累的。。。)

如果敲git st就表示git status那就简单多了,我们只需要敲一行命令,告诉Git,以后st就表示status:

$ git config --global alias.st status

当然还有别的命令可以简写,比如co表示checkout,ci表示commit,br表示branch:

$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
查看原文

赞 0 收藏 0 评论 0

前端森林 发布了文章 · 2018-09-07

复杂数据处理

前端对于数据的处理一般会用到foreach、map、reduce、Object.values()、Object.keys()、Object.entries()等方法,下面逐次进行分析

foreach
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。foreach方法不会返回执行结果。

注意: forEach() 对于空数组是不会执行回调函数的。foreach会改变原数组。

语法:
array.forEach(function(currentValue, index, arr), thisValue)
示例:
    let schedulesObj = {};
    dateArr.forEach((key) => {
      if (!schedulesObj[key]) {
        schedulesObj[key] = [];
      }
      schedulesObj[key].push(item);
    });

map
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

map() 方法按照原始数组元素顺序依次处理元素。

注意: map() 不会对空数组进行检测。

注意: map() 不会改变原始数组。

语法:
array.map(function(currentValue,index,arr), thisValue)
示例:
    const initItems = initEvaluateItems.map(item => {
      const { score, id, itemName, levelDesc, maxLevel } = item;
      return {
        score,
        id,
        itemName,
        levelDesc,
        maxLevel
      };
    });

reduce
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

reduce() 可以作为一个高阶函数,用于函数的 compose。

注意: reduce() 对于空数组是不会执行回调函数的。

语法:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
示例:
let scheduleIdArray = Object.keys(curScheduleMonth).map(v => curScheduleMonth[v]).reduce((total, item) => {
    total = [...total, ...item];
    return total;
  }, []);

Object.keys()

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。

语法:
Object.keys(obj)
示例:
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']

Object.values()

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

语法:
Object.values(obj)
示例:
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj)); // ['b', 'c', 'a']

Object.entries()

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。

语法:
Object.entries(obj)
示例:
const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]

1.时间相关

{
    "success":true,
    "code": "success",
    "message": "成功",
    "data": {
        "monthData":[
            {
               "month":"2018-05",
               "displayDesc":"有服务",
               "showType":"1",
               "tips":"请您选择"
            }
        ]
        "calendarData":[
            {
               "date":["2018-06-02","2018-07-09"],
               "displayDesc":"有服务",
               "showType":"1",
               "tips":"请您评价"
            }
        ],
        "schedules":[
            {
               "scheduleId":"1",
               "appCode":"106",
               "appName":"公共服务",
               "cityId":"321568",
               "categoryCode":"16",
               "scheduleType":"1",
               "userDesc":"社区医疗",
               "systemDesc":"",
               "remind":"1",
               "repeat":"1",
               "status":"2",
               "serviceUrl":"",
               "beginTime":"2018-04-25",
               "endTime":"2018-04-26",
            }
        ]
    }
}
import moment from 'moment/moment';

/**
 * 通过beginTime和endTime,将列表值按照天的维度进行整理,产出的数据结构scheduleByDay
 * @param schedules
 */
export function genSchedulesObj(schedules = []) {
  let schedulesObj = {};
  schedules.forEach((item) => {
    let { beginTime, endTime } = item;
    let _beginTime = new Date(beginTime).getTime();
    let _endTime = new Date(endTime).getTime();
    let dateArr = [];
    let dateReduce = ((_endTime - _beginTime) / (1000 * 24 * 60 * 60) + 1) || 1;
    dateReduce > 0 ? {} : (dateReduce = 0);
    for (let i = 0; i < dateReduce; i++) {
      dateArr.push(moment(_beginTime).format('YYYY-MM-DD'));
      _beginTime += (1000 * 24 * 3600);
    }
    dateArr.forEach((key) => {
      if (!schedulesObj[key]) {
        schedulesObj[key] = [];
      }
      schedulesObj[key].push(item);
    });
  });
  // let flag = true;
  // for (let key in schedulesObj) {
  //   for (let i = 0, len = schedulesObj[key].length; i < len; i++) {
  //     if (schedulesObj[key][i].status < 3) {
  //       flag = false;
  //       break;
  //     }
  //   }
  // }
  return {
    schedulesObj
  };
}

/**
 * calendarData 日期上显示代办内容,根据这个数据创建tagData是一个一维数组,产出的数据结构tagDataByMonth
 * @param calendarData
 */
export function genCalendarDataObj(calendarData = []) {
  let calendarDataObj = {};
  calendarData.forEach((item) => {
    item.date.forEach((key) => {
      if (!calendarDataObj[key]) {
        calendarDataObj[key] = [];
      }
      calendarDataObj[key].push({
        displayDesc: item.displayDesc,
        showType: item.showType
      });
    });
  });
  return calendarDataObj;
}
/**
 * 获取当前月、上一个月、下一月及当前月的开始、结束日期
 */
export function getFormatMonth(currentDate) {
  const beginDate = moment(currentDate).startOf('month').add(-1, 'M').format('YYYY-MM-DD');
  const endDate = moment(currentDate).endOf('month').add(1, 'M').format('YYYY-MM-DD');
  const preMont = moment(currentDate).subtract(1, 'months').format('YYYY-MM');
  const nextMont = moment(currentDate).add(1, 'months').format('YYYY-MM');

  const currMont = moment(currentDate).format('YYYY-MM');

  const month = preMont + ',' + currMont + ',' + nextMont;

  return {
    beginDate,
    endDate,
    preMont,
    nextMont,
    currMont,
    month
  };
}

2.工具类函数

/**
 * 正则表达式获取地址栏参数
 */

export const getURLParameters = (url) => {
  url = url.split('?')[1] || '';
  url = url.split('&');
  return url.reduce((total, item) => {
    let itemArr = item.split('=');
    total[itemArr[0]] = itemArr[1];
    return total;
  }, {});
};

/**
 * filter过滤
 */
const filterArr = (scheduleByDay[currentDate] || []).filter(v => {
    return v.status !== 4;
  });


const tagData = Object.keys(tagDataByMonth).map((key) => {
    const obj = tagDataByMonth[key][0];
    const scheduleByDayItem = scheduleByDay[key] || [];
    return {
      date: key,
      tag: scheduleByDayItem.length === 1 ? scheduleByDayItem[0].userDesc : obj.displayDesc,
      tagColor: obj.showType === '1' ? '#F5A623' : '#CCCCCC'
    };
  });

let scheduleIdArray = Object.keys(curScheduleMonth).map(v => curScheduleMonth[v]).reduce((total, item) => {
    total = [...total, ...item];
    return total;
  }, []);
  let scheduleId = scheduleIdArray.length ? scheduleIdArray[0].scheduleId : null;
  let isOnlyOne = scheduleId ? scheduleIdArray.every(v => v.scheduleId === scheduleId) : false;

/**
   * 获取服务端时间
   */
  getServerTimeAsync() {
    return new Promise((resolve) => {
      try {
        my.call('getServerTime', (res) => {
          resolve(res.time);
        });
      } catch (e) {
        resolve(new Date().getTime());
      }
    });
  },
/**
   * 检查文本域的长度
   * @param keyword
   * @returns {*}
   */
  checkKeywordLength(keyword) {
    const { maxlength } = this.data;
    if (keyword.length > maxlength) {
      keyword = keyword.substring(0, maxlength);
    }
    return keyword;
  },


const { data: { items: initEvaluateItems } } = serviceKey;
    const initItems = initEvaluateItems.map(item => {
      const { score, id, itemName, levelDesc, maxLevel } = item;
      return {
        score,
        id,
        itemName,
        levelDesc,
        maxLevel
      };
    });

3.层级较深的数据结构

{
    "success": true, 
    "value": {
        "merchant": {
            "id": 0, #物理id
            "partakerId": 0, 
            "partakerName": "string", 
            "merchantPid": "string",
            "merchantName": "string", 
            "owners": {
                "guarantee_owner":[{"account":"string","name":"string"}],
            }, #负责人      
        },
        "extension":{
            keyValues: {
                channel:{
                    key:{
                        id:"21",
                        creator:"流年",
                        dataSource:"",
                        key:"duration",
                        label:"项目周期",
                        type:"date",
                        isRequire:"Y"   
                    },
                    value:"2018-06-02 17:55:12"
                },
                is_sign:{
                    key:{
                        id:"32",
                        creator:"lily",
                        dataSource:"[{'key':'current','value':'今天'},{'key':'last','value':'昨天'}]",
                        key:"startTime",
                        label:"启动时间",
                        type:"select",
                        isRequire:"N"   
                    },
                    value:"last"
                },
                merchantInfo:{
                    key:{
                        id:"02",
                        creator:"jack",
                        dataSource:"",
                        key:"taskCount",
                        label:"任务量",
                        type:"number",
                        isRequire:"Y"   
                    },
                    value:"55"
                },
                code:"DEFAULT",
                tempName:"社区服务"
            }
        }, #动态字段
        
    }, 
    "msg": "string", #错误信息
    "code": "string" #错误码
}




const { stat, value = {}, msg } = response || {};
      if (stat === 'fail') {
        message.error(msg);
      }
      const { merchant = {}, extension = {} } = value;
      const { keyValues = {} } = extension;
      const extenData = Object.entries(keyValues).map(v => {
        const [arr1, arr2] = v;
        const { key, recordId, value: newValue } = arr2;
        return {
          key,
          value: newValue,
          recordId
        };
      });
      console.log('动态数据-----', extenData);


const linksObj = {
  活动信息: links.slice(0, 2),
  活动商户信息: links.slice(2, 8),
  保障商户信息: links.slice(8),
};     
   
   
   getFormDataDom = (data) => {
        const { getFieldDecorator } = this.props.form;
        const { formItem = {} } = this.props;
        return data.map((val) => {
          const { name, id, isSelect = false, isSelectInputOwners = false, isInput = false } = val;
          let isSimpleInitial;
          let isSelectInputOwnersInitial;
          if (isSelect || isInput) {
            isSimpleInitial = formItem && formItem[id] ? formItem[id] : '';
          }
          if (isSelectInputOwners) {
            isSelectInputOwnersInitial = formItem && formItem[id] && formItem[id].length > 0 ? formItem[id].map(v => v.name) : [];
          }
          const initialValue = isSelectInputOwners ? isSelectInputOwnersInitial : isSelect || isInput ? isSimpleInitial : '';
          return (
            <Col span={12} key={id}>
              <FormItem label={name} {...formItemLayout}>
                {getFieldDecorator(`${id}`, {
                  initialValue
                })(this.getFormItem(formItem, val))
                }
              </FormItem>
            </Col>
          );
        });
  };
 
 
    
 extenArr = (extenData = []) => {
    return extenData.map((item) => {
      const { key, value } = item;
      const { fieldKey, fieldLabel } = key;
      let { dataSource } = key;
      let spanValue = '';
      if (dataSource === '') {
        spanValue = value;
      } else {
        try {
          dataSource = dataSource.replace(/'/img, '"');
          const jsonValue = JSON.parse(dataSource);
          spanValue = jsonValue.reduce((total, i) => {
            total[i.key] = i.value;
            return total;
          }, {})[value];
        } catch (e) {
          spanValue = '';
        }
      }
      return {
        name: fieldLabel,
        id: fieldKey,
        spanValue
      };
    });
  };




        <Form>
          <Row>
            {
              Object.entries(linksObj)
                .map((item, index) => {
                  const [item1, item2] = item;
                  return <div key={index}>
                    <h3>{item1}</h3>
                    <Row style={{ borderBottom: index === Object.entries(linksObj).length - 1 ? '1px dashed #ccc' : 'none' }}>
                      {
                        this.getFormDataDom(item2)
                      }
                    </Row>
                         </div>;
                })
            }
            {
              this.getFormDataDom(this.extenArr(extenData))
            }
          </Row>
        </Form>
      
查看原文

赞 1 收藏 1 评论 0

前端森林 发布了文章 · 2018-09-06

复杂数据处理

前端对于数据的处理一般会用到foreach、map、reduce、Object.values()、Object.keys()、Object.entries()等方法,下面逐次进行分析

foreach
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。foreach方法不会返回执行结果。

注意: forEach() 对于空数组是不会执行回调函数的。foreach会改变原数组。

语法:
array.forEach(function(currentValue, index, arr), thisValue)
示例:
    let schedulesObj = {};
    dateArr.forEach((key) => {
      if (!schedulesObj[key]) {
        schedulesObj[key] = [];
      }
      schedulesObj[key].push(item);
    });

map
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

map() 方法按照原始数组元素顺序依次处理元素。

注意: map() 不会对空数组进行检测。

注意: map() 不会改变原始数组。

语法:
array.map(function(currentValue,index,arr), thisValue)
示例:
    const initItems = initEvaluateItems.map(item => {
      const { score, id, itemName, levelDesc, maxLevel } = item;
      return {
        score,
        id,
        itemName,
        levelDesc,
        maxLevel
      };
    });

reduce
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

reduce() 可以作为一个高阶函数,用于函数的 compose。

注意: reduce() 对于空数组是不会执行回调函数的。

语法:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
示例:
let scheduleIdArray = Object.keys(curScheduleMonth).map(v => curScheduleMonth[v]).reduce((total, item) => {
    total = [...total, ...item];
    return total;
  }, []);

Object.keys()

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。

语法:
Object.keys(obj)
示例:
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']

Object.values()

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

语法:
Object.values(obj)
示例:
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj)); // ['b', 'c', 'a']

Object.entries()

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。

语法:
Object.entries(obj)
示例:
const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]

1.时间相关

{
    "success":true,
    "code": "success",
    "message": "成功",
    "data": {
        "monthData":[
            {
               "month":"2018-05",
               "displayDesc":"有服务",
               "showType":"1",
               "tips":"请您选择"
            }
        ]
        "calendarData":[
            {
               "date":["2018-06-02","2018-07-09"],
               "displayDesc":"有服务",
               "showType":"1",
               "tips":"请您评价"
            }
        ],
        "schedules":[
            {
               "scheduleId":"1",
               "appCode":"106",
               "appName":"公共服务",
               "cityId":"321568",
               "categoryCode":"16",
               "scheduleType":"1",
               "userDesc":"社区医疗",
               "systemDesc":"",
               "remind":"1",
               "repeat":"1",
               "status":"2",
               "serviceUrl":"",
               "beginTime":"2018-04-25",
               "endTime":"2018-04-26",
            }
        ]
    }
}
import moment from 'moment/moment';

/**
 * 通过beginTime和endTime,将列表值按照天的维度进行整理,产出的数据结构scheduleByDay
 * @param schedules
 */
export function genSchedulesObj(schedules = []) {
  let schedulesObj = {};
  schedules.forEach((item) => {
    let { beginTime, endTime } = item;
    let _beginTime = new Date(beginTime).getTime();
    let _endTime = new Date(endTime).getTime();
    let dateArr = [];
    let dateReduce = ((_endTime - _beginTime) / (1000 * 24 * 60 * 60) + 1) || 1;
    dateReduce > 0 ? {} : (dateReduce = 0);
    for (let i = 0; i < dateReduce; i++) {
      dateArr.push(moment(_beginTime).format('YYYY-MM-DD'));
      _beginTime += (1000 * 24 * 3600);
    }
    dateArr.forEach((key) => {
      if (!schedulesObj[key]) {
        schedulesObj[key] = [];
      }
      schedulesObj[key].push(item);
    });
  });
  // let flag = true;
  // for (let key in schedulesObj) {
  //   for (let i = 0, len = schedulesObj[key].length; i < len; i++) {
  //     if (schedulesObj[key][i].status < 3) {
  //       flag = false;
  //       break;
  //     }
  //   }
  // }
  return {
    schedulesObj
  };
}

/**
 * calendarData 日期上显示代办内容,根据这个数据创建tagData是一个一维数组,产出的数据结构tagDataByMonth
 * @param calendarData
 */
export function genCalendarDataObj(calendarData = []) {
  let calendarDataObj = {};
  calendarData.forEach((item) => {
    item.date.forEach((key) => {
      if (!calendarDataObj[key]) {
        calendarDataObj[key] = [];
      }
      calendarDataObj[key].push({
        displayDesc: item.displayDesc,
        showType: item.showType
      });
    });
  });
  return calendarDataObj;
}
/**
 * 获取当前月、上一个月、下一月及当前月的开始、结束日期
 */
export function getFormatMonth(currentDate) {
  const beginDate = moment(currentDate).startOf('month').add(-1, 'M').format('YYYY-MM-DD');
  const endDate = moment(currentDate).endOf('month').add(1, 'M').format('YYYY-MM-DD');
  const preMont = moment(currentDate).subtract(1, 'months').format('YYYY-MM');
  const nextMont = moment(currentDate).add(1, 'months').format('YYYY-MM');

  const currMont = moment(currentDate).format('YYYY-MM');

  const month = preMont + ',' + currMont + ',' + nextMont;

  return {
    beginDate,
    endDate,
    preMont,
    nextMont,
    currMont,
    month
  };
}

2.工具类函数

/**
 * 正则表达式获取地址栏参数
 */

export const getURLParameters = (url) => {
  url = url.split('?')[1] || '';
  url = url.split('&');
  return url.reduce((total, item) => {
    let itemArr = item.split('=');
    total[itemArr[0]] = itemArr[1];
    return total;
  }, {});
};

/**
 * filter过滤
 */
const filterArr = (scheduleByDay[currentDate] || []).filter(v => {
    return v.status !== 4;
  });


const tagData = Object.keys(tagDataByMonth).map((key) => {
    const obj = tagDataByMonth[key][0];
    const scheduleByDayItem = scheduleByDay[key] || [];
    return {
      date: key,
      tag: scheduleByDayItem.length === 1 ? scheduleByDayItem[0].userDesc : obj.displayDesc,
      tagColor: obj.showType === '1' ? '#F5A623' : '#CCCCCC'
    };
  });

let scheduleIdArray = Object.keys(curScheduleMonth).map(v => curScheduleMonth[v]).reduce((total, item) => {
    total = [...total, ...item];
    return total;
  }, []);
  let scheduleId = scheduleIdArray.length ? scheduleIdArray[0].scheduleId : null;
  let isOnlyOne = scheduleId ? scheduleIdArray.every(v => v.scheduleId === scheduleId) : false;

/**
   * 获取服务端时间
   */
  getServerTimeAsync() {
    return new Promise((resolve) => {
      try {
        my.call('getServerTime', (res) => {
          resolve(res.time);
        });
      } catch (e) {
        resolve(new Date().getTime());
      }
    });
  },
/**
   * 检查文本域的长度
   * @param keyword
   * @returns {*}
   */
  checkKeywordLength(keyword) {
    const { maxlength } = this.data;
    if (keyword.length > maxlength) {
      keyword = keyword.substring(0, maxlength);
    }
    return keyword;
  },


const { data: { items: initEvaluateItems } } = serviceKey;
    const initItems = initEvaluateItems.map(item => {
      const { score, id, itemName, levelDesc, maxLevel } = item;
      return {
        score,
        id,
        itemName,
        levelDesc,
        maxLevel
      };
    });

3.层级较深的数据结构

{
    "success": true, 
    "value": {
        "merchant": {
            "id": 0, #物理id
            "partakerId": 0, 
            "partakerName": "string", 
            "merchantPid": "string",
            "merchantName": "string", 
            "owners": {
                "guarantee_owner":[{"account":"string","name":"string"}],
            }, #负责人      
        },
        "extension":{
            keyValues: {
                channel:{
                    key:{
                        id:"21",
                        creator:"流年",
                        dataSource:"",
                        key:"duration",
                        label:"项目周期",
                        type:"date",
                        isRequire:"Y"   
                    },
                    value:"2018-06-02 17:55:12"
                },
                is_sign:{
                    key:{
                        id:"32",
                        creator:"lily",
                        dataSource:"[{'key':'current','value':'今天'},{'key':'last','value':'昨天'}]",
                        key:"startTime",
                        label:"启动时间",
                        type:"select",
                        isRequire:"N"   
                    },
                    value:"last"
                },
                merchantInfo:{
                    key:{
                        id:"02",
                        creator:"jack",
                        dataSource:"",
                        key:"taskCount",
                        label:"任务量",
                        type:"number",
                        isRequire:"Y"   
                    },
                    value:"55"
                },
                code:"DEFAULT",
                tempName:"社区服务"
            }
        }, #动态字段
        
    }, 
    "msg": "string", #错误信息
    "code": "string" #错误码
}




const { stat, value = {}, msg } = response || {};
      if (stat === 'fail') {
        message.error(msg);
      }
      const { merchant = {}, extension = {} } = value;
      const { keyValues = {} } = extension;
      const extenData = Object.entries(keyValues).map(v => {
        const [arr1, arr2] = v;
        const { key, recordId, value: newValue } = arr2;
        return {
          key,
          value: newValue,
          recordId
        };
      });
      console.log('动态数据-----', extenData);


const linksObj = {
  活动信息: links.slice(0, 2),
  活动商户信息: links.slice(2, 8),
  保障商户信息: links.slice(8),
};     
   
   
   getFormDataDom = (data) => {
        const { getFieldDecorator } = this.props.form;
        const { formItem = {} } = this.props;
        return data.map((val) => {
          const { name, id, isSelect = false, isSelectInputOwners = false, isInput = false } = val;
          let isSimpleInitial;
          let isSelectInputOwnersInitial;
          if (isSelect || isInput) {
            isSimpleInitial = formItem && formItem[id] ? formItem[id] : '';
          }
          if (isSelectInputOwners) {
            isSelectInputOwnersInitial = formItem && formItem[id] && formItem[id].length > 0 ? formItem[id].map(v => v.name) : [];
          }
          const initialValue = isSelectInputOwners ? isSelectInputOwnersInitial : isSelect || isInput ? isSimpleInitial : '';
          return (
            <Col span={12} key={id}>
              <FormItem label={name} {...formItemLayout}>
                {getFieldDecorator(`${id}`, {
                  initialValue
                })(this.getFormItem(formItem, val))
                }
              </FormItem>
            </Col>
          );
        });
  };
 
 
    
 extenArr = (extenData = []) => {
    return extenData.map((item) => {
      const { key, value } = item;
      const { fieldKey, fieldLabel } = key;
      let { dataSource } = key;
      let spanValue = '';
      if (dataSource === '') {
        spanValue = value;
      } else {
        try {
          dataSource = dataSource.replace(/'/img, '"');
          const jsonValue = JSON.parse(dataSource);
          spanValue = jsonValue.reduce((total, i) => {
            total[i.key] = i.value;
            return total;
          }, {})[value];
        } catch (e) {
          spanValue = '';
        }
      }
      return {
        name: fieldLabel,
        id: fieldKey,
        spanValue
      };
    });
  };




        <Form>
          <Row>
            {
              Object.entries(linksObj)
                .map((item, index) => {
                  const [item1, item2] = item;
                  return <div key={index}>
                    <h3>{item1}</h3>
                    <Row style={{ borderBottom: index === Object.entries(linksObj).length - 1 ? '1px dashed #ccc' : 'none' }}>
                      {
                        this.getFormDataDom(item2)
                      }
                    </Row>
                         </div>;
                })
            }
            {
              this.getFormDataDom(this.extenArr(extenData))
            }
          </Row>
        </Form>
      
查看原文

赞 1 收藏 1 评论 0

前端森林 发布了文章 · 2018-09-06

Linux学习---命令

Linux学习---命令

常用目录的作用

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

命令基本格式

  • 命令提示符

clipboard.png

  • 命令格式

clipboard.png

文件处理命令

  • ls:查询目录中内容(ll相当于ls-l)

clipboard.png

文件权限

clipboard.png

  • mkdir:建立目录

clipboard.png

  • cd:切换所在目录(ctrl+l进行清屏)

clipboard.png

相对路径和绝对路径(Linux下通过tab键可以补全命令或目录,给予充分提示)

clipboard.png

  • pwd:查询所在目录位置

clipboard.png

  • rmdir:删除空目录

clipboard.png

  • 删除文件或目录

clipboard.png

  • cp:复制文件或目录

clipboard.png

  • mv:剪切或改名(原文件与目标文件不在同一个目录为剪切;在同一个目录为改名)

clipboard.png

  • ln:链接命令

clipboard.png

clipboard.png

创建软链接时原文件一定要写绝对路径

clipboard.png

硬链接和软链接的区别

clipboard.png

文件搜索命令

clipboard.png

  • locate

clipboard.png

clipboard.png

clipboard.png

查看原文

赞 2 收藏 2 评论 0