docs: 批量博文

This commit is contained in:
杭城小刘
2020-02-25 17:46:51 +08:00
parent 8e5d2c9e7f
commit 6e99436a9e
373 changed files with 18071 additions and 1116 deletions

View File

@@ -0,0 +1,196 @@
# 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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Chrome-Vue-tools1.png)
改用命令
```
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
```
![改用命令](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Chrome-Vue-tools3.png)
继续 npm install
* 第三步:编译项目文件
```
npm run build
```
![编译项目文件](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Chrome-Vue-tools4.png)
* 第四步:添加至 Chrome 浏览器的拓展
```
浏览器地址栏输入chrome://extensions/
点击“加载已解压的拓展程序”选择本地 clone 下来的文件夹中的 shells -> chrome 文件夹vue-devtools-master/shells/chrome
```
![Chrome 添加拓展](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Chrome-Vue-tools5.png)
* 第五步:重启浏览器
* 第六步:在浏览器中的调试 Vue 代码
![Chrome 调试 Vue](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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,576 @@
# 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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/QQ20180602-210826@2x.png)
* 有些大佬不要脚手架,喜欢自己初始化项目,用 npm 挨个安装所需要的依赖。然后自己配置 webpack 的 options。需要调试的话需要做下面的配置
```
config.devtool = '#cheap-module-eval-source-map'
```
这样你就可以在浏览器当中像写普通的 JS 一样进行调试代码了。比如
![调试界面](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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('方法名')
- 某些页面可能我们只需要切换数据源和样式模版。所以数据源有可能是变化的,页面的模版也是变化的。
这样子我们可能会用到 `v-if、v-else-if...v-else` 来切换页面的模版。但是在模版里面去 `v-for` 循环展示数据源。
早期遇到错误,大概意思是说我们循环动态生成的元素,有 key 重复了,最后查找资料得到解决方案。在判断模版的时候需要使用 `<template v-if="type==1"></template>`
```
<template v-else-if="collectType==4">
<b-card v-for="(item, index) in qualifications" v-bind:key="'qualification' + index" :title="item.company"
sub-title="">
<p class="card-text">
<em>法定代表人:{ {item.legalperson} }</em>
<em>成立时间:{ {item.create} }</em>
<em>注册资本:{ {item.capital} }(万元)</em>
</p>
<p v-for="(qualification,index) in item.qualifications" v-bind:key="index" style="font-size:13px;">
{ {qualification} }
</p>
</b-card>
</template>
```

View File

@@ -0,0 +1,605 @@
# 反爬技术研究
> 对于内容型的公司,数据的安全性很重要。对于内容公司来说,数据的重要性不言而喻。比如你一个做在线教育的平台,题目的数据很重要吧,但是被别人通过爬虫技术全部爬走了?如果核心竞争力都被拿走了,那就是凉凉。再比说有个独立开发者想抄袭你的产品,通过抓包和爬虫手段将你核心的数据拿走,然后短期内做个网站和 App短期内成为你的劲敌。
## 一、爬虫手段
目前爬虫技术都是从渲染好的 html 页面直接找到感兴趣的节点,然后获取对应的文本.
有些网站安全性做的好,比如列表页可能好获取,但是详情页就需要从列表页点击对应的 item将 itemId 通过 form 表单提交,服务端生成对应的参数,然后重定向到详情页(重定向过来的地址后才带有详情页的参数 detailID这个步骤就可以拦截掉一部分的爬虫开发者
## 二、制定出**Web 端反爬技术方案**
从这2个角度网页所见非所得、查接口请求没用出发制定了下面的反爬方案。
1. 使用HTTPS 协议
2. 单位时间内限制掉请求次数过多,则封锁该账号
3. 前端技术限制 (接下来是核心技术)
举例:比如需要正确显示的数据为“19950220”
#### 2.1 原始数据加密
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
````
#### 2.2 前端拿到数据后再解密,解密后根据自定义的字体 Render 页面
1. 先将拿到的字符串按照“3.1415926”拆分为数组
2. 对数组的每1个数据按照“线性变换”y=kx+bk和b同样按照当前的日期求解得到逆向求解到原本的值。
3. 将步骤2的的到的数据依次拼接再根据 ttf 文件 Render 页面上。
#### 2.3 后端需要根据上一步设计的协议将数据进行加密处理
下面以 **Node.js** 为例讲解后端需要做的事情
1. 首先后端设置接口路由
2. 获取路由后面的参数
3. 根据业务需要根据 SQL 语句生成对应的数据。如果是数字部分,则需要按照上面约定的方法加以转换
4. 将生成数据转换成 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();
};
```
#### 2.4 前端根据服务端返回的数据逆向解密
```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
#### 2.5 根据 ttf 文件 Render 页面
![自定义字体文件](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/WX20180724-184215.png)
上面计算的到的1773然后根据ttf文件页面看到的就是1995
#### 2.6 加密混淆
为了防止爬虫人员查看 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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180726-161418.png)
![接口返回数据](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180810-151239@2x.png)
![数字反爬-网页显示效果、审查元素、接口结果情况4](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180810-151308@2x.png)
![汉字反爬-网页显示效果、审查元素、接口结果情况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,267 @@
# Chrome 调试技巧
> **写在前面**
> Chrome 有非常强大的调试功能
> 本文包括浏览器调试不包括web移动端调试。
> 本文调试均在chrome浏览器进行
### alert
这个不用多说了,不言自明
### console
#### 基本输出
想必大家都在用console.log在控制台输出点东西其实console还有其它的方法
```
console.log("打印字符串");//在控制台打印自定义字符串
console.error("我是个错误");//在控制台打印自定义错误信息
console.info("我是个信息");//在控制台打印自定义信息
console.warn("我是个警告");//在控制台打印自定义警告信息
console.debug("我是个调试");//在控制台打印自定义调试信息
cosole.clear();//清空控制台(这个下方截图中没有)
```
![console](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-01-15-01.png)
注意上面输出的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](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/1460000016256735.png)
<todo>
#### DOM输出
下面几个比较简单的,就不举例子了,简单说一下:
```
var ul = document.getElementsByTagName("ul");
console.dirxml(ul); //树形输出table节点即<table>和它的innerHTML由于document.getElementsByTagName是动态的所以这个得到的结果肯定是动态的
```
![console](https://github.com/FantasticLBP/knowledge-kit/tree/master/assets/1460000016256736.png)
#### 对象输出
```
var o = {
name:"Lily",
age: 18
};
console.dir(obj);//显示对象自有属性和方法
```
![console](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/1460000016256738.png)
#### 成组输出
```
//建立一个参数组
console.group("start"); //引号里是组名,自己起
console.log("sub1");
console.log("sub1");
console.log("sub1");
console.groupEnd("end");
```
![console](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/1460000016256740.png)
Chrome开发者工具中的Sources标签页也在Watch表达式下面显示调用栈。
#### 计时
```
console.time() //计时开始
fib(100); //用上述函数计算100个斐波那契数
console.timeEnd() //计时结束并输出时长
```
![console](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/1460000016256741.png)
断言语句这个c++调试里面也经常用到。js中当第一个表达式或参数为true时候什么也不发生为false时终止程序并报错
```
console.assert(true, "我错了");
console.assert(false, "我真的错了");
```
![console](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/1460000016256743.png)
Chrome开发者工具中的Audits标签页也可以实现性能分析。
### debugger
这个重量级的是博主最常用的可能是c++出身对于单步调试由衷的热爱。单步调试就是点一下执行一句程序并且可以查看当前作用域可见的所有变量和值。而debugger就是告诉程序在那里停下来进行单步调试俗称断点。
![debugger](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/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](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/1460000016256747.png)
这里第一个属性中对象引用的值是不可靠的。当你第一次在开发者工具中显示这个属性时num的值就已经确定了。之后无论你对同一个引用重新打开多少次都不会变化。
2.尽可能使用 source map。有时生产代码不能使用source map但不管怎样你都不应该直接对生产代码进行调试。
### 异常调试
```
<script>
const age = 23;
age = 24;
console.log(age);
</script>
```
代码会报错。为了诸多原因,我们希望提前解决,所以我们希望准确知道代码在哪里有问题。也就是需要调试,希望 JS 像其他编程语言一样可以调试。
默认情况下会在 console 里面报错。如下图
![默认报错](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-12-24-chrome1.png)
为了准确定位,我们可以在调试模式的右侧开启 “Pause on exception”按钮
![开启Pause on exception](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-12-24-chrome2.png)
如果我们的代码加了 try.catch.,那么之前的设置是不能定位到异常的位置。
```
<script>
try {
const age = 23;
age = 24;
console.log(age);
} catch (error) {
console.log(error)
}
</script>
```
![不能定位try.catch内部的错误](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-12-24-chrome3.png)
如果想捕获try.catch里面的异常则可以在调试面板的右侧勾选“Pause on caught exceptions”设置完即使是 try.catch 里面的异常也可以定位到具体位置
![打开“Pause on caught exceptions”](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-12-24-chrome4.png)

View File

@@ -0,0 +1,193 @@
# 大前端动画
> 大前端开发中经常会遇到动画的开发那么什么是动画在物理学中运动就是研究物体在时间维度和空间维度上改变的现象所以动画也一样动画主要研究2个因素发生运动物体的**时间**和**空间**。
#### Web前端开发中的动画
在 Web 前端开发中实现动画有2种方式。要么依靠 CSS 实现动画,要么依靠 JS 控制实现动画。
##### CSS 实现动画
首先要说 CSS 中的4个概念animation、transition、transform、translate
| 属性 | 含义 |
| :---: | :---: |
| transition\(过度动画\) | 用于设置元素的样式过渡效果,和 animation 有类似的效果,但存在使用场合有着较大差别 |
| transform\(变形\) | 用于设置元素的旋转、位移、缩放。和设置元素的动画并没直接关系,就跟写 css 属性一样 |
| translate\(移动\) | 用于设置元素的位置,就是 transform 的一个属性 |
| animation动画 | 用于设置怨毒的动画属性它是一个简写有6个属性值 |
**transition**
字面意思过渡是指元素从属性a的某个值过渡到属性a的另一个值这就是一个状态的改变但是需要一个条件来触发从而发生这种转变比如 `&:hover,&:checked,&:focus、媒体查询或者 JS`
```
#box {
height: 100px;
width: 100px;
background: green;
transition: transform 1s ease-in 1s;
}
#box:hover{
transform: rotate(180deg) scale(0.5,0.5);
transform: translateX(100px);
transform: translateY(100px) translateX(100px) scale(0.5, 0.5);
}
<div id="box"></div>
```
分析:给 div 添加了一个过渡动画,动画指定了 transform 动画,触发时机为当鼠标移上去的时候。因此当鼠标移入的时候元素的 transform 属性发生变化,那么这个时候触发了 transition 动画,当鼠标移除的时候也产生了 transform 的变化,因此还是会触发 transition产生动画。
上面设置了3个 transform 只有最后一个生效
因此 transition 产生动画的条件是设置的 property 发生变化,这种动画的特点是需要一个驱动力去触发。因此就存在一些缺点:
1. 需要事件触发,没法在网页加载时自动发生
2. 是一次性的,不能重复发生,除非再次触发
3. 只可以定义开始状态和结束状态,不能定义中间状态,因此没有丰富的动画空间
4. 一条 transition 规则,只能定义一个属性的变化,不能涉及多个属性
语法:**transitionproperty duration timing-function delay**
| 属性 |含义 |
| :---: | :---: |
| transition-property | 规定设置过渡效果的 css 属性名称 |
| transition-duration | 规定完成过渡效果需要时间 |
| transition-timing-function | 规定速度效果的速度曲线 |
| transition-delay | 规定动画效果何时开始 |
**animation**
animation 总体来说是对 transition 的增强,不再受限于触发时机和动画的属性值。
```
.box {
height: 100px;
width: 100px;
border: 15px solid black;
animation: changebox 4s ease-in-out 1s 1 alternate running forwards;
}
.box:hover {
animation-play-state: paused;
}
@keyframes changebox {
10% {
background: red;
}
50% {
width: 80px;
}
70% {
border: 15px solid yellow;
}
100% {
width: 180px;
height: 180px;
}
}
<div class="box"></div>
```
**animation: animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, animation-fill-mode**
|属性 | 含义 |
| :----: | :----: |
| animation-name | 用来调用@keyframes定义好的动画,与@keyframes定义的动画名称一致 |
| animation-duration | 指定元素播放动画所持续的时间 |
| animation-timing-function | 指定速度效果的速度曲线,是针对每一个小动画所在时间范围内的变化频率|
| animation-delay| 定义在执行动画之前的等待时间|
| animation-iteration-count| 定义动画的播放次数可选具体次数或者无限次infinite|
| animation-direction |设置动画播放方向normal按时间轴顺序、alternate轮流即来回往复进行) |
| animation-play-state| 控制元素的播放状态running继续、paused暂停|
| animation-fill-mode | 控制动画结束后元素的样式有4个值 none回到动画之前的状态、forwards元素停留在动画结束后的状态、backwords动画回到第一帧的状态、both根据 animation-direction 轮流应用 forwards 和 backwords 规则)。注意与 iteration-count 不要冲突|
总结:单个动画效果、简单的由 transtion 实现,复杂的用 animation 实现。animation 出现后市面上出现了很多这种 css 动画库,其中我在使用 animate.css 推荐小伙伴们使用下
##### JS 实现动画
大家用 JS 写动画立马想到的是 setTimeout 和 setInterval但是较好的动画体验是保持在 60fps 最好上面的2个 api 由于会受到 runloop 的影响并不会特别准时JS 有个 requestAnimationFrame api 可以保持动画在 60fps
JS 实现动画的本质就是控制元素在时间和空间上的变化的研究。
假如要实现一个匀速直线动画,让一个 div 在 3秒内在水平方向上从向由右移动500px。那么如何实现先从物理问题上解决吧。
总时间: 3s
总位移: 500px
那么每秒移动多少 500px/3s
我们设计一个 JS 函数。4个参数 属性开始值,属性结束值,动画执行时间,回调函数
大体思路是外界传入上面4个参数我们可以记录函数调用刚开始的时刻也就是开始时间start然后通过 performance.now\(\) 拿到当前时间now然后 period = \(now-start\) 就是经过的时间。然后通过 period/time 就是时间的进度百分比,拿这个百分比再去乘以总的属性值差就是当前的属性值,然后将计算结果实时调用回调函数(这个回调函数就是指定这个属性值如何应用到动画元素上)
```
/**
* 执行补间动画方法
*
* @param {Number} start 开始数值
* @param {Number} end 结束数值
* @param {Number} time 补间时间
* @param {Function} callback 每帧回调
* @param {Function} timing 速度曲线,默认匀速
*/
function animate(start, end, time, callback, timing = t => t) {
let startTime = performance.now() // 设置开始的时间戳
let period = end - start // 拿到数值差值
// 创建每帧之前要执行的函数
function loop() {
liveAnimationFunction = requestAnimationFrame(loop) // 下一调用每帧之前要执行的函数
const passTime = performance.now() - startTime // 获取当前时间和开始时间差
let per = passTime / time // 计算当前已过百分比
if (per >= 1) { // 判读如果已经执行
per = 1 // 设置为最后的状态
cancelAnimationFrame(raf) // 停掉动画
}
const pass = period * timing(per) // 通过已过时间百分比*开始结束数值差得出当前的数值
callback(pass)
}
let liveAnimationFunction = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数
}
function doMove(easing) {
asing])
}
function move(box, value) {
box.style.transform = `translateX(${value}px)`
}
```
#### Native 端动画iOS为例
其实动画的本质就是元素时间和空间上发生变化的研究。在 web 前端如此,在 native 端也是如此,不过就是换了一些 api 如此。
举个例子,就拿上面所说的水平位移为例,下面给 iOS 的原生代码
```
//CALayer 层动画
CABasicAnimation *positionAnimation = [CABasicAnimation animation];
//指定动画路径是水平方向x轴
positionAnimation.keyPath = @"position.x";
//指定位移距离
positionAnimation.toValue = @1000;
//下面2行代码让动画停留在动画结束的位置
positionAnimation.fillMode = kCAFillModeForwards;//(效果完全等同于 css 中的 animation-fill-mode属性
positionAnimation.removedOnCompletion = NO;
[self.animationView.layer addAnimation:positionAnimation forKey:nil];
```
css 中的 animation-fill-mode控制动画结束后元素的样式有4个值 none回到动画之前的状态、forwards元素停留在动画结束后的状态、backwords动画回到第一帧的状态、both根据 animation-direction 轮流应用 forwards 和 backwords 规则))和 nativeiOS端的 fillMode 属性一致。
下面提出 iOS 端的 fillMode 取值选项
```
/* `fillMode' options. */
CA_EXTERN CAMediaTimingFillMode const kCAFillModeForwards
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBackwards
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBoth
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeRemoved
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
```
看得出来 fillMode web 端和 native 端是一模一样的。
再举个例子,平时我们有可能画一根横线,在 web 端 和 native 端都存在 view 这样的概念,在 web 端可能会是一个 div高度设置为1或者是 canvas 实现canvas 拿到当前上下文、绘制路径、关闭路径、填充颜色)。在 native 端也一样,开启绘图上下文、拿到上下对象、绘制路径、上色、关闭上下文。
所以其他具体的例子也就不举了,本质上大前端的所有动画干的事情都一样,所以我们需要处理好时间和位置的关系。

View File

@@ -0,0 +1,10 @@
# Node.js 在 Linux 下的安装
如果是在新电脑上面部署 Node 项目,首先进入官网下载 Node 包,解压到系统的一个合适文件夹
执行下面2步命令将 Node 的命令关联到全局命令,这样就可以在命令行中执行 Node 脚本
```
sudo ln -s /home/LBP/node-v10.12.0-linux-x64/bin/node /usr/local/bin/
sudo ln -s /home/LBP/node-v10.12.0-linux-x64/bin/npm /usr/local/bin/

