mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
docs: 批量博文
This commit is contained in:
196
Chapter2 - Web FrontEnd/2.1.md
Normal file
196
Chapter2 - Web FrontEnd/2.1.md
Normal 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>
|
||||
```
|
||||

|
||||
|
||||
* 再先看一组`: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>
|
||||
```
|
||||
|
||||
|
||||

|
||||
|
||||
问题抛出来了,那么来研究下: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>
|
||||
|
||||
```
|
||||
|
||||
|
||||

|
||||
|
||||
* :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>
|
||||
```
|
||||
|
||||

|
||||
|
||||
* 接下来: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>
|
||||
```
|
||||
|
||||

|
||||
61
Chapter2 - Web FrontEnd/2.10.md
Normal file
61
Chapter2 - Web FrontEnd/2.10.md
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
# 调试工具安装
|
||||
|
||||
---
|
||||
|
||||
## 安装方式一
|
||||
|
||||
Vue-devtools 可以从 Chrome 商店直接下载安装,前提需要翻墙。
|
||||
|
||||
## 安装方式二
|
||||
|
||||
* 第一步:找到 Vue-devtools 的 github 地址,并将其 clone 到本地。
|
||||
|
||||
```
|
||||
git clone https://github.com/vuejs/vue-devtools.git
|
||||
```
|
||||
|
||||
* 第二步:安装项目所依赖的 npm 包
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
遇到的问题:
|
||||
|
||||

|
||||
|
||||
改用命令
|
||||
|
||||
```
|
||||
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
|
||||
```
|
||||
|
||||

|
||||
|
||||
继续 npm install
|
||||
|
||||
* 第三步:编译项目文件
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||

|
||||
|
||||
* 第四步:添加至 Chrome 浏览器的拓展
|
||||
|
||||
```
|
||||
浏览器地址栏输入:chrome://extensions/
|
||||
|
||||
点击“加载已解压的拓展程序”选择本地 clone 下来的文件夹中的 shells -> chrome 文件夹(vue-devtools-master/shells/chrome )
|
||||
```
|
||||
|
||||

|
||||
|
||||
* 第五步:重启浏览器
|
||||
|
||||
* 第六步:在浏览器中的调试 Vue 代码
|
||||

|
||||
|
||||
|
||||
10
Chapter2 - Web FrontEnd/2.11.md
Normal file
10
Chapter2 - Web FrontEnd/2.11.md
Normal 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
|
||||
|
||||
180
Chapter2 - Web FrontEnd/2.12.md
Normal file
180
Chapter2 - Web FrontEnd/2.12.md
Normal 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);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
223
Chapter2 - Web FrontEnd/2.13.md
Normal file
223
Chapter2 - Web FrontEnd/2.13.md
Normal 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 会实时编译,但是最后编译的文件并没有输出到目标文件夹
|
||||
|
||||
## 一些截图
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
完整的 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还是写到一个新的文件中
|
||||
|
||||
在写图片的读取规则的时候,用到了use,use数组里面是一个个对象,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
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
18
Chapter2 - Web FrontEnd/2.14.md
Normal file
18
Chapter2 - Web FrontEnd/2.14.md
Normal 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
|
||||
|
||||

