Giter Site home page Giter Site logo

blog's People

Contributors

wangshuxian6 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

git

git 公用配置

.gitattributes

# Auto detect text files and perform LF normalization
# git config --global core.autocrlf false
text=LF
text eol=lf

.gitignore
WePY

node_modules
dist
.DS_Store

es7 async / await 简单示例

es7 async / await

function asyncF(name) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve('my name is ' + name)
		}, 1000)
	})
}

function sum(a, b) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve(a + b)
		}, 1000)
	})
}

async function fn() {
	const line = await asyncF('tom')
	const n = await sum(2, 3)
	console.log(line + ':' + n)
}
//2秒后终端显示
//my name is tom:5

Node

node

Install Node.js on Ubuntu 16.04

sudo apt-get update
cd ~
curl -sL https://deb.nodesource.com/setup_12.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt-get install nodejs
sudo apt-get install npm
nodejs -v

centos

curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum install nodejs

node --version
npm --version

npm 短语 test start stop 不需要加run
npm test
npm start
npm stop

npm run xxxx

npm 前置钩子,后置钩子
'pretest':'echo 111',
'posttest':'echo 222',
'test':'echo 333'

传递参数 --xxxx

配置变量
'config':{
'xxx':'xxxx'
}

使用
linux,mac
$npm_package_config_xxx

windows
%npm_package_config_xxx

文件操作推荐使用包,防止跨系统区别


npm全称Node Package Manager


Node.js概述:

Node.js官网:www.nodejs.org
1.Node.js是C++编写的基于V8引擎的JS运行时环境。
2.Node.js是一门基于ECMAScript开发的服务器端语言,提供了(全端JS没有的)很多扩展对象。

前端js:

1.ES原生对象:String、Number、Boolean、Math、Date、Error、Function、Object、Array、RegExp...
2.BOM&DOM
3.自定义对象

Node.js:

1.ES原生对象
2.Node.js内置对象
3.大量的第三方对象
4.自定义对象

3.Node.js可以编写独立的服务器应用,无需借助于其他web服务器。
Node.js的意义:

1.执行效率比PHP/JSP/JAVA要快
2.用一种语言统一了前后端开发。

特点:
1.单线程逻辑处理
2.非阻塞
3.异步I/O处理
4.事件驱动编程

2.Node.js的两种运行方式:

1.交互模式——用于测试
读取用户输入、执行运算、输出执行结果、继续下一循环
执行方法:输入一行js语句,回车执行

2.脚本模式——用于开发
把要执行的所有js语句编写在一个独立的js文件中,一次性的提交给nodejs处理。此文件可以没有后缀
执行方法:node d:\xx\xx.js

3.Node.js的基础语法及ES6新特性

1.数据类型:
(1)原始类型
string、number、boolean...
原始类型数据直接赋值即可

(2)引用类型
ES原生对象、Node.js对象、自定义对象
引用类型通常需要使用new关键字创建

2.模板字符串
ES6中提供的一种新的字符串形式
(1)使用模板方式定义字符串,数据可以实现换行
(2)可以使用${}拼接变量,并且可以执行运算

3.严格模式
ES5中新增一种比普通模式更为严格的js运行模式。

使用方法:
(1)在整个脚本文件中启用严格模式
在脚本文件的开头:"use strict";
用于新项目
(2)在单个函数内启用严格模式

		function info(){
			"use strict";
			.........
		}

用于老项目维护

规则:
-(1)修改常量的值是非法的——将静默失败升级为错误
-(2)不允许对未声明的变量赋值
-(3)匿名函数的this不再指向全局

4.变量的作用域
全局作用域、局部作用域、块级作用域(ES6中专有)
块级作用域:只在当前代码块中使用
代码块:任何一个{}都是一个代码块,if/for/while...
代码块中使用“let”声明块级作用域变量,出了代码块,便不可使用。使用let需要启用严格模式。

5.循环结构
for...of...(ES6)
遍历数组的元素值

6.函数
匿名函数的自调
=> 箭头函数

		() => {
	
		}

箭头函数只用于匿名函数
箭头函数中不存在arguments对象

7.对象
创建对象的方式:
-(1)对象直接量
-(2)构造函数方式
-(3)原型继承方式
-(4)class方式——ES新增

class 类:是一组相似对象的属性和行为的抽象集合。(描述一类事物统一的属性和功能的程序结构)
事物的属性就是class的属性,事物的功能就是class的方法。
使用class方式创建自定义对象是,必须启用严格模式。

		"use strict";
		//创建自定义对象
		class Emp {
  			constructor(ename){   
				this.ename=ename; 
  			}
  			work(){    
 			 }
		}
		var e1=new Emp("tom");

		// 实现继承:
		class Programmer extends Emp{
  			constructor(ename,salary,skills){
    				super(ename,salary);
    				this.skills=skills;
  			}  
		}
4.全局对象

Node.js中的全局对象是Global.
在交互模式下,声明的全局变量是global的成员。——全局对象的污染
在脚本模式下,声明的全局变量不是global的成员。——避免了全局对象的污染
Global对象的成员属性和成员方法

1.console 用于向stdout(标准输出)和stderr(标准错误)输出信息。

console.log() //向stdout输出日志信息
console.info() //同上
console.error() //向stderr输出错误信息
console.warn() //同上
console.trace() //向stderr输出栈轨迹信息
console.dir() //向stdout输出指定对象的字符串表示
console.assert() //断言,为真的断言,错误信息不会输出;为假的断言,抛出错误对象,输出错误信息,并且终止脚本的运行,可以自定义错误信息。
console.time() console.timeEnd()//测试代码的执时间

注意:console中的成员方法是异步的,输出顺序和书写顺序不一定完全一致。

2.process 进程
process.arch //获取CPU架构类型
process.platform //获取操作系统类型
process.env //获取操作系统环境变量
process.cwd() //获取当前文件所在工作目录
process.execPath //获取Node.js解释器所在目录
process.versions //获取Node.js版本信息
process.uptime() //获取Node.js进程运行时间(s)
process.memoryUsage()//获取Node.js进程内存使用信息
process.pid //获取进程ID号
process.kill( pid ) //向指定进程ID号发送退出信号

3.定时器
global.setTimeout() //一次性定时器
global.setInterval() //周期性定时器
global.setImmediate()//在下次事件循环开始之前立即执行的定时器
process.nextTick() //本次事件循环结束之后立即执行的定时器

5.模块系统:

Node.js中使用“Module(模块)”来规划不同的功能对象。
模块的分类:
-(1)核心模块——Node.js的内置模块(有些不需引入可直接使用,有些需要引入)
-(2)第三方模块
-(3)自定义模块
每一个被加载的js文件对应一个模块对象,包含响应的功能和函数。
模块中声明的变量或函数的作用域叫做“模块作用域”,这些变量和函数都不是global的成员,默认只能在当前的js文件(当前模块)中使用。

Node.js启动时运行的第一个模块叫做“主模块”——main module,也是自身模块。
获取主模块对象:
process.mainModule
require.main

除主模块之外的其他模块都称为子模块。
默认情况下,某一个模块不能直接使用其他模块中封装的数据,但是每个模块可以导出(exports)自己内部的对象供其他模块使用,也可用引入(require)并使用其他模块导出的对象。

每一个模块内部都可以使用一个变量:module,指向当前模块自己。

	//判断当前模块是否主模块
	console.log(require.main===module);

模块的引入:require()
(在交互模式下,Node.js自带的模块无需引入,直接使用)


//Promise和generator

1-Promise和generator-不完善的包装器

function asyncFn(name) {
	return new Promise((resolve) => {
		setTimeout(() => {
			resolve('my name is ' + name)
		}, 1000)
	})
}

function fn1() {
	console.log(asyncFn('leo'))
}
fn1()
//Promise 对象
//
function* fn2() {
	console.log(yield asyncFn('leo')) //调用next()时,运行到yield暂停,返回yield右侧的值。而函数体内默认返回undefined
}

let generatorFn = fn2()

function exec(generatorFn, value) {
	let result = generatorFn.next(value)
	if (!result.done) {
		//如果没有执行完毕
		if (result.value instanceof Promise) {
			//如果返回的是Promise对象
			result.value.then((result) => {
				exec(generatorFn, result)
			})
		} else {
			//如果返回的不是Promise对象//执行到最后时,此时yield返回一个值
			exec(generatorFn, result.value)
		}
	}
}
exec(generatorFn) //my name is leo

2-Promise和generator

//Promise和generator
function asyncFn(name) {
	return new Promise((resolve) => {
		setTimeout(() => {
			resolve('my name is ' + name)
		}, 1000)
	})
}

function sum(a, b) {
	return new Promise((resolve) => {
		setTimeout(() => {
			resolve(a + b)
		}, 1000)
	})
}

function fn(name) {
	sum(1, 5).then((num) => {
		if (num > 6) {
			asyncFn(name).then((value) => {
				console.log(value)
			})
		} else {
			console.log('error')
		}
	})
}
fn('harry') //error

3-最终效果
generator函数可以在一次 next() 时返回一个Promise对象并停止,
在返回的Promise对象里处理接受的参数并执行下一个 next()

//安装Generator函数的执行器(包装器) co
//npm install --save-dev co
//
let co = require('co')

function asyncF(name) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve('my name is ' + name)
		}, 1000)
	})
}

function sum(a, b) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve(a + b)
		}, 1000)
	})
}

function* fn(name) {
	if ((yield sum(2, 5)) > 6) {
		console.log(yield asyncF(name))
	} else {
		console.log('error')
	}
}

let fnx = co.wrap(fn)
fnx('leo')
//node yield.js
//1秒后终端显示
//my name is leo

Dart

Dart

https://dart.cn/guides/language/language-tour


Dart 开发语言概览

一个简单的 Dart 程序

// Define a function.
void printInteger(int aNumber) {
  print('The number is $aNumber.'); // Print to console.
}

// This is where the app starts executing.
void main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.
}

// 注释。

以双斜杠开头的一行语句称为单行注释。Dart 同样支持多行注释和文档注释。查阅注释获取更多相关信息。

void

一种特殊的类型,表示一个值永远不会被使用。类似于 main() 和 printInteger() 的函数,以 void 声明的函数返回类型,并不会返回值。

int

另一种数据类型,表示一个整型数字。 Dart 中一些其他的内置类型包括 String、List 和 bool。

42

表示一个数字字面量。数字字面量是一种编译时常量。

print()

一种便利的将信息输出显示的方式。

'...' (或 "...")

表示字符串字面量。

$variableName (或 ${expression})

表示字符串插值:字符串字面量中包含的变量或表达式。查阅字符串获取更多相关信息。

main()
一个特殊且 必须的 顶级函数,Dart 应用程序总是会从该函数开始执行。查阅 main() 函数 获取更多相关信息。

var
用于定义变量,通过这种方式定义变量不需要指定变量类型。这类变量的类型 (int) 由它的初始值决定 (42)。


重要概念

所有变量引用的都是 对象,每个对象都是一个 类 的实例。数字、函数以及 null 都是对象。除去 null 以外(如果你开启了 空安全), 所有的类都继承于 Object类。

尽管 Dart 是强类型语言,但是在声明变量时指定类型是可选的,因为 Dart 可以进行类型推断。在上述代码中,变量 number 的类型被推断为 int 类型。

如果你开启了 空安全,变量在未声明为可空类型时不能为 null。你可以通过在类型后加上问号 ? 将类型声明为可空。例如,int? 类型的变量可以是整形数字或 null。如果你 明确知道 一个表达式不会为空,但 Dart 不这么认为时,你可以在表达式后添加 !来断言表达式不为空(为空时将抛出异常)。例如:int x = nullableButNotNullInt!

如果你想要显式地声明允许任意类型,使用 Object?(如果你 开启了空安全)、 Object 或者 特殊类型 dynamic 将检查延迟到运行时进行。

Dart 支持泛型,比如 List<int>(表示一组由 int 对象组成的列表)或 List<Object>(表示一组由任何类型对象组成的列表)。

Dart 支持顶级函数(例如 main 方法),同时还支持定义属于类或对象的函数(即 静态 和 实例方法)。你还可以在函数中定义函数(嵌套 或 局部函数)。

Dart 支持顶级 变量,以及定义属于类或对象的变量(静态和实例变量)。实例变量有时称之为域或属性。

Dart 没有类似于 Java 那样的 public、protected 和 private 成员访问限定符。如果一个标识符以下划线 _ 开头则表示该标识符在库内是私有的。可以查阅 库和可见性 获取更多相关信息。

标识符 可以以字母或者下划线 _ 开头,其后可跟字符和数字的组合。

Dart 中 表达式 和 语句 是有区别的,表达式有值而语句没有。比如条件表达式 expression condition ? expr1 : expr2 中含有值 expr1 或 expr2。与 if-else 分支语句相比,if-else 分支语句则没有值。一个语句通常包含一个或多个表达式,但是一个表达式不能只包含一个语句。

Dart 工具可以显示 警告 和 错误 两种类型的问题。警告表明代码可能有问题但不会阻止其运行。错误分为编译时错误和运行时错误;编译时错误代码无法运行;运行时错误会在代码运行时导致 异常。


关键字

Dart 语言所使用的关键字

abstract2 dynamic2 implements2 show1
as2 else import2 static2
assert enum in super
async1 export2 in2 super
await3 extends is sync1
break external2 library2 this
case factory2 mixin2 throw
catch false new true
class final null try
const finally on1 typedef2
continue for operator2 var
covariant2 Function2 part2 void
default get2 rethrow while
deferred2 hide1 return with
do if set2 yield3

应该避免使用这些单词作为标识符。但是,带有上标的单词可以在必要的情况下作为标识符:

带有上标 1 的关键字为 上下文关键字,只有在特定的场景才有意义,它们可以在任何地方作为有效的标识符。

带有上标 2 的关键字为 内置标识符,其作用只是在JavaScript代码转为Dart代码时更简单,这些关键字在大多数时候都可以作为有效的标识符,但是它们不能用作类名或者类型名或者作为导入前缀使用。

带有上标 3 的关键字为 Dart 1.0 发布后用于 支持异步 相关内容。不能在由关键字 async、async* 或 sync* 标识的方法体中使用 await 或 yield 作为标识符。

其它没有上标的关键字为 保留字,均不能用作标识符。


变量 Variables

创建一个变量并将其初始化:

var name = 'Bob';

变量仅存储对象的引用

这里名为 name 的变量存储了一个 String 类型对象的引用,“Bob” 则是该对象的值。

name 变量的类型被推断为 String,但是你可以为其指定类型。
如果一个对象的引用不局限于单一的类型,可以将其指定为 Object(或 dynamic)类型。

Object name = 'Bob';

除此之外你也可以指定类型:

String name = 'Bob';

默认值

在 Dart 中,未初始化的变量拥有一个默认的初始化值:null。(如果你未迁移至 空安全,所有变量都为可空类型。)
即便数字也是如此,因为在 Dart 中一切皆为对象,数字也不例外。

int? lineCount;
assert(lineCount == null);

assert() 的调用将会在生产环境的代码中被忽略掉。
在开发过程中,assert(condition) 将会在 条件判断 为 false 时抛出一个异常。详情请查阅 Assert。


final 和 const

如果你不想更改一个变量,可以使用关键字 final 或者 const 修饰变量,
这两个关键字可以替代 var 关键字或者加在一个具体的类型前。
一个 final 变量只可以被赋值一次;
一个 const 变量是一个编译时常量(const 变量同时也是 final 的)。
顶层的 final 变量或者类的 final 变量在其第一次使用的时候被初始化。

实例变量 可以是 final 的但不可以是 const

创建并设置两个 final 变量:

final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';

你不能修改一个 final 变量的值

使用关键字 const 修饰变量表示该变量为 编译时常量。

如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(顺序不能颠倒)。
在声明 const 变量时可以直接为其赋值,也可以使用其它的 const 变量为其赋值:

const bar = 1000000; // 直接赋值 [Unit of pressure (dynes/cm2)]
const double atm = 1.01325 * bar; // 利用其它 const 变量赋值 (Standard atmosphere)

const 关键字不仅仅可以用来定义常量,还可以用来创建 常量值,该常量值可以赋予给任何变量。
可以将构造函数声明为 const 的,这种类型的构造函数创建的对象是不可改变的。

var foo = const [];
final bar = const [];
const baz = []; // 相当于 `const []` (Equivalent to `const []`)

如果使用初始化表达式为常量赋值可以省略掉关键字 const,比如上面的常量 baz 的赋值就省略掉了 const。
详情请查阅 不要冗余地使用 const。

没有使用 final 或 const 修饰的变量的值是可以被更改的,即使这些变量之前引用过 const 的值。
``dart
foo = [1, 2, 3]; // foo 的值之前为 const [] (Was const [])

>常量的值不可以被修改

