混淆和压缩
1
2
3
4
5
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
}),
]
支持jQuery

npm install jquery,然后

1
2
3
plugins: [
new webpack.ProvidePlugin({ jQuery: 'jquery' }),
]
生成html入口文件
1
2
3
4
5
6
7
const HtmlWebpackPlugin = require('html-webpack-plugin');
...
plugins: [
new HtmlWebpackPlugin({
template: 'index.template.html',
}),
]

template文件如

1
2
3
4
5
6
7
8
9
10
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hello React</title>
</head>
<body>
<div id='root'></div>
</body>
</html>

插件会在template的<body>内最后插入js脚本,在<head>内最后插入css样式

参见:Using the HTML Webpack Plugin for generating HTML files

dev-server.js还要修改

1
2
3
4
5
6
7
8
9
10
11
const compiler = webpack(webpackConfig);
...
app.use('*', (req, res, next) => {
const filename = path.join(compiler.outputPath, 'index.html');
compiler.outputFileSystem.readFile(filename, (err, result) => {
if (err) return next(err);
res.set('content-type', 'text/html');
res.send(result);
res.end();
});
});

参见:issue 145

提取vendor模块
1
2
3
4
5
6
7
8
9
10
const pkg = require('./package.json');
...
entry: {
vendor: Object.keys(pkg.dependencies),
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest'],
}),
]

CommonsChunkPlugin需要这第二个参数manifest,这样才能在app模块更新时不影响vendor模块的chunkhash值。

参见:Splitting Bundles

资源名hash化
1
2
3
output: {
filename: '[name].[chunkhash].js',
}

name可以理解为entry配置中指定的模块名。 chunkhash是某个分片的hash值,hash是bundle整体的hash值。HMR热更新会与chunkhash冲突,需要HMR热更新的开发环境只能用[name].[hash].js

提取css文件
1
2
3
4
5
6
7
8
9
10
const ExtractTextPlugin = require('extract-text-webpack-plugin');
...
plugins: [
new ExtractTextPlugin('[name].[chunkhash].css'),
],
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css') },
],
}

HMR热更新会与ExtractTextPlugin冲突,需要HMR热更新的开发环境不能用ExtractTextPlugin,比如:

1
2
3
4
5
module: {
loaders: [
{ test: /\.css$/, loader: 'style!css' },
],
}
启用css-modules

在loader中加上modules即为启用,localIdentName是设置生成样式的命名规则

1
2
3
4
5
6
module: {
loaders: [{
test: /\.css$/,
loader: 'style!css?modules&localIdentName=[local]__[hash:base64:5]'
}],
}

使用ExtractTextPlugin时:

1
2
3
4
5
6
module: {
loaders: [{
test: /\.css$/,
loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[local]__[hash:base64:5]'
}],
}

使用sass时:

1
2
3
4
5
6
module: {
loaders: [{
test: /\.scss$/,
loader: 'style!css?modules&localIdentName=[local]__[hash:base64:5]!sass'
}],
}

src/styles目录是全局样式,其他目录是使用css-modules的局部样式:

