class
可以看作是一个语法糖,因为它的绝大部分功能,ES5 都可以做到。新的 class
写法只是让对象原型的写法更清晰、更像面向对象编程的语法而已。ES5 的写法:
1 | function Point(x, y) { |
ES6 的写法:
1 | class Point { |
constructor
方法是默认方法,通过 new
命令生成对象实例时,自动调用该方法。一个类必须有 constructor
方法,如果没有显式定义,一个空的 constructor
方法会被默认添加。
constructor
方法默认返回实例对象(即 this
),完全可以指定返回另一个对象。但是如果这么做,会导致实例对象不是类的实例,如:
1 | class Foo { |
与 ES5 不同,ES6 的类 class 的写法必须用 new
调用,否则会报错。而 ES5 的构造函数可以不用 new
执行(作为普通函数执行)。
另外,ES6 的 class,实例的属性除非显式定义在其本身(即定义在 this
对象上),否则都是定义在原型上(即定义在 class
上)。所有的实例都共享一个原型对象。
1 | class Point { |
在类的内部,可以使用 get
和 set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
1 | class MyClass { |
上面代码中,prop
属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。
存值函数和取值函数是设置在属性的 Descriptor 对象上的。
1 | class CustomHTMLElement { |
与函数一样,类也可以使用表达式的形式定义。
1 | const MyClass = class Me { |
上面的代码定义了一个类,类的名字是 Me
,但 Me
只在 Class 的内部可用,指代当前类,在 Class 外部,这个类只能用 MyClass
引用。
如果类的内部没有用到的话,可以省略 Me
。
采用 Class 表达式,可以写出立即执行的 Class。
1 | let person = new class { |
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static
关键字,就表示该方法不会被实例继承。
如果静态方法包含 this
关键字,这个 this
指的是类,而不是实例。
1 | class Foo { |
上面的代码中,静态方法 bar
调用了 this.baz
,这里的 this
指的是 Foo
类,而不是 Foo
的实例,等同于调用了 Foo.baz
。另外,这个例子可以看出,静态方法可以与非静态方法重名。
实例属性除了定义在 constructor()
方法里面的 this
上面,也可以定义在类的最顶层,这时不需要在实例属性前面加上 this
。这种新写法的好处是,所有的实例对象的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
1 | class foo { |
直接写在顶层就好,不能使用 var
let
const
这样的关键字,会报错,因为 class 类的写法不是在写一个函数。
而且值得注意的是,写属性与写方法有所不同,属性是加在实例上的,也就是 this
上的,而方法是加在原型上的。
使用 extends
关键字可以实现类的继承,但子类必须在 constructor
方法中调用 super
方法,否则新建实例时会报错。这是因为子类的自己的 this
对象,必须选通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super
方法,子类就得不到 this
对象。
1 | class Point { /* ... */ } |
另一个要注意的地方是,在子类的构造函数中,只有调用了 super
之后,才可以使用 this
关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有 super
方法才能调用父类实例。
1 | class Point { |
super
这个关键字,既可以当作函数使用,也可以当作对象使用。
super
作为函数调用时,代表父类的构造函数,ES6 要求,子类的构造函数必须执行一次 super
函数。super()
只能用在子类的构造函数之中,用在其他地方就报错。
1 | class A {} |
上面的代码中,子类 B
的构造函数之中的 super()
,虽然代表了父类 A
的构造函数,但返回的是子类 B
的实例,即 super
内的 this
指的是 B
的实例,因此 super()
在这里相当于 A.prototype.constructor.call(this)
。
super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
1 | class A { |
上面的代码中,子类 B
中的 super.p()
,就是将 super
当作一个对象使用。这时 super
在普通方法之中,指向 A.prototype
,所以 super.p()
就相当于 A.prototype.p()
。
需要注意的是,由于 super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super
调用的。如果属性定义在父类的原型对象上,super
就可以取到。
1 | class A { |
ES6 规定,在子类普通方法中通过 super
调用父类的方法时,方法内部的 this
指向当前的子类实例。
1 | class A { |
上面代码中,super.print()
虽然调用的是 A.prototype.print()
,但是 A.prototype.print()
内部的 this
指向子类 B
的实例,导致输出的是 2
, 而不是 1
。也就是说,实际执行的是 super.print.call(this)
。
由于 this
指向子类实例,所以如果通过 super
对某个属性赋值,这时 super
就是 this
,赋值的属性会变成子类实例的属性。
1 | class A { |
上面代码中,super.x
赋值为 3
,这时等同于对 this.x
赋值为 3
。而当读取 super.x
的时候,读的是 A.prototype.x
,所以返回 undefined
。
如果 super
作为对象,用在静态方法之中,这时 super
将指向父类,而不是父类的原型对象。
1 | class Parent { |
上面代码中,super
在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
另外,在子类的静态方法中通过 super
调用父类的方法时,方法内部的 this
指向当前的子类,而不是子类的实例。
1 | class A { |
上面代码中,静态方法 B.m
里面,super.print
指向父类的静态方法 print
。这个方法里面的 this
指向的是 B
,而不是 B
的实例。一开始调用 B.m
,因为 B
没有这个属性(静态属性),所以返回 undefined
;但是当给 B.x
赋值为 3
的话,那么再调用 B.m
返回 3
。
注意,使用 super
的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
1 | class A {} |
上面代码中,console.log(super)
当中的 super
,无法看出是作为函数使用,还是作为对象使用,所以 JS 引擎解析代码时会报错。但如果能清晰表明 super
的数据类型,就不会报错。
1 | class A {} |
上面代码中,super.valueOf()
表明 super
是一个对象,因此就不会报错。同时,由于 super
使得 this
指向 B
的实例,所以 super.valueOf()
返回的是一个 B
的实例。
最后,由于对象总是继承其他对象的,所以可以在任意一个对象中,使用 super
关键字。
1 | var obj = { |
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块都只能在运行时确定这些东西。
ES6 模块不是对象,而是通过 export
命令显式指定输出的代码,再通过 import
命令输入。
由于 ES6 模块是编译时加载,使用静态分析成为可能。
ES6 的模块自动采用严格模式,不管有没有在模块的头部加上 "use strict"
。
严格模式的限制:
with
语句delete prop
,会报错,只能删除属性 delete global[prop]
eval
不会在它的外层作用域引入变量eval
和 arguments
不能被重新赋值arguments
不会自动反映函数参数的变化arguments.callee
arguments.caller
this
指向全局变量fn.caller
和 fn.arguments
获取函数调用的堆栈protected
、static
和interface
)其中,尤其需要注意 this
的限制。ES6 模块之中,顶层的 this
指向 undefined
,即不应该在顶层代码使用 this
。
export
命令用于规定模块的对外接口。一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取 。如果希望外部能够读取模块内部的某个变量,必须使用 export
关键字输出该变量。例如:
1 | export var firstName = 'Michael'; |
特别注意: export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
1 | // 报错 |
上面的代码报错是因为没有提供对外的接口。第一种直接输出 1,第二种通过变量 m
还是直接输出的 1。1
只是一个值,不是接口。正确的写法如下:
1 | // 写法一 |
上面的三种写法都规定了对外接口 m
,其他的脚本可以通过这个接口取到值 1
,所以三种写法都对。它们的实质是,在接口与模块内部变量之间,建立了一一对应的关系。
function
和 class
的输出,也必须遵守这样的写法:
1 | // 报错 |
另外,export
语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。例如下面的代码,输出的变量是 foo
,值为 bar
,但是 500ms 之后变成了 baz
。
1 | export var foo = 'bar'; |
使用 export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过 import
命令加载这个模块。
1 | // main.js |
import
命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许加载模块的脚本里,改写接口。但如果加载的变量是个对象,改成对象的属性是允许的。例如:
1 | import {a} from './xxx.js'; |
注意,import
命令具有提升效果,会提升到整个模块的头部,首先执行。
1 | foo(); |
上面的代码不会报错,因为 import
的执行早于 foo
的调用。这种行为的本质是,import
命令是编译阶段执行的,在代码运行之前。
也正因为 import
是静态执行,所以不能使用表达式和变量——这些只有在运行时才能得到结果的语法结构。
1 | // 报错 |
上面三种写法都会报错,因为它们用到了表达式、变量和 if
结构。在静态分析阶段,这些语法都是没法得到值的。
最后,import
语句会执行所加载的模块,因此可以有下面的写法:
1 | import 'lodash'; |
上面代码仅仅执行 lodash
模块,但是不输入任何值。
如果多次重复执行同一句 import
语句,那么只会执行一次,而不会执行多次。
1 | // 例 1 |
目前通过 Babel 转码,CommonJS 的 require
和 ES6 模块的 import
命令可以写在同一个模块里,但最好不要这样做。因为 import
在静态解析阶段执行,所以它是一个模块之中最早执行的。
下面的代码可能不会得到预期结果:
1 | require('core-js/modules/es6.symbol'); |
除了指定加载某个输出值,还可以整体加载,即用星号(*
)指定一个对象,所有输出值都加载在这个对象上面。
1 | // circle.js |
然后加载 circle.js
这个模块
1 | import {area, circumference} from './circle.js'; |
注意,模块整体加载所在的那个对象(上例是 circle
),应该是可以静态分析的,所以不允许运行时改变,以下会报错。
1 | import * as circle from './circle.js'; |
export default
命令从上面的例子可以看出,使用 import
命令时,需要知道所要加载的变量名或者函数名,否则无法加载。为了方便,可以用 export default
命令,为模块指定默认输出。
本质上,export default
就是输出一个叫 default
的变量或方法,然后系统允许你给它取任意名字。但它后面不能跟变量声明语句。
1 | // 第一组---------------------------- |
上面的第一组,foo
函数的函数名 foo
,在模块外面是无效的,加载的时候视同匿名函数。
上面的第二组,使用 export default
时,对应的 import
语句不需要使用大括号。
上面的第三组,不使用 export default
,对应的 import
语句需要使用大括号。
上面的第四组,不能在 export default
后面使用变量声明语句。
上面的第五组,体现 export default
就是输出一个叫 default
的变量。
1 | // 正确 |
上面的代码,后一句报错是因为没有指定对外的接口,而前一句的对外接口是 default
。
输出一个类:
1 | // MyClass.js |
export
与 import
的复合写法如果在一个模块之中,先输入后输出同一模块,import
语句可以与 export
语句写在一起。
1 | export {foo, bar} from 'my_module'; |
上面的代码中,export
和 import
语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo
和 bar
实际上并没有被导入当前模块,只是相当于对外转发了两个接口,导致当前模块不能直接使用 foo
和 bar
。
浏览器中加载脚本,一般是用下面的代码:
1 | <script src="path/to/myModule.js" defer></script> |
defer
与 async
的区别是:defer
要等整个页面在内存中正常渲染结束(DOM结构完全生成,以及其他脚本执行完成),才会执行;async
是一旦下载完,渲染引擎就会中断渲染,执行这个脚本后,再继续渲染。一句话,defer
是渲染完再执行,async
是下载完就执行。另外,如果有多个 defer
脚本,会按照它们在页面出现的顺序加载,而多个 async
脚本是不保证加载顺序的。
1 | <script type="module" src="path/to/module.js"></script> |
加上 type="module"
之后,浏览器就知道这是一个 ES6 模块。浏览器对带有 type="module"
的 <script>
,都是异步加载,不会造成浏览器堵塞,等整个页面渲染完后,再执行脚本,相当于打开了 defer
属性。
如果网页中有多个 <script type="module">
,它们会按照在页面出现的顺序依次执行。
但如果此时 <script>
的 async
属性也打开了,这时只要加载完成,渲染引擎就会中断渲染立即执行脚本,执行完后再恢复渲染。一旦用了 async
属性,<script>
就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块。
两个重大差异:
另一个差异是因为 CommonJS 加载的是一个对象(即 module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
本文基本是上面链接的笔记和再整理。
]]>ES6 允许按照一定的模式,从数组和对象中提取值,这种方式被称为解构(Destructuring)。
以前为变量赋值,只能直接指定值,例如:
1 | let a = 1; |
但是解构赋值允许这样写:
1 | let [a, b, c] = [1, 2, 3]; |
意义就跟上面一样了,为 a
,b
,c
分别赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予相应的值。以下是更多例子:
1 | let [foo, [[bar], baz]] = [1, [[2], 3]]; |
如果解构不成功,变量的值就等于 undefined
。
另一种情况是不完全解构,即等号左边的模式,只匹一部分的等号右边的数组。这种情况下,解构依然可以成功。
1 | let [x, y] = [1, 2, 3]; |
如果等号的右边不是数组(或者严格的说,不是可遍历的结构),那么将会报错。
解构赋值允许指定默认值。但要注意的是,ES6 内部使用严格相等运算符号(===
),判断一个位置是否有值。所以,只有当一个数组成员严格等于 undefined
,默认值才会生效。
1 | let [foo = true] = []; |
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
1 | function f() { |
对象的解构赋值和数组有一个重要的不同,数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
1 | let {foo, bar} = {foo: 'aaa', bar: 'bbb'}; |
如果变量名与属性名不一致,必须写成下面这样:
1 | let {foo: baz} = {foo: 'aaa', bar: 'bbb'}; |
实际上说明,对像的解构赋值是下面形式的简写:
1 | let {foo: foo, bar: bar} = {foo: 'aaa', bar: 'bbb'}; |
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
与数组一样,解构也可以用于嵌套结构的对象。
1 | let obj = { |
在上面的例子中,p
是模式,不是变量,所以 p
不会被赋值,如果 p
也要作为变量赋值,要写成下面这样:
1 | // 续上 |
同数组解构的默认值一样,对象的属性值严格等于 undefined
时默认值才会生效。如果解构失败,变量的值等于 undefined
。
1 | var {x = 3} = {}; |
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。如果要将一个已经声明的变量用于解构赋值,也要小心。
1 | let {foo: {bar}} = {baz: 'baz'}; |
上面的写法会报错,因为 JS 引擎会将 {x}
解释成一个代码块,从而发生语法错误。只有不将花括号写在行首,避免 JS 将它解释成代码块,才能解决问题。
1 | let x; |
解构赋值允许等号左边的模式之中,不放置任何变量名,因为可以写出非常古怪的表达式,它们没有意义,但是语法合法,可以执行。
1 | ({} = [true, false]); |
对象的解构赋值,可以很方便的将现有对象的方法,赋值到某个变量。
1 | let {log, sin, cos} = Math; |
由于数组的本质也是对象,因此可以对数组进行对象的解构。
1 | let arr = [1, 2, 3]; |
对字符串进行解构赋值时,字符串被转换成了一个类似数组的对象。
1 | const [a, b, c, d, e] = 'hello'; |
因为字符串还有个 length
属性,所以这个属性也可被解构。
1 | let {length: len} = 'hello'; |
如果等号右边是数值和布尔值,则会先转为对象。
1 | let {toString: s} = 123; |
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于 undefined
和 null
无法转换成对象,所以对它们进行解构的时候,会报错。
1 | let {prop: x} = undefined; |
函数的参数也可以使用解构赋值。
1 | function add([x, y]) { |
不过,如果用下面的写法,会得到不一样的结果:
1 | function move({x, y} = {x: 0, y: 0}) { |
上面这段代码是为函数 move
的参数指定默认值,而不是为 x
和 y
变量指定默认值,所以会得到不同结果。
undefined
就会触发函数参数的默认值。
1 | [1, undefined, 3].map((x = 'yes') => x); |
解构的过程中,如果模式中出现了圆括号,可能会引起处理上的歧义。ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号。
1 | // 全部报错 |
函数参数也属于变量声明,因此不能带有圆括号。
1 | // 全部报错 |
1 | // 全部报错 |
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
1 | // 以下均正确 |
除了上面注释里写明的原因,上面三行都能正确执行的原因,是它们都是赋值语句,而不是声明语句。
1 | let x = 1; |
函数只有一个返回值,如果要返回多个值,只能将它们放在数组或者对象里返回。然后用解构赋值取出它们。
1 | // 返回一个数组 |
解构赋值可以方便的将一组参数与变量名对应起来。
1 | // 参数是一组有序的值 |
1 | let jsonData = { |
1 | jQuery.ajax = function (url, { |
创建带命名空间的辅助函数:
1 | import {createNamespacedHelpers} from 'vuex'; |
然后添加组件定义:
1 | export default { |
重命名带命名空间的辅助函数是个很好的实践,因为未来可能还会为其他模块添加辅助函数。例如,如果不这么做,可能最后会有两个 mapGetters
,而这是不可行的。这里将 mapGetters
重命名为 postsGetters
,同时将 mapActions
重命名为 postsActions
。
本文基本是上面链接的笔记和再整理。
]]>扩展运算符是三个点 ...
,用于将一个数组转为用逗号分隔的参数序列,它主要用于函数调用。
要注意上面提到的,是转化为参数序列,并不是单单用于数组拆解的,所以如果你打开 DevTools 的 console,在里面输入 ...[1, 2, 3]
,那么你不会得到 1,2,3
,而是会得到一个错误:Uncaught SyntaxError: Unexpected token ...
。
1 | console.log(...[1, 2, 3]) |
注意上面的例子,把解构出来的值,放到数组 []
里面,那么得到的是个数组;而如果把解构出来的值,放到对象 {}
里面,那么会得到一个以 index
为 key
,元素为 value
的对象,这个对象是没有 length
属性的。
扩展运算符后面可以放置表达式。如果扩展运算符后面是一个空数组,则不产生任何效果。另外,扩展运算符如果放在括号中,JavaScript 引擎会认为这是函数调用,如果这时不是函数调用,就会报错。
1 | const arr = [ |
由于扩展运算符可以展开数组,所以不再需要 apply
方法,将数组转为函数的参数了。
1 | // 旧的写法 |
下面是用扩展运算符取代 apply
方法的一个实际例子,应用 Math.max
方法,简化求出一个数最大元素的写法。
1 | // 旧的写法 |
数组是复合数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。下面的代码中,a2
并不是 a1
的克隆,而是指向同一份数据的另一个指针。修改 a2
会直接导致 a1
的变化。
1 | const a1 = [1, 2]; |
在 ES5 中,一般使用变通的方法来复制数组
1 | const a1 = [1, 2]; |
用扩展运算符来写的话,就会比较简便,两种写法里,a2
都是 a1
的克隆:
1 | const a1 = [1, 2]; |
扩展运算符提供了数组合并的新写法:
1 | const a1 = ['a', 'b']; |
如果数据里面的元素,是复合的数据类型,无论是 concat
还是用解构运算符,都是浅复制,也就是,新数组里面的成员,依然是对原数组成员的引用(指针),如果修改了原数组成员,那么新数组中成员也会变化。
1 | const a1 = [{foo: 1}]; |
扩展运算符可以与解构赋值结合起来,用于生成数组。如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
1 | const [first, ...rest] = [1, 2, 3, 4, 5]; |
扩展运算符可以将字符串转化为真正的数组,而且能正确识别四个字节的 Unicode 字符。
1 | [...'hello'] |
在 Vue 中,主要使用 Vuex 中的 mapGetters
和 mapActions
方法,来简化代码写法:
1 | // ... |
@keyframes bouncedelay
变成了 @keyframes a
,查看了一下 webpack 的配置文件,把问题原因锁定到 cssnano 上。首先是找到了 cssnano 的官网,配置项很多,感觉一个个看下来太浪费时间,直接就找问题本身。于是乎在 github 上找到了一个 issue,按别人提供的方法,果然解决了问题。
修改 webpack 配置,方法如下:
1 | // 修改之前 |
1 | // 修改之后 |
修改 webpack 的配置之后再试,打包出来的代码中 bouncedelay 就没有变成 a 了。
补充说明:
reduceIdents
选项,是用于去掉 css 中 “用户自定义字符串标识符” 的 postcss 插件false
。我看的教材上,用的 vue-cli,以前的安装方式就是 npm install -g vue-cli
,但是新版本变成了 npm install -g @vue/cli
。
其实官方的文档有说,但是我没有注意而已……老版本的 vue-cli
是 v2 版本,新版的 @vue/cli
是 v3 版本。
另一个问题是,使用 vue init
建立新项目之后,在命令行中运行 npm run dev
居然出了黄色的警告提示:There are multiple modules with names that only differ in casing. 比较迷的是,我头一天用的时候,是没有任何提示的,第二天回公司,重新运行,就出一大堆提示了。找了一堆资料,大概有两种原因:
- 用
import Vue from 'Vue'
这样的写法引用了模块,应该改成小写的:import Vue from 'vue'
- 使用了会区分大小写的命令行,例如 git bash 或者 powershell
我自己遇到的问题是第二种,头一天用的 cmd 命令行,第二天用的 powershell,所以就出问题了。有遇到相同问题的朋友,可以找找看,是不是这两个原因。
下载了网上的一个测试 node 服务器,用里面的 package.json
文件,用 npm install
安装之后,不停报错。分析命令行提示,缺少 node-gyp
。继续在网上找资料,node-gyp
是没有整合到 nodejs
的安装包里,需要自己编译安装。而要在 windows 平台上编译安装,又需要安装 windows-build-tools
。
按项目说明来:
1 | npm install -g --production windows-build-tools |
然后,然后就特么一直卡在那里不动了……
又找了一堆资料,好像是最新版本的这个工具有问题,安装 4.0.0
的就不会出错,那么再来:
1 | npm install -g --production windows-build-tools@4.0.0 |
等待命令行窗口里面该搞的搞完了,就可以继续输入命令安装 node-gyp
了:
1 | npm install -g node-gyp |
bcrypt
的安装需要依赖 node-gyp
,所以上面才折腾半天的。但问题是,如果直接按下载的 package.json
提供的版本安装 1.x.x
版本时,会提示 github 上找不到……
OK,还是安最新的吧。。。
1 | npm install bcrypt |
前不久重装了一下系统,然后安装了一下需要用的工具。在我的工具文件夹里,使用 npm install
的时候,总是提示 base64-url 安装错误,/path/to/base64-url.tar.gz 文件不存在
,而且这个 path
是一个本地路径,也是一个无法在 windows 系统里存在的路径。
折腾半天无果,发现是被同目录下的 package-lock.json
影响了。因为之前从 cnpm
切换到 npm
时,生成的 package-lock.json
基于了当时安装好的 node 模块,所以路径指向了本地。
我删掉 package-lock.json
之后再装新版本,就没这些问题了。当然,有些版本变化较大的工具,还是要卸载后安装对应的旧版,也挺糟心的。
什么是 nrm?
nrm can help you easy and fast switch between different npm registries, now include: npm, cnpm, taobao, nj(nodejitsu).
就是用来快速切换 npm 源的一个管理工具,里有 npm 原始源地址,也有淘宝和其他的。
1 | npm install -g nrm |
安装之后,直接在命令行里用 nrm ls
就可以显示已经有的源,和正在使用的源;用 nrm use [name]
就能切换到指定源了,例如 nrm use taobao
。用 nrm
来完成这些小动作真的会省心不少,推荐。
首先,下载 nginx 的可执行文件,是个压缩文件,解压之后,放到一个容易访问的目录,以下用 nginx_dir
代替。ngnix 的默认配置文件是 nginx_dir/conf/nginx.conf
。
其次,在 nginx_dir/conf/
下新建一个目录,叫 vhosts
,并且在 nginx_dir/conf/vhosts/
下新建一个文件,名为 vhosts.conf
。
第三,在 nginx_dir/conf/nginx.conf
的配置里,位于 http {...}
的最后面,于 }
之前,添加 include vhosts/*.conf;
。添加的代码意义为:在 nginx.conf
中引入 vhosts
下所有的 .conf
文件。
以下即是 vhosts.conf
里面的配置,因为开发中主要用到反向代理,其他的按文档按需添加就行:
1 | example 代理相关配置 |
第四,统统设置完之后,打开命令行,定位到 nginx_dir
,使用如下命令检查配置是否正确:
1 | $ nginx.exe -t |
如果出现以下提示,表明配置没问题,出现其他提示自行排查:
1 | nginx: the configuration file nginx_dir/conf/nginx.conf syntax is ok |
最后,因为是在 windows 环境下,所以启动 nginx 使用以下命令:
1 | $ start nginx.exe |
使用 start
运行而不是直接运行,是让命令行的窗口可以进行别的操作,而不是一直僵在前台。
补充:
nginx -s quit
命令来关闭运行中的 nginx 进程nginx -s reload
命令来重启运行中的 nginx,一般用于修改配置文件之后操作forEach()
方法对数据的每一个元素执行一次提供的函数
1 | array.forEach(callback (currentValue, index, array) {}[, thisArg]); |
callback
的第一个参数,当前正处理的元素callback
的第二个参数,当前正处理的元素的索引callback
的第三个参数,是正在处理的数组undefined
没有办法中止或者跳出 forEach 循环,除非抛出一个错误。
filter()
方法创建一个新数组,包含通过所提供的函数实现的测试的所有元素。
1 | var newArray = array.filter(callback (currentValue[, index[, array]]) {}[, thisArg]); |
(currentValue, index, array)
,返回 true
表示保留该元素(通过了测试),false
则不保留callback
的第一个参数,当前正在处理的元素callback
的第二个参数,当前正在处理的元素的索引值callback
的第三个参数,调用 filter
的数组一个新的通过测试的元素集合的数组,如果没有元素通过测试,就返回空数组。
map()
创建一个新数组,新数组中的每个元素,是原数组里每个元素,调用所提供的函数后返回的结果。
1 | var newArray = array.map(callback (currentValue[, index[, array]]) {}[, thisArg]); |
callback
的第一个参数,数组中当前正在处理的元素callback
的第二个参数,数组中当前正在处理的元素的索引值callback
的第三个参数,调用 map
的数组一个新数组,每个元素都是回调函数的结果。
reduce()
对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。
1 | array.reduce(callback (accumulator, currentValue[, currentIndex[, array]]) {}[, initialValue]); |
callback
的第一个参数,累加器累加回调的返回值,它是上一次调用回调时返回的累积值,或 initialValue
callback
的第二个参数,数组中当前正在处理的元素callback
的第三个参数,数组中当前正在处理的元素的索引。如果提供了 initialValue
,则索引号为 0,否则索引为 1callback
的第四个参数,调用 reduce
的数组callback
的第一个参数的值。如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数上调用 reduce
会报错函数累计处理的结果
如果没有提供 initialValue
,reduce
会从索引 1 的地方开始执行 callback 方法,跳过第一个索引。如果提供 initialValue
,从索引 0 开始。
reduceRight
总体上与 reduce
相同,区别在于,使用数组中的值,从右向左传给 callback
every()
测试数组中所有元素是否都通过了指定函数的测试。
1 | array.every(callback (currentValue[, index[, array]]) {}[, thisArg]); |
callback
的第一个参数,当前处理的元素callback
的第二个参数,当前处理的元素的索引值callback
的第三个参数,调用 every
的数组callback
时使用的 this 值如果每个元素都通过了测试函数,则返回 true
,否则返回 false
。
在空数组上调用 every
方法,无论测试函数是什么,都会返回 true
。
some()
测试数组中某些元素是否通过指定函数的测试。
1 | array.some(callback (currentValue[, index[, array]]) {}[, thisArg]); |
callback
的第一个参数,当前正在处理的元素callback
的第二个参数,当前正在处理的元素的索引值callback
的第三个参数,调用 some
的数组callback
时使用的 this 值如果任意一个元素通过了测试函数,就返回 true
,否则返回 false
。
在空数组上调用 some
方法,无论测试函数是什么,都会返回 false
。
以下命令均在 root 权限下执行,所以无 sudo
废话不多说,先安装:
1 | apt-get install --no-install-recommends build-essential autoconf libtool libssl-dev libpcre3-dev libev-dev asciidoc xmlto automake |
使用 Simple Obfs 有两种方式,一种是作为 55 的插件,另一种是独立模式,我怕麻烦,所以使用作为 55 插件的方法。以前用的是 Python 版的 55,不支持外挂插件的样子,所以换成 55-libev。安装之:
1 | apt-get update |
如果安装成功了,在命令行下输入 ss-server
会出现相应提示的,这一步不详说。(要注意的是,Python 版的命令是 ssserver
,55-libev 的命令是 ss-server
,有一横杠的区别)
接下来修改 55 的配置文件,在配置文件里,添加两行参数:
1 | "plugin": "obfs-server", |
万事具备,使用如下命令启动 55:
1 | ss-server -c config.json |
如果输出没有报错,就表明启动正常,接下来按 Ctrl+C
结束掉。为啥?当然是重新开始后台启动啊:
1 | nohup ss-server -c config.json > /dev/null 2>&1 & |
注释:
/dev/null
代表空设备文件>
表示重定向到哪里,例如: echo "123" > /home/123.txt
2
表示 stderr 标准错误&
表示等同于的意思,表示 2 的输出重定向等同于 11
表示 stdout 标准输出,系统默认是 1,所以 > /dev/null
等同于 1 > /dev/null
> /dev/null
表示标准输出重定向到空设备文件,也就是不输出任何信息到终端2>&1
接着,标准错误输出重定向(等同于)标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件结尾处的 &
将命令同时放入后台运行下载 obfs-local,然后把压缩包里面的两个文件,解压到 55 相同的目录里面。接着打开 55 的服务器配置界面,把 插件程序 和 插件选项 两个空位置,填写如下配置:
1 | 插件程序:obfs-local |
到此为止,全部完成,终于又能用 Google 了~
flexbox
这样是一个一维系统。使用 Grid 布局方式可以同时将 CSS 规则作用于父元素(成为 Grid Container)和子元素(成为 Grid Items)。CSS Grid Layout(又叫做 Grid),是一个二维的、基于网格的布局系统,目标是完全改变我们基于网格设计的用户界面。CSS 已经常用于 web 页面的布局,但并不是总做得非常好。开始,我们使用表格 tables 布局,然后使用浮动 floats,定位 position 和 inline-block,但说到底,这些方法都算作 hacks,漏掉了一些重要的功能性(比如垂直剧中)。Flexbox 可以解决问题,但是它是为简单的一维布局而设计的,并不是为复杂的二维布局而设计(Flexobox 和 Grid 事实上可以同时工作)。Grid 是最开始为解决布局问题而创造出来的 CSS 模块。
写这篇指南出于两个基本原因:一是 Rachel Andrew 写佳作 Get Ready for CSS Grid Layout,它是一本清楚而透彻介绍 Grid 的书,也是本文的基础所在,我高度建议大家买来读一下。另一个原因是 Chris Coyier 的 A Complete Guide to Flexbox,它是我定位 flexbox 问题的随手工具书,它同时也帮助了一大批的人,你在 Google “flexbox” 的时候,它也出现在明显位置。你可以注意到他和我文章的很多相似之处,因为有最好的,为啥不借用?
我写这个指南主要目的是介绍 Grid 的概念,因为它已经出现在了最新版的规范里。所以我也不会涵盖到已过时的 IE 浏览器,并且当规范成熟时,尽量更新这份指南。
要开始使用 Grid,首先要定义一个容器元素,使用 display: grid
,然后设置列和行的尺寸,使用 grid-template-column
和 grid-template-rows
,接着用 grid-column
和 grid-row
安置它的子元素。与 flexbox 类似,代码出现的顺序与 grid items 的排列没有关系。CSS 代码层面可以用任何顺序来写,而且使用媒体查询可以很容易地把 grid 重排。想象一下把整个页面都布局好,然后用几行 CSS 代码将它完全重排以适应不同的屏幕宽度。Grid 是迄今介绍过的最强大的 CSS 模块。
深入 Grid 概念之前有些重要术语需要知道。因为这里包含的规则概念上相似,所以如果不先记住 Grid 规范中定义的术语,很容易在使用的时候相互混淆。不过不用担心,因为术语没有很多。
使用了 display: grid
的元素即为 Grid Container。它是所有 grid items 的直接父元素。在下面这个例子中,container
就是 grid container。
1 | <div class="container"> |
Grid Container 元素的直接子元素。下面的例子中,item
元素就是 grid items,但是 sub-item
元素不是。
1 | <div class="container"> |
这些分割线组成了 grid 的结构。它们可以是垂直的(column grid lines)或者水平的(row grid lines),或者位于 row 或 column 的两侧。图示的黄线就是一个 column grid line。
位于两条 grid line 之间的空间。可以想像成 grid 里的列或者行。
位于两相邻行和两相邻列的 grid lines 之间的空间。它是 grid 布局里面的“单元”。图示中是行的 grid lines 1 和 2,以及列的 grid lines 2 和 3 之间的 grid cell。
被四条 grid lines 包围的总体空间就是 Grid Area。一个 grid area 可以由任意个数的 grid cells 组成。图示中是 1 和 3 行 grid lines 以及 1 和 3 列 grid lines 包围而成的 grid area。
把元素定义为 grid container 从而为它的内容形成一个新的 grid formatting context。
1 | .container { |
Note: The ability to pass grid parameters down through nested elements (aka subgrids) has been moved to level 2 of the CSS Grid specification. Here’s a quick explanation.
定义列和行,使用空格将这些属性值连起来。这些值包含了 track size,它们之间的空间代表的是 grid line。
fr
单元)1 | .container { |
例子:
当在 track 的值上留空时,grid lines 会自己分配正值或负值:
1 | .container { |
也可以为这些 lines 指一个固定的名字,注意一下 line 名的括号语法:
1 | .container { |
要注意的是,一条 line 可以拥有不止一个名字。例如,第二条 line 拥有两个名字:row1-end 和 row2-start:
1 | .container { |
如果定义的时候有重复部分,那么可以直接使用 repeat()
记号进行组织:
1 | .container { |
它等价于:
1 | .container { |
如果多个 lines 使用相同的名字,它们会参照 line name 和计数。
1 | .item { |
fr
单元允许你使用 gird container 中的空白空间去设置 track 的尺寸。例如,下面的代码会将每个 item 的宽度设置为 grid container 宽的 1/3。
1 | .container { |
空白空间是去除固定 item 之后的值来计算的。下面的例子表示,用于 fr
来表示的空白空间,不包括这 50px。
1 | .container { |
先用 grid-area
属性来定义 grid areas 的名字,之后参照这些名字来定义 grid template。Repeating the name of a grid area causes the content to span those cells. 一个点号 .
表示一个空的 cell。语法的本身就提供了 gird 视觉化的结构。
grid-area
指定的 grid area 的名字1 | .container { |
例如:
1 | .item-a { |
这段代码产生了一个四列宽度三行高的 grid 。整个的顶部行都会组成 header 区。中间的行会组成 main 区,一个空白 cell,一个 sidebar 区。最后一行全部组成 footer。
声明中的每一行,必须有相同的 cell 数目。
可以使用任意数量的点号来指定单个空白的 cell。只要点号之间没有其他的空白,那么它们就会组成一个空白 cell。
要注意不能使用这个语法来命名 lines,只能用它来命名 areas。当使用它来命名 areas 的时候,area 两边的 lines 会被自动命名的。如果你的 grid area 的名字是 foo,那么这个 area 的起始 row line 和起始 column line 就会被命名为 foo-start,并且最后一个 row line 和最后一个 column line 会被命名为 foo-end。这就意味着,一些 lines 会有多个命名,比如上个例子中最左边的 line,它就拥有三个名字:header-start,main-start 和 footer-start。
这个属性就是 grid-template-rows
,grid-template-columns
,grid-template-areas
三个属性的缩写。
grid-template-columns
和 grid-template-rows
分别设置为指定的值,而将 grid-template-area
设置为 none
。1 | .container { |
它同时也接受,看起来复杂,但是用起来很方便的语法同时指定三个属性,例如:
1 | .container { |
等价于:
1 | .container { |
grid-template
并不会重设一些暗藏的属性(比如 grid-auto-columns
,grid-auto-rows
和 grid-auto-flow
),但往往在很多情况下这是想要进行的设置,所以建议使用 grid
属性来代替 grid-template
。
用来指定 grid lines 的尺寸。可以想象成设置 column / rows 之间的沟槽的宽度。
1 | .container { |
例子:
1 | .container { |
沟槽仅会在 columns / rows 之间产生,不会在外边界产生。
注意:grid-
这个前缀将会被移除,到时 grid-column-gap
和 grid-row-gap
会被重命名为 column-gap
和 row-gap
。无前缀属性现已经被 Chrome 68+,Safari 11.2 Release 50+ 和 Opera 54+ 所支持。
grid-row-gap
和 grid-column-gap
的缩写形式
1 | .container { |
例子:
1 | .container { |
如果没有设置 grid-row-gap
,那么它会被设置为与 grid-column-gap
相同的值。
注意:grid-
这个前缀将会被移除,到时 grid-gap
会被重命名为 gap
。无前缀版属性现已经被 Chrome 68+,Safari 11.2 Release 50+ 和 Opera 54+ 所支持。
让 grid items 沿着内联轴(row)对齐(与 align-items
沿着块级轴(column) 相反)。这个属性会对 container 里所有的 grid items 都生效。
1 | .container { |
例子:
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
这些表现,也要以在单独的 grid items 上通过 justify-self
属性各自设置。
将 grid items 沿着块级轴(column)对齐(与 justify-items
沿着内联轴(row) 相反)。这个属性同样会对 container 里所有的 grid items 都生效。
例子:
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
这些表现,也要以在单独的 grid items 上通过 align-self
属性各自设置。
place-items
是 align-items
和 justify-items
和缩写方式。
align-items
,第二个值设置 justify-items
。如果第二个值被省略,那么第一个值则同时用于两个属性。除了 Edge 之外的所有主流浏览器都支持 place-items
的简写方式。
有时所有的 grid 的尺寸之后可能还是小于 grid container,这常发生在指定 grid items 的尺寸的时候,用的是 px 这样的固定单位。在这种情况下,可以设置 grid 在 grid container 里面的对齐方式。这个属性将 grid items 沿着内联轴(row)进行对齐(与 align-content
相反,align-content
是沿着块级轴(column)对齐)。
1 | .container { |
例子:
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
有时所有的 grid 的尺寸之后可能还是小于 grid container,这常发生在指定 grid items 的尺寸的时候,用的是 px 这样的固定单位。在这种情况下,可以设置 grid 在 grid container 里面的对齐方式。这个属性将 grid items 沿着块级轴(column)进行对齐(与 justify-content
相反,justify-content
是沿着内联轴(row)对齐)。
1 | .container { |
例子:
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
1 | .container { |
place-content
是同时设置 align-content
和 justify-content
属性的简写。
align-content
,第二个值设置 justify-content
,如果第二个值被省略,那么第一个值则同时作用于两个属性设置。除了 Edge 之外的所有主流浏览器都支持 place-content
属性。
指定自动生成的 grid tracks 的尺寸(也就是稳式的 grid tracks)。当 grid items 比 cell 的数量多,或者一个 grid item 被放在了显式的 grid 外面,隐式的 tracks 就会产生。(参见The Difference Between Explicit and Implicit Grids)
fr
单元1 | .container { |
想象一下隐式的 grid track 的产生:
1 | .container { |
代码创建了一个 2x2 的 grid。
但是想象一下使用 grid-column
和 grid-row
来放置 grid items:
1 | .item-a { |
我们已经指定了 .item-b 从 line 5 开始,并且于 line 6 处结束,但是我们从来没有定义过 line 5 和 line 6。因为我们的参考线是不存在的,隐式的宽度为 0 的 tracks 就被创造出来去填充 gaps。我们可以用 grid-auto-columns
和 grid-auto-rows
去指定这些隐式 tracks 的宽度。
1 | .container { |
如果并没有明确声明 grid 中的 grid items 的位置,那么自动安放算法就会生效从而自动排列 items。这个属性控制自动算法如何来生效。
1 | .container { |
注意 dense 只是改变 items 视觉上的顺序,让它们乱序的出现,对易用性来说这是不好的。
例子:
1 | <section class="container"> |
定义一个两行五列的 grid,设置 grid-auto-flow
为 row
(默认值):
1 | .container { |
把 items 放置在 grid 中后,只需要指定其中两个点:
1 | .item-a { |
因为已经设置了 grid-auto-flow
为 row
,所以 grid 将会看起来像这样。要注意其中三个并没有被设置,所以它们沿着可用的行流动。
如果设置 grid-auto-flow
为 column
,那么 item-b,item-c,item-d 就会沿着列流动。
1 | .container { |
这是一个将 grid-template-rows
,grid-template-columns
,grid-template-areas
,grid-auto-rows
,grid-auto-column
和 grid-auto-flow
这些属性简写到一起的属性。(注意:只能对显式或隐式的 grid 使用单独声明来指定属性)
grid-template
这个简写方式相同grid-template-rows
为指定的值。如果在斜杠的右边有 auto-flow
关键字,那么它会把 grid-auto-flow
设置为 column
。如果还加上了 dense
关键字,那么自动安置算法会使用“密集包装算法(dense packing algorithm)”。如果 grid-auto-column
被省略,它会被设置为 auto
。grid-template-columns
的值。如果 auto-flow
关键字被使用,那么 grid-auto-flow
会被设置为 row
。如果还加上了 dense
关键字,那么自动安置算法会使用“密集包装算法(dense packing algorithm)”。如果 grid-auto-rows
被省略,它会被设置为 auto
。下面两块代码是等价的:
1 | .container { |
1 | .container { |
下面两块代码是等价的:
1 | .container: { |
1 | .container { |
下面两块代码是等价的:
1 | .container { |
1 | .container { |
下面两块代码是等价的:
1 | .container { |
1 | .container { |
它也可以使用更复杂但是更方便的语法一次设置好每项。可以指定 grid-template-areas
,grid-template-rows
,grid-template-columns
然后所有其他的子属性都会被设置为它们的初始值。要做的就是指定 line 名和 track 尺寸以及它们各自的 grid areas。例子如下:
1 | .container { |
上面的代码等价于:
1 | .container { |
float
,display: inline-block
,display: table-cell
,vertical-align
和 column-*
这些属性对 grid item 没有作用。
用于确定在 grid 中的 grid items 相对于指定的 grid lines 的位置。grid-column-start
/ grid-row-start
是 item 开始的 line,而 grid-column-end
/ grid-row-end
是 item 结束的 line。
1 | .item { |
例如:
1 | .item-a { |
1 | .item-b { |
如果 grid-column-end
/ grid-row-end
没有被声明,那么 item 会默认跨越 1 个 track。
items 还可以相互重叠,可以使用 z-index
控制堆叠的顺序。
grid-column
是 grid-column-start
+ grid-column-end
的简写,grid-row
是 grid-row-start
+ grid-row-end
的简写。
1 | .item { |
例如:
1 | .item-c { |
如果没有设置 end line 的值,那么 item 会默认跨越 1 个 track。
给 item 命名,然后可以被 grid-template-area
引用。或者这个属性可以被用于更加简写的方式,由四个属性组成:grid-row-start
+ grid-column-start
+ grid-row-end
+ grid-column-end
。
1 | .item { |
例子:
作为给 item 指派一个名字的方式:
1 | .item-d { |
作为以下四个属性的简写方式:grid-row-start
+ grid-column-start
+ grid-row-end
+ grid-column-end
:
1 | .item-d { |
将 cell 中的 grid item 沿着内联轴(row)对齐(与 align-self
相对,align-self
是沿块级轴(column)对齐)。这个属性只对单独一个的 cell 里面的 grid item 生效。
1 | .item { |
例子:
1 | .item-a { |
1 | .item-a { |
1 | .item-a { |
1 | .item-a { |
要为 grid 中所有的 items 设置水平对齐方式,使用 justify-items
对 grid container(父元素)进行设置。
将 cell 中的 grid item 沿着块级轴(column)对齐(与 justify-self
相对,justify-self
是沿内联轴(row)对齐)。这个属性只对单独一个的 cell 里面的 grid item 生效。
1 | .item { |
例子:
1 | .item-a { |
1 | .item-a { |
1 | .item-a { |
1 | .item-a { |
要为 grid 中所有的 items 设置垂直对齐方式,使用 align-items
对 grid container(父元素)进行设置。
是同时设置 align-self
和 justify-self
属性的简写方式。
align-self
,第二个值设置 justify-self
。如果第二个值被忽略,那么第一个值,同时作用于两个属性。例子:
1 | .item-a { |
1 | .item-a { |
除 Edge 之外的所有主流浏览器都支持 place-self
这个简写属性。
参照 CSS Grid Layout Module Level 1 的规范,gird 属性中有 5 项是支持动画效果的:
grid-gap
,grid-row-gap
,grid-column-gap
为长度,百分比,或 calc(计算值?)的时候grid-template-columns
,grid-template-rows
作为简单列表时的长度,百分比或者 calc(计算值?)的时候,而且区别之处只有列表里面的长度,百分比或者 calc(计算值?)Flexbox Layout (Flexible Box) 模式用于提供一个更有效率的布局途径,让容器(container) 中的子元素(items) 在尺寸未知或者动态变化的时候,也能对齐和分配项目之间的空白,因为称作 flex (弹性)。
Flex 布局的主要思想是让容器能够改变里面项目的宽度/高度(或者顺序),以便用最佳方式填充可用空间(大多用来适应各种不同的设备和屏幕尺寸)。flex 容器能让里面的子元素展示以适应可用空间,或者让它们收缩以防止溢出。
更重要的是,flexbox 的布局方式与常规布局(比如 block 就是基于垂直方向的,而 inline 是基于水平方向的)相比是方向无关的。虽然常规布局对页面来说可以做得很好,但它们缺乏灵活性去适应大型 APP 或复杂的 APP (特别当方向改变,窗口缩放,窗口拉伸,收缩等等)。
注意: Flexbox 布局方式适合的是组件化的、小型的布局,如果是大型的布局,Grid 布局更为适合。
Flexbox 是一个完整的模式,而不是一个单独的属性,所以它包含了整套的属性。这些属性有的是设置到容器上的(父级元素,也就是 flex container),有的是设置到子元素上的(子元素,也就是 flex items)。
如果说常规的布局是基于 block 和 inline 的流动方向,那么 flex 布局基于的就是 flex 的流动方向。下图指明了 flex 布局的规范。
通常的,项目元素会基于主轴(从 main-start 到 main-end)或者交叉轴(从 cross-start 到 cross-end)陈列。
flex-direction
属性决定width
或者 height
中的一个用来定义一个 flex 容器,inline 级还是 block 级别取决于设置的值。它为 flex 容器的直接子元素定义了 flex 上下文。
1 | .container { |
这个属性确定主轴的方向,定义了子元素在父容器中的陈列。Flexbox 是一种单向布局理念,flex 子元素基本都是以水平行或者垂直列的方式呈现。
1 | .container { |
row
(默认值):在 ltr
语言中是从左到右,rtl
语言中是从右到左row-reverse
: 在 ltr
语言中是从右到左,rtl
语言中是从左到右column
:跟 row
相同,但是是从上到下column-reverse
:跟 row-reverse
相同,但是是从下到上默认的,flex 子元素会试着呈现在一行里面,改变这个值可以让子元素按需折行
1 | .container { |
nowrap
(默认值):所有的 flex 子元素都位于一行wrap
:所有的 flex 子元素可以换行陈列于若干行,从上至下wrap-reverse
:所有的 flex 子元素可以换行陈列于若干行,从下至上这只是 flex-direction
和 flex-wrap
的缩写方法而已,用于同时定义父容器的主轴交叉轴和是否折行,默认值是 row nowrap
1 | flex-flow: <'flex-direction'> || <'flex-wrap'> |
此属性用于定义主轴的对齐方式。它用于分配剩余的空白空间(It helps distribute extra free space left over when either all the flex items on a line are inflexible, or are flexible but have reached their maximum size.)。同时它也能控制子元素在超过一行时的对齐。
1 | .containter { |
flex-start
(默认值):子元素从起始处开始呈现flex-end
:子元素从末尾开始呈现center
:子元素中间对齐space-between
: 子元素均匀分布于行内,首元素位于行起始space-around
:子元素及其周围空间都会均匀分布。值得注意的是,视觉上空白并不均匀,因为所有子元素都有两个边缘。首元素与父容器的边缘有一单位的空白,但与下一个子元素的边缘有两个单位的空白(也就是上一个元素的右空白与下一个元素的左空白叠加在一起)space-evenly
:子元素均匀分布,每两个子元素之间的空白都是相等的用来定义 flex 子元素在交叉轴方向上,默认的陈列方式。可以看作是 justify-content
在交叉轴上的版本。
1 | .container { |
flex-start
:从 cross-start 的起始边缘开始呈现flex-end
: 从 cross-end 的结束边缘开始呈现center
:子元素垂直居中于交叉轴baseline
:子元素按它们的 baseline 对齐stretch
(默认值):拉伸以填满容器(同时受到 min-width / max-width 的影响)这个属性用于处理 flex 容器内行与行之间、在交叉轴上有多余空间时的对齐问题,如同 justify-content
在主轴上对子元素对齐的处理。
注意: 这个属性在 flex 子元素只有一行的情况下是不生效的。
1 | .container { |
flex-start
:行从父容器起始处呈现flex-end
:行从父容器结束处呈现center
:行与父容器中间对齐space-between
:行之间均匀分布,第一行处于父容器起始,最后一行处理父容器结束处space-around
:行之间均匀分布,并且每行的空白也是平均的stretch
(默认值):每行伸展以占据空白区域默认的,flex 子元素是按源码里的顺序来排列的。然而 order
属性可以控制它们在父容器中出现的顺序。
1 | .item { |
这个属性定义的是 flex 子元素按需增长的能力。它接受的值是无单的数字作为比例,它规定了子元素占据 flex 父容器内可用空间的量。
如果所有的子元素都把 flex-grow
设置为 1,那么父容器中剩余的空间将会平分给每一个子元素。如果其中一个子元素的值设置为 2,那么它将占据其他子元素 2 倍的空间 (或者试图占据 2 倍)。
1 | .item { |
负数的值是非法的。
这个属性定义的是 flex 子元素按需收缩的能力。
1 | .item { |
负数的值是非法的。
它定义的是子元素在分配到剩余空间之前的元素的默认尺寸。它的值可以是长度 (例如:20%,5rem 等等) 或者是关键字。auto
这个关键字意义是“参照宽度 width 或者高度 height 属性” (这是由 main-size
关键字暂代实现,直到被弃用为止)。content
这个关键字意义是“尺寸由子元素的内容来决定” - 这个关键字并没有被很好的支持,所以也不好测试,更不知道是 max-content
、min-content
或 fit-content
中的哪个起的作用。
1 | .item { |
如果被设置为 0,那么内容周围的多余空白就没被考虑进去,如果设置为 auto
,那么多余空白的分布,取决于它的 flex-grow
值。见下图:
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
这个属性是 flex-grow
、flex-shrink
和 flex-basis
三个属性的简写方式,其中第二和第三个参数(也就是 flex-shrink
和 flex-basis
)是可选的。默认值是 0 1 auto
。
1 | .item { |
强烈建议使用这个缩写的属性 而不是单独设置各种属性的值。因为缩写方法会智能地设置其他值。
这个属性允许单个的 flex 子元素的默认的对齐(或者被 align-item
声明过)被覆盖。
参看 align-items
解释,看可设置的值。
1 | .item { |
注意:float
、clear
和 vertical-align
对 flex 子元素无影响。
每次用 SSH 的方式登入服务器的时候,都会提示如下信息:
1 | Welcome to Ubuntu *.*.* LTS (GNU/Linux *.*.*-*-generic x86_64) |
字面提示是有 13 个包可以升级,其中 10 个是安全更新。
事实上 Ubuntu 更新还是挺容易的,因为有 apt-get
,不过更新完之后,还是会提示一些安全更新没做完,所以找了一下解决方案。
1 | apt-get update |
apt-get update
从服务器更新可用的软件包列表。apt-get upgrade
根据列表,更新已安装的软件包。这个命令不会删除在列表中已经没有的软件包,也不会安装有依赖需求但尚未安装的软件包。apt-get dist-upgrade
根据列表,更新已安装的软件包。这个命令可能会为了解决软件包冲突而删除一些已安装的软件包,也可能会为了解决软件包依赖问题安装新的软件包。所以使用上面三个命令完成更新之后,用 reboot
命令重启系统就行。需要注意的是,以上所有命令都需要在 root 权限下执行。
更新完成之后,去启动 SS,然后就报错了,从没见过的错误:
1 | load libsodium failed with path None |
不清楚是更新的系统造成的,还是其他原因,反正 SS 不能用了。搜索了一下,发现用这个 lib 的话,SS 可以支持新的高效率一点的加密方式 chacha20
等等这些。
于是乎,装一下 libsodium
吧~
1 | apt-get install build-essential |
安装完毕之后,修改 SS 配置文件,把加密换成 chacha20-ietf-poly1305
,启动 SS,没有任何报错,一切正常。
客户端也把加密方式修改成相应的,完成~~
1 | add-apt-repository ppa:chris-lea/libsodium |
参考资料:
]]>项目里有需要处理一堆目录的 html 文件自动生成,并且生成到相应的目录中。与 html 文件相对应的样式 css 文件和脚本 js 文件,也要放到相应的目录中。例如:
1 | 目录结构如下: |
说到自动生成 html 文件,第一想法就是 HtmlWebpackPlugin
,但是这个插件不支持 webpack 中类似于 [name]
这样的命名,所以想用 [name]
这样的命名方式变向成生文件夹的方法是行不通的。另外,每个条 new HtmlWebpackPlugin()
语句,只能生成一个页面,像我这样要大批量生成文件,是不太方便的。
因为要做到生成的文件,有对应的目录,所以在配置 webpack 的 entry 的时候,可以使用多入口方式。
大体代码如下:
1 | var entries: { |
1 | package main |
代码运行的结果是:
1 | len=0 cap=0 slice=[] |
差一点就看看跳过了,不过最后一行的结果,cap
执行的结果显示,这个 numbers
切片的容量为 6。
如果是写其他的语言,比如 JavaScript,那么这个结果可能会跟 length
作类比从而产生“为什么容量不是 5”这种想法,毕竟我们只在 slice 里添加了 5 个元素。翻了一下官方的文档也没有解释,后来在 stackoverflow 上看到了别人的回答,大致总结如下:
Go 会为你的 slice 提供比你需要的更多的容量,原因是在 slice 的底层,有个不可变动的(immutable)数组(array)在实际起作用。当你要为 slice 添加元素从而让切片的容量更大的时候,实际上是创建了一个新数组,把原来的切片元素和新添加的元素放到新的数组里,并把这个数组作为新 slice 的底层。如果你添加很多数据到 slice 里,就会反复去创建和复制这些数据,影响性能。所以运行时会分配比你期望的更多的容量到 slice,让复制数据这些操作变得不那么频繁。
虽然原因找下来,感觉这个问题似乎不怎么重要,不过有人如果看到这里,跟我有同样的疑惑,也可以做个参照。
参考资料:
]]>服务器安装的是 ubuntu 18 LTS x64 版本,准备使用 55 和 kcp 当梯子。
使用两个命令即可:
1 | apt-get install python-pip |
执行第二个命令如果报错,例如 ImportError: No module named setuptools
,只需要再安装 setuptools 即可。
先查看自己服务器的 Python 版本:
- 在终端上输入 python,进入 python shell
- 输入 help() 查看 python 版本
- 查看完毕后,输入 exit() 退出 python shell
接下来安装 setuptools:
1 | wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python |
安装完之后,在 /etc/
目录下创建配置文件,命名为 shadowsocks.json
:
1 | { |
启动和停止 55 的方法是:
1 | ssserver -c /etc/shadowsocks.json -d start # 这是后台启动的方式 |
直接访问项目地址吧,安装过程不说了,https://github.com/kuoruan/shell-scripts。
安装过程中可能会提示 iptables
相关的错误,原因是 iptables
在 centos 和 ubuntu 上有差异。脚本似乎是针对 centos 写的,不过无所谓,脚本里面已经提示了自己去解决 iptables
问题,但直接按提示去解决会出错。
解决办法无外乎两步:首先添加相应规则到 iptables
,然后重启 iptables
服务。
添加 iptables
配置1
iptables -I INPUT -p udp --dport 29900 -j ACCEPT # 29900 换成你自己的
保存 iptables
的配置(ubuntu 下)1
iptables-save
重启 iptables
,因为 ubuntu 用的是 ufw
作为 iptables
的前端,所以使用如下命令重启:1
service ufw restart
在配置 kcptun 的时候,加速的 IP 填写外网的 IP,如果填本地 IP(127.0.0.1 或者 0.0.0.0)都会出来 dial tcp 127.0.0.1:8388: connect: connection refused
这种错误。
Install
和 Show
。Install
是自动安装这些支持,Show
是显示缺了些什么。但实际情况是,点了 Install
之后,控制台一堆报错并且安装失败。点击 Install
或者 Show
,控制台里已经显示缺少如下工具:
1 | github.com/ramya-rao-a/go-outline |
然后用 go install
或者 go get -v
都安装不成功。
src
目录,新建 golang.org
目录,在此目录下继续新建 x
目录安装目录/golang.org/x
目录,执行以下两个命令1 | git clone https://github.com/golang/tools.git |
第一个安装除 go-lint
之外其他工具的支持,第二个安装 go-lint
支持。
以上步骤都处理完之后,就可以用 go get ***
和 go install ***
这种方式把缺的工具都装好。安装之后重启 VScode 即使用 Go 语言相关支持。
对于同一个页面功能由不同的同事开发,都用到了 webpack
以及 CommonsChunkPlugin
,最后把打包出来的代码,整合到一起的时候,冲突了。
各自用 webpack
打包代码没有问题,但是加载到页面上时,代码报错且错误难以定位。
在 webpack
的配置选项里使用 output.jsonpFunction
。
看一下文档里说的:
output.jsonpFunction
string
仅用在输出目标为 web,且使用 jsonp 的方式按需加载代码块时。
一个命名的 JSONP 函数用于异步加载代码块或者把多个初始化代码块合并到一起时使用(如 CommonsChunkPlugin, AggressiveSplittingPlugin)。
当同一个页面上有多个 webpack 实例(源于不同的编译),需要修改这个函数名。
如果使用了output.library
选项,那么这个library
的命名会自动附加上。
事实上 webpack 并不在全局命名空间下运行,但是 CommonsChunkPlugin
这样的插件会使用异步 JSONP 的方法按需加载代码块。插件会注册一个全局的函数叫 window.webpackJsonp
,所以同一个页面上运行多个源自不同 webpack 打包出来的代码时,可能会引起冲突。
webpack - configuration - output - jsonpfunction
How to run multiple webpack instances on the same page…and avoid any conflicts
]]>git archive --format zip -o filename.zip HEAD
修剪远程分支
git remote prune origin
显示本地分支与远程分支跟踪关系
git branch -vv
重命名本地分支
git branch -m oldname newname
本地分支与远程分支建立关系
git branch --set-upstream-to=origin/<branch> <cur branch>
强制覆盖本地文件的修改
1 | git fetch |
-login -i
不能反了。]]>