>可以在常量中使用 类型检查和强制类型转换 (is 和 as)、 集合中的 if 以及 展开操作符 `...` 和 `...?`:
```dart
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: 'int'}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread.

可以查阅 Lists、Maps 和 Classes 获取更多关于使用 const 创建常量值的信息。


内置类型

Dart 语言支持下列内容:

Numbers (int, double)
Strings (String)
Booleans (bool)
Lists (也被称为 arrays)

Sets (Set)
Maps (Map)
Runes (常用于在 Characters API 中进行字符替换)

Symbols (Symbol)
The value null (Null)

使用字面量来创建对象也受到支持。例如 'This is a string' 是一个字符串字面量,true 是一个布尔字面量。

由于 Dart 中每个变量引用都指向一个对象(一个 类 的实例),通常也可以使用 构造器 来初始化变量。
一些内置的类型有它们自己的构造器。例如你可以使用 Map() 来创建一个 map 对象。

num

https://api.dart.dev/stable/dart-core/num-class.html
https://dart.cn/guides/language/numbers
Dart 支持两种 Number 类型:

int 和 double 都是 num 的子类。
num 中定义了一些基本的运算符
比如 +-*/ 等,
还定义了 abs()ceil()floor() 等方法(位运算符,比如 >> 定义在 int 中)。
如果 num 及其子类不满足你的要求,可以查看 dart:math 库中的 API。

num x = 1; // x can have both int and double values
x += 2.5;

int

整数值;长度不超过 64 位,具体取值范围 依赖于不同的平台。
在 DartVM 上其取值位于 -263 至 263 - 1 之间。
在 Web 上,整型数值代表着 JavaScript 的数字(64 位无小数浮点型),其允许的取值范围在 -253 至 253 - 1 之间。

整数是不带小数点的数字,下面是一些定义整数字面量的例子:

var x = 1;
var hex = 0xDEADBEEF;
var exponent = 8e5;

在 Dart 2.1 之前,在浮点数上下文中使用整数字面量是错误的。

整型字面量将会在必要的时候自动转换成浮点数字面量:
double z = 1; // Equivalent to double z = 1.0.
整型支持传统的位移操作

比如移位<<、>>、按位与&、按位或|,例如:

assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111

double

64 位的双精度浮点数字,且符合 IEEE 754 标准。

如果一个数字包含了小数点,那么它就是浮点型的。下面是一些定义浮点数字面量的例子:

var y = 1.1;
var exponents = 1.42e5;

字符串和数字之间转换的方式:

// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

数字字面量为编译时常量

很多算术表达式只要其操作数是常量,则表达式结果也是编译时常量。

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;

字符串 String

Dart 字符串(String 对象)包含了 UTF-16 编码的字符序列。
可以使用单引号或者双引号来创建字符串:

var s1 = '使用单引号创建字符串字面量。';
var s2 = "双引号也可以用于创建字符串字面量。";
var s3 = '使用单引号创建字符串时可以使用斜杠来转义那些与单引号冲突的字符串:\'。';
var s4 = "而在双引号中则不需要使用转义与单引号冲突的字符串:'";

${表达式}

在字符串中,请以 ${表达式} 的形式使用表达式,
如果表达式是一个标识符,可以省略掉 {}
如果表达式的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串。

var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, ' +
        'which is very handy.');
assert('That deserves all caps. ' +
        '${s.toUpperCase()} is very handy!' ==
    'That deserves all caps. ' +
        'STRING INTERPOLATION is very handy!');

== 运算符负责判断两个对象的内容是否一样,如果两个字符串包含一样的字符编码序列,则表示相等。

可以使用 + 运算符或并列放置多个字符串来连接字符串:

多行字符串

使用三个单引号或者三个双引号也能创建多行字符串:

var s1 = '''
你可以像这样创建多行字符串。
''';

var s2 = """这也是一个多行字符串。""";

“raw” 字符串

https://dart.cn/guides/language/language-tour#characters
在字符串前加上 r 作为前缀创建 “raw” 字符串(即不会被做任何处理(比如转义)的字符串):

var s = r'In a raw string, not even \n gets special treatment.';

// 代码中文解释
var s = r'在 raw 字符串中,转义字符串 \n 会直接输出 “\n” 而不是转义为换行。';

字符串字面量的插值表达式

https://dart.cn/guides/libraries/library-tour#strings-and-regular-expressions
字符串字面量是一个编译时常量,只要是编译时常量都可以作为字符串字面量的插值表达式:

// 可以将下面三个常量作为字符串插值拼接到字符串字面量中。(These work in a const string.)
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// 而下面三个常量不能作为字符串插值拼接到字符串字面量。
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';

bool

Dart 使用 bool 关键字表示布尔类型
布尔类型只有两个对象 true 和 false,两者都是编译时常量。

应该总是显示地检查布尔值

Dart 的类型安全不允许你使用类似 if (nonbooleanValue) 或者 assert (nonbooleanValue) 这样的代码检查布尔值。
相反,你应该总是显示地检查布尔值,比如像下面的代码这样:

// 检查是否为空字符串 (Check for an empty string).
var fullName = '';
assert(fullName.isEmpty);

// 检查是否小于等于零。
var hitPoints = 0;
assert(hitPoints <= 0);

// 检查是否为 null。
var unicorn;
assert(unicorn == null);

// 检查是否为 NaN。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

List

数组 (Array) 是几乎所有编程语言中最常见的集合类型,在 Dart 中数组由 List 对象表示。通常称之为 List。

Dart 中 List 字面量看起来与 JavaScript 中数组字面量一样。
下面是一个 Dart List 的示例:

var list = [1, 2, 3];

这里 Dart 推断出 list 的类型为 List<int>,如果往该数组中添加一个非 int 类型的对象则会报错。你可以阅读 类型推断 获取更多相关信息。

可以在 Dart 的集合类型的最后一个项目后添加逗号

这个尾随逗号并不会影响集合,但它能有效避免「复制粘贴」的错误。

var list = [
  'Car',
  'Boat',
  'Plane',
];

List 的下标索引从 0 开始

第一个元素的下标为 0,最后一个元素的下标为 list.length - 1
可以像 JavaScript 中的用法那样获取 Dart 中 List 的长度以及元素:

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

在 List 字面量前添加 const 关键字会创建一个编译时常量:

var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an error.

扩展操作符...和 空感知扩展操作符...?

https://github.com/dart-lang/language/blob/master/accepted/2.3/spread-collections/feature-specification.md
Dart 在 2.3 引入了 扩展操作符...和 空感知扩展操作符...?,它们提供了一种将多个元素插入集合的简洁方法。

可以使用扩展操作符...将一个 List 中的所有元素插入到另一个 List 中:
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
如果扩展操作符右边可能为 null ,你可以使用 null-aware 扩展操作符...?来避免产生异常:
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

集合中的 if 和 集合中的 for 操作

https://github.com/dart-lang/language/blob/master/accepted/2.3/control-flow-collections/feature-specification.md
在构建集合时,可以使用条件判断 if 和循环 for

使用 集合中的 if 来创建一个 List 的示例,它可能包含 3 个或 4 个元素:
var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];
使用 集合中的 for 将列表中的元素修改后添加到另一个列表中的示例:
var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

Set

https://api.dart.dev/stable/dart-core/Set-class.html
在 Dart 中,set 是一组特定元素的无序集合。 Dart 支持的集合由集合的字面量和 Set 类提供。
使用 Set 字面量来创建一个 Set 集合的方法:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Dart 推断 halogens 变量是一个 Set<String> 类型的集合,如果往该 Set 中添加类型不正确的对象则会报错

可以使用在 {} 前加上类型参数的方式创建一个空的 Set,或者将{}赋值给一个 Set 类型的变量:

var names = <String>{}; // 类型+{}的形式创建Set。
// Set<String> names = {}; // 声明类型变量的形式创建 Set (This works, too).
// var names = {}; // 这样的形式将创建一个 Map 而不是 Set (Creates a map, not a set.)

Map 字面量语法相似于 Set 字面量语法。
因为先有的 Map 字面量语法,所以 {} 默认是 Map 类型。
如果忘记在 {} 上注释类型或赋值到一个未声明类型的变量上,那么 Dart 会创建一个类型为 Map<dynamic, dynamic> 的对象。

使用 add() 方法或 addAll() 方法向已存在的 Set 中添加项目:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

使用 .length 可以获取 Set 中元素的数量:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

可以在 Set 字面量前添加 const 关键字创建一个 Set 编译时常量:

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // This line will cause an error.

从 Dart 2.3 开始,Set 可以像 List 一样支持使用扩展操作符......?以及 Collection if 和 for 操作

Map

通常来说,Map 是用来关联 keys 和 values 的对象。
其中键和值都可以是任何类型的对象。
每个 键 只能出现一次但是 值 可以重复出现多次。
Dart 中 Map 提供了 Map 字面量以及 Map 类型两种形式的 Map。

一对使用 Map 字面量创建 Map 的例子:

var gifts = {
  // 键:    值
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

Dart 将 gifts 变量的类型推断为 Map<String, String>,
而将 nobleGases 的类型推断为 Map<int, String>。
如果你向这两个 Map 对象中添加不正确的类型值,将导致运行时异常。

可以使用 Map 的构造器创建 Map:

var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map<int, String>();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

如果你之前是使用的 C# 或 Java 这样的语言,也许你想使用 new Map() 构造 Map 对象。
但是在 Dart 中,new 关键词是可选的,且不被建议使用

向现有的 Map 中添加键值对与 JavaScript 的操作类似:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // 添加键值对 (Add a key-value pair)
从一个 Map 中获取一个值的操作也与 JavaScript 类似。

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

如果检索的 Key 不存在于 Map 中则会返回一个 null:

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

使用 .length 可以获取 Map 中键值对的数量:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

在一个 Map 字面量前添加 const 关键字可以创建一个 Map 编译时常量:

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // This line will cause an error.

Map 可以像 List 一样支持使用扩展操作符......?以及集合的 if 和 for 操作。

runes 与 grapheme clusters

https://api.dart.dev/stable/dart-core/Runes-class.html
https://pub.flutter-io.cn/packages/characters
在 Dart 中,runes 公开了字符串的 Unicode 码位。使用 characters 包 来访问或者操作用户感知的字符,也被称为 Unicode (扩展) grapheme clusters。

Unicode 编码为每一个字母、数字和符号都定义了一个唯一的数值。因为 Dart 中的字符串是一个 UTF-16 的字符序列,所以如果想要表示 32 位的 Unicode 数值则需要一种特殊的语法。

表示 Unicode 字符的常见方式是使用 \uXXXX,其中 XXXX 是一个四位数的 16 进制数字。例如心形字符的 Unicode 为 \u2665。对于不是四位数的 16 进制数字,需要使用大括号将其括起来。例如大笑的 emoji 表情😆的 Unicode 为 \u{1f600}

如果你需要读写单个 Unicode 字符,可以使用 characters 包中定义的 characters getter。它将返回 Characters 对象作为一系列 grapheme clusters 的字符串。
下面是使用 characters API 的样例:

import 'package:characters/characters.dart';
...
var hi = 'Hi 🇩🇰';
print(hi);
print('The end of the string: ${hi.substring(hi.length - 1)}');
print('The last character: ${hi.characters.last}\n');

在使用 List 操作 Rune 的时候需要小心,根据所操作的语种、字符集等不同可能会导致字符串出现问题,具体可参考 Stack Overflow 中的提问: [我如何在 Dart 中反转一个字符串?][How do I reverse a String in Dart?]。

Symbol

https://api.dart.dev/stable/dart-core/Symbol-class.html
Symbol 表示 Dart 中声明的操作符或者标识符。你几乎不会需要 Symbol,但是它们对于那些通过名称引用标识符的 API 很有用,因为代码压缩后,尽管标识符的名称会改变,但是它们的 Symbol 会保持不变。

Symbol 字面量是编译时常量。

在标识符前加 # 前缀来获取 Symbol

#radix
#bar

函数 Function

https://api.dart.dev/stable/dart-core/Function-class.html
Dart 是一种真正面向对象的语言,所以即便函数也是对象并且类型为 Function,这意味着函数可以被赋值给变量或者作为其它函数的参数。你也可以像调用函数一样调用 Dart 类的实例

定义一个函数

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

虽然高效 Dart 指南建议在公开的 API 上定义返回类型,不过即便不定义,该函数也依然有效:

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

如果函数体内只包含一个表达式,你可以使用简写语法:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

语法 => 表达式 是 { return 表达式; } 的简写, => 有时也称之为 箭头 函数。
=>;之间的只能是 表达式 而非 语句。比如你不能将一个 if语句 放在其中,但是可以放置 条件表达式。

参数 Parameter

函数可以有两种形式的参数:必要参数 和 可选参数。
必要参数定义在参数列表前面,可选参数则定义在必要参数后面。
可选参数可以是 命名的 或 位置的。
某些 API(特别是 Flutter 控件的构造器)只使用命名参数,即便参数是强制性的。

向函数传入参数或者定义函数参数时,可以使用 [尾随逗号][trailing comma]。

命名参数

命名参数默认为可选参数,除非他们被特别标记为 required

当你调用函数时,可以使用 参数名: 参数值 的形式来指定命名参数。

enableFlags(bold: true, hidden: false);

定义函数时,使用 {param1, param2, …} 来指定命名参数:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool? bold, bool? hidden}) {...}

虽然命名参数是可选参数的一种类型,但是你仍然可以使用 required 来标识一个命名参数是必须的参数,此时调用者必须为该参数提供一个值。

const Scrollbar({Key? key, required Widget child})

如果调用者想要通过 Scrollbar 的构造函数构造一个 Scrollbar 对象而不提供 child 参数,则会导致编译错误。

可选的位置参数

使用 [] 将一系列参数包裹起来作为位置参数:

String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

不使用可选参数调用上述函数的示例:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

使用可选参数调用上述函数的示例:

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

默认参数值

可以用 = 为函数的命名参数和位置参数定义默认值,
默认值必须为编译时常量,
没有指定默认值的情况下默认值为 null。

设置可选参数默认值示例:
/// 设置 [bold][hidden] 标识……
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold 的值将为 true;而 hidden 将为 false。
enableFlags(bold: true);

在老版本的 Dart 代码中会使用冒号:而不是 = 来设置命名参数的默认值。
原因在于刚开始的时候命名参数只支持 :
不过现在这个支持已经过时,所以我们建议你现在仅 使用 = 来指定默认值。

为位置参数设置默认值:
String say(String from, String msg,
    [String device = 'carrier pigeon']) {
  var result = '$from says $msg with a $device';
  return result;
}

assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');
List 或 Map 同样也可以作为默认值

下面的示例定义了一个名为 doStuff() 的函数,并为其名为 list 和 gifts 的参数指定了一个 List 类型的值和 Map 类型的值。

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}

main() 函数

每个 Dart 程序都必须有一个 main() 顶级函数作为程序的入口,
main() 函数返回值为 void 并且有一个 List<String> 类型的可选参数。

下面是一个简单 main() 函数:

void main() {
  print('Hello, World!');
}

使用命令行访问带参数的 main() 函数示例:

// 使用命令 dart args.dart 1 test 运行该应用
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

可以通过使用 参数库 来定义和解析命令行参数
https://pub.flutter-io.cn/packages/args

函数是一级对象

可以将函数作为参数传递给另一个函数

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// 将 printElement 函数作为参数传递。
list.forEach(printElement);

可以将函数赋值给一个变量

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

该示例中使用了匿名函数

匿名函数

大多数方法都是有名字的,比如 main() 或 printElement()。
可以创建一个没有名字的方法,称之为 匿名函数、 Lambda 表达式 或 Closure 闭包。
可以将匿名方法赋值给一个变量然后使用它,比如将该变量添加到集合或从中删除。

匿名方法看起来与命名方法类似,在括号之间可以定义参数,参数之间用逗号分割。

后面大括号中的内容则为函数体:

([[类型] 参数[, …]]) {
  函数体;
};

下面代码定义了只有一个参数 item 且没有参数类型的匿名方法。
List 中的每个元素都会调用这个函数,打印元素位置和值的字符串:

const list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

如果函数体内只有一行返回语句,你可以使用胖箭头缩写法。

list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));

词法作用域

Dart 是词法有作用域语言,变量的作用域在写代码的时候就确定了,
大括号内定义的变量只能在大括号内访问,与 Java 类似。

一个嵌套函数中变量在多个作用域中的示例:

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

注意 nestedFunction() 函数可以访问包括顶层变量在内的所有的变量。

词法闭包

闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。

函数可以封闭定义到它作用域内的变量。

接下来的示例中,函数 makeAdder() 捕获了变量 addBy。
无论函数在什么时候返回,它都可以使用捕获的 addBy 变量。

/// 返回一个将 [addBy] 添加到该函数参数的函数。
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

void main() {
  // 生成加 2 的函数。
  var add2 = makeAdder(2);

  // 生成加 4 的函数。
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

测试函数是否相等

顶级函数,静态方法和示例方法相等性的测试示例:

void foo() {} // 定义顶层函数 (A top-level function)

class A {
  static void bar() {} // 定义静态方法
  void baz() {} // 定义实例方法
}

void main() {
  Function x;

  // 比较顶层函数是否相等。
  x = foo;
  assert(foo == x);

  // 比较静态方法是否相等。
  x = A.bar;
  assert(A.bar == x);

  // 比较实例方法是否相等。
  var v = A(); // A 的实例 #1
  var w = A(); // A 的实例 #2
  var y = w;
  x = w.baz;

  // 这两个闭包引用了相同的实例对象,因此它们相等。
  assert(y.baz == x);

  // 这两个闭包引用了不同的实例对象,因此它们不相等。
  assert(v.baz != w.baz);
}

返回值

所有的函数都有返回值。
没有显示返回语句的函数最后一行默认为执行 return null;

foo() {}
assert(foo() == null);

运算符 Operator

Dart 支持下表的操作符。你可以将这些运算符实现为 一个类的成员。

描述 运算符
一元后缀 表达式++ 表达式-- () [] . ?.
一元前缀 -表达式 !表达式 ~表达式 ++表达式 --表达式
乘除法 * / % ~/
加减法 + -
位运算 << >> >>>
二进制与 &
二进制异或 ^
二进制或 |
关系和类型测试 >= > <= < as is is!
相等判断 == !=
逻辑与 &&
逻辑或 ||
空判断 ??
条件表达式 表达式 1 ? 表达式 2 : 表达式 3
级联 ..    ?.... ?..
赋值 = *= /= += -= &= ^= 等等……

一旦你使用了运算符,就创建了表达式。
一些运算符表达式的示例:

a++
a + b
a = b
a == b
c ? a : b
a is T

在 运算符表 中,运算符的优先级按先后排列,即第一行优先级最高,最后一行优先级最低,而同一行中,最左边的优先级最高,最右边的优先级最低。

// 括号提高了可读性。
// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...

// 难以理解,但是与上面的代码效果一样。
if (n % i == 0 && d % i == 0) ...

对于有两个操作数的运算符,左边的操作数决定了运算符的功能。
比如对于一个 Vector 对象和一个 Point 对象,表达式 aVector + aPoint 中所使用的是 Vector 对象中定义的相加运算符 +

算术运算符

Dart 支持常用的算术运算符:

运算符 描述
  • | 加
    – | 减
    -表达式 | 一元负, 也可以作为反转(反转表达式的符号)
  • | 乘
    / | 除
    ~/ | 除并取整
    % | 取模

示例:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 结果是一个浮点数
assert(5 ~/ 2 == 2); // 结果是一个整数
assert(5 % 2 == 1); // 取余

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

Dart 还支持自增自减操作。

++var var = var + 1 (表达式的值为 var + 1)
var++ var = var + 1 (表达式的值为 var)
--var var = var – 1 (表达式的值为 var – 1)
var-- var = var – 1 (表达式的值为 var)

示例:

var a, b;

a = 0;
b = ++a; // 在 b 赋值前将 a 增加 1。
assert(a == b); // 1 == 1

a = 0;
b = a++; // 在 b 赋值后将 a 增加 1。
assert(a != b); // 1 != 0

a = 0;
b = --a; // 在 b 赋值前将 a 减少 1。
assert(a == b); // -1 == -1

a = 0;
b = a--; // 在 b 赋值后将 a 减少 1。
assert(a != b); // -1 != 0

关系运算符

== 相等
!= 不等

| 大于
< | 小于
= | 大于等于
<= | 小于等于

要判断两个对象 x 和 y 是否表示相同的事物使用 == 即可。(在极少数情况下,可能需要使用 identical() 函数来确定两个对象是否完全相同。)。

== 运算符的一些规则:

假设有变量 x 和 y,且 x 和 y 至少有一个为 null,则当且仅当 x 和 y 均为 null 时 x == y 才会返回 true,否则只有一个为 null 则返回 false。

x.==(y) 将会返回值,这里不管有没有 y,即 y 是可选的。也就是说 == 其实是 x 中的一个方法,并且可以被重写。详情请查阅重写运算符。

类型判断运算符

as、is、is! 运算符是在运行时判断对象类型的运算符。

Operator Meaning
as 类型转换(也用作指定 类前缀))
is 如果对象是指定类型则返回 true
is! 如果对象是指定类型则返回 false

当且仅当 obj 实现了 T 的接口,obj is T 才是 true。例如 obj is Object 总为 true,因为所有类都是 Object 的子类。

仅当你确定这个对象是该类型的时候,你才可以使用 as 操作符可以把对象转换为特定的类型。例如:

(employee as Person).firstName = 'Bob';

如果你不确定这个对象类型是不是 T,请在转型前使用 is T 检查类型。

if (employee is Person) {
  // Type check
  employee.firstName = 'Bob';
}

你可以使用 as 运算符进行缩写:

(emp as Person).firstName = 'Bob';

上述两种方式是有区别的:如果 employee 为 null 或者不为 Person 类型,则第一种方式将会抛出异常,而第二种不会。

赋值运算符

可以使用 = 来赋值,同时也可以使用 ??= 来为值为 null 的变量赋值。

// Assign value to a
a = value;
// Assign value to b if b is null; otherwise, b stays the same
b ??= value;

+= 这样的赋值运算符将算数运算符和赋值运算符组合在了一起。

= –= /= %= >>= ^=
+= *= ~/= <<= &= |=

复合运算符的原理

场景 复合运算 等效表达式
假设有运算符 op: a op= b a = a op b
示例: a += b a = a + b

使用赋值以及复合赋值运算符:

var a = 2; // 使用 = 赋值 (Assign using =)
a *= 3; // 赋值并做乘法运算 Assign and multiply: a = a * 3
assert(a == 6);

逻辑运算符

使用逻辑运算符你可以反转或组合布尔表达式

运算符 描述
!表达式 对表达式结果取反(即将 true 变为 false,false 变为 true)
|| 逻辑或
&& 逻辑与

使用逻辑表达式

if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

按位和移位运算符

在 Dart 中,二进制位运算符可以操作二进制的某一位,但仅适用于整数。

运算符 描述
& 按位与
| 按位或
^ 按位异或
~表达式 按位取反(即将 “0” 变为 “1”,“1” 变为 “0”)
<< 位左移

| 位右移

使用按位和移位运算符

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // 按位与 (AND)
assert((value & ~bitmask) == 0x20); // 取反后按位与 (AND NOT)
assert((value | bitmask) == 0x2f); // 按位或 (OR)
assert((value ^ bitmask) == 0x2d); // 按位异或 (XOR)
assert((value << 4) == 0x220); // 位左移 (Shift left)
assert((value >> 4) == 0x02); // 位右移 (Shift right)

条件表达式

Dart 有两个特殊的运算符可以用来替代 if-else 语句:
condition ? expr1 : expr2

条件 ? 表达式 1 : 表达式 2 :如果条件为 true,执行表达式 1并返回执行结果,否则执行表达式 2 并返回执行结果。

expr1 ?? expr2
表达式 1 ?? 表达式 2:如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值。

根据布尔表达式确定赋值时,请考虑使用 ? :

var visibility = isPublic ? 'public' : 'private';

如果赋值是根据判定是否为 null 则考虑使用 ??

String playerName(String? name) => name ?? 'Guest';

上述示例还可以写成至少下面两种不同的形式,只是不够简洁:

// Slightly longer version uses ?: operator.
String playerName(String? name) => name != null ? name : 'Guest';

// Very long version uses if-else statement.
String playerName(String? name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}

级联运算符

级联运算符 .., ?.. 可以让你在同一个对象上连续调用多个对象的变量或方法。

var paint = Paint()
  ..color = Colors.black
  ..strokeCap = StrokeCap.round
  ..strokeWidth = 5.0;

等同于

var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

如果级联操作的对象可以为空,则在第一次操作中使用空级联?..。从?..开始保证该空对象上不尝试任何级联操作。

querySelector('#confirm') // Get an object.
  ?..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

?.. 运行在 2.12 和以上的 版本 中可用。
上面的代码相当于:

var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));

级联运算符可以嵌套

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = '[email protected]'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

在返回对象的函数中谨慎使用级联操作符。

例如,下面的代码是错误的:

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // 出错:void 对象中没有方法 write (Error: method 'write' isn't defined for 'void').

上述代码中的 sb.write() 方法返回的是 void,返回值为 void 的方法则不能使用级联运算符。

严格来说 .. 级联操作并非一个运算符而是 Dart 的特殊语法。

其他运算符

运算符 名字 描述
() 使用方法 代表调用一个方法
[] 访问 List 访问 List 中特定位置的元素
. 访问成员 成员访问符
?. 条件访问成员 与上述成员访问符类似,但是左边的操作对象不能为 null,例如 foo?.bar,如果 foo 为 null 则返回 null ,否则返回 bar

流程控制语句

可以使用下面的语句来控制 Dart 代码的执行流程:

if 和 else

for 循环

while 和 do-while 循环

break 和 continue

switch 和 case

assert

使用 try-catch 和 throw 也能影响控制流,详情参考异常部分

If 和 Else

Dart 支持 if - else 语句,其中 else 是可选的,比如下面的例子。你也可以参考条件表达式。

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

不同于 JavaScript,Dart 的 if 语句中的条件必须是布尔值而不能为其它类型

For 循环

可以使用标准的 for 循环进行迭代

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

在 Dart 语言中,for 循环中的闭包会自动捕获循环的 索引值 以避免 JavaScript 中一些常见的陷阱。

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

上述代码执行后会输出 0 和 1,但是如果在 JavaScript 中执行同样的代码则会输出两个 2。

如果要遍历的对象是一个可迭代对象(例如 List 或 Set),并且你不需要知道当前的遍历索引,则可以使用 for-in 方法进行 遍历:

for (var candidate in candidates) {
  candidate.interview();
}

可迭代对象同时可以使用 forEach() 方法作为另一种选择:

var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3

While 和 Do-While

while 循环会在执行循环体前先判断条件:

while (!isDone()) {
  doSomething();
}

do-while 循环则会 先执行一遍循环体 再判断条件:

do {
  printLine();
} while (!atEndOfPage());

Break 和 Continue

使用 break 可以中断循环:

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

使用 continue 可以跳过本次循环直接进入下一次循环:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

如果你正在使用诸如 List 或 Set 之类的 Iterable 对象,你可以用以下方式重写上述例子:

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());

Switch 和 Case

Switch 语句在 Dart 中使用 == 来比较整数、字符串或编译时常量,
比较的两个对象必须是同一个类型且不能是子类并且没有重写 == 操作符。
枚举类型非常适合在 Switch 语句中使用。
Dart 中的 Switch 语句仅适用于有限的情况,比如使用解释器和扫描器的场景。

每一个非空的 case 子句都必须有一个 break 语句,也可以通过 continue、throw 或者 return 来结束非空 case 语句。
每个 case 子句都可以有局部变量且仅在该 case 语句内可见。

不匹配任何 case 语句的情况下,会执行 default 子句中的代码:

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

忽略了 case 子句的 break 语句,会产生错误:

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // 错误: 没有 break

  case 'CLOSED':
    executeClosed();
    break;
}

但是,Dart 支持空的 case 语句,允许其以 fall-through 的形式执行。

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // case 语句为空时的 fall-through 形式。
  case 'NOW_CLOSED':
    // case 条件值为 CLOSED 和 NOW_CLOSED 时均会执行该语句。
    executeNowClosed();
    break;
}

在非空 case 语句中想要实现 fall-through 的形式,可以使用 continue 语句配合 label 的方式实现:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // 继续执行标签为 nowClosed 的 case 子句。

  nowClosed:
  case 'NOW_CLOSED':
    // case 条件值为 CLOSED 和 NOW_CLOSED 时均会执行该语句。
    executeNowClosed();
    break;
}

断言 assert

在开发过程中,可以在条件表达式为 false 时使用 assert(条件, 可选信息); 语句来打断代码的执行。

assert 的第一个参数可以是值为布尔值的任何表达式。
如果表达式的值为 true,则断言成功,继续执行。
如果表达式的值为 false,则断言失败,抛出一个 AssertionError 异常。

assert 是否生效依赖开发工具和使用的框架:

Flutter 在调试模式时生效。
一些开发工具比如 dartdevc 通常情况下是默认生效的。
其他一些工具,比如 dart 以及 dart2js 通过在运行 Dart 程序时添加命令行参数 --enable-asserts 使 assert 生效。
在生产环境代码中,断言会被忽略,与此同时传入 assert 的参数不被判断。

// 确保变量值不为 null (Make sure the variable has a non-null value)
assert(text != null);

// 确保变量值小于 100。
assert(number < 100);

// 确保这是一个 https 地址。
assert(urlString.startsWith('https'));

assert 的第二个参数可以为其添加一个字符串消息。

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

异常 Exception

https://dart.cn/guides/libraries/library-tour#exceptions
Dart 代码可以抛出和捕获异常。
异常表示一些未知的错误情况,如果异常没有捕获则会被抛出从而导致抛出异常的代码终止执行。

与 Java 不同的是,Dart 的所有异常都是非必检异常,方法不必声明会抛出哪些异常,并且你也不必捕获任何异常。

Dart 提供了 Exception 和 Error 两种类型的异常以及它们一系列的子类,你也可以定义自己的异常类型。
但是在 Dart 中可以将任何非 null 对象作为异常抛出而不局限于 Exception 或 Error 类型。

抛出异常

下面是关于抛出或者 引发 异常的示例:

throw FormatException('Expected at least 1 section');

你也可以抛出任意的对象:

throw 'Out of llamas!';

优秀的代码通常会抛出 Error 或 Exception 类型的异常

因为抛出异常是一个表达式,所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:

void distanceTo(Point other) => throw UnimplementedError();

捕获异常

捕获异常可以避免异常继续传递(重新抛出异常除外)。
捕获一个异常可以给你处理它的机会:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

对于可以抛出多种异常类型的代码,也可以指定多个 catch 语句,每个语句分别对应一个异常类型,
如果 catch 语句没有指定异常类型则表示可以捕获任意异常类型:
可以使用 on 或 catch 来捕获异常,使用 on 来指定异常类型,使用 catch 来捕获异常对象,两者可同时使用。

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 指定异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 其它类型的异常
  print('Unknown exception: $e');
} catch (e) {
  // // 不指定类型,处理其它全部
  print('Something really unknown: $e');
}

你可以为 catch 方法指定两个参数,第一个参数为抛出的异常对象,第二个参数为栈信息 StackTrace 对象:

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

关键字 rethrow 可以将捕获的异常再次抛出:

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // 运行时错误
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // 允许调用者查看异常。
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

Finally

无论是否抛出异常,finally 语句始终执行,
如果没有指定 catch 语句来捕获异常,则异常会在执行完 finally 语句后抛出:

try {
  breedMoreLlamas();
} finally {
  // 总是清理,即便抛出了异常。
  cleanLlamaStalls();
}

finally 语句会在任何匹配的 catch 语句后执行:

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // 先处理异常。
} finally {
  cleanLlamaStalls(); // 然后清理。
}

类 class

Dart 是支持基于 mixin 继承机制的面向对象语言,所有对象都是一个类的实例,而除了 Null 以外的所有的类都继承自 Object 类。
基于 mixin 的继承 意味着尽管每个类(top class Object? 除外)都只有一个超类,一个类的代码可以在其它多个类继承中重复使用。
扩展方法 是一种在不更改类或创建子类的情况下向类添加功能的方式。

使用类的成员

对象的 成员 由函数和数据(即 方法 和 实例变量)组成。
方法的 调用 要通过对象来完成,这种方式可以访问对象的函数和数据。

使用.来访问对象的实例变量或方法:

var p = Point(2, 2);

// 获取 y 值
assert(p.y == 2);

// 调用变量 p 的 distanceTo() 方法。
double distance = p.distanceTo(Point(4, 4));

使用 ?. 代替 . 可以避免因为左边表达式为 null 而导致的问题:

// If p is non-null, set a variable equal to its y value.
var a = p?.y;

使用构造函数 constructor

可以使用 构造函数 来创建一个对象。
构造函数的命名方式可以为 类名类名 . 标识符 的形式。

例如下述代码分别使用 Point()Point.fromJson() 两种构造器创建了 Point 对象:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

以下代码具有相同的效果,但是构造函数名前面的的 new 关键字是可选的:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

一些类提供了常量构造函数。使用常量构造函数,在构造函数名之前加 const 关键字,来创建编译时常量时:

var p = const ImmutablePoint(2, 2);

两个使用相同构造函数相同参数值构造的编译时常量是同一个对象:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它们是同一个实例 (They are the same instance!)

在 常量上下文 场景中,你可以省略掉构造函数或字面量前的 const 关键字。
例如下面的例子中我们创建了一个常量 Map:

// Lots of const keywords here.
// 这里有很多 const 关键字
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

根据上下文,你可以只保留第一个 const 关键字,其余的全部省略:

// Only one const, which establishes the constant context.
// 只需要一个 const 关键字,其它的则会隐式地根据上下文进行关联。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

但是如果无法根据上下文判断是否可以省略 const,则不能省略掉 const 关键字,否则将会创建一个 非常量对象
例如:

var a = const ImmutablePoint(1, 1); // 创建一个常量 (Creates a constant)
var b = ImmutablePoint(1, 1); // 不会创建一个常量 (Does NOT create a constant)

assert(!identical(a, b)); // 这两变量并不相同 (NOT the same instance!)

获取对象的类型

可以使用 Object 对象的 runtimeType 属性在运行时获取一个对象的类型,该对象类型是 Type 的实例。

print('The type of a is ${a.runtimeType}');

到目前为止,已经解了如何 使用 类。其余部分将向你介绍如何 实现 一个类。

实例变量

声明实例变量的示例:

class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}

所有未初始化的实例变量其值均为 null。

所有实例变量均会隐式地声明一个 Getter 方法。
非终值的实例变量和 late final 声明但未声明初始化的实例变量还会隐式地声明一个 Setter 方法。

class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
}

void main() {
  var point = Point();
  point.x = 4; // 使用 x 的 Setter 方法。
  assert(point.x == 4); // 使用 x 的 Getter 方法。
  assert(point.y == null); // 默认值为 null。
}

实例变量可能是final,在这种情况下,必须精确设置一次。
使用构造器参数或使用构造器的初始化列表

class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

构造函数

声明一个与类名一样的函数即可声明一个构造函数(对于命名式构造函数 还可以添加额外的标识符)。
大部分的构造函数形式是生成式构造函数,其用于创建一个类的实例:

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // 还会有更好的方式来实现此逻辑,敬请期待。
    this.x = x;
    this.y = y;
  }
}

使用 this 关键字引用当前实例。
当且仅当命名冲突时使用 this 关键字才有意义,否则 Dart 会忽略 this 关键字。

对于大多数编程语言来说在构造函数中为实例变量赋值的过程都是类似的,而 Dart 则提供了一种特殊的语法糖来简化该步骤:

class Point {
  double x = 0;
  double y = 0;

  // 在构造函数体执行前用于设置 x 和 y 的语法糖。
  Point(this.x, this.y);
}

默认构造函数

如果你没有声明构造函数,那么 Dart 会自动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。

构造函数不被继承

子类不会继承父类的构造函数,如果子类没有声明构造函数,那么只会有一个默认无参数的构造函数。

构造函数是不能被继承的,这将意味着子类不能继承父类的命名式构造函数,
如果你想在子类中提供一个与父类命名构造函数名字一样的命名构造函数,则需要在子类中显式地声明。

命名式构造函数

可以为一个类声明多个命名式构造函数来表达更明确的意图:

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);

  // 命名式构造函数
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

调用父类非默认构造函数

默认情况下,子类的构造函数会调用父类的匿名无参数构造方法,并且该调用会在子类构造函数的函数体代码执行前,
如果子类构造函数还有一个 初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,

总的来说,这三者的调用顺序如下:

初始化列表
父类的无参数构造函数
当前类的构造函数

如果父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用:指定。

下面的示例中,Employee 类的构造函数调用了父类 Person 的命名构造函数。

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // Prints:
  // in Person
  // in Employee
  // Instance of 'Employee'
}

因为参数会在子类构造函数被执行前传递给父类的构造函数,因此该参数也可以是一个表达式,比如一个函数:

class Employee extends Person {
  Employee() : super.fromJson(fetchDefaultData());
  // ···
}

传递给父类构造函数的参数不能使用 this 关键字,因为在参数传递的这一步骤,子类构造函数尚未执行,子类的实例对象也就还未初始化,因此所有的实例成员都不能被访问,但是类成员可以。

初始化列表

除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量。
每个实例变量之间使用逗号分隔。

// Initializer list sets instance variables before
// the constructor body runs.
// 使用初始化列表在构造函数体执行前设置实例变量。
Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

初始化列表表达式 = 右边的语句不能使用 this 关键字。

在开发模式下,你可以在初始化列表中使用 assert 来验证输入数据:

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

使用初始化列表设置 final 字段非常方便
下面的示例中就使用初始化列表来设置了三个 final 变量的值

import 'dart:math';

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
}

重定向构造函数

有时候类中的构造函数仅用于调用类中其它的构造函数,
此时该构造函数没有函数体,只需在函数签名后使用:指定需要重定向到的其它构造函数:

class Point {
  double x, y;

  // 该类的主构造函数。
  Point(this.x, this.y);

  // 委托实现给主构造函数。
  Point.alongXAxis(double x) : this(x, 0);
}

常量构造函数

如果类生成的对象都是不变的,可以在生成这些对象时就将其变为编译时常量。
你可以在类的构造函数前加上 const 关键字并确保所有实例变量均为 final 来实现该功能。

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

常量构造函数创建的实例并不总是常量,具体可以参考 https://dart.cn/guides/language/language-tour#using-constructors

工厂构造函数

使用 factory 关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。
例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。
在工厂构造函数中无法访问 this。

在如下的示例中,
Logger 的工厂构造函数从缓存中返回对象,
Logger.fromJson 工厂构造函数从 JSON 对象中初始化一个最终变量。

class Logger {
  final String name;
  bool mute = false;

  // _cache 变量是库私有的,因为在其名字前面有下划线。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

工厂构造函数的调用方式与其他构造函数一样:

var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

方法

方法是为对象提供行为的函数。

实例方法

对象的实例方法可以访问实例变量和 this。
下面的 distanceTo() 方法就是一个实例方法的例子:

import 'dart:math';

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

操作符

运算符是有着特殊名称的实例方法。
Dart 允许您使用以下名称定义运算符:

< + | []

| / | ^ | []=
<= | ~/ | & | ~
= | * | << | ==
– | % | >> |  

你可能注意到有一些 操作符 没有出现在列表中,例如 !=
因为它们仅仅是语法糖。表达式 e1 != e2 仅仅是 !(e1 == e2) 的一个语法糖。

为了表示重写操作符,使用 operator 标识来进行标记。

下面是重写 +- 操作符的例子:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

Getter 和 Setter

Getter 和 Setter 是一对用来读写对象属性的特殊方法,
上面说过实例对象的每一个属性都有一个隐式的 Getter 方法,
如果为非 final 属性的话还会有一个 Setter 方法,
可以使用 get 和 set 关键字为额外的属性添加 Getter 和 Setter 方法:

class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算产生的属性:right 和 bottom。
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

使用 Getter 和 Setter 的好处是,你可以先使用你的实例变量,过一段时间过再将它们包裹成方法且不需要改动任何代码,即先定义后更改且不影响原有逻辑。

像自增++这样的操作符不管是否定义了 Getter 方法都会正确地执行。
为了避免一些不必要的异常情况,运算符只会调用 Getter 一次,然后将其值存储在一个临时变量中。

抽象方法

实例方法、Getter 方法以及 Setter 方法都可以是抽象的,
定义一个接口方法而不去做具体的实现让实现它的类去实现该方法,
抽象方法只能存在于 抽象类中。

直接使用分号;替代方法体即可声明一个抽象方法:

abstract class Doer {
  // 定义实例变量和方法等等……

  void doSomething(); // 定义一个抽象方法。
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 提供一个实现,所以在这里该方法不再是抽象的……
  }
}

抽象类 abstract

使用关键字 abstract 标识类可以让该类成为 抽象类,抽象类将无法被实例化。
抽象类常用于声明接口方法、有时也会有具体的方法实现。
如果想让抽象类同时可被实例化,可以为其定义 工厂构造函数

抽象类常常会包含 抽象方法。

一个声明具有抽象方法的抽象类示例:

// This class is declared abstract and thus
// can't be instantiated.
// 该类被声明为抽象的,因此它不能被实例化。
abstract class AbstractContainer {
  // 定义构造函数、字段、方法等……

  void updateChildren(); // 抽象方法。
}

隐式接口 implements

每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。
如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。

一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:

// A person. The implicit interface contains greet().
// Person 类的隐式接口中包含 greet() 方法。
class Person {
  // In the interface, but visible only in this library.
  final String _name;

  // 构造函数不在接口中。
  Person(this._name);

  // greet() 方法在接口中。
  String greet(String who) => '你好,$who。我是$_name。';
}

// Person 接口的一个实现。
class Impostor implements Person {
  String get _name => '';

  String greet(String who) => '你好$who。你知道我是谁吗?';
}

String greetBob(Person person) => person.greet('小芳');

void main() {
  print(greetBob(Person('小芸')));
  print(greetBob(Impostor()));
}

如果需要实现多个类接口,可以使用逗号分割每个接口类:

class Point implements Comparable, Location {...}

扩展一个类 extends

使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

重写类成员 @OverRide

子类可以重写父类的实例方法(包括 操作符)、 Getter 以及 Setter 方法。
可以使用 @override 注解来表示你重写了一个成员:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}
covariant

你可以使用 covariant 关键字 来缩小代码中那些符合 类型安全 的方法参数或实例变量的类型。

class Animal {
  void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
  @override
  void chase(covariant Mouse x) { ... }
}
如果重写 == 操作符,必须同时重写对象 hashCode 的 Getter 方法。

noSuchMethod 方法

如果调用了对象上不存在的方法或实例变量将会触发 noSuchMethod 方法,
可以重写 noSuchMethod 方法来追踪和记录这一行为:

class A {
  // 除非你重写 noSuchMethod,否则调用一个不存在的成员会导致 NoSuchMethodError。
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: '
        '${invocation.memberName}');
  }
}
只有下面其中一个条件成立时,你才能调用一个未实现的方法:

接收方是静态的 dynamic 类型。

接收方具有静态类型,定义了未实现的方法(抽象亦可),并且接收方的动态类型实现了 noSuchMethod 方法且具体的实现与 Object 中的不同。

https://github.com/dart-lang/sdk/blob/master/docs/language/informal/nosuchmethod-forwarding.md

扩展方法

https://dart.cn/guides/language/extension-methods
扩展方法是向现有库添加功能的一种方式。
你可能已经在不知道它是扩展方法的情况下使用了它。
例如,当您在 IDE 中使用代码完成功能时,它建议将扩展方法与常规方法一起使用。

这里是一个在 String 中使用扩展方法的样例,我们取名为 parseInt(),它在 string_apis.dart 中定义:

import 'string_apis.dart';

print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

枚举类型

枚举类型是一种特殊的类型,也称为 enumerations 或 enums,用于定义一些固定数量的常量值。

使用枚举 enum

使用关键字 enum 来定义枚举类型:

enum Color { red, green, blue }

你可以在声明枚举类型时使用 尾随逗号。

每一个枚举值都有一个名为 index 成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值。
例如,第一个枚举值的索引是 0 ,第二个枚举值的索引是 1。以此类推。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

想要获得全部的枚举值,使用枚举类的 values 方法获取包含它们的列表:

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

可以在 Switch 语句中使用枚举,
但是需要注意的是必须处理枚举值的每一种情况,即每一个枚举值都必须成为一个 case 子句,不然会出现警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('红如玫瑰!');
    break;
  case Color.green:
    print('绿如草原!');
    break;
  default: // 没有该语句会出现警告。
    print(aColor); // 'Color.blue'
}

枚举类型有如下两个限制:

枚举不能成为子类,也不可以 mix in,你也不可以实现一个枚举。

不能显式地实例化一个枚举类。

使用 mixin 为类添加功能

mixin 是一种在多重继承中复用某个类中代码的方法模式。

使用 with 关键字并在其后跟上 mixin 类的名字来使用 mixin 模式:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

实现一个 mixin

想要实现一个 mixin,请创建一个继承自 Object 且未声明构造函数的类。
除非你想让该类与普通的类一样可以被正常地使用,否则请使用关键字 mixin 替代 class。
例如:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

可以使用关键字 on 来指定哪些类可以使用该 Mixin 类,

比如有 Mixin 类 A,但是 A 只能被 B 类使用,则可以这样定义 A:

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

mixin 关键字在 Dart 2.1 中才被引用支持。早期版本中的代码通常使用 abstract class 代替。

类变量和方法 static

使用关键字 static 可以声明类变量或类方法。

静态变量

静态变量(即类变量)常用于声明类范围内所属的状态变量和常量:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

静态变量在其首次被使用的时候才被初始化。

静态方法

静态方法(即类方法)不能对实例进行操作,因此不能使用 this。
但是他们可以访问静态变量。
如下面的例子所示,你可以在一个类上直接调用静态方法:

import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

对于一些通用或常用的静态方法,应该将其定义为顶级函数而非静态方法。

可以将静态方法作为编译时常量。例如,你可以将静态方法作为一个参数传递给一个常量构造函数。


泛型 Generic

如果你查看数组的 API 文档,你会发现数组 List 的实际类型为 List<E>
<…>符号表示数组是一个 泛型(或 参数化类型)
通常 使用一个字母来代表类型参数,比如 E、T、S、K 和 V 等等

为什么使用泛型?

泛型常用于需要要求类型安全的情况,但是它也会对代码运行有好处:

适当地指定泛型可以更好地帮助代码生成。
使用泛型可以减少代码重复。

比如你想声明一个只能包含 String 类型的数组,你可以将该数组声明为 List<String>(读作“字符串类型的 list”),这样的话就可以很容易避免因为在该数组放入非 String 类变量而导致的诸多问题,同时编译器以及其他阅读代码的人都可以很容易地发现并定位问题:

var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

另一个使用泛型的原因是可以减少重复代码。
泛型可以让你在多个不同类型实现之间共享同一个接口声明,
比如下面的例子中声明了一个类用于缓存对象的接口:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

不久后你可能又会想专门为 String 类对象做一个缓存,于是又有了专门为 String 做缓存的类:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

如果过段时间你又想为数字类型也创建一个类,那么就会有很多诸如此类的代码……

这时候可以考虑使用泛型来声明一个类,让不同类型的缓存实现该类做出不同的具体实现即可:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

在上述代码中,T 是一个替代类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。

使用集合字面量

List、Set 以及 Map 字面量也可以是参数化的。
定义参数化的 List 只需在中括号前添加 <type>
定义参数化的 Map 只需要在大括号前添加 <keyType, valueType>

var names = <String>['小芸', '小芳', '小民'];
var uniqueNames = <String>{'小芸', '小芳', '小民'};
var pages = <String, String>{
  'index.html': '主页',
  'robots.txt': '网页机器人提示',
  'humans.txt': '我们是人类,不是机器'
};

使用类型参数化的构造函数

在调用构造方法时也可以使用泛型,只需在类名后用尖括号<...>将一个或多个类型包裹即可:

var nameSet = Set<String>.from(names);

下面代码创建了一个键为 Int 类型,值为 View 类型的 Map 对象:

var views = Map<int, View>();

泛型集合以及它们所包含的类型

Dart的泛型类型是 固化的,这意味着即便在运行时也会保持类型信息:

var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

与 Java 不同的是,Java 中的泛型是类型 擦除 的,这意味着泛型类型会在运行时被移除。
在 Java 中你可以判断对象是否为 List 但不可以判断对象是否为 List。

限制参数化类型 <T extends SomeBaseClass>

有时使用泛型的时候可能会想限制泛型的类型范围,这时候可以使用 extends 关键字:

class Foo<T extends SomeBaseClass> {
  // 具体实现……
  String toString() => "'Foo<$T>' 的实例";
}

class Extender extends SomeBaseClass {...}

这时候就可以使用 SomeBaseClass 或者它的子类来作为泛型参数:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

这时候也可以指定无参数的泛型,这时无参数泛型的类型则为 Foo<SomeBaseClass>

var foo = Foo();
print(foo); // 'Foo<SomeBaseClass>' 的实例 (Instance of 'Foo<SomeBaseClass>')

将非 SomeBaseClass 的类型作为泛型参数则会导致编译错误:

var foo = Foo<Object>();

使用泛型方法 function<T>

https://github.com/dart-lang/sdk/blob/master/pkg/dev_compiler/doc/GENERIC_METHODS.md
起初 Dart 只支持在类的声明时指定泛型,现在同样也可以在方法上使用泛型,称之为 泛型方法:

T first<T>(List<T> ts) {
  // 处理一些初始化工作或错误检测……
  T tmp = ts[0];
  // 处理一些额外的检查……
  return tmp;
}

方法 first 的泛型 T 可以在如下地方使用:

函数的返回值类型 T

参数的类型 List<T>

局部变量的类型 T tmp

库和可见性

import 和 library 关键字可以帮助你创建一个模块化和可共享的代码库。
代码库不仅只是提供 API 而且还起到了封装的作用:以下划线_开头的成员仅在代码库中可见。
每个 Dart 程序都是一个库,即便没有使用关键字 library 指定。

Dart 的库可以使用 包工具 来发布和部署。
https://dart.cn/guides/packages

如果你对 Dart 为何使用下划线而不使用 public 或 private 作为可访问性关键字,可以查看 SDK issue 33383
dart-lang/sdk#33383

使用库 import

使用 import 来指定命名空间以便其它库可以访问。

比如你可以导入代码库 dart:html 来使用 Dart Web 中相关 API:

import 'dart:html';

import 的唯一参数是用于指定代码库的 URI,
对于 Dart 内置的库,使用 dart:xxxxxx 的形式。
而对于其它的库,你可以使用一个文件系统路径或者以 package:xxxxxx 的形式。
package:xxxxxx 指定的库通过包管理器(比如 pub 工具)来提供:

import 'package:test/test.dart';

URI 代表统一资源标识符。

URL(统一资源定位符)是一种常见的 URI。

指定库前缀 as

如果导入的两个代码库有冲突的标识符,你可以为其中一个指定前缀。
比如如果 library1 和 library2 都有 Element 类,那么可以这么处理:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// 使用 lib1 的 Element 类。
Element element1 = Element();

// 使用 lib2 的 Element 类。
lib2.Element element2 = lib2.Element();

导入库的一部分 show hide

如果你只想使用代码库中的一部分,你可以有选择地导入代码库。例如:

// 只导入 lib1 中的 foo。(Import only foo).
import 'package:lib1/lib1.dart' show foo;

// 导入 lib2 中除了 foo 外的所有。
import 'package:lib2/lib2.dart' hide foo;

延迟加载库 deferred as

延迟加载(也常称为 懒加载)允许应用在需要时再去加载代码库,

可能使用到延迟加载的场景:

为了减少应用的初始化时间。

处理 A/B 测试,比如测试各种算法的不同实现。

加载很少会使用到的功能,比如可选的屏幕和对话框。

目前只有 dart2js 支持延迟加载 Flutter、Dart VM 以及 DartDevc 目前都不支持延迟加载。

用 deferred as 关键字来标识需要延时加载的代码库:

import 'package:greetings/hello.dart' deferred as hello;

当实际需要使用到库中 API 时先调用 loadLibrary 函数加载库:

Future<void> greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

在前面的代码,使用 await 关键字暂停代码执行直到库加载完成。

loadLibrary 函数可以调用多次也没关系,代码库只会被加载一次。

当你使用延迟加载的时候需要牢记以下几点:

延迟加载的代码库中的常量需要在代码库被加载的时候才会导入,未加载时是不会导入的。

导入文件的时候无法使用延迟加载库中的类型。如果你需要使用类型,则考虑把接口类型转移到另一个库中然后让两个库都分别导入这个接口库。

Dart会隐式地将 loadLibrary() 导入到使用了 deferred as 命名空间 的类中。 loadLibrary() 函数返回的是一个 Future。

实现库

https://dart.cn/guides/libraries/create-library-packages
查阅 创建依赖库包 可以获取有关如何实现库包的建议,包括:

如何组织库的源文件。

如何使用 export 命令。

何时使用 part 命令。

何时使用 library 命令。

如何使用倒入和导出命令实现多平台的库支持。


异步支持

https://dart.cn/guides/libraries/library-tour#dartasync---asynchronous-programming
Dart 代码库中有大量返回 Future 或 Stream 对象的函数,这些函数都是 异步 的,它们会在耗时操作(比如I/O)执行完毕前直接返回而不会等待耗时操作执行完毕。

async 和 await 关键字用于实现异步编程,并且让你的代码看起来就像是同步的一样。

处理 Future

可以通过下面两种方式,获得 Future 执行完成的结果:

使用 async 和 await;

使用 Future API,具体描述参考 https://dart.cn/guides/libraries/library-tour#future

使用 async 和 await 的代码是异步的,但是看起来有点像同步代码。

例如,下面的代码使用 await 等待异步函数的执行结果。

await lookUpVersion();

必须在带有 async 关键字的 异步函数 中使用 await:

Future<void> checkVersion() async {
  var version = await lookUpVersion();
  // 使用 version 继续处理逻辑
}

尽管异步函数可以处理耗时操作,但是它并不会等待这些耗时操作完成,异步函数执行时会在其遇到第一个 await 表达式(代码行)时返回一个 Future 对象,然后等待 await 表达式执行完毕后继续执行。

使用 try、catch 以及 finally 来处理使用 await 导致的异常:

try {
  version = await lookUpVersion();
} catch (e) {
  // 无法找到版本时做出的反应
}

你可以在异步函数中多次使用 await 关键字。
例如,下面代码中等待了三次函数结果:

var entrypoint = await findEntryPoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await 表达式的返回值通常是一个 Future 对象;如果不是的话也会自动将其包裹在一个 Future 对象里。
Future 对象代表一个“承诺”,await 表达式会阻塞直到需要的对象返回。

如果在使用 await 时导致编译错误,请确保 await 在一个异步函数中使用。
例如,如果想在 main() 函数中使用 await,那么 main() 函数就必须使用 async 关键字标识。

声明异步函数 async

异步函数 是函数体由 async 关键字标记的函数。

将关键字 async 添加到函数并让其返回一个 Future 对象。
假设有如下返回 String 对象的方法:

String lookUpVersion() => '1.0.0';

将其改为异步函数,返回值是 Future:

Future<String> lookUpVersion() async => '1.0.0';

注意,函数体不需要使用 Future API。如有必要,Dart 会创建 Future 对象。

如果函数没有返回有效值,需要设置其返回类型为 Future。

处理 Stream

如果想从 Stream 中获取值,可以有两种选择:

使用 async 关键字和一个 异步循环(使用 await for 关键字标识)。

使用 Stream API。详情参考 https://dart.cn/guides/libraries/library-tour#stream

在使用 await for 关键字前,确保其可以令代码逻辑更加清晰并且是真的需要等待所有的结果执行完毕。
例如,通常不应该在 UI 事件监听器上使用 await for 关键字,因为 UI 框架发出的事件流是无穷尽的。

使用 await for 定义异步循环看起来是这样的:

await for (varOrType identifier in expression) {
  // 每当 Stream 发出一个值时会执行
}

表达式 的类型必须是 Stream。执行流程如下:

等待直到 Stream 返回一个数据。

使用 1 中 Stream 返回的数据执行循环体。

重复 1、2 过程直到 Stream 数据返回完毕。

使用 break 和 return 语句可以停止接收 Stream 数据,这样就跳出了循环并取消注册监听 Stream。

如果在实现异步 for 循环时遇到编译时错误,请检查确保 await for 处于异步函数中。

例如,要在应用程序的 main() 函数中使用异步 for 循环,main() 函数体必须标记为 async:

Future<void> main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

生成器 generator

当你需要延迟地生成一连串的值时,可以考虑使用 生成器函数。

Dart 内置支持两种形式的生成器方法:

同步 生成器:返回一个 Iterable 对象。

异步 生成器:返回一个 Stream 对象。

通过在函数上加 sync* 关键字并将返回值类型设置为 Iterable 来实现一个 同步 生成器函数,在函数中使用 yield 语句来传递值:

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

实现 异步 生成器函数与同步类似,只不过关键字为 async* 并且返回值为 Stream:

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

如果生成器是递归调用的,可是使用 yield* 语句提升执行性能:

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

可调用类 call

通过实现类的 call() 方法,允许使用类似函数调用的方式来使用该类的实例。

在下面的示例中,WannabeFunction 类定义了一个 call() 函数,函数接受三个字符串参数,
函数体将三个字符串拼接,字符串间用空格分割,并在结尾附加了一个感叹号。

class WannabeFunction {
  String call(String a, String b, String c) => '$a $b $c!';
}

var wf = WannabeFunction();
var out = wf('你好', ',使用 Dart 的', '朋友');

void main() => print(out);

隔离区

大多数计算机中,甚至在移动平台上,都在使用多核 CPU。
为了有效利用多核性能,开发者一般使用共享内存的方式让线程并发地运行。
然而,多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。

为了解决多线程带来的并发问题,Dart 使用 isolate 替代线程,
所有的 Dart 代码均运行在一个 isolate 中。
每一个 isolate 有它自己的堆内存以确保其状态不被其它 isolate 访问。

可以查阅下面的文档获取更多相关信息:

Dart 异步编程:隔离区和事件循环

dart:isolate API 参考 介绍了 Isolate.spawn() 和 TransferableTypedData 的用法

Background parsing cookbook on the Flutter site
Flutter 网站上关于后台解析的 Cookbook


类型别名 typedef

类型别名(通常称为typedef ,因为它使用关键字typedef定义词进行声明)是指类型类型的简明方法。
下面是一个声明和使用名为 IntList 的类型别名的示例:

typedef IntList = List<int>;
IntList il = [1, 2, 3];

类型别名可以具有类型参数:

typedef ListMapper<X> = Map<X, List<X>>;
Map<String, List<String>> m1 = {}; // Verbose.
ListMapper<String> m2 = {}; // Same thing but shorter and clearer.

在大多数情况下,建议使用内联功能类型而不是类型别名。但是,函数类型别名仍可能很有用:

typedef Compare<T> = int Function(T a, T b);

int sort(int a, int b) => a - b;

void main() {
  assert(sort is Compare<int>); // True!
}

元数据

使用元数据可以为代码增加一些额外的信息。
元数据注解以 @ 开头,其后紧跟一个编译时常量(比如 deprecated)或者调用一个常量构造函数。

Dart 中有两个注解是所有代码都可以使用的:@deprecated@override
你可以查阅 扩展一个类 获取有关 @OverRide 的使用示例。

元数据可以在 library、class、typedef、type parameter、 constructor、factory、function、field、parameter 或者 variable 声明之前使用,
也可以在 import 或 export 之前使用。
可使用反射在运行时获取元数据信息。

下面是使用 @deprecated 的示例:

class Television {
  /// _弃用: 使用 [turnOn] 替代_
  @deprecated
  void activate() {
    turnOn();
  }

  /// 打开 TV 的电源。
  void turnOn() {...}
}

自定义元数据注解

下面的示例定义了一个带有两个参数的 @todo 注解:

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

使用 @Todo 注解的示例:

import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

注释

Dart 支持单行注释、多行注释和文档注释。

单行注释

单行注释以 // 开始。所有在 // 和该行结尾之间的内容均被编译器忽略。

void main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

多行注释

多行注释以 /* 开始,以 */ 结尾。所有在 /**/ 之间的内容均被编译器忽略(不会忽略文档注释),
多行注释可以嵌套。

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

文档注释

文档注释可以是多行注释,也可以是单行注释,文档注释以 /// 或者 /** 开始。
在连续行上使用 /// 与多行文档注释具有相同的效果。

在文档注释中,除非用中括号括起来,否则分析器会忽略所有文本。
使用中括号可以引用类、方法、字段、顶级变量、函数和参数。
括号中的符号会在已记录的程序元素的词法域中进行解析。

一个引用其他类和成员的文档注释:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
///
/// Just like any other animal, llamas need to eat,
/// so don't forget to [feed] them some [Food].
class Llama {
  String? name;

  /// Feeds your llama [food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

在生成的文档中,[feed] 会成为一个链接,指向 feed 方法的文档, [Food] 会成为一个链接,指向 Food 类的 API 文档。

解析 Dart 代码并生成 HTML 文档,可以使用 Dart 的 文档生成工具
关于生成文档的示例,请参考 Dart API documentation
查看关于文档结构的建议,请参考文档: Guidelines for Dart Doc Comments.


Dart 编程语言规范

https://dart.cn/guides/language/spec

Mac mini 更换机械硬盘为固态硬盘

  • 克隆备份系统
  • 拆解更换硬盘
  • 打开ssd的trim功能
  • 电脑全程需打开-系统偏好设置-安全与隐私-点击允许(如果提示)

克隆系统

安装CarbonCopyCloner
CarbonCopyCloner505 Shark .dmg.zip
首次安装后打开一次,然后用资源管理器强制关闭,再次打开即可使用
左侧选择老硬盘,中间选择新硬盘后,点击clone开始,最少耗时半小时
克隆完成后打开-系统偏好设置-启动磁盘-点击解锁-选择固态硬盘-点击锁定
点击重新启动,重启需要较长时间5分钟左右
启动后点击关于本机,存储空间,固态硬盘显示在第一列即表示已从固态硬盘启动,可开始下一步

更换硬盘

图文详情 https://www.25pp.com/news/news_76521.html
取下后盖6个螺丝
取下链接后盖的固定无线螺丝
取下风扇的螺丝以及风扇
取下固定主办的螺丝
分离主板上的2个排线于主板的连接,其中之一需要取下螺丝
使用两根细改锥顶出主板5厘米后分离电源排线于主板的连接
取下硬盘的固定螺丝
取出电源左侧的固定铁片
取出电源
取出硬盘架
分离硬盘螺丝,分离硬盘贴纸,分离硬盘排线后取出硬盘
更换硬盘
反序装会

打开硬盘的trim功能

用于加快固态硬盘的读写,延长硬盘寿命
安装Trim+Enabler_4.1.2
Trim+Enabler_4.1.2_xclient.info.dmg.zip
安装后打开Trim+Enabler,打开kengen,点击open,选择已安装的Trim+Enabler,点击save,
保持kengen开启,关闭Trim+Enabler,再次打开Trim+Enabler,即可使用。
在Trim+Enabler点击开启trim,
重启电脑后,打开系统偏好设置,隐私,点击允许,出现重启电脑,确定后自动重启电脑
重启后查看概述
以下表示开启成功
1011527158147_ pic

动态配置 dynamic config

dynamic config

npm install cross-env --save-dev

./package.json

{
  "name": "1",
  "version": "0.0.1",
  "description": "dynamic config",
  "main": "main.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development TYPE=typeA node main.js",
    "build": "cross-env NODE_ENV=production TYPE=typeA node main.js",
    "clean": "find ./dist -maxdepth 1 -not -name 'project.config.json' -not -name 'dist' | xargs rm -rf",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "王树贤 <[email protected]>",
  "license": "MIT",
  "devDependencies": {
    "cross-env": "^5.1.6"
  }
}

./main.js

const config = require('./config')

console.log(config)

console.log('env.NODE_ENV in main.js::', process.env.NODE_ENV)
console.log('env.TYPE in main.js::', process.env.TYPE)

./config/index.js

const defaultConfig = require('./default.json')

console.log(defaultConfig)
// node config/index.js
// { type: 'default' }

const myType = process.env.TYPE

module.exports = (() => {
  console.log('fn')
  if (typeof defaultConfig === 'object') {
    const config = Object.assign(defaultConfig, {num: 123}, {myType})
    return config
  } else {
    console.log('defaultConfig is not json')
  }
})()

console.log('env.NODE_ENV in index.js::', process.env.NODE_ENV)

./config/default.json

{
  "type": "default"
}

JavaScript 并发模型与事件循环

JavaScript 的并发模型基于"事件循环"
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop#事件循环

default


只包含同步函数的事件循环流程

浏览器遇到script标签,开始解析,进行第一轮事件循环。

每遇到一个同步函数,创建包含该函数的参数和局部变量的栈帧,压入主线程。
运行该函数,如果函数内包含另一个函数,创建包含该函数的参数和局部变量的栈帧。压倒之前帧之上。
最上一个栈帧开始执行,该帧返回时,即弹出栈,离开主线程。
继续执行内层的帧,知道最内层的帧返回,栈为空。


React-Redux

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

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系
mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象

// 容器组件的代码
//    <FilterLink filter="SHOW_ALL">
//      All
//    </FilterLink>

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

functional paradigm

JS 函数式编程

https://legacy.gitbook.com/book/llh911001/mostly-adequate-guide-chinese/details

JavaScript 语言的基础概念-1

JavaScript 的函数是可调用的

当 hi 后面紧跟 () 的时候就会运行并返回一个值;如果没有 (),hi 就简单地返回存到这个变量里的函数

hi;
// function(name){
//  return "Hi " + name
// }

hi("jonas");
// "Hi jonas"

当我们说函数是“一等公民”的时候,我们实际上说的是它们和其他对象都一样...所以就是普通公民(坐经济舱的人?)。函数真没什么特殊的,你可以像对待任何其他数据类型一样对待它们——把它们存在数组里,当作参数传递,赋值给变量...等等。
用一个函数把另一个函数包起来,目的仅仅是延迟执行,真的是非常糟糕的编程习惯

编程界的流行的错误规范示例1:
除了徒增代码量,提高维护和检索代码的成本外,没有任何用处

// 太傻了
// 只是简单传递相同的参数
var getServerStuff = function(callback){
  return ajaxCall(function(json){
    return callback(json);
  });
};

正确示例

// 这才像样
var getServerStuff = ajaxCall;

编程界的流行的错误规范示例2
如果一个函数被不必要地包裹起来了,而且发生了改动,那么包裹它的那个函数也要做相应的变更。

httpGet('/post/2', function(json){
  return renderPost(json);
})

如果 httpGet 要改成可以抛出一个可能出现的 err 异常,那我们还要回过头去把“胶水”函数也改了

// 把整个应用里的所有 httpGet 调用都改成这样,可以传递 err 参数。
httpGet('/post/2', function(json, err){
  return renderPost(json, err);
})

正确示例
一等公民函数的形式

httpGet('/post/2', renderPost);  // renderPost 将会在 httpGet 中调用,想要多少参数都行

在函数式编程中避免使用this (原型链式垃圾代码,环境变量会被轻易的改变)


JavaScript 语言的基础概念-2

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用

slice 符合纯函数的定义是因为对相同的输入它保证能返回相同的输出。
而 splice 却会嚼烂调用它的那个数组,然后再吐出来;这就会产生可观察到的副作用,即这个数组永久地改变了。

var xs = [1,2,3,4,5];

// 纯的
xs.slice(0,3);
//=> [1,2,3]

xs.slice(0,3);
//=> [1,2,3]

xs.slice(0,3);
//=> [1,2,3]


// 不纯的
xs.splice(0,3);
//=> [1,2,3]

xs.splice(0,3);
//=> [4,5]

xs.splice(0,3);
//=> []

不纯的笨函数的依赖状态是影响系统复杂度的罪魁祸首

// 不纯的
var minimum = 21;

var checkAge = function(age) {
  return age >= minimum;
};


// 纯的
var checkAge = function(age) {
  var minimum = 21;
  return age >= minimum;
};

不可变(immutable)对象,这样就能保留纯粹性.
使用纯函数的形式,函数就能做到自给自足

var immutableState = Object.freeze({
  minimum: 21
});

JavaScript 语言的基础概念-3

副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互

只要是跟函数外部环境发生的交互就都是副作用
函数式编程的哲学就是假定副作用是造成不正当行为的主要原因
副作用可能包含,但不限于:
-- 更改文件系统
-- 往数据库插入记录
-- 发送一个 http 请求
-- 可变数据
-- 打印/log
-- 获取用户输入
-- DOM 查询
-- 访问系统状态


JavaScript 语言的基础概念-4

函数是不同数值之间的特殊关系:每一个输入值返回且只返回一个输出值,而非多个输出值

函数可以描述为一个集合,这个集合里的内容是 (输入, 输出) 对:[(1,2), (3,6), (5,10)](看起来这个函数是把输入值加倍)


纯函数的意义

纯函数就是数学上的函数,而且是函数式编程的全部

1-可缓存性(Cacheable)

纯函数总能够根据输入来做缓存。实现缓存的一种典型方式是 memoize 技术:

var squareNumber  = memoize(function(x){ return x*x; });

squareNumber(4);
//=> 16

squareNumber(4); // 从缓存中读取输入值为 4 的结果
//=> 16

squareNumber(5);
//=> 25

squareNumber(5); // 从缓存中读取输入值为 5 的结果
//=> 25
// 简单的实现,尽管它不太健壮
var memoize = function(f) {
  var cache = {};

  return function() {
    var arg_str = JSON.stringify(arguments);
    cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
    return cache[arg_str];
  };
};
通过延迟执行的方式把不纯的函数转换为纯函数

并没有真正发送 http 请求——只是返回了一个函数,当调用它的时候才会发请求。
这个函数之所以有资格成为纯函数,是因为它总是会根据相同的输入返回相同的输出:给定了 url 和 params 之后,它就只会返回同一个发送 http 请求的函数。
我们的 memoize 函数工作起来没有任何问题,虽然它缓存的并不是 http 请求所返回的结果,而是生成的函数

var pureHttpCall = memoize(function(url, params){
  return function() { return $.getJSON(url, params); }
})

2-可移植性/自文档化(Portable / Self-Documenting)

在 JavaScript 的设定中,可移植性可以意味着把函数序列化(serializing)并通过 socket 发送。也可以意味着代码能够在 web workers 中运行。总之,可移植性是一个非常强大的特性

命令式编程中“典型”的方法和过程都深深地根植于它们所在的环境中,通过状态、依赖和有效作用(available effects)达成;
纯函数与此相反,它与环境无关,只要我们愿意,可以在任何地方运行它。

面向对象语言的问题是,它们永远都要随身携带那些隐式的环境。你只需要一个香蕉,但却得到一个拿着香蕉的大猩猩...以及整个丛林

纯函数是完全自给自足的,它需要的所有东西都能轻易获得
纯函数的依赖很明确,因此更易于观察和理解——没有偷偷摸摸的小动作
纯函数对于其依赖必须要诚实,这样我们就能知道它的目的
纯函数能够提供多得多的信息
通过强迫“注入”依赖,或者把它们当作参数传递,我们的应用也更加灵活;因为数据库或者邮件客户端等等都参数化了

// 不纯的
var signUp = function(attrs) {
  var user = saveUser(attrs);
  welcomeUser(user);
};

var saveUser = function(attrs) {
    var user = Db.save(attrs);
    ...
};

var welcomeUser = function(user) {
    Email(user, ...);
    ...
};

// 纯的
var signUp = function(Db, Email, attrs) {
  return function() {
    var user = saveUser(Db, attrs);
    welcomeUser(Email, user);
  };
};

var saveUser = function(Db, attrs) {
    ...
};

var welcomeUser = function(Email, user) {
    ...
};

3-可测试性(Testable)

纯函数让测试更加容易。我们不需要伪造一个“真实的”支付网关,或者每一次测试之前都要配置、之后都要断言状态(assert the state)。只需简单地给函数一个输入,然后断言输出就好了
函数式编程的社区正在开创一些新的测试工具,能够帮助我们自动生成输入并断言输出。
Quickcheck——一个为函数式环境量身定制的测试工具。


4-合理性(Reasonable)

引用透明性(referential transparency)

如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么我们就说这段代码是引用透明的

由于纯函数总是能够根据相同的输入返回相同的输出,所以它们就能够保证总是返回同一个结果,这也就保证了引用透明性

根据一个参数修改其他参数的值

var Immutable = require('immutable');

var decrementHP = function(player) {
  return player.set("hp", player.hp-1);
};

var isSameTeam = function(player1, player2) {
  return player1.team === player2.team;
};

var punch = function(player, target) {
  if(isSameTeam(player, target)) {
    return target;
  } else {
    return decrementHP(target);
  }
};

var jobe = Immutable.Map({name:"Jobe", hp:20, team: "red"});
var michael = Immutable.Map({name:"Michael", hp:20, team: "green"});

punch(jobe, michael);
//=> Immutable.Map({name:"Michael", hp:19, team: "green"})

5-并行代码

可以并行运行任意纯函数。因为纯函数根本不需要访问共享的内存,而且根据其定义,纯函数也不会因副作用而进入竞争态(race condition)

并行代码在服务端 js 环境以及使用了 web worker 的浏览器那里是非常容易实现的,因为它们使用了线程(thread)。不过出于对非纯函数复杂度的考虑,当前主流观点还是避免使用这种并行


柯里化(curry)

使用柯里化(curry)的原因

纯函数程序写起来就有点费力
通过到处传递参数来操作数据,禁止使用状态

curry 的概念:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

高阶函数(higher order function):参数或返回值为函数的函数

每传递一个参数调用函数,就返回一个新函数处理剩余的参数。这就是一个输入对应一个输出。
哪怕输出是另一个函数,它也是纯函数。
当然 curry 函数也允许一次传递多个参数,但这只是出于减少 () 的方便。

curry 函数能够让函数式编程不那么繁琐和沉闷
通过简单地传递几个参数,就能动态创建实用的新函数;而且还能带来一个额外好处,那就是保留了数学的函数定义,尽管参数不止一个。

创建一些 curry 函数
策略性地把要操作的数据(String, Array)放到最后一个参数里

var curry = require('lodash').curry;

var match = curry(function(what, str) {
  return str.match(what);
});

var replace = curry(function(what, replacement, str) {
  return str.replace(what, replacement);
});

var filter = curry(function(f, ary) {
  return ary.filter(f);
});

var map = curry(function(f, ary) {
  return ary.map(f);
});

使用 curry 函数
这里表明的是一种“预加载”函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数

match(/\s+/g, "hello world");
// [ ' ' ]

match(/\s+/g)("hello world");
// [ ' ' ]

var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }

hasSpaces("hello world");
// [ ' ' ]

hasSpaces("spaceless");
// null

filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]

var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }

findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]

var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }

var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }

censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'

curry 函数工具库
ramda http://ramdajs.com/
lodash-fp https://github.com/lodash/lodash-fp


代码组合(compose)

两个函数组合之后返回了一个新函数
组合某种类型(本例中是函数)的两个元素本就该生成一个该类型的新元素

f 和 g 都是函数,x 是在它们之间通过“管道”传输的值。
g 将先于 f 执行,因此就创建了一个从右到左的数据流。
这样做的可读性远远高于嵌套一大堆的函数调用
让代码从右向左运行,而不是由内而外运行

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
}

使用组合

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);

shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"

顺序很重要
reverse 反转列表,head 取列表中的第一个元素;所以结果就是得到了一个 last 函数(即取列表的最后一个元素),虽然它性能不高。这个组合中函数的执行顺序应该是显而易见的。尽管我们可以定义一个从左向右的版本,但是从右向左执行更加能够反映数学上的含义

var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head, reverse);

last(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'uppercut'

结合律(associativity)
符合结合律意味着不管你是把 g 和 h 分到一组,还是把 f 和 g 分到一组都不重要

// 结合律(associativity)
var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
// true
compose(toUpperCase, compose(head, reverse));

// 或者
compose(compose(toUpperCase, head), reverse);

可变组合(variadic compose)
组合是高于其他所有原则的设计原则
如何为 compose 的调用分组不重要,所以结果都是一样的
运用结合律能为我们带来强大的灵活性,还有对执行结果不会出现意外的那种平和心态

// 前面的例子中我们必须要写两个组合才行,但既然组合是符合结合律的,我们就可以只写一个,
// 而且想传给它多少个函数就传给它多少个,然后让它自己决定如何分组。

var lastUpper = compose(toUpperCase, head, reverse);

lastUpper(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'UPPERCUT'


var loudLastUpper = compose(exclaim, toUpperCase, head, reverse)

loudLastUpper(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'UPPERCUT!'

结合律的一大好处是任何一个函数分组都可以被拆开来,然后再以它们自己的组合方式打包在一起
最佳实践是让组合可重用,就像 last 和 angry 那样。
如果熟悉 Fowler 的《重构》一书的话,你可能会认识到这个过程叫做 “extract method”——只不过不需要关心对象的状态。

var loudLastUpper = compose(exclaim, toUpperCase, head, reverse);

// 或
var last = compose(head, reverse);
var loudLastUpper = compose(exclaim, toUpperCase, last);

// 或
var last = compose(head, reverse);
var angry = compose(exclaim, toUpperCase);
var loudLastUpper = compose(angry, last);

// 更多变种...

pointfree 模式
函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式
通过管道把数据在接受单个参数的函数间传递。
利用 curry,我们能够做到让每个函数都先接收数据,然后操作数据,最后再把数据传递到下一个函数那里去。
在 pointfree 版本中,不需要 word 参数就能构造函数;而在非 pointfree 的版本中,必须要有 word 才能进行进行一切操作。

pointfree 模式能够帮助我们减少不必要的命名,让代码保持简洁和通用。
对函数式代码来说,pointfree 是非常好的石蕊试验,因为它能告诉我们一个函数是否是接受输入返回输出的小函数

// 非 pointfree,因为提到了数据:word
var snakeCase = function (word) {
  return word.toLowerCase().replace(/\s+/ig, '_');
};

// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

注意:
组合的一个常见错误是,在没有局部调用之前,就组合类似 map 这样接受两个参数的函数。

// 错误做法:我们传给了 `angry` 一个数组,根本不知道最后传给 `map` 的是什么东西。
var latin = compose(map, angry, reverse);

latin(["frog", "eyes"]);
// error


// 正确做法:每个函数都接受一个实际参数。
var latin = compose(map(angry), reverse);

latin(["frog", "eyes"]);
// ["EYES!", "FROG!"])

debug 组合
使用不纯的 trace 函数来追踪代码的执行情况
trace 函数允许我们在某个特定的点观察数据以便 debug

var trace = curry(function(tag, x){
  console.log(tag, x);
  return x;
});

var dasherize = compose(join('-'), toLower, split(' '), replace(/\s{2,}/ig, ' '));

dasherize('The world is a vampire');
// TypeError: Cannot read property 'apply' of undefined
var dasherize = compose(join('-'), toLower, trace("after split"), split(' '), replace(/\s{2,}/ig, ' '));
// after split [ 'The', 'world', 'is', 'a', 'vampire' ]
var dasherize = compose(join('-'), map(toLower), split(' '), replace(/\s{2,}/ig, ' '));

dasherize('The world is a vampire');

// 'the-world-is-a-vampire'

phpstorm

JetBrains根据.eslintrc格式化代码

步骤1:

Preferences | Language & Frameworks | JavaScript | Code Quality Tools | ESLint->Configuration file
在输入框里输入或选择你的.eslintrc文件
phpstorm-eslint-1

步骤2:

Preferences | Keymap->搜索“eslint”关键字,找到Fix ESLint Problems,添加你喜欢的快捷键。
phpstorm-eslint-2


svn

https://subversion.apache.org/packages.html

svn

检出项目

svn checkout svn://192.168.1.1/pro/domain

更新项目
如果后面没有目录,默认将当前目录以及子目录下的所有文件都更新到最新版本

svn update

配置全局忽略文件

svn propset svn:ignore -R -F .svnignore .

.svnignore 示例

dist
node_modules
.idea
.wepycache
.DS_Store
*.wpy___jb_tmp___

查看状态

svn status
svn status --no-ignore

递归添加当前目录下的所有新文件

svn add . --force

递归添加当前目录下的所有新文件,包括已忽略

svn add . --no-ignore --force

提交代码

svn commit -m "提交描述"

Design Pattern 设计模式

Design Pattern

所谓模式,就是特定于一种问题场景的解决办法。

模式(Pattern) = 问题场景(Context) + 解决办法(Solution)

Vue

安装,升级,卸载

卸载 npm uninstall -g vue-cli
推荐直接删除系统目录node_modules/vue-cli

安装 npm install -g @vue/cli
vue create my-project

cd my-project
npm run serve
空格选择取消,回车确定

或者 npx -p vue-cli vue create my-project // 旧版
npx -p @vue/cli vue create my-project // 新版

在声明组件时,使用基于类(class-based)的 API,则可以使用官方维护的 vue-class-component 装饰器:

https://github.com/vuejs/vue-class-component

Vue

Vue实例1
index.js

import Vue from 'vue'

new Vue({
    el:'#root',
    template:'<div>main</div>'
})

Vue实例2
index.js

import Vue from 'vue'
import App from './app.vue'

import './assets/styles/global.styl'

const root =document.createElement('div')
document.body.appendChild(root)

new Vue({
    render:(h)=>h(app)
}).$mount(root)

Vue实例3
index.js

import Vue from 'vue'

const app=new Vue({
    //el:'#root',
    template:'<div>main</div>'
})

app.$mount('#root')

vue实例属性

import Vue from 'vue'

const app = new Vue({
    //el:'#root',
    template: "<div ref='div'>{{text}}</div>",
    data: {
        text: 0
    }
})

app.$mount('#root')

app.text += 1 // 响应式

console.log(app.$data)
console.log(app.$props)
console.log(app.$el) // <div>1</div> // 节点
console.log(app.$options) // new Vue 时传入的整个对象
app.$options.data.text += 1 // 非响应式,
app.$data.text += 1 // 响应式

setTimeout(() => {
    app.text += 1
}, 1000);

app.$options.render = (h) => {
    return h('div', {}, 'new render function')
}

// 只在下一次数据更新时起作用
// 1秒后 页面由数字变为 new render function

console.log(app.$root) // 根节点,Vue 对象
console.log(app.$root === app) // true

console.log(app.$children) // 根节点的子节点

console.log(app.$slote)
console.log(app.$scopedSlots) // 插槽

console.log(app.$refs) //空对象或DOM节点对象或组件实例 app.$refs.div

console.log(app.$isServer)

vue实例方法-Watch 属性监听

import Vue from 'vue'

const app = new Vue({
    //el:'#root',
    template: "<div ref='div'>{{text}}</div>",
    data: {
        text: 0
    },
    // 组件退出自动销毁,防止内存溢出
    watch: {
        text(newText, oldText) {
            // 监听text属性值变化
            console.log(`${newText}:${oldText}`)
        }
    }
})

app.$mount('#root')

// 组件退出不会自动销毁
const unWatch = app.$watch('text', (newText, oldText) => {
    // 监听text属性值变化
    console.log(`${newText}:${oldText}`)
})

unWatch() // 调用方法即可手动销毁watch监听,防止内存溢出

vue实例方法- 事件监听

import Vue from 'vue'

const app = new Vue({
    //el:'#root',
    template: "<div ref='div'>{{text}}</div>",
    data: {
        text: 0
    }
})

app.$mount('#root')

//$on与$emit需要同时作用于同一个Vue对象上(app)才会生效
//不会冒泡
// 监听事件
app.$on('test', (a, b) => {
    console.log(`test emited :${a}-${b}`)
})

// 触发事件
app.$emit('test', 1, 2)

// 监听事件,只触发一次
app.$once('test2', (a, b) => {
    console.log(`test emited :${a}-${b}`)
})

// 触发事件
app.$emit('test2', 1, 2)

vue实例方法 设置数据

import Vue from 'vue'

const app = new Vue({
    //el:'#root',
    template: "<div ref='div'>{{text}}</div>",
    data: {
        text: 0,
        obj: {}
    }
})

app.$mount('#root')

app.obj.a = 1 // 新增对象属性非响应式
app.$forceUpdate() // 强制组件重新渲染一次,属性对象添加新值时,不建议使用,影响性能

app.$set(app.obj, 'a', 1) //响应式
app.$delete() //删除值

app.$nextTick(() => { }) //下一次dom更新时调用 //解决vue异步队列更新延迟渲染

vue实例生命周期方法 lifecycle

import Vue from 'vue'

const app = new Vue({
    el: '#root',
    // template: "<div ref='div'>{{text}}</div>", //比较耗时
    data: {
        text: 0,
        obj: {}
    },
    beforeCreate() {
        // 一次性
        // 禁止数据操作
        console.log('beforeCreate', this.$el)
        // undefined
    },
    created() {
        // 一次性
        // 可以执行与数据有关的操作
        console.log('created', this.$el)
        // undefined
    },
    beforeMount() {
        // 一次性, 服务端渲染不会调用,服务端没有dom环境
        // 可以执行与数据有关的操作
        console.log('beforeMount', this.$el)
        // <div id='root'></div>
        // 挂载节点
    },
    render(h) {
        // 比较耗时
        // 如果没有 template,则执行
        // 创建template并渲染
        return h('div', {}, this.text)
    },
    mounted() {
        // 一次性, 服务端渲染不会调用,服务端没有dom环境
        // 挂载dom
        // 执行与dom有关的操作
        console.log('mounted', this.$el)
        // <div>0</div>
        // 覆盖挂载节点
    },
    beforeUpdate() {
        // 数据更新前
        console.log('beforeUpdate', this)
    },
    updated() {
        // 数据更新时
        console.log('updated', this)
    },
    activated() {
        console.log('activated', this)
    },
    deactivated() {
        console.log('deactivated', this)
    },
    beforeDestroy() {
        console.log('beforeDestroy', this)
    },
    destroyed() {
        // 组件销毁时
        console.log('destroyed', this)
    },
    renderError(h, error) {
        // 仅在开发环境执行
        // 只在本组件发生错误时调用,不会冒泡,不会捕获子组件错误
        return h('div', {}, error.stack)
    },
    errorCaptured() {
        // 正式环境也有效
        // 可收集线上环境错误信息
        // 向上冒泡,可以捕获所有子组件错误
    }
})

setTimeout(() => {
    app.destroy() // 手动销毁组件,不推荐
}, 1000)

vue 数据绑定,事件绑定

import Vue from 'vue'

// v-html 显示标签之中所有的内容,可以防止被vue转义,会被注入攻击。
const app = new Vue({
    el: '#root',
    template: `
        <div ref='div'>{{isActive ? 'active' : 'not active'}}</div>
        <div>{{getJoinedArr(arr)}}</div>
        <div v-bind:id='mainId' v-on:click='handleClick'>
            <p :id='mainId2' @click='handleClickP' v-html='html'></p>
        </div>
    `, //比较耗时
    data: {
        isActive: false,
        html: '<span>123</span>', //vue默认会转译字符串,防止注入攻击
        arr: [1, 2, 3],
        mainId: 'main',
        mainId2: 'main2'
    },
    methods: {
        // vue 事件已经优化
        handleClick() {

        },
        handleClickP() {

        },
        getJoinedArr(arr) {
            return arr.join(' ')
        }
    }
})

vue 样式绑定

import Vue from 'vue'

// v-html 显示标签之中所有的内容,可以防止被vue转义,会被注入攻击。
const app = new Vue({
    el: '#root',
    template: `
        <div :class="{active : isActive}"></div>
        <div :class="[isActive ? 'active' : '']"></div>
        <div :class="[{active : isActive}]"></div>
        <div :style='styles'></div>
        <div :style="[styles,styles2]"></div>
    `,
    // isActive为true则使用active
    // :style 会自动补全样式前缀
    data: {
        isActive: false,
        styles: {
            color: 'red',
            appearance: 'none'
        },
        styles2: {
            color: 'black'
        }
    },
    computed: {
        classNames() {

        }
    }
})

vue computed, watch

import Vue from 'vue'

// v-html 显示标签之中所有的内容,可以防止被vue转义,会被注入攻击。
const app = new Vue({
    el: '#root',
    template: `
        <div>
            <span>name:{{firstName + ' ' + lastName}}</span>
            <span>name:{{name}}</name>
            <span>name:{{getName()}}</name>
            <p>number:{{number}}</p>
            <p><input type='text' v-model="number" /></p>

            <p>{{firstName}}</p>
            <input v-model="name2" />

            <p>{{fullName}}</p>
        </div>
    `,
    data: {
        firstName: 'harry',
        lastName: 'potter',
        number: 0,
        fullName: '',
        obj: { a: '' }
    },
    computed: {
        // 在computed对象中可以声明很多方法,返回数据
        // computed是类中的get方法
        // 会缓存正在依赖的data数据以提高性能,开销小
        // 适合大数据量
        // 不可修改正在依赖的data值
        name() {
            console.log('new name')
            return `${this.firstName} ${this.lastName}`
        },
        // 可设置
        name2: {
            get() {
                return `${this.firstName} ${this.lastName}`
            },
            set(name) {
                const names = name.split(' ')
                this.firstName = names[0]
                this.lastName = names[1]
            }
        }
    },
    watch: {
        // watch不适用于显示数据
        // watch适用于监听到数据改变时,向服务端发送请求
        // 不可修改正在依赖的data值,否则导致无限循环触发watch事件
        firstName(newName, oldName) {
            // 初始绑定不会执行
            this.fullName = newName + ' ' + this.lastName
        },
        lastName: {
            // 初始绑定后会立即执行一次
            handler(newName, oldName) {
                this.fullName = this.firsttName + ' ' + newName
            },
            immediate: true,
            deep: true //适用于对象值,开销大,监听对象值,修改对象属性时必须使用,直接修改对象整体可为false,
        },
        'obj.a': {
            // 使用字符串表示对象深入属性调用,可以降低开销,只监听最后的属性值
            // 初始绑定后会立即执行一次
            handler(newName, oldName) {
                this.fullName = this.firsttName + ' ' + newName
            },
            immediate: true,
            deep: false
        }
    },
    methods: {
        // 没有缓存,开销大,实时数据
        getName() {
            console.log('get name')
            return `${this.firstName} ${this.lastName}`
        }
    }
})

vue 原生指令 directive
v-text 完全替换子节点内容(innerText)
v-html (innerHtml)
v-show 是否显示该节点 (原理::style="display:none"),仍然在dom中
v-if ,v-else-if,v-else是否将节点插入dom,动态增删节点,引起重绘,性能低,大量节点非常耗时。
v-for 循环数组或对象,key使用数据绑定唯一值,不推荐使用index,因为与数组变化无关,无法节省性能,可能导致错误缓存
v-on,@,在vue对象实例上绑定事件,会寻找dom节点或使用$on绑定组件
v-bind
v-model ,默认只用于input,可改变数值。checkbox :value="1",使用动态解析,值为1,非字符串
v-model.number 数值修饰符,将输入的字符串转为数值型.input 输入默认为字符型
v-model.trim 去除首尾空格
v-model.lazy 输入完毕才触发更新
v-pre 不解析子节点,不解析{{}}
v-cloak 直接引用vue时防止双花括号的闪现
v-once 数据绑定只执行一次,再次变化数据不会更新渲染。不再检测虚拟dom数据,节省开销。

import Vue from 'vue'

// v-html 显示标签之中所有的内容,可以防止被vue转义,会被注入攻击。
const app = new Vue({
    el: '#root',
    template: `
        <div>
            <p>name:{{text}}</p>
            <p v-text="text"></p>
            <p v-text="'name:' + text"></p>
            <p v-html="html"></p>

            <p v-show="active"></p>
            <p v-if="active"></p>
            <p v-else-if="text===0">else if content</p>
            <p v-else>else content</p>

            <ul>
                <li v-for="item in arr" :key="item">{{item}}</li>
            </ul>

            <ul>
                <li v-for="(item,index) in arr" :key="item">{{item}}:{{index}}</li>
            </ul>

            <ul>
                <li v-for="(value,key) in obj" :key="value">{{value}}:{{key}}</li>
            </ul>

            <ul>
                <li v-for="(value,key,index) in obj" :key="value">{{value}}:{{key}}:{{index}}</li>
            </ul>

            <p v-on:click="a">click</p>
            <p @click="b">click2</p>

            <p>{{text1}}</p>
            <input text="text" v-model="text1" />

            <input type="checkbox" v-model="active" />

            <div>
                <input type="checkbox" :value="1" v-model="arr" />
                <input type="checkbox" :value="2" v-model="arr" />
                <input type="checkbox" :value="3" v-model="arr" />
            </div>

            <div>
                <input type="radio" value="one" v-model="picked" />
                <input type="radio" value="two" v-model="picked" />
            </div>

            <input text="text" v-model.number="text1" />
            <input text="text" v-model.trim="text1" />
            <input text="text" v-model.lazy="text1" />

            <p v-pre>name:{{text}}</p>
            <p v-cloak>name:{{text}}</p>
            <p v-once>name:{{text}}</p>
        </div>
    `,
    data: {
        text: 0,
        active: false,
        html: '<span>main</span>',
        arr: [1, 2, 3], //绑定选中的值,默认checkbox全部选中,数组中的值对应多选框已选项的值,修改选中项,该数组也会改变 //checkbox未指定value时,value='on'
        obj: {
            a: 10,
            b: 20,
            c: 30
        },
        text1: 0,
        picked: '', //绑定选中的值,默认为空表示都不选中。 选中radio时,值会变为 'one' 或 'two'
    },
    methods: {
        a() {

        },
        b() {

        }
    }
})

vue 组件

vue 定义全局组件

import Vue from 'vue'

// 二级组件
const component = {
    template: `
        <div>
            component
        </div>
    `
}

// 定义全局组件 comp
Vue.component('CompOne', component)

// 根组件
const app = new Vue({
    el: '#root',
    template: '<CompOne></CompOne>',
    //template:'<comp-one></comp-one>',
})

vue 定义局部组件

import Vue from 'vue'

// 二级组件
const component = {
    template: `
        <div>
            {{text}}
        </div>
    `
}

// 根组件
const app = new Vue({
    // 局部组件,当前组件内使用
    components: {
        CompOne: component
    },
    el: '#root',
    template: `
        <div>
            
        </div>
    `,
})

vue 父子组件传参-1

import Vue from 'vue'

// 二级组件
const component = {
    template: `
        <div>
            {{text}}
            <span v-show="active">can see</span>
            {{propOne}}
            <span @click="handleChange"></span>
        </div>
    `,
    data() {
        // data不是通过new Vue内生成,需要以函数形式定义,返回新建对象,防止数据重叠
        return {
            text: 123
        }
    },
    props: {
        // 使组件可配置,通过父组件约束本组件
        // 不可以在组件内修改本组件的props的值,可以通过事件通知父组件修改
        // 单项数据流
        active: Boolean,
        propOne: String,
        onChange: Function
    },
    methods: {
        handleChange() {
            this.onChange()
        }
    }
}

// 根组件
const app = new Vue({
    // 局部组件,当前组件内使用
    components: {
        CompOne: component
    },
    el: '#root',
    template: `
        <div>
            <CompOne :active="true" :prop-one="prop" :onChange="handleChange"></CompOne>
            <CompOne :active="false" propOne="text2"></CompOne>
        </div>
    `,
    data: {
        prop: 0
    },
    //template:'<comp-one></comp-one>',
    methods: {
        handleChange() {
            this.prop += 1
        }
    }
})

vue 父子组件传参-2,推荐
通过this.$emit('change') 触发自身组件的事件属性 @change // 在父组件内的自身组件

import Vue from 'vue'

// 二级组件
const component = {
    template: `
        <div>
            {{text}}
            <span v-show="active">can see</span>
            {{propOne}}
            <span @click="handleChange"></span>
        </div>
    `,
    data() {
        // data不是通过new Vue内生成,需要以函数形式定义,返回新建对象,防止数据重叠
        return {
            text: 123
        }
    },
    props: {
        // 使组件可配置,通过父组件约束本组件
        // 不可以在组件内修改本组件的props的值,可以通过事件通知父组件修改
        // 单项数据流
        active: Boolean,
        propOne: String,
        // onChange: Function // 使用 this.$emit('change') 后,无需定义此处
    },
    methods: {
        handleChange() {
            // this.onChange()
            this.$emit('change')
            // 触发自身组件的事件属性 @Change // 在父组件内的自身组件
        }
    }
}

// 根组件
const app = new Vue({
    // 局部组件,当前组件内使用
    components: {
        CompOne: component
    },
    mounted() {
        console.log(this.$refs.comp1) // 组件实例
    },
    el: '#root',
    template: `
        <div>
            <-- <CompOne :active="true" :prop-one="prop" :onChange="handleChange"></CompOne> -->
            <CompOne ref="comp1" :active="true" :prop-one="prop" @change="handleChange"></CompOne>
            <CompOne :active="false" propOne="text2"></CompOne>
        </div>
    `,
    data: {
        prop: 0
    },
    //template:'<comp-one></comp-one>',
    methods: {
        handleChange() {
            this.prop += 1
        }
    }
})

vue props 数据验证

props: {
        // 使组件可配置,通过父组件约束本组件
        // 不可以在组件内修改本组件的props的值,可以通过事件通知父组件修改
        // 单项数据流
        active: {
            type: Boolean, // 类型
            required: true, // 是否必须
            default: false, // 默认值 // 如果为true,可以不定义属性 // default 与 required 一般不会同时使用
        },
        obj2: {
            default() {
                // 对象值必须使用函数返回新对象.防止组件共用数据
                return {

                }
            },
            validator(value) {
                // 自定义验证传入的props值
                // 无需type
                return typeof value === 'object'
            }
        },
        propOne: String,
        // onChange: Function // 使用 this.$emit('change') 后,无需定义此处
    }

vue 组件继承-1

import Vue from 'vue'

// 仅是对象
const component = {
    template: `
        <div>
           {{propOne}}
        </div>
    `,
    props: {
        active: Boolean,
        propOne: String,
    },
    data() {
        return {
            text: 0
        }
    },
    methods: {
        handleChange() {
        }
    },
    mounted() {

    }
}

// 扩展对象,得到Vue类的一个子类,并传入component对象配置文件
const CompVue = Vue.extend(component)

// 直接声明一个组件并挂在到root节点
new CompVue({
    el: '#root',
    propsData: {
        // 定义属性
        propOne: 'prop-1'
    },
    data: {
        // 定义data,与对象data合并,相同data属性会覆盖
        text: 123
    },
    mounted() {
        // 与对象内的合并,并且不会覆盖,后调用
    }
})

vue 组件继承,扩展,适用于对公用组件的继承和扩展

import Vue from 'vue'

// 仅是对象
const component = {
    template: `
        <div>
           {{propOne}}
        </div>
    `,
    props: {
        active: Boolean,
        propOne: String,
    },
    data() {
        return {
            text: 0
        }
    },
    methods: {
        handleChange() {
        }
    },
    mounted() {

    }
}

// 
const componet2 = {
    extends: compoent, //继承自compoent,扩展组件,覆盖数据,添加新属性
    data() {
        return {
            text: 1
        }
    }
}

// 直接声明一个组件并挂在到root节点
new Vue({
    el: '#root',
    components: {
        Comp: componet2
    },
    template: `<comp></comp>`
})

vue 自定义双向绑定,实现v-model

import Vue from 'vue'

// 仅是对象
const component = {
    template: `
        <div>
           {{propOne}}
           <input type="text" @input="handleInput" :value="value" />
        </div>
    `,
    props: {
        active: Boolean,
        propOne: String,
        value: ''
    },
    data() {
        return {
            text: 0
        }
    },
    methods: {
        handleChange() {
        },
        handleInput(event) {
            // 触发在父组件上的自身组件的input,并传入值
            this.$emit('input', event.target.value)
        }
    },
    mounted() {

    }
}

// 
const componet2 = {
    extends: compoent, // 继承自compoent,扩展组件,覆盖数据,添加新属性
    data() {
        return {
            text: 1
        }
    }
}

// 直接声明一个组件并挂在到root节点
new Vue({
    el: '#root',
    components: {
        Comp: componet2
    },
    data() {
        return {
            value: '123'
        }
    },
    // input 事件在自组件内被触发,执行 value=arguments[0]。
    // 接受arguments,并将arguments[0]赋值给value
    template: `<comp :value="value" @input=" value=arguments[0] "></comp>`
})

vue 插槽,用来定义布局组件,在父组件内调用,以放置内容
具名插槽

import Vue from 'vue'

// 布局组件,只有样式,不放内容
const component = {
    template: `
        <div :style="style">
           <div class="header">
                <slot name="header" ></slot>
           </div>

           <div class="body">
                <slot name="body" ></slot>
           </div>
        </div>
    `,
    props: {

    },
    data() {
        return {
            style: {
                width: '200px',
                height: '200px',
                border: "1px solid #aaa"
            }
        }
    },
    methods: {

    }
}

// 
const componet2 = {
    extends: compoent, // 继承自compoent,扩展组件,覆盖数据,添加新属性
    data() {
        return {
            text: 1
        }
    }
}

// 直接声明一个组件并挂在到root节点
new Vue({
    el: '#root',
    components: {
        Comp: componet2
    },
    data() {
        return {
            value: '123'
        }
    },
    // 直接在组件内放置内容无效,需要使用插槽
    template: `
        <comp">
            <span slot="header">header content</span>
            <span slot="body">body content</span>
        </comp>
        `
})

vue 作用域插槽

import Vue from 'vue'

// 布局组件,只有样式,不放内容
const component = {
    template: `
        <div :style="style">
           <slot value="3"></slot>
        </div>
    `,
    props: {

    },
    data() {
        return {
            style: {
                width: '200px',
                height: '200px',
                border: "1px solid #aaa"
            },
            a: 1
        }
    },
    methods: {

    }
}

// 
const componet2 = {
    extends: compoent, // 继承自compoent,扩展组件,覆盖数据,添加新属性
    data() {
        return {
            text: 1
        }
    }
}

// 直接声明一个组件并挂在到root节点
new Vue({
    el: '#root',
    components: {
        Comp: componet2
    },
    data() {
        return {
            text: 1
        }
    },
    mounted() {
        console.log(this.$refs.comp.a) // 1,组件实例的值,不推荐调用实例
        console.log(this.$refs.span) // DOM 对象
    },
    // 直接在组件内放置内容无效,需要使用插槽
    // 作用域插槽,添加变量范围,使用定义在组件slot上的属性值.同时仍可调用本地属性
    template: `
        <comp ref="comp">
            <span slot-scope="props" ref="span">{{props.value}}:{{text}}</span>
        </comp>
        `
})

vue provide,不推荐,可能会被废弃,提供跨层级通信

import Vue from 'vue'

const ChildComponent = {
    template: `
    <div>child:{{data.value}}</div>