1
2
3
4
5
6
7
8
9
10
11
module: {
loaders: [{
test: /\.scss$/,
exclude: path.resolve(__dirname, 'src/styles'),
loader: 'style!css?modules&localIdentName=[local]__[hash:base64:5]!sass'
}, {
test: /\.scss$/,
include: path.resolve(__dirname, 'src/styles'),
loader: 'style!css!sass'
}
}
去除没用的css

这个插件较慢,只需在生产环境下使用

1
2
3
4
5
const PurifyCSSPlugin = require('purifycss-webpack-plugin');
...
plugins: [
new PurifyCSSPlugin({}),
]

参考

共享common代码

api端和client端要共享某些代码(如utils函数、数据库schema等),可以把这些代码打包成本地npm包:

  1. 在代码目录下定义package.json

    1
    2
    3
    4
    5
    6
    {
    "name": "foo",
    "version": "1.0.0",
    "dependencies": { ... },
    "devDependencies": { ... }
    }
  2. 用babel将es6编译成es5,拷贝package.json到生成的lib/目录,然后在lib/中打包

    1
    2
    3
    4
    #!/bin/sh 
    rm -fr lib/ && babel -d lib/ src/
    cp package.json lib/
    (cd lib/ && npm pack)
  3. lib目录下用npm pack将目录打包成tgz文件,比如你在package.json中设置包名为foo版本为1.0.0,则会生成foo-1.0.0.tgz。还可以通过tar -tf foo-1.0.0.tgz来查看包中内容。

  4. npm install /path/to/foo-1.0.0.tgz

变量以$开头,如$var;字符串中使用变量,要用#{}括起来

标准的css注释/* comment */编译后保留,单行注释// comment编译后忽略

@import引入外部文件

%开头的选择器是placeholder selector,编译后忽略

mixin类似宏,是可重用的代码块,可以指定参数和默认值;用@mixin定义,用@include引用。用@extend继承css类,编译后会在父选择器声明处添上子选择器名。应该多用组合(@mixin)少用继承(@extend),只有当selector间确实存在继承关系时才用继承。

嵌套selectors

默认由空格拼接外内selector名

1
2
3
.item {
> .item-name { ... } => .item > .item-name { ... }
}

使用&则直接引用该selector名

1
2
3
.item {
&-name { ... } => .item-name { ... }
}

编译bootstrap

定制bootstrap

v3版用bootstrap-sass(npm install bootstrap-sass),先拷贝assets/stylesheets/bootstrap/_variables.scss_customVariables.scss,再引用bootstrap,最后再引用其他自定义的样式

1
2
3
@import 'customVariables';
@import 'path-to-bootstrap-sass/assets/stylesheets/bootstrap';
@import 'otherCustomStyles';

可使用path-to-bootstrap-sass/assets/stylesheets/bootstrap/mixins/中的@mixin,如make-row()make-*-column()button-variant()

webpack打包

webpack中使用bootstrap-loader打包bootstrap,配置jQuery时先npm install jquery,然后用说明中的imports-loader,或者用new webpack.ProvidePlugin({ jQuery: 'jquery' })

工具

CodeKit来自动编译scss,并实时刷新页面

参考


另:less语法

变量以@开头,如@var,定义和使用都是@var

mixin定义时形如.mixin-name(@param1: default1, ...),使用时形如.minxin-name(...)

嵌套selectors,与sass相同

参考

页面inline元素后的换行会被转成一个空格(文字节点),导致inline元素后有少许空隙。而react标签后的换行不输出,这样inline元素后没有空隙。统一两者的办法是删除页面inline元素后的换行。

1
2
3
4
5
6
7
8
9
10
11
12
<ul>
<li>one</li
><li>two</li>
</ul>

<!-- or -->

<ul>
<li>one</li><!--
--><li>two</li><!--
-->
</ul>

工具

axios

axios向服务端发起异步请求

normalizr

拿到服务端返回的json可能多层嵌套,用normalizr处理后保存在redux中;使用时用denormalizr还原

bookshelf,连接sql数据库的orm,使用knex生成查询串

where(filter)的filter参数除了可以{id}这样的obj,还可以用q=>{ q.whereIn('id', ids); }这样的func,q是knex中的queryBuilder。

fetch(options)的options参数中{ withRelated: [xxx] }可以预取关联的model数据。

比如一对多关系

一Book有多Page,一Page属于一Book

(Book)上可以定义获取(Page)的函数:

1
pages() { return this.hasMany(Page, 'pageId'); }

然后fetch时提供withRelated:来预取:

1
const book = await Book.fetch({ withRelated: ['pages']});

之后用book.related('pages')访问这个预取的model

(Page)上可以定义获取(Book)的函数:

1
book() { return this.belongsTo(Book, 'bookId'); }

然后fetch时提供withRelated:来预取:

1
const page = await Page.fetch({ withRelated: ['book'] });

之后用page.related('book')访问这个预取的model

比如多对多关系

一Buyer买多Item,一Item被多Buyer买

(如 Buyer)上可以定义获取(如 Item)的函数:

1
2
3
4
5
items() {
return this.belongsToMany(Item)
.through(BuyerItem, 'buyerId', 'itemId')
.withPivot(['buyCount']);
}

(注:使用.through()时,中间表BuyerItem要有id域)

然后fetch时提供withRelated:来预取:

1
const buyer = await Buyer.fetch({ withRelated: ['items'] });

之后用buyer.related('items')访问这个预取的model

buyer的关联items没有预取时,直接取items:

1
await buyer.load(['items']);

保存时自动hash密码

initialize中注册回调函数:

1
2
3
4
initialize() {
bookshelf.Model.prototype.initialize.call(this);
this.on('saving', util.hashPasswordWithBcrypt);
}

使用事务

1
2
3
4
5
6
7
bookshelf.transaction(await transacting => {
...
await xxx.fetch(xxx, { transacting });
await xxx.save(xxx, { transacting });
await xxx.destroy({ transacting });
...
}

参考

  • let声明常量,var声明变量
  • 注释:///*...*/,多行注释/*...*/可嵌套!
  • 句尾分号可省略
  • 类型别名:typealias AudioSample = UInt16
  • 空合运算符, a ?? b等价于a!=nil ? a! : b

String

  • String是值类型,由编译器保证只在必要时拷贝
  • 字符串插值:\(...)
  • .characters表示对应的unicode字符组,.characters.count表示所含的unicode字符数
  • 不能用数字作索引,使用.startIndex.endIndex.startIndex.advancedBy(7)
  • 相等:==,前缀:.hasPrefix(),后缀:.hasSuffix()
  • 可赋值给Selector:let mySelector: Selector = “tappedButton:"

Tuple

  • let http404Error = (404, "Not Found")
  • 只需要一部分元组值,忽略的部分用_标记:let (justTheStatusCode, _) = http404Error
  • 通过下标访问元组元素:http404Error.0
  • 定义元组时给元素命名:let http200Status = (statusCode: 200, description: "OK"),然后通过名字访问这些元素:http200Status.description

Collection

  • 数组类型[Int]、集合类型Set<Int>、字典类型[String:Int]
  • 已推断出类型时,[]空数组,[:]空字典
  • 合并两数组可用+
  • 数组可区间替换,如array[4...6] = ["Bananas", "Apples"],把中间三个元素换为两个
  • 带下标遍历数组,如:for (index, value) in array.enumerate())
  • 集合的值和字典的键必须hashable;所有基本类型(如String, Int, Double, Bool)都hashable

Enum

  • 用法一:存储任何类型的关联值,类似union,给enum中各变量声明不同类型(且不赋实际值)

    1
    2
    3
    4
    enum Barcode {
    case UPCA(Int, Int, Int, Int)
    case QRCode(String)
    }

  • 用法二:给enum中各变量赋予同类型的原始值(rawValue)

    1
    2
    3
    4
    5
    enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
    }
    带原始值的枚举类型自带一个failable构造器init?(rawValue:)

  • 关联值是自身这一枚举类型,要在这关联值前用indirect表示该成员可递归

    1
    2
    3
    4
    5
    enum ArithmeticExpression {
    case Number(Int)
    indirect case Addition(ArithmeticExpression, ArithmeticExpression)
    indirect case Multiplication(ArithmeticExpression, ArithmeticExpression)
    }