View File

@@ -0,0 +1,48 @@
# 浏览器不通窗口之间的通信
## localStorage
“同源”情况下,一个窗口更新 localStorage另一个窗口监听 window 对象的 “storage” 事件,来实现通信
```
window.localStorage.setItem('name','杭城小刘');
window.addEventListener('storage', function (e) {
console.log(e);
console.log(e.newValue);
})
```
## WebSocket
所有的 WebSocket 都监听同一个服务器地址,利用 `send` 发消息,利用 `onmessage` 获取消息的变化,不仅能窗口,还可以跨浏览器通信,兼容性最佳。只是需要消耗点服务器资源
```
var ws = new WebSockte('ws://192.168.0.0.1:8080/');
ws.onopen = function (event) {
ws.send({now: new Date()});
}
ws.onmessage = function (event) {
console.log(event)
}
```
## postMessage
借助 iframe 或 window.open
```
otherWindow.postMessage(message targetOrigin, [transfer])
```
## cookie + setInterval
在页面 A 设置一个使用 setInterval 定时器不断刷新,检查 Cookies 的值是否变化,如果变化就是进行刷新操作
由于 Cookies 在同一个域下可读,所以这样做的缺点是浪费资源
## SharedWorker
HTML5 中的 Web Worker 可以分为2种不同线程类型一种是专用线程 Dedicated Worker一种是共享线程 Shared Worker
- Dedicated Worker 直接使用 new Worker() 创建,这种 webWorker 是当前页面专有的
- SharedWorker 可以被多个 window、标签页、iframe 共同使用,但必须保证这些标签页都是同源的

View File