`,
    mounted() {
        console.log(this.$parent.$options.name)
        console.log(this.grandFather) // 祖父实例
    },
    inject: ['grandFather', 'data'], // 注入grandFather,data // 必须存在树状结构关系
}

const component = {
    name: 'comp',
    components: {
        ChildComponent
    },
    template: `
        <div>
           <ChildComponent />
        </div>
    `,
    props: {

    },
    data() {
        return {
            a: 1
        }
    },
    methods: {

    }
}

// 
const componet2 = {
    extends: compoent, // 继承自compoent,扩展组件,覆盖数据,添加新属性
    data() {
        return {
            text: 1
        }
    }
}

// 直接声明一个组件并挂在到root节点
new Vue({
    el: '#root',
    components: {
        Comp: componet2
    },
    provide() {
        // 默认非响应式,只提供一次数据,不会实时更新 // 除非定义
        const data = {}
        Object.defineProperty(data, 'value', {
            get: () => this.value, // 在子组件调用value 时,调用get(),返回最新的this.value
            enumerable: true // 允许被读取
        })

        return {
            grandFather: this, // 将自生提供出去以引用
            data: data
        }
    },
    data() {
        return {
            text: 1,
            value: 1
        }
    },
    mounted() {

    },
    template: `
        <comp ></comp>
        `
})

vue render function ,虚拟DOM,VNode

import Vue from 'vue'

// 布局组件,只有样式,不放内容
const component = {
    name: 'comp',
    // template: `
    //     <div :style=""style>
    //         <slot></slot>
    //     </div>
    // `,
    props: {

    },
    data() {
        return {
            a: 1,
            style: {
                color: 'red'
            }
        }
    },
    methods: {

    },
    render(createElement) {
        return createElement(
            'div',
            {
                style: this.style
            },
            // 没有名字的slot,如果有名字使用 this.$slots.xxx
            this.$slots.default
        )
    }
}