Optional

  • 实际上是enum:
    1
    2
    3
    4
    enum Optional<T> {
    case None
    case Some(T)
    }
  • 可空类型(形如Type?),可能存在某个值或不存在值,用someOptional!强制展开
  • 可空绑定,如if let constName = someOptional { }
  • 可空链式调用,如street = paul.residence?.address?.street,返回的都是可空值(即使原函数返回非空值)
  • 隐式展开可空值(形如Type!),变量在首次初始化后不再为空(一般作实例变量,由类初始化),直接用someOptional访问

Control Flow

  • if/for/while等的条件语句不需要括号:for index in 1...n
  • switch的每个值都至少要有个case分支对应,当现有的分支无法涵盖所有值时要使用default分支
  • case分支不会fallthrough,不需要显式地break;但可用fallthrough关键字让控制流直接接上下一个case的执行代码(跳过下一个case的条件检查)
  • case可以区间匹配(如数值区间3...5,tuple区间(_, 0),逗号分隔的多个区间1,3,5...7),各case的区间重叠时按序选择匹配
  • case中的模式匹配:
    1
    2
    3
    4
    5
    6
    7
    8
    let color = (1.0, 1.0, 1.0, 1.0)
    switch color {
    case (0.0, 0.5...1.0, let blue, _):
    print("Green and \(blue * 100)% blue")
    case let (r, g, b, 1.0) where r == g && g == b:
    print("Opaque grey \(r * 100)%")
    ...
    }
  • 循环和switch语句可以加标签,这样多层嵌套时可以break/continue到指定标签
  • guard语句类似if语句,但guard总是有一个else分句

Function & Closure

  • 内外参数同名:func someFunc(paraName: Int) {};显式指定外部参数名: func someFunc(externalParaName localParaName: Int) {}
  • 参数列表末可以提供默认参数:func someFunc(paraName: Int = 12) {}
  • 可变参数数组,如func mean(numbers: Double...) -> Double {},最多只能有一个且放在参数列表最最后(默认参数之后)
  • 参数默认是常量,若要将参数作为可修改副本使用,在参数名前加varfunc someFunc(var paraName: String) {}
  • inout参数由外部在调用时传引用&varParam
    1
    2
    3
    4
    5
    func swapTwoInts(inout a: Int, inout _ b: Int) {
    let tmp = a; a = b; b = tmp
    }
    ...
    swapTwoInts(&someInt, &anotherInt)
  • 函数类型由参数和返回值类型组成,如:(Int, Int) -> Int, (Int) -> Int, () -> ()
  • 函数中可以嵌套定义新函数
  • 闭包语法:
    1
    2
    3
    { (parammeters) -> returnType in
    statements
    }
  • 闭包可用$0, $1, $2等速记参数:reversed = sort(names, { $0 > $1 })

Struct & Class

  • struct不能继承;struct是值类型(传拷贝),class是引用类型(传引用)
  • 所有基本类型(包括字符串、数组、字典)都是值类型,用struct实现的,编译器会保证只有确实必要时才执行实际拷贝
  • struct可以按成员初始化:let vga = Resolution(width: 640, height: 480)
  • lazy var在首次使用时才初始化;全局常量/变量自动是lazy的(不需lazy标记)
  • 若某实例方法要修改struct/enum等值类型中的属性(包括修改self),得给方法添加mutating声明
  • 定义下标索引,如:
    1
    2
    3
    4
    subscript(index: Int) -> Int {
    get {}
    set(newValue) {}
    }
    也可以是二维的,如: subscript(row: Int, column: Int) -> Double { ... } ,访问时可用matrix[1, 0]
  • 子类重载父类特性时要加override声明
  • 不想被重载的特性用final声明
  • 类至少有一个designated构造器,它先初始化本类引入的所有存储型属性,然后调用父类的designated构造器(delegate up),再为继承的属性设置新值
  • convenience构造器要前加convenience声明,它调用本类的其他构造器并最终调用到某个designated构造器(delegate across),再为任意属性设置新值
  • 两阶段初始化:就是个中序遍历,先初始化存储型属性,再访问super.init(),再之后算阶段二用来作些自定义
  • 若子类没有designated构造器,则自动继承父类的designated构造器
  • 若子类实现了父类的所有designated构造器(不管是自动继承的还是自己实现的),则自动继承父类的convenience构造器
  • 可失败构造器:init?(...),若要返回的对象隐式展开:init!(...)
  • 子类都必须实现的构造器,前加required修饰
  • 用闭包和函数设置属性的默认值:
    1
    2
    3
    4
    5
    6
    class SomeClass {
    let someProperty: SomeType = {

    return someValue
    }()
    }
  • 每个类最多只能有一个析构器deinit,子类析构器的最后会自动调用父类析构器