|
||||
|
||||
想了想,在我们的项目中 Android 原生的代码在使用 webview 的时候额外设置了代码具体如下
|
||||
|
||||
|
||||
|
||||
```
|
||||
mWebView.getSettings().setDomStorageEnabled(true);
|
||||
```
|
||||
|
||||
|
||||
|
||||
25
Chapter2 - Web FrontEnd/2.15.md
Normal file
25
Chapter2 - Web FrontEnd/2.15.md
Normal 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 的网站,随机发送包含针对这个网站的相关产品广告的垃圾邮件给这些用户
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
13
Chapter2 - Web FrontEnd/2.16.md
Normal file
13
Chapter2 - Web FrontEnd/2.16.md
Normal 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
|
||||
}
|
||||
```
|
||||
576
Chapter2 - Web FrontEnd/2.17.md
Normal file
576
Chapter2 - Web FrontEnd/2.17.md
Normal 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 住全场。
|
||||
* 脚手架已经帮你处理好了这块需求了,看下图
|
||||
|
||||

|
||||
|
||||
* 有些大佬不要脚手架,喜欢自己初始化项目,用 npm 挨个安装所需要的依赖。然后自己配置 webpack 的 options。需要调试的话,需要做下面的配置
|
||||
|
||||
```
|
||||
config.devtool = '#cheap-module-eval-source-map'
|
||||
|
||||
```
|
||||
|
||||
这样你就可以在浏览器当中像写普通的 JS 一样进行调试代码了。比如
|
||||
|
||||

|
||||
|
||||
|
||||
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 并 use(main.js)
|
||||
|
||||
```
|
||||
import VueRouter from 'vue-router'
|
||||
Vue.use(VueRouter);
|
||||
```
|
||||
|
||||
- 配置路由(main.js)
|
||||
|
||||
```
|
||||
const routes = [
|
||||
{path:"/home",component:"Home"},
|
||||
{path:"/news",component:"News"},
|
||||
];
|
||||
```
|
||||
|
||||
- 实例化 router(main.js)
|
||||
|
||||
```
|
||||
const router = new VueRouter({
|
||||
routes
|
||||
//等于 routes:routes,只有当key和value一致的时候可以简写
|
||||
});
|
||||
```
|
||||
|
||||
- 挂载 router(main.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>
|
||||
```
|
||||
605
Chapter2 - Web FrontEnd/2.18.md
Normal file
605
Chapter2 - Web FrontEnd/2.18.md
Normal file
@@ -0,0 +1,605 @@
|
||||
# 反爬技术研究
|
||||
|
||||
> 对于内容型的公司,数据的安全性很重要。对于内容公司来说,数据的重要性不言而喻。比如你一个做在线教育的平台,题目的数据很重要吧,但是被别人通过爬虫技术全部爬走了?如果核心竞争力都被拿走了,那就是凉凉。再比说有个独立开发者想抄袭你的产品,通过抓包和爬虫手段将你核心的数据拿走,然后短期内做个网站和 App,短期内成为你的劲敌。
|
||||
|
||||
|
||||
|
||||
## 一、爬虫手段
|
||||
|
||||
目前爬虫技术都是从渲染好的 html 页面直接找到感兴趣的节点,然后获取对应的文本.
|
||||
有些网站安全性做的好,比如列表页可能好获取,但是详情页就需要从列表页点击对应的 item,将 itemId 通过 form 表单提交,服务端生成对应的参数,然后重定向到详情页(重定向过来的地址后才带有详情页的参数 detailID),这个步骤就可以拦截掉一部分的爬虫开发者
|
||||
|
||||
|
||||
|
||||
## 二、制定出**Web 端反爬技术方案**
|
||||
|
||||
从这2个角度(网页所见非所得、查接口请求没用)出发,制定了下面的反爬方案。
|
||||
|
||||
|
||||
1. 使用HTTPS 协议
|
||||
2. 单位时间内限制掉请求次数过多,则封锁该账号
|
||||
3. 前端技术限制 (接下来是核心技术)
|
||||
|
||||
举例:比如需要正确显示的数据为“19950220”
|
||||
|
||||
#### 2.1 原始数据加密
|
||||
|
||||
1. 先按照自己需求利用相应的规则(数字乱序映射,比如正常的0对应还是0,但是乱序就是 0 <-> 1,1 <-> 9,3 <-> 8,...)制作自定义字体(ttf)
|
||||
2. 根据上面的乱序映射规律,求得到需要返回的数据 19950220 -> 17730220
|
||||
3. 对于第一步得到的字符串,依次遍历每个字符,将每个字符根据按照线性变换(y=kx+b)。线性方程的系数和常数项是根据当前的日期计算得到的。比如当前的日期为“2018-07-24”,那么线性变换的 k 为 7,b 为 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+b,k和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 = "";
|
||||
}
|
||||
else if (rawNumber == 2) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 3) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 4) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 5) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 6) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 7) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 8) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 9) {
|
||||
mapData = "";
|
||||
}
|
||||
else if (rawNumber == 0) {
|
||||
mapData = "";
|
||||
}
|
||||
}
|
||||
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 页面
|
||||
|
||||