// 
const componet2 = {
    extends: compoent, // 继承自compoent,扩展组件,覆盖数据,添加新属性
    data() {
        return {
            text: 1
        }
    }
}

// 直接声明一个组件并挂在到root节点
new Vue({
    el: '#root',
    components: {
        Comp: componet2
    },
    data() {
        return {
            text: 1,
            value: 1
        }
    },
    mounted() {

    },
    // template: `
    //     <comp ref="comp" >
    //         <span ref="span">{{value}}</span>
    //     </comp>
    // `,
    render(createElement) {
        // 虚拟DOM,创建VNode类,存储在内存中。与真实DOM对比,将差异更新到DOM,提高性能。
        // template模版的最终编译渲染。等同直接声明template属性
        // return this.$createElement() // render()  也会自动传入此函数
        return createElement(
            'comp',
            {
                // 传递属性
                ref: 'comp'
            },
            [
                createElement(
                    'span',
                    {
                        ref: 'span'
                    },
                    this.value
                )
            ])
    }
})

vue


vue


vue


Javascript utils

Javascript 工具函数整理

arrayToObject.js
https://github.com/redux-utilities/redux-actions/blob/master/src/utils/arrayToObject.js

export default (array, callback) =>
  array.reduce(
    (partialObject, element) => callback(partialObject, element),
    {}
  );

camelCase.js
https://github.com/redux-utilities/redux-actions/blob/master/src/utils/camelCase.js

import camelCase from 'lodash/camelCase';

const namespacer = '/';

export default type =>
  type.indexOf(namespacer) === -1
    ? camelCase(type)
    : type
        .split(namespacer)
        .map(camelCase)
        .join(namespacer);

compose合并函数依次执行 - 来源redux

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

数字转为千分位字符

/**
 * 数字转为千分位字符
 * @param {Number} num
 * @param {Number} point 保留几位小数,默认2位
 */
function parseToThousandth(num, point = 2) {
  let [sInt, sFloat] = (Number.isInteger(num) ? `${num}` : num.toFixed(point)).split('.')
  sInt = sInt.replace(/\d(?=(\d{3})+$)/g, '$&,')
  return sFloat ? `${sInt}.${sFloat}` : `${sInt}`
}

带延时功能的链式调用

// 1) 调用方式
new People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian')
// 2) 打印结果
(等待3s)--> 'whr eat apple' -(等待5s)--> 'whr eat durian'
// 3) 以下是代码实现
class People {
  constructor(name) {
    this.name = name
    this.queue = Promise.resolve()
  }
  eat(food) {
    this.queue = this.queue.then(() => {
      console.log(`${this.name} eat ${food}`)
    })
    return this
  }
  sleep(time = 0) {
    this.queue = this.queue.then(() => new Promise(res => {
      setTimeout(() => {
        res()
      }, time)
    }))
    return this
  }
}

一行代码实现简单模版引擎

function template(tpl, data) {
  return tpl.replace(/{{(.*?)}}/g, (match, key) => data[key.trim()])
}
// 使用:
template('我是{{name}},年龄{{age}},性别{{sex}}', {name: 'aa', age: 18, sex: '男'})
// "我是aa,年龄18,性别男"

循环对象

buildSignString(responseData={page:1,appId:123456}){
      let signArr
      for (let key in responseData){
        // console.log('key--',key) page,appId
        // console.log('value--',responseData[key]) 1,123456
      }
      console.log('signArr---',signArr)
    }


中文url编码

newString = encodeURI(string)

去掉字符串首位

const a='abcd'

const b=a.slice(1,-1)

console.log(b)
// bc

将一位数组分割成每三个一组

var data = ['法国','澳大利亚','智利','新西兰','西班牙','加拿大','阿根廷','美国','0','国产','波多黎各','英国','比利时','德国','意大利','意大利',];
var result = [];
for(var i=0,len=data.length;i<len;i+=3){
   result.push(data.slice(i,i+3));
}

[['法国','澳大利亚','智利'],['新西兰','西班牙','加拿大'],['阿根廷','美国','0'],['国产','波多黎各','英国'],['比利时','德国','意大利'],['意大利'],]

数组浅拷贝

const newImages = state.list.slice(0)

JS获取URL中参数值(QueryString)

function getQueryString(name) {
    let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
    let r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配
    let context = "";
    if (r != null)
        context = r[2];
    reg = null;
    r = null;
    return context == null || context == "" || context == "undefined" ? "" : context;
}
alert(getQueryString("q"));

检测是否为 PC 端浏览器

export const isPC = () => { //是否为PC端
  const userAgentInfo = navigator.userAgent;
  const Agents = ["Android", "iPhone",
    "SymbianOS", "Windows Phone",
    "iPad", "iPod"];
  let flag = true;
  for (let v = 0; v < Agents.length; v++) {
    if (userAgentInfo.indexOf(Agents[v]) > 0) {
      flag = false;
      break;
    }
  }

  return flag;
}

判断是否是在safari浏览器中打开

var issafariBrowser = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);

横竖屏检测最佳实践

export const isHorizontalScreen = () => {
  const clientWidth = document.documentElement.clientWidth;
  const screenWidth = window.screen.width;
  const screenHeight = window.screen.height;
  // 2.在某些机型(如华为P9)下出现 srceen.width/height 值交换,所以进行大小值比较判断
  const realScreenWidth = screenWidth < screenHeight ? screenWidth : screenHeight;
  const realScreenHeight = screenWidth >= screenHeight ? screenWidth : screenHeight;
  if (clientWidth == realScreenWidth) {
    // 竖屏
    return false
  }
  if (clientWidth == realScreenHeight) {
    // 横屏
    return true
  }
}

image to dataurl

export function getDataUri(url, callback) {
    let image = new Image();

    image.onload = function () {
        let canvas = document.createElement('canvas');
        canvas.width = image.width; // or 'width' if you want a special/scaled size
        canvas.height = image.height; // or 'height' if you want a special/scaled size

        canvas.getContext('2d').drawImage(image, 0, 0);

        // Get raw image data
        //callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));

        // ... or get as Data URI
        callback(canvas.toDataURL('image/png'));
    };

    image.src = url;
}

// Usage
getDataUri('/logo.png', function (dataUri) {
    // Do whatever you'd like with the Data URI!
});

计时

console.time()和console.timeEnd()方法
Chrome等浏览器自带一个console.time()和console.timeEnd()方法,能够用更简单的代码实现上述功能。
当需要统计一段代码的执行时间时,可以使用console.time方法与console.timeEnd方法,其中console.time方法用于标记开始时间,console.timeEnd方法用于标记结束时间,并且将结束时间与开始时间之间经过的毫秒数在控制台中输出。这两个方法的使用方法如下所示。
console.time(label)
console.timeEnd(label)
这两个方法均使用一个参数,参数值可以为任何字符串,但是这两个方法所使用的参数字符串必须相同,才能正确地统计出开始时间与结束时间之间所经过的毫秒数。

console.time(label)
console.timeEnd(label)

let start = window.performance.now();
...
let end = window.performance.now();
let time = end - start;

去掉当前页面的 hash 而不刷新页面

window.history.pushState(
    {},
    '',
    window.location.href.slice(
        0,
        window.location.href.indexOf('#')
            ? window.location.href.indexOf('#')
            : 0))

获取前N天的日期

方法一: 用setDate();

function getDate(index){
	let date = new Date(); //当前日期
	let newDate = new Date();
	newDate.setDate(date.getDate() + index);//官方文档上虽然说setDate参数是1-31,其实是可以设置负数的
	let time = newDate.getFullYear()+"-"+(newDate.getMonth()+1)+"-"+newDate.getDate();
	return time;
}
console.log(getDate(7)); // 2019-7-10
console.log(getDate(-7)); // 2019-6-26

方法二: 时间戳进行转换

let date= new Date();
let newDate = new Date(date.getTime() - 7*24*60*60*1000);
let time = newDate.getFullYear()+"-"+(newDate.getMonth()+1)+"-"+newDate.getDate();
conosole.log(time);

// 获取近n天的日期列表

function getDateList(index) {
  let list = []
  if (index >= 0) {
    for (let i = 0; i < index; i++) {
      list.push(getDate(i))
    }
  } else {
    for (let i = 0; i > index; i--) {
      list.push(getDate(i))
    }
    list.reverse()
  }
  return list
}

console.log(getDateList(-2)) // ["2019-7-2", "2019-7-3"]

复制内容到剪贴板

function copy(content) {
    const input = document.createElement('input');
    input.setAttribute('readonly', 'readonly');
    input.setAttribute('value', content);
    document.body.appendChild(input);
    input.setSelectionRange(0, 9999);
    input.select()
    if (document.execCommand('copy')) {
      const result = document.execCommand('copy');
      if (result) {
        console.log('复制成功');
      } else {
        console.warn('复制失败')
      }

    }
    document.body.removeChild(input);
  }

URLSearchParams 查询参数

假设浏览器的url参数是 "?name=蜘蛛侠&age=16"

new URLSearchParams(location.search).get("name"); // 蜘蛛侠

classList

这是一个对象,该对象里封装了许多操作元素类名的方法:

<p class="title"></p>

let elem = document.querySelector("p");

// 增加类名
elem.classList.add("title-new"); // "title title-new"

// 删除类名
elem.classList.remove("title"); // "title-new"

// 切换类名(有则删、无则增,常用于一些切换操作,如显示/隐藏)
elem.classList.toggle("title"); // "title-new title"

// 替换类名
elem.classList.replace("title", "title-old"); // "title-new title-old"

// 是否包含指定类名
elem.classList.contains("title"); // false

online state

监听当前的网络状态变动,然后执行对应的方法:

window.addEventListener("online", xxx);

window.addEventListener("offline", () => {
  alert("你断网啦!");
});
  1. deviceOrientation

陀螺仪,也就是设备的方向,又名重力感应,该API在IOS设备上失效的解决办法,将域名协议改成https;

从左到右分别为alpha、beta、gamma;

window.addEventListener("deviceorientation", event => {
  let {
    alpha,
    beta,
    gamma
  } = event;

  console.log(`alpha:${alpha}`);
  console.log(`beta:${beta}`);
  console.log(`gamma:${gamma}`);
});

使用场景:页面上的某些元素需要根据手机摆动进行移动,达到视差的效果,比如王者荣耀进入游戏的那个界面,手机转动背景图会跟着动😂

orientation

可以监听用户手机设备的旋转方向变化;

 window.addEventListener("orientationchange", () => {
  document.body.innerHTML += `<p>屏幕旋转后的角度值:${window.orientation}</p>`;
}, false);

也可以使用css的媒体查询:

/* 竖屏时样式 */
@media all and (orientation: portrait) {
  body::after {
    content: "竖屏"
  }
}

/* 横屏时样式 */
@media all and (orientation: landscape) {
  body::after {
    content: "横屏"
  }
}

使用场景:页面需要用户开启横屏来获得更好的体验,如王者荣耀里面的活动页😂

PHP

php消息队列

使用场景:

数据冗余。需要持久化数据并处理。

解耦--将系统分割为入队系统和出队系统,系统之间不会互相影响。

  • 队列处理订单系统
  • 配送系统

流量削峰,配合缓存--特殊时间段的高并发请求。瞬间高访问量。
Redis的List类型实现秒杀活动,抢购活动。

异步通信

系统扩展--新增功能只需要订阅消息队列即可

排序保证--单进单出

RabbitMQ
更专业的消息系统

消息队列概念:
队列结构的中间件,用于分流,减压
消息放入队列后,不需要立即处理.放入队列成功则返回成功,放入队列失败则返回失败。
放入队列后直接返回,不需等待并获取处理结果.
由订阅者/消费者按顺序处理

-

队列介质

Mysql:可靠性高,易实现,速度慢
Redis: (缓存类) 速度快,单条大消息包时 效率低
消息系统:(RabbitMQ) 专业性强,可靠,学习成本高

消息处理触发机制

死循环方式读取:易实现,故障时无法及时恢复。一个程序不断读取队列后处理。适合秒杀业务

定时任务:压力均分,有处理量上限.出队系统定时执行。任务间隔和数量不易控制。当前任务未完成时,却启动了下一个任务,则会导致问题。适合订单系统,物流配货系统

守护进程:监听队列。类似于php-fpm和php-cg,需要shell基础


解耦案例:队列处理订单系统和配送系统

- -

- -

databases/order_queue.sql
订单队列表

CREATE TABLE `order_queue` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单的id号',
  `order_id` int(11) NOT NULL,
  `mobile` varchar(20) NOT NULL COMMENT '用户的手机号',
  `address` varchar(100) NOT NULL COMMENT '用户的地址',
  `created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '订单创建的时间',
  `updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '处理完成的时间',
  `status` tinyint(2) NOT NULL COMMENT '当前状态,0 未处理,1 已处理,2处理中',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

include/db.php
数据库操作类

<?php
// 数据库连接类
class DB{
  //私有的属性
  private static $dbcon=false;
  private $host;
  private $port;
  private $user;
  private $pass;
  private $db;
  private $charset;
  private $link;
  //私有的构造方法
  private function __construct(){
    $this->host =  'localhost';
    $this->port =  '3306';
    $this->user =  'root';
    $this->pass =  'root';
    $this->db =  'imooc';
    $this->charset= 'utf8';
    //连接数据库
    $this->db_connect();
    //选择数据库
    $this->db_usedb();
    //设置字符集
    $this->db_charset();
   }
   //连接数据库
   private function db_connect(){
    $this->link=mysqli_connect($this->host.':'.$this->port,$this->user,$this->pass);
    if(!$this->link){
      echo "数据库连接失败<br>";
      echo "错误编码".mysqli_errno($this->link)."<br>";
      echo "错误信息".mysqli_error($this->link)."<br>";
      exit;
    }
   }
   //设置字符集
    private function db_charset(){
     mysqli_query($this->link,"set names {$this->charset}");
    }
    //选择数据库
   private function db_usedb(){
     mysqli_query($this->link,"use {$this->db}");
   }
   //私有的克隆
   private function __clone(){
     die('clone is not allowed');
   }
   //公用的静态方法
   public static function getIntance(){
     if(self::$dbcon==false){
      self::$dbcon=new self;
     }
     return self::$dbcon;
   }
   //执行sql语句的方法
    public function query($sql){
     $res=mysqli_query($this->link,$sql);
     if(!$res){
      echo "sql语句执行失败<br>";
      echo "错误编码是".mysqli_errno($this->link)."<br>";
      echo "错误信息是".mysqli_error($this->link)."<br>";
     }
     return $res;
   }
    //获得最后一条记录id
    public function getInsertid(){
     return mysqli_insert_id($this->link);
    }
   /**
    * 查询某个字段
    * @param
    * @return string or int
    */
    public function getOne($sql){
     $query=$this->query($sql);
      return mysqli_free_result($query);
    }
    //获取一行记录,return array 一维数组
    public function getRow($sql,$type="assoc"){
     $query=$this->query($sql);
     if(!in_array($type,array("assoc",'array',"row"))){
       die("mysqli_query error");
     }
     $funcname="mysqli_fetch_".$type;
     return $funcname($query);
    }
    //获取一条记录,前置条件通过资源获取一条记录
    public function getFormSource($query,$type="assoc"){
    if(!in_array($type,array("assoc","array","row")))
    {
      die("mysqli_query error");
    }
    $funcname="mysqli_fetch_".$type;
    return $funcname($query);
    }
    //获取多条数据,二维数组
    public function getAll($sql){
     $query=$this->query($sql);
     $list=array();
     while ($r=$this->getFormSource($query)) {
      $list[]=$r;
     }
     return $list;
    }