@@ -0,0 +1,9 @@
# 神器Puppeteer
> 为什么要说神器呢?因为在我眼里它就是神器,即使有些场景它没办法完美解决,但是它已经很强大了,足以满足很多开发或者需求场景。比如网页脚本注入、网页截图、爬虫、自动化测试等等功能
## 介绍
[官方文档](https://pptr.dev)

View File

@@ -0,0 +1,839 @@
# Vue 小结
> 本次串讲的主要目的在于给我们移动端的同学揭秘下目前前端开发的现状,和一些典型框架或者说是库的产生背景、以及设计思想和解决了什么样的问题。以 **Vue.js** 为例。此次讲解围绕以下几个方面展开:
> - [MV* 框架模式](#1)
> - [Vue.js 的概述](#2)
> - [Vue MVVM 的实现](#3)
> - [Vue 与 React 的对比](#4)
> - [有 Vue 基础如何快速上手 Weex](#5)
## MV* 框架模式<div id='1'></div>
### 历史
最早期的 Web 开发是洪荒时代,开发者可能写着类似以下的代码。检查用户的输入合法性,然后提交用户的表单字段到达服务器。服务器再校验一遍用户的合法性
```HTML
<html>
<head>
<meta charset="UTF-8">
<meta name="description" content="洪荒时代开发Web网页">
<title>洪荒时代</title>
<meta>
</head>
<body>
<form action="http://sdg.com/login" method="POST" onsubmit="return validate();">
<label for="username">用户名</label>
<input type="text" name="username" id="username" placeholder="请输入用户名">
<label for="password">密码</label>
<input type="password" name="password" id="password" placeholder="请输入密码">
<input type="submit">
</form>
</body>
<script>
/*
* 判断字符串是否为空
*/
function isNotEmptyStr($str) {
if($str == "" || $str == undefined || $str == null || $str == "null") {
return false;
}
return true;
}
function validate () {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
if (!isNotEmptyStr(username)) {
alert("请输入用户名");
return false;
}
if (!isNotEmptyStr(password)) {
alert("请输入密码");
return false;
}
}
</script>
</html>
```
```php
$username = addslashes($_REQUEST['username']);
$password = md5($_REQUEST['password']);
//数据表
$table = "user";
//3.得到连接对象
$PdoMySQL = new PdoMySQL();
if ($action == "login") {
$salt = "CRO";
$identidier = md5($salt.md5($username.$salt));
$token = md5(uniqid(rand(),true));
$time = time()+60*60*24*7;
$currentime = time();
$allrow = $PdoMySQL->find($table,"username='{$username}' and password='{$password}'");
$PdoMySQL->update(["time"=>$time,"identifier"=>$identidier],$table,"username='{$username}' and password='{$password}'");
$autoRows = $PdoMySQL->find($table,"username='".$username."' and identifier='".$userid."'");
if(count($autoRows) == 1){
if($currentime < $autoRows[0]["time"]){
setcookie('auth',base64_encode($autoRows[0]["id"]));
// 跳转到主页
}else{
// 给出用户信息失败的提示 alert
}
}
}
```
再到后来 Javascript 技术的发展越来越完善,网页开发有了更复杂的 JS 动画、CSS的特性也越来越强让洪荒时代的 web 开发步入到“火药文明时代”。一些大型应用的场景,页面的数据状态非常多,传统的页面开发方式有了一些问题。
1. 比如页面一个报错如果是服务端渲染,那么 error 信息直接显示到页面上。对于用户而言这些 error 信息很懵逼,体验很不好
2. error 信息里面有你的服务端信息,比如什么语言,什么框架,什么版本,什么引擎、什么服务器,这些东西对于不怀好心的 Eve 就可以利用现有漏洞去攻击服务器
3. 开发维护方式很不友好。假如你的页面有报错信息,你甚至需要前端开发者和服务端开发者一起去排查问题。开发方式就是前端开发者写模版代码,写好之后将代码交给服务端开发者,服务端开发者根据业务,去操作数据库执行 SQL ,再通过类似于 JSP、PHP 这种传统的技术渲染页面。开发效率极低。
后来诞生了 ajax 技术。通过 ajax 提高一个较好的体验( 是一种在无需重新加载整个网页的情况下能够更新部分网页的技术。Ajax 在浏览器与 Web 服务器之间使用异步数据传输HTTP 请求),这样就可使网页从服务器请求少量的信息,而不是整个页面)。有了 ajax 赋能前端开发采用了前后端分离的方案,服务端、前端各司其职。前后端开发者通过接口通信,前端开发者专心做提高用户体验的前端事情,比如写酷炫的动画。传统的服务端渲染的路子走不通了。在此背景下催生了 **REST api** 。前端开发人员高兴坏了,开发者有了能力去开发大型应用。
再到后来旧版本、性能低、不主动拥抱变化的浏览器逐渐淘汰,体验不好,用户自然不愿意去用,那么就要淘汰。移动智能设备的诞生让传统的 PC 页面开始在移动端进行尝试,发现效果还可以。当用户也越来越挑剔、用户体验的要求也越来越高。那么传统的开发方式也不能满足现在的需求了。用户多了,业务复杂了,那么 MVC 也满足不了现在开发者的要求,于是 MVVM 诞生了。当然前端也在搞工程化。
应用越复杂,现有状况就是数据状态分散在 model 和 view 中。假如Jquery时代经常将数据隐藏在form表单中只不过是隐藏的。比如 `<input class="hidden" id="userId" name="userId">` 点击按钮更新用户信息的时候经常需要将隐藏的数据也提交掉。在此背景下诞生了最早一批的框架,代表有 Backbone、Ember。
### MV* 说明MVC、MVP、MVVM...
1. 先不讲 MVC 是什么,先谈谈软件设计的一些原则和理念。
- 可靠性:应用的功能可以正常使用
- 健壮性:在用户非正常使用的时候,应用也可以正常反应,不要奔溃
- 效率性:启动时间、响应时间、效率等在用户可以容忍范围之内
以上3点是表象层的东西大多数开发者或者团队都会注意。除了这三点还有一些东西是需要在工程层面需要注意的方面。
- 可拓展性:软件不是一次性产品,需要不断的迭代更新
- 容易理解:代码易读、规范
- 可测试性:代码能够方便的编写单元测试和集成测试
- 可复用性:可复用,不需要一次次编写轮子
于是,软件设计领域有了几个通用设计原则帮助我们实现这些目标:单一功能原则、聚合复用原则、接口隔离原则、依赖倒置原则...
基于这些设计目标和理念又有了设计模式MVC、MVVM 就属于这个范畴。
2. MV*
![MVC](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-01-29-MVC.png)
- MVC:Model(模型) + View视图 + Controller控制器主要目的在于分层各司其职。 View 通过 Controller 来和 Model 联系。Controller 用来管理 View 和 Model。View 将事件传递给 ControllerController 完成业务逻辑后要求 Model 改变Model 将新的数据发送到 View用户得到反馈。
![MVP](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/2019-01-29-MVP.png)
- MVP从 MVC 演变而来,都通过 Presenter/Controller 负责逻辑处理View 负责界面展示Model 负责数据。在 MVP 中主要逻辑在 Presenter 中。View 与 Model 不发生联系,都通过 Presenter 传递。View 层非常薄,不部署任何业务逻辑,没有任何主动性,而 Presenter非常厚所有逻辑都部署在那里。
![MVVM](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-01-29-MVVM.png)
- MVVM将 MVP 中的 中Presenter 变成了 ViewModelView 的变动会自动同步到 ViewModelViewModel 的变化也会同步到 View 上,这种同步的实现是对 ViewModel 中的属性实现了 Observer当对属性存取会触发 setter 和 getter都会触发对应的操作。
## Vue.js <div id="2"></div>
对于 Vue.js 来说不只是技术的革新也是开发方式的革新。前端框架和移动端框架的差异:前端框架更像是革命性的革新,连开发方式都是天翻地覆的变化。前端里面 MVVM 的思想每个库基本都有实现;移动端的话比较少,几个大厂才有实现方式,但是使用起来感觉并不是很美好。
举个例子iOS 端的 ReactiveCocoa 使用起来高学习门槛、易出错、调试困难、风格不统一等被诟病。后来美团自研了 EasyReact。它的诞生是为了解决 iOS 工程实现 MVVM 架构但没有对应的框架支撑,而导致的风格不统一、可维护性差、开发效率低等多种问题。而 MVVM 中最重要的一个功能就是绑定EasyReact 就是为了让绑定和响应式的代码变得 Easy 起来。
### 什么是 Vue.js
> Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层不仅易于上手还便于与第三方库或既有项目整合。另一方面当与现代化的工具链以及各种支持类库结合使用时Vue 也完全能够为复杂的单页应用提供驱动。
在我看来 Vue.js 的核心思想就是「数据驱动、组件化开发、虚拟Dom」。当然结合它的脚手架让你开发一个复杂且良好的大型应用变得很容易。下面看一个 Demo 来说明下 Vue.js 的强大威力。
```HTML
<html>
<head>
<title>Vue</title>
<style>
div{
margin: 50px;
}
input {
border: 1px solid cyan;
height: 30px;
line-height: 30px;
}
p {
font-size: 30px;
}
</style>
</head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<body>
<div id="el">
<input type="text" v-model="username">
<p>{ {username} }</p>
<ul>
<li v-for="(el,index) in hobby" :id="index">{ {el.msg} }</li>
</ul>
</div>
</body>
<script>
var vm = new Vue({
el: '#el',
data () {
return {
username: '@杭城小刘',
hobby: [
{msg: '电影'},
{msg: '美食'},
{msg: '旅游'},
{msg: '乒乓球'},
{msg: '编程'}
]
}
}
})
</script>
</html>
```
在上面的代码中就声明了一个 MVVM 框架的 Web 应用,怎么体现?你可以在打开 Chrome 的调试界面,快捷键为 `Command + Option + i`,你可以在 console 中输入以下指令,可以看到界面会自动更新
```Javascript
vm.$data.username = '刘斌鹏'
vm.username = '刘斌鹏'
vm._data.username = '刘斌鹏'
vm.$data.hobby.push({msg: '探索本质'})
vm.$data.hobby.pop()
vm.$data.hobby.shift()
```
为什么呢?底层实现原理是通过 `new Vue({})` 声明了一个 MVVM 对象,绑定的 View 通过 el 获取到,数据就是原生的 Javascript 对象,这个 ViewModel 将 View 和 Model 绑定在一起, View 和 Model 不直接联系,但是 `v-model="username"` 是个什么鬼? `v-model` 是 Vue.js 中的一个指令,底层实现就是 Vue.js 将该 input 的值和 Model 中的 username 进行了绑定,代码如下
```HTML
<input v-bind:value="username" v-on:input="sth=$event.target.value">
```
我们通过 ViewModel 操纵的是 Model 当 Model 中的数据改变,假如通过 `vm.$data.username` 就会触发属性的 getter如果通过 `vm.$data.username = '刘斌鹏'` 访问的就是属性的 setterVue 观察到属性变化会自动操作 View 的响应式变化。
### 如何学习(前置条件)
- npm
npm其实是Node.js的包管理工具package manager。开发时会用到很多别人写的JavaScript代码。如果我们要使用别人写的某个包每次都根据名称搜索一下官方网站下载代码解压再使用非常繁琐。于是一个集中管理的工具应运而生大家都把自己开发的模块打包后放到 npm 官网上如果要使用直接通过npm安装就可以直接用不用管代码存在哪应该从哪下载。更重要的是如果我们要使用模块A而模块A又依赖于模块B模块B又依赖于模块X和模块Ynpm可以根据依赖关系把所有依赖的包都下载下来并管理起来。否则靠我们自己手动管理肯定既麻烦又容易出错。
- AMD、CommonJS、CMD 等规范
1. CommonJS 规范
由于为了编写大型应用程序,代码不可能编写在一个文件里,所以代码(函数、变量)分散在多个文件里面,每个应用程序都有相应的解决方案,在 Node 中就是“模块”。模块的好处也是不言而喻的,当你编写好某个功能拓展的时候可以很方便的集成到其他的模块中去引用。那么 Node 如何实现模块?由于 Javascript 是函数式编程语言,所以可以利用闭包实现。将我们的代码用闭包实现起来就可以实现将“变量”只在当前代码内有效,外部无法访问,实现了模块的隔离。所以我们可以将需要暴露出去的东西暴露给外部,这样子就可以组织大型应用程序的开发
模拟 CommonJS 的实现
```Javascript
// 准备module对象:
var module = {
id: 'hello',
exports: {}
};
var load = function (module) {
// 读取的hello.js代码:
function greet(name) {
console.log('Hello, ' + name + '!');
}
module.exports = greet;
// hello.js代码结束
return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);
```
上述代码就可以实现将所需要的东西实现模块。CommonJS 规范使用步骤1. 编写代码逻辑,通过 `module.export = 变量;` 暴露给外部2. 调用者通过 `let 变量名 = require('模块名')` 来导入所需要的模块,用一个变量去承接,然后访问属性和方法
2.由于 CommonJS 中的规范针对于 Node 很适合,因为代码文件是放在服务端磁盘,所以是同步的,读取速度很快,代码同步执行没问题。但是要在浏览器端使用这套规范显然是行不通的。为什么?看看下面代码有什么问题?
```Javascript
let Hello = require('./Hello');
Hello.sayHi()
```
用户访问页面后卡死了?因为浏览器的环境下代码资源都需要通过网络获取,所以会比较慢,如果是同步用户访问的话基本上不会去第二次访问你的网站了。在此背景下产生了针对浏览器环境下的模块问题的 AMD 规范Asynchronous Module Definition想一想如果是你的话如何设计采用异步加载的方式模块的加载不影响后续代码的执行如果遇到的代码是依赖于模块那么这些代码都会被放到一个回调函数中等模块加载完毕才会去执行回调函数里面的内容。AMD 也采用 `require()` 语句,不同于 CommonJS 它要求2个参数。
```Javascript
reuqire([module], callback)
```
说明:第一个参数是一个数组,里面是要加载模块;第二个参数 callback 是加载成功的回调函数。比如
```Javascript
require(['./Hello'], () => {
Hello.sayHi()
})
```
- Webpack
查看以前的文章 [Webpack](./2.19.md)、[webpack-dev-server](./2.13.md)
- ES6
几个概念ES、JS、CoffeeScript、TypeScript
ES(ECMAScript):标准
JS浏览器对其的实现
CoffeeScript可以编译为 Javascript抛弃 JS 中一些不好的设计
TypeScript 是现今对 JavaScript 的改进中,唯一完全兼容并作为它的超集存在的解决方案
- Flexbox
传统布局解决方案比如盒模型在实现一些效果的时候不是很方便,所以 W3C 在2009年提出了 Flex 布局系统。
[Flex参考资料](http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html)
- html、CSS
[MDN](https://developer.mozilla.org/zh-CN/search?q=Session&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=canvas&topic=svg&topic=webgl&topic=mobile&topic=webdev&topic=http&topic=webext)
### 如何学习、进阶
#### 学习
- 看着 [Vue官方文档](https://cn.vuejs.org/v2/guide/) 边看边写,因为在你 coding 的时候是拿着键盘写代码的,也需要感觉,所以平时多敲代码,边思考
- 对于没有接触过 ES6 和 Webpack 的童鞋来说,不建议直接用官方的脚手架 **vue-cli** 构件项目。所以先花点时间去学习下 ES6 的威力和 Webpack 解决了什么样的问题和它的简单用法
- 了解下 npm 的概念和解决了什么样的问题
- 一些 CSS 的知识
- 等适应了 Vue-cli 和工程构建方式以及代码组织方式后可以看看 Vue-Router、Vuex
- Vue-Router、Vuex 应用到工程项目中去,做一个 TodoList 项目
- 项目结束复盘、review 下
- [项目 Vue 小结](./2.17.md)
#### 进阶
- [Vue 代码风格指南](https://cn.vuejs.org/v2/style-guide/#避免-v-if-和-v-for-用在一起-必要)
- ES6 吃透(万变不离其宗,不要一昧追求新技术,掌握本质核心)
- 封装高阶组件slot 等技术点)
- 设计优秀良好的组件(比如用 TS 书写代码类型更为安全)
- 封装公司或者业务线或者产品为核心点的组件库
- 关注代码实现原理
- 关注前端的技术社区:[segmentfault](https://segmentfault.com)...
- 思考 Vue 框架设计的思想。类比其他框架甚至是大前端如何实现或者有没有类似的问题
- 尝试找到应用的性能症结所在,分析问题,给出解决方案并优化
- 参加行业的大会。VueConf、ReactConf
## MVVM 实现原理 <div id='3'></div>
### 几种实现双向绑定的实现原理。
看看下面的代码
```Javascript
var Book = {};
var name = '';
Object.defineProperty(Book, 'name', {
set: function (value) {
name = value;
console.log('本书名称叫做:' + value);
},
get: function () {
return '<' + name + '>';
}
});
Book.name = 'Vue.js 权威指北'
console.log(`我买了本书叫做${Book.name}`);
```
![Object.defineProperty](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-01-28-Object.defineProperty.png)
发现打印出来的东西和 Vue console 中输出基本一直,所以猜想 Vue 的实现也是依赖 `Object.defineProperty`
目前主流的框架基本都实现了单向数据绑定在我看来双向数据绑定无非就是在单项数据绑定的基础上实现了给可输入元素input、textarea添加了 changeinput事件来动态修改 Model 和 View所以我们的注意力不需要注意双向还是单向数据绑定。Vue 支持单双向数据绑定。
实现数据绑定的做法大致有如下几种方式:
- 发布者-订阅者模式Backbone.js。不去讨论
- 脏值检查Angular.js。基本通过 DOM 事件、比如用户输入、按钮点击、XHR 响应事件、浏览器 Location 变更事件、Timer、apply 等
- 数据劫持Vue.js。通过数据劫持结合发布者-订阅者模式实现。`Object.defineProperty()` 拦截属性的 setter 和 getter。在数据变动的时候发布消息给订阅者、触发相应的监听回调。
思路整理:
- 实现一个属性监听器 Observer能够对数据对象的所有属性进行监听如果有变动则将最新的值通知给订阅者
- 实现一个指令解析 Compiler对每个元素节点进行扫描和解析根据指令模版替换数据以及绑定相应的更新函数
- 实现一个 Wacther作为连接 Observer 和 Compiler 的桥梁,能够订阅并观察到每个属性的变化通知,执行指令绑定的相应回调,从而更新视图
- MVVM 入口函数整合Observer、Compiler、Wacther
![MVVM](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-01-28-Vue-MVVM.png)
看几个属性Object.defineProperty 中的 writable 和 configurable 和 enumerable 的理解
configurable 如果为 false 则不可以修改, 不可以删除。writable 如果设置为 false 则不可以采用数据运算符进行赋值
做个实验看看特殊情况。如果 writable 为 true 的时候, configurable 为 false 结果如何?
```Javascript
var o = {}; // 创建一个新对象
Object.defineProperty(o, "a", {
value : "original",
writable : false, // 这个地方为 false
enumerable : true,
configurable : true
});
o.a = 'LBP';
console.log(o.a) // "original" 此时候, 是更改不了 a 的.
var o = {}; // 创建一个新对象
Object.defineProperty(o, "a", {
value : "original",
writable : true,
enumerable : true,
configurable : false //这里为false
});
o.a = "LBP";
console.log(o.a) //LBP.此时候, a 进行了改变
delete o.a // 返回 false
```
结论onfigurable 控制是否可以删除; writable 控制是否可以修改(赋值) enumerable 控制是否可以枚举
1. 实现 Observer
可以利用 Obeject.defineProperty() 来监听属性变动,将需要 Observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter
给这个对象的某个值赋值就会触发setter那么就能监听到了数据变化。
```javascript
var data = {name: '杭城小刘'};
observe(data);
data.name = 'LBP';
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
// 取出所有属性遍历
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
function defineReactive(data, key, val) {
observe(val); // 监听子属性
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, //不能再delete
get: function() {
return val;
},
set: function(newVal) {
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
}
});
}
```
这样我们已经可以监听每个数据的变化了,那么监听到变化之后就是怎么通知订阅者了,所以接下来我们需要实现一个消息订阅器,很简单,维护一个数组,用来收集订阅者,数据变动触发 notify再调用订阅者的 update 方法,代码改善之后是这样:
```javascript
...
function defineReactive(data, key, val) {
var dep = new Dep();
observe(val); // 监听子属性
Object.defineProperty(data, key, {
...
set: function(newVal) {
if (val === newVal) return;
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
dep.notify(); // 通知所有订阅者
}
});
}
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
```
那么问题来了,谁是订阅者?怎么往订阅器添加订阅者?
没错,上面的思路整理中我们已经明确订阅者应该是 Watcher, 而且 `var dep = new Dep()` 是在 `defineReactive` 方法内部定义的,所以想通过 `dep` 添加订阅者,就必须要在闭包内操作,所以我们可以在 `getter`里面动手脚:
```javascript
// Observer.js
...
Object.defineProperty(data, key, {
get: function() {
// 由于需要在闭包内添加watcher所以通过Dep定义一个全局target属性暂存watcher, 添加完移除
Dep.target && dep.addDep(Dep.target);
return val;
}
...
});
// Watcher.js
Watcher.prototype = {
get: function(key) {
Dep.target = this;
this.value = data[key]; // 这里会触发属性的getter从而添加订阅者
Dep.target = null;
}
}
```
这里已经实现了一个 Observer 了,已经具备了监听数据和数据变化通知订阅者的功能
2. 实现 Compile
compile 主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图,如图所示:
![MVVM-Compile](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-01-28-Vue-MVVM-2.png)
因为遍历解析的过程有多次操作dom节点为提高性能和效率会先将根节点 `el` 转换成文档碎片 `fragment` 进行解析编译操作,解析完成,再将 `fragment` 添加回原来的真实dom节点中
```javascript
function Compile(el) {
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
this.$fragment = this.node2Fragment(this.$el);
this.init();
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
init: function() { this.compileElement(this.$fragment); },
node2Fragment: function(el) {
var fragment = document.createDocumentFragment(), child;
// 将原生节点拷贝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
}
};
```
compileElement 方法将遍历所有节点及其子节点,进行扫描解析编译,调用对应的指令渲染函数进行数据渲染,并调用对应的指令更新函数进行绑定,详看代码及注释说明:
```javascript
Compile.prototype = {
...
compileElement: function(el) {
var childNodes = el.childNodes, me = this;
[].slice.call(childNodes).forEach(function(node) {
var text = node.textContent;
var reg = /\{\{(.*)\}\}/; // 表达式文本
// 按元素节点方式编译
if (me.isElementNode(node)) {
me.compile(node);
} else if (me.isTextNode(node) && reg.test(text)) {
me.compileText(node, RegExp.$1);
}
// 遍历编译子节点
if (node.childNodes && node.childNodes.length) {
me.compileElement(node);
}
});
},
compile: function(node) {
var nodeAttrs = node.attributes, me = this;
[].slice.call(nodeAttrs).forEach(function(attr) {
// 规定:指令以 v-xxx 命名
// 如 <span v-text="content"></span> 中指令为 v-text
var attrName = attr.name; // v-text
if (me.isDirective(attrName)) {
var exp = attr.value; // content
var dir = attrName.substring(2); // text
if (me.isEventDirective(dir)) {
// 事件指令, 如 v-on:click
compileUtil.eventHandler(node, me.$vm, exp, dir);
} else {
// 普通指令
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}
}
});
}
};
// 指令处理集合
var compileUtil = {
text: function(node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
...
bind: function(node, vm, exp, dir) {
var updaterFn = updater[dir + 'Updater'];
// 第一次初始化视图
updaterFn && updaterFn(node, vm[exp]);
// 实例化订阅者此操作会在对应的属性消息订阅器中添加了该订阅者watcher
new Watcher(vm, exp, function(value, oldValue) {
// 一旦属性值有变化,会收到通知执行此更新函数,更新视图
updaterFn && updaterFn(node, value, oldValue);
});
}
};
// 更新函数
var updater = {
textUpdater: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
}
...
};
```
这里通过递归遍历保证了每个节点及子节点都会解析编译到,包括了{ {} }表达式声明的文本节点。指令的声明规定是通过特定前缀的节点属性来标记,如 `<span v-text="content" other-attr` 中 `v-text` 便是指令,而 `other-attr` 不是指令,只是普通的属性。
监听数据、绑定更新函数的处理是在`compileUtil.bind()` 这个方法中,通过 `new Watcher()` 添加回调来接收数据变化的通知
3. 实现Watcher
Watcher 订阅者作为 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个 update() 方法
3、待属性变动 dep.notice() 通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。
```javascript
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
// 此处为了触发属性的getter从而在dep添加自己结合Observer更易理解
this.value = this.get();
}
Watcher.prototype = {
update: function() {
this.run(); // 属性值变化收到通知
},
run: function() {
var value = this.get(); // 取到最新值
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal); // 执行Compile中绑定的回调更新视图
}
},
get: function() {
Dep.target = this; // 将当前订阅者指向自己
var value = this.vm[exp]; // 触发getter添加自己到属性订阅器中
Dep.target = null; // 添加完毕,重置
return value;
}
};
// 这里再次列出Observer和Dep方便理解
Object.defineProperty(data, key, {
get: function() {
// 由于需要在闭包内添加watcher所以可以在Dep定义一个全局target属性暂存watcher, 添加完移除
Dep.target && dep.addDep(Dep.target);
return val;
}
...
});
Dep.prototype = {
notify: function() {
this.subs.forEach(function(sub) {
sub.update(); // 调用订阅者的update方法通知变化
});
}
};
```
实例化 `Watcher` 的时候,调用 `get()` 方法,通过 `Dep.target = watcherInstance` 标记订阅者是当前watcher实例强行触发属性定义的 `getter` 方法,`getter` 方法执行的时候,就会在属性的订阅器 `dep` 添加当前 watcher 实例从而在属性值有变化的时候watcherInstance 就能收到更新通知。
4. 实现MVVM
MVVM 作为数据绑定的入口,整合 Observer、Compile、Watcher 三者,通过 Observer 来监听自己的 Model 数据变化通过Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 Model 变更的双向绑定效果。
一个简单的 MVVM 构造器是这样子:
```javascript
function MVVM(options) {
this.$options = options;
var data = this._data = this.$options.data;
observe(data, this);
this.$compile = new Compile(options.el || document.body, this)
}
```
但是这里有个问题,从代码中可看出监听的数据对象是 options.data每次需要更新视图则必须通过 `var vm = new MVVM({data:{name: '杭城小刘'} }); vm._data.name = 'LBP'; ` 这样的方式来改变数据。
显然不符合我们一开始的期望,我们所期望的调用方式应该是这样的:
`var vm = new MVVM({data: {name: '杭城小刘'} }); vm.name = 'LBP';`
所以这里需要给 MVVM 实例添加一个属性代理的方法,使访问 vm 的属性代理为访问 vm._data 的属性,改造后的代码如下:
```javascript
function MVVM(options) {
this.$options = options;
var data = this._data = this.$options.data, me = this;
// 属性代理,实现 vm.xxx -> vm._data.xxx
Object.keys(data).forEach(function(key) {
me._proxy(key);
});
observe(data, this);
this.$compile = new Compile(options.el || document.body, this)
}
MVVM.prototype = {
_proxy: function(key) {
var me = this;
Object.defineProperty(me, key, {
configurable: false,
enumerable: true,
get: function proxyGetter() {
return me._data[key];
},
set: function proxySetter(newVal) {
me._data[key] = newVal;
}
});
}
};
```
这里主要还是利用了 `Object.defineProperty()` 这个方法来劫持了 vm 实例对象的属性的读写权,使读写 vm 实例的属性转成读写了 `vm._data` 的属性值,达到鱼目混珠的效果
1. 什么是单向绑定和双向绑定?
单向绑定:将 Model 绑定到 View 上。当我们通过接口或者事件操作 Model 的改变的时候那么 View 的改变会自动触发View 自动刷新改变。
2. 双向绑定:将 Model 绑定到 View 上,通过也将 View 绑定到 Model 上。这样 View 的改变会触发 Model 的改变Model 的改变也会自动触发 View 的自动更新。
3. Vue 中如何实现单项数据绑定?
- 通过插值表达式。通过 `{ {data} }` 的形式将数据 Model 中的某个属性绑定到 Dom 节点上
- 通过 v-bind 指令。通过 `v-bind:class="hasError"` 将某个 Model 的属性绑定到对应的属性上。这样 Vue 在识别到 v-bind 指令的时候会自动将属性跟 Model 绑定起来,这样就可以通过 ViewModel 操作 Model 来动态的更新 View 层。
4. Vue 中实现双向绑定
Vue 中通过 `v-model` 实现双向绑定。可以实现 View 到 Model 的双向绑定。View 变动了 Model 会跟着变, Model 变了 View 会自动更新。
## Vue 与 React 的对比 <div id='4'></div>
先看看以下代码针对同一个字符串反转的功能2个库如何实现
```vue
<div id="app">
<p>{ { message } }</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('');
}
}
});
```
```react
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: 'Hello React.js!'
};
}
reverseMessage() {
this.setState({
message: this.state.message.split('').reverse().join('')
});
}
render() {
return (
<div>
<p>{this.state.message}</p>
<button onClick={() => this.reverseMessage()}>
Reverse Message
</button>
</div>
)
}
}
ReactDOM.render(App, document.getElementById('app'));
```
相似之处:
- 都有非常多的 star开发者非常拥护
- 都使用 Virtua DOM
- 提供了响应式和组件化的视图组件
- 将注意力放在核心实现,将其他功能比如路由、全局状态管理交给相关的库
差别:
- React 严格上只针对 MVC 的 view 层,Vue 则是 MVVM 模式
- 数据绑定: Vue 实现了数据的双向绑定,React 数据流动是单向的
单向数据流是指数据的流向只能由父组件通过props将数据传递给子组件不能由子组件向父组件传递数据要想实现数据的双向绑定只能由子组件接收父组件props传过来的方法去改变父组件的数据而不是直接将子组件的数据传递给父组件。
单向数据量组件props是父级往下传递你不能向上去修改父组件的数据并且也不能在自身组件中修改props的值。React不算mvvm虽然可以实现双向绑定在React中实现双向绑定通过state属性但如果将state绑定到视图中时直接修改state属性是不可的需要通过调用setState去触发更新视图反过来视图如果要更新也需要监听视图变化 然后调用setState去同步state状态。标准MVVM应该属于给视图绑定数据后操作数据即是更新视图
- virtual DOM 不一样,Vue 会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树.而对于 React 而言,每当应用的状态被改变时,全部组件都会重新渲染,所以 React 中会需要 shouldComponentUpdate 这个生命周期函数方法来进行控制
- 组件写法不一样, React推荐的做法是 JSX + inline style, 也就是把 HTML 和 CSS 全都写进 JavaScript 中,即 'all in js'; Vue 推荐的做法是 webpack+vue-loader 的单文件组件格式,即 html,css,JS 写在同一个文件
- 代码书写方式
使用 Vue 你可以很方便的将现有的工程迁移或者接入 Vue因为工程现有的 HTML 就是 Vue 中的视图模版,你只需要做一些 Webpack 配置化的东西,代码改动成本低,后期不用 Vue 了你更换框架的成本也比较低。
但是使用 React 你如果需要对现有工程接入的话成本很高,你甚至是重写代码,代码组织方式,工程处理方式基本也改变了。开发者可能需要适应一段时间,门槛稍高。
- 运行时性能
在 React 中当某个组件的状态发生变化的时候,它会以该组件为根,将所有的子组件树进行更新。对于如果知道不需要更新的组件可能需要使用 `PureComponent` 或者手动实现 `shouldComponentUpdate` 方法。Vue 中不需要额外注意这些事情,默认实现的。使得开发者专心做业务开发。
Vue.js使用基于依赖追踪的观察并且使用异步队列更新。轻量高性能
- 开发方式
在 React 中组件的渲染功能都依赖于 JSXJavascript的一种语法糖尽管这种方式对于 Javascript 来说很爽,但是对于已有业务进行重构是很麻烦的,为什么?你需要将你页面的东西拆分为组件,但是在 React 中组件的输出是靠 `render` 函数render 函数内部不能直接写 HTML而是需要 JSX 语法糖。
Vue.js 在这方面就比较友好,对于已经有的项目可以低成本的接入,因为已有的 HTML 代码就是模版代码,然后将业务写入到 Script 标签,操作 ViewModel。虽然 Vue.js 的组件也支持 JSX 的方法来写代码,为的就是让 React 开发者很快上手。
```react
import React, { Component } from 'react';
import { Image, ScrollView, Text } from 'react-native';
class AwkwardScrollingImageWithText extends Component {
render() {
return (
<ScrollView>
<Image
source={ {uri: 'https://i.chzbgr.com/full/7345954048/h7E2C65F9/'} }
style={ {width: 320, height:180} } />
<Text>
在iOS上React Native的ScrollView组件封装的是原生的UIScrollView。
在Android上封装的则是原生的ScrollView。
在iOS上React Native的Image组件封装的是原生的UIImageView。
在Android上封装的则是原生的ImageView。
React Native封装了这些基础的原生组件使你在得到媲美原生应用性能的同时还能受益于React优雅的架构设计。
</Text>
</ScrollView>
);
}
}
```
- 组件作用域内的 CSS
React 中的 css 是通过 css-in-JS 来实现的,和传统书写 CSS 是有区别的,不是无缝对接的,
Vue 中的 css 编写和传统的开发是一致的,你可以在  .vue 文件中对标签添加 scoped 属性来告诉 css-loader 这些 css 规则只在该模块内有效。
```CSS
<style scoped>
@media (min-width: 250px) {
.list-container:hover {
background: orange;
}
}
</style>
```
这个属性的作用就是会自动添加一个属性,为组件内的 css 指定作用域,编译成 `.list-container[data-v-21e5b78]:hover`
- 向上拓展
React 和 Vue 都提供路由、全局状态管理的解决方案,区别在于 Vue 是官方维护的React 则是社区维护的。Vuex、Redux、Vue-Router
都有脚手架Vue-cli 允许你自定义一些设备而 React 不支持。
- 向下拓展
React 学习曲线比较陡峭、也可以说对现有的工程改造门槛较高,需要大范围改写,相比 Vue 则较为友善点,侵入性低。可以像 jQuery 一样引入一个核心的 min.js 文件就可以改造接入现有工程。
- 原生渲染
React 有 React Native 一个较为成熟的方案Vue 则有阿里的 Weex。差别在于你写了 React Native 应用则不能在浏览器运行,而 Weex 可以在浏览器中和移动设备上运行。所谓多端运行的能力,
- 开发缺点
Vue 中不能检测到属性的添加、删除,所以可以用类似 React 中的 set 方法。
## 有 Vue 基础如何快速上手 Weex <div id='5'></div>
1. quick demo
2. 虽然都是采用 Vue.js 开发,但是存在 Weex 与平台的差异上下文、DOM、样式、事件Weex 不支持事件冒泡和捕获、样式Weex支持单个类选择器、并且只支持 CSS 规则的子集)、Vue 网页端的一些配置、钩子、在 Weex 中不支持
- html 标签
目前 Weex 支持了基本容器div、文本text、图片image、视频video等组件但是需要注意是组件而不是标签虽然写起来跟标签一样很像但是写其他的标签必须和这些组合起来使用。类比 Native 的视图层级
- Weex 中不存在 Dom
Weex 解析 Vue 得到的不是 dom而是原生布局树
- 支持有限的事件
因为在移动端中所有有些网页端的事件是不支持的,请查看[支持的事件列表](http://weex.apache.org/cn/wiki/common-events.html)
- 没有 BOM但可以调用原生 Api
DOMBOM
javascript组成ECMAScript 基本语法;BOM(Borwser Object Model:浏览器对象模型,使用对象模拟了浏览器的各个部分内容);DOM(Document Object Model:文档对象模型:浏览器加载显示网页的时候浏览器会为每个标签都创建一个对应的对象描述该标签的所有信息)
在 Weex 中能够调用原生设备的 api使用方法是通过注册、调用模块来实现的其中一些模块是 Weex 内置的,比如 clipboard、navigator、storage 等。为了保持框架的通用性Weex 内置的原生模块很有限,不过 Weex 提供了横向拓展的能力,可以拓展原生模块。具体参考 [Androi 拓展](http://weex.apache.org/cn/guide/extend-android.html)、[iOS 拓展](http://weex.apache.org/cn/guide/extend-ios.html)
- 样式差异
Weex 中的样式是由原生渲染器解析的出于性能和功能复杂角度的考虑Weex 对于 css 特性做了一些取舍。Weex 中只支持单个类名选择器,不支持关系选择器、也不知支持属性选择器;组件级别的作用域,为了保持 Web 和 Native 的一致性,需要使用 style scoped 的写法;支持基本的盒模型和 flexbox 的写法box-sizing 默认为 border-boxmarginpaddingborder 属性不支持合并简写;不支持 display:none可以用 display: 0; 代替,display < 0.01 的时候可以点击穿透样式属性不支持简写、提高解析效率css 不支持 3D 变化)
- 单位
Weex 中所有的 css 属性值单位为 px也可以省略不写
- Flexbox 支持不完全
`align-items: baseline;align-content:space-around;align-self:wrap_revserse;` 等
- 显隐性
在 Weex 中的 iOS 和 Android 端不支持 `display:none;` 所以 v-show 条件渲染写法也是不支持的,可以用 `v-if` 代替,或者 `display:0;` 模拟。由于移动端的渲染特点是当 opacity < 0.01 的时候 view 是可以点击穿透,所以 Weex 中当元素 display < 0.01 的时候元素看不见,但是占位空间还在,但用户无法与之交互,同样点击时会发生穿透的效果。
- css3
相比 React Native 不能用 css3Weex 的 css3 的支持程度算比较高,但是有一些 css3 的属性还是不支持的。transform 支持 2Dfont-family 支持 ttf 和 woff 字体格式的自定义的字体liner-gradient 只支持双色渐变
- 调试方式
如果说 React Native 的调试方式解放了原生开发调试、那么 Weex 就是赋予了 web 模式调试原生应用的能力。

View File

@@ -0,0 +1,140 @@
# CSS-埋点统计
> 当一个网站或者 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,20 @@
# Javascript 常用工具封装
## 类型判断
```Javascript
function type (o) {
return Object.prototype.toString.call(o).toString().slice(8, -1);
}
function sayHi () {
console.log(`Everybody sayHi`);
}
console.log(type(1)) // Number
console.log(type('@航程小刘(http://github.com/FantasticLBP)')) // String
console.log(type(null)) // Null
console.log(type(false)) // Boolean
console.log(type({})) // Object
console.log(type([1,2,3])) // Array
console.log(type(sayHi)) // Function
```

View File

@@ -0,0 +1,2 @@
# 浏览器布局与DOM绘制

View File

@@ -0,0 +1,238 @@
# React核心技术剖析
## 虚拟 Dom
传统的开发方式的是关心接口请求后的数据处理以及 Dom 操作。在推出 React 和 Vue 之流后,开发者无须直接操作 Dom而是去关心数据流动。因为在 MVVM 框架中很重要,但是在名字上没有体现出其重要性的一个角色:**binder** 层帮我们做好了 view 到 model 的绑定关系(底层实现类似对属性的 getter、setter 进行拦截,然后设置 watcher。然后通过指令去绑定到 Dom 和 数据。setter 的时候发现数据变动了则发送通知,然后 watcher 监听到后去自动刷新 Dom。你只需要关心数据。
那么如何实现的呢?我们知道 Dom 操作很耗费性能,但是到底消耗在哪里,重排、重绘、渲染算一个,可以看看这篇[文章](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.31.md)。
结合实际开发步骤想想看,先声明数据,再写 jsx 模版然后数据驱动setState拿到第一份的 Virtual Dom。在数据变动后 setState 拿到最新的数据和样式模版,生成最新的 Virtual Dom。然后根据 diff 算法找出变动的地方,然后该组件重新渲染其子组件也会跟着一起渲染。
虚拟 Dom 本质上来看就是 JS 对象(将一个 Dom 节点用 js 对象模拟表示出来)。直接操作 Dom 成本高,采用一种方式将 Dom 的树形结构表示出来,那么就采用了 js 对象去描述一个 Dom 结构。 然后再每次数据变动之后,根据数据和样式模版渲染生成新的虚拟 Dom。再利用 Diff 算法计算出差异部分,再去渲染。
React 性能高效的一个原因就是 Virtual Dom 的应用和 diff 之后的 Batch Update批量处理类比 Vue 中的 $nextTick。有 Native 开发经验的同学对于这里应该有似曾相识的感觉,和 RunLoop 很像。任何 UI 层变动的东西提交给系统,系统再下一次的运行循环到来的时候统一去渲染。)
## Diff 算法
diff 算法大体上做的事情就是拿到前后2个状态的 Virtual Dom ,然后按照同层级节点去比较,发现当前的节点有差异,则不向下进行比较,直接将当前节点重新渲染。
## JSX 的原理
JSX 做的事情是为了告诉 React 样式模版是什么。本质上来说 JSX 就是 `React.createElement` 的可读性更强的版本。`React.createElement` 接收三个参数。参数1:标签类型参数2:属性参数3:子元素。
```Javascript
render () {
const { content } = this.props
return <div><span>item-testing</span></div>
}
// 等价于下面的写法
render () {
const { content } = this.props
return React.createElement('div', {}, React.createElement('span', {}, 'item-testing'))
}
```
## 生命周期
![React生命周期](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-06-17-ReactLifecycle.PNG)
## 状态管理
Redux 设计上是对 Flux 的改进,增加了 reducer。Flux 就不再介绍了。解决了各个组件之间数据传递的复杂问题。先看看 Redux 进行状态管理的一个流程吧。
![Redux-数据流动](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-06-26-Redux-Structures.png)
### 开发步骤
- 各个组件在需要修改传递数据的时候创建一个 Action
- 利用 dispatch 提交给 store
- store 本身不处理 state所以将 action 转发给 reducer
- reducer 根据 action 的 type 判断具体如何处理数据。 reducer 中返回的函数是纯函数(输入给定的时候,输出的结果也是恒定的。且不改变输入值),函数的返回值就是 statestate 返回给 storestore 可以通过 `getState()` 拿到最新的 state 数据。
- 各个组件如果需要知道 state 的数据变化,那么可以在组件的 constructor 中设置监听订阅subscribe store代码store.subscribe(this.handleStateChange))。订阅的地方设置一个处理函数,然后在处理函数里面根据 store 获取到最新的 statethis.setState(store.getState()))。
### 开发经验
- Redux 中每次创建 action 都需要设置 typetype 为字符串,所以很容易写错,且各个组件都直接用字符串的方式创建 action 的 type 会比较分散,字符串拼写错误造成的 bug 难以排查,所以需要一个地方集中统一处理 action type。思路为在 `src/store` 文件夹下面创建 actionTypes.js 文件夹,创建全部大写的变量,然后导出。
<details>
<summary>展开示例代码</summary>
```javascript
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
export const INIT_TODO_DATA = 'init_todo_data';
export const GET_INIT_LIST = 'get_init_list'
```
</details>
- Redux 使用的时候全局工程会创建很多 action所以和上面的思想一样需要集中统一处理符合“收口原则”、“单一原则”。做法就是在 `src/store` 目录下创建一个 actionCreators.js 文件。然后在里面引入 actionType.js根据业务导出几个产生 action 的函数。
<details>
<summary>展开示例代码</summary>
```javascript
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
export const getInputChangeAction = (value) => {
return {
type: CHANGE_INPUT_VALUE,
value
}
};
export const getAddTodoItemAction = () => ({
type: ADD_TODO_ITEM
});
export const getDeleteTodoItemAction = (value) => ({
type: DELETE_TODO_ITEM,
value
});
```
</details>
- store 发现 action 提交的数据是函数类型的时候,会自动执行函数
### 核心思想
- 单一数据源:整个应用的 state 被存储在一棵 Object tree 中,并且这个 object tree 只存在于唯一一个 store 中
- State 是只读的:唯一改变 state 的方式就是触发 actionaction 是描述已发生时间的普通对象
- 使用纯函数来执行修改:为了描述 action 如何改变 state tree你需要根据业务编写 reducer
### Redux-thunk
Redux-thunk 是 redux 里面常用的一个中间件。中间件?针对谁和谁的中间?对 action 和 store 的中间件。本来 action 只可以返回一个对象,灵活性较低,但是采用了 redux-thunk 之后action 不仅可以传递对象,还可以传递函数。 action 通过 dispatch 传递给 store。 dispatch 判断 action 的类型,如果是对象则直接传递;如果是函数则直接执行。
![不使用redux-thunk时action返回函数报错](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-06-24-reduxThunk.png)
- 异步函数不应该放在组件的生命周期函数里面。复杂的业务逻辑和异步函数适合拆分。目前主流的解决方案有2种中间件redux-thunk、redux-saga。采用不同的策略
- redux-thunk将异步任务分离到 action 中。
- redux-saga将异步任务拆分到单独的文件中而不是 action 里面。
相比较而言redux-saga 比 redux-thunk 功能更加强大,提供的有用的功能更多。
- react-redux网上经常说的 react-redux 里面既有 UI 组件、也有容器组件。connect 方法将一个 UI 组件(傻瓜组件) 和 store、dispatch 联合在一起后connect 函数的返回结果就是一个容器组件
```javascript
export default connect(mapStateToProps, mapDispatchToProps)(TodoListReactRedux)
```
## 组件的写法
```javascript
// 功能组件
function Welcome (props) {
return <h2>Hello, {props.name}</h2>;
}
// 等价于下面的写法。ES6 类
class Welcome extends React.Component {
render () {
return <h2>hello, {this.props.name}</h2>;
}
}
```
###
## 开发tips
- 不要直接操作 state只能通过 setState 操作数据props 是只读的
- setState 的时候如果依赖之前的 state 数据,那么 setState 第一个参数可以更改为函数方式这个函数有2个参数
```javascript
setState((state, props) => ({ count: state.count + props.increment }));
```
- 路由设置的时候,我们经常会设置路径。每个路径匹配到具体的页面资源会呈现出来。但是在一开始的时候会遇到疑问,为什么我在浏览器里面输入了 “/home”。但是出来的内容还是 “/” 皮配到的页面,后来知道了还可以设置 **exact** 属性可以精确控制。
```javascript
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Route path='/' exact component={Home}></Route>
<Route path='/login' exact component={Login}></Route>
<Route path='/write' exact component={Write}></Route>
<Route path='/detail/:id' exact component={Detail}></Route>
</div>
</BrowserRouter>
</Provider>
```
- 在 React 的开发中有2个名字会很熟悉傻瓜组件、容器组件。假如一个 TodoList 的 UI 部分和逻辑处理部分都在 一个 TodoList 组件里面进行解决,那么代码将会冗余且不易测试,为了解决此问题,我们常常会将 UI 部分单独抽离出去,只负责显示出 UI这种组件叫做傻瓜组件UI组件。页面需要的数据或者点击事件的处理函数都通过 **props** 的形式由父组件传递下来。父组件在渲染的时候只负责逻辑的展示,在自身的 render 函数里面调用之前分离出去的傻瓜组件UI组件。为了保证代码的健壮性和安全性UI 组件需要的数据和函数都通过 props 传递,且加一个 propTypes 安全校验。
- 无状态组件:当一个组件只有 render 函数的时候,这样的组件被叫做**无状态组件**。做法就是将 `class TodoList extends React.Dom` 修改成一个函数。函数形式的无状态组件效率比较高。因为类形式的组件,会有生命周期等函数,效率会低一些。
```javascript
const TodoListUI = (props) => {
return <div>props.name</div>;
}
```
- export default 在一个模块里面只可以存在一个,使用的时候不需要 `{}`export 可以存在多个,使用的时候需要使用 `{}`
```javascript
export const person = {
name: 'lbp',
age: 22
}
export const testing = 'testing'
export default store;
import store from './store'
import { person, testing } from './store'
```
## React 和 Vue 的对比
- React 是单向数据流数据是不可变的。Vue 是双向数据流,数据是可以变的。什么意思?看下面的例子
Vue.js
```javascript
<input type="text" maxlength="11" autocomplete="off" class="form-control input-lg input-flat input-flat-user" placeholder="请输入手机号码" name="resetmobile" v-validate="'required|resetmobile'" v-model="resetmobile">
```
React.js
```javascript
constructor(props) {
super(props);
this.state = store.getState()
// 函数的 this 绑定放在顶部对 React 性能提升有好处
this.handleInputChange = this.handleInputChange.bind(this)
store.subscribe(this.handleStateChange);
}
<input placeholder="things to do..."
style={ { width: 300, marginRight: 20 } }
value={props.inputValue}
onChange={props.handleInputChange}
/>
handleInputChange(e) {
/*
// 方式1
this.setState({
inputValue: this.inputRef.value
})
*/
//方式2:redux先创建 action然后 dispatch 给 store然后 store 转发给 reducer、reducer处理完后给 store、依赖的组件设置监听然后在监听的回调里面拿到最新的数据去 render UI
const action = getInputChangeAction(e.target.value);
store.dispatch(action);
}
// 负责订阅 store 里面 state 的改变;感知到 store 里面的 state 改变,则在当前组件里面 setState
handleStateChange () {
this.setState(store.getState());
}
```
可以看出来Vue 通过简单的一个内置命令 `v-model` 将 model 和 view 双向绑定了起来数据是双向的。model 改变 view 自动刷新;用户在 input 写了文字model 的值也自动改变。React 先设置一个 input 组件监听用户的输入事件onChange然后在 onChange 里面拿到当前输入框里面的数据,然后你可以直接 setState 去操作数据setState 后才会触发 render 函数dom 才会跟着更新。
- React 比 Vue 更加适合构建大型项目。
什么是大型项目?这句话没什么意义,那就说一些区别吧。通过上面的说明知道 Vue 在内部做了双向绑定对于数据的处理更加方便。React 对于数据变动还需要自己手动去调用 setState。假如有多个数据按照 Vue 的原理会启动 n 个 watcher 去监听所以性能会有一些问题。React 性能相关都需要开发者去处理。
- Vue 设计思想How easy it can be。ReactHow corrct it can be 和 all in jscss写法也在用 js 控制,比如 styled-component

View File

@@ -0,0 +1,58 @@
# ES6学习总结
## 闭包
匿名函数保存的是引用。看看下面代码例子
```javascript
var array = new Array();
// 实验1:
for (var index = 0;index < 3; index++) {
array.push(function(){
console.log(index)
})
}
array[1]() // 输出2
// 实验2
for (var index = 0; index < 3; index++) {
(function (n) {
array.push(function(){
console.log(n);
})
})(index)
}
array[1]() // 输出1
```
### 闭包与内存泄漏
js 内存控制主要是通过计数器。当某个对象 A 引用对象 B那么 B 的计数器加1若 A 释放掉了,那么 A 引用对于 B 的计数器将变为 0。若 B 的所有计数器变为0的时候那么 B 将会被释放。引用循环将会造成内存泄漏什么是引用循环A 引用 BB 引用 A会造成引用循环内存泄漏
正是因为闭包的这个特点,我们可以将闭包应用在某些场合。
### 闭包应用场合
- 封装
```javascript
var Person = function () {
var name = "LBP";
return {
getName () {
return 'I am ' + name;
},
setName (newName) {
name = newName;
}
}
}();
console.log(Person.name);
console.log(Person.getName());
Person.name = "杭城小刘";
Person.setName('杭城小刘');
console.log(Person.getName());
```
-

View File

@@ -0,0 +1,165 @@
# 富文本编辑器原理探索
> 经常在做企业网站的管理系统的时候需要用到富文本编辑器,之前基本上都是直接去 npm 或者 github 上面搜找一些排名考前或者 readme 写的好的库,直接拿来用。万变不离其宗,是时候探索下本质了。
## contenteditable
要想实现富文本需要开启“编辑”的能力,系统提供了一个 api**contenteditable** 允许我们对内容进行编辑。下面是来自 [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable) 的官方解释。
> The contenteditable global attribute is an enumerated attribute indicating if the element should be editable by the user. If so, the browser modifies its widget to allow editing.
> The attribute must take one of the following values:
> - true or the empty string, which indicates that the element must be editable;
> - false, which indicates that the element must not be editable.
> If this attribute is not set, its default value is inherited from its parent element.
contenteditable 的值设置为 true 或者空字符串`""` 允许内容被编辑false 则代表不可被编辑。
它不仅可以作用在 textarea、div、甚至是网页所见的都可以进行编辑。所以利用这点儿你可以做一些😈坏事情 ,比如修改教务处网页上的成绩单和绩点分数,修改天气预报的温度走势情况,反手修改某一天的温度为 66度。
## document.execCommand
想想看你在输入框里面输入了一段文字你点击上面的加粗按钮如何实现MDN 告诉我们有个 api 可以满足需求。当元素进入编辑模式的时候document 对象暴露出一个 **execCommand** 方法去操纵当前的可编辑区域 。看看下方 [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) 给出的解释。
> When an HTML document has been switched to designMode, its document object exposes an execCommand method to run commands that manipulate the current editable region, such as form inputs or contentEditable elements.
> document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
> A Boolean that is false if the command is unsupported or disabled.
aCommandName命令名称。比如加粗、下划线、无序列表、段落、H1等等
aShowDefaultUI布尔值。是否展示默认的样式一般为 false
aValueArgument一些命令所需要的额外参数比如 insertImage 插入图片所需要的图片 url
完整的命令和各个浏览器的支持情况可以查看 MDN。
## Selection 和 Range 对象
在执行 document.execCommand 的时候需要知道对谁在什么范围内执行命令。这里有一个选区的概念,也就是 Selection用来表示用户选择的范围。说明用户不选中任何内容也就是只有一闪一闪的光标的情况也算是一种特殊的选中
一个页面包含多个选中区域Firefox 支持。所以 Selection 可以看作是 Range 对象的集合。通常情况下我们一般只存在一个选中的区域,所以 `document.getSelection().getRangeAt(0)` 就可以拿到当前选区的信息。
Range 对象请看下图
![选区Range对象1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-06-28-DocumentRangeObject1.png)
![选区Range对象2](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-06-28-DocumentRangeObject2.png)
上面说到**光标**也是一个特殊的选区,当 endOffset 和 startOffset 相等的时候collapsed 属性就为 true。
通过 document.getSelection().getRangeAt(0) 就可以获取到选区的信息那么可以将当前选区保存下来等到需要的时候再拿出来并展示。Selection 对象还有几个开放的方法addRange、collapse、collapseToEnd、collapseToStart可以操纵光标比如插入文字后光标的位置
## 理论联系实际、一切从实际出发
动手做一个简易的富文本编辑器吧。(不想写一个 Vue 或者 React 工程,拿最简单的 html 撸一个吧)
思路:
- 新建 html 文件
- 设置2个大的 div一个展示功能按钮一个展示编辑区域编辑区域需要设置允许可编辑
- 样式布局
- 各个功能按钮添加点击事件监听
- 因为点击各个按钮都是执行 document.execCommand唯一不同的就是命名名称和参数不一样所以简单封装函数
- 调用封装的函数,传递参数
下面贴出代码
```html
<html>
<head>
<title>富文本</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<style>
.commandZone {
margin: 20px;
margin-bottom: 0px;
background: burlywood;
}
.editor {
border: 1px solid gray;
margin: 0px 20px 20px 20px;
height: 300px;
}
.btn {
margin: 10px 20px;
color: black;
font-size: 20px;
line-height: 20px;
display: inline;
}
</style>
</head>
<body>
<div class="commandZone">
<button id="paragraphBtn" class="btn">段落</button>
<select name="hstyle" id="hstyle">
<option value="1">h1</option>
<option value="2">h2</option>
<option value="3">h3</option>
<option value="4">h4</option>
<option value="5">h5</option>
<option value="h6">h6</option>
</select>
<button id="boldBtn" class="btn">加粗</button>
<button id="undoBtn" class="btn">后退</button>
<button id="redoBtn" class="btn">前进</button>
<button id="insertHorizontalRuleBtn" class="btn">水平线</button>
<button id="insertUnorderedListBtn" class="btn">无序列表</button>
<button id="createLinkBtn" class="btn">插入链接</button>
<button id="insertImageBtn" class="btn">插入图片</button>
</div>
<div class="editor" contenteditable="true"></div>
</body>
<script>
var hStyle = '<h1>';
document.getElementById('hstyle').onchange = function () {
var optionSelectedIndex = document.getElementsByTagName('option');
hStyle = optionSelectedIndex[document.getElementById('hstyle').selectedIndex].innerHTML;
execEditorCommand('formatBlock', hStyle);
}
function execEditorCommand(name, args = null) {
document.execCommand(name, false, args);
}
document.getElementById('boldBtn').onclick = function () {
execEditorCommand('bold', null);
}
document.getElementById('insertHorizontalRuleBtn').onclick = function () {
execEditorCommand('insertHorizontalRule', null);
}
document.getElementById('insertUnorderedListBtn').onclick = function () {
execEditorCommand('insertUnorderedList', null);
}
document.getElementById('undoBtn').onclick = function () {
execEditorCommand('undo', null);
}
document.getElementById('redoBtn').onclick = function () {
execEditorCommand('redo', null);
}
document.getElementById('paragraphBtn').onclick = function () {
execEditorCommand('formatBlock', '<p>');
}
document.getElementById('createLinkBtn').onclick = function () {
let link = window.prompt('请输入链接地址');
execEditorCommand('createLink', link);
}
document.getElementById('insertImageBtn').onclick = function () {
let image = window.prompt('请输入图片地址');
execEditorCommand('insertImage', image);
}
</script>
</html>
```
![简易富文本编辑器](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-06-28-RichTextEditor.png)
## 注意点
- 执行的是原生的 document.execCommand 方法,浏览器自身会对 contenteditable 这个可编辑区维护一个 undo 栈和一个 redo 栈,所以我们才能执行前进和后退的操作,如果我们改写了原生方法,就会破坏原有的栈结构,这时就需要自己去维护,代价很大
- 如果是 Vue style 里面如果加上 scope 的话,里面的样式对编辑区的内容是不生效的,因为编辑区里面是后来才创建的元素,所以要么删了 scope要么用 /deep/ 解决Vue 是这样。React 的 styled-components 也有类似问题。

View File

@@ -0,0 +1,181 @@
# Vue3 核心技术
写这篇文章是之前看到了尤大的微博Vue3 发布了新版本,其在声明中写到对于之前的 Object.defineProperty 用 Proxy 改写了。
> The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc). [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
## 为什么要改写?
Vue3 之前的版本响应式是通过 `Object.defineProperty` 实现的。getter 用来依赖收集setter 在数据变化时通知订阅者更新视图。这种方式存在一些缺陷:
1. 无法检测到对象属性的新增或者删除。
Object.defineProperty 只会是属性的读写才会触发所以新增、删除都无法检测。Vue 为了做到新增、删除也是响应式,做了一些手段 `Vue.set(obj, propertName/index, value)`、响应式对象的子对象新增属性,可以给子响应式对象重新赋值。
2. 不能监听数组的变化
Vue 在数组做了 hack把无法监听的情况通过重写数组某些方法实现响应式。数组的操作也限制在 `push/pop/shift/unshift/splice/sort/reverse` 7个方法。
## Proxy 如何解决?
Vue3 利用 Proxy 实现数据读取和设置拦截,在拦截 trap 中实现数据依赖收集和触发视图更新的操作。
```Javascript
function get(target, key, receiver) { // handler.get的拦截实现
const res = Reflect.get(target, key, receiver)
if(isSymbol(key) && builtInSymbols.has(key)) return res
if (isRef(res)) return res.value
track(target, OperationTypes.GET, key) // 收集依赖
return isObject(res) ? reactive(res) : res
}
// handler.set的拦截操作
function set(target, key, value, receiver) {
value = toRaw(value) // 获取缓存响应数据
oldValue = target[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) { //set拦截只限对象本身
... // 不同环境操作处理并省略下面trigger方法第二参数获取逻辑
trigger(target, OperationTypes.x, key) // 触发视图更新
}
return result
}
```
## Proxy
Proxy - “代理”。当访问对象之前增加一个中间层,这样就不直接访问对象,而是通过中间层做一个中转。这个中间层就是本文主角 - **Proxy**。相信大家很早听到代理可能是`网络代理`。比如你需要访问国外站点,请求的时候你的电脑发起的网络请求被代理服务器做转发,然后再请求真正的目的服务器,响应的时候国外站点将响应发送到代理服务器,代理服务器再将数据内容返回到你的电脑。 `网络代理`分为「正向代理」和「反向代理」。nginx 就是架设在用户浏览器和目的服务器之间的中间层。 浏览器通过 nginx 才可以访问到服务器。通过代理可以实现负载均衡、访问控制等目的。
Javascript 中的 Proxy 类似。区别在于 Proxy 是对象和对象之间的一层代理。应用了 Proxy 之后,我们通过 Proxy 来访问或者操作目标对象。Proxy 会改变 Javascript 中的一些基础操作执行路径,比如属性读写、对象遍历、函数调用等。这种强大的能力被叫做**元编程**meta programming机制可实现很多之前无法实现的功能。
Proxy 会修改 Javascript 的一些底层代码执行方式,所以它无法被完全 polyfill。
## 核心概念
- target指的是目标对象也就是被代理的对象
- trap可以理解为一些预定义的触发点以及定义在这些触发点上的函数。比如属性的读取、函数调用等。如果在这些触发点上定义了函数那么在对 Proxy 执行对应操作的时候,定义的函数将会被调用
- handler是一个对象包装了所有 Proxy 提供的 trap 函数,可以认为是 trap 的集合
## Proxy 创建
在以下简单的例子中当对象中不存在属性名时缺省返回数为37。例子中使用了 get。
```Javascript
let handler = {
get: function(target, name){
return name in target ? target[name] : 37;
}
};
let proxy = new Proxy({}, handler);
proxy.a = 1;
proxy.b = undefined;
console.log(proxy.a, proxy.b); // 1, undefined
console.log('c' in proxy, proxy.c); // false, 37
```
上面的代码定义了 Proxy 对象 proxy被代理的对象是一个空对象 {}, 有了代理对象,对被代理对象的操作都会通过代理对象进行处理。当访问 proxy.c 的时候,我们定义的 handle 对象中的 get 函数trap会被调用。 get 函数接受的参数分别为 target对象和需要访问的属性名称。
## 有哪些 traps
所有的traps是可选的。如果某个trap没有定义那么默认的行为会应用到目标对象上。
- handler.getPrototypeOf()
在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
- handler.setPrototypeOf()
在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
- handler.isExtensible()
在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
- handler.preventExtensions()
在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
- handler.getOwnPropertyDescriptor()
在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
- handler.defineProperty()
在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
- handler.has()
在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
- handler.get()
在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
- handler.set()
在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
- handler.deleteProperty()
在删除代理对象的某个属性时触发该操作即使用delete运算符比如在执行 delete proxy.foo 时。
- handler.ownKeys()
Object.getOwnPropertyNames 和Object.getOwnPropertySymbols的trap.
- handler.apply()
当目标对象为函数,且被调用时触发。
- handler.construct()
new 运算符的trap。
## Reflect
**Reflect** 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
与大多数全局对象不同Reflect 不是一个构造函数。你不能将其与一个 new 运算符一起使用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是**静态**的就像Math对象
```Javascript
let handler = {
get: function(target, name){
return Reflect.get(target, name);
}
};
let proxy = new Proxy({}, handler);
proxy.a = 1;
proxy.b = undefined;
console.log(proxy.a, proxy.b); // 1, undefined
console.log('c' in proxy, proxy.c); // false, 37
```
Reflect 对象提供和了以下 static 方法,用来方便写 trap 函数时将操作传递到目标对象上。
- Reflect.apply()
对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。
- Reflect.construct()
对构造函数进行 new 操作,相当于执行 new target(...args)。
- Reflect.defineProperty()
和 Object.defineProperty() 类似。
- Reflect.deleteProperty()
作为函数的delete操作符相当于执行 delete target[name]。
- Reflect.enumerate()
该方法会返回一个包含有目标对象身上所有可枚举的自身字符串属性以及继承字符串属性的迭代器for...in 操作遍历到的正是这些属性。
- Reflect.get()
获取对象身上某个属性的值,类似于 target[name]。
- Reflect.getOwnPropertyDescriptor()
类似于 Object.getOwnPropertyDescriptor()。
- Reflect.getPrototypeOf()
类似于 Object.getPrototypeOf()。
- Reflect.has()
判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
- Reflect.isExtensible()
类似于 Object.isExtensible().
- Reflect.ownKeys()
返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响).
- Reflect.preventExtensions()
类似于 Object.preventExtensions()。返回一个Boolean。
- Reflect.set()
将值分配给属性的函数。返回一个Boolean如果更新成功则返回true。
- Reflect.setPrototypeOf()
类似于 Object.setPrototypeOf()。
## Proxy 优缺点
优点:
看上去的 getter、setter 的功能一样,都可以做到当访问或者改写一个对象的时候调用某个特定的函数。但是还是有区别的:
1. Proxy除了对属性值的获取和改写还可以改变其他 JavaScript 基础操作的执行方式,也就是它的功能比 getter、setter 多很多
2. getter、setter 是定义在对象本身上的,而 Proxy 是定义在另一个对象上的。这表示 **Proxy 允许我们比较方便的修改一些我们不方便直接改变其定义方式的对象的执行方式,比如一些第三方库中定义的对象等**
缺点:
1. 兼容性问题,无完全 polyfill
虽然大部分浏览器支持 Proxy 特性,但是一些浏览器或者其低版本不支持 Proxy其中 IE、QQ 浏览器、百度浏览器等完全不支持,因此 Proxy 有兼容性问题。那能否像ES6其他特性那样有对应的 polyfill 解决方案呢答案并不那么乐观。其中作为ES6转换的翘楚 Babel在其官网明确做了说明:
> Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled.
也就是说由于ES5的限制ES6的Proxy没办法被完全polyfill所以babel没有提供对应的转换支持Proxy的实现是需要JS引擎级别提供支持目前大部分主要的JS引擎提供了支持
2. 性能问题
Proxy 的性能比 Promise 还差这就要需要在性能和简单实用上进行权衡。另外Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,性能这块相信会逐步得到改善。

View File

@@ -0,0 +1,16 @@
# immutable.jS 与 React
## 引用带来副作用
js 中数据类型分为2种primitive value 和 Object。js 中的对象非常灵活,某个地方对对象进行修改后产生的结果难以预期,所以我们需要不可变对象。
但是自己实现的对象拷贝算法,可能浪费时间也浪费空间, 所以诞生了 immutable.js 这个库。
## 核心实现
immutable.js 基于哈希映射树和 vector map tries。只 clone 该节点和其祖先节点,其他保持不变,这样可以共享大部分相同的节点,大大提高性能。
1. 避免直接修改线性结构
2. 减小内存开销(增量更新)

View File

@@ -0,0 +1,63 @@
## 前端工程化与 CI、CD
首先明确,这个话题很大,所以文章篇幅特别长,所以拆分成多个部分进行记录。
### 用 husky 和 lint-staged
1. 什么是 lint
> In computer programming, lint is a Unix utility that flags some suspicious and non-portable constructs (likely to be bugs) in C language source code; generically, lint or a linter is any tool that flags suspicious usage in software written in any computer language. The term lint-like behavior is sometimes applied to the process of flagging suspicious language usage. Lint-like tools generally perform static analysis of source code.
上面是[维基百科](https://en.wikipedia.org/wiki/Lint_%28software%29)的定义。 **Lint 就是对代码进行静态分析,并试图找出潜在问题的工具。**
2. Lint 的好处,有以下三点:
- 可以让代码更加阅读性良好
- 具有更高的开发效率。工程师会花 50% 时间去做问题定位和解决 bug其中很多错误是低级的而 lint 可以很容易定位发现这些低级错误
- 可以让代码存在更少 Bug。
3. Lint 的反馈链条太长
假如 lint 是在 ci、cd 的后续步骤则会发现整个反馈流程太长,所以应该是 commit 之前做 lint。CI 流程做 lint 的缺点Lint 在整个开发工作流中反馈链条太长,浪费时间
4. 所以有工具可以做这个事情。
lint-staged、husky 允许你在代码提交的时候做掉 eslint。commitlint 可以让你在提交的时候将 commit message 写的规范,
```json
"dependencies": {
"eslint": "^6.7.2",
"lint-staged": "^9.5.0",
"mocha": "^6.2.2"
},
"script": {
"test": "NODE_ENV=development jest",
"lint": "eslint . --fix --format codefrane"
},
"devDependencies": {
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"husky": "^3.1.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"**/*.js": [
"eslint --fix --cache --format=pretty",
"git add"
]
}
```
关键词: commitlint、lint-staged
### 前端代码风格自动化系列

View File

@@ -0,0 +1,12 @@
# npm 改进之工程化
npm 3 最大的改进就是对于依赖的处理不一样了。[官方文档](https://docs.npmjs.com/how-npm-works/npm3?utm_source=ourjs.com)
## 例子
模块 A依赖于模块 B
https://www.jianshu.com/p/69ba32550c08
https://www.cnblogs.com/wonyun/p/9349691.html

View File

@@ -0,0 +1,338 @@
# 前端模块化演进之路
有这样一个场景,客户端运行很久,但是法务部和数据部需要收集用户的一些信息,这些信息收集好之后需要进行相应的数据处理,之后上报到服务端。客户端提供一个纯粹的 JS 执行引擎,不需要 WebView 容器。iOS 端有成熟的 JavaScriptCore、Android 可以使用 V8 引擎。这样一个引擎配套有一个 SDK访问 Native 的基础能力和数据运算能力,可以看成是一个阉割版的 Hybrid SDK 额外增加了一些数据处理能力。
问题结束了吗处理逻辑的时候还需要用到2个库[cheerio](https://github.com/cheeriojs/cheerio) 和 [sql](https://github.com/nearform/sql)。因为都是 Node 工程,所以纯粹的 JS 环境是没办法直接执行。所以需求就进行了转变 ———— 将 Node 项目打包成 UMD 规范。这样就可以在纯粹的 JS 环境下运行。接下来的文章就分析下各种规范。其实也就是前端模块化演进之路的几种规范。
## 前端模块化演进之路开发的价值
随着互联网的飞速发展,前端开发越来越复杂。本文将从实际项目中遇到的问题出发,讲述模块化能解决哪些问题,以及以 Sea.js 为例讲解如何进行前端的模块化开发。
1. 恼人的命名冲突
我们从一个简单的习惯出发。我做项目时,常常会将一些通用的、底层的功能抽象出来,独立成一个个函数,比如
```Javascript
function each(arr) {
// 实现代码
}
function log(str) {
// 实现代码
}
```
并像模像样的将这些代码抽取出来并统一到 `util.js` 中,在需要使用的地方引入该文件,看起来很棒,团队内的同事很感激我提供了这么便利的工具包。
直到团队越来越大,开始有人抱怨
> 小杨:我定义了一个 each 方法遍历对象,但是 util.js 中已经存在一个 each 方法,每次都需要改方法名,我只能叫 eachObject 方法。<br>张三:我定义了一个 log 方法,可是王武的代码出问题了,谁来看看?
抱怨越来越多,最后参照 Java 的方式,引入**命名空间**解决问题。于是 util.js 代码变成了
```Javascript
var org = {};
org.Utils = {};
org.Utils.each = function (arr) {
// 实现代码
};
org.Utils.log = function (str) {
// 实现代码
};
```
可能看上去的代码很 low其实命名空间在前端领域的布道者是 Yahoo的 YUI2 项目,看看下面的代码,是 Yahoo的一个开源项目
```Javascript
if (org.cometd.Utils.isString(response)) {
return org.cometd.JSON.fromJSON(response);
}
if (org.cometd.Utils.isArray(response)) {
return response;
}
```
通过命名空间虽然可以极大的解决冲突问题,但是每次在调用一个方法时都需要写一大堆命名空间相关的代码,剥夺了编码乐趣。
另一种方式是一个自执行函数来实现。
```Javascript
(function (args) {
//...
})(this);
```
2. 繁琐的文件依赖
继续上述场景,很多情况下都需要开发 UI 层通用组件,这样项目组就不需要重复造轮子。其中有一个高频使用的组件就是 dialog.js
```Javascript
<script src="util.js"></script>
<script src="dialog.js"></script>
<script>
org.Dialog.init({ /* 传入配置 */ });
</script>
```
虽然公共组做项目都会编写使用文档、发送邮件告知全员(项目地址、使用方式等),但是还是有人问「为什么 dialog.js 有问题」,最后排查的结果基本都是没有引入 util.js
```Javascript
<script src="dialog.js"></script>
<script>
org.Dialog.init({ /* 传入配置 */ });
</script>
```
**命名冲突**和**文件依赖**是前端开发中2个经典问题经过开发者不断的思考和研究诞生了模块化的解决方案以 CMD 为例
```Javascript
define(function(require, exports) {
exports.each = function (array) {
// ...
};
exports.log = function(message) {
// ...
};
});
```
通过 exports 就可以向外提供接口, dialog.js 代码变成
```Javascript
define(function(require, exports) {
var util = require('./util.js')
exports.init = function () {
// ...
};
});
```
使用的时候可以通过 `require('./util.js')` 获取到 util.js 中通过 exports 暴露的接口。 require 的方式在其他很多语言中都有解决方案include、
## 模块化的好处
1. 模块的版本管理:通过别名等配置,配合构建工具,可以轻松实现模块的版本管理
2. 提高可维护性: 模块化可以实现每个文件的职责单一,非常有利于代码的维护。
3. 前端性能优化: 对于前端开发来说,异步加载模块对于页面性能非常有益。
4. 跨环境共享模块: CMD 模块定义规范与 NodeJS 的模块规范非常相近,所以通过 Sea.JS 的 NodeJS 版本,可以方便的实现模块的跨服务器和浏览器共享。
## CommonJS 规范
CommonJS 是服务器端模块的规范。NodeJS 采用了这个规范。CommonJS 加载模块是同步的,所以只有加载完成后才能执行后面的操作。
因为服务器的特点,加载的模块文件一般都存在在本地硬盘,所以加载起来比较快,不用考虑异步的方式。
CommonJS 模块化的饿规范中,每个文件都是一个模块,拥有独立的作用域、变量、以及方法等,对其他模块不可见。 CommonJS 规范规定,每个模块内部, **module** 变量表示当前模块,它是一个对象,它的 **exports** 属性是对外的接口,加载某个模块,其实是加载该模块的 module.exports 属性require 方法用于加载模块。
```Javascript
// Person.js
function Person () {
this.eat = function () {
console.log('eat something')
}
this.sleep = function () {
console.log('sleep')
}
}
var person = new Person();
exports.person = person;
exports.name = name;
// index.js
let person = require('./Person').person;
person.eat()
```
### CommonJS 与 ES6 模块的差异
1. CommonJS 模块输出的是**值的拷贝**ES6 模块输出的是**值的引用**
2. CommonJS 模块是运行时加载ES6 模块是编译时输出接口
CommonJS 模块导出的是一个对象module.exports 属性),该对象只在脚本运行完才会生成。
ES6 的模块机制是 JS 引擎对脚本进行静态分析的时候,遇到模块加载命令 import就会生成一个只读引用等到脚本真正执行时再根据这个只读引用到被加载的模块中取值
## AMD 规范
AMDAsynchronous Module Definition 是在 Require.JS 推广的过程中对模块定义的规范化产出。AMD 推崇依赖前置。它是 CommonJS 模块化规范的超集,作用在浏览器上。它的特点是异步,利用了浏览器的并发能力,让模块的依赖阻塞变少。
AMD 的 API
```Javascript
define(id?, dependencyies?, factory);
```
id 是模块的名字,是可选参数。 dependencies 指定了该模块所依赖的模块列表,是一个数组,也是可选参数。每个依赖的模块的输出都将作为参数依次传入 factory 中。
```Javascript
require([module], callback)
```
**AMD 规范允许输出模块兼容 CommonJS 规范,这时 define 方法如下**
```Javascript
define(['module1', 'module2'], function(module1, module2) {
function foo () {
// ...
}
return { foo: foo };
});
```
```Javascript
define(function(require, exports, module) {
var requestedModule1 = require('./module1')
var requestedModule2 = require('./module2')
function foo () {
// ...
}
return { foo: foo };
});
```
优点: 适合在浏览器环境中加载模块,可以实现并行加载多个模块
缺点: 提高了开发成本,并不能按需加载,而是提前加载所有的依赖
## CMD 规范
CMD 是 Sea.JS 推广的过程中对模块定义的规范化产出。CMD 推崇依赖就近。
CMD 规范尽量保持简单,并与 CommonJS 规范中的 Module 保持兼容,通过 CMD 规范编写的模块,可以在 NodeJS 中运行。
[CMD 模块定义规范 ](https://github.com/seajs/seajs/issues/242)
CMD 中 require 依赖的描述用数组,则是异步加载,如果是单个依赖使用字符串,则是同步加载。
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出CMD是SeaJS 在推广过程中被广泛认知。SeaJS 出自国内蚂蚁金服玉伯。二者的区别玉伯在12年如是说
RequireJS 和 SeaJS 都是很不错的模块加载器,两者区别如下:
1. 两者定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 服务器端
2. 两者遵循的标准有差异。RequireJS 遵循的是 AMD异步模块定义规范SeaJS 遵循的是 CMD 通用模块定义规范。规范的不同导致了两者API 的不同。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
3. 两者社区理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
4. 两者代码质量有差异。RequireJS 是没有明显的 bugSeaJS 是明显没有 bug。
5. 两者对调试等的支持有差异。SeaJS 通过插件,可以实现 Fiddler 中自动映射的功能,还可以实现自动 combo 等功能非常方便便捷。RequireJS无这方面的支持。
6. 两者的插件机制有差异。RequireJS 采取的是在源码中预留接口的形式源码中留有为插件而写的代码。SeaJS 采取的插件机制则与 Node 的方式一致开放自身,让插件开发者可直接访问或修改,从而非常灵活,可以实现各种类型的插件。
## UMD 规范
UMDUniversal Module Definition是随着大前端的趋势产生希望提供一个前后端跨平台的解决方案支持 AMD、CMD、CommonJS 模块方式)。
实现原理:
1. 先判断是否支持 Node.js 模块格式exports 是否存在),存在则使用 Node.js 模块格式
2. 再判断是否支持 AMD 模块格式define 是否存在),存在则使用 AMD 模块格式
3. 前2个都不存在则将模块公开到全局window 或 global
```Javascript
// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
}(this, function () {
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
```
可能有些人就要问了,为什么在上面的判断中写了 AMD怎么没有 CMD 😂 因为前端构建工具 Webpack 不可识别 CMD 规范,使用 CMD 就需要引用工具,比如 Sea.JS
讲道理,如果想判断 CMD那 UMD 代码如何写?
```Javascript
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof define === 'function' && define.cmd) {
// CMD
define(function(require, exports, module) {
module.exports = factory()
})
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
}(this, function() {
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}))
```
![模块化](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-02-02-JSModule.png)
## 回到正题
Cheerio 如何打包到普通的 JS 执行环境中。
Webpack 支持的模块化参数如下图所示:
![Webpack 模块化参数](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-02-02-WebpackModuleOptions.png)
借助 Webpack 可以方便的打出一个 umd 规范的包。
```Javascript
module.exports = {
entry: './src/cheerio.js',
output: {
filename: 'cheerio.js',
// export to AMD, CommonJS, or window
libraryTarget: 'umd',
// the name exported to window
library: 'cheerio',
globalObject: 'this'
}
}
```
## 总结
手机端(无论 iOS 还是 Android的底层渲染内核都是类 Chrome v8 引擎。v8 引擎在执行 JS 代码时,是将代码先以 MacroAssembler 汇编库在内存中先编译成机器码再送往 CPU 执行的,并不是像其它 JS 引擎那样解析一行执行一行。所以,静态加载的 ES6 模块规范,更有助于 v8 引擎发挥价值。而运行时加载的 CommonJS、AMD、CMD 规范等,均不利于 v8 引擎施展拳脚。
在 NodeJS 开发项目中Node9 已经支持 ES6 语法,完全可以使用 ES6 模块规范。NodeJS 的诞生,本身就基于 Google 的 v8 引擎,没有理由不考虑发挥 v8 的最大潜能。
在浏览器 JS 开发项目中,因为从服务器加载文件需要时间,使用 CommonJS 规范肯定是不合适了。至于是使用原生的 ES 模块规范,还是使用 Sea.js要看具体场景。如果想页面尽快加载Sea.js 适合;如果是单页面网站,适合使用原生的 ES6 模块规范。还有一点,浏览器并非只有 Chrome 一家,对于没有使用 v8 引擎的浏览器,使用 ES6 原生规范的优势就又减少了一点。

View File

@@ -0,0 +1,54 @@
# generator函数
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,105 @@
# h5自定义对象
### 一、方式一
在很早以前我们自定义元素的属性要通过 `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://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2017-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,28 @@
# JSON的一些骚操作
* 将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,641 @@
# Vue 技术的小技巧
1. Vue 项目开发中经常遇到事件阻止冒泡的需求。在传统的写法上就是调用 `event.preventDefault()、event.stopPropagation()`。尽管我们可以在 methods 里面这样写但是更好的规范是methods 只做数据的逻辑处理,而不是去处理 DOM 相关的细节(阻止事件冒泡)
2. 为了解决这个问题, Vue 为 v-on 提供了事件修饰符。通过`.`表示的指令后缀来调用修饰符
- .stop
- .prevent
- .capture
- .self
- .once
```Javascript
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件侦听器时使用事件捕获模式 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当事件在该元素本身比如不是子元素触发时触发回调 -->
<div v-on:click.self="doThat">...</div>
```
3. 给 props 属性设置多个类型
props 在开发组件的时候几乎是必用,为了更好的容错处理,比如给自定义的 button 上暴露一个 width 属性
`flexiable-button`
```
<template>
<button :style="computedWidth">我是长度为:{ {computedWidth} }的按钮</button>
</template>
export default {
prop: {
width: [String, Number],
default: '100px'
},
computed: {
computedWidth () {
let attr = {}
if (typeof this.width === 'String') {
attr.width = this.width
}
if (typeof this.width === 'Number') {
attr.width = this.width + 'px'
}
return attr
}
}
}
```
`在其他组件里面使用`
```
<template>
<flex-button :width="100px">按钮1</flex-button>
<flex-button :width="200">按钮2</flex-button>
</template>
```
4. data 初始化
因为 props 要比 data 先初始化完成,所以我们可以用 props 对 data 完成初始化操作
```
export default {
data () {
return {
buttonSize: this.size
}
},
props: {
size: String
}
}
```
除了以上,子组件的 `data` 函数还可以有参数(当前实例对象),我们可以用这一点做一些自己的判断
```
export default {
data (vm) {
return {
buttonSize: vm.size
}
},
props: {
size: String
}
}
```
5. template
我们经常需要条件渲染或者循环展示,`v-for` 遇上 `v-if` 会结合产生什么故事呢?
之前有一个需求,在个人中心模块展示「收藏列表」。收藏列表分为很多类:公司、个人、商品、类别等等。当用户点击某个大的类别的时候再去展示相应的列表。当然想到的是 v-for 和 v-if。但是当直接操作 ul 加上 v-if 和 v-for 之后发现结果并不符合预期,最后想到的是 `template`。这样子解决了我们的需求
```
<template v-if="collectType=='company'">
<b-card v-for="(item, index) in companies" v-bind:key="'company' + index">
<b-row style="line-height:25px;">
<b-col cols="12" sm="6" lg="6" md="6" xl="6">
法定代表人:{ {item.legal_person} }
</b-col>
<b-col cols="12" sm="6" lg="6" md="6" xl="6">
注册资本:{ {item.register_capital} }万元
</b-col>
</b-row>
</b-card>
</template>
<template v-if="collectType=='arc'">
<b-card v-for="(item, index) in architects" v-bind:key="'architect' + index">
<b-row style="line-height:25px;">
<b-col cols="12" sm="6" lg="6" md="6" xl="6">
注册类别:{ {item.category} }
</b-col>
<b-col cols="12" sm="6" lg="6" md="6" xl="6">
注册等级:{ {item.level} }
</b-col>
</b-row>
</b-card>
</template>
```
6. lifecyele hook
- 生命周期钩子可以是一个数组类型,且数组中的函数会依次执行
- 生命周期的钩子函数还可以作用于 DOM 元素上,利用这一点,我们可以用父组件中的方法来初始化子组件的生命周期钩子
下面的例子结合了上面2个知识点
`company-basic-info.vue子组件`
```
created: [
function callA () {
console.log('子组件-callA')
},
function callB () {
console.log('子组件-callB')
},
function callC () {
console.log('子组件-callC')
}
]
```
`company-detail.vue父组件`
```
<company-basic-info :companyInfo="companyInfo" @hook:created="handleCompanyBasicCreated()"></company-basic-info>
<script>
export default {
methods: {
handleSubCreated: function () {
console.log('我观察到子组件创建了')
}
}
}
</script>
```
```
子组件-callA
company-basic-info.vue?58e9:85 子组件-callB
company-basic-info.vue?58e9:88 子组件-callC
company-detail.vue?8308:103 我观察到子组件创建了
```
7.render 函数
```
<template>
<div class="box">
<h2>title</h2>
this is content
</div>
</template>
```
用 render 函数实现
```
export default {
render (h) {
let _c = h
return _c('div',
{ class: 'box'},
[_c('h2', {}, 'title'), 'this is content'])
}
}
```
事实上Vue 会把模版template编译成渲染函数render。上面的模版会被编译成类似下面的渲染函数
```
let render = function () {
return _c('div',
{staticClass:"box"},
[_c('h2', [_v("title")]), _v("this is content")])
}
```
官方解释说 render 函数比 template 更加接近编译器。
template -> 预编译工具vue-loader、vue-template-compile-> render -> resolve vnode
![Vue 生命周期](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-13-lifecycle.png)
渲染函数的好处:
8. errorCaptured
它会在捕获一个来自子孙组件的错误时被调用。有时候我们想收集错误日志,却不想把错误信息暴露到浏览器控制台,利用它可以实现。
`company-basic-info.vue`子组件报错
```
mounted () {
this.$nextTick(() => {
consol.log('我是故意发生的错误')
})
}
```
`company.vue`父组件拦截到错误
```
methods: {
errorCaptured (err, vm, info) {
console.log(err)
console.log(vm)
console.log(info)
//return false
//不 return false 的话报错信息继续向上冒泡
}
}
```
`App.vue`拦截错误
```
errorCaptured (err, vm, info) {
console.log('AppVue' + err)
console.log('vm' + vm)
console.log('info' + info)
}
```
9. v-once
v-once 创建低开销的静态组件。渲染普通的 HTML 元素在 Vue 中是非常快的,但是有的时候你可能需要一个包含了大量内容的组件。你可以在**根元素上**添加 `v-once` 特性以确保这些内容只计算了一次然后缓存起来。
```
<template>
<div class="content-container" v-once>
...
</div>
</template>
```
10. slot-scope
作用域插槽,当我们用过很多框架就知道很多设计的第三方组件都支持插槽。用户可以插入自定义的 HTML 内容来自定义使用。
插槽也就是 slot是组件的一块 HTML 模版,这块模版的显不显示、以及怎样显示由父组件决定。所以 slot 关心的问题是「显示不显示」、「怎样显示」
由于插槽是一块模版,所以从模版种类划分为为:非插槽模版、插槽模版
- 非插槽模版指的是 HTML 模版,指的是 'div、span、ul、table' 这些,非插槽模版的显示与隐藏以及怎样显示由插件自身控制
- 插槽模版也就是 slot是一个空壳子因为它的显示于隐藏以及最后用什么样的 HTML 模版显示由父控件自身控制。但是插槽的显示位置却由子组件自身决定, slot 写的组件 template 的哪块,父组件传过来的模版将来就显示在哪块。
### 单个插槽 | 默认插槽 | 匿名插槽
单个插槽是 Vue 官方的叫法,但其实它也被叫做「默认插槽」。与「具名插槽」相对应。我们可以叫做「匿名插槽」,因为它不用设置 name 属性
单个插槽可以放在组件的任何位置。但是见名知意一个组件中只可以存在一个匿名插槽。相对应具名插槽就可以有很多个但是必须设置名字name属性不同就可以了。
父组件
```
<template>
<div class="content-container">
<h1>这是父组件</h1>
<chile>
<div class="list">
<span>iOS</span>
<span>Web</span>
<span>Vue</span>
</div>
</child>
</div>
</template>
```
子组件
```
<template>
<div class="child">
<h3>这里是子组件</h3>
<slot></slot>
<div>
</template>
```
在父组件里面写了 html 模版所哟子组件匿名插槽这块模版被使用了。最终结果
![效果](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-14-slot1.png)
### 具名插槽
匿名插槽没有设置 name 属性,所以是匿名插槽。那么插槽添加了 name 属性,那么就变成了「具名插槽」。具名插槽可以在一个组件中出现 n次。
父组件
```
<template>
<div class="father">
<h3>这里是父组件</h3>
<child>
<div class="tmpl" slot="up">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
<div class="tmpl" slot="down">
<span>菜单-1</span>
<span>菜单-2</span>
<span>菜单-3</span>
<span>菜单-4</span>
<span>菜单-5</span>
<span>菜单-6</span>
</div>
<div class="tmpl">
<span>菜单->1</span>
<span>菜单->2</span>
<span>菜单->3</span>
<span>菜单->4</span>
<span>菜单->5</span>
<span>菜单->6</span>
</div>
</child>
</div>
</template>
```
子组件
```
<template>
<div class="child">
// 具名插槽
<slot name="up"></slot>
<h3>这里是子组件</h3>
// 具名插槽
<slot name="down"></slot>
// 匿名插槽
<slot></slot>
</div>
</template>
```
![效果图](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-14-slot2.png)
父组件通过 html 模版上的 slot 属性关联具名插槽,没有 slot 属性的 html 模版默认关联匿名插槽
### 作用域插槽 | 带数据的插槽
Vue 官方叫做作用域插槽。对比前面几种情况我们可以叫做「带数据的插槽」。作用域插槽要求在 slot 上面绑定数据
```
<slot name="up" :data="data"></slot>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
},
}
```
11. 组件之间的通信
- 父->子组件之间的通信
> 父向子组件传递数据通过 props
```
//父
<template>
<header-box :title-txt="showTitleTxt"></header-box>
</template>
//子
<script>
props: {
titleTxt: String
},
</script>
```
- 子->父组件之间的通信
> 通过 $emit 和 $on 传递数据
```
//父
<template>
<button-counter v-on:increment="incrementTotal"></button-counter>
</template>
<script>
methods: {
incrementTotal () {
console.log('子组件调用你了')
}
}
</script>
//子
<template>
<button @click="incrementCounter">{ {counter} }</button>
</template>
<script>
methods: {
incrementCounter () {
this.$emit('increment')
}
}
</script>
```
- 非父子组件
> 可以通过一个空的 Vue 实例作为中央事件总线(也可以使用当前的 App 实例,不需要额外创建)
方式1
```
import Vue from 'Vue'
export default new Vue()
```
方式2
```
window.VueInstance = new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>',
beforeCreate () {
Vue.prototype.bus = this
},
mounted () {
this.$nextTick(function () {
GlobalUtil.util.setConsole()
})
}
})
//传递数据
this.bus.$emit('HotSearchChange', itemIndex)
//接收数据
this.bus.$on('HotSearchChange', (itemIndex) => {
this.fetchHotSearchWords(itemIndex)
})
```
12. 路由切换判断是否需要登录
我们在做前端项目的时候经常需要添加路由设置。其中有个需求就是当你可能跳转一个页面的时候需要判断用户是否登录,如果登录的话直接跳转,未登录先跳转到登录页。
> 我们可能想到的是监听 Router 的变化,在跳转前判断,如果用户是登录状态直接跳转;用户未登录状态则先跳转到登陆页,登录成功后回退到之前的页面。
Vue 的 router 已经提供了这样的功能
- 在 main.js 里面添加监听、判断的逻辑
```
/* 控制判断哪些页面需要用户登录才可以访问 */
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requireAuth)) {
if (!window.VueInstance.$store.getters.getUserInfo) {
next({
path: '/Auth/login',
query: {redirect: to.fullPath} // 将跳转的路由path作为参数登录成功后跳转到该路由
})
} else {
if (!window.VueInstance.$store.getters.getUserInfo.uid && !window.VueInstance.$store.getters.getUserInfo.token) {
next({
path: '/Auth/login',
query: {redirect: to.fullPath} // 将跳转的路由path作为参数登录成功后跳转到该路由
})
} else {
next()
}
}
} else {
next()
}
})
```
- 在 router 的 index.js 里面为需要登录才可以访问的页面添加配置项
```
let routes = [
{ path: '/SearchCompany', name: 'SearchCompany', component: SearchCompany, meta: {requireAuth: true} },
//...
]
```
- 在登录页为登录成功后做判断处理
```
if (this.$route.query.redirect) {
this.$router.push({path: this.$route.query.redirect})
} else {
this.$router.push({path: '/' })
}
```
13. Vue 上传文件
```
if (document.getElementById("bidFile").files[0]) {
const formData = new FormData()
formData.append('file', document.getElementById("bidFile").files[0])
formData.append('uid', this.$store.getters.getUserInfo.uid)
formData.append('token', this.$store.getters.getUserInfo.token)
formData.append('project_id', this.project_id)
let loader = this.$loading.show({
loader: 'dots',
container: this.fullPage ? null : this.$refs.formContainer,
canCancel: true,
onCancel: this.onCancel,
})
fetch.bhfilepost('order/upBidingFile',formData).then((res) => {
loader.hide()
if (res.status !== -404) {
this.selectedProjectInfo = res.data.data
}
})
}
```
```
//fetch.js
bhfilepost (url, params) {
Axios.defaults.baseURL = 'https://bh.datacubr.com'
params['key'] = key
return Axios({
method: 'post',
url,
data: params,
headers: {
'Content-Type': ' multipart/form-data'
}
}).then((response) => {
return checkStatus(response)
}).then((res) => {
return checkCode(res)
})
},
```
14. Vue 以base64形式下载pdf/压缩包也可以
压缩包的话,`content-type` 设置为 `type: "application/zip"`
```
base64ToArrayBuffer(data) {
let binaryString = window.atob(data)
let binaryLen = binaryString.length
let bytes = new Uint8Array(binaryLen)
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i)
bytes[i] = ascii
}
return bytes
}
handleDownload (filename, operationname) {
let loader = this.$loading.show({
loader: 'dots',
container: this.fullPage ? null : this.$refs.formContainer,
canCancel: true,
onCancel: this.onCancel,
})
fetch.bhpost('document/templateTmp',{
type: operationname,
uid: this.$store.getters.getUserInfo.uid,
token: this.$store.getters.getUserInfo.token,
order_no: this.order_no
}).then((res) => {
loader.hide()
if (res.status === 200) {
var arrBuffer = this.base64ToArrayBuffer(res.data.data.info)
// It is necessary to create a new blob object with mime-type explicitly set
// otherwise only Chrome works like it should
var newBlob = new Blob([arrBuffer], { type: "application/pdf" })
// IE doesn't allow using a blob object directly as link href
// instead it is necessary to use msSaveOrOpenBlob
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(newBlob)
return
}
// For other browsers:
// Create a link pointing to the ObjectURL containing the blob.
var data = window.URL.createObjectURL(newBlob)
var link = document.createElement('a')
document.body.appendChild(link) //required in FF, optional for Chrome
link.href = data
link.download = filename + ".pdf"
link.click()
window.URL.revokeObjectURL(data)
link.remove()
}
})
},
```
15. 推荐几个好用的库
最近用 Vue 全家桶和 Bootstrap-Vue 框架做了一版网站。所以有一些好用的包或者组件分享一下。
- [cxlt-vue2-toastr](https://www.npmjs.com/package/cxlt-vue2-toastr)
说明cxlt-vue2-toastr是弹出提示的Vue2组件基于toastr的样式和animate.css的动画效果。
- [vue-photo-preview](https://www.npmjs.com/package/vue-photo-preview)
说明:可以全屏、切换、预览、文字说明的图片查看器
- [animate.css](https://www.npmjs.com/package/animate.css)
说明:基本上 JS 可以实现的动画效果基本都可以实现。蛮强大的
- [axios](https://www.npmjs.com/package/axios)
说明:支持 promise、可以跑在浏览器和 Node 环境、可以拦截请求和响应头
- [vee-validate](https://www.npmjs.com/package/vee-validate)
说明:一个校验的包,内置蛮多规则,你也可以自定义,支持国际化

View File

@@ -0,0 +1,44 @@
# 第二部分
第二部分主要介绍 Web 前端开发中遇到的问题或者有趣的知识
* [1、-last-child与-last-of-type你只是会用有研究过区别吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.1.md)
* [2、正则表达式](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.2.md)
* [3、CSS-埋点统计](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.3.md)
* [4、generator函数](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.4.md)
* [5、H5性能优化方面的探索](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.5.md)
* [6、h5自定义对象](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.6.md)
* [7、Javascript-prototype](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.7.md)
* [8、JSON的一些骚操作](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.8.md)
* [9、Vue开发小技巧](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.9.md)
* [10、Vue-devtools](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.10.md)
* [11、H5页面保存页面为图片](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.11.md)
* [12、Promise](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.12.md)
* [13、webpack-dev-server 的配置和使用](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.13.md)
* [14、Web 与 H5 交互的坑](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.14.md)
* [15、前端持久化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.15.md)
* [16、VS-Code](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.16.md)
* [17、Vue tips](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.17.md)
* [18、Web反爬虫技术](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.18.md)
* [19、webpack小集](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.19.md)
* [20、大前端](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.20.md)
* [21、canvas](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.21.md)
* [21、动画控制](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.22.md)
* [23、打造自己的图标字体文件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.23.md)
* [24、Chrome 调试技巧](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.24.md)
* [25、大前端动画](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.25.md)
* [26、Linux 下安装 Node](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.26.md)
* [27、浏览器不通窗口之间的通信之道](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.27.md)
* [28、神器Puppeteer](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.28.md)
* [29、从Vue.js谈谈前端开发的技术栈演变](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.29.md)
* [30、Javascript 常用工具封装](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.30.md)
* [31、浏览器布局与DOM绘制](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.31.md)
* [32、React核心技术剖析](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.32.md)
* [33、ES6学习总结](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.33.md)
* [34、富文本编辑器的原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.34.md)
* [35、Vue3 核心技术](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.35.md)
* [36、immutable.js 与 React](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.36.md)
* [37、前端工程化与 CI、CD](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.37.md)
* [38、npm 改进之工程化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.38.md)
* [39、前端模块化演进之路](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.39.md)