|
||||
上面计算的到的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 等等。
|
||||
|
||||

|
||||

|
||||
|
||||
这几种组合拳打下来。对于一般的爬虫就放弃了。
|
||||
|
||||
|
||||
|
||||
## 四、反爬手段再升级
|
||||
|
||||
上面说的方法主要是针对**数字**做的反爬手段,如果要对汉字进行反爬怎么办?接下来提供几种方案
|
||||
|
||||
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://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. 对于数字和汉字的处理手段都不一致
|
||||
|
||||
这几种组合拳打下来。对于一般的爬虫就放弃了。
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
前面的 ttf 转 svg 网站当 ttf 文件太大会限制转换,让你购买,贴出个新链接。[ttf转svg](https://convertio.co/zh/font-converter/)
|
||||
|
||||
|
||||
|
||||
## [Demo 地址](https://github.com/FantasticLBP/Anti-WebSpider)
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
运行步骤
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
1079
Chapter2 - Web FrontEnd/2.19.md
Normal file
1079
Chapter2 - Web FrontEnd/2.19.md
Normal file
File diff suppressed because it is too large
Load Diff
94
Chapter2 - Web FrontEnd/2.2.md
Normal file
94
Chapter2 - Web FrontEnd/2.2.md
Normal 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');
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
14
Chapter2 - Web FrontEnd/2.20.md
Normal file
14
Chapter2 - Web FrontEnd/2.20.md
Normal 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 就是这样一种概念。
|
||||
|
||||
|
||||
|
||||
111
Chapter2 - Web FrontEnd/2.21.md
Normal file
111
Chapter2 - Web FrontEnd/2.21.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Canvas
|
||||
|
||||
## 支持性
|
||||
|
||||
由于浏览器对 Canvas 的支持标准不一致,所以通常 <canvas> 内部添加一些说明行的 HTML 代码,如果浏览器支持 Canvas,它将忽略 <canvas> 内部的 HTML,如果浏览器不支持 Canvas,它将显示 <canvas> 内部的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);
|
||||
```
|
||||
|
||||

|
||||
|
||||
* #### 绘制矩形
|
||||
|
||||
不同于 SVG,Canvas 只提供了一种原生的图形绘制能力:矩形。 所有的其他图形的绘制都至少需要生成一条路径。
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### 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();
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
100
Chapter2 - Web FrontEnd/2.22.md
Normal file
100
Chapter2 - Web FrontEnd/2.22.md
Normal 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>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
120
Chapter2 - Web FrontEnd/2.23.md
Normal file
120
Chapter2 - Web FrontEnd/2.23.md
Normal 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”>!</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)
|
||||
267
Chapter2 - Web FrontEnd/2.24.md
Normal file
267
Chapter2 - Web FrontEnd/2.24.md
Normal 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();//清空控制台(这个下方截图中没有)
|
||||
```
|
||||
|
||||

|
||||
|
||||
注意上面输出的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");
|
||||
```
|
||||
|
||||

|
||||
<todo>
|
||||
|
||||
#### DOM输出
|
||||
|
||||
下面几个比较简单的,就不举例子了,简单说一下:
|
||||
|
||||
```
|
||||
var ul = document.getElementsByTagName("ul");
|
||||
console.dirxml(ul); //树形输出table节点,即<table>和它的innerHTML,由于document.getElementsByTagName是动态的,所以这个得到的结果肯定是动态的
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### 对象输出
|
||||
|
||||
```
|
||||
var o = {
|
||||
name:"Lily",
|
||||
age: 18
|
||||
};
|
||||
console.dir(obj);//显示对象自有属性和方法
|
||||
```
|
||||
|
||||

|
||||
|
||||
对于多个对象的集合,你可以这样,输出更清晰:
|
||||
|
||||
```
|
||||
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.group("start"); //引号里是组名,自己起
|
||||
console.log("sub1");
|
||||
console.log("sub1");
|
||||
console.log("sub1");
|
||||
console.groupEnd("end");
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### 函数计数和跟踪
|
||||
|
||||
```
|
||||
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);
|
||||
```
|
||||
|
||||

|
||||
注:Chrome开发者工具中的Sources标签页也在Watch表达式下面显示调用栈。
|
||||
|
||||
#### 计时
|
||||
|
||||
```
|
||||
console.time() //计时开始
|
||||
fib(100); //用上述函数计算100个斐波那契数
|
||||
console.timeEnd() //计时结束并输出时长
|
||||
```
|
||||
|
||||

|
||||
断言语句,这个c++调试里面也经常用到。js中,当第一个表达式或参数为true时候什么也不发生,为false时终止程序并报错
|
||||
|
||||
```
|
||||
console.assert(true, "我错了");
|
||||
console.assert(false, "我真的错了");
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### 性能分析
|
||||
|
||||
```
|
||||
function F(){
|
||||
var i = 0;
|
||||
function f(){
|
||||
while(i++ == 1000);
|
||||
}
|
||||
function g(){
|
||||
while(i++ == 100000);
|
||||
}
|
||||
f();
|
||||
g();
|
||||
}
|
||||
console.profile();
|
||||
F();
|
||||
console.profileEnd();
|
||||
```
|
||||
|
||||

|
||||
注:Chrome开发者工具中的Audits标签页也可以实现性能分析。
|
||||
|
||||
### debugger
|
||||
|
||||
这个重量级的是博主最常用的,可能是c++出身,对于单步调试由衷的热爱。单步调试就是点一下,执行一句程序,并且可以查看当前作用域可见的所有变量和值。而debugger就是告诉程序在那里停下来进行单步调试,俗称断点。
|
||||
|
||||

|
||||
|
||||
右边按钮如下:
|
||||
|
||||
- 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:异常情况自动断点设置。
|
||||
|
||||
其实右侧还有很多强大的功能
|
||||

|
||||
|
||||
- Watch:Watch表达式
|
||||
- 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)
|
||||
|
||||

|
||||
|
||||
- Global Listeners:全局事件监听
|
||||
- Event Listener Breakpoints:事件监听器断点,列出了所有页面及脚本事件,包括:鼠标、键盘、动画、定时器、XHR等等。
|
||||
|
||||
### chrome中的调试技巧
|
||||
|
||||
1. DOM元素的控制台书签
|
||||
|
||||
Chrome开发者工具和Firebug都提供了书签功能,用于显示你在元素标签页(Chrome)或HTML标签页(Firebug)中最后点击的DOM元素。如果你依次选择了A元素、B元素和C元素,那么$0 表示C元素,$1 表示B元素,$2 表示A元素。(这个和正则表达式的$符号类似,不过顺序不同)
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||

|
||||
|
||||
这里,第一个属性中对象引用的值是不可靠的。当你第一次在开发者工具中显示这个属性时,num的值就已经确定了。之后无论你对同一个引用重新打开多少次都不会变化。
|
||||
|
||||
2.尽可能使用 source map。有时生产代码不能使用source map,但不管怎样,你都不应该直接对生产代码进行调试。
|
||||
|
||||
|
||||
### 异常调试
|
||||
|
||||
```
|
||||
<script>
|
||||
const age = 23;
|
||||
age = 24;
|
||||
console.log(age);
|
||||
</script>
|
||||
```
|
||||
|
||||
代码会报错。为了诸多原因,我们希望提前解决,所以我们希望准确知道代码在哪里有问题。也就是需要调试,希望 JS 像其他编程语言一样可以调试。
|
||||
|
||||
默认情况下会在 console 里面报错。如下图
|
||||

|
||||
|
||||
为了准确定位,我们可以在调试模式的右侧开启 “Pause on exception”按钮
|
||||

|
||||
|
||||
如果我们的代码加了 try.catch.,那么之前的设置是不能定位到异常的位置。
|
||||
|
||||
```
|
||||
<script>
|
||||
try {
|
||||
const age = 23;
|
||||
age = 24;
|
||||
console.log(age);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
</script>
|
||||
```
|
||||

|
||||
|
||||
如果想捕获try.catch里面的异常,则可以在调试面板的右侧勾选“Pause on caught exceptions”,设置完即使是 try.catch 里面的异常也可以定位到具体位置
|
||||

|
||||
193
Chapter2 - Web FrontEnd/2.25.md
Normal file
193
Chapter2 - Web FrontEnd/2.25.md
Normal 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 规则,只能定义一个属性的变化,不能涉及多个属性
|
||||
|
||||
语法:**transition:property 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 规则))和 native(iOS)端的 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 端也一样,开启绘图上下文、拿到上下对象、绘制路径、上色、关闭上下文。
|
||||
|
||||
所以其他具体的例子也就不举了,本质上大前端的所有动画干的事情都一样,所以我们需要处理好时间和位置的关系。
|
||||
10
Chapter2 - Web FrontEnd/2.26.md
Normal file
10
Chapter2 - Web FrontEnd/2.26.md
Normal 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/
|
||||
48
Chapter2 - Web FrontEnd/2.27.md
Normal file
48
Chapter2 - Web FrontEnd/2.27.md
Normal 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 共同使用,但必须保证这些标签页都是同源的
|
||||
|
||||
|
||||
9
Chapter2 - Web FrontEnd/2.28.md
Normal file
9
Chapter2 - Web FrontEnd/2.28.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 神器Puppeteer
|
||||
|
||||
> 为什么要说神器呢?因为在我眼里它就是神器,即使有些场景它没办法完美解决,但是它已经很强大了,足以满足很多开发或者需求场景。比如网页脚本注入、网页截图、爬虫、自动化测试等等功能
|
||||
|
||||
## 介绍
|
||||
|
||||
[官方文档](https://pptr.dev)
|
||||
|
||||
|
||||
839
Chapter2 - Web FrontEnd/2.29.md
Normal file
839
Chapter2 - Web FrontEnd/2.29.md
Normal 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:Model(模型) + View(视图) + Controller(控制器),主要目的在于分层,各司其职。 View 通过 Controller 来和 Model 联系。Controller 用来管理 View 和 Model。View 将事件传递给 Controller,Controller 完成业务逻辑后要求 Model 改变,Model 将新的数据发送到 View,用户得到反馈。
|
||||
|
||||

|
||||
- MVP:从 MVC 演变而来,都通过 Presenter/Controller 负责逻辑处理,View 负责界面展示,Model 负责数据。在 MVP 中主要逻辑在 Presenter 中。View 与 Model 不发生联系,都通过 Presenter 传递。View 层非常薄,不部署任何业务逻辑,没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
|
||||
|
||||

|
||||
- MVVM:将 MVP 中的 中,Presenter 变成了 ViewModel,View 的变动会自动同步到 ViewModel,ViewModel 的变化也会同步到 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 = '刘斌鹏'` 访问的就是属性的 setter,Vue 观察到属性变化会自动操作 View 的响应式变化。
|
||||
|
||||
|
||||
### 如何学习(前置条件)
|
||||
|
||||
- npm
|
||||
npm其实是Node.js的包管理工具(package manager)。开发时,会用到很多别人写的JavaScript代码。如果我们要使用别人写的某个包,每次都根据名称搜索一下官方网站,下载代码,解压,再使用,非常繁琐。于是一个集中管理的工具应运而生:大家都把自己开发的模块打包后放到 npm 官网上,如果要使用,直接通过npm安装就可以直接用,不用管代码存在哪,应该从哪下载。更重要的是,如果我们要使用模块A,而模块A又依赖于模块B,模块B又依赖于模块X和模块Y,npm可以根据依赖关系,把所有依赖的包都下载下来并管理起来。否则,靠我们自己手动管理,肯定既麻烦又容易出错。
|
||||
|
||||
- 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}`);
|
||||
```
|
||||

|
||||
|
||||
发现打印出来的东西和 Vue console 中输出基本一直,所以猜想 Vue 的实现也是依赖 `Object.defineProperty`
|
||||
|
||||
目前主流的框架基本都实现了单向数据绑定,在我看来双向数据绑定无非就是在单项数据绑定的基础上实现了给可输入元素(input、textarea)添加了 change(input)事件来动态修改 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
|
||||
|
||||

|
||||
|
||||
看几个属性: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 主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图,如图所示:
|
||||

|
||||
|
||||
因为遍历解析的过程有多次操作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 中组件的渲染功能都依赖于 JSX(Javascript的一种语法糖,尽管这种方式对于 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
|
||||
DOM?BOM?
|
||||
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-box,margin,padding,border 属性不支持合并简写;不支持 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 不能用 css3,Weex 的 css3 的支持程度算比较高,但是有一些 css3 的属性还是不支持的。transform 支持 2D;font-family 支持 ttf 和 woff 字体格式的自定义的字体;liner-gradient 只支持双色渐变
|
||||
- 调试方式
|
||||
如果说 React Native 的调试方式解放了原生开发调试、那么 Weex 就是赋予了 web 模式调试原生应用的能力。
|
||||
140
Chapter2 - Web FrontEnd/2.3.md
Normal file
140
Chapter2 - Web FrontEnd/2.3.md
Normal 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;
|
||||
?>
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
**说明**
|
||||
|
||||
* 当然这种方式使用比较简单的事件埋点。复杂的话还是需要 JS 操作。
|
||||
* JS 埋点统计用户可以通过浏览器禁用,CSS的话没办法禁用
|
||||
|
||||
|
||||
|
||||
20
Chapter2 - Web FrontEnd/2.30.md
Normal file
20
Chapter2 - Web FrontEnd/2.30.md
Normal 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
|
||||
```
|
||||
2
Chapter2 - Web FrontEnd/2.31.md
Normal file
2
Chapter2 - Web FrontEnd/2.31.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# 浏览器布局与DOM绘制
|
||||
|
||||
238
Chapter2 - Web FrontEnd/2.32.md
Normal file
238
Chapter2 - Web FrontEnd/2.32.md
Normal 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'))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 生命周期
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 状态管理
|
||||
|
||||
Redux 设计上是对 Flux 的改进,增加了 reducer。Flux 就不再介绍了。解决了各个组件之间数据传递的复杂问题。先看看 Redux 进行状态管理的一个流程吧。
|
||||
|
||||

|
||||
|
||||
### 开发步骤
|
||||
|
||||
- 各个组件在需要修改传递数据的时候创建一个 Action
|
||||
- 利用 dispatch 提交给 store
|
||||
- store 本身不处理 state,所以将 action 转发给 reducer
|
||||
- reducer 根据 action 的 type 判断具体如何处理数据。 reducer 中返回的函数是纯函数(输入给定的时候,输出的结果也是恒定的。且不改变输入值),函数的返回值就是 state,state 返回给 store,store 可以通过 `getState()` 拿到最新的 state 数据。
|
||||
- 各个组件如果需要知道 state 的数据变化,那么可以在组件的 constructor 中设置监听订阅(subscribe) store(代码:store.subscribe(this.handleStateChange))。订阅的地方设置一个处理函数,然后在处理函数里面根据 store 获取到最新的 state(this.setState(store.getState()))。
|
||||
|
||||
### 开发经验
|
||||
|
||||
- Redux 中每次创建 action 都需要设置 type,type 为字符串,所以很容易写错,且各个组件都直接用字符串的方式创建 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 的方式就是触发 action,action 是描述已发生时间的普通对象
|
||||
- 使用纯函数来执行修改:为了描述 action 如何改变 state tree,你需要根据业务编写 reducer
|
||||
|
||||
### Redux-thunk
|
||||
|
||||
Redux-thunk 是 redux 里面常用的一个中间件。中间件?针对谁和谁的中间?对 action 和 store 的中间件。本来 action 只可以返回一个对象,灵活性较低,但是采用了 redux-thunk 之后,action 不仅可以传递对象,还可以传递函数。 action 通过 dispatch 传递给 store。 dispatch 判断 action 的类型,如果是对象则直接传递;如果是函数则直接执行。
|
||||
|
||||

|
||||
|
||||
- 异步函数不应该放在组件的生命周期函数里面。复杂的业务逻辑和异步函数适合拆分。目前主流的解决方案有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。React:How corrct it can be 和 all in js(css写法也在用 js 控制,比如 styled-component)
|
||||
|
||||
|
||||
|
||||
58
Chapter2 - Web FrontEnd/2.33.md
Normal file
58
Chapter2 - Web FrontEnd/2.33.md
Normal 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 引用 B,B 引用 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());
|
||||
```
|
||||
|
||||
-
|
||||
|
||||
165
Chapter2 - Web FrontEnd/2.34.md
Normal file
165
Chapter2 - Web FrontEnd/2.34.md
Normal 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 对象请看下图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
上面说到**光标**也是一个特殊的选区,当 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>
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 注意点
|
||||
|
||||
- 执行的是原生的 document.execCommand 方法,浏览器自身会对 contenteditable 这个可编辑区维护一个 undo 栈和一个 redo 栈,所以我们才能执行前进和后退的操作,如果我们改写了原生方法,就会破坏原有的栈结构,这时就需要自己去维护,代价很大
|
||||
- 如果是 Vue style 里面如果加上 scope 的话,里面的样式对编辑区的内容是不生效的,因为编辑区里面是后来才创建的元素,所以要么删了 scope,要么用 /deep/ 解决(Vue 是这样)。React 的 styled-components 也有类似问题。
|
||||
181
Chapter2 - Web FrontEnd/2.35.md
Normal file
181
Chapter2 - Web FrontEnd/2.35.md
Normal 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 作为新标准将受到浏览器厂商重点持续的性能优化,性能这块相信会逐步得到改善。
|
||||
16
Chapter2 - Web FrontEnd/2.36.md
Normal file
16
Chapter2 - Web FrontEnd/2.36.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# immutable.jS 与 React
|
||||
|
||||
|
||||
## 引用带来副作用
|
||||
|
||||
js 中数据类型分为2种,primitive value 和 Object。js 中的对象非常灵活,某个地方对对象进行修改后产生的结果难以预期,所以我们需要不可变对象。
|
||||
但是自己实现的对象拷贝算法,可能浪费时间也浪费空间, 所以诞生了 immutable.js 这个库。
|
||||
|
||||
|
||||
|
||||
## 核心实现
|
||||
|
||||
immutable.js 基于哈希映射树和 vector map tries。只 clone 该节点和其祖先节点,其他保持不变,这样可以共享大部分相同的节点,大大提高性能。
|
||||
|
||||
1. 避免直接修改线性结构
|
||||
2. 减小内存开销(增量更新)
|
||||
63
Chapter2 - Web FrontEnd/2.37.md
Normal file
63
Chapter2 - Web FrontEnd/2.37.md
Normal 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
|
||||
|
||||
|
||||
### 前端代码风格自动化系列
|
||||
|
||||
|
||||
12
Chapter2 - Web FrontEnd/2.38.md
Normal file
12
Chapter2 - Web FrontEnd/2.38.md
Normal 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
|
||||
338
Chapter2 - Web FrontEnd/2.39.md
Normal file
338
Chapter2 - Web FrontEnd/2.39.md
Normal 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 规范
|
||||
|
||||
AMD(Asynchronous 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 是没有明显的 bug,SeaJS 是明显没有 bug。
|
||||
|
||||
5. 两者对调试等的支持有差异。SeaJS 通过插件,可以实现 Fiddler 中自动映射的功能,还可以实现自动 combo 等功能,非常方便便捷。RequireJS无这方面的支持。
|
||||
|
||||
6. 两者的插件机制有差异。RequireJS 采取的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采取的插件机制则与 Node 的方式一致开放自身,让插件开发者可直接访问或修改,从而非常灵活,可以实现各种类型的插件。
|
||||
|
||||
|
||||
|
||||
|
||||
## UMD 规范
|
||||
|
||||
UMD(Universal 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 {};
|
||||
}))
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 回到正题
|
||||
|
||||
Cheerio 如何打包到普通的 JS 执行环境中。
|
||||
|
||||
Webpack 支持的模块化参数如下图所示:
|
||||

|
||||
|
||||
借助 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 原生规范的优势就又减少了一点。
|
||||
54
Chapter2 - Web FrontEnd/2.4.md
Normal file
54
Chapter2 - Web FrontEnd/2.4.md
Normal 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>
|
||||
```
|
||||
|
||||
|
||||
|
||||
122
Chapter2 - Web FrontEnd/2.5.md
Normal file
122
Chapter2 - Web FrontEnd/2.5.md
Normal 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
|
||||

|
||||
|
||||
2、本地局域网
|
||||

|
||||
|
||||
关键词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 OK(from cache ),有的缓存是304 Not modified。如果运维移除了Entity Tag就一直是200(from cache)。如果没有移除的话2者是交替出现的。
|
||||
|
||||
|
||||
|
||||
为什么2者会有区别?
|
||||
|
||||
* 200 OK(from cache)是直接点击链接或者在浏览器地址栏中输入网址敲回车键的结果
|
||||
* 而304 modified是我们刷新了浏览器页面时触发或者设置了长缓存、但Entity Tags没有移除时触发
|
||||
|
||||
做了 实验得出结论:
|
||||
|
||||
* 直接访问有缓存的网站都触发 200 OK \(from cache\)
|
||||
|
||||
* 刷新浏览器则会触发304
|
||||
|
||||
* 同一域名下,没有 Entity Tag 的资源直接访问,是 200 OK \(from cache\) 的结果
|
||||
|
||||
* 同一域名下,有Entity Tag 的资源直接访问,是出现304 Not Modified
|
||||
105
Chapter2 - Web FrontEnd/2.6.md
Normal file
105
Chapter2 - Web FrontEnd/2.6.md
Normal 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条指令,结果如下图
|
||||

|
||||
可以看出来,dataset后跟的属性是驼峰命名原则,如果多个单词第二个单词首字母需要大写,检查元素可以看到神奇的变化。
|
||||
|
||||
94
Chapter2 - Web FrontEnd/2.7.md
Normal file
94
Chapter2 - Web FrontEnd/2.7.md
Normal 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 对象一样。
|
||||
28
Chapter2 - Web FrontEnd/2.8.md
Normal file
28
Chapter2 - Web FrontEnd/2.8.md
Normal 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;
|
||||
});
|
||||
```
|
||||
|
||||
* 将对象转换为json,JSON.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,' ');
|
||||
```
|
||||
|
||||
|
||||
|
||||
641
Chapter2 - Web FrontEnd/2.9.md
Normal file
641
Chapter2 - Web FrontEnd/2.9.md
Normal 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
|
||||
|
||||

|
||||
|
||||
渲染函数的好处:
|
||||
|
||||
|
||||
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 模版所哟子组件匿名插槽这块模版被使用了。最终结果
|
||||

|
||||
|
||||
### 具名插槽
|
||||
匿名插槽没有设置 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>
|
||||
```
|
||||
|
||||

|
||||
|
||||
父组件通过 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)
|
||||
说明:一个校验的包,内置蛮多规则,你也可以自定义,支持国际化
|
||||
|
||||
44
Chapter2 - Web FrontEnd/chapter2.md
Normal file
44
Chapter2 - Web FrontEnd/chapter2.md
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user