    public function selectAll($table,$where,$fields='*',$order='',$skip=0,$limit=1000)
    {
              if(is_array($where)){
                    foreach ($where as $key => $val) {
                        if (is_numeric($val)) {
                            $condition = $key.'='.$val;
                        }else{
                            $condition = $key.'=\"'.$val.'\"';
                        }
                    }
              } else {
                $condition = $where;
              }
              if (!empty($order)) {
                  $order = " order by ".$order;
              }
              $sql = "select $fields from $table where $condition $order limit $skip,$limit";
              $query = $this->query($sql);
              $list = array();
              while ($r= $this->getFormSource($query)) {
                  $list[] = $r;
              }
              return $list;
    }
     /**
     * 定义添加数据的方法
     * @param string $table 表名
     * @param string orarray $data [数据]
     * @return int 最新添加的id
     */
     public function insert($table,$data){
     //遍历数组,得到每一个字段和字段的值
     $key_str='';
     $v_str='';
     foreach($data as $key=>$v){
     //  if(empty($v)){
     //   die("error");
     // }
        //$key的值是每一个字段s一个字段所对应的值
        $key_str.=$key.',';
        $v_str.="'$v',";
     }
     $key_str=trim($key_str,',');
     $v_str=trim($v_str,',');
     //判断数据是否为空
     $sql="insert into $table ($key_str) values ($v_str)";
     $this->query($sql);
    //返回上一次增加操做产生ID值
     return $this->getInsertid();
   }
   /*
    * 删除一条数据方法
    * @param1 $table, $where=array('id'=>'1') 表名 条件
    * @return 受影响的行数
    */
    public function deleteOne($table, $where){
      if(is_array($where)){
        foreach ($where as $key => $val) {
          $condition = $key.'='.$val;
        }
      } else {
        $condition = $where;
      }
      $sql = "delete from $table where $condition";
      $this->query($sql);
      //返回受影响的行数
      return mysqli_affected_rows($this->link);
    }
    /*
    * 删除多条数据方法
    * @param1 $table, $where 表名 条件
    * @return 受影响的行数
    */
    public function deleteAll($table, $where){
      if(is_array($where)){
        foreach ($where as $key => $val) {
          if(is_array($val)){
            $condition = $key.' in ('.implode(',', $val) .')';
          } else {
            $condition = $key. '=' .$val;
          }
        }
      } else {
        $condition = $where;
      }
      $sql = "delete from $table where $condition";
      $this->query($sql);
      //返回受影响的行数
      return mysqli_affected_rows($this->link);
    }
   /**
    * [修改操作description]
    * @param [type] $table [表名]
    * @param [type] $data [数据]
    * @param [type] $where [条件]
    * @return [type]
    */
   public function update($table,$data,$where,$limit=0){
     //遍历数组,得到每一个字段和字段的值
     $str='';
    foreach($data as $key=>$v){
     $str.="$key='$v',";
    }
    $str=rtrim($str,',');
      if(is_array($where)){
        foreach ($where as $key => $val) {
          if(is_array($val)){
            $condition = $key.' in ('.implode(',', $val) .')';
          } else {
            $condition = $key. '=' .$val;
          }
        }
      } else {
        $condition = $where;
      }

        if (!empty($limit)) {
            $limit = " limit ".$limit;
        }else{
            $limit='';
        }
    //修改SQL语句
    $sql="update $table set $str where $condition $limit";
    $this->query($sql);
    //返回受影响的行数
    return mysqli_affected_rows($this->link);
   }
}
?>

queue/order.php
接受用户订单信息并写入队列

<?php
// 接受用户订单信息并写入队列

include '../include/db.php';

if (!empty($_GET['mobile'])) {
  // 订单中心处理流程,省略
  // ...

  // 用户数据过滤,防止注入,省略
  // ...

  $order_id = rand(10000, 99999);

  // 生成订单信息
  $insert_data = array(
      'order_id' => $order_id,
      'mobile' => $_GET['mobile'],
      'created_at' => date('Y-m-d H:i:s', time()),
      'status' => 0
  );

  // 把生成的订单信息存入队列表
  $db = DB::getIntance();
  $res = $db->insert('order_queue', $insert_data);
  if ($res) {
    echo $insert_data['order_id'] . "保存成功";
  } else {
    echo "保存失败";
  }
}

queue/goods.php
配送系统处理队列中的订单并进行标记

<?php
// 配送系统处理队列中的订单并进行标记

include '../include/db.php';
$db = DB::getIntance();

// 1: 把要处理的记录更新为等待处理 // 锁机制,防止冲突
$waiting = array('status' => 0); // 等待处理的记录
$lock = array('status' => 2); // 2-正在处理的状态
$res_lock = $db->update('order_queue', $lock, $waiting, 2);

// 2:把要处理的记录进行配送系统的处理
if ($res_lock) {
  $res = $db->selectAll('order_queue', $lock);

  // 配货系统进行处理

  // 把处理过的记录更新为已完成
  $success = array(
      'status' => 1,
      'updated_at' => date('Y-m-d H:i:s', time())
  );

  $res_last = $db->update('order_queue', $success, $lock);

  if ($res_last) {
    echo "success:" . $res_last;
  } else {
    echo "fail:" . $res_last;
  }
} else {
  // 如果没有更新记录
  echo "all finish:";
}

需要定时运行的脚本文件
goods.sh

#!/bin/bash
#运行在命令行

#输出时间
date "+%G-%m-%d %H:%M:S"

cd /queue/

php goods.php
# linux 部署定时任务的方式

# m    h     dom  mon dow command
# 每分 每小时 每日 每月 每周 命令
crontab -e
# 进入编辑器
# /1 * * * * /home/queue/goods.sh >> /home/queue/log.log 2>&1
# 每分钟执行一次定时任务(脚本),并输出日志,错误输出转为标准输出
touch log.log

# 监控日志文件
tail -f log.log

流量削峰案例:Redis的List类型实现秒杀

List类型为双向链表
redis-1
redis-2

秒杀架构设计

list-
list-2

databases/redis_queue.sql
数据库设计
使用微秒 time_stamp varchar(24) NOT NULL,

 CREATE TABLE `redis_queue` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(11) NOT NULL DEFAULT '0',
  `time_stamp` varchar(24) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8;


queue_redis/user.php

<?php

// 加载redis组件
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 队列名
$redis_name = 'miaosha';

$uid = $_GET['uid'];

// 获取redis里已有的数量
$num = 10;
// 如果当天人数少于10,则加入这个队列,否则返回秒杀已完成
// 取'miaosha'队列的长度
if ($redis->lLen($redis_name) < 10) {
  // 加入'miaosha'队列的尾部 // microtime() 微秒
  $redis->rPush($redis_name, $uid . '%' . microtime());
  echo $uid . "秒杀成功";
} else {
  echo "秒杀已结束";
}

// 关闭redis链接
$redis->close();

queue_redis/saveToDb.php

<?php

include '../include/db.php';

// 加载redis组件
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 队列名
$redis_name = 'miaosha';

$db = DB::getIntance();

// 无限循环读取队列
// php saveToDb.php

while (true) {
// 从队列左侧取出一个值
  $user = lPop($redis_name);

// 判断这个值是否存在
  if (!$user || $user == 'nil') {
    // 减轻服务器压力
    sleep(1);
    // 跳出循环
    continue;
  }
// 分离出时间,uid
  $user_arr = explode('%', $user);
  $insert_data = array(
      'uid' => $user_arr[0],
      'time_stamp' => $user_arr[0]
  );

// 保存到数据库
  $res = $db->insert('redis_queue', $insert_data);
// 数据库插入失败的回滚机制
  if (!$res) {
    // 从左侧插回去
    $redis->rPush($redis_name, $user);
  }
}
// 释放redis

开启无限循环

php saveToDb.php

RabbitMQ

rabbitmq


WePY开发微信小程序

安装

npm install wepy-cli -g
wepy init standard my-project

更新

wepy upgrade

初始化项目后的注意事项:

关闭ES6转ES5,由WePY自动处理
关闭样式自动补全,由WePY自动处理
关闭代码压缩

安装util

否则可能报错 module "npm/lodash/_nodeUtil.js" is not defined

npm i util --save-dev

安装wepy-compiler-typescript

https://www.npmjs.com/package/wepy-compiler-typescript

npm install wepy-compiler-typescript --save-dev
<script lang="typescript" src ="./index.ts"></script>

安装less autoprefix

npm install less-plugin-autoprefix --save-dev

wepy.config.js

const LessPluginAutoPrefix = require('less-plugin-autoprefix');

//...
//...

  compilers: {
    less: {
      compress: true,
      plugins: [new LessPluginAutoPrefix({browsers: ['Android >= 2.3', 'Chrome > 20', 'iOS >= 6']})]
    }

wepy-async-function

npm install wepy-async-function --save

wepy.config.js

babel: {
            "presets": [
                "env"
            ],
            "plugins": [
                "transform-export-extensions",
                "syntax-export-extensions"
            ]
        }

app.wpy

import 'wepy-async-function'

export default class extends wepy.app {

    constructor () {
        super();
        this.use('promisify');
    }

}
wepy build --no-cache

promise-polyfill

npm install promise-polyfill --save

app.wpy

import Promise from 'promise-polyfill'

export default class extends wepy.app {

    constructor () {
        super();
        this.use('promisify');
    }

}

详细说明 https://github.com/Tencent/wepy/wiki/升级指南

wepy 文件压缩插件

https://github.com/Tencent/wepy/tree/master/packages/wepy-plugin-filemin
支持css,xml,json的压缩

安装
npm install wepy-plugin-filemin --save-dev
配置wepy.config.js

module.exports.plugins = {
    'filemin': {
        filter: /\.(json|wxml|xml)$/
    }
};

wepy生产文件去注释

配置wepy.config.js

module.exports.plugins = {
    uglifyjs: {
      filter: /\.js$/,
      config: {
        comments: false // 去注释
      }
    },
    imagemin: {
      filter: /\.(jpg|png|jpeg)$/,
      config: {
        jpg: {
          quality: 80
        },
        png: {
          quality: 80
        }
      }
    },
    'filemin': {
      filter: /\.(json|wxml|xml)$/
    }
  }

快速开始配置

package.json

{
  "name": "news",
  "version": "0.0.2",
  "description": "A WePY project",
  "main": "dist/app.js",
  "scripts": {
    "dev": "wepy build --watch",
    "build": "cross-env NODE_ENV=production wepy build --no-cache",
    "dev:web": "wepy build --output web",
    "clean": "find ./dist -maxdepth 1 -not -name 'project.config.json' -not -name 'dist' | xargs rm -rf",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "wepy": {
    "module-a": false,
    "./src/components/list": "./src/components/wepy-list.wpy"
  },
  "author": "王树贤 <[email protected]>",
  "license": "MIT",
  "dependencies": {
    "crypto-js": "^3.1.9-1",
    "promise-polyfill": "^8.0.0",
    "redux": "^3.7.2",
    "redux-actions": "^2.2.1",
    "redux-promise": "^0.5.3",
    "wepy-redux": "^1.5.3",
    "wepy": "^1.6.0",
    "wepy-async-function": "^1.4.4",
    "wepy-com-toast": "^1.0.2"
  },
  "devDependencies": {
    "babel-eslint": "^7.2.1",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-plugin-transform-export-extensions": "^6.22.0",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-env": "^1.6.1",
    "cross-env": "^5.1.3",
    "eslint": "^3.18.0",
    "eslint-config-standard": "^7.1.0",
    "eslint-friendly-formatter": "^2.0.7",
    "eslint-plugin-html": "^2.0.1",
    "eslint-plugin-promise": "^3.5.0",
    "eslint-plugin-standard": "^2.0.1",
    "less-plugin-autoprefix": "^1.5.1",
    "util": "^0.11.0",
    "wepy-compiler-babel": "^1.5.1",
    "wepy-compiler-less": "^1.3.10",
    "wepy-compiler-typescript": "^1.5.9",
    "wepy-eslint": "^1.5.3",
    "wepy-plugin-imagemin": "^1.5.3",
    "wepy-plugin-uglifyjs": "^1.3.7"
  }
}

wepy.config.js

const path = require('path');
var prod = process.env.NODE_ENV === 'production';
const LessPluginAutoPrefix = require('less-plugin-autoprefix');

module.exports = {
  wpyExt: '.wpy',
  eslint: true,
  cliLogs: !prod,
  build: {
    web: {
      htmlTemplate: path.join('src', 'index.template.html'),
      htmlOutput: path.join('web', 'index.html'),
      jsOutput: path.join('web', 'index.js')
    }
  },
  resolve: {
    alias: {
      counter: path.join(__dirname, 'src/components/counter'),
      '@': path.join(__dirname, 'src')
    },
    aliasFields: ['wepy', 'weapp'],
    modules: ['node_modules']
  },
  compilers: {
    less: {
      compress: true,
      plugins: [new LessPluginAutoPrefix({browsers: ['Android >= 2.3', 'Chrome > 20', 'iOS >= 6']})]
    },
    /*sass: {
      outputStyle: 'compressed'
    },*/
    babel: {
      sourceMap: true,
      presets: [
        'env'
      ],
      plugins: [
        'transform-class-properties',
        'transform-decorators-legacy',
        'transform-object-rest-spread',
        'transform-export-extensions',
        "syntax-export-extensions",
      ]
    }
  },
  plugins: {
  },
  appConfig: {
    noPromiseAPI: ['createSelectorQuery']
  }
}

if (prod) {

  // 压缩sass
  // module.exports.compilers['sass'] = {outputStyle: 'compressed'}

  // 压缩js
  module.exports.plugins = {
    uglifyjs: {
      filter: /\.js$/,
      config: {
      }
    },
    imagemin: {
      filter: /\.(jpg|png|jpeg)$/,
      config: {
        jpg: {
          quality: 80
        },
        png: {
          quality: 80
        }
      }
    }
  }
}

app.wpy

<style lang="less">
  .container {
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    box-sizing: border-box;
  }
</style>

<script>
  import wepy from 'wepy'
  import 'wepy-async-function'
  import Promise from 'promise-polyfill'

  import { setStore } from 'wepy-redux'
  import configStore from './store'

  const store = configStore()
  setStore(store)

  export default class extends wepy.app {
    config = {
      pages: [
        'pages/index'
      ],
      window: {
        backgroundTextStyle: 'light',
        navigationBarBackgroundColor: '#fff',
        navigationBarTitleText: 'WeChat',
        navigationBarTextStyle: 'black'
      }
    }

    globalData = {
      userInfo: null
    }

    constructor () {
      super()
      this.use('requestfix')
      this.use('promisify');
    }

    onLaunch() {

    }

    getUserInfo(cb) {
      const that = this
      if (this.globalData.userInfo) {
        return this.globalData.userInfo
      }
      wepy.getUserInfo({
        success (res) {
          that.globalData.userInfo = res.userInfo
          cb && cb(res.userInfo)
        }
      })
    }
  }
</script>


wepy d.ts 类型申明
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/wepy/index.d.ts

npm install --save-dev @types/wepy

ES6 Promise 理解3-Promise.all

1>Promise.all-按函数顺序通过一个数组返回各个值,全部成功或者一个失败即返回

function asyncFun(a, b) {
	    return new Promise(function (resolve, reject) {
	        //resolve, reject两个参数均为函数
	        //有结果时调用resolve,并传入参数
	        //发生错误时调用reject,传入错误
	        //处理异常
	        if (typeof a !== 'number' || typeof b !== 'number') {
	            //判断参数,不合法则调用reject抛出异常并传入参数,参数为错误信息
	            reject(new Error('no number'))
	        }
	        setTimeout(function () {
	            resolve(a + b)//传入参数ab之和
	        }, 200)
	    })
	}

// let resultList=[]
// asyncFun(1,2)
// 	.then((result)=>{
// 		resultList.push(result)
// 		return asyncFun(2,3)
// 	})
// 	.then((result)=>{
// 		resultList.push(result)
// 	})

//按函数顺序通过一个数组返回各个值,全部成功或者一个失败即返回
let promise=Promise.all([
	asyncFun(1,2),
	asyncFun(2,3),
	asyncFun(3,4)
])

promise.then((result)=>{
	console.log(result)
})

//[3, 5, 7]

2-1-Promise.race-其中一个返回或错误即返回

function asyncFun(a, b) {
	    return new Promise(function (resolve, reject,time) {
	        //resolve, reject两个参数均为函数
	        //有结果时调用resolve,并传入参数
	        //发生错误时调用reject,传入错误
	        //处理异常
	        if (typeof a !== 'number' || typeof b !== 'number') {
	            //判断参数,不合法则调用reject抛出异常并传入参数,参数为错误信息
	            reject(new Error('no number'))
	        }
	        setTimeout(function () {
	            resolve(a + b)//传入参数ab之和
	        }, time)
	    })
	}

// let resultList=[]
// asyncFun(1,2)
// 	.then((result)=>{
// 		resultList.push(result)
// 		return asyncFun(2,3)
// 	})
// 	.then((result)=>{
// 		resultList.push(result)
// 	})


//其中一个返回或错误即返回
let promise=Promise.race([
	asyncFun(1,2,300),
	asyncFun(2,3,200),
	asyncFun(3,4,100)
])

promise.then((result)=>{
	console.log(result)
})
//3

2-2-Promise.race(iterable) 方法返回一个 promise ,并伴随着 promise对象解决的返回值或拒绝的错误原因, 只要 iterable 中有一个 promise 对象"解决(resolve)"或"拒绝(reject)"

var promise1 = new Promise(function (resolve, reject) {
	setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function (resolve, reject) {
	setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function (value) {
	console.log(value);
	// Both resolve, but promise2 is faster
});
// expected output: "two"

3-Promise.resolve(value)方法返回一个以给定值解析后的Promise对象

//Promise.resolve(value)方法返回一个以给定值解析后的Promise对象
let promise=Promise.resolve('hello')
promise.then((result)=>{
	console.log(result)
})
//hello
//
let promise1=new Promise((resolve,reject)=>{
	resolve('haha')
})
promise1.then((result)=>{
	console.log(result)
})
//haha

4-Promise.reject(reason)方法返回一个用reason拒绝的Promise

//Promise.reject(reason)方法返回一个用reason拒绝的Promise
let promise2=Promise.reject('error')

//第一个参数不会被执行,可以写成null
promise2.then(null,(error)=>{
	console.log(error)
})
//error

如果执行了某一个then的resolve没有返回值,则默认返回一个 undefined,并传递给下一个then,下一个then()始终会执行

如果执行了一个then的reject返回一个值或Promise,则可以执行下一个then(),并传递这个值。
如果执行了一个then的reject,但不返回任何值或抛出异常,则不会执行下一个then(),但可以使用catch()捕获该异常

Flex 弹性盒

Flex 弹性盒

Flex容器(Flex Container):父元素显式设置了display:flex

.Container{
    // Flex容器属性  flex-direction || flex-wrap || flex-flow || justify-content || align-items || align-content
    // flex-direction属性控制Flex项目沿着主轴(Main Axis)的排列方向。
    // flex-direction: row || column || row-reverse || column-reverse;
    // 行(水平)、列(垂直)或者行和列的反向
    // 水平和垂直在Flex世界中不是什么方向(概念)。它们常常被称为主轴(Main-Axis)和侧轴(Cross-Axis)

    // flex-wrap: wrap || nowrap || wrap-reverse;
    // flex-wrap属性的默认值是nowrap。也就是说,Flex项目在Flex容器内不换行排列。
    // 希望Flex容器内的Flex项目达到一定数量时,能换行排列,把它(flex-wrap)的值设置为wrap就有这种可能
    // 当一行再不能包含所有列表项的默认宽度,他们就会多行排列。即使调整浏览器大小
    // wrap-reverse 让Flex项目在容器中多行排列,只是方向是反的
    // 789
    // 123456

    // flex-flow: row wrap;
    // flex-flow是flex-direction和flex-wrap两个属性的速记属性

    // justify-content: flex-start || flex-end || center || space-between || space-around;
    // justify-content属性主要定义了Flex项目在Main-Axis上的对齐方式。
    // justify-content的默认属性值是flex-start
    // flex-start让所有Flex项目靠Main-Axis开始边缘(左对齐)
    // flex-end让所有Flex项目靠Main-Axis结束边缘(右对齐)
    // center让所有Flex项目排在Main-Axis中间(居中对齐)
    // space-between让除了第一个和最一个Flex项目的两者间间距相同(两端对齐)
    // space-around让每个Flex项目具有相同的空间
    // 和space-between有点不同,space-around使第一个Flex项目和最后一个Flex项目距Main-Axis开始边缘和结束边缘的的间距是其他相邻Flex项目间距的一半。

    // align-items属性类似于justify-content属性
    // align-items: flex-start || flex-end || center || stretch || baseline;
    // 用来控制Flex项目在Cross-Axis对齐方式。这也是align-items和justify-content两个属性之间的不同之处
    // align-items的默认值是stretch。让所有的Flex项目高度和Flex容器高度一样
    // flex-start让所有Flex项目靠Cross-Axis开始边缘(顶部对齐)
    // flex-end让所有Flex项目靠Cross-Axis结束边缘(底部对齐)
    // center让Flex项目在Cross-Axis中间(居中对齐)
    // baseline 让所有Flex项目在Cross-Axis上沿着他们自己的基线对齐。看上去有点像flex-start,但略有不同

    // align-content属性用于多行的Flex容器。它也是用来控制Flex项目在Flex容器里的排列方式,排列效果和align-items值一样,但除了baseline属性值
    // 默认值是stretch
    // stretch会拉伸Flex项目,让他们沿着Cross-Axis适应Flex容器可用的空间
    // flex-start。这次是让多行Flex项目靠Cross-Axis开始边缘。沿着Cross-Axis从上到下排列。因此Flex项目在Flex容器中顶部对齐。
    // flex-end刚好和flex-start相反,让多行Flex项目靠着Cross-Axis结束位置。让Flex项目沿着Cross-Axis从下到上排列,即底部对齐。
    // center让多行Flex项目在Cross-Axis中间。在Flex容器中居中对齐。





    display:flex;
}

ES6 Promise 理解2-类实例

1-

'use strict'

class User {
	constructor(name) {
		this.name = name
	}

	send(cb) {
		let name = this.name
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (name === 'leo') {
					resolve('success')
				} else {
					reject('error')
				}
			}, 500)
		})
	}
}

const user = new User('tom')
user.send()
	.then((result) => {
		//如果执行成功则执行本函数,并接受参数'success'
		console.log(result)
	}).catch((error) => {
		//参数检测不通过,执行reject,本函数,接受参数'error'
		console.log(error)
	})

	//error

2-名称密码验证

'use strict'

class User {
	constructor(name, password) {
		this.name = name
		this.password = password
	}

	validateName(cb) {
		let name = this.name
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (name === 'leo') {
					resolve('success')
				} else {
					reject('error')
				}
			}, 500)
		})
	}

	validatePassword(cb) {
		let password = this.password
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (password === '123') {
					resolve('success')
				} else {
					reject('error')
				}
			}, 500)
		})
	}
}



const user = new User('tom', '123')
user.validateName()
	.then((result) => {
		//如果名称验证通过,标示执行成功,则执行本函数,并接受参数'success'
		console.log(result)
		//执行下一个验证
		return user.validatePassword() //返回一个Promise,下一个then即可以执行
	}).then((result) => {
		//如果密码验证通过,则执行本函数
		console.log(result)
	}).catch((error) => {
		// 任意一个参数检测不通过,执行reject,本函数,接受参数'error'
		console.log(error)
	})

	//error

3-可以直接返回一个非Promise

'use strict'

class User {
	constructor(name, password) {
		this.name = name
		this.password = password
	}

	validateName(cb) {
		let name = this.name
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (name === 'leo') {
					resolve('success')
				} else {
					reject('error')
				}
			}, 500)
		})
	}

	validatePassword(cb) {
		let password = this.password
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (password === '123') {
					resolve('success')
				} else {
					reject('error')
				}
			}, 500)
		})
	}
}



const user = new User('leo', '123')
user.validateName()
	.then((result) => {
		//如果名称验证通过,标示执行成功,则执行本函数,并接受参数'success'
		console.log(result)
		//可以直接返回一个非Promise
		return 'name ok'
               //会自动封装成 Promise.resolve('name ok') 返回,供下一个then调用
	}).then((result) => {
		//接受上一个的返回值
		console.log(result)
	}).catch((error) => {
		// 任意一个参数检测不通过,执行reject,本函数,接受参数'error'
		console.log(error)
	})

	// success
	// name ok

4-异常处理1

'use strict'

class User {
	constructor(name, password) {
		this.name = name
		this.password = password
	}

	validateName(cb) {
		let name = this.name
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (name === 'leo') {
					resolve('success')
				} else {
					reject('error-name')
				}
			}, 500)
		})
	}

	validatePassword(cb) {
		let password = this.password
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (password === '123') {
					resolve('success')
				} else {
					reject('error')
				}
			}, 500)
		})
	}
}



const user = new User('tom', '123')
user.validateName()
	.then((result) => {
		//如果名称验证通过,标示执行成功,则执行本函数,并接受参数'success'
		console.log(result)

		throw new Error('1-error')
		//没有处理.validateName()的异常,该函数不执行,所以下一个then也不执行
		return user.validatePassword()
	}).then((result) => {
		//接受上一个的返回值
		console.log(5,result)
		throw new Error('2-error')
	}).catch((error) => {
		// 任意一个参数检测不通过,执行reject,本函数,接受参数'error'
		//名称验证通过时
		//处理第一个then的错误
		console.log(error)
	})

	// error-name

5-异常处理2

'use strict'

class User {
	constructor(name, password) {
		this.name = name
		this.password = password
	}

	validateName(cb) {
		let name = this.name
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (name === 'leo') {
					resolve('success')
				} else {
					reject('error-name')
				}
			}, 500)
		})
	}

	validatePassword(cb) {
		let password = this.password
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				//模拟异步
				if (password === '123') {
					resolve('success')
				} else {
					reject('error')
				}
			}, 500)
		})
	}
}



const user = new User('tom', '123')
user.validateName()
	.then((result) => {
		//如果名称验证通过,标示执行成功,则执行本函数,并接受参数'success'
		console.log(result)

		throw new Error('1-error')

		return user.validatePassword()
	}, (error) => {
		//名称验证未通过时
		//处理validateName()的错误,可以向下执行
		console.log(error)
	}).then((result) => {
		//接受上一个的返回值
		console.log(5, result)
		throw new Error('2-error')
	}).catch((error) => {
		// 任意一个参数检测不通过,执行reject,本函数,接受参数'error'
		//名称验证通过时
		//处理第一个then的错误
		console.log(error)
	})

	// error-name
	//5 undefined
	//Error: 2-error
  	//  at user.validateName.then.then (<anonymous>:56:9)

PM2

pm2

https://pm2.io/doc/zh/runtime/overview/

nodejs 进程管理工具
npm I pm2 -g

pm2配置
pm2.yml

apps:
    // 使用pm2运行的文件
  - script: ./server/server.js

   // 为启动的服务指定名称
    name: vue-todo

    // 启动服务时的可用环境变量
    env_production:
      NODE_ENV: production

      // 只能本地访问,禁止外网访问
      HOST: localhost

      PORT: 8888
    
    env_development:
          NODE_ENV: development
    
          // 只能本地访问,禁止外网访问
          HOST: localhost
    
          PORT: 3333

使用pm2启动服务端server.js
--env production ,表示使用pm2.yml 内 env_production的环境变量

pm2 start pm2.yml --env production

使用pm2重启服务端server.js

pm2 restart vue-todo

使用pm2停止服务端server.js

pm2 stop vue-todo

使用pm2查看已启动的服务

pm2 list

使用pm2查看指定服务的日志

pm2 log vue-todo

服务端部署

// 连接到服务器
ssh root@tom

// 克隆仓库
git clone [email protected]:test/vue-todo.git

// 安装依赖
cd vue-todo
npm i

// 构建正式环境文件
npm run build

// 启动项目服务端渲染服务器
pm2 start pm2.yml --env production

使用nginx 反向代理nodejs服务

nginx 监听80端口,将对80端口的请求转发到nodejs监听的本地其他端口
安装nginx

yum install nginx

linux 配置nginx

cd /etc.nginx
cd conf.d/
vim todo.conf

service nginx reload

weex

weex

https://weex.apache.org/cn/

https://github.com/apache/incubator-weex/

npm install weex-toolkit -g

使用async/await
npm install --save-dev babel-plugin-transform-runtime

在 /babelrc 中加入如下内容

"plugins": [
  [
    "transform-runtime",
    {
      "helpers": false,
      "polyfill": false,
      "regenerator": true,
      "moduleName": "babel-runtime"
    }
  ]
]

示例

<template>
  <div class="wrapper">
    <image :src="logo" class="logo"/>
    <text class="greeting">The environment is ready!--</text>
    <HelloWorld/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  data () {
    return {
      logo: 'https://gw.alicdn.com/tfs/TB1yopEdgoQMeJjy1XaXXcSsFXa-640-302.png'
    }
  },
  async created () {
    await this.promising()
  },
  methods: {
    promising () {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(123456)
        }, 5000)
      })
    }
  }
}
</script>

<style scoped>
  .wrapper {
    justify-content: center;
    align-items: center;
  }

  .logo {
    width: 424px;
    height: 200px;
  }

  .greeting {
    text-align: center;
    margin-top: 70px;
    font-size: 50px;
    color: #41B883;
  }

  .message {
    margin: 30px;
    font-size: 32px;
    color: #727272;
  }
</style>
初始化

weex create awesome-app

构建app

https://github.com/weexteam/weex-pack/wiki/%E5%A6%82%E4%BD%95%E7%94%A8weexpack%E5%88%9B%E5%BB%BAweex%E9%A1%B9%E7%9B%AE%E5%B9%B6%E6%9E%84%E5%BB%BAapp
npm install weexpack -g

weex platform add ios
weex platform add android

安卓配置

安装Android Studio
https://developer.android.com/studio/
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

新建一个 Android 项目,在第二步中选择 Android 5.1
安装 Android 模拟器
安装 Android build-tool 版本为 23.0.2

配置Android_HOME环境变量和PATH

vim ~/.bash_profile

然后在顶部加入

export ANDROID_HOME=/Users/zl/Library/Android/sdk
  
export PATH=$PATH:$ANDROID_HOME/tools

export PATH=$PATH:$ANDROID_HOME/platform-tools

source ~/.bash_profile
关闭所有终端

ios 配置

安装xcode 并打开一次
安装cocoapods
安装RVM

curl -L http://get.rvm.io | bash -s stable

更换Ruby镜像

1)检查当前镜像 gem sources -l
(2)移除当前镜像 gem sources --remove https://rubygems.org/ (具体看你上一步检查的结果)
(3)更换新的镜像 gem sources -a https://gems.ruby-china.com/
(4)检查新镜像是否安装成功 gem sources -l

安装CocoaPods

sudo gem install -n /usr/local/bin cocoapods

下载标准配置文件

pod setup

运行

weexpack run android
weexpack run ios

打包

weexpack build ios
weexpack build android


每个单页中使用 vue 实例方法

import Vue from 'vue'

React

安装React和React-DOM

npm install react --save-dev
npm install react-dom --save-dev

或者建立快速项目

sudo npm install create-react-app -g
create-react-app my-app

cd my-app
npm start

推荐使用npx,防止以后使用了全局的没更新的老版本项目
或者使用npx建立快速项目

npx create-react-app my-app

cd my-app
npm start

You can now view react-app in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://192.168.1.129:3000/

Note that the development build is not optimized.
To create a production build, use npm run build.

从父组件向子组件传值
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

App.js

import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Box from './components/box'

let comments = [
  {'author': 'tom', 'comment': 'hello', 'text': '123'},
  {'author': 'lili', 'comment': 'bucuo', 'text': '555'},
]

class App extends Component {
  render() {
    return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo"/>
            <h1 className="App-title">Welcome to React</h1>
          </header>
          <p className="App-intro">
            To get started, edit <code>src/App.js</code> and save to reload.
          </p>
          <p>
            你好
          </p>
          <Box data={comments}/>
        </div>
    );
  }
}

export default App;

box.js

import React, {Component} from 'react'
import './box.css'
import Comment from './comment'

class Box extends Component {
  render() {
    return (
        <div className='box'>
          <p>hello box</p>
          <Comment author='tom' comment='不错' data={this.props.data}>hello</Comment>
        </div>
    )
  }
}

export default Box

comment.js

import React, {Component} from 'react'
import './comment.css'

class Comment extends Component {
  render() {
    let commentNodes = this.props.data.map((comment, index) => {
      return (
          <div key={index}>
            <p>{comment.author}</p>
            <p>{comment.comment}</p>
          </div>
      )
    })

    return (
        <div className='comment'>
          {commentNodes}
          {/*<p>{this.props.author}</p>*/}
          {/*<p>{this.props.comment}</p>*/}
          {/*<p className='child'>{this.props.children}</p>*/}
        </div>
    )
  }
}

export default Comment

从服务器获取数据并更新
App.js

import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Box from './components/box'

class App extends Component {
  render() {
    return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo"/>
            <h1 className="App-title">Welcome to React</h1>
          </header>
          <p className="App-intro">
            To get started, edit <code>src/App.js</code> and save to reload.
          </p>
          <p>
            你好
          </p>
          <Box url='./comment.json'/>
        </div>
    );
  }
}

export default App;
//comment.json需要在public静态资源文件夹存放一份,否则热更新无法获取到该文件

box.js

import React, {Component} from 'react'
import './box.css'
import Comment from './comment'
import 'whatwg-fetch'

class Box extends Component {
  constructor(props) {
    super(props)
    this.state = {data: []}
    this.getComments()
    setInterval(() => {
      this.getComments()
    }, 5000)
  }

  getComments() {
    fetch(this.props.url, {
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    }).then((response) => {
      return response.json()
    }).then((comments) => {
      this.setState({data: comments})
    })
  }

  render() {
    return (
        <div className='box'>
          <p>hello box</p>
          <Comment author='tom' comment='不错' data={this.state.data}>aaa</Comment>
        </div>
    )
  }
}

export default Box

comment.js

import React, {Component} from 'react'
import './comment.css'

class Comment extends Component {
  render() {
    let commentNodes = this.props.data.map((comment,index) => {
      return (
          <div key={index}>
            <p>{comment.author}</p>
            <p>{comment.comment}</p>
          </div>
      )
    })

    return (
        <div className='comment'>
          {commentNodes}
          {/*<p>{this.props.author}</p>*/}
          {/*<p>{this.props.comment}</p>*/}
          {/*<p className='child'>{this.props.children}</p>*/}
        </div>
    )
  }
}

export default Comment

comment.json

[
  {
    "author": "tom",
    "comment": "hello",
    "text": "123"
  },
  {
    "author": "lili",
    "comment": "bucuo",
    "text": "555"
  }
]

事件,ref

import React, {Component} from 'react'
import './comment.css'

class Comment extends Component {
  handleSubmit(event) {
    event.preventDefault()
    let text = this.refs.text.value
    console.log(text)
  }

  render() {
    let commentNodes = this.props.data.map((comment, index) => {
      return (
          <div key={index}>
            <p>{comment.author}</p>
            <p>{comment.comment}</p>
          </div>
      )
    })

    return (
        <div className='comment'>
          {commentNodes}
          <form onSubmit={this.handleSubmit.bind(this)}>
            <input type="text" name='name' ref='text'/>
            <button type="submit">sunmit</button>
          </form>
        </div>
    )
  }
}

export default Comment

将数据从子组件传给父组件
box.js

import React, {Component} from 'react'
import './box.css'
import Comment from './comment'
import 'whatwg-fetch'

class Box extends Component {
  constructor(props) {
    super(props)
    this.state = {data: []}
    this.getComments()
    setInterval(() => {
      this.getComments()
    }, 5000)
  }

  getComments() {
    fetch(this.props.url, {
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    }).then((response) => {
      return response.json()
    }).then((comments) => {
      this.setState({data: comments})
    })
  }

  handleCommentSubmit(object) {
    console.log(object)
  }

  render() {
    return (
        <div className='box'>
          <p>hello box</p>
          <Comment onCommentSubmit={this.handleCommentSubmit.bind(this)}
                   author='tom' comment='不错'
                   data={this.state.data}>aaa</Comment>
        </div>
    )
  }
}

export default Box

comment.js

import React, {Component} from 'react'
import './comment.css'

class Comment extends Component {
  handleSubmit(event) {
    event.preventDefault()
    let text = this.refs.text.value
    console.log(text)
    this.props.onCommentSubmit({text})
  }

  render() {
    let commentNodes = this.props.data.map((comment, index) => {
      return (
          <div key={index}>
            <p>{comment.author}</p>
            <p>{comment.comment}</p>
          </div>
      )
    })

    return (
        <div className='comment'>
          {commentNodes}
          <form onSubmit={this.handleSubmit.bind(this)}>
            <input type="text" name='name' ref='text'/>
            <button type="submit">sunmit</button>
          </form>
        </div>
    )
  }
}

export default Comment

更新状态,重新渲染
box.js

import React, {Component} from 'react'
import './box.css'
import Comment from './comment'
import 'whatwg-fetch'

class Box extends Component {
  constructor(props) {
    super(props)
    this.state = {data: []}
    this.getComments()
  }

  getComments() {
    fetch(this.props.url, {
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    }).then((response) => {
      return response.json()
    }).then((comments) => {
      this.setState({data: comments})
    })
  }

  handleCommentSubmit(object) {
    console.log(object)
    let comments = this.state.data
    let newComments = comments.concat(object)
    this.setState({data: newComments})
  }

  render() {
    return (
        <div className='box'>
          <p>hello box</p>
          <Comment onCommentSubmit={this.handleCommentSubmit.bind(this)}
                   author='tom' comment='不错'
                   data={this.state.data}>aaa</Comment>
        </div>
    )
  }
}

export default Box

comment.js

import React, {Component} from 'react'
import './comment.css'

class Comment extends Component {
  handleSubmit(event) {
    event.preventDefault()
    let author = this.refs.author.value
    let comment = this.refs.comment.value
    console.log(author)
    this.props.onCommentSubmit({author, comment})
  }

  render() {
    let commentNodes = this.props.data.map((comment, index) => {
      return (
          <div key={index}>
            <p>{comment.author}</p>
            <p>{comment.comment}</p>
          </div>
      )
    })

    return (
        <div className='comment'>
          {commentNodes}
          <form onSubmit={this.handleSubmit.bind(this)}>
            <input type="text" name='author' ref='author'/>
            <input type="text" name='comment' ref='comment'/>
            <button type="submit">sunmit</button>
          </form>
        </div>
    )
  }
}

export default Comment

Vue-router

Vue-router

npm i vue-router -S

routes.js 路由映射关系文件

// routes.js 路由映射关系文件

import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
    {
        path: '/app',
        component: Todo,
        name: 'todo'
    },
    {
        path: '/login',
        component: Login,
        name: 'login'
    }
]

router.js 路由设置文件
不推荐
每次调用会缓存路由文件,不被释放,在服务端渲染会累加缓存,内存溢出

import Router from 'vue-router'
import routes from './routes'

// 全局唯一的路由() 不推荐
const router = new Router({
    routes
})

export default router

router.js 路由设置文件
适用于服务端渲染的路由

import Router from 'vue-router'
import routes from './routes'

// 适用于服务端渲染,防止内存溢出
// 每次 import 该router都将创建一个新的路由
export default () => {
    return new Router({
        routes
    })
}

index
注入路由,每个组件都可获得路由实例

// index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './app.vue'

import './assets/styles/global.styl'
import createRouter from './config/router'

Vue.use(VueRouter)

const router = createRouter()

new Vue({
    router,
    render: (h) => h(App)
}).$mount('#root')

Vue-router 配置
路由跳转,redirect: '/app', // 跳转到 path '/app'

// routes.js 路由映射关系文件

import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
    {
        path: '/',
        redirect: '/app', // 跳转到 path '/app'
    },
    {
        path: '/app',
        component: Todo,
        name: 'todo'
    },
    {
        path: '/login',
        component: Login,
        name: 'login'
    }
]

服务端适合SEO的history路由

import Router from 'vue-router'
import routes from './routes'

// #hash路由用来表示定位,不是记录路由状态,不会被搜索引擎解析,没有SEO

// 适用于服务端渲染,防止内存溢出
// 每次 import 该router都将创建一个新的路由
export default () => {
    return new Router({
        routes,
        mode: 'history' // 没有#hash
    })
}

路由配置参数

import Router from 'vue-router'
import routes from './routes'

// #hash路由用来表示定位,不是记录路由状态,不会被搜索引擎解析,没有SEO

// 适用于服务端渲染,防止内存溢出
// 每次 import 该router都将创建一个新的路由
export default () => {
    return new Router({
        routes,
        mode: 'history', // 没有#hash
        base: '/base/', //基路径,所有路由path前加上了/base/ loaclhost/app =>  loaclhost/base/app   //非强制,原路径仍有效
        linkActiveClass: 'active-link', // 在路由部分匹配时激活
        linkExactActiveClass: 'exact-active-link', // 在路由完全匹配时激活
        scrollBehavior(to, from, savedPosition) {
            if (savedPosition) {
                // 如果有滚动信息,则保存并返回。用来记录滚动位置
                return savedPosition
            } else {
                // 没有保存位置则滚动到页面顶部
                return { x: 0, y: 0 }
            }
        },
        parseQuery(query) {
            // 对页面参数的转换 localhost/app?a=1&b=2
            // 将页面参数由字符串转为json对象
        },
        stringifyQuery(obj) {
            // 将页面参数由json对象转为字符串
        },
        fallback: true, // 对于不支持history模式的浏览器,自动转为#hash模式,防止不支持history时每次跳转重新请求数据,变为多页应用
    })
}

Vue-router 路由参数传递

app.vue

// app.vue
// router-link 通过事件将a标签的默认跳转改为前端路由跳转
<template>
  <div id="app">
    <div id="nav">
        // path跳转
      <router-link to="/app">Home</router-link>
      <router-link to="/login">About</router-link>

        // 路由名称跳转,传入对象,需:解析
        <router-link :to="{name:'app'}">Home</router-link>
    </div>
    <router-view/>
  </div>
</template>

routes.js 路由映射关系文件

// routes.js 路由映射关系文件

import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
    {
        path: '/',
        redirect: '/app', // 跳转到 path '/app'
    },
    {
        path: '/app',
        component: Todo,
        name: 'todo',
        meta: {
            // 页面元信息,适于SEO,用于搜索引擎解析排序保存在路由中
            // 可以在得到路由对象时修改信息
            title: 'this is app',
            description: 'app'
        },
        // 子路由,嵌套路由
        // 在Todo组件中放置 <router-view/> 以显示路由页面
        children: [
            {
                path: '/test',
                component: Login,
            }
        ]
    },
    {
        path: '/login',
        component: Login,
        name: 'login'
    }
]

路由过度动画

// app.vue
// router-link 通过事件将a标签的默认跳转改为前端路由跳转
<template>
  <div id="app">
    <div id="nav">
        // path跳转
      <router-link to="/app">Home</router-link>
      <router-link to="/login">About</router-link>

        // 路由名称跳转,传入对象,需:解析
        <router-link :to="{name:'app'}">Home</router-link>
    </div>
    <transition name="fade">
        // 全部组件使用组件切换过度动画
        <router-view/>
    </transition>
  </div>
</template>

<style lang="less">

    // transition上的class: 'fade' +  '-enter-active'
    .fade-enter-active, 
    .fade-leave-active{
        transition:opacity 0.5s;
    }

    .fade-enter,
    .fade-leave-to{
        opacity:0
    }
</style>

路由传参
app.vue

// app.vue
// router-link 通过事件将a标签的默认跳转改为前端路由跳转
<template>
  <div id="app">
    <div id="nav">
        // path跳转
      <router-link to="/app/123">Home</router-link>
      <router-link to="/login">About</router-link>

        // 路由名称跳转,传入对象,需:解析
        <router-link :to="{name:'app'}">Home</router-link>
    </div>
    <transition name="fade">
        // 全部组件使用组件切换过度动画
        <router-view/>
    </transition>
  </div>
</template>



<style lang="less">

    // transition上的class: 'fade' +  '-enter-active'
    .fade-enter-active, 
    .fade-leave-active{
        transition:opacity 0.5s;
    }

    .fade-enter,
    .fade-leave-to{
        opacity:0
    }
</style>
// 组件内获取路由参数

mounted(){
    // 当前匹配到的路由的所有信息
    console.log(this.$route)
    // fullPath,hash,matched,meta,params,path,query
    // params:{id:'123'}
}

路由传参-props:true

// 路由传参-props:true ,推荐,解耦,更适合组件化
// routes.js 路由映射关系文件

import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
    {
        path: '/',
        redirect: '/app', // 跳转到 path '/app'
    },
    {
        path: '/app/:id', // 只能匹配地址 /app/xxx
        props: true, // 会将path 的id当作props传入组件Todo,组件内直接this.id取得
        component: Todo,
        name: 'todo',
    },
    {
        path: '/login',
        props: {
            // 也可以自定义 props
            id: '456',
        },
        component: Login,
        name: 'login'
    },
    {
        path: '/login2/:id',
        props: (route) => ({
            // 通过方法生成props对象
            // 自动传入路由对象
            id: route.query.b, // 通过query参数生成props
        }),
        component: Login2,
        name: 'login2'
    }
]

单页多routerView

// 单页多routerView
// 适用于三栏布局,顶部切换时左侧栏变动较大
// routes.js 路由映射关系文件

import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
    {
        path: '/',
        redirect: '/app', // 跳转到 path '/app'
    },
    {
        path: '/app/:id', // 只能匹配地址 /app/xxx
        // props: true, // 会将path 的id当作props传入组件Todo,组件内直接this.id取得
        components: {
            // 单页面多个 router-view
            default: Todo, // 未命名的默认 router-view 所显示的组件为Todo
            routerViewA: Login, //命名为routerViewA的router-view 所显示的组件为Login
        },
        name: 'todo',
    },
    {
        path: '/login',
        props: {
            // 也可以自定义 props
            id: '456',
        },
        components: {
            // 单页面多个 router-view
            default: Login, // 未命名的默认 router-view 所显示的组件为Login
            routerViewA: Todo, //命名为routerViewA的router-view 所显示的组件为Todo
        },
        name: 'login'
    },
    {
        path: '/login2/:id',
        props: (route) => ({
            // 通过方法生成props对象
            // 自动传入路由对象
            id: route.query.b, // 通过query参数生成props
        }),
        component: Login2,
        name: 'login2'
    }
]

导航守卫
全局导航守卫,index.js

// index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './app.vue'

import './assets/styles/global.styl'
import createRouter from './config/router'

Vue.use(VueRouter)

const router = createRouter()

// 全局导航守卫

// 每次路由跳转时触发
// 适合数据校验
router.beforeEach((to, from, next) => {
    console.log('before each invoked')

    if (to.fullPath === '/app') {
        // next('/login') // 跳转到登陆页
        next({
            path: '/login',
            replace // 本次跳转记录消失,防止跳回本页
        })
    } else {
        next()
    }

    // 执行next后路由才会真正跳转,否则不跳转
    // next()
})

router.beforeResolve((to, from, next) => {
    console.log('before Resolve invoked')

    // 执行next后路由才会真正跳转,否则不跳转
    next()
})

// 每次路由跳转完成后触发,无需next()
router.afterEach((to, from) => {
    console.log('after Each invoked')
})

new Vue({
    router,
    render: (h) => h(App)
}).$mount('#root')

路由配置钩子
routes.js

// 路由配置钩子
// routes.js 路由映射关系文件

import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
    {
        path: '/',
        redirect: '/app', // 跳转到 path '/app'
    },
    {
        path: '/app/:id', // 只能匹配地址 /app/xxx
        // props: true, // 会将path 的id当作props传入组件Todo,组件内直接this.id取得
        component: Todo,
        name: 'todo',
        beforeEnter(to, from, next) {
            // 每次进入路由前触发
            // 在before each 和 before resolve 中间触发
            next() // 不执行next则不会进入路由
        },
    },
    {
        path: '/login',
        props: {
            // 也可以自定义 props
            id: '456',
        },
        component: Login,
        name: 'login'
    },
]

组件内路由钩子

// 组件内路由钩子

// 进入之前,用于数据获取
beforeRouteEnter(to, from, next){
    // 此时得不到组件实例this
    next(vm => {
        // 自动得到进入路由后的组件实例vm
        // 此时可以得到组件实例vm
        console.log(vm)
    })
}

// 路由跳转时,组件不变,路由参数改变时触发
// 用于数据获取,推荐
beforeRouteUpdate(to, from, next){
    // 若触发update,表示缓存了组件,不会再触发beforeRouteEnter,mounted
    next()
}

// 路由离开时
// 用于修改表单后未保存,离开之前提醒是否离开
beforeRouteLeave(to, from, next){
    // next()
    if (global.confirm('are you sure?')) {
        // 确定后才会离开
        next()
    }
}

// 触发顺序
// beforeEach
// app beforeEnter
// todo beforeEnter
// before resolve
// after each

异步加载组件,提高首屏加载速度
routes.js

// 路由配置钩子
// routes.js 路由映射关系文件
// 异步加载组件需要安装 npm i babel-plugin-syntax-dynamic-import
// .babelrc 配置 "plugins":["syntax-dynamic-import"]

// import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
    {
        path: '/',
        redirect: '/app', // 跳转到 path '/app'
    },
    {
        path: '/app/:id', // 只能匹配地址 /app/xxx
        // props: true, // 会将path 的id当作props传入组件Todo,组件内直接this.id取得
        // component: Todo,
        component: ()=> import('../views/todo/todo.vue'), // 需要时才会加载该组件,异步加载
        name: 'todo',
        beforeEnter(to, from, next) {
            // 每次进入路由前触发
            // 在before each 和 before resolve 中间触发
            next() // 不执行next则不会进入路由
        },
    },
    {
        path: '/login',
        props: {
            // 也可以自定义 props
            id: '456',
        },
        component: Login,
        name: 'login'
    },
]







https://github.com/Jokcy/vue-todo-tech.git


NUXT

Nuxt Vue.js 通用应用框架

https://zh.nuxtjs.org
https://github.com/nuxt/nuxt.js
https://github.com/nuxt-community

安装

npm install -g vue-cli

生成生产静态文件需要首先开启 开发环境


指令 描述
nuxt 开启一个监听3000端口的服务器,同时提供hot-reloading功能
nuxt build 构建整个应用,压缩合并JS和CSS文件(用于生产环境)
nuxt start 开启一个生产模式的服务器(必须先运行nuxt build命令)
nuxt generate 构建整个应用,并为每一个路由生成一个静态页面(用于静态服务器)

NUXT 架构
nuxt


Vue 通用(高级)组件开发

Vue 通用(高级)组件开发

notification之基本组件实现-普通用法-不方便使用

./client/components/notification/notification.vue

// ./client/components/notification/notification.vue

<template>
	<transition name="fade">
		<div class="notification">
			<span class="content">{{content}}</span>
			<a class="btn" @click="handleClick">{{btn}}</a>
		</div>
	</transition>
</template>

<script>
	export default {
		name: 'notification',
		props: {
			content: {
				type: String,
				required: true
			},
			btn: {
				type: String,
				default: '关闭'
			}
		},
		methods: {
			handleClick(e) {
				e.preventDefault() // 阻止默认事件
				this.$emit('close') // 触发父组件的事件
			}
		}
	}
</script>

<style lang="stylus" scoped>
	.fade-enter-active, .fade-leave-active
		transition:opacity .5s
	.fade-enter, .fade-leave-to
		opacity:0
</style>

./client/components/notification/index.js

// ./client/components/notification/index.js
// vue插件
// 编写可发布的vue组件


// 全局注册notification组件,在每个业务组件内自动注册notification
import Notification from './notification.vue'

export default (Vue) => {
    // 接受 Vue 参数

    // 注册组件
    Vue.component(Notification.name, Notification)
}

./create-app.js

// ./create-app.js

// 每次服务端渲染,会渲染一个新的app
// 如果单例渲染,会包含上次渲染的状态

// npm i vue-meta -S

import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
import Meta from 'vue-meta'

import App from './app.vue'
import createStore from './store/store'
import createRouter from './config/router'

import Notification from './components/notification'

import './assets/styles/global.styl'

Vue.use(VueRouter)
Vue.use(Vuex)
Vue.use(Meta)

// 插件形式注册使用组件,自动注入Vue
// 组件将定义在全局,随处可用
Vue.use(Notification)

// 每次调用都返回一个新对象,防止内存溢出
export default () => {
    const router = createRouter()
    const store = createStore()

    const app = new Vue({
        router,
        store,
        render: h => h(App)
    })

    return { app, router, store }
}

App.vue

// App.vue
<template>
    <div>
        <p>{{ textA }}</p>
        <p>{{ textPlus }}</p>

        <notification content="test notify"></notification>
    </div>
</template>

notification : 通过方法调用,推荐

./client/components/notification/notification.vue

// ./client/components/notification/notification.vue
// transition组件隐藏时会调用after-leave方法
// transition组件完全显示时会调用after-enter方法
<template>
	<transition name="fade" @after-leave="afterLeave" @after-enter="afterEnter">
		<div 
		class="notification" 
		:style="style" 
		v-show="visible" 
		@mouseenter="clearTimer" 
		@mouseleave="createTimer">
			<span class="content">{{content}}</span>
			<a class="btn" @click="handleClick">{{btn}}</a>
		</div>
	</transition>
</template>

<script>
	export default {
		name: 'notification',
		data() {
			return {
				visible: true
			}
		},
		props: {
			content: {
				type: String,
				required: true
			},
			btn: {
				type: String,
				default: '关闭'
			}
		},
		computed: {
			style() {
				return {}
			}
		}
		methods: {
			handleClick(e) {
				e.preventDefault() // 阻止默认事件
				this.$emit('close') // 触发父组件的事件,开始关闭 // 在父组件监听
			},
			afterLeave() {
				this.$emit('closed') // 触发父组件的事件,关闭完成 // 在父组件监听
			},
			afterEnter() {
				// 即使默认组件用不到,只在扩展组件覆盖,也要事先声明
			},
			clearTimer() {
	
			},
			createTimer() {
	
			}
		}
	}
</script>

<style lang="stylus" scoped>
	.fade-enter-active, .fade-leave-active
		transition:opacity .5s
	.fade-enter, .fade-leave-to
		opacity:0
</style>

./client/components/notification/index.js

// ./client/components/notification/index.js
// vue插件
// 编写可发布的vue组件


// 全局注册notification组件,在每个业务组件内自动注册notification
import Notification from './notification.vue'

import notify from './function'

export default (Vue) => {
    // 接受 Vue 参数

    // 注册组件
    Vue.component(Notification.name, Notification)

    // 扩展vue内置方法
    Vue.prototype.$notify = notify
}

./client/components/notification/function.js

// ./client/components/notification/function.js
// 扩展组件的功能
import Vue from 'vue'
import Component from './func-notification'

// 创建vue组件
// 通过使用 Vue.extend 返回方法创建组件
const NotificationConstructor = Vue.extend(Component)

const instances = [] // 组件实例数组
let seed = 1 // 组件id

// 删除节点防止内存溢出,需传入待删除的节点对象
const removeInstance = (instance) => {
	if (!instance) return

	const length = instances.length

	// 找到当前组件实例的索引位置
	const index = instances.findIndex(inst => instance.id === inst.id)

	//instances.splice(index,1)
	instance.splice(index, 1)

	// 删除节点时重新计算所有其他节点的高度
	if (length <= 1) return // 只剩最后节点时不需要计算
	const removeHeight = instance.vm.height
	// 从删掉的节点位置开始向上计算
	for (let i = index; i < len - 1; i++) {
		instances[i].verticalOffset = parseInt(instances[i].verticalOffset) - removeHeight - 16
	}
}

// 方法
const notify = (options) => {
	// 有DOM 操作,不能在服务端运行
	if (Vue.prototype.$isServer) return

	//得到autoClose,解构剩下的参数
	const { autoClose, ...rest } = options

	const instance = new NotificationConstructor({
		propsData:...rest, // 传入参数,覆盖主组件的props
		data: {
			autoClose: autoClose === undefined ? 3000 : autoClose
		}, // 传入参数,覆盖主组件的data
	})

	const id = `notification_${seed++}`
	instance.id = id

	// 生成组件的DOM对象,但未插入页面。因为没有指定插入节点
	instance.vm = instance.$mount() // 返回vue对象

	// 将组件dom插入页面
	document.body.appendChild(instance.vm.$el)
	instance.vm.visible = true

	// 计算每个提醒组件的定位
	let verticalOffset = 0
	instance.forEach((item) => {
		verticalOffset += item.$el.offsetHeight + 16 // 组件之间间隔16
	})
	verticalOffset += 16 // 默认距离屏幕底部16
	instance.verticalOffset = verticalOffset
	instances.push(instance)

	// 监听组件的事件'closed',销毁节点对象,防止多个节点内存溢出
	instance.vm.$on('closed', () => {
		removeInstance(instance)
		document.body.removeChild(instance.vm.$el) // 移除节点dom
		instance.vm.$destory() // 销毁实例
	})

	instance.vm.$on('close', () => {
		instance.vm.visible = false
		// 之后会触发closed事件
	})

	return instance.vm // 用于调用方法后继续操作该组件对象
}

export default notify

./client/components/notification/func-notification.js

// ./client/components/notification/func-notification.js
// 通过方法调用的专用组件
// 扩展组件,无需模版。复用组件的模版

import Notification from './notification.vue'

export default {
	extends: Notification, // 继承主组件以扩展
	computed: {
		style() {
			// 覆盖主组件的属性
			return {
				position: 'fixed',
				right: '20px',
				bottom: `${this.verticalOffset}px`,
			}
		}
	},
	data() {
		return {
			verticalOffset: 0, // 声明默认值
			autoClose: 3000, // 提醒出现3秒后自动关闭
			height: 0,
			visible: false,
		}
	},
	mounted() {
		this.createTimer()
	},
	methods: {
		createTimer() {
			// 提醒出现3秒后自动关闭
			if (this.autoClose) {
				this.timer = setTimeout(() => {
					this.visible = false
				}, this.autoClose)
			}
		},
		clearTimer() {
			if (this.timer) {
				clearTimeout(this.timer)
			}
		},
		afterEnter() {
			// 覆盖主组件的方法
			this.height = this.$el.offsetHeight // 获取实际高度
		}
	},
	beforeDestory() {
		// 退出组件时销毁定时器防止内存溢出
		this.clearTimer()
	}
}

App.vue

// App.vue
<template>
    <div>
        <p>{{ textA }}</p>
        <p>{{ textPlus }}</p>
        <button @click="notify">click notify</button>
        <!-- <notification content="test notify"></notification> -->
    </div>
</template>

<script>
    // Actions,Mutations 帮助函数
    import {
        mapState,
        mapGetters,
        mapActions, // 对应异步操作
        mapMutations // 对应同步操作
    } from 'vuex'
    
    export default {
        metaInfo: {
            title: 'wang \'s App', // 进入页面时vue-meta会自动修改页面title值,下级组件的meta 会覆盖上级
        },
    
        methods: {
            ...mapActions(['updateCountAsync', 'a/add', 'textAction']), // b模块未声明命名空间,textAction无需写模块名
            ...mapMutations(['updateCount', 'a/updateText']), // 帮助函数声明模块化的 updateText mutation
            // Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
            // 启用命名空间后,调用模块内的mutations --- a/updateText
    
            notify() {
                // 调用组件的方法
                this.$notify({
                    content: 'test $notify',
                    btn: 'close'
                })
            }
        },
    
        mounted() {
            this['a/add']()
    
            this.textAction() // b模块未声明命名空间
        },
    
        computed: {
            // 模块后,带命名空间的调用
            // textA(){
            //     return this.$store.state.a.text
            // }
    
            // 使用帮助函数调用模块化的 state
            ...mapState({
                textA: state => state.a.text, // 必须使用方法返回
                textC: state => state.c.text, // 使用动态注册的模块c
            }),
    
            // 使用帮助函数调用模块化的 启用命名空间的 // 'a/textPlus'
            // 'a/textPlus' 无法在模版中直接使用
            // ...mapGetters(['fullName', 'a/textPlus'])
    
            // 使用帮助函数 重命名getters
            ...mapGetters({
                'fullName': 'fullName', // 全局的getter
                'textPlus': 'a/textPlus', // a模块启用命名空间的getter - 'a/textPlus' 重命名为 'textPlus',可在模版使用
            })
        }
    }
</script>

redux-actions,(use async action) 异步动作的使用

redux-actions是一个自动生成redux的动作和处理函数的库

https://redux-actions.js.org
https://github.com/redux-utilities/redux-actions
异步action的使用
store/types/index.js

export const ASYNC_INCREMENT = 'ASYNC_INCREMENT'

store/actions/async.js

import {ASYNC_INCREMENT} from '../types/index'
import {createAction} from 'redux-actions'

export const asyncIncrement = createAction(ASYNC_INCREMENT, (payload) => {
  return new Promise((resolve, reject) => {
    // 可以对参数进行处理
    setTimeout(() => {
      resolve(payload)
    }, 2000)
  })
})

store/reducers/async.js
注意:引入action只能通过直接引入动作,如:
import {asyncIncrement} from '../store/actions'
不可以用定义的字面量方式引入异步动作:如:import {CLEARCART, ASYNC_INCREMENT} from '../store/types'
只有同步动作可以用字面量方式引入
以字面量方式引入动作将强制为同步方式,即处理动作时将立即dispatch动作

import {handleActions} from 'redux-actions'
import {ASYNC_INCREMENT} from '../types/index'

export default handleActions({
  [ASYNC_INCREMENT](state, action) {
    console.log('action.payload', action.payload)
    return action.payload
  }
}, {
  num: 0
})

pages/cart.wpy

<style lang="less">

</style>
<template>
  <view class="cart">
    <view @tap="asyncIncrementSelf">异步操作</view>
    <view>异步显示:::{{asyncnum.num}}</view>
  </view>
</template>
<script>
  import wepy from 'wepy'
  import {connect} from 'wepy-redux'
  import {asyncIncrement} from '../store/actions'   // 关键
  @connect({
    asyncnum(state) {
      return state.asyncnum
    }
  }, {
    asyncIncrement
  })

  export default class Cart extends wepy.component {
    props = {}

    data = {}
    computed = {}

    events = {}

    watch = {}

    methods = {
      asyncIncrementSelf() {
        this.methods.asyncIncrement({num: 2})
      }
    }

    onLoad() {

    }
  }
</script>

TypeScript

TypeScript

https://www.tslang.cn

TypeScript是一门静态类型的语言

TypeScript是一门开源编程语言,由Microsoft开发并维护。它首次发布于2012年10月。TypeScript是ECMAScript的超集,它支持JavaScript的所有语法和语义,并且在此之上提供了更多额外的特性,例如静态类型和更丰富的语法


安装TypeScript编译器和可执行程序(tsc),并且添加到环境变量的全局路径中。

npm install-g typescript

tsc –v
使用 third party library

在从 npm 安装 third party library(第三方库) 时,一定要记得同时安装此 library 的类型声明文件(typing definition)。你可以从 TypeSearch 中找到并安装这些第三方库的类型声明文件。

举个例子,如果想安装 lodash 类型声明文件,我们可以运行下面的命令:

npm install --save-dev @types/lodash

http://microsoft.github.io/TypeSearch/


导入其他资源

想要在 TypeScript 中使用非代码资源(non-code asset),我们需要告诉 TypeScript 推断导入资源的类型。在项目里创建一个 custom.d.ts 文件,这个文件用来表示项目中 TypeScript 的自定义类型声明。我们为 .svg 文件设置一个声明:

custom.d.ts

declare module "*.svg" {
  const content: any;
  export default content;
}

这里,我们通过指定任何以 .svg 结尾的导入(import),将 SVG 声明(declare) 为一个新的模块(module),并将模块的 content 定义为 any。我们可以通过将类型定义为字符串,来更加显式地将它声明为一个 url。同样的概念适用于其他资源,包括 CSS, SCSS, JSON 等。

global.d.ts

declare module "*.png";
declare module "*.gif";
declare module "*.jpg";
declare module "*.jpeg";
declare module "*.svg";
declare module "*.css";
declare module "*.less";
declare module "*.scss";
declare module "*.sass";
declare module "*.styl";
declare namespace wx{

}

非空断言运算符 !

声明但未赋初始值时,确定值不会是null或undefined,才可以使用!.
使用时值为null或undefined,编译器将不会判断为错误类型
使用条件成立,但不推荐

private testb!: string
console.log(this.testb)

使用条件不成立,报错

private testb!: string = 'ss'
// 不可使用

类型错误,报错

this.testb = null
console.log(this.testb)

类型错误,报错

this.testb =123
console.log(this.testb)

主动告诉编译器前面的值不会是null或undefined,编译器将
这是一种告诉编译器的方法:“这个表达式不能为null或者在这里没有undefined ,所以不要抱怨它是null或者undefined的可能性。 有时候类型检查器本身无法做出这个决定。

我觉得在这个解释中使用“assert”这个术语有点误导。 从开发者主张的角度来看,它是“断言”的,而不是在测试即将进行的意义上。 最后一行确实表明它没有发出JavaScript代码。

尾随 "bang" 语法 (!),TypeScript 感叹号(!)结尾的语法,它会从前面的表达式里移除null和undefined。 所以我们也可以写成

document.getElementById('root')!

通过名字后面加?为指定可选参数

export interface Props {
  name: string;
  enthusiasmLevel?: number;
  onIncrement?: () => void;
  onDecrement?: () => void;
}

Flutter

Flutter

https://flutter.io/
https://github.com/flutter/flutter

环境变量

export PATH=/Users/zl/code/flutter/bin:$PATH

export PUB_HOSTED_URL=https://pub.flutter-io.cn

export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
vim ~/.bash_profile
source ~/.bash_profile

新建必要文件夹并授权
/usr/local/ 新建 Cellar,opt,Frameworks 文件夹
授权

sudo chown -R $(whoami) /usr/local/Cellar
sudo chown -R `whoami`:admin /usr/local/opt
sudo chown -R `whoami`:admin /usr/local/Frameworks
sudo chown -R `whoami`:admin /usr/local/bin
sudo chown -R `whoami`:admin /usr/local/share
xcode-select --install

Download brew at https://brew.sh/

检查环境
flutter doctor

同步项目的包
flutter packages get

ES7 对象展开运算符,Object Spread notation

####示例1

let objectA = {
	a: 1,
	b: 2
}

let objectB = {
	...objectA,
	c: 3
}
console.log(objectB)
//{a: 1, b: 2, c: 3}

...Object. 对象展开操作符,类似数组展开操作符,会将一个对象的一级属性全部取出:

let objectB = {
	...objectA,
	c: 3
}

等同于

let objectB = {
	a: 1,
	b: 2,
	c: 3
}

####示例2

let objectC = {
	a: 1,
	b: 2
}

let objectD = {
	...objectC,
	b: 9,
	c: 3
}
console.log(objectD)
//{a: 1, b: 9, c: 3}

被展开的对象后面位置的参数都将合并到被展开的对象内返回,并且覆盖同名参数。
上例等同于

let objectC = {
	a: 1,
	b: 2
}

let objectD = {
	a: 1,
	b: 2,
	b: 9,
	c: 3
}
console.log(objectD)
//{a: 1, b: 9, c: 3}

于是后面的参数b将前面的参数b覆盖了。

ES6 Promise 理解1-函数

1-普通函数

function asyncFun(a, b, cb) {
    setTimeout(function () {
        cb(a + b)
    }, 200)
}
asyncFun(1, 2, function (result) {
    console.log(result)
})
console.log(2)

//2
//3

2-promise产生的原因,回调嵌套,回调地狱,糟糕的逻辑

//promise产生的原因,回调嵌套,回调地狱,糟糕的逻辑
function asyncFun(a, b, cb) {
    setTimeout(function () {
        cb(a + b)
    }, 200)
}
asyncFun(1, 2, function (result) {
    if (result > 2) {
        asyncFun(result, 2, function (result) {
            if (result > 4) {
                asyncFun(result, 2, function (result) {
                    console.log('ok')
                })
            }
        })
    }
})

console.log(2)

3-去掉回调函数,用promise优化逻辑

function asyncFun(a, b) {
    return new Promise(function (resolve, reject) {
        //有结果时调用resolve,并传入参数
        //发生错误时调用reject,传入错误
        setTimeout(function () {
            resolve(a + b)//传入参数ab之和
        }, 200)
    })
}
asyncFun(1, 2)
    //asyncFun 返回一个promise对象,坑定会执行第一个then()
    .then(function (result) {
        //没有错误时执行第一个函数,并接受参数result
        console.log(1)
    })

console.log(2)

//2
//1

4-promise 多次返回Promise

function asyncFun(a, b) {
    return new Promise(function (resolve, reject) {
        //有结果时调用resolve,并传入参数
        //发生错误时调用reject,传入错误
        setTimeout(function () {
            resolve(a + b)//传入参数ab之和
        }, 200)
    })
}
asyncFun(1, 2)
    //asyncFun 返回一个promise对象,坑定会执行第一个then()
    .then(function (result) {
        //没有错误时执行第一个函数,并接受参数result
        if (result > 2) {
            console.log(3)
            return asyncFun(result, 2)//再次返回一个promise,以便执行下一个then()
        }
    })
    .then(function (result) {
        if (result > 4) {
            console.log('ok')
        }
    })

console.log(2)

//2
//3
//ok

5-promise-捕获异常1

function asyncFun(a, b) {
    return new Promise(function (resolve, reject) {
        //resolve, reject两个参数均为函数
        //有结果时调用resolve,并传入参数
        //发生错误时调用reject,传入错误
        //处理异常
        if (typeof a !== 'number' || typeof b !== 'number') {
            //判断参数,不合法则调用reject抛出异常并传入参数,参数为错误信息
            reject(new Error('no number'))
        }
        setTimeout(function () {
            resolve(a + b)//传入参数ab之和
        }, 200)
    })
}
asyncFun(1, 'a')
    //promise在内部对所有异常进行捕获,故参数错误时不会显示错误信息
    //必须通过.catch或···捕获异常
    //asyncFun 返回一个promise对象,坑定会执行第一个then()
    .then(function (result) {
        //没有错误时执行第一个函数,并接受参数result
        if (result > 2) {
            console.log(3)
            return asyncFun(result, 2)//再次返回一个promise,以便执行下一个then()
        }
    })
    .then(function (result) {
        if (result > 4) {
            console.log('ok')
        }
    })
    //捕获异常方法之一:::最终的捕获异常函数
    .catch(function (error) {
		//发生异常,不会执行resolve
        console.log(error)
    })