ARC

  • 对生命周期中会变为nil的实例使用弱引用(weak, optional)
  • 对初始化赋值后再也不会变为nil的实例使用无主引用(unowned)
  • 两个类中的属性互相引用,要打破循环强引用:
    • 两个都可为nil:其中一个使用弱引用(weak)
    • 一个可为nil,一个不可为nil:不可为nil的使用无主引用(unowned)
    • 两个都不可为nil:一个使用隐式展开可空属性(Type!),一个使用无主引用(unowned)
  • 将闭包赋给属性,要打破循环引用,用无主引用来捕获self,如:
    1
    2
    3
    4
    lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    ...
    }
    1
    2
    3
    4
    lazy var someClosure: () -> String = {
    [unowned self, weak delegate = self.delegate!] in
    ...
    }

Error Handling

  • 错误用符合ErrorType协议的值表示:enum VendingMachineError: ErrorType
  • throws声明函数将抛出错误:func canThrowErrors() throws -> String
  • throw抛出错误:throw VendingMachineError.OutOfStock
  • 调用抛出错误的函数时,前面加上trytry canThrowErrors()
  • 用do-catch捕获错误:
    1
    2
    3
    4
    5
    6
    do {
    try canThrowErrors()
    ...
    } catch someError {
    ...
    }
  • swift的错误和其他语言的异常类似,但是不会展开调用堆栈,因此throw语句的性能可以几乎和return语句一样
  • 如果确认某个声明了throws的函数不会抛出错误,可以用try!禁止错误传播:try! willOnlyThrowIfTrue(false)
  • defer语句将操作压栈,推迟到当前作用域结束时执行

Type Casting

  • 类型检查用is,强制类型转换用as,如let view = object as UIView,尝试类型转换用as?,如if let view = object as? UIView { ... }
  • 可以转换整个数组:if let viewArray = objectArray as? [UIView] { ... },有任一元素转换失败时整个返回nil
  • AnyObject表示任何class类型,Any表示任何类型

Extension

  • 可以添加新的方法,但不能重写已有的方法
  • 可以添加计算型属性,但不能添加存储型属性,也不能给已有属性添加属性观测器(willSet/didSet等)
  • 可以添加convenience构造器,但不能添加designated构造器或析构器

Protocol

  • 如果协议的方法将改变遵循该协议的对象的属性,则在该方法前加mutating关键字(为了使struct/enum等值类型也能遵循该协议)
  • 协议可以继承一个或多个其他协议:protocol SomeProtocol: InheritedProtocolOne, InheritedProtocolTwo {}
  • 限制只能由class遵循该协议(struct/enum不能遵循该协议),要在继承列表开头使用class关键字:protocol SomeClassOnlyProtocol: class, InheritedProtocol {}
  • 变量遵循了多个协议:person: protocol<Named, Aged>
  • 能用is/as/as?检查变量是否遵循协议
  • 协议包含可选方法或属性时,要加@objc前缀:
    1
    2
    3
    4
    @objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int
    optional var fixedIncrement: Int { get }
    }
  • 可通过Extension为协议提供默认实现,扩展协议时还能用where描述限制条件:
    1
    2
    3
    extension CollectionType where Generator.Element : TextRepresentable {
    // 扩展CollectionType协议,但只适用于元素遵循TextRepresentable的情况
    }
  • 可以用typealias声明关联类型,给类型提供占位名,当某类实现这协议时能自动推断出实际的关联类型
    1
    2
    3
    4
    5
    protocol Container {
    typealias ItemType
    mutating func append(item: ItemType)
    ...
    }

Generics

  • 泛型,形如struct Stack<T> { ... }
  • 扩展泛型时,不需要在扩展的定义中再次声明类型参数,原类型参数可以直接使用:
    1
    2
    3
    extension Stack {
    var topItem: T? { ... }
    }
  • 泛型的类型参数可以添加约束,如:
    1
    2
    3
    4
    5
    func allItemsMatch<C1: Container, C2: Container
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
    (someContainer: C1, anotherContainer: C2) -> Bool {
    ...
    }

Access Control

  • private源文件内可见;internal模块内可见,是默认值,模块指以独立单元构建和发布的framework或application;public都可见
  • 函数的访问级别由参数和返回值中最小的一个决定

Operators

  • 算数运算符默认不会溢出,溢出运算符有:&+&-&*
  • 重载运算符,如:
    1
    2
    3
    4
    struct Vector2D { var x = 0.0,, y = 0.0 }
    func +(left: Vector2D, right: Vector2D) -> Vector2D { } // 中缀
    prefix func -(vector: Vector2D) -> Vector2D {} // 前缀
    func += (inout left: Vector2D, right: Vector2D) { left = left + right } // 组合赋值
  • 自定义操作符可声明优先级和结合性:
    1
    2
    infix operator +- { associativity left precedence 140 }
    func +- (left: Vector2D, right: Vector2D) -> Vector2D { }

Command Line

  • demangle: xcrun swift-demangle xxx

Using with Cocoa & ObjC

  • 在.m文件中使用swift:#import "ProductModuleName-Swift.h"
  • 在.swift文件中使用objc:把要使用的objc头文件加入ProductModuleName-Bridging-Header.h中,之后所有.swift文件中不需要import就能使用
  • 从objc类继承的swift类会由编译器自动插入@objc,因而在objc中可用
  • 不从objc类继承的swift类要在objc中可用,需要自己添加@objc标注
  • @objc(<#name#>)可以给swift类/方法起objc别名(不带名称空间)
  • swift闭包和objc闭包互通,swift闭包中的变量类似objc闭包中的__block变量
  • @NSManaged就像@dynamic,表示NSManagedObject子类属性的存储和实现将在运行时提供

内存布局

  • struct按值顺序排,会字节对齐
  • swift类都继承自SwiftObject,SwiftObject实现了协议,但不是NSObject的子类
  • SwiftObject前两个8字节分别是isa和refCount (swift中的NSObject类空着这后8字节refCount不用)
  • swift中的NSObject布局:isa, superclass, cache, …, 构成vtable的各方法指针
  • class的optional,跟指针一样,指向地址或nil;struct的optional多加1字节表示值是否nil
  • protocol共占40字节;class的protocol布局为:isa, 空着不用16字节, 指向底层类的metadata的指针,指向实现protocol的vtable的指针;struct的protocol布局为:24字节内inline存储值(超过24字节保存malloc的地址8字节, 空着不用16字节),指向底层类的metadata的指针,指向实现protocol的vtable的指针

Playground和.swift文件

  • main.swift和playground一样都是order-dependent的,且允许顶层代码
  • iOS里加@UIApplicationMain的那个文件就相当于main.swift
  • 其他swift文件都是order-independent的,且不允许顶层代码

Reference

  • [《The Swift Programming Language》][1]
  • 《Using Swift with Cocoa and Objective-C》
  • Intermediate Swift, WWDC2014
  • Exploring Swift Memory Layout [part I][] & [part II][] [1]: http://wiki.jikexueyuan.com/project/swift/ [part I]: https://www.mikeash.com/pyblog/friday-qa-2014-07-18-exploring-swift-memory-layout.html [part II]: https://www.mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html

for-of

for-of通过迭代器遍历各种集合,for-in遍历对象属性

迭代器

对象通过[Symbol.iterator]()方法指定它的迭代器,迭代器是实现了.next()方法的对象,.next()方法的返回值形如{done: false, value: xxx}

迭代器贯穿ES6的始终,它是数据和循环的新标准。

生成器

生成器函数用function*声明。生成器函数内部的yield类似return,区别是只能return一次却可以yield多次。生成器执行时遇到yield就暂停,后续可恢复执行状态。

当你调用生成器函数时,它并不立即执行,而是返回一个暂停在函数入口的迭代器。当你调用迭代器的.next()方法时,函数恢复执行,直到下一个yield后再暂停。

.next()方法接受一个可选参数,该参数在稍后函数恢复执行时会代替yield表达式的返回值

生成器是迭代器。所有的生成器都有内建[Symbol.iterator]()和.next()方法的实现。你只须编写循环部分的行为。

使对象可遍历:通过[Symbol.iterator]()方法指定对象的迭代器为生成器函数,编写这个生成器函数来yield这个对象的每一个值。

当你面对一个复杂的循环时,你可以拆分出生成数据的代码,将其转换为独立的生成器函数,然后使用for (let data of myNewGenerator(args))遍历我们所需的数据。

yield只能生成一个值;yield*可以后接迭代器,一次次直到遍历该迭代器的所有值

可用yield*在生成器中调用生成器

字符串模板

模板用 `(反撇号)括起来,字符串插值形如${user.name}

标签模板,在模板字符串开始的反撇号前加一个额外的标签

1
var message = SaferHTML`<p>${bonk.sender} 向你示好。</p>`;
上面代码等效于
1
var message = SaferHTML(templateData, bonk.sender);
templateData是一个不可变数组,存储着模板所有的字符串部分。

不定参数和默认参数

1
function containsAll(haystack, ...needles)
1
function animalSentence(animals2="tigers", animals3="bears")

默认值表达式自左向右求值,这意味着,后面的赋值式可以使用前面的刚赋了值的参数

没有默认值的参数隐式默认为undefined

传递undefined值等效于不传值

解构赋值

数组解构

1
[ variable1, variable2, ..., variableN ] = array;

如果你想在赋值的同时声明变量,可在赋值语句前加入var、let或const关键字,如:

1
const [foo, [[bar], baz]] = [1, [[2], 3]];
可以在对应位留空来跳过被解构数组中的某些元素:
1
var [,,third] = ["foo", "bar", "baz"];
可以通过“不定参数”模式捕获数组中的所有尾随元素:
1
var [head, ...tail] = [1, 2, 3, 4];
1
log(...array); // log所有值
当访问空数组或越界访问数组时,对其解构与对其索引的结果都是:undefined
1
2
[][0] //undefined
var [missing] = []; //undefined
数组解构赋值的模式同样适用于任意迭代器:
1
2
function* fibs() { … }
var[first,second,third] = fibs();

对象解构

首先指定属性,然后指定变量:

1
var{name:nameA}={name:"Bender"}; // nameA
当属性名与变量名一致时可简写:
1
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
解构对象时若赋值语句前没有let、const或var关键字,要将整个表达式用一对小括号包裹(防止js引擎将任何以{开始的语句解析为块语句):
1
({ blowUp } = { blowUp: 10 });
当你要解构的属性未定义时你可以提供一个默认值:
1
2
var [missing = true] = [];
var { message: msg = "Something went wrong" } = {}; // msg

箭头函数

只有一个参数时,形如:arg => expr

多个参数(或无参数、不定参数、默认参数、参数解构)时,要用小括号包裹参数:

1
var total = values.reduce((a, b) => a + b, 0);
返回的对象字面量要包裹在小括号里:
1
var chewToys = puppies.map(puppy => ({}));

符号

1
let isMoving = Symbol(“the description");

symbol不能被自动转换为字符串,要通过String(sym)sym.toString()显式转换

获取symbol: * Symbol() // 新建symbol * Symbol.for(key) // 共享symbol * 少许几个自带的symbol,如Symbol.iterator

集合

Map或Set的遍历顺序就是其中元素的插入顺序

WeakMap或WeakSet都不可遍历

代理

有形如obj.[[xxx]]()的14个内部方法

1
var proxy = new Proxy(target, handler)
handler对象所含的方法将对应重写proxy的内部方法,没被重写的proxy方法调用会转发到target上执行

let和const

let就是新的var,块级作用域,for循环的每次迭代绑定新的let变量(不像原来的所有迭代都绑定同一个var变量)

const声明常量

模块

一个文件就是一个模块

导出:

1
2
3
4
export {detectCats, Kittydar};
export { v1 as streamV1, v2 as streamV2 };
export default { field1: value1, field2: value2 };
export {Tea, Cinnamon} from "sri-lanka";
或直接在function、class、let/const/var的声明前加export关键字

导入:

1
2
3
4
import {detectCats, Kittydar} from "kittydar.js";
import {flip as flipOmelet} from "eggs.js";
import _ from "lodash"; // 导入default
import * as cows from “cows”;

ES7

Async函数和生成器类似,但特定于异步编程。调用generator返回iterator,调用async函数则返回promise。generator使用yield来暂停并生成值,async函数则使用await来暂停并等着promise。

参考

系统基础

吞吐率(qps, request per second)量化描述了服务器的并发处理能力

压力测试的前提条件: * 并发用户数 * 总请求数 * 请求资源描述(静态/动态)

数据链路层的流量控制是通过控制接收方来实现的

支持400MHz前端总线频率的32位处理器,理论上它的总线带宽为: 32bit*400MHz = 12.8Gbps = 1.6GB/s

进程优先级(top命令中的PR)表示进程调度器分配给进程的时间片个数。一个时间片的长度跟CPU的主频和操作系统有关,比如Linux上一般为10ms,那么PR值为15表示这个进程的时间片为150ms。

Linux对进程的动态调整,体现在进程的nice属性中(top命令中的NI)

内核级线程一一对应着轻量级进程,所以也有上下文切换开销

系统负载(top命令中LoadAvg)表示运行队列中就绪态的进程数平均值, top中显示的是最近1分钟、5分钟、15分钟分别计算的系统负载

进程有独立的内存空间,但只能共享CPU寄存器。进程被挂起的本质是将它在CPU寄存器中的数据拿出来暂存在内核态堆栈中,而恢复工作的本质是将它的数据重新装入CPU寄存器。这段装入装出的数据被称为“硬件上下文”,它是进程上下文的一部分。除此之外,进程上下文还包含进程运行时需要的一切状态信息。

top的RES值表示其占用的物理内存大小,SWAP值表示其使用的虚拟内存大小,VIRT值等于RES和SWAP的总和,默认单位是KB。

IOWait指CPU空闲等待IO的时间比例,描述了CPU的性能。CPU可能因为IO在wait,也可能因为频繁切换进程等其他原因在wait,所以IOWait不能代表IO操作的性能。要真正了解当前IO,可以进行磁盘IO测试或查看网络IO流量等。

同步非阻塞IO:轮询

多路IO就绪通知 * select/poll:本质上没多大差别,告知所有就绪的文件描述符(level triggered,水平触发),轮询 * sigio:告知刚刚变为就绪状态的文件描述符(edge triggered,边缘触发),轮询 * epoll:支持水平触发和边缘触发,事件通知

内存映射:将内存中某块地址空间和指定的磁盘文件相关联,从而把对这块内存的访问转换为对磁盘文件的访问 在大多数情况下,使用内存映射可以提高磁盘IO的性能,它无须使用read()或write()等系统调用来访问文件,而是通过mmap()系统调用来建立内存和磁盘文件的关联,然后像访问内存一样访问文件。 内存映射和直接访问文件没有本质上差别,因为数据从磁盘到用户态内存都要经过两次复制:磁盘<->内核缓冲区,内核缓冲区<->用户态内存。

sendfile()系统调用:处理静态文件请求时,磁盘文件的数据先经过内核缓冲区,到达用户内存空间;因为是不需要任何处理的静态数据,所以它们又被送到网卡对应的内核缓冲区,然后再被送入网卡进行发送。sendfile()系统调用可以将磁盘文件的特定部分直接传送到客户端的socket描述符,加快静态大文件的请求速度


缓存

询问服务器本地缓存是否可用(缓存协商) * Last-Modified 和 If-Modified-Since * ETag 和 If-None-Match

使用Last-Modified存在一些缺点,比如,每次文件的修改时间变化后,无论内容是否真的变化,浏览器都会重新获取全部内容;再比如,同一个文件存储在多台Web服务器上,用户的请求在这些服务器之间轮询,实现负载均衡,而这些服务器上同一个文件的最后修改时间很难保证完全相同,这会导致用户的请求每次切换到新的服务器时就需要重新获取全部内容。这时候,使用直接标记内容的某种ETag算法,就可以避免这些问题。

直接使用本地缓存 * Expires 和 Cache-Control

Expires使用绝对时间,若客户端和服务端的时间不一致,本地缓存的有效期检查就有问题。Cache-Control使用max-age这一相对时间弥补了Expires的不足。当响应头中同时含有Expires和Cache-Control时,浏览器优先使用Cache-Control。

要更新静态文件的缓存,在引用文件时让url发生变化即可,比如增加一些个性化的参数:the-static-file-url?v=1.2

长连接:客户端在发出的http请求头中包含Connection: Keep-Alive,服务端一般默认支持长连接。当客户端和服务端对长连接的超时设置不一致时,以较短的超时时间为准

gzip压缩:请求头里面可用Accept-Encoding告知浏览器支持的压缩方式,而服务端则用Content-Encoding作为回应

浏览器三种刷新方式: * Ctrl+F5或Ctrl+刷新按钮:强制刷新,缓存都无效 * F5或刷新按钮:一般刷新,缓存协商有效(Last-Modified有效),但本地缓存无效(Expires无效) * 点击超链接或地址栏输入后跳转:以最少请求来获取网页,会对所有没过期内容直接使用本地缓存(Expires只对这种方式有效)

浏览器对同一域名有最大并发数限制。为了增加并发,尤其是对一些静态资源,可以使用多个域名。但由于域名解析本身也耗时,所以实践上2-4个为宜。需要注意是,加载js脚本时会暂停加载其他资源。

当我们扩展缓存系统的服务器数后,由于分区算法的改变,重建和预热缓存需要时间,但我们不需要考虑缓存数据在分区之间的迁移,因为这是缓存,不影响站点的正常运转。


数据库

在select语句前加explain,可以看查询过程是否使用了索引

一次查询只能使用一个索引,多个索引无法共同使用,所以有时要创建组合索引

索引对where / order by / group by子句中使用的字段都可起作用,若查询子句只包含组合索引的最左前缀(创建组合索引时的多个key中最左的几个key),则查询会直接使用这个组合索引

mysql可以开启慢查询日志,和未使用索引查询的日志

大多数的慢查询都是因为索引使用不当,其他原因包括查询语句过于复杂(比如联合查询)或者数据表记录数过多,通过反范式化设计(引入冗余数据)和数据分区可以有效改善这一状况。

mysql可以开启查询缓存。缓存过期策略是:当一个数据表有更新操作后(如update或insert),涉及这个表的所有查询缓存都失效。

mysql可以开启线程池

两种数据库格式: * MyISAM:表锁定 * Innodb:行锁定、事务(预写日志)

简单地说,3NF(第三范式)要求在一个数据表中,非主键字段之间不存在依赖关系


负载均衡

  • http重定向:使用http响应头的Location域
  • DNS负载均衡
  • 反向代理负载均衡
  • NAT
  • 直接路由
  • IP隧道

后端怎么保存session: * 本地化保存,要对ip做hash或用cookie记后端服务器编号 * 分布式session服务器

NAT,内核态转发 * Netfilter/iptables。Netfilter在内核中维护着一些数据包过滤表,iptables这命令行工具可以对Netfilter的过滤表进行插入、修改或删除操作 * IPVS/ipvsadm。ipvsadm比iptables用起来更方便

当实际服务器的吞吐率小于3000qps时,反向代理和NAT负载均衡的整体吞吐率差距不大。这意味着对于一些开销较大的内容,使用简单的反向代理来搭建负载均衡系统非常值得考虑,至少在初期是个快速有效的方案,而且它非常容易迁移到NAT方式。

将基于NAT的集群和DNS轮询混合使用,你可以组建多个条件允许的NAT集群,比如5个100Mbps出口带宽的集群,然后通过DNS轮询方式来将用户请求均衡地指向这些集群,同时你还可以利用DNS智能解析实现地域就近访问。

直接路由(也使用ipvsadm): 工作在数据链路层(第二层),调度器通过修改数据包的目标MAC地址,将它转发到实际服务器。实际服务器要添加和调度器相同的IP别名,设置不寻找其他拥有这个IP别名的服务器,不响应网络中针对这个IP别名的ARP广播,这样才可以让转发到实际服务器的数据包找到归属。

IP别名:一个网络接口除了拥有一个IP地址,最多还可以为它设置256个IP别名

直接路由比起NAT转发的优势在于,实际服务器的响应数据包不经过调度器而直接发往用户端(实际服务器要直接接入外部网络)

RFC1918规定的私有IP地址范围是:

1
2
3
10.0.0.0      -   10.255.255.255     (10/8 prefix)
172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
IP隧道: 与直接路由类似,但实际服务器和调度器可以不在同一个WAN网段,将调度器收到的IP数据包封装在新数据包中转发给实际服务器,然后实际服务器的响应数据包可以直接发往用户端。


扩展数据库

主从复制(主服务器开启日志,写操作只能在主服务器)、读写分离(MySQL Proxy,数据库反向代理)

垂直分区:将不需要联合使用的数据库表分布到不同的服务器

水平分区:将同一数据表中的记录通过特定算法分离成不同的数据表,从而可以部署到不同的服务器

事实上,很多大规模的站点基本上都经历了从简单主从复制到垂直分区,再到水平分区的过程

分区算法: * 按哈希分:按照分区索引字段的哈希做分区,对分区扩展并不友好,一旦我们需要从10个分区扩展到20个分区,这便涉及所有数据的重新分区 * 按范围分:按照分区索引字段的范围做分区,较好扩展,但各个分区的工作量会存在较大差异 * 维持映射关系:维持每个记录的分区对应关系,可能十分庞大

分布式的并行计算:Map/Reduce


工具

用apache附带的ab做压力测试

用Nmon可以监视服务器每秒上下文切换次数

反向代理缓存服务器:Squid太古老笨重,可用Varnish

用mysqlsla查看mysql慢查询日志

Spock Proxy,mysql多个水平分区的访问调度


参考

错误预防

断言:

  • NSParameterAssert/NSAssert用于ObjC函数,NSCParameterAssert/NSCAssert用于C函数
  • ObjC函数中NSParameterAssert用在开头检查输入参数,其他情况都用NSAssert
  • 不用assert,因为它只终止程序不输出信息

参数非nil的编译检查:

1
2
3
- (void)doSomethingWithRequiredString:(NSString *)requiredString 
bar:(NSString *)optionalString
__attribute((nonnull(1)));

LLDB

e(即expression)对表达式求值,是lldb最基本的命令

p(即print)是expression --的简写,poexpression -O --的简写

p/<format>可以指定print的输出格式,如:

1
2
(lldb) p/x 16            // 0x10
(lldb) p/t (char)16 // 0b00010000,t stands for two

lldb可以声明变量名以$开始的变量:

1
2
(lldb) e int $a = 2
(lldb) p $a * 19 // 38
1
2
(lldb) e NSArray *$array = @[ @"Sunday", @"Monday" ]
(lldb) p [$array count] // 2

若lldb不清楚返回值的类型,需要明确指定:

1
(lldb) p (char)[[$array objectAtIndex:0] characterAtIndex:0]    // 'S'

或者导入框架,然后就不需指定返回值类型:

1
2
(lldb) expr @import UIKit
(lldb) p slef.window.bounds

为方便起见,可在~/.lldbinit中添加:

1
command alias uikit expr @import UIKit

c/n/s/fin(即continue / next / step / finish)分别对应Continue、Step Over、Step Into、Step Out

frame info可以查看当前函数调用帧

thread return [value]可以让函数直接return或return某个值

例子

查看window的hierarchy(pviews):

1
po [[UIWindow keyWindow] recursiveDescription]

查看viewController的hierarchy(pvc):

1
po [[[UIWindow keyWindow] rootViewController] _printHierarchy] // _printHierarchy是iOS8里的私有方法

更改视图属性后刷新界面(caflush):

1
e (void)[CATransaction flush]

先找到某button的target:

1
2
3
4
5
6
7
8
(lldb) po [$myButton allTargets]
{(
<MagicEventListener: 0x7fb58bd2e240>
)}
(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
<__NSArrayM 0x7fb58bd2aa40>(
_handleTap:
)

然后在-[MyEventListener _handleTap:]上设置符号断点

Chisel

LLDB内置支持python,通过script命令可调用python代码:

1
2
(lldb) script import os
(lldb) script os.system("open http://www.objc.io/")

Chisel就是通过~/.lldbinit加载的便利iOS调试的python脚本集合:

  • 查看window的hierarchy:pviews

  • 查看viewController的hierarchy:pvc

  • 查看responder链:presponder

  • 更改视图属性后刷新界面:caflush

  • 显示/隐藏视图:show/hide

  • 监视ivar变化:wiar

    1
    (lldb) wivar $myView _layer

  • 在message上设置断点:bmessage

    1
    (lldb) bmessage -[MyViewController viewDidAppear:]

Aspects

Aspects可以hook进方法打印log:

1
2
3
[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];

其中block的第一个参数是id<AspectInfo>,其他参数(可选)就是被hook方法的参数列表

参考: