pasoul / blog Goto Github PK
View Code? Open in Web Editor NEW🛠 学习笔记 & 源码分析 & 代码规范 & 数据结构与算法
🛠 学习笔记 & 源码分析 & 代码规范 & 数据结构与算法
安装babel-core babel-loader转义es6语法
npm i -D babel-loader babel-core
创建一个webpack.config.js
module.exports = {
entry: {
app: './app.js'
},
output: {
filename: '[name].[hash:5].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
}
]
}
}
test定义一个规则,匹配所有以.js结尾的文件,对这些文件用babel-loader进行es6的转义,exclude表示不需要对node_modules中的第三方库进行转义
虽然定义好了loader,但是babel-loader不知道以什么规范进行转义,这时候需要用到babel的presets,presets其实是对babel规范的汇总,一般有一下几类:
es2015/es2016/es2017/latest
使用es2015也就是我们常说的es6,编译箭头函数、编译const、let、编译class等待,更多细节查看文档
使用es2016也就是es7,涉及到幂运算符,更多细节可看文档
使用es2017也就是es8,包含我们经常使用的async/await语法糖,更多细节可看文档
latest是个特殊的presets,包含了es2015、es2016、es2017一直到目前为止正式发布(不包括stage0-stage3)的规范
env
babel-preset-es20xx和babel-preset-lastest的问题在于它们太过于庞大,即包含了过多在某些情况下不需要的功能,比如大多数浏览器已经支持generator,如果你使用babel-preset-es2015,它仍然会将generator函数编译成复杂的es5代码,这是没有必要的
babel-preset-env和babel-preset-lastest功能相似,但是使用babel-preset-env我们可以声明环境,然后该preset只会编译该环境下缺少的特性的代码
比如我们根据browserslist针对某些浏览器环境进行转义:
比如:
"babel": {
"presets": [
[
"env",
{
"targets": {
"browsers": ["last 2 versions", "ie >= 7"]
}
}
]
]
},
2.支持超过市场份额5%的浏览器
"targets": {
"browsers": "> 5%"
}
"targets": {
"chrome": 56
}
如何安装: npm install --save-dev babel-preset-env,如果安装的是最新的babel-loader,需要安装最新的preset-env:npm install "babel-loader@^8.0.0-beta" @babel/core @babel/preset-env
更多配置可以参考官方文档
要注意:这也意味着我们需要安装一些插件或preset以处理某些实验特性(stage0-stage3),这些往往不是babel-preset-lastest的一部分
使用babel-preset-env的好处就是你不再需要使用babel-preset-esxxxx
继续对我们的webpack.config.js做修改,增加preset预设
module.exports = {
entry: {
app: './app.js'
},
output: {
filename: '[name].[hash:5].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { // 给这个loader指定参数
presets: [ // 给babel-preset-env设置参数
[
'env',
{
targets: {
browsers: ['>1%', 'last 2 versions']
}
}
]
]
}
}
}
]
}
}
对app.js进行修改,增加一些es6的代码
let func = () => {}
const NUM = 40
let arr = [1, 2, 3]
let arrB = arr.map(item => item * 4)
console.log(arrB)
执行webpack打包,观察打包后生成的文件把es6的代码成功转义成es5:
如果把target改成只针对chome浏览器版本为52:
target: {
chrome: '52'
}
如果给app.js增加一些es6的方法,比如includes和new Set()
let func = () => {}
const NUM = 40
let arr = [1, 2, 3]
let arrB = arr.map(item => item * 4)
console.log(arrB.includes(8))
console.log('new set', new Set(arrB))
打开编译后的文件,发现并没有对includes和set进行转义,而低版本浏览器显然是没有这些方法的,其实babel-preset-env只会针对语法进行转义,而不会对函数和方法(Set、Map、Promise、Generator、Array.from、Object.assign等等)进行转义,因此我们需要babel-polyfill和babel-runtime
babel-polyfill
全局垫片,为应用准备,不适用于框架,相当于对全局的变量进行污染,把没有的方法定义到全局
安装:npm install --save babel-polyfill
babel-runtime
局部垫片,为开发框架准备,使用es6语法,又不想污染全局变量
安装:npm install --save babel-plugin-transform-runtime npm install --save babel-runtime
app.js引入安装的babel-polyfill:
import 'babel-polyfill'
let func = () => {}
const NUM = 40
let arr = [1, 2, 3]
let arrB = arr.map(item => item * 4)
console.log(arrB.includes(8))
console.log(arrB)
console.log('new set', new Set(arrB))
发现打包后的文件增大了到了265kb,因为引入了全局垫片
同时babel-polyfill给全局定义了includes方法和set函数
2.非应用级:如开发框架
修改webpack.config.js配置,新建.babelrc文件,把options的配置放在.babelrc中
module.exports = {
entry: {
app: './app.js'
},
output: {
filename: '[name].[hash:5].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
}
]
}
}
.babelrc:
{
"presets": [
[
"env",
{
"targets": {
"browsers": ['>1%', 'last 2 versions']
}
}
]
],
plugins: ["transform-runtime"]
}
观察打包后的文件,发现run-time仅仅是对es6的方法进行了修改,没有增加全局变量
同时文件也仅仅增加到了40+kb
[lukou@deploy ~]$ lsb_release -a
LSB Version: :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description: CentOS Linux release 8.2.2004 (Core)
Release: 8.2.2004
Codename: Core
docker pull jenkins/jenkins
docker images
docker run --name jenkins -u root -d -v /var/jenkins_home:/var/jenkins_home -p 8080:8080 -p 50000:50000 --restart=always jenkins/jenkins
参数说明:
-name jenkins 容器名称为jenkins
-d 后台运行
-v /var/jenkins_home:/var/jenkins_home 将容器内Jenkins的配置映射到宿主机上,容器删除仍可保留配置,-v参数中,冒号":"前面的目录是宿主机目录,后面的目录是容器内目录,
-p 端口映射
-u root 以root身份运行
docker ps
sed -i 's/http:\/\/updates.jenkins-ci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' /var/jenkins_home/updates/default.json
sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' /var/jenkins_home/updates/default.json
# 如果没有指定-v,需要进入容器查看密码
# docker exec -it jenkins /bin/bash
cat /var/jenkins_home/secrets/initialAdminPassword
• Git Parameter:构建时可以选择分支
• NodeJS:构建时执行npm script
• Publish over SSH:用ssh发送构建产物至目标服务器
在jenkins镜像里面安装node
apt-get install curl
jenkins@b138034161de:/$ apt-get install curl
E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
root身份登录后可以正常安装
root@cb0c931a18d2:/# apt-get install curl
Reading package lists... Done
Building dependency tree
Reading state information... Done
curl is already the newest version (7.52.1-5+deb9u10).
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
nvm install v12.9.1
nvm alias default v12.9.1
WARNING: failed to autodetect C++ compiler version (CXX=g++)
WARNING: failed to autodetect C compiler version (CC=gcc)
ERROR: No acceptable C compiler found!
Please make sure you have a C compiler installed on your system and/or
consider adjusting the CC environment variable if you installed
it in a non-standard prefix.
nvm: install v12.9.1 failed!
首先检查gcc(gcc --version)或g++(g++ --version)是否安装
如果显示:gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4)
证明环境已安装gcc 但版本过低所以没办法完成编译会报错
重新下载或更新就可以了,重新下载需先卸载:
apt-get remove gcc
若没安装:
apt-get install gcc
apt-get install g++
如果安装gcc报错:
root@cb0c931a18d2:~/.ssh# apt-get install gcc
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package gcc
需要先执行:
apt-get update
安装zip(可选)
(最快方法)查看宿主机的zip位置
whereis zip
zip: /usr/bin/zip /usr/share/man/man1/zip.1.gz
拷贝到容器中:
sudo docker cp /usr/bin/zip jenkins:/usr/bin/zip
如果需要发送压缩包,则需要安装zip:
apt-get install zip
如果报错:
root@19009acbf6f2:~# apt-get install zip
Reading package lists... Done
Building dependency tree
Reading state information... Done
Package zip is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
E: Package 'zip' has no installation candidate
解决方案:
apt-get -y update
apt-get install zip
Docker命令
之前我们总结对象的类型-接口时,了解到接口有两个作用,一个是对对象的形状进行描述,而这一节讲述另一个作用:对类的一部分行为进行抽象。
一般来讲,一个类只能继承自另外一个类,有时候不同的类有相同的特性,可以把这些特性单独提取成一个接口(interface
),然后用类去实现(implements)它。
比如门Door
是一个类,防盗门是门的子类,防盗门有一个报警的方法,这时另一个类Car,也有报警的功能,我们可以把报警这个功能单独提取成一个接口,让防盗门和车都去实现它:
interface Alarm {
alert();
}
class Door{
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
如果Car没有实现接口定义的alert方法,编译将会报错:
class Car implements Alarm {
}
// Class 'Car' incorrectly implements interface 'Alarm'.
// Property 'alert' is missing in type 'Car'.
一个类可以实现多个接口:
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
class Car implements Alarm,Light {
alert() {
console.log('Car alert');
}
lightOn(){
console.log('open light')
}
lightOff(){
console.log('close light')
}
}
如果没有明确指定类型,typescript
会根据类型推导的规则推断出一个类型。
我们声明了一个变量x,并初始化赋值为1,注意我们没有给变量x指定类型,然后将x重新赋值为字符串'1',此时编译将会报错:
let x = 1;
x = '1';
// Type '"1"' is not assignable to type 'number'.
问题的原因就是在没有指定类型的时候,typescript
会推断出x的类型是number
类型,上面的例子等价于:
let x:number = 1;
x = '1';
如果定义的时候没有赋值,将会被推断成any
类型,而完全不会被类型检查
let x;
x = 1;
x = '1';
x = true;
参考:
JavaScript
的数据类型分为两类:原始类型和对象类型。JavaScript
的原始类型包括:数字、字符串、布尔值、null
和undefined
。JavaScript
除了原始类型数据以外的都是对象,对象(object
)是属性(property
)的集合,每个属性都由键值对构成。
普通的JavaScript
对象是键值对的无序集合,JavaScript
同样定义了一种特殊对象--数组(array
),表示带编号的值的有序集合。JavaScript
专门为数组定义了语法,使其具有与普通的对象不同的行为特性。
JavaScript
还定义了另一种特殊的对象—函数(function
),函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行的代码,并返回运算结果。和数组一样,函数的行为特征与其他对象都不一样,JavaScript
为函数定义了专用语法。对于JavaScript
函数来讲,最重要的是,它们都是真值,并且JavaScript
可以将他们当做普通对象来对待。(常见假值:0, null, undefined, ‘’, NaN, false)。
如果函数用来初始化(使用new
运算符)一类对象,我们称之为构造函数(constructor
)。每个构造函数定义了一类对象--由构造函数初始化的对象组成的集合,类可以看成对象类型的子类型。除了数组(Array
)类和函数(Function
)类以外,JavaScript
语言核心定义了其他三种有用的类,分别是:
Date
)类定义了代表日期的对象RegExp
)类定义了表示正则表达式的对象Error
)类定义了那些在JavaScript程序中运行时错误和语法错误的对象JavaScript
的类型也可以分为拥有方法的类型和不用有方法的类型(只有null
和undefined
)。
同样也可以分为可变类型(mutable
)和不可变类型(immutable
),对象和数组属于可变类型,比如修改对象属性值和数组某个元素的值。数字、布尔、字符串、null
和undefined
是不可变类型,比如你没办法修改某个数字的内容,即使是字符串,你可以把它看成字符组成的数组,可以访问字符串任意位置的文本,但是无法修改字符串的内容。
JavaScript
可以自由的进行数据类型转换,比如,如果程序希望在使用字符串的地方使用了数字,JavaScript
会自动的将数字转换成字符串。如果在期望使用布尔值的地方使用了非布尔值,JavaScript
也会进行相应的转换。JavaScript
灵活的类型转换规则,对判断相等的定义也有影响。等号运算符“==”所进行的类型转换在后面会提到。
JavaScript
变量是无类型的(untyped
),变量可以被赋为任意类型的值。使用var
关键字声明(declare
)变量。JavaScript
采用词法作用域,不在任何函数内声明的变量称为全局变量(global variable
),它在JavaScript
程序中的任何地方都是可见的。在函数内声明的变量具有函数作用域(function scope
),并且只在函数内可见。
typescript
支持几乎与javascript
相同的基础数据类型:Number
、String
、Boolean
、Undefined
、Null
,在此基础上,typescript
还扩展了void
、any
等常见类型数据。
// 声明一个number类型变量
let n:number = 2;
在typescript
,如果未按照类型注解给变量赋值,比如将字符串赋值给n,将会在编译阶段报错:
Type '"1"' is not assignable to type 'number'.
// 声明一个布尔类型的变量
let hasMore:boolean = true;
// 声明一个string类型变量
let nickname:string = "zhangsan";
// null和undefined
let un:undefined = undefined;
let nu:null = null;
// 默认情况下null和undefined是所有类型的子类型。 就是说你可以把null和undefined赋值给number类型的变量
let n:number = undefined;
然而,当你设置了--strictNullChecks
表示严格校验空值,undefined
和null
只能赋值给它们各自或者void
类型。因为未避免发生不可预知的错误,鼓励使用--strictNullChecks
有时候我们声明一个变量的时候不确定它是什么类型,它有可能是动态的内容,比如用户输入的值,这时我们希望编译器可以直接通过对它们的检查。
// 声明一个任意值类型变量
let notSure:any = "123";
notSure = 123;
notSure = true;
任意值类型在声明数组的时候也非常有用,比如数组的元素的值是任意类型:
let arr:any = [1, '1', true]
通常,我们用空值void
声明一个没有返回值的函数,很少用它声明一个变量,因为意义不大,只能赋值undefined
和null
(严格校验空值的时候,只能赋值undefined
)
// 声明一个空值类型变量
let empty:void = undefined;
empty = null;
// 声明一个没有返回值的函数
function fn():void {
console.log("no return value");
}
// 等价于
function fn():void {
console.log("no return value");
return undefined;
}
类型别名通常用来给一个类型取新名字。本质上不会创建一个新的类型,而是对原有类型的引用。
type 新名字 = 类型
type Name = string;
// NameResolver表示这是一个函数类型,并且函数的返回值类型是string
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
}
else {
return n();
}
}
开发微信页面,chrome与安卓真机(安卓4.4及以上)联机调试
手机中打开“设置”->”开发人员选项”->”USB调试” ,注意此时需要手机与电脑通过usb连接
用Android机在微信端访问 http://debugx5.qq.com
在打开的网页中选择 【信息】->【TBS settings】,勾选 【是否打开 TBS 内核 Inspector 调试功能】 ,重启微信
打开pc端chrome, 在地址栏中输入chrome://inspect/#devices 选中discover usb devices,注意首次联机需要翻墙
/*
* 说明:请实现 flatten(input) 函数,input 为一个 javascript 对象(Object 或者 Array),返回值为扁平化后的结果。
* 示例:
* var input = {
* a: 1,
* b: [ 1, 2, { c: true }, [ 3 ] ],
* d: { e: 2, f: 3 },
* g: null,
* }
* var output = flatten(input);
* output如下
* {
* "a": 1,
* "b[0]": 1,
* "b[1]": 2,
* "b[2].c": true,
* "b[3][0]": 3,
* "d.e": 2,
* "d.f": 3,
* // "g": null, 值为null或者undefined,丢弃
* }
*/
var result = {}
function isObject(val) {
return Object.prototype.toString.call(val) === '[object Object]'
}
function isArray(val) {
return Array.isArray(val)
}
function flat(input, prefix = "") {
if (isObject(input)) {
for (let key in input) {
let item = input[key];
if (!isObject(item) && !isArray(item) && item !== null && item !== undefined) {
prefix ? result[`${prefix}.${key}`] = item : result[`${key}`] = item
} else if (isObject(item) || isArray(item)) {
flat(item, prefix ? `${prefix}.${key}` : key)
}
}
} else if (isArray(input)) {
for (let key in input) {
let item = input[key];
if (!isObject(item) && !isArray(item) && item !== null && item !== undefined) {
prefix ? result[`${prefix}[${key}]`] = item : result[`${key}`] = item
} else if (isArray(item) || isObject(item)) {
flat(item, prefix ? `${prefix}[${key}]` : key)
}
}
} else if (input !== null && input !== undefined) {
result[prefix] = input
}
}
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型
首先,我们来实现一个函数 createArray
,它可以创建一个长度为5的数组,同时将每一项都填充一个默认值
function createArray(value: any):Array<any> {
let result = [];
for (let i = 0; i < 5; i++) {
result.push(value)
}
return result;
}
console.log(createArray(1)); // [1, 1, 1, 1, 1]
上例中,我们使用了数组泛型来定义返回值的类型
这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型,Array<any>
表示返回值可以是任意类型,但我们期望的是返回数组的每一项类型都是输入值value
的类型。
这时候,泛型就派上用场了:
function createArray<T>(value: T):Array<T> {
let result: T[] = [];
for (let i = 0; i < 5; i++) {
result.push(value)
}
return result;
}
console.log(createArray<number>(1));
上例中,我们在函数名后添加了 <T>
,其中 T
用来指代任意输入的类型,在后面的输入value: T
和输出 Array<T>
中即可使用了。
接着在调用的时候,可以指定它具体的类型为 number
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap<number, string>([7, 'seven']) // ["seven", 7]
上例中,我们定义swap函数,用于交换元组的元素,泛型定义了两个类型,分别对应元组中的元素类型
代码的入口
打包的入口
单个或多个
如何定义一个entry:
01:入口是一个文件
module.exports = {
entry: 'index.js'
}
02:入口是多个文件
module.exports = {
entry: ['index.js', 'vendor.js']
}
03:为entry定义一个key,理解成一个独特的chunk(代码块)
module.exports = {
entry: {
index: ['index.js', 'app.js'],
vendor: 'vendor.js'
}
}
打包成的文件(bundle)
一个或多个
module.exports = {
entry: {
index: 'index.js'
},
output: {
filename: 'index.min.js'
}
}
name为文件入口文件的名称,分别为index和vendor, hash是webpack为文件进行md5处理生成的字符串,5是指定字符串的长度,便于版本控制
module.exports = {
entry: {
index: 'index.js',
vendor: 'vendor.js'
},
output: {
filename: '[name].min.[hash:5].js'
}
}
webpack可以使用loader对文件进行预处理,这允许你打包除js以外的任何静态资源,比如css-loader,可以把css变成js的模块,在js文件中引入进来
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: 'css-loader'
}
]
}
}
编译相关
babel-loader、ts-loader
样式相关
style-loader、css-loader、less-loader、sass-loader、postcss-loader
文件相关
file-loader、url-loader
插件参与到打包的整个过程中,目的在于处理loader无法处理的其他事情,比如
var webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}
优化相关
CommonsChunkPlugin:将公共模块拆分成一个独立的文件,会在最开始的时候加载一次,便缓存到浏览器中供后续使用
UglifyjsWebpackPlugin:压缩、混淆js
功能相关
ExtractTextWebpackPlugin:从一个或者多个bundle提取文本到独立的文件当中
HtmlWebpackPlugin:生成html文件
HotModuleReplacementPlugin:热更新插件
CopyWebpackPlugin:把项目中已经用到的第三方库移动到打包好的目录中
Chunk代码块:webpack会把文件打包成一个个的chunk,CommonsChunkPlugin会把文件提取成单独的chunk,动态懒加载会把文件从bundle中提取出来,打包成chunk
Bundle:打包后的文件,可以理解成一个或者多个chunk组成
Module:模块,Loader可以把除js以外的文件处理成模块
数组是值的有序集合。数组每个元素所处的位置以数字表示,称为索引。数组元素索引不一定要连续的,它们之间可以有空缺,每一个JavaScript
数组都有一个length属性。针对非稀疏数组,该属性就是数组元素的个数,针对稀疏数组,length比所有元素的索引要大。
JavaScript
数组是JavaScript
对象的特殊形式,数组的索引实际上碰巧是整数的属性名差不多。通常,数组的实现是经过优化的,用数字索引来访问数组一般来说比访问常规属性的对象要快很多。
数组继承自Array.prototype
中的属性,它定义了一套丰富的数组操作方法,大多数这些方法对‘类数组’也有效。
对象是Js的基本数据类型,对象是一种复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。对象也可看做数属性的无序集合,每个属性都是一个名/值对。属性名是字符串,因此我们可以把对象看成是字符串到值的映射。这种基本数据结构有很多叫法:‘字典’、‘散列’、‘散列表’、‘关联数组’等。
然而对象不仅仅是字符串到值的映射,除了可以保持自有的属性,Js对象还可以从一个称为原型的对象继承属性,对象的方法通常是继承的属性,这种“原型式继承”是Js的核心特征。
除了字符串、数字、true、false、null和undefined之外。Js中的值都是对象。
对象是可变的,我们通过引用而非值来操作对象。如果变量x是指向一个对象的引用,那么执行var y = x;
,变量y也指向同一个对象的引用,而非这个对象的副本。通过变量y
来修改这个对象亦会对x
造成印象。
属性包括属性名和属性值,属性名可以是包含空字符串在内的所有字符串,但对象中不能存在两个同名的属性。值可以是任意Js值,或者在ES5中,可以是一个getter
或setter
函数,除了属性名和属性值之外,每个属性还有一个与之相关的值,称为"属性特性":
在ES5之前,通过代码给对象创建的所有属性都是可写的、可枚举的和可配置的。在ES5中则可以对这些特性加以配置。
除了包含属性之外,每个对象还拥有三个相关的对象特性(object attribute):
最后,我们用下面这些属于来对三类JS对象和两类属性做区分:
ECMAScript
规范定义的对象或类,例如,数组、函数、日期和正则表达式都是内置对象HTMLElement
对象均是宿主对象,既然宿主环境定义的方法可以当成普通的Js函数对象,那么宿主对象也可以当成内置对象export default function(a, b) {
return a + b
}
2.创建app.js引入sum.js,并调用sum导出的函数
// es6 module方式引入
import sum from './sum.js'
console.log('sum(1, 2)', sum(1, 2))
在webpack中直接打包
webpack app.js bundle.js
创建html文件,引入bundle.js,可以在浏览器控制台看到输出的结果
module.exports = function(a, b) {
return a - b
}
在app.js通过require引入函数
var decrease = require('./decrease.js');
console.log('decrease(1, 2)', decrease(1, 2))
6、通过AMD方式引入模块
创建muti.js,导出一个函数
define(function(require, factory) {
return function(a, b) {
return a * b
}
})
app.js通过require引入模块
require(['./muti'], function(muti) {
console.log('muti', muti(1,2))
})
注意:通过AMD引入模块,执行webpack app.js bundle.js时,由于AMD是异步加载的过程,webpack会创建一个异步加载的js文件0.bundle.js
默认webpack会读取当前路径所在目录下的webpack.config.js或webpackfile.js,如果想自定义webpack配置文件需要--config命令 + 自定义文件名称
创建一个webpack.conf.js文件,设置以下配置项
module.exports = {
entry: {
// 指定入口文件
app: './app.js'
},
output: {
// 指定输入文件的名称,name对应entry的key
filename: '[name].[hash:5].js'
}
}
var Util = {}
Util.type = Util.type || {}
Util.type.method = function() {}
比如: Util.cookie.get / Util.storage.save
弊端:需要提前个团队成员约定好各自的命名空间,需要通过命名空间路径的方式才能找到一个方法...
define(
// 模块名,可省略
'alpha',
// 依赖
['require', 'exports', 'beta'],
// 模块输出
function(require, exports, beta) {
// 通过exports对外提供接口
// 即使没有使用到其中的一个模块,这个模块也会被提前执行
}
)
Common Module Definition
一个文件为一个模块
使用define来定义一个模块
使用require来加载一个模块
代表作:sea.js
尽可能懒执行
AMD和CMD最显著的区别就是执行方式不一样,AMD先把所有依赖都前置(执行),可以在任意地方require,cmd在只有用到依赖的地方才会去执行
define(function(require, exports, module) {
// 通过require引入依赖
var $ = require('jquery')
// 通过exports对外提供接口
exports.doSomething = ...
// 或者通过module.exports提供整个接口
module.exports = ...
})
AMD
ES Module(推荐的,3.0版本原生就支持import 和 export)
CommonJs
oocss
smacss
...
联合类型表示一个值的类型可以是多个类型中的一种,多个类型之间用竖线(|)
分开,比如number|string
表示类型既可以是number
也可以是string
// 声明一个变量,值类型可以是number类型和string类型
let age: number|string = '10';
age = 10;
但是如果我们给age
赋值的类型是其他类型,编译将会报错:
let age: number|string = '10';
age = 10;
age = true;
// Type 'true' is not assignable to type 'string | number'
我们声明了一个函数,参数类型是联合类型number|string
,然后在函数体内部尝试访问参数的length
属性,这时编译报错:
function getSometing(arg:number|string) {
console.log(arg.length);
}
Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'
当typescript
无法确定参数arg
到底是哪个类型,我们只能访问联合类型中所有类型公有的属性和方法,很明显number
类型的参数,没有length
属性
如果我们访问number
和string
的公有属性,�是没问题的:
function getSometing(arg:number|string) {
console.log(arg.toString());
}
我们分别声明两个接口:Chicken
和Duck
,它们分别有自己的方法:鸡会飞和生蛋,鸭子会游泳和生蛋
然后定义了doSomething
函数,参数是animal
,它的类型要么是Chicken
要么是Duck
interface Chicken {
fly();
layEggs();
}
interface Duck {
swim();
layEggs();
}
function doSomething(animal:Chicken|Duck) {
animal.fly();
}
如果我们调用animal
的fly
方法,编译将会报错,因为typescript
不知道animal
的类型到底是Chicken
还是Duck
,如果我们传入的是Duck
,很明显Duck
是不会飞的(接口没有定义fly
)
Property 'fly' does not exist on type 'Chicken | Duck'.
Property 'fly' does not exist on type 'Duck'
但是如果我们调用animal
的layEggs
方法,编译通过,因为不管animal
的类型是Chicken
还是Duck
,它们都拥有生蛋(layEggs
)的能力,你可以大胆的调用。
function doSomething(animal:Chicken|Duck) {
animal.layEggs();
}
参考:
枚举通常用于取值限定在一定范围的场景,比如一周只有七天,颜色被限定为红绿蓝等。
枚举使用enum关键字来定义
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚举成员默认被赋值为从0开始递增的数字,同时也可以从枚举值反向映射到枚举名
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days['Sun']); // 0
console.log(Days['Mon']); // 1
console.log(Days['Tue']); // 2
console.log(Days['0']); // Sun
console.log(Days['1']); // Mon
console.log(Days['2']); // Tue
我们来看编译后的代码,如果让我们实现一个对象的属性和属性值之间的映射,大家应该知道怎么做了
var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
我们也可以给枚举项手动赋值,typescript
只支持枚举值为数字和字符串类型
enum Days {Sun = 7, Mon = 2, Tue, Wed, Thu, Fri, Sat};
console.log(Days['Sun']);// 7
console.log(Days['Mon']);// 2
console.log(Days['Tue']);// 3
console.log(Days['Wed']);// 4
console.log(Days['Sat']);// 7
console.log(Days['0']);// undefined
console.log(Days['1']);// undefined
console.log(Days['2']);// Mon
console.log(Days['3']);// Tue
console.log(Days['7']);// Sat
上面的例子中,我们未手动赋值枚举项会接着上一个枚举项的值递增。
我们注意到未赋值的最后一项和手动赋值的第一项冲突了,但是typescript
不会察觉到,所以Days['7']
的值会被覆盖,编译结果是:
var Days;
(function (Days) {
Days[Days["Sun"] = 7] = "Sun";
Days[Days["Mon"] = 2] = "Mon";
Days[Days["Tue"] = 3] = "Tue";
Days[Days["Wed"] = 4] = "Wed";
Days[Days["Thu"] = 5] = "Thu";
Days[Days["Fri"] = 6] = "Fri";
Days[Days["Sat"] = 7] = "Sat";
})(Days || (Days = {}));
所以在实际使用中我们应该避免出现覆盖的情况。
我们也可以给枚举项赋值为负数、小数:
// 小数
enum Days {Sun = 7, Mon = 2.5, Tue, Wed, Thu, Fri, Sat};
console.log(Days['Sun']); // 7
console.log(Days['Mon']); // 2.5
console.log(Days['Tue']); // 3.5
console.log(Days['Sat']); // 7.5
// 负数
enum Days {Sun = 7, Mon = -2, Tue, Wed, Thu, Fri, Sat};
console.log(Days['Sun']); // 7
console.log(Days['Mon']); // -2
console.log(Days['Tue']); // -1
console.log(Days['Sat']); // 3
我们可以看到无论是前面手动赋值类型是正整数、小数还是负数,未赋值项的递增步长仍然为1。
手动给枚举项赋值的类型也可以是string
:
enum Days {Sun = 7, Mon = 2, Tue, Wed, Thu, Fri, Sat = 'S'};
console.log(Days['Fri']); // 6
console.log(Days['Sat']); // S
需要注意的是,string
类型的枚举项后面不能存在其他枚举项,比如:
enum Days {Sun = 7, Mon = 2, Tue, Wed, Thu, Fri = 'F', Sat};
console.log(Days['Fri']); // F
console.log(Days['Sat']); // undefined
// 编译报错:Enum member must have initializer.
这里typescript
提示枚举成员必须具有初始化表达式,意思是说我们必须给Fri = 'F'
后面的枚举项也手动赋值。
减少代码冗余
提高加载速度
CommonsChunkPlugin是webpack内置的插件,用于建立一个独立的文件,这个文件包括多个入口chunk的公共模块,通过将公共模块拆分出来,在最开始的时候加载一次,便存到缓存中供后续使用。
new webpack.optimize. CommonsChunkPlugin(options)
{
// chunk的名称,如果传入数组,相当于数组的每一项都调用实例化插件
name: string, // or
name: string[],
// chunk的文件名模板,可以包含跟output.filename相同的占位符
filename: string,
// 通常是个>1的整数,表示至少被chunk引入多少次,该模块才会被提取到公共chunk里
// 如果传入Infinity,则立刻会生成公共chunk,但是里面没有模块
minChunks: number|Infinity|function,
// 这是一个元素为chunk名称的数组,插件将会从这些chunks里面提取公共chunk
// 可见如果minChunks是整数,应当小于chunks的个数
// 如果缺省,则所有入口chunk都会被选中
chunks: [],
// 如果设置为true,也会在entry的子模块中查找共同依赖
children: boolean,
// 如果设置为true,也会在entry的所有依赖模块中查找共同依赖
deepChildren: boolean
}
分别创建subPageA.js和subPageB.js,这两个js文件分别引入moduleA.js
subPageA.js
import './moduleA'
export default 'subPageA'
subPageB.js
import './moduleA'
export default 'subPageB'
moduleA.js
export default 'moduleA'
创建项目的入口文件pageA.js,分别引入subPageA.js和subPageB.js
import './subPageA'
import './subPageB'
export default 'pageA'
创建webpack.config.js
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
'pageA': './pageA.js'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common', // 指定生成的公共chunk名称
minChunks: 2 // 指定被引入两次以上的代码,将被打包到公共chunk里
})
]
}
因为依赖了webpack内置的插件,需要安装webpack,执行
npm i -S webpack
执行webpack打包,生成pageA.bundle.js和common.bundle.js
我们希望common.bundle.js包含我们的moduleA.js,因为这个模块同时被subPageA.js和subPageB.js引用,但实际上,moduleA.js依然被打包进pageA.js,而common.bundle.js里面只包含webpack打包的一些代码
实际上CommonsChunkPlugin只针对多entry有效,比如再创建一个pageB.js,同样对subPageA.js和subPageB.js进行依赖
PageB.js
import './subPageA'
import './subPageB'
export default 'pageB'
再给webpack.config.js添加pageB.js的入口
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
'pageA': './pageA.js',
'pageB': './pageB.js' // 增加入口pageB.js
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common', // 指定生成的公共chunk名称
minChunks: 2
})
]
}
我们希望subPageA.js、subPageB.js和moduleA.js都被打包进公共文件,执行webpack,查看结果:
新增了pageB.bundle.js
打包后的pageB.bundle.js
打包后的pageA.bundle.js
重点是common.bundle.js,它成功地把我们希望的文件打包进来
引入第三方库lodash.js,然后在pageA.js和pageB.js中引用
import './subPageA'
import './subPageB'
import * as _ from 'lodash'
export default 'pageA'
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
'pageA': './pageA.js',
'pageB': './pageB.js',
'vendor': ['lodash'] // 数组,里面是我们的第三方库
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', // 传入一个已存在的 chunk 名称而被选择
minChunks: Infinity, // 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块
})
]
}
此时生成了一个vendor.bundle.js,大小为547kb
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
'pageA': './pageA.js',
'pageB': './pageB.js',
'vendor': ['lodash'] // 数组,里面是我们的第三方库
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', // 传入一个已存在的 chunk 名称而被选择
minChunks: Infinity, // 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest', // 传入一个任意非entry的name
minChunks: Infinity
}),
]
}
执行webpack查看打包结果,增加了一个manifest.bundle.js文件,同时vendor.bundle.js大小变成541kb
同时vendor.bundle.js只包含了lodash的代码
manifest.bundle.js单独的把webpack的生成代码提取出来
解决方法:
在webpack.config.js里面再增加一个CommonsChunkPlugin实例,用于提取公共业务代码
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
'pageA': './pageA.js',
'pageB': './pageB.js',
'vendor': ['lodash'] // 数组,里面是我们的第三方库
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common', // 提取业务代码
minChunks: 2
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
minChunks: Infinity
}),
]
}
执行webpack打包,发现报错如下:
在普通模式下,CommonsChunkPlugin不能作用于非入口文件
需要指定范围
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
'pageA': './pageA.js',
'pageB': './pageB.js',
'vendor': ['lodash'] // 数组,里面是我们的第三方库
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2,
chunks: ['pageA', 'pageB'] // 指定从pageA和pageB选择公共代码
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'manifest'], // 合并
minChunks: Infinity
})
]
}
查看打包后的结果,增加了common.bundle.js
里面包含了公共的业务代码
打包后的pageA.js和pageB.js公共文件都被提取出来
函数的定义方式有两种:函数声明和函数表达式
// 函数声明
function add(x, y) {
return x+y;
}
// 函数表达式
let reduce = function(x, y) {
return x - y;
}
一个函数有输入输出,对函数类型的约束本质上是对输入值和输出值的约束,为上面两个函数添加类型声明
函数声明的类型定义较简单:
function add(x:number, y:number):number {
return x + y;
}
这里我们定义了参数x
和y
的类型为number
,返回值类型也为number
,除了类型的约束,参数的个数也限制为2
个,输入多余或者少于要求的参数,是不被允许的
function add(x:number, y:number):number {
return x + y;
}
add(1, 2, 3);
// Expected 2 arguments, but got 3
function add(x:number, y:number):number {
return x + y;
}
add(1);
// Expected 2 arguments, but got 1
我们对上面的函数表达式定义类型:
let reduce = function(x:number, y:number):number {
return x - y;
}
上面我们仅仅对等号右侧的匿名函数进行类型定义,等号左边的reduce
是通过赋值之后类型推导推断出来的,准确的来说叫上下文归类,是类型推导的一种,事实上我们也可以给reduce
定义类型:
下面是完整的类型声明:
let reduce: (x:number, y:number) => number = function(x:number, y:number):number {
return x - y;
}
整个(x:number, y:number) => number
是对reduce的类型定义,注意 =>
并非是ES6的箭头函数,它表示函数输入输出之间的管道,左侧是函数输入值,右侧是函数输出值
我们多次强调函数是由输入值和输出值组成的,即使函数没有任何返回值,我们也要必须指定返回值类型为void
而不能留空。
官网:https://webpack.js.org/
中文官网:https://doc.webpack-china.org/
github:https://github.com/webpack/webpack
引用官方的一段话:本质上,webpack是一个现代JavaScript应用程序的静态模块打包器(module bundler)。当webpack处理应用程序时,它会递归的构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或者多个bundle
webpack v1.0.0 --- 2014.2.20
编译、打包
HMR(模块热更新)
代码分割
文件处理(loader 、plugin)
webpack v2.2.0 --- 2017.1.18
Tree Shaking(项目中引入但是没有使用的代码打包的时候会被删除掉)
ES module(import 、export)
动态Import(V1.0的require.ensure)
提供新的官方文档
webpack v3.0.0 --- 2017.6.19
Scope Hoisting(作用域提升)
Magic Comments(配合动态import使用,指定打包后的chunk名)
webpack v4.0.0 --- 2018.2.25
V1 -> V2
迁移指南中文版:https://doc.webpack-china.org/guides/migrating/
V2 -> V3
webpack开发者基本不会修改api,导致基于旧版本webpack的项目需要进行大规模的迁移,升级webpack之后,基本上使用npm update更新依赖即可
传统javascript
中,回忆我们是如何使用类的:使用实例化构造函数,通过原型链实现继承,在es6
中,我们终于迎来了class
,详细的文档移步ECMAScript 6 入门
typescript
不仅实现了es6
的class
,还在此基础上做了一些扩展。
Cat
和Dog
都继承自Animal
,可以分别实现了自己的eat
方法gzip是GNUzip的缩写,它是一个GNU自由软件的文件压缩程序。在web应用中,通过Nginx开启Gzip,传输压缩过后的静态文件,可以节省网络带宽,提高搜索引擎抓取的速度,提升网站浏览速度等。
浏览器请求url,并在request-header中加入Accept-Encoding:gzip表明浏览器支持Gzip,并告知服务器采用何种压缩方式
服务器收到浏览器的请求后,判断浏览器是否支持Gzip,如果支持,则返回压缩后的内容,并且在response headers中返回Content-Encoding:gzip,如果不支持,则返回未压缩后的内容。
如果浏览器接收到压缩过的文件,需要进行解压缩操作
以淘宝为例,打开控制台:
请求headers:
可以看到网站支持Gzip,当开启Gzip后,压缩倍率可以用在线工具监测
压缩前的文件181k,压缩后的文件75k,压缩率58%,极大的节省了服务器的网络带宽,这对于访问量巨大的淘宝来讲节约的流量非常可观。
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
参数配置:
gzip on: 开启Gzip
gzip_disable "msie6": IE6对Gzip支持不友好,不给它开启了
gzip_vary on: 在header里增加Vary: Accept-Encoding,告诉缓存服务器如何缓存和筛选合适的版本(关于vary的研究单独看这里)
gzip_proxied any: Nginx开启Gzip的条件,比如expired(如果header头包含Expires信息),no-cache(如果header头包含 Cache-Control: no-cache), any (无条件开启)
gzip_comp_level 6: 压缩等级1-10,数字越大压缩的越好,同时也越消耗CPU,一般建议5
gzip_buffers:设置gzip压缩时使用的缓冲区的个数以及每个缓冲区的大小
gzip_http_version:仅对指定版本的http请求的响应进行压缩,通常取值1.1
gzip_types:压缩文件类型,注意:javaScript文件类型application/javascript和text/javascript都加上,否则会出现js没有被压缩的文件,text/html默认被压缩,不需要显式的指定
在实际业务中,某些文件不需要初始化的时候就加载进来,我们需要将这部分代码进行分割,只在合适的时机加载它们,这样既可以节省用户带宽,也可以提高页面载入速度
2.require.include
微信公众号网页开发和移动web开发无本质区别,只是在移动web开发的基础上增加了一些微信提供的扩展能力,如:支付、分享、地理定位等,可以理解为:移动web + 微信能力 = 微信公众号网页开发
微信JS-SDK:是开发者在网页上通过JavaScript代码调用微信原生功能的工具包,开发者可以用它在网页上实现录制和播放语音、扫一扫、分享等能力
为了识别用户,每个用户在每个公众号下都有一个OpenId,如果需要在多公众号、移动应用之间做用户共通,则需前往微信开放平台,将这些公众号和应用绑定到一个开放平台账号下,绑定后,一个用户虽然对多个公众号和应用有多个不同的OpenID,但他对所有这些同一开放平台账号下的公众号和应用,只有一个UnionID,此UnionID是在拉取用户信息(需scope为 snsapi_userinfo)时返回的(后续授权功能会提到)
如果用户在微信端访问网页,公众号可以通过微信网页授权来访问用户的基本信息。
在公众号请求网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com 无法进行OAuth2.0鉴权
如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可
假设我们有这样的需求:用户进入网页通过静默授权拿到用户openId,在指定场景下(如设置用户信息)需要手动授权拿到用户的userInfo
export default function() {
// mode: base 静默授权 userInfo 手动授权
const query = useLocation().query
const mode = query.mode === 'userInfo'
? 'zuc/wx/authorizeUserInfo'
: 'zuc/wx/authorizeBase';
window.location.replace(
`${config.h5LoginDomain}${mode}?redirectUrl=${encodeURIComponent(query.fromPath)}`,
);
return null;
}
request.interceptors.response.use(async response => {
if (response) {
const data = await response.clone().json();
if ( data.code === 30000118 ) {
// 30000118:您的登录已过期,请重新登录
history.push(
`/login?fromPath=${encodeURIComponent(location.href)}`,
);
}
}
return response;
});
handleClick() {
history.push(
`/micro-store/h5/login?fromPath=${encodeURIComponent(location.href)}&mode=userInfo`,
);
}
192.168.30.77 xxx.abc.com
,我们本地访问地址由原来的:192.168.30.77:8080/index
改成xxx.abc.com:8080/index
axios
、fetch
、umi-request
请求时默认不带cookie
的,需要设置credentials: ‘include’
。但此属性要求后端设置的Access-Control-Allow-Origin
不能为*
,此问题在通过CROS解决跨域问题时尤为常见,报错如下:Access-Control-Allow-Origin
中,当然为了安全,可以把允许跨域访问的域名放在一个集合里,然后判断浏览器的请求头是不是在这个集合中webpack允许我们在js模块中import
css文件,这一切归功于强大的loader,可以使你在import
或‘加载’模块时预处理文件,编译普通css模块一般使用style-loader
和css-loader
,后者将解析(resolve) import/require()
的css模块,包括处理css中的url()
和@import
,前者将解析后的样式嵌入到style
标签中,内联到html的head标签
npm install style-loader css-loader --save-dev
建议将style-loader和css-loader结合使用
项目目录:
dist 存放打包后的文件
src/css 创建极简单的样式
base.css:
html{
background: #f00;
}
app.js 引入css文件
import './css/base.css'
webpack.config.js
var path = require('path')
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
修改webpack.config.js
var path = require('path')
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
publicPath: './dist/' // 为了使资源保持正确的路径,必须设置 webpack 配置中的 output.publicPath 属性,以便生成绝对路径
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader/url'
},
{
loader: 'file-loader'
}
]
}
]
}
}
执行webpack打包,浏览器中查看html文件,原来style标签的内容通过link方式引入进来
同时dist目录生成单独的css文件,但是这种方法比较小众,如果引入多个css模块,会产生多余的http请求
试想以下两个场景:
场景一:你在开发新功能的时候,同事A找你要小程序体验二维码,于是你只能:
git stash // 缓存本地修改
git checkout release // 切换到测试分支
yarn test // 打测试包
... // 预览、切回分支
git stash pop // 还原本地修改
然后整理被打断的代码思路,继续coding,暗自吐槽(二维码是有35min过期时间的,一会又得找我了...)
场景二:小程序到了提测日期,恰巧负责开发的你今天请假了,意味着没人可以为测试同学打包,因此耽误了项目进度。
以上两个场景我们发现一个共同的问题,小程序的体验发布太依赖开发者,因为通常只有开发者熟悉微信开发者工具一系列的上传流程,从而导致影响开发进度、发布流程不可控等一系列问题...
如果小程序可以通过Jenkins集成,将大大解放开发者的生产力,将Jenkins权限收回到测试同学手里,每次发布都需要测试同学执行构建,一定程度上解决发布过程不可控的问题。
微信开发者工具目前只支持Mac和Windows环境,可以在单独的Mac mini机器上搭一个Jenkins环境,专门用于打包ios app和小程序。
除了图形化工具,开发者工具还提供了命令行与 HTTP 服务两种接口供外部调用,开发者可以通过命令行或 HTTP 请求指示工具进行登录、预览、上传等操作。官方文档
我们先尝试使用命令行工具启动并登录微信开发者工具:
命令行工具所在位置:
macOS: <安装路径>/Contents/MacOS/cli
Windows: <安装路径>/cli.bat
以下示例皆运行在MacOS 10.13.5环境:
如果微信开发者安装在应用程序里面,其中<安装路径>则为 /Applications/wechatwebdevtools.app
启动微信开发者工具
终端执行/Applications/wechatwebdevtools.app/Contents/MacOS/cli -o
,结果如下:
并且开发者工具已经启动:
ps: mac环境下如果是新安装的开发者工具,一定要先打开并通过安全验证。
登录微信开发者工具
终端执行/Applications/wechatwebdevtools.app/Contents/MacOS/cli -l
,结果如下:
扫描二维码后,终端打印login success
,并且此时开发者工具已经登录:
我们尝试使用HTTP 服务预览项目:
在启动和登录开发者工具后,我们需要获取工具运行所在的端口号(端口是不固定的),然后调用http服务预览此项目,执行以下命令获取端口号:
port=$(cat "/Users/pengyong/Library/Application Support/微信web开发者工具/Default/.ide")
echo "微信开发者工具运行在${port}端口"
假设我的项目地址在:/User/demo,开发者工具开启在55228端口,在浏览器输入http://127.0.0.1:55228/preview?projectpath=/User/demo
,可以查看预览二维码
我们知道开发者可以通过命令行或 HTTP 请求指示工具进行启动、登录、预览等操作,接下来就进入正题:安装和部署Jenkins。
Jenkins运行依赖java环境,终端输入java -version,看输出是否正确比如:java version "1.8.0_151"
一般Mac安装Jenkins有两种方法:
这里我推荐第二种安装方式,因为第一种方式会生成一个共享的用户Jenkins,接下来所有构建的操作都是基于Jenkins这个用户的,它的权限与你当前登录的系统用户权限不同,导致构建过程中出现很多问题。
apache-tomcat-8.5.37.tar.gz
这个版本,重命名为Tomcat8
,放在/Users/用户/Library
这个目录下。sudo vi ~/.bash_profile
export PATH=$JAVA_HOME/bin:$PATH:/Users/pengyong/Library/Tomcat8/bin
source ~/.bash_profile
sudo chmod 755 /Users/pengyong/Library/Tomcat8/bin/*.sh
lsof -i:8080
kill PID
startup.sh
,如果出现下图所示内容,则启动成功shutdown.sh
至此,Tomcat的安装基本上完成了,接下来安装Jenkins
1. 官网下载Jenkins war包,放入你的Tomcat/webapps目录下
初次访问会让你输入密码,可以根据路径提示获取密码
如果文件提示没有权限无法打开,需要先修改权限,如下:
Save and Continue
Save and Finish
有可能页面一直展示loading,你可以尝试直接访问Jenkins主页,如果出现这个页面,Jenkins的安装过程到此结束,你可以创建任务了。
Git parameter: 能够实现选择指定分支进行构建的功能
description setter: 用于生成预览二维码
至此准备工作已完成,让我们开始构建小程序吧。
构建一个自由风格的软件项目
,点击ok进入到配置界面参数化构建过程
build_type
用于选择构建的是开发版、测试版还是生产版的小程序
branch
用于选择构建的分支(如果没有这个选项,检查Git parameter这个插件有没有安装)
upload_desc
和upload_version
两个文本参数分别用于在构建时填写小程序的备注和版本
脚本如下,可以按需修改:
echo -------------------------------------------------------
echo GIT_BRANCH: ${GIT_BRANCH}
echo -------------------------------------------------------
# 执行项目构建
yarn install
if [ "$build_type" == "dev" ]
then
yarn run test
else
yarn run $build_type
fi
# 打开微信开发者工具
/Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli -o
port=$(cat "/Users/pengyong/Library/Application Support/微信web开发者工具/Default/.ide")
echo "微信开发者工具运行在${port}端口"
return_code=$(curl -sL -w %{http_code} http://127.0.0.1:${port}/open)
if [ $return_code == 200 ]
then
echo "返回状态码200,devtool启动成功!"
else
echo "返回状态码${return_code},devtool启动失败"
exit 1
fi
if [ "$build_type" == "dev" ]
then
echo "发布开发版!"
# wget -o下载预览二维码,以build_id命名
/usr/local/bin/wget -O $BUILD_ID.png http://127.0.0.1:${port}/preview?projectpath=/Users/pengyong/.jenkins/workspace/wechat
echo "预览成功!请扫描二维码进入开发版!"
elif [ "$build_type" == 'prod' ] || [ "$build_type" == "test" ] || [ "$build_type" == "test:demo" ]
then
echo "准备上传!"
# 上传到微信平台
/Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli -u $upload_version@/Users/pengyong/.jenkins/workspace/wechat --upload-desc $upload_desc
echo "上传成功!请到微信小程序后台设置体验版或提交审核!"
fi
这里使用img标签用于展示构建后的二维码(注:端口号是你jenkins启动的端口,任务名称是我们任务构建这一步填写的名称,这里我们是wechat):
<img src="http://本机ip:端口/job/任务名称/ws/${BUILD_ID}.png" alt="非开发版请到后台预览" width="200" height="200" /> <a href="http://本机ip:端口/job/任务名称/ws/${BUILD_ID}.png" target="_blank">二维码${BUILD_ID}</a>
至此我们的任务构建配置基本完成了
Build with parameters
,选择你要构建的类型、分支等参数,这里我们选择的是开发版:解决的方法是在系统管理 -> 全局安全配置 -> 标记格式器 -> 选择Safe HTML
这时预览二维码就出来了,注意二维码是有过期时间的(35min)
至此预览二维码的构建任务已经完成,我们尝试上传代码到微信平台
目前小程序的发布大多还是依赖开发者手动上传,并且小程序持续集成还是有很多问题:开发者工具不支持Linux环境、无法通过命令行生成体验版二维码等等。但是持续集成这个方向还是值得大家研究的,开发和发布要两开花嘛...
在typescript
中,我们使用接口(interface)描述对象的类型
在面向对象语言中,接口(interface)是很重要的概念,它是对行为的抽象,而具体如何行动由类(class)去实现(implement)
在typescript
中,接口不仅可以对类的行为进行抽象,也可以对对象的形状进行描述
我们通过一个简单的例子,来看接口如何对对象的形状进行描述的:
interface Person {
name:string;
age:number
}
let student:Person = {
name: 'zhangsan',
age: 18
};
上面的例子中,我们定义了接口Person
,然后定义了变量student
,变量的类型是Person
,这样我们就约束了student
的形状必须跟Person
一致。即必须有name
何age
两个属性。
定义的对象,多一些属性是不被允许的:
interface Person {
name:string;
age:number
}
let student:Person = {
name: 'zhangsan',
age: 18,
address: 'hangzhou'
};
我们为student
对象新增了一个属性address
,编译报错:
Type '{ name: string; age: number; address: string; }' is not assignable to type 'Person'.
Object literal may only specify known properties, and 'address' does not exist in type 'Person'
意思是变量不能指定Person
类型,因为变量只能指定在Person
中的已知属性,而address
不存在于类型Person
这里我们注意到{ name: string; age: number; address: string; }
是typescript
对student
做的类型推导,关于类型推导,我们后面再聊。
定义的对象,少一些属性也是不被允许的:
interface Person {
name:string;
age:number
}
let student:Person = {
name: 'zhangsan'
};
Type '{ name: string; }' is not assignable to type 'Person'.
Property 'age' is missing in type '{ name: string; }'
可见,接口Person
要求对象一定具备name
和age
两个属性,即形状必须保持一致。
[lukou@deploy ~]$ lsb_release -a
LSB Version: :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description: CentOS Linux release 8.2.2004 (Core)
Release: 8.2.2004
Codename: Core
• step 1:安装一些必要的工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
• step 2:添加软件源信息
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
• Step 3: 更新 Docker-CE
sudo yum makecache fast
• 错误: yum makecache: error: argument timer: invalid choice: ‘fast’ (choose from ‘timer’)
• 问题: ‘索引的时候出错,即centos8没有该参数,解决办法为:去掉fast参数’
• 命令改成: sudo yum makecache
• 再次执行: sudo yum makecache
• Step 5: 开启Docker服务
• 启动1: sudo service docker start
• 启动2: sudo systemctl start docker
• Step 4: 安装 Docker-CE
sudo yum -y install docker-ce
• 报错: Problem: package docker-ce-3:19.03.12-3.el7.x86_64 requires containerd.io >= 1.2.2-3, but none of the providers can be installed;
• 根据提示,需要containerd.io的版本 >= 1.2.2-3,操作如下:
• 安装wget指令 yum install wget;
• 获取rpm包 wget https://download.docker.com/linux/centos/7/x86_64/edge/Packages/containerd.io-1.2.6-3.3.el7.x86_64.rpm;
• 升级containerd.io(安装rpm包) yum -y install containerd.io-1.2.6-3.3.el7.x86_64.rpm;
• 再次执行 sudo yum -y install docker-ce;
• Step 6: 安装效验
• docker version
• sudo docker ps
• 设置开机自启动
sudo systemctl enable docker
• 重启生效
systemctl restart docker.service
Mongoose是Nodejs环境下对Mongodb进行便捷操作的工具,使用之前需要先安装Mongodb和Nodejs,推荐一款mongodb的可视化工具Robomongo
npm i mongoose
安装成功之后可以通过require('mongoose')使用,并且可以在package.json的dependencies看到当前版本号
创建mongoose.js,执行以下代码node mongoose.js
const mongoose = require('mongoose')
const db = 'mongodb://localhost:27017/test'
mongoose.set('debug', true)
// 连接数据库
mongoose.connect(db)
// 连接中断,重新连接
mongoose.connection.on('disconnect', () => {
mongoose.connect(db)
})
// 连接出错
mongoose.connection.on('error', (err) => {
console.error(err)
})
// 在连接成功并且本次连接的所有models里面的onOpen执行完成之后执行
mongoose.connection.on('open', () => {
console.log('Connect on mongodb open', db)
})
// 成功连接到数据库时触发,重新连接的时候可能会被多次触发
mongoose.connection.on('connected', () => {
console.log('Connect on mongodb connected', db)
})
连接成功后先执行connected的回调再执行open的回调,所有的events参考http://www.nodeclass.com/api/mongoose.html#connection_Connection
Schema是Mongoose中用到的一种数据模式,可以理解成表结构的定义
通过mongoose.Schema来调用Schema,然后使用new方法创建schema对象
例如定义一个user的Schema
module.exports = mongoose
const mongoose = require('./mongoose');
const schema = mongoose.Schema;
const UserSchema = new schema({
account: String, // 登录账户
password: String, // 登录密码
age: Number, // 年龄
lastlogin: { type: Date, default: Date.now } // 上次登录时间
})
定义一个Schema需要指定字段名和类型,允许的SchemaTypes如下
定义好Schema之后,接下来就是生成Model,Model是由Schema生成的模型,用于对数据库的操作,model()的第一个参数是模型名称
根据UserSchema生成一个model并导出,修改后的代码如下:
const mongoose = require('./mongoose');
const schema = mongoose.Schema;
const UserSchema = new schema({
account: String, // 登录账户
password: String, // 登录密码
age: Number, // 年龄
lastlogin: { type: Date, default: Date.now } // 上次登录时间
})
model.exports = mongoose.model('User', UserSchema)
创建一个test.js,进行一些简单的数据库操作
const User = require('./user.js');
function insertUser() {
// 构建documents
let user = new User({
account: 'a123456',
password: 'aaaaaa',
age: 18,
lastlogin: new Date()
});
user.save((err, res) => {
if (err) {
console.log(err)
} else {
console.log('res:'+res)
}
});
}
insertUser();
const User = require('./user.js');
const Util = require('util');
function update() {
let whereStr = {"account": "a123456"};
let updateStr = {"password": "111111"};
User.update(whereStr, updateStr, (err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
});
}
update();
根据账号更新密码,结果如图:
更新数据常用方法还有findByIdAndUpdate(id, [update], [options], [callback])
const User = require('./user.js');
const Util = require('util');
function findByIdAndUpdate() {
let id = '5a91306ef88eea0b14532731';
let updateStr = {"password": "abcdef"};
User.findByIdAndUpdate(id, updateStr, (err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
});
}
findByIdAndUpdate();
3、删除Model.remove(conditions,[callback])
const User = require('./user.js');
const Util = require('util');
function del() {
let whereStr = {"account": "a123456"};
User.remove(whereStr, (err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
})
}
del();
结果会在对应数据表中删除匹配到的数据,res结果res{ n: 1, ok: 1 },其中n为影响到的行数,ok为是否成功
其他常用方法: Model.findByIdAndRemove(id, [options], [callback])
4、条件查询Model.find(conditions, [fields], [options], [callback])
const User = require('./user.js');
const Util = require('util');
function getByConditions() {
let whereStr = {"account": "a123456"};
User.find(whereStr, (err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
});
}
getByConditions();
找到的结果以数组形式返回
第二个参数可以设置查询输出的字段,比如:
const User = require('./user.js');
const Util = require('util');
function getByConditions() {
let whereStr = {"account": "a123456"};
let field = {"account": 1, "password": 1, "_id": 0}
User.find(whereStr, field, (err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
});
}
getByConditions();
输出字段只有account和password,1表示输出,0表示不输出
如果要查询年龄段,条件范围应该怎么写?
User.find({"age": {$gte: 18, $lte: 40}, [field], [options], [callback])
其实类似的还有:
$or 或关系
$nor 或关系取反
$gt 大于
$gte 大于等于
$lt 小于
$lte 小于等于
$ne 不等于
$in 在多个值范围内
$nin 不在多个值范围内
$all 匹配数组中多个值
$regex 正则,用于模糊查询
$size 匹配数组大小
$maxDistance 范围查询,距离(基于LBS)
$mod 取模运算
$near 邻域查询,查询附近的位置(基于LBS)
$exists 字段是否存在
$elemMatch 匹配内数组内的元素
$within 范围查询(基于LBS)
$box 范围查询,矩形范围(基于LBS)
$center 范围醒询,圆形范围(基于LBS)
$centerSphere 范围查询,球形范围(基于LBS)
$slice 查询字段集合中的元素(比如从第几个之后,第N到第M个元素)
查询条件也可以使用正则表达式,即模糊查询
User.find({"account": /7$/}, [field], [options], [callback])
查看查询条件更多规则
5、查询数量Model.count(conditions, [callback])
const User = require('./user.js');
const Util = require('util');
function getCountByConditions() {
let whereStr = {};
User.count(whereStr, (err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
});
}
getCountByConditions();
res会输出数量
6、根据_id查询Model.findById(id, [fields], [options], [callback])
const User = require('./user.js');
const Util = require('util');
function getById() {
let id = '5a916eee7bbb870ba20cf581';
User.findById(id, (err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
});
}
getById();
7、分页查询
const User = require('./user.js');
const Util = require('util');
function getByPaper(page) {
let size = 2; // 每页显示数量
let currentPage = page; // 当前页数
let sort = {"lastlogin": -1}; // 排序方法,按照登录时间倒叙排列
let condition = {};
var skipNum = (currentPage - 1) * size; // 跳过数
User.find(condition).skip(skipNum).limit(size).sort(sort).exec((err, res) => {
if (err) {
console.log('err'+err)
} else {
console.log('res'+Util.inspect(res))
}
});
}
getByPaper(1);
getByPaper(2);
getByPaper(3);
如果构建的过程中需要选参数:
勾选This project is parameterized
通常我们只会用到Choice Parameter和Git参数(需要安装Git Parameter Plug-In)两种选项:
• Choice Parameter:提供select选择功能
• Git参数:可以自行选择要构建的分支
• 名称:当前选中的值会赋值给变量SERVER_IP,在shell中通过$SERVER_IP获取
• 选项:select选择框提供的选项,默认选中第一个值
实际效果如下:
• 名称:当前选中的分支会赋值给变量SERVER_IP,在shell中通过$SERVER_IP获取
• 参数类型:分支
• 默认值 + 已选值:指定默认选中哪个分支
实际效果如下:
通常我们配置git仓库地址会报错:
需要我们在服务器上生成的id_rsa.pub添加到gitlib的ssh keys下面,具体做法如下:
新的解决方案:
将宿主机的ssh文件拷贝到docker容器中,共享一份ssh key
// 将宿主机的/home/lukou/.ssh拷贝到jenkins容器的/root目录下
sudo docker cp /home/lukou/.ssh jenkins:/root
旧的解决方案:
以在docker镜像为例,如果直接在服务器装的jenkins可以免去这一步:
docker exec -it jenkins /bin/bash
cd ~/.ssh
cat id_rsa.pub
#如果没有该文件需要我们手动创建,一路回车即可
ssh-keygen -t rsa -C "[email protected]"
git ls-remote -h -- [email protected]:frontend/bumblebee.git
指定当前构建的分支为$BRANCH,即参数化构建 - 用户手动选中的分支
如果我们生产环境只能构建master分支
# 判断分支
if [ "$BRANCH" != "origin/master" ];then
echo "当前分支是$BRANCH,生产环境必须是master分支,退出构建"
exit 1
fi
# 构建
nvm use v12.9.1
npm -v
node -v
#!/bin/bash -ilex
# set enviroment
PROD_NAME="dist"
PROD_ZIP="$PROD_NAME.zip"
USER="lukou"
APP_IP=$SERVER_IP
APP_HOME="/home/lukou/tmp/$DIRECTORY"
# 构建
nvm use v12.9.1
npm -v
node -v
npm config set registry https://registry.npm.taobao.org
npm install
npm run build
zip -rp $PROD_ZIP ./$PROD_NAME
echo $PWD
scp $PROD_ZIP $USER@$APP_IP:$APP_HOME/
scp template/* $USER@$APP_IP:$APP_HOME/
# 开始部署
ssh -T $USER@$APP_IP << EOF
#sudo chmod 777 -R $APP_HOME
cd $APP_HOME
unzip -oq $PROD_ZIP
rm -rf ./static
rm -rf ./index.html
mv ./dist/* ./
rm -rf ./$PROD_NAME
rm -rf ./$PROD_ZIP
EOF
+ scp dist.zip [email protected]:/home/lukou/tmp/coupon_qiuyu/
Host key verification failed.
lost connection
Build step 'Execute shell' marked build as failure
Finished: FAILURE
原因:
猜测是因为当前docker容器没有连接过目标服务器所致,查看目标服务器~/.ssh/know_hosts,确实没有当前docker容器的id_rsa.pub,一般连接过的都会在这里产生记录
解决办法:
如果jenkins在docker容器中,此操作在容器中进行
• 复制~/.ssh/id_rsa.pub
• 登录目标服务器
ssh [email protected]
cd ~/.ssh
vi authorized_keys
添加复制的id_rsa.pub
回到docker容器或者原服务器中,使用ssh连接目标服务器即可
root@cb0c931a18d2:~/.ssh# ssh [email protected]
The authenticity of host '121.41.121.230 (121.41.121.230)' can't be established.
RSA key fingerprint is SHA256:KI315BM3JQvdx7RpazSER7WUD4SWPe6KAs5G/lPOynA.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '121.41.121.230' (RSA) to the list of known hosts.
Last login: Sat Jul 11 09:12:17 2020 from 120.26.131.154
Welcome to aliyun Elastic Compute Service!
类型断言指手动指定一个值的类型,一般用来告诉typescript
编译器:我已经明确知道这个值的类型是什么
<类型>值
或 值 as 类型
在高级类型一节中,我们提到当一个联合类型的变量不能确定是哪个类型的时候,我们只能访问所有类型公有的属性和方法
function getLength(something: string | number): number {
return something.length;
}
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'
但有时候,我们确实需要在不明确变量类型的时候,就访问它的属性或方法,比如:
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'
上例中,我们访问something.length
编译会报错,此时可以使用类型断言,将something
的类型断言成string
:
function getLength(something: string | number): number {
if ((<string>something).length) {
return (something as string).length;
} else {
return something.toString().length;
}
}
上面对两种写法都做了演示,实际生产中最好统一语法。
类型断言,不能断言成联合类型中不存在的类型,比如这里将something
断言为boolean
类型,不在string|number
中,编译将会报错
function toBoolean(something: string | number): boolean {
return <boolean>something;
}
Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type 'number' is not comparable to type 'boolean'
其中neither type sufficiently overlaps with the other
也提示我们:两种类型都没有与另一种类型充分重叠。
参考:
2.由于单页面共享一个浏览器滚动条,并且页面通过keep-alive缓存,导致详情页没有正确回到顶部
3.为了避免上述情况,在detail页面强制滚动到顶部
window.scrollTo(0, 0)
4.从详情页返回到首页,由于每次返回到首页,首页都会重新请求数据更新页面,导致页面出现白屏,如果轻拂一下页面,就可以正常展示了
参考vue的issue,在首页请求数据之后,动态的设置scrollTop
this.$nextTick(() => {
window.scrollTo(0, 1)
window.scrollTo(0, 0)
})
list页面点击列表的某一项(比如活动列表),进入到detail页面进行编辑,list页面和detail页面关系是这样:
{
path: '/list',
component: List,
children: [
path: 'detail',
component: Detail
]
}
detail页面返回到list页面时,希望刷新整个列表,但不刷新list页面(list页面还有其他的数据)
定义event bus,分别在list页面和detail页面引入
在list的created钩子函数里面监听刷新事件
Bus.$on('refreshList', () => {
// ... 这里请求接口刷新列表
})
Bus.$emit('refreshList')
一切看起来没问题,但是产品要求,点击菜单栏要求刷新当前页面(非刷新浏览器),目前的做法是通过vuex动态修改顶级router-view的显示隐藏
这就导致每点击一次侧边栏,页面重新渲染一次,created函数重新执行一次,refreshList事件就重新绑定一次(event bus是不会随着页面销毁而销毁的,需要手动销毁,查看vue issue),导致从detail页面返回list页面的时候,refreshList的回调函数请求了n+1次(比如点击了n次侧边栏)
通过watch路由变化,希望销毁对refreshList的监听,this.callback
是对刷新函数的一次封装
watch: {
'$route': function () {
Bus.$off('refreshList',this.callback)
}
}
官网也有对off事件的介绍
但是无论重复绑定多少次refreshList事件,来回切换list和detail页面,永远只销毁一次
查看vue源码的$off方法,它会判断传入的event事件是数组函数字符串,然后通过Object.create(null)新建一个对象,key是index,value是事件处理函数this.callback,形如:
{
0: this.callback,
1: this.callback
}
最后遍历该对象,移除对应的callback
最终发现它确实只销毁一次,这样对应了官网api第三条:如果同时提供了事件与回调,则只移除这个回调的监听器。
通过在list页面定义refresh方法,在detail页面的beforeDestroy钩子函数调用this.$parent.refresh()刷新页面,也看出event bus不适用于父子组件,官网也提到适用于兄弟组件的数据通讯
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.