console.log(2)

//2
// VM414:39 Error: no number
// at <anonymous>:12:11
// at new Promise (<anonymous>)
// at asyncFun (<anonymous>:4:9)
// at <anonymous>:21:1

6-promise-捕获异常2

//,去掉回调函数,用promise优化逻辑
function asyncFun(a, b) {
    return new Promise(function (resolve, reject) {
        //resolve, reject两个参数均为函数
        //有结果时调用resolve,并传入参数
        //发生错误时调用reject,传入错误
        //处理异常
        if (typeof a !== 'number' || typeof b !== 'number') {
            //判断参数,不合法则调用reject抛出异常并传入参数,参数为错误信息
            reject(new Error('no number'))
        }
        setTimeout(function () {
            resolve(a + b)//传入参数ab之和
        }, 200)
    })
}
asyncFun(1, 'a')
    //promise在内部对所有异常进行捕获,故参数错误时不会显示错误信息
    //必须通过.catch或每个then的第二个参数(为函数)来捕获异常
    //asyncFun 返回一个promise对象,坑定会执行第一个then()
    .then(function (result) {
        //没有错误时执行第一个函数,并接受参数result
        if (result > 2) {
            console.log(3)
            return asyncFun(result, 2)//再次返回一个promise,以便执行下一个then()
        }
    }, function (error) {
        //发生异常,不会执行resolve,而是执行reject,即本函数
        //单独的捕获异常后,则catch不会再捕获该异常
        console.log('first:', error)
    })
    .then(function (result) {
        if (result > 4) {
            console.log('ok')
        }
    })
    //捕获异常方法之一:::最终的捕获异常函数
    .catch(function (error) {
        console.log('second:', error)
    })
console.log(2)
//2
// VM414:39 first: Error: no number
// at <anonymous>:12:11
// at new Promise (<anonymous>)
// at asyncFun (<anonymous>:4:9)
// at <anonymous>:21:1

7

节流,防抖,wait-promise 与异步 Timer

throttle 和 debounce

节流:设定一个时间间隔,某个频繁触发的函数,在这个时间间隔内只会执行一次。也就是说,这个频繁触发的函数会以一个固定的周期执行。

节流函数,只允许一个函数在 X 毫秒内执行一次

与防抖相比,节流函数最主要的不同在于它保证在 X 毫秒内至少执行一次我们希望触发的事件 handler。

防抖:设定一个时间间隔,当某个频繁触发的函数执行一次后,在这个时间间隔内不会再次被触发,如果在此期间尝试触发这个函数,则时间间隔会重新开始计算。

防抖函数:防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。

https://github.com/akira-cn/wait-promise
https://www.h5jun.com/post/wait-promise.html

npm install wait-promise

import wait from 'wait-promise'

 let a=1

      setInterval(()=>{
        a++
        //console.log('a----',a)
      },2000)

      console.log('await开始--------')

      console.log(wait)

      await wait.before(20000).until(()=>{
        if(a>18){
          console.log('a>18',a)
          return true
        }else{
          console.log('a<18',a)
          return false
        }
      }).then(()=>{
        console.log('成功')
        console.log('结束--------')
      }).catch(()=>{
        console.log('失败')
        console.log('结束--------')
      })
    }
//失败
let a=1

      setInterval(()=>{
        //a++
        //console.log('a----',a)
      },2000)

      console.log('await开始--------')

      console.log(wait)

      await wait.before(5000).until(()=>{
        setTimeout(()=>{
          a=19
        },4000)
        if(a>18){
          console.log('a>18',a)
          return true
        }else{
          console.log('a=<18',a)
          return false
        }
      }).then(()=>{
        console.log('成功')
        console.log('结束--------')
      }).catch(()=>{
        console.log('失败')
        console.log('结束--------')
      })
    }

微信小程序

懒加载

<view wx:for="{{list}}" class='item item-{{index}}'
 wx:key="{{index}}">
	<image class="{{item.show ? 'active': ''}}" src="{{item.show ? item.src : item.def}}"></image>
</view>
image{
	transition: all .3s ease;
	opacity: 0;
}
.active{
	opacity: 1;
}

小程序支持调用createSelectQuery创建一个SelectorQuery实例,并使用select方法来选择节点,并通过boundingClientRect来获取节点信息。

wx.createSelectorQuery().select('.item').boundingClientRect((ret)=>{
	console.log(ret)
}).exec()

小程序里面有个onPageScroll函数,是用来监听页面的滚动的。 还有个getSystemInfo函数,可以获取获取系统信息,里面包含屏幕的高度

showImg(){
	let group = this.data.group
	let height = this.data.height  // 页面的可视高度
	
	wx.createSelectorQuery().selectAll('.item').boundingClientRect((ret) => {
	 ret.forEach((item, index) => {
	   if (item.top <= height) { 判断是否在显示范围内
	     group[index].show = true // 根据下标改变状态
	   }
	 })
	 this.setData({
	   group
	 })
	}).exec()

}
onPageScroll(){ // 滚动事件
	this.showImg()
}

官方图片懒加载

<image lazy-load="true" mode="scaleToFill"></image>

观察者模式的实现和使用

观察者库,class实现

let instance
export default class Observer {
    constructor() {
        if (instance)
            return instance

        instance = this

        this.list = {}
    }
    listen(key, fn) {
        this.list[key] = this.list[key] || []
        this.list[key].push(fn)
    }
    trigger(...events) {
        let key = Array.prototype.shift.call(events)
        //截取参数中的第一个
        let fns = this.list[key]
        if (!fns || fns.length === 0) {
            return false
        }
        for (let i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    }
    remove(key, fn) {
        let fns = this.list[key]
        if (!fns) {
            return false
        }
        if (!fn) {
            fns && (fns.length = 0)
        } else {
            for (let i = fns.length - 1; i >= 0; i--) {
                let _fn = fns[i]
                if (_fn === fn) {
                    fns.splice(i, 1)
                }
            }
        }
    }
}

示例
在浏览器端运行
main.html

<html>

<head>

</head>

<body>
    <script type='module' src='./main.js'></script>
</body>

</html>

main.js

import listen1 from './listen-1.js'
import listen2 from './listen-2.js'
import trigger1 from './trigger-1.js'
import trigger2 from './trigger-2.js'


listen1()
listen2()

trigger1()
trigger2()

listen1

import Observer from './observer.js'

export default function listen1() {
    let observer = new Observer()

    observer.listen('event-1', () => {
        console.log(1)
    })
}

listen2

import Observer from './observer.js'

export default function listen2() {
    let observer = new Observer()

    observer.listen('event-2', () => {
        console.log(2)
    })
}

trigger1

import Observer from './observer.js'

export default function trigger1() {
    let observer = new Observer()

    observer.trigger('event-1')
}

trigger2

import Observer from './observer.js'

export default function trigger2() {
    let observer = new Observer()

    observer.trigger('event-2')
}

观察者库的函数实现

let Observer = function () {
    let list = {},
        listen,
        trigger,
        remove;
    listen = function (key, fn) {
        list[key] = list[key] || [];
        list[key].push(fn);
    };
    trigger = function () {
        let key = Array.prototype.shift.call(arguments),
            fns = list[key];
        if (!fns || fns.length === 0) {
            return false;
        }
        for (let i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments);
        }
    };
    remove = function (key, fn) {
        let fns = list[key];
        if (!fns) {
            return false;
        }
        if (!fn) {
            fns && (fns.length = 0);
        } else {
            for (let i = fns.length - 1; i >= 0; i--) {
                let _fn = fns[i];
                if (_fn === fn) {
                    fns.splice(i, 1);
                }
            }
        }
    };
    return {
        listen: listen,
        trigger: trigger,
        remove: remove
    }
}

export {Observer}

Docker

Docker

Docker 通过 Dockerfile 来对环境进行描述,通过镜像进行交付,使用时不再需要关注环境不一致相关的问题。


初始一个lamp容器

docker search lamp
docker pull

// 绑定本地当前目录下的/www/ 到 docker 容器的/srv/http
// 将本地端口443(左侧443)转发到容器端口443(右侧443)
docker run --name lamp -p 443:443 -v $PWD/www:/srv/http -d greyltc/lamp

访问浏览器地址
https://localhost/
详细说明 https://hub.docker.com/r/greyltc/lamp/

docker 部分命令

docker ps // 查看所有正在运行容器
docker stop containerId // containerId 是容器的ID

docker ps -a // 查看所有容器
docker ps -a -q // 查看所有容器ID

docker stop $(docker ps -a -q) //  stop停止所有容器
docker  rm $(docker ps -a -q) //   remove删除所有容器

linux 当前目录 【未测试】

$(pwd)

apt-get install -y $buildDeps --no-install-recommends
用 --no-install-recommends 去减少没有用的包下载下来

/var/lib/docker Docker 相关的文件
docker rmi 清理一些没用的镜像

--rm 是指当前新建的这个容器,会随容器停止而删除

docker run --rm

CSS3

CSS3

CSS3是层叠样式表(Cascading Style Sheets)语言的最新版本,旨在扩展CSS2.1。

它带来了许多期待已久的新特性, 例如圆角、阴影、gradients(渐变) 、transitions(过渡) 与 animations(动画) 。以及新的布局方式,如 multi-columns 、 flexible box 与 grid layouts。实验性特性以浏览器引擎为前缀(vendor-prefixed),应避免在生产环境中使用,或极其谨慎地使用,因为将来它们的语法和语义都有可能被更改。

W3C 会定期的发布这些 snapshots,如 2007, 2010, 20152017


CSS 模块状态

稳定模块(Stable modules)

有些 CSS 模块已经十分稳定,其状态为 CSSWG 规定的三个推荐品级之一:Candidate Recommendation(候选推荐), Proposed Recommendation(建议推荐)或 Recommendation(推荐)。表明这些模块已经十分稳定,使用时也不必添加前缀, 但有些特性仍有可能在 Candidate Recommendation 阶段被放弃。

CSS Color Module Level 3 | Recommendation 自 2011 年 6 月 7 日
-- | --

  • 增加 opacity 属性,还有 hsl(), hsla(), rgba() 和 rgb() 函数来创建  值。 它还将 currentColor 关键字定义为合法的颜色值。transparent 颜色目前是真彩色 (多亏了支持 alpha 通道) 并且是 rgba(0,0,0,0.0) 的别名。它废弃了 system-color keywords(系统颜色关键字), 它们已经不能在生产环境中使用。

Selectors Level 3 | Recommendation 自 2011 年 9 月 29 日
-- | --
增加:

  • 子串匹配的属性选择器, E[attribute^="value"], E[attribute$="value"], E[attribute*="value"]。
  • 新的伪类::target, :enabled 和 :disabled, :checked, :indeterminate, :root, :nth-child 和 :nth-last-child, :nth-of-type 和 :nth-last-of-type, :last-child, :first-of-type 和 :last-of-type, :only-child 和 :only-of-type, :empty, 和 :not。
  • 伪元素使用两个冒号而不是一个来表示::after 变为 ::after, :before 变为 ::before, :first-letter 变为 ::first-letter, 还有 :first-line 变为 ::first-line。
  • 新的 general sibling combinator(普通兄弟选择器)  ( h1~pre )。

在设备宽为 375px 中 1rem=100px
100vw/375px*100

html {
        font-size: 26.666667vw;
        box-sizing: border-box;
      }

Vue 服务端渲染的配置和原理

服务端渲染的配置和原理

vue-server-render

服务端渲染出首屏页面html文件,不含js.
服务端将js插入渲染出的页面,返回客户端

./build/webpack.config.server.js

// ./build/webpack.config.server.js
// 服务端渲染配置

// 安装服务端渲染库 npm i vue-server-renderer -S
const VueServerPlugin = require('vue-server-render/server-plugin')

const path = require('path')
const ExtractPlugin = require('extract-text-webpack-plugin')
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')


// const defaultPluins = [
//     new webpack.DefinePlugin({
//         'process.env': {
//             NODE_ENV: '"development"'
//         }
//     })
// ]

const config = {
    target: 'node', // 服务端node环境
    entry: path.join(__dirname, '../client/server-entry.js'),
    devtool: 'source-map',
    output: {
        libraryTarget: 'commonjs2', // 模块类型 // module.exports=
        filename: 'server-entry.js', // 服务端无需缓存
        path: path.join(__dirname, '../server-build')
    },
    externals: Object.keys(require('../package.json').dependencies), // vue,vue-router,vuex // 排除服务端打包的文件,直接使用node_modules内的
    module: {
        // node环境没有css,不能将css插入DOM中。
        rules: [
            {
                test: /\.styl/,
                use: ExtractPlugin.extract({
                    fallback: 'vue-style-loader',
                    use: [
                        'css-loader',
                        {
                            loader: 'postcss-loader',
                            options: {
                                sourceMap: true,
                            }
                        },
                        'stylus-loader'
                    ]
                })
            },
        ]
    },
    plugins: [
        new ExtractPlugin('styles.[contentHash:8].css'),
        // new webpack.HotModuleReplacementPlugin(),
        // new webpack.NoEmitOnErrorsPlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
            'process.env.VUE_ENV': '"server"', // vue-server-render 会用到
        }),
        // new VueServerPlugin(), // 服务端渲染,输出json文件 vue-ssr-server-bundle.json
        new VueServerPlugin({
            filename: 'vue-ssr-server-bundle'
        })
    ],
}

// dependencies 应用运行时环境 -S
// devDependencies 应用开发时工具环境,运行时不需要 -D

module.exports = config

./build/webpack.config.client.js

// ./build/webpack.config.client.js

const path = require('path')
const HTMLPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const merge = require('webpack-merge')
const ExtractPlugin = require('extract-text-webpack-plugin')
const baseConfig = require('./webpack.config.base')
const VueClientPlugin = require('vue-server-renderer/client-plugin')

const defaultPlugins = [
    new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: isDev ? '"development"' : '"production"'
        }
    }),
    new HTMLPlugin({
        template: path.join(__dirname, 'template.html')
    }),
    new VueClientPlugin()
]

plugins: defaultPluins.concat([
    new ExtractPlugin('styles.[contentHash:8].css'),
    new webpack.optimize, CommonChunkPlugin({
        name: 'vendor'
    }),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'runtime'
    })
])

./server/server.js

// ./server/server.js
// npm i koa -S
// npm i koa-router -S 
// npm i axios -S 服务端也会使用
// npm i memory-fs -D 只在开发环境使用, 类似nodejs fs,扩展了fs.不会把文件写入磁盘,而是写入内存,节省时间,提高效率
// 只有 nodejs可以提供服务端渲染

const Koa = require('koa')

// npm i koa-send -S   // 帮助发送静态资源文件
const send = require('koa-send')

const path = require('path')

const pageRouter = require('./routers/dev-ssr')

const app = new Koa()

// 服务端渲染区分正式与开发环境
const isDev = process.env.NODE_ENV === 'development'

// 记录日志中间件
app.use(async (ctx, next) => {
    try {
        console.log(`request with path ${ctx.path}`)
        await next()
    } catch (error) {
        console.log(error)
        ctx.status = 500 // 服务器发生错误,返回500
        if (isDev) {
            // 开发环境 直接显示在页面上  提供给开发者的页面
            ctx.body = error.message
        } else {
            // 正式环境 提供给用户的优化后的页面
            ctx.body = 'please try again later'
        }
    }
})

// 处理favicon.ico资源
app.use(async (ctx, next) => {
    if (ctx.path === '/favicon.ico') {
        await send(ctx, '/favicon.ico', { root: path.join(__dirname, '../') })
    } else {
        await next()
    }
})

app.use(pageRouter.routes())
    .use(pageRouter.allowedMethods())

const HOST = process.env.HOST || '0.0.0.0'
const PORT = process.env.PORT || 3333

app.listen(PORT, HOST, () => {
    console.log(`server is listening on ${HOST}:${PORT}`)
})

./nodemon.json

// ./nodemon.json
// 有修改时自动重启服务
// npm i nodemon -D
{
    "restartable": "rs", // 输入rs命令重启服务
    "ignore": [
        // 忽略对某些文件修改操作的监听 // 配置文件
        ".git",
        "node_modules/**/node_modules",
        ".eslintrc",
        "client",
        "build/webpack.config.client.js",
        "public"
    ],
    "verbose": true,
    "env": {
        "NODE_ENV": "development"
    },
    "ext": "js json ejs", // 监听的文件 
}

./server/routers/dev-ssr.js

// ./server/routers/dev-ssr.js
// 处理开发环境的服务端渲染

// 旧版nodejs 不支持import,服务端不需babel编译,直接使用require
const Router = require('koa-router')
const axios = require('axios')
const MemoryFS = require('memory-fs')
const path = require('path')
const fs = require('fs')

const webpack = require('webpack')
const VueServerRenderer = require('vue-server-renderer') // 服务端渲染

const serverRender = require('./server-render')
const serverConfig = require('../../build/webpack.config.server')

const serverCompiler = webpack(serverConfig) //在nodejs中使用webpack编译文件,生成服务端渲染需要的bundle

const mfs = new MemoryFS()
serverCompiler.outputFileSystem = mfs // 指定输出目录在mfs

let bundle // 记录每次生成的打包文件

// 每次文件变化时
serverCompiler.watch({}, (error, states) => {
    // 处理错误
    if (error) throw error
    states = states.toJson()

    // 非打包错误,例如eslint错误
    states.errors.forEach(error => console.log(error))
    states.warnings.forEach(warn => console.log(warn))

    // 读取bundle
    const bundlePath = path.join(
        serverConfig.output.path,
        'vue-ssr-server-bundle.json'
    )

    // bundle=mfs.readFileSync(bundlePath,'utf-8') // 返回字符串
    bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8')) // 转成json
    console.log('new bundle')
})

const handleSSR = async (ctx) => {
    if (!bundle) {
        // 服务器刚刚启动,bundle尚未准备好
        ctx.body = 'wait!!!'
        return
    }

    // 获取webpack打包的js包,用于插入模版
    const clientManifestResp = await axios.get(
        'http://127.0.0.1:8000/public/vue-ssr-client-manifest.json'
    )

    // 实际需要的内容
    const clientManifest = clientManifestResp.data

    // 读取ejs模版文件
    const template = fs.readFileSync(
        path.join(__dirname, '../server.template.ejs'),
        'utf-8'
    )

    const renderer = VueServerRenderer.createBundleRenderer(bundle, {
        inject: false, //取消 vue默认注入
        clientManifest, // 自动生成带script标签的字符串
    })

    await serverRender(ctx, renderer, template)
}

const router = new Router()
router.get('*', handleSSR) // 所有请求都通过handleSSR处理

module.exports = router

./server/server.template.ejs

// ./server/server.template.ejs
// 帮助生成服务端渲染文件的模版
// npm i ejs -S

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>

    // <%= %> 会转义特殊字符
    <%- style %> // 不转译
</head>
<body>
    <div id="root">
        <%- appString %>
    </div>
    
    <%- scripts %>
</body>
</html>
// ./server/routers/ssr.js
// 处理正式环境的服务端渲染

./server/router/server-render.js

// ./server/router/server-render.js
// 服务端渲染

const ejs = require('ejs')

module.exports = async (ctx, renderer, template) => {
    ctx.headers['Content-Type'] = 'text/html' // 服务端渲染最终返回html页面

    const context = { url: ctx.path } // 用于传入vue-server-renderer里

    try {
        const appString = await renderer.renderToString(context)

        // 渲染
        const html = ejs.render(template, {
            appString,
            style: context.renderStyles(), // 带style标签的完整字符串
            scripts: context.renderScripts(),
        })

        ctx.body = html // 返回完整页面
    } catch (error) {
        console.log('render error:', error)
        throw error
    }
}

./clent/server-entry.js

// ./clent/server-entry.js
// 服务端渲染入口文件

import createApp from './create-app'

export default context => {
    return new Promise((resolve, reject) => {
        const { app, router, store } = createApp()

        router.push(context.url) // 服务端环境主动跳转页面,加载组件

        router.onReady(() => {
            // router.onReady一般在服务端渲染用到
            // router.push()后,等到所有异步操作执行完成才会调用本回调函数
            // 开始获取数据

            // 根据 context.url 匹配到响应组件
            const matchedComponents = router.getMatchedComponents()
            if (!matchedComponents.length) {
                // 没有匹配到组件,返回错误,并结束
                return reject(new Error('no component matched'))
            }
            resolve(app)
        })
    })
}

package.json

// package.json
{
    "script": {
        "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js",
        // "dev:server": "cross-env NODE_ENV=development node server/server.js"
        "dev:server": "nodemon server/server.js", // 启动后,有修改自动重启服务
        "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\" "
    }
}
// 开发时   npm run dev:server     ,npm run dev:client
// 访问 loaclhost:3333 服务端渲染
// npm i concurrently -D   // 一次启动两个服务,接受字符串参数

./create-app.js

// ./create-app.js

// 每次服务端渲染,会渲染一个新的app
// 如果单例渲染,会包含上次渲染的状态

import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'

import App from './app.vue'
import createStore from './store/store'
import createRouter from './config/router'

import './assets/styles/global.styl'

Vue.use(VueRouter)
Vue.use(Vuex)

// 每次调用都返回一个新对象,防止内存溢出
export default () => {
    const router = createRouter()
    const store = createStore()

    const app = new Vue({
        router,
        store,
        render: h => h(App)
    })

    return { app, router, store }
}

编码规范

编码规范

fex-team 文档与源码编写风格

任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。 —— Martin Fowler

好的代码一定是整洁的,给阅读的人一种如沐春风,赏心悦目的感觉。

整洁的代码如同优美的散文。 —— Grady Booch

好代码的特性

整洁的代码不一定是好代码,但好代码一定是整洁的

整洁是好代码的必要条件。

整洁的代码一定是高内聚低耦合的,也一定是可读性强、易维护的。

高内聚低耦合

开闭原则OCP (The Open-Close Principle)

单一职责原则SRP (Single Responsibility Principle)

依赖倒置原则DIP (Dependence Inversion Principle)

最少知识原则LKP (Least Knowledge Principle)) / 迪米特法则 (Law Of Demeter)

里氏替换原则LSP (Liskov Substitution Principle)

接口隔离原则ISP (Interface Segregation Principle)

组合/聚合复用原则CARP (Composite/Aggregate Reuse Principle)

可读性

命名
名副其实

好的名称一定是名副其实的,不需要注释解释即可明白其含义的。

/**
* 创建后的天数
**/
int d;
int daysSinceCreation;
容易区分
可读的

不要用自创的缩写,或者中英文混写

足够短

在足够表达其含义的情况下越短越好

格式
垂直格式

通常一行只写一个表达式或者子句。

一组代码代表一个完整的思路,不同组的代码中间用空行间隔。

类静态变量、实体变量应定义在类的顶部。类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。

水平格式

要有适当的缩进和空格。

团队统一

通常,同一个团队的风格尽量保持一致

类与函数
类和函数应短小,更短小
函数只做一件事(同一层次的事)

同一个函数的每条执行语句应该是统一层次的抽象。

例如,我们经常会写一个函数需要给某个DTO赋值,然后再调用接口,接着返回结果。那么这个函数应该包含三步:DTO赋值,调用接口,处理结果。如果函数中还包含了DTO赋值的具体操作,那么说明此函数的执行语句并不是在同一层次的抽象。

参数越少越好

参数越多的函数,调用时越麻烦。尽量保持参数数量足够少,最好是没有

注释
别给糟糕的代码加注释,重构他

注释不能美化糟糕的代码。

当企图使用注释前,先考虑是否可以通过调整结构,命名等操作,消除写注释的必要,往往这样做之后注释就多余了。

好的注释提供信息、表达意图、阐释、警告

注释最好提供一些代码没有的额外信息,展示自己的设计意图,而不是写具体如何实现。

删除掉注释的代码

git等版本控制已经帮我们记录了代码的变更历史

错误处理
错误处理很重要,但他不能搞乱代码逻辑

错误处理应该集中在同一层处理,并且错误处理的函数最好不包含其他的业务逻辑代码,只需要处理错误信息即可。

抛出异常时提供足够多的环境和说明,方便排查问题

异常抛出时最好将执行的类名,关键数据,环境信息等均抛出,此时自定义的异常类就派上用场了,通过统一的一层处理异常,可以方便快速地定位到问题。

特例模型可消除异常控制或者null判断

大多数的异常都是来源于NPE,有时候这个可以通过Null Object来消除掉。

尽量不要返回null,不要传null参数

不返回null和不传null也是为了尽量降低NPE的可能性。

什么不是好的代码

重复

