feat: 初始化

This commit is contained in:
杭城小刘
2018-09-11 10:11:39 +08:00
committed by 杭城小刘
commit 8e5d2c9e7f
264 changed files with 12082 additions and 0 deletions

BIN
第二部分 Web 前端/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,197 @@
# :last-child与:last-of-type
> 同学们遇到过给同一组元素的最后一个元素设置css失效的情况吗我遇到过当时使用:last-child居然不起作用看到名字不科学啊明明是“最后一个元素”那为什么设置CSS失效呢今天来一探究竟吧
* 先看一组`:last-child`正常工作的代码
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
}
li {
list-style: circle;
border-bottom: 1px solid #000000;
}
li:last-child {
border-color: red;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<!--<p>我是来骚扰的</p>-->
</ul>
</body>
</html>
```
![效果1](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180507-091957@2x.png)
* 再先看一组`:last-child`不正常工作的代码
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
}
li {
list-style: circle;
border-bottom: 1px solid #000000;
}
li:last-child {
border-color: red;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<p>我是来骚扰的</p>
</ul>
</body>
</html>
```
![效果2](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180507-092046@2x.png)
问题抛出来了,那么来研究下:last-child和:last-of-type究竟是何方神圣。
1. :last-child**The last-child CSS pseudo-class represents the last element among a group of sibling elements.:last-child这个css伪类代表的一组兄弟元素当中最后一个元素但经过代码发现它说的一组元素应该是指其父元素的所有子元素且类型为:last-child前面指定的类型的一组元素。**
2. :last-of-type**The last-of-type CSS pseudo-class represents the last element of its type among a group of sibling elements.**:last-of-type这个css伪类代表其类型的一组兄弟元素中的最后一个元素**)所以它指的是和**:last-of-type前面的元素类型一致的一组元素的最后一个元素
同理::nth-last-child和:nth-last-of-type的区别在于父元素的子元素中且与:nth-last-child前面的元素类型一致的最后一个元素
做个验证
* :nth-last-child可以正常工作的代码
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
}
li {
list-style: circle;
border-bottom: 1px solid #000000;
}
li:nth-last-child(1){
border-color: red;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<!--<p>我是来骚扰的</p>-->
</ul>
</body>
</html>
```
![效果3](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180507-092145@2x.png)
* :nth-last-child不能正常工作的代码
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
}
li {
list-style: circle;
border-bottom: 1px solid #000000;
}
li:nth-last-child(1){
border-color: red;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<p>我是来骚扰的</p>
</ul>
</body>
</html>
```
![](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180507-092232@2x.png)
* 接下来:nth-last-of-type闪亮登场
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
}
li {
list-style: circle;
border-bottom: 1px solid #000000;
}
li:nth-last-of-type(1){
border-color: red;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<p>我是来骚扰的</p>
</ul>
</body>
</html>
```
![](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180507-092358@2x.png)

View File

@@ -0,0 +1,61 @@
# 调试工具安装
---
## 安装方式一
Vue-devtools 可以从 Chrome 商店直接下载安装,前提需要翻墙。
## 安装方式二
* 第一步:找到 Vue-devtools 的 github 地址,并将其 clone 到本地。
```
git clone https://github.com/vuejs/vue-devtools.git
```
* 第二步:安装项目所依赖的 npm 包
```
npm install
```
遇到的问题:
![遇到的问题](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Chrome-Vue-tools1.png)
改用命令
```
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
```
![改用命令](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Chrome-Vue-tools3.png)
继续 npm install
* 第三步:编译项目文件
```
npm run build
```
![编译项目文件](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Chrome-Vue-tools4.png)
* 第四步:添加至 Chrome 浏览器的拓展
```
浏览器地址栏输入chrome://extensions/
点击“加载已解压的拓展程序”选择本地 clone 下来的文件夹中的 shells -> chrome 文件夹vue-devtools-master/shells/chrome
```
![Chrome 添加拓展](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Chrome-Vue-tools5.png)
* 第五步:重启浏览器
* 第六步:在浏览器中的调试 Vue 代码
![Chrome 调试 Vue](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Chrome-Vue-tools6.png)

View File

@@ -0,0 +1,10 @@
# H5页面保存页面为图片
方案有个github的js库可以将html转换为canvas然后canvas可以转换为图片然后图片可以下载所以基本路线就是
* html2canvas.js 将htmldom转为canvas [https://github.com/niklasvh/html2canvas](https://github.com/niklasvh/html2canvas "html2canvas")
* canvasAPI toDataUrl\(\)将canvas转为base64格式
* 图片下载
具体描述https://juejin.im/post/5a17c5e26fb9a04527254689

View File

@@ -0,0 +1,180 @@
## Promise
## 一、基础使用
举个例子:
```
//方式一
function test(resolve, reject) {
var timeout = Math.random() * 2;
console.log("开始尝试promise");
setTimeout(function() {
if (timeout < 1) {
resolve(timeout);
} else {
reject(timeout);
}
}, 1000);
}
function resolve(time) {
console.log("小于1的数字" + time);
}
function reject(time) {
console.log("不小于1的数字" + time);
}
var p1 = new Promise(test);
var p2 = p1.then(function(result) {
console.log("成功" + result);
});
var p3 = p1.catch(function(reason) {
console.log("失败" + reason);
});
// 方式二
new Promise(test).then(function(result) {
console.log("成功" + result);
}).catch(function(reason) {
console.log("失败" + reason);
});
//方式三
<div id="test-promise-log">
</div>
var logging = document.getElementById("test-promise-log");
while (logging.children.length > 1) {
logging.removeChild(logging.children[logging.children.length - 1]);
}
function log(s) {
var p = document.createElement("p");
p.innerHTML = s;
logging.appendChild(p);
}
new Promise(function(resolve, reject) {
log("start new Promise...");
var timeout = Math.random() * 2;
log('set timeout to: ' + timeout + 'seconds.');
setTimeout(function() {
if (timeout < 1) {
log('call resolve()...');
resolve('200 OK');
} else {
log('call reject()...');
reject('timeout in ' + timeout + 'sconds.');
}
}, timeout * 1000);
}).then(function(result) {
log('Done: ' + result);
}).catch(function(reason) {
log("Failed: " + reason);
});
------------
start new Promise...
set timeout to: 1.6579586637257697seconds.
call reject()...
Failed: timeout in 1.6579586637257697sconds.
```
可见 Promise 的最大好处就是在异步执行的流程中,将执行代码和处理结果的代码清晰地分离了。
## 二、串行执行
比如有若干个异步任务需要先做任务1如果任务1成功后执行任务2...,任何一个环节中的任务失败则不再继续执行错误处理函数。
要完成这个需求,传统写法需要一层一层的嵌套代码有了 Promise ,就可以
```
task1.then(task2).then(task3).catch(erroeHandler);
```
举个例子
```
function add(num) {
return new Promise(function(resolve, reject) {
log(num + " + " + num + "...");
setTimeout(resolve, 500, num + num);
});
}
function mul(num) {
return new Promise(function(resolve, reject) {
log(num + " x " + num + "...");
setTimeout(resolve, 500, num * num);
});
}
new Promise(function(resolve, reject) {
resolve(100);
}).then(add).then(mul).then(add).then(mul).then(function(result) {
log("the result is " + result);
});
---------------------
100 + 100...
200 x 200...
40000 + 40000...
80000 x 80000...
the result is 6400000000
```
## 三、并行执行
```
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "p1 success");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "p2 success");
});
Promise.all([p1, p2]).then(function(results) {
console.log(results)
});
```
多个 任务需要同时进行也就是并行执行,那么就可以使用 Promise.all\(\) 实现
## 四、容错处理,只需要拿到先返回的结果。
```
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "p1 success");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "p2 success");
});
//使用第一个返回的结果
Promise.race([p1, p2]).then(function(result) {
console.log("结果是: " + result);
});
```

View File

@@ -0,0 +1,223 @@
# webpack-dev-server 的配置和使用
> webpack-dev-server是一个小型的`Node.js Express`服务器,它使用`webpack-dev-middleware`来服务于webpack的包,除此自外,它还有一个通过[Sock.js](http://sockjs.org/)来连接到服务器的微型运行时.
>
> 说人话就是可以极大的提高开发效率。这么好的东西那就用起来
1、看看 webpack.config.js
```
module.exports = {
target:"web", //编译平台 web
entry:path.join(__dirname,"src/index.js"), //应用程序的主入口
output:{
filename:"bundle.js", //输出文件名
path: path.join(__dirname, "dist")
},
module:{
rules:[
{
test:/\.vue$/,
loader:"vue-loader"
},
{
test:/\.css$/,
use:[
"style-loader",
"css-loader"
]
},
{
test:/\.(gif|jpg|jpeg|png|svg)$/,
use:[
{
loader:"url-loader",
options:{
limit:1024,
name:'[name]-lbp.[ext]' //甚至可以加一些名字处理规则 '[name]-lbp.[ext]'
}
}
]
}
]
},
/*
在 plugins 中写 webpack.DefinePlugin 是为了在 webpack 打包的时候选择源代码的版本。当在 dev 的时候会打包开发所需要的所有东西,比如警告信息
*/
plugins:[
new webpack.DefinePlugin({
'process.env':{
NODE_ENV: isDev ? '"development"' : '"production"'
}
}),
new HTMLPlugin()
]
}
```
配置文件提供1个入口和一个出口webpack 根据这个文件来执行 js 的打包和编译。webpack-dev-server 其中的部分功能就克服了上面2个问题。 webpack-dev-server 主要启动了1个使用 express 的 HTTP 服务器(资源文件)。由于这个 HTTP 服务器和 client 使用了 websocket 通讯协议原始文件作出了改动webpack-dev-server 会实时编译,但是最后编译的文件并没有输出到目标文件夹
## 一些截图
![](/assets/todo-20180226-1.png)
![](/assets/todo-20180226-2.png)
![](/assets/todo-20180226-3.png)
![](/assets/todo-20180226-4.png)
![](/assets/todo-20180226-5.png)
完整的 webpack.config.js
```
const path = require("path");
const HTMLPlugin = require("html-webpack-plugin")
const webpack = require("webpack")
//process.env 是读取系统环境。比如在启动服务的时候会通过 npm run build/dev 运行,其真实入口是在 webpack.config.js ,然后我们在 webpack.config.js设置为 production 或 development。那么就可以通过 process.env.NODE_ENV 获取
const isDev = process.env.NODE_ENV === 'developement'
//webpack 会将文件打包为 bundle.js
//需要为 .vue 文件声明一个类型, 因为 webpack 只识别 .js 且支持的语法为 ES5
//增加一个 module 模块,一个键为 rules ,值可以是多个数组。检测文件以 .vue 结尾的话就以 vue-loader 解析
//vue-loader 为 webpack 解析 .vue
/*
查看 dist 文件夹下的 bundle.js 文件
最顶部都是 webpack 处理包的代码
其次是 vue 代码
webpack 做的事情就是将不同类型的静态资源打包成 JS然后在 HTML 中引入 JS 就可以减小 HTTP 请求。
以 css 结尾的文件使用 css-loader。
css-loader:从css文件中将内容读了出来。
style-loader判断将css代码插入到html还是写到一个新的文件中
在写图片的读取规则的时候用到了useuse数组里面是一个个对象loader 可以配置一些选项
url-loader 可以将图片转换为 base64 字符串直接写到 JS 内容里面而不用生成一个图片,这对于一些小的图片是比较有利的,这样子可以减小 http 请求。
url-loader 封装了 file-loader读取了文件内容并做一些简单操作再把图片换个名称存在一个地方
limit选项如果图片小于1024就可以转义成 base64
name选项 可以处理图片的名字
配置完之后则需要安装相应的模块npm install
配置环境变量:
1、MAC 平台NODE_ENV=production
2、Window 平台 SET NODE_ENV=production
3、为了配置开发环境和正式环境需要安装 cross-env包。需要修改package.json 文件中的 scripts-> build 和 dev
*/
const config = {
target:"web", //编译平台 web
entry:path.join(__dirname,"src/index.js"), //应用程序的主入口
output:{
filename:"bundle.js", //输出文件名
path: path.join(__dirname, "dist")
},
module:{
rules:[
{
test:/\.vue$/,
loader:"vue-loader"
},
{
test:/\.css$/,
use:[
"style-loader",
"css-loader"
]
},
{
test:/\.(gif|jpg|jpeg|png|svg)$/,
use:[
{
loader:"url-loader",
options:{
limit:1024,
name:'[name]-lbp.[ext]' //甚至可以加一些名字处理规则 '[name]-lbp.[ext]'
}
}
]
}
]
},
/*
在 plugins 中写 webpack.DefinePlugin 是为了在 webpack 打包的时候选择源代码的版本。当在 dev 的时候会打包开发所需要的所有东西,比如警告信息
*/
plugins:[
new webpack.DefinePlugin({
'process.env':{
NODE_ENV: isDev ? '"development"' : '"production"'
}
}),
new HTMLPlugin()
]
}
//为了判断是开发环境还是生成环境,判断了 isDEV
/*
webpack 2.0 以后增加了 devSever。用来处理开发环境的配置
0.0.0.0:可以通过 localhost 和本地电脑 ip 访问项目
overlay在 webpack 编译的时候如果有错误显示在网页上。errors:true
*/
if (isDev) {
config.devtool = "#cheap-module-eval-source-map"
config.devServer = {
port:8000,
host:'0.0.0.0',
overlay:{
errors:true
},
hot:true
}
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
)
}
module.exports = config
```

View File

@@ -0,0 +1,18 @@
# Web 与 H5 交互的坑
1、遇到一个问题 ,一个功能在 iOS 手机上正常工作,但是在 Android 上不正常依照经验来看无非就是2个原因1、URL参数少传递了2、JS 在移动端的 webview 上报错了,所以我让远程对接的人员将 url 打印出来,发现没错。继续让他打印查看下 js 错误日志,发现 “Cannot read property "getItem" of null”。 代码出错行数在 259。看了下具体代码就是读取 localstorage
![报错信息](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/Andoid_Webview_Localstroage_erroe.jpg)
想了想,在我们的项目中 Android 原生的代码在使用 webview 的时候额外设置了代码具体如下
```
mWebView.getSettings().setDomStorageEnabled(true);
```

View File

@@ -0,0 +1,25 @@
# 前端持久化
1. 早期的前端存储主要是 cookie后来经过前端工程师不屑的追求以及开发有了后来的 localStorage 和 sessionStorage 技术。
2. 再到后来技术演进有了 indexDB 技术,也就是一个事务型的 key-value 形式的数据库
3. 说说 cookie 有了,为什么还要有 localStorage因为 cookie 虽然可以存储一些数据,但是大小非常有限,也就是 4k。并且 cookie 的本质工作是用来追踪浏览器用户的信息,因为 HTTP 是无状态协议,即服务器不知道用户上一次做了些什么,这严重阻碍了 Web 应用程序的时下。也就是通过 HTTP 连接无法知道用户干了些什么,比如说当用户在浏览一个商城的时候无法记录他购买了什么商品,介于此我们可以使用 cookie 技术用来绕开 HTTP 无状态的的特点,服务器可以设置或读取 cookie 中包含的信息,借此维护用户和服务器会话中的状态。
cookie 的另一个日常应用场景就是当用户勾选了某个网站的“下次自动登录”,那么在下次访问这个网站的时候,用户发现没有输入账号和密码就已经是登录状态。这是因为前一次登录的时候服务器发送了包含登录凭证(用户名和密,以及包含登录时长 的 token 信息)的 cookie 到用户的硬盘上,第二次登录时发现登录时长还有效,那么则自动登录
4. 对于存储数据这个功能来说cookie 的缺陷
* cookie 会被附加到每个 HTTP 请求的头部去,所以如果用来存储数据,那么每次请求就加大了请求数据量
* cookie 在 HTTP 请求中是明文传递的,所以存在安全性问题(除非 HTTPS
* cookie 只有4k的存储空间
5.cookie 的另一种不好的方面表现在当用户去访问某个网站的时候通过搜索引擎查到的这个网站包含一种叫做网页臭虫的图片通常是1像素大小以便于隐藏它们的作用是将所有访问过此页面的计算机写入 cookie而后电子商务网站读取这些 cookie 信息,并寻找写入这些 cookie 的网站,随机发送包含针对这个网站的相关产品广告的垃圾邮件给这些用户

View File

@@ -0,0 +1,13 @@
# VS-Code 的配置
1、Mac 下 按住 “command+shift+p”打开命令面板输入Preferences: Open User Settings在右边的区域输入
```
{
"window.zoomLevel": -1,
"editor.fontSize": 17,
"files.autoSave": "onFocusChange",
"terminal.integrated.fontSize": 15,
"editor.tabSize": 2
}
```

View File

@@ -0,0 +1,556 @@
# Vue
> Vue学习过程中遇到的一些小tips
1、组件化
Vue 的组件可以分为全局组件和局部组件
全局组件:声明好后可以在全局使用
局部组件:只可以在当前模块使用
```
//全局组件
Vue.componetns('todo-item',{
template:'<li> {{content}} </li>',
prop:['content']
});
//局部组件
var HobbyItem = {
template:"<li>hobby</li>"
}
new Vue({
el:"#test",
components:{
'todo-item':HobbyItem
}
});
```
2、模块、实例
每一个模块都是 Vue 的一个实例,也就是说可以向每一个模版中像写 Vue 的实例一样进行开发。
3、父组件与子组件通信用 props 传递
4、子组件向外触发事件 this.$emit(事件名,参数列表)
5、template 模版下最外层只可有一个根元素
```
<template>
<div>
<p></p>
...
</div>
</template>
```
6、单独在 .vue 文件中data 是作为函数存在的。
```
data: function(){
}
data () {
}
```
7、在 vue-cli 脚手架中,组件的声明方式
```
export default{
components : {
'todo-item':Todo-Item
}
}
components:['Todo-item']
```
8、style 中可以声明样式作用域
style 同名的样式不会对其他组件有影响
9、在用 Vue 框架在开发的时候一般都会用 **Vue-cli** 脚手架,但是这样经过 webpack 打包后源代码会被打包成 dist 目录下的 bundle.js此时的代码是被压缩处理过的且经过 webpack 的处理各个代码模块的逻辑,代码可读性不是很好且不可调试,此时有些人在开发阶段就会需要调试?此时怎么办呢
慌不要慌,小哥哥带你 hold 住全场。
* 脚手架已经帮你处理好了这块需求了,看下图
![配置图例](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/QQ20180602-210826@2x.png)
* 有些大佬不要脚手架,喜欢自己初始化项目,用 npm 挨个安装所需要的依赖。然后自己配置 webpack 的 options。需要调试的话需要做下面的配置
```
config.devtool = '#cheap-module-eval-source-map'
```
这样你就可以在浏览器当中像写普通的 JS 一样进行调试代码了。比如
![调试界面](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/QQ20180602-211328@2x.png)
10、Vue 中 **<template>...</template>** 底层的做法是 ** render()**方法, 对 template 中的元素依次遍历创造节点
11、使用 stylus 写 css 的时候需要告诉 webpack如何处理代码所以
```
<style lang="stylus">
...
</style>
```
12、在使用 v-for 或 jsx 的map方法时候需要在生成的节点设置一个唯一的**key**作为标识,这样下次渲染的时候就可以根据 key 值判断,是否需要刷新 dom ,提高了效率
13、props 的另一种写法
```
props:[
todos:{
type:Array,
required:true
}
]
```
14、css 单独打包,缓存下来,提高程序的体验。需要使用**extrat-text-webpack-plugin**
15、业务代码与框架代码如果打包在一起后期维护很麻烦因为框架代码基本不需要很快更新但是业务代码经常更新所以应该拆分出来单独打包。将框架代码方便缓存。
16、webpack 的2个概念
hash 各个文件生成的**hash**值相同
chunkhash不同文件模块生成的**hash**值不同
- Vue 使用官方组件(比如 vue-resource
- 先引入
- 再 use必须在 main.js 中)
```
import VueResource from 'vue-resource'
Vue.use(VueResource);
```
- 第三方组件
- 哪里用,哪里引入(比如 axios
- npm install 组件名 --save
将安装的组件名称的依赖写入 package.json
- 网络请求组件
- vue-resource
- axios
- fetch-jsonp
- 父组件给子组件传值
- 父组件在调用子组件的时候传递
- 子组件 声明 porps
- 同时可以在子组件接收的时候验证传值的正确性
```
props:['name','age']
props:{
'name':String,
'age':Number
}
```
- 父组件给子组件传递方法
```
//父
<script>
export default{
methods:{
run(){
alert("run");
}
}
}
</script>
<v-header :run="run()"></v-header>
//子
export default{
props:["run"],
methods:{
test(){
run();
}
}
}
```
- 子组件可以通过上面传递值给父组件
- 父组件可以将自身传递给子组件。所以可以在子组件里面访问父组件的属性和执行父组件的方法
- 父组件主动获取子组件的数据和方法
- 在调用子组件的时候给子组件定义一个 ref
```
<v-heaer ref="header"></v-header>
```
- 在父组件里面通过**this.$refs.header.属性** 和 **this.$refs.header.方法**
- 子组件里面获取父组件的属性和方法 **this.$parent.属性** 和**this.$parent.方法**
- 非父子组件间的传值
- 先定义1个中间的组件
- 在需要暴露数据的一方。import 中间组件,调用 $emit(事件名称,数据)
- 在需要接受数据的一方。import 中间组件,调用 $on(事件名称,function(data){ //... })
```
//中间组件
import Vue from 'Vue';
var VueEvent = new Vue();
export default = Vue;
//Home.vue(传递数据给 News.vue)
import VueEvent from '../model/VueEvent.js';
VueEvent.$emit("postData",{"name":"杭城小刘"});
//News.vue(接收数据)
import VueEvent from '../model/VueEvent.js';
new Vue({
mounted(){
VueEvent.$on("postData",function(data){
console.log("从Home组件接收到的数据"+data);
});
}
});
```
- 路由的使用规则
- 创建、引用组件main.js
```
import Home form './Components/Home.vue'
import News form './Components/News.vue'
```
- 导入 vue-router 并 usemain.js
```
import VueRouter from 'vue-router'
Vue.use(VueRouter);
```
- 配置路由main.js
```
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
];
```
- 实例化 routermain.js
```
const router = new VueRouter({
routes
//等于 routes:routes,只有当key和value一致的时候可以简写
});
```
- 挂载 routermain.js
```
var vue = new Vue({
el:"#app",
router,
data(){
return {
msg:"root components"
}
}
});
```
- 在模版里面放上路由的出口。将 <router-view></router-view> 写到根组件上 (App.vue)
```
//App.vue
<router-view></router-view>
```
- 要实现类似导航效果,可以使用 **<router-link></router-link>**
```
<router-link to="/home">首页</router-link>
<router-link to="/news">新闻</router-link>
```
- 默认首页
```
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
{path:"*",redirect:"News"} //默认跳转到首页
];
```
- Vue 动态路由和 Get 传值
```
<ul>
<li v-for="(item,key) in news"><router-link to="/content"></router-link>{{item}}</li>
</ul>
//新增加的 router-link 需要在路由配置里面添加配置项。
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
{path:"/content",component:"Content"},
{path:"*",redirect:"News"} //默认跳转到首页
];
```
- 上面的写法是静态路由,也就是不能传递参数
- 那么什么是动态路由,也就是可以传递参数
```
//新增加的 router-link 需要在路由配置里面添加配置项。
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
{path:"/content:newid",component:"Content"}, //动态路由
{path:"*",redirect:"News"} //默认跳转到首页
];
```
- 子组件页面获取动态路由传递过来的值
```
this.$route.params
```
- 子组件拿到父组件动态传递过来的值用动态路由
```
<ul>
<li v-for="(item,key) in news"><router-link :to="'/content' + key" ></router-link>{{item}}</li>
</ul>
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
{path:"/content:newid",component:"Content"}, //动态路由
{path:"*",redirect:"News"} //默认跳转到首页
];
this.$route.params
```
- Get 传值
```
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
{path:"/content",component:"Content"}, //动态路由
{path:"/product",component:"Product"}, //
{path:"*",redirect:"News"} //默认跳转到首页
];
<ul>
<li v-for="(item,key) in news"><router-link :to="'/content?id='+key" ></router-link>{{item}}</li>
</ul>
//拿到值
this.$route.query
```
- 编程式导航JS 跳转控)
```
//直接跳转到某个组件
this.$route.push({path:'News' });
//跳转到某个组件并且传递值
this.$route.push({path:'/content/495'});
```
- 命名路由跳转
```
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
{path:"/content",component:"Content",name:'news'}, //动态路由
{path:"/product",component:"Product"}, //
{path:"*",redirect:"News"} //默认跳转到首页
];
//跳转
this.$router.push({name:'news'});
```
- vue-router 默认的是 hash 模式, 也就是 127.0.0.1/# 这种。所以我们在初始化 vue-router 的时候可以修改它的模式
```
const VueRouter = new VueRouter({
mode: 'history',//hash 模式改为 history 模式
routs
})
```
- 嵌套路由(比如顶部的菜单栏不变,点击左边的菜单栏实现页面内容的切换)
```
//注册路由嵌套
const routes = [
{path:"/home",component:"Home"},
{path:"/news",component:"News"},
{
path:"/user",
components:"User",
children:[
{path:'useradd',component:'UserAdd'},
{path:'userlist',component:'UserList'},
]
},
{path:"/content",component:"Content",name:'news'}, //动态路由
{path:"/product",component:"Product"}, //
{path:"*",redirect:"News"} //默认跳转到首页
];
//将<router-view></router-view>放到动态加载的子组件的地方
//User.vue
<template>
<div>
<ul>
<router-link to='/useradd'></router-link><li>增加用户</li>
<router-link to='/userlist'></router-link><li>用户列表</li>
</ul>
<router-view></router-view>
</div>
</template>
```
- Vuex主要解决不同组件之间的数据共享、数据持久化不适合与小项目主要用于大项目
- 安装 vuex
- src 目录下新建一个 vuex 的文件夹
- vuex 文件夹下面新建一个 store.js 文件
- 引入 vuex
```
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
```
- 定义数据
```
// state 在 vuex 中用于存储数据
var state = {
count:1
}
```
- 定义方法
```
var muations = {
incCount(){
++state.count;
}
}
```
- 暴露 vuex
```
const store = new Vuex.Store({
state,
mutations
});
export default store;
```
- 使用 vuex(解决不同组件之间数据共享问题;数据持久化)
- 引入 vuex
```
import store from '../vuex/store.js'
```
- 注册 vuex
```
export default{
data(){
return {
msg: 'Hello'
}
},
store,
methods:{
}
}
```
- 使用
```
//访问属性
this.$store.state.count
//触发方法+不带参数
this.$store.commit('incCount');
//触发方法+不带参数
this.$store.commit('方法名',参数);
var mutation = {
addList(state,data{
state.list = data;
}
}
```
- getters 类似于计算属性,改变 state 里面的 count 的值会触发 setters 里面的方法,从而在里面可以获取新的值(做一些逻辑操作)
```
var getters = {
computedCount: (state) => {
return state.count*2
}
}
```
- action 类似于 mutation ,在外面使用的时候用 this.$state.dispath('方法名')

View File

@@ -0,0 +1,604 @@
# 反爬技术研究
> 对于内容型的公司,数据的安全性很重要。对于内容公司来说,数据的重要性不言而喻。比如你一个做在线教育的平台,题目的数据很重要吧,但是被别人通过爬虫技术全部爬走了?如果核心竞争力都被拿走了,那就是凉凉。再比说有个独立开发者想抄袭你的产品,通过抓包和爬虫手段将你核心的数据拿走,然后短期内做个网站和 App短期内成为你的劲敌。
# 爬虫手段
- 目前爬虫技术都是从渲染好的 html 页面直接找到感兴趣的节点,然后获取对应的文本
- 有些网站安全性做的好,比如列表页可能好获取,但是详情页就需要从列表页点击对应的 item将 itemId 通过 form 表单提交,服务端生成对应的参数,然后重定向到详情页(重定向过来的地址后才带有详情页的参数 detailID这个步骤就可以拦截掉一部分的爬虫开发者
# 制定出**Web 端反爬技术方案**
本人从这2个角度网页所见非所得、查接口请求没用出发制定了下面的反爬方案。
- 使用HTTPS 协议
- 单位时间内限制掉请求次数过多,则封锁该账号
- 前端技术限制 (接下来是核心技术)
```markdown
# 比如需要正确显示的数据为“19950220”
1. 先按照自己需求利用相应的规则数字乱序映射比如正常的0对应还是0但是乱序就是 0 <-> 11 <-> 9,3 <-> 8,...制作自定义字体ttf
2. 根据上面的乱序映射规律,求得到需要返回的数据 19950220 -> 17730220
3. 对于第一步得到的字符串依次遍历每个字符将每个字符根据按照线性变换y=kx+b。线性方程的系数和常数项是根据当前的日期计算得到的。比如当前的日期为“2018-07-24”那么线性变换的 k 为 7b 为 24。
4. 然后将变换后的每个字符串用“3.1415926”拼接返回给接口调用者。(为什么是3.1415926,因为对数字伪造反爬,所以拼接的文本肯定是数字的话不太会引起研究者的注意,但是数字长度太短会误伤正常的数据,所以用所熟悉的 Π)
```
1773 -> “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -> 313.1415926733.1415926733.141592645
02 -> "0*7+24" + "3.1415926" + "2*7+24" -> 243.141592638
20 -> "2*7+24" + "3.1415926" + "0*7+24" -> 383.141592624
```
# 前端拿到数据后再解密,解密后根据自定义的字体 Render 页面
1. 先将拿到的字符串按照“3.1415926”拆分为数组
2. 对数组的每1个数据按照“线性变换”y=kx+bk和b同样按照当前的日期求解得到逆向求解到原本的值。
3. 将步骤2的的到的数据依次拼接再根据 ttf 文件 Render 页面上。
```
- 后端需要根据上一步设计的协议将数据进行加密处理
下面以 **Node.js** 为例讲解后端需要做的事情
- 首先后端设置接口路由
- 获取路由后面的参数
- 根据业务需要根据 SQL 语句生成对应的数据。如果是数字部分,则需要按照上面约定的方法加以转换。
- 将生成数据转换成 JSON 返回给调用者
```js
// json
var JoinOparatorSymbol = "3.1415926";
function encode(rawData, ruleType) {
if (!isNotEmptyStr(rawData)) {
return "";
}
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var encodeData = "";
for (var index = 0; index < rawData.length; index++) {
var datacomponent = rawData[index];
if (!isNaN(datacomponent)) {
if (ruleType < 3) {
var currentNumber = rawDataMap(String(datacomponent), ruleType);
encodeData += (currentNumber * month + day) + JoinOparatorSymbol;
}
else if (ruleType == 4) {
encodeData += rawDataMap(String(datacomponent), ruleType);
}
else {
encodeData += rawDataMap(String(datacomponent), ruleType) + JoinOparatorSymbol;
}
}
else if (ruleType == 4) {
encodeData += rawDataMap(String(datacomponent), ruleType);
}
}
if (encodeData.length >= JoinOparatorSymbol.length) {
var lastTwoString = encodeData.substring(encodeData.length - JoinOparatorSymbol.length, encodeData.length);
if (lastTwoString == JoinOparatorSymbol) {
encodeData = encodeData.substring(0, encodeData.length - JoinOparatorSymbol.length);
}
}
```
```javascript
//字体映射处理
function rawDataMap(rawData, ruleType) {
if (!isNotEmptyStr(rawData) || !isNotEmptyStr(ruleType)) {
return;
}
var mapData;
var rawNumber = parseInt(rawData);
var ruleTypeNumber = parseInt(ruleType);
if (!isNaN(rawData)) {
lastNumberCategory = ruleTypeNumber;
//字体文件1下的数据加密规则
if (ruleTypeNumber == 1) {
if (rawNumber == 1) {
mapData = 1;
}
else if (rawNumber == 2) {
mapData = 2;
}
else if (rawNumber == 3) {
mapData = 4;
}
else if (rawNumber == 4) {
mapData = 5;
}
else if (rawNumber == 5) {
mapData = 3;
}
else if (rawNumber == 6) {
mapData = 8;
}
else if (rawNumber == 7) {
mapData = 6;
}
else if (rawNumber == 8) {
mapData = 9;
}
else if (rawNumber == 9) {
mapData = 7;
}
else if (rawNumber == 0) {
mapData = 0;
}
}
//字体文件2下的数据加密规则
else if (ruleTypeNumber == 0) {
if (rawNumber == 1) {
mapData = 4;
}
else if (rawNumber == 2) {
mapData = 2;
}
else if (rawNumber == 3) {
mapData = 3;
}
else if (rawNumber == 4) {
mapData = 1;
}
else if (rawNumber == 5) {
mapData = 8;
}
else if (rawNumber == 6) {
mapData = 5;
}
else if (rawNumber == 7) {
mapData = 6;
}
else if (rawNumber == 8) {
mapData = 7;
}
else if (rawNumber == 9) {
mapData = 9;
}
else if (rawNumber == 0) {
mapData = 0;
}
}
//字体文件3下的数据加密规则
else if (ruleTypeNumber == 2) {
if (rawNumber == 1) {
mapData = 6;
}
else if (rawNumber == 2) {
mapData = 2;
}
else if (rawNumber == 3) {
mapData = 1;
}
else if (rawNumber == 4) {
mapData = 3;
}
else if (rawNumber == 5) {
mapData = 4;
}
else if (rawNumber == 6) {
mapData = 8;
}
else if (rawNumber == 7) {
mapData = 3;
}
else if (rawNumber == 8) {
mapData = 7;
}
else if (rawNumber == 9) {
mapData = 9;
}
else if (rawNumber == 0) {
mapData = 0;
}
}
else if (ruleTypeNumber == 3) {
if (rawNumber == 1) {
mapData = "&#xefab;";
}
else if (rawNumber == 2) {
mapData = "&#xeba3;";
}
else if (rawNumber == 3) {
mapData = "&#xecfa;";
}
else if (rawNumber == 4) {
mapData = "&#xedfd;";
}
else if (rawNumber == 5) {
mapData = "&#xeffa;";
}
else if (rawNumber == 6) {
mapData = "&#xef3a;";
}
else if (rawNumber == 7) {
mapData = "&#xe6f5;";
}
else if (rawNumber == 8) {
mapData = "&#xecb2;";
}
else if (rawNumber == 9) {
mapData = "&#xe8ae;";
}
else if (rawNumber == 0) {
mapData = "&#xe1f2;";
}
}
else{
mapData = rawNumber;
}
} else if (ruleTypeNumber == 4) {
var sources = ["年", "万", "业", "人", "信", "元", "千", "司", "州", "资", "造", "钱"];
//判断字符串为汉字
if (/^[\u4e00-\u9fa5]*$/.test(rawData)) {
if (sources.indexOf(rawData) > -1) {
var currentChineseHexcod = rawData.charCodeAt(0).toString(16);
var lastCompoent;
var mapComponetnt;
var numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
var characters = ["a", "b", "c", "d", "e", "f", "g", "h", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
if (currentChineseHexcod.length == 4) {
lastCompoent = currentChineseHexcod.substr(3, 1);
var locationInComponents = 0;
if (/[0-9]/.test(lastCompoent)) {
locationInComponents = numbers.indexOf(lastCompoent);
mapComponetnt = numbers[(locationInComponents + 1) % 10];
}
else if (/[a-z]/.test(lastCompoent)) {
locationInComponents = characters.indexOf(lastCompoent);
mapComponetnt = characters[(locationInComponents + 1) % 26];
}
mapData = "&#x" + currentChineseHexcod.substr(0, 3) + mapComponetnt + ";";
}
} else {
mapData = rawData;
}
}
else if (/[0-9]/.test(rawData)) {
mapData = rawDataMap(rawData, 2);
}
else {
mapData = rawData;
}
}
return mapData;
}
```
```javascript
//api
module.exports = {
"GET /api/products": async (ctx, next) => {
ctx.response.type = "application/json";
ctx.response.body = {
products: products
};
},
"GET /api/solution1": async (ctx, next) => {
try {
var data = fs.readFileSync(pathname, "utf-8");
ruleJson = JSON.parse(data);
rule = ruleJson.data.rule;
} catch (error) {
console.log("fail: " + error);
}
var data = {
code: 200,
message: "success",
data: {
name: "@杭城小刘",
year: LBPEncode("1995", rule),
month: LBPEncode("02", rule),
day: LBPEncode("20", rule),
analysis : rule
}
}
ctx.set("Access-Control-Allow-Origin", "*");
ctx.response.type = "application/json";
ctx.response.body = data;
},
"GET /api/solution2": async (ctx, next) => {
try {
var data = fs.readFileSync(pathname, "utf-8");
ruleJson = JSON.parse(data);
rule = ruleJson.data.rule;
} catch (error) {
console.log("fail: " + error);
}
var data = {
code: 200,
message: "success",
data: {
name: LBPEncode("建造师",rule),
birthday: LBPEncode("1995年02月20日",rule),
company: LBPEncode("中天公司",rule),
address: LBPEncode("浙江省杭州市拱墅区石祥路",rule),
bidprice: LBPEncode("2万元",rule),
negative: LBPEncode("2018年办事效率太高、负面基本没有",rule),
title: LBPEncode("建造师",rule),
honor: LBPEncode("最佳奖",rule),
analysis : rule
}
}
ctx.set("Access-Control-Allow-Origin", "*");
ctx.response.type = "application/json";
ctx.response.body = data;
},
"POST /api/products": async (ctx, next) => {
var p = {
name: ctx.request.body.name,
price: ctx.request.body.price
};
products.push(p);
ctx.response.type = "application/json";
ctx.response.body = p;
}
};
```
```javascript
//路由
const fs = require("fs");
function addMapping(router, mapping){
for(var url in mapping){
if (url.startsWith("GET")) {
var path = url.substring(4);
router.get(path,mapping[url]);
console.log(`Register URL mapping: GET: ${path}`);
}else if (url.startsWith('POST ')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`Register URL mapping: POST ${path}`);
} else if (url.startsWith('PUT ')) {
var path = url.substring(4);
router.put(path, mapping[url]);
console.log(`Register URL mapping: PUT ${path}`);
} else if (url.startsWith('DELETE ')) {
var path = url.substring(7);
router.del(path, mapping[url]);
console.log(`Register URL mapping: DELETE ${path}`);
} else {
console.log(`Invalid URL: ${url}`);
}
}
}
function addControllers(router, dir){
fs.readdirSync(__dirname + "/" + dir).filter( (f) => {
return f.endsWith(".js");
}).forEach( (f) => {
console.log(`Process controllers:${f}...`);
let mapping = require(__dirname + "/" + dir + "/" + f);
addMapping(router,mapping);
});
}
module.exports = function(dir){
let controllers = dir || "controller";
let router = require("koa-router")();
addControllers(router,controllers);
return router.routes();
};
```
- 前端根据服务端返回的数据逆向解密
```javascript
$("#year").html(getRawData(data.year,log));
// util.js
var JoinOparatorSymbol = "3.1415926";
function isNotEmptyStr($str) {
if (String($str) == "" || $str == undefined || $str == null || $str == "null") {
return false;
}
return true;
}
function getRawData($json,analisys) {
$json = $json.toString();
if (!isNotEmptyStr($json)) {
return;
}
var date= new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var datacomponents = $json.split(JoinOparatorSymbol);
var orginalMessage = "";
for(var index = 0;index < datacomponents.length;index++){
var datacomponent = datacomponents[index];
if (!isNaN(datacomponent) && analisys < 3){
var currentNumber = parseInt(datacomponent);
orginalMessage += (currentNumber - day)/month;
}
else if(analisys == 3){
orginalMessage += datacomponent;
}
else{
//其他情况待续,本 Demo 根据本人在研究反爬方面的技术并实践后持续更新
}
}
return orginalMessage;
}
```
比如后端返回的是323.14743.14743.1446根据我们约定的算法可以的到结果为1773
- 根据 ttf 文件 Render 页面
![自定义字体文件](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180724-184215@2x.png)
上面计算的到的1773然后根据ttf文件页面看到的就是1995
- 然后为了防止爬虫人员查看 JS 研究问题,所以对 JS 的文件进行了加密处理。如果你的技术栈是 Vue 、React 等webpack 为你提供了 JS 加密的插件,也很方便处理
[JS混淆工具](http://www.javascriptobfuscator.com/Javascript-Obfuscator.aspx)
- 个人觉得这种方式还不是很安全。于是想到了各种方案的组合拳。比如
##  反爬升级版
个人觉得如果一个前端经验丰富的爬虫开发者来说,上面的方案可能还是会存在被破解的可能,所以在之前的基础上做了升级版本
1. 组合拳1: 字体文件不要固定,虽然请求的链接是同一个,但是根据当前的时间戳的最后一个数字取模,比如 Demo 中对4取模有4种值 0、1、2、3。这4种值对应不同的字体文件所以当爬虫绞尽脑汁爬到1种情况下的字体时没想到再次请求字体文件的规则变掉了 😂
2. 组合拳2: 前面的规则是字体问题乱序,但是只是数字匹配打乱掉。比如 **1** -> **4**, **5** -> **8**。接下来的套路就是每个数字对应一个 **unicode 码** ,然后制作自己需要的字体,可以是 .ttf、.woff 等等。
![网页检察元素得到的效果](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180726-161418.png)
![接口返回数据](https://fantasticlbp.gitbooks.io/knowledge-kit/content/assets/WX20180726-161429.png)
这几种组合拳打下来。对于一般的爬虫就放弃了。
## 反爬手段再升级
上面说的方法主要是针对**数字**做的反爬手段,如果要对汉字进行反爬怎么办?接下来提供几种方案
1. **方案1:** 对于你站点频率最高的词云,做一个汉字映射,也就是自定义字体文件,步骤跟数字一样。先将常用的汉字生成对应的 ttf 文件;根据下面提供的链接,将 ttf 文件转换为 svg 文件,然后在下面的“字体映射”链接点进去的网站上面选择前面生成的 svg 文件将svg文件里面的每个汉字做个映射也就是将汉字专为 unicode 码(注意这里的 unicode 码不要去在线直接生成因为直接生成的东西也就是有规律的。我给的做法是先用网站生成然后将得到的结果做个简单的变化比如将“e342”转换为 “e231”然后接口返回的数据按照我们的这个字体文件的规则反过去映射出来。
2. **方案2:** 将网站的重要字体,将 html 部分生成图片,这样子爬虫要识别到需要的内容成本就很高了,需要用到 OCR。效率也很低。所以可以拦截钓一部分的爬虫
3. **方案3:** 看到携程的技术分享“反爬的最高境界就是 Canvas 的指纹,原理是不同的机器不同的硬件对于 Canvas 画出的图总是存在像素级别的误差,因此我们判断当对于访问来说大量的 canvas 的指纹一致的话,则认为是爬虫,则可以封掉它”。
本人将方案1实现到 Demo 中了。
### 关键步骤
1. 先根据你们的产品找到常用的关键词,生成**词云**
2. 根据词云,将每个字生成对应的 unicode 码
3. 将词云包括的汉字做成一个字体库
4. 将字体库 .ttf 做成 svg 格式,然后上传到 [icomoon](https://icomoon.io/app/#/select/font) 制作自定义的字体,但是有规则,比如 **“年”** 对应的 **unicode 码**是 **“\u5e74”** ,但是我们需要做一个 **恺撒加密** ,比如我们设置 **偏移量** 为1那么经过**恺撒加密** **“年”**对应的 **unicode** 码是**“\u5e75”** 。利用这种规则制作我们需要的字体库
5. 在每次调用接口的时候服务端做的事情是:服务端封装某个方法,将数据经过方法判断是不是在词云中,如果是词云中的字符,利用规则(找到汉字对应的 unicode 码再根据凯撒加密设置对应的偏移量Demo 中为1将每个汉字加密处理加密处理后返回数据
6. 客户端做的事情:
- 先引入我们前面制作好的汉字字体库
- 调用接口拿到数据,显示到对应的 Dom 节点上
- 如果是汉字文本,我们将对应节点的 css 类设置成汉字类,该类对应的 font-family 是我们上面引入的汉字字体库
```css
//style.css
@font-face {
font-family: "NumberFont";
src: url('http://127.0.0.1:8080/Util/analysis');
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@font-face {
font-family: "CharacterFont";
src: url('http://127.0.0.1:8080/Util/map');
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h2 {
font-family: "NumberFont";
}
h3,a{
font-family: "CharacterFont";
}
```
![接口效果](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180810-095006%402x.png)
![审查元素效果](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180810-095124%402x.png)
### 传送门
[字体制作的步骤](https://blog.csdn.net/fdipzone/article/details/68166388)、[ttf转svg](https://everythingfonts.com/ttf-to-svg)、[字体映射规则](https://icomoon.io/app/#/select/font)
## 实现的效果
1. 页面上看到的数据跟审查元素看到的结果不一致
2. 去查看接口数据跟审核元素和界面看到的三者不一致
3. 页面每次刷新之前得出的结果更不一致
4. 对于数字和汉字的处理手段都不一致
这几种组合拳打下来。对于一般的爬虫就放弃了。
![数字反爬-网页显示效果、审查元素、接口结果情况1](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/WX20180810-151046@2x.png)
![数字反爬-网页显示效果、审查元素、接口结果情况2](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/WX20180810-151203@2x.png)
![数字反爬-网页显示效果、审查元素、接口结果情况3](https://github.com/FantasticLBP/knowledge-kit/blob/master/assets/WX20180810-151239@2x.png?raw=true)
![数字反爬-网页显示效果、审查元素、接口结果情况4](https://github.com/FantasticLBP/knowledge-kit/blob/master/assets/WX20180810-151308@2x.png?raw=true)
![汉字反爬-网页显示效果、审查元素、接口结果情况1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180810-095006%402x.png)
![汉字反爬-网页显示效果、审查元素、接口结果情况2](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180810-095124%402x.png)
<hr>
前面的 ttf 转 svg 网站当 ttf 文件太大会限制转换,让你购买,下面贴出个新的链接。
[ttf转svg](https://convertio.co/zh/font-converter/)
## [Demo 地址](https://github.com/FantasticLBP/Anti-WebSpider)
![效果演示](https://raw.githubusercontent.com/FantasticLBP/Anti-WebSpider/master/Anti-WebSpider.gif)
运行步骤
```powershell
//客户端。先查看本机 ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js 里面将接口地址修改为本机 ip
$ cd Demo
$ ls
REST Spider-release file-Server.js
Spider-develop Util rule.json
$ node file-Server.js
Server is runnig at http://127.0.0.1:8080/
//服务端 先安装依赖
$ cd REST/
$ npm install
$ node app.js
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
* \d :匹配一个数字
* \w : 匹配任意一个字母或数字
* . : 可以匹配任意字符串
* \* : 可以匹配任意个字符包括0个
* +: 至少一个字符
* ? : 表示0个或1个字符
* {n} :表示n个字符
* {n-m} : 表示n-m个字符
* \[ \] :表示范围
* \[0-9a-zA-Z\\_\] : 可以匹配一个数字、字母或者下划线
* \[0-9a-zA-Z\\_\]+: 可以匹配至少由一个数字、字母或者下划线组成的字符串
* \[0-9a-zA-Z\_$\]\[0-9a-zA-Z\_\_$\]\* : 可以匹配由数字、字母或者下划线,后接任意个由一个数字、字母或者下划线、$组成的字符串
# RegExp
JS有2种方式创建一个正则表达式。
第一种方式是直接通过/正则表达式/写出来。
第二种 是通过new RegExp\('正则表达式'\)创建一个RegExp对象。
#### 注意
因为第二种的写法问题,所以每个`\` 需要转义,也就是 `\\`
```
<script>
function test(){
var node = document.getElementById("validate").value;
var regStr = /^[0-9a-zA-Z]{3,8}\s*\d[3-8]*$/;
var regStr2 = new RegExp('^[0-9a-zA-Z]{3,8}\\s*\\d[3-8]*$');
console.log(regStr2.test(node));
}
</script>
<input type="text" placeholder="请输入" id="validate" />
<button onclick="test()">检测</button>
```
# 分组
除了简单地判断是否匹配外,正则表达式还可以用来提取分组 ,用 `()`
```
<script>
function test(){
var node = document.getElementById("validate").value;
var regStr = /^(\d{3})-(\d{3,8})$/;
var regStr2 = new RegExp('^(\\d{3})-(\\d{3,8})$');
console.log(regStr2.exec(node));
}
</script>
<input type="text" placeholder="请输入" id="validate" />
<button onclick="test()">检测</button>
```
* 如果正则表达式中定义了组就可以在RegExp对象上用exec\(\)方法提取出子串来。
* exec\(\)方法在匹配成功后会返回一个Array第一个元素为正则表达式匹配到的整个字符串后面的元素则表示匹配成功的子串。
* exec\(\)方法在匹配失败后会返回null
# 贪婪匹配
由于 正则表达式默认使用贪婪匹配模式,因此会造成一些问题。比如
```
var res = /^(\d+)(0*)$/;
res.exec('102300'); //['102300','102300','']
```
由于\d+采用贪婪匹配模式所以会匹配到后面的0所以加上\d+?代表使用非贪婪匹配模式
```
var res = /^(\d+?)(0*)$/;
res.exec('102300');
```

View File

@@ -0,0 +1,14 @@
# 大前端
记录大前端领域较好的文章
1. [“观点|蚂蚁金服玉伯:我们是如何从前端技术进化到体验科技的?”](https://juejin.im/post/5b6904f1f265da0f48613ab0#comment)
2. [Pattern: Backends For Frontends](https://samnewman.io/patterns/architectural/bff/)
3. [了解 BFF 架构](https://segmentfault.com/a/1190000009558309)
什么情况需要 BFF
- 比如你的后台写好了接口App 的第一个版本上线后发现现有的数据结构不能满足第二个版本的界面需求了。这时候如果没做版本控制,直接将接口做了修改,那么对于 App 没有升级的用户来说是灾难,接口很可能会挂掉。
- 如果之前是 iOS 和 Android App后来加了小程序和 H5 页面,可能需求不一样,这样直接在接口上做修改的话,之前的接口会有非常多的判断代码,逻辑太乱了。一个好的设计是 **“单一原则”**。 后台做基于领域模型的 RPC 接口,前端则根据一个中间服务拿数据,常见的有 Node、PHP、Python提供这种服务 BFF 就是这样一种概念。

View File

@@ -0,0 +1,111 @@
# Canvas
## 支持性
由于浏览器对 Canvas 的支持标准不一致,所以通常 &lt;canvas&gt; 内部添加一些说明行的 HTML 代码,如果浏览器支持 Canvas它将忽略 &lt;canvas&gt; 内部的 HTML如果浏览器不支持 Canvas它将显示 &lt;canvas&gt; 内部的HTML。
## 一、基础使用
使用 Canvas 前,用 canvas.getContext 来判断浏览器是否支持 Canvas
```
var canvas = document.getElementById("test-canvas");
if (canvas.getContext) {
console.log("你的浏览器支持canvas");
} else {
console.log("你的浏览器不支持canvas");
}
```
getContext\('2d'\) 方法拿到一个 CanvasRenderingContext2D 对象,所有的绘图操作都需要通过这个对象完成。
```
var ctx = canvas.getContext("2d");
```
如果需要绘制 3D图形我们可以通过
```
var gl = canvas.getContext("webgl");
```
### 1、绘制形状
```
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect(10,10,50,50);
ctx.fillStyle = "rgba(0,0,200,0.5)";
ctx.fillRect(30,30,50,50);
```
![](/assets/canvas - 绘制图形1.png)
* #### 绘制矩形
不同于 SVGCanvas 只提供了一种原生的图形绘制能力:矩形。 所有的其他图形的绘制都至少需要生成一条路径。
1. 绘制一个填充矩形 fillRect\(x,y,width,height\)
2. 绘制一个矩形的边框 strokeRect\(x,y,width,height\)
3. 清除矩形的指定区域 clearRect\(x,y,width,height\)
```
ctx.fillRect(20,20,160,160);
ctx.clearRect(30,30,140,140);
ctx.strokeRect(80,80,40,40);
```
![](/assets/canvas - 绘制图形2.png)
#### 2、绘制路径
图形的基本元素是路径,路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状点的集合。一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形需要一些额外的步骤。
* 首先你需要创建路径的起始点
* 然后使用画图命令去画出路径
* 之后把路径封闭
* 一旦路径生成,你就可以通过秒变或者填充路径区域来渲染图形了。
beginPath\(\):新建一条路径,生成之后,图形绘制命令被只想到路径上生成路径。
closePath\(\):闭合路径之后图形绘制命令又重新指向到上下文中。
stroke\(\):通过线条来绘制图形轮廓。给图形描边。
fill\(\): 通过填充路径的内容区域生成实心的图形。
```
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(75,50);
ctx.lineTo(100,75);
ctx.lineTo(100,25);
ctx.fillStyle = "red";
ctx.fill();
```

View File

@@ -0,0 +1,100 @@
# 动画控制的另一种技术
> 在 HTML5 的时代里我们可以通过 css3 的 animation 和 kerframes 配合使用动画;也可以使用 css 的 transform 控制动画;在 JS 里面我们通常用 setTimeout 和 setInterval 来控制动画时间。setTimeout 和 setInterval 对于控制动画时间不是很准确,因为它是靠电脑的刷新频率。并且当浏览器切换到其他页面或者最小化的时候动画还在执行并不会停止,显然是在做一些无用功。接下来要介绍一个新的特性,并且主流浏览器都对他进行了支持。
### requestAnimationFrame
来看看 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame) 对它的介绍
> window.requestAnimationFrame() 方法告诉浏览器您希望执行动画并在浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。
>
> 注意:若您想在下次重绘时产生另一个动画画面,您的回调例程必须调用 requestAnimationFrame()
>
> 当你需要更新屏幕画面时就可以调用此方法。在浏览器下次重绘前执行回调函数。回调的次数通常是每秒60次但大多数浏览器通常匹配 W3C 所建议的刷新频率。在大多数浏览器里,当运行在后台标签页或者隐藏的<iframe>里时,`requestAnimationFrame()` 会暂停调用以提升性能和电池寿命。
>
> 回调函数会被传入一个参数,[`DOMHighResTimeStamp`](https://developer.mozilla.org/zh-CN/docs/Web/API/DOMHighResTimeStamp),指示当前被 `requestAnimationFrame()` 排序的回调函数被触发的时间。即使每个回调函数的工作量的计算都花了时间单个帧中的多个回调也都将被传入相同的时间戳。该时间戳是一个十进制数单位毫秒最小精度为1ms(1000μs)。
#### 语法
```javascript
window.requestAnimationFrame(callback)
```
参数:
callback 一个指定回调函数形式的参数。该函数在下次重绘动画时调用。这个回调函数只有一个参数 **DOMHighResTimeStamp** ,指示 requestAnimationFrame() 开始触发回调函数的当前时间 preformance.now() 返回的时间)
返回值:
一个 **long** 整数,请求 ID是回调列表中的唯一标识。是个非零值。后续可以作为 **window.cancelAnimationFrame()** 以取消回调函数。
#### Demo
```html
<html>
<head>
<meta charset="utf-8">
<title>动画</title>
</head>
<body>
<div id="test" style="width:1px;height: 17px;background: #0f0;">0%</div>
<input type="button" value="Run" id="run" >
</body>
<script>
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.mzRequestAnimationFrame;
var start = null;
var ele = document.getElementById("test");
var progress =0;
function step(timestamp){
console.log("step" + timestamp);
progress += 1;
ele.style.width = progress + "%";
ele.innerHTML = progress + "%";
if (progress < 100) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
document.getElementById("run").addEventListener("click", function(){
console.log("run");
ele.style.width = "1px";
progress = 0;
requestAnimationFrame(step);
},false);
</script>
</html>
```

View File

@@ -0,0 +1,120 @@
# 自定义图标字体文件
### 一、优势和劣势
字体图标除了图像清晰度之外,比位图还有哪些优势呢?
1. **轻量性**一个图标字体比一系列的图像特别是在Retina屏中使用双倍图像要小。一旦图标字体加载了图标就会马上渲染出来不需要下载一个图像。可以减少HTTP请求还可以配合HTML5离线存储做性能优化。
2. **灵活性**图标字体可以用过font-size属性设置其任何大小还可以加各种文字效果包括颜色、Hover状态、透明度、阴影和翻转等效果。可以在任何背景下显示。使用位图的话必须得为每个不同大小和不同效果的图像输出一个不同文件。
3. **兼容性**网页字体支持所有现代浏览器包括IE低版本。
除了以上优势之外,当然也有劣势
1. 图标字体只能被渲染成单色或者CSS3的渐变色由于此限制使得它不能广泛使用。
2. 使用版权上有限制,有好多字体是收费的。当然也有很多免费开源的精美字体图标供下载使用。
3. 创作自已的字体图标很费时间,重构人员后期维护的成本偏高。
### 二、如何获取图标字体文件
1. 去付费网站购买。(缺点很明显,不能个人化定制。比如,很多网站做的反爬技术就是利用将数据利用自己设计的乱序字体文件显示到页面上,这样达到的效果就是爬虫看到页面上的数据但是自己去获取 Dom 节点拿到的数据却是错误的。具体看我的这篇文章:[Web 端反爬技术方案](https://github.com/FantasticLBP/Anti-WebSpider)
2. 找设计师生成字体文件
- 步骤1: 设计师利用创建矢量图标的软件,制作好并导出格式为 **SVG** 的图标(比如 “Illustrator” 或者 “Inkscape” ,也可以利用 PhotoShop 的路径工具)
注意:一定是封闭的路径,不能是单边路径,否则生成的图标显示不出来。多个图层的话尽量合并
- 步骤2: 将所有到处的 SVG 文件放到一个文件夹中并命名(最好符合工程模块的命名规范)
- 步骤3: 打开制作图标的 [网站](https://icomoon.io/app/#/projects) 将步骤2得到的 SVG 文件依次上传,然后也可以自定义图标对应的 unicode 码
- 步骤4 全部制作完成后点击页面右下角的 “Generate Font” 下载字体文件包
### 三、如何使用
1. 将字符直接写入 DOM 节点的位置上
```html
<style>
.iconfont{
font-family: 'AwesomeFont';
}
</style>
<span cass=”iconfont”>&#x0021;</span>
```
2. 使用 CSS 生成内容
```html
<a href=”javascript:;” class=”iconfont praise”>赞</a>
```
```css
.iconfont {
font-family:'AwesomeFont';
}
.praise:before {
content: “f00a”;
}
```
3. 使用 data-icon 属性
```html
<style>
[data-icon]:before {
font-family:'AwesomeFont';
content: attr(data-icon);
speak:none;
}
</style>
<span aria-hidden=”true” data-icon=”!”></span>赞</a>
```
### 四、常见问题
1. 跨域问题
- Apache
```
<FilesMatch “.(eot|ttf|otf|woff)”>
Header set Access-Control-Allow-Origin “*”
</FilesMatch>
```
- Nginx
```
location ~* .(eot|ttf|woff)$ {
add_header Access-Control-Allow-Origin *;
}
```
- 放在同一个域下
- 使用base64置入CSS中(Icomoon在导出图标时设置里勾选Encode & Embed Font in CSS选项)。
2. 字体图标出现锯齿
```css
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
```
<hr>
参考资料:
[PS导出SVG](https://www.jianshu.com/p/b8fef9378b0b)

View File

@@ -0,0 +1,229 @@
# [Chrome 调试技巧](https://segmentfault.com/a/1190000016256731)
> **写在前面**
> 本文包括浏览器调试不包括web移动端调试。
> 本文调试均在chrome浏览器进行
### alert
这个不用多说了,不言自明
### console
#### 基本输出
想必大家都在用console.log在控制台输出点东西其实console还有其它的方法
```
console.log("打印字符串");//在控制台打印自定义字符串
console.error("我是个错误");//在控制台打印自定义错误信息
console.info("我是个信息");//在控制台打印自定义信息
console.warn("我是个警告");//在控制台打印自定义警告信息
console.debug("我是个调试");//在控制台打印自定义调试信息
cosole.clear();//清空控制台(这个下方截图中没有)
```
![console](https://segmentfault.com/img/remote/1460000016256734)
注意上面输出的error和throw出的error不一样前者只是输出错误信息无法捕获不会冒泡更不会中止程序运行。
#### 格式化输出
除此以外console还支持自定义样式和类似c语言的printf形式
```
console.log("%s年",2016);//%s表示字符串
console.log("%d年%d月",2016,11);//%d表示整数
console.log("%f",3.1415926);//%f小数
console.log("%o",console);//%o表示对象
console.log("%c自定义样式","font-size:30px;color:#00f");
console.log("%c我是%c自定义样式","font-size:20px;color:green","font-size:10px;color:red");
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256735.png)
<todo>
#### DOM输出
下面几个比较简单的,就不举例子了,简单说一下:
```
var ul = document.getElementsByTagName("ul");
console.dirxml(ul); //树形输出table节点即<table>和它的innerHTML由于document.getElementsByTagName是动态的所以这个得到的结果肯定是动态的
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256736.png)
#### 对象输出
```
var o = {
name:"Lily",
age: 18
};
console.dir(obj);//显示对象自有属性和方法
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256737.png)
对于多个对象的集合,你可以这样,输出更清晰:
```
var stu = [{name:"Bob",age:13,hobby:"playing"},{name:"Lucy",age:14,hobby:"reading"},{name:"Jane",age:11,hobby:"shopping"}];
console.log(stu);
console.table(stu);
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256738.png)
#### 成组输出
```
//建立一个参数组
console.group("start"); //引号里是组名,自己起
console.log("sub1");
console.log("sub1");
console.log("sub1");
console.groupEnd("end");
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256739.png)
#### 函数计数和跟踪
```
function fib(n){ //输出前n个斐波那契数列值
if(n == 0) return;
console.count("调用次数");//放在函数里,每当这句代码运行输出所在函数执行次数
console.trace();//显示函数调用轨迹(访问调用栈)
var a = arguments[1] || 1;
var b = arguments[2] || 1;
console.log("fib=" + a);
[a, b] = [b, a + b];
fib(--n, a, b);
}
fib(6);
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256740.png)
Chrome开发者工具中的Sources标签页也在Watch表达式下面显示调用栈。
#### 计时
```
console.time() //计时开始
fib(100); //用上述函数计算100个斐波那契数
console.timeEnd() //计时结束并输出时长
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256741.png)
断言语句这个c++调试里面也经常用到。js中当第一个表达式或参数为true时候什么也不发生为false时终止程序并报错
```
console.assert(true, "我错了");
console.assert(false, "我真的错了");
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256742.png)
#### 性能分析
```
function F(){
var i = 0;
function f(){
while(i++ == 1000);
}
function g(){
while(i++ == 100000);
}
f();
g();
}
console.profile();
F();
console.profileEnd();
```
![console](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256743.png)
Chrome开发者工具中的Audits标签页也可以实现性能分析。
### debugger
这个重量级的是博主最常用的可能是c++出身对于单步调试由衷的热爱。单步调试就是点一下执行一句程序并且可以查看当前作用域可见的所有变量和值。而debugger就是告诉程序在那里停下来进行单步调试俗称断点。
![debugger](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256744.jpeg)
右边按钮如下:
- Pause/Resume script execution暂停/恢复脚本执行(程序执行到下一断点停止)。
- Step over next function call执行到下一步的函数调用跳到下一行
- Step into next function call进入当前函数。
- Step out of current function跳出当前执行函数。
- Deactive/Active all breakpoints关闭/开启所有断点(不会取消)。
- Pause on exceptions异常情况自动断点设置。
其实右侧还有很多强大的功能
![debugger](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256745.png)
- WatchWatch表达式
- Call Stack: 栈中变量的调用,这里是递归调用,肯定是在内存栈部分调用。
- Scope当前作用域变量观察。
- BreakPoints当前断点变量观察。
- XHR BreakPoints面向Ajax专为异步而生的断点调试功能。
- DOM BreakPoints主要包括下列DOM断点注册方式见下图
1. 当节点属性发生变化时断点Break on attributes modifications
2. 当节点内部子节点变化时断点Break on subtree modifications
3. 当节点被移除时断点Break on node removal
![debugger](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256746.png)
- Global Listeners全局事件监听
- Event Listener Breakpoints事件监听器断点列出了所有页面及脚本事件包括鼠标、键盘、动画、定时器、XHR等等。
### chrome中的调试技巧
1. DOM元素的控制台书签
Chrome开发者工具和Firebug都提供了书签功能用于显示你在元素标签页Chrome或HTML标签页Firebug中最后点击的DOM元素。如果你依次选择了A元素、B元素和C元素那么&dollar;0 表示C元素&dollar;1 表示B元素&dollar;2 表示A元素。这个和正则表达式的&dollar;符号类似,不过顺序不同)
1. 如果你想调试f函数用debug(f)语句可以增加这种断点。
2. Sources标签页左侧面板上有一个代码片段Snippet子标签页可用于保存代码片段帮你调试代码。
3. 可以用Chrome开发者工具Sources标签页中的格式化按钮Pretty Print Button格式化压缩后的代码。
4. 在Network面板选择一个资源文件右键Copy Response可快速复制响应内容。
5. 利用媒体查询这个主要是在Device Mode调节不同的分辨率显示。
6. 选择Elements按 Esc > Emulation > Sensors进行传感器模拟。
7. 点击渐入效果样式图标(紫色图标),可以预览动画效果,并可对相应的贝塞尔曲线(cubic-bezier)进行调节动画效果。
8. 在Source中按住Alt键并拖动鼠标进行多列内容选择。
9. Elements面板右键执行DOM元素节点选择Force Element State或者点击右侧Toggle Element State图标可以出发伪类。
10. Network面板中选择一张图片在右侧图片上鼠标右键选择copy it as a Data URI,就可以获取图片的Data URL (base64编码)。
11. 通过按住Ctrl键可以添加多个编辑光标同时对多处进行编辑。按下Ctrl + U可以撤销编辑。
12. Elements面板右侧的Style编辑器中点击颜色十六进制编码前的小色块会弹出一个调色板。
13. 按下Alt键并且鼠标双击选择DOM元素前面的箭头就会展开该DOM元素下的所有字节点元素.
14. 快捷键:
- **快速定位到行:**快捷键`Ctrl+O`(Mac:`CMD+O`),输入:行号:列号 来进行定位
- **元素搜索:**快捷键`Ctrl+F`(Mac:`CMD+F`),试试在搜索栏输入ID选择符或者类选择符就可以定位到元素啦
### 调试过程注意事项
1.避免记录引用类型
当记录对象或数组时永远记得你在记录什么。记录原始类型时使用带断点的watch表达式。如果是异步代码避免记录引用类型。
```
var arr = [{ num: 0 }];
setInterval(function(){
console.log(arr);
arr[0].num += 1;
}, 1000);
```
![careful](/Users/liubinpeng/Desktop/Github/knowledge-kit/第二部分 Web 前端/assets/1460000016256747.png)
这里第一个属性中对象引用的值是不可靠的。当你第一次在开发者工具中显示这个属性时num的值就已经确定了。之后无论你对同一个引用重新打开多少次都不会变化。
2.尽可能使用 source map。有时生产代码不能使用source map但不管怎样你都不应该直接对生产代码进行调试。

View File

@@ -0,0 +1,140 @@
> 当一个网站或者 App 的规模达到一定程度,需要分析用户在 App 或者网站的相应操作,则需要埋点统计用户行为,这个不用多说,具体实现有 JS 脚本写好埋点事件并调接口,今天 get 到一种新的埋点统计方式保证耳目一新。下面代码简单示范一下。
```
//index.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title>CSS埋点</title>
<style>
.background {
background-size: 100% 100%;
width: 100%;
height: 100%;
position: fixed;
z-index: -100;
}
html {
background-color: #fff;
}
.notice-content {
border: 1px #ccc solid;
padding: 19px;
border-radius: 10px;
width: 80%;
margin-left: 10%;
margin-top: 10%;
}
.check-content {
padding: 0 !important;
width: 80% !important;
margin-left: 25px;
margin-top: 10px;
}
.confirm {
float: left;
position: relative !important;
left: 6%;
height: 32px !important;
line-height: 32px !important;
}
.btn {
border: 1px solid #ff6689;
background-color: #ff6689;
width: 60%;
margin-left: 20%;
margin-top: 36px;
font-size: 16px;
font-weight: bold;
color: #FFFFFF;
}
.title {
display: block;
text-align: center;
font-size: 20px;
margin-bottom: 19px;
}
span {
display: block;
margin-bottom: 7px;
}
.mui-checkbox input[type=checkbox]:checked:before,
.mui-radio input[type=radio]:checked:before {
color: #ff6689;
}
.body-content {
width: 100%;
height: 100%;
}
body {
background-color: rgba(239, 239, 244, 0) !important;
}
.link:active::after {
margin: 100px 100px;
color: red;
content: url("http://192.168.1.100:8888/Hotels_Server/view/count.php?action=visit");
}
</style>
</head>
<body>
<div class="loading">
</div>
<div style="" class="body-content">
<div class="background">
<!-- <img id="background" src="img/background.png"> -->
</div>
<div class="notice-content">
<label class="title">登记须知</label>
<span>1.本次登记仅限于中国地区。</span>
<span>2.完成登记审核通过后,生育登记服务卡可到乡(镇、街道)直接领取,也可选择邮寄到付快递给申请人。</span>
<span>3.申请登记信息需真实完整,如有虚假,申请人将承担相应的法律责任。</span>
</div>
<a class="link title">访问</a>
</div>
</body>
</html>
```
```
//count.php
<?php
$actionName = $_REQUEST["action"];
//时间格式化
$time = time();
$time = Date("Y-m-d",$time);
echo "访问动作->" .$actionName. " 访问时间->" . $time;
?>
```
![css点击统计](/assets/2287777-c1d479c5171de2d0.png)
![php代码统计](/assets/2287777-a50bd17a33290204.png)
**说明**
* 当然这种方式使用比较简单的事件埋点。复杂的话还是需要 JS 操作。
* JS 埋点统计用户可以通过浏览器禁用CSS的话没办法禁用

View File

@@ -0,0 +1,54 @@
generator生成器是ES6标准引入的新的数据类型一个generator看上起像一个函数但是可以返回多次.
```
<script>
'use strict';
function* foo(max) {
var n = 0;
while (n < max) {
yield n;
n++;
}
return n;
}
var f = foo(3);
//方式1
console.log(f.next());
console.log(f.next());
console.log(f.next());
console.log(f.next());
//方式2
for (var f of foo(3)) {
console.log(f);
}
</script>
```
* next\(\)方法会执行一个generator的代码然后每次遇到yield x就返回一个对象{value:x,done:true/false},然后暂停。返回的value就是yield的返回值done表示这个generator是否已经执行结束了如果为done为true则value就是return的返回值。
* 当执行到done为true时这个generator对象就已经全部执行完毕就不要再继续调用next\(\)
* 第2个方法就是直接调用for...of循环迭代generator对象这种方式不需要我们自己判断done
#### 优点
generator可以把异步代码变成“同步”代码
```
<script>
try {
r1 = yield ajax("http://test1.com/get", data1);
r2 = yield ajax("http://test1.com/get", data1);
r3 = yield ajax("http://test1.com/get", data1);
success(r3);
} catch (e) {
//TODO handle the exception
handle(e);
}
</script>
```

View File

@@ -0,0 +1,122 @@
# H5性能优化方面的探索
> H5很重要很重要很重要重要的事情必须重复多遍H5的优点跨平台、迭代快、开发体验好。缺点加载慢用户体验差。所以在接下来很长一段时间内我将会从H5的几个缺点发面去研究如何优化。
## 一、缓存问题及其解决办法
经常遇到一个问题H5页面由于缓存问题经常在H5发布新版本之后客户端App看不到最新的效果之前由于杂七杂八的问题项目工期紧没好好研究最近抽空研究了下缓存问题。
缓存问题具体表现为UIWebview首次打开加载慢第二次加载速度明显快H5资源更新过后在App上看不到更改的效果
为此我认为是缓存造成的问题我进入App目录下看到Library下的Caches下面有很多文件名称很长的文件点击预览可以看到是图片、css等本来我想着找出H5资源缓存到App中的特点然后用NSFileManager删除掉缓存文件发现此路不通。
#### 我想通过控制变量法研究缓存是否存在。
#### 做了一个实验。步骤如下:
* 用HBuilder一个编辑器开启后本机端口8020就可以访问网页打开H5工程
* 在App的一个UIWebview页面上通过和电脑在同一个局域网的方式加载网页
* 在App上查看效果观察某个元素的样式
* 在HBuilder编辑器中修改元素样式
* 在App上将UIWebView返回上一界面再次进入查看该元素的样式
* 确定有没有变化,来确定有没有缓存
结论:页面实时效果变化的,没有缓存
对比实验:
* 用HBuilder一个编辑器开启后本机端口8020就可以访问网页打开H5工程
* git提交到服务端
* 在App的一个UIWebview页面上通过公网IP的方式加载网页
* 在App上查看效果观察某个元素的样式
* 在HBuilder编辑器中修改元素样式
* git提交后发布到服务器上
* 在App上将UIWebView返回上一界面再次进入查看该元素的样式
* 确定有没有变化,来确定有没有缓存
结论页面没有看到最新的效果明显缓存了。但是我很想知道为什么本地局域网的方式请求网页不会缓存而通过公网IP的方式会缓存。
为此我做了进一步的实验用谷歌浏览器分别请求本地局域网和公网ip查看资源加载的情况。
1、公网IP
![公网IP
](/assets/屏幕快照 2017-09-15 下午5.56.28.png)
2、本地局域网
![本地局域网](/assets/屏幕快照 2017-09-15 下午6.27.16.png)
关键词Status Code
结论从图上可以看出本地局域网不管首次加载还是刷新都是直接请求而通过局域网的方式请求首次请求是从服务器上获取在此刷新的时候是从from memory cache中获取的。
#### 猜想
局域网 的方式网速都比较快所以不会缓存;
公网IP的方式可能由于网速问题会将首次请求到的资源缓存下来。
所以确定缓存存在了,那么如何避免缓存?
* App在启动后请求一个接口这个接口的目的是获取当前H5资源的版本号
* 将获得的版本号保存下来App本地保存
* 由于UIWebView上加载网页发起网络请求都可以通过一个代理方法所拦截所以我们可以在这个代理方法中判断url的参数可能是[http://www.a.com/login、http://www.a.com/login.html、http://www.a.com/login.html?name=geek、http://www.a.com/login\#readme等等所以我们判断过url后考虑如何将版本号加到url里面](http://www.a.com/login、http://www.a.com/login.html、http://www.a.com/login.html?name=geek、http://www.a.com/login#readme等等所以我们判断过url后考虑如何将版本号加到url里面)
* 由于我们的App使用了不同模块的UIWebView但是都是在UIWebView上需要大量的JS交互所以使用了WebViewJavascriptBridge这个库。UIWebView本身的代理方法不会执行所以修改这个库里面的WebViewJavascriptBridge.m文件的代码差不多是下面的方式
```
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [rntity Tag 的资源直接访问equest URL];
if ([request.URL.absoluteString containsString:@"http"] || [request.URL.absoluteString containsString:@"https"]) {
if ([request.URL.absoluteString containsString:@"?"]) {
url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&h5V=%@",request.URL.absoluteString,[ProjectUtil getH5VersionString]]];
}else{
url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?h5V=%@",request.URL.absoluteString,[ProjectUtil getH5VersionString]]];
}
}
LBPLOG(@"url->%@",[url absoluteString]);
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
if ([_base isCorrectProcotocolScheme:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
```
总结:
App的缓存问题暂时研究到这里后期会继续研究其他方面的问题
# 拓展
通过浏览器我们知道有的缓存是200 OKfrom cache 有的缓存是304 Not modified。如果运维移除了Entity Tag就一直是200from cache。如果没有移除的话2者是交替出现的。
为什么2者会有区别
* 200 OKfrom cache是直接点击链接或者在浏览器地址栏中输入网址敲回车键的结果
* 而304 modified是我们刷新了浏览器页面时触发或者设置了长缓存、但Entity Tags没有移除时触发
做了 实验得出结论:
* 直接访问有缓存的网站都触发 200 OK \(from cache\)
* 刷新浏览器则会触发304
* 同一域名下,没有 Entity Tag 的资源直接访问,是 200 OK \(from cache\) 的结果
* 同一域名下有Entity Tag 的资源直接访问是出现304 Not Modified

View File

@@ -0,0 +1,104 @@
# 一、方式一
在很早以前我们自定义元素的属性要通过 `user-defined-attribute="value"`的方式来设置自己需要的属性
设置自定义属性
```
<h1 user-defined-attribute="share">杭城小刘</h1>
```
获取自定义属性
```
document.getElementsByTagName("h1")[0].getAttribute("user-defined-attribute")
```
# 二、方式二
现在H5为我们提供了一个data属性 **"data-" **作为前缀可以让所有的HTML元素都支持自定义的属性只要在标签里面以 **"data-"**
为前缀定义需要的属性即可
设置自定义属性
```
<h1 data-share="true">杭城小刘</h1>
```
获取自定义属性使用H5自定义属性对象Dataset来获取
```
var myDiv = document.getElementsByTagName("h1")[0];
var theValue = myDiv.dataset; //DOMStringMap对象
document.getElementsByTagName("h1")[0].dataset.share
document.getElementsByTagName("h1")[0].dataset["share"]
```
```
document.getElementsByTagName("h1")[0].getAttribute("data-share")
```
`DOMStringMap`是HTML5一种新的含有多个名-值对的交互变量
# 三、H5 dataset的操作
删掉一个data属性
```
delete myDiv.dataset.share
```
增加一个属性
```
myDiv.dataset.happy="ok"
```
# 四、dataset兼容性处理
如果不支持dataset有必要做一下兼容性处理
```
<script>
if (myDiv.dataset) {
myDiv.dataset.sad = "false";
var thevalue = myDiv.dataset.sad;
} else {
myDiv.setAttribute("data-attribute", "sad");
var theValue = myDiv.getAttribute("data-attribute"); // 获取自定义属性
}
</script>
```
做一个实验:
```
<!DOCTYPE html>
<html>
<head>
<title>我的标题</title>
<meta charset="utf-8" />
</head>
<body>
<h1 data-share="true">杭城小刘</h1>
</body>
<script>
console.log(document.getElementsByTagName("h1")[0].getAttribute("user-defined-attribute"));
</script>
</html>
```
然后利用chrome调试在console命令行分别输入3条指令结果如下图
![实验结果](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/屏幕快照%202017-12-05%20下午10.19.04.png)
可以看出来dataset后跟的属性是驼峰命名原则如果多个单词第二个单词首字母需要大写检查元素可以看到神奇的变化。

View File

@@ -0,0 +1,94 @@
### 原型继承
JS中的继承跟其他面向对象语言的继承有点不太一样要正确的了解JS中的继承还得了解JS的前世今生。
#### 一、从JS的诞生之初说起
要理解Javascript的设计思想还得从它的诞生说起。
1994年网景公司Netscape发布了 Navigator 浏览器 0.9 版。这是历史上第一个比较成熟的网络浏览器,轰动一时,但是这个版本的浏览器只能用来浏览,不具备与访问者交互的能力。比如网页上有一个地方需要用户输入“用户名” ,当时浏览器无法判断访问者是否真的填写了,只有让服务器判断,如果没有填写,服务器就返回错误,这太浪费时间和服务器资源了。
因此,网景公司需要一种网页脚本语言,使得浏览器可以与网页互动,工程师 Brendan Eich 负责开发这种新语言。他觉得没有必要设计的很复杂,这种语言只要能够完成一些简单的操作就足够了,比如判断用户有没有填写表单。
1994年正是面向对象编程OOP最兴盛的时期C++是当时最流行的语言Java语言的1.0版即将在第二年推出Sun 公司正在大肆造势。
Brendan Eich 无疑收到了影响JS里面所有的数据类型都是对象这一点与Java非常相似但是他随即遇到一个问题到底要不要设计继承机制
#### 二、Brendan Eich的选择
如果 真的是一种简易的脚本语言其实不需要有“继承”机制但是JS里面都是对象必须有一种机制将所有的对象都联系起来所以他最后还是设计了“继承”。
但是他没有引入“类”的概念因为一旦有了“类”JS就是一门完全面向对象的语言了增加了初学者的难度他考虑到 C++ 和 Java 都使用 new 命令,生成实例。
C++ 写法
ClassName *object = new ClassName(param);
Java 写法
Foo foo = new Foo();
因此引入了 new 命令用来从原型对象生成一个实例对象但是JS没有“类”的概念怎么表示原型对象
这时他想到 C++ 和 Java 都是使用 new 命令会调用类的构造函数constructor。他就设计了一个简化版在 JS 语言中new 命令后跟的不是类,而是构造函数。
举例来说,用狗的构造函数表示狗对象的原型。
function Dog(name){
this.name = name;
}
对这个构造函数使用 new 就会生成一个狗对象的实例。
var dog1 = new Dog("阿拉斯加");
console.log(dog1.name); //阿拉斯加
注意:构造函数中的 this 关键字,指向了新构建的实例对象。
#### 三、new 运算符的缺点
用构造函数生成的实例对象,有一个缺点就是无法共享属性和方法。
比如在Dog对象的构造函数中设置一个实例对象的共有属性category
function Dog(name){
this.name = name;
this.categey = "dog";
}
var dog1 = new Dog("阿拉斯加");
var dog2 = new Dog("萨摩");
console.log(dog1.category); //dog
console.log(dog2.category); //dog
dog1.category = "阿拉斯加";
console.log(dog1.category); //阿拉斯加
console.log(dog2.category); //dog
这时候2个对象的category属性是独立的修改其中一个不会影响另一个。
这样 的弊端就是每一个实例对象,都有自己的属性和方法的副本,无法做到数据的共享,对内存的极大浪费。
#### 四、prototype 属性的引入
考虑到这一点Brendan Eich 决定为构造函数设置一个 prototype 属性。
这个属性包含一个对象,所有的实例对象需要共享的属性和方法都保存在这个对象里面,那些不需要共享的属性和方法就放在构造函数里面。
实例对象一旦创建,将自动引用 prototype 对象的属性和方法, 也就是说实例对象的属性和方法,分成两种,一种是自己(子类)的属性和方法,另一种是引用的(父类)。
function Dog(name) {
this.name = name;
}
Dog.prototype.category = "dog";
var dog1 = new Dog("阿拉斯加");
var dog2 = new Dog("萨摩");
console.log(dog1.category); //dog
console.log(dog2.category); //dog
dog1.category = "啸天犬";
console.log(dog1.category); //啸天犬
console.log(dog2.category); //dog
dog1.__proto__.category = "啸天犬";
console.log(dog1.category); //啸天犬
console.log(dog2.category); //啸天犬
现在 category 属性存放在 prototype 对象里面,被子类共享,只要修改 prototype 对象里面的 category 值,子类都会被改变。
修改可以通过2种方法修改
1、dog1.__proto__.category = "啸天犬";
2、Dog.prototype.category = "啸天犬";
五、总结
由于所有的实例对象共享同一个 prototype 对象,那么外界看起来就是 prototype 对象就像是实例对象的原型, 而实例对象就好像继承了 prototype 对象一样。

View File

@@ -0,0 +1,27 @@
* 将json转换为对象JSON.parse\(\)函数的第二个参数用来转换解析出的属性
```
JSON.parse('{"name":"lbp","age":"20"}',function(key,value){
if(key == "name"){
return value + "同学";
}
return value;
});
```
* 将对象转换为jsonJSON.stringify\(\)函数的第二个参数用来筛选对象的键值
```
var student = {"name":"小米","age":22,"height":177,"skills":["js","oc"]};
function convert(key,value){
if (typeof value === "string") {
return value.toString().toUpperCase();
}
return value;
}
JSON.stringify(student,convert,' ');
```

View File

@@ -0,0 +1,241 @@
> vue-cli作为vue的脚手架可以帮助我们在实际开发中自动生成vue.js的模板工程。第一次使用这个工具记录下步骤
前提:需要 vue 和 webpack
1、全局安装 vue-cli
```
npm install vue-cli -g
```
2、初始化
```
vue init webpack 项目名称
```
学过 Node 的人都知道此时会生成一个 package.json 文件,需要你输入或者选择一些工程信息。
3、进入项目文件夹
```
cd 项目名称
```
4、然后启动项目
```
npm run dev
```
## 关键点讲解
1、程序主要入口是 main.js
```
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = true
/* eslint-disable no-new */
new Vue({
el:"#app",
components:{App},
template:'<App />'
})
```
* 可以看到注册了 App 组建Vue 是组件化思想)
2、App.vue
```
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
name: "App",
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
```
* 导入了 HelloWorld 模块,并注册为一个组件。
* 其中看到一个 name ,只有作为组件选项时起作用。允许组件模板递归地调用自身。注意,组件在全局用`Vue.component()`注册时,全局 ID 自动作为组件的 name。
指定`name`选项的另一个好处是便于调试。有名字的组件有更友好的警告信息。另外,当在有[vue-devtools](https://github.com/vuejs/vue-devtools),未命名组件将显示成`<AnonymousComponent>`,这很没有语义。通过提供`name`选项,可以获得更有语义信息的组件树。
* &lt;HelloWorld /&gt; 这样写就是将注册好的组件使用(可以看成是搭积木)
3、HelloWorld.vue
```
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Hello 杭城小刘Welcome to Your Vue.js App'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
```
* 做为组件,对外暴露了 name
* {{msg}} 绑定了数据
效果图
![效果图](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Vue-20180225-1.png)
## 实验
有了基本的了解,我们参考 vue 的组件化思想,动手做一个展示 编程语言兴趣的小组件。
```
//Hobby.vue
<template>
<div id="hobby">
<span>{{it}}</span>
</div>
</template>
<script>
export default {
name: "Hobby",
data() {
return {
it: "iOS、JS、Node、PHP、Vue、Python、Sass"
}
}
}
</script>
<style scoped>
#hobby {
color: red;
font-size: 30px;
text-decoration: underline;
line-height: 30px;
}
</style>
//HelloWorld.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<Hobby />
</div>
</template>
<script>
import Hobby from './Hobby'
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Hello 杭城小刘Welcome to Your Vue.js App'
}
},
components: {
Hobby
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
```
效果图
![添加了Hobby组件](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Vue-20180225-2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,4 @@
# 第二部分
第二部分主要介绍 Web 前端开发中遇到的问题或者有趣的知识