重复可能是软件中一`切邪恶的根源。 —— Robert C.Martin

函数过长、类过大、参数过长

发散式变化、霰弹式修改、依恋情结

如果一个类不是单一职责的,则不同的变化可能都需要修改这个类,说明存在发散式变化,应考虑将不同的变化分离开。

如果某个变化需要修改多个类的方法,则说明存在霰弹式修改,应考虑将这些需要修改的方法放入同一个类。

如果函数对于某个类的兴趣高于了自己所处的类,说明存在依恋情结,应考虑将函数转移到他应有的类中。

数据泥团

有时候会发现三四个相同的字段,在多个类和函数中均出现,这时候说明有必要给这一组字段建立一个类,将其封装起来。

过多的if...else 或者使用switch

过多的if...else或者switch,都应该考虑用多态来替换掉。甚至有些人认为除个别情况外,代码中就不应该存在if...else。

javascript对象多种表现

let a={b:()=>console.log(1)}
a.b()
//1
let d={a(){console.log(3)}}
d.a()
//3
let e={['a'](){console.log(5)}}
e.a()
//5
e['a']()
//5
e['a']()===e.a()

####使用中括号解析变量或数值为对象的字面量属性:

数值字面量属性的解析

let c = { [1]: 2 }
console.log(c)
//{1: 2}

值为数值的变量字面量属性的解析

let f = 1
let d = { [f]: 2 }
console.log(d)
//{1: 2}

值为字符串的变量字面量属性的解析

let e = 'a'
let k = { [e]: 2 }
console.log(k)
//{a: 2}

Vuex

Vuex

npm i veux -S

store.js

// store.js

import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

// 会缓存全局唯一的store,服务端渲染会内存溢出
const store = new Vuex.Store({
    // 初始化store
    state: {
        count: 0
    },

    // 修改store
    mutations: {
        updateCount(state, countNum) {
            state.count = countNum
        }
    }
})

export default store

index.js

// index.js

import store from './store/store'

new Vue({
    router,
    store,
    render: (h) => h(App)
}).$mount('#root')

App.vue

// App.vue
// 直接调用store

mounted(){
    console.log(this.$store) // 应用入口传入的store对象

    // 修改store
    // 传入mutation,countNum
    this.$store.commit('updateCount', 1)
}

computed: {
    count(){
        // 获得count
        return this.$store.state.count
    }
}

服务端渲染

store.js

// store.js

import Vuex from 'vuex'

//服务端渲染用法,返回函数,防止内存溢出
export default ()=>{
    return new Vuex.Store({
        // 初始化store
        state: {
            count: 0
        },
    
        // 修改store
        mutations: {
            updateCount(state, countNum) {
                state.count = countNum
            }
        }
    })
}

index.js

// index.js
// 服务端渲染配置store

import Vue from 'vue'
import VueRouter from 'vue-router'
// import store from './store/store'
import Vuex from 'vuex'
import createStore from './store/store'

vue.use(VueRouter)
Vue.use(Vuex)

const router = createRouter()
const store = createStore()

new Vue({
    router,
    store,
    render: (h) => h(App)
}).$mount('#root')

App.vue

// App.vue
// 直接调用store

mounted(){
    console.log(this.$store) // 应用入口传入的store对象

    // 修改store
    // 传入mutation,countNum
    this.$store.commit('updateCount', 1)
}

computed: {
    count(){
        // 获得count
        return this.$store.state.count
    }
}

重新组织,单模块的store

./store/state/state.js

// ./store/state/state.js
// 单模块的state

// 所有值都要事先声明,才能实现响应式
export default {
    count: 0
}

./store/mutations/mutations.js

// ./store/mutations/mutations.js

export default {
    updateCount(state, countNum) {
        state.count = countNum
    }
}

./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store
        // 单独的state,更好的组织state
        state: defaultState,

        // 修改store
        mutations, // ES6
    })
}

Vuex : state,getters

./store/state/state.js

// ./store/state/state.js
// 单模块的state

// 所有值都要事先声明,才能实现响应式
export default {
    count: 0,
    firstName: 'harry',
    lastName: 'potter',
}

./store/getters/getters.js

// ./store/getters/getters.js
// 单模块
// 对象

// getters类似computed属性
// 用于生成在应用内可直接使用的数据,方便实用,无需在computed中格式化,使代码可复用,易维护
// 用来将后端数据格式化为适合view层显示的数据
export default {
    fullName(state) {
        // 默认接受state
        return `${state.firstNmae} ${state.lastName}`
    }
}

./store/mutations/mutations.js

// ./store/mutations/mutations.js

export default {
    updateCount(state, countNum) {
        state.count = countNum
    }
}

./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store
        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6
    })
}

index.js

// index.js
// 服务端渲染配置store

import Vue from 'vue'
import VueRouter from 'vue-router'
// import store from './store/store'
import Vuex from 'vuex'
import createStore from './store/store'

vue.use(VueRouter)
Vue.use(Vuex)

const router = createRouter()
const store = createStore()

new Vue({
    router,
    store,
    render: (h) => h(App)
}).$mount('#root')

App.vue

// App.vue
// 直接调用store

mounted(){
    console.log(this.$store) // 应用入口传入的store对象

    // 修改store
    // 传入mutation,countNum
    this.$store.commit('updateCount', 1)
}

computed: {
    count(){
        // 获得count
        return this.$store.state.count
    },
    fullName(){
        return this.$store.getters.fullName
    }
}

使用帮助方法,更简洁的使用store中的数据,无需声明

mapState,mapGetters
App.vue

// App.vue
// 不直接调用store

// 支持对象扩展语法  npm i babel-preset-stage-1 -D
// .babelrc "presets":["env","stage-1"]
// 使用帮助方法,更简洁的使用store中的数据,无需声明
import {
    mapState,
    mapGetters
} from 'vuex'

mounted(){
    console.log(this.$store) // 应用入口传入的store对象

    // 修改store
    // 传入mutation,countNum
    this.$store.commit('updateCount', 1)
}

computed: {
    // ...mapState(['count']), // 自动获取 this.$store.state.count

    // ...mapState({
    //     counter:'count', // {{counter}},更改名称的用法,自动获取 this.$store.state.count
    // }),

    ...mapState({
        // 推荐:使用函数,更改名称的用法,自动获取 this.$store.state.count
        // 适合计算
        counter: (state) => state.count,
    }),
    // count(){
    //     // 获得count
    //     return this.$store.state.count
    // },

    ...mapGetters(['fullName']), // 自动获取 this.$store.getters.fullName

    // fullName(){
    //     return this.$store.getters.fullName
    // }
}

Vuex : mutation,action

所有对state的修改都使用mutation,必须是同步操作,不能是异步操作
mutation
./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6
    })
}

action
./actions/actions.js
异步修改store,用来处理异步数据修改的方法,适合异步请求数据

// ./actions/actions.js

// 一个对象
// 异步修改store,用来处理异步数据修改的方法,适合异步请求数据

export default {
    updateCountAsync(store, data) {
        // 接受整个store对象 + 触发action时传入的唯一一个参数,一般为对象
        setTimeout(() => {
            store.commit('updateCount', data.num)
        }, data.time)
    }
}

./store/mutations/mutations.js

// ./store/mutations/mutations.js
// 适合同步修改数据
export default {
    updateCount(state, countNum) {
        state.count = countNum
    }
}

./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,
    })
}

./store/state/state.js

// ./store/state/state.js
// 单模块的state

// 所有值都要事先声明,才能实现响应式
export default {
    count: 0,
    firstName: 'harry',
    lastName: 'potter',
}

App.vue

mounted(){
    // 提交action必须使用 this.$store.dispatch
    this.$store.dispatch(
        'updateCountAsync', // action的方法名字面量
        {
            // 提交action时传入的参数对象
            num: 5,
            time: 2000,
        }
    )
}

Actions,Mutations 帮助函数
App.vue

// App.vue

// Actions,Mutations 帮助函数
import {
    mapState,
    mapGetters,
    mapActions, // 对应异步操作
    mapMutations // 对应同步操作
} from 'vuex'

methods: {
    ...mapActions(['updateCountAsync']),
    ...mapMutations(['updateCount'])
}

mounted(){
    // 提交action必须使用 this.$store.dispatch
    // this.$store.dispatch(
    //     'updateCountAsync', // action的方法名字面量
    //     {
    //         // 提交action时传入的参数对象
    //         num: 5,
    //         time: 2000,
    //     }
    // )

    // 使用帮助函数后可以简化调用action,无需传入方法名,只传参数对象
    this.updateCountAsync({
        num: 5,
        time: 2000
    })

    // 使用帮助函数后可以简化调用mutation,无需传入方法名,只传参数对象
    this.updateCount({
        num: i++
    })
}

Vuex 模块化

./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,

        // 模块化,带作用域
        modules: {
            a: {
                state: {
                    text: 1
                }
            },
            b: {
                state: {
                    text: 2
                }
            }
        }
    })
}

App.vue
使用方法直接调用模块化的store

// App.vue

<p>{{textA}}</p>

computed:{
    // 模块后,带命名空间的调用
    textA(){
        return this.$store.state.a.text
    }
}

使用帮助函数调用模块化的store
App.vue

// App.vue

<p>{{ textA }}</p>

// Actions,Mutations 帮助函数
import {
    mapState,
    mapGetters,
    mapActions, // 对应异步操作
    mapMutations // 对应同步操作
} from 'vuex'

computed: {
    // 模块后,带命名空间的调用
    // textA(){
    //     return this.$store.state.a.text
    // }

    // 使用帮助函数调用
    ...mapState({
        textA: state => state.a.text // 必须使用方法返回
    })
}

Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,

        // 模块化,带作用域
        modules: {
            a: {
                state: {
                    text: 1
                },
                mutations: {
                    updateText(state, text) {
                        // 自动获得a模块的state,非全局state
                        console.log('a.state', state)
                        state.text = text
                    }
                }
            },
            b: {
                state: {
                    text: 2
                }
            }
        }
    })
}

App.vue

// App.vue

<p>{{ textA }}</p>

// Actions,Mutations 帮助函数
import {
    mapState,
    mapGetters,
    mapActions, // 对应异步操作
    mapMutations // 对应同步操作
} from 'vuex'

methods: {
    ...mapActions(['updateCountAsync']),
    ...mapMutations(['updateCount','updateText']), // 帮助函数声明模块化的 updateText mutation
    // Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
},

mounted(){
    this.updateText('123') //调用模块化的 updateText
},

computed: {
    // 模块后,带命名空间的调用
    // textA(){
    //     return this.$store.state.a.text
    // }

    // 使用帮助函数调用模块化的 state
    ...mapState({
        textA: state => state.a.text // 必须使用方法返回
    })
}

启用模块命名空间后,namespaced:true,需通过变量方式调用模块的mutation
./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,

        // 模块化,带作用域
        modules: {
            a: {
                namespaced:true, // 使a模块的mutations具有独立命名空间,不在全局mutations中。防止命名冲突。
                state: {
                    text: 1
                },
                mutations: {
                    updateText(state, text) {
                        // 自动获得a模块的state,非全局state
                        console.log('a.state', state)
                        state.text = text
                    }
                }
            },
            b: {
                state: {
                    text: 2
                }
            }
        }
    })
}

App.vue

// App.vue

<p>{{ textA }}</p>

// Actions,Mutations 帮助函数
import {
    mapState,
    mapGetters,
    mapActions, // 对应异步操作
    mapMutations // 对应同步操作
} from 'vuex'

methods: {
    ...mapActions(['updateCountAsync']),
    ...mapMutations(['updateCount', 'a/updateText']), // 帮助函数声明模块化的 updateText mutation
    // Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
    // 启用命名空间后,调用模块内的mutations --- a/updateText
},

mounted(){
    // this.updateText('123') //调用模块化的 updateText // 未启用模块命名空间

    // 启用模块命名空间后,namespaced:true,需通过变量方式调用模块的mutation
    this['a/updateText']('123')
},

computed: {
    // 模块后,带命名空间的调用
    // textA(){
    //     return this.$store.state.a.text
    // }

    // 使用帮助函数调用模块化的 state
    ...mapState({
        textA: state => state.a.text // 必须使用方法返回
    })
}

启用命名空间的 getters,actions
./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,

        // 模块化,带作用域
        modules: {
            a: {
                namespaced:true, // 使a模块的mutations具有独立命名空间,不在全局mutations中。防止命名冲突。
                state: {
                    text: 1
                },
                mutations: {
                    updateText(state, text) {
                        // 自动获得a模块的state,非全局state
                        console.log('a.state', state)
                        state.text = text
                    }
                },
                getters:{
                    // 启用命名空间的 getters
                    textPlus(state){
                        // 自动获得a模块的state,非全局state
                        return state.text+1
                    }
                }
            },
            b: {
                state: {
                    text: 2
                }
            }
        }
    })
}

App.vue

// App.vue

<p>{{ textA }}</p>

// Actions,Mutations 帮助函数
import {
    mapState,
    mapGetters,
    mapActions, // 对应异步操作
    mapMutations // 对应同步操作
} from 'vuex'

methods: {
    ...mapActions(['updateCountAsync']),
    ...mapMutations(['updateCount', 'a/updateText']), // 帮助函数声明模块化的 updateText mutation
    // Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
    // 启用命名空间后,调用模块内的mutations --- a/updateText
},

mounted(){
    // this.updateText('123') //调用模块化的 updateText // 未启用模块命名空间

    // 启用模块命名空间后,namespaced:true,需通过变量方式调用模块的mutation
    this['a/updateText']('123')

    // 启用模块命名空间后,namespaced:true,需通过变量方式调用模块的Getter
    console.log(this['a/textPlus'])
},

computed: {
    // 模块后,带命名空间的调用
    // textA(){
    //     return this.$store.state.a.text
    // }

    // 使用帮助函数调用模块化的 state
    ...mapState({
        textA: state => state.a.text // 必须使用方法返回
    }),

    // 使用帮助函数调用模块化的 启用命名空间的  getters // 'a/textPlus'
    // 'a/textPlus' 无法在模版中直接使用
    ...mapGetters(['fullName', 'a/textPlus'])
}

使用帮助函数 重命名getters,方便模版使用
App.vue

// App.vue

<p>{{ textA }}</p>
    <p>{{ textPlus }}</p>

// Actions,Mutations 帮助函数
import {
    mapState,
    mapGetters,
    mapActions, // 对应异步操作
    mapMutations // 对应同步操作
} from 'vuex'

methods: {
    ...mapActions(['updateCountAsync']),
    ...mapMutations(['updateCount', 'a/updateText']), // 帮助函数声明模块化的 updateText mutation
    // Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
    // 启用命名空间后,调用模块内的mutations --- a/updateText
},

mounted(){

},

computed: {
    // 模块后,带命名空间的调用
    // textA(){
    //     return this.$store.state.a.text
    // }

    // 使用帮助函数调用模块化的 state
    ...mapState({
        textA: state => state.a.text // 必须使用方法返回
    }),

    // 使用帮助函数调用模块化的 启用命名空间的 // 'a/textPlus'
    // 'a/textPlus' 无法在模版中直接使用
    // ...mapGetters(['fullName', 'a/textPlus'])

    // 使用帮助函数 重命名getters
    ...mapGetters({
        'fullName': 'fullName', // 全局的getter
        'textPlus': 'a/textPlus', // a模块启用命名空间的getter - 'a/textPlus' 重命名为 'textPlus',可在模版使用
    })
}

在模块内获取全局的getters, state - rootState
./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    return new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,

        // 模块化,带作用域
        modules: {
            a: {
                // namespaced: true 会在调用mutation时自动补全模块名前缀 a/
                namespaced: true, // 使a模块的mutations具有独立命名空间,不在全局mutations中。防止命名冲突。
                state: {
                    text: 1
                },
                mutations: {
                    updateText(state, text) {
                        // 自动获得a模块的state,非全局state
                        console.log('a.state', state)
                        state.text = text
                    }
                },
                getters: {
                    // 启用命名空间的 getters
                    textPlus(state, getters, rootState) {
                        // 自动获得a模块的state,非全局state
                        // 自动获得全局的,所有的getter方法-getters
                        // 自动获得全局的 state - rootState
                        // return state.text + rootState.count // 全局的count
                        return state.text + rootState.b.text // b模块的text
                    }
                },
                actions: {
                    add({ state, commit, rootState }) {
                        // 自动获取a模块,包括state,mutations,getters,rootState

                        // 默认commit 本模块的mutation
                        commit('updateText', rootState.count)

                        // 手动commit 全局的mutation
                        commit('updateCount', { num: rootState.count }, { root: true })
                    }
                },
                modules: {
                    // 模块内可以再声明模块
                }
            },
            b: {
                state: {
                    text: 2
                },
                actions: {
                    textAction({ commit }) {
                        commit('a/updateText', 'text text', { root: true }) // 通过指定完整命名空间,调用其他模块的mutation
                    }
                }
            }
        }
    })
}

App.vue

// App.vue

<p>{{ textA }}</p>
    <p>{{ textPlus }}</p>

// Actions,Mutations 帮助函数
import {
    mapState,
    mapGetters,
    mapActions, // 对应异步操作
    mapMutations // 对应同步操作
} from 'vuex'

methods: {
    ...mapActions(['updateCountAsync', 'a/add', 'textAction']), // b模块未声明命名空间,textAction无需写模块名
    ...mapMutations(['updateCount', 'a/updateText']), // 帮助函数声明模块化的 updateText mutation
    // Vuex 默认会将全部mutation,包括模块化的,放入全局mutations
    // 启用命名空间后,调用模块内的mutations --- a/updateText
},

mounted(){
    this['a/add']()

    this.textAction() // b模块未声明命名空间
},

computed: {
    // 模块后,带命名空间的调用
    // textA(){
    //     return this.$store.state.a.text
    // }

    // 使用帮助函数调用模块化的 state
    ...mapState({
        textA: state => state.a.text // 必须使用方法返回
    }),

    // 使用帮助函数调用模块化的 启用命名空间的 // 'a/textPlus'
    // 'a/textPlus' 无法在模版中直接使用
    // ...mapGetters(['fullName', 'a/textPlus'])

    // 使用帮助函数 重命名getters
    ...mapGetters({
        'fullName': 'fullName', // 全局的getter
        'textPlus': 'a/textPlus', // a模块启用命名空间的getter - 'a/textPlus' 重命名为 'textPlus',可在模版使用
    })
}

动态注册模块

index.js

// index.js
// 服务端渲染配置store

import Vue from 'vue'
import VueRouter from 'vue-router'
// import store from './store/store'
import Vuex from 'vuex'
import createStore from './store/store'

vue.use(VueRouter)
Vue.use(Vuex)

const router = createRouter()
const store = createStore()

// 动态注册模块,配合异步加载组件
store.registerModule('c', {
    state: {
        text: 3
    }
})

new Vue({
    router,
    store,
    render: (h) => h(App)
}).$mount('#root')

App.vue

computed: {
    // 使用帮助函数调用模块化的 state
    ...mapState({
        textA: state => state.a.text, // 必须使用方法返回
        textC: state => state.c.text, // 使用动态注册的模块c
    })
}

Vuex 热更新

./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    const store = new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,

        // 模块化,带作用域
        modules: {
            a: {
                // namespaced: true 会在调用mutation时自动补全模块名前缀 a/
                namespaced: true, // 使a模块的mutations具有独立命名空间,不在全局mutations中。防止命名冲突。
                state: {
                    text: 1
                },
                mutations: {
                    updateText(state, text) {
                        // 自动获得a模块的state,非全局state
                        console.log('a.state', state)
                        state.text = text
                    }
                },
                getters: {
                    // 启用命名空间的 getters
                    textPlus(state, getters, rootState) {
                        // 自动获得a模块的state,非全局state
                        // 自动获得全局的,所有的getter方法-getters
                        // 自动获得全局的 state - rootState
                        // return state.text + rootState.count // 全局的count
                        return state.text + rootState.b.text // b模块的text
                    }
                },
                actions: {
                    add({ state, commit, rootState }) {
                        // 自动获取a模块,包括state,mutations,getters,rootState

                        // 默认commit 本模块的mutation
                        commit('updateText', rootState.count)

                        // 手动commit 全局的mutation
                        commit('updateCount', { num: rootState.count }, { root: true })
                    }
                },
                modules: {
                    // 模块内可以再声明模块
                }
            },
            b: {
                state: {
                    text: 2
                },
                actions: {
                    textAction({ commit }) {
                        commit('a/updateText', 'text text', { root: true }) // 通过指定完整命名空间,调用其他模块的mutation
                    }
                }
            }
        }
    })

    if (module.hot) {
        // 启用Vuex热更新,开发时不刷新页面更新,防止刷新后丢失状态
        module.hot.accept([
            './state/state',
            './mutations/mutations',
            './getters/getters',
            './actions/actions'
        ], () => {
            // 开启热更新
            // import 只能用于代码最外层
            const newState = require('./state/state').dafault
            const newMutations = require('./mutations/mutations').default
            const newGetters = require('./getters/getters').default
            const newActions = require('./actions/actions').default

            // 动态更新模块
            store.hotUpdate({
                state: newState,
                mutations: newMutations,
                getters: newGetters,
                actions: newActions
            })
        })
    }

    return store
}

Vuex API和配置

解绑模块
index.js

// 解绑模块
store.unregisterModule('c')

store.watch
index.js

store.watch(
    (state) => {
        // 监听state
        // 相当于 getter方法
        state.count + 1
    },
    (newCount) => {
        // 当第一个方法参数发生变动时触发,并传入改变的状态
        console.log('new count watched:', newCount)

    }
)

监听mutation ,监听action
index.js

// 用于制作Vuex插件
// store订阅,监听mutation // 可以获得所有mutation的变化
store.subscribe((mutation, state) => {
    // 每次有一个mutation被调用,都会执行回调函数,传入正在调用的mutation,和最新state
    console.log(mutation.type) // updateCount
    console.log(mutation.payload) // mutation接受的参数 {num:3}
})

// store订阅,监听action
store.subscribeAction((action, state) => {
    // 每次有一个action被调用,都会执行回调函数,传入正在调用的action,和最新state
    console.log(action.type) // updateCountAsync
    console.log(action.payload) // action接受的参数 {num:5,time:2000}
})

Vuex 插件
./store/store.js

// ./store/store.js

import Vuex from 'vuex'

// 导入默认state
import defaultState from './state/state'

// 导入默认mutations
import mutations from './mutations/mutations'

// 导入默认的getters
import getters from './getters/getters'

// 导入默认的actions
import actions from './actions/actions'

const isDev = process.env.NODE_ENV === 'development'

//服务端渲染用法,返回函数,防止内存溢出
export default () => {
    const store = new Vuex.Store({
        // 初始化store

        // strict:true, // 防止从外部修改state, //this.$state.count=2 // 仅限开发环境,正式环境需关闭
        strict: isDev,

        // 单独的state,更好的组织state
        state: defaultState,

        getters,

        // 修改store
        mutations, // ES6

        // 通过提交mutation异步修改store
        actions,

        // 使用插件,按顺序执行
        plugins: [
            (store) => {
                // 初始接受store
                // vue初始化时调用plugins
                store.subscribe(xxx) // 订阅事件
                store.watch(xxx)
            }
        ],

        
    })

    
    return store
}

vuex

Markdown

Markdown 基本语法

http://www.markdown.cn

Markdown语法主要分为如下几大部分: 标题,段落,区块引用,代码区块,强调,列表,分割线,链接,图片,反斜杠 \,符号'`'。

4.1 标题

两种形式:
1)使用=和-标记一级和二级标题。

一级标题

二级标题

效果:

一级标题

二级标题
2)使用#,可表示1-6级标题。

一级标题

二级标题

三级标题

四级标题

五级标题
六级标题

效果:

一级标题

二级标题

三级标题

四级标题

五级标题

六级标题
4.2 段落

段落的前后要有空行,所谓的空行是指没有文字内容。若想在段内强制换行的方式是使用两个以上空格加上回车(引用中换行省略回车)。

4.3 区块引用

在段落的每行或者只在第一行使用符号>,还可使用多个嵌套引用,如:

区块引用

嵌套引用
效果:

区块引用

嵌套引用
4.4 代码区块

代码区块的建立是在每行加上4个空格或者一个制表符(如同写代码一样)。如
普通段落:

void main()
{
printf("Hello, Markdown.");
}

代码区块:

void main()
{
printf("Hello, Markdown.");
}
注意:需要和普通段落之间存在空行。

4.5 强调

在强调内容两侧分别加上*或者_,如:

斜体斜体
粗体粗体
效果:

斜体,斜体
粗体,粗体
4.6 列表

使用·、+、或-标记无序列表,如:

-(+) 第一项 -(+) 第二项 - (+*)第三项
注意:标记后面最少有一个_空格_或_制表符_。若不在引用区块中,必须和前方段落之间存在空行。

效果:

第一项
第二项
第三项
有序列表的标记方式是将上述的符号换成数字,并辅以.,如:

1 . 第一项
2 . 第二项
3 . 第三项
效果:

第一项
第二项
第三项
4.7 分割线

分割线最常使用就是三个或以上*,还可以使用-和_。

4.8 链接

链接可以由两种形式生成:行内式和参考式。
行内式:

younghz的Markdown库
效果:

younghz的Markdown库。
参考式:

[younghz的Markdown库1][1]
[younghz的Markdown库2][2]
[1]:https:://github.com/younghz/Markdown "Markdown"
[2]:https:://github.com/younghz/Markdown "Markdown"
效果:

younghz的Markdown库1
younghz的Markdown库2
注意:上述的[1]:https:://github.com/younghz/Markdown "Markdown"不出现在区块中。

4.9 图片

添加图片的形式和链接相似,只需在链接的基础上前方加一个!。

![cardPoint](./cardPoint.jpg)
//  同级目录下的cardPoint.jpg

4.10 反斜杠\

相当于反转义作用。使符号成为普通符号。

4.11 符号'`'

起到标记作用。如:

ctrl+a
效果:

ctrl+a
5. 谁在用?

Markdown的使用者:

GitHub
简书
Stack Overflow
Apollo
Moodle
Reddit
等等
6. 尝试一下

Chrome下的插件诸如stackedit与markdown-here等非常方便,也不用担心平台受限。
在线的dillinger.io评价也不错
Windowns下的MarkdownPad也用过,不过免费版的体验不是很好。
Mac下的Mou是国人贡献的,口碑很好。
Linux下的ReText不错。
当然,最终境界永远都是笔下是语法,心中格式化 :)。

注意:不同的Markdown解释器或工具对相应语法(扩展语法)的解释效果不尽相同,具体可参见工具的使用说明。 虽然有人想出面搞一个所谓的标准化的Markdown,[没想到还惹怒了健在的创始人John Gruber] (http://blog.codinghorror.com/standard-markdown-is-now-common-markdown/)。

以上基本是所有traditonal markdown的语法。

UI组件

提示组件

tip
toggleToast('加载成功')

<div class="tip">发送成功</div>
.tip{
	position: fixed;
	top: 50%;
	left: 50%;
	transform: translate(-50%,-50%);
	display: inline-block;
	padding:0.5rem;
	border-radius: 3px;
	background-color: rgba(0,0,0,0.5);
	color:#FFF;
	transition: all 0.3s linear;
	opacity: 0;
	z-index: -1;
}

.show{
	opacity: 1;
	z-index: 1;
}
const toggleToast = (tip) => {
		let tipElement = $(".tip")
		tipElement.html(tip)
		tipElement.addClass('show')
		setTimeout(() => {
			tipElement.removeClass('show')
		},3000)
	}

Redux

Redux (https://github.com/reactjs/redux) 是一个“可预测化状态的 JavaScript 容器”
安装redux

npm install --save redux

actionCreator
main.html

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script src="redux.js"></script>
<script src="main.js" type="module"></script>
</body>
</html>

main.js

//import { createStore } from 'redux'

let actionCreator = () => {
  //负责构建一个 action
  return {
    type: 'AN_ACTION'
  }
}
console.log(actionCreator())
//{ type: 'AN_ACTION' }

Reducer函数是action的订阅者。
Reducer函数只是一个纯函数,它接收应用程序的当前状态以及发生的action,然后返回修改后的新状态(或者有人称之为归并后的状态)。
如何把数据变更传播到整个应用程序?
使用订阅者来监听状态的变更情况。

Redux实例称为store并用以下方式创建:
createStore 函数必须接收一个能够修改应用状态的函数

import {createStore} from 'redux'
var store = createStore(() => {})

Flux 的Store 可以保存你的 data,而 Reducer 不能
在 Redux 中,每次调用 reducer 时,都会传入待更新的 state。这样的话,Redux 的 store 就变成了
“无状态的 store” 并且改了个名字叫 Reducer。
每当一个 action 发生时,Redux 都能调用这个函数。
往 createStore 传 Reducer 的过程就是给 Redux 绑定 action 处理函数(也就是 Reducer)的过程。

reducer
我们的 reducer 被调用了,但我们并没有 dispatch 任何 action...
这是因为在初始化应用 state 的时候,Redux dispatch 了一个初始化的 action

 ({ type:'@ @redux/INIT' })

在被调用时,一个 reducer 会得到这些参数:(state, action)
在应用初始化时,state 还没被初始化,因此它的值是 "undefined"

//import { createStore } from 'redux'
let actionCreator = () => {
  //负责构建一个 action
  return {
    type: 'AN_ACTION'
  }
}
console.log(actionCreator())
//{ type: 'AN_ACTION' }

let reducer = (...args) => {
  console.log(args)
}

let store = Redux.createStore(reducer)
//Reducer was called with args [ undefined, { type: '@@redux/INIT' } ]

从 Redux 实例中读取 state
一个 reducer 只是一个函数,它能收到程序当前的 state 与 action,
然后返回一个 modify(又或者学别人一样称之为 reduce )过的新 state ”
我们的 reducer 目前什么都不返回,所以程序的 state 当然只能是 reducer() 返回的那个叫“undefined” 的东西

//import { createStore } from 'redux'
let actionCreator = () => {
  //负责构建一个 action
  return {
    type: 'AN_ACTION'
  }
}
console.log(actionCreator())
//{ type: 'AN_ACTION' }

let reducer = (state, action) => {
  console.log(state)
  console.log(action)
}

let store = Redux.createStore(reducer)
//undefined
//{type: "@@redux/INITv.0.z.b.x.p"}
console.log(store.getState())
//undefined

在 reducer 收到 undefined 的 state 时,给程序发一个初始状态
调用 reducer ,只是为了响应一个派发来的 action

let reducer = (state, action) => {
  if (typeof state === 'undefined') {
    return {}
  }
  return state
}

let store = Redux.createStore(reducer)
console.log(store.getState())
//{}

更加清晰的ES6模式

let reducer = (state = {}, action) => {
  return state
}

let store = Redux.createStore(reducer)
console.log(store.getState())
//{}

常见模式:在 reducer 里用 switch 来响应对应的 action
用 switch 的时候, 永远 不要忘记放个 “default” 来返回 “state”,否则,你的 reducer 可能会返回 “undefined” (等于你的 state 就丢了)
之所以这个例子能用ES7 Object Spread notation ,是因为它只对 state 里的 { message: action.value} 做了浅拷贝(也就是说, state 第一个层级的属性直接被 { message: action.value } 覆盖掉了 —— 与之相对,其实也有优雅的合并方式 )
但是如果数据结构更复杂或者是嵌套的,那处理state更新的时候,很可能还需要考虑一些完全不同的做法:

let reducer = (state = {}, action) => {
  switch (action.type) {
    case 'SAY_SOMETHING':
      return {
        ...state,
        message: action.value
      }
    default:
      return state;
  }
}

let store = Redux.createStore(reducer)
console.log(store.getState())
//{}

reducer 是可以处理任何类型的数据结构
让每个 reducer 只处理整个应用的部分 state

let userReducer = function (state = {}, action) {
  switch (action.type) {
      // etc.
    default:
      return state;
  }
}

let itemsReducer = function (state = [], action) {
  switch (action.type) {
      // etc.
    default:
      return state;
  }
}

createStore 只接收一个 reducer 函数
combineReducers 接收一个对象并返回一个函数,当 combineReducers 被调用时,它会去调用每个
reducer,并把返回的每一块 state 重新组合成一个大 state 对象(也就是 Redux 中的 Store)
最终的 state 完全是一个简单的对象,由userReducer 和 itemsReducer 返回的部分 state 共同组成。

let reducer = Redux.combineReducers({
  user: userReducer,
  items: itemsReducer
})

let store = Redux.createStore(reducer)
console.log(store.getState())
//{user: {…}, items: Array(0)}

调度action
dispatch 函数,是 Redux 提供的,并且它会将 action 传递给任何一个 reducer
dispatch 函数本质上是 Redux的实例的属性 "dispatch"
每一个 reducer 都被调用了,但是没有一个 action type 是 reducer 需要的,因此 state 是不会发生变化的

let userReducer = function (state = {}, action) {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.name
      }
    default:
      return state;
  }
}

let itemsReducer = function (state = [], action) {
  switch (action.type) {
    case 'ADD_ITEM':
      return [
        ...state,
        action.item
      ]
    default:
      return state;
  }
}

let reducer = Redux.combineReducers({
  user: userReducer,
  items: itemsReducer
})

let store = Redux.createStore(reducer)
console.log(store.getState())
//{user: {…}, items: Array(0)}
store.dispatch({
  type: 'AN_ACTION'
})

使用 action creator 发送一个我们想要的 action
分发 action,通过 reducer 函数修改应用状态
处理一个 action,并且它改变了应用的 state
至止,我们接触的应用流程是这样的:ActionCreator -> Action -> dispatcher -> reducer
同步 action creator,它创建同步的 action
当 action creator 被调用时,action 会被立即返回

let userReducer = function (state = {}, action) {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.name
      }
    default:
      return state;
  }
}

let itemsReducer = function (state = [], action) {
  switch (action.type) {
    case 'ADD_ITEM':
      return [
        ...state,
        action.item
      ]
    default:
      return state;
  }
}

let reducer = Redux.combineReducers({
  user: userReducer,
  items: itemsReducer
})

let store = Redux.createStore(reducer)
console.log(store.getState())
//{user: {…}, items: Array(0)}

let setNameActionCreator = function (name) {
  return {
    type: 'SET_NAME',
    name: name
  }
}
store.dispatch(setNameActionCreator('harry'))

console.log(store.getState())
//items:[]
//user:{name: "harry"}

使用中间件处理自定义 action,比如异步 action

let thunkMiddleware = function ({dispatch, getState}) {
  // console.log('Enter thunkMiddleware');
  return function (next) {
    // console.log('Function "next" provided:', next);
    return function (action) {
      // console.log('Handling action:', action);
      return typeof action === 'function' ?
          action(dispatch, getState) :
          next(action)
    }
  }
}

const finalCreateStore = Redux.applyMiddleware(thunkMiddleware)(Redux.createStore)
//针对多个中间件, 使用:applyMiddleware(middleware1, middleware2, ...)(createStore)

let reducer = Redux.combineReducers({
  speaker: function (state = {}, action) {
    console.log('speaker was called with state', state, 'and action', action)

    switch (action.type) {
      case 'SAY':
        return {
          ...state,
          message: action.message
        }
      default:
        return state
    }
  }
})

const store = finalCreateStore(reducer)

let asyncSayActionCreator = function (message) {
  return function (dispatch) {
    setTimeout(function () {
      console.log(new Date(), 'Dispatch action now:')
      dispatch({
        type: 'SAY',
        message
      })
    }, 2000)
  }
}

store.dispatch(asyncSayActionCreator('HI'))

function logMiddleware({dispatch, getState}) {
  return function (next) {
    return function (action) {
      console.log('logMiddleware action received:', action)
      return next(action)
    }
  }
}

// 同样的,下面是一个中间件,它会丢弃所有经过的 action(不是很实用,
// 但是如果加一些判断就能实现丢弃一些 action,放到一些 action 给下一个中间件):
function discardMiddleware({dispatch, getState}) {
  return function (next) {
    return function (action) {
      console.log('discardMiddleware action received:', action)
    }
  }
}

监视 Redux store 更新

let itemsReducer = (state = [], action) => {
  console.log('itemsReducer was called with state', state, 'and action', action)

  switch (action.type) {
    case 'ADD_ITEM':
      return [
        ...state,
        action.item
      ]
    default:
      return state;
  }
}

let reducer = Redux.combineReducers({items: itemsReducer})
let store = Redux.createStore(reducer)
store.subscribe(() => {
  console.log('store_0 has been updated. Latest store state:', store.getState());
  // 在这里更新你的视图
})

let addItemActionCreator = (item) => {
  return {
    type: 'ADD_ITEM',
    item: item
  }
}

store.dispatch(addItemActionCreator({id: 1234, description: 'anything'}))
//  items:Array(1)
//  description:"anything"
//  id:1234

HTML5

HTML5

HTML5 是定义 HTML 标准的最新的版本。 该术语表示两个不同的概念:

  • 它是一个新版本的HTML语言,具有新的元素,属性和行为,
  • 它有更大的技术集,允许更多样化和强大的网站和应用程序。这个集合有时称为HTML5和朋友,通常缩写为HTML5。

语义

HTML5 中的节段和提纲

HTML5 中新的提纲和节段元素一览: 
<section>, <article>, <nav>, <header>, <footer>, <aside><hgroup>.

使用 HTML5 的音频和视频

<audio><video> 元素嵌入和允许操作新的多媒体内容。

HTML5 的表单

看一下 HTML5 中对 web 表单的改进:
强制验证 API,一些新的属性,<input> 属性type 的一些新值 ,新的 <output> 元素。

新的语义元素

除了节段,媒体和表单元素之外,还有众多的新元素,
例如 <mark><figure><figcaption><data><time><output><progress>, 
或者 <meter><main>,这增加了有效的 HTML5 元素的数量。

<iframe> 的改进

使用 sandbox, seamless, 和 srcdoc 属性,
作者们现在可以精确控制 <iframe> 元素的安全级别以及期望的渲染。

MathML

允许直接嵌入数学公式。

HTML5 入门

  • 本文介绍了如何标示在网页设计或 Web 应用程序中使用 HTML5 时碰到的问题。

HTML5 兼容的解析器

  • 用于把 HTML5 文档的字节转换成 DOM 的解释器,已经被扩展了,并且现在精确地定义了在所有情况下使用的行为,甚至当碰到无效的 HTML 这种情况。这就导致了 HTML5 兼容的浏览器之间极大的可预测性和互操作性。

通信

Web Sockets

  • 允许在页面和服务器之间建立持久连接并通过这种方法来交换非 HTML 数据。

Server-sent events

  • 允许服务器向客户端推送事件,而不是仅在响应客户端请求时服务器才能发送数据的传统范式。

WebRTC

  • 这项技术,其中的 RTC 代表的是即时通信,允许连接到其他人,直接在浏览器中控制视频会议,而不需要一个插件或是外部的应用程序。

离线 & 存储

离线资源:应用程序缓存

  • 火狐全面支持 HTML5 离线资源规范。其他大多数针对离线资源仅提供了某种程度上的支持。

在线和离线事件

  • Firefox 3 支持 WHATWG 在线和离线事件,这可以让应用程序和扩展检测是否存在可用的网络连接,以及在连接建立和断开时能感知到。

WHATWG 客户端会话和持久化存储 (又名 DOM 存储)

  • 客户端会话和持久化存储让 web 应用程序能够在客户端存储结构化数据。

IndexedDB

  • 是一个为了能够在浏览器中存储大量结构化数据,并且能够在这些数据上使用索引进行高性能检索的 Web 标准。

自 web 应用程序中使用文件

对新的 HTML5 文件 API 的支持已经被添加到 Gecko 中,
从而使 Web 应用程序可以访问由用户选择的本地文件。
这包括使用 type file 的 <input> 元素的新的 multiple 属性针对多文件选择的支持。 
还有 FileReader。

多媒体

使用 HTML5 音视频

<audio><video> 元素嵌入并支持新的多媒体内容的操作。

WebRTC
_ 这项技术,其中的 RTC 代表的是即时通信,允许连接到其他人,在浏览器中直接控制视频会议,而不需要一个插件或是外部的应用程序。

使用 Camera API

  • 允许使用,操作计算机摄像头,并从中存储图像。

Track 和 WebVTT

 <track> 元素支持字幕和章节。WebVTT 一个文本轨道格式。

3D, 图像 & 效果

Canvas 教程

了解有关新的 <canvas> 元素以及如何在火狐中绘制图像和其他对象。

HTML5 针对 元素的文本 API

HTML5 文本 API 现在由 <canvas> 元素支持。

WebGL

WebGL 通过引入了一套非常地符合 OpenGL ES 2.0 
并且可以用在 HTML5 <canvas> 元素中的 API 给 Web 带来了 3D 图像功能。

SVG

  • 一个基于 XML 的可以直接嵌入到 HTML 中的矢量图像格式。

性能 & 集成

Web Workers

  • 能够把 JavaScript 计算委托给后台线程,通过允许这些活动以防止使交互型事件变得缓慢。

XMLHttpRequest Level 2

  • 允许异步读取页面的某些部分,允许其显示动态内容,根据时间和用户行为而有所不同。这是在 Ajax背后的技术。

即时编译的 JavaScript 引擎

  • 新一代的 JavaScript 引擎功能更强大,性能更杰出。

History API

  • 允许对浏览器历史记录进行操作。这对于那些交互地加载新信息的页面尤其有用。

contentEditable 属性:把你的网站改变成 wiki !

  • HTML5 已经把 contentEditable 属性标准化了。了解更多关于这个特性的内容。

拖放

  • HTML5 的拖放 API 能够支持在网站内部和网站之间拖放项目。同时也提供了一个更简单的供扩展和基于 Mozilla 的应用程序使用的 API。

HTML 中的焦点管理
-支持新的 HTML5 activeElement 和 hasFocus 属性。

基于 Web 的协议处理程序

  • 你现在可以使用 navigator.registerProtocolHandler() 方法把 web 应用程序注册成一个协议处理程序。

requestAnimationFrame

  • 允许控制动画渲染以获得更优性能。

全屏 API

  • 为一个网页或者应用程序控制使用整个屏幕,而不显示浏览器界面。

指针锁定 API

  • 允许锁定到内容的指针,这样游戏或者类似的应用程序在指针到达窗口限制时也不会失去焦点。

在线和离线事件

  • 为了构建一个良好的具有离线功能的 web 应用程序,你需要知道什么时候你的应用程序确实离线了。顺便提一句,在你的应用程序又再回到在线状态时你也需要知道。

设备访问

使用 Camera API

  • 允许使用和操作计算机的摄像头,并从中存取照片。

触控事件
对用户按下触控屏的事件做出反应的处理程序。

使用地理位置定位
让浏览器使用地理位置服务定位用户的位置。

检测设备方向
让用户在运行浏览器的设备变更方向时能够得到信息。这可以被用作一种输入设备(例如制作能够对设备位置做出反应的游戏)或者使页面的布局跟屏幕的方向相适应(横向或纵向)。

指针锁定 API
允许锁定到内容的指针,这样游戏或者类似的应用程序在指针到达窗口限制时也不会失去焦点。


样式

CSS 已经扩展到能够以一个更加复杂的方法给元素设置样式。这通常被称为 CSS3, 尽管 CSS 已经不再是很难触动的规范,并且不同的模块并不全部位于 level 3:其中一些位于 level 1 而另一些位于 level 4,覆盖了所有中间的层次。

新的背景样式特性

  • 现在可以使用 box-shadow 给逻辑框设置一个阴影,而且还可以设置 多背景。

更精美的边框

  • 现在不仅可以使用图像来格式化边框,使用 border-image 和它关联的普通属性,而且可以通过 border-radius 属性来支持圆角边框。

为你的样式设置动画

  • 使用 CSS Transitions 以在不同的状态间设置动画,或者使用 CSS Animations 在页面的某些部分设置动画而不需要一个触发事件,你现在可以在页面中控制移动元素了。

排版方面的改进

  • 作者们如今有更强大的能力来使自己的网页文字达到更佳的排版。他们不但可以控制 text-overflow 和 hyphenation, 还可以给它设置一个 阴影 或者更精细地控制它的 decorations。感谢新的 @font-face 规则,现在我们可以下载并应用自定义的字体了。

新的展示性布局

  • 为了提高设计的灵活性,已经有两种新的布局被添加了进来:CSS 多栏布局, 以及 CSS 灵活方框